Saltar al contenido
CADCAMFUN
Documentos

Documentación

solucionador de bocetos

Bocetos 2D restringidos resueltos por Levenberg-Marquardt, con grados de libertad, puntos libres y conflictos con nombre.

Traducido automáticamente del inglés. Ver el original en inglés

¿Qué hace el solucionador?

El solucionador de bocetos convierte un conjunto de puntos, líneas, círculos y arcos, además de restricciones geométricas y dimensionales, en coordenadas concretas. vive en packages/core/src/cad/sketch.ts (522 líneas) y se exporta desde @cadcamfun/core como solveSketch, sketchProfiles y polygonArea. El historial paramétrico primero resuelve sus expresiones en números y luego llama al solucionador; los perfiles resueltos alimentan extrusiones, revoluciones y cajeras.

AporteProducción
entidades: puntos, líneas, círculos, arcoslas mismas entidades con coordenadas y radios resueltos
restricciones: 24 tipos, opcionalmente nombradosestado, grados de libertad, puntos libres, ecuaciones redundantes, conflictos
options: maxIterations (200), tolerance (1e-9)iteraciones utilizadas y el residuo más grande que queda
Boceto resuelto extruido Arrastrar para orbitar · desplazarse para hacer zoom · arrastrar con el botón derecho para desplazarse
The fully constrained 40 × 25 rectangle with a Ø10 hole at (20, 12.5), exactly as sketchProfiles returned it (the circle is a 50-point loop at the default 0.01 mm tolerance), extruded 6 mm.

modelo de datos

Los puntos son las incógnitas. Cada línea, círculo y arco se refiere a puntos por id, por lo que dos líneas que comparten un extremo comparten el punto y se mueven juntas. Un círculo suma una incógnita más, su radio. Un arco es un centro y dos puntos en el círculo, en sentido antihorario desde start a end; su radio es |inicio − centro| y el solucionador suma su propia ecuación manteniendo end en el mismo círculo.

Tipos de entidad y croquis (sketch.ts)
export interface SketchPoint  { id: string; kind: "point";  x: number; y: number }
export interface SketchLine   { id: string; kind: "line";   p1: PointId; p2: PointId }
export interface SketchCircle { id: string; kind: "circle"; center: PointId; radius: number }
/** Counter-clockwise from `start` to `end` about `center`. */
export interface SketchArc    { id: string; kind: "arc";    center: PointId; start: PointId; end: PointId }

export interface Sketch {
  readonly entities: ReadonlyArray<SketchEntity>
  readonly constraints: ReadonlyArray<Constraint>
}

El vector desconocido se presenta como el de cada punto. x, y en orden de entidad, entonces cada círculo r. Un rectángulo de cuatro puntos tiene 8 incógnitas; agrega un círculo con su propio centro y tiene 11.

Catálogo de restricciones

Cada restricción aporta ecuaciones residuales que son cero cuando se cumple. Una restricción puede conllevar una id; sin uno se llama type#index (por ejemplo vertical#2) en informes de conflictos.

TipoCamposSignificadoEcuaciones
fixedpunto, x?, y?fija un punto (cualquiera de las coordenadas es opcional)1–2
coincidenta, bdos puntos son uno2
horizontal / verticallíneauna línea está nivelada o a plomo1
horizontalPoints / verticalPointsa, bdos puntos comparten y / x1
distancea, b, valordistancia punto a punto1
distanceX / distanceYa, b, valordesplazamiento con signo a lo largo de un eje1
lengthlínea, valorlongitud de línea1
parallel / perpendiculara, bentre dos lineas1
equalLengtha, bdos lineas1
equalRadiusa, bdos círculos o arcos1
radius / diametercirculo, valortamaño de un círculo o arco1
anglea, b, valorde la línea a a la línea b, en sentido antihorario, radianes1
pointOnLine / pointOnCirclepunto, línea | círculoincidencia1
midpointpunto, líneapunto en el medio de una línea2
symmetrica, b, líneapar de espejos sobre una línea2
tangentLineCirclelínea, círculola línea toca el círculo1
tangentCirclesa, b, interno?externo r1 + r2 o interno |r1 − r2|1
concentrica, blos centros coinciden2

