@faicad/faijs 0.22.2 → 0.29.5

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 (488) hide show
  1. package/LICENSE +202 -28
  2. package/dist/api/api-namespace.d.ts +5 -5
  3. package/dist/api/api-namespace.d.ts.map +1 -1
  4. package/dist/api/api-namespace.js +4 -4
  5. package/dist/api/api-namespace.js.map +1 -1
  6. package/dist/api/appearance.d.ts +138 -0
  7. package/dist/api/appearance.d.ts.map +1 -0
  8. package/dist/api/appearance.js +230 -0
  9. package/dist/api/appearance.js.map +1 -0
  10. package/dist/api/assert.d.ts +2 -2
  11. package/dist/api/assert.js +8 -8
  12. package/dist/api/assert.js.map +1 -1
  13. package/dist/api/asset.d.ts +1 -1
  14. package/dist/api/asset.js +2 -2
  15. package/dist/api/asset.js.map +1 -1
  16. package/dist/api/boolean.d.ts +1 -1
  17. package/dist/api/boolean.d.ts.map +1 -1
  18. package/dist/api/boolean.js +128 -16
  19. package/dist/api/boolean.js.map +1 -1
  20. package/dist/api/cadquery-selectors/borrow-bridge.d.ts +14 -0
  21. package/dist/api/cadquery-selectors/borrow-bridge.d.ts.map +1 -0
  22. package/dist/api/cadquery-selectors/borrow-bridge.js +52 -0
  23. package/dist/api/cadquery-selectors/borrow-bridge.js.map +1 -0
  24. package/dist/api/cadquery-selectors/edge.d.ts +40 -0
  25. package/dist/api/cadquery-selectors/edge.d.ts.map +1 -0
  26. package/dist/api/cadquery-selectors/edge.js +70 -0
  27. package/dist/api/cadquery-selectors/edge.js.map +1 -0
  28. package/dist/api/cadquery-selectors/entity.d.ts +121 -0
  29. package/dist/api/cadquery-selectors/entity.d.ts.map +1 -0
  30. package/dist/api/cadquery-selectors/entity.js +167 -0
  31. package/dist/api/cadquery-selectors/entity.js.map +1 -0
  32. package/dist/api/cadquery-selectors/face.d.ts +33 -0
  33. package/dist/api/cadquery-selectors/face.d.ts.map +1 -0
  34. package/dist/api/cadquery-selectors/face.js +353 -0
  35. package/dist/api/cadquery-selectors/face.js.map +1 -0
  36. package/dist/api/cadquery-selectors/grammar.d.ts +50 -0
  37. package/dist/api/cadquery-selectors/grammar.d.ts.map +1 -0
  38. package/dist/api/cadquery-selectors/grammar.js +345 -0
  39. package/dist/api/cadquery-selectors/grammar.js.map +1 -0
  40. package/dist/api/cadquery-selectors/index.d.ts +18 -0
  41. package/dist/api/cadquery-selectors/index.d.ts.map +1 -0
  42. package/dist/api/cadquery-selectors/index.js +17 -0
  43. package/dist/api/cadquery-selectors/index.js.map +1 -0
  44. package/dist/api/cadquery-selectors/predicates.d.ts +76 -0
  45. package/dist/api/cadquery-selectors/predicates.d.ts.map +1 -0
  46. package/dist/api/cadquery-selectors/predicates.js +85 -0
  47. package/dist/api/cadquery-selectors/predicates.js.map +1 -0
  48. package/dist/api/cadquery-selectors/resolve.d.ts +41 -0
  49. package/dist/api/cadquery-selectors/resolve.d.ts.map +1 -0
  50. package/dist/api/cadquery-selectors/resolve.js +243 -0
  51. package/dist/api/cadquery-selectors/resolve.js.map +1 -0
  52. package/dist/api/cadquery-selectors/types.d.ts +96 -0
  53. package/dist/api/cadquery-selectors/types.d.ts.map +1 -0
  54. package/dist/api/cadquery-selectors/types.js +11 -0
  55. package/dist/api/cadquery-selectors/types.js.map +1 -0
  56. package/dist/api/chamfer.d.ts +13 -5
  57. package/dist/api/chamfer.d.ts.map +1 -1
  58. package/dist/api/chamfer.js +63 -7
  59. package/dist/api/chamfer.js.map +1 -1
  60. package/dist/api/compound-geom.d.ts +1 -1
  61. package/dist/api/compound-geom.js +1 -1
  62. package/dist/api/compound-geom.js.map +1 -1
  63. package/dist/api/draft.d.ts +1 -1
  64. package/dist/api/draft.js +1 -1
  65. package/dist/api/draft.js.map +1 -1
  66. package/dist/api/edge-ref.d.ts +1 -1
  67. package/dist/api/edge-ref.d.ts.map +1 -1
  68. package/dist/api/edge-ref.js +11 -3
  69. package/dist/api/edge-ref.js.map +1 -1
  70. package/dist/api/engrave.d.ts +1 -1
  71. package/dist/api/engrave.js +7 -7
  72. package/dist/api/engrave.js.map +1 -1
  73. package/dist/api/extrude.d.ts +6 -1
  74. package/dist/api/extrude.d.ts.map +1 -1
  75. package/dist/api/extrude.js +59 -5
  76. package/dist/api/extrude.js.map +1 -1
  77. package/dist/api/face-ref.d.ts +1 -1
  78. package/dist/api/face-ref.js +1 -1
  79. package/dist/api/face-ref.js.map +1 -1
  80. package/dist/api/feature-repair.d.ts +1 -1
  81. package/dist/api/feature-repair.js +1 -1
  82. package/dist/api/feature-repair.js.map +1 -1
  83. package/dist/api/fillet-variable.d.ts +1 -1
  84. package/dist/api/fillet-variable.js +1 -1
  85. package/dist/api/fillet-variable.js.map +1 -1
  86. package/dist/api/fillet.d.ts +28 -8
  87. package/dist/api/fillet.d.ts.map +1 -1
  88. package/dist/api/fillet.js +64 -9
  89. package/dist/api/fillet.js.map +1 -1
  90. package/dist/api/geom.d.ts +1 -1
  91. package/dist/api/geom.js +1 -1
  92. package/dist/api/geom.js.map +1 -1
  93. package/dist/api/helix.d.ts +1 -1
  94. package/dist/api/helix.js +1 -1
  95. package/dist/api/helix.js.map +1 -1
  96. package/dist/api/import-brep.d.ts +20 -2
  97. package/dist/api/import-brep.d.ts.map +1 -1
  98. package/dist/api/import-brep.js +28 -3
  99. package/dist/api/import-brep.js.map +1 -1
  100. package/dist/api/import-step.d.ts +17 -2
  101. package/dist/api/import-step.d.ts.map +1 -1
  102. package/dist/api/import-step.js +51 -3
  103. package/dist/api/import-step.js.map +1 -1
  104. package/dist/api/index.d.ts +6 -1
  105. package/dist/api/index.d.ts.map +1 -1
  106. package/dist/api/index.js +7 -2
  107. package/dist/api/index.js.map +1 -1
  108. package/dist/api/internal/compat-op.js +1 -1
  109. package/dist/api/internal/compat-op.js.map +1 -1
  110. package/dist/api/internal/mesh-solid-op.d.ts +236 -0
  111. package/dist/api/internal/mesh-solid-op.d.ts.map +1 -0
  112. package/dist/api/internal/mesh-solid-op.js +352 -0
  113. package/dist/api/internal/mesh-solid-op.js.map +1 -0
  114. package/dist/api/internal/svg-asset-resolver.d.ts +1 -1
  115. package/dist/api/internal/svg-asset-resolver.js +1 -1
  116. package/dist/api/internal/svg-asset-resolver.js.map +1 -1
  117. package/dist/api/knurl.d.ts +1 -1
  118. package/dist/api/knurl.js +2 -2
  119. package/dist/api/knurl.js.map +1 -1
  120. package/dist/api/loft.d.ts +1 -1
  121. package/dist/api/loft.js +1 -1
  122. package/dist/api/loft.js.map +1 -1
  123. package/dist/api/measurement/index.d.ts +9 -5
  124. package/dist/api/measurement/index.d.ts.map +1 -1
  125. package/dist/api/measurement/index.js +47 -20
  126. package/dist/api/measurement/index.js.map +1 -1
  127. package/dist/api/meta.d.ts +93 -0
  128. package/dist/api/meta.d.ts.map +1 -0
  129. package/dist/api/meta.js +99 -0
  130. package/dist/api/meta.js.map +1 -0
  131. package/dist/api/pattern.d.ts +5 -3
  132. package/dist/api/pattern.d.ts.map +1 -1
  133. package/dist/api/pattern.js +32 -4
  134. package/dist/api/pattern.js.map +1 -1
  135. package/dist/api/place.d.ts +1 -1
  136. package/dist/api/place.js +1 -1
  137. package/dist/api/place.js.map +1 -1
  138. package/dist/api/primitives.d.ts +1 -1
  139. package/dist/api/primitives.d.ts.map +1 -1
  140. package/dist/api/primitives.js +119 -4
  141. package/dist/api/primitives.js.map +1 -1
  142. package/dist/api/profile.d.ts +39 -12
  143. package/dist/api/profile.d.ts.map +1 -1
  144. package/dist/api/profile.js +4 -4
  145. package/dist/api/profile.js.map +1 -1
  146. package/dist/api/reconcile.d.ts +1 -1
  147. package/dist/api/reconcile.js +1 -1
  148. package/dist/api/reconcile.js.map +1 -1
  149. package/dist/api/replicate.d.ts.map +1 -1
  150. package/dist/api/replicate.js +122 -1
  151. package/dist/api/replicate.js.map +1 -1
  152. package/dist/api/screw.d.ts +1 -1
  153. package/dist/api/screw.js +3 -3
  154. package/dist/api/screw.js.map +1 -1
  155. package/dist/api/sdf.d.ts +1 -1
  156. package/dist/api/sdf.js +2 -2
  157. package/dist/api/sdf.js.map +1 -1
  158. package/dist/api/section-by-plane.d.ts +1 -1
  159. package/dist/api/section-by-plane.d.ts.map +1 -1
  160. package/dist/api/section-by-plane.js +2 -1
  161. package/dist/api/section-by-plane.js.map +1 -1
  162. package/dist/api/shell.d.ts +11 -4
  163. package/dist/api/shell.d.ts.map +1 -1
  164. package/dist/api/shell.js +53 -7
  165. package/dist/api/shell.js.map +1 -1
  166. package/dist/api/sketch-on-face.d.ts +41 -0
  167. package/dist/api/sketch-on-face.d.ts.map +1 -1
  168. package/dist/api/sketch-on-face.js +100 -1
  169. package/dist/api/sketch-on-face.js.map +1 -1
  170. package/dist/api/sketch-on-plane.d.ts +7 -5
  171. package/dist/api/sketch-on-plane.d.ts.map +1 -1
  172. package/dist/api/sketch-on-plane.js +6 -7
  173. package/dist/api/sketch-on-plane.js.map +1 -1
  174. package/dist/api/split-by-plane.d.ts +1 -1
  175. package/dist/api/split-by-plane.d.ts.map +1 -1
  176. package/dist/api/split-by-plane.js +2 -1
  177. package/dist/api/split-by-plane.js.map +1 -1
  178. package/dist/api/split.d.ts +1 -1
  179. package/dist/api/split.js +2 -2
  180. package/dist/api/split.js.map +1 -1
  181. package/dist/api/sweep.d.ts +1 -1
  182. package/dist/api/sweep.js +1 -1
  183. package/dist/api/sweep.js.map +1 -1
  184. package/dist/api/thicken.d.ts +1 -1
  185. package/dist/api/thicken.js +1 -1
  186. package/dist/api/thicken.js.map +1 -1
  187. package/dist/api/topo-resolve.d.ts +74 -1
  188. package/dist/api/topo-resolve.d.ts.map +1 -1
  189. package/dist/api/topo-resolve.js +180 -2
  190. package/dist/api/topo-resolve.js.map +1 -1
  191. package/dist/api/transform.d.ts +5 -1
  192. package/dist/api/transform.d.ts.map +1 -1
  193. package/dist/api/transform.js +62 -11
  194. package/dist/api/transform.js.map +1 -1
  195. package/dist/api/wire.d.ts +1 -1
  196. package/dist/api/wire.js +1 -1
  197. package/dist/api/wire.js.map +1 -1
  198. package/dist/brep/brep-ops.d.ts +9 -7
  199. package/dist/brep/brep-ops.d.ts.map +1 -1
  200. package/dist/brep/brep-ops.js +50 -26
  201. package/dist/brep/brep-ops.js.map +1 -1
  202. package/dist/brep/brep-topology.d.ts +5 -4
  203. package/dist/brep/brep-topology.d.ts.map +1 -1
  204. package/dist/brep/brep-topology.js +6 -5
  205. package/dist/brep/brep-topology.js.map +1 -1
  206. package/dist/brep/effective-deflection.d.ts +35 -0
  207. package/dist/brep/effective-deflection.d.ts.map +1 -0
  208. package/dist/brep/effective-deflection.js +49 -0
  209. package/dist/brep/effective-deflection.js.map +1 -0
  210. package/dist/brep/engine/adapters/brep-mock.d.ts.map +1 -1
  211. package/dist/brep/engine/adapters/brep-mock.js +27 -0
  212. package/dist/brep/engine/adapters/brep-mock.js.map +1 -1
  213. package/dist/brep/engine/adapters/brepkit.d.ts +18 -0
  214. package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
  215. package/dist/brep/engine/adapters/brepkit.js +46 -1
  216. package/dist/brep/engine/adapters/brepkit.js.map +1 -1
  217. package/dist/brep/engine/adapters/occt.d.ts +1 -1
  218. package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
  219. package/dist/brep/engine/adapters/occt.js +4 -2
  220. package/dist/brep/engine/adapters/occt.js.map +1 -1
  221. package/dist/brep/engine/primitives.d.ts +29 -7
  222. package/dist/brep/engine/primitives.d.ts.map +1 -1
  223. package/dist/brep/engine/primitives.js.map +1 -1
  224. package/dist/brep/engine/registry.d.ts +24 -1
  225. package/dist/brep/engine/registry.d.ts.map +1 -1
  226. package/dist/brep/engine/registry.js +24 -0
  227. package/dist/brep/engine/registry.js.map +1 -1
  228. package/dist/brep/engine/types.d.ts +54 -3
  229. package/dist/brep/engine/types.d.ts.map +1 -1
  230. package/dist/brep/engine/types.js +10 -0
  231. package/dist/brep/engine/types.js.map +1 -1
  232. package/dist/brep/export/export-model.d.ts +40 -0
  233. package/dist/brep/export/export-model.d.ts.map +1 -1
  234. package/dist/brep/export/export-model.js +274 -30
  235. package/dist/brep/export/export-model.js.map +1 -1
  236. package/dist/brep/export/step-meta-header.d.ts +102 -0
  237. package/dist/brep/export/step-meta-header.d.ts.map +1 -0
  238. package/dist/brep/export/step-meta-header.js +225 -0
  239. package/dist/brep/export/step-meta-header.js.map +1 -0
  240. package/dist/brep/export/step.d.ts +5 -1
  241. package/dist/brep/export/step.d.ts.map +1 -1
  242. package/dist/brep/export/step.js +11 -2
  243. package/dist/brep/export/step.js.map +1 -1
  244. package/dist/brep/face-evolution.d.ts +1 -1
  245. package/dist/brep/face-evolution.d.ts.map +1 -1
  246. package/dist/brep/face-evolution.js +20 -1
  247. package/dist/brep/face-evolution.js.map +1 -1
  248. package/dist/brep/handle-bridge.d.ts +1 -1
  249. package/dist/brep/handle-bridge.js +1 -1
  250. package/dist/brep/handle-bridge.js.map +1 -1
  251. package/dist/brep/mesh-solid.d.ts +218 -0
  252. package/dist/brep/mesh-solid.d.ts.map +1 -0
  253. package/dist/brep/mesh-solid.js +229 -0
  254. package/dist/brep/mesh-solid.js.map +1 -0
  255. package/dist/brep/text/fontRegistry.d.ts +32 -1
  256. package/dist/brep/text/fontRegistry.d.ts.map +1 -1
  257. package/dist/brep/text/fontRegistry.js +51 -1
  258. package/dist/brep/text/fontRegistry.js.map +1 -1
  259. package/dist/brep/text/text-to-solid.d.ts.map +1 -1
  260. package/dist/brep/text/text-to-solid.js +8 -3
  261. package/dist/brep/text/text-to-solid.js.map +1 -1
  262. package/dist/brepkit-kernel/brepkitKernel.d.ts +18 -2
  263. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  264. package/dist/brepkit-kernel/brepkitKernel.js +449 -123
  265. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  266. package/dist/brepkit-kernel/brepkitWasm.d.ts +8 -0
  267. package/dist/brepkit-kernel/brepkitWasm.d.ts.map +1 -1
  268. package/dist/brepkit-kernel/brepkitWasm.js +10 -0
  269. package/dist/brepkit-kernel/brepkitWasm.js.map +1 -1
  270. package/dist/browser-host/browser-font-provider.d.ts +12 -0
  271. package/dist/browser-host/browser-font-provider.d.ts.map +1 -1
  272. package/dist/browser-host/browser-font-provider.js +25 -0
  273. package/dist/browser-host/browser-font-provider.js.map +1 -1
  274. package/dist/browser.d.ts +6 -3
  275. package/dist/browser.d.ts.map +1 -1
  276. package/dist/browser.js +10 -3
  277. package/dist/browser.js.map +1 -1
  278. package/dist/cad-runtime/backend-dispatch.d.ts +28 -3
  279. package/dist/cad-runtime/backend-dispatch.d.ts.map +1 -1
  280. package/dist/cad-runtime/backend-dispatch.js +119 -4
  281. package/dist/cad-runtime/backend-dispatch.js.map +1 -1
  282. package/dist/cad-runtime/browser-lib-loader.js.map +1 -1
  283. package/dist/cad-runtime/direct-executor.d.ts +15 -3
  284. package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
  285. package/dist/cad-runtime/direct-executor.js +33 -1
  286. package/dist/cad-runtime/direct-executor.js.map +1 -1
  287. package/dist/cad-runtime/ports.d.ts +3 -3
  288. package/dist/cad-runtime/ports.d.ts.map +1 -1
  289. package/dist/cad-runtime/ports.js.map +1 -1
  290. package/dist/cad-runtime/preview-exec.d.ts +4 -4
  291. package/dist/cad-runtime/preview-exec.js +2 -2
  292. package/dist/cad-runtime/preview-exec.js.map +1 -1
  293. package/dist/cad-runtime/runtime.d.ts +42 -7
  294. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  295. package/dist/cad-runtime/runtime.js +68 -5
  296. package/dist/cad-runtime/runtime.js.map +1 -1
  297. package/dist/define-op.d.ts +15 -1
  298. package/dist/define-op.d.ts.map +1 -1
  299. package/dist/define-op.js +73 -2
  300. package/dist/define-op.js.map +1 -1
  301. package/dist/env-agnostic.d.ts +2 -2
  302. package/dist/env-agnostic.d.ts.map +1 -1
  303. package/dist/env-agnostic.js +1 -1
  304. package/dist/env-agnostic.js.map +1 -1
  305. package/dist/geometry2d/adapt.d.ts +39 -4
  306. package/dist/geometry2d/adapt.d.ts.map +1 -1
  307. package/dist/geometry2d/adapt.js +14 -3
  308. package/dist/geometry2d/adapt.js.map +1 -1
  309. package/dist/geometry2d/blueprint.d.ts +2 -2
  310. package/dist/geometry2d/blueprint.js +2 -2
  311. package/dist/geometry2d/blueprint.js.map +1 -1
  312. package/dist/geometry2d/bridge/lift-on-plane.d.ts +18 -8
  313. package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -1
  314. package/dist/geometry2d/bridge/lift-on-plane.js +65 -12
  315. package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -1
  316. package/dist/geometry2d/bridge/plane.d.ts +17 -0
  317. package/dist/geometry2d/bridge/plane.d.ts.map +1 -1
  318. package/dist/geometry2d/bridge/plane.js +22 -0
  319. package/dist/geometry2d/bridge/plane.js.map +1 -1
  320. package/dist/geometry2d/curve2d.d.ts +25 -0
  321. package/dist/geometry2d/curve2d.d.ts.map +1 -1
  322. package/dist/geometry2d/curve2d.js +41 -5
  323. package/dist/geometry2d/curve2d.js.map +1 -1
  324. package/dist/index.d.ts +6 -2
  325. package/dist/index.d.ts.map +1 -1
  326. package/dist/index.js +10 -3
  327. package/dist/index.js.map +1 -1
  328. package/dist/io/bytes-node.d.ts +11 -0
  329. package/dist/io/bytes-node.d.ts.map +1 -0
  330. package/dist/io/bytes-node.js +28 -0
  331. package/dist/io/bytes-node.js.map +1 -0
  332. package/dist/io/bytes.d.ts +33 -0
  333. package/dist/io/bytes.d.ts.map +1 -0
  334. package/dist/io/bytes.js +41 -0
  335. package/dist/io/bytes.js.map +1 -0
  336. package/dist/io/fai-zip/container-read.d.ts +102 -0
  337. package/dist/io/fai-zip/container-read.d.ts.map +1 -0
  338. package/dist/io/fai-zip/container-read.js +305 -0
  339. package/dist/io/fai-zip/container-read.js.map +1 -0
  340. package/dist/io/fai-zip/container-write.d.ts +70 -0
  341. package/dist/io/fai-zip/container-write.d.ts.map +1 -0
  342. package/dist/io/fai-zip/container-write.js +186 -0
  343. package/dist/io/fai-zip/container-write.js.map +1 -0
  344. package/dist/io/fai-zip/container.d.ts +107 -0
  345. package/dist/io/fai-zip/container.d.ts.map +1 -0
  346. package/dist/io/fai-zip/container.js +89 -0
  347. package/dist/io/fai-zip/container.js.map +1 -0
  348. package/dist/io/fai-zip/index.d.ts +18 -0
  349. package/dist/io/fai-zip/index.d.ts.map +1 -0
  350. package/dist/io/fai-zip/index.js +4 -0
  351. package/dist/io/fai-zip/index.js.map +1 -0
  352. package/dist/io/index.d.ts +10 -0
  353. package/dist/io/index.d.ts.map +1 -0
  354. package/dist/io/index.js +13 -0
  355. package/dist/io/index.js.map +1 -0
  356. package/dist/io/zip.d.ts +68 -0
  357. package/dist/io/zip.d.ts.map +1 -0
  358. package/dist/io/zip.js +147 -0
  359. package/dist/io/zip.js.map +1 -0
  360. package/dist/lang/code-to-args.d.ts +1 -1
  361. package/dist/lang/code-to-args.js +1 -1
  362. package/dist/lang/code-to-args.js.map +1 -1
  363. package/dist/lang/metadata-extractor.d.ts.map +1 -1
  364. package/dist/lang/metadata-extractor.js +2 -21
  365. package/dist/lang/metadata-extractor.js.map +1 -1
  366. package/dist/lang/statement-summary.d.ts +1 -1
  367. package/dist/lang/statement-summary.js +1 -1
  368. package/dist/lang/statement-summary.js.map +1 -1
  369. package/dist/lang/types.d.ts +6 -7
  370. package/dist/lang/types.d.ts.map +1 -1
  371. package/dist/lang/types.js +1 -1
  372. package/dist/lang/types.js.map +1 -1
  373. package/dist/mesh/index.d.ts +2 -1
  374. package/dist/mesh/index.d.ts.map +1 -1
  375. package/dist/mesh/index.js +2 -1
  376. package/dist/mesh/index.js.map +1 -1
  377. package/dist/mesh/io.d.ts +13 -0
  378. package/dist/mesh/io.d.ts.map +1 -1
  379. package/dist/mesh/io.js +38 -17
  380. package/dist/mesh/io.js.map +1 -1
  381. package/dist/mesh/primitives.d.ts.map +1 -1
  382. package/dist/mesh/primitives.js +41 -6
  383. package/dist/mesh/primitives.js.map +1 -1
  384. package/dist/mesh/reconcile.d.ts +1 -1
  385. package/dist/mesh/reconcile.js +1 -1
  386. package/dist/mesh/reconcile.js.map +1 -1
  387. package/dist/mesh/rigid-transform.d.ts +3 -3
  388. package/dist/mesh/rigid-transform.js +3 -3
  389. package/dist/mesh/rigid-transform.js.map +1 -1
  390. package/dist/mesh/stl-unit.d.ts +24 -0
  391. package/dist/mesh/stl-unit.d.ts.map +1 -0
  392. package/dist/mesh/stl-unit.js +17 -0
  393. package/dist/mesh/stl-unit.js.map +1 -0
  394. package/dist/mesh/threemf-bambu.d.ts +142 -0
  395. package/dist/mesh/threemf-bambu.d.ts.map +1 -0
  396. package/dist/mesh/threemf-bambu.js +334 -0
  397. package/dist/mesh/threemf-bambu.js.map +1 -0
  398. package/dist/mesh/threemf-loader.d.ts +65 -7
  399. package/dist/mesh/threemf-loader.d.ts.map +1 -1
  400. package/dist/mesh/threemf-loader.js +738 -99
  401. package/dist/mesh/threemf-loader.js.map +1 -1
  402. package/dist/mesh/threemf-view.d.ts +71 -0
  403. package/dist/mesh/threemf-view.d.ts.map +1 -0
  404. package/dist/mesh/threemf-view.js +100 -0
  405. package/dist/mesh/threemf-view.js.map +1 -0
  406. package/dist/mesh/types.d.ts +38 -0
  407. package/dist/mesh/types.d.ts.map +1 -1
  408. package/dist/mesh/types.js.map +1 -1
  409. package/dist/node-host/cli.d.ts +44 -6
  410. package/dist/node-host/cli.d.ts.map +1 -1
  411. package/dist/node-host/cli.js +76 -26
  412. package/dist/node-host/cli.js.map +1 -1
  413. package/dist/node-host/font-family-index.d.ts +93 -0
  414. package/dist/node-host/font-family-index.d.ts.map +1 -0
  415. package/dist/node-host/font-family-index.js +273 -0
  416. package/dist/node-host/font-family-index.js.map +1 -0
  417. package/dist/node-host/fs-asset-resolver.d.ts +8 -3
  418. package/dist/node-host/fs-asset-resolver.d.ts.map +1 -1
  419. package/dist/node-host/fs-asset-resolver.js +13 -5
  420. package/dist/node-host/fs-asset-resolver.js.map +1 -1
  421. package/dist/node-host/index.d.ts +8 -0
  422. package/dist/node-host/index.d.ts.map +1 -1
  423. package/dist/node-host/index.js +1 -0
  424. package/dist/node-host/index.js.map +1 -1
  425. package/dist/node-host/node-font-provider.d.ts +28 -0
  426. package/dist/node-host/node-font-provider.d.ts.map +1 -1
  427. package/dist/node-host/node-font-provider.js +49 -0
  428. package/dist/node-host/node-font-provider.js.map +1 -1
  429. package/dist/node.d.ts +1 -0
  430. package/dist/node.d.ts.map +1 -1
  431. package/dist/node.js +1 -0
  432. package/dist/node.js.map +1 -1
  433. package/dist/occt-kernel/highLevelApi.d.ts +4 -1
  434. package/dist/occt-kernel/highLevelApi.d.ts.map +1 -1
  435. package/dist/occt-kernel/highLevelApi.js +4 -2
  436. package/dist/occt-kernel/highLevelApi.js.map +1 -1
  437. package/dist/occt-kernel/occt-primitives.d.ts.map +1 -1
  438. package/dist/occt-kernel/occt-primitives.js +36 -3
  439. package/dist/occt-kernel/occt-primitives.js.map +1 -1
  440. package/dist/occt-kernel/occtKernel.d.ts +19 -14
  441. package/dist/occt-kernel/occtKernel.d.ts.map +1 -1
  442. package/dist/occt-kernel/occtKernel.js +19 -32
  443. package/dist/occt-kernel/occtKernel.js.map +1 -1
  444. package/dist/occt-kernel/stepColorParser.d.ts +18 -0
  445. package/dist/occt-kernel/stepColorParser.d.ts.map +1 -1
  446. package/dist/occt-kernel/stepColorParser.js +38 -0
  447. package/dist/occt-kernel/stepColorParser.js.map +1 -1
  448. package/dist/occt-kernel/stepMetaParser.d.ts +44 -0
  449. package/dist/occt-kernel/stepMetaParser.d.ts.map +1 -0
  450. package/dist/occt-kernel/stepMetaParser.js +166 -0
  451. package/dist/occt-kernel/stepMetaParser.js.map +1 -0
  452. package/dist/occt-kernel/topologyExt.d.ts +1 -1
  453. package/dist/occt-kernel/topologyExt.d.ts.map +1 -1
  454. package/dist/occt-kernel/topologyExt.js +4 -5
  455. package/dist/occt-kernel/topologyExt.js.map +1 -1
  456. package/dist/primitives/text/cjk-font.d.ts +2 -15
  457. package/dist/primitives/text/cjk-font.d.ts.map +1 -1
  458. package/dist/primitives/text/cjk-font.js +13 -1
  459. package/dist/primitives/text/cjk-font.js.map +1 -1
  460. package/dist/runtime-state.d.ts +85 -9
  461. package/dist/runtime-state.d.ts.map +1 -1
  462. package/dist/runtime-state.js +72 -2
  463. package/dist/runtime-state.js.map +1 -1
  464. package/dist/sdk.d.ts +3 -3
  465. package/dist/sdk.d.ts.map +1 -1
  466. package/dist/sdk.js +5 -5
  467. package/dist/sdk.js.map +1 -1
  468. package/dist/shape.d.ts +108 -5
  469. package/dist/shape.d.ts.map +1 -1
  470. package/dist/shape.js +130 -3
  471. package/dist/shape.js.map +1 -1
  472. package/dist/step/stepMetaParser.d.ts +55 -0
  473. package/dist/step/stepMetaParser.d.ts.map +1 -0
  474. package/dist/step/stepMetaParser.js +177 -0
  475. package/dist/step/stepMetaParser.js.map +1 -0
  476. package/dist/topology/naming/lineage.d.ts +6 -0
  477. package/dist/topology/naming/lineage.d.ts.map +1 -1
  478. package/dist/topology/naming/lineage.js +8 -0
  479. package/dist/topology/naming/lineage.js.map +1 -1
  480. package/dist/topology/naming/resolve-edge.d.ts +20 -1
  481. package/dist/topology/naming/resolve-edge.d.ts.map +1 -1
  482. package/dist/topology/naming/resolve-edge.js +54 -20
  483. package/dist/topology/naming/resolve-edge.js.map +1 -1
  484. package/dist/weapp.d.ts +6 -2
  485. package/dist/weapp.d.ts.map +1 -1
  486. package/dist/weapp.js +7 -1
  487. package/dist/weapp.js.map +1 -1
  488. package/package.json +19 -3
