Documentação
Solucionador de esboço
Esboços 2D restritos resolvidos por Levenberg – Marquardt, com graus de liberdade, pontos livres e conflitos nomeados.
Traduzido automaticamente do inglês. Ver o original em inglês
O que o solucionador faz
O solucionador de esboço transforma um conjunto de pontos, linhas, círculos e arcos, além de restrições geométricas e dimensionais, em coordenadas concretas. Ele vive em packages/core/src/cad/sketch.ts (522 linhas) e é exportado de @cadcamfun/core como solveSketch, sketchProfiles e polygonArea. O histórico paramétrico resolve primeiro suas expressões para números e depois chama o solucionador; os perfis resolvidos alimentam extrusões, revoluções e bolsões.
| Entrada | Saída |
|---|---|
| entidades: pontos, linhas, círculos, arcos | as mesmas entidades com coordenadas e raios resolvidos |
| restrições: 24 tipos, opcionalmente nomeados | status, graus de liberdade, pontos livres, equações redundantes, conflitos |
| options: maxIterations (200), tolerance (1e-9) | iterações usadas e o maior resíduo restante |
Modelo de dados
Os pontos são as incógnitas. Cada linha, círculo e arco referem-se a pontos por id, portanto, duas linhas que compartilham uma extremidade compartilham o ponto e se movem juntas. Um círculo adiciona mais uma incógnita, seu raio. Um arco é um centro e dois pontos no círculo, no sentido anti-horário a partir start a end; seu raio é |início − centro| e o solucionador adiciona sua própria equação mantendo end no mesmo 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>
} O vetor desconhecido é apresentado como cada ponto x, y em ordem de entidade, então cada círculo r. Um retângulo de quatro pontos possui 8 incógnitas; adicione um círculo com seu próprio centro e terá 11.
Catálogo de restrições
Cada restrição contribui com equações residuais que são zero quando válidas. Uma restrição pode carregar um id; sem um é nomeado type#index (por exemplo vertical#2) em relatórios de conflitos.
| Tipo | Campos | Significado | Equações |
|---|---|---|---|
fixed | ponto, x?, y? | fixa um ponto (qualquer coordenada opcional) | 1–2 |
coincident | a, b | dois pontos são um | 2 |
horizontal / vertical | linha | uma linha é nivelada ou aprumada | 1 |
horizontalPoints / verticalPoints | a, b | dois pontos compartilham y / x | 1 |
distance | a, b, valor | distância ponto a ponto | 1 |
distanceX / distanceY | a, b, valor | deslocamento assinado ao longo de um eixo | 1 |
length | linha, valor | comprimento da linha | 1 |
parallel / perpendicular | a, b | entre duas linhas | 1 |
equalLength | a, b | duas linhas | 1 |
equalRadius | a, b | dois círculos ou arcos | 1 |
radius / diameter | círculo, valor | tamanho de um círculo ou arco | 1 |
angle | a, b, valor | da linha a à linha b, sentido anti-horário, radianos | 1 |
pointOnLine / pointOnCircle | ponto, linha | círculo | incidência | 1 |
midpoint | ponto, linha | apontar no meio de uma linha | 2 |
symmetric | a, b, linha | par de espelhos sobre uma linha | 2 |
tangentLineCircle | linha, círculo | linha toca círculo | 1 |
tangentCircles | a, b, interno? | r externo1 + r2 ou interno |r1 − r2| | 1 |
concentric | a, b | centros coincidem | 2 |
Resolvendo: Levenberg – Marquardt
O solucionador minimiza a soma dos resíduos quadrados com Levenberg-Marquardt e um Jacobiano de diferença central. Cada etapa resolve as equações normais amortecidas pela fatoração de Cholesky; um degrau que reduz o residual é aceito e o amortecimento cai, um degrau que não o faz é rejeitado e o amortecimento aumenta. Como cada etapa é a menor alteração que reduz os resíduos, um ponto arrastado arrasta o resto do esboço da mesma forma que faria em uma prancheta.
- Construa a função residual de cada restrição e registre qual restrição possui cada equação.
- 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.
- Pare quando o resíduo estiver abaixo
tolerance(1e-9) ou depoismaxIterations(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 | Onde |
|---|---|---|
| padrão de maxIterações | 200 | SolveOptions |
| padrão de tolerância | 1e-9 | SolveOptions |
| amortecimento inicial λ | 1e-3 | solveSketch |
| amortecimento de piso | 1e-12 | solveSketch |
| resolvido se residual ≤ | 1e-6 | status |
| tolerância de classificação | 1e-7 | nullSpace |
| limite livre desconhecido | 1e-6 | pontos grátis |
| limite de conflito (por equação) | 1e-6 | conflitos |
Status e diagnóstico
Após a resolução, graus de liberdade = incógnitas – classificação e equações redundantes = equações – classificação. O status decorre desses dois números e se o resíduo atingiu 1e-6.
| Status | Doença | O que fazer |
|---|---|---|
fully-constrained | resolvido, dof 0, sem redundância | nada |
under-constrained | resolvido, dof > 0 | adicione dimensões para os IDs em free |
over-constrained | resolvido, redundante > 0 | remova uma restrição duplicada (inofensiva, mas barulhenta) |
conflicting | não resolvido, redundante > 0 | corrigir a restrição em conflicts[0] |
failed | não resolvido, sem redundância | comece mais próximo da forma pretendida ou aumente 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"} Menos mudança, e não menos surpresa
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 Do esboço aos perfis
sketchProfiles encadeia linhas e arcos de ponta a ponta através de seus ids de pontos compartilhados em loops fechados e retorna cada círculo como um loop próprio. Um arco torna-se pontos espaçados para que a corda permaneça 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 em vez de ser descartado.
O slot acima foi resolvido como over-constrained com dof 0 e 1 equação redundante: fazendo top horizontal está implícito uma vez que c1–a1 e c2–b1 são verticais e c1, c2 compartilham um y. A solução ainda é bem-sucedida; o status sinaliza apenas a duplicata.
Solução de problemas
| Sintoma | Causa | Consertar |
|---|---|---|
SketchError: Duplicate entity id | duas entidades compartilham um ID | ids são únicos em pontos, linhas, círculos e arcos |
SketchError: Unknown unknown | uma restrição nomeia um ponto ou círculo que não existe | verifique os ids na restrição |
conflicting com uma longa lista de conflitos | uma dimensão errada tira várias equações do zero | corrija a primeira entrada; o resto geralmente são colaterais |
failed | o começo está longe de qualquer solução | aproxime-se da forma pretendida; aumentar maxIterações |
perfil faltando em loops | uma corrente não se fecha: está em open | faça IDs compartilhados de pontos finais ou adicione uma restrição coincidente |
| círculo tem poucos pontos | tolerância muito grosseira para um raio pequeno | passe uma tolerância menor para sketchProfiles |