@faicad/faijs 0.13.1 → 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 (341) 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 +3 -2
  24. package/dist/api/edge-ref.d.ts.map +1 -1
  25. package/dist/api/edge-ref.js +10 -6
  26. package/dist/api/edge-ref.js.map +1 -1
  27. package/dist/api/engrave.d.ts.map +1 -1
  28. package/dist/api/engrave.js +1 -0
  29. package/dist/api/engrave.js.map +1 -1
  30. package/dist/api/extrude.d.ts.map +1 -1
  31. package/dist/api/extrude.js +131 -3
  32. package/dist/api/extrude.js.map +1 -1
  33. package/dist/api/face-ref.d.ts.map +1 -1
  34. package/dist/api/face-ref.js +7 -4
  35. package/dist/api/face-ref.js.map +1 -1
  36. package/dist/api/fai_drill.d.ts +1 -1
  37. package/dist/api/fai_drill.d.ts.map +1 -1
  38. package/dist/api/fai_drill.js +31 -18
  39. package/dist/api/fai_drill.js.map +1 -1
  40. package/dist/api/fai_extrude.d.ts +1 -1
  41. package/dist/api/fai_extrude.d.ts.map +1 -1
  42. package/dist/api/fai_extrude.js +2 -1
  43. package/dist/api/fai_extrude.js.map +1 -1
  44. package/dist/api/fai_split.d.ts +1 -1
  45. package/dist/api/fai_split.d.ts.map +1 -1
  46. package/dist/api/fai_split.js +2 -1
  47. package/dist/api/fai_split.js.map +1 -1
  48. package/dist/api/fillet.d.ts.map +1 -1
  49. package/dist/api/fillet.js +6 -5
  50. package/dist/api/fillet.js.map +1 -1
  51. package/dist/api/generated/operations.js +16 -16
  52. package/dist/api/generated/operations.js.map +1 -1
  53. package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
  54. package/dist/api/generated/script-face-manifest.js +0 -1
  55. package/dist/api/generated/script-face-manifest.js.map +1 -1
  56. package/dist/api/generated/script-face.d.ts +1 -4
  57. package/dist/api/generated/script-face.d.ts.map +1 -1
  58. package/dist/api/generated/script-face.js +2 -3
  59. package/dist/api/generated/script-face.js.map +1 -1
  60. package/dist/api/generated/sketching.js +1 -1
  61. package/dist/api/generated/sketching.js.map +1 -1
  62. package/dist/api/generated/topology.d.ts +0 -48
  63. package/dist/api/generated/topology.d.ts.map +1 -1
  64. package/dist/api/generated/topology.js +20 -62
  65. package/dist/api/generated/topology.js.map +1 -1
  66. package/dist/api/import-brep.d.ts +36 -0
  67. package/dist/api/import-brep.d.ts.map +1 -0
  68. package/dist/api/import-brep.js +72 -0
  69. package/dist/api/import-brep.js.map +1 -0
  70. package/dist/api/import-step.d.ts +28 -0
  71. package/dist/api/import-step.d.ts.map +1 -0
  72. package/dist/api/import-step.js +57 -0
  73. package/dist/api/import-step.js.map +1 -0
  74. package/dist/api/index.d.ts +7 -2
  75. package/dist/api/index.d.ts.map +1 -1
  76. package/dist/api/index.js +19 -5
  77. package/dist/api/index.js.map +1 -1
  78. package/dist/api/internal/compat-op.d.ts +2 -0
  79. package/dist/api/internal/compat-op.d.ts.map +1 -1
  80. package/dist/api/internal/compat-op.js +10 -2
  81. package/dist/api/internal/compat-op.js.map +1 -1
  82. package/dist/api/knurl.d.ts.map +1 -1
  83. package/dist/api/knurl.js +1 -0
  84. package/dist/api/knurl.js.map +1 -1
  85. package/dist/api/load.d.ts +3 -1
  86. package/dist/api/load.d.ts.map +1 -1
  87. package/dist/api/load.js +3 -1
  88. package/dist/api/load.js.map +1 -1
  89. package/dist/api/pattern.d.ts +32 -0
  90. package/dist/api/pattern.d.ts.map +1 -0
  91. package/dist/api/pattern.js +121 -0
  92. package/dist/api/pattern.js.map +1 -0
  93. package/dist/api/place.d.ts +35 -0
  94. package/dist/api/place.d.ts.map +1 -0
  95. package/dist/api/place.js +113 -0
  96. package/dist/api/place.js.map +1 -0
  97. package/dist/api/primitives.d.ts.map +1 -1
  98. package/dist/api/primitives.js +10 -3
  99. package/dist/api/primitives.js.map +1 -1
  100. package/dist/api/screw.d.ts.map +1 -1
  101. package/dist/api/screw.js +1 -0
  102. package/dist/api/screw.js.map +1 -1
  103. package/dist/api/sdf.d.ts.map +1 -1
  104. package/dist/api/sdf.js +1 -0
  105. package/dist/api/sdf.js.map +1 -1
  106. package/dist/api/sketch.d.ts.map +1 -1
  107. package/dist/api/sketch.js +1 -0
  108. package/dist/api/sketch.js.map +1 -1
  109. package/dist/api/split.d.ts +31 -0
  110. package/dist/api/split.d.ts.map +1 -0
  111. package/dist/api/split.js +96 -0
  112. package/dist/api/split.js.map +1 -0
  113. package/dist/api/surface/arg-spec.d.ts +9 -0
  114. package/dist/api/surface/arg-spec.d.ts.map +1 -1
  115. package/dist/api/surface/arg-spec.js +49 -30
  116. package/dist/api/surface/arg-spec.js.map +1 -1
  117. package/dist/api/svgExtrude.d.ts.map +1 -1
  118. package/dist/api/svgExtrude.js +1 -0
  119. package/dist/api/svgExtrude.js.map +1 -1
  120. package/dist/api/text.d.ts.map +1 -1
  121. package/dist/api/text.js +1 -0
  122. package/dist/api/text.js.map +1 -1
  123. package/dist/api/topo-resolve.d.ts.map +1 -1
  124. package/dist/api/topo-resolve.js +65 -3
  125. package/dist/api/topo-resolve.js.map +1 -1
  126. package/dist/api/transform.d.ts +4 -0
  127. package/dist/api/transform.d.ts.map +1 -1
  128. package/dist/api/transform.js +44 -12
  129. package/dist/api/transform.js.map +1 -1
  130. package/dist/brep/brep-ops.d.ts +34 -1
  131. package/dist/brep/brep-ops.d.ts.map +1 -1
  132. package/dist/brep/brep-ops.js +62 -22
  133. package/dist/brep/brep-ops.js.map +1 -1
  134. package/dist/brep/engine/adapters/brep-mock.d.ts.map +1 -1
  135. package/dist/brep/engine/adapters/brep-mock.js +8 -0
  136. package/dist/brep/engine/adapters/brep-mock.js.map +1 -1
  137. package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
  138. package/dist/brep/engine/adapters/brepkit.js +6 -1
  139. package/dist/brep/engine/adapters/brepkit.js.map +1 -1
  140. package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
  141. package/dist/brep/engine/adapters/occt.js +24 -1
  142. package/dist/brep/engine/adapters/occt.js.map +1 -1
  143. package/dist/brep/engine/primitives.d.ts +17 -0
  144. package/dist/brep/engine/primitives.d.ts.map +1 -1
  145. package/dist/brep/engine/primitives.js.map +1 -1
  146. package/dist/brep/engine/types.d.ts +49 -6
  147. package/dist/brep/engine/types.d.ts.map +1 -1
  148. package/dist/brep/engine/types.js.map +1 -1
  149. package/dist/brep/export/step.d.ts.map +1 -1
  150. package/dist/brep/export/step.js +6 -1
  151. package/dist/brep/export/step.js.map +1 -1
  152. package/dist/brep/face-evolution.d.ts +78 -8
  153. package/dist/brep/face-evolution.d.ts.map +1 -1
  154. package/dist/brep/face-evolution.js +130 -17
  155. package/dist/brep/face-evolution.js.map +1 -1
  156. package/dist/brepkit-kernel/brepkitKernel.d.ts +1 -1
  157. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  158. package/dist/brepkit-kernel/brepkitKernel.js +27 -1
  159. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  160. package/dist/browser.d.ts +0 -2
  161. package/dist/browser.d.ts.map +1 -1
  162. package/dist/browser.js +2 -2
  163. package/dist/browser.js.map +1 -1
  164. package/dist/cad-runtime/admit-compat-lib.d.ts +19 -1
  165. package/dist/cad-runtime/admit-compat-lib.d.ts.map +1 -1
  166. package/dist/cad-runtime/admit-compat-lib.js +21 -1
  167. package/dist/cad-runtime/admit-compat-lib.js.map +1 -1
  168. package/dist/cad-runtime/backend-dispatch.d.ts +44 -3
  169. package/dist/cad-runtime/backend-dispatch.d.ts.map +1 -1
  170. package/dist/cad-runtime/backend-dispatch.js +46 -3
  171. package/dist/cad-runtime/backend-dispatch.js.map +1 -1
  172. package/dist/cad-runtime/browser-lib-loader.d.ts +2 -0
  173. package/dist/cad-runtime/browser-lib-loader.d.ts.map +1 -1
  174. package/dist/cad-runtime/browser-lib-loader.js +9 -1
  175. package/dist/cad-runtime/browser-lib-loader.js.map +1 -1
  176. package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
  177. package/dist/cad-runtime/direct-executor.js +14 -4
  178. package/dist/cad-runtime/direct-executor.js.map +1 -1
  179. package/dist/cad-runtime/module-registry.d.ts +3 -1
  180. package/dist/cad-runtime/module-registry.d.ts.map +1 -1
  181. package/dist/cad-runtime/module-registry.js +13 -1
  182. package/dist/cad-runtime/module-registry.js.map +1 -1
  183. package/dist/cad-runtime/ports.d.ts +7 -0
  184. package/dist/cad-runtime/ports.d.ts.map +1 -1
  185. package/dist/cad-runtime/ports.js.map +1 -1
  186. package/dist/cad-runtime/runtime-concurrent-error.d.ts +38 -0
  187. package/dist/cad-runtime/runtime-concurrent-error.d.ts.map +1 -0
  188. package/dist/cad-runtime/runtime-concurrent-error.js +43 -0
  189. package/dist/cad-runtime/runtime-concurrent-error.js.map +1 -0
  190. package/dist/cad-runtime/runtime.d.ts +54 -2
  191. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  192. package/dist/cad-runtime/runtime.js +186 -10
  193. package/dist/cad-runtime/runtime.js.map +1 -1
  194. package/dist/define-op.d.ts +22 -0
  195. package/dist/define-op.d.ts.map +1 -1
  196. package/dist/define-op.js +82 -12
  197. package/dist/define-op.js.map +1 -1
  198. package/dist/fcstd/attachment.d.ts +72 -0
  199. package/dist/fcstd/attachment.d.ts.map +1 -0
  200. package/dist/fcstd/attachment.js +93 -0
  201. package/dist/fcstd/attachment.js.map +1 -0
  202. package/dist/fcstd/build-fai-zip.d.ts +11 -0
  203. package/dist/fcstd/build-fai-zip.d.ts.map +1 -1
  204. package/dist/fcstd/build-fai-zip.js +21 -8
  205. package/dist/fcstd/build-fai-zip.js.map +1 -1
  206. package/dist/fcstd/cli.d.ts +3 -0
  207. package/dist/fcstd/cli.d.ts.map +1 -0
  208. package/dist/fcstd/cli.js +37 -0
  209. package/dist/fcstd/cli.js.map +1 -0
  210. package/dist/fcstd/codegen.d.ts +23 -1
  211. package/dist/fcstd/codegen.d.ts.map +1 -1
  212. package/dist/fcstd/codegen.js +159 -48
  213. package/dist/fcstd/codegen.js.map +1 -1
  214. package/dist/fcstd/container.d.ts +16 -1
  215. package/dist/fcstd/container.d.ts.map +1 -1
  216. package/dist/fcstd/container.js +12 -1
  217. package/dist/fcstd/container.js.map +1 -1
  218. package/dist/fcstd/contour.d.ts +9 -1
  219. package/dist/fcstd/contour.d.ts.map +1 -1
  220. package/dist/fcstd/contour.js +28 -18
  221. package/dist/fcstd/contour.js.map +1 -1
  222. package/dist/fcstd/convert.d.ts +31 -2
  223. package/dist/fcstd/convert.d.ts.map +1 -1
  224. package/dist/fcstd/convert.js +73 -22
  225. package/dist/fcstd/convert.js.map +1 -1
  226. package/dist/fcstd/document.d.ts +8 -1
  227. package/dist/fcstd/document.d.ts.map +1 -1
  228. package/dist/fcstd/document.js +4 -1
  229. package/dist/fcstd/document.js.map +1 -1
  230. package/dist/fcstd/expressions.d.ts +14 -1
  231. package/dist/fcstd/expressions.d.ts.map +1 -1
  232. package/dist/fcstd/expressions.js +11 -1
  233. package/dist/fcstd/expressions.js.map +1 -1
  234. package/dist/fcstd/external-geo.d.ts +27 -2
  235. package/dist/fcstd/external-geo.d.ts.map +1 -1
  236. package/dist/fcstd/external-geo.js +23 -2
  237. package/dist/fcstd/external-geo.js.map +1 -1
  238. package/dist/fcstd/feature-translate.d.ts +85 -3
  239. package/dist/fcstd/feature-translate.d.ts.map +1 -1
  240. package/dist/fcstd/feature-translate.js +189 -9
  241. package/dist/fcstd/feature-translate.js.map +1 -1
  242. package/dist/fcstd/index.d.ts +23 -0
  243. package/dist/fcstd/index.d.ts.map +1 -0
  244. package/dist/fcstd/index.js +19 -0
  245. package/dist/fcstd/index.js.map +1 -0
  246. package/dist/fcstd/placement.d.ts +49 -6
  247. package/dist/fcstd/placement.d.ts.map +1 -1
  248. package/dist/fcstd/placement.js +46 -6
  249. package/dist/fcstd/placement.js.map +1 -1
  250. package/dist/fcstd/planegcs-backend.d.ts +16 -1
  251. package/dist/fcstd/planegcs-backend.d.ts.map +1 -1
  252. package/dist/fcstd/planegcs-backend.js +16 -1
  253. package/dist/fcstd/planegcs-backend.js.map +1 -1
  254. package/dist/fcstd/sketch-parse.d.ts +43 -0
  255. package/dist/fcstd/sketch-parse.d.ts.map +1 -1
  256. package/dist/fcstd/sketch-parse.js +26 -0
  257. package/dist/fcstd/sketch-parse.js.map +1 -1
  258. package/dist/fcstd/sketch-solver.d.ts +20 -2
  259. package/dist/fcstd/sketch-solver.d.ts.map +1 -1
  260. package/dist/fcstd/sketch-solver.js +6 -1
  261. package/dist/fcstd/sketch-solver.js.map +1 -1
  262. package/dist/fcstd/sketch-verify.d.ts +19 -1
  263. package/dist/fcstd/sketch-verify.d.ts.map +1 -1
  264. package/dist/fcstd/sketch-verify.js +14 -1
  265. package/dist/fcstd/sketch-verify.js.map +1 -1
  266. package/dist/fcstd/structural-types.d.ts +83 -0
  267. package/dist/fcstd/structural-types.d.ts.map +1 -0
  268. package/dist/fcstd/structural-types.js +116 -0
  269. package/dist/fcstd/structural-types.js.map +1 -0
  270. package/dist/fcstd/unpack.d.ts +21 -2
  271. package/dist/fcstd/unpack.d.ts.map +1 -1
  272. package/dist/fcstd/unpack.js +11 -2
  273. package/dist/fcstd/unpack.js.map +1 -1
  274. package/dist/fcstd-convert.d.ts +30 -0
  275. package/dist/fcstd-convert.d.ts.map +1 -0
  276. package/dist/fcstd-convert.js +27 -0
  277. package/dist/fcstd-convert.js.map +1 -0
  278. package/dist/index.d.ts +1 -0
  279. package/dist/index.d.ts.map +1 -1
  280. package/dist/index.js.map +1 -1
  281. package/dist/lang/determinism-scanner.d.ts +62 -0
  282. package/dist/lang/determinism-scanner.d.ts.map +1 -0
  283. package/dist/lang/determinism-scanner.js +619 -0
  284. package/dist/lang/determinism-scanner.js.map +1 -0
  285. package/dist/lang/symbol-table.generated.d.ts +7 -3
  286. package/dist/lang/symbol-table.generated.d.ts.map +1 -1
  287. package/dist/lang/symbol-table.generated.js +7 -3
  288. package/dist/lang/symbol-table.generated.js.map +1 -1
  289. package/dist/mesh/index.d.ts.map +1 -1
  290. package/dist/mesh/index.js +3 -2
  291. package/dist/mesh/index.js.map +1 -1
  292. package/dist/node-host/cli.d.ts.map +1 -1
  293. package/dist/node-host/cli.js +56 -2
  294. package/dist/node-host/cli.js.map +1 -1
  295. package/dist/runtime-state.d.ts +18 -6
  296. package/dist/runtime-state.d.ts.map +1 -1
  297. package/dist/runtime-state.js +9 -1
  298. package/dist/runtime-state.js.map +1 -1
  299. package/dist/shape.d.ts +15 -0
  300. package/dist/shape.d.ts.map +1 -1
  301. package/dist/shape.js +26 -3
  302. package/dist/shape.js.map +1 -1
  303. package/dist/topology/naming/build-naming.d.ts +14 -30
  304. package/dist/topology/naming/build-naming.d.ts.map +1 -1
  305. package/dist/topology/naming/build-naming.js +21 -54
  306. package/dist/topology/naming/build-naming.js.map +1 -1
  307. package/dist/topology/naming/capture-topo-ref.d.ts.map +1 -1
  308. package/dist/topology/naming/capture-topo-ref.js +5 -0
  309. package/dist/topology/naming/capture-topo-ref.js.map +1 -1
  310. package/dist/topology/naming/index.d.ts +3 -0
  311. package/dist/topology/naming/index.d.ts.map +1 -1
  312. package/dist/topology/naming/index.js +3 -0
  313. package/dist/topology/naming/index.js.map +1 -1
  314. package/dist/topology/naming/lineage-resolve.d.ts +80 -0
  315. package/dist/topology/naming/lineage-resolve.d.ts.map +1 -0
  316. package/dist/topology/naming/lineage-resolve.js +120 -0
  317. package/dist/topology/naming/lineage-resolve.js.map +1 -0
  318. package/dist/topology/naming/lineage.d.ts +349 -0
  319. package/dist/topology/naming/lineage.d.ts.map +1 -0
  320. package/dist/topology/naming/lineage.js +398 -0
  321. package/dist/topology/naming/lineage.js.map +1 -0
  322. package/dist/topology/naming/migrate.d.ts +41 -0
  323. package/dist/topology/naming/migrate.d.ts.map +1 -0
  324. package/dist/topology/naming/migrate.js +88 -0
  325. package/dist/topology/naming/migrate.js.map +1 -0
  326. package/dist/topology/naming/role-name.d.ts +176 -0
  327. package/dist/topology/naming/role-name.d.ts.map +1 -0
  328. package/dist/topology/naming/role-name.js +344 -0
  329. package/dist/topology/naming/role-name.js.map +1 -0
  330. package/dist/topology/naming/roles.d.ts +20 -22
  331. package/dist/topology/naming/roles.d.ts.map +1 -1
  332. package/dist/topology/naming/roles.js +51 -49
  333. package/dist/topology/naming/roles.js.map +1 -1
  334. package/dist/topology/naming/types.d.ts +65 -18
  335. package/dist/topology/naming/types.d.ts.map +1 -1
  336. package/dist/topology/naming/types.js.map +1 -1
  337. package/dist/weapp.d.ts +22 -0
  338. package/dist/weapp.d.ts.map +1 -0
  339. package/dist/weapp.js +23 -0
  340. package/dist/weapp.js.map +1 -0
  341. package/package.json +18 -5
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fcstd CLI — batch driver entry: .FCStd → .fai.zip with a machine contract.
4
+ *
5
+ * Exit codes (plan §5.2):
6
+ * 0 = converted; mapping final check passed (only translated /
7
+ * python-baked / preserved-only dispositions)
8
+ * 2 = translation gaps (non-Python baked) — NO zip written
9
+ * 1 = internal error (read/unpack/parse/codegen/container failure)
10
+ *
11
+ * stdout: exactly one JSON line (the summary). Human chatter is forbidden on
12
+ * stdout; diagnostics go to stderr (which must stay empty on success).
13
+ *
14
+ * Run (published): faijs-fcstd-convert <in.FCStd> [out.fai.zip]
15
+ * Run (in-repo): npm run fcstd:convert -w @faicad/faijs -- <in.FCStd> [out.fai.zip]
16
+ * With no <out>, acts as a dry audit (no file written).
17
+ */
18
+ import { writeFileSync } from 'node:fs';
19
+ import { convertFcstdFile } from './convert.js';
20
+ const [input, output] = process.argv.slice(2);
21
+ if (!input) {
22
+ console.error('usage: faijs-fcstd-convert <in.FCStd> [out.fai.zip]');
23
+ process.exit(1);
24
+ }
25
+ const summary = await convertFcstdFile(input);
26
+ if (summary.ok && output && summary.zip) {
27
+ writeFileSync(output, summary.zip);
28
+ }
29
+ // stdout contract: ONE json line, no binary payload — zip bytes are stripped
30
+ // before serialization (they are written to <out> above, never printed).
31
+ const { zip: _zip, ...report } = summary;
32
+ void _zip;
33
+ console.log(JSON.stringify(report));
34
+ if (!summary.ok) {
35
+ process.exit(summary.gaps.length > 0 ? 2 : 1);
36
+ }
37
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/fcstd/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;AAC9C,IAAI,OAAO,CAAC,EAAE,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACxC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;AACrC,CAAC;AACD,6EAA6E;AAC7E,yEAAyE;AACzE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;AACzC,KAAK,IAAI,CAAC;AACV,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAEpC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;IAChB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * fcstd CLI — batch driver entry: .FCStd → .fai.zip with a machine contract.\n *\n * Exit codes (plan §5.2):\n * 0 = converted; mapping final check passed (only translated /\n * python-baked / preserved-only dispositions)\n * 2 = translation gaps (non-Python baked) — NO zip written\n * 1 = internal error (read/unpack/parse/codegen/container failure)\n *\n * stdout: exactly one JSON line (the summary). Human chatter is forbidden on\n * stdout; diagnostics go to stderr (which must stay empty on success).\n *\n * Run (published): faijs-fcstd-convert <in.FCStd> [out.fai.zip]\n * Run (in-repo): npm run fcstd:convert -w @faicad/faijs -- <in.FCStd> [out.fai.zip]\n * With no <out>, acts as a dry audit (no file written).\n */\nimport { writeFileSync } from 'node:fs';\nimport { convertFcstdFile } from './convert.js';\n\nconst [input, output] = process.argv.slice(2);\nif (!input) {\n console.error('usage: faijs-fcstd-convert <in.FCStd> [out.fai.zip]');\n process.exit(1);\n}\n\nconst summary = await convertFcstdFile(input);\nif (summary.ok && output && summary.zip) {\n writeFileSync(output, summary.zip);\n}\n// stdout contract: ONE json line, no binary payload — zip bytes are stripped\n// before serialization (they are written to <out> above, never printed).\nconst { zip: _zip, ...report } = summary;\nvoid _zip;\nconsole.log(JSON.stringify(report));\n\nif (!summary.ok) {\n process.exit(summary.gaps.length > 0 ? 2 : 1);\n}\n"]}
@@ -6,13 +6,21 @@
6
6
  * heuristics — explicit downgrade per plan §12).