Resolviendo: Levenberg-Marquardt

El solucionador minimiza la suma de residuos al cuadrado con Levenberg-Marquardt y un jacobiano en diferencia central. Cada paso resuelve las ecuaciones normales amortiguadas mediante factorización de Cholesky; se acepta un paso que baja el residual y la amortiguación baja, un paso que no lo hace se rechaza y la amortiguación crece. Debido a que cada paso es el cambio más pequeño que reduce los residuos, un punto arrastrado arrastra el resto del boceto como lo haría en un tablero de dibujo.

  1. Construya la función residual de cada restricción y registre a qué restricción pertenece cada ecuación.
  2. Start with λ = 1e-3. Solve (JᵀJ + λ·max(1, diag)) Δx = −Jᵀr; if Cholesky fails, multiply λ by 10 and retry.
  3. Accept the step if the residual norm drops (λ = max(λ/5, 1e-12)); otherwise λ ×= 10.
  4. Deténgase cuando el residual esté por debajo tolerance (1e-9) o después maxIterations (200).
  5. Analyse the Jacobian at the solution: rank by Gaussian elimination with full pivoting (tolerance 1e-7), null-space basis, free unknowns (component above 1e-6).
ConstanteValorDónde
maxIteraciones predeterminadas200SolveOptions
tolerancia predeterminada1e-9SolveOptions
amortiguación inicial λ1e-3solveSketch
suelo amortiguador1e-12solveSketch
resuelto si residual ≤1e-6estado
tolerancia de rango1e-7nullSpace
umbral libre-desconocido1e-6puntos gratis
umbral de conflicto (por ecuación)1e-6conflictos

Estado y diagnóstico

Después de resolver, grados de libertad = incógnitas - rango y ecuaciones redundantes = ecuaciones - rango. El estado se deriva de esos dos números y de si el residual alcanzó 1e-6.

EstadoCondiciónque hacer
fully-constrainedresuelto, dof 0, sin redundancianada
under-constrainedresuelto, gld > 0agregar dimensiones para los identificadores en free
over-constrainedresuelto, redundante > 0eliminar una restricción duplicada (inofensiva pero ruidosa)
conflictingno resuelto, redundante > 0arreglar la restricción en conflicts[0]
failedno resuelto, sin redundanciacomience más cerca de la forma deseada o aumente las iteraciones máximas

These results come from running the solver on a rectangle drawn slightly askew: a(0,0) b(38,2) c(41,27) d(−1,24), with a fixed at the origin and the four lines horizontal/vertical.

Resultados reales, uno por restricción adicional
// + length(l1) = 40
{"status":"under-constrained","dof":1,"free":["c","d"],"iterations":3,"redundant":0}
// + length(l2) = 25
{"status":"fully-constrained","dof":0,"iterations":3,"residual":7.95e-12,
 "points":[["a",0,0],["b",40,0],["c",40,25],["d",0,25]]}
// + length(l3) = 40   (implied by the others)
{"status":"over-constrained","dof":0,"redundant":1}
// + length(l3) = 30   named "bad"
{"status":"conflicting","redundant":1,
 "conflicts":["bad","vertical#2","length#5","vertical#4"],
 "message":"The constraints cannot all hold; the worst miss is bad"}

El menor cambio, y no la menor sorpresa

With height still free, moving c to (55, 31) and solving gave c = (40, 27.5) and d = (0, 27.5): the free height settled at the mean of the dragged c.y (31) and the old d.y (24), because Levenberg–Marquardt makes the smallest change that satisfies the constraints.

API

Firmas
export const solveSketch: (sketch: Sketch, options?: SolveOptions) => SolveResult
export const sketchProfiles: (sketch: Sketch, tolerance?: number /* 0.01 */) =>
  { readonly loops: Vec2[][]; readonly open: Vec2[][] }