@@ -2,7 +2,7 @@
2
2
  * threemf-loader — headless 3MF (ZIP/OPC) geometry loader.
3
3
  *
4
4
  * 3MF is a ZIP container whose geometry lives in `3D/3dmodel.model`, an XML
5
- * document. Design requirement (unit-system §2.4/§5): the unit MUST be read
5
+ * document. Design requirements (unit-system §2.4/§5): the unit MUST be read
6
6
  * from the real (inflated) XML, NOT from the raw bytes — `3D/3dmodel.model` is
7
7
  * deflate-compressed inside the ZIP, so scanning the raw buffer cannot find
8
8
  * `<model unit="…">` (the 3d_editor precedent returned 'millimeter' for real
@@ -17,11 +17,15 @@
17
17
  * x' = a*x + d*y + g*z + p
18
18
  * y' = b*x + e*y + h*z + q
19
19
  * z' = c*x + f*y + i*z + r
20
- * so object-local mesh coords bake into world-space positions (the op contract
21
- * is world-space; `planeDistance` must line up with baked positions).
20
+ * so object-local mesh coords bake into world-space positions.
21
+ *
22
+ * Return: an `ThreemfArchive` — per-instance objects (one per `<build><item>`,
23
+ * transform baked, or per-`<resources>` object when `<build>` has no items at
24
+ * all), plus every non-model archive entry surfaced as `extraEntries` so the
25
+ * host can reach Bambu's private `model_settings.config` / thumbnails through
26
+ * the *same unzipped map* (single unzip, two consumer chains).
22
27
  */
23
- import { unzipSync, strFromU8 } from 'fflate';
24
- import { parseXmlDocument } from '../api/xml-dom.js';
28
+ import { readZipEntries } from '../io/zip.js';
25
29
  import { UNIT_SCALE } from '../units.js';
26
30
  /** Map a 3MF unit string to a faijs length unit name. */
27
31
  function threemfUnitToFaijs(unit) {
@@ -35,129 +39,764 @@ function threemfUnitToFaijs(unit) {
35
39
  default: return null;
36
40
  }
37
41
  }
38
- /** Extract the declared unit from an already-parsed `<model>` document. */
39
- function modelUnit(doc) {
40
- const model = doc.documentElement;
41
- if (model && model.getAttribute('unit')) {
42
- return model.getAttribute('unit');
43
- }
44
- // Default per 3MF spec is millimeter.
45
- return 'millimeter';
42
+ /** `#RRGGBB` → sRGB components in 0..1 (leading `#` required, alpha ignored). */
43
+ function hexToRgb(hex) {
44
+ const m = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(hex);
45
+ if (!m)
46
+ return null;
47
+ return [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255];
46
48
  }
47
- /** Parse the `3D/3dmodel.model` XML and produce an index+position mesh. */
48
- async function parseModelXml(xml) {
49
- const doc = await parseXmlDocument(xml);
50
- const unit = modelUnit(doc);
51
- const faijsUnit = threemfUnitToFaijs(unit);
52
- if (faijsUnit === null) {
53
- throw new Error(`[mesh/threemf] unsupported <model unit>: ${JSON.stringify(unit)}`);
49
+ /** Bake a column-major ST_Matrix3D (translation = last three tokens) into positions. */
50
+ function bakeTransform(positions, m) {
51
+ const [a, b, c, d, e, f, g, h, iz, p, q, r] = m;
52
+ const out = new Float32Array(positions.length);
53
+ for (let v = 0; v < positions.length; v += 3) {
54
+ const x = positions[v];
55
+ const y = positions[v + 1];
56
+ const z = positions[v + 2];
57
+ out[v] = a * x + d * y + g * z + p;
58
+ out[v + 1] = b * x + e * y + h * z + q;
59
+ out[v + 2] = c * x + f * y + iz * z + r;
54
60
  }
55
- const scale = UNIT_SCALE[faijsUnit];
56
- // resources → object → mesh → vertices / triangles
57
- const resources = doc.documentElement.getElementsByTagName('resources')[0];
58
- const objects = resources ? resources.getElementsByTagName('object') : [];
59
- const byObjectId = new Map();
60
- for (let i = 0; i < objects.length; i++) {
61
- const obj = objects[i];
62
- const id = Number(obj.getAttribute('id'));
61
+ return out;
62
+ }
63
+ /**
64
+ * Compose two ST_Matrix3D column-major transforms (12 tokens each, same
65
+ * layout as `bakeTransform`: `[a b c, d e f, g h i, p q r]`, translation last).
66
+ *
67
+ * Semantics: the child transform is applied first, then the parent —
68
+ * `M = parent · child` (the child lives in the parent's space, exactly like
69
+ * three.js `clone.applyMatrix4(parent).applyMatrix4(child)`).
70
+ */
71
+ function composeTransform(parent, child) {
72
+ const [a1, b1, c1, d1, e1, f1, g1, h1, i1, p1, q1, r1] = parent;
73
+ const [a2, b2, c2, d2, e2, f2, g2, h2, i2, p2, q2, r2] = child;
74
+ return [
75
+ a1 * a2 + d1 * b2 + g1 * c2,
76
+ b1 * a2 + e1 * b2 + h1 * c2,
77
+ c1 * a2 + f1 * b2 + i1 * c2,
78
+ a1 * d2 + d1 * e2 + g1 * f2,
79
+ b1 * d2 + e1 * e2 + h1 * f2,
80
+ c1 * d2 + f1 * e2 + i1 * f2,
81
+ a1 * g2 + d1 * h2 + g1 * i2,
82
+ b1 * g2 + e1 * h2 + h1 * i2,
83
+ c1 * g2 + f1 * h2 + i1 * i2,
84
+ a1 * p2 + d1 * q2 + g1 * r2 + p1,
85
+ b1 * p2 + e1 * q2 + h1 * r2 + q1,
86
+ c1 * p2 + f1 * q2 + i1 * r2 + r1,
87
+ ];
88
+ }
89
+ /**
90
+ * Parse an ST_Matrix3D attribute (12 or 16 tokens with trailing "0 0 0 1").
91
+ * Returns the first 12 tokens when valid, `null` when malformed (caller
92
+ * ignores the transform instead of baking NaN — mirrors the host tolerance
93
+ * contract).
94
+ */
95
+ function parseTransformAttr(raw) {
96
+ if (!raw)
97
+ return null;
98
+ const t = raw.trim().split(/\s+/).map(Number);
99
+ const valid = t.length === 12 ||
100
+ (t.length === 16 && t[12] === 0 && t[13] === 0 && t[14] === 0 && t[15] === 1);
101
+ if (!valid || !t.every((n) => Number.isFinite(n)))
102
+ return null;
103
+ return t.length === 12 ? t : t.slice(0, 12);
104
+ }
105
+ /** Detect triangle-level material attributes (fast-3mf port). */
106
+ const TRI_MATERIAL_ATTR_RE = /(?:^|\s)(?:p1|p2|p3|pid)="/;
107
+ /**
108
+ * Loose vertex re-parse: extracts x/y/z independently, tolerating attribute
109
+ * reordering or newlines inside a vertex tag. Slow path — only used when the
110
+ * strict regex scan reports a count mismatch (fast-3mf port).
111
+ */
112
+ function parseVerticesLoose(vertSec) {
113
+ const vRe = /<vertex\s+([^>]*?)\/?>/g;
114
+ const positions = [];
115
+ let vm;
116
+ while ((vm = vRe.exec(vertSec)) !== null) {
117
+ const a = vm[1];
118
+ const x = /(?:^|\s)x="(-?[\d.eE+-]+)"/.exec(a)?.[1];
119
+ const y = /(?:^|\s)y="(-?[\d.eE+-]+)"/.exec(a)?.[1];
120
+ const z = /(?:^|\s)z="(-?[\d.eE+-]+)"/.exec(a)?.[1];
121
+ if (x === undefined || y === undefined || z === undefined)
122
+ return null;
123
+ positions.push(+x, +y, +z);
124
+ }
125
+ return new Float32Array(positions);
126
+ }
127
+ /**
128
+ * Loose triangle re-parse: extracts v1/v2/v3 (and pid/p1/p2/p3 when needed)
129
+ * per attribute, tolerating attribute reordering (fast-3mf port). Slow path —
130
+ * only used when the strict regex scan reports a count mismatch.
131
+ */
132
+ function parseTrianglesLoose(triSec, withProps) {
133
+ const tFullRe = /<triangle\s+([^>]*?)\/?>/g;
134
+ const idx = [];
135
+ const props = [];
136
+ let tm;
137
+ while ((tm = tFullRe.exec(triSec)) !== null) {
138
+ const a = tm[1];
139
+ const v1 = /(?:^|\s)v1="(\d+)"/.exec(a)?.[1];
140
+ const v2 = /(?:^|\s)v2="(\d+)"/.exec(a)?.[1];
141
+ const v3 = /(?:^|\s)v3="(\d+)"/.exec(a)?.[1];
142
+ if (v1 === undefined || v2 === undefined || v3 === undefined)
143
+ return null;
144
+ idx.push(+v1, +v2, +v3);
145
+ if (withProps) {
146
+ const p = {};
147
+ const pid = /(?:^|\s)pid="([^"]*)"/.exec(a)?.[1];
148
+ if (pid !== undefined)
149
+ p.pid = pid;
150
+ const p1 = /(?:^|\s)p1="([^"]*)"/.exec(a)?.[1];
151
+ if (p1 !== undefined)
152
+ p.p1 = +p1;
153
+ const p2 = /(?:^|\s)p2="([^"]*)"/.exec(a)?.[1];
154
+ if (p2 !== undefined)
155
+ p.p2 = +p2;
156
+ const p3 = /(?:^|\s)p3="([^"]*)"/.exec(a)?.[1];
157
+ if (p3 !== undefined)
158
+ p.p3 = +p3;
159
+ props.push(p);
160
+ }
161
+ }
162
+ return { index: new Uint32Array(idx), props: withProps ? props : null };
163
+ }
164
+ /**
165
+ * Stream-scan one model XML document for `<resources>` material libraries and
166
+ * `<object>` entries, merging into the shared maps (caller keeps sub-models
167
+ * first / root last so the root wins id collisions).
168
+ *
169
+ * Text-scanner (no DOM) — ported from the host's retired fast-3mf parser, so
170
+ * very large model documents (Bambu exports reach ~140 MB of XML, where a full
171
+ * DOM parse exhausts the heap) stay linear in memory. Attribute extraction
172
+ * keeps fast-3mf's tolerance: attribute reordering / newline variance falls
173
+ * back to a loose re-parse; namespace-prefixed attributes (`p:objectid`,
174
+ * `p:transform`) match through substring.
175
+ */
176
+ function scanResources(xml, scale, basematerialsById, colorgroupsById, byObjectId) {
177
+ const resourcesStart = xml.indexOf('<resources>');
178
+ if (resourcesStart < 0)
179
+ return;
180
+ const resourcesEnd = xml.indexOf('</resources>', resourcesStart);
181
+ const resBody = resourcesEnd >= 0
182
+ ? xml.slice(resourcesStart, resourcesEnd)
183
+ : xml.slice(resourcesStart);
184
+ // basematerials: id + base list (name/displaycolor).
185
+ const bmRe = /<basematerials\s+([^>]*)>([\s\S]*?)<\/basematerials>/g;
186
+ let m;
187
+ while ((m = bmRe.exec(resBody)) !== null) {
188
+ const id = /(?:^|\s)id="([^"]+)"/.exec(m[1])?.[1];
189
+ if (id === undefined)
190
+ continue;
191
+ const bases = [];
192
+ const baseRe = /<base\s+([^>]*?)\/?>/g;
193
+ let bm2;
194
+ while ((bm2 = baseRe.exec(m[2])) !== null) {
195
+ bases.push({
196
+ name: /(?:^|\s)name="([^"]*)"/.exec(bm2[1])?.[1],
197
+ displaycolor: /(?:^|\s)displaycolor="([^"]*)"/.exec(bm2[1])?.[1],
198
+ });
199
+ }
200
+ basematerialsById.set(id, bases);
201
+ }
202
+ // colorgroup: id + color list (hex → sRGB components).
203
+ const cgRe = /<colorgroup\s+([^>]*)>([\s\S]*?)<\/colorgroup>/g;
204
+ while ((m = cgRe.exec(resBody)) !== null) {
205
+ const id = /(?:^|\s)id="([^"]+)"/.exec(m[1])?.[1];
206
+ if (id === undefined)
207
+ continue;
208
+ const colors = [];
209
+ const colorRe = /<color\s+([^>]*?)\/?>/g;
210
+ let cm;
211
+ while ((cm = colorRe.exec(m[2])) !== null) {
212
+ const hex = /(?:^|\s)color="([^"]*)"/.exec(cm[1])?.[1]?.slice(0, 7) ?? '#000000';
213
+ const rgb = hexToRgb(hex) ?? [0, 0, 0];
214
+ colors.push(rgb[0], rgb[1], rgb[2]);
215
+ }
216
+ colorgroupsById.set(id, new Float32Array(colors));
217
+ }
218
+ // objects — scanned across the whole document (not just `<resources>`) so
219
+ // out-of-resources `<object>` placements still load (external-data tolerance).
220
+ const objRe = /<object\s+([^>]*)>([\s\S]*?)<\/object>/g;
221
+ while ((m = objRe.exec(xml)) !== null) {
222
+ const attrs = m[1];
223
+ const body = m[2];
224
+ const idRaw = /(?:^|\s)id="([^"]+)"/.exec(attrs)?.[1];
225
+ if (idRaw === undefined)
226
+ continue;
227
+ const id = Number(idRaw);
63
228
  if (!Number.isInteger(id))
