Documentazione
Risolutore di schizzi
Schizzi 2D vincolati risolti da Levenberg–Marquardt, con gradi di libertà, punti liberi e conflitti con nome.
Tradotto automaticamente dall'inglese. Vedi l'originale in inglese
Cosa fa il risolutore
Il risolutore di schizzi trasforma un insieme di punti, linee, cerchi e archi oltre a vincoli geometrici e dimensionali in coordinate concrete. Vive dentro packages/core/src/cad/sketch.ts (522 righe) e viene esportato da @cadcamfun/core come solveSketch, sketchProfiles e polygonArea. La cronologia parametrica risolve prima le sue espressioni in numeri, quindi richiama il risolutore; i profili risolti alimentano estrusioni, rivoluzioni e tasche.
| Ingresso | Produzione |
|---|---|
| entità: punti, linee, cerchi, archi | le stesse entità con coordinate e raggi risolti |
| vincoli: 24 tipi, facoltativamente denominati | stato, gradi di libertà, punti liberi, equazioni ridondanti, conflitti |
| options: maxIterations (200), tolerance (1e-9) | iterazioni utilizzate e il residuo più grande rimasto |
Modello di dati
I punti sono le incognite. Ogni linea, cerchio e arco si riferisce a punti tramite id, quindi due linee che condividono un'estremità condividono il punto e si muovono insieme. Un cerchio aggiunge un'altra incognita, il suo raggio. Un arco è un centro e due punti sul cerchio, in senso antiorario start a end; il suo raggio è |inizio − centro| e il risolutore aggiunge la propria equazione mantenendo end sullo stesso cerchio.
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>
} Il vettore sconosciuto è disposto come ogni punto x, y in ordine di entità, quindi quello di ogni cerchio r. Un rettangolo di quattro punti ha 8 incognite; aggiungi un cerchio con il proprio centro e ha 11.
Catalogo dei vincoli
Ogni vincolo contribuisce con equazioni residue che sono zero quando vale. Un vincolo può portare un id; senza uno viene nominato type#index (per esempio vertical#2) nelle relazioni sui conflitti.
| Tipo | Campi | Senso | Equazioni |
|---|---|---|---|
fixed | punto, x?, y? | fissa un punto (una delle coordinate è opzionale) | 1–2 |
coincident | a, b | due punti sono uno | 2 |
horizontal / vertical | linea | una linea è a livello o a piombo | 1 |
horizontalPoints / verticalPoints | a, b | due punti condividono y/x | 1 |
distance | a, b, valore | distanza punto a punto | 1 |
distanceX / distanceY | a, b, valore | offset con segno lungo un asse | 1 |
length | linea, valore | lunghezza della linea | 1 |
parallel / perpendicular | a, b | tra due linee | 1 |
equalLength | a, b | due righe | 1 |
equalRadius | a, b | due cerchi o archi | 1 |
radius / diameter | cerchio, valore | dimensione di un cerchio o di un arco | 1 |
angle | a, b, valore | dalla linea a alla linea b, in senso antiorario, radianti | 1 |
pointOnLine / pointOnCircle | punto, linea | cerchio | incidenza | 1 |
midpoint | punto, linea | punto al centro di una linea | 2 |
symmetric | a, b, linea | coppia di specchi attorno ad una linea | 2 |
tangentLineCircle | linea, cerchio | la linea tocca il cerchio | 1 |
tangentCircles | a, b, interno? | esterno r1 + r2 oppure interno |r1 − r2| | 1 |
concentric | a, b | i centri coincidono | 2 |
Risoluzione: Levenberg-Marquardt
Il risolutore minimizza la somma dei residui quadrati con Levenberg–Marquardt e uno Jacobiano della differenza centrale. Ogni passaggio risolve le equazioni normali smorzate mediante la fattorizzazione di Cholesky; un gradino che abbassa il residuo viene accettato e lo smorzamento diminuisce, un gradino che non abbassa il residuo viene rifiutato e lo smorzamento cresce. Poiché ogni passaggio rappresenta la modifica più piccola che riduce i residui, un punto trascinato trascina il resto dello schizzo come farebbe su un tavolo da disegno.
- Costruisci la funzione residua di ogni vincolo e registra quale vincolo possiede ciascuna equazione.
- 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.
- Fermarsi quando il residuo è inferiore
tolerance(1e-9) o dopomaxIterations(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).
| Costante | Valore | Dove |
|---|---|---|
| valore predefinito per maxIterazioni | 200 | SolveOptions |
| difetto di tolleranza | 1e-9 | SolveOptions |
| smorzamento iniziale λ | 1e-3 | solveSketch |
| pavimento smorzante | 1e-12 | solveSketch |
| risolto se residuo ≤ | 1e-6 | stato |
| tolleranza di rango | 1e-7 | nullSpace |
| soglia libera sconosciuta | 1e-6 | punti gratuiti |
| soglia di conflitto (per equazione) | 1e-6 | conflitti |
Stato e diagnosi
Dopo la soluzione, gradi di libertà = incognite − rango ed equazioni ridondanti = equazioni − rango. Lo stato deriva da questi due numeri e dal fatto che il residuo abbia raggiunto 1e-6.
| Stato | Condizione | Cosa fare |
|---|---|---|
fully-constrained | risolto, gdl 0, nessuna ridondanza | niente |
under-constrained | risolto, gdl > 0 | aggiungi dimensioni per gli ID in free |
over-constrained | risolto, ridondante > 0 | rimuovere un vincolo duplicato (innocuo ma rumoroso) |
conflicting | non risolto, ridondante > 0 | fissare il vincolo conflicts[0] |
failed | non risolto, nessuna ridondanza | iniziare più vicino alla forma desiderata o aumentare maxIterations |
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"} Il minimo cambiamento, non ultima la 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 Dallo schizzo ai profili
sketchProfiles concatena linee e archi da un'estremità all'altra attraverso i rispettivi ID punto condivisi in cicli chiusi e restituisce ogni cerchio come un ciclo a sé stante. Un arco diventa punti distanziati in modo che la corda rimanga all'interno 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 invece di essere lasciato cadere.
Lo slot sopra risolto come over-constrained con gdl 0 e 1 equazione ridondante: creazione top orizzontale è implicito quando c1–a1 e c2–b1 sono verticali e c1, c2 condividono a y. La soluzione riesce ancora; lo stato contrassegna solo il duplicato.
Risoluzione dei problemi
| Sintomo | Causa | Aggiustare |
|---|---|---|
SketchError: Duplicate entity id | due entità condividono un ID | gli ID sono univoci per punti, linee, cerchi e archi |
SketchError: Unknown unknown | un vincolo nomina un punto o un cerchio che non è presente | controllare gli ID nel vincolo |
conflicting con un lungo elenco di conflitti | una dimensione sbagliata fa scomparire diverse equazioni dallo zero | correggere la prima voce; il resto è solitamente collaterale |
failed | l'inizio è lontano da qualsiasi soluzione | avvicinarsi alla forma prevista; aumentare maxIterazioni |
profilo mancante da loops | una catena non si chiude: è dentro open | rendere gli ID condivisi dei punti finali o aggiungere un vincolo coincidente |
| il cerchio ha troppo pochi punti | tolleranza troppo grossolana per un raggio piccolo | passare una tolleranza minore a sketchProfiles |