three-slicer 0.1.3 → 0.1.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.
- package/README.md +67 -8
- package/components/README.md +1 -1
- package/components/dist/SettingsPanel.js +12 -12
- package/data/README.md +39 -2
- package/data/config-schema.json +164 -0
- package/data/filaments.js +2 -0
- package/data/processes.js +1 -1
- package/data/ui-tree.json +21 -2
- package/engine/README.md +56 -4
- package/engine/src/data.js +2 -0
- package/engine/src/settings.js +214 -6
- package/engine/src/slicer.worker.js +154 -5
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/package.json +15 -2
- package/types/data/filaments.js.d.ts +27 -0
- package/types/data.d.ts +7 -0
- package/types/settings-keys.d.ts +35 -1
- package/types/settings.d.ts +42 -0
- package/types/viewer-gcode.d.ts +29 -0
- package/types/viewer-toolpath.d.ts +9 -1
- package/types/viewer.d.ts +53 -0
- package/viewer/README.md +48 -2
- package/viewer/dist/Viewport.js +2706 -1525
- package/viewer/dist/gcode_parse.js +190 -0
- package/viewer/dist/make_worker.js +7 -0
- package/viewer/dist/model_loaders.js +139 -211
- package/viewer/dist/parse_3mf-8oCnCJNK.js +216 -0
- package/viewer/dist/parse_3mf.worker.js +10 -0
- package/viewer/dist/toolpath_gpu.js +162 -137
- package/viewer/styles.css +56 -2
package/data/ui-tree.json
CHANGED
|
@@ -670,14 +670,33 @@
|
|
|
670
670
|
"group": "Retraction",
|
|
671
671
|
"line": 4000,
|
|
672
672
|
"options": [
|
|
673
|
-
"
|
|
673
|
+
"filament_retraction_length",
|
|
674
|
+
"filament_z_hop",
|
|
675
|
+
"filament_z_hop_types",
|
|
676
|
+
"filament_retract_lift_above",
|
|
677
|
+
"filament_retract_lift_below",
|
|
678
|
+
"filament_retract_lift_enforce",
|
|
679
|
+
"filament_retraction_speed",
|
|
680
|
+
"filament_deretraction_speed",
|
|
681
|
+
"filament_retract_restart_extra",
|
|
682
|
+
"filament_retraction_minimum_travel",
|
|
683
|
+
"filament_retract_when_changing_layer",
|
|
684
|
+
"filament_wipe",
|
|
685
|
+
"filament_wipe_distance",
|
|
686
|
+
"filament_retract_before_wipe",
|
|
687
|
+
"filament_retract_after_wipe",
|
|
688
|
+
"filament_long_retractions_when_cut",
|
|
689
|
+
"filament_retraction_distances_when_cut"
|
|
674
690
|
]
|
|
675
691
|
},
|
|
676
692
|
{
|
|
677
693
|
"group": "Ironing",
|
|
678
694
|
"line": 4061,
|
|
679
695
|
"options": [
|
|
680
|
-
"
|
|
696
|
+
"filament_ironing_flow",
|
|
697
|
+
"filament_ironing_spacing",
|
|
698
|
+
"filament_ironing_inset",
|
|
699
|
+
"filament_ironing_speed"
|
|
681
700
|
]
|
|
682
701
|
}
|
|
683
702
|
]
|
package/engine/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm i three-slicer
|
|
|
17
17
|
```js
|
|
18
18
|
import { createSlicer } from 'three-slicer'
|
|
19
19
|
|
|
20
|
-
const slicer = await createSlicer() // loads the WASM kernel (~3.
|
|
20
|
+
const slicer = await createSlicer() // loads the WASM kernel (~3.3 MB, base64-inlined)
|
|
21
21
|
|
|
22
22
|
// Batch: full result at once
|
|
23
23
|
const r = slicer.slice(stlArrayBuffer, { layer_height: 0.2, wall_loops: 2 })
|
|
@@ -37,15 +37,67 @@ Off-main-thread (browser): `new Worker(engineWorkerURL(), { type: 'module' })`
|
|
|
37
37
|
|
|
38
38
|
Two levels — use whichever fits:
|
|
39
39
|
|
|
40
|
-
1. **Kernel params (this package's native contract)** — a flat JSON object of ~
|
|
41
|
-
2. **OrcaSlicer settings map** — a sparse `{schemaKey: value}` map using original OrcaSlicer option keys (
|
|
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:
|
|
42
42
|
|
|
43
43
|
```js
|
|
44
44
|
import { deriveKernelParams } from 'three-slicer/settings'
|
|
45
45
|
slicer.slice(stl, deriveKernelParams({ layer_height: 0.25, sparse_infill_density: 15 }))
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
**Known limit:** `deriveKernelParams` maps a curated whitelist of
|
|
48
|
+
**Known limit:** `deriveKernelParams` maps a curated whitelist of 92 schema keys to kernel params. Editing other schema keys has no slicing effect (they exist for UI/metadata). Scalar options use their first element only; the filament options listed under *Materials* below are the exception — those keep every extruder's entry.
|
|
49
|
+
|
|
50
|
+
## Materials and multi-material
|
|
51
|
+
|
|
52
|
+
A material is a **filament preset**: a set of schema values (temperatures, flow, diameter, cooling, retraction/z-hop overrides) that a printer profile declares itself compatible with. Read the catalog through the facade, never by decoding `three-slicer/data/filaments.js` by hand:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { filamentPresets, deriveKernelParams } from 'three-slicer/settings'
|
|
56
|
+
|
|
57
|
+
const filaments = await filamentPresets() // lazy — the artifact loads on first call
|
|
58
|
+
filaments.listFor('Bambu Lab X1 Carbon 0.4 nozzle') // [{name, type, vendor}, …]
|
|
59
|
+
filaments.recommendedFor('Bambu Lab X1 Carbon 0.4 nozzle') // the vendor's shortlist, filtered to the compatible set
|
|
60
|
+
Object.assign(settings, filaments.settingsFor('Bambu PLA Basic @BBL X1C'))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**One material per extruder.** Upstream stores every filament option as one entry per extruder, so a multi-material settings map writes each extruder's material at its own index:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
const params = {
|
|
67
|
+
...deriveKernelParams({
|
|
68
|
+
nozzle_temperature: [255, 220], // T0 ABS, T1 PLA
|
|
69
|
+
filament_flow_ratio: [0.95, 1.0],
|
|
70
|
+
}),
|
|
71
|
+
// -> { extruder_nozzle_temp: [255, 220], extruder_flow_ratio: [0.95, 1.0], … }
|
|
72
|
+
extruder_count: 2, // not a derived key — set it on the params directly
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The kernel reads those arrays **positionally** and reloads the whole loaded-filament set — diameter, flow, retraction length/speed, z-hop, plus an `M109` when the temperatures actually disagree — at every `T` change. A single-element (or absent) option produces **no** `extruder_*` array at all, and the kernel then reads its scalars exactly as before, so a single-material slice is byte-identical to what it was before this feature existed.
|
|
77
|
+
|
|
78
|
+
`support_filament` / `support_interface_filament` are 1-based filament indices (`0` = "Default", keep the loaded tool) selecting which extruder prints the support base/raft and the support interface. `0` is omitted from the params rather than sent, so it emits no `T` command anywhere and leaves the support G-code byte-identical.
|
|
79
|
+
|
|
80
|
+
### Painting a region onto another extruder
|
|
81
|
+
|
|
82
|
+
Painting is the only way a **single** object can print in two materials — there is no triangle boundary to split on, only painted facets. The painted facets of each state are projected per layer and the sliced polygon is partitioned against them; where two extruders claim the same area the higher-numbered one wins (a total order on a small integer, so the result never depends on paint or clipper ordering).
|
|
83
|
+
|
|
84
|
+
Painting states are upstream's `EnforcerBlockerType`, one enum serving two jobs: `1` = ENFORCER = **Extruder1**, `2` = BLOCKER = **Extruder2**, `3..16` = Extruder3..16. The state-addressed protocol lives on the worker (`three-slicer/worker`); the direct handle's `slice.paint({enforcer})` is the original boolean pair only.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
worker.postMessage({ cmd: 'prepare', stl }) // -> {type:'prepared', facets}
|
|
88
|
+
worker.postMessage({ cmd: 'paint', state: 3, facet, hx, hy, hz, cx, cy, cz, radius, states: [1,2,3] })
|
|
89
|
+
// -> {type:'painted', enf, blk, counts: {1: n, 2: n, 3: n}}
|
|
90
|
+
worker.postMessage({ cmd: 'erase', facet, hx, hy, hz, cx, cy, cz, radius }) // back to the default extruder
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`erase` is its **own command**, not `{cmd:'paint', state: 0}`: embind coerces a JS `false` to the int `0` == NONE, and the legacy blocker brush sends exactly that `false` — so the state path rejects `0` outright and only `erase`, which takes no state argument, can clear anything. A message without `state`/`states` gets the original reply verbatim, so existing listeners are unaffected.
|
|
94
|
+
|
|
95
|
+
Two consequences worth knowing before you build on this:
|
|
96
|
+
|
|
97
|
+
- Because one selector serves both jobs, **a support BLOCKER paint and an Extruder2 paint are the same mark**, and a facet can hold only one of them. The two brushes are therefore mutually exclusive, which is a data constraint, not a UI simplification.
|
|
98
|
+
- The painted multi-material path is taken only when support is **off** — it emits no support at all. **Paint and support cannot currently produce two materials together.**
|
|
99
|
+
|
|
100
|
+
Results carry the split: `stats.filament_mm_by_tool` (indexed by tool number, one slot per extruder, sums to `filament_mm`) and `stats.filament_mm_purge` (the prime/wipe tower share, already included in the per-tool figures). In the streamed toolpath, `paths[k+3]` encodes `role + tool * 16` — mask with `& 15` for the role and `>>> 4` for the tool.
|
|
49
101
|
|
|
50
102
|
## License
|
|
51
103
|
|
package/engine/src/data.js
CHANGED
|
@@ -16,3 +16,5 @@ export { default as printers } from 'three-slicer/data/printers.json' with { typ
|
|
|
16
16
|
// other direction: a dynamic JSON import needs the attribute in Node, and that same attribute makes browsers
|
|
17
17
|
// reject the text/javascript response a dev server gives for .json.
|
|
18
18
|
export const loadProcesses = () => import('three-slicer/data/processes.js').then(m => m.default)
|
|
19
|
+
// Same story for the filament presets (~540 KB, only needed once a material picker opens).
|
|
20
|
+
export const loadFilaments = () => import('three-slicer/data/filaments.js').then(m => m.default)
|
package/engine/src/settings.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
// Maps the actual values of the (editable) right-hand settings panel -> kernel slice parameters.
|
|
2
2
|
// - Settings state is a sparse map (key->value): only edited keys are stored, otherwise the config-schema default.
|
|
3
3
|
// - For vector types (coFloats/coInts, …) only the first element is used/edited (simplification).
|
|
4
|
-
import { schema, printers, loadProcesses } from './data.js'
|
|
4
|
+
import { schema, printers, loadProcesses, loadFilaments } from './data.js'
|
|
5
5
|
|
|
6
6
|
export function schemaDefault(key) { return schema[key]?.default }
|
|
7
7
|
export function settingRaw(settings, key) { return (settings && key in settings) ? settings[key] : schemaDefault(key) }
|
|
8
8
|
// Normalize to a scalar ([0] for vectors)
|
|
9
|
-
|
|
9
|
+
// A per-extruder column reduced to the single value the kernel's scalar reader wants. The FIRST SET entry, not
|
|
10
|
+
// literally index 0: the filament card writes one column per extruder and leaves a null wherever that extruder has
|
|
11
|
+
// no preset, so an assignment that starts at T2 puts a null at index 0. Taking that null made filament_diameter 0,
|
|
12
|
+
// which divides into the extrusion maths and turned the whole slice into NaN — measured as
|
|
13
|
+
// "; filament used [mm] = nan, nan" from a print whose T1 had no material picked.
|
|
14
|
+
export function settingScalar(settings, key) {
|
|
15
|
+
const v = settingRaw(settings, key)
|
|
16
|
+
if (!Array.isArray(v)) return v
|
|
17
|
+
return v.find(entry => entry != null && entry !== '') ?? v[0]
|
|
18
|
+
}
|
|
10
19
|
|
|
11
20
|
// Stage 8: real ported Fill patterns (gyroid TPMS/honeycomb/3dhoneycomb/crosshatch/concentric) + the older approximations
|
|
12
21
|
const KERNEL_PATTERNS = ['rectilinear', 'grid', 'triangles', 'zigzag', 'gyroid', 'gyroid_approx',
|
|
@@ -67,6 +76,45 @@ export function processPresets() {
|
|
|
67
76
|
return processesPromise
|
|
68
77
|
}
|
|
69
78
|
|
|
79
|
+
// Filament (material) presets — same lazy facade as the process presets above, over the ~540 KB filaments.js.
|
|
80
|
+
// Entries come out as objects rather than bare names because a material picker groups by type, and the type
|
|
81
|
+
// label only exists in this file. Both lists are in upstream order: byPrinter is the compatible set,
|
|
82
|
+
// defaultsByModel the vendor's recommendation (a subset, so the two overlap by design).
|
|
83
|
+
let filamentsPromise = null
|
|
84
|
+
export function filamentPresets() {
|
|
85
|
+
filamentsPromise ??= loadFilaments().then(data => {
|
|
86
|
+
// type/vendor are empty strings when the profile chain declares neither — the picker buckets those itself
|
|
87
|
+
const view = i => { const [name, , type, vendor] = data.presets[i]; return { name, type, vendor } }
|
|
88
|
+
return {
|
|
89
|
+
/** Every key a filament preset can set — clear these before applying a different material */
|
|
90
|
+
keys: data.keys,
|
|
91
|
+
/** Every material in the catalog — for a picker shown before any printer is chosen */
|
|
92
|
+
all: () => data.presets.map((_, i) => view(i)),
|
|
93
|
+
/** Materials compatible with a printer profile, as `{name, type, vendor}` */
|
|
94
|
+
listFor: (printerProfileName) => (data.byPrinter[printerProfileName] ?? []).map(view),
|
|
95
|
+
/** The vendor's recommended materials for that printer's model, same shape as listFor.
|
|
96
|
+
* Filtered to the compatible set, which is not redundant: the recommendation is declared on the machine
|
|
97
|
+
* *model* and so is nozzle-agnostic, while its entries name nozzle-specific presets ("… @Kobra 3 0.4 nozzle").
|
|
98
|
+
* On a 0.2 nozzle profile 41% of the raw entries (1749 of 4259 across all printers) name a material whose
|
|
99
|
+
* own compatible list excludes it — offering those would apply a preset upstream considers incompatible. */
|
|
100
|
+
recommendedFor: (printerProfileName) => {
|
|
101
|
+
const compatible = new Set(data.byPrinter[printerProfileName] ?? [])
|
|
102
|
+
return (data.defaultsByModel[printerEntry(printerProfileName)?.[2] ?? ''] ?? [])
|
|
103
|
+
.filter(i => compatible.has(i)).map(view)
|
|
104
|
+
},
|
|
105
|
+
/** The settings a material applies, ready to merge. `null` when unknown. */
|
|
106
|
+
settingsFor: (presetName) => {
|
|
107
|
+
const preset = data.presets.find(([name]) => name === presetName)
|
|
108
|
+
if (!preset) return null
|
|
109
|
+
const row = data.sets[preset[1]], out = {}
|
|
110
|
+
data.keys.forEach((key, i) => { if (row[i] != null) out[key] = row[i] })
|
|
111
|
+
return out
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
return filamentsPromise
|
|
116
|
+
}
|
|
117
|
+
|
|
70
118
|
function printerEntry(profileName) {
|
|
71
119
|
for (const models of Object.values(printers.byVendor)) {
|
|
72
120
|
const entry = models[profileName]
|
|
@@ -88,12 +136,102 @@ export function printerSettings(profileName) {
|
|
|
88
136
|
/** The vendor's recommended process preset for a printer, or '' when the profile names none. */
|
|
89
137
|
export function printerDefaultPreset(profileName) { return printerEntry(profileName)?.[3] ?? '' }
|
|
90
138
|
|
|
139
|
+
// ---- Imported project settings (a slicer-written 3mf's Metadata/project_settings.config) --------------------
|
|
140
|
+
// Upstream serializes every option as a STRING (or an array of them, one per extruder) — a bool is "0"/"1" and a
|
|
141
|
+
// float is "0.2". This settings map holds real JS types, and the difference is not cosmetic: `bool()` below reads
|
|
142
|
+
// a value with `!!v`, and `!!"0"` is TRUE — importing raw would turn every disabled option on. So each value is
|
|
143
|
+
// coerced by its schema type, and anything the schema does not know (inherits_group, *_settings_id, version, the
|
|
144
|
+
// whole preset-bookkeeping half of that file) is dropped rather than carried into a map keyed by schema keys.
|
|
145
|
+
// The `inherits` / `different_settings_to_system` machinery upstream applies on top of this (Preset.cpp:2577) is
|
|
146
|
+
// deliberately NOT reproduced: it exists to reconcile a stored preset against a LOCAL vendor preset database of a
|
|
147
|
+
// possibly different version. project_settings.config is already flattened, so taking its values as written is
|
|
148
|
+
// both simpler and closer to what the project author actually sliced.
|
|
149
|
+
const FALSE_STRINGS = new Set(['0', 'false', 'no', 'off', ''])
|
|
150
|
+
|
|
151
|
+
function coerceScalar(type, value) {
|
|
152
|
+
if (typeof value !== 'string') return value
|
|
153
|
+
switch (type) {
|
|
154
|
+
case 'coBool': case 'coBools':
|
|
155
|
+
return !FALSE_STRINGS.has(value.trim().toLowerCase())
|
|
156
|
+
case 'coFloat': case 'coFloats': case 'coInt': case 'coInts':
|
|
157
|
+
return asNumber(value)
|
|
158
|
+
case 'coPercent': case 'coPercents':
|
|
159
|
+
// A percent option's value is the bare number (the schema default of sparse_infill_density is 20, not "20%")
|
|
160
|
+
// and upstream serializes it that way — but the '%' does show up in hand-edited configs, so strip it.
|
|
161
|
+
return asNumber(value.trim().replace(/%$/, ''))
|
|
162
|
+
case 'coFloatOrPercent': case 'coFloatsOrPercents':
|
|
163
|
+
// "50%" must stay a string — the percent IS the value, and Number('50%') is NaN anyway.
|
|
164
|
+
return value.trim().endsWith('%') ? value : asNumber(value)
|
|
165
|
+
case 'coPoint': case 'coPoints':
|
|
166
|
+
return parsePoint(value) ?? value
|
|
167
|
+
case 'coPointsGroups':
|
|
168
|
+
return parsePointGroup(value) ?? value
|
|
169
|
+
default:
|
|
170
|
+
return value // strings and enums — the kernel and the panel both read these as written
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Points are the other shape trap, and a quiet one: upstream writes a point as the STRING "XxY", while every
|
|
175
|
+
// consumer here indexes it as a [x, y] pair — so `printable_area[1][0]` on a raw import is the CHARACTER '2' of
|
|
176
|
+
// "256x0", and the bed comes out 2mm wide (measured on a real MakerWorld project: 2 x NaN).
|
|
177
|
+
// Two separators exist in the wild: ConfigOptionPoint::serialize writes 'x', while some Orca options
|
|
178
|
+
// (best_object_pos) write "X,Y". A points GROUP is one string holding a comma-separated LIST of "XxY" points,
|
|
179
|
+
// so there the comma cannot also separate a point's own coordinates — hence two parsers rather than one regex.
|
|
180
|
+
function parsePoint(text) {
|
|
181
|
+
const split = String(text).split('x')
|
|
182
|
+
const pair = split.length === 2 ? split : String(text).split(',')
|
|
183
|
+
if (pair.length !== 2) return null
|
|
184
|
+
const x = Number(pair[0]), y = Number(pair[1])
|
|
185
|
+
return Number.isFinite(x) && Number.isFinite(y) ? [x, y] : null
|
|
186
|
+
}
|
|
187
|
+
function parsePointGroup(text) {
|
|
188
|
+
const points = String(text).split(',').map(entry => {
|
|
189
|
+
const [a, b] = entry.split('x')
|
|
190
|
+
const x = Number(a), y = Number(b)
|
|
191
|
+
return Number.isFinite(x) && Number.isFinite(y) ? [x, y] : null
|
|
192
|
+
})
|
|
193
|
+
return points.length && points.every(Boolean) ? points : null
|
|
194
|
+
}
|
|
195
|
+
// Number('') is 0 and Number(' ') is 0, neither of which means "the option was set to zero" — an unparsable
|
|
196
|
+
// value is handed back untouched so the schema default keeps applying instead of a fabricated 0.
|
|
197
|
+
function asNumber(value) {
|
|
198
|
+
if (value.trim() === '') return value
|
|
199
|
+
const n = Number(value)
|
|
200
|
+
return Number.isFinite(n) ? n : value
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A 3mf project's raw `project_settings.config` object -> a settings map this package's own API accepts.
|
|
205
|
+
* Keys the config schema does not define are dropped. Returns `{settings, applied, skipped}`.
|
|
206
|
+
*/
|
|
207
|
+
export function normalizeProjectSettings(raw) {
|
|
208
|
+
const settings = {}
|
|
209
|
+
let applied = 0
|
|
210
|
+
const skipped = []
|
|
211
|
+
for (const [key, value] of Object.entries(raw || {})) {
|
|
212
|
+
const type = schema[key]?.type
|
|
213
|
+
if (!type) { skipped.push(key); continue }
|
|
214
|
+
settings[key] = Array.isArray(value) ? value.map(entry => coerceScalar(type, entry)) : coerceScalar(type, value)
|
|
215
|
+
applied++
|
|
216
|
+
}
|
|
217
|
+
return { settings, applied, skipped }
|
|
218
|
+
}
|
|
219
|
+
|
|
91
220
|
|
|
92
221
|
// Right-panel settings values -> kernel parameters (derived from schema keys)
|
|
93
222
|
export function deriveKernelParams(settings) {
|
|
94
223
|
const S = k => settingScalar(settings, k)
|
|
95
224
|
const num = (k, d) => { const v = Number(S(k)); return Number.isFinite(v) ? v : d }
|
|
96
225
|
const bool = (k, d) => { const v = settingRaw(settings, k); return typeof v === 'boolean' ? v : (Array.isArray(v) ? !!v[0] : (v == null ? d : !!v)) }
|
|
226
|
+
// Filament retraction overrides: upstream lets a material override the machine's retraction (TPU wants a
|
|
227
|
+
// different pull-back than PLA on the same printer). These schema keys are nullable and carry no default of
|
|
228
|
+
// their own — "unset" means keep the machine value — so this reads the settings map directly rather than
|
|
229
|
+
// through settingScalar, whose schema fallback would not distinguish the two.
|
|
230
|
+
const override = (filamentKey, machineKey, fallback) => {
|
|
231
|
+
const raw = settings?.[filamentKey]
|
|
232
|
+
const value = Number(Array.isArray(raw) ? raw[0] : raw)
|
|
233
|
+
return Number.isFinite(value) ? value : num(machineKey, fallback)
|
|
234
|
+
}
|
|
97
235
|
|
|
98
236
|
let line_width = num('line_width', 0); if (!line_width) line_width = 0.42 // 0=auto -> default 0.42
|
|
99
237
|
|
|
@@ -128,6 +266,69 @@ export function deriveKernelParams(settings) {
|
|
|
128
266
|
const machine = {}
|
|
129
267
|
for (const [param, [key, fallback]] of Object.entries(MACHINE_LIMITS)) machine[param] = num(key, fallback)
|
|
130
268
|
|
|
269
|
+
// Support filament mapping: which extruder prints the support base/raft, and which prints the support interface.
|
|
270
|
+
// Upstream's "Default" is the value 0 = keep whatever tool is current, and an absent key means the same thing to
|
|
271
|
+
// the kernel (its JSON reader falls back to its own 0 default). Unmapped supports are therefore omitted rather
|
|
272
|
+
// than sent as 0, so a settings map that maps neither produces byte-for-byte the parameters it produced before
|
|
273
|
+
// these keys existed — same reason the per-feature widths above are omitted when unedited.
|
|
274
|
+
const supportTools = {}
|
|
275
|
+
for (const key of ['support_filament', 'support_interface_filament']) {
|
|
276
|
+
const extruderIndex = num(key, 0)
|
|
277
|
+
if (extruderIndex > 0) supportTools[key] = extruderIndex
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Multi-material: upstream stores every filament option as one entry per extruder (coFloats), and the material
|
|
281
|
+
// picker writes each extruder's material at its own index. Only a second entry makes these arrays appear, so a
|
|
282
|
+
// single-material slice sends exactly the keys it always did — the kernel then reads its scalars as before.
|
|
283
|
+
// A hole (a material that overrides nothing) is filled with the value tool 0 resolved to, because the kernel
|
|
284
|
+
// reads the array positionally and cannot tell "absent" from "0".
|
|
285
|
+
// 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.
|
|
287
|
+
const towerSettings = {}
|
|
288
|
+
{
|
|
289
|
+
const width = num('prime_tower_width', 0)
|
|
290
|
+
if (width > 0) towerSettings.prime_tower_width = width
|
|
291
|
+
// Read the map itself, NOT settingRaw: wipe_tower_x/y carry an upstream schema default of (15, 220), which is
|
|
292
|
+
// a coordinate off the front of a 200mm bed. Taking it as "the user chose a position" put the tower outside
|
|
293
|
+
// the plate and disabled the automatic placement entirely — measured as a tower at z=-127.5 on a 200mm bed.
|
|
294
|
+
// 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)
|
|
296
|
+
return (v != null && v !== '' && Number.isFinite(n)) ? n : null }
|
|
297
|
+
const towerX = chosen('wipe_tower_x'), towerY = chosen('wipe_tower_y')
|
|
298
|
+
if (towerX != null) towerSettings.prime_tower_x = towerX
|
|
299
|
+
if (towerY != null) towerSettings.prime_tower_y = towerY
|
|
300
|
+
// The purging volumes table (flat N×N, mm³) and its scalars. Sent verbatim — the kernel indexes it [from*N+to].
|
|
301
|
+
const matrix = settingRaw(settings, 'flush_volumes_matrix')
|
|
302
|
+
if (Array.isArray(matrix) && matrix.length >= 4) towerSettings.flush_volumes_matrix = matrix.map(Number)
|
|
303
|
+
// Upstream defaults enable_prime_tower to false and relies on flushing into the model; the kernel defaults it
|
|
304
|
+
// to true because that was the only destination it had. Both are sent only when the map carries them, so an
|
|
305
|
+
// untouched project keeps whatever the kernel decides.
|
|
306
|
+
if ('enable_prime_tower' in (settings ?? {})) towerSettings.enable_prime_tower = !!settings.enable_prime_tower
|
|
307
|
+
if ('flush_into_infill' in (settings ?? {})) towerSettings.flush_into_infill = !!settings.flush_into_infill
|
|
308
|
+
const multiplier = num('flush_multiplier', 0)
|
|
309
|
+
if (multiplier > 0) towerSettings.flush_multiplier = multiplier
|
|
310
|
+
const prime = num('prime_volume', 0)
|
|
311
|
+
if (prime > 0) towerSettings.prime_volume = prime
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const perExtruder = {}
|
|
315
|
+
for (const [param, key, scalar] of [
|
|
316
|
+
['extruder_nozzle_temp', 'nozzle_temperature', num('nozzle_temperature', 200)],
|
|
317
|
+
['extruder_filament_diameter', 'filament_diameter', num('filament_diameter', 1.75)],
|
|
318
|
+
['extruder_flow_ratio', 'filament_flow_ratio', num('filament_flow_ratio', 1.0)],
|
|
319
|
+
['extruder_retract_length', 'filament_retraction_length', override('filament_retraction_length', 'retraction_length', 0.8)],
|
|
320
|
+
['extruder_retract_speed', 'filament_retraction_speed', override('filament_retraction_speed', 'retraction_speed', 30)],
|
|
321
|
+
['extruder_z_hop', 'filament_z_hop', override('filament_z_hop', 'z_hop', 0.4)],
|
|
322
|
+
]) {
|
|
323
|
+
const raw = settings?.[key]
|
|
324
|
+
if (!Array.isArray(raw) || raw.length < 2) continue
|
|
325
|
+
perExtruder[param] = raw.map(v => {
|
|
326
|
+
if (v == null || v === '') return scalar // null survives a JSON round-trip; Number(null) would be 0
|
|
327
|
+
const n = Number(Array.isArray(v) ? v[0] : v)
|
|
328
|
+
return Number.isFinite(n) ? n : scalar
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
|
|
131
332
|
return {
|
|
132
333
|
...widths,
|
|
133
334
|
...machine,
|
|
@@ -146,11 +347,17 @@ export function deriveKernelParams(settings) {
|
|
|
146
347
|
skirt_height: num('skirt_height', 1), // stage 33: the kernel used to hardcode the first layer
|
|
147
348
|
brim_width: num('brim_width', 0),
|
|
148
349
|
brim_object_gap: num('brim_object_gap', 0), // stage 33: the kernel used to hardcode w*0.5
|
|
149
|
-
|
|
150
|
-
|
|
350
|
+
...perExtruder,
|
|
351
|
+
// Prime tower. These were the one part of multi-material the settings map could not reach: the kernel read its
|
|
352
|
+
// own prime_tower_* parameters and nothing mapped the upstream keys onto them, so the tower's size and place
|
|
353
|
+
// were whatever the viewer decided. Only sent when the user actually set them — an unset width keeps the
|
|
354
|
+
// kernel's own default, and an unset position lets the viewer's auto-placement stand.
|
|
355
|
+
...towerSettings,
|
|
356
|
+
retract_length: override('filament_retraction_length', 'retraction_length', 0.8), // vector[0]
|
|
357
|
+
retraction_minimum_travel: override('filament_retraction_minimum_travel', 'retraction_minimum_travel', 2), // stage 33: used to be the kernel constant 2.0
|
|
151
358
|
gcode_resolution: num('resolution', 0.01), // stage 33: tree-support path simplification tolerance
|
|
152
|
-
retract_speed:
|
|
153
|
-
z_hop:
|
|
359
|
+
retract_speed: override('filament_retraction_speed', 'retraction_speed', 30),
|
|
360
|
+
z_hop: override('filament_z_hop', 'z_hop', 0.4),
|
|
154
361
|
travel_speed: num('travel_speed', 120),
|
|
155
362
|
first_layer_speed: num('initial_layer_speed', 30),
|
|
156
363
|
print_speed: num('outer_wall_speed', 60),
|
|
@@ -185,6 +392,7 @@ export function deriveKernelParams(settings) {
|
|
|
185
392
|
const n = Number(v); return Number.isFinite(n) ? n : 0.5 })(), // "50%" -> 0.5 (ratio of extrusion width)
|
|
186
393
|
support_on_build_plate_only: bool('support_on_build_plate_only', false),
|
|
187
394
|
support_interface_bottom_layers: num('support_interface_bottom_layers', 0),
|
|
395
|
+
...supportTools, // present only when a support extruder is mapped (see above)
|
|
188
396
|
raft_layers: num('raft_layers', 0),
|
|
189
397
|
raft_expansion: num('raft_expansion', 1.5), // stage 33: the kernel used to hardcode +3.0
|
|
190
398
|
raft_contact_distance: num('raft_contact_distance', 0.1), // stage 33: previously ignored by the kernel
|
|
@@ -27,6 +27,85 @@ const loadCore = async () => {
|
|
|
27
27
|
|
|
28
28
|
let modPromise = null
|
|
29
29
|
|
|
30
|
+
// Stage 20 painting states = upstream's EnforcerBlockerType (see packages/wasm-core/selector_bridge.h):
|
|
31
|
+
// 0=NONE, 1=ENFORCER(==Extruder1), 2=BLOCKER(==Extruder2), 3..16=Extruder3..Extruder16.
|
|
32
|
+
// The kernel exposes both a boolean pair (selector_paint/…_count/…_overlay) and integer-state twins (…_state);
|
|
33
|
+
// the boolean wrappers are NOT redundant, they are what maps false -> BLOCKER(2) on the C++ side.
|
|
34
|
+
const PAINT_STATE_ENFORCER = 1
|
|
35
|
+
const PAINT_STATE_BLOCKER = 2
|
|
36
|
+
const PAINT_STATE_MAX = 16
|
|
37
|
+
|
|
38
|
+
// A state is only a state when it is literally an integer in [1,16]. This predicate is the hazard guard: embind
|
|
39
|
+
// coerces a JS `false` to the int 0 == NONE, so a boolean that leaked into the state path would paint nothing and
|
|
40
|
+
// silently turn blocker painting into a no-op. `Number.isInteger(false)` is false, so a boolean can never pass here
|
|
41
|
+
// and never reaches the *_state entry points — it stays on the boolean binding that means BLOCKER.
|
|
42
|
+
const isPaintState = (value) => Number.isInteger(value) && value >= PAINT_STATE_ENFORCER && value <= PAINT_STATE_MAX
|
|
43
|
+
|
|
44
|
+
// Stands in for an overlay the request did not ask for. Shared and never transferred (the transfer list drops
|
|
45
|
+
// zero-length buffers), so the one instance stays valid for every reply that needs a placeholder.
|
|
46
|
+
const EMPTY_OVERLAY = new Float32Array(0)
|
|
47
|
+
|
|
48
|
+
// Throws (-> the catch below posts {type:'error'}) rather than clamping: a bad state is a caller bug, and silently
|
|
49
|
+
// substituting one would paint the wrong extruder, which is far harder to notice than an error banner.
|
|
50
|
+
const validatedPaintStates = (states) => {
|
|
51
|
+
for (const state of states)
|
|
52
|
+
if (!isPaintState(state)) throw new Error(`paint state must be an integer 1..${PAINT_STATE_MAX} (got ${JSON.stringify(state)})`)
|
|
53
|
+
return states
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Which states a message wants reported per-state. null == a legacy message (no `state`/`states` field) -> the reply
|
|
57
|
+
// must stay exactly {enf, blk} / {enf, blk} as it was before the protocol was widened, because the in-app support
|
|
58
|
+
// flow (viewer use_slicer.js) reads those two fields and nothing else.
|
|
59
|
+
const reportedPaintStates = (message, paintedState) => {
|
|
60
|
+
if (Array.isArray(message.states)) return validatedPaintStates(message.states)
|
|
61
|
+
return paintedState === null ? null : [paintedState]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The state a paint message asks for: an explicit `state` wins, otherwise null meaning "use the legacy boolean path".
|
|
65
|
+
// Note the check is on presence-and-integerness, never on truthiness — see isPaintState above.
|
|
66
|
+
const explicitPaintState = (message) => {
|
|
67
|
+
if (message.state === undefined || message.state === null) return null
|
|
68
|
+
validatedPaintStates([message.state])
|
|
69
|
+
return message.state
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Brush cursor shape (selector_bridge.h CURSOR_*). Sent as a name because the message is a protocol a host writes
|
|
73
|
+
// by hand; the integer stays on this side of it. Anything unknown is the sphere the brush has always used.
|
|
74
|
+
const cursorShapeOf = (message) => (message.cursor === 'circle' ? 1 : 0)
|
|
75
|
+
|
|
76
|
+
// The tools that are not the radius brush: upstream's Smart fill and Bucket fill, plus its single-triangle POINTER
|
|
77
|
+
// cursor — which is literally bucket fill with propagation off, so the three share one dispatch. None of them takes
|
|
78
|
+
// a radius or a camera: what they select comes from the mesh's own topology, which is why they are a branch rather
|
|
79
|
+
// than more optional fields on the brush call. `state === null` means erase (the entry points with no state
|
|
80
|
+
// argument), matching how the brush splits paint from erase.
|
|
81
|
+
const FILL_TOOLS = new Set(['smart', 'bucket', 'triangle'])
|
|
82
|
+
const applyFill = (Module, message, state) => {
|
|
83
|
+
const angle = Number.isFinite(message.angle) ? message.angle : 30
|
|
84
|
+
if (message.tool === 'smart') {
|
|
85
|
+
if (state === null) Module.selector_seed_fill_erase(message.facet, message.hx, message.hy, message.hz, angle)
|
|
86
|
+
else Module.selector_seed_fill(message.facet, message.hx, message.hy, message.hz, angle, state)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
// Upstream passes -1 as the angle when it is not propagating (GLGizmoPainterBase.cpp ~861): with a single facet
|
|
90
|
+
// there is no neighbour to measure an angle against, and -1 is how that call says "no angle limit".
|
|
91
|
+
const propagate = message.tool === 'bucket'
|
|
92
|
+
const bucketAngle = propagate ? angle : -1
|
|
93
|
+
if (state === null) Module.selector_bucket_fill_erase(message.facet, message.hx, message.hy, message.hz, bucketAngle, propagate)
|
|
94
|
+
else Module.selector_bucket_fill(message.facet, message.hx, message.hy, message.hz, bucketAngle, propagate, state)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// paint and erase both mutate the same selector, so both have to re-read every count the caller asked about: an
|
|
98
|
+
// erase over a T3 region lowers T3 exactly the way overpainting it with T2 would, and a reply carrying only the
|
|
99
|
+
// state that was written would leave the other chips showing stale numbers. `enf`/`blk` stay unconditional so a
|
|
100
|
+
// listener predating the per-state map keeps working; `counts` appears only when the message asked for states,
|
|
101
|
+
// which is what keeps a legacy paint reply byte-identical to what it was before the protocol was widened.
|
|
102
|
+
const paintedReply = (Module, message, paintedState) => {
|
|
103
|
+
const reply = { type: 'painted', enf: Module.selector_painted_count(true), blk: Module.selector_painted_count(false) }
|
|
104
|
+
const states = reportedPaintStates(message, paintedState)
|
|
105
|
+
if (states) reply.counts = Object.fromEntries(states.map(state => [state, Module.selector_painted_count_state(state)]))
|
|
106
|
+
return reply
|
|
107
|
+
}
|
|
108
|
+
|
|
30
109
|
self.onmessage = async (e) => {
|
|
31
110
|
const d = e.data
|
|
32
111
|
try {
|
|
@@ -35,11 +114,81 @@ self.onmessage = async (e) => {
|
|
|
35
114
|
// Warmup: only load the kernel (+ spawn the mt pthread pool) ahead of time — removes the perceived load on the first slice
|
|
36
115
|
if (d.cmd === 'warmup') { self.postMessage({ type: 'warm' }); return }
|
|
37
116
|
// Stage 20: manual support painting — selector state persists in this worker Module (slicing uses the same Module).
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
117
|
+
// `keepPaint` says the mesh is the same model in a new place, so the marks carry over — the reply reports whether
|
|
118
|
+
// they actually did, since a face-count change makes the kernel fall back to a clean registration.
|
|
119
|
+
if (d.cmd === 'prepare') {
|
|
120
|
+
const kept = d.keepPaint ? Module.selector_reprepare(new Uint8Array(d.stl)) : (Module.selector_prepare(new Uint8Array(d.stl)), false)
|
|
121
|
+
self.postMessage({ type: 'prepared', facets: Module.selector_facet_count(), kept })
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
// paint: legacy {enforcer:boolean} stays on the boolean binding (false == BLOCKER); {state:1..16} reaches any
|
|
125
|
+
// extruder through the state binding. `enf`/`blk` are always reported so an old listener keeps working, and the
|
|
126
|
+
// per-state `counts` map is added ONLY when the message asked for states (keeps the legacy reply byte-identical).
|
|
127
|
+
if (d.cmd === 'paint') {
|
|
128
|
+
const paintedState = explicitPaintState(d)
|
|
129
|
+
if (FILL_TOOLS.has(d.tool)) {
|
|
130
|
+
// The fills are newer than the boolean protocol, so there is no legacy reading to fall back to — a fill
|
|
131
|
+
// without an explicit integer state is a malformed message, not an enforcer stroke.
|
|
132
|
+
if (paintedState === null) throw new Error(`a '${d.tool}' fill needs an explicit integer state`)
|
|
133
|
+
applyFill(Module, d, paintedState)
|
|
134
|
+
}
|
|
135
|
+
else if (paintedState === null) Module.selector_paint(d.facet, d.hx, d.hy, d.hz, d.cx, d.cy, d.cz, d.radius, d.enforcer)
|
|
136
|
+
else Module.selector_paint_shape(d.facet, d.hx, d.hy, d.hz, d.cx, d.cy, d.cz, d.radius, paintedState, cursorShapeOf(d))
|
|
137
|
+
self.postMessage(paintedReply(Module, d, paintedState)); return
|
|
138
|
+
}
|
|
139
|
+
// erase: the brush writes NONE, returning the brushed facets to the default extruder (upstream's shift+drag).
|
|
140
|
+
// It is its OWN command and NOT `{cmd:'paint', state:0}`, because 0 is the one integer a boolean can become:
|
|
141
|
+
// embind coerces a JS `false` to 0 == NONE, so widening isPaintState to admit 0 would let a stray boolean on
|
|
142
|
+
// the state path erase silently — the exact failure that guard was written to stop (see isPaintState above).
|
|
143
|
+
// A boolean cannot forge the string 'erase', and the kernel binding it reaches takes no state argument at all,
|
|
144
|
+
// so there is nothing left for a coercion to slip through. The reply is a normal 'painted' one: an erase is a
|
|
145
|
+
// change to the same counts, and the viewer's listeners must not need to know which brush produced it.
|
|
146
|
+
if (d.cmd === 'erase') {
|
|
147
|
+
if (FILL_TOOLS.has(d.tool)) applyFill(Module, d, null)
|
|
148
|
+
else Module.selector_erase(d.facet, d.hx, d.hy, d.hz, d.cx, d.cy, d.cz, d.radius)
|
|
149
|
+
self.postMessage(paintedReply(Module, d, null)); return
|
|
150
|
+
}
|
|
151
|
+
// importPaint: load painting out of a 3mf. `facets` is an Int32Array of facet indices in the CURRENT selector's
|
|
152
|
+
// numbering and `hex` the newline-joined per-facet split-tree strings the 3mf carried, in the same order.
|
|
153
|
+
// It REPLACES every mark (the kernel's deserialize resets first), so it belongs immediately after a 'prepare'
|
|
154
|
+
// and nowhere else — an import in the middle of a session would silently discard what the user had painted.
|
|
155
|
+
// A kernel built before this binding existed simply has no selector_import_paint; the reply says applied:0
|
|
156
|
+
// rather than throwing, because losing a 3mf's paint must not also lose its geometry.
|
|
157
|
+
if (d.cmd === 'importPaint') {
|
|
158
|
+
const applied = Module.selector_import_paint ? Module.selector_import_paint(d.facets, d.hex ?? '') : 0
|
|
159
|
+
const reply = paintedReply(Module, d, null)
|
|
160
|
+
reply.applied = applied
|
|
161
|
+
self.postMessage(reply); return
|
|
162
|
+
}
|
|
163
|
+
// clear wipes every state at once, so each requested count is 0 by construction — no need to ask the kernel back.
|
|
164
|
+
if (d.cmd === 'clear') {
|
|
165
|
+
Module.selector_clear()
|
|
166
|
+
const reply = { type: 'painted', enf: 0, blk: 0 }
|
|
167
|
+
const states = reportedPaintStates(d, null)
|
|
168
|
+
if (states) reply.counts = Object.fromEntries(states.map(state => [state, 0]))
|
|
169
|
+
self.postMessage(reply); return
|
|
170
|
+
}
|
|
171
|
+
// overlay: `enf`/`blk` (states 1/2) are always sent because the viewer's overlay rebuild consumes exactly those two;
|
|
172
|
+
// `overlays` adds the requested states, so an MMU caller can ask for 3..16 without losing the support overlays.
|
|
173
|
+
// The kernel already hands back a detached Float32Array (bindings.cpp to_f32 does typed_memory_view().slice()),
|
|
174
|
+
// so the arrays are posted as they are and their buffers TRANSFERRED. The previous Array.from() turned each one
|
|
175
|
+
// into a plain JS Array, which the structured clone then had to copy element by element — measured at 1.1 ms of
|
|
176
|
+
// a 3.6 ms stroke sample on a 40k-triangle mesh, and growing with the painted area because the whole overlay is
|
|
177
|
+
// resent every sample. Transferring costs nothing and the arrays are this worker's own copies, so nothing here
|
|
178
|
+
// can observe them being neutered.
|
|
179
|
+
if (d.cmd === 'overlay') {
|
|
180
|
+
const states = reportedPaintStates(d, null)
|
|
181
|
+
// enf/blk stay unconditional for the listener that predates the per-state map. When the request names states,
|
|
182
|
+
// they are the ONLY ones read, so building the other two would be pure waste on the hot path.
|
|
183
|
+
const wanted = states && !states.includes(PAINT_STATE_ENFORCER) && !states.includes(PAINT_STATE_BLOCKER)
|
|
184
|
+
const reply = { type: 'overlay',
|
|
185
|
+
enf: wanted ? EMPTY_OVERLAY : Module.selector_overlay(true),
|
|
186
|
+
blk: wanted ? EMPTY_OVERLAY : Module.selector_overlay(false) }
|
|
187
|
+
if (states) reply.overlays = Object.fromEntries(states.map(state => [state, Module.selector_overlay_state(state)]))
|
|
188
|
+
const transfer = [reply.enf, reply.blk, ...Object.values(reply.overlays ?? {})]
|
|
189
|
+
.filter(a => a?.buffer && a.byteLength > 0).map(a => a.buffer)
|
|
190
|
+
self.postMessage(reply, transfer); return
|
|
191
|
+
}
|
|
43
192
|
|
|
44
193
|
if (d.stall) return // stage-30 test hook: simulate a hang -> verifies the main-thread watchdog fires (not set in production)
|
|
45
194
|
// mt (SAB): share the address of the real support progress counter (u32) with main once — even while the kernel is
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-slicer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"types": "./types/viewer-loaders.d.ts",
|
|
42
42
|
"default": "./viewer/dist/model_loaders.js"
|
|
43
43
|
},
|
|
44
|
+
"./viewer/gcode": {
|
|
45
|
+
"types": "./types/viewer-gcode.d.ts",
|
|
46
|
+
"default": "./viewer/dist/gcode_parse.js"
|
|
47
|
+
},
|
|
44
48
|
"./components": {
|
|
45
49
|
"types": "./types/components.d.ts",
|
|
46
50
|
"default": "./components/dist/SettingsPanel.js"
|
|
@@ -69,6 +73,7 @@
|
|
|
69
73
|
},
|
|
70
74
|
"files": [
|
|
71
75
|
"engine",
|
|
76
|
+
"!engine/**/.omc",
|
|
72
77
|
"types",
|
|
73
78
|
"components/dist",
|
|
74
79
|
"viewer/dist",
|
|
@@ -80,6 +85,7 @@
|
|
|
80
85
|
"data/invalidation-map.json",
|
|
81
86
|
"data/printers.json",
|
|
82
87
|
"data/processes.js",
|
|
88
|
+
"data/filaments.js",
|
|
83
89
|
"README.md",
|
|
84
90
|
"LICENSE.txt"
|
|
85
91
|
],
|
|
@@ -97,8 +103,15 @@
|
|
|
97
103
|
"wasm",
|
|
98
104
|
"orcaslicer",
|
|
99
105
|
"three",
|
|
106
|
+
"three.js",
|
|
100
107
|
"react",
|
|
101
108
|
"arachne",
|
|
102
|
-
"tree-support"
|
|
109
|
+
"tree-support",
|
|
110
|
+
"three-slicer",
|
|
111
|
+
"slicer",
|
|
112
|
+
"3d",
|
|
113
|
+
"mesh",
|
|
114
|
+
"clipping"
|
|
103
115
|
]
|
|
104
116
|
}
|
|
117
|
+
|