Документация
Решатель эскизов
Ограниченные 2D-эскизы, решенные Левенбергом–Марквардтом, со степенями свободы, свободными точками и именованными конфликтами.
Автоматически переведено с английского. Смотреть оригинал на английском
Что делает решатель
Решатель эскизов преобразует набор точек, линий, окружностей и дуг, а также геометрические и размерные ограничения в конкретные координаты. Оно живет в packages/core/src/cad/sketch.ts (строки 522) и экспортируется из @cadcamfun/core как solveSketch, sketchProfiles и polygonArea. Параметрическая история сначала преобразует свои выражения в числа, а затем вызывает решатель; решенные профили подачи выдавливают, вращают и делают карманы.
| Вход | Выход |
|---|---|
| объекты: точки, линии, круги, дуги | те же объекты с решенными координатами и радиусами |
| ограничения: 24 виды, опционально названные | статус, степени свободы, свободные точки, избыточные уравнения, конфликты |
| options: maxIterations (200), tolerance (1e-9) | использовано итераций и остался наибольший остаток |
Модель данных
Точки – это неизвестные. Каждая линия, круг и дуга относятся к точкам по идентификатору, поэтому две линии, имеющие общий конец, разделяют точку и движутся вместе. К окружности добавляется еще одно неизвестное — ее радиус. Дуга — это центр и две точки окружности, направленные против часовой стрелки от start до end; его радиус |start − center| и решатель добавляет свое собственное уравнение, сохраняя end в том же кругу.
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>
} Неизвестный вектор представлен как координаты каждой точки. x, y в порядке объектов, затем каждый круг r. Прямоугольник из четырех точек имеет 8 неизвестных; добавьте круг со своим центром, и у него будет 11.
Каталог ограничений
Каждое ограничение вносит вклад в остаточные уравнения, которые равны нулю, когда оно выполняется. Ограничение может нести id; без него это называется type#index (например vertical#2) в отчетах о конфликтах.
| Тип | Поля | Значение | Уравнения |
|---|---|---|---|
fixed | точка, х?, у? | закрепляет точку (любая координата необязательна) | 1–2 |
coincident | a, b | две точки - это одна | 2 |
horizontal / vertical | линия | линия ровная или отвесная | 1 |
horizontalPoints / verticalPoints | a, b | две точки делят y/x | 1 |
distance | а, б, значение | расстояние между точками | 1 |
distanceX / distanceY | а, б, значение | знаковое смещение по одной оси | 1 |
length | линия, значение | длина строки | 1 |
parallel / perpendicular | a, b | между двумя строками | 1 |
equalLength | a, b | две линии | 1 |
equalRadius | a, b | два круга или дуги | 1 |
radius / diameter | круг, значение | размер круга или дуги | 1 |
angle | а, б, значение | от строки a к строке b, против часовой стрелки, радианы | 1 |
pointOnLine / pointOnCircle | точка, линия | круг | заболеваемость | 1 |
midpoint | точка, линия | точка в середине линии | 2 |
symmetric | а, б, линия | зеркальная пара около линии | 2 |
tangentLineCircle | линия, круг | линия касается круга | 1 |
tangentCircles | а, б, внутренний? | внешний r1 + r2 или внутренний |r1 − r2| | 1 |
concentric | a, b | центры совпадают | 2 |
Решение: Левенберг – Марквардт.
Решатель минимизирует сумму квадратов остатков с помощью Левенберга – Марквардта и якобиана центральной разности. На каждом этапе затухающие нормальные уравнения решаются с помощью факторизации Холецкого; шаг, снижающий невязку, принимается, и затухание падает, шаг, который не уменьшается, отклоняется, и затухание растет. Поскольку каждый шаг представляет собой наименьшее изменение, уменьшающее остатки, перетаскиваемая точка перетаскивает остальную часть эскиза так, как это было бы на чертежной доске.
- Постройте функцию невязки каждого ограничения и запишите, какому ограничению принадлежит каждое уравнение.
- 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.
- Остановитесь, когда остаток станет ниже
tolerance(1e-9) или послеmaxIterations(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).
| Постоянный | Ценить | Где |
|---|---|---|
| maxIterations по умолчанию | 200 | SolveOptions |
| допуск по умолчанию | 1e-9 | SolveOptions |
| начальное демпфирование λ | 1e-3 | solveSketch |
| демпфирующий пол | 1e-12 | solveSketch |
| решено, если остаток ≤ | 1e-6 | статус |
| ранговая толерантность | 1e-7 | nullSpace |
| свободный-неизвестный порог | 1e-6 | бесплатные баллы |
| порог конфликта (на уравнение) | 1e-6 | конфликты |
Статус и диагноз
После решения степени свободы = неизвестные — ранг и избыточные уравнения = уравнения — ранг. Статус следует из этих двух чисел и от того, достиг ли остаток 1e-6.
| Статус | Состояние | Что делать |
|---|---|---|
fully-constrained | решено, степень свободы 0, без избыточности | ничего |
under-constrained | решено, степень свободы > 0 | добавить размеры для идентификаторов в free |
over-constrained | решено, избыточно > 0 | удалить дублирующее ограничение (безвредно, но шумно) |
conflicting | не решено, избыточно > 0 | исправить ограничение в conflicts[0] |
failed | не решено, нет избыточности | начните ближе к намеченной форме или увеличьте 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"} Наименьшее изменение, не меньшее удивление
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 От эскиза к профилям
sketchProfiles соединяет линии и дуги из конца в конец через их общие идентификаторы точек в замкнутые циклы и возвращает каждый круг как собственный цикл. Дуга становится точками, расположенными так, что хорда остается внутри. 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 вместо того, чтобы быть сброшенным.
Слот выше решен как over-constrained с dof 0 и 1 избыточным уравнением: создание top горизонтальный подразумевается, если c1–a1 и c2–b1 вертикальны и c1, c2 имеют общий y. Решение все еще удается; статус только отмечает дубликат.
Поиск неисправностей
| Симптом | Причина | Исправить |
|---|---|---|
SketchError: Duplicate entity id | два объекта имеют общий идентификатор | идентификаторы уникальны для точек, линий, кругов и дуг |
SketchError: Unknown unknown | ограничение называет точку или круг, которых там нет | проверьте идентификаторы в ограничении |
conflicting с длинным списком конфликтов | одно неправильное измерение приводит к отклонению нескольких уравнений от нуля | исправить первую запись; остальное обычно является залогом |
failed | начало далеко от любого решения | приблизиться к намеченной форме; поднять maxIterations |
профиль отсутствует в loops | цепочка не замыкается: она в open | сделайте общие идентификаторы конечных точек или добавьте ограничение совпадения |
| в круге слишком мало точек | допуск слишком грубый для малого радиуса | передать меньший допуск в SketchProfiles |