mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-24 09:23:27 +00:00
Load profile includes so synced Bambu Lab presets get their G-code back
This commit is contained in:
+17
-13
@@ -76,9 +76,10 @@ and the count of errors the original parse hit.
|
||||
|
||||
Each entry is one preset **in source form**: what its JSON sub-file states and nothing
|
||||
that resolving it derives — the preset's own config diff, the name of the preset it
|
||||
inherits, and the parse metadata (name, sub-path, description, instantiation, setting
|
||||
and filament ids, renames). Non-instantiated base presets are stored too; the children
|
||||
that inherit from them cannot resolve without them.
|
||||
inherits, the names of the presets it includes, and the parse metadata (name, sub-path,
|
||||
description, instantiation, setting and filament ids, renames). Non-instantiated base
|
||||
presets are stored too; the children that inherit from or include them cannot resolve
|
||||
without them.
|
||||
|
||||
**The payload names its own keys.** The dictionary holds the distinct `opt_key`s the
|
||||
file uses, the `ConfigOptionType` each was written as, and the distinct enum *value
|
||||
@@ -161,9 +162,9 @@ cache nothing can invalidate is worse than no cache.
|
||||
|
||||
Vendors load in a fixed order, because filament inheritance crosses exactly one
|
||||
boundary: any vendor's filament may inherit from the shared Orca filament library,
|
||||
and nothing else reaches across vendors. The library therefore goes first, alone;
|
||||
every other vendor follows in parallel, resolving against it; and the results are
|
||||
merged in a stable order:
|
||||
and nothing else reaches across vendors — an `include` is always vendor-local. The
|
||||
library therefore goes first, alone; every other vendor follows in parallel, resolving
|
||||
against it; and the results are merged in a stable order:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -211,13 +212,16 @@ and its cache was never written back.
|
||||
|
||||
Serving from a cache is not a memory-image restore. The entries are deserialized and
|
||||
then installed one by one — inheritance resolved against the presets installed before
|
||||
them and the currently loaded filament library, configs flattened onto the collection
|
||||
defaults, validated and registered — by the same function the JSON path calls straight
|
||||
after parsing a sub-file. The two paths share everything below the parse, which is what
|
||||
makes a cache-loaded bundle indistinguishable from a JSON-loaded one by construction
|
||||
rather than by test coverage. Installation also rebuilds each preset's file path from
|
||||
the local data directory, so a shipped cache never carries the generating machine's
|
||||
paths.
|
||||
them and the currently loaded filament library, includes layered in, configs flattened
|
||||
onto the collection defaults, validated and registered — by the same function the JSON
|
||||
path calls straight after parsing a sub-file. An `include` layers what the included
|
||||
base states, between the parent and the preset's own keys: the base's diff against the
|
||||
default, taken when the base itself was installed and before the per-variant padding
|
||||
`inherits` sees, so only what a template sets reaches the presets including it. The two
|
||||
paths share everything below the parse, which is what makes a cache-loaded bundle
|
||||
indistinguishable from a JSON-loaded one by construction rather than by test coverage.
|
||||
Installation also rebuilds each preset's file path from the local data directory, so a
|
||||
shipped cache never carries the generating machine's paths.
|
||||
|
||||
App upgrades work because a cache normally survives one. Only a deliberate
|
||||
`CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at
|
||||
|
||||
+253
-253
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Bambulab",
|
||||
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
||||
"version": "02.08.00.09",
|
||||
"version": "02.08.00.10",
|
||||
"force_update": "0",
|
||||
"description": "the initial version of BBL configurations",
|
||||
"machine_model_list": [
|
||||
@@ -67,6 +67,22 @@
|
||||
"name": "fdm_filament_common",
|
||||
"sub_path": "filament/fdm_filament_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_bowden",
|
||||
"sub_path": "filament/fdm_filament_template_direct_bowden.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_bowden_e3d",
|
||||
"sub_path": "filament/fdm_filament_template_direct_bowden_e3d.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_dual",
|
||||
"sub_path": "filament/fdm_filament_template_direct_dual.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_dual_e3d",
|
||||
"sub_path": "filament/fdm_filament_template_direct_dual_e3d.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_abs",
|
||||
"sub_path": "filament/fdm_filament_abs.json"
|
||||
@@ -8246,261 +8262,9 @@
|
||||
{
|
||||
"name": "Generic TPU @BBL H2S",
|
||||
"sub_path": "filament/Generic TPU @BBL H2S.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_bowden",
|
||||
"sub_path": "filament/fdm_filament_template_direct_bowden.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_bowden_e3d",
|
||||
"sub_path": "filament/fdm_filament_template_direct_bowden_e3d.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_dual",
|
||||
"sub_path": "filament/fdm_filament_template_direct_dual.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_template_direct_dual_e3d",
|
||||
"sub_path": "filament/fdm_filament_template_direct_dual_e3d.json"
|
||||
}
|
||||
],
|
||||
"machine_list": [
|
||||
{
|
||||
"name": "fdm_machine_common",
|
||||
"sub_path": "machine/fdm_machine_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_bbl_3dp_001_common",
|
||||
"sub_path": "machine/fdm_bbl_3dp_001_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_bbl_3dp_002_common",
|
||||
"sub_path": "machine/fdm_bbl_3dp_002_common.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.4 nozzle template change_filament_gcode",
|
||||
"sub_path": "machine/Bambu Lab A1 0.4 nozzle template change_filament_gcode.json"
|
||||
@@ -8856,6 +8620,242 @@
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.4 nozzle template time_lapse_gcode",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.4 nozzle template time_lapse_gcode.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_machine_common",
|
||||
"sub_path": "machine/fdm_machine_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_bbl_3dp_001_common",
|
||||
"sub_path": "machine/fdm_bbl_3dp_001_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_bbl_3dp_002_common",
|
||||
"sub_path": "machine/fdm_bbl_3dp_002_common.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.4 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A1 mini 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A1 mini 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab A2L 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab A2L 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2S 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2S 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1P 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1P 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P1S 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P1S 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab P2S 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab P2S 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1 Carbon 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1 Carbon 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X1E 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X1E 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2C 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2C 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab H2D Pro 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab H2D Pro 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.2 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.6 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu Lab X2D 0.8 nozzle",
|
||||
"sub_path": "machine/Bambu Lab X2D 0.8 nozzle.json"
|
||||
}
|
||||
],
|
||||
"process_list": [
|
||||
|
||||
@@ -437,6 +437,7 @@ def resolve_filament_field(name, field, filaments, ofl_filaments, seen=None, in_
|
||||
the same hop semantics as resolve_filament_id: own value, else walk
|
||||
`inherits` in the vendor map with OFL base-bundle fallback. Values are list
|
||||
options — the first element counts; "" when the chain never defines one.
|
||||
Templates pulled in by `include` are not consulted: none states either field.
|
||||
"""
|
||||
if seen is None:
|
||||
seen = set()
|
||||
@@ -887,8 +888,8 @@ def _vendor_json_files(vendor_path):
|
||||
def check_preset_name_uniqueness(profiles_dir, vendor):
|
||||
"""No two profiles in a bundle may share a type and a name, indexed or not.
|
||||
|
||||
The loader resolves "inherits" through a per-type map of the bundle's profiles
|
||||
(PresetBundle.cpp load_subfiles), and std::map::emplace keeps the first
|
||||
The loader resolves "inherits" and "include" through per-type maps of the bundle's
|
||||
profiles (PresetBundle.cpp load_subfiles), and std::map::emplace keeps the first
|
||||
insertion: a second file claiming the name is silently dropped, and which one
|
||||
wins is nothing but index order. An unindexed twin counts too - it is one
|
||||
sub_path edit away from deciding that silently.
|
||||
@@ -1287,11 +1288,11 @@ def check_normalized(profiles_dir, vendor):
|
||||
|
||||
Those two commands define a profile file's canonical shape - identifying keys
|
||||
first, keys the slicer no longer reads gone, filament options that are vectors
|
||||
written as vectors - and a <vendor>.json's canonical lists, ordered parents-first
|
||||
so the loader resolves every "inherits" in one pass. Running them over a
|
||||
contributed bundle has to be a no-op; where it would not be, the file that was
|
||||
reviewed is not the file that ships, and the next maintainer to run normalize
|
||||
carries an unrelated diff into their own change.
|
||||
written as vectors - and a <vendor>.json's canonical lists, ordered
|
||||
dependencies-first so the loader resolves every "inherits" and "include" in one
|
||||
pass. Running them over a contributed bundle has to be a no-op; where it would
|
||||
not be, the file that was reviewed is not the file that ships, and the next
|
||||
maintainer to run normalize carries an unrelated diff into their own change.
|
||||
|
||||
It asks the normalize and update-index sections below rather than restating what
|
||||
they do, because a second definition of normal is free to drift from the one that
|
||||
@@ -2034,9 +2035,9 @@ def trim_profiles(profiles_dir=PROFILES_DIR, vendors=None, profile_types=None,
|
||||
cli_config.json carry no "type" - all of them stay. A file that cannot be parsed
|
||||
is reported and kept: never delete what could not be read.
|
||||
|
||||
An unindexed file that some surviving profile names in "inherits" is kept too,
|
||||
and reported, UNLESS an indexed profile already carries that name: "inherits" is
|
||||
resolved by preset name, so the indexed one is the parent every child actually
|
||||
An unindexed file that some surviving profile names in "inherits" or "include" is
|
||||
kept too, and reported, UNLESS an indexed profile already carries that name: both
|
||||
are resolved by preset name, so the indexed one is the parent every child actually
|
||||
gets, and the unindexed file is a stale copy the loader never reaches. Where no
|
||||
indexed profile provides the name the inheriting preset really is broken, and
|
||||
deleting the file would destroy the only record of the settings it was written
|
||||
@@ -2069,7 +2070,7 @@ def trim_profiles(profiles_dir=PROFILES_DIR, vendors=None, profile_types=None,
|
||||
listed.add(posixpath.normpath(sub_path.replace("\\", "/")))
|
||||
|
||||
candidates = {} # path -> profile, for every unindexed preset
|
||||
inherited = set() # every name the files that stay claim as a parent
|
||||
inherited = set() # every name the files that stay claim as a parent or include
|
||||
provided = {} # name -> sub_path, for the profiles the loader can see
|
||||
for sub in subs:
|
||||
for path in _walk_json(os.path.join(vendor_dir, sub)):
|
||||
@@ -2084,8 +2085,7 @@ def trim_profiles(profiles_dir=PROFILES_DIR, vendors=None, profile_types=None,
|
||||
if not isinstance(profile, dict):
|
||||
continue
|
||||
if sub_path in listed or profile.get("type") not in PROFILE_TYPES:
|
||||
if profile.get("inherits"):
|
||||
inherited.add(profile["inherits"])
|
||||
inherited.update(profile_dependencies(profile))
|
||||
if sub_path in listed and profile.get("name"):
|
||||
provided[profile["name"]] = sub_path
|
||||
continue
|
||||
@@ -2104,8 +2104,7 @@ def trim_profiles(profiles_dir=PROFILES_DIR, vendors=None, profile_types=None,
|
||||
for path, profile in rescued.items():
|
||||
del candidates[path]
|
||||
kept[path] = profile
|
||||
if profile.get("inherits"):
|
||||
inherited.add(profile["inherits"])
|
||||
inherited.update(profile_dependencies(profile))
|
||||
|
||||
for path in sorted(kept):
|
||||
print_warning(f"{_rel(path, profiles_dir)}: not indexed by {vendor}.json but "
|
||||
@@ -2142,40 +2141,48 @@ def trim_profiles(profiles_dir=PROFILES_DIR, vendors=None, profile_types=None,
|
||||
# update-index
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def topological_sort(profiles):
|
||||
"""Order index entries parents-first, so the loader resolves inherits in one pass.
|
||||
def profile_dependencies(profile):
|
||||
"""The names a profile needs loaded before it: its parent and every include."""
|
||||
include = profile.get("include") or []
|
||||
if isinstance(include, str):
|
||||
include = [include]
|
||||
return [name for name in [profile.get("inherits"), *include] if name]
|
||||
|
||||
Entries whose parent is not in the same section keep their own (sorted) order at
|
||||
the end; the loader finds those parents through the base bundle instead.
|
||||
|
||||
def topological_sort(profiles):
|
||||
"""Order index entries dependencies-first, so the loader resolves every
|
||||
"inherits" and "include" in one pass.
|
||||
|
||||
Entries that neither depend on nor are depended on by another in the same section
|
||||
go at the end in name order; the loader finds their parents, if any, through the
|
||||
base bundle instead. Every entry on a dependency cycle, which no order can satisfy,
|
||||
goes there too.
|
||||
"""
|
||||
graph = defaultdict(list)
|
||||
in_degree = defaultdict(int)
|
||||
by_name = {p["name"]: p for p in profiles}
|
||||
all_names = set(by_name)
|
||||
|
||||
placed = set()
|
||||
for profile in profiles:
|
||||
parent = profile.get("inherits")
|
||||
child = profile["name"]
|
||||
if parent in all_names:
|
||||
graph[parent].append(child)
|
||||
in_degree[child] += 1
|
||||
in_degree.setdefault(parent, 0)
|
||||
placed.add(child)
|
||||
placed.add(parent)
|
||||
for parent in profile_dependencies(profile):
|
||||
if parent in all_names:
|
||||
graph[parent].append(child)
|
||||
in_degree[child] += 1
|
||||
in_degree.setdefault(parent, 0)
|
||||
|
||||
queue = sorted(name for name, degree in in_degree.items() if degree == 0)
|
||||
result = []
|
||||
while queue:
|
||||
current = queue.pop(0)
|
||||
result.append(by_name[current])
|
||||
placed.add(current)
|
||||
for child in sorted(graph[current]):
|
||||
in_degree[child] -= 1
|
||||
if in_degree[child] == 0:
|
||||
queue.append(child)
|
||||
|
||||
result.extend(by_name[name] for name in sorted(all_names - placed))
|
||||
ordered = {p["name"] for p in result}
|
||||
result.extend(by_name[name] for name in sorted(all_names - ordered))
|
||||
return result
|
||||
|
||||
|
||||
@@ -2225,15 +2232,15 @@ def build_index_sections(profiles_dir, vendor, profile_types=None):
|
||||
"name": name,
|
||||
"sub_path": os.path.relpath(path, vendor_dir).replace(os.sep, "/"),
|
||||
}
|
||||
if profile.get("inherits"):
|
||||
entry["inherits"] = profile["inherits"]
|
||||
for key in ("inherits", "include"):
|
||||
if profile.get(key):
|
||||
entry[key] = profile[key]
|
||||
by_name[name].append(entry["sub_path"])
|
||||
entries.append(entry)
|
||||
|
||||
sorted_entries = topological_sort(entries)
|
||||
for entry in sorted_entries:
|
||||
entry.pop("inherits", None) # ordering input only, not part of the index
|
||||
sections[profile_type + "_list"] = sorted_entries
|
||||
# inherits/include were ordering input only; the index holds name and sub_path
|
||||
sections[profile_type + "_list"] = [{"name": e["name"], "sub_path": e["sub_path"]}
|
||||
for e in topological_sort(entries)]
|
||||
|
||||
for rel, found in sorted(unplaceable.items()):
|
||||
problems.append(f'{rel}: type {found!r} is not one of {list(PROFILE_TYPES)}, so it '
|
||||
@@ -2418,7 +2425,8 @@ def build_parser():
|
||||
add("update-index", [vendor_opt, type_opt, dry_run_opt, profiles_opt],
|
||||
"regenerate the *_list sections of <vendor>.json",
|
||||
"Rebuild the *_list sections of each <vendor>.json from the files on disk,\n"
|
||||
"ordered parents-first so the loader resolves inherits in one pass.\n"
|
||||
"ordered dependencies-first so the loader resolves inherits and include\n"
|
||||
"in one pass.\n"
|
||||
"\n"
|
||||
"A profile is indexed under the section its own \"type\" names, so run\n"
|
||||
"normalize first: it is what writes a missing type. Two files claiming one\n"
|
||||
|
||||
@@ -411,6 +411,26 @@ class TestUpdateIndex(TreeCase):
|
||||
for entry in self.t.read_index("V")["filament_list"]:
|
||||
self.assertEqual(sorted(entry), ["name", "sub_path"])
|
||||
|
||||
def test_include_targets_are_listed_before_their_users(self):
|
||||
# The loader resolves include like inherits: in one pass over the list, so
|
||||
# a template must be listed before every preset that includes it - even
|
||||
# though a template has no parent of its own to order it by.
|
||||
self.t.write("V", "machine/P.json", {"type": "machine", "name": "P",
|
||||
"include": ["T start", "T end"]})
|
||||
self.t.write("V", "machine/T start.json", {"type": "machine", "name": "T start"})
|
||||
self.t.write("V", "machine/T end.json", {"type": "machine", "name": "T end"})
|
||||
self.t.write("V", "filament/F.json", {"type": "filament", "name": "F",
|
||||
"inherits": "B", "include": "S"})
|
||||
self.t.write("V", "filament/B.json", {"type": "filament", "name": "B"})
|
||||
self.t.write("V", "filament/S.json", {"type": "filament", "name": "S"})
|
||||
rc, out = self.run_command("update-index")
|
||||
self.assertEqual(rc, 0, out)
|
||||
machines = [e["name"] for e in self.t.read_index("V")["machine_list"]]
|
||||
self.assertEqual(machines, ["T end", "T start", "P"])
|
||||
filaments = [e["name"] for e in self.t.read_index("V")["filament_list"]]
|
||||
self.assertLess(filaments.index("B"), filaments.index("F"))
|
||||
self.assertLess(filaments.index("S"), filaments.index("F"))
|
||||
|
||||
def test_a_profile_with_no_usable_type_is_reported_not_dropped(self):
|
||||
self.t.write("V", "filament/A.json", {"type": "filament", "name": "A"})
|
||||
self.t.write("V", "filament/B.json", {"name": "B"})
|
||||
|
||||
@@ -28,6 +28,12 @@ if (ORCA_TOOLS)
|
||||
target_link_libraries(generate_system_cache libslic3r boost_headeronly)
|
||||
target_compile_definitions(generate_system_cache PRIVATE ${_DEV_DEFS})
|
||||
|
||||
# profile_include_dump: prints what included templates contribute to a vendor's presets,
|
||||
# to diff against the same tool built in BambuStudio. Built only on request.
|
||||
add_executable(profile_include_dump EXCLUDE_FROM_ALL profile_include_dump.cpp)
|
||||
target_link_libraries(profile_include_dump libslic3r boost_headeronly)
|
||||
target_compile_definitions(profile_include_dump PRIVATE ${_DEV_DEFS})
|
||||
|
||||
endif()
|
||||
|
||||
# Function that adds source file encoding check to a target
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
// Prints what the profile `include` key contributes to one vendor's system
|
||||
// presets, so that the dump from OrcaSlicer and the one from BambuStudio can be
|
||||
// diffed: the two copies of this file differ only in how they load the vendor.
|
||||
// For every preset whose values pass through an included template, from its
|
||||
// own file or an ancestor's, it prints the templates those files name, in the
|
||||
// order they apply, then the loaded value of each key the templates set. Every
|
||||
// line starts with the preset, so a plain diff names the preset of each
|
||||
// difference.
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace fs = boost::filesystem;
|
||||
using nlohmann::json;
|
||||
|
||||
namespace {
|
||||
|
||||
// What a sub-file states about where its values come from, and what it sets.
|
||||
struct Entry
|
||||
{
|
||||
std::string inherits;
|
||||
std::vector<std::string> includes;
|
||||
std::vector<std::string> keys;
|
||||
};
|
||||
|
||||
// Sub-files by name, per section of the vendor index: `inherits` and `include`
|
||||
// both resolve within a section.
|
||||
using Sections = std::map<std::string, std::map<std::string, Entry>>;
|
||||
|
||||
json read_json(const fs::path &path)
|
||||
{
|
||||
boost::nowide::ifstream in(path.string());
|
||||
if (!in)
|
||||
throw std::runtime_error("Cannot read " + path.string());
|
||||
return json::parse(in);
|
||||
}
|
||||
|
||||
Sections read_vendor(const fs::path &dir, const std::string &vendor)
|
||||
{
|
||||
// Keys that name and place a sub-file rather than set a value.
|
||||
static const std::set<std::string> metadata = {"name", "type", "from", "instantiation", "inherits", "include"};
|
||||
Sections sections;
|
||||
const json index = read_json(dir / (vendor + ".json"));
|
||||
for (const char *section : {"process_list", "filament_list", "machine_list"}) {
|
||||
const auto list = index.find(section);
|
||||
if (list == index.end())
|
||||
continue;
|
||||
for (const json &item : *list) {
|
||||
const json file = read_json(dir / vendor / item.at("sub_path").get<std::string>());
|
||||
// A file without a name goes by its name in the index.
|
||||
Entry &entry = sections[section][file.value("name", item.at("name").get<std::string>())];
|
||||
entry.inherits = file.value("inherits", "");
|
||||
if (const auto include = file.find("include"); include != file.end()) {
|
||||
if (include->is_string())
|
||||
entry.includes.push_back(include->get<std::string>());
|
||||
else
|
||||
for (const json &name : *include)
|
||||
entry.includes.push_back(name.get<std::string>());
|
||||
}
|
||||
for (auto it = file.begin(); it != file.end(); ++it)
|
||||
if (metadata.count(it.key()) == 0)
|
||||
entry.keys.push_back(it.key());
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
const PresetCollection &presets_of(const PresetBundle &bundle, const std::string §ion)
|
||||
{
|
||||
if (section == "process_list")
|
||||
return bundle.prints;
|
||||
if (section == "filament_list")
|
||||
return bundle.filaments;
|
||||
return bundle.printers;
|
||||
}
|
||||
|
||||
size_t dump(const PresetBundle &bundle, const Sections §ions, std::ostream &out)
|
||||
{
|
||||
size_t dumped = 0;
|
||||
for (const auto &[section, entries] : sections) {
|
||||
std::vector<const Preset *> presets;
|
||||
for (const Preset &preset : presets_of(bundle, section).get_presets())
|
||||
if (preset.is_system)
|
||||
presets.push_back(&preset);
|
||||
std::sort(presets.begin(), presets.end(), [](const Preset *a, const Preset *b) { return a->name < b->name; });
|
||||
for (const Preset *preset : presets) {
|
||||
// The preset and its ancestors, root first: the order their includes apply in.
|
||||
std::vector<const Entry *> lineage;
|
||||
for (auto it = entries.find(preset->name); it != entries.end() && lineage.size() <= entries.size();
|
||||
it = entries.find(it->second.inherits))
|
||||
lineage.insert(lineage.begin(), &it->second);
|
||||
std::string templates;
|
||||
std::set<std::string> keys;
|
||||
for (const Entry *entry : lineage)
|
||||
for (const std::string &name : entry->includes) {
|
||||
templates += (templates.empty() ? "" : "; ") + name;
|
||||
if (const auto it = entries.find(name); it != entries.end())
|
||||
keys.insert(it->second.keys.begin(), it->second.keys.end());
|
||||
}
|
||||
if (templates.empty())
|
||||
continue;
|
||||
const std::string prefix = section.substr(0, section.find('_')) + " | " + preset->name + " | ";
|
||||
out << prefix << "include = " << templates << "\n";
|
||||
for (const std::string &key : keys)
|
||||
out << prefix << key << " = " << (preset->config.has(key) ? preset->config.opt_serialize(key) : "<absent>") << "\n";
|
||||
++dumped;
|
||||
}
|
||||
}
|
||||
return dumped;
|
||||
}
|
||||
|
||||
// Orca: load the vendor as the app does, against the filament library when the
|
||||
// directory has one: parsed from its JSON files or, with from_cache, from the
|
||||
// preset cache the app reads on every launch after the first.
|
||||
void load_vendor(PresetBundle &bundle, const std::string &dir, const std::string &vendor, bool from_cache)
|
||||
{
|
||||
const auto rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
|
||||
PresetBundle library;
|
||||
const PresetBundle *base = nullptr;
|
||||
if (fs::is_regular_file(fs::path(dir) / (std::string(PresetBundle::ORCA_FILAMENT_LIBRARY) + ".json"))) {
|
||||
library.load_vendor_configs_from_json(dir, PresetBundle::ORCA_FILAMENT_LIBRARY, PresetBundle::LoadSystem, rule, nullptr, false);
|
||||
base = &library;
|
||||
}
|
||||
if (!from_cache) {
|
||||
bundle.load_vendor_configs_from_json(dir, vendor, PresetBundle::LoadSystem, rule, base, false);
|
||||
return;
|
||||
}
|
||||
// Parse once to write <vendor>.opc beside the profile, then load that
|
||||
// cache into a clean bundle. Any cache already there goes first, so that a
|
||||
// failed write cannot leave it to be loaded; one that was not there before
|
||||
// goes again afterwards.
|
||||
const fs::path cache = fs::path(dir) / (vendor + ".opc");
|
||||
const bool had_cache = fs::exists(cache);
|
||||
fs::remove(cache);
|
||||
PresetBundle parsed;
|
||||
parsed.set_is_validation_mode(true); // parse the JSON: validation never serves a cache
|
||||
parsed.set_generate_vendor_caches(true);
|
||||
parsed.load_vendor_configs_from_json(dir, vendor, PresetBundle::LoadSystem, rule, base);
|
||||
const bool loaded = bundle.load_vendor_cache(cache.string(), vendor, parsed.vendors.at(vendor).config_version, base);
|
||||
if (!had_cache)
|
||||
fs::remove(cache);
|
||||
if (!loaded)
|
||||
throw std::runtime_error("The preset cache " + cache.string() + " was not written or was rejected");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: profile_include_dump -p <profiles dir> -o <dump file> [-v <vendor>] [-l <log level>] [-c]\n"
|
||||
" -v vendor to dump, BBL if omitted\n"
|
||||
" -l log level, 0 (fatal) to 5 (trace); 1 if omitted\n"
|
||||
" -c load the presets from the vendor's preset cache, generated first, not the JSON\n";
|
||||
std::string dir, output, vendor = "BBL";
|
||||
unsigned log_level = 1;
|
||||
bool from_cache = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "-c") {
|
||||
from_cache = true;
|
||||
continue;
|
||||
}
|
||||
if (i + 1 == argc) {
|
||||
std::cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
const std::string value = argv[++i];
|
||||
if (arg == "-p")
|
||||
dir = value;
|
||||
else if (arg == "-o")
|
||||
output = value;
|
||||
else if (arg == "-v")
|
||||
vendor = value;
|
||||
else if (arg == "-l" && value.size() == 1 && value[0] >= '0' && value[0] <= '5')
|
||||
log_level = unsigned(value[0] - '0');
|
||||
else {
|
||||
std::cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (dir.empty() || output.empty() || !fs::is_directory(dir)) {
|
||||
std::cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
set_logging_level(log_level);
|
||||
|
||||
try {
|
||||
PresetBundle bundle;
|
||||
load_vendor(bundle, dir, vendor, from_cache);
|
||||
boost::nowide::ofstream out(output);
|
||||
if (!out)
|
||||
throw std::runtime_error("Cannot write " + output);
|
||||
const size_t dumped = dump(bundle, read_vendor(dir, vendor), out);
|
||||
out.close();
|
||||
if (!out)
|
||||
throw std::runtime_error("Failed writing " + output);
|
||||
std::cerr << "Dumped " << dumped << " " << vendor << " presets that include a template\n";
|
||||
} catch (const std::exception &ex) {
|
||||
std::cerr << ex.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -950,6 +950,9 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
|
||||
}
|
||||
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INHERITS, it.value());
|
||||
}
|
||||
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump());
|
||||
} else if (boost::iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
|
||||
key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value());
|
||||
} else {
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#define ORCA_JSON_KEY_UPDATE_TIME "updated_time"
|
||||
#define ORCA_JSON_KEY_CREATED_TIME "created_time"
|
||||
#define BBL_JSON_KEY_INHERITS "inherits"
|
||||
#define BBL_JSON_KEY_INCLUDES "include"
|
||||
#define BBL_JSON_KEY_INSTANTIATION "instantiation"
|
||||
#define BBL_JSON_KEY_NOZZLE_DIAMETER "nozzle_diameter"
|
||||
#define BBL_JSON_KEY_PRINTER_TECH "machine_tech"
|
||||
|
||||
@@ -6598,7 +6598,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
// now, or deserialized from the vendor's cache; the code is shared so a
|
||||
// cache-loaded bundle cannot come out different from a JSON-loaded one.
|
||||
// Resolves `inherits` against the presets loaded before this one
|
||||
// (config_maps) or against base_bundle's filament library, flattens, validates
|
||||
// (config_maps) or against base_bundle's filament library, layers each
|
||||
// `include` (include_maps) under the preset's own keys, flattens, validates
|
||||
// and registers the preset. Returns the reason loading failed, empty on
|
||||
// success.
|
||||
std::string PresetBundle::load_vendor_preset(
|
||||
@@ -6607,9 +6608,10 @@ std::string PresetBundle::load_vendor_preset(
|
||||
const PresetBundle* base_bundle,
|
||||
LoadConfigBundleAttributes flags,
|
||||
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, std::string>& filament_id_maps,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
|
||||
std::map<std::string, std::string>& filament_id_maps,
|
||||
PresetCollection* presets_collection, size_t& count, bool is_from_lib,
|
||||
const std::set<std::string>* retain_configs)
|
||||
const std::set<std::string>* retain_configs, const std::set<std::string>* retain_includes)
|
||||
{
|
||||
const VendorProfile* current_vendor_profile = &this->vendors.at(vendor_name);
|
||||
const std::string subfile = path + "/" + vendor_name + "/" + entry.sub_path;
|
||||
@@ -6652,14 +6654,30 @@ std::string PresetBundle::load_vendor_preset(
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (presets_collection->type() == Preset::TYPE_PRINTER)
|
||||
default_config = &presets_collection->default_preset_for(entry.config_src).config;
|
||||
else
|
||||
default_config = &presets_collection->default_preset().config;
|
||||
}
|
||||
else
|
||||
default_config = &presets_collection->default_preset_for(entry.config_src).config;
|
||||
config = *default_config;
|
||||
// Layer each included preset's own keys over the parent, in the order listed;
|
||||
// this preset's own keys go on top.
|
||||
for (const std::string& name : entry.includes) {
|
||||
auto it = include_maps.find(name);
|
||||
if (it == include_maps.end()) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not find include " << name << " for " << preset_name;
|
||||
continue;
|
||||
}
|
||||
config.apply(it->second);
|
||||
}
|
||||
config.apply(entry.config_src);
|
||||
// Record what a base states, its diff against the default, for the presets
|
||||
// that include it. It is taken before extend_default_config_length pads every
|
||||
// per-variant key to the base's variant count: the padded defaults would
|
||||
// otherwise override the values each includer inherits.
|
||||
if (entry.instantiation == "false" && (retain_includes == nullptr || retain_includes->count(preset_name) != 0)) {
|
||||
DynamicPrintConfig included;
|
||||
included.apply_only(config, config.diff(presets_collection->default_preset_for(config).config));
|
||||
include_maps.emplace(preset_name, std::move(included));
|
||||
}
|
||||
extend_default_config_length(config, true, *default_config);
|
||||
if (entry.instantiation == "false" && "Template" != vendor_name) {
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
@@ -7118,6 +7136,15 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
}
|
||||
entry.name = key_values[BBL_JSON_KEY_NAME];
|
||||
entry.description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
||||
// A file that states no instantiation and is named as G-code, or has no
|
||||
// name, is a template that is only there to be included. A nameless one
|
||||
// goes by its name in the vendor index.
|
||||
if (auto it = key_values.find(BBL_JSON_KEY_INSTANTIATION);
|
||||
(it == key_values.end() || it->second.empty()) && (entry.name.empty() || entry.name.find("gcode") != std::string::npos)) {
|
||||
key_values[BBL_JSON_KEY_INSTANTIATION] = "false";
|
||||
if (entry.name.empty())
|
||||
entry.name = subfile_iter.first;
|
||||
}
|
||||
if(key_values.find(BBL_JSON_KEY_INSTANTIATION) == key_values.end())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Missing instantiation attribute for " << entry.name;
|
||||
@@ -7146,6 +7173,20 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
if (auto it = key_values.find(BBL_JSON_KEY_INCLUDES); it != key_values.end()) {
|
||||
// An array of names, or one bare name; load_from_json kept the JSON text.
|
||||
nlohmann::json includes = nlohmann::json::parse(it->second);
|
||||
if (!includes.is_array())
|
||||
includes = nlohmann::json::array({std::move(includes)});
|
||||
for (const auto& name : includes) {
|
||||
if (name.is_string())
|
||||
entry.includes.push_back(name.get<std::string>());
|
||||
else {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid include " << name.dump() << " for " << entry.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key_values.find(ORCA_JSON_KEY_RENAMED_FROM) != key_values.end()) {
|
||||
if (!unescape_strings_cstyle(key_values[ORCA_JSON_KEY_RENAMED_FROM], entry.renamed_from)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Config \"" << dir << "\": The preset \"" << entry.name
|
||||
@@ -7162,7 +7203,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
return reason;
|
||||
};
|
||||
|
||||
std::map<std::string, DynamicPrintConfig> configs;
|
||||
std::map<std::string, DynamicPrintConfig> configs, include_maps;
|
||||
std::map<std::string, std::string> filament_id_maps;
|
||||
// Orca: whether to (re)write the vendor's cache after this parse, leaving it
|
||||
// in step with the profile so the next run reads it instead. It is written
|
||||
@@ -7179,6 +7220,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
auto load_subfiles = [&](std::vector<std::pair<std::string, std::string>>& subfiles,
|
||||
std::vector<CachedPreset>& entries, const char* kind, bool is_from_lib = false) {
|
||||
configs.clear();
|
||||
include_maps.clear();
|
||||
filament_id_maps.clear();
|
||||
for (auto& subfile : subfiles) {
|
||||
CachedPreset entry;
|
||||
@@ -7186,8 +7228,8 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
if (reason.empty()) {
|
||||
const int errors_before_install = m_errors;
|
||||
reason = load_vendor_preset(entry, dir, vendor_name, base_bundle, flags,
|
||||
substitution_context, substitutions, configs, filament_id_maps, presets,
|
||||
presets_loaded, is_from_lib);
|
||||
substitution_context, substitutions, configs, include_maps, filament_id_maps,
|
||||
presets, presets_loaded, is_from_lib);
|
||||
install_errors += m_errors - errors_before_install;
|
||||
}
|
||||
if (!reason.empty()) {
|
||||
@@ -7982,26 +8024,29 @@ bool PresetBundle::load_vendor_cache(const std::string& cache_path, const std::s
|
||||
// parsed), so no substitutions are reported, as before.
|
||||
ConfigSubstitutionContext substitution_context { ForwardCompatibilitySubstitutionRule::EnableSilent };
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::map<std::string, DynamicPrintConfig> configs;
|
||||
std::map<std::string, DynamicPrintConfig> configs, include_maps;
|
||||
std::map<std::string, std::string> filament_id_maps;
|
||||
const std::string path = boost::filesystem::path(cache_path).parent_path().string();
|
||||
size_t count = 0;
|
||||
auto install_entries = [&](const std::vector<CachedPreset>& entries, PresetCollection* presets, bool is_from_lib) {
|
||||
configs.clear();
|
||||
include_maps.clear();
|
||||
filament_id_maps.clear();
|
||||
// Only configs of presets that other entries inherit are ever looked
|
||||
// up again; registering just those skips one full config copy for
|
||||
// every leaf preset. The library's filaments are all retained — they
|
||||
// become the m_config_maps other vendors resolve against.
|
||||
std::set<std::string> inherited;
|
||||
for (const CachedPreset& entry : entries)
|
||||
// Only configs of presets that other entries inherit or include are
|
||||
// ever looked up again; registering just those skips one full config
|
||||
// copy for every leaf preset. The library's filaments are all retained
|
||||
// — they become the m_config_maps other vendors resolve against.
|
||||
std::set<std::string> inherited, included;
|
||||
for (const CachedPreset& entry : entries) {
|
||||
if (! entry.inherits.empty())
|
||||
inherited.insert(entry.inherits);
|
||||
included.insert(entry.includes.begin(), entry.includes.end());
|
||||
}
|
||||
const std::set<std::string>* retain_configs = is_from_lib ? nullptr : &inherited;
|
||||
for (const CachedPreset& entry : entries) {
|
||||
const std::string reason = load_vendor_preset(entry, path, vendor_name,
|
||||
base_bundle, LoadConfigBundleAttribute::LoadSystem, substitution_context, substitutions,
|
||||
configs, filament_id_maps, presets, count, is_from_lib, retain_configs);
|
||||
configs, include_maps, filament_id_maps, presets, count, is_from_lib, retain_configs, &included);
|
||||
if (! reason.empty())
|
||||
throw std::runtime_error("entry " + entry.name + " failed to install: " + reason);
|
||||
}
|
||||
|
||||
@@ -634,22 +634,24 @@ private:
|
||||
// load_vendor_configs_from_json reads a cache.
|
||||
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const PresetBundle* base_bundle);
|
||||
|
||||
// Load one source-form preset entry into this bundle: resolve `inherits`,
|
||||
// flatten, validate and register the preset. Returns the reason loading
|
||||
// failed, empty on success. See the definition for the sharing contract
|
||||
// between the JSON parse and the cache load.
|
||||
// retain_configs, when non-null, names the only presets registered into
|
||||
// config_maps (a full config copy each). The cache load passes the names its
|
||||
// entries inherit — the only ones ever looked up again; the JSON parse
|
||||
// retains all, not knowing what later subfiles inherit.
|
||||
// Load one source-form preset entry into this bundle: resolve `inherits`
|
||||
// and `include`, flatten, validate and register the preset. Returns the
|
||||
// reason loading failed, empty on success. See the definition for the
|
||||
// sharing contract between the JSON parse and the cache load.
|
||||
// retain_configs / retain_includes, when non-null, name the only presets
|
||||
// registered into config_maps / include_maps (a config copy each). The
|
||||
// cache load passes the names its entries inherit / include — the only
|
||||
// ones ever looked up again; the JSON parse retains all, not knowing what
|
||||
// later subfiles name.
|
||||
std::string load_vendor_preset(const CachedPreset& entry,
|
||||
const std::string& path, const std::string& vendor_name,
|
||||
const PresetBundle* base_bundle,
|
||||
LoadConfigBundleAttributes flags,
|
||||
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, std::string>& filament_id_maps,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
|
||||
std::map<std::string, std::string>& filament_id_maps,
|
||||
PresetCollection* presets_collection, size_t& count, bool is_from_lib,
|
||||
const std::set<std::string>* retain_configs = nullptr);
|
||||
const std::set<std::string>* retain_configs = nullptr, const std::set<std::string>* retain_includes = nullptr);
|
||||
|
||||
// Clear every collection's m_printer_hold_alias, which reset() leaves alone.
|
||||
void clear_printer_hold_aliases();
|
||||
|
||||
@@ -254,7 +254,7 @@ constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ"
|
||||
// save_entries below), or a change to the cache's own layout or the
|
||||
// meaning of its stamps. Option-schema drift is NOT such a change — the
|
||||
// dictionary handles it, which is why this no longer moves every release.
|
||||
constexpr uint32_t CACHE_VERSION = 1;
|
||||
constexpr uint32_t CACHE_VERSION = 2;
|
||||
|
||||
// A stamp-string read that refuses an absurd length before allocating anything.
|
||||
// The stamps are read from files named from the outside (peek_version is
|
||||
@@ -325,7 +325,7 @@ void visit_entry(Archive& ar, Entry& e, ConfigFn&& config)
|
||||
{
|
||||
ar(e.name, e.sub_path);
|
||||
config();
|
||||
ar(e.inherits, e.description, e.instantiation, e.setting_id, e.filament_id, e.renamed_from);
|
||||
ar(e.inherits, e.includes, e.description, e.instantiation, e.setting_id, e.filament_id, e.renamed_from);
|
||||
}
|
||||
|
||||
// The count comes from a file that has already passed magic and CRC, but a
|
||||
|
||||
@@ -107,12 +107,13 @@ void load_config(cereal::BinaryInputArchive& ar, DynamicPrintConfig& config, con
|
||||
// comes after.
|
||||
void skip_config(cereal::BinaryInputArchive& ar, const CacheDictionary& dict);
|
||||
|
||||
// One preset as its JSON subfile states it: the config diff, the name of the
|
||||
// preset it inherits, and the parse metadata — everything the parse phase of
|
||||
// load_vendor_configs_from_json extracts and nothing it derives. Inheritance
|
||||
// is resolved when the entry is installed, against whatever filament library
|
||||
// is loaded then, so a cache carries no other vendor's values and no other
|
||||
// vendor's update can make it stale.
|
||||
// One preset as its JSON subfile states it: the config diff, the names of the
|
||||
// preset it inherits and the presets it includes, and the parse metadata —
|
||||
// everything the parse phase of load_vendor_configs_from_json extracts and
|
||||
// nothing it derives. Inheritance and includes are resolved when the entry is
|
||||
// installed, against whatever filament library is loaded then, so a cache
|
||||
// carries no other vendor's values and no other vendor's update can make it
|
||||
// stale.
|
||||
// Written and read by visit_entry in PresetCacheFormat.cpp, which lists every
|
||||
// field below in this order — once, for the save, the load and the name peek alike.
|
||||
struct CachedPreset
|
||||
@@ -121,6 +122,7 @@ struct CachedPreset
|
||||
std::string sub_path; // path under the vendor's directory
|
||||
DynamicPrintConfig config_src; // the preset's own diff, nothing inherited
|
||||
std::string inherits;
|
||||
std::vector<std::string> includes; // layered under config_src, in this order
|
||||
std::string description;
|
||||
std::string instantiation; // "true"/"false" as stated; anything else was already counted as a parse error
|
||||
std::string setting_id;
|
||||
|
||||
@@ -490,6 +490,24 @@ TEST_CASE("save_to_json round-trips plugin capability references as strings", "[
|
||||
CHECK(reloaded.option<ConfigOptionStrings>("slicing_pipeline_plugin")->values == refs);
|
||||
}
|
||||
|
||||
TEST_CASE("load_from_json hands a preset's include list to the caller instead of the config", "[Config]") {
|
||||
ScopedTemporaryFile tmp(".json");
|
||||
{
|
||||
boost::nowide::ofstream ofs(tmp.string());
|
||||
ofs << R"({"type":"machine","name":"P","instantiation":"true","include":["T start","T end"],"machine_end_gcode":"M84"})";
|
||||
}
|
||||
DynamicPrintConfig config;
|
||||
ConfigSubstitutionContext substitutions(ForwardCompatibilitySubstitutionRule::Disable);
|
||||
std::map<std::string, std::string> key_values;
|
||||
std::string reason;
|
||||
REQUIRE(config.load_from_json(tmp.string(), substitutions, false, key_values, reason) == 0);
|
||||
CHECK(reason.empty());
|
||||
CHECK(key_values["include"] == R"(["T start","T end"])");
|
||||
CHECK_FALSE(config.has("include"));
|
||||
CHECK(substitutions.unrecogized_keys.empty());
|
||||
CHECK(config.opt_string("machine_end_gcode") == "M84");
|
||||
}
|
||||
|
||||
TEST_CASE("save_to_json writes the same document to a stream as to a file", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("layer_height", new ConfigOptionFloat(0.2));
|
||||
|
||||
@@ -53,41 +53,64 @@ void write_vendor_tree(const fs::path& dir, const std::string& vendor, const std
|
||||
}
|
||||
|
||||
// A small but complete vendor: one machine model, one process, a non-instantiated
|
||||
// base filament with an instantiated child inheriting it, a second standalone
|
||||
// filament carrying explicit metadata, and one machine preset with a rename — so
|
||||
// the equivalence test below sees every CachedPreset field populated.
|
||||
void write_full_vendor_tree(const fs::path& dir, const std::string& vendor, const std::string& version)
|
||||
// base filament with an instantiated child that inherits it and includes a
|
||||
// dual-extruder template, a second standalone filament carrying explicit
|
||||
// metadata, and one machine preset with a rename that includes a G-code template
|
||||
// — so the equivalence test below sees every CachedPreset field populated. The
|
||||
// templates are listed before the presets that include them, as update-index
|
||||
// orders them, or after them when `templates_last` asks for the broken order.
|
||||
void write_full_vendor_tree(const fs::path& dir, const std::string& vendor, const std::string& version,
|
||||
bool templates_last = false)
|
||||
{
|
||||
fs::create_directories(dir / vendor / "process");
|
||||
fs::create_directories(dir / vendor / "filament");
|
||||
fs::create_directories(dir / vendor / "machine");
|
||||
const std::string filament_template = R"({"name":")" + vendor + R"( dual template","sub_path":"filament/template.json"})";
|
||||
const std::string filament_presets = R"({"name":")" + vendor + R"( Base PLA","sub_path":"filament/base.json"},)"
|
||||
R"({"name":")" + vendor + R"( PLA @0.4","sub_path":"filament/pla.json"},)"
|
||||
R"({"name":")" + vendor + R"( Silk PLA @0.4","sub_path":"filament/silk.json"})";
|
||||
const std::string machine_template = R"({"name":")" + vendor + R"( 0.4 template machine_start_gcode","sub_path":"machine/start.json"})";
|
||||
const std::string machine_presets = R"({"name":")" + vendor + R"( 0.4 nozzle","sub_path":"machine/printer.json"})";
|
||||
std::ofstream((dir / (vendor + ".json")).string())
|
||||
<< R"({"version":")" << version << R"(","name":")" << vendor << R"(",)"
|
||||
<< R"("machine_model_list":[{"name":"Test Model","sub_path":"machine/model.json"}],)"
|
||||
<< R"("process_list":[{"name":"0.20mm Standard @)" << vendor << R"(","sub_path":"process/standard.json"}],)"
|
||||
<< R"("filament_list":[)"
|
||||
<< R"({"name":")" << vendor << R"( Base PLA","sub_path":"filament/base.json"},)"
|
||||
<< R"({"name":")" << vendor << R"( PLA @0.4","sub_path":"filament/pla.json"},)"
|
||||
<< R"({"name":")" << vendor << R"( Silk PLA @0.4","sub_path":"filament/silk.json"}],)"
|
||||
<< R"("machine_list":[{"name":")" << vendor << R"( 0.4 nozzle","sub_path":"machine/printer.json"}]})";
|
||||
<< (templates_last ? filament_presets + "," + filament_template : filament_template + "," + filament_presets)
|
||||
<< R"(],"machine_list":[)"
|
||||
<< (templates_last ? machine_presets + "," + machine_template : machine_template + "," + machine_presets)
|
||||
<< "]}";
|
||||
std::ofstream((dir / vendor / "machine" / "model.json").string())
|
||||
<< R"({"type":"machine_model","name":"Test Model","nozzle_diameter":"0.4"})";
|
||||
std::ofstream((dir / vendor / "process" / "standard.json").string())
|
||||
<< R"({"type":"process","name":"0.20mm Standard @)" << vendor
|
||||
<< R"(","from":"system","instantiation":"true","layer_height":"0.2"})";
|
||||
// The base sets two per-variant keys; the template restates one of them for
|
||||
// two variants and adds a third; the child restates the third.
|
||||
std::ofstream((dir / vendor / "filament" / "base.json").string())
|
||||
<< R"({"type":"filament","name":")" << vendor
|
||||
<< R"( Base PLA","from":"system","instantiation":"false","filament_id":"GFA_base","filament_cost":"42"})";
|
||||
<< R"( Base PLA","from":"system","instantiation":"false","filament_id":"GFA_base","filament_cost":"42",)"
|
||||
<< R"("activate_air_filtration":["1"],"filament_max_volumetric_speed":["12"]})";
|
||||
std::ofstream((dir / vendor / "filament" / "template.json").string())
|
||||
<< R"({"type":"filament","name":")" << vendor << R"( dual template","from":"system","instantiation":"false",)"
|
||||
<< R"("filament_extruder_variant":["Direct Drive Standard","Direct Drive High Flow"],)"
|
||||
<< R"("filament_max_volumetric_speed":["20","22"],"filament_flush_temp":["0","0"]})";
|
||||
std::ofstream((dir / vendor / "filament" / "pla.json").string())
|
||||
<< R"({"type":"filament","name":")" << vendor
|
||||
<< R"( PLA @0.4","from":"system","instantiation":"true","filament_id":"GFA00","filament_cost":"20",)"
|
||||
<< R"("setting_id":"GFSA04","description":"Test PLA description"})";
|
||||
std::ofstream((dir / vendor / "filament" / "silk.json").string())
|
||||
<< R"({"type":"filament","name":")" << vendor
|
||||
<< R"( Silk PLA @0.4","from":"system","instantiation":"true","inherits":")" << vendor << R"( Base PLA"})";
|
||||
<< R"( Silk PLA @0.4","from":"system","instantiation":"true","inherits":")" << vendor << R"( Base PLA",)"
|
||||
<< R"("include":[")" << vendor << R"( dual template"],"filament_flush_temp":["5","5"]})";
|
||||
// The template states two G-codes; the printer restates one of them.
|
||||
std::ofstream((dir / vendor / "machine" / "start.json").string())
|
||||
<< R"({"type":"machine","name":")" << vendor << R"( 0.4 template machine_start_gcode","from":"system",)"
|
||||
<< R"("instantiation":"false","machine_start_gcode":"G28 ; template","machine_end_gcode":"M84 ; template"})";
|
||||
std::ofstream((dir / vendor / "machine" / "printer.json").string())
|
||||
<< R"({"type":"machine","name":")" << vendor
|
||||
<< R"( 0.4 nozzle","from":"system","instantiation":"true","printer_model":"Test Model","printer_variant":"0.4",)"
|
||||
<< R"("include":[")" << vendor << R"( 0.4 template machine_start_gcode"],"machine_end_gcode":"M84 ; own",)"
|
||||
<< R"("renamed_from":")" << vendor << R"( old 0.4 nozzle"})";
|
||||
}
|
||||
|
||||
@@ -598,6 +621,7 @@ TEST_CASE("a cache-loaded vendor is indistinguishable from a JSON-loaded one", "
|
||||
const Preset* pr = from_cache.printers.find_preset("Acme 0.4 nozzle", false);
|
||||
REQUIRE(pr != nullptr);
|
||||
CHECK(pr->renamed_from == std::vector<std::string>{"Acme old 0.4 nozzle"});
|
||||
CHECK(pr->config.opt_string("machine_start_gcode") == "G28 ; template"); // through the include
|
||||
}
|
||||
|
||||
TEST_CASE("a cache-served vendor reports the errors its parse counted", "[VendorCache]")
|
||||
@@ -1618,3 +1642,73 @@ TEST_CASE("a stamp string with an absurd length is rejected, not allocated", "[V
|
||||
CHECK(VendorCacheFile::peek_version(cache, "Evil").empty());
|
||||
}
|
||||
|
||||
TEST_CASE("an included template's keys land on the preset, between the parent's and its own", "[VendorCache]")
|
||||
{
|
||||
InstallDirs dirs;
|
||||
write_full_vendor_tree(dirs.system, "Acme", "1.0.0");
|
||||
PresetBundle bundle;
|
||||
bundle.load_vendor_configs_from_json(dirs.system.string(), "Acme", PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
CHECK(bundle.error_count() == 0);
|
||||
|
||||
const Preset* pr = bundle.printers.find_preset("Acme 0.4 nozzle", false);
|
||||
REQUIRE(pr != nullptr);
|
||||
CHECK(pr->config.opt_string("machine_start_gcode") == "G28 ; template"); // from the include
|
||||
CHECK(pr->config.opt_string("machine_end_gcode") == "M84 ; own"); // the preset's own key wins
|
||||
CHECK(presets_for(bundle.printers, "Acme").size() == 1); // the template is no preset
|
||||
|
||||
const Preset* silk = bundle.filaments.find_preset("Acme Silk PLA @0.4", false);
|
||||
REQUIRE(silk != nullptr);
|
||||
const auto* speed = silk->config.option<ConfigOptionFloats>("filament_max_volumetric_speed");
|
||||
REQUIRE(speed != nullptr);
|
||||
CHECK(speed->values == std::vector<double>{20., 22.}); // the include wins over the parent
|
||||
const auto* flush = silk->config.option<ConfigOptionInts>("filament_flush_temp");
|
||||
REQUIRE(flush != nullptr);
|
||||
CHECK(flush->values == std::vector<int>{5, 5}); // the preset's own key wins
|
||||
// A per-variant key the template never mentions keeps the parent's value.
|
||||
// The loader pads every base to its variant count; an include taken from the
|
||||
// padded copy would carry the padded default [0,0] over the parent's 1.
|
||||
const auto* air = silk->config.option<ConfigOptionBools>("activate_air_filtration");
|
||||
REQUIRE(air != nullptr);
|
||||
CHECK(air->values == std::vector<unsigned char>{1, 1});
|
||||
CHECK(presets_for(bundle.filaments, "Acme").size() == 2); // the template is no preset
|
||||
}
|
||||
|
||||
TEST_CASE("an include listed after the preset that names it is an error, and the preset loads without it", "[VendorCache]")
|
||||
{
|
||||
InstallDirs dirs;
|
||||
write_full_vendor_tree(dirs.system, "Acme", "1.0.0", /*templates_last=*/true);
|
||||
PresetBundle bundle;
|
||||
bundle.load_vendor_configs_from_json(dirs.system.string(), "Acme", PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
// One error per unresolved include: the printer's and the filament's.
|
||||
CHECK(bundle.error_count() == 2);
|
||||
const Preset* pr = bundle.printers.find_preset("Acme 0.4 nozzle", false);
|
||||
REQUIRE(pr != nullptr);
|
||||
CHECK(pr->config.opt_string("machine_start_gcode") != "G28 ; template");
|
||||
const Preset* silk = bundle.filaments.find_preset("Acme Silk PLA @0.4", false);
|
||||
REQUIRE(silk != nullptr);
|
||||
CHECK(silk->config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>{12.});
|
||||
}
|
||||
|
||||
TEST_CASE("a G-code template that states no instantiation is included, not loaded as a preset", "[VendorCache]")
|
||||
{
|
||||
InstallDirs dirs;
|
||||
write_full_vendor_tree(dirs.system, "Acme", "1.0.0");
|
||||
const fs::path start = dirs.system / "Acme" / "machine" / "start.json";
|
||||
SECTION("named as G-code") {
|
||||
std::ofstream(start.string()) << R"({"type":"machine","name":"Acme 0.4 template machine_start_gcode","from":"system",)"
|
||||
<< R"("machine_start_gcode":"G28 ; template"})";
|
||||
}
|
||||
SECTION("not named, so included by its name in the vendor index") {
|
||||
std::ofstream(start.string()) << R"({"type":"machine","from":"system","machine_start_gcode":"G28 ; template"})";
|
||||
}
|
||||
PresetBundle bundle;
|
||||
bundle.load_vendor_configs_from_json(dirs.system.string(), "Acme", PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
CHECK(bundle.error_count() == 0);
|
||||
const Preset* pr = bundle.printers.find_preset("Acme 0.4 nozzle", false);
|
||||
REQUIRE(pr != nullptr);
|
||||
CHECK(pr->config.opt_string("machine_start_gcode") == "G28 ; template");
|
||||
CHECK(presets_for(bundle.printers, "Acme").size() == 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user