@faicad/faijs 0.13.2 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/dist/api/api-namespace.d.ts.map +1 -1
  2. package/dist/api/api-namespace.js +30 -4
  3. package/dist/api/api-namespace.js.map +1 -1
  4. package/dist/api/boolean.d.ts +19 -0
  5. package/dist/api/boolean.d.ts.map +1 -1
  6. package/dist/api/boolean.js +51 -10
  7. package/dist/api/boolean.js.map +1 -1
  8. package/dist/api/chamfer.d.ts.map +1 -1
  9. package/dist/api/chamfer.js +8 -7
  10. package/dist/api/chamfer.js.map +1 -1
  11. package/dist/api/compound-geom.d.ts +45 -0
  12. package/dist/api/compound-geom.d.ts.map +1 -0
  13. package/dist/api/compound-geom.js +85 -0
  14. package/dist/api/compound-geom.js.map +1 -0
  15. package/dist/api/compound.d.ts +2 -0
  16. package/dist/api/compound.d.ts.map +1 -1
  17. package/dist/api/compound.js +2 -0
  18. package/dist/api/compound.js.map +1 -1
  19. package/dist/api/copy.d.ts +1 -0
  20. package/dist/api/copy.d.ts.map +1 -1
  21. package/dist/api/copy.js +4 -2
  22. package/dist/api/copy.js.map +1 -1
  23. package/dist/api/edge-ref.d.ts.map +1 -1
  24. package/dist/api/edge-ref.js +7 -4
  25. package/dist/api/edge-ref.js.map +1 -1
  26. package/dist/api/engrave.d.ts.map +1 -1
  27. package/dist/api/engrave.js +1 -0
  28. package/dist/api/engrave.js.map +1 -1
  29. package/dist/api/extrude.d.ts.map +1 -1
  30. package/dist/api/extrude.js +131 -3
  31. package/dist/api/extrude.js.map +1 -1
  32. package/dist/api/face-ref.d.ts.map +1 -1
  33. package/dist/api/face-ref.js +7 -4
  34. package/dist/api/face-ref.js.map +1 -1
  35. package/dist/api/fai_drill.d.ts +1 -1
  36. package/dist/api/fai_drill.d.ts.map +1 -1
  37. package/dist/api/fai_drill.js +31 -18
  38. package/dist/api/fai_drill.js.map +1 -1
  39. package/dist/api/fai_extrude.d.ts +1 -1
  40. package/dist/api/fai_extrude.d.ts.map +1 -1
  41. package/dist/api/fai_extrude.js +2 -1
  42. package/dist/api/fai_extrude.js.map +1 -1
  43. package/dist/api/fai_split.d.ts +1 -1
  44. package/dist/api/fai_split.d.ts.map +1 -1
  45. package/dist/api/fai_split.js +2 -1
  46. package/dist/api/fai_split.js.map +1 -1
  47. package/dist/api/fillet.d.ts.map +1 -1
  48. package/dist/api/fillet.js +6 -5
  49. package/dist/api/fillet.js.map +1 -1
  50. package/dist/api/generated/operations.js +16 -16
  51. package/dist/api/generated/operations.js.map +1 -1
  52. package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
  53. package/dist/api/generated/script-face-manifest.js +0 -1
  54. package/dist/api/generated/script-face-manifest.js.map +1 -1
  55. package/dist/api/generated/script-face.d.ts +1 -4
  56. package/dist/api/generated/script-face.d.ts.map +1 -1
  57. package/dist/api/generated/script-face.js +2 -3
  58. package/dist/api/generated/script-face.js.map +1 -1
  59. package/dist/api/generated/sketching.js +1 -1
  60. package/dist/api/generated/sketching.js.map +1 -1
  61. package/dist/api/generated/topology.d.ts +0 -48
  62. package/dist/api/generated/topology.d.ts.map +1 -1
  63. package/dist/api/generated/topology.js +20 -62
  64. package/dist/api/generated/topology.js.map +1 -1
  65. package/dist/api/import-brep.d.ts +36 -0
  66. package/dist/api/import-brep.d.ts.map +1 -0
  67. package/dist/api/import-brep.js +72 -0
  68. package/dist/api/import-brep.js.map +1 -0
  69. package/dist/api/import-step.d.ts +28 -0
  70. package/dist/api/import-step.d.ts.map +1 -0
  71. package/dist/api/import-step.js +57 -0
  72. package/dist/api/import-step.js.map +1 -0
  73. package/dist/api/index.d.ts +7 -2
  74. package/dist/api/index.d.ts.map +1 -1
  75. package/dist/api/index.js +19 -5
  76. package/dist/api/index.js.map +1 -1
  77. package/dist/api/internal/compat-op.d.ts.map +1 -1
  78. package/dist/api/internal/compat-op.js +2 -1
  79. package/dist/api/internal/compat-op.js.map +1 -1
  80. package/dist/api/knurl.d.ts.map +1 -1
  81. package/dist/api/knurl.js +1 -0
  82. package/dist/api/knurl.js.map +1 -1
  83. package/dist/api/load.d.ts +3 -1
  84. package/dist/api/load.d.ts.map +1 -1
  85. package/dist/api/load.js +3 -1
  86. package/dist/api/load.js.map +1 -1
  87. package/dist/api/pattern.d.ts +32 -0
  88. package/dist/api/pattern.d.ts.map +1 -0
  89. package/dist/api/pattern.js +121 -0
  90. package/dist/api/pattern.js.map +1 -0
  91. package/dist/api/place.d.ts +35 -0
  92. package/dist/api/place.d.ts.map +1 -0
  93. package/dist/api/place.js +113 -0
  94. package/dist/api/place.js.map +1 -0
  95. package/dist/api/primitives.d.ts.map +1 -1
  96. package/dist/api/primitives.js +10 -3
  97. package/dist/api/primitives.js.map +1 -1
  98. package/dist/api/screw.d.ts.map +1 -1
  99. package/dist/api/screw.js +1 -0
  100. package/dist/api/screw.js.map +1 -1
  101. package/dist/api/sdf.d.ts.map +1 -1
  102. package/dist/api/sdf.js +1 -0
  103. package/dist/api/sdf.js.map +1 -1
  104. package/dist/api/sketch.d.ts.map +1 -1
  105. package/dist/api/sketch.js +1 -0
  106. package/dist/api/sketch.js.map +1 -1
  107. package/dist/api/split.d.ts +31 -0
  108. package/dist/api/split.d.ts.map +1 -0
  109. package/dist/api/split.js +96 -0
  110. package/dist/api/split.js.map +1 -0
  111. package/dist/api/surface/arg-spec.d.ts +9 -0
  112. package/dist/api/surface/arg-spec.d.ts.map +1 -1
  113. package/dist/api/surface/arg-spec.js +49 -30
  114. package/dist/api/surface/arg-spec.js.map +1 -1
  115. package/dist/api/svgExtrude.d.ts.map +1 -1
  116. package/dist/api/svgExtrude.js +1 -0
  117. package/dist/api/svgExtrude.js.map +1 -1
  118. package/dist/api/text.d.ts.map +1 -1
  119. package/dist/api/text.js +1 -0
  120. package/dist/api/text.js.map +1 -1
  121. package/dist/api/topo-resolve.d.ts.map +1 -1
  122. package/dist/api/topo-resolve.js +65 -3
  123. package/dist/api/topo-resolve.js.map +1 -1
  124. package/dist/api/transform.d.ts +4 -0
  125. package/dist/api/transform.d.ts.map +1 -1
  126. package/dist/api/transform.js +44 -12
  127. package/dist/api/transform.js.map +1 -1
  128. package/dist/brep/brep-ops.d.ts +34 -1
  129. package/dist/brep/brep-ops.d.ts.map +1 -1
  130. package/dist/brep/brep-ops.js +62 -22
  131. package/dist/brep/brep-ops.js.map +1 -1
  132. package/dist/brep/engine/adapters/brep-mock.d.ts.map +1 -1
  133. package/dist/brep/engine/adapters/brep-mock.js +8 -0
  134. package/dist/brep/engine/adapters/brep-mock.js.map +1 -1
  135. package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
  136. package/dist/brep/engine/adapters/brepkit.js +6 -1
  137. package/dist/brep/engine/adapters/brepkit.js.map +1 -1
  138. package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
  139. package/dist/brep/engine/adapters/occt.js +24 -1
  140. package/dist/brep/engine/adapters/occt.js.map +1 -1
  141. package/dist/brep/engine/primitives.d.ts +17 -0
  142. package/dist/brep/engine/primitives.d.ts.map +1 -1
  143. package/dist/brep/engine/primitives.js.map +1 -1
  144. package/dist/brep/engine/types.d.ts +49 -6
  145. package/dist/brep/engine/types.d.ts.map +1 -1
  146. package/dist/brep/engine/types.js.map +1 -1
  147. package/dist/brep/export/step.d.ts.map +1 -1
  148. package/dist/brep/export/step.js +6 -1
  149. package/dist/brep/export/step.js.map +1 -1
  150. package/dist/brep/face-evolution.d.ts +78 -8
  151. package/dist/brep/face-evolution.d.ts.map +1 -1
  152. package/dist/brep/face-evolution.js +130 -17
  153. package/dist/brep/face-evolution.js.map +1 -1
  154. package/dist/brepkit-kernel/brepkitKernel.d.ts +1 -1
  155. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  156. package/dist/brepkit-kernel/brepkitKernel.js +27 -1
  157. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  158. package/dist/cad-runtime/admit-compat-lib.d.ts +19 -1
  159. package/dist/cad-runtime/admit-compat-lib.d.ts.map +1 -1
  160. package/dist/cad-runtime/admit-compat-lib.js +21 -1
  161. package/dist/cad-runtime/admit-compat-lib.js.map +1 -1
  162. package/dist/cad-runtime/backend-dispatch.d.ts +44 -3
  163. package/dist/cad-runtime/backend-dispatch.d.ts.map +1 -1
  164. package/dist/cad-runtime/backend-dispatch.js +46 -3
  165. package/dist/cad-runtime/backend-dispatch.js.map +1 -1
  166. package/dist/cad-runtime/browser-lib-loader.d.ts +2 -0
  167. package/dist/cad-runtime/browser-lib-loader.d.ts.map +1 -1
  168. package/dist/cad-runtime/browser-lib-loader.js +9 -1
  169. package/dist/cad-runtime/browser-lib-loader.js.map +1 -1
  170. package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
  171. package/dist/cad-runtime/direct-executor.js +14 -4
  172. package/dist/cad-runtime/direct-executor.js.map +1 -1
  173. package/dist/cad-runtime/module-registry.d.ts +3 -1
  174. package/dist/cad-runtime/module-registry.d.ts.map +1 -1
  175. package/dist/cad-runtime/module-registry.js +13 -1
  176. package/dist/cad-runtime/module-registry.js.map +1 -1
  177. package/dist/cad-runtime/ports.d.ts +7 -0
  178. package/dist/cad-runtime/ports.d.ts.map +1 -1
  179. package/dist/cad-runtime/ports.js.map +1 -1
  180. package/dist/cad-runtime/runtime-concurrent-error.d.ts +38 -0
  181. package/dist/cad-runtime/runtime-concurrent-error.d.ts.map +1 -0
  182. package/dist/cad-runtime/runtime-concurrent-error.js +43 -0
  183. package/dist/cad-runtime/runtime-concurrent-error.js.map +1 -0
  184. package/dist/cad-runtime/runtime.d.ts +54 -2
  185. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  186. package/dist/cad-runtime/runtime.js +186 -10
  187. package/dist/cad-runtime/runtime.js.map +1 -1
  188. package/dist/define-op.d.ts +22 -0
  189. package/dist/define-op.d.ts.map +1 -1
  190. package/dist/define-op.js +82 -12
  191. package/dist/define-op.js.map +1 -1
  192. package/dist/fcstd/attachment.d.ts +8 -1
  193. package/dist/fcstd/attachment.d.ts.map +1 -1
  194. package/dist/fcstd/attachment.js +7 -1
  195. package/dist/fcstd/attachment.js.map +1 -1
  196. package/dist/fcstd/build-fai-zip.d.ts.map +1 -1
  197. package/dist/fcstd/build-fai-zip.js +12 -7
  198. package/dist/fcstd/build-fai-zip.js.map +1 -1
  199. package/dist/fcstd/codegen.d.ts +10 -1
  200. package/dist/fcstd/codegen.d.ts.map +1 -1
  201. package/dist/fcstd/codegen.js +150 -48
  202. package/dist/fcstd/codegen.js.map +1 -1
  203. package/dist/fcstd/contour.d.ts.map +1 -1
  204. package/dist/fcstd/contour.js +23 -24
  205. package/dist/fcstd/contour.js.map +1 -1
  206. package/dist/fcstd/convert.d.ts +2 -18
  207. package/dist/fcstd/convert.d.ts.map +1 -1
  208. package/dist/fcstd/convert.js +35 -53
  209. package/dist/fcstd/convert.js.map +1 -1
  210. package/dist/fcstd/external-geo.d.ts +12 -1
  211. package/dist/fcstd/external-geo.d.ts.map +1 -1
  212. package/dist/fcstd/external-geo.js +15 -1
  213. package/dist/fcstd/external-geo.js.map +1 -1
  214. package/dist/fcstd/feature-translate.d.ts +21 -1
  215. package/dist/fcstd/feature-translate.d.ts.map +1 -1
  216. package/dist/fcstd/feature-translate.js +108 -6
  217. package/dist/fcstd/feature-translate.js.map +1 -1
  218. package/dist/fcstd/structural-types.d.ts +83 -0
  219. package/dist/fcstd/structural-types.d.ts.map +1 -0
  220. package/dist/fcstd/structural-types.js +116 -0
  221. package/dist/fcstd/structural-types.js.map +1 -0
  222. package/dist/index.d.ts +1 -0
  223. package/dist/index.d.ts.map +1 -1
  224. package/dist/index.js.map +1 -1
  225. package/dist/lang/determinism-scanner.d.ts +62 -0
  226. package/dist/lang/determinism-scanner.d.ts.map +1 -0
  227. package/dist/lang/determinism-scanner.js +619 -0
  228. package/dist/lang/determinism-scanner.js.map +1 -0
  229. package/dist/lang/symbol-table.generated.d.ts +7 -3
  230. package/dist/lang/symbol-table.generated.d.ts.map +1 -1
  231. package/dist/lang/symbol-table.generated.js +7 -3
  232. package/dist/lang/symbol-table.generated.js.map +1 -1
  233. package/dist/mesh/index.d.ts.map +1 -1
  234. package/dist/mesh/index.js +3 -2
  235. package/dist/mesh/index.js.map +1 -1
  236. package/dist/node-host/cli.d.ts.map +1 -1
  237. package/dist/node-host/cli.js +56 -2
  238. package/dist/node-host/cli.js.map +1 -1
  239. package/dist/runtime-state.d.ts +18 -6
  240. package/dist/runtime-state.d.ts.map +1 -1
  241. package/dist/runtime-state.js +9 -1
  242. package/dist/runtime-state.js.map +1 -1
  243. package/dist/shape.d.ts +15 -0
  244. package/dist/shape.d.ts.map +1 -1
  245. package/dist/shape.js +26 -3
  246. package/dist/shape.js.map +1 -1
  247. package/dist/topology/naming/build-naming.d.ts +14 -30
  248. package/dist/topology/naming/build-naming.d.ts.map +1 -1
  249. package/dist/topology/naming/build-naming.js +21 -54
  250. package/dist/topology/naming/build-naming.js.map +1 -1
  251. package/dist/topology/naming/capture-topo-ref.d.ts.map +1 -1
  252. package/dist/topology/naming/capture-topo-ref.js +5 -0
  253. package/dist/topology/naming/capture-topo-ref.js.map +1 -1
  254. package/dist/topology/naming/index.d.ts +3 -0
  255. package/dist/topology/naming/index.d.ts.map +1 -1
  256. package/dist/topology/naming/index.js +3 -0
  257. package/dist/topology/naming/index.js.map +1 -1
  258. package/dist/topology/naming/lineage-resolve.d.ts +80 -0
  259. package/dist/topology/naming/lineage-resolve.d.ts.map +1 -0
  260. package/dist/topology/naming/lineage-resolve.js +120 -0
  261. package/dist/topology/naming/lineage-resolve.js.map +1 -0
  262. package/dist/topology/naming/lineage.d.ts +349 -0
  263. package/dist/topology/naming/lineage.d.ts.map +1 -0
  264. package/dist/topology/naming/lineage.js +398 -0
  265. package/dist/topology/naming/lineage.js.map +1 -0
  266. package/dist/topology/naming/migrate.d.ts +41 -0
  267. package/dist/topology/naming/migrate.d.ts.map +1 -0
  268. package/dist/topology/naming/migrate.js +88 -0
  269. package/dist/topology/naming/migrate.js.map +1 -0
  270. package/dist/topology/naming/role-name.d.ts +176 -0
  271. package/dist/topology/naming/role-name.d.ts.map +1 -0
  272. package/dist/topology/naming/role-name.js +344 -0
  273. package/dist/topology/naming/role-name.js.map +1 -0
  274. package/dist/topology/naming/roles.d.ts +20 -22
  275. package/dist/topology/naming/roles.d.ts.map +1 -1
  276. package/dist/topology/naming/roles.js +51 -49
  277. package/dist/topology/naming/roles.js.map +1 -1
  278. package/dist/topology/naming/types.d.ts +65 -18
  279. package/dist/topology/naming/types.d.ts.map +1 -1
  280. package/dist/topology/naming/types.js.map +1 -1
  281. package/dist/weapp.d.ts +1 -1
  282. package/dist/weapp.d.ts.map +1 -1
  283. package/dist/weapp.js +1 -1
  284. package/dist/weapp.js.map +1 -1
  285. package/package.json +1 -1
