Zum Inhalt springen
CADCAMFUN
Doku

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.

EingangAusgabe
entitäten: Punkte, Linien, Kreise, Bögendie gleichen Einheiten mit gelösten Koordinaten und Radien
einschränkungen: 24 Arten, optional benanntstatus, Freiheitsgrade, freie Punkte, redundante Gleichungen, Konflikte
options: maxIterations (200), tolerance (1e-9)verwendete Iterationen und das größte verbleibende Residuum
Gelöste Skizze extrudiert Zum Orbit ziehen · zum Zoomen scrollen · zum Schwenken mit der rechten Maustaste ziehen
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.

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.

Element- und Skizzentypen (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>
}

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.

TypFelderBedeutungGleichungen
fixedpunkt, x?, y?fixiert einen Punkt (beide Koordinaten optional)1–2
coincidenta, bzwei Punkte sind eins2
horizontal / verticallinieeine Linie ist eben oder lotrecht1
horizontalPoints / verticalPointsa, bzwei Punkte teilen sich y/x1
distancea, b, Wertpunkt-zu-Punkt-Abstand1
distanceX / distanceYa, b, Wertvorzeichenbehafteter Versatz entlang einer Achse1
lengthlinie, Wertzeilenlänge1
parallel / perpendiculara, bzwischen zwei Zeilen1
equalLengtha, bzwei Zeilen1
equalRadiusa, bzwei Kreise oder Bögen1
radius / diameterkreis, Wertgröße eines Kreises oder Bogens1
anglea, b, Wertvon Linie a zu Linie b, gegen den Uhrzeigersinn, Bogenmaß1
pointOnLine / pointOnCirclepunkt, Linie | Kreishäufigkeit1
midpointpunkt, Liniepunkt in der Mitte einer Linie2
symmetrica, b, Liniespiegelpaar um eine Linie2
tangentLineCirclelinie, Kreislinie berührt Kreis1
tangentCirclesa, b, intern?extern r1 + r2 oder intern |r1 − r2|1
concentrica, bzentren fallen zusammen2

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.

  1. Erstellen Sie die Restfunktion jeder Einschränkung und notieren Sie, welche Einschränkung jede Gleichung besitzt.
  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. Stoppen Sie, wenn der Restwert darunter liegt tolerance (1e-9) oder danach 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).
KonstanteWertWo
maxIterations-Standard200SolveOptions
toleranzvorgabe1e-9SolveOptions
anfangsdämpfung λ1e-3solveSketch
dämpfungsboden1e-12solveSketch
gelöst, wenn Rest ≤1e-6status
rangtoleranz1e-7nullSpace
frei-unbekannter Schwellenwert1e-6kostenlose Punkte
konfliktschwelle (pro Gleichung)1e-6konflikte

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.

StatusZustandWas zu tun
fully-constrainedgelöst, Dof 0, keine Redundanznichts
under-constrainedgelöst, dof > 0fügen Sie Dimensionen für die IDs hinzu free
over-constrainedgelöst, überflüssig > 0entfernen Sie eine doppelte Einschränkung (harmlos, aber laut)
conflictingnicht gelöst, überflüssig > 0beheben Sie die Einschränkung conflicts[0]
failednicht gelöst, keine Redundanzbeginnen 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.

Echte Ergebnisse, eines pro hinzugefügter Einschränkung
// + 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

Unterschriften
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
}
Platte mit einem Loch, Ende an Ende
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.
Schlitz aus zwei Bögen und zwei Linien Zum Orbit ziehen · zum Zoomen scrollen · zum Schwenken mit der rechten Maustaste ziehen
Ein Schlitz von 30 mm mit dem Radius 8: zwei Bögen und zwei Linien mit gemeinsamen Endpunkten, gelöst und verkettet durch SketchProfiles in einer Schleife mit 66-Punkten (Fläche 680.739 mm² gegenüber genau 681.062 mm²; das Polygon ist eingeschrieben, daher ist es immer etwas kleiner).

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

SymptomUrsacheFix
SketchError: Duplicate entity idzwei Entitäten teilen sich eine IDiDs sind für Punkte, Linien, Kreise und Bögen eindeutig
SketchError: Unknown unknowneine Einschränkung benennt einen Punkt oder Kreis, der nicht vorhanden istüberprüfen Sie die IDs in der Einschränkung
conflicting mit einer langen Konfliktlisteeine falsche Dimension zieht mehrere Gleichungen vom Nullpunkt abden ersten Eintrag korrigieren; der Rest sind in der Regel Sicherheiten
failedder Anfang ist noch lange keine Lösungnähern Sie sich der beabsichtigten Form; maxIterations erhöhen
profil fehlt in loopseine Kette schließt sich nicht: Sie ist drin openmachen Sie Endpunkte zu gemeinsamen IDs oder fügen Sie eine zusammenfallende Einschränkung hinzu
kreis hat zu wenige Punktetoleranz zu grob für kleinen Radiusübergeben Sie eine kleinere Toleranz an SketchProfiles