7
7
  * M5.2 lowering: params → JS constants; calls → `const partN = await cad.x(...)`;
8
8
  * statement ids sN. Sketch contours enter as blueprint literals.
9
+ *
10
+ * 本文件为 Placement 发射平台 `cad.place`(旋转四元数 + 平移,单点)、为产物聚合发射
11
+ * 平台 `cad.compound`(几何复合体,内核 `makeCompound`,持 OCCT 句柄)。这两个是 faijs
12
+ * 平台几何 op(H11,方案 §4),不再借用 `../3d_editor` 的 `cad.group` /
13
+ * `cad.translate` / `cad.rotate_euler`(编辑器交互 op,JSDoc 已 @deprecated)。
9
14
  */
10
15
  import type { FcstdDocument } from './document.js';
11
16
  import type { CadCall } from './feature-translate.js';
12
17
  import type { Contour } from './contour.js';
13
18
  import { type Placement } from './placement.js';
19
+ /** Per-object codegen outcome: what was emitted for one FCStd object. */
14
20
  export interface GenObjectResult {
21
+ /** FCStd object name */
15
22
  name: string;
23
+ /** FCStd type id */
16
24
  type: string;
17
25
  /** variable name bound in generated code (partN) or undefined when baked */
18
26
  variable?: string;
@@ -26,6 +34,7 @@ export interface GenObjectResult {
26
34
  loopCount?: number;
27
35
  };
28
36
  }
