@faicad/faijs 0.18.3 → 0.22.2

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 (241) hide show
  1. package/dist/api/api-namespace.d.ts.map +1 -1
  2. package/dist/api/api-namespace.js +4 -1
  3. package/dist/api/api-namespace.js.map +1 -1
  4. package/dist/api/brep-mirror/joinery-brep.d.ts +1 -1
  5. package/dist/api/brep-mirror/joinery-brep.d.ts.map +1 -1
  6. package/dist/api/brep-mirror/joinery-brep.js +22 -20
  7. package/dist/api/brep-mirror/joinery-brep.js.map +1 -1
  8. package/dist/api/generated/script-face-manifest.d.ts +4 -0
  9. package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
  10. package/dist/api/generated/script-face-manifest.js.map +1 -1
  11. package/dist/api/index.d.ts +4 -0
  12. package/dist/api/index.d.ts.map +1 -1
  13. package/dist/api/index.js +3 -0
  14. package/dist/api/index.js.map +1 -1
  15. package/dist/api/knurl.d.ts.map +1 -1
  16. package/dist/api/knurl.js +1 -0
  17. package/dist/api/knurl.js.map +1 -1
  18. package/dist/api/primitives.d.ts +4 -3
  19. package/dist/api/primitives.d.ts.map +1 -1
  20. package/dist/api/primitives.js +9 -3
  21. package/dist/api/primitives.js.map +1 -1
  22. package/dist/api/profile.d.ts +34 -5
  23. package/dist/api/profile.d.ts.map +1 -1
  24. package/dist/api/profile.js +168 -186
  25. package/dist/api/profile.js.map +1 -1
  26. package/dist/api/punch-hole.d.ts +77 -0
  27. package/dist/api/punch-hole.d.ts.map +1 -0
  28. package/dist/api/punch-hole.js +207 -0
  29. package/dist/api/punch-hole.js.map +1 -0
  30. package/dist/api/sketch-on-face.d.ts +85 -0
  31. package/dist/api/sketch-on-face.d.ts.map +1 -0
  32. package/dist/api/sketch-on-face.js +136 -0
  33. package/dist/api/sketch-on-face.js.map +1 -0
  34. package/dist/api/sketch-on-plane.d.ts +87 -0
  35. package/dist/api/sketch-on-plane.d.ts.map +1 -0
  36. package/dist/api/sketch-on-plane.js +106 -0
  37. package/dist/api/sketch-on-plane.js.map +1 -0
  38. package/dist/api/surface/arg-spec.d.ts +8 -0
  39. package/dist/api/surface/arg-spec.d.ts.map +1 -1
  40. package/dist/api/surface/arg-spec.js.map +1 -1
  41. package/dist/api/transform.d.ts +5 -5
  42. package/dist/api/transform.d.ts.map +1 -1
  43. package/dist/api/transform.js +12 -10
  44. package/dist/api/transform.js.map +1 -1
  45. package/dist/api/xml-dom.d.ts +27 -0
  46. package/dist/api/xml-dom.d.ts.map +1 -0
  47. package/dist/api/xml-dom.js +45 -0
  48. package/dist/api/xml-dom.js.map +1 -0
  49. package/dist/boolean/csg-backend.d.ts +6 -6
  50. package/dist/boolean/csg-backend.d.ts.map +1 -1
  51. package/dist/boolean/csg-backend.js +9 -9
  52. package/dist/boolean/csg-backend.js.map +1 -1
  53. package/dist/boolean/csg-core.d.ts +10 -10
  54. package/dist/boolean/csg-core.d.ts.map +1 -1
  55. package/dist/boolean/csg-core.js +24 -24
  56. package/dist/boolean/csg-core.js.map +1 -1
  57. package/dist/boolean/extrude-helpers.d.ts +8 -8
  58. package/dist/boolean/extrude-helpers.d.ts.map +1 -1
  59. package/dist/boolean/extrude-helpers.js +13 -13
  60. package/dist/boolean/extrude-helpers.js.map +1 -1
  61. package/dist/brep/brep-ops.d.ts +24 -10
  62. package/dist/brep/brep-ops.d.ts.map +1 -1
  63. package/dist/brep/brep-ops.js +46 -18
  64. package/dist/brep/brep-ops.js.map +1 -1
  65. package/dist/brep/brep-topology.d.ts.map +1 -1
  66. package/dist/brep/brep-topology.js +3 -1
  67. package/dist/brep/brep-topology.js.map +1 -1
  68. package/dist/brep/export/export-model.d.ts +79 -0
  69. package/dist/brep/export/export-model.d.ts.map +1 -0
  70. package/dist/brep/export/export-model.js +335 -0
  71. package/dist/brep/export/export-model.js.map +1 -0
  72. package/dist/brep/primitives-brep.d.ts.map +1 -1
  73. package/dist/brep/primitives-brep.js +3 -1
  74. package/dist/brep/primitives-brep.js.map +1 -1
  75. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  76. package/dist/brepkit-kernel/brepkitKernel.js +4 -2
  77. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  78. package/dist/browser-host/csg-worker-protocol.d.ts +3 -3
  79. package/dist/browser-host/csg-worker-protocol.d.ts.map +1 -1
  80. package/dist/browser-host/csg-worker-protocol.js.map +1 -1
  81. package/dist/browser-host/csg-worker.js +3 -3
  82. package/dist/browser-host/csg-worker.js.map +1 -1
  83. package/dist/browser-host/inline-csg-backend.d.ts +3 -3
  84. package/dist/browser-host/inline-csg-backend.d.ts.map +1 -1
  85. package/dist/browser-host/inline-csg-backend.js +3 -3
  86. package/dist/browser-host/inline-csg-backend.js.map +1 -1
  87. package/dist/browser-host/worker-csg-backend.d.ts +3 -3
  88. package/dist/browser-host/worker-csg-backend.d.ts.map +1 -1
  89. package/dist/browser-host/worker-csg-backend.js.map +1 -1
  90. package/dist/cad-runtime/direct-executor.d.ts +4 -0
  91. package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
  92. package/dist/cad-runtime/direct-executor.js +17 -1
  93. package/dist/cad-runtime/direct-executor.js.map +1 -1
  94. package/dist/cad-runtime/ports.d.ts +3 -3
  95. package/dist/cad-runtime/ports.d.ts.map +1 -1
  96. package/dist/cad-runtime/ports.js.map +1 -1
  97. package/dist/cad-runtime/runtime.d.ts +24 -1
  98. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  99. package/dist/cad-runtime/runtime.js +53 -3
  100. package/dist/cad-runtime/runtime.js.map +1 -1
  101. package/dist/define-op.d.ts +22 -0
  102. package/dist/define-op.d.ts.map +1 -1
  103. package/dist/define-op.js +15 -0
  104. package/dist/define-op.js.map +1 -1
  105. package/dist/env-agnostic.d.ts +7 -7
  106. package/dist/env-agnostic.d.ts.map +1 -1
  107. package/dist/env-agnostic.js +3 -3
  108. package/dist/env-agnostic.js.map +1 -1
  109. package/dist/geometry2d/adapt.d.ts +46 -0
  110. package/dist/geometry2d/adapt.d.ts.map +1 -0
  111. package/dist/geometry2d/adapt.js +41 -0
  112. package/dist/geometry2d/adapt.js.map +1 -0
  113. package/dist/geometry2d/bbox2d.d.ts +82 -0
  114. package/dist/geometry2d/bbox2d.d.ts.map +1 -0
  115. package/dist/geometry2d/bbox2d.js +123 -0
  116. package/dist/geometry2d/bbox2d.js.map +1 -0
  117. package/dist/geometry2d/blueprint-sketcher.d.ts +27 -0
  118. package/dist/geometry2d/blueprint-sketcher.d.ts.map +1 -0
  119. package/dist/geometry2d/blueprint-sketcher.js +29 -0
  120. package/dist/geometry2d/blueprint-sketcher.js.map +1 -0
  121. package/dist/geometry2d/blueprint.d.ts +102 -0
  122. package/dist/geometry2d/blueprint.d.ts.map +1 -0
  123. package/dist/geometry2d/blueprint.js +225 -0
  124. package/dist/geometry2d/blueprint.js.map +1 -0
  125. package/dist/geometry2d/blueprints.d.ts +58 -0
  126. package/dist/geometry2d/blueprints.d.ts.map +1 -0
  127. package/dist/geometry2d/blueprints.js +72 -0
  128. package/dist/geometry2d/blueprints.js.map +1 -0
  129. package/dist/geometry2d/bridge/lift-on-plane.d.ts +50 -0
  130. package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -0
  131. package/dist/geometry2d/bridge/lift-on-plane.js +78 -0
  132. package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -0
  133. package/dist/geometry2d/bridge/on-surface.d.ts +98 -0
  134. package/dist/geometry2d/bridge/on-surface.d.ts.map +1 -0
  135. package/dist/geometry2d/bridge/on-surface.js +94 -0
  136. package/dist/geometry2d/bridge/on-surface.js.map +1 -0
  137. package/dist/geometry2d/bridge/plane.d.ts +69 -0
  138. package/dist/geometry2d/bridge/plane.d.ts.map +1 -0
  139. package/dist/geometry2d/bridge/plane.js +96 -0
  140. package/dist/geometry2d/bridge/plane.js.map +1 -0
  141. package/dist/geometry2d/canned-blueprints.d.ts +44 -0
  142. package/dist/geometry2d/canned-blueprints.d.ts.map +1 -0
  143. package/dist/geometry2d/canned-blueprints.js +94 -0
  144. package/dist/geometry2d/canned-blueprints.js.map +1 -0
  145. package/dist/geometry2d/compound-blueprint.d.ts +72 -0
  146. package/dist/geometry2d/compound-blueprint.d.ts.map +1 -0
  147. package/dist/geometry2d/compound-blueprint.js +95 -0
  148. package/dist/geometry2d/compound-blueprint.js.map +1 -0
  149. package/dist/geometry2d/curve2d.d.ts +298 -0
  150. package/dist/geometry2d/curve2d.d.ts.map +1 -0
  151. package/dist/geometry2d/curve2d.js +1110 -0
  152. package/dist/geometry2d/curve2d.js.map +1 -0
  153. package/dist/geometry2d/index.d.ts +22 -0
  154. package/dist/geometry2d/index.d.ts.map +1 -0
  155. package/dist/geometry2d/index.js +21 -0
  156. package/dist/geometry2d/index.js.map +1 -0
  157. package/dist/geometry2d/organise.d.ts +28 -0
  158. package/dist/geometry2d/organise.d.ts.map +1 -0
  159. package/dist/geometry2d/organise.js +108 -0
  160. package/dist/geometry2d/organise.js.map +1 -0
  161. package/dist/geometry2d/pen-sketcher.d.ts +365 -0
  162. package/dist/geometry2d/pen-sketcher.d.ts.map +1 -0
  163. package/dist/geometry2d/pen-sketcher.js +520 -0
  164. package/dist/geometry2d/pen-sketcher.js.map +1 -0
  165. package/dist/geometry2d/point.d.ts +44 -0
  166. package/dist/geometry2d/point.d.ts.map +1 -0
  167. package/dist/geometry2d/point.js +57 -0
  168. package/dist/geometry2d/point.js.map +1 -0
  169. package/dist/geometry2d/svg-ellipse.d.ts +74 -0
  170. package/dist/geometry2d/svg-ellipse.d.ts.map +1 -0
  171. package/dist/geometry2d/svg-ellipse.js +163 -0
  172. package/dist/geometry2d/svg-ellipse.js.map +1 -0
  173. package/dist/lang/code-to-args.d.ts +29 -3
  174. package/dist/lang/code-to-args.d.ts.map +1 -1
  175. package/dist/lang/code-to-args.js +41 -2
  176. package/dist/lang/code-to-args.js.map +1 -1
  177. package/dist/lang/codegen.d.ts +45 -1
  178. package/dist/lang/codegen.d.ts.map +1 -1
  179. package/dist/lang/codegen.js +135 -6
  180. package/dist/lang/codegen.js.map +1 -1
  181. package/dist/lang/host-arg.d.ts +6 -0
  182. package/dist/lang/host-arg.d.ts.map +1 -1
  183. package/dist/lang/host-arg.js.map +1 -1
  184. package/dist/lang/metadata-extractor.d.ts +25 -0
  185. package/dist/lang/metadata-extractor.d.ts.map +1 -1
  186. package/dist/lang/metadata-extractor.js +289 -25
  187. package/dist/lang/metadata-extractor.js.map +1 -1
  188. package/dist/lang/parse-error.d.ts +7 -1
  189. package/dist/lang/parse-error.d.ts.map +1 -1
  190. package/dist/lang/parse-error.js.map +1 -1
  191. package/dist/lang/security-scanner.d.ts +9 -1
  192. package/dist/lang/security-scanner.d.ts.map +1 -1
  193. package/dist/lang/security-scanner.js +38 -1
  194. package/dist/lang/security-scanner.js.map +1 -1
  195. package/dist/lang/symbol-table.generated.d.ts +3 -0
  196. package/dist/lang/symbol-table.generated.d.ts.map +1 -1
  197. package/dist/lang/symbol-table.generated.js +3 -0
  198. package/dist/lang/symbol-table.generated.js.map +1 -1
  199. package/dist/mesh/io.d.ts +62 -9
  200. package/dist/mesh/io.d.ts.map +1 -1
  201. package/dist/mesh/io.js +124 -17
  202. package/dist/mesh/io.js.map +1 -1
  203. package/dist/mesh/threemf-loader.d.ts +15 -0
  204. package/dist/mesh/threemf-loader.d.ts.map +1 -0
  205. package/dist/mesh/threemf-loader.js +163 -0
  206. package/dist/mesh/threemf-loader.js.map +1 -0
  207. package/dist/mesh/transform.d.ts +2 -2
  208. package/dist/mesh/transform.d.ts.map +1 -1
  209. package/dist/mesh/transform.js +3 -3
  210. package/dist/mesh/transform.js.map +1 -1
  211. package/dist/mesh/types.d.ts +2 -2
  212. package/dist/mesh/types.d.ts.map +1 -1
  213. package/dist/mesh/types.js.map +1 -1
  214. package/dist/node-host/cli.d.ts.map +1 -1
  215. package/dist/node-host/cli.js +53 -26
  216. package/dist/node-host/cli.js.map +1 -1
  217. package/dist/occt-kernel/occtKernel.d.ts.map +1 -1
  218. package/dist/occt-kernel/occtKernel.js +4 -2
  219. package/dist/occt-kernel/occtKernel.js.map +1 -1
  220. package/dist/occt-kernel/topologyExt.d.ts.map +1 -1
  221. package/dist/occt-kernel/topologyExt.js +4 -1
  222. package/dist/occt-kernel/topologyExt.js.map +1 -1
  223. package/dist/runtime-state.d.ts +18 -0
  224. package/dist/runtime-state.d.ts.map +1 -1
  225. package/dist/runtime-state.js +23 -0
  226. package/dist/runtime-state.js.map +1 -1
  227. package/dist/sdk.d.ts +1 -1
  228. package/dist/sdk.d.ts.map +1 -1
  229. package/dist/sdk.js +1 -1
  230. package/dist/sdk.js.map +1 -1
  231. package/dist/tolerance.d.ts +38 -0
  232. package/dist/tolerance.d.ts.map +1 -0
  233. package/dist/tolerance.js +40 -0
  234. package/dist/tolerance.js.map +1 -0
  235. package/dist/topology/naming/score.js +3 -3
  236. package/dist/topology/naming/score.js.map +1 -1
  237. package/dist/units.d.ts +248 -0
  238. package/dist/units.d.ts.map +1 -0
  239. package/dist/units.js +397 -0
  240. package/dist/units.js.map +1 -0
  241. package/package.json +19 -2
