Pular para o conteúdo
CADCAMFUN
Docs

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.

EntradaSaída
entidades: pontos, linhas, círculos, arcosas mesmas entidades com coordenadas e raios resolvidos
restrições: 24 tipos, opcionalmente nomeadosstatus, graus de liberdade, pontos livres, equações redundantes, conflitos
options: maxIterations (200), tolerance (1e-9)iterações usadas e o maior resíduo restante
Esboço resolvido extrudado Arraste para orbitar · role para ampliar · arraste com o botão direito para deslocar
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.

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.

Tipos de entidade e esboço (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>
}

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.

TipoCamposSignificadoEquações
fixedponto, x?, y?fixa um ponto (qualquer coordenada opcional)1–2
coincidenta, bdois pontos são um2
horizontal / verticallinhauma linha é nivelada ou aprumada1
horizontalPoints / verticalPointsa, bdois pontos compartilham y / x1
distancea, b, valordistância ponto a ponto1
distanceX / distanceYa, b, valordeslocamento assinado ao longo de um eixo1
lengthlinha, valorcomprimento da linha1
parallel / perpendiculara, bentre duas linhas1
equalLengtha, bduas linhas1
equalRadiusa, bdois círculos ou arcos1
radius / diametercírculo, valortamanho de um círculo ou arco1
anglea, b, valorda linha a à linha b, sentido anti-horário, radianos1
pointOnLine / pointOnCircleponto, linha | círculoincidência1
midpointponto, linhaapontar no meio de uma linha2
symmetrica, b, linhapar de espelhos sobre uma linha2
tangentLineCirclelinha, círculolinha toca círculo1
tangentCirclesa, b, interno?r externo1 + r2 ou interno |r1 − r2|1
concentrica, bcentros coincidem2

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.

  1. Construa a função residual de cada restrição e registre qual restrição possui cada equação.
  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. Pare quando o resíduo estiver abaixo tolerance (1e-9) ou depois 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).
ConstanteValorOnde
padrão de maxIterações200SolveOptions
padrão de tolerância1e-9SolveOptions
amortecimento inicial λ1e-3solveSketch
amortecimento de piso1e-12solveSketch
resolvido se residual ≤1e-6status
tolerância de classificação1e-7nullSpace
limite livre desconhecido1e-6pontos grátis
limite de conflito (por equação)1e-6conflitos

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.

StatusDoençaO que fazer
fully-constrainedresolvido, dof 0, sem redundâncianada
under-constrainedresolvido, dof > 0adicione dimensões para os IDs em free
over-constrainedresolvido, redundante > 0remova uma restrição duplicada (inofensiva, mas barulhenta)
conflictingnão resolvido, redundante > 0corrigir a restrição em conflicts[0]
failednão resolvido, sem redundânciacomece 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.

Resultados reais, um por restrição adicionada
// + 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

Assinaturas
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
}
Prato com furo, ponta a ponta
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.
Slot de dois arcos e duas linhas Arraste para orbitar · role para ampliar · arraste com o botão direito para deslocar
Um slot de 30 mm de raio 8: dois arcos e duas linhas compartilhando pontos finais, resolvidos e encadeados por sketchProfiles em um loop de 66 pontos (área 680.739 mm² contra os exatos 681.062 mm²; o polígono é inscrito, portanto é sempre um pouco menor).

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

SintomaCausaConsertar
SketchError: Duplicate entity idduas entidades compartilham um IDids são únicos em pontos, linhas, círculos e arcos
SketchError: Unknown unknownuma restrição nomeia um ponto ou círculo que não existeverifique os ids na restrição
conflicting com uma longa lista de conflitosuma dimensão errada tira várias equações do zerocorrija a primeira entrada; o resto geralmente são colaterais
failedo começo está longe de qualquer soluçãoaproxime-se da forma pretendida; aumentar maxIterações
perfil faltando em loopsuma corrente não se fecha: está em openfaça IDs compartilhados de pontos finais ou adicione uma restrição coincidente
círculo tem poucos pontostolerância muito grosseira para um raio pequenopasse uma tolerância menor para sketchProfiles