@@ -39,39 +39,38 @@ export function extractContours(geoms) {
39
39
  if (start.used)
40
40
  continue;
41
41
  start.used = true;
42
- const segments = [start.seg];
43
- let tail = start.b;
44
42
  const head = start.a;
45
- let extended = true;
46
- while (extended) {
47
- extended = false;
48
- // GOTCHA (ballend corpus, 2026-09-20): restart the scan from the pool
49
- // start after EVERY match. Scanning on with the drifted tail let a
50
- // later-indexed segment touching the new tail steal the chain before
51
- // the correct earlier-indexed continuation was reached, breaking a
52
- // perfectly closed loop (extractContours returned 0).
43
+ // GOTCHA (slittingsaw corpus, 2026-09-20): junction points with free
44
+ // branches (three+ segments sharing an endpoint) defeat first-come
45
+ // chaining — a branch to a dead end consumed the segments and the real
46
+ // loop never closed. DFS with backtracking: try each candidate
47
+ // continuation; the FIRST chain that closes at the head wins; a dead
48
+ // branch returns its segments to the pool. Closure-stop (tap GOTCHA) and
49
+ // pool-restart are both subsumed by the DFS ordering.
50
+ const dfs = (tail, walk) => {
51
+ if (walk.length > 0 && near(tail, head))
52
+ return walk;
53
53
  for (const cand of pool) {
54
54
  if (cand.used)
55
55
  continue;
56
+ cand.used = true;
56
57
  if (near(tail, cand.a)) {
57
- cand.used = true;
58
- segments.push(cand.seg);
59
- tail = cand.b;
60
- extended = true;
61
- break;
58
+ const r = dfs(cand.b, [...walk, cand.seg]);
59
+ if (r)
60
+ return r;
62
61
  }
63
62
  else if (near(tail, cand.b)) {
64
- cand.used = true;
65
- segments.push(reverseSeg(cand.seg));
66
- tail = cand.a;
67
- extended = true;
68
- break;
63
+ const r = dfs(cand.a, [...walk, reverseSeg(cand.seg)]);
64
+ if (r)
65
+ return r;
69
66
  }
67
+ cand.used = false; // dead branch — return the segment to the pool
70
68
  }
71
- }
72
- const closed = near(tail, head);
73
- if (segments.length > 0 && closed) {
74
- contours.push({ segments, closed: true });
69
+ return undefined;
70
+ };
71
+ const solved = dfs(start.b, [start.seg]);
72
+ if (solved && solved.length > 1) {
73
+ contours.push({ segments: solved, closed: true });
75
74
  }
76
75
  }
77
76
  // circles are self-closed contours
@@ -1 +1 @@
1
- {"version":3,"file":"contour.js","sourceRoot":"","sources":["../../src/fcstd/contour.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M3.6 — contour extraction: solved sketch geometry → closed loops → 2D\n * contour (R6: pure data, no OCCT dependency; the cad-face wiring that turns\n * contours into Blueprint/extrude inputs happens in M4.6/M5).\n *\n * Extraction: collect endpoints of every non-construction geometry, chain\n * segments sharing endpoints (tolerance-based), keep closed loops.\n */\nimport type { SketchGeom } from './sketch-parse.js';\n\n/** One segment of a 2D contour: either a straight line or a circular arc (planar, sketch-local coordinates). */\nexport type ContourSeg =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number }\n | {\n kind: 'arc';\n cx: number;\n cy: number;\n radius: number;\n startAngle: number;\n endAngle: number;\n ccw: boolean;\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n };\n\n/** A chained run of segments; `closed` when the chain's ends meet (R6: pure data, no OCCT dependency). */\nexport interface Contour {\n /** chained segments in traversal order */\n segments: ContourSeg[];\n /** true when the chain's endpoints meet within JOIN_TOL */\n closed: boolean;\n}\n\nconst JOIN_TOL = 1e-7;\n\nfunction segEnds(g: SketchGeom): [ContourSeg, { x: number; y: number }, { x: number; y: number }] | undefined {\n switch (g.kind) {\n case 'line':\n return [\n { kind: 'line', x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n case 'arc':\n return [\n {\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: g.startAngle, endAngle: g.endAngle, ccw: true,\n x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2,\n },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n default:\n return undefined; // circles are closed on their own; points don't join\n }\n}\n\n/**\n * Extract closed contours (chains of segments whose endpoints meet).\n * @param geoms solved sketch geometry to chain (non-construction segments only)\n * @returns closed loops plus self-closed circles (open chains are dropped)\n */\nexport function extractContours(geoms: SketchGeom[]): Contour[] {\n const pool: { seg: ContourSeg; a: { x: number; y: number }; b: { x: number; y: number }; used: boolean }[] = [];\n for (const g of geoms) {\n const s = segEnds(g);\n if (s) pool.push({ seg: s[0], a: s[1], b: s[2], used: false });\n }\n\n const near = (p: { x: number; y: number }, q: { x: number; y: number }): boolean =>\n Math.hypot(p.x - q.x, p.y - q.y) <= JOIN_TOL;\n\n const contours: Contour[] = [];\n for (const start of pool) {\n if (start.used) continue;\n start.used = true;\n const segments: ContourSeg[] = [start.seg];\n let tail = start.b;\n const head = start.a;\n let extended = true;\n while (extended) {\n extended = false;\n // GOTCHA (ballend corpus, 2026-09-20): restart the scan from the pool\n // start after EVERY match. Scanning on with the drifted tail let a\n // later-indexed segment touching the new tail steal the chain before\n // the correct earlier-indexed continuation was reached, breaking a\n // perfectly closed loop (extractContours returned 0).\n for (const cand of pool) {\n if (cand.used) continue;\n if (near(tail, cand.a)) {\n cand.used = true;\n segments.push(cand.seg);\n tail = cand.b;\n extended = true;\n break;\n } else if (near(tail, cand.b)) {\n cand.used = true;\n segments.push(reverseSeg(cand.seg));\n tail = cand.a;\n extended = true;\n break;\n }\n }\n }\n const closed = near(tail, head);\n if (segments.length > 0 && closed) {\n contours.push({ segments, closed: true });\n }\n }\n\n // circles are self-closed contours\n for (const g of geoms) {\n if (g.kind === 'circle' && g.radius > 0) {\n contours.push({\n segments: [{\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: 0, endAngle: Math.PI * 2, ccw: true,\n x1: g.cx + g.radius, y1: g.cy, x2: g.cx + g.radius, y2: g.cy,\n }],\n closed: true,\n });\n }\n }\n return contours;\n}\n\nfunction reverseSeg(s: ContourSeg): ContourSeg {\n if (s.kind === 'line') return { kind: 'line', x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n return { ...s, startAngle: s.endAngle, endAngle: s.startAngle, ccw: !s.ccw, x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n}\n"]}
1
+ {"version":3,"file":"contour.js","sourceRoot":"","sources":["../../src/fcstd/contour.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M3.6 — contour extraction: solved sketch geometry → closed loops → 2D\n * contour (R6: pure data, no OCCT dependency; the cad-face wiring that turns\n * contours into Blueprint/extrude inputs happens in M4.6/M5).\n *\n * Extraction: collect endpoints of every non-construction geometry, chain\n * segments sharing endpoints (tolerance-based), keep closed loops.\n */\nimport type { SketchGeom } from './sketch-parse.js';\n\n/** One segment of a 2D contour: either a straight line or a circular arc (planar, sketch-local coordinates). */\nexport type ContourSeg =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number }\n | {\n kind: 'arc';\n cx: number;\n cy: number;\n radius: number;\n startAngle: number;\n endAngle: number;\n ccw: boolean;\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n };\n\n/** A chained run of segments; `closed` when the chain's ends meet (R6: pure data, no OCCT dependency). */\nexport interface Contour {\n /** chained segments in traversal order */\n segments: ContourSeg[];\n /** true when the chain's endpoints meet within JOIN_TOL */\n closed: boolean;\n}\n\nconst JOIN_TOL = 1e-7;\n\nfunction segEnds(g: SketchGeom): [ContourSeg, { x: number; y: number }, { x: number; y: number }] | undefined {\n switch (g.kind) {\n case 'line':\n return [\n { kind: 'line', x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n case 'arc':\n return [\n {\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: g.startAngle, endAngle: g.endAngle, ccw: true,\n x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2,\n },\n { x: g.x1, y: g.y1 },\n { x: g.x2, y: g.y2 },\n ];\n default:\n return undefined; // circles are closed on their own; points don't join\n }\n}\n\n/**\n * Extract closed contours (chains of segments whose endpoints meet).\n * @param geoms solved sketch geometry to chain (non-construction segments only)\n * @returns closed loops plus self-closed circles (open chains are dropped)\n */\nexport function extractContours(geoms: SketchGeom[]): Contour[] {\n const pool: { seg: ContourSeg; a: { x: number; y: number }; b: { x: number; y: number }; used: boolean }[] = [];\n for (const g of geoms) {\n const s = segEnds(g);\n if (s) pool.push({ seg: s[0], a: s[1], b: s[2], used: false });\n }\n\n const near = (p: { x: number; y: number }, q: { x: number; y: number }): boolean =>\n Math.hypot(p.x - q.x, p.y - q.y) <= JOIN_TOL;\n\n const contours: Contour[] = [];\n for (const start of pool) {\n if (start.used) continue;\n start.used = true;\n const head = start.a;\n // GOTCHA (slittingsaw corpus, 2026-09-20): junction points with free\n // branches (three+ segments sharing an endpoint) defeat first-come\n // chaining — a branch to a dead end consumed the segments and the real\n // loop never closed. DFS with backtracking: try each candidate\n // continuation; the FIRST chain that closes at the head wins; a dead\n // branch returns its segments to the pool. Closure-stop (tap GOTCHA) and\n // pool-restart are both subsumed by the DFS ordering.\n const dfs = (tail: { x: number; y: number }, walk: ContourSeg[]): ContourSeg[] | undefined => {\n if (walk.length > 0 && near(tail, head)) return walk;\n for (const cand of pool) {\n if (cand.used) continue;\n cand.used = true;\n if (near(tail, cand.a)) {\n const r = dfs(cand.b, [...walk, cand.seg]);\n if (r) return r;\n } else if (near(tail, cand.b)) {\n const r = dfs(cand.a, [...walk, reverseSeg(cand.seg)]);\n if (r) return r;\n }\n cand.used = false; // dead branch — return the segment to the pool\n }\n return undefined;\n };\n const solved = dfs(start.b, [start.seg]);\n if (solved && solved.length > 1) {\n contours.push({ segments: solved, closed: true });\n }\n }\n\n // circles are self-closed contours\n for (const g of geoms) {\n if (g.kind === 'circle' && g.radius > 0) {\n contours.push({\n segments: [{\n kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,\n startAngle: 0, endAngle: Math.PI * 2, ccw: true,\n x1: g.cx + g.radius, y1: g.cy, x2: g.cx + g.radius, y2: g.cy,\n }],\n closed: true,\n });\n }\n }\n return contours;\n}\n\nfunction reverseSeg(s: ContourSeg): ContourSeg {\n if (s.kind === 'line') return { kind: 'line', x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n return { ...s, startAngle: s.endAngle, endAngle: s.startAngle, ccw: !s.ccw, x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { STRUCTURAL_TYPES, STRUCTURAL_TYPES_EXTENDED, isFemStructural } from './structural-types.js';
2
+ export { STRUCTURAL_TYPES, STRUCTURAL_TYPES_EXTENDED, isFemStructural };
1
3
  /** V2 tolerance: solver must reproduce stored geometry (single source). */
2
4
  export declare const SKETCH_T1 = 0.000001;
3
5
  /** Dispositions allowed in a conforming container (C4). */
@@ -50,24 +52,6 @@ export interface ConvertSummary {
50
52
  error?: string;
51
53
  elapsedMs: number;
52
54
  }
53
- /**
54
- * H7 companion (2026-09-20): FEM workbench objects carry SIMULATION semantics
55
- * (analysis containers, meshes, solver settings, boundary conditions, result
56
- * pipelines) — no modeling geometry. Native classes without a Proxy property
57
- * used to fall into `type-not-whitelisted` and block whole files whose
58
- * modeling part was a single box. Like structural types they are
59
- * `preserved-only`; their regenerable field data (FemMesh / result Data
60
- * properties) is deliberately NOT carried into the container.
61
- *
62
- * Remote task on record (user decision, 2026-09-20): faijs will port
63
- * FreeCAD's FEM analysis capability in the future — separate feature; this
64
- * classification only stops simulation objects from blocking conversion.
65
- * Python subclasses (*Python with Proxy) never reach this set — H10's
66
- * python-opaque handles them.
67
- */
68
- export declare const FEM_STRUCTURAL_TYPES: Set<string>;
69
- /** True when the type is a FEM simulation object (structured non-modeling). */
70
- export declare function isFemStructural(type: string): boolean;
71
55
  /**
72
56
  * Convert one FCStd file through the full pipeline (unpack → parse → sketch
73
57
  * solving → codegen → container build) and return a structured summary.
@@ -1 +1 @@
1
- {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"AA+BA,2EAA2E;AAC3E,eAAO,MAAM,SAAS,WAAO,CAAC;AAE9B,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,aAA4D,CAAC;AAE9F;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,0GAA0G;AAC1G,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,EAAE,EAAE,OAAO,CAAC;IACZ,0DAA0D;IAC1D,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,8EAA8E;IAC9E,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAeD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,aAgB/B,CAAC;AAEH,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AA4BD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA0KpG"}
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"AA8BA,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,eAAe,EAAE,CAAC;AAExE,2EAA2E;AAC3E,eAAO,MAAM,SAAS,WAAO,CAAC;AAE9B,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,aAA4D,CAAC;AAE9F;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,0GAA0G;AAC1G,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,EAAE,EAAE,OAAO,CAAC;IACZ,0DAA0D;IAC1D,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,8EAA8E;IAC9E,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AA8BD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAiMpG"}
@@ -26,58 +26,16 @@ import { effectivePlacement } from './attachment.js';
26
26
  import { buildFaiZip } from './build-fai-zip.js';
27
27
  import { isOk } from '../api/result.js';
28
28
  import { zipSync, unzipSync, strToU8 } from 'fflate';
29
+ import { STRUCTURAL_TYPES, STRUCTURAL_TYPES_EXTENDED, isFemStructural, isNonModelingType, } from './structural-types.js';
30
+ // Kept on this module's public surface: these sets are part of the
31
+ // `./fcstd-convert` contract that the batch project and its tests consume. The
32
+ // definitions live in `structural-types.ts` so codegen can share them without
33
+ // a circular import.
34
+ export { STRUCTURAL_TYPES, STRUCTURAL_TYPES_EXTENDED, isFemStructural };
29
35
  /** V2 tolerance: solver must reproduce stored geometry (single source). */
30
36
  export const SKETCH_T1 = 1e-6;
31
37
  /** Dispositions allowed in a conforming container (C4). */
32
38
  export const ALLOWED_DISPOSITIONS = new Set(['translated', 'python-baked', 'preserved-only']);
33
- /** Non-modeling structural/datum types: never translation gaps. They carry
34
- * no feature semantics (containers, datum planes/lines/origins, groups) —
35
- * their semantics are consumed by the translator (Body.Group ordering,
36
- * datum-plane UpToFace anchors), not emitted as cad calls. */
37
- const STRUCTURAL_TYPES = new Set([
38
- 'PartDesign::Body', 'App::Origin', 'App::Plane', 'App::Line',
39
- 'App::DocumentObjectGroup', 'App::Part', 'PartDesign::Plane', 'PartDesign::Line',
40
- 'PartDesign::CoordinateSystem',
41
- // H10 companion fix (plan §3.5): datum/annotation types, not modeling
42
- // features — previously misclassified as translation gaps.
43
- 'App::Point', 'App::Annotation',
44
- ]);
45
- /**
46
- * H7 companion (2026-09-20): FEM workbench objects carry SIMULATION semantics
47
- * (analysis containers, meshes, solver settings, boundary conditions, result
48
- * pipelines) — no modeling geometry. Native classes without a Proxy property
49
- * used to fall into `type-not-whitelisted` and block whole files whose
50
- * modeling part was a single box. Like structural types they are
51
- * `preserved-only`; their regenerable field data (FemMesh / result Data
52
- * properties) is deliberately NOT carried into the container.
53
- *
54
- * Remote task on record (user decision, 2026-09-20): faijs will port
55
- * FreeCAD's FEM analysis capability in the future — separate feature; this
56
- * classification only stops simulation objects from blocking conversion.
57
- * Python subclasses (*Python with Proxy) never reach this set — H10's
58
- * python-opaque handles them.
59
- */
60
- export const FEM_STRUCTURAL_TYPES = new Set([
61
- 'Fem::FemAnalysis',
62
- 'Fem::FemMeshObject',
63
- 'Fem::FemResultObject',
64
- 'Fem::FemPostPipeline',
65
- 'Fem::ConstraintFixed',
66
- 'Fem::ConstraintForce',
67
- 'Fem::ConstraintPressure',
68
- 'Fem::ConstraintContact',
69
- 'Fem::ConstraintBearing',
70
- 'Fem::ConstraintDisplacement',
71
- 'Fem::FemAnalysisPython',
72
- 'Fem::FemMeshObjectPython',
73
- 'Fem::FemResultObjectPython',
74
- 'Fem::FemSolverObjectPython',
75
- 'Fem::FemMeshShapeBaseObjectPython',
76
- ]);
77
- /** True when the type is a FEM simulation object (structured non-modeling). */
78
- export function isFemStructural(type) {
79
- return FEM_STRUCTURAL_TYPES.has(type);
80
- }
81
39
  /**
82
40
  * C4 final check: reclassify `baked` entries. A baked disposition is only
83
41
  * legitimate for Python-opaque objects (python-baked), structural/datum
@@ -92,9 +50,11 @@ function auditMapping(mapping) {
92
50
  if (o.reason === 'python-opaque') {
93
51
  o.disposition = 'python-baked';
94
52
  }
95
- else if (STRUCTURAL_TYPES.has(o.type) || FEM_STRUCTURAL_TYPES.has(o.type)) {
53
+ else if (STRUCTURAL_TYPES.has(o.type) || isFemStructural(o.type) || STRUCTURAL_TYPES_EXTENDED.has(o.type)) {
96
54
  o.disposition = 'preserved-only';
97
- o.reason = o.reason ?? (FEM_STRUCTURAL_TYPES.has(o.type) ? 'fem-simulation' : 'structural');
55
+ o.reason = o.reason
56
+ ?? (isFemStructural(o.type) ? 'fem-simulation'
57
+ : STRUCTURAL_TYPES_EXTENDED.has(o.type) ? 'container-link' : 'structural');
98
58
  }
99
59
  else {
100
60
  gaps.push({ name: o.name, type: o.type, reason: o.reason ?? 'unspecified' });
@@ -196,15 +156,37 @@ export async function convertFcstdFile(input, opts) {
196
156
  // H7: objects whose Shape is stored as a ZIP .brp member (pure-Shape
197
157
  // carriers, e.g. Part::Feature) — collected from the archive members so the
198
158
  // translator can record `shape-asset` instead of a translation gap.
159
+ // H7 follow-up: SubShape carriers (feature result caches in Body-less
160
+ // PartDesign files) join the same set — the translator only honors SubShape
161
+ // evidence for features whose SubShape .brp member exists.
162
+ // GOTCHA (PadTest, 2026-09-21): non-modeling types are EXCLUDED. A
163
+ // `PartDesign::Plane` also has a Shape .brp — the plane face — so the
164
+ // generic rule imported it as a solid and the Body chain unioned the datum
165
+ // plane into the part (a bare face cannot take part in the boolean). A
166
+ // datum's shape is support geometry for attachment/up-to resolution.
199
167
  const shapeCarriers = new Set();
168
+ // E4: objects whose Shape/SubShape `file` attribute points at a missing or
169
+ // ZERO-BYTE member (FC_site_simple-102: `Site.Shape.brp` exists but is 0
170
+ // bytes). They must surface as an explicit convert-time gap — without this
171
+ // the object falls through to python-opaque and the broken-asset fact is
172
+ // silently swallowed into `python-baked` (the product then reports no
173
+ // geometry instead of naming the defect).
174
+ const brokenShapeAssets = new Set();
200
175
  for (const obj of doc.value.objects) {
201
- const fileAttr = obj.properties.get('Shape')?.children[0]?.attributes['file'];
202
- if (fileAttr && memberText(unpacked.value, fileAttr) !== undefined)
176
+ if (isNonModelingType(obj.type))
177
+ continue;
178
+ const shapeFile = obj.properties.get('Shape')?.children[0]?.attributes['file'];
179
+ const subShapeFile = obj.properties.get('SubShape')?.children[0]?.attributes['file'];
180
+ const shapeOk = shapeFile !== undefined && !!memberText(unpacked.value, shapeFile);
181
+ const subShapeOk = subShapeFile !== undefined && !!memberText(unpacked.value, subShapeFile);
182
+ if (shapeOk || subShapeOk)
203
183
  shapeCarriers.add(obj.name);
184
+ else if (shapeFile !== undefined || subShapeFile !== undefined)
185
+ brokenShapeAssets.add(obj.name);
204
186
  }
205
187
  let gen;
206
188
  try {
207
- gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements, shapeCarriers);
189
+ gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements, shapeCarriers, brokenShapeAssets);
208
190
  }
209
191
  catch (e) {
210
192
  return fail(`codegen failed: ${e.message}`);
@@ -1 +1 @@
1
- {"version":3,"file":"convert.js","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * B0 — THE FCStd → .fai.zip conversion pipeline (batch CLI and tests share it;\n * `src/fcstd/cli.ts` is its only CLI). Returns a structured summary; the caller\n * decides exit codes and output formatting (C4: batch driver must never absorb\n * failures).\n *\n * 2026-09-20: the M5.4-era dev script `scripts/fcstd-to-fai-zip.ts` was a second\n * implementation of this same pipeline (no C4 audit, no exit-code contract) and\n * has been deleted. Whether the conversion entry is published or not, this file\n * is the single implementation.\n *\n * Dispositions (plan §2, C4): translated | python-baked | preserved-only.\n * Any other baked reason = translation gap → result.ok = false, no zip\n * written (final check `auditMapping`).\n */\nimport { readFileSync } from 'node:fs';\nimport { unpackFcstd, memberText } from './unpack.js';\nimport { parseDocumentXml } from './document.js';\nimport { parseSketchObject } from './sketch-parse.js';\nimport { createPlanegcsSolver } from './planegcs-backend.js';\nimport { classifySketch } from './sketch-verify.js';\nimport { resolveExternalGeometry } from './external-geo.js';\nimport { extractContours } from './contour.js';\nimport type { Contour } from './contour.js';\nimport { generateModel } from './codegen.js';\nimport { placementOf, type Placement } from './placement.js';\nimport { effectivePlacement } from './attachment.js';\nimport { buildFaiZip } from './build-fai-zip.js';\nimport { isOk } from '../api/result.js';\nimport { zipSync, unzipSync, strToU8 } from 'fflate';\n\n/** V2 tolerance: solver must reproduce stored geometry (single source). */\nexport const SKETCH_T1 = 1e-6;\n\n/** Dispositions allowed in a conforming container (C4). */\nexport const ALLOWED_DISPOSITIONS = new Set(['translated', 'python-baked', 'preserved-only']);\n\n/**\n * Optional knobs for `convertFcstdFile`.\n */\nexport interface ConvertOptions {\n /**\n * Return the container bytes even when the mapping audit reports gaps.\n *\n * Diagnostics only. C4 requires that a gapped document produces **no\n * product**, and the CLI never sets this — `ok` stays false and the exit\n * code stays 2 regardless. It exists because everything the pipeline built\n * before the audit is otherwise unobservable for exactly the documents that\n * need triage: gaps and non-L0 sketches usually co-occur (a sketch the\n * solver cannot handle usually starves the features built on it), so\n * \"which contour assets / mapping entries would this document have had\"\n * cannot be answered from `gaps[]` alone.\n */\n keepGappedContainer?: boolean;\n}\n\n/** Structured result of one FCStd → .fai.zip conversion; the caller decides exit codes and formatting. */\nexport interface ConvertSummary {\n /** input file path */\n file: string;\n /** conversion succeeded AND mapping final check passed */\n ok: boolean;\n /** translation gaps (non-Python baked) — empty when ok */\n gaps: { name: string; type: string; reason: string }[];\n counts: { translated: number; pythonBaked: number; preservedOnly: number; baked: number };\n sketches: { total: number; l0: number; l1: number; l2: number };\n /** container bytes (undefined when ok=false, unless `keepGappedContainer`) */\n zip?: Uint8Array;\n /** human-readable failure when the pipeline itself failed */\n error?: string;\n elapsedMs: number;\n}\n\n/** Non-modeling structural/datum types: never translation gaps. They carry\n * no feature semantics (containers, datum planes/lines/origins, groups) —\n * their semantics are consumed by the translator (Body.Group ordering,\n * datum-plane UpToFace anchors), not emitted as cad calls. */\nconst STRUCTURAL_TYPES = new Set([\n 'PartDesign::Body', 'App::Origin', 'App::Plane', 'App::Line',\n 'App::DocumentObjectGroup', 'App::Part', 'PartDesign::Plane', 'PartDesign::Line',\n 'PartDesign::CoordinateSystem',\n // H10 companion fix (plan §3.5): datum/annotation types, not modeling\n // features — previously misclassified as translation gaps.\n 'App::Point', 'App::Annotation',\n]);\n\n/**\n * H7 companion (2026-09-20): FEM workbench objects carry SIMULATION semantics\n * (analysis containers, meshes, solver settings, boundary conditions, result\n * pipelines) — no modeling geometry. Native classes without a Proxy property\n * used to fall into `type-not-whitelisted` and block whole files whose\n * modeling part was a single box. Like structural types they are\n * `preserved-only`; their regenerable field data (FemMesh / result Data\n * properties) is deliberately NOT carried into the container.\n *\n * Remote task on record (user decision, 2026-09-20): faijs will port\n * FreeCAD's FEM analysis capability in the future — separate feature; this\n * classification only stops simulation objects from blocking conversion.\n * Python subclasses (*Python with Proxy) never reach this set — H10's\n * python-opaque handles them.\n */\nexport const FEM_STRUCTURAL_TYPES = new Set([\n 'Fem::FemAnalysis',\n 'Fem::FemMeshObject',\n 'Fem::FemResultObject',\n 'Fem::FemPostPipeline',\n 'Fem::ConstraintFixed',\n 'Fem::ConstraintForce',\n 'Fem::ConstraintPressure',\n 'Fem::ConstraintContact',\n 'Fem::ConstraintBearing',\n 'Fem::ConstraintDisplacement',\n 'Fem::FemAnalysisPython',\n 'Fem::FemMeshObjectPython',\n 'Fem::FemResultObjectPython',\n 'Fem::FemSolverObjectPython',\n 'Fem::FemMeshShapeBaseObjectPython',\n]);\n\n/** True when the type is a FEM simulation object (structured non-modeling). */\nexport function isFemStructural(type: string): boolean {\n return FEM_STRUCTURAL_TYPES.has(type);\n}\n\n/**\n * C4 final check: reclassify `baked` entries. A baked disposition is only\n * legitimate for Python-opaque objects (python-baked), structural/datum\n * containers (preserved-only), or preserved display members. Everything\n * else is a translation gap.\n */\nfunction auditMapping(\n mapping: { objects: { name: string; type: string; disposition: string; reason?: string }[] },\n): ConvertSummary['gaps'] {\n const gaps: ConvertSummary['gaps'] = [];\n for (const o of mapping.objects) {\n if (o.disposition === 'baked') {\n // python-opaque stays legitimate but is renamed for the ledger\n if (o.reason === 'python-opaque') {\n o.disposition = 'python-baked';\n } else if (STRUCTURAL_TYPES.has(o.type) || FEM_STRUCTURAL_TYPES.has(o.type)) {\n o.disposition = 'preserved-only';\n o.reason = o.reason ?? (FEM_STRUCTURAL_TYPES.has(o.type) ? 'fem-simulation' : 'structural');\n } else {\n gaps.push({ name: o.name, type: o.type, reason: o.reason ?? 'unspecified' });\n }\n }\n }\n return gaps;\n}\n\n/**\n * Convert one FCStd file through the full pipeline (unpack → parse → sketch\n * solving → codegen → container build) and return a structured summary.\n * @param input path to the .FCStd file to convert\n * @param opts optional knobs (see {@link ConvertOptions}); defaults keep the C4 contract\n * @returns a summary with ok=false (and no zip, unless `keepGappedContainer`) on any pipeline failure or translation gap\n */\nexport async function convertFcstdFile(input: string, opts?: ConvertOptions): Promise<ConvertSummary> {\n const t0 = Date.now();\n const baseName = input.replace(/^.*[/\\\\]/, '').replace(/\\.fcstd$/i, '');\n const fail = (error: string): ConvertSummary => ({\n file: input, ok: false, gaps: [],\n counts: { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 },\n sketches: { total: 0, l0: 0, l1: 0, l2: 0 }, error, elapsedMs: Date.now() - t0,\n });\n\n let raw: Uint8Array;\n try {\n raw = new Uint8Array(readFileSync(input));\n } catch (e) {\n return fail(`read failed: ${(e as Error).message}`);\n }\n const unpacked = unpackFcstd(raw);\n if (!isOk(unpacked)) return fail(`unpack failed: ${JSON.stringify(unpacked.error)}`);\n const xml = memberText(unpacked.value, 'Document.xml');\n if (xml === undefined) return fail('Document.xml missing');\n const doc = parseDocumentXml(xml);\n if (!isOk(doc)) return fail(`parse failed: ${doc.error.message}`);\n\n // M3: solve every sketch (same pipeline as the dev script)\n const solver = await createPlanegcsSolver();\n const sketchVerdict = new Map<string, { level: 'L0' | 'L1' | 'L2'; reason?: string; loopCount?: number }>();\n const sketchContours = new Map<string, Contour[]>();\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const offPlane = sk.geoms.some((g) => {\n const zs = g.kind === 'point' ? [g.z]\n : g.kind === 'line' ? [g.z1, g.z2]\n : [g.cz];\n return zs.some((z) => Math.abs(z) > 1e-9);\n });\n const preBlocked =\n offPlane ? 'sketch-geometry-off-plane'\n : sk.geoms.some((g) => !Number.isFinite((g as { x?: number }).x ?? 0)) ? 'unsupported-geometry'\n : undefined;\n if (preBlocked) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: preBlocked });\n continue;\n }\n let external: { geoId: number; polyline: [number, number][] }[] | undefined;\n if (sk.externalGeoIds.length > 0) {\n const ext = await resolveExternalGeometry(\n obj.properties.get('ExternalGeometry'), doc.value, unpacked.value, obj.properties.get('Placement'),\n );\n const usable = ext.links.filter((l) => l.polyline.length === 2);\n if (usable.length === 0) {\n sketchVerdict.set(obj.name, {\n level: 'L2',\n reason: `external-geometry-unresolved: ${ext.failures[0]?.reason ?? 'no links'}`,\n });\n continue;\n }\n external = usable.map((l, i) => ({ geoId: -3 - i, polyline: l.polyline }));\n }\n try {\n const r = await solver.solve(sk.geoms, sk.constraints, external);\n if (!isOk(r)) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: 'solver-error' });\n continue;\n }\n const verdict = classifySketch(r.value, sk.geoms, SKETCH_T1);\n const contours = verdict.level === 'L0' ? extractContours(r.value.geoms) : [];\n sketchVerdict.set(obj.name, { ...verdict, loopCount: contours.length });\n if (verdict.level === 'L0') sketchContours.set(obj.name, contours);\n } catch (e) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: `solver-throw: ${(e as Error).message.slice(0, 60)}` });\n }\n }\n\n // M4/M5: translate + codegen\n // H3: attachment-resolved placements — an attached sketch's stored Placement\n // is recomputed by FreeCAD from Support ∘ AttachmentOffset; resolve the chain\n // so non-XY-plane sketches land on their support frame (fall back to the\n // stored value when the attachment is deactivated or unresolvable).\n const placements = new Map<string, Placement>();\n for (const obj of doc.value.objects) {\n placements.set(obj.name, effectivePlacement(obj, placements));\n }\n // H7: objects whose Shape is stored as a ZIP .brp member (pure-Shape\n // carriers, e.g. Part::Feature) — collected from the archive members so the\n // translator can record `shape-asset` instead of a translation gap.\n const shapeCarriers = new Set<string>();\n for (const obj of doc.value.objects) {\n const fileAttr = obj.properties.get('Shape')?.children[0]?.attributes['file'];\n if (fileAttr && memberText(unpacked.value, fileAttr) !== undefined) shapeCarriers.add(obj.name);\n }\n\n let gen;\n try {\n gen = generateModel(doc.value, sketchVerdict, sketchContours, baseName, placements, shapeCarriers);\n } catch (e) {\n return fail(`codegen failed: ${(e as Error).message}`);\n }\n\n // M2: container with shadow, then inject model/ + updated mapping\n const built = buildFaiZip(unpacked.value, baseName + '.FCStd');\n if (built.error || !built.result) return fail(`container build failed: ${built.error ?? 'unknown'}`);\n const members: Record<string, Uint8Array> = {};\n for (const [k, v] of Object.entries(unzipSync(built.result.zip))) members[k] = v;\n members['model/main.fai.js'] = strToU8(gen.code);\n for (const f of gen.files) members[f.path] = strToU8(f.code);\n\n const mapping = built.result.mapping;\n for (const o of gen.objects) {\n const entry = mapping.objects.find((e) => e.name === o.name);\n if (!entry) continue;\n entry.disposition = o.disposition;\n if (o.reason) entry.reason = o.reason;\n if (o.sketch) {\n entry.sketch = {\n level: o.sketch.level === 'L0' ? 'solved' : o.sketch.level === 'L1' ? 'initial-value' : 'baked',\n reason: o.sketch.reason,\n gcs: o.sketch,\n };\n }\n if (o.disposition === 'translated' && !entry.artifacts.includes('model/main.fai.js')) {\n entry.artifacts.push('model/main.fai.js');\n }\n }\n\n // M11.4 (G9): L1/L2 sketches persist raw geometry as a contour asset\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const verdict = sketchVerdict.get(obj.name);\n if (!verdict || verdict.level === 'L0') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const asset = {\n sketch: obj.name,\n level: verdict.level,\n reason: verdict.reason,\n geoms: sk.geoms,\n constraints: sk.constraints.map((c) => ({ index: c.index, type: c.type, refs: c.refs, value: c.value, isDriving: c.isDriving })),\n };\n const path = `assets/${obj.name}.contour.json`;\n members[path] = strToU8(JSON.stringify(asset, null, 2));\n const entry = mapping.objects.find((e) => e.name === obj.name);\n if (entry && !entry.artifacts.includes(path)) entry.artifacts.push(path);\n }\n\n // C4 final check: reclassify python-opaque; everything else baked = gap\n const gaps = auditMapping(mapping);\n members['mapping.json'] = strToU8(JSON.stringify(mapping, null, 2));\n\n const counts = { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 };\n for (const o of mapping.objects) {\n const d = o.disposition as string; // auditMapping may rename baked -> python-baked\n if (d === 'translated') counts.translated++;\n else if (d === 'python-baked') counts.pythonBaked++;\n else if (d === 'preserved-only') counts.preservedOnly++;\n else counts.baked++;\n }\n const sketches = { total: sketchVerdict.size, l0: 0, l1: 0, l2: 0 };\n for (const v of sketchVerdict.values()) sketches[v.level.toLowerCase() as 'l0' | 'l1' | 'l2']++;\n\n if (gaps.length > 0) {\n // C4: translation gaps → no container produced (exit contract: 2).\n // `keepGappedContainer` is a diagnostics-only escape hatch (see ConvertOptions).\n if (!opts?.keepGappedContainer) {\n return { file: input, ok: false, gaps, counts, sketches, elapsedMs: Date.now() - t0 };\n }\n const gappedZip = zipSync(members, { level: 6 });\n return { file: input, ok: false, gaps, counts, sketches, zip: gappedZip, elapsedMs: Date.now() - t0 };\n }\n\n const zip = zipSync(members, { level: 6 });\n return { file: input, ok: true, gaps, counts, sketches, zip, elapsedMs: Date.now() - t0 };\n}\n"]}
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../../src/fcstd/convert.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * B0 — THE FCStd → .fai.zip conversion pipeline (batch CLI and tests share it;\n * `src/fcstd/cli.ts` is its only CLI). Returns a structured summary; the caller\n * decides exit codes and output formatting (C4: batch driver must never absorb\n * failures).\n *\n * 2026-09-20: the M5.4-era dev script `scripts/fcstd-to-fai-zip.ts` was a second\n * implementation of this same pipeline (no C4 audit, no exit-code contract) and\n * has been deleted. Whether the conversion entry is published or not, this file\n * is the single implementation.\n *\n * Dispositions (plan §2, C4): translated | python-baked | preserved-only.\n * Any other baked reason = translation gap → result.ok = false, no zip\n * written (final check `auditMapping`).\n */\nimport { readFileSync } from 'node:fs';\nimport { unpackFcstd, memberText } from './unpack.js';\nimport { parseDocumentXml } from './document.js';\nimport { parseSketchObject } from './sketch-parse.js';\nimport { createPlanegcsSolver } from './planegcs-backend.js';\nimport { classifySketch } from './sketch-verify.js';\nimport { resolveExternalGeometry } from './external-geo.js';\nimport { extractContours } from './contour.js';\nimport type { Contour } from './contour.js';\nimport { generateModel } from './codegen.js';\nimport type { Placement } from './placement.js';\nimport { effectivePlacement } from './attachment.js';\nimport { buildFaiZip } from './build-fai-zip.js';\nimport { isOk } from '../api/result.js';\nimport { zipSync, unzipSync, strToU8 } from 'fflate';\nimport {\n STRUCTURAL_TYPES,\n STRUCTURAL_TYPES_EXTENDED,\n isFemStructural,\n isNonModelingType,\n} from './structural-types.js';\n\n// Kept on this module's public surface: these sets are part of the\n// `./fcstd-convert` contract that the batch project and its tests consume. The\n// definitions live in `structural-types.ts` so codegen can share them without\n// a circular import.\nexport { STRUCTURAL_TYPES, STRUCTURAL_TYPES_EXTENDED, isFemStructural };\n\n/** V2 tolerance: solver must reproduce stored geometry (single source). */\nexport const SKETCH_T1 = 1e-6;\n\n/** Dispositions allowed in a conforming container (C4). */\nexport const ALLOWED_DISPOSITIONS = new Set(['translated', 'python-baked', 'preserved-only']);\n\n/**\n * Optional knobs for `convertFcstdFile`.\n */\nexport interface ConvertOptions {\n /**\n * Return the container bytes even when the mapping audit reports gaps.\n *\n * Diagnostics only. C4 requires that a gapped document produces **no\n * product**, and the CLI never sets this — `ok` stays false and the exit\n * code stays 2 regardless. It exists because everything the pipeline built\n * before the audit is otherwise unobservable for exactly the documents that\n * need triage: gaps and non-L0 sketches usually co-occur (a sketch the\n * solver cannot handle usually starves the features built on it), so\n * \"which contour assets / mapping entries would this document have had\"\n * cannot be answered from `gaps[]` alone.\n */\n keepGappedContainer?: boolean;\n}\n\n/** Structured result of one FCStd → .fai.zip conversion; the caller decides exit codes and formatting. */\nexport interface ConvertSummary {\n /** input file path */\n file: string;\n /** conversion succeeded AND mapping final check passed */\n ok: boolean;\n /** translation gaps (non-Python baked) — empty when ok */\n gaps: { name: string; type: string; reason: string }[];\n counts: { translated: number; pythonBaked: number; preservedOnly: number; baked: number };\n sketches: { total: number; l0: number; l1: number; l2: number };\n /** container bytes (undefined when ok=false, unless `keepGappedContainer`) */\n zip?: Uint8Array;\n /** human-readable failure when the pipeline itself failed */\n error?: string;\n elapsedMs: number;\n}\n\n/**\n * C4 final check: reclassify `baked` entries. A baked disposition is only\n * legitimate for Python-opaque objects (python-baked), structural/datum\n * containers (preserved-only), or preserved display members. Everything\n * else is a translation gap.\n */\nfunction auditMapping(\n mapping: { objects: { name: string; type: string; disposition: string; reason?: string }[] },\n): ConvertSummary['gaps'] {\n const gaps: ConvertSummary['gaps'] = [];\n for (const o of mapping.objects) {\n if (o.disposition === 'baked') {\n // python-opaque stays legitimate but is renamed for the ledger\n if (o.reason === 'python-opaque') {\n o.disposition = 'python-baked';\n } else if (STRUCTURAL_TYPES.has(o.type) || isFemStructural(o.type) || STRUCTURAL_TYPES_EXTENDED.has(o.type)) {\n o.disposition = 'preserved-only';\n o.reason = o.reason\n ?? (isFemStructural(o.type) ? 'fem-simulation'\n : STRUCTURAL_TYPES_EXTENDED.has(o.type) ? 'container-link' : 'structural');\n } else {\n gaps.push({ name: o.name, type: o.type, reason: o.reason ?? 'unspecified' });\n }\n }\n }\n return gaps;\n}\n\n/**\n * Convert one FCStd file through the full pipeline (unpack → parse → sketch\n * solving → codegen → container build) and return a structured summary.\n * @param input path to the .FCStd file to convert\n * @param opts optional knobs (see {@link ConvertOptions}); defaults keep the C4 contract\n * @returns a summary with ok=false (and no zip, unless `keepGappedContainer`) on any pipeline failure or translation gap\n */\nexport async function convertFcstdFile(input: string, opts?: ConvertOptions): Promise<ConvertSummary> {\n const t0 = Date.now();\n const baseName = input.replace(/^.*[/\\\\]/, '').replace(/\\.fcstd$/i, '');\n const fail = (error: string): ConvertSummary => ({\n file: input, ok: false, gaps: [],\n counts: { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 },\n sketches: { total: 0, l0: 0, l1: 0, l2: 0 }, error, elapsedMs: Date.now() - t0,\n });\n\n let raw: Uint8Array;\n try {\n raw = new Uint8Array(readFileSync(input));\n } catch (e) {\n return fail(`read failed: ${(e as Error).message}`);\n }\n const unpacked = unpackFcstd(raw);\n if (!isOk(unpacked)) return fail(`unpack failed: ${JSON.stringify(unpacked.error)}`);\n const xml = memberText(unpacked.value, 'Document.xml');\n if (xml === undefined) return fail('Document.xml missing');\n const doc = parseDocumentXml(xml);\n if (!isOk(doc)) return fail(`parse failed: ${doc.error.message}`);\n\n // M3: solve every sketch (same pipeline as the dev script)\n const solver = await createPlanegcsSolver();\n const sketchVerdict = new Map<string, { level: 'L0' | 'L1' | 'L2'; reason?: string; loopCount?: number }>();\n const sketchContours = new Map<string, Contour[]>();\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const offPlane = sk.geoms.some((g) => {\n const zs = g.kind === 'point' ? [g.z]\n : g.kind === 'line' ? [g.z1, g.z2]\n : [g.cz];\n return zs.some((z) => Math.abs(z) > 1e-9);\n });\n const preBlocked =\n offPlane ? 'sketch-geometry-off-plane'\n : sk.geoms.some((g) => !Number.isFinite((g as { x?: number }).x ?? 0)) ? 'unsupported-geometry'\n : undefined;\n if (preBlocked) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: preBlocked });\n continue;\n }\n let external: { geoId: number; polyline: [number, number][] }[] | undefined;\n if (sk.externalGeoIds.length > 0) {\n const ext = await resolveExternalGeometry(\n obj.properties.get('ExternalGeometry'), doc.value, unpacked.value, obj.properties.get('Placement'),\n );\n const usable = ext.links.filter((l) => l.polyline.length === 2);\n if (usable.length === 0) {\n sketchVerdict.set(obj.name, {\n level: 'L2',\n reason: `external-geometry-unresolved: ${ext.failures[0]?.reason ?? 'no links'}`,\n });\n continue;\n }\n external = usable.map((l, i) => ({ geoId: -3 - i, polyline: l.polyline }));\n }\n try {\n const r = await solver.solve(sk.geoms, sk.constraints, external);\n if (!isOk(r)) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: 'solver-error' });\n continue;\n }\n const verdict = classifySketch(r.value, sk.geoms, SKETCH_T1);\n const contours = verdict.level === 'L0' ? extractContours(r.value.geoms) : [];\n sketchVerdict.set(obj.name, { ...verdict, loopCount: contours.length });\n if (verdict.level === 'L0') sketchContours.set(obj.name, contours);\n } catch (e) {\n sketchVerdict.set(obj.name, { level: 'L2', reason: `solver-throw: ${(e as Error).message.slice(0, 60)}` });\n }\n }\n\n // M4/M5: translate + codegen\n // H3: attachment-resolved placements — an attached sketch's stored Placement\n // is recomputed by FreeCAD from Support ∘ AttachmentOffset; resolve the chain\n // so non-XY-plane sketches land on their support frame (fall back to the\n // stored value when the attachment is deactivated or unresolvable).\n const placements = new Map<string, Placement>();\n for (const obj of doc.value.objects) {\n placements.set(obj.name, effectivePlacement(obj, placements));\n }\n // H7: objects whose Shape is stored as a ZIP .brp member (pure-Shape\n // carriers, e.g. Part::Feature) — collected from the archive members so the\n // translator can record `shape-asset` instead of a translation gap.\n // H7 follow-up: SubShape carriers (feature result caches in Body-less\n // PartDesign files) join the same set — the translator only honors SubShape\n // evidence for features whose SubShape .brp member exists.\n // GOTCHA (PadTest, 2026-09-21): non-modeling types are EXCLUDED. A\n // `PartDesign::Plane` also has a Shape .brp — the plane face — so the\n // generic rule imported it as a solid and the Body chain unioned the datum\n // plane into the part (a bare face cannot take part in the boolean). A\n // datum's shape is support geometry for attachment/up-to resolution.\n const shapeCarriers = new Set<string>();\n // E4: objects whose Shape/SubShape `file` attribute points at a missing or\n // ZERO-BYTE member (FC_site_simple-102: `Site.Shape.brp` exists but is 0\n // bytes). They must surface as an explicit convert-time gap — without this\n // the object falls through to python-opaque and the broken-asset fact is\n // silently swallowed into `python-baked` (the product then reports no\n // geometry instead of naming the defect).\n const brokenShapeAssets = new Set<string>();\n for (const obj of doc.value.objects) {\n if (isNonModelingType(obj.type)) continue;\n const shapeFile = obj.properties.get('Shape')?.children[0]?.attributes['file'];\n const subShapeFile = obj.properties.get('SubShape')?.children[0]?.attributes['file'];\n const shapeOk = shapeFile !== undefined && !!memberText(unpacked.value, shapeFile);\n const subShapeOk = subShapeFile !== undefined && !!memberText(unpacked.value, subShapeFile);\n if (shapeOk || subShapeOk) shapeCarriers.add(obj.name);\n else if (shapeFile !== undefined || subShapeFile !== undefined) brokenShapeAssets.add(obj.name);\n }\n\n let gen;\n try {\n gen = generateModel(\n doc.value, sketchVerdict, sketchContours, baseName, placements, shapeCarriers,\n brokenShapeAssets,\n );\n } catch (e) {\n return fail(`codegen failed: ${(e as Error).message}`);\n }\n\n // M2: container with shadow, then inject model/ + updated mapping\n const built = buildFaiZip(unpacked.value, baseName + '.FCStd');\n if (built.error || !built.result) return fail(`container build failed: ${built.error ?? 'unknown'}`);\n const members: Record<string, Uint8Array> = {};\n for (const [k, v] of Object.entries(unzipSync(built.result.zip))) members[k] = v;\n members['model/main.fai.js'] = strToU8(gen.code);\n for (const f of gen.files) members[f.path] = strToU8(f.code);\n\n const mapping = built.result.mapping;\n for (const o of gen.objects) {\n const entry = mapping.objects.find((e) => e.name === o.name);\n if (!entry) continue;\n entry.disposition = o.disposition;\n if (o.reason) entry.reason = o.reason;\n if (o.sketch) {\n entry.sketch = {\n level: o.sketch.level === 'L0' ? 'solved' : o.sketch.level === 'L1' ? 'initial-value' : 'baked',\n reason: o.sketch.reason,\n gcs: o.sketch,\n };\n }\n if (o.disposition === 'translated' && !entry.artifacts.includes('model/main.fai.js')) {\n entry.artifacts.push('model/main.fai.js');\n }\n }\n\n // M11.4 (G9): L1/L2 sketches persist raw geometry as a contour asset\n for (const obj of doc.value.objects) {\n if (obj.type !== 'Sketcher::SketchObject') continue;\n const verdict = sketchVerdict.get(obj.name);\n if (!verdict || verdict.level === 'L0') continue;\n const sk = parseSketchObject(obj.properties.get('Geometry'), obj.properties.get('Constraints'), false);\n const asset = {\n sketch: obj.name,\n level: verdict.level,\n reason: verdict.reason,\n geoms: sk.geoms,\n constraints: sk.constraints.map((c) => ({ index: c.index, type: c.type, refs: c.refs, value: c.value, isDriving: c.isDriving })),\n };\n const path = `assets/${obj.name}.contour.json`;\n members[path] = strToU8(JSON.stringify(asset, null, 2));\n const entry = mapping.objects.find((e) => e.name === obj.name);\n if (entry && !entry.artifacts.includes(path)) entry.artifacts.push(path);\n }\n\n // C4 final check: reclassify python-opaque; everything else baked = gap\n const gaps = auditMapping(mapping);\n members['mapping.json'] = strToU8(JSON.stringify(mapping, null, 2));\n\n const counts = { translated: 0, pythonBaked: 0, preservedOnly: 0, baked: 0 };\n for (const o of mapping.objects) {\n const d = o.disposition as string; // auditMapping may rename baked -> python-baked\n if (d === 'translated') counts.translated++;\n else if (d === 'python-baked') counts.pythonBaked++;\n else if (d === 'preserved-only') counts.preservedOnly++;\n else counts.baked++;\n }\n const sketches = { total: sketchVerdict.size, l0: 0, l1: 0, l2: 0 };\n for (const v of sketchVerdict.values()) sketches[v.level.toLowerCase() as 'l0' | 'l1' | 'l2']++;\n\n if (gaps.length > 0) {\n // C4: translation gaps → no container produced (exit contract: 2).\n // `keepGappedContainer` is a diagnostics-only escape hatch (see ConvertOptions).\n if (!opts?.keepGappedContainer) {\n return { file: input, ok: false, gaps, counts, sketches, elapsedMs: Date.now() - t0 };\n }\n const gappedZip = zipSync(members, { level: 6 });\n return { file: input, ok: false, gaps, counts, sketches, zip: gappedZip, elapsedMs: Date.now() - t0 };\n }\n\n const zip = zipSync(members, { level: 6 });\n return { file: input, ok: true, gaps, counts, sketches, zip, elapsedMs: Date.now() - t0 };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type FcstdArchive } from './unpack.js';
2
- import type { FcstdDocument, FcstdProperty } from './document.js';
2
+ import type { FcstdDocument, FcstdObject, FcstdProperty } from './document.js';
3
3
  /**
4
4
  * One resolved external-geometry link of a sketch.
5
5
  */
@@ -23,6 +23,17 @@ export interface ExternalGeoResult {
23
23
  reason: string;
24
24
  }[];
25
25
  }
26
+ /**
27
+ * The `.brp` member holding an object's shape: the `Shape` property when
28
+ * present, else `SubShape` (GOTCHA, hole_puzzle corpus 2026-09-20: PartDesign
29
+ * features like Chamfer002/Pocket002 store their result cache in SubShape and
30
+ * have NO Shape property — external links pointing at them failed with
31
+ * "source shape not loadable" before the fallback existed).
32
+ *
33
+ * @param obj - the FCStd object to inspect.
34
+ * @returns the .brp member file name, or undefined when neither Shape nor SubShape carries one.
35
+ */
36
+ export declare function shapeBrpFile(obj: FcstdObject): string | undefined;
26
37
  /**
27
38
  * Resolve every external link of a sketch to sketch-local 2D polylines.
28
39
  *
@@ -1 +1 @@
1
- {"version":3,"file":"external-geo.d.ts","sourceRoot":"","sources":["../../src/fcstd/external-geo.ts"],"names":[],"mappings":"AAWA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAe,aAAa,EAAE,MAAM,eAAe,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1D;AAiCD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,eAAe,EAAE,aAAa,GAAG,SAAS,EAC1C,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,aAAa,GAAG,SAAS,GACzC,OAAO,CAAC,iBAAiB,CAAC,CA4E5B"}
1
+ {"version":3,"file":"external-geo.d.ts","sourceRoot":"","sources":["../../src/fcstd/external-geo.ts"],"names":[],"mappings":"AAWA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1D;AAiCD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAGjE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,eAAe,EAAE,aAAa,GAAG,SAAS,EAC1C,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,aAAa,GAAG,SAAS,GACzC,OAAO,CAAC,iBAAiB,CAAC,CA4E5B"}
@@ -39,6 +39,20 @@ function placementOf(obj) {
39
39
  q: [Number(a['Q0'] ?? 0), Number(a['Q1'] ?? 0), Number(a['Q2'] ?? 0), Number(a['Q3'] ?? 1)],
40
40
  };
41
41
  }
42
+ /**
43
+ * The `.brp` member holding an object's shape: the `Shape` property when
44
+ * present, else `SubShape` (GOTCHA, hole_puzzle corpus 2026-09-20: PartDesign
45
+ * features like Chamfer002/Pocket002 store their result cache in SubShape and
46
+ * have NO Shape property — external links pointing at them failed with
47
+ * "source shape not loadable" before the fallback existed).
48
+ *
49
+ * @param obj - the FCStd object to inspect.
50
+ * @returns the .brp member file name, or undefined when neither Shape nor SubShape carries one.
51
+ */
52
+ export function shapeBrpFile(obj) {
53
+ return obj.properties.get('Shape')?.children[0]?.attributes['file']
54
+ ?? obj.properties.get('SubShape')?.children[0]?.attributes['file'];
55
+ }
42
56
  /**
43
57
  * Resolve every external link of a sketch to sketch-local 2D polylines.
44
58
  *
@@ -64,7 +78,7 @@ export async function resolveExternalGeometry(externalGeoProp, doc, archive, ske
64
78
  if (brpCache.has(objName))
65
79
  return brpCache.get(objName);
66
80
  const src = doc.objects.find((o) => o.name === objName);
67
- const brpFile = src?.properties.get('Shape')?.children[0]?.attributes['file'];
81
+ const brpFile = src ? shapeBrpFile(src) : undefined;
68
82
  if (!brpFile) {
69
83
  brpCache.set(objName, undefined);
70
84
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"external-geo.js","sourceRoot":"","sources":["../../src/fcstd/external-geo.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M6.3 — external geometry resolution: decode ExternalGeometry links\n * (App::PropertyLinkSubList), load the source object's .brp via OCCT,\n * extract the named edge polyline (TopExp::MapShapes + IndexedMap order —\n * Edge13 = wireframe edgeGroups[12], verified against hole_puzzle.fcstd),\n * and project it into sketch-local 2D via the sketch Placement inverse.\n *\n * Result: fixed 2D segments/points that the solver treats as immutable\n * constraints targets (geoId -3, -4, ... in link order).\n */\nimport { initOcctWasm } from '../occt-kernel/occtKernel.js';\nimport { memberText, type FcstdArchive } from './unpack.js';\nimport type { FcstdDocument, FcstdObject, FcstdProperty } from './document.js';\n\n/**\n * One resolved external-geometry link of a sketch.\n */\nexport interface ExternalLink {\n /** source object name, e.g. \"Chamfer002\" */\n obj: string;\n /** sub-element name, e.g. \"Edge13\" */\n sub: string;\n /** resolved sketch-local 2D polyline */\n polyline: [number, number][];\n}\n\n/**\n * Result of external-geometry resolution: successfully projected links plus\n * per-link failures (with a human-readable reason) for the caller to report.\n */\nexport interface ExternalGeoResult {\n links: ExternalLink[];\n failures: { obj: string; sub: string; reason: string }[];\n}\n\n/** Quaternion (x,y,z,w) inverse-rotate + translate into sketch-local frame. */\nfunction makeInverseTransformer(q: [number, number, number, number], p: [number, number, number]) {\n const n = Math.hypot(q[0], q[1], q[2], q[3]);\n const [qx, qy, qz, qw] = [-q[0] / n, -q[1] / n, -q[2] / n, q[3] / n]; // conjugate\n return (w: [number, number, number]): [number, number, number] => {\n // local = R⁻¹ · (world − P): subtract the translation FIRST, then rotate\n // by the conjugate quaternion. GOTCHA (probe-hole-ext.ts): rotating first\n // and subtracting after (R⁻¹·v − P) is only correct for identity rotation\n // — with Sketch005's 90° placement it shifted projected points by the\n // rotated translation (−140,30 instead of (−40,40)) and the solver chased\n // the wrong frame (delta 1.0e2).\n const [vx, vy, vz] = [w[0] - p[0], w[1] - p[1], w[2] - p[2]];\n const tx = 2 * (qy * vz - qz * vy);\n const ty = 2 * (qz * vx - qx * vz);\n const tz = 2 * (qx * vy - qy * vx);\n const rx = vx + qw * tx + (qy * tz - qz * ty);\n const ry = vy + qw * ty + (qz * tx - qx * tz);\n const rz = vz + qw * tz + (qx * ty - qy * tx);\n return [rx, ry, rz];\n };\n}\n\nfunction placementOf(obj: FcstdObject): { q: [number, number, number, number]; p: [number, number, number] } {\n const el = obj.properties.get('Placement')?.children[0];\n const a = el?.attributes ?? {};\n return {\n p: [Number(a['Px'] ?? 0), Number(a['Py'] ?? 0), Number(a['Pz'] ?? 0)],\n q: [Number(a['Q0'] ?? 0), Number(a['Q1'] ?? 0), Number(a['Q2'] ?? 0), Number(a['Q3'] ?? 1)],\n };\n}\n\n/**\n * Resolve every external link of a sketch to sketch-local 2D polylines.\n *\n * @param externalGeoProp - the sketch's ExternalGeometry property.\n * @param doc - the parsed FCStd document, used to locate source objects and their shapes.\n * @param archive - the FCStd container, used to read the source `.brp` shape files.\n * @param sketchPlacement - the sketch's Placement property, whose inverse maps\n * source geometry into sketch-local coordinates.\n * @returns the resolved sketch-local polylines per link, plus a failure entry\n * (with reason) for every link that could not be resolved.\n */\nexport async function resolveExternalGeometry(\n externalGeoProp: FcstdProperty | undefined,\n doc: FcstdDocument,\n archive: FcstdArchive,\n sketchPlacement: FcstdProperty | undefined,\n): Promise<ExternalGeoResult> {\n const links: ExternalLink[] = [];\n const failures: ExternalGeoResult['failures'] = [];\n const listEl = externalGeoProp?.children[0];\n if (!listEl) return { links, failures };\n\n const skPl = placementOf({ name: '', type: '', properties: new Map([['Placement', sketchPlacement ?? { name: 'Placement', type: '', tagName: 'Property', children: [], valueXml: '', valueText: '', attributes: {} } as FcstdProperty]]) as never });\n const toLocal = makeInverseTransformer(skPl.q, skPl.p);\n\n const kernel: {\n fromBREP: (s: string) => unknown;\n wireframe: (s: unknown, deflection: number) => { points: Float32Array; edgeGroups: number[] };\n } = (await initOcctWasm()) as never;\n\n const brpCache = new Map<string, { points: Float32Array; edgeGroups: number[] } | undefined>();\n const wireframeOf = (objName: string) => {\n if (brpCache.has(objName)) return brpCache.get(objName);\n const src = doc.objects.find((o) => o.name === objName);\n const brpFile = src?.properties.get('Shape')?.children[0]?.attributes['file'];\n if (!brpFile) {\n brpCache.set(objName, undefined);\n return undefined;\n }\n const brp = memberText(archive, brpFile);\n if (!brp) {\n brpCache.set(objName, undefined);\n return undefined;\n }\n try {\n const shape = kernel.fromBREP(brp);\n const wf = kernel.wireframe(shape, 0.01);\n brpCache.set(objName, wf);\n return wf;\n } catch {\n brpCache.set(objName, undefined);\n return undefined;\n }\n };\n\n for (const link of listEl.children) {\n const obj = link.attributes['obj'] ?? '';\n const sub = link.attributes['sub'] ?? '';\n const ord = Number(sub.replace('Edge', ''));\n if (!obj || !sub.startsWith('Edge') || !Number.isInteger(ord) || ord < 1) {\n failures.push({ obj, sub, reason: `unsupported sub-element: ${sub}` });\n continue;\n }\n const wf = wireframeOf(obj);\n if (!wf) {\n failures.push({ obj, sub, reason: 'source shape not loadable' });\n continue;\n }\n const idx = ord - 1; // Edge13 → edgeGroups[12]\n const g0 = wf.edgeGroups[idx * 3];\n const g1 = wf.edgeGroups[idx * 3 + 1];\n if (g0 === undefined || g1 === undefined) {\n failures.push({ obj, sub, reason: `edge ordinal out of range (${wf.edgeGroups.length / 3} edges)` });\n continue;\n }\n const n = Math.floor(g1 / 3);\n const polyline: [number, number][] = [];\n for (let p = 0; p < n; p++) {\n const w: [number, number, number] = [\n wf.points[g0 + p * 3]!, wf.points[g0 + p * 3 + 1]!, wf.points[g0 + p * 3 + 2]!,\n ];\n const l = toLocal(w);\n polyline.push([l[0], l[1]]);\n }\n // drop consecutive duplicates\n const deduped = polyline.filter(\n (pt, i) => i === 0 || Math.hypot(pt[0] - polyline[i - 1]![0], pt[1] - polyline[i - 1]![1]) > 1e-9,\n );\n if (deduped.length >= 2) links.push({ obj, sub, polyline: deduped });\n else failures.push({ obj, sub, reason: 'degenerate polyline' });\n }\n return { links, failures };\n}\n"]}
1
+ {"version":3,"file":"external-geo.js","sourceRoot":"","sources":["../../src/fcstd/external-geo.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * M6.3 — external geometry resolution: decode ExternalGeometry links\n * (App::PropertyLinkSubList), load the source object's .brp via OCCT,\n * extract the named edge polyline (TopExp::MapShapes + IndexedMap order —\n * Edge13 = wireframe edgeGroups[12], verified against hole_puzzle.fcstd),\n * and project it into sketch-local 2D via the sketch Placement inverse.\n *\n * Result: fixed 2D segments/points that the solver treats as immutable\n * constraints targets (geoId -3, -4, ... in link order).\n */\nimport { initOcctWasm } from '../occt-kernel/occtKernel.js';\nimport { memberText, type FcstdArchive } from './unpack.js';\nimport type { FcstdDocument, FcstdObject, FcstdProperty } from './document.js';\n\n/**\n * One resolved external-geometry link of a sketch.\n */\nexport interface ExternalLink {\n /** source object name, e.g. \"Chamfer002\" */\n obj: string;\n /** sub-element name, e.g. \"Edge13\" */\n sub: string;\n /** resolved sketch-local 2D polyline */\n polyline: [number, number][];\n}\n\n/**\n * Result of external-geometry resolution: successfully projected links plus\n * per-link failures (with a human-readable reason) for the caller to report.\n */\nexport interface ExternalGeoResult {\n links: ExternalLink[];\n failures: { obj: string; sub: string; reason: string }[];\n}\n\n/** Quaternion (x,y,z,w) inverse-rotate + translate into sketch-local frame. */\nfunction makeInverseTransformer(q: [number, number, number, number], p: [number, number, number]) {\n const n = Math.hypot(q[0], q[1], q[2], q[3]);\n const [qx, qy, qz, qw] = [-q[0] / n, -q[1] / n, -q[2] / n, q[3] / n]; // conjugate\n return (w: [number, number, number]): [number, number, number] => {\n // local = R⁻¹ · (world − P): subtract the translation FIRST, then rotate\n // by the conjugate quaternion. GOTCHA (probe-hole-ext.ts): rotating first\n // and subtracting after (R⁻¹·v − P) is only correct for identity rotation\n // — with Sketch005's 90° placement it shifted projected points by the\n // rotated translation (−140,30 instead of (−40,40)) and the solver chased\n // the wrong frame (delta 1.0e2).\n const [vx, vy, vz] = [w[0] - p[0], w[1] - p[1], w[2] - p[2]];\n const tx = 2 * (qy * vz - qz * vy);\n const ty = 2 * (qz * vx - qx * vz);\n const tz = 2 * (qx * vy - qy * vx);\n const rx = vx + qw * tx + (qy * tz - qz * ty);\n const ry = vy + qw * ty + (qz * tx - qx * tz);\n const rz = vz + qw * tz + (qx * ty - qy * tx);\n return [rx, ry, rz];\n };\n}\n\nfunction placementOf(obj: FcstdObject): { q: [number, number, number, number]; p: [number, number, number] } {\n const el = obj.properties.get('Placement')?.children[0];\n const a = el?.attributes ?? {};\n return {\n p: [Number(a['Px'] ?? 0), Number(a['Py'] ?? 0), Number(a['Pz'] ?? 0)],\n q: [Number(a['Q0'] ?? 0), Number(a['Q1'] ?? 0), Number(a['Q2'] ?? 0), Number(a['Q3'] ?? 1)],\n };\n}\n\n/**\n * The `.brp` member holding an object's shape: the `Shape` property when\n * present, else `SubShape` (GOTCHA, hole_puzzle corpus 2026-09-20: PartDesign\n * features like Chamfer002/Pocket002 store their result cache in SubShape and\n * have NO Shape property — external links pointing at them failed with\n * \"source shape not loadable\" before the fallback existed).\n *\n * @param obj - the FCStd object to inspect.\n * @returns the .brp member file name, or undefined when neither Shape nor SubShape carries one.\n */\nexport function shapeBrpFile(obj: FcstdObject): string | undefined {\n return obj.properties.get('Shape')?.children[0]?.attributes['file']\n ?? obj.properties.get('SubShape')?.children[0]?.attributes['file'];\n}\n\n/**\n * Resolve every external link of a sketch to sketch-local 2D polylines.\n *\n * @param externalGeoProp - the sketch's ExternalGeometry property.\n * @param doc - the parsed FCStd document, used to locate source objects and their shapes.\n * @param archive - the FCStd container, used to read the source `.brp` shape files.\n * @param sketchPlacement - the sketch's Placement property, whose inverse maps\n * source geometry into sketch-local coordinates.\n * @returns the resolved sketch-local polylines per link, plus a failure entry\n * (with reason) for every link that could not be resolved.\n */\nexport async function resolveExternalGeometry(\n externalGeoProp: FcstdProperty | undefined,\n doc: FcstdDocument,\n archive: FcstdArchive,\n sketchPlacement: FcstdProperty | undefined,\n): Promise<ExternalGeoResult> {\n const links: ExternalLink[] = [];\n const failures: ExternalGeoResult['failures'] = [];\n const listEl = externalGeoProp?.children[0];\n if (!listEl) return { links, failures };\n\n const skPl = placementOf({ name: '', type: '', properties: new Map([['Placement', sketchPlacement ?? { name: 'Placement', type: '', tagName: 'Property', children: [], valueXml: '', valueText: '', attributes: {} } as FcstdProperty]]) as never });\n const toLocal = makeInverseTransformer(skPl.q, skPl.p);\n\n const kernel: {\n fromBREP: (s: string) => unknown;\n wireframe: (s: unknown, deflection: number) => { points: Float32Array; edgeGroups: number[] };\n } = (await initOcctWasm()) as never;\n\n const brpCache = new Map<string, { points: Float32Array; edgeGroups: number[] } | undefined>();\n const wireframeOf = (objName: string) => {\n if (brpCache.has(objName)) return brpCache.get(objName);\n const src = doc.objects.find((o) => o.name === objName);\n const brpFile = src ? shapeBrpFile(src) : undefined;\n if (!brpFile) {\n brpCache.set(objName, undefined);\n return undefined;\n }\n const brp = memberText(archive, brpFile);\n if (!brp) {\n brpCache.set(objName, undefined);\n return undefined;\n }\n try {\n const shape = kernel.fromBREP(brp);\n const wf = kernel.wireframe(shape, 0.01);\n brpCache.set(objName, wf);\n return wf;\n } catch {\n brpCache.set(objName, undefined);\n return undefined;\n }\n };\n\n for (const link of listEl.children) {\n const obj = link.attributes['obj'] ?? '';\n const sub = link.attributes['sub'] ?? '';\n const ord = Number(sub.replace('Edge', ''));\n if (!obj || !sub.startsWith('Edge') || !Number.isInteger(ord) || ord < 1) {\n failures.push({ obj, sub, reason: `unsupported sub-element: ${sub}` });\n continue;\n }\n const wf = wireframeOf(obj);\n if (!wf) {\n failures.push({ obj, sub, reason: 'source shape not loadable' });\n continue;\n }\n const idx = ord - 1; // Edge13 → edgeGroups[12]\n const g0 = wf.edgeGroups[idx * 3];\n const g1 = wf.edgeGroups[idx * 3 + 1];\n if (g0 === undefined || g1 === undefined) {\n failures.push({ obj, sub, reason: `edge ordinal out of range (${wf.edgeGroups.length / 3} edges)` });\n continue;\n }\n const n = Math.floor(g1 / 3);\n const polyline: [number, number][] = [];\n for (let p = 0; p < n; p++) {\n const w: [number, number, number] = [\n wf.points[g0 + p * 3]!, wf.points[g0 + p * 3 + 1]!, wf.points[g0 + p * 3 + 2]!,\n ];\n const l = toLocal(w);\n polyline.push([l[0], l[1]]);\n }\n // drop consecutive duplicates\n const deduped = polyline.filter(\n (pt, i) => i === 0 || Math.hypot(pt[0] - polyline[i - 1]![0], pt[1] - polyline[i - 1]![1]) > 1e-9,\n );\n if (deduped.length >= 2) links.push({ obj, sub, polyline: deduped });\n else failures.push({ obj, sub, reason: 'degenerate polyline' });\n }\n return { links, failures };\n}\n"]}
@@ -27,6 +27,14 @@ export interface CadCall {
27
27
  literals?: unknown[];
28
28
  /** FCStd object this call came from */
29
29
  source: string;
30
+ /**
31
+ * When true, `inputs` is bookkeeping-only (dependency tracking / variable
32
+ * remapping / consumed-set) and must NOT be rendered as positional args —
33
+ * the real args come from `params`. Used by params-only ops like
34
+ * `cad.compound` whose members live in `params.members` (rendering `inputs`
35
+ * positionally would shadow `params`).
36
+ */
37
+ noPositionalArgs?: boolean;
30
38
  }
31
39
  /**
32
40
  * A raw JS expression argument that M5 renders verbatim instead of JSON-encoding.
@@ -69,6 +77,14 @@ export type TranslateVerdict = {
69
77
  kind: 'preserved-only';
70
78
  reason: string;
71
79
  };
80
+ /**
81
+ * H7 (hole_puzzle corpus, 2026-09-20): marker input for a subtract whose base
82
+ * is implied by the Body's feature order (no explicit BaseFeature property).
83
+ * Codegen retargets this at the chain head when folding the feature into its
84
+ * Body chain. Never a legal variable name (contains `::`), so it cannot be
85
+ * confused with a real input.
86
+ */
87
+ export declare const BODY_CHAIN_BASE = "::body-chain-base::";
72
88
  /**
73
89
  * True when the object type is on the M4.1 translation whitelist.
74
90
  *
@@ -161,9 +177,13 @@ export declare function placementPos(obj: FcstdObject): [number, number, number]
161
177
  * its geometry (sketch contours or prior solid).
162
178
  * @param docObjects - the full document object list, needed by the UpToFace
163
179
  * datum-plane path; optional.
180
+ * @param shapeCarriers - objects whose Shape is stored as a .brp member; optional.
181
+ * @param brokenShapeAssets - objects whose Shape `file` attribute points at a missing/empty member (explicit gap); optional.
164
182
  * @returns the cad-op call plan, or an explicit bake/preserve verdict with reason.
165
183
  */
166
184
  export declare function translateObject(obj: FcstdObject, inputVar: (depName: string) => string | undefined, docObjects?: readonly FcstdObject[],
167
185
  /** H7: names of objects whose Shape is stored as a .brp member (probed from the ZIP). */
168
- shapeCarriers?: ReadonlySet<string>): TranslateVerdict;
186
+ shapeCarriers?: ReadonlySet<string>,
187
+ /** E4: objects whose Shape `file` attribute points at a missing/empty member. */
188
+ brokenShapeAssets?: ReadonlySet<string>): TranslateVerdict;
169
189
  //# sourceMappingURL=feature-translate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-translate.d.ts","sourceRoot":"","sources":["../../src/fcstd/feature-translate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGjF;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,gFAAgF;IAChF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,MAAM;IACrB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAEhD;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuB/C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAMxD;AAeD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAKjG;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAG7E;AAoBD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACnB,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;AAW/F;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,WAAW,CAMnF;AAwED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,SAAS,CAYxI;AAWD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CASvE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EACjD,UAAU,CAAC,EAAE,SAAS,WAAW,EAAE;AACnC,yFAAyF;AACzF,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,gBAAgB,CAkgBlB"}
1
+ {"version":3,"file":"feature-translate.d.ts","sourceRoot":"","sources":["../../src/fcstd/feature-translate.ts"],"names":[],"mappings":"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"}