コンテンツへスキップ
CADCAMFUN
ドキュメント

ドキュメント

スケッチソルバー

Levenberg-Marquardt によって解決された、自由度、自由点、名前付き競合を含む制約付きの 2D スケッチ。

英語から自動翻訳されています。 英語の原文を見る

ソルバーが行うこと

スケッチ ソルバーは、点、線、円、円弧のセットと幾何学的拘束および寸法拘束を具体的な座標に変換します。それはに住んでいます packages/core/src/cad/sketch.ts (522 行) からエクスポートされます @cadcamfun/core として solveSketch, sketchProfiles そして polygonArea。パラメトリックヒストリーは、最初にその式を数値に解決してから、ソルバーを呼び出します。解決されたプロファイルは押し出し、回転、ポケットに供給されます。

入力出力
エンティティ: 点、線、円、円弧解決された座標と半径を持つ同じエンティティ
制約: 24 種類、オプションで名前を付けるステータス、自由度、フリーポイント、冗長方程式、競合
options: maxIterations (200), tolerance (1e-9)使用された反復と残った最大の残差
押し出された解決されたスケッチ ドラッグして周回します · スクロールしてズーム · 右ドラッグしてパンします
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.

データモデル

ポイントは未知数です。すべての線、円、円弧は ID によって点を参照するため、端を共有する 2 つの線は点を共有し、一緒に移動します。円は、その半径という未知の要素をさらに追加します。円弧は、円上の中心と 2 点であり、そこから反時計回りに start まで end;その半径は |開始 − 中心| です。そしてソルバーは独自の方程式保持を追加します end 同じサークルで。

エンティティとスケッチのタイプ (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>
}

未知のベクトルはすべての点のように配置されます。 x, y エンティティの順序で、次にすべてのサークルの r。 4 つの点からなる長方形には 8 個の未知数があります。独自の中心を持つ円を追加すると、11 が含まれます。

制約カタログ

各制約は、それが成立する場合にはゼロとなる残差方程式に寄与します。制約には、 id;何もなければそれは名前が付けられます type#index (例えば vertical#2) 紛争報告書に記載されています。

タイプフィールド意味方程式
fixed点、x?、y?点を固定します (いずれかの座標はオプション)1–2
coincidenta, b2点は1点です2
horizontal / verticalライン線が水平または垂直である1
horizontalPoints / verticalPointsa, b2 点が y / x を共有する1
distancea、b、値点間距離1
distanceX / distanceYa、b、値1 つの軸に沿った符号付きオフセット1
length行、値線の長さ1
parallel / perpendiculara, b2行の間1
equalLengtha, b2行1
equalRadiusa, b2つの円または円弧1
radius / diameter円、値円または円弧のサイズ1
anglea、b、値線aから線bまで、反時計回り、ラジアン1
pointOnLine / pointOnCircle点、線 |丸入射1
midpoint点、線線の真ん中を指す2
symmetrica、b、行約一直線上のミラーペア2
tangentLineCircle線、円線が円に接する1
tangentCirclesa、b、内部?外部 r1 + r2 または内部 |r1 − r2|1
concentrica, b中心が一致する2

解決: レーベンバーグ=マルカート

ソルバーは、レーベンバーグ・マルカート法と中心差ヤコビアンを使用して残差二乗和を最小化します。各ステップでは、コレスキー因数分解によって減衰正規方程式を解きます。残差を下げるステップは受け入れられ、減衰が低下します。そうでないステップは拒否され、減衰が増加します。すべてのステップは残差を減らす最小の変更であるため、ドラッグされた点は製図板上で行うのと同じようにスケッチの残りの部分をドラッグします。

  1. すべての制約の残差関数を構築し、どの制約が各方程式を所有しているかを記録します。
  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. 残差が以下になったら停止 tolerance (1e-9) 以降 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).
絶え間ない価値どこ
maxIterations のデフォルト200SolveOptions
許容誤差のデフォルト1e-9SolveOptions
初期減衰 λ1e-3solveSketch
制振床1e-12solveSketch
残差 ≤ の場合に解決されます1e-6状態
ランク許容範囲1e-7nullSpace
空き-不明なしきい値1e-6無料ポイント
競合しきい値 (式ごと)1e-6衝突

ステータスと診断

解くと、自由度 = 未知数 − ランク、冗長方程式 = 方程式 − ランクとなります。ステータスは、これら 2 つの数値と、残差が 1e-6 に達したかどうかから決まります。

状態状態何をするか
fully-constrained解決済み、dof 0、冗長性なし何もない
under-constrained解決しました。dof > 0iD のディメンションを追加します 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.

実際の結果、追加された制約ごとに 1 つ
// + 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 落とされる代わりに。
2 つの円弧と 2 つの直線からなるスロット ドラッグして周回します · スクロールしてズーム · 右ドラッグしてパンします
半径 30 の 8 mm スロット: 終点を共有する 2 つの円弧と 2 つの直線。sketchProfiles によって解決され、1 つの 66 点ループにチェーンされます (正確な 680.739 mm² に対する領域 681.062 mm²。多角形は内接するため、常にわずかに小さくなります)。

上記のスロットは次のように解決されました over-constrained dof 0 と 1 の冗長方程式を使用: 作成 top c1-a1 と c2-b1 が垂直で、c1、c2 が y を共有すると、水平は暗黙的に解釈されます。解決は依然として成功します。ステータスは重複にフラグを立てるだけです。

トラブルシューティング

症状原因修理
SketchError: Duplicate entity id2 つのエンティティが ID を共有するiD は点、線、円、円弧全体で一意です
SketchError: Unknown unknown制約により、そこに存在しない点または円に名前が付けられます制約内の ID を確認してください
conflicting 長い競合リストがある次元が 1 つ間違っていると、いくつかの方程式がゼロから導き出されます最初のエントリを修正します。残りは通常担保となる
failedスタートは解決策にはほど遠い意図した形状に近づけます。 maxIterations を増やす
プロファイルがありません loopsチェーンは閉じません:それは入っています openエンドポイントを共有IDにするか、一致制約を追加します
円のポイントが少なすぎます小さな半径に対して公差が粗すぎますより小さい許容値をスケッチプロファイルに渡します