@@ -20,6 +20,7 @@ import { isHostVarRef, isHostParamRef, isHostCallRef, isHostExprRef } from './ho
20
20
  import { ParseError } from './parse-error.js';
21
21
  import { fnv1a32 } from './fnv-hash.js';
22
22
  import { assertSecure } from './security-scanner.js';
23
+ import { SCRIPT_UNIT_NAMES, UNIT_DIM, UNIT_SCALE, SCRIPT_UNIT_TO_NAME } from '../units.js';
23
24
  const CONTROL_FLOW_TYPES = new Set([
24
25
  'IfStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
25
26
  'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'TryStatement',
@@ -36,8 +37,8 @@ function derivePackageName(specifier) {
36
37
  }
37
38
  return specifier.split('/')[0];
38
39
  }
39
- function numFold(v, usedParam = false) {
40
- return Number.isFinite(v) ? { ok: true, value: v, usedParam } : { ok: false };
40
+ function numFold(v, usedParam = false, usedUnit = false) {
41
+ return Number.isFinite(v) ? { ok: true, value: v, usedParam, usedUnit } : { ok: false };
41
42
  }
42
43
  function looseEq(a, b) {
43
44
  let x = a;
@@ -57,12 +58,23 @@ function looseEq(a, b) {
57
58
  function tryFoldConstExpr(node, symbols) {
58
59
  switch (node.type) {
59
60
  case 'Literal':
60
- return { ok: true, value: node.value, usedParam: false };
61
+ return { ok: true, value: node.value, usedParam: false, usedUnit: false };
61
62
  case 'Identifier': {
63
+ // P7/D10.4: unit constants (MM, INCH, DEGREE, …) are recognized as
64
+ // foldable constants. Their values come from UNIT_SCALE (the single
65
+ // source of truth). The `usedUnit` flag is set so that `parseValueExpr`
66
+ // can mark the line as computed (host read-only downgrade), preventing
67
+ // the folded base value (e.g. 254) from being written back to source.
68
+ if (SCRIPT_UNIT_NAMES.has(node.name)) {
69
+ const unitName = SCRIPT_UNIT_TO_NAME[node.name];
70
+ if (unitName) {
71
+ return { ok: true, value: UNIT_SCALE[unitName], usedParam: false, usedUnit: true };
72
+ }
73
+ }
62
74
  if (!symbols.paramNames.has(node.name))
63
75
  return { ok: false };
64
76
  const v = symbols.paramValues.get(node.name);
65
- return v === undefined ? { ok: false } : { ok: true, value: v, usedParam: true };
77
+ return v === undefined ? { ok: false } : { ok: true, value: v, usedParam: true, usedUnit: false };
66
78
  }
67
79
  case 'UnaryExpression': {
68
80
  const operand = tryFoldConstExpr(node.argument, symbols);
@@ -70,11 +82,11 @@ function tryFoldConstExpr(node, symbols) {
70
82
  return { ok: false };
71
83
  const v = operand.value;
72
84
  switch (node.operator) {
73
- case '-': return typeof v === 'number' ? numFold(-v, operand.usedParam) : { ok: false };
74
- case '+': return typeof v === 'number' ? numFold(+v, operand.usedParam) : { ok: false };
75
- case '!': return { ok: true, value: !v, usedParam: operand.usedParam };
76
- case '~': return typeof v === 'number' ? { ok: true, value: ~v, usedParam: operand.usedParam } : { ok: false };
77
- case 'typeof': return { ok: true, value: typeof v, usedParam: operand.usedParam };
85
+ case '-': return typeof v === 'number' ? numFold(-v, operand.usedParam, operand.usedUnit) : { ok: false };
86
+ case '+': return typeof v === 'number' ? numFold(+v, operand.usedParam, operand.usedUnit) : { ok: false };
87
+ case '!': return { ok: true, value: !v, usedParam: operand.usedParam, usedUnit: operand.usedUnit };
88
+ case '~': return typeof v === 'number' ? { ok: true, value: ~v, usedParam: operand.usedParam, usedUnit: operand.usedUnit } : { ok: false };
89
+ case 'typeof': return { ok: true, value: typeof v, usedParam: operand.usedParam, usedUnit: operand.usedUnit };
78
90
  default: return { ok: false };
79
91
  }
80
92
  }
@@ -88,20 +100,21 @@ function tryFoldConstExpr(node, symbols) {
88
100
  const a = left.value;
89
101
  const b = right.value;
90
102
  const usedParam = left.usedParam || right.usedParam;
91
- const numBin = (op) => typeof a === 'number' && typeof b === 'number' ? numFold(op(a, b), usedParam) : { ok: false };
103
+ const usedUnit = left.usedUnit || right.usedUnit;
104
+ const numBin = (op) => typeof a === 'number' && typeof b === 'number' ? numFold(op(a, b), usedParam, usedUnit) : { ok: false };
92
105
  const cmpBin = (op) => {
93
106
  if (typeof a === 'number' && typeof b === 'number')
94
- return { ok: true, value: op(a, b), usedParam };
107
+ return { ok: true, value: op(a, b), usedParam, usedUnit };
95
108
  if (typeof a === 'string' && typeof b === 'string')
96
- return { ok: true, value: op(a, b), usedParam };
109
+ return { ok: true, value: op(a, b), usedParam, usedUnit };
97
110
  return { ok: false };
98
111
  };
99
112
  switch (node.operator) {
100
113
  case '+': {
101
114
  if (typeof a === 'number' && typeof b === 'number')
102
- return numFold(a + b, usedParam);
115
+ return numFold(a + b, usedParam, usedUnit);
103
116
  if (typeof a === 'string' || typeof b === 'string')
104
- return { ok: true, value: String(a) + String(b), usedParam };
117
+ return { ok: true, value: String(a) + String(b), usedParam, usedUnit };
105
118
  return { ok: false };
106
119
  }
107
120
  case '-': return numBin((x, y) => x - y);
@@ -113,10 +126,10 @@ function tryFoldConstExpr(node, symbols) {
113
126
  case '<=': return cmpBin((x, y) => x <= y);
114
127
  case '>': return cmpBin((x, y) => x > y);
115
128
  case '>=': return cmpBin((x, y) => x >= y);
116
- case '==': return { ok: true, value: looseEq(a, b), usedParam };
117
- case '!=': return { ok: true, value: !looseEq(a, b), usedParam };
118
- case '===': return { ok: true, value: a === b, usedParam };
119
- case '!==': return { ok: true, value: a !== b, usedParam };
129
+ case '==': return { ok: true, value: looseEq(a, b), usedParam, usedUnit };
130
+ case '!=': return { ok: true, value: !looseEq(a, b), usedParam, usedUnit };
131
+ case '===': return { ok: true, value: a === b, usedParam, usedUnit };
132
+ case '!==': return { ok: true, value: a !== b, usedParam, usedUnit };
120
133
  default: return { ok: false };
121
134
  }
122
135
  }
@@ -129,10 +142,11 @@ function tryFoldConstExpr(node, symbols) {
129
142
  return { ok: false };
130
143
  const a = left.value;
131
144
  const usedParam = left.usedParam || right.usedParam;
145
+ const usedUnit = left.usedUnit || right.usedUnit;
132
146
  switch (node.operator) {
133
- case '&&': return { ok: true, value: a ? right.value : left.value, usedParam };
134
- case '||': return { ok: true, value: a ? left.value : right.value, usedParam };
135
- case '??': return { ok: true, value: a === null ? right.value : left.value, usedParam };
147
+ case '&&': return { ok: true, value: a ? right.value : left.value, usedParam, usedUnit };
148
+ case '||': return { ok: true, value: a ? left.value : right.value, usedParam, usedUnit };
149
+ case '??': return { ok: true, value: a === null ? right.value : left.value, usedParam, usedUnit };
136
150
  default: return { ok: false };
137
151
  }
138
152
  }
@@ -141,6 +155,7 @@ function tryFoldConstExpr(node, symbols) {
141
155
  const quasis = node.quasis;
142
156
  const exprs = node.expressions;
143
157
  let usedParam = false;
158
+ let usedUnit = false;
144
159
  for (let i = 0; i < quasis.length; i++) {
145
160
  const cooked = quasis[i].value?.cooked;
146
161
  parts.push(cooked ?? '');
@@ -149,10 +164,11 @@ function tryFoldConstExpr(node, symbols) {
149
164
  if (!r.ok)
150
165
  return { ok: false };
151
166
  usedParam = usedParam || r.usedParam;
167
+ usedUnit = usedUnit || r.usedUnit;
152
168
  parts.push(String(r.value));
153
169
  }
154
170
  }
155
- return { ok: true, value: parts.join(''), usedParam };
171
+ return { ok: true, value: parts.join(''), usedParam, usedUnit };
156
172
  }
157
173
  case 'ConditionalExpression': {
158
174
  const test = tryFoldConstExpr(node.test, symbols);
@@ -161,7 +177,7 @@ function tryFoldConstExpr(node, symbols) {
161
177
  const chosen = tryFoldConstExpr(test.value ? node.consequent : node.alternate, symbols);
162
178
  if (!chosen.ok)
163
179
  return { ok: false };
164
- return { ok: true, value: chosen.value, usedParam: test.usedParam || chosen.usedParam };
180
+ return { ok: true, value: chosen.value, usedParam: test.usedParam || chosen.usedParam, usedUnit: test.usedUnit || chosen.usedUnit };
165
181
  }
166
182
  default:
167
183
  return { ok: false };
@@ -240,6 +256,11 @@ export function collectExprIdentifiers(node, symbols, line, params, refs, opts)
240
256
  else if (symbols.declared.has(name)) {
241
257
  refs.add(name);
242
258
  }
259
+ else if (SCRIPT_UNIT_NAMES.has(name)) {
260
+ // P6/D7: unit constants (MM, INCH, DEGREE, …) are global read-only constants.
261
+ // Treat as a known reference — do not throw E_REFERENCE.
262
+ refs.add(name);
263
+ }
243
264
  else if (opts?.lenient === true) {
244
265
  // lenient:未知标识符跳过(ArgSource 记录路径,保证提取不抛新错)
245
266
  }
@@ -310,6 +331,130 @@ function isExpressionNode(node) {
310
331
  return false;
311
332
  return true;
312
333
  }
334
+ // ── P6/D8: 静态量纲校验(dimension pass) ──
335
+ /**
336
+ * P6/D8 R4: Determine the dimension of an AST expression node.
337
+ *
338
+ * Returns the DimName if the expression can be statically determined to carry
339
+ * a dimension, `'bare'` for a bare number literal, or `null` if it cannot be
340
+ * determined (R5: pass through).
341
+ */
342
+ function inferDim(node) {
343
+ if (!node)
344
+ return null;
345
+ switch (node.type) {
346
+ case 'Literal':
347
+ if (typeof node.value === 'number')
348
+ return 'bare';
349
+ return null;
350
+ case 'UnaryExpression':
351
+ if (node.operator === '-' || node.operator === '+' || node.operator === '~') {
352
+ return inferDim(node.argument);
353
+ }
354
+ return null;
355
+ case 'Identifier':
356
+ // Unit constant? → its dimension
357
+ if (SCRIPT_UNIT_NAMES.has(node.name)) {
358
+ const unitName = SCRIPT_UNIT_TO_NAME[node.name];
359
+ return unitName ? (UNIT_DIM[unitName] ?? null) : null;
360
+ }
361
+ // Declared variable or parameter → cannot determine (R5: pass through)
362
+ return null;
363
+ case 'BinaryExpression': {
364
+ const left = inferDim(node.left);
365
+ const right = inferDim(node.right);
366
+ switch (node.operator) {
367
+ case '*': {
368
+ // number * unitConst → unit's dim; unitConst * number → same
369
+ if (left === 'bare' && right && right !== 'bare')
370
+ return right;
371
+ if (right === 'bare' && left && left !== 'bare')
372
+ return left;
373
+ // both bare → bare (dimensionless multiply)
374
+ if (left === 'bare' && right === 'bare')
375
+ return 'bare';
376
+ // both have dims → compound (length * length → area); pass through
377
+ return null;
378
+ }
379
+ case '/': {
380
+ // same dim / same dim → dimensionless (bare)
381
+ if (left && right && left === right && left !== 'bare')
382
+ return 'bare';
383
+ // dim / bare → dim
384
+ if (left && left !== 'bare' && right === 'bare')
385
+ return left;
386
+ // bare / dim → 1/dim (not a simple dim); pass through
387
+ if (left === 'bare' && right && right !== 'bare')
388
+ return null;
389
+ if (left === 'bare' && right === 'bare')
390
+ return 'bare';
391
+ return null;
392
+ }
393
+ case '+':
394
+ case '-': {
395
+ // Both sides must have the same dimension (R3)
396
+ if (left === right)
397
+ return left;
398
+ // R3: if both sides are determinable but differ → mismatch.
399
+ // But inferDim can't report errors — it returns the dim or null.
400
+ // We return a special sentinel 'mismatch' to signal checkDim.
401
+ if (left && right && left !== 'bare' && right !== 'bare' && left !== right) {
402
+ return 'mismatch';
403
+ }
404
+ // If one is bare and the other has a dim → the result has that dim
405
+ if (left === 'bare' && right && right !== 'bare')
406
+ return right;
407
+ if (right === 'bare' && left && left !== 'bare')
408
+ return left;
409
+ // If either is null (unknown), pass through (R5)
410
+ return null;
411
+ }
412
+ default:
413
+ return null;
414
+ }
415
+ }
416
+ case 'ParenthesizedExpression':
417
+ return inferDim(node.expression);
418
+ case 'ArrayExpression':
419
+ // vec3 — check first element for dimensionality
420
+ if (node.elements && node.elements.length > 0 && node.elements[0]) {
421
+ return inferDim(node.elements[0]);
422
+ }
423
+ return null;
424
+ default:
425
+ return null;
426
+ }
427
+ }
428
+ /**
429
+ * P6/D8: Check if an argument expression has the required dimension.
430
+ *
431
+ * - R2: bare number literal on a dimensioned slot → E_DIM_BARE_NUMBER
432
+ * - R3: mismatched dimensions → E_DIM_MISMATCH
433
+ * - R5: cannot determine → pass (no error)
434
+ *
435
+ * Appends errors to ctx.dimErrors (does not throw — extraction continues).
436
+ */
437
+ function checkDim(node, expectedDim, ctx) {
438
+ const actualDim = inferDim(node);
439
+ // R5: cannot determine → pass through
440
+ if (actualDim === null)
441
+ return;
442
+ // R3: mismatched dimensions within the expression (e.g. mm + degree)
443
+ if (actualDim === 'mismatch') {
444
+ ctx.dimErrors.push(new ParseError(`dimension mismatch in expression: "${ctx.sourceText.slice(node.start, node.end)}"`, lineOf(node), 'E_DIM_MISMATCH'));
445
+ return;
446
+ }
447
+ // R2: bare number on dimensioned slot
448
+ if (actualDim === 'bare') {
449
+ ctx.dimErrors.push(new ParseError(`bare number literal requires unit suffix (expected ${expectedDim}): "${ctx.sourceText.slice(node.start, node.end)}"`, lineOf(node), 'E_DIM_BARE_NUMBER'));
450
+ return;
451
+ }
452
+ // R3: dimension mismatch with expected
453
+ if (actualDim !== expectedDim) {
454
+ ctx.dimErrors.push(new ParseError(`dimension mismatch: expected ${expectedDim}, got ${actualDim} in "${ctx.sourceText.slice(node.start, node.end)}"`, lineOf(node), 'E_DIM_MISMATCH'));
455
+ return;
456
+ }
457
+ }
313
458
  /**
314
459
  * 在折叠之前记录一条参数槽(ArgSource)。
315
460
  *
@@ -358,7 +503,10 @@ function parseValueExpr(node, ctx, path) {
358
503
  const r = tryFoldConstExpr(node, ctx.symbols);
359
504
  if (r.ok) {
360
505
  // F1-E1:仅折叠表达式引用参数才标 computed;纯字面量(负字面量)不标
361
- if (r.usedParam)
506
+ // P7/D10.4: unit constants (usedUnit) also mark computed — the folded
507
+ // base value (e.g. 254 for `10 * INCH`) must NOT be written back to
508
+ // source code (would lose the unit and change geometry in non-base contexts).
509
+ if (r.usedParam || r.usedUnit)
362
510
  ctx.computed.value = true;
363
511
  return r.value;
364
512
  }
@@ -948,6 +1096,8 @@ export function extractMetadata(code, options) {
948
1096
  let terminalShapes;
949
1097
  // P0-B:参数槽记录累积(跨语句共享,语句顺序追加)
950
1098
  const argSources = [];
1099
+ // P6/D8: dimension 校验错误累积(跨语句共享)
1100
+ const allDimErrors = [];
951
1101
  // ── 逐顶层语句分类 ──
952
1102
  for (const stmtNode of bodyNodes) {
953
1103
  const rawLine = lineOf(stmtNode);
@@ -961,6 +1111,8 @@ export function extractMetadata(code, options) {
961
1111
  stmtId: asStmtId(`s${line}`),
962
1112
  codeOffset,
963
1113
  argSources,
1114
+ opDims: options?.opDims,
1115
+ dimErrors: allDimErrors,
964
1116
  };
965
1117
  switch (stmtNode.type) {
966
1118
  case 'ImportDeclaration':
@@ -1097,16 +1249,22 @@ export function extractMetadata(code, options) {
1097
1249
  }
1098
1250
  }
1099
1251
  }
1100
- // P0-B:名称集合 = 参数 ∪ 已声明变量 ∪ 命名空间绑定 ∪ 本地函数名(字典序去重)。
1252
+ // P0-B:名称集合 = 参数 ∪ 已声明变量 ∪ 命名空间绑定 ∪ 本地函数名 ∪ 单位常量(字典序去重)。
1101
1253
  // 宿主把「参数表达式」中的未知标识符当作参数名(期望名)处理时用它做联想。
1254
+ // P6/D7:单位常量名(MM, INCH, DEGREE, …)加入 knownNames,使表达式实时校验通过。
1102
1255
  const names = [
1103
1256
  ...new Set([
1104
1257
  ...symbols.paramNames,
1105
1258
  ...symbols.declared,
1106
1259
  ...symbols.nsBindings.keys(),
1107
1260
  ...symbols.localFnParams.keys(),
1261
+ ...SCRIPT_UNIT_NAMES,
1108
1262
  ]),
1109
1263
  ].sort();
1264
+ // P6/D8: dimension 校验——如果有错误,抛出第一个(用 stage='dimension' 包装)
1265
+ if (allDimErrors.length > 0) {
1266
+ throw allDimErrors[0];
1267
+ }
1110
1268
  return { lines, params, imports, functions, blocks, keep: symbols.keep, meta, terminalShapes, argSources, names };
1111
1269
  }
1112
1270
  function isLiteralValue(init) {
@@ -1206,6 +1364,14 @@ function classifyOpCall(call, line, vctx, opts) {
1206
1364
  throw new ParseError('expected <ns>.<op>(...) or local function call', line, 'E_STATEMENT');
1207
1365
  }
1208
1366
  const positional = parsePositionalArgs(call.arguments ?? [], vctx, line);
1367
+ // P6/D8: dimension pass — check each argument against declared paramDims
1368
+ if (vctx.opDims && call.arguments) {
1369
+ // Build the full callee key for opDims lookup: 'cad.box' for default ns,
1370
+ // 'otherNs.box' for non-default ns, or just 'box' for local fn.
1371
+ const dimKey = namespace !== undefined ? `${namespace}.${callee}` : `${vctx.symbols.defaultNsName}.${callee}`;
1372
+ const localKey = local ? callee : dimKey;
1373
+ checkOpCallDims(call, local ? localKey : dimKey, vctx);
1374
+ }
1209
1375
  const summary = buildLineSummary({
1210
1376
  callee,
1211
1377
  line,
@@ -1221,6 +1387,104 @@ function classifyOpCall(call, line, vctx, opts) {
1221
1387
  attachAssemblySummary(summary);
1222
1388
  return summary;
1223
1389
  }
1390
+ /**
1391
+ * P6/D8: Check dimension constraints on op call arguments.
1392
+ *
1393
+ * Handles both object-form (`cad.box({ width: 10 * MM })`) and positional-form
1394
+ * (`cad.box(10 * MM, 10 * MM, 10 * MM)`). For positional form, uses slotMap
1395
+ * to map positional args to parameter names. For vec3 params, checks each element.
1396
+ *
1397
+ * Errors are accumulated in `ctx.dimErrors` (does not throw).
1398
+ */
1399
+ function checkOpCallDims(call, calleeKey, ctx) {
1400
+ const decl = ctx.opDims?.[calleeKey];
1401
+ if (!decl || !decl.paramDims)
1402
+ return;
1403
+ const args = call.arguments ?? [];
1404
+ if (args.length === 0)
1405
+ return;
1406
+ const slotMap = decl.slotMap;
1407
+ const shapeArity = slotMap?.shapeArity ?? 0;
1408
+ // ── Object form ──
1409
+ // Look for an ObjectExpression arg that contains keys matching paramDims.
1410
+ // It could be at position `shapeArity` (with slotMap) or at any position
1411
+ // (without slotMap — e.g. fai_extrude(shape, { length: 5 })).
1412
+ if (slotMap) {
1413
+ const firstNonShape = args[shapeArity];
1414
+ if (firstNonShape?.type === 'ObjectExpression') {
1415
+ const hasDimKeys = firstNonShape.properties.some((p) => p?.type === 'Property' && p.key?.type === 'Identifier' && p.key.name in decl.paramDims);
1416
+ if (hasDimKeys) {
1417
+ for (const prop of firstNonShape.properties) {
1418
+ if (prop?.type !== 'Property')
1419
+ continue;
1420
+ const keyNode = prop.key;
1421
+ if (keyNode?.type !== 'Identifier')
1422
+ continue;
1423
+ const paramName = keyNode.name;
1424
+ const dim = decl.paramDims[paramName];
1425
+ if (!dim)
1426
+ continue;
1427
+ checkDim(prop.value, dim, ctx);
1428
+ if (slotMap.vec3Keys?.includes(paramName) && prop.value?.type === 'ArrayExpression') {
1429
+ for (const el of prop.value.elements ?? []) {
1430
+ if (el)
1431
+ checkDim(el, dim, ctx);
1432
+ }
1433
+ }
1434
+ }
1435
+ return;
1436
+ }
1437
+ }
1438
+ }
1439
+ else {
1440
+ // No slotMap — scan all args for ObjectExpression with dim keys
1441
+ for (const arg of args) {
1442
+ if (arg?.type !== 'ObjectExpression')
1443
+ continue;
1444
+ const hasDimKeys = arg.properties.some((p) => p?.type === 'Property' && p.key?.type === 'Identifier' && p.key.name in decl.paramDims);
1445
+ if (!hasDimKeys)
1446
+ continue;
1447
+ for (const prop of arg.properties) {
1448
+ if (prop?.type !== 'Property')
1449
+ continue;
1450
+ const keyNode = prop.key;
1451
+ if (keyNode?.type !== 'Identifier')
1452
+ continue;
1453
+ const paramName = keyNode.name;
1454
+ const dim = decl.paramDims[paramName];
1455
+ if (!dim)
1456
+ continue;
1457
+ checkDim(prop.value, dim, ctx);
1458
+ }
1459
+ return;
1460
+ }
1461
+ }
1462
+ // ── Positional form ──
1463
+ if (!slotMap)
1464
+ return;
1465
+ const keys = slotMap.keys;
1466
+ const vec3Keys = new Set(slotMap.vec3Keys ?? []);
1467
+ let keyIdx = 0;
1468
+ for (let i = shapeArity; i < args.length && keyIdx < keys.length; i++) {
1469
+ const arg = args[i];
1470
+ if (arg?.type === 'ObjectExpression')
1471
+ break; // trailing options object
1472
+ const paramName = keys[keyIdx];
1473
+ const dim = decl.paramDims[paramName];
1474
+ keyIdx++;
1475
+ if (!dim)
1476
+ continue;
1477
+ if (vec3Keys.has(paramName) && arg?.type === 'ArrayExpression') {
1478
+ for (const el of arg.elements ?? []) {
1479
+ if (el)
1480
+ checkDim(el, dim, ctx);
1481
+ }
1482
+ }
1483
+ else if (arg) {
1484
+ checkDim(arg, dim, ctx);
1485
+ }
1486
+ }
1487
+ }
1224
1488
  /**
1225
1489
  * P2-f4:装配语句摘要附加(cad.assembly / asmN.do_assemble / asmN.solve)。
1226
1490
  *