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.
| Aporte | Producción |
|---|---|
| entidades: puntos, líneas, círculos, arcos | las mismas entidades con coordenadas y radios resueltos |
| restricciones: 24 tipos, opcionalmente nombrados | estado, grados de libertad, puntos libres, ecuaciones redundantes, conflictos |
| options: maxIterations (200), tolerance (1e-9) | iteraciones utilizadas y el residuo más grande que queda |
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.
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.
| Tipo | Campos | Significado | Ecuaciones |
|---|---|---|---|
fixed | punto, x?, y? | fija un punto (cualquiera de las coordenadas es opcional) | 1–2 |
coincident | a, b | dos puntos son uno | 2 |
horizontal / vertical | línea | una línea está nivelada o a plomo | 1 |
horizontalPoints / verticalPoints | a, b | dos puntos comparten y / x | 1 |
distance | a, b, valor | distancia punto a punto | 1 |
distanceX / distanceY | a, b, valor | desplazamiento con signo a lo largo de un eje | 1 |
length | línea, valor | longitud de línea | 1 |
parallel / perpendicular | a, b | entre dos lineas | 1 |
equalLength | a, b | dos lineas | 1 |
equalRadius | a, b | dos círculos o arcos | 1 |
radius / diameter | circulo, valor | tamaño de un círculo o arco | 1 |
angle | a, b, valor | de la línea a a la línea b, en sentido antihorario, radianes | 1 |
pointOnLine / pointOnCircle | punto, línea | círculo | incidencia | 1 |
midpoint | punto, línea | punto en el medio de una línea | 2 |
symmetric | a, b, línea | par de espejos sobre una línea | 2 |
tangentLineCircle | línea, círculo | la línea toca el círculo | 1 |
tangentCircles | a, b, interno? | externo r1 + r2 o interno |r1 − r2| | 1 |
concentric | a, b | los centros coinciden | 2 |
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.
- Construya la función residual de cada restricción y registre a qué restricción pertenece cada ecuación.
- Start with λ = 1e-3. Solve (JᵀJ + λ·max(1, diag)) Δx = −Jᵀr; if Cholesky fails, multiply λ by 10 and retry.
- Accept the step if the residual norm drops (λ = max(λ/5, 1e-12)); otherwise λ ×= 10.
- Deténgase cuando el residual esté por debajo
tolerance(1e-9) o despuésmaxIterations(200). - 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).
| Constante | Valor | Dónde |
|---|---|---|
| maxIteraciones predeterminadas | 200 | SolveOptions |
| tolerancia predeterminada | 1e-9 | SolveOptions |
| amortiguación inicial λ | 1e-3 | solveSketch |
| suelo amortiguador | 1e-12 | solveSketch |
| resuelto si residual ≤ | 1e-6 | estado |
| tolerancia de rango | 1e-7 | nullSpace |
| umbral libre-desconocido | 1e-6 | puntos gratis |
| umbral de conflicto (por ecuación) | 1e-6 | conflictos |
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.
| Estado | Condición | que hacer |
|---|---|---|
fully-constrained | resuelto, dof 0, sin redundancia | nada |
under-constrained | resuelto, gld > 0 | agregar dimensiones para los identificadores en free |
over-constrained | resuelto, redundante > 0 | eliminar una restricción duplicada (inofensiva pero ruidosa) |
conflicting | no resuelto, redundante > 0 | arreglar la restricción en conflicts[0] |
failed | no resuelto, sin redundancia | comience 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.
// + 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
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
} 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.
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íntoma | Causa | Arreglar |
|---|---|---|
SketchError: Duplicate entity id | dos entidades comparten una identificación | los identificadores son únicos en puntos, líneas, círculos y arcos. |
SketchError: Unknown unknown | una 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 conflictos | una dimensión incorrecta saca varias ecuaciones de cero | arreglar la primera entrada; el resto suelen ser garantía |
failed | el comienzo está lejos de cualquier solución | acercarse a la forma deseada; elevar maxIteraciones |
perfil faltante de loops | una cadena no se cierra: está en open | hacer que los puntos finales tengan identificadores compartidos o agregar una restricción coincidente |
| el círculo tiene muy pocos puntos | tolerancia demasiado gruesa para un radio pequeño | pasar una tolerancia menor a sketchProfiles |