Dokumentation
Skizzenlöser
Von Levenberg-Marquardt gelöste eingeschränkte 2D-Skizzen mit Freiheitsgraden, freien Punkten und benannten Konflikten.
Automatisch aus dem Englischen übersetzt. Englisches Original ansehen
Was der Löser tut
Der Skizzenlöser wandelt eine Reihe von Punkten, Linien, Kreisen und Bögen sowie geometrischen und dimensionalen Einschränkungen in konkrete Koordinaten um. Es lebt darin packages/core/src/cad/sketch.ts (522 Zeilen) und wird aus exportiert @cadcamfun/core als solveSketch, sketchProfiles und polygonArea. Der parametrische Verlauf löst seine Ausdrücke zunächst in Zahlen auf und ruft dann den Löser auf; Die gelösten Profile versorgen Extrudate, Rotationen und Taschen.
| Eingang | Ausgabe |
|---|---|
| entitäten: Punkte, Linien, Kreise, Bögen | die gleichen Einheiten mit gelösten Koordinaten und Radien |
| einschränkungen: 24 Arten, optional benannt | status, Freiheitsgrade, freie Punkte, redundante Gleichungen, Konflikte |
| options: maxIterations (200), tolerance (1e-9) | verwendete Iterationen und das größte verbleibende Residuum |
Datenmodell
Punkte sind die Unbekannten. Jede Linie, jeder Kreis und jeder Bogen bezieht sich anhand der ID auf Punkte, d. h. zwei Linien, die ein gemeinsames Ende haben, teilen sich den Punkt und bewegen sich gemeinsam. Ein Kreis fügt eine weitere Unbekannte hinzu, seinen Radius. Ein Bogen besteht aus einem Mittelpunkt und zwei Punkten auf dem Kreis, die gegen den Uhrzeigersinn verlaufen start bis end; sein Radius ist |start − centre| und der Löser fügt seine eigene Gleichungsführung hinzu end auf demselben Kreis.
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>
} Der unbekannte Vektor wird als jeder Punkt dargestellt x, y in der Reihenfolge der Entitäten, dann die jedes Kreises r. Ein Rechteck aus vier Punkten hat 8 Unbekannte; Füge einen Kreis mit eigenem Mittelpunkt hinzu und er hat 11.
Einschränkungskatalog
Jede Einschränkung trägt zu Restgleichungen bei, die Null sind, wenn sie gilt. Eine Einschränkung kann eine tragen id; ohne eins wird es benannt type#index (zum Beispiel vertical#2) in Konfliktberichten.
| Typ | Felder | Bedeutung | Gleichungen |
|---|---|---|---|
fixed | punkt, x?, y? | fixiert einen Punkt (beide Koordinaten optional) | 1–2 |
coincident | a, b | zwei Punkte sind eins | 2 |
horizontal / vertical | linie | eine Linie ist eben oder lotrecht | 1 |
horizontalPoints / verticalPoints | a, b | zwei Punkte teilen sich y/x | 1 |
distance | a, b, Wert | punkt-zu-Punkt-Abstand | 1 |
distanceX / distanceY | a, b, Wert | vorzeichenbehafteter Versatz entlang einer Achse | 1 |
length | linie, Wert | zeilenlänge | 1 |
parallel / perpendicular | a, b | zwischen zwei Zeilen | 1 |
equalLength | a, b | zwei Zeilen | 1 |
equalRadius | a, b | zwei Kreise oder Bögen | 1 |
radius / diameter | kreis, Wert | größe eines Kreises oder Bogens | 1 |
angle | a, b, Wert | von Linie a zu Linie b, gegen den Uhrzeigersinn, Bogenmaß | 1 |
pointOnLine / pointOnCircle | punkt, Linie | Kreis | häufigkeit | 1 |
midpoint | punkt, Linie | punkt in der Mitte einer Linie | 2 |
symmetric | a, b, Linie | spiegelpaar um eine Linie | 2 |
tangentLineCircle | linie, Kreis | linie berührt Kreis | 1 |
tangentCircles | a, b, intern? | extern r1 + r2 oder intern |r1 − r2| | 1 |
concentric | a, b | zentren fallen zusammen | 2 |
Lösung: Levenberg–Marquardt
Der Löser minimiert die Summe der quadrierten Residuen mit Levenberg-Marquardt und einer zentralen Differenz-Jacobian-Methode. Jeder Schritt löst die gedämpften Normalgleichungen durch Cholesky-Faktorisierung; Ein Schritt, der den Rest verringert, wird akzeptiert und die Dämpfung sinkt, ein Schritt, der dies nicht tut, wird abgelehnt und die Dämpfung nimmt zu. Da es sich bei jedem Schritt um die kleinste Änderung handelt, die die Reste reduziert, zieht ein gezogener Punkt den Rest der Skizze auf die gleiche Weise wie auf einem Zeichenbrett.
- Erstellen Sie die Restfunktion jeder Einschränkung und notieren Sie, welche Einschränkung jede Gleichung besitzt.
- 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.
- Stoppen Sie, wenn der Restwert darunter liegt
tolerance(1e-9) oder danachmaxIterations(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).
| Konstante | Wert | Wo |
|---|---|---|
| maxIterations-Standard | 200 | SolveOptions |
| toleranzvorgabe | 1e-9 | SolveOptions |
| anfangsdämpfung λ | 1e-3 | solveSketch |
| dämpfungsboden | 1e-12 | solveSketch |
| gelöst, wenn Rest ≤ | 1e-6 | status |
| rangtoleranz | 1e-7 | nullSpace |
| frei-unbekannter Schwellenwert | 1e-6 | kostenlose Punkte |
| konfliktschwelle (pro Gleichung) | 1e-6 | konflikte |
Status und Diagnose
Nach der Lösung sind Freiheitsgrade = Unbekannte − Rang und redundante Gleichungen = Gleichungen − Rang. Der Status ergibt sich aus diesen beiden Zahlen und daraus, ob das Residuum 1e-6 erreicht hat.
| Status | Zustand | Was zu tun |
|---|---|---|
fully-constrained | gelöst, Dof 0, keine Redundanz | nichts |
under-constrained | gelöst, dof > 0 | fügen Sie Dimensionen für die IDs hinzu free |
over-constrained | gelöst, überflüssig > 0 | entfernen Sie eine doppelte Einschränkung (harmlos, aber laut) |
conflicting | nicht gelöst, überflüssig > 0 | beheben Sie die Einschränkung conflicts[0] |
failed | nicht gelöst, keine Redundanz | beginnen Sie näher an der beabsichtigten Form oder erhöhen Sie 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"} Geringste Veränderung, nicht die geringste Überraschung
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 Von der Skizze bis zum Profil
sketchProfiles verkettet Linien und Bögen Ende an Ende durch ihre gemeinsamen Punkt-IDs in geschlossenen Schleifen und gibt jeden Kreis als eigene Schleife zurück. Ein Bogen wird zu Punkten, die so beabstandet sind, dass die Sehne innerhalb bleibt 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 anstatt fallen gelassen zu werden.
Der obige Slot wurde als gelöst over-constrained mit dof 0 und 1 redundante Gleichung: Erstellen top horizontal wird impliziert, sobald c1–a1 und c2–b1 vertikal sind und c1, c2 ein gemeinsames Y haben. Die Lösung gelingt immer noch; Der Status kennzeichnet nur das Duplikat.
Fehlerbehebung
| Symptom | Ursache | Fix |
|---|---|---|
SketchError: Duplicate entity id | zwei Entitäten teilen sich eine ID | iDs sind für Punkte, Linien, Kreise und Bögen eindeutig |
SketchError: Unknown unknown | eine Einschränkung benennt einen Punkt oder Kreis, der nicht vorhanden ist | überprüfen Sie die IDs in der Einschränkung |
conflicting mit einer langen Konfliktliste | eine falsche Dimension zieht mehrere Gleichungen vom Nullpunkt ab | den ersten Eintrag korrigieren; der Rest sind in der Regel Sicherheiten |
failed | der Anfang ist noch lange keine Lösung | nähern Sie sich der beabsichtigten Form; maxIterations erhöhen |
profil fehlt in loops | eine Kette schließt sich nicht: Sie ist drin open | machen Sie Endpunkte zu gemeinsamen IDs oder fügen Sie eine zusammenfallende Einschränkung hinzu |
| kreis hat zu wenige Punkte | toleranz zu grob für kleinen Radius | übergeben Sie eine kleinere Toleranz an SketchProfiles |