three-slicer-viewer 0.2.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/COMPONENTS.md +44 -0
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/THIRD-PARTY-NOTICES.md +19 -0
- package/TOOLPATH_SPEC.md +173 -0
- package/VIEWER.md +423 -0
- package/data/config-schema-lean.json +1 -0
- package/data/preset-keys.json +1 -0
- package/data/ui-tree-keys.json +1 -0
- package/dist/components.js +206 -0
- package/dist/gcode_parse.js +190 -0
- package/dist/index.js +6416 -0
- package/dist/make_worker.js +26 -0
- package/dist/model_loaders.js +183 -0
- package/dist/parse_3mf-CQ5rAX6P.js +299 -0
- package/dist/parse_3mf.worker.js +10 -0
- package/dist/sl1_encode.worker.js +10 -0
- package/dist/sl1_write-CvrNejbW.js +302 -0
- package/dist/sla_reconstruct-CXkYhux2.js +176 -0
- package/dist/sla_reconstruct.worker.js +24 -0
- package/dist/sla_slice.worker.js +73 -0
- package/dist/toolpath.js +408 -0
- package/package.json +83 -0
- package/src/data.js +2 -0
- package/src/settings/data.js +6 -0
- package/src/settings/index.js +2 -0
- package/src/settings/kernel_setting_keys.js +132 -0
- package/src/settings/preset_file.js +99 -0
- package/src/settings/settings_core.js +638 -0
- package/src/settings/toggle_eval.js +90 -0
- package/types/components.d.ts +39 -0
- package/types/data.d.ts +3 -0
- package/types/gcode.d.ts +29 -0
- package/types/index.d.ts +7 -0
- package/types/loaders.d.ts +33 -0
- package/types/settings-keys.d.ts +1855 -0
- package/types/settings.d.ts +173 -0
- package/types/toggle.d.ts +30 -0
- package/types/toolpath.d.ts +138 -0
- package/types/viewer.d.ts +256 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Stage 25 S5.1: partial translation of toggle-rules — upstream C++ toggle_options conditions (enable_if) -> JS.
|
|
2
|
+
// Scope: among the rules touching keys the viewer handles, only those "fully" translatable with the hardcoded locals below.
|
|
3
|
+
// Untranslatable ones (unknown locals, enum comparisons, …) fail open (stay enabled) — avoids wrongly disabling (honest partial implementation).
|
|
4
|
+
// Translating all 907 keys / 231 rules is out of scope (noted in the README). Reference: toggle_print_fff_options.locals in toggle-rules.json.
|
|
5
|
+
import { leanSchema } from './data.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The evaluator bound to a rule set and a schema. This package ships NO rules: toggle-rules.json is extracted from
|
|
9
|
+
* upstream's toggle_options logic and stays with the AGPL package, which binds it and re-exports the result as
|
|
10
|
+
* `three-slicer/toggle`. Bound to nothing, disabledKeys() disables nothing — every field stays enabled.
|
|
11
|
+
*/
|
|
12
|
+
export function makeToggle(toggles = {}, schema = leanSchema) {
|
|
13
|
+
// Settings value accessor (settings map first, schema default otherwise). Percentages ("15%") become numbers.
|
|
14
|
+
function makeCfg(settings) {
|
|
15
|
+
const val = k => {
|
|
16
|
+
let v = settings ? settings[k] : undefined
|
|
17
|
+
if (v === undefined || v === '') { const d = schema[k]?.default; v = Array.isArray(d) ? d[0] : d }
|
|
18
|
+
return v
|
|
19
|
+
}
|
|
20
|
+
const num = k => { const v = val(k); const n = parseFloat(v); return Number.isFinite(n) ? n : 0 }
|
|
21
|
+
return {
|
|
22
|
+
bool: k => { const v = val(k); return v === true || v === 1 || v === '1' || v === 'true' },
|
|
23
|
+
int: k => Math.trunc(num(k)),
|
|
24
|
+
float: k => num(k),
|
|
25
|
+
has: k => k in schema,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Upstream locals translated inline (viewer-related conditions only). Each value is a boolean.
|
|
30
|
+
function makeLocals(cfg) {
|
|
31
|
+
const have_raft = cfg.int('raft_layers') > 0
|
|
32
|
+
const has_spiral_vase = cfg.bool('spiral_mode')
|
|
33
|
+
const has_top_shell = cfg.int('top_shell_layers') > 0 || (has_spiral_vase && cfg.int('bottom_shell_layers') > 1)
|
|
34
|
+
const has_bottom_shell = cfg.int('bottom_shell_layers') > 0
|
|
35
|
+
return {
|
|
36
|
+
have_raft,
|
|
37
|
+
have_perimeters: cfg.int('wall_loops') > 0,
|
|
38
|
+
have_infill: cfg.float('sparse_infill_density') > 0,
|
|
39
|
+
has_spiral_vase,
|
|
40
|
+
has_top_shell,
|
|
41
|
+
has_bottom_shell,
|
|
42
|
+
has_solid_infill: has_top_shell || has_bottom_shell,
|
|
43
|
+
have_skirt: cfg.int('skirt_loops') > 0,
|
|
44
|
+
have_support_material: cfg.bool('enable_support') || have_raft,
|
|
45
|
+
have_support_interface: cfg.int('support_interface_top_layers') > 0 || cfg.int('support_interface_bottom_layers') > 0,
|
|
46
|
+
have_prime_tower: cfg.bool('enable_prime_tower'),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Map of keys disabled under the current settings { key: condition }. Only rules that are unambiguously false.
|
|
51
|
+
function disabledKeys(cfg) {
|
|
52
|
+
const locals = makeLocals(cfg)
|
|
53
|
+
const groups = ['toggle_print_fff_options', 'toggle_filament_options', 'toggle_printer_options',
|
|
54
|
+
'TabFilament::toggle_options', 'TabPrinter::toggle_options']
|
|
55
|
+
const out = {}
|
|
56
|
+
for (const gname of groups) {
|
|
57
|
+
const g = toggles[gname]
|
|
58
|
+
if (!g || !Array.isArray(g.rules)) continue
|
|
59
|
+
for (const rule of g.rules) {
|
|
60
|
+
if (!rule.enable_if || !Array.isArray(rule.fields)) continue
|
|
61
|
+
const en = evalEnableIf(rule.enable_if, locals, cfg)
|
|
62
|
+
if (en === false) for (const k of rule.fields) if (!(k in out)) out[k] = rule.enable_if
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
return { makeCfg, disabledKeys, evalEnableIf }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Evaluates enable_if (up to the first comma) to a boolean. Returns null (= stays enabled) when not fully translatable.
|
|
71
|
+
export function evalEnableIf(expr, locals, cfg) {
|
|
72
|
+
let s = String(expr).split(',')[0].trim()
|
|
73
|
+
if (!s) return null
|
|
74
|
+
// Direct config access pattern -> value substitution
|
|
75
|
+
s = s.replace(/config->opt_bool\(\s*"([^"]+)"\s*\)/g, (_, k) => cfg.bool(k) ? 'true' : 'false')
|
|
76
|
+
s = s.replace(/config->opt_int\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.int(k)))
|
|
77
|
+
s = s.replace(/config->opt_float\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.float(k)))
|
|
78
|
+
// Substitute known locals (longest name first — prevents partial matches)
|
|
79
|
+
for (const name of Object.keys(locals).sort((a, b) => b.length - a.length))
|
|
80
|
+
s = s.replace(new RegExp('\\b' + name + '\\b', 'g'), locals[name] ? 'true' : 'false')
|
|
81
|
+
// Check the remaining string is a safe boolean expression (only true/false/numbers/operators/parens) — otherwise unknown -> null
|
|
82
|
+
const stripped = s.replace(/\btrue\b|\bfalse\b/g, '1')
|
|
83
|
+
if (!/^[\s()!<>=&|0-9.]*$/.test(stripped)) return null
|
|
84
|
+
try { return !!Function('"use strict";return (' + s + ')')() } catch { return null }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The unbound default: nothing disables. `three-slicer/toggle` is the bound one.
|
|
88
|
+
const unbound = makeToggle({}, leanSchema)
|
|
89
|
+
export const makeCfg = unbound.makeCfg
|
|
90
|
+
export const disabledKeys = unbound.disabledKeys
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// three-slicer/components
|
|
2
|
+
import type * as React from 'react'
|
|
3
|
+
import type { SlicerSettings } from './settings-keys.d.ts'
|
|
4
|
+
|
|
5
|
+
export interface SettingsPanelProps {
|
|
6
|
+
/**
|
|
7
|
+
* The config schema to render from. Defaults to this package's reduced schema — types, defaults, enum values,
|
|
8
|
+
* layout flags and units, but no label or tooltip, so a field is labelled by its key. `three-slicer/components`
|
|
9
|
+
* passes the full schema; a host may pass its own (a translation, its own vocabulary).
|
|
10
|
+
*/
|
|
11
|
+
schema?: Record<string, Record<string, unknown>>
|
|
12
|
+
/** The tab/page/group tree. Defaults to one flat page per builder over this package's ui-tree keys. */
|
|
13
|
+
uiTree?: Record<string, Array<{ page: string; groups: Array<{ group: string; options: string[] }> }>>
|
|
14
|
+
/** The enable/disable evaluator. Defaults to the unbound one, which disables nothing. */
|
|
15
|
+
toggle?: { makeCfg(settings: unknown): unknown; disabledKeys(cfg: unknown): Record<string, string> }
|
|
16
|
+
/** Sparse map — only edited keys. Missing keys fall back to the config-schema default. */
|
|
17
|
+
settings: SlicerSettings
|
|
18
|
+
/** React setState shape. The only channel through which state leaves the component. */
|
|
19
|
+
setSettings: React.Dispatch<React.SetStateAction<SlicerSettings>>
|
|
20
|
+
/** Label click — for deep links / detail views. Omit for a plain label (zero router coupling). */
|
|
21
|
+
onOptionOpen?: (optKey: string) => void
|
|
22
|
+
/** When embedding inside another panel */
|
|
23
|
+
embedded?: boolean
|
|
24
|
+
/** Replace schema-driven widgets per key (e.g. `{ printable_area: MyBedEditor }`) */
|
|
25
|
+
customWidgets?: Record<string, React.ComponentType<any>>
|
|
26
|
+
/**
|
|
27
|
+
* Pins the panel to one builder (optionally one page) and drops the search/group/page/mode chrome, so the form
|
|
28
|
+
* can be folded into another card. This is how `<Viewport/>`'s `motionPanel` slot is meant to be filled:
|
|
29
|
+
*
|
|
30
|
+
* ```jsx
|
|
31
|
+
* <SettingsPanel settings={settings} setSettings={setSettings} embedded
|
|
32
|
+
* only={{ builder: 'TabPrinter::build_kinematics_page' }} />
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
only?: { builder: string; page?: string }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare const SettingsPanel: React.FC<SettingsPanelProps>
|
|
39
|
+
export default SettingsPanel
|
package/types/data.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const leanSchema: Record<string, { type: string; default?: unknown; enum_values?: string[]; min?: number; max?: number; default_type?: string; defined_in?: string; line?: number }>
|
|
2
|
+
export const uiTreeKeys: Record<string, string[]>
|
|
3
|
+
export const presetKeys: Record<string, string[]>
|
package/types/gcode.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// three-slicer/viewer/gcode — G-code text -> kernel-shaped layer stream (renderable without slicing).
|
|
2
|
+
// Feed the returned layers straight into buildSegmentData (three-slicer/viewer/toolpath).
|
|
3
|
+
|
|
4
|
+
export interface GcodeLayer {
|
|
5
|
+
z: number
|
|
6
|
+
/** Stride 8: [x0,y0,z0,enc, x1,y1,z1,enc] per segment, enc = role + tool*16 (role 0 = travel). */
|
|
7
|
+
paths: Float32Array
|
|
8
|
+
/** One entry per segment; 0 means "use the default line width". */
|
|
9
|
+
widths: Float32Array
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ParseGcodeResult {
|
|
13
|
+
layers: GcodeLayer[]
|
|
14
|
+
stats: { layers: number; path_segments: number; travel_segments: number; tools: number[] }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ParseGcodeOptions {
|
|
18
|
+
/** Used to derive bead width from E when the file has no ;WIDTH: comments. Default 1.75. */
|
|
19
|
+
filamentDiameter?: number
|
|
20
|
+
/** Bead-height fallback when a layer's height cannot be inferred from z steps. Default 0.2. */
|
|
21
|
+
defaultLayerHeight?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parses G0/G1/G2/G3 (I/J arcs), G90/G91, M82/M83, G92, T<n>, and the layer/role comment conventions of
|
|
26
|
+
* OrcaSlicer/PrusaSlicer (;TYPE: ;WIDTH: ;LAYER_CHANGE ;Z:), Cura (;TYPE: ;LAYER:) and this kernel's own output.
|
|
27
|
+
* Recovery is lossy where G-code carries no data: unknown roles become wall(1), widths are derived from E.
|
|
28
|
+
*/
|
|
29
|
+
export function parseGcode(text: string, opts?: ParseGcodeOptions): ParseGcodeResult
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './viewer'
|
|
2
|
+
export { default } from './viewer'
|
|
3
|
+
export * from './toolpath'
|
|
4
|
+
export const log: { info(...a: unknown[]): void; warn(...a: unknown[]): void; error(...a: unknown[]): void }
|
|
5
|
+
export function effectiveSettings(settings: Record<string, unknown>, plateSettings: Record<string, unknown>, plate: number): Record<string, unknown>
|
|
6
|
+
export function plateTechnology(settings: Record<string, unknown>, plateSettings: Record<string, unknown>, plate: number): 'FFF' | 'SLA'
|
|
7
|
+
export function statsFromKernel(stats: Record<string, unknown>, throughput?: unknown): Record<string, unknown>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// three-slicer/viewer/loaders — pure loaders, independent of the kernel.
|
|
2
|
+
|
|
3
|
+
/** The 5 built-ins plus any extension registered via `registerLoader()`, which pushes onto this array. */
|
|
4
|
+
export const SUPPORTED_EXT: string[]
|
|
5
|
+
|
|
6
|
+
/** Lowercased extension. Empty string when there is no dot. */
|
|
7
|
+
export function fileExt(name: string): string
|
|
8
|
+
|
|
9
|
+
export interface LoadedObject {
|
|
10
|
+
name: string
|
|
11
|
+
/** Triangle vertex coordinates (N*9, model z-up) */
|
|
12
|
+
modelPos: Float32Array
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A single file may contain several objects (3mf/amf/step) -> always an array. */
|
|
16
|
+
export function loadModel(name: string, buffer: ArrayBuffer): Promise<LoadedObject[]>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Registers an extra format. Formats needing heavy dependencies (STEP=OCCT WASM, etc.) are not bundled here,
|
|
20
|
+
* so the app wires them up itself. The `<Viewport/>` file dialog and drag-and-drop filters pick them up automatically.
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* import { registerLoader } from 'three-slicer/viewer/loaders'
|
|
24
|
+
* registerLoader('step,stp', async (buffer, name) => [{ name, modelPos }])
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function registerLoader(
|
|
28
|
+
exts: string | string[],
|
|
29
|
+
fn: (buffer: ArrayBuffer, name: string) => LoadedObject[] | Promise<LoadedObject[]>,
|
|
30
|
+
): void
|
|
31
|
+
|
|
32
|
+
/** Connected-component split. Returns `null` when there is only one component (cannot split) — not an empty array. */
|
|
33
|
+
export function splitConnectedComponents(localPos: Float32Array): Float32Array[] | null
|