three-slicer-viewer 0.3.2 → 0.3.3
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/VIEWER.md +24 -0
- package/dist/components.js +47 -43
- package/dist/gcode_parse.js +99 -95
- package/dist/index.js +3855 -3758
- package/dist/{model_loaders-DxlFNFOu.js → model_loaders-Dk9rqXFb.js} +27 -27
- package/dist/model_loaders.js +2 -2
- package/dist/parse_3mf-CpbA-6Iq.js +666 -0
- package/dist/parse_3mf.worker.js +67 -5
- package/dist/toolpath.js +248 -176
- package/package.json +6 -1
- package/src/core/paint_tools.js +8 -0
- package/src/settings/kernel_setting_keys.js +28 -0
- package/src/settings/settings_core.js +55 -5
- package/types/paint.d.ts +7 -0
- package/types/viewer.d.ts +1 -1
- package/dist/parse_3mf-BZISF3eW.js +0 -327
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The painting tools that act on one click instead of a drag (upstream's Smart fill, Bucket fill and the single-facet
|
|
2
|
+
// POINTER). Read by the brush input, both brush panels and the slicer worker (`three-slicer-viewer/paint`), so a
|
|
3
|
+
// tool added here is one the whole paint path treats as a fill.
|
|
4
|
+
export const FILL_TOOLS = new Set(['smart', 'bucket', 'triangle'])
|
|
5
|
+
|
|
6
|
+
// The fills that spread across neighbouring facets and so take a fill angle. Triangle is bucket fill with the
|
|
7
|
+
// propagation off (the worker passes -1 as its angle), so it has no angle to set.
|
|
8
|
+
export const ANGLE_FILL_TOOLS = new Set(['smart', 'bucket'])
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
// AUTO-GENERATED by types/gen_kernel_params.mjs — DO NOT EDIT.
|
|
2
2
|
// The schema keys deriveKernelParams consumes (the distinct "From setting" column of engine/PARAMS.md).
|
|
3
3
|
export const KERNEL_SETTING_KEYS = [
|
|
4
|
+
"alternate_extra_wall",
|
|
4
5
|
"bottom_shell_layers",
|
|
6
|
+
"bottom_solid_infill_flow_ratio",
|
|
5
7
|
"bottom_surface_filament_id",
|
|
8
|
+
"bridge_flow",
|
|
6
9
|
"bridge_no_support",
|
|
7
10
|
"bridge_speed",
|
|
11
|
+
"brim_flow_ratio",
|
|
8
12
|
"brim_object_gap",
|
|
9
13
|
"brim_width",
|
|
10
14
|
"close_fan_the_first_x_layers",
|
|
11
15
|
"default_acceleration",
|
|
16
|
+
"detect_thin_wall",
|
|
12
17
|
"enable_arc_fitting",
|
|
13
18
|
"enable_pressure_advance",
|
|
14
19
|
"enable_prime_tower",
|
|
@@ -17,27 +22,36 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
17
22
|
"filament_cost",
|
|
18
23
|
"filament_density",
|
|
19
24
|
"filament_diameter",
|
|
25
|
+
"filament_end_gcode",
|
|
20
26
|
"filament_flow_ratio",
|
|
21
27
|
"filament_map",
|
|
28
|
+
"filament_max_volumetric_speed",
|
|
22
29
|
"filament_retraction_length",
|
|
23
30
|
"filament_retraction_minimum_travel",
|
|
24
31
|
"filament_retraction_speed",
|
|
25
32
|
"filament_settings_id",
|
|
26
33
|
"filament_type",
|
|
27
34
|
"filament_z_hop",
|
|
35
|
+
"filter_out_gap_fill",
|
|
36
|
+
"first_layer_flow_ratio",
|
|
28
37
|
"flush_into_infill",
|
|
29
38
|
"flush_multiplier",
|
|
30
39
|
"flush_volumes_matrix",
|
|
31
40
|
"full_fan_speed_layer",
|
|
41
|
+
"gap_fill_flow_ratio",
|
|
42
|
+
"gap_infill_speed",
|
|
32
43
|
"hot_plate_temp",
|
|
33
44
|
"independent_support_layer_height",
|
|
34
45
|
"infill_direction",
|
|
46
|
+
"infill_wall_overlap",
|
|
35
47
|
"initial_layer_line_width",
|
|
36
48
|
"initial_layer_print_height",
|
|
37
49
|
"initial_layer_speed",
|
|
38
50
|
"inner_wall_filament_id",
|
|
51
|
+
"inner_wall_flow_ratio",
|
|
39
52
|
"inner_wall_line_width",
|
|
40
53
|
"internal_solid_filament_id",
|
|
54
|
+
"internal_solid_infill_flow_ratio",
|
|
41
55
|
"internal_solid_infill_line_width",
|
|
42
56
|
"ironing_flow",
|
|
43
57
|
"ironing_spacing",
|
|
@@ -60,12 +74,16 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
60
74
|
"max_volumetric_extrusion_rate_slope",
|
|
61
75
|
"nozzle_diameter",
|
|
62
76
|
"nozzle_temperature",
|
|
77
|
+
"only_one_wall_first_layer",
|
|
63
78
|
"outer_wall_filament_id",
|
|
79
|
+
"outer_wall_flow_ratio",
|
|
64
80
|
"outer_wall_line_width",
|
|
65
81
|
"outer_wall_speed",
|
|
82
|
+
"precise_outer_wall",
|
|
66
83
|
"pressure_advance",
|
|
67
84
|
"prime_tower_width",
|
|
68
85
|
"prime_volume",
|
|
86
|
+
"print_flow_ratio",
|
|
69
87
|
"printable_area",
|
|
70
88
|
"printable_height",
|
|
71
89
|
"raft_contact_distance",
|
|
@@ -76,14 +94,17 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
76
94
|
"retraction_length",
|
|
77
95
|
"retraction_minimum_travel",
|
|
78
96
|
"retraction_speed",
|
|
97
|
+
"scarf_joint_flow_ratio",
|
|
79
98
|
"seam_position",
|
|
80
99
|
"seam_slope_type",
|
|
100
|
+
"set_other_flow_ratios",
|
|
81
101
|
"skirt_distance",
|
|
82
102
|
"skirt_height",
|
|
83
103
|
"skirt_loops",
|
|
84
104
|
"slow_down_layer_time",
|
|
85
105
|
"sparse_infill_density",
|
|
86
106
|
"sparse_infill_filament_id",
|
|
107
|
+
"sparse_infill_flow_ratio",
|
|
87
108
|
"sparse_infill_line_width",
|
|
88
109
|
"sparse_infill_pattern",
|
|
89
110
|
"spiral_mode",
|
|
@@ -93,8 +114,10 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
93
114
|
"support_bottom_z_distance",
|
|
94
115
|
"support_expansion",
|
|
95
116
|
"support_filament",
|
|
117
|
+
"support_flow_ratio",
|
|
96
118
|
"support_interface_bottom_layers",
|
|
97
119
|
"support_interface_filament",
|
|
120
|
+
"support_interface_flow_ratio",
|
|
98
121
|
"support_interface_pattern",
|
|
99
122
|
"support_interface_spacing",
|
|
100
123
|
"support_interface_top_layers",
|
|
@@ -108,7 +131,10 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
108
131
|
"support_threshold_overlap",
|
|
109
132
|
"support_top_z_distance",
|
|
110
133
|
"support_type",
|
|
134
|
+
"thick_bridges",
|
|
135
|
+
"top_bottom_infill_wall_overlap",
|
|
111
136
|
"top_shell_layers",
|
|
137
|
+
"top_solid_infill_flow_ratio",
|
|
112
138
|
"top_surface_filament_id",
|
|
113
139
|
"top_surface_line_width",
|
|
114
140
|
"travel_acceleration",
|
|
@@ -124,8 +150,10 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
124
150
|
"tree_support_tip_diameter",
|
|
125
151
|
"tree_support_top_rate",
|
|
126
152
|
"tree_support_wall_count",
|
|
153
|
+
"wall_direction",
|
|
127
154
|
"wall_generator",
|
|
128
155
|
"wall_loops",
|
|
156
|
+
"wall_sequence",
|
|
129
157
|
"wipe_tower_x",
|
|
130
158
|
"wipe_tower_y",
|
|
131
159
|
"z_hop",
|
|
@@ -59,12 +59,29 @@ const PASSTHROUGH_NUM = [
|
|
|
59
59
|
// rather than silently dropping them.
|
|
60
60
|
'outer_wall_filament_id', 'inner_wall_filament_id', 'sparse_infill_filament_id',
|
|
61
61
|
'top_surface_filament_id', 'bottom_surface_filament_id', 'internal_solid_filament_id',
|
|
62
|
+
// Read by the kernel's classic wall generator (wall_generator = classic).
|
|
63
|
+
'filter_out_gap_fill',
|
|
64
|
+
// Upstream's flow multipliers (GCode.cpp:7343-7390): the print-wide one and each role's own (emit.cpp role_flow_ratio).
|
|
65
|
+
'print_flow_ratio', 'top_solid_infill_flow_ratio', 'bottom_solid_infill_flow_ratio', 'bridge_flow', 'brim_flow_ratio',
|
|
66
|
+
'scarf_joint_flow_ratio', 'outer_wall_flow_ratio', 'inner_wall_flow_ratio', 'sparse_infill_flow_ratio',
|
|
67
|
+
'internal_solid_infill_flow_ratio', 'gap_fill_flow_ratio', 'support_flow_ratio', 'support_interface_flow_ratio',
|
|
68
|
+
'first_layer_flow_ratio',
|
|
62
69
|
]
|
|
63
|
-
const PASSTHROUGH_BOOL = ['independent_support_layer_height'
|
|
70
|
+
const PASSTHROUGH_BOOL = ['independent_support_layer_height',
|
|
71
|
+
'detect_thin_wall', 'precise_outer_wall', 'only_one_wall_first_layer', 'alternate_extra_wall',
|
|
72
|
+
// thick_bridges picks upstream's round bridge thread; set_other_flow_ratios turns the per-role ratios on.
|
|
73
|
+
'thick_bridges', 'set_other_flow_ratios']
|
|
74
|
+
// coPercent options. A 3mf import delivers "15%" while the schema default is the bare 15, so the sign is stripped and
|
|
75
|
+
// the kernel gets the number of percent either way.
|
|
76
|
+
const PASSTHROUGH_PERCENT = ['infill_wall_overlap', 'top_bottom_infill_wall_overlap']
|
|
77
|
+
// coEnum options the kernel reads by their upstream spelling.
|
|
78
|
+
const PASSTHROUGH_STR = ['wall_sequence', 'wall_direction']
|
|
64
79
|
// Per-filament vectors, one entry per filament, passed positionally like every other per-extruder array.
|
|
65
80
|
// filament_map is which physical extruder each filament sits in; density and cost turn extruded millimetres into
|
|
66
|
-
// the grams and currency the G-code footer reports.
|
|
67
|
-
|
|
81
|
+
// the grams and currency the G-code footer reports. gap_infill_speed is per extruder rather than per filament; the
|
|
82
|
+
// kernel reads its first entry, the wall filament's on the single-material path. filament_max_volumetric_speed caps
|
|
83
|
+
// every extrusion's speed as upstream does (GCode.cpp:7492).
|
|
84
|
+
const PASSTHROUGH_NUM_VECTOR = ['filament_map', 'filament_density', 'filament_cost', 'gap_infill_speed', 'filament_max_volumetric_speed']
|
|
68
85
|
// Material identity. The kernel needs the type for the two decisions upstream makes by material name (PETG's
|
|
69
86
|
// extra unretract, TPU on the first layer) and the settings id for the footer.
|
|
70
87
|
const PASSTHROUGH_STR_VECTOR = ['filament_type', 'filament_settings_id']
|
|
@@ -192,6 +209,22 @@ export function serializeProjectSettings(settings) {
|
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
|
|
212
|
+
// Issue 63: the custom G-code the kernel expands with upstream's PlaceholderParser (custom_gcode.cpp). The kernel
|
|
213
|
+
// gets the flattened settings it expands them against only when one of these holds text, which keeps every caller
|
|
214
|
+
// without a template on the raw path, byte-identical to before. The settings go in the form a 3mf's
|
|
215
|
+
// project_settings.config stores (serializeProjectSettings), as one JSON string: the kernel's flat key search
|
|
216
|
+
// never looks inside a string value, so no key in it can be taken for a kernel parameter the map omits.
|
|
217
|
+
// The kernel names the same templates in custom_gcode.cpp; test_custom_gcode.mjs checks each key here is expanded there.
|
|
218
|
+
export const CUSTOM_GCODE_KEYS = ['machine_start_gcode', 'machine_end_gcode', 'filament_end_gcode']
|
|
219
|
+
|
|
220
|
+
function placeholderConfig(settings, plate) {
|
|
221
|
+
const hasText = key => [settings?.[key]].flat().some(value => String(value ?? '').trim() !== '')
|
|
222
|
+
if (!CUSTOM_GCODE_KEYS.some(hasText)) return {}
|
|
223
|
+
// upstream's Print::get_plate_number_formatted: the 1-based plate, zero-padded to two digits
|
|
224
|
+
const plateNumber = String(plate + 1).padStart(2, '0')
|
|
225
|
+
return { placeholder_config: JSON.stringify({ ...serializeProjectSettings(settings), $plate_number: plateNumber }) }
|
|
226
|
+
}
|
|
227
|
+
|
|
195
228
|
// Right-panel settings values -> kernel parameters (derived from schema keys)
|
|
196
229
|
// opts.plate: which plate's entry to take from per-plate array options (wipe_tower_x/y — upstream coFloats,
|
|
197
230
|
// one entry per plate). Defaults to 0, so every existing caller reads exactly what it always did.
|
|
@@ -228,8 +261,13 @@ export function deriveKernelParams(settings, opts) {
|
|
|
228
261
|
// the default style silently sliced grid. An explicit non-tree style keeps winning over the type.
|
|
229
262
|
const styleRaw = str('support_style')
|
|
230
263
|
const typeRaw = str('support_type')
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
let support_style = 'grid'
|
|
265
|
+
if (/tree|organic/i.test(styleRaw) || (/tree/i.test(typeRaw) && styleRaw === 'default')) support_style = 'tree'
|
|
266
|
+
// Which tree. Upstream's slim / strong / hybrid run TreeSupport and organic runs TreeSupport3D
|
|
267
|
+
// (SupportParameters.hpp:190-204), and a tree type left on 'default' resolves to organic there. The kernel defaults to
|
|
268
|
+
// organic too, so only the three others are sent: an organic slice keeps exactly the parameters it had. Before this the
|
|
269
|
+
// style never reached the kernel and every tree style sliced organic.
|
|
270
|
+
const tree_style = { tree_slim: 'slim', tree_strong: 'strong', tree_hybrid: 'hybrid' }[styleRaw]
|
|
233
271
|
|
|
234
272
|
// Bed: bounding box of the first rectangle in printable_area
|
|
235
273
|
const pa = settingRaw(settings, 'printable_area')
|
|
@@ -309,6 +347,12 @@ export function deriveKernelParams(settings, opts) {
|
|
|
309
347
|
if (Number.isFinite(value)) passthrough[key] = value
|
|
310
348
|
}
|
|
311
349
|
for (const key of PASSTHROUGH_BOOL) if (present(key)) passthrough[key] = bool(key, false)
|
|
350
|
+
for (const key of PASSTHROUGH_PERCENT) {
|
|
351
|
+
if (!present(key)) continue
|
|
352
|
+
const value = Number(String(S(key)).replace('%', ''))
|
|
353
|
+
if (Number.isFinite(value)) passthrough[key] = value
|
|
354
|
+
}
|
|
355
|
+
for (const key of PASSTHROUGH_STR) if (present(key)) passthrough[key] = String(S(key))
|
|
312
356
|
for (const key of PASSTHROUGH_NUM_VECTOR) {
|
|
313
357
|
if (!present(key)) continue
|
|
314
358
|
const raw = settings[key]
|
|
@@ -330,6 +374,10 @@ export function deriveKernelParams(settings, opts) {
|
|
|
330
374
|
}
|
|
331
375
|
}
|
|
332
376
|
|
|
377
|
+
// Sent only for the three non-organic styles (see tree_style above), so an empty map still derives the same keys.
|
|
378
|
+
const treeStyle = {}
|
|
379
|
+
if (tree_style) treeStyle.tree_style = tree_style
|
|
380
|
+
|
|
333
381
|
const perExtruder = {}
|
|
334
382
|
for (const [param, key, scalar] of [
|
|
335
383
|
['extruder_nozzle_temp', 'nozzle_temperature', num('nozzle_temperature')],
|
|
@@ -352,6 +400,7 @@ export function deriveKernelParams(settings, opts) {
|
|
|
352
400
|
...widths,
|
|
353
401
|
...machine,
|
|
354
402
|
...passthrough, // present only for keys the settings map actually holds (see PASSTHROUGH_* above)
|
|
403
|
+
...treeStyle,
|
|
355
404
|
layer_height: num('layer_height'),
|
|
356
405
|
first_layer_height: num('initial_layer_print_height'),
|
|
357
406
|
line_width,
|
|
@@ -392,6 +441,7 @@ export function deriveKernelParams(settings, opts) {
|
|
|
392
441
|
// to it would rewrite the emitted G-code for every caller. The kernel keeps its own preamble when these are empty.
|
|
393
442
|
machine_start_gcode: String(settings?.machine_start_gcode ?? ''),
|
|
394
443
|
machine_end_gcode: String(settings?.machine_end_gcode ?? ''),
|
|
444
|
+
...placeholderConfig(settings, plate),
|
|
395
445
|
enable_support: bool('enable_support'),
|
|
396
446
|
support_threshold_angle: num('support_threshold_angle'),
|
|
397
447
|
support_top_z_distance: num('support_top_z_distance'),
|
package/types/paint.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// three-slicer-viewer/paint — the painting tool names the viewer and the slicer worker share.
|
|
2
|
+
|
|
3
|
+
/** The tools that fill on one click rather than paint along a drag: 'smart', 'bucket', 'triangle'. */
|
|
4
|
+
export const FILL_TOOLS: ReadonlySet<string>
|
|
5
|
+
|
|
6
|
+
/** The fills that spread across neighbouring facets and take a fill angle: 'smart', 'bucket'. */
|
|
7
|
+
export const ANGLE_FILL_TOOLS: ReadonlySet<string>
|
package/types/viewer.d.ts
CHANGED
|
@@ -169,7 +169,7 @@ export interface ViewportProps {
|
|
|
169
169
|
* way: OrbitControls preventDefaults `contextmenu` itself.
|
|
170
170
|
* - `logs` — console diagnostics, from the component and from the slice worker.
|
|
171
171
|
*/
|
|
172
|
-
export type ViewportFeature = 'shortcuts' | 'warmup' | 'drop' | 'filePicker' | 'contextMenu' | 'logs'
|
|
172
|
+
export type ViewportFeature = 'shortcuts' | 'warmup' | 'drop' | 'filePicker' | 'contextMenu' | 'logs' | 'versionBadge'
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
175
|
* The panels that accept `'readonly'` as well as `true`/`false` — the right column and the cards in it. Everything
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import { unzip as Y, unzipSync as H } from "three/examples/jsm/libs/fflate.module.js";
|
|
2
|
-
import { S as W } from "./viewer_defaults-B7UUZ582.js";
|
|
3
|
-
function q(t) {
|
|
4
|
-
return new Promise((o) => {
|
|
5
|
-
let s = !1;
|
|
6
|
-
const e = (r) => {
|
|
7
|
-
s || (s = !0, o(r));
|
|
8
|
-
};
|
|
9
|
-
try {
|
|
10
|
-
Y(t, (r, i) => e(r ? H(t) : i));
|
|
11
|
-
} catch {
|
|
12
|
-
e(H(t));
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
const L = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0];
|
|
17
|
-
function K(t, o) {
|
|
18
|
-
const s = new Array(12);
|
|
19
|
-
for (let e = 0; e < 4; e++)
|
|
20
|
-
for (let r = 0; r < 3; r++)
|
|
21
|
-
s[e * 3 + r] = t[e * 3] * o[r] + t[e * 3 + 1] * o[3 + r] + t[e * 3 + 2] * o[6 + r] + (e === 3 ? o[9 + r] : 0);
|
|
22
|
-
return s;
|
|
23
|
-
}
|
|
24
|
-
function U(t) {
|
|
25
|
-
if (!t) return L;
|
|
26
|
-
const o = t.trim().split(/\s+/).map(Number);
|
|
27
|
-
return o.length === 12 && o.every(Number.isFinite) ? o : L;
|
|
28
|
-
}
|
|
29
|
-
const x = {};
|
|
30
|
-
function f(t, o) {
|
|
31
|
-
const e = (x[o] || (x[o] = new RegExp(`\\b${o}\\s*=\\s*"([^"]*)"`))).exec(t);
|
|
32
|
-
return e ? e[1] : null;
|
|
33
|
-
}
|
|
34
|
-
const B = /<vertex\s+x="([^"]*)"\s+y="([^"]*)"\s+z="([^"]*)"/g, D = /<triangle\s+v1="([^"]*)"\s+v2="([^"]*)"\s+v3="([^"]*)"([^>]*)/g, C = [
|
|
35
|
-
["paint_color", "color"],
|
|
36
|
-
// multi-material: state 1..16 == Extruder1..16
|
|
37
|
-
["paint_supports", "supports"],
|
|
38
|
-
// support enforcer/blocker: states 1 and 2
|
|
39
|
-
["paint_seam", "seam"],
|
|
40
|
-
// unsupported by the kernel — collected for the warning only
|
|
41
|
-
["paint_fuzzy_skin", "fuzzy"]
|
|
42
|
-
// ditto
|
|
43
|
-
];
|
|
44
|
-
function G() {
|
|
45
|
-
return { color: /* @__PURE__ */ new Map(), supports: /* @__PURE__ */ new Map(), seam: /* @__PURE__ */ new Map(), fuzzy: /* @__PURE__ */ new Map() };
|
|
46
|
-
}
|
|
47
|
-
function Q(t) {
|
|
48
|
-
return !t || C.every(([, o]) => t[o].size === 0);
|
|
49
|
-
}
|
|
50
|
-
function J(t, o, s) {
|
|
51
|
-
for (const [e, r] of C) {
|
|
52
|
-
if (!t.includes(e)) continue;
|
|
53
|
-
const i = f(t, e);
|
|
54
|
-
i && s[r].set(o, i);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function Z(t) {
|
|
58
|
-
const o = [];
|
|
59
|
-
B.lastIndex = 0;
|
|
60
|
-
let s;
|
|
61
|
-
for (; s = B.exec(t); ) o.push(+s[1], +s[2], +s[3]);
|
|
62
|
-
if (!o.length)
|
|
63
|
-
for (const i of t.match(/<vertex\b[^>]*>/g) || []) o.push(+f(i, "x"), +f(i, "y"), +f(i, "z"));
|
|
64
|
-
if (o.length < 9) return null;
|
|
65
|
-
const e = [], r = G();
|
|
66
|
-
for (D.lastIndex = 0; s = D.exec(t); ) {
|
|
67
|
-
const i = e.length / 3;
|
|
68
|
-
e.push(+s[1], +s[2], +s[3]), s[4].length > 2 && J(s[4], i, r);
|
|
69
|
-
}
|
|
70
|
-
if (!e.length)
|
|
71
|
-
for (const i of t.match(/<triangle\b[^>]*>/g) || []) {
|
|
72
|
-
const p = e.length / 3;
|
|
73
|
-
e.push(+f(i, "v1"), +f(i, "v2"), +f(i, "v3")), J(i, p, r);
|
|
74
|
-
}
|
|
75
|
-
return e.length ? { verts: o, tris: e, paint: r } : null;
|
|
76
|
-
}
|
|
77
|
-
function tt(t) {
|
|
78
|
-
const o = /* @__PURE__ */ new Map(), s = /<object\b([^>]*)>([\s\S]*?)<\/object>/g;
|
|
79
|
-
let e;
|
|
80
|
-
for (; e = s.exec(t); ) {
|
|
81
|
-
const p = f(e[1], "id");
|
|
82
|
-
if (!p) continue;
|
|
83
|
-
const u = e[2], n = [];
|
|
84
|
-
for (const a of u.match(/<component\b[^>]*>/g) || []) {
|
|
85
|
-
const c = f(a, "objectid");
|
|
86
|
-
c && n.push({ objectid: c, path: f(a, "p:path") || f(a, "path"), transform: U(f(a, "transform")) });
|
|
87
|
-
}
|
|
88
|
-
o.set(p, n.length ? { components: n } : { mesh: Z(u) });
|
|
89
|
-
}
|
|
90
|
-
const r = [], i = /<build\b[^>]*>([\s\S]*?)<\/build>/.exec(t);
|
|
91
|
-
if (i)
|
|
92
|
-
for (const p of i[1].match(/<item\b[^>]*>/g) || []) {
|
|
93
|
-
const u = f(p, "objectid");
|
|
94
|
-
u && r.push({ objectid: u, path: f(p, "p:path") || f(p, "path"), transform: U(f(p, "transform")) });
|
|
95
|
-
}
|
|
96
|
-
return { objects: o, items: r };
|
|
97
|
-
}
|
|
98
|
-
function k(t) {
|
|
99
|
-
const o = String(t).replace(/^\/+/, "");
|
|
100
|
-
return o.includes("%") ? decodeURIComponent(o) : o;
|
|
101
|
-
}
|
|
102
|
-
function R(t) {
|
|
103
|
-
const o = {};
|
|
104
|
-
for (const s of t.match(/<metadata\b[^>]*>/g) || []) {
|
|
105
|
-
const e = f(s, "key");
|
|
106
|
-
e && (o[e] = f(s, "value") ?? "");
|
|
107
|
-
}
|
|
108
|
-
return o;
|
|
109
|
-
}
|
|
110
|
-
function et(t) {
|
|
111
|
-
const o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), e = /<object\b([^>]*)>([\s\S]*?)<\/object>/g;
|
|
112
|
-
let r;
|
|
113
|
-
for (; r = e.exec(t); ) {
|
|
114
|
-
const u = f(r[1], "id");
|
|
115
|
-
if (u) {
|
|
116
|
-
const n = r[2];
|
|
117
|
-
o.set(u, R(n.replace(/<(?:part|volume)\b[\s\S]*?<\/(?:part|volume)>/g, "")));
|
|
118
|
-
const a = [];
|
|
119
|
-
for (const c of n.match(/<volume\b[\s\S]*?<\/volume>/g) || []) {
|
|
120
|
-
const d = R(c).volume_type, y = d === "SupportBlocker" ? "blocker" : d === "SupportEnforcer" ? "enforcer" : null, _ = Number(f(c, "firstid")), S = Number(f(c, "lastid"));
|
|
121
|
-
y && Number.isInteger(_) && Number.isInteger(S) && _ >= 0 && S >= _ && a.push({ firstTriangle: _, lastTriangle: S, kind: y });
|
|
122
|
-
}
|
|
123
|
-
a.length && s.set(u, a);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const i = [], p = /<plate\b[^>]*>([\s\S]*?)<\/plate>/g;
|
|
127
|
-
for (; r = p.exec(t); ) {
|
|
128
|
-
const u = r[1], n = R(u.replace(/<model_instance\b[\s\S]*?<\/model_instance>/g, "")), a = [];
|
|
129
|
-
for (const c of u.match(/<model_instance\b[\s\S]*?<\/model_instance>/g) || []) {
|
|
130
|
-
const d = R(c).object_id;
|
|
131
|
-
d && a.push(d);
|
|
132
|
-
}
|
|
133
|
-
i.push({ index: Math.max(0, (Number(n.plater_id) || i.length + 1) - 1), objectIds: a, gcodeFile: n.gcode_file || null });
|
|
134
|
-
}
|
|
135
|
-
return { objects: o, plates: i, volumes: s };
|
|
136
|
-
}
|
|
137
|
-
const nt = Object.freeze({
|
|
138
|
-
manualSupportPoints: "prepared-roundtrip",
|
|
139
|
-
drainHoles: "preserved-unsupported",
|
|
140
|
-
modifierVolumes: "prepared-mask-filtering",
|
|
141
|
-
uiEditing: "unavailable"
|
|
142
|
-
});
|
|
143
|
-
function V(t, o, s, e, r) {
|
|
144
|
-
const i = /* @__PURE__ */ new Map();
|
|
145
|
-
if (!t?.trim()) return i;
|
|
146
|
-
const p = t.trim().split(/\r?\n/), u = new RegExp(`^${o}_format_version=(\\d+)$`).exec(p[0]), n = u ? Number(u[1]) : 0;
|
|
147
|
-
for (const a of p.slice(u ? 1 : 0)) {
|
|
148
|
-
const c = /^object_id=(\d+)\|(.*)$/.exec(a);
|
|
149
|
-
if (!c || i.has(Number(c?.[1]))) continue;
|
|
150
|
-
const d = Number(c[1]), y = c[2].trim() ? c[2].trim().split(/\s+/).map(Number) : [], _ = s(n);
|
|
151
|
-
if (!_ || y.length % _ !== 0 || y.some((l) => !Number.isFinite(l))) {
|
|
152
|
-
e.push({ code: o === "support_points" ? "SLA_SUPPORT_POINT_COUNT" : "SLA_DRAIN_HOLE_COUNT", object: d }), i.set(d, []);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const S = [];
|
|
156
|
-
for (let l = 0; l < y.length; l += _) S.push(r(y.slice(l, l + _), n));
|
|
157
|
-
i.set(d, S);
|
|
158
|
-
}
|
|
159
|
-
return i;
|
|
160
|
-
}
|
|
161
|
-
function X(t, o) {
|
|
162
|
-
const [s, e, r] = o;
|
|
163
|
-
return [s * t[0] + e * t[3] + r * t[6] + t[9], s * t[1] + e * t[4] + r * t[7] + t[10], s * t[2] + e * t[5] + r * t[8] + t[11]];
|
|
164
|
-
}
|
|
165
|
-
function ot(t, o) {
|
|
166
|
-
const [s, e, r] = o, i = [s * t[0] + e * t[3] + r * t[6], s * t[1] + e * t[4] + r * t[7], s * t[2] + e * t[5] + r * t[8]], p = Math.hypot(...i);
|
|
167
|
-
return p ? i.map((u) => u / p) : i;
|
|
168
|
-
}
|
|
169
|
-
function st(t, o) {
|
|
170
|
-
const s = (n) => {
|
|
171
|
-
const a = t.get(n);
|
|
172
|
-
return a ? o.decode(a) : null;
|
|
173
|
-
}, e = {
|
|
174
|
-
settings: null,
|
|
175
|
-
// Metadata/project_settings.config — the flattened preset (raw upstream strings)
|
|
176
|
-
objectMeta: /* @__PURE__ */ new Map(),
|
|
177
|
-
// 3mf object id -> per-object metadata (name, extruder, per-object overrides)
|
|
178
|
-
plates: [],
|
|
179
|
-
// [{index, objectIds}]
|
|
180
|
-
hasLayerHeightProfile: !1,
|
|
181
|
-
hasCustomGcodePerLayer: !1,
|
|
182
|
-
volumeMeta: /* @__PURE__ */ new Map(),
|
|
183
|
-
sla: { capabilities: { ...nt }, issues: [], supportPoints: /* @__PURE__ */ new Map(), drainHoles: /* @__PURE__ */ new Map() },
|
|
184
|
-
viewerSettings: null,
|
|
185
|
-
// Metadata/three_slicer_settings.json — this package's own member (write_3mf.js)
|
|
186
|
-
plateSettings: null,
|
|
187
|
-
// ...its two halves: global viewer knobs, and per-plate overrides keyed by plate index
|
|
188
|
-
plateCount: null,
|
|
189
|
-
// ...and the plate count it was saved with (empty plates hold no <plate> record)
|
|
190
|
-
gcodePlates: null
|
|
191
|
-
// a .gcode.3mf's sliced plates: [{index, gcode}] (write_3mf.js writeGcode3MF), else null
|
|
192
|
-
}, r = s("Metadata/project_settings.config");
|
|
193
|
-
if (r)
|
|
194
|
-
try {
|
|
195
|
-
e.settings = JSON.parse(r);
|
|
196
|
-
} catch {
|
|
197
|
-
e.settings = null;
|
|
198
|
-
}
|
|
199
|
-
const i = s("Metadata/three_slicer_settings.json");
|
|
200
|
-
if (i) {
|
|
201
|
-
let n = null;
|
|
202
|
-
try {
|
|
203
|
-
n = JSON.parse(i);
|
|
204
|
-
} catch {
|
|
205
|
-
n = null;
|
|
206
|
-
}
|
|
207
|
-
const a = (c) => c && typeof c == "object" && !Array.isArray(c);
|
|
208
|
-
if (a(n)) {
|
|
209
|
-
a(n.viewer) && Object.keys(n.viewer).length && (e.viewerSettings = n.viewer);
|
|
210
|
-
let c = [];
|
|
211
|
-
a(n.plates) && (c = Object.entries(n.plates));
|
|
212
|
-
const d = c.filter(([y, _]) => /^\d+$/.test(y) && a(_) && Object.keys(_).length);
|
|
213
|
-
d.length && (e.plateSettings = Object.fromEntries(d.map(([y, _]) => [Number(y), _]))), Number.isInteger(n.plateCount) && n.plateCount >= 1 && (e.plateCount = n.plateCount);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
const p = s("Metadata/model_settings.config");
|
|
217
|
-
if (p) {
|
|
218
|
-
const n = et(p);
|
|
219
|
-
e.objectMeta = n.objects, e.plates = n.plates, e.volumeMeta = n.volumes;
|
|
220
|
-
}
|
|
221
|
-
const u = e.plates.map((n) => ({ index: n.index, gcode: n.gcodeFile && s(k(n.gcodeFile)) })).filter((n) => n.gcode);
|
|
222
|
-
return u.length && (e.gcodePlates = u), e.sla.supportPoints = V(
|
|
223
|
-
s("Metadata/Slic3r_PE_sla_support_points.txt"),
|
|
224
|
-
"support_points",
|
|
225
|
-
(n) => n === 0 ? 3 : n === 1 ? 5 : 0,
|
|
226
|
-
e.sla.issues,
|
|
227
|
-
(n, a) => ({
|
|
228
|
-
position: n.slice(0, 3),
|
|
229
|
-
radius: a === 0 ? W : n[3],
|
|
230
|
-
type: a === 0 || n[4] === 2 ? "manual" : n[4] === 1 ? "island" : "slope"
|
|
231
|
-
})
|
|
232
|
-
), e.sla.drainHoles = V(
|
|
233
|
-
s("Metadata/Slic3r_PE_sla_drain_holes.txt"),
|
|
234
|
-
"drain_holes",
|
|
235
|
-
(n) => n === 1 ? 8 : 0,
|
|
236
|
-
e.sla.issues,
|
|
237
|
-
(n) => {
|
|
238
|
-
const a = Math.hypot(n[3], n[4], n[5]), c = a ? n.slice(3, 6).map((d) => d / a) : n.slice(3, 6);
|
|
239
|
-
return { position: n.slice(0, 3).map((d, y) => d + c[y]), normal: c, radius: n[6], height: n[7] - 1 };
|
|
240
|
-
}
|
|
241
|
-
), e.hasLayerHeightProfile = !!s("Metadata/layer_heights_profile.txt")?.trim(), e.hasCustomGcodePerLayer = !!s("Metadata/custom_gcode_per_layer.xml")?.trim(), e;
|
|
242
|
-
}
|
|
243
|
-
async function ct(t, o = "model") {
|
|
244
|
-
const s = ArrayBuffer.isView(t) ? new Uint8Array(t.buffer, t.byteOffset, t.byteLength) : new Uint8Array(t), e = await q(s), r = /* @__PURE__ */ new Map();
|
|
245
|
-
for (const l of Object.keys(e)) r.set(k(l), e[l]);
|
|
246
|
-
const i = new TextDecoder(), p = /* @__PURE__ */ new Map(), u = (l) => {
|
|
247
|
-
const w = k(l);
|
|
248
|
-
if (p.has(w)) return p.get(w);
|
|
249
|
-
const m = r.get(w), M = m ? tt(i.decode(m)) : null;
|
|
250
|
-
return p.set(w, M), M;
|
|
251
|
-
};
|
|
252
|
-
let n = "3D/3dmodel.model";
|
|
253
|
-
const a = r.get("_rels/.rels");
|
|
254
|
-
if (a) {
|
|
255
|
-
for (const l of i.decode(a).match(/<Relationship\b[^>]*>/g) || [])
|
|
256
|
-
if ((f(l, "Type") || "").endsWith("/3dmodel")) {
|
|
257
|
-
n = k(f(l, "Target"));
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
const c = st(r, i), d = u(n);
|
|
262
|
-
if (!d && c.gcodePlates) return { objects: [], project: c };
|
|
263
|
-
if (!d) throw new Error(`3MF root model not found: ${n}`);
|
|
264
|
-
const y = [], _ = (l, w, m, M, P, N) => {
|
|
265
|
-
if (N > 16) return;
|
|
266
|
-
const T = u(w)?.objects.get(l);
|
|
267
|
-
if (!T) return;
|
|
268
|
-
if (T.components) {
|
|
269
|
-
for (const j of T.components) _(j.objectid, j.path || w, K(j.transform, m), M, P, N + 1);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (!T.mesh) return;
|
|
273
|
-
const { verts: E, tris: I, paint: v } = T.mesh, z = M.length / 9;
|
|
274
|
-
for (let j = 0; j < I.length; j++) {
|
|
275
|
-
const h = I[j] * 3, b = E[h], g = E[h + 1], A = E[h + 2];
|
|
276
|
-
M.push(
|
|
277
|
-
b * m[0] + g * m[3] + A * m[6] + m[9],
|
|
278
|
-
b * m[1] + g * m[4] + A * m[7] + m[10],
|
|
279
|
-
b * m[2] + g * m[5] + A * m[8] + m[11]
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
if (v) for (const [, j] of C)
|
|
283
|
-
for (const [h, b] of v[j]) P[j].set(z + h, b);
|
|
284
|
-
}, S = d.items.length ? d.items : [...d.objects.keys()].filter((l) => d.objects.get(l).mesh).map((l) => ({ objectid: l, path: n, transform: L }));
|
|
285
|
-
return S.forEach((l, w) => {
|
|
286
|
-
const m = [], M = G();
|
|
287
|
-
if (_(l.objectid, l.path || n, l.transform, m, M, 0), m.length < 9) return;
|
|
288
|
-
let P = new Float32Array(m);
|
|
289
|
-
const N = [], O = c.volumeMeta.get(l.objectid) || [];
|
|
290
|
-
if (O.length) {
|
|
291
|
-
const h = /* @__PURE__ */ new Set();
|
|
292
|
-
for (let b = 0; b < O.length; b++) {
|
|
293
|
-
const g = O[b], A = g.firstTriangle * 9, $ = Math.min(m.length, (g.lastTriangle + 1) * 9);
|
|
294
|
-
if (!(A >= $)) {
|
|
295
|
-
for (let F = g.firstTriangle; F <= g.lastTriangle; F++) h.add(F);
|
|
296
|
-
N.push({ id: `${l.objectid}:modifier:${b}`, kind: g.kind, tris: new Float32Array(m.slice(A, $)) });
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (h.size) {
|
|
300
|
-
const b = [];
|
|
301
|
-
for (let g = 0; g < m.length / 9; g++) h.has(g) || b.push(...m.slice(g * 9, g * 9 + 9));
|
|
302
|
-
P = new Float32Array(b);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const T = (c.sla.supportPoints.get(w + 1) || []).map((h) => ({ ...h, position: X(l.transform, h.position) })), E = (c.sla.drainHoles.get(w + 1) || []).map((h) => ({
|
|
306
|
-
...h,
|
|
307
|
-
position: X(l.transform, h.position),
|
|
308
|
-
normal: ot(l.transform, h.normal)
|
|
309
|
-
}));
|
|
310
|
-
let I = 1 / 0, v = 1 / 0, z = -1 / 0, j = -1 / 0;
|
|
311
|
-
for (let h = 0; h < P.length; h += 3) {
|
|
312
|
-
const b = P[h], g = P[h + 1];
|
|
313
|
-
b < I && (I = b), b > z && (z = b), g < v && (v = g), g > j && (j = g);
|
|
314
|
-
}
|
|
315
|
-
y.push({
|
|
316
|
-
name: S.length > 1 ? `${o}#${w + 1}` : o,
|
|
317
|
-
tris: P,
|
|
318
|
-
objectid: l.objectid,
|
|
319
|
-
paint: Q(M) ? null : M,
|
|
320
|
-
sla: { supportPoints: T, drainHoles: E, modifierVolumes: N },
|
|
321
|
-
bbox: { minX: I, minY: v, maxX: z, maxY: j }
|
|
322
|
-
});
|
|
323
|
-
}), { objects: y, project: c };
|
|
324
|
-
}
|
|
325
|
-
export {
|
|
326
|
-
ct as p
|
|
327
|
-
};
|