64
229
  continue;
65
- const meshEl = obj.getElementsByTagName('mesh')[0];
66
- if (!meshEl)
230
+ const name = /(?:^|\s)name="([^"]*)"/.exec(attrs)?.[1];
231
+ const objPid = /(?:^|\s)pid="([^"]*)"/.exec(attrs)?.[1];
232
+ const objPindexRaw = /(?:^|\s)pindex="([^"]*)"/.exec(attrs)?.[1];
233
+ const objPindex = objPindexRaw !== undefined && objPindexRaw !== '' ? Number(objPindexRaw) : undefined;
234
+ // 料号(`<object partnumber>`)。
235
+ const partNumber = /(?:^|\s)partnumber="([^"]*)"/.exec(attrs)?.[1];
236
+ // 对象级 `<metadatagroup><metadata name="ns:key">…`(vendor 前缀键,值取 text 或 name)。
237
+ // faijs 写出的 `faijs:description` 提升为对象级 `description`(§6.1 写回 §5.1 读,
238
+ // 保证 description 往返不落进自定义键);其余键保留原始名进 metadata。
239
+ const metadata = {};
240
+ let objectDescription;
241
+ const mgRe = /<metadatagroup[^>]*>([\s\S]*?)<\/metadatagroup>/g;
242
+ let mgm;
243
+ while ((mgm = mgRe.exec(body)) !== null) {
244
+ const mdRe = /<metadata\s+([^>]*?)(?:\/>|>([\s\S]*?)<\/metadata>)/g;
245
+ let mdm;
246
+ while ((mdm = mdRe.exec(mgm[1])) !== null) {
247
+ const key = /(?:^|\s)name="([^"]*)"/.exec(mdm[1])?.[1];
248
+ if (key === undefined)
249
+ continue;
250
+ const text = mdm[2] !== undefined ? /(?:^|\s)value="([^"]*)"/.exec(mdm[1])?.[1] : undefined;
251
+ const val = text ??
252
+ (mdm[2] !== undefined ? mdm[2].replace(/<[^>]*>/g, '').trim() : undefined);
253
+ if (val === undefined)
254
+ continue;
255
+ if (key === 'faijs:description')
256
+ objectDescription = val;
257
+ else
258
+ metadata[key] = val;
259
+ }
260
+ }
261
+ let meta = {
262
+ id,
263
+ name,
264
+ description: objectDescription,
265
+ partNumber,
266
+ metadata,
267
+ pid: objPid,
268
+ pindex: objPindex,
269
+ };
270
+ // mesh: vertices + triangles (fast-3mf port — strict regex scan with a
271
+ // loose attribute-order fallback; missing sub-sections are tolerated and
272
+ // the object still registers, matching the retired host behavior).
273
+ const meshStart = body.indexOf('<mesh>');
274
+ const meshEnd = body.indexOf('</mesh>');
275
+ if (meshStart >= 0 && meshEnd > meshStart) {
276
+ const meshBody = body.slice(meshStart, meshEnd);
277
+ // vertices
278
+ const vertStart = meshBody.indexOf('<vertices>');
279
+ const vertEnd = meshBody.indexOf('</vertices>');
280
+ if (vertStart >= 0 && vertEnd > vertStart) {
281
+ const vertSec = meshBody.slice(vertStart, vertEnd);
282
+ let vc = 0;
283
+ let p = 0;
284
+ while ((p = vertSec.indexOf('<vertex ', p)) !== -1) {
285
+ vc++;
286
+ p += 8;
287
+ }
288
+ const vRe = /x="(-?[\d.eE+-]+)"\s+y="(-?[\d.eE+-]+)"\s+z="(-?[\d.eE+-]+)"/g;
289
+ let positions = new Float32Array(vc * 3);
290
+ let vm;
291
+ let vi = 0;
292
+ while ((vm = vRe.exec(vertSec)) !== null) {
293
+ positions[vi] = +vm[1] * scale;
294
+ positions[vi + 1] = +vm[2] * scale;
295
+ positions[vi + 2] = +vm[3] * scale;
296
+ vi += 3;
297
+ }
298
+ // XML attributes are unordered (spec XSD) — if any vertex failed to
299
+ // match (different attribute order / newline after tag), re-scan each
300
+ // attribute independently instead of giving up.
301
+ if (vc > 0 && vi !== vc * 3) {
302
+ const loose = parseVerticesLoose(vertSec);
303
+ if (!loose) {
304
+ throw new Error(`[mesh/threemf] cannot parse <vertices> in object ${id}`);
305
+ }
306
+ positions = new Float32Array(loose.length);
307
+ for (let i = 0; i < loose.length; i++)
308
+ positions[i] = loose[i] * scale;
309
+ }
310
+ meta = { ...meta, positions };
311
+ }
312
+ // triangles
313
+ const triStart = meshBody.indexOf('<triangles>');
314
+ const triEnd = meshBody.indexOf('</triangles>');
315
+ if (triStart >= 0 && triEnd > triStart) {
316
+ const triSec = meshBody.slice(triStart, triEnd);
317
+ let tc = 0;
318
+ let p = 0;
319
+ while ((p = triSec.indexOf('<triangle ', p)) !== -1) {
320
+ tc++;
321
+ p += 10;
322
+ }
323
+ const idx = new Uint32Array(tc * 3);
324
+ const hasMatAttrs = TRI_MATERIAL_ATTR_RE.test(triSec);
325
+ // Object-level pid without per-triangle material attrs: synthesize
326
+ // triangle properties so every triangle still routes through the
327
+ // resource (object pindex default) — fast-3mf semantics.
328
+ const needsProps = hasMatAttrs || objPid !== undefined;
329
+ const triProps = needsProps ? [] : undefined;
330
+ const tRe = /v1="(\d+)"\s+v2="(\d+)"\s+v3="(\d+)"/g;
331
+ let tm;
332
+ let ti = 0;
333
+ while ((tm = tRe.exec(triSec)) !== null) {
334
+ idx[ti] = +tm[1];
335
+ idx[ti + 1] = +tm[2];
336
+ idx[ti + 2] = +tm[3];
337
+ ti += 3;
338
+ }
339
+ if (tc > 0 && ti !== tc * 3) {
340
+ // Attribute order / newline variance → re-scan each attribute
341
+ // independently (slow path, rare).
342
+ const loose = parseTrianglesLoose(triSec, needsProps);
343
+ if (!loose) {
344
+ throw new Error(`[mesh/threemf] cannot parse <triangles> in object ${id}`);
345
+ }
346
+ idx.set(loose.index);
347
+ if (loose.props) {
348
+ triProps.length = 0;
349
+ triProps.push(...loose.props);
350
+ }
351
+ }
352
+ else if (needsProps) {
353
+ if (hasMatAttrs) {
354
+ const tFullRe = /<triangle\s+([^>]*?)\/?>/g;
355
+ let tfm;
356
+ while ((tfm = tFullRe.exec(triSec)) !== null) {
357
+ const a = tfm[1];
358
+ const tv = /v1="(\d+)"\s+v2="(\d+)"\s+v3="(\d+)"/.exec(a);
359
+ if (!tv)
360
+ continue;
361
+ const prop = {};
362
+ const p1 = /\bp1="([^"]*)"/.exec(a)?.[1];
363
+ if (p1 !== undefined)
364
+ prop.p1 = +p1;
365
+ const p2 = /\bp2="([^"]*)"/.exec(a)?.[1];
366
+ if (p2 !== undefined)
367
+ prop.p2 = +p2;
368
+ const p3 = /\bp3="([^"]*)"/.exec(a)?.[1];
369
+ if (p3 !== undefined)
370
+ prop.p3 = +p3;
371
+ const pidm = /\bpid="([^"]*)"/.exec(a)?.[1];
372
+ if (pidm !== undefined)
373
+ prop.pid = pidm;
374
+ triProps.push(prop);
375
+ }
376
+ }
377
+ else {
378
+ // Object-level pid only → synthesized props (no per-triangle
379
+ // material attrs) so materializeMesh's per-triangle lookups fall
380
+ // through to the object-level defaults.
381
+ for (let i = 0; i < tc; i++)
382
+ triProps.push({});
383
+ }
384
+ }
385
+ meta = { ...meta, indices: idx, triProps };
386
+ }
387
+ }
388
+ // `<components>` children — Bambu builds plates/assemblies this way.
389
+ // A pure component container has no mesh; it still registers in byObjectId
390
+ // so build items referencing it expand recursively (component instances).
391
+ const compStart = body.indexOf('<components>');
392
+ if (compStart >= 0) {
393
+ const compEnd = body.indexOf('</components>', compStart);
394
+ const compSec = compEnd >= 0
395
+ ? body.slice(compStart + '<components>'.length, compEnd)
396
+ : body.slice(compStart + '<components>'.length);
397
+ const comps = [];
398
+ const compRe = /<component\s+([^>]*?)\/?>/g;
399
+ let cpm;
400
+ while ((cpm = compRe.exec(compSec)) !== null) {
401
+ const oid = Number(/objectid="([^"]+)"/.exec(cpm[1])?.[1]);
402
+ if (!Number.isInteger(oid))
403
+ continue;
404
+ comps.push({
405
+ objectId: oid,
406
+ transform: parseTransformAttr(/\btransform="([^"]*)"/.exec(cpm[1])?.[1] ?? null),
407
+ });
408
+ }
409
+ if (comps.length > 0)
410
+ meta = { ...meta, components: comps };
411
+ }
412
+ if (!meta.positions && !meta.components)
67
413
  continue;