export const polygonArea: (ring: ReadonlyArray<Vec2>) => number   // positive counter-clockwise
export class SketchError extends Error {}

export interface SolveResult {
  sketch: Sketch; status: SolveStatus; dof: number; iterations: number
  residual: number          // largest absolute residual left
  free: string[]            // points and circles that can still move
  conflicts: string[]       // worst first, only when not solved
  redundant: number
  message?: string
}
Placa con agujero, de punta a punta.
import { solveSketch, sketchProfiles, polygonArea } from "@cadcamfun/core"

const P = (id: string, x: number, y: number) => ({ id, kind: "point" as const, x, y })
const L = (id: string, p1: string, p2: string) => ({ id, kind: "line" as const, p1, p2 })

const result = solveSketch({
  entities: [P("a", 0, 0), P("b", 38, 2), P("c", 41, 27), P("d", -1, 24),
    L("l1", "a", "b"), L("l2", "b", "c"), L("l3", "c", "d"), L("l4", "d", "a"),
    P("o", 20, 12), { id: "h", kind: "circle", center: "o", radius: 4 }],
  constraints: [
    { type: "fixed", point: "a", x: 0, y: 0 },
    { type: "horizontal", line: "l1" }, { type: "vertical", line: "l2" },
    { type: "horizontal", line: "l3" }, { type: "vertical", line: "l4" },
    { type: "length", line: "l1", value: 40 }, { type: "length", line: "l2", value: 25 },
    { type: "distanceX", a: "a", b: "o", value: 20 }, { type: "distanceY", a: "a", b: "o", value: 12.5 },
    { type: "diameter", circle: "h", value: 10 },
  ],
})
result.status                       // "fully-constrained", dof 0
const { loops } = sketchProfiles(result.sketch)
loops.map((l) => [l.length, polygonArea(l)])
// [[50, 78.333], [4, 1000]]   circle: 50 points; rectangle: 4 points, area 1000

Del boceto a los perfiles

sketchProfiles encadena líneas y arcos de un extremo a otro a través de sus identificadores de puntos compartidos en bucles cerrados y devuelve cada círculo como un bucle propio. Un arco se convierte en puntos espaciados para que la cuerda permanezca dentro tolerance of the true arc (n = ⌈sweep / (2·acos(1 − tol/r))⌉, at least 2); a full circle gets at least 8 points. A chain that does not close is returned in open en lugar de dejarse caer.
Ranura de dos arcos y dos líneas. Arrastrar para orbitar · desplazarse para hacer zoom · arrastrar con el botón derecho para desplazarse
Una ranura de 30 mm de radio 8: dos arcos y dos líneas que comparten puntos finales, resueltos y encadenados por sketchProfiles en un bucle de 66 puntos (área 680.739 mm² contra los 681.062 mm² exactos; el polígono está inscrito, por lo que siempre es un poco más pequeño).

La ranura de arriba se resolvió como over-constrained con dof 0 y 1 ecuación redundante: haciendo top horizontal está implícito una vez que c1–a1 y c2–b1 son verticales y c1, c2 comparten una y. La solución aún tiene éxito; el estado solo marca el duplicado.

Solución de problemas

SíntomaCausaArreglar
SketchError: Duplicate entity iddos entidades comparten una identificaciónlos identificadores son únicos en puntos, líneas, círculos y arcos.
SketchError: Unknown unknownuna restricción nombra un punto o círculo que no está allícomprobar los identificadores en la restricción
conflicting con una larga lista de conflictosuna dimensión incorrecta saca varias ecuaciones de ceroarreglar la primera entrada; el resto suelen ser garantía
failedel comienzo está lejos de cualquier soluciónacercarse a la forma deseada; elevar maxIteraciones
perfil faltante de loopsuna cadena no se cierra: está en openhacer que los puntos finales tengan identificadores compartidos o agregar una restricción coincidente
el círculo tiene muy pocos puntostolerancia demasiado gruesa para un radio pequeñopasar una tolerancia menor a sketchProfiles