ドキュメント
スケッチソルバー
Levenberg-Marquardt によって解決された、自由度、自由点、名前付き競合を含む制約付きの 2D スケッチ。
英語から自動翻訳されています。 英語の原文を見る
ソルバーが行うこと
スケッチ ソルバーは、点、線、円、円弧のセットと幾何学的拘束および寸法拘束を具体的な座標に変換します。それはに住んでいます packages/core/src/cad/sketch.ts (522 行) からエクスポートされます @cadcamfun/core として solveSketch, sketchProfiles そして polygonArea。パラメトリックヒストリーは、最初にその式を数値に解決してから、ソルバーを呼び出します。解決されたプロファイルは押し出し、回転、ポケットに供給されます。
| 入力 | 出力 |
|---|---|
| エンティティ: 点、線、円、円弧 | 解決された座標と半径を持つ同じエンティティ |
| 制約: 24 種類、オプションで名前を付ける | ステータス、自由度、フリーポイント、冗長方程式、競合 |
| options: maxIterations (200), tolerance (1e-9) | 使用された反復と残った最大の残差 |
データモデル
ポイントは未知数です。すべての線、円、円弧は ID によって点を参照するため、端を共有する 2 つの線は点を共有し、一緒に移動します。円は、その半径という未知の要素をさらに追加します。円弧は、円上の中心と 2 点であり、そこから反時計回りに start まで end;その半径は |開始 − 中心| です。そしてソルバーは独自の方程式保持を追加します 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。 4 つの点からなる長方形には 8 個の未知数があります。独自の中心を持つ円を追加すると、11 が含まれます。
制約カタログ
各制約は、それが成立する場合にはゼロとなる残差方程式に寄与します。制約には、 id;何もなければそれは名前が付けられます type#index (例えば vertical#2) 紛争報告書に記載されています。
| タイプ | フィールド | 意味 | 方程式 |
|---|---|---|---|
fixed | 点、x?、y? | 点を固定します (いずれかの座標はオプション) | 1–2 |
coincident | a, b | 2点は1点です | 2 |
horizontal / vertical | ライン | 線が水平または垂直である | 1 |
horizontalPoints / verticalPoints | a, b | 2 点が y / x を共有する | 1 |
distance | a、b、値 | 点間距離 | 1 |
distanceX / distanceY | a、b、値 | 1 つの軸に沿った符号付きオフセット | 1 |
length | 行、値 | 線の長さ | 1 |
parallel / perpendicular | a, b | 2行の間 | 1 |
equalLength | a, b | 2行 | 1 |
equalRadius | a, b | 2つの円または円弧 | 1 |
radius / diameter | 円、値 | 円または円弧のサイズ | 1 |
angle | a、b、値 | 線aから線bまで、反時計回り、ラジアン | 1 |
pointOnLine / pointOnCircle | 点、線 |丸 | 入射 | 1 |
midpoint | 点、線 | 線の真ん中を指す | 2 |
symmetric | a、b、行 | 約一直線上のミラーペア | 2 |
tangentLineCircle | 線、円 | 線が円に接する | 1 |
tangentCircles | a、b、内部? | 外部 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 | 衝突 |
ステータスと診断
解くと、自由度 = 未知数 − ランク、冗長方程式 = 方程式 − ランクとなります。ステータスは、これら 2 つの数値と、残差が 1e-6 に達したかどうかから決まります。
| 状態 | 状態 | 何をするか |
|---|---|---|
fully-constrained | 解決済み、dof 0、冗長性なし | 何もない |
under-constrained | 解決しました。dof > 0 | iD のディメンションを追加します 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 共有ポイント ID を介して線と円弧を端から端まで連鎖させて閉じたループにし、すべての円を独自のループとして返します。円弧は、弦が内側に留まるように間隔をあけた点になります。 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 | 2 つのエンティティが ID を共有する | iD は点、線、円、円弧全体で一意です |
SketchError: Unknown unknown | 制約により、そこに存在しない点または円に名前が付けられます | 制約内の ID を確認してください |
conflicting 長い競合リストがある | 次元が 1 つ間違っていると、いくつかの方程式がゼロから導き出されます | 最初のエントリを修正します。残りは通常担保となる |
failed | スタートは解決策にはほど遠い | 意図した形状に近づけます。 maxIterations を増やす |
プロファイルがありません loops | チェーンは閉じません:それは入っています open | エンドポイントを共有IDにするか、一致制約を追加します |
| 円のポイントが少なすぎます | 小さな半径に対して公差が粗すぎます | より小さい許容値をスケッチプロファイルに渡します |