37
+ /** Full codegen output: lowered calls, per-object ledger and generated script(s). */
29
38
  export interface GenResult {
30
39
  /** ordered translated calls (dependency order) */
31
40
  calls: CadCall[];
@@ -46,6 +55,15 @@ export interface GenResult {
46
55
  * M5.1/M5.2 — translate every object in dependency order and lower to JS.
47
56
  * `sketchVerdict` supplies the M3 outcome per sketch object name; sketches
48
57
  * whose contour feeds a Pad/Pocket appear as inputs.
58
+ *
59
+ * @param doc parsed FCStd object graph to translate
60
+ * @param sketchVerdict per-sketch M3 verdict keyed by object name
61
+ * @param sketchContours solved contours per L0 sketch, keyed by object name
62
+ * @param baseName source base name used in generated file headers/labels
63
+ * @param placements per-object Placement used to re-orient placed geometry; missing → identity
64
+ * @param shapeCarriers objects whose Shape is a ZIP .brp member (pure-Shape carriers → shape-asset)
65
+ * @param brokenShapeAssets objects whose Shape `file` attribute points at a missing/empty member (explicit gap)
66
+ * @returns the lowered call plan, per-object dispositions and generated code
49
67
  */
50
68
  export declare function generateModel(doc: FcstdDocument, sketchVerdict: Map<string, {
51
69
  level: 'L0' | 'L1' | 'L2';
@@ -53,5 +71,9 @@ export declare function generateModel(doc: FcstdDocument, sketchVerdict: Map<str
53
71
  loopCount?: number;
54
72
  }>, sketchContours: Map<string, Contour[]>, baseName: string,
55
73
  /** M8.3: per-object Placement (sketches + features); missing → identity */
56
- placements?: Map<string, Placement>): GenResult;
74
+ placements?: Map<string, Placement>,
75
+ /** H7: objects whose Shape is a ZIP .brp member (pure-Shape carriers → shape-asset). */
76
+ shapeCarriers?: ReadonlySet<string>,
77
+ /** E4: objects whose Shape `file` attribute points at a missing/empty member (explicit gap). */
78
+ brokenShapeAssets?: ReadonlySet<string>): GenResult;
57
79
  //# sourceMappingURL=codegen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/fcstd/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAoB,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,KAAK,SAAS,EAA0C,MAAM,gBAAgB,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,WAAW,EAAE,YAAY,GAAG,OAAO,GAAG,gBAAgB,CAAC;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7E;AAED,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb;wFACoF;IACpF,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtD,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqDD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9F,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EACtC,QAAQ,EAAE,MAAM;AAChB,2EAA2E;AAC3E,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAClC,SAAS,CA6UX"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/fcstd/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAoB,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,KAAK,SAAS,EAA6C,MAAM,gBAAgB,CAAC;AAG3F,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,WAAW,EAAE,YAAY,GAAG,OAAO,GAAG,gBAAgB,CAAC;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7E;AAED,qFAAqF;AACrF,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb;wFACoF;IACpF,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtD,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9F,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EACtC,QAAQ,EAAE,MAAM;AAChB,2EAA2E;AAC3E,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;AACnC,wFAAwF;AACxF,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC;AACnC,gGAAgG;AAChG,iBAAiB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACtC,SAAS,CAwZX"}
@@ -1,5 +1,6 @@
1
- import { translateObject, isJsExpr, jsExpr } from './feature-translate.js';
2
- import { isIdentityPlacement, quatToEulerXYZDeg } from './placement.js';
1
+ import { translateObject, isJsExpr, jsExpr, BODY_CHAIN_BASE } from './feature-translate.js';
2
+ import { isIdentityPlacement, invertApplyPlacement } from './placement.js';
3
+ import { isNonModelingType } from './structural-types.js';
3
4
  /** Extract link dependencies relevant for ordering. */
4
5
  function depsOf(obj) {
5
6
  const out = [];
@@ -10,9 +11,21 @@ function depsOf(obj) {
10
11
  if (v)
11
12
  out.push(v);
12
13
  }
13
- const shapes = obj.properties.get('Shapes')?.children[0];
14
- if (shapes) {
15
- for (const link of shapes.children) {
14
+ // GOTCHA (ArchDetail corpus, 2026-09-21): App::PropertyLinkList properties.
15
+ // `Shapes` is the multi-input geometry list (Part::MultiFuse / MultiCommon);
16
+ // `Links` is Part::Compound's member list. Neither is a single-value link
17
+ // property, so missing it here left the Compound with NO dependency on its
18
+ // members — Kahn then placed it at its document position, and ArchDetail's
19
+ // compounds sit at doc index 10-14 while every member sits at 269+ (Draft
20
+ // emits the wire first, the compound last, but the file order is sorted by
21
+ // name). inputVar() found nothing yet → `compound-missing-members` for all
22
+ // five compounds, for a pure ordering reason.
23
+ const linkLists = ['Shapes', 'Links'];
24
+ for (const p of linkLists) {
25
+ const el = obj.properties.get(p)?.children[0];
26
+ if (!el)
27
+ continue;
28
+ for (const link of el.children) {
16
29
  const v = link.attributes['value'];
17
30
  if (v)
18
31
  out.push(v);
@@ -50,14 +63,45 @@ function bodyFeatureNames(obj) {
50
63
  * M5.1/M5.2 — translate every object in dependency order and lower to JS.
51
64
  * `sketchVerdict` supplies the M3 outcome per sketch object name; sketches
52
65
  * whose contour feeds a Pad/Pocket appear as inputs.
66
+ *
67
+ * @param doc parsed FCStd object graph to translate
68
+ * @param sketchVerdict per-sketch M3 verdict keyed by object name
69
+ * @param sketchContours solved contours per L0 sketch, keyed by object name
70
+ * @param baseName source base name used in generated file headers/labels
71
+ * @param placements per-object Placement used to re-orient placed geometry; missing → identity
72
+ * @param shapeCarriers objects whose Shape is a ZIP .brp member (pure-Shape carriers → shape-asset)
73
+ * @param brokenShapeAssets objects whose Shape `file` attribute points at a missing/empty member (explicit gap)
74
+ * @returns the lowered call plan, per-object dispositions and generated code
53
75
  */
54
76
  export function generateModel(doc, sketchVerdict, sketchContours, baseName,
55
77
  /** M8.3: per-object Placement (sketches + features); missing → identity */
56
- placements) {
78
+ placements,
79
+ /** H7: objects whose Shape is a ZIP .brp member (pure-Shape carriers → shape-asset). */
80
+ shapeCarriers,
81
+ /** E4: objects whose Shape `file` attribute points at a missing/empty member (explicit gap). */
82
+ brokenShapeAssets) {
57
83
  const byName = new Map(doc.objects.map((o) => [o.name, o]));
84
+ // GOTCHA (test_geomop corpus, 2026-09-20): a dependency on a Body
85
+ // CONTAINER (Part::Cut with Tool→Body) really waits for the Body's
86
+ // features to have built the chain — expand the dep to the member
87
+ // features so Kahn orders the consumer AFTER the chain exists (and
88
+ // chainVar.get(Body) resolves). Treating the container itself as
89
+ // satisfiable instead let the Cut run before the chain was built.
58
90
  const nodes = new Map();
59
91
  for (const obj of doc.objects) {
60
- nodes.set(obj.name, { name: obj.name, obj, deps: new Set(depsOf(obj)) });
92
+ const deps = new Set();
93
+ for (const d of depsOf(obj)) {
94
+ const target = byName.get(d);
95
+ if (target?.type === 'PartDesign::Body') {
96
+ for (const m of bodyFeatureNames(target))
97
+ if (byName.has(m))
98
+ deps.add(m);
99
+ }
100
+ else {
101
+ deps.add(d);
102
+ }
103
+ }
104
+ nodes.set(obj.name, { name: obj.name, obj, deps });
61
105
  }
62
106
  // Kahn topological sort; objects with unbuilt deps fall back to insertion
63
107
  // order iteration until progress stalls (remaining are baked with reason).
@@ -86,7 +130,7 @@ placements) {
86
130
  const node = nodes.get(name);
87
131
  if (built.has(node.name))
88
132
  continue;
89
- if ([...node.deps].every((d) => built.has(d) || !byName.has(d))) {
133
+ if ([...node.deps].every((d) => built.has(d) || !byName.has(d) || byName.get(d).type === 'PartDesign::Body')) {
90
134
  built.add(node.name);
91
135
  order.push(node.name);
92
136
  progress = true;
@@ -147,16 +191,28 @@ placements) {
147
191
  }
148
192
  continue;
149
193
  }
150
- if (obj.type === 'App::Origin' || obj.type === 'App::Plane' || obj.type === 'App::Line') {
151
- results.push({ name, type: obj.type, calls: [], disposition: 'preserved-only', reason: 'datum' });
194
+ // Non-modeling objects are preserved-only and MUST be short-circuited
195
+ // BEFORE translation. GOTCHA (PadTest, 2026-09-21): this used to list only
196
+ // `App::Origin/Plane/Line`, so a Body's `PartDesign::Plane` went through
197
+ // the translator, matched the shape-asset rule (datum planes store a
198
+ // `Shape` .brp — the plane face) and was folded into the Body's chain:
199
+ // `cad.union(pad, datumPlane)`. That both corrupts the solid and fails at
200
+ // run time (`cad.load` requires a solid; a plane face has none). The
201
+ // predicate is shared with the C4 audit (`structural-types.ts`) so codegen
202
+ // and the disposition ledger can never disagree about what is non-modeling.
203
+ if (isNonModelingType(obj.type)) {
204
+ results.push({ name, type: obj.type, calls: [], disposition: 'preserved-only', reason: 'structural' });
152
205
  continue;
153
206
  }
154
207
  // Sketches are now real face variables (see the Sketcher::SketchObject
155
208
  // branch above), so every dependency that resolves to one flows through.
156
209
  const verdict = translateObject(obj, (dep) => {
157
- const v = variables.get(dep);
158
- return v !== undefined ? v : undefined;
159
- }, doc.objects);
210
+ // GOTCHA (test_geomop corpus, 2026-09-20): a dependency on a Body
211
+ // CONTAINER (Part::Cut with Tool→Body) resolves against the Body's
212
+ // accumulated chain head, not `variables` — the container name is
213
+ // never registered there (its result lives in chainVar).
214
+ return variables.get(dep) ?? chainVar.get(dep);
215
+ }, doc.objects, shapeCarriers, brokenShapeAssets);
160
216
  node.verdict = verdict;
161
217
  if (verdict.kind === 'translated') {
162
218
  // rename output vars to partN sequence
@@ -165,6 +221,15 @@ placements) {
165
221
  variables.set(call.out, v);
166
222
  // remap inputs that were intermediate (Pocket_cut) or named outputs
167
223
  call.inputs = call.inputs.map((i) => variables.get(i) ?? i);
224
+ // GOTCHA (ArchDetail s444, 2026-09-21): `cad.compound` passes its
225
+ // members as LEXICAL VARIABLE NAMES inside `params.members` — they
226
+ // must be remapped like `inputs`, otherwise renderArgs emits them as
227
+ // string literals and the op receives strings (no BREP handle) and
228
+ // throws "compound members are not all on the BREP chain".
229
+ const members = call.params?.members;
230
+ if (Array.isArray(members)) {
231
+ call.params.members = members.map((m) => typeof m === 'string' ? (variables.get(m) ?? m) : m);
232
+ }
168
233
  call.out = v;
169
234
  calls.push(call);
170
235
  }
@@ -185,24 +250,17 @@ placements) {
185
250
  const sketchLink = obj.properties.get('Sketch')?.children[0]?.attributes['value'];
186
251
  const pl = (sketchLink ? placements?.get(sketchLink) : undefined) ?? placements?.get(name);
187
252
  if (lastVar && pl && !isIdentityPlacement(pl)) {
188
- let cur = lastVar;
189
- const euler = quatToEulerXYZDeg(pl.q);
190
- const isRotation = Math.abs(euler[0]) > 1e-9 || Math.abs(euler[1]) > 1e-9 || Math.abs(euler[2]) > 1e-9;
191
- if (isRotation) {
192
- const rv = newVar();
193
- variables.set(cur, rv); // map old name → rotated var for consumers
194
- calls.push({ out: rv, op: 'cad.rotate_euler', source: name, inputs: [cur], params: { anglesDeg: euler } });
195
- cur = rv;
196
- }
197
- const needsTranslate = Math.abs(pl.p[0]) > 1e-9 || Math.abs(pl.p[1]) > 1e-9 || Math.abs(pl.p[2]) > 1e-9;
198
- if (needsTranslate) {
199
- const tv = newVar();
200
- variables.set(cur, tv);
201
- calls.push({ out: tv, op: 'cad.translate', source: name, inputs: [cur], params: { offset: [...pl.p] } });
202
- cur = tv;
203
- }
253
+ // 单个刚性放置:旋转(四元数,绕局部原点)+ 平移 = FreeCAD Placement(P,Q)。
254
+ // 直接发 cad.place,避免 euler 往返损失精度(方案 §4.7:两语句合一)。
255
+ const cur = lastVar;
256
+ const rv = newVar();
257
+ variables.set(cur, rv); // map old name → placed var for consumers
258
+ calls.push({
259
+ out: rv, op: 'cad.place', source: name, inputs: [cur],
260
+ params: { rotation: [...pl.q], position: [...pl.p] },
261
+ });
204
262
  // the object's variable is now the fully placed result
205
- variables.set(name, cur);
263
+ variables.set(name, rv);
206
264
  }
207
265
  // M9.4 (D-C): fold the feature into its Body's chain — AFTER the placement
208
266
  // step so the chain accumulates the PLACED feature shape. Pocket/Cut
@@ -225,6 +283,14 @@ placements) {
225
283
  // (bbox already exact at delta 0).
226
284
  if (prev) {
227
285
  for (const c of verdict.calls) {
286
+ // H7 (hole_puzzle corpus): the Pocket had NO BaseFeature property —
287
+ // the translator emitted BODY_CHAIN_BASE as the subtract's base
288
+ // input; retarget it at the chain head here (after the earlier
289
+ // inputs remap, which leaves the marker untouched since it is not
290
+ // a variable name).
291
+ if (c.inputs) {
292
+ c.inputs = c.inputs.map((i) => (i === BODY_CHAIN_BASE ? prev : i));
293
+ }
228
294
  if (c.op === 'cad.extrude' && c.params.baseFeature !== undefined) {
229
295
  // GOTCHA (PadTest V6 residual): the up-to extrude builds its
230
296
  // prism in the SKETCH-LOCAL frame, but the chain head (prev)
@@ -235,25 +301,24 @@ placements) {
235
301
  // AddShape 48199 vs rebuilt deficit ~9108 mm^3 → 3.2% total).
236
302
  const needsInverse = pl !== undefined && !isIdentityPlacement(pl);
237
303
  if (needsInverse && pl) {
304
+ // 逆向刚性放置(单点发射,方案 §4.7):rotation 取共轭四元数、
305
+ // position 取 invertApplyPlacement(pl, 0) = -(invQ·p),合成为
306
+ // 一个 cad.place,避免 euler 往返。语义 = invertApplyPlacement(pl, prev)。
238
307
  const invQ = [
239
308
  -pl.q[0], -pl.q[1], -pl.q[2], pl.q[3],
240
309
  ];
241
- const invEuler = quatToEulerXYZDeg(invQ);
242
- const tv = newVar();
243
- const tvCall = {
244
- out: tv, op: 'cad.translate', source: name,
245
- inputs: [prev], params: { offset: [-pl.p[0], -pl.p[1], -pl.p[2]] },
246
- };
310
+ const invPos = invertApplyPlacement(pl, [0, 0, 0]);
247
311
  const rv = newVar();
248
312
  const rvCall = {
249
- out: rv, op: 'cad.rotate_euler', source: name,
250
- inputs: [tv], params: { anglesDeg: invEuler },
313
+ out: rv, op: 'cad.place', source: name,
314
+ inputs: [prev],
315
+ params: { rotation: invQ, position: invPos },
251
316
  };
252
317
  // insert BEFORE the extrude call: faijs is a statement
253
318
  // language — `baseFeature: partN` referencing a later
254
319
  // statement is E_REFERENCE (parser rejects forward refs).
255
320
  const at = calls.indexOf(c);
256
- calls.splice(at < 0 ? calls.length : at, 0, tvCall, rvCall);
321
+ calls.splice(at < 0 ? calls.length : at, 0, rvCall);
257
322
  c.params.baseFeature = jsExpr(rv);
258
323
  }
259
324
  else {
@@ -283,6 +348,14 @@ placements) {
283
348
  chainVar.set(body, nv);
284
349
  }
285
350
  }
351
+ // H7 guard (motor_mount_inch corpus): a feature outside a Body (or with
352
+ // no chain head yet) never gets its BODY_CHAIN_BASE retargeted — the
353
+ // marker would leak into the generated JS as an illegal identifier
354
+ // (parser kills the whole file). Downgrade to an explicit gap instead.
355
+ if (verdict.calls.some((c) => c.inputs?.includes(BODY_CHAIN_BASE))) {
356
+ results.push({ name, type: obj.type, calls: [], disposition: 'baked', reason: 'pocket-missing-dependency' });
357
+ continue;
358
+ }
286
359
  results.push({ name, type: obj.type, variable: verdict.calls.at(-1)?.out, calls: verdict.calls, disposition: 'translated', reason: verdict.reason });
287
360
  }
288
361
  else if (verdict.kind === 'baked') {
@@ -302,7 +375,7 @@ placements) {
302
375
  // (including the chain union/subtract calls emitted for it) go to
303
376
  // model/<BodyName>.fai.js; everything else (loose Part features) stays in
304
377
  // main.fai.js. When no Body has geometry, everything lands in main (the
305
- // single-file shape). Aggregate entry groups per-file roots via cad.group.
378
+ // single-file shape). Aggregate entry compounds per-file roots via cad.compound.
306
379
  const callBody = new Map();
307
380
  for (const r of results) {
308
381
  const b = memberToBody.get(r.name);
@@ -330,20 +403,49 @@ placements) {
330
403
  // further down (`let` TDZ ReferenceError at run time: `part8` used `part6`).
331
404
  // Filtering `calls` in place preserves the topological order.
332
405
  const assigned = new Map(); // call.out → Body file name
406
+ // chain-head var → terminal alias (and owning Body) — needed DURING
407
+ // routing: a foreign chain head is never in `assigned` (it is a Body's
408
+ // accumulated var, not a call output), so input-body checks must consult
409
+ // this map to detect cross-Body references.
410
+ const headToTerminal = new Map();
411
+ const headToBody = new Map();
412
+ for (const b of bodiesWithGeo) {
413
+ headToTerminal.set(chainVar.get(b), `${b}_out`);
414
+ headToBody.set(chainVar.get(b), b);
415
+ }
416
+ const mainPending = [];
417
+ const mainOuts = new Set();
333
418
  for (const c of calls) {
334
419
  const own = callBody.get(c.out);
335
- const viaInput = c.inputs.map((inp) => assigned.get(inp)).find((b) => b !== undefined);
420
+ const inputBodies = c.inputs.map((inp) => assigned.get(inp) ?? headToBody.get(inp)).filter((b) => b !== undefined);
421
+ const viaInput = inputBodies[0];
336
422
  const b = own !== undefined && bodiesWithGeo.has(own) ? own : viaInput;
337
- if (b !== undefined && bodiesWithGeo.has(b)) {
423
+ // GOTCHA (test_geomop corpus, 2026-09-20): a call whose own Body
424
+ // differs from an input's Body (Part::Cut with Base in another Body's
425
+ // chain) must NOT be emitted into its own Body file — the input
426
+ // variable lives in ANOTHER module and is not declared there
427
+ // (SEC_FREE_IDENT at check time). Same for inputs that will live in
428
+ // MAIN (loose Part boxes): routing is a forward pass over the
429
+ // topologically sorted calls, so main outputs are already known.
430
+ // NOTE: Body-chain fold calls (union/subtract emitted during folding)
431
+ // are NOT in `results`, so `own` is undefined for them — the main-input
432
+ // check must not depend on `own`.
433
+ const mixed = inputBodies.some((ib) => ib !== b) || (own !== undefined && viaInput !== undefined && viaInput !== own);
434
+ const touchesMain = c.inputs.some((i) => mainOuts.has(i));
435
+ if (b !== undefined && bodiesWithGeo.has(b) && !mixed && !touchesMain && (own === undefined || own === b)) {
338
436
  if (!perBody.has(b))
339
437
  perBody.set(b, []);
340
438
  perBody.get(b).push(c);
341
439
  assigned.set(c.out, b);
342
440
  }
343
441
  else {
344
- mainCalls.push(c);
442
+ mainPending.push(c);
443
+ mainOuts.add(c.out);
345
444
  }
346
445
  }
446
+ for (const c of mainPending) {
447
+ mainCalls.push({ ...c, inputs: c.inputs.map((i) => headToTerminal.get(i) ?? i) });
448
+ }
347
449
  for (const b of bodiesWithGeo) {
348
450
  const bodyCalls = perBody.get(b) ?? [];
349
451
  // terminal: alias the chain head to <Body>_out so the aggregate entry
@@ -355,7 +457,7 @@ placements) {
355
457
  ];
356
458
  files.push({ path: `model/${b}.fai.js`, code: lowerBody(withTerminal, `${baseName}/${b}`, b), body: b });
357
459
  }
358
- // main.fai.js: aggregate the per-Body terminals via cad.group. M10c:
460
+ // main.fai.js: aggregate the per-Body terminals via cad.compound. M10c:
359
461
  // cross-file references close through the standard relative-import
360
462
  // contract (module-registry D6) — each Body module's terminal alias
361
463
  // `<Body>_out` is its live shape, so a named import binds it.
@@ -364,7 +466,7 @@ placements) {
364
466
  lines.push(`// Units: mm (faijs contract; FCStd internal units are mm)`);
365
467
  const members = [...bodiesWithGeo].map((b) => `${b}_out`);
366
468
  // Always import the per-Body terminals: the aggregate entry references
367
- // `<Body>_out` in BOTH the cad.group (multi-Body) and the single-Body alias
469
+ // `<Body>_out` in BOTH the cad.compound (multi-Body) and the single-Body alias
368
470
  // path, so a missing import is a SEC_FREE_IDENT parse error (GOTCHA: the
369
471
  // single-Body branch previously emitted `let part_out = <Body>_out;` with
370
472
  // no import). `mainCalls` may still be empty here.
@@ -379,7 +481,7 @@ placements) {
379
481
  }
380
482
  if (members.length > 1) {
381
483
  rootVar = 'part_out';
382
- lines.push(`let part_out = cad.group({ members: [${members.join(', ')}] });`);
484
+ lines.push(`let part_out = cad.compound({ members: [${members.join(', ')}] });`);
383
485
  }
384
486
  else if (members.length === 1) {
385
487
  // single Body: the imported terminal IS the result — alias keeps a
@@ -428,7 +530,7 @@ function lower(calls, baseName) {
428
530
  const consumed = new Set(calls.flatMap((c) => c.inputs));
429
531
  const roots = calls.filter((c) => !consumed.has(c.out)).map((c) => c.out);
430
532
  if (roots.length > 1) {
431
- lines.push(`let part_out = cad.group({ members: [${roots.join(', ')}] });`);
533
+ lines.push(`let part_out = cad.compound({ members: [${roots.join(', ')}] });`);
432
534
  }
433
535
  else if (roots.length === 0) {
434
536
  lines.push(`// no translated geometry (all baked)`);
@@ -469,13 +571,22 @@ function containsJsExpr(v) {
469
571
  }
470
572
  function renderArgs(call) {
471
573
  const positional = [
472
- ...call.inputs.map((i) => i),
574
+ ...(call.noPositionalArgs ? [] : call.inputs.map((i) => i)),
473
575
  ...(call.literals ?? []).map((l) => renderValue(l)),
474
576
  ].filter((s) => s.length > 0); // M7.1: drop empty entries so we never emit `(, `
475
577
  const named = [];
476
578
  for (const [k, v] of Object.entries(call.params ?? {})) {
477
579
  if (v === undefined)
478
580
  continue;
581
+ // GOTCHA (ArchDetail s444, 2026-09-21): `cad.compound`'s `members` are
582
+ // LEXICAL VARIABLE NAMES, not data strings — render them bare, like
583
+ // positional inputs. renderValue would quote them and the op would
584
+ // receive strings with no BREP handle
585
+ // ("compound members are not all on the BREP chain").
586
+ if (k === 'members' && Array.isArray(v)) {
587
+ named.push(`members: [${v.join(', ')}]`);
588
+ continue;
589
+ }
479
590
  named.push(`${k}: ${renderValue(v)}`);
480
591
  }
481
592
  const namedBlock = named.length ? `{ ${named.join(', ')} }` : '';