68
- const verticesEl = meshEl.getElementsByTagName('vertices')[0];
69
- const trianglesEl = meshEl.getElementsByTagName('triangles')[0];
70
- if (!verticesEl || !trianglesEl)
414
+ byObjectId.set(id, meta);
415
+ }
416
+ }
417
+ const MODEL_METADATA_FIELD = {
418
+ Title: 'title',
419
+ Designer: 'designer',
420
+ Description: 'description',
421
+ Copyright: 'copyright',
422
+ LicenseTerms: 'licenseTerms',
423
+ Rating: 'rating',
424
+ CreationDate: 'creationDate',
425
+ ModificationDate: 'modificationDate',
426
+ Application: 'application',
427
+ };
428
+ /**
429
+ * Parse `FileMeta` from the root model document's direct `<metadata>` children
430
+ * (3MF Core model context). Well-known names (Title/Designer/…) map onto the
431
+ * FileMeta fields; any other (vendor-namespaced) name is kept verbatim in
432
+ * `FileMeta.metadata` (key preserves its namespace prefix). Value = text
433
+ * content or a `value` attribute; empties are skipped.
434
+ */
435
+ function parseModelMetadata(rootDoc) {
436
+ // 仅取 `<model …>` 到 `<resources>`/`<build>` 之前的前导区段——模型级元数据
437
+ // 是 `<model>` 的直接子级,排在 `<resources>` 之前;避免把对象级/metadatagroup
438
+ // 里的同名 `<metadata>` 误收进 fileMeta。
439
+ const modelStart = rootDoc.indexOf('<model');
440
+ if (modelStart < 0)
441
+ return undefined;
442
+ const resAt = rootDoc.indexOf('<resources>', modelStart);
443
+ const buildAt = rootDoc.indexOf('<build', modelStart);
444
+ const endCandidates = [resAt, buildAt].filter((i) => i >= 0);
445
+ const sectionEnd = endCandidates.length > 0 ? Math.min(...endCandidates) : rootDoc.length;
446
+ const section = rootDoc.slice(modelStart, sectionEnd);
447
+ const meta = {};
448
+ let custom;
449
+ const mdRe = /<metadata\s+([^>]*?)(?:\/>|>([\s\S]*?)<\/metadata>)/g;
450
+ let mdm;
451
+ while ((mdm = mdRe.exec(section)) !== null) {
452
+ const attrs = mdm[1];
453
+ const name = /(?:^|\s)name="([^"]*)"/.exec(attrs)?.[1];
454
+ if (name === undefined)
71
455
  continue;
