three-slicer 0.1.5 → 0.1.6

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.
package/README.md CHANGED
@@ -4,6 +4,8 @@ Browser/WASM 3D-printing slicer package derived from [OrcaSlicer](https://github
4
4
 
5
5
  The package is published as a single npm package, `three-slicer`, with subpath exports for the engine, viewer, components, worker, and extracted OrcaSlicer data.
6
6
 
7
+ ![A sliced Benchy in the viewer's Preview tab — organic tree supports, per-feature toolpath colors, dual layer-range slider, filament and print-time estimates](https://raw.githubusercontent.com/kimgh06/Web_Three_Slicer/main/web/viewer/public/usage.png)
8
+
7
9
  ## Links
8
10
 
9
11
  - Package: [npmjs.com/package/three-slicer](https://www.npmjs.com/package/three-slicer)
@@ -19,6 +21,7 @@ The package is published as a single npm package, `three-slicer`, with subpath e
19
21
  - React `<SettingsPanel/>` generated from extracted OrcaSlicer metadata.
20
22
  - Vendor printer, print-process and filament preset catalogs, with one material assignable per extruder.
21
23
  - Support painting and material painting, plus per-tool filament and purge statistics.
24
+ - 3MF **project** import in the viewer: a slicer-written `.3mf` (OrcaSlicer/BambuStudio save, MakerWorld download) restores its plate layout, project settings, and painted facets — not just the meshes.
22
25
  - Extracted data files for custom UIs: config schema, UI tree, toggle rules, invalidation map, and the printer/process/filament catalogs.
23
26
  - Automatic multithreaded WASM selection on cross-origin-isolated browser pages.
24
27
 
@@ -107,7 +110,7 @@ export default function App() {
107
110
  }
108
111
  ```
109
112
 
110
- The viewer handles model loading, drag and drop, transform controls, multi-plate layout, worker slicing, GPU toolpath preview, and G-code export. It supports STL, OBJ, 3MF (including the production extension used by Orca/Bambu/Prusa), AMF, and PLY out of the box; other formats such as STEP can be added with `registerLoader()`. Viewer and component styles are bundled into their Shadow DOM roots, so host app CSS does not need to import package CSS.
113
+ The viewer handles model loading, drag and drop, transform controls, multi-plate layout, worker slicing, GPU toolpath preview, and G-code export. It supports STL, OBJ, 3MF (including the production extension used by Orca/Bambu/Prusa), AMF, and PLY out of the box; other formats such as STEP can be added with `registerLoader()`. A `.3mf` written by a slicer is treated as a project, not just geometry: its plate layout, project settings, and support/material painting are restored on import (where a facet carries both paint kinds, material paint wins and the dropped support paint is reported). Viewer and component styles are bundled into their Shadow DOM roots, so host app CSS does not need to import package CSS.
111
114
 
112
115
  ## Quick Start: Settings Panel
113
116
 
@@ -4,10 +4,10 @@ Reusable React components for the browser slicer. Props-driven — no global sta
4
4
 
5
5
  ## `<SettingsPanel/>`
6
6
 
7
- An OrcaSlicer-style settings form generated from `@three-slicer/data` (schema + UI tree + toggle rules): tab/page/group navigation, mode filter (simple/advanced/expert), search across 923 options, dirty markers with per-option reset, and enable/disable rules evaluation.
7
+ An OrcaSlicer-style settings form generated from `three-slicer/data` (schema + UI tree + toggle rules): tab/page/group navigation, mode filter (simple/advanced/expert), search across 923 options, dirty markers with per-option reset, and enable/disable rules evaluation.
8
8
 
9
9
  ```bash
10
- npm i three-slicer/components react
10
+ npm i three-slicer # react is a peer dependency — npm installs it for you
11
11
  ```
12
12
 
13
13
  ```jsx
package/data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @three-slicer/data
1
+ # three-slicer/data
2
2
 
3
- OrcaSlicer configuration metadata extracted from the original C++ sources, as plain JSON. Used by `three-slicer` (defaults, settings mapping) and `three-slicer/components` (settings UI), and usable standalone for building slicer UIs.
3
+ OrcaSlicer configuration metadata extracted from the original C++ sources, shipped inside the `three-slicer` package as its `/data` subpath. Used by `three-slicer` (defaults, settings mapping) and `three-slicer/components` (settings UI), and usable standalone for building slicer UIs.
4
4
 
5
5
  | File | Contents |
6
6
  |---|---|
package/engine/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Browser/WASM 3D-slicing kernel reverse-engineered from [OrcaSlicer](https://github.com/OrcaSlicer/OrcaSlicer). Slices binary STL to G-code entirely client-side (or in Node) — no server, no native binaries.
4
4
 
5
- **No three.js dependency.** This is a headless SDK; the only dependency is `@three-slicer/data` (config metadata JSON). For a ready-made React viewer see `three-slicer/viewer`.
5
+ **No three.js dependency.** This is a headless SDK with zero runtime dependencies; the config metadata it reads ships inside the same package (`three-slicer/data`). For a ready-made React viewer see `three-slicer/viewer`.
6
6
 
7
7
  Ported from the original C++ sources: Clipper polygon ops, Arachne variable-width walls, real fill patterns (gyroid TPMS / honeycomb / 3D-honeycomb / crosshatch / concentric), tree supports, pressure equalizer, arc fitting (G2/G3), scarf seams, ironing, multi-material with prime tower. Kernel changes are gated by a 120+ invariant test suite and golden byte-identical G-code checks.
8
8
 
@@ -38,7 +38,7 @@ Off-main-thread (browser): `new Worker(engineWorkerURL(), { type: 'module' })`
38
38
  Two levels — use whichever fits:
39
39
 
40
40
  1. **Kernel params (this package's native contract)** — a flat JSON object of ~90 keys (`layer_height`, `wall_loops`, `infill_density` 0–1, `sparse_infill_pattern`, `enable_support`, `wall_generator: 'classic' | 'arachne'`, …). See `src/settings.js` for the full shape.
41
- 2. **OrcaSlicer settings map** — a sparse `{schemaKey: value}` map using original OrcaSlicer option keys (923 defined in `@three-slicer/data`); unset keys fall back to schema defaults:
41
+ 2. **OrcaSlicer settings map** — a sparse `{schemaKey: value}` map using original OrcaSlicer option keys (923 defined in `three-slicer/data`); unset keys fall back to schema defaults:
42
42
 
43
43
  ```js
44
44
  import { deriveKernelParams } from 'three-slicer/settings'
@@ -217,9 +217,47 @@ export function normalizeProjectSettings(raw) {
217
217
  return { settings, applied, skipped }
218
218
  }
219
219
 
220
+ // The exact inverse, for WRITING a project_settings.config: every value back to the string shape upstream
221
+ // serializes (bool -> "1"/"0", point -> "XxY", a points group -> "X1xY1,X2xY2"), because that is what every other
222
+ // slicer's reader — and normalizeProjectSettings above — expects to coerce FROM. Writing the JS types raw would
223
+ // hand OrcaSlicer a bool it reads with its own string parser and this package a round-trip that only works here.
224
+ // Keys the schema does not define are dropped for the same reason normalize drops them: they could not be typed.
225
+ const pointToText = (point) => (Array.isArray(point) ? `${point[0]}x${point[1]}` : String(point))
226
+
227
+ function serializeScalar(type, value) {
228
+ if (value == null) return ''
229
+ switch (type) {
230
+ case 'coBool': case 'coBools':
231
+ return value ? '1' : '0'
232
+ case 'coPoint': case 'coPoints':
233
+ return pointToText(value)
234
+ case 'coPointsGroups':
235
+ return Array.isArray(value) ? value.map(pointToText).join(',') : String(value)
236
+ default:
237
+ return String(value) // numbers, percents, strings, enums — String() is upstream's own spelling for all of them
238
+ }
239
+ }
240
+
241
+ /** A settings map (real JS types) -> the all-strings object a 3mf's `project_settings.config` stores. */
242
+ export function serializeProjectSettings(settings) {
243
+ const out = {}
244
+ for (const [key, value] of Object.entries(settings || {})) {
245
+ const type = schema[key]?.type
246
+ if (!type) continue
247
+ // A coPoint's value IS an array ([x, y]) — the generic array path would serialize its two numbers separately.
248
+ out[key] = (type !== 'coPoint' && Array.isArray(value))
249
+ ? value.map(entry => serializeScalar(type, entry))
250
+ : serializeScalar(type, value)
251
+ }
252
+ return out
253
+ }
254
+
220
255
 
221
256
  // Right-panel settings values -> kernel parameters (derived from schema keys)
222
- export function deriveKernelParams(settings) {
257
+ // opts.plate: which plate's entry to take from per-plate array options (wipe_tower_x/y — upstream coFloats,
258
+ // one entry per plate). Defaults to 0, so every existing caller reads exactly what it always did.
259
+ export function deriveKernelParams(settings, opts) {
260
+ const plate = opts?.plate ?? 0
223
261
  const S = k => settingScalar(settings, k)
224
262
  const num = (k, d) => { const v = Number(S(k)); return Number.isFinite(v) ? v : d }
225
263
  const bool = (k, d) => { const v = settingRaw(settings, k); return typeof v === 'boolean' ? v : (Array.isArray(v) ? !!v[0] : (v == null ? d : !!v)) }
@@ -283,7 +321,9 @@ export function deriveKernelParams(settings) {
283
321
  // A hole (a material that overrides nothing) is filled with the value tool 0 resolved to, because the kernel
284
322
  // reads the array positionally and cannot tell "absent" from "0".
285
323
  // Prime tower keys, omitted when unset so the kernel/viewer defaults stay in charge (same rule as supportTools).
286
- // wipe_tower_x/y are coFloats upstream — one entry per plate — so the scalar reduction takes the first set one.
324
+ // wipe_tower_x/y are coFloats upstream — one entry per plate — indexed by opts.plate. A hole (null entry) means
325
+ // nobody chose a position for THAT plate, so the automatic placement stands there; a scalar applies to every
326
+ // plate (the pre-array form, kept readable so old saved settings keep meaning what they meant).
287
327
  const towerSettings = {}
288
328
  {
289
329
  const width = num('prime_tower_width', 0)
@@ -292,7 +332,9 @@ export function deriveKernelParams(settings) {
292
332
  // a coordinate off the front of a 200mm bed. Taking it as "the user chose a position" put the tower outside
293
333
  // the plate and disabled the automatic placement entirely — measured as a tower at z=-127.5 on a 200mm bed.
294
334
  // Absent from the map means nobody chose one, and the placement beside the model stands.
295
- const chosen = (key) => { const v = settings?.[key]; const n = Number(Array.isArray(v) ? v[0] : v)
335
+ const chosen = (key) => { const raw = settings?.[key]
336
+ const v = Array.isArray(raw) ? raw[plate] : raw
337
+ const n = Number(v)
296
338
  return (v != null && v !== '' && Number.isFinite(n)) ? n : null }
297
339
  const towerX = chosen('wipe_tower_x'), towerY = chosen('wipe_tower_y')
298
340
  if (towerX != null) towerSettings.prime_tower_x = towerX
@@ -109,6 +109,12 @@ const paintedReply = (Module, message, paintedState) => {
109
109
  self.onmessage = async (e) => {
110
110
  const d = e.data
111
111
  try {
112
+ // Asking for the painting must not be what LOADS the kernel. Every other command needs it, but if this worker
113
+ // has never loaded it then selector_prepare has never run either, so there is no painting to report — and
114
+ // making a save wait on a 4MB WASM load before the first slice is seconds of nothing, for an empty answer.
115
+ if (d.cmd === 'exportPaint' && !modPromise) {
116
+ self.postMessage({ type: 'paintExport', supported: false, facets: [], hex: '' }); return
117
+ }
112
118
  if (!modPromise) modPromise = loadCore()
113
119
  const Module = await modPromise
114
120
  // Warmup: only load the kernel (+ spawn the mt pthread pool) ahead of time — removes the perceived load on the first slice
@@ -160,6 +166,17 @@ self.onmessage = async (e) => {
160
166
  reply.applied = applied
161
167
  self.postMessage(reply); return
162
168
  }
169
+ // exportPaint: the reverse of importPaint — every marked facet's split tree as {facets, hex} in the CURRENT
170
+ // selector's numbering, the same parallel-array pairing importPaint takes (hex is one newline-joined blob).
171
+ // The caller (3mf save) rebases the facet indices back onto per-object numbering; this worker cannot, because
172
+ // which objects were merged in what order is the viewer's knowledge. A kernel built before the binding existed
173
+ // reports supported:false rather than throwing — the save then falls back to the paint it imported.
174
+ if (d.cmd === 'exportPaint') {
175
+ const exported = Module.selector_export_paint ? Module.selector_export_paint() : null
176
+ self.postMessage({ type: 'paintExport', supported: !!Module.selector_export_paint,
177
+ facets: exported?.facets ?? [], hex: exported?.hex ?? '' })
178
+ return
179
+ }
163
180
  // clear wipes every state at once, so each requested count is 0 by construction — no need to ask the kernel back.
164
181
  if (d.cmd === 'clear') {
165
182
  Module.selector_clear()
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-slicer",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Browser/WASM 3D-printing slicer reverse-engineered from OrcaSlicer: slicing engine (Arachne walls, tree/grid supports, multi-material), React viewer with GPU toolpath preview, and schema-driven settings UI.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -16,7 +16,7 @@ export function settingScalar(settings: SlicerSettings | null | undefined, key:
16
16
  * ponytail: 53 returned keys, so Record instead of listing each — kernel params are passed straight to slice(),
17
17
  * not an API consumers read field by field. Expand it when field access is actually needed.
18
18
  */
19
- export function deriveKernelParams(settings: SlicerSettings | null | undefined): Record<string, unknown>
19
+ export function deriveKernelParams(settings: SlicerSettings | null | undefined, opts?: { plate?: number }): Record<string, unknown>
20
20
 
21
21
  /**
22
22
  * A 3mf project's raw `Metadata/project_settings.config` -> a settings map this API accepts.
@@ -30,6 +30,13 @@ export function normalizeProjectSettings(raw: Record<string, unknown> | null | u
30
30
  skipped: string[]
31
31
  }
32
32
 
33
+ /**
34
+ * The inverse, for WRITING a 3mf's `Metadata/project_settings.config`: every value back to the string shape
35
+ * upstream serializes (a bool as "1"/"0", a point as "XxY", a points group as "X1xY1,X2xY2"), so another slicer
36
+ * reads what it expects. Keys the config schema does not define are dropped.
37
+ */
38
+ export function serializeProjectSettings(settings: SlicerSettings | null | undefined): Record<string, string | string[]>
39
+
33
40
  // ---- Lazily loaded presets --------------------------------------------------
34
41
  // Both facades hide the column layout of the artifact they wrap; the promise is created once and reused.
35
42
 
package/viewer/README.md CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  3D slicer viewer as a React component: three.js viewport (orbit/transform gizmos), model import (STL/OBJ/3MF/AMF/PLY + pluggable formats such as STEP, multi-object, drag & drop), Web Worker slicing via `three-slicer`, support and material painting, per-extruder filament presets, multi-plate, and a GPU-instanced volumetric toolpath preview ported from OrcaSlicer's libvgcode (millions of segments in a single draw path, per-feature colors, layer range slider, G-code export).
4
4
 
5
+ ![The Prepare tab — drag-and-drop viewport with the printer, filament and process cards in the right sidebar](../../slice-sidebar.png)
6
+
5
7
  ```bash
6
- npm i three-slicer/viewer react react-dom three@^0.160.0
8
+ npm i three-slicer # react, react-dom and three are peer dependencies — npm installs them for you
7
9
  ```
8
10
 
9
11
  ```jsx
10
12
  import { useState } from 'react'
11
13
  import Viewport from 'three-slicer/viewer'
12
- import 'three-slicer/viewer/styles.css'
13
14
 
14
15
  function App() {
15
16
  const [settings, setSettings] = useState({}) // OrcaSlicer schema keys; sparse — defaults fill the rest
@@ -17,8 +18,12 @@ function App() {
17
18
  }
18
19
  ```
19
20
 
21
+ Styles are injected into the component's Shadow DOM root — nothing to import, and host CSS cannot collide.
22
+
20
23
  Slice parameters are derived from `settings` via `three-slicer`'s schema mapping — pair it with `three-slicer/components`' `<SettingsPanel/>` sharing the same state for a full slicer UI.
21
24
 
25
+ A `.3mf` written by a slicer (OrcaSlicer/BambuStudio save, MakerWorld download) imports as a **project**: plate layout, project settings, and support/material painting are restored, not just the meshes. Where a facet carries both paint kinds, material paint wins and the dropped support paint is reported.
26
+
22
27
  Props: `settings`, `setSettings`, and three optional React-node slots rendered in the right sidebar —
23
28
  `processPanel` (the process card), `motionPanel` (folded into the printer card) and `filamentPanel`
24
29
  (folded into the filament card, next to the material picker).