72
- const vertexEls = verticesEl.getElementsByTagName('vertex');
73
- const pos = new Float32Array(vertexEls.length * 3);
74
- for (let v = 0; v < vertexEls.length; v++) {
75
- const el = vertexEls[v];
76
- pos[v * 3] = Number(el.getAttribute('x')) * scale;
77
- pos[v * 3 + 1] = Number(el.getAttribute('y')) * scale;
78
- pos[v * 3 + 2] = Number(el.getAttribute('z')) * scale;
456
+ const valAttr = /(?:^|\s)value="([^"]*)"/.exec(attrs)?.[1];
457
+ const val = valAttr ?? (mdm[2] !== undefined ? mdm[2].replace(/<[^>]*>/g, '').trim() : '');
458
+ if (val === '')
459
+ continue;
460
+ const field = MODEL_METADATA_FIELD[name];
461
+ if (field) {
462
+ meta[field] = val;
463
+ }
464
+ else {
465
+ custom = custom ?? {};
466
+ custom[name] = val;
467
+ }
468
+ }
469
+ if (custom && Object.keys(custom).length > 0)
470
+ meta.metadata = custom;
471
+ return Object.keys(meta).length > 0 ? meta : undefined;
472
+ }
473
+ /**
474
+ * Parse the 3MF model XML documents (root + sub-models) into archive objects.
475
+ * skeletons (component containers) plus the `<build>`, while the actual mesh
476
+ * geometry lives in per-object files `3D/Objects/object_*.model` referenced
477
+ * through `p:path` (production extension) — which we ignore in favor of
478
+ * merging every model document's `<resources>` by object id (mirrors the
479
+ * host's fast-3mf collection order: sub-models first, root model last, root
480
+ * wins on id collisions).
481
+ *
482
+ * `unit` is read from the ROOT model document (the file the build items
483
+ * belong to); all documents share the same declared unit in practice.
484
+ */
485
+ function parseModelXml(docTexts) {
486
+ // Last document = root model (caller appends it last).
487
+ const rootDoc = docTexts[docTexts.length - 1];
488
+ const unitMatch = /<model\b[^>]*\bunit="([^"]+)"/.exec(rootDoc);
489
+ const unit = unitMatch ? unitMatch[1] : 'millimeter';
490
+ const unitName = threemfUnitToFaijs(unit);
491
+ if (unitName === null) {
492
+ throw new Error(`[mesh/threemf] unsupported <model unit>: ${JSON.stringify(unit)}`);
493
+ }
494
+ const scale = UNIT_SCALE[unitName];
495
+ // 整体级元数据(`<model>` 直接子级 `<metadata>`;well-known 名映射到
496
+ // FileMeta 字段,vendor 前缀名兜底进 FileMeta.metadata)。
497
+ const fileMeta = parseModelMetadata(rootDoc);
498
+ // resources → material libraries (basematerials / colorgroup) + objects,
499
+ // merged across every model document (sub-models first, root last).
500
+ const basematerialsById = new Map();
501
+ const colorgroupsById = new Map();
502
+ const byObjectId = new Map();
503
+ for (const text of docTexts) {
504
+ scanResources(text, scale, basematerialsById, colorgroupsById, byObjectId);
505
+ }
506
+ // (scanResources above handles basematerials / colorgroup / objects)
507
+ // (mesh/components + registration handled by scanResources)
508
+ /**
509
+ * Materialize object instances: a mesh object yields ONE ThreemfObject with
510
+ * the transform baked in and base/vertex colors resolved; a component
511
+ * container recursively expands each child (composing transforms, child
512
+ * applied first), one ThreemfObject per leaf mesh instance — mirroring the
513
+ * host's clone-and-apply semantics without shared geometry. `path` guards
514
+ * reference cycles (a self-referencing component chain terminates).
515
+ */
516
+ function materialize(src, mat, path) {
517
+ if (src.positions && src.indices) {
518
+ return [materializeMesh(src, mat)];
79
519
  }
80
- const triEls = trianglesEl.getElementsByTagName('triangle');
81
- const idx = new Uint32Array(triEls.length * 3);
82
- for (let t = 0; t < triEls.length; t++) {
83
- const el = triEls[t];
84
- idx[t * 3] = Number(el && el.getAttribute('v1'));
85
- idx[t * 3 + 1] = Number(el && el.getAttribute('v2'));
86
- idx[t * 3 + 2] = Number(el && el.getAttribute('v3'));
520
+ if (src.components && src.components.length > 0) {
521
+ const out = [];
522
+ for (const comp of src.components) {
523
+ if (path.has(comp.objectId))
524
+ continue;
525
+ const ref = byObjectId.get(comp.objectId);
526
+ if (!ref)
527
+ continue;
528
+ const next = comp.transform
529
+ ? (mat ? composeTransform(mat, comp.transform) : comp.transform)
530
+ : mat;
531
+ const nextPath = new Set(path);
532
+ nextPath.add(comp.objectId);
533
+ out.push(...materialize(ref, next, nextPath));
534
+ }
535
+ return out;
87
536
  }
88
- byObjectId.set(id, { positions: pos, indices: idx });
89
- }
90
- // build → item → per-instance transforms (ST_3D, column-major, translation last 3).
91
- const build = doc.documentElement.getElementsByTagName('build')[0];
92
- const itemEls = build ? build.getElementsByTagName('item') : [];
93
- const allPos = [];
94
- const allIdx = [];
95
- for (let i = 0; i < itemEls.length; i++) {
96
- const item = itemEls[i];
97
- const objId = Number(item.getAttribute('objectid'));
537
+ return [];
538
+ }
539
+ /**
540
+ * Materialize a single mesh object instance: bake the optional build
541
+ * transform and resolve base/vertex colors. `vertexColors` expands the
542
+ * geometry (each triangle owns 3 independent vertices) so per-triangle
543
+ * p1/p2/p3 survive; `baseColor` keeps the compact form and applies only
544
+ * when every triangle references the same base index (multi-base objects
545
+ * fall back to the default material, matching the host contract).
546
+ */
547
+ /** 组装零件级 meta(仅当至少一个字段存在)。
548
+ * 组件容器(component 引用)沿 ref 链透传同一 meta 对象引用。 */
549
+ function srcMeta(src) {
550
+ const meta = {};
551
+ if (src.name !== undefined && src.name !== '')
552
+ meta.name = src.name;
553
+ if (src.description !== undefined && src.description !== '')
554
+ meta.description = src.description;
555
+ if (src.partNumber !== undefined && src.partNumber !== '')
556
+ meta.partNumber = src.partNumber;
557
+ if (src.metadata && Object.keys(src.metadata).length > 0)
558
+ meta.metadata = src.metadata;
559
+ return Object.keys(meta).length > 0 ? meta : undefined;
560
+ }
561
+ function materializeMesh(src, mat) {
562
+ const positions = src.positions;
563
+ const indices = src.indices;
564
+ let pid;
565
+ if (src.triProps) {
566
+ for (const p of src.triProps) {
567
+ const eff = p.pid ?? src.pid;
568
+ if (eff !== undefined) {
569
+ pid = eff;
570
+ break;
571
+ }
572
+ }
573
+ }
574
+ else {
575
+ pid = src.pid;
576
+ }
577
+ const cg = pid !== undefined ? colorgroupsById.get(pid) : undefined;
578
+ if (cg) {
579
+ const triCount = indices.length / 3;
580
+ const outPos = new Float32Array(triCount * 9);
581
+ const outCol = new Float32Array(triCount * 9);
582
+ const outIdx = new Uint32Array(triCount * 3);
583
+ const objectPindex = src.pindex;
584
+ for (let t = 0; t < triCount; t++) {
585
+ const v1 = indices[t * 3];
586
+ const v2 = indices[t * 3 + 1];
587
+ const v3 = indices[t * 3 + 2];
588
+ const tp = src.triProps ? src.triProps[t] : undefined;
589
+ const p1 = tp?.p1 ?? objectPindex ?? 0;
590
+ const p2 = tp?.p2 ?? p1;
591
+ const p3 = tp?.p3 ?? p1;
592
+ // vertex-contiguous layout (x1 y1 z1 x2 y2 z2 x3 y3 z3 / R1 G1 B1 …):
593
+ // each emitted vertex owns 3 consecutive components.
594
+ const vs = [v1, v2, v3];
595
+ const ps = [p1, p2, p3];
596
+ for (let k = 0; k < 3; k++) {
597
+ const sv = vs[k];
598
+ const sp = ps[k];
599
+ outPos[t * 9 + k * 3] = positions[sv * 3];
600
+ outPos[t * 9 + k * 3 + 1] = positions[sv * 3 + 1];
601
+ outPos[t * 9 + k * 3 + 2] = positions[sv * 3 + 2];
602
+ outCol[t * 9 + k * 3] = cg[sp * 3];
603
+ outCol[t * 9 + k * 3 + 1] = cg[sp * 3 + 1];
604
+ outCol[t * 9 + k * 3 + 2] = cg[sp * 3 + 2];
605
+ }
606
+ outIdx[t * 3] = t * 3;
607
+ outIdx[t * 3 + 1] = t * 3 + 1;
608
+ outIdx[t * 3 + 2] = t * 3 + 2;
609
+ }
610
+ return {
611
+ id: src.id,
612
+ name: src.name,
613
+ positions: mat ? bakeTransform(outPos, mat) : outPos,
614
+ indices: outIdx,
615
+ vertexColors: outCol,
616
+ ...(srcMeta(src) ? { meta: srcMeta(src) } : {}),
617
+ };
618
+ }
619
+ const bases = pid !== undefined ? basematerialsById.get(pid) : undefined;
620
+ if (bases && bases.length > 0) {
621
+ const idx0 = src.triProps ? (src.triProps[0]?.p1 ?? src.pindex ?? 0) : (src.pindex ?? 0);
622
+ let same = true;
623
+ let refKey = '';
624
+ if (src.triProps) {
625
+ for (const tp of src.triProps) {
626
+ const effPid = tp.pid ?? src.pid;
627
+ const key = effPid !== undefined ? `${effPid}:${tp.p1 ?? src.pindex ?? 0}` : '';
628
+ if (refKey === '')
629
+ refKey = key;
630
+ else if (key !== refKey) {
631
+ same = false;
632
+ break;
633
+ }
634
+ }
635
+ }
636
+ else {
637
+ refKey = pid !== undefined ? `${pid}:${src.pindex ?? 0}` : '';
638
+ }
639
+ const base = bases[idx0];
640
+ // baseColor 只对「全部三角形同一 (pid, base)」的对象设置(P2 单色便捷
641
+ // 字段);无材质三角形或混合 pid/base 的对象 → undefined(走 materialGroups)。
642
+ const baseColor = same && refKey !== '' && base?.displaycolor
643
+ ? hexToRgb(base.displaycolor.slice(0, 7))
644
+ : undefined;
645
+ // P4(3MF Core 逐三角形材质):p1=p2=p3 的三角形按(有效 pid, base 索引)
646
+ // 分组成 materialGroups(连续三角形区间 + 该 base 的 displaycolor);
647
+ // 无材质(对象与三角形均无 pid)、混合 p1/p2/p3(顶点属性插值)跳过。
648
+ // 同色对象仍走 baseColor 便捷字段(P2 兼容);多 base 对象在
649
+ // materialGroups 表达(baseColor undefined)。
650
+ const groups = [];
651
+ const triProps = src.triProps;
652
+ const triCount = triProps?.length ?? indices.length / 3;
653
+ if (triProps) {
654
+ let runStart = -1;
655
+ let runKey = '';
656
+ let runColor;
657
+ const flush = (end) => {
658
+ if (runStart >= 0 && runColor) {
659
+ groups.push({ start: runStart, count: end - runStart, color: runColor });
660
+ }
661
+ runStart = -1;
662
+ runKey = '';
663
+ runColor = undefined;
664
+ };
665
+ for (let t = 0; t < triCount; t++) {
666
+ const tp = triProps[t];
667
+ let key = '';
668
+ let color;
669
+ if (tp) {
670
+ const effPid = tp.pid ?? src.pid;
671
+ if (effPid !== undefined) {
672
+ const bases = basematerialsById.get(effPid);
673
+ const bi = tp.p1 ?? src.pindex ?? 0;
674
+ if (bases && bi >= 0 && bi < bases.length && tp.p1 === tp.p2 && tp.p2 === tp.p3) {
675
+ const c = bases[bi]?.displaycolor;
676
+ if (c) {
677
+ key = `${effPid}:${bi}`;
678
+ const rgb = hexToRgb(c.slice(0, 7));
679
+ if (rgb)
680
+ color = [rgb[0], rgb[1], rgb[2]];
681
+ }
682
+ }
683
+ }
684
+ }
685
+ if (key !== '' && key === runKey)
686
+ continue;
687
+ flush(t);
688
+ if (key !== '') {
689
+ runStart = t;
690
+ runKey = key;
691
+ runColor = color;
692
+ }
693
+ }
694
+ flush(triCount);
695
+ }
696
+ // 单一分组且覆盖全部三角形 = 单色对象 → 走 baseColor(P2 便捷字段),
697
+ // 不挂 materialGroups(避免冗余双表达);仅部分覆盖或多组才用 materialGroups。
698
+ const singleFull = groups.length === 1 && groups[0].start === 0 && groups[0].count === triCount;
699
+ return {
700
+ id: src.id,
701
+ name: src.name,
702
+ positions: mat ? bakeTransform(positions, mat) : positions,
703
+ indices,
704
+ baseColor: baseColor ?? undefined,
705
+ ...(!singleFull && groups.length > 0 ? { materialGroups: groups } : {}),
706
+ ...(srcMeta(src) ? { meta: srcMeta(src) } : {}),
707
+ };
708
+ }
709
+ return {
710
+ id: src.id,
711
+ name: src.name,
712
+ positions: mat ? bakeTransform(positions, mat) : positions,
713
+ indices,
714
+ ...(srcMeta(src) ? { meta: srcMeta(src) } : {}),
715
+ };
716
+ }
717
+ // build → item → per-instance transforms baked to world space.
718
+ // `<build>` lives in the ROOT model document only.
719
+ const buildMatch = /<build\b[^>]*>/.exec(rootDoc);
720
+ let buildSec = '';
721
+ if (buildMatch) {
722
+ const buildStart = buildMatch.index;
723
+ const buildEnd = rootDoc.indexOf('</build>', buildStart);
724
+ buildSec = buildEnd >= 0 ? rootDoc.slice(buildStart, buildEnd) : rootDoc.slice(buildStart);
725
+ }
726
+ const itemRe = /<item\b[^>]*>/g;
727
+ const out = [];
728
+ let itemCount = 0;
729
+ let im;
730
+ while ((im = itemRe.exec(buildSec)) !== null) {
731
+ itemCount++;
732
+ const attrs = im[0];
733
+ const objId = Number(/(?:^|\s)objectid="([^"]+)"/.exec(attrs)?.[1]);
98
734
  const src = byObjectId.get(objId);
99
735
  if (!src)
100
736
  continue;
101
- const mat = item.getAttribute('transform');
102
- const [a, b, c, d, e, f, g, h, iz, p, q, r] = mat
103
- ? mat.trim().split(/\s+/).map(Number)
104
- : [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0];
105
- const base = allPos.length / 3;
106
- for (let v = 0; v < src.positions.length; v += 3) {
107
- const x = src.positions[v];
108
- const y = src.positions[v + 1];
109
- const z = src.positions[v + 2];
110
- allPos.push(a * x + d * y + g * z + p, b * x + e * y + h * z + q, c * x + f * y + iz * z + r);
111
- }
112
- for (let t = 0; t < src.indices.length; t++) {
113
- allIdx.push(src.indices[t] + base);
114
- }
737
+ // ST_Matrix3D: 4×3 (12) or 4×4 (16) whose trailing row is "0 0 0 1";
738
+ // malformed transforms are IGNORED (identity) — never bake NaN into
739
+ // geometry (mirrors the host's fast-3mf tolerance contract).
740
+ const tokens = parseTransformAttr(/\btransform="([^"]*)"/.exec(attrs)?.[1] ?? null);
741
+ out.push(...materialize(src, tokens ?? undefined, new Set([objId])));
115
742
  }
116
- // Fallback: if the build list is empty (some exporters omit <build>), emit
117
- // every object's mesh directly.
118
- if (itemEls.length === 0) {
119
- for (const [, src] of byObjectId) {
120
- const base = allPos.length / 3;
121
- for (let v = 0; v < src.positions.length; v++)
122
- allPos.push(src.positions[v]);
123
- for (let t = 0; t < src.indices.length; t++)
124
- allIdx.push(src.indices[t] + base);
743
+ if (itemCount === 0) {
744
+ // No build items: emit every `<resources>` object directly (¶ the empty
745
+ // `<build>` contract — NOT a fallback; see plan §8 decision 8).
746
+ for (const src of byObjectId.values()) {
747
+ out.push(...materialize(src, undefined, new Set([src.id])));
125
748
  }
126
749
  }
127
- if (allPos.length === 0) {
750
+ if (out.length === 0) {
128
751
  throw new Error('[mesh/threemf] no buildable geometry found in 3dmodel.model');
129
752
  }
130
- return {
131
- positions: new Float32Array(allPos),
132
- indices: new Uint32Array(allIdx),
133
- unit,
134
- };
753
+ return { unitName, objects: out, ...(fileMeta ? { fileMeta } : {}) };
135
754
  }
136
755
  /**
137
- * Parse 3MF bytes into a mm-base mesh.
756
+ * Parse 3MF bytes into a structured archive.
757
+ *
758
+ * Supports Bambu's multi-file layout: `3D/3dmodel.model` (root, holds the
759
+ * `<build>` and component-container skeletons) plus per-object sub-models
760
+ * `3D/Objects/*.model` whose `<resources>` are merged by object id
761
+ * (sub-models first, root last — root wins collisions).
762
+ *
138
763
  * @param buffer - the raw .3mf (ZIP) bytes.
139
- * @returns the parsed mesh plus the declared source unit.
764
+ * @returns an `ThreemfArchive` with mm-base per-instance objects, the declared
765
+ * source unit, and every non-model archive entry.
140
766
  * @throws when the buffer is not a 3MF/ZIP archive or the unit is invalid.
141
767
  */
142
768
  export async function parseThreemf(buffer) {
143
769
  let entries;
144
770
  try {
145
- entries = unzipSync(new Uint8Array(buffer));
771
+ entries = readZipEntries(new Uint8Array(buffer));
146
772
  }
147
773
  catch {
148
774
  throw new Error('[mesh/threemf] not a valid 3MF ZIP archive');
149
775
  }
150
- const modelKey = Object.keys(entries).find((k) => k.toLowerCase() === '3d/3dmodel.model');
151
- if (!modelKey) {
776
+ const rootKey = [...entries.keys()].find((k) => k.toLowerCase() === '3d/3dmodel.model');
777
+ if (!rootKey) {
152
778
  throw new Error('[mesh/threemf] missing 3D/3dmodel.model entry');
153
779
  }
154
- const raw = entries[modelKey];
155
- const xml = strFromU8(raw);
156
- const parsed = await parseModelXml(xml);
157
- return {
158
- positions: parsed.positions,
159
- indices: parsed.indices,
160
- sourceUnit: parsed.unit,
161
- };
780
+ // Sub-model documents first (fast-3mf collection order), root model last.
781
+ const docKeys = [];
782
+ for (const k of entries.keys()) {
783
+ if (k.toLowerCase() === rootKey.toLowerCase())
784
+ continue;
785
+ if (/^3d\/.+\/[^/]+\.model$/i.test(k))
786
+ docKeys.push(k);
787
+ }
788
+ docKeys.push(rootKey);
789
+ const docTexts = docKeys.map((k) => new TextDecoder().decode(entries.get(k)));
790
+ const parsed = parseModelXml(docTexts);
791
+ // `extraEntries` = everything that is NOT a model document — Bambu's
792
+ // `Metadata/model_settings.config`, `Metadata/project_settings.config`,
793
+ // `Metadata/thumbnail.png`, etc.
794
+ const extraEntries = new Map();
795
+ for (const [k, v] of entries) {
796
+ if (/\.model$/i.test(k))
797
+ continue;
798
+ extraEntries.set(k, v);
799
+ }
800
+ return { unit: parsed.unitName, objects: parsed.objects, extraEntries, ...(parsed.fileMeta ? { fileMeta: parsed.fileMeta } : {}) };
162
801
  }
163
802
  //# sourceMappingURL=threemf-loader.js.map