FOrcaSlicer — Flexible OrcaSlicer dev roadmap

Snapmaker U1 · OrcaSlicer fork · May 15 – Jul 27 2026 · click any card to expand
May 15, 2026 — session start
Setup
Architecture & repo decisions
Design goals, hardware constraints, repo selection
Major Decision
  • wall[0] = 0.2mm outer nozzle; wall[1+] = 0.4mm inner nozzle
  • Min 2 walls if color or material is different; 1 wall allowed otherwise
  • Slope-compensated gap between wall[0] and wall[1] only
  • Accumulator sync: 0.4 nozzle fires when next 0.2 layer pushes accumulated height above 0.4 max
  • Color-to-nozzle priority: color match → smallest nozzle = outer → extruder 0 fallback
  • Color depth: min = nozzle diameter; thin section = bleed-through or 50/50 split
  • Per-patch nozzle override: global=YES follows mixed logic; global=NO single nozzle owns patch
  • Priority hierarchy locked: CL (color patch loops) > OW (outer wall) > IW (inner wall)
  • Started on OrcaSlicer-FullSpectrum fork, later pivoted to Snapmaker Orca due to cleaner Filament for Features UI
  • FullSpectrum had resolved_model_config_for_tab stripping sparse_infill_filament — unfixable cleanly
  • VS2019 required · CMake 3.29 · Build script: build_and_run.bat · build.ps1 includes timing
  • Strawberry Perl in PATH causes CMake version conflict — fix: override PATH
  • DLL strategy: copy all from installed Snapmaker_Orca\ except Snapmaker_Orca.dll
  • PrintConfig.hpp must be UTF-8 (not ANSI) — has // -*- coding: utf-8 -*- on line 1; run encoding fix script after editing in VS2019
  • DO NOT run encoding conversion on PrintConfig.cpp — Chinese characters corrupt on ANSI→UTF-8
Phase 1 — config + pipeline scaffold (ALH=Adaptive Layer Height)
Phase 1a
New files — NozzleLayerPlanner, ColorDepthProcessor, NozzleVerifyDialog
Core logic classes, all building cleanly
  • SyncPoint struct: z, accumulated_height, accumulated_layers, gap_offset, expanding, outer_wall_extruder
  • plan() consumes ALH layer heights, produces sync points
  • Binary search queries: sync_point_at(), outer_wall_extruder_at(), gap_offset_at()
  • apply_depth(): per-layer ClipperUtils offset — built but not yet correctly wired (see Phase 5)
  • detect_thin_sections(): stubbed · ThinSectionBehavior: BleedThrough | Split50_50
  • inward_offset(): uses scaled() for correct Clipper unit conversion
  • Shows T1–T4 nozzle sizes, material names, color swatches
  • Verify button wires to MainFrame print button gate · update_extruders() rebuilds on reslice
Milestone
  • All 3 files registered in CMakeLists and building with zero errors
Phase 1b–d
PrintConfig fields + Pipeline hooks + Tab UI
New config fields, PrintObject, PerimeterGenerator, Multimaterial tab
  • outer_wall_loops (coInt) · outer_wall_layer_height_max (coFloat) · outer_wall_seam_position (coEnum)
  • has_mixed_nozzle_sizes (coBool) · filament_color_depth (coFloat, placeholder)
  • Mixed Nozzle optgroup in Multimaterial tab: outer_wall_loops, outer_wall_layer_height_max, outer_wall_seam_position
Milestone
  • App launches · Multimaterial tab stable · Mixed Nozzle group visible · Slices without errors
Phase 2 — detection · May 16
Phase 2
PresetBundle auto-detection
has_mixed_nozzle_sizes computed automatically · outer_wall_loops clamped
  • has_mixed_nozzle_sizes computed at end of full_fff_config(): any nozzle differs from values[0] by > 1e-4 → mixed=true
  • Never user-set — always derived at config assembly time
  • outer_wall_loops clamped to wall_loops when mixed (null-checked)
Milestone
  • Mixed nozzle auto-detected · outer_wall_loops clamped at slice time
Phase 3 — Repo pivot + NozzleVerifyDialog + print gate — May 16–17
Phase 3a
Fork from Snapmaker Orca instead of FullSpectrum
Cleaner base, Filament for Features already working
Major Decision
  • FullSpectrum: unresolvable resolved_model_config_for_tab filtering — sparse_infill_filament couldn't show in Multimaterial tab
  • Snapmaker Orca is the actual target hardware — cleaner starting point
  • DLL workaround: use installed Snapmaker_Orca DLLs except Snapmaker_Orca.dll → saved as %USERPROFILE%\SnapmakerOrcaDLLs\
Phase 3b
NozzleVerifyDialog fully wired + print gate
Dialog shows after slice, print button gated until verify
  • Mixed nozzle detection enabled in on_slicing_completed()
  • Filament color read from full_config() filament_colour vector (hex alpha stripped); stored by value to avoid dangling ptr
  • update_extruders() called on re-slice
  • get_enable_print_status(): nozzle verify gate added · get_enable_slice_status(): gate NOT added
Milestone
  • Dialog shows T1–T4 with nozzle sizes, material names, filament colors · Print gated until Verify
Phase 4 — OW/IW toolpath split + UI + per-nozzle line width — May 17 ✅ (OW=Outer Wall, IW=Inner Wall)
Phase 4a
inner_wall_filament field + UI
New extruder selector for inner wall, dropdown registered
  • inner_wall_filament (coInt): gui_type=i_enum_open, min=1, default=1 · wall_loops label → "All walls" · wall_filament label → "Outer wall"
  • Added to Filament for Features group · registered to dynamic_filament_list_1_based · added to toggle_line loop
  • fdm_process_U1.json: "inner_wall_filament":"2"
Milestone
  • Inner wall dropdown shows extruder list · default extruder 2
Phase 4b–c
PrintRegion toolpath routing + GCode.cpp perimeter split
frExternalPerimeter → wall_filament · frPerimeter → inner_wall_filament
  • extruder(): frExternalPerimeter → wall_filament; frPerimeter → inner_wall_filament
  • Only add inner_wall_filament when erPerimeter entities exist in this layer — prevents empty tool changes at cone tips
  • If PERIMETERS and inner/outer filaments differ and can_sort(): erExternalPerimeter → outer wall extruder; erPerimeter → inner wall extruder
  • Push directly into reg.perimeters — avoids owning temporary collections (heap corruption fix)
Milestone
  • Outer wall white (extruder 1), inner wall yellow (extruder 2) · No crashes
Phase 4d–f
OW>1, per-nozzle line width, UI polish
outer_wall_loops drives loop tagging · line widths rescaled per extruder nozzle diameter
  • classic path: is_external = loop.depth < std::max(1, outer_wall_loops)
  • Arachne path: is_external = (int)extrusion->inset_idx < std::max(1, outer_wall_loops)
  • PrintRegion.cpp flow(): ratio = config_width / nozzle[0]; rescaled = ratio × actual_nozzle_diameter
  • fdm_process_U1.json: all line widths converted to percentages
  • outer_wall_loops label turns red when value > wall_loops (distinct from orange "modified" state)
  • U1 nozzle diameters: sync dialog removed; each extruder writes nozzle_diameter[i] independently
Milestone
  • Color swatches match sidebar · Each nozzle independently settable · outer_wall_loops label validation works
Phase 5 — Color patch pipeline — May 18–28 ✅ Complete
Phase 5a
Color patch — convex + concave surfaces working ✅
Committed ba3d32bc68 — single red loop on painted face, inner core correct
  • Cube flat face: 1 red line on painted face only ✅
  • Sphere convex curved face: correct ✅
  • Torus inner hole (concave): correct single arc ✅ — fixed by building full_model from region slices (layer.lslices empty at segmentation time)
  • Adjacent painted faces same extruder: continuous red, no white seam ✅
  • Color patch loops UI slider in MMU gizmo panel ✅
  • GCode verified: T3 prints correct loops per layer ✅
  • Layer.hpp: color_patch_regions (vector) — shell strip per extruder per layer
  • PrintObjectSlice.cpp apply_mm_segmentation(): shell_strip = intersection(full_model_boundary_strip, stolen_expanded); inner core returned to base region
  • PerimeterGenerator: loop_number overridden to patch_loops-1; is_color_patch suppresses infill
  • color_patch_loops default: {1,1,1,1} for 4-head U1
Phase 5b
Color patch pipeline — complete ✅
All CL values, all surface types, per-object settings, Original mode verified
  • Original mode: G-code output identical to stock OrcaSlicer — verified by G-code diff ✅
  • Color Patch mode: N outer loops in painted color; inner loops + infill use base filament ✅
  • Full wrap, all CL values: CL=1 through 10+ correct ✅
  • Partial face, all CL values: CL=1 through 10+ correct including odd CL ✅
  • Mixed nozzle sizes: each painted extruder uses its own flow values for loop width ✅
  • Per-object CL: each object on plate has independent CL values per extruder ✅
  • Infill suppression: no infill inside color patch zone — only CL loops print ✅
  • Multiple painted colors: T2, T3, T4 with different CL on same object works ✅
  • Project persistence: per-object CL settings save/load with 3MF ✅
  • T1 (black) = base material, wall_filament of model region
  • T2/T3/T4 = painted colors — users paint onto T1 body
  • color_patch_enabled[ext_idx]=false → Original mode (pass-through to stock pipeline)
  • color_patch_enabled[ext_idx]=true + color_patch_loops[ext_idx] > 0 → Color Patch mode
  • color_patch_loops default changed to {0,0,0,0} — Original mode is now the default
Milestone
Phase 5c
Architecture — final implementation
Key files, per-object CL flow, loop_width formula, config propagation
  • color_patch_loops: coInts, default {0,0,0,0} in GCodeConfig
  • color_patch_enabled: coBools, default {false,false,false,false} in GCodeConfig
  • Both added to posSlice invalidation (Print.cpp) and posPerimeters invalidation (PrintObject.cpp)
  • Gate: if (cp_enabled && patch_loops > 0) — FOS pipeline only when both true
  • Per-object read: reads from model_object->config first, falls back to global print()->config()
  • loop_width = ext_width/2 + (CL>1 ? ext_sp2 : 0) + (CL>2 ? (CL-2)×perim_sp : 0) + perim_width/2
  • Uses target_lr (painted extruder's region) for flow values — supports mixed nozzle sizes
  • shell_strip stored in color_patch_regions[ext_idx] and color_patch_loops_effective[ext_idx]
  • mine subtraction: removes only shell_strip (not full stolen) — T1 keeps inner area
  • color_patch_regions: vector — shell strips per extruder
  • color_patch_loops_effective: vector — resolved per-object CL values per extruder
  • loop_number = patch_loops-1 set after all adjustments (overrides alternate_extra_wall etc.)
  • last overridden with color_patch_regions[wall_ext] clipped to current surface
  • offset_ex (not offset2_ex) for inner loop offsets — prevents thin wedge collapse
  • Gap detection skipped for color patch regions (is_cp_region_gap)
  • Infill suppressed for color patch regions (is_cp_region)
  • All CL loops get erExternalPerimeter role via cp_loops lambda
  • Forces process_classic (not Arachne) for color patch regions
  • inner_wall_filament added to painted region config — was missing, caused black inner loops
  • cfg.wall_filament, cfg.inner_wall_filament, cfg.solid_infill_filament, cfg.sparse_infill_filament all set to painted_extruder_id
  • Original button (left): sets color_patch_enabled[ext_idx]=false in model_object->config
  • Color Patch button (right): sets color_patch_enabled[ext_idx]=true, ensures color_patch_loops > 0
  • CL slider reads/writes color_patch_loops[ext_idx] in model_object->config
  • on_config_change must pass full preset config — partial config does NOT propagate correctly
  • Re-slice triggered via wxGetApp().plater()->update()
Phase 6 — App rename & branding — May 28 ✅
Phase 6
Renamed to FOrcaSlicer — Flexible OrcaSlicer + Fork-a-Slicer
Exe, DLL, installer, icon, web UI, splash, README all updated · v2.3.2-fos.1 released
Major Decision
  • version.inc: SLIC3R_APP_NAME "FOrcaSlicer" · SLIC3R_APP_KEY must stay "Snapmaker_Orca" — translation .mo files are named Snapmaker_Orca.mo, changing it crashes the app
  • src/CMakeLists.txt: OUTPUT_NAME "FOrcaSlicer" (exe) · OUTPUT_NAME "FOrcaSlicer-core" (DLL) — DLL named -core to avoid CMake linker collision with exe base name
  • src/Snapmaker_Orca_app_msvc.cpp: loads FOrcaSlicer-core.dll
  • src/slic3r/GUI/Plater.cpp + GUI_App.cpp: title bar updated · startup wizard skipped (requires Snapmaker login)
  • installer.nsi: all references updated · VIProductVersion "2.3.2.1" (Windows resource requires integers only)
  • src/FOrcaSlicer.ico: 7-size ICO from FOrcaSlicer_Icon.png
  • resources/web + images: splash_logo.svg, logo.png, flutter_web title, guide welcome text all updated
  • Version: 2.3.2-fos.1 — based on Snapmaker OrcaSlicer commit 48c601c3b1 (their unreleased 2.3.2 dev branch)
  • Version regex in version.inc parses only X.X.X — the -fos.1 suffix is safely ignored at runtime
  • Build: cmake --build . --config Release --target ALL_BUILD -- -m
  • Install: cmake --build . --config Release --target INSTALL -- -m
  • Installer: makensis.exe /DVERSION=2.3.2-fos.1 installer.nsi
  • Portable: Compress-Archive -Path build\FOrcaSlicer\* -DestinationPath FOrcaSlicer_Windows_Portable_V2.3.2-fos.1.zip
  • PerimeterGenerator.cpp must be saved as UTF-8 (no BOM) with LF line endings — encoding checker fails with exit code -2 otherwise
Milestone
  • GitHub release v2.3.2 published · FOrcaSlicer_Windows_Installer_V2.3.2-fos.1.exe + Portable zip
Phase 7 — Color patch top/bottom surfaces — May 28–30 ✅ Complete
Phase 7a
Color patch top/bottom solid surfaces — design & iterations
Extend color patch to painted top/bottom faces — solid infill in painted color
Major Decision
  • Side walls: CL loops only (shell_strip) — existing behavior unchanged
  • Top/bottom layers where painted region ends: painted extruder gets full stolen area, slices in original mode (natural loops + solid infill)
  • Detection: check if object top/bottom shell zone boundary coincides with where painted region ends — single intersection check per layer
  • Base extruder: subtracts full stolen on top/bottom layers; subtracts only shell_strip on wall layers
  • discover_vertical_shells staircasing: if painted extruder dst uses full stolen for top/bottom, discover_vertical_shells propagates larger area downward into wall layers → staircase artifact. Fix: painted extruder dst always uses shell_strip; solid infill comes from fill_expolygons being set to full exposed area
  • fill_expolygons clipping: fill_surfaces are clipped by fill_expolygons in Layer.cpp multi-region path. If fill_expolygons is only shell_strip, solid infill gets clipped away. Fix: for top/bottom CP layers, dst gets exposed top/bottom portion of stolen so fill_expolygons covers full top face
  • Exposed area split: for layers where painted region spans top face AND side face (crossing an edge), stolen is split — exposed portion gets full dst; side portion gets shell_strip only
  • CL slider re-slice fix: wxGetApp().plater()->update() alone doesn't mark object dirty. Fix: also call notify_instance_update(obj_idx, 0) + post_event(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)
  • ❌ Detecting top/bottom via top_shell_layers / bottom_shell_layers config alone → false positives on partial side patches
  • ❌ Area/shape consistency check across layers → too slow, OOM errors in parallel loop
  • ❌ Scan loop up/down from current layer → same OOM risk; replaced with single boundary intersection check
  • fos_near_obj_top = (layer_id >= total_layers - top_shells)
  • fos_near_obj_bottom = (layer_id < bot_shells)
  • If near boundary: check if stolen intersects object boundary at that boundary layer
  • Split stolen into exposed vs side portions using segmentation data of layer above/below
Phase 7b
Final state — shipped as v2.3.2-fos.1
Wall layers + top/bottom layers behave correctly · known limitations documented
  • Painted extruder gets shell_strip → PerimeterGenerator generates CL loops only
  • color_patch_regions stores shell_strip for PerimeterGenerator
  • Base extruder subtracts shell_strip from mine → keeps inner painted area
  • Painted extruder gets exposed portion of stolen as dst → fill_expolygons covers full top/bottom area
  • color_patch_regions NOT populated → PerimeterGenerator runs in original mode (natural loops + solid infill)
  • color_patch_is_top_bottom flag → PerimeterGenerator suppresses sparse infill, allows solid infill
  • Base extruder subtracts full segmented.expolygons from mine → gives up full painted zone
  • PrintObjectSlice.cpp: color patch pipeline, top/bottom detection, mine subtraction, dst assignment
  • PerimeterGenerator.cpp: infill suppression for CP regions, top/bottom solid infill, top_fills guard
  • PerimeterGenerator.hpp + Layer.hpp: color_patch_is_top_bottom pointer/vector added
  • LayerRegion.cpp: passes color_patch_is_top_bottom to PerimeterGenerator
  • Curved surfaces (sphere, cone, cylinder): color patch behaves as original wedge method — regression cause unknown, not investigated; lower priority for U1 flat-faced use cases
  • UI sync: occasionally requires toggling Original → Color Patch after painting fresh object; low frequency, simple workaround
  • Top/bottom shell staircase near boundaries: same as stock OrcaSlicer multi-material discover_vertical_shells propagation — not a regression, accepted
Milestone
Phase 7c
CL slider / mode button re-slice fix — May 30 ✅
Two root causes found and fixed — config timestamp + invalidation path
  • ModelObject::config.set_key_value() does not bump the config timestamp → PrintApply.cpp sees no change → returns APPLY_STATUS_UNCHANGED → no re-slice triggered
  • color_patch_loops and color_patch_enabled are stored in ModelObject::config (per-object DynamicConfig) but are not fields of PrintObjectConfig → even when timestamp changed, object_config_from_model_object never included them in the diff passed to invalidate_state_by_config_options
  • GLGizmoMmuSegmentation.cpp: call mo->config.touch() after every set_key_value for color_patch keys — 5 locations: Original button, Color Patch button ×2, CL slider, drag input
  • PrintApply.cpp: when object_config_changed is true, manually check if color_patch_loops or color_patch_enabled changed in per-object DynamicConfig and append them to the diff before calling invalidate_state_by_config_options
  • PrintObject.cpp: changed color_patch keys invalidation from posPerimeters to posSlice — perimeters-only invalidation was returning false when already invalidated, preventing re-slice
  • GLGizmoMmuSegmentation.cpp · PrintApply.cpp · PrintObject.cpp
Milestone
  • CL slider and mode button changes now reliably trigger re-slice
Phase 7d
Mixed nozzle + color patch hotfix — May 30 ✅
Three fixes shipped in v2.3.2-fos.1
  • has_mixed_nozzle_sizes flag was defined but never computed — always false
  • Added computation in normalize_fdm_1(): iterates nozzle_diameter values, sets flag true if any differ from the first
  • This activates the Phase 2 code path in PerimeterGenerator that reads outer_wall_loops for mixed nozzle geometry
  • For color patch regions with CL=1 (loop_number=0), i0_half_width was using perimeter_spacing/2 — for large nozzles (0.6/0.8mm) this was too large, inset exceeded shell_strip width → empty offsets → missing color patch
  • Fix: use ext_perimeter_width/2 for CL=1, perimeter_spacing/2 for CL≥2. Condition: (is_cp_region && loop_number > 0)
  • Color Patch mode is meaningless when selected painting extruder is the OW extruder — would just replicate OW behavior
  • Guard: compares m_selected_extruder_idx against wall_filament-1 from print config. If match: auto-switches to Original mode, Color Patch button visually disabled, tooltip explains why
  • Note: ImGui::BeginDisabled not available in this ImGui version — used style color override + && !is_ow_extruder condition instead
  • Occasional color patch sync issue on fresh objects (pre-existing) — toggle Original → Color Patch to fix
  • Mixed nozzle + color patch with multiple feature extruders (OW/IW/infill all different) not yet fully validated — treat as separate workflows for now
Milestone
  • All three fixes shipped in v2.3.2-fos.1
Phase 8 — Foreign preset crash fixes + mixed nozzle polish — May 31 ✅ · v2.3.2-fos.2 released
Phase 8a
Foreign preset crash fixes — 7 issues resolved
Fresh install with Bambu/generic presets no longer crashes · system preset auto-sync disabled
  • Fresh install downloads 2.3.14 presets from Snapmaker server — incompatible with FOrcaSlicer 2.3.2 codebase
  • FOrcaSlicer-specific config options missing from foreign presets → null pointer crashes when UI reads them
  • Symptom: clicking Multimaterial tab crashes with Access violation in DynamicConfig::opt_int() on foreign presets
  • Preset.cpp Preset::normalize(): add defaults for outer_wall_loops, outer_wall_layer_height_max, outer_wall_seam_position, color_patch_loops, color_patch_enabled, inner_wall_filament before handle_legacy_sla()
  • Tab.cpp TabPrint::reload_config(): same defaults added at start before Tab::reload_config()
  • OptionsGroup.cpp get_config_value() + get_config_value2(): null guards added for all coInt/coBool/coInts/coBools/coEnum/coEnums cases — returns 0/false instead of crashing
  • Symptom: adding Snapmaker U1 crashes in Sidebar::on_filaments_change() with access violation on choices.back()
  • Root cause: choices vector empty when called before sidebar combo boxes initialized
  • Fix: int last_selection = choices.empty() ? 0 : choices.back()->GetSelection()
  • Symptom: clicking paint gizmo with no object selected crashes on notify_instance_update
  • Fix: wrap all 4 FOS-added notify_instance_update blocks with null check on mo_notify before accessing model objects
  • Symptom: slicing fails with "Line width too small (skin_infill_line_width)" on 0.2mm nozzle second launch
  • Root cause: validation check extrusion_width_min <= layer_height triggers when both are 0.2mm — <= should be <
  • Fix: extrusion_width_min < layer_height in validate_extrusion_width lambda
  • Symptom: Unknown option exception: internal_solid_infill_acceleration fatal crash on startup
  • Root cause: option used in GCode.cpp and Print.cpp but never registered; also copy-paste bug gave skeleton_infill_line_width the wrong label/tooltip
  • Fix: register internal_solid_infill_acceleration as separate coFloatOrPercent entry; restore correct label/tooltip for skeleton_infill_line_width
  • sync_system_preset default changed from true to false — prevents downloading incompatible 2.3.14 presets on fresh install
  • Shortcut renamed from "Snapmaker Orca.lnk" to "FOrcaSlicer.lnk"
Milestone
  • Fresh install with any printer preset no longer crashes · system preset auto-sync disabled by default
Phase 8b
Mixed nozzle + color patch polish
SLIC3R_APP_KEY rename · inner_wall_filament fix · CL=1 large nozzle refinement
  • Files: src/common_func/common_func.hpp + version.inc
  • App data now at %APPDATA%\FOrcaSlicer\ instead of %APPDATA%\Snapmaker_Orca\
  • All locale .mo files already copied to FOrcaSlicer.mo — no action needed
  • Pre-install step: copy Snapmaker_Orca.mo → FOrcaSlicer.mo for all locales before making installer
  • Missing def->mode = comAdvanced and def->set_default_value(new ConfigOptionInt(1)) — IW dropdown rendered incorrectly on first launch
  • Added both to match wall_filament definition
  • Refined fix from Phase 7d: i0_half_width for CL=1 color patch now uses ext_perimeter_spacing/2 (not ext_perimeter_width/2)
  • Spacing is slightly less than width — gives single CL=1 loop enough room in shell_strip for large nozzles (0.6mm, 0.8mm) at all layer heights
  • Condition: (is_cp_region && loop_number > 0) ? perimeter_spacing/2 : (is_cp_region ? ext_perimeter_spacing/2 : ext_perimeter_width/2)
  • Color patch FlowErrorNegativeSpacing with mixed nozzle sizes (e.g. T1=0.2mm, T3=0.6mm) — root cause not yet diagnosed; look in PrintObjectSlice.cpp shell_strip calculation and LayerRegion.cpp flow setup
  • IW dropdown may show incorrectly on absolute first launch — clicking refreshes it; root cause is dynamic_filament_list_1_based not yet populated when tab widget first created
  • outer_wall_layer_height_max field exists in UI but NozzleLayerPlanner accumulator logic not yet implemented — field has no effect on slicing
Phase 8c
top_surface_line_width .3mf load fix + v2.3.2-fos.2 release
Percentage line widths loading as absolute mm from saved .3mf · fos.2 ships all Phase 8 fixes
  • Symptom: top_surface_line_width loading as 105mm instead of 105% when reopening a saved .3mf file
  • Root cause: percentage flag not preserved when line width values are serialized/deserialized from .3mf — value stored as float, % marker lost
  • Color patch (when CL=1) seam artifacts at arc endpoints when seam position is nearest or aligned — use random seam to avoid
  • Top/bottom painted face boundary follows stock OrcaSlicer shell propagation — bisector/normal separation planned for future
  • Mixed nozzle Phase 2 (separate perimeter generator per nozzle) not yet implemented — line widths use absolute mm in presets
  • IW dropdown may display incorrectly on very first launch — clicking refreshes correctly
  • Accumulator sync for IW max layer height not yet implemented
Milestone
  • v2.3.2-fos.2 released · FOrcaSlicer_Windows_Installer_V2.3.2-fos.2.exe + Portable zip
Phase 9 — Nozzle panel redesign + mixed nozzle spacing + stability — Jun 3 · v2.3.2-fos.3/fos.4
Phase 9a
Mixed nozzle OW/IW spacing fix + flow clamp + stability
Correct OW-to-IW transition spacing · Flow.cpp crash prevention · null guards
  • OW loops now use ext_perimeter_spacing, IW loops use perimeter_spacing
  • Correct transition spacing at OW-to-IW boundary: 0.5 × (ext_perimeter_spacing + perimeter_spacing)
  • Fix only activates when wall_filament != inner_wall_filament (fos_ow_iw_split flag)
  • with_spacing() now clamps instead of throws when width < height
  • Prevents crash with color patch + mixed nozzle at higher layer heights
  • top_surface_line_width fixed to "105%" in source — was "105" (no % marker)
  • Note: percentage-based line widths break mixed nozzle flow calculation — all U1 process preset line widths kept as absolute mm (correct for mixed nozzle where perimeter generator runs in OW nozzle context)
  • Tab.cpp: outer_wall_loops vs wall_loops color check now guards against null config options
  • OptionsGroup.cpp: additional null guards for coInt case in get_config_value
  • Tab.cpp: outer_wall_layer_height_max option line and toggle commented out — pending accumulator sync implementation
Milestone
  • v2.3.2-fos.3 released with spacing fix and flow clamp
Phase 9b
Nozzle diameter panel redesign (fos.4)
2-column grid UI · mixed mode toggle · auto-detect · PRP refresh chain
Major Decision
  • Printer Preset = PTP (Printer Type Profile) · Process Preset = PRP (Process Recipe Profile)
  • Replaced notebook tabs with 2-column grid matching filament panel style
  • Mixed mode toggle (SwitchButton, default OFF) — auto-detects mixed mode on PTP load if nozzle diameters differ
  • Color bar per row: 0.2mm=white, 0.4mm=10% gray, 0.6mm=20% gray, 0.8mm=30% gray · label format: "N: X.Xmm"
  • Process preset info note added above PRP dropdown — "Process presets are filtered by Nozzle 1 diameter. The layer height value you set determines your actual sliced result."
  • Changing any nozzle → confirm dialog (Sync/Cancel) → all nozzles sync to selected diameter → switch to matching system preset → PRP refreshes
  • Each nozzle independent — OW nozzle change updates inherits on custom PTP → PRP refreshes
  • Turning off mixed with differing diameters → confirm → sync all to Nozzle 1 → switch system preset
  • update_nozzle_settings(switch_machine=true) → reads OW nozzle idx from wall_filament → updates inherits → update_compatible(Always) → CallAfter → update_presets + load_current_preset
  • is_compatible_with_parent_printer() checks active_printer.preset.inherits() against compatible_printers list
  • Custom PTPs need correct inherits: "Snapmaker U1 (X.X nozzle)" based on OW nozzle diameter
  • PRP filtered by Nozzle 1 diameter regardless of which nozzle is assigned as OW — deferred until accumulator layer height sync implemented
  • Dark mode: nozzle color bars (white/gray scale) not tested — may need color inversion
Milestone
  • v2.3.2-fos.4 released · installer.nsi VIProductVersion 2.3.2.4
Phase 10 — OW nozzle → model color sync + painting erase warning — Jun 4 · v2.3.2-fos.5
Phase 10a
Nozzle diameter title icon
nozzle_diameter.svg added · ScalableButton wired in Plater.cpp
  • Added nozzle_diameter.svg to resources/images/ — grey top arc, green side lines, outer green-stroked ellipse, inner green-filled ellipse representing nozzle tip
  • SVG: width="16" height="16" viewBox="0 0 16 16", inline attributes, stroke-width="0.9", no CorelDRAW boilerplate
  • Plater.cpp: uncommented nozzle_icon ScalableButton, updated icon key to "nozzle_diameter", wired into nozzle_title_sizer
Phase 10b
OW nozzle → model preview color sync
3D viewport and paint gizmo now reflect OW nozzle color assignment
  • GL volume color: vol->extruder_id → filament_colour[extruder_id-1]; extruder_id always 1 for unpainted objects
  • simple_render (shells/thumbnails after slicing) hardcoded model_volume->extruder_id() as base color index
  • Paint gizmo ebt_colors[0] (base color for unpainted faces) also used mv->extruder_id()-1
  • Key design decision: do NOT set mo->config["extruder"] — breaks slicing by overriding per-feature filament assignments in object_config_from_model_object. Color override is purely visual.
  • Tab.cpp on_value_change: when wall_filament changes, CallAfter patches vol->extruder_id directly on all non-modifier GL volumes → update_volumes_colors_by_extruder(). Does NOT touch mo->config
  • GLCanvas3D.cpp reload_scene: after update_volumes_colors_by_extruder(), re-reads wall_filament and re-applies extruder_id override if OW > 1 — ensures color persists across every reload_scene
  • Tab.cpp · GLCanvas3D.cpp · Plater.cpp · resources/images/nozzle_diameter.svg
Milestone
  • v2.3.2-fos.5 released · VIProductVersion 2.3.2.5
Phase 10c
Multi-material painting erase warning on OW change
Prompt to erase MMU painting when OW nozzle changed · currently commented out pending testing
  • When wall_filament changes: check mv->is_mm_painted() across all objects
  • If painted: MessageDialog — "Changing the Outer Wall nozzle will erase all multi-material painting on all objects."
  • Yes: mmu_segmentation_facets.reset() on all model part volumes → plater()->update()
  • No: read old value from get_selected_preset().config, revert via m_config_manipulation.apply(), return early — dropdown snaps back
  • Currently commented out — more testing needed before enabling
Phase 11 — OW color override + right drag rotate + startup fixes — Jun 4 · v2.3.2-fos.6
Phase 11a
OW color override — complete fix across all render paths
3DScene.cpp core fix · paint gizmo base color · thumbnail render · data_changed tracking
  • simple_render (shells/thumbnails) still hardcoded extruder_id() as base color
  • Paint gizmo ebt_colors[0] used mv->extruder_id()-1, not OW extruder
  • 3DScene.cpp update_colors_by_extruder: reads wall_filament from config, uses OW extruder index for all model volumes when OW > 1
  • 3DScene.cpp simple_render: added ow_extruder parameter; idx==0 base color uses OW extruder when OW > 1
  • 3DScene.cpp render / render_with_outline: pass OW extruder to simple_render
  • GLCanvas3D.cpp render_thumbnail_internal: pass OW extruder to simple_render (both picking and non-picking paths)
  • GLGizmoMmuSegmentation.cpp init_model_triangle_selectors: ebt_colors[0] uses OW extruder color as base
  • GLGizmoMmuSegmentation.cpp update_triangle_selectors_colors + data_changed: static last_ow_ext_idx tracks wall_filament changes, triggers color update when changed
Milestone
  • Model color, thumbnails, and paint gizmo base color all correctly reflect OW nozzle assignment
Phase 11b
Right mouse drag rotates view + startup + PRP save fix
Right drag → rotate · mixed mode auto-detect on startup · PRP refresh after save
  • is_camera_rotate: added evt.RightIsDown() — right drag now rotates same as left drag
  • is_camera_pan: removed right drag — pan is now middle button only
  • load_current_presets called update_nozzle_settings() without switch_machine=true — mixed mode auto-detect never fired on relaunch
  • Fix: update_nozzle_settings() → update_nozzle_settings(true)
  • After save_preset, PRP stayed on wrong nozzle until PTP manually re-selected
  • Fix: after update_presets_from_to in save_preset, call sidebar().update_nozzle_settings(true) when m_type == TYPE_PRINTER
Milestone
  • v2.3.2-fos.6 released · VIProductVersion 2.3.2.6
Phase 12 — Nozzle verify panel + mouse config + GCode legend — Jun 5 · v2.3.2-fos.7
Phase 12a
Nozzle verification integrated into GCode legend panel
Floating dialog replaced with inline panel in Filament legend · Print button gate preserved
Major Decision
  • After slicing with mixed nozzles, a "Nozzle Verification" section appears at top of Filament legend panel (above filament table)
  • 2-row grid: Nozzle 1–N labels on top, diameter values with color-coded rectangles below (0.2mm=white, 0.4mm=10% grey, 0.6mm=20% grey, 0.8mm=30% grey)
  • Green "Verify nozzle sizes" button gates Print button until clicked → turns grey "[OK]" state
  • Re-slicing resets button back to green "Verify nozzle sizes"
  • Floating NozzleVerifyDialog commented out (preserved for future reference)
  • GCodeViewer.cpp — FOS nozzle verify block in case EViewType::ColorPrint
  • Plater.cpp — verify_nozzle_sizes(), is_nozzle_verify_required(), is_nozzle_verified(), reset on reslice
  • MainFrame.cpp — reset_nozzle_verified(), set_nozzle_verified_only()
Phase 12b
Mouse drag button assignment in Preferences + GCode legend defaults
Settings → General: per-button rotate/pan/none · Filament view default · OW first in Line Type
Major Decision
  • Settings → General (below Use free camera): 3-row table — Left drag / Middle drag / Right drag, each with radio buttons: Rotate | Pan | Not assigned
  • Defaults: Left=Rotate, Middle=Pan, Right=Rotate
  • Config keys: mouse_left_btn_fn, mouse_middle_btn_fn, mouse_right_btn_fn (values: "rotate" | "pan" | "none")
  • GLCanvas3D.cpp is_camera_rotate + is_camera_pan read new keys
  • ColorPrint moved to first in view_type_items list
  • m_view_type default changed to EViewType::ColorPrint
  • After sort_remove_duplicates(m_roles), erExternalPerimeter and erPerimeter are swapped so OW appears before IW
Milestone
  • v2.3.2-fos.7 released · VIProductVersion 2.3.2.7
Phase 13 — Per-nozzle FLP (Filament Preset) filtering for mixed nozzle — Jun 7–8 · v2.3.2-fos.8 / fos.8.1
Phase 13a
Per-nozzle filament profile filtering — correct volumetric speed per slot
Slot 3/4 (0.2mm) now auto-selects 0.2mm FLP · 1.6mm³/s instead of 12mm³/s
Major Decision
  • PresetComboBoxes.cpp FLP filter used nozzle_diameter[0] for all slots
  • Preset.cpp update_compatible_internal only marked FLPs compatible via parent printer inheritance — 0.2mm FLPs incompatible when printer inherits from 0.4mm parent
  • PresetBundle.cpp update_compatible auto-selection ignored nozzle diameter per slot
  • Alias collision: Generic PLA and Generic PLA @U1 0.2 nozzle share alias "Generic PLA" — map key collision caused wrong preset to display/select
  • update_compatible_internal: FOS block marks filament presets compatible if compatible_printers matches any nozzle diameter in mixed nozzle printer config
  • Restricted to TYPE_FILAMENT only · uses vector copy of cp->values to avoid pointer invalidation
  • update_compatible: unconditional FOS block after select_other_filament_if_incompatible gate
  • Iterates all filament slots, checks compatible_printers against slot's nozzle diameter
  • On mismatch: first_compatible lambda scores by nozzle match (required) + same alias (+1000) + same material type (+10)
  • Per-slot display filter in PlaterPresetComboBox::update(): filters by slot nozzle diameter AND printer model
  • get_preset_name override returns full preset name in mixed nozzle mode
  • Appends @U1 X.X nozzle suffix to presets lacking nozzle suffix — consistent naming
Milestone
  • Slot 1/2 (0.4mm): Generic PLA @U1 0.4 nozzle, 12mm³/s ✓ · Slot 3/4 (0.2mm): auto-selects Generic PLA @U1 0.2 nozzle, 1.6mm³/s ✓ · v2.3.2-fos.8 released
Phase 13b
Hotfix — FLP dropdown lockup + crash on preset reselection · v2.3.2-fos.8.1
fos.8 get_preset_name fabricated names broke 0.4mm slot dropdown · nullptr crashes fixed
  • fos.8 get_preset_name fabricated "Generic PLA @U1 0.4 nozzle" — no actual preset with that name exists in bundle; broke system_presets map key matching → drop_down stuck, ComboBox unable to reopen. 0.2mm slots unaffected because "Generic PLA @U1 0.2 nozzle" is a real preset name.
  • DynamicFilamentList::update and DynamicFilamentList1Based::update called find_preset()->get_filament_type() without null guard — nullptr crash during preset selection state transition
  • combo->update() called synchronously during selection event in on_select_preset — potential dropdown state corruption
  • Null guard in DynamicFilamentList::update and DynamicFilamentList1Based::update — checks find_preset() result before calling get_filament_type()
  • Deferred combo->update() in on_select_preset via wxTheApp->CallAfter to avoid corrupting dropdown state during selection event
  • Simplified get_preset_name: returns preset.name directly in mixed nozzle mode — no fabricated suffix; 0.4mm base presets shown as-is (e.g. "Generic PLA"), non-0.4mm already have nozzle suffix in actual name
  • FOS suffix-stripping in switch_to_tab: if display name not found in bundle, strips @U1 X.X nozzle via regex before alias lookup
Milestone
  • FLP dropdown on 0.4mm slots reopens correctly · no crash on reselection, multimaterial tab, or slice · fos.8 per-nozzle filtering intact · v2.3.2-fos.8.1 released
Phase 13c — Inner wall filament + outer wall loops save/load fix — Jul 12 · v2.3.2-fos.8.2
Phase 13c
Fix — inner wall filament + outer wall loops not surviving project save/load · v2.3.2-fos.8.2
Both FOS keys missing from s_Preset_print_options · dropped by apply_only on project reload
Major Decision
  • v2.3.2-fos.8.2 designated the initial public release of FOrcaSlicer
  • Inner wall filament: saved assignment (e.g. N3) came back as the Outer wall nozzle on reload — exported G-code routed inner perimeters to the OW tool
  • Outer wall loops: saved quantity discarded on reload, silently reverted to default
  • Both applied correctly within a session — loss only visible after close/reopen, or via the two keys missing from a reloaded project's G-code config header
  • inner_wall_filament and outer_wall_loops were defined in PrintConfig.cpp with set_key_value defaults in Preset.cpp, but neither was listed in s_Preset_print_options — the registry of valid print-preset keys
  • On project load PresetCollection::load_external_preset rebuilds the config via apply_only(combined_config, print_options) — keys absent from print_options are filtered out; both keys were written on save but dropped before reaching the preset
  • wall_filament / sparse_infill_filament / solid_infill_filament unaffected because already registered — only the two FOS-added keys were missing, which is why the bug looked selective
  • Added "outer_wall_loops" to s_Preset_print_options (after "wall_loops") and "inner_wall_filament" (after "wall_filament")
  • installer.nsi: VIProductVersion bumped to 2.3.2.8 (stale at 2.3.2.7 since fos.7) · no slicing code changed
  • Thick internal bridges: internal bridge extruded at nozzle_diameter × sqrt(bridge_flow) with no clamp against layer height — extreme under mixed nozzles (0.8mm nozzle on 0.1mm layer → 0.8mm-tall extrusion, 8× layer height, overlapping sparse infill beneath). Upstream Orca behavior, not FOS-specific — stock Orca with uniform 0.8mm nozzles at 0.24mm layers shows the same. Workaround: disable Thick internal bridges when solid-infill nozzle ≫ layer height; layer-height-aware clamp under consideration
Milestone
  • IW filament survives save/load (saved IW=N3 reloads as N3, G-code routes inner perimeters to that tool) ✓ · outer wall loops survives save/load ✓ · both keys now in reloaded G-code config header ✓ · both regain reset-dot and preset-baseline tracking ✓ · v2.3.2-fos.8.2 released
Phase 13d — Vendor profile refresh + duplicate profile cleanup — Jul 13 · v2.3.2-fos.8.3
Phase 13d
Fix — corrected Snapmaker profiles never reached existing installs · v2.3.2-fos.8.3
Stale bundle version froze cached profiles at the broken 105% state · 20 duplicate-name profiles · FOrcaSlicer branding in About
  • Same project file produced different line widths on two computers — upgraded installs showed a mix of percentages and mm in the Quality tab, including a bare "105" top surface width (105 mm, no % sign)
  • Only 0.4mm nozzle presets affected; 0.2/0.6/0.8 clean · fresh installs also clean, which made it hard to pin down
  • Not cosmetic: line widths are coFloatOrPercent with ratio_over = nozzle_diameter, so a stored 105% resolves to a different physical width per nozzle (0.21mm on 0.2, 0.84mm on 0.8) — under mixed nozzles this silently changes what gets printed
  • U1 process presets went through three revisions: df0a7e9d58 (mm → 105%/110%, top_surface became bare 105), 03a404bda3 (repaired 105 → 105%), 7b5c73e1af (everything back to absolute mm) — corrected mm values shipped in resources/profiles/ ever since
  • PresetUpdater::check_installed_vendor_profiles only refreshes %APPDATA%/FOrcaSlicer/system/<Vendor>/ when the resource bundle declares a newer version — Snapmaker.json sat at "02.02.46.02" through every FOS profile edit, so the check never fired and machines kept whatever profiles they first installed (the df0a7e9d58 state, frozen)
  • Every profile fix was faithfully packaged into the installer, then silently ignored on upgrade
  • 20 process profiles declared the same "name" key as a real sibling → nondeterministic preset binding: 19 "* copy.json" files (0.2/0.6/0.8 presets) + "0.20 Standard @Snapmaker U1 (0.4 nozzle)_old.json"
  • The _old.json is the 0.4-nozzle collision — exactly why 0.4 misbehaved; an older thinner revision, not referenced by the Snapmaker.json index (prior cleanups eeebb38a47 / 710caf2ac1 never shipped — dev branch only)
  • Snapmaker.json bundle version 02.02.46.02 → 02.02.47.00 — Semver::parse is 3-component (4th field invisible to comparison), so bumping .02 → .03 would have done nothing; moving the patch field (46 → 47) makes 2.2.46 < 2.2.47 true and triggers the refresh · also keeps FOS ahead of Snapmaker's official 02.02.46.x so their online updater won't overwrite it
  • Deleted the 20 duplicate-name profiles — install_bundles_rsrc calls remove_all() on the vendor folder before recopying, so the bump both delivers corrected profiles and sweeps stale files off existing machines
  • libslic3r.h SLIC3R_APP_FULL_NAME "Snapmaker Orca" → "FOrcaSlicer" — drives Help > About menu, About dialog title, error captions, MessageDialog titles; display-only (paths driven by SLIC3R_APP_KEY, already FOrcaSlicer)
  • common_func.hpp: added FOS_VERSION "2.3.2-fos.8.3" — kept separate from Snapmaker_VERSION deliberately (that macro is written into 3mf metadata, project settings, user_backup folder name; changing it would break project round-trips and orphan backups)
  • AboutDialog.cpp: version line reads "FOrcaSlicer 2.3.2-fos.8.3" with "Based on Snapmaker Orca 2.3.2 / Orca Slicer" beneath · fixed mojibake © — raw U+00A9 in a narrow literal decoded via current locale (GBK → CJK glyph); now pure ASCII, decoded explicitly via wxString::FromUTF8
  • No slicing code changed · upgrade note: corrected profiles apply automatically on first launch; customized user presets untouched — only the system bundle under %APPDATA%\FOrcaSlicer\system\Snapmaker\ is replaced
Milestone
  • Upgrading to 8.3 refreshes the cached vendor bundle — all U1 line widths load as absolute mm on every nozzle ✓ · 20 duplicate-name profiles removed from existing installs ✓ · zero percentage line widths and zero duplicate names remain in the Snapmaker profile tree ✓ · same project yields identical line widths across machines ✓ · Help > About reads "About FOrcaSlicer", dialog shows FOrcaSlicer 2.3.2-fos.8.3 with correct © ✓ · v2.3.2-fos.8.3 released
Phase 13e — Support, tree support + prime tower were all sized for nozzle 1 — Jul 14 · v2.3.2-fos.8.4
Phase 13e
Fix — support, tree support and prime tower all computed geometry from nozzle 1 · v2.3.2-fos.8.4
Single theme: three features sized for nozzle 1 regardless of printing tool · no-op on uniform-nozzle machines
  • Support on filament 2 of a 0.2/0.4/0.6/0.8 machine: slicer picks the right nozzle, then tells it to lay a 0.22mm line — nozzle 1's width; the 0.4mm tip asked for a bead thinner than itself
  • Prime tower worse: every tool wiped at 1.0mm and rammed at 2.0mm — the 0.2mm nozzle included, at 5× and 10× its own diameter
  • Not rounding errors — extrusions a nozzle physically cannot lay
  • Absolute line widths in a process preset are authored against nozzle 1 (the reference nozzle); any feature printed by a different tool must re-derive the width by ratio. PrintRegion::flow() has done this all along for frExternalPerimeter/frPerimeter/frInfill/frSolidInfill/frTopSolidInfill — but support roles aren't in that list, and support never passes through PrintRegion::flow() at all: it goes through free functions in Flow.cpp that pick the right nozzle then hand it an unadjusted width
  • Tree support wrong a second way: support_extrusion_width (sizes the tree base) was read from support_interface_filament's nozzle, not the base's · two further sites hardcoded nozzle_diameter.get_at(0), pinning branch spacing and collision resolution to nozzle 1 outright
  • Prime tower kept a single scalar and overwrote it: WipeTower2::m_perimeter_width assigned inside set_extruder(), called once per filament — last filament configured won by accident of iteration order (0.8 × 1.25 = 1.0mm for everyone); the code even states "all extruders are now assumed to have the same diameter" · meanwhile m_filpar[idx].nozzle_diameter was populated all along and never read
  • New fos_width_for_nozzle() — the nozzle-1 → printing-nozzle ratio rule, exported so support, SupportParameters and TreeSupport all derive widths identically instead of three different ways; percent widths pass through untouched (get_abs_value already ratios over the nozzle it's given) · support_material_flow, support_material_1st_layer_flow, support_material_interface_flow now route through it
  • SupportParameters: support_extrusion_width now comes from support_filament (was support_interface_filament), ratio-derived — it sizes the support base, so it was taking dimensions from the wrong tool
  • TreeSupport: two hardcoded nozzle_diameter.get_at(0) reads replaced with the support filament's nozzle, ratio-derived — these drive collision resolution and branch spacing; a tree printed by a 0.6mm tool was built to 0.2mm dimensions
  • Structure width (footprint, box margins, brim spacing, tower perimeter + sparse fill) bound to wipe_tower_filament — the tool that genuinely prints them (set_extruder marks it the only non-soluble one, so first_toolchange_to_nonsoluble lands finish_layer on it); previously whichever set_extruder call ran last
  • Wipe and ramming now extrude at each tool's own width and flow, from m_filpar[tool].nozzle_diameter
  • Depth planning follows: plan_toolchange() and save_on_last_wipe() reserve depth with the same per-tool widths — width and spacing must move together or the purge block over/under-fills and reserved depth stops matching emitted lines · ramming E is volumetric (ramming_speed × time / filament_area) so only line length changes, purged volume unaffected
  • Support/raft base, Support/raft interface and Wipe tower offer "Default" = whichever filament is already loaded — invalid under mixed nozzles and unfixable: support paths are generated once at slicing time with a single flow, but the tool isn't chosen until G-code export, where ToolOrdering reassigns the tool on already-generated extrusions and nothing re-renders them; a path built for a 0.2mm tip can be handed to a 0.8mm tip
  • When nozzle diameters differ: those three dropdowns show "Empty" instead of "Default", and the slicer refuses to slice until a filament is chosen, naming which dropdown is blank · deliberately NOT auto-filled with filament 1 — silently landing on nozzle 1 is precisely the bug being fixed · uniform-nozzle machines: "Default" behaves exactly as before
  • FOS_VERSION → 2.3.2-fos.8.4 · installer VIProductVersion 2.3.2.8 → 2.3.2.9 · upgrade note: mixed-nozzle users with support/prime tower asked to pick filaments on first slice; pre-8.4 projects may open with these blank and the slicer names which it needs
  • Support layer height still follows nozzle 1 — consistent with the 8.x rule (all nozzles follow nozzle 1's process settings), but a 0.8mm nozzle can be asked to print 0.1mm support layers; a layer-height clamp would be a new feature, not a fix — deferred
  • Ramming-to-wipe alignment: Y offsets aligning the ramming block to the first wipe line still use the tower's structure width — with per-tool widths can be off up to ~0.4mm; cosmetic, no effect on purge volume or tower geometry
Milestone
  • Verified in G-code (0.2/0.4/0.6/0.8, support base fil.3, interface fil.2, tower fil.2): support 0.22 → 0.66mm on 0.6 tool ✓ · interface 0.22 → 0.44mm on 0.4 tool ✓ · tower wipe 1.0×4 → 0.25/0.5/0.75/1.0 ✓ · ramming 2.0×4 → 0.5/1.0/1.5/2.0 ✓ · purge block stays inside tower wall ✓ · tree branch spacing scales with support nozzle ✓ · uniform-nozzle slices unchanged (0.6×4: same 0.62mm support, same 0.75/1.5 tower widths, same extrusion counts) ✓ · v2.3.2-fos.8.4 released
Phase 14 — Per-nozzle line width and speed — Jul 14 · v2.3.2-fos.8.5
Phase 14
Per-nozzle line width and speed · v2.3.2-fos.8.5
Each feature prints at the width and speed of the nozzle that actually prints it — not Nozzle 1 for everything · per-nozzle prime tower width · layer height guarded by smallest used nozzle
  • On mixed-nozzle printers, each print feature now takes its line width and speed from the nozzle that actually prints it — set in Quality and Speed, per-nozzle tabs (Nozzle 1–4); assignment follows the Process filament dropdowns (outer wall, inner wall, infill, solid infill, support)
  • Line width: outer wall, inner wall, top surface, sparse infill, internal solid infill, support · Speed: outer wall, inner wall, small perimeter, sparse infill, internal solid infill, top surface, gap infill, ironing, support, support interface
  • Values typed in the Nozzle 2–4 tabs take effect immediately — no preset reload needed
  • Each nozzle gets a Prime tower line width field (Quality tab) — 0 for auto (1.25 × that nozzle's diameter), or set a value; every tool wipes and rams at its own width, tower walls/footprint use the wipe-tower filament's value
  • Previously the whole tower used a single width from whichever tool was configured last — a 0.2 nozzle could be laying 1.0mm tower lines
  • Layer height and initial layer height remain a single global setting, shown below the nozzle tabs — automatically limited to the range the smallest nozzle actually used can print (can't ask a 0.2mm tip to lay a 0.6mm layer); the limit follows assignments, so using only larger nozzles opens the range up
  • The limit counts the object's own filament too — if a layer-height limit looks too low, check the filament assigned to the object in the object list, not just the Process dropdowns
Milestone
  • Verified in G-code across mixed 0.2/0.4/0.6/0.8 setups and a uniform 0.6 regression — single-nozzle-size printers unchanged, output identical to before ✓ · not yet bed-tested — real-print reports wanted on mixed-nozzle models · v2.3.2-fos.8.5 released
Phase 14a — Hotfix: painted patch width + Nozzle 1 preset — Jul 17 · v2.3.2-fos.8.5.1
Phase 14a
Hotfix over 8.5 — per-nozzle width on painted patches + Nozzle 1 keeps its own preset · v2.3.2-fos.8.5.1
Two fixes, no new features
  • On a mixed-nozzle printer, a surface assigned to a nozzle by painting (color patch / segmentation) now prints at that nozzle's line width — same as a surface assigned through the Process feature dropdowns
  • In 8.5, painted patches printed at the base region's width regardless of which nozzle laid them: a 0.2mm tip could be pushed to ~0.4mm and a 0.8mm tip held to ~0.4mm; feature-dropdown assignment (outer wall, inner wall, infill, solid infill) was unaffected — only painted regions. Restores the behavior that shipped before 8.5
  • After the fix, a painted 0.2/0.4/0.6/0.8 model lays roughly 0.22/0.42/0.62/0.82 per nozzle — occasional wider lines on the large nozzle are normal gap infill
  • Nozzle 1's process preset now follows Nozzle 1's own diameter · previously it was tied to whichever nozzle printed the outer wall: assigning the outer wall to a different nozzle — or painting a model so the outer-wall filament wasn't nozzle 1 — locked Nozzle 1's preset to that nozzle's diameter, and it would snap back on reload
  • Nozzle 1 now stays on its own preset regardless of the outer-wall assignment; nozzles 2–4 keep their own presets as before
Milestone
  • Single-nozzle-size printers unchanged, output identical to before ✓ · if you don't paint color patches or reassign the outer wall on a mixed-nozzle machine, these fixes change nothing · v2.3.2-fos.8.5.1 released
Phase 14b — Hotfix: crash when adding U1 PTP on a fresh install — Jul 19 · v2.3.2-fos.8.5.2
Phase 14b
Hotfix over 8.5.1 — crash when adding the Snapmaker U1 on a fresh install · v2.3.2-fos.8.5.2
Dangling raw pointers into a resized std::vector in the per-nozzle notebook · one crash fix, no new features
  • On a fresh install, adding the Snapmaker U1 (mixed-nozzle PTP) crashed immediately — hard access violation, no dialog, no error message
  • Longstanding latent bug in the per-nozzle notebook, exposed only on the fresh-install path; independent of the 8.5.1 fixes
  • Stack: TabPrint::fos_reload_slot_config (Tab.cpp ~3517) → ConfigOptionsGroup::reload_config (OptionsGroup.cpp ~750) → get_config_value → crash at config.def() (~945)
  • Per-nozzle slot optgroups are built with raw pointers into the m_fos_slot_configs std::vector (&m_fos_slot_configs[n]) · the notebook build and fos_reload_slot_config call resize(nozzle_count) at three sites — when resize GROWS the vector it reallocates the buffer, dangling every optgroup's m_config; the next reload_config() reads the freed config and faults
  • Adding the U1 on a fresh install grows the slot vector (1 → 4) — that reallocation is the trigger; on an existing profile the vector is already sized, hence "fresh install only"
  • Ruled out (recorded so they aren't re-chased): the "contains incorrect keys: color_patch_enabled, inner_wall_filament..." log flood is BENIGN — process/object-scope keys correctly stripped from machine presets, not the crash · an initial null-guard on get_config_value's opt deref was a misread of the crashing line (the fault is the config access at 945, before any opt deref)
  • Tab.hpp: m_fos_slot_configs changed from std::vector to std::deque — deque keeps existing element addresses stable across resize/grow at the end, so the optgroup raw pointers never dangle; verified no contiguity assumption on the container (only single-element addresses, no .data()/.reserve()/.capacity()), so it's a drop-in and one change covers all three resize sites
  • OptionsGroup.cpp: null guard in get_config_value and get_config_value2 — if config.def()->get(opt_key) returns null, return empty instead of dereferencing; defensive hardening for a separate latent null-deref, NOT this crash
  • FOS_VERSION 2.3.2-fos.8.5.1 → 2.3.2-fos.8.5.2 · installer VIProductVersion 2.3.2.11 → 2.3.2.12 · everything from 8.5.1 unchanged
  • The release log truncates mid-write at a hard crash and carries no stack. Best: Visual Studio on the build machine — rename %APPDATA%\FOrcaSlicer aside to force a fresh config, run under the debugger with symbols, reproduce · WER local dumps (HKLM LocalDumps, DumpType=2) → .dmp opened in VS with the matching PDB · quick triage: Event Viewer → Application Error → faulting module + offset
Milestone
  • Reproduced on a fresh config (delete %APPDATA%\FOrcaSlicer, add U1 PTP) → hard crash with the stack above; after the deque fix + rebuild, same path → no crash, confirmed by user ✓ · v2.3.2-fos.8.5.2 released
Phase 14c — Hotfix: per-nozzle widths apply on PTP create — Jul 20 · v2.3.2-fos.8.5.3
Phase 14c
Hotfix over 8.5.2 — per-nozzle line widths now apply the moment a mixed-nozzle PTP is created · v2.3.2-fos.8.5.3
Lazy optgroup build meant the first slice used Nozzle 1's width until the Quality tab was opened · one fix, no new features
  • On a mixed-nozzle printer, creating a new print-type profile (PTP) assigned each nozzle the correct process preset, but the slice still used Nozzle 1's line width for every nozzle until you opened the Quality tab (or re-picked a nozzle's process preset)
  • Painted color-patch surfaces showed the same thing — the painted nozzle's width didn't take effect until you visited Quality; explains the cross-machine difference where one install had visited Quality and the other hadn't
  • update_prp_nozzle_rows (ParamsPanel.cpp) auto-selects the correct per-nozzle PRP for each slot then calls fos_reload_slot_config(i) to resolve the fos_nozzle_*_line_width arrays — but fos_reload_slot_config bails early on m_fos_slot_optgroups[slot_idx].empty(), and the per-nozzle notebook optgroups build LAZILY, only when Quality is first opened
  • So on a freshly created PTP the slot configs never load; fos_resolve_nozzle_arrays keeps the arrays mirrored to slot 0 (Nozzle 1). Opening Quality builds the optgroups, the reload finally runs, and the widths snap correct — probe showed RESOLVE stuck at ow=0.220×4 (nd=0.2,0.4,0.6,0.8) until Quality was opened, then reaching 0.220,0.420,0.620,0.820
  • Ruled out: the s21 "N1 eviction" fix was NOT the mechanism — the probe showed Nozzle 1 / slot 0 correct throughout, never evicted; this is a different mechanism (PTP-create optgroup timing)
  • First fix removed the bail so the load+resolve run even with empty optgroups — fixed create but REGRESSED normal use: clicking a per-nozzle tab collapsed all lines to 0.2. The bail is load-bearing — during normal use the print tab is torn down and rebuilt constantly, firing fos_reload_slot_config(0) alone while slots 1..N are momentarily empty; the bail stops fos_resolve_nozzle_arrays running on that partial snapshot. Reverted in full
  • Lesson: the optgroup-empty bail is not just a UI guard — it protects the resolved arrays from single-slot resolves during tab teardown/rebuild churn. Any create-time populate must load the COMPLETE set first and resolve exactly once
  • New TabPrint::fos_populate_all_slots(bool mixed_active) — loads every slot config from its source (slot 0 from *m_config; slots 1..N-1 from each nozzle tab's stored selected_preset_name(), which survives optgroup teardown) then calls fos_resolve_nozzle_arrays() ONCE on the complete set; no optgroup dependency, no UI reload · uniform mode mirrors N1 · a per-slot change-guard preserves slot hand-edits
  • ParamsPanel.cpp: after the per-slot auto-select loop in update_prp_nozzle_rows, call fos_populate_all_slots(mixed_active) — the create/diameter-change path · fos_reload_slot_config's optgroup-empty bail left intact so the churn path stays protected
  • FOS_VERSION 2.3.2-fos.8.5.2 → 2.3.2-fos.8.5.3 · installer VIProductVersion 2.3.2.12 → 2.3.2.13 · everything from 8.5.2/8.5.1 unchanged
Milestone
  • G-code (cube_build_3.gcode) toolpath ;WIDTH: tags by active tool — T0=0.22, T1=0.45, T2=0.62, T3 ~0.82; each nozzle extrudes its own width, config block fos_nozzle_outer_wall_line_width = 0.22,0.42,0.62,0.82 · create is correct with NO Quality visit; Nozzle 2 tab-click no longer collapses; uniform mode mirrors N1 correctly ✓ · v2.3.2-fos.8.5.3 released
Phase 14d — Hotfix: global Filament-for-Features regression + "Global" label + CJK/i18n — Jul 21 · v2.3.2-fos.8.5.4
Phase 14d
Hotfix over 8.5.3 — global Filament-for-Features slicing regression + per-object "Global" label + imgui CJK + zh_CN pass · v2.3.2-fos.8.5.4
Forced object extruder=1 clobbered every per-feature assignment · four fixes, no new features
  • Objects were stored with a forced extruder=1 · PrintObject apply_to_print_region_config step-1 fill does, in effect: if (extruder != 0) { sparse_infill = solid_infill = wall = inner_wall = extruder; } — so a non-zero object extruder OVERWRITES every global per-feature assignment with that one filament
  • With OW=1/IW=2/infill=3/solid=4 set globally, all four collapsed to Filament 1 and the prime tower vanished (only one filament in use)
  • Fix: keep object extruder = 0 (unassigned) so the clobber branch is skipped and features resolve from the global assignment · Model.cpp (3 spots ConfigOptionInt(1) → (0)), GUI_ObjectList.cpp (update_filament_values_for_items + add-mesh path back to 0) · also restored OrcaSlicer's per-object model (object display color driven by object-level filament only; misleading per-feature outer-wall color preview removed)
  • extruder=0 previously displayed "default", which was misleading — it's the fallback/global state (features fall back to the global assignment), not literally a default · now reads "Global" (全局), reusing the existing msgid and matching the Global/Objects mode toggle
  • Six sites, all // FOS: tagged: wxExtensions.cpp get_default_extruder_color_icon (shared icon covers editor dropdown row + modifier/layer icons), ExtraRenderers.cpp dropdown fallback row, ObjectDataViewModel.cpp object/modifier/layer display strings · for modifier parts / layer ranges 0 means "inherit parent" but it's one shared icon, so all read "Global"
  • Garbled button: ImGuiWrapper::button() only has wxString overloads; imgui.button(_u8L("...")) passed a UTF-8 std::string, which hit the implicit wxString(std::string) ANSI/Latin-1 ctor → translated CJK bytes mis-decoded → mojibake (imgui.title takes std::string directly, so the adjacent title looked fine) · fix: _u8L → _L on both buttons ("Verify nozzle sizes", "Nozzle sizes verified")
  • English nozzle labels: the Nozzle 1..N header used raw sprintf("Nozzle %d") with no translation → fixed to wxString::Format(_L("Nozzle %d")) → 出口 %d
  • General trap (recorded): any translated _u8L passed to a wxString-only sink mojibakes via the implicit std::string → wxString ANSI ctor · rule: _L for wxString sinks, _u8L only for std::string/char* sinks — only the nozzle-verify overlay was audited this round
  • Broad zh_CN pass: 耗材丝 → 耗材 (137 incl. a 耗材丝丝 double), default → 默认 (was 缺省), Global → 全局, Filament for Features → 特征对应的耗材, Wipe tower → 擦拭塔, Ooze prevention → 预防渗出, added Preheat delta temperature → 预热温差 (msgid was never extracted to the .po), stripped leftover English "Skirt"/"Brim" from field labels (Brim header → 加固边沿); zh_TW untouched
  • Pipeline root cause (probe-confirmed): the app loads AddCatalog(SLIC3R_APP_KEY) = FOrcaSlicer.mo, but gettext_po_to_mo emitted Snapmaker_Orca.mo (wrong name) and called bare msgfmt (not on Windows PATH → target always failed) — so the runtime zh_CN catalog was frozen at a hand-made May-17 .mo and .po edits never reached the app
  • Fix: gettext_po_to_mo now calls bundled tools/msgfmt.exe and emits ${SLIC3R_APP_KEY}.mo = FOrcaSlicer.mo; sibling gettext_merge_po_with_pot uses tools/msgmerge.exe · verified building the gettext_po_to_mo target regenerates FOrcaSlicer.mo from the .po via the official pipeline · all *.mo gitignored, catalog now reproducible from source (was a manual artifact that vanished on clean build)
Milestone
  • Global Filament-for-Features resolves correctly (OW=1/IW=2/infill=3/solid=4 no longer collapse, prime tower returns) ✓ · per-object no-filament cell reads "Global"/全局 ✓ · nozzle-verify overlay button and Nozzle 1–4 headers display correct Chinese ✓ · zh_CN translations survive a clean build ✓ · single-nozzle-size printers unaffected · FOS_VERSION → 2.3.2-fos.8.5.4, installer VIProductVersion 2.3.2.13 → 2.3.2.14 · v2.3.2-fos.8.5.4 released
Phase 14e — Outer-wall object identity + Default-filament cleanup + PRP scaling — Jul 22 · v2.3.2-fos.8.5.5
Phase 14e
An object's filament is now its outer wall · Default/Global fallback removed · PRP panel DPI scaling fixed · v2.3.2-fos.8.5.5
Objects always carry a concrete filament — no more invisible default
  • An object's filament is now defined by its outer wall (wall_filament) end to end — the per-object Filament column and the 3D object color both show the outer-wall filament (what you see on the print), not a separate object "default"; the old "Global" label is gone
  • Every entry point stays in sync and drives the same value: right-click menu, inline Filament dropdown, Object Table dropdown, outer-wall filament selector, 3D preview · one shared feature group is the source of truth; the step-1 object-extruder clobber in apply_to_print_region_config is deleted, features resolve from the step-2 copy loop
  • Right-click "Change Default Filament" → "Unify Object Filaments" — sets outer wall, inner wall, sparse infill and solid infill of the selected object(s) to one filament in a single action (the "print this whole object on filament N" shortcut); the Object Table dropdown does the same · both menu strings translated to Simplified Chinese
  • Support base/interface and the wipe tower keep their own filament controls — on mixed-nozzle printers they still must be a concrete filament (gate unchanged)
  • Object-list Fila. column no longer offers a "Global" row for objects/model parts — they list filaments 1..N and default to filament 1; parameter modifiers and layer ranges keep the inherit row (0 = inherit is still meaningful there)
  • Filament for Features dropdowns (walls, inner walls, sparse infill, solid infill) list filaments only; a feature still holding 0 is printed on filament 1 · the four feature filaments now default 1 / min 1 (were 0/0), and clamp_feature_extruder stays as the crash-guard for legacy/hand-edited configs holding 0
  • Outer wall, inner wall, top surface, sparse infill, solid infill and support line-width tooltips now state that setting the value to 0 uses the Default line width (documents existing behavior so 0 reads as "inherit"); Chinese tooltips updated
  • On two monitors at different scaling (e.g. 100% + 150%), the Nozzle 2–4 PRP rows stayed frozen at build-time scale while Nozzle 1 tracked the current monitor — their combos, save/search buttons and row spacing looked oversized. Root cause: N2–N4 are TabPrintNozzle tabs that remove themselves from tabs_list and sit in no msw_rescale path · fix: new TabPrintNozzle::fos_rescale_row (rescales the owned combo/buttons/bitmaps/row-height only — the full Tab::msw_rescale is UB on nozzle tabs) plus a TabPrint::msw_rescale override for the per-nozzle notebook slot optgroups; no more toggling tabs to fix spacing
  • Also since 8.5.4: fixed a false "Too small line width" error that could block slicing a mixed-nozzle object at a tall layer height · FOS_VERSION → 2.3.2-fos.8.5.5, installer VIProductVersion 2.3.2.14 → 2.3.2.15
Milestone
  • Object identity is its outer wall across Fila column, 3D tint, menu, all dropdowns and preview ✓ · Default/Global row removed where objects/features carry a concrete filament ✓ · PRP rows and per-nozzle notebook tabs rescale with the current monitor ✓ · false too-small-width slice block fixed ✓ · single-nozzle-size printers unaffected · v2.3.2-fos.8.5.5 released
Phase 15 — First macOS build · cross-platform release — Jul 27 · v2.3.2-fos.8.5.6
Phase 15
First macOS build — universal DMG, rename finished on macOS, unified user data · v2.3.2-fos.8.5.6
No slicing behavior changed — Windows slicing output identical to 8.5.5
Major Decision
  • v2.3.2-fos.8.5.6 is the first release with a macOS build — FOrcaSlicer is now cross-platform (Windows + macOS)
  • Ships a macOS DMG built from source in GitHub Actions — a universal binary (app, crash handler and Sentry library all carry x86_64 + arm64 slices), running natively on Intel and Apple Silicon; minimum macOS 12.0 (Monterey), tested on Intel and Apple Silicon (M4 Max, macOS 26)
  • Not signed or notarized (no Apple Developer certificate) — first launch is refused with "Apple cannot check it for malicious software"; clear quarantine once: xattr -dr com.apple.quarantine "/Applications/FOrcaSlicer.app", or System Settings > Privacy & Security > Open Anyway (the Control-click bypass was removed in macOS 15)
  • The 8.x rename covered the Windows app name/icon/exe; several macOS-only code paths were outside its scope and never exercised (no macOS build existed). Now all correct: app bundle + executable are FOrcaSlicer (not "Snapmaker Orca"); the FOrcaSlicer icon shows everywhere including the Dock tile (previously reverted to the Snapmaker icon while running); the language list is populated again — the mac build generated catalogs under the old name so the app found no FOrcaSlicer.mo and offered no languages, now all ~20 locales load
  • Several paths were still hardcoded to Snapmaker_Orca, so FOrcaSlicer read/wrote a mixture of its own directory and Snapmaker Orca's; every path now derives from the application key, so FOrcaSlicer is fully independent of any other Orca-family slicer installed alongside it
  • Now from FOrcaSlicer's own directory: profile/web version files read at startup, language/region detection (reads FOrcaSlicer.conf, was Snapmaker_Orca.conf), crash report data (FOrcaSlicer/SentryData) · Windows users: no action needed, no settings lost — presets/profiles were always in the FOrcaSlicer directory
  • Two GitHub Actions workflows: universal release build (deps matrix → universal build, DMG + dSYM) and a single-arch x86_64 fast-test · three CI defects fixed, each costing a failed run: pinned CMake 3.31.6 (brew yields CMake 4.x which drops cmake_minimum_required <3.5 compat); uninstall Homebrew zstd before any cache action (zstd removal mid-job changed the cache compression method so every lookup missed); compute the dep cache key once after checkout (a restored dep prefix mutated hashFiles() for the next restore) · required step order: Checkout → Remove zstd → Compute dep cache key → cache action → rest
  • Nozzle tabs after adding a U1 PTP: the per-nozzle notebook shows only one tab until you click away and back — cosmetic, presets created correctly
  • macOS dark mode on first launch: notebook and nozzle-diameter labels can render light-on-light until the system appearance is toggled — the app samples appearance before the window exists and doesn't re-check; cosmetic, macOS only
  • macOS builds are unsigned and unnotarized (see quarantine note)
Milestone
  • Universal DMG verified genuinely universal (CI audit: all three Mach-O files carry x86_64 + arm64; everything else statically linked) ✓ · launches natively on Intel and Apple Silicon (M4 Max, macOS 26) ✓ · clean Mac creates only ~/Library/Application Support/FOrcaSlicer/, no Snapmaker_Orca directory ✓ · Windows data unified, nothing written to Snapmaker_Orca ✓ · FOS_VERSION → 2.3.2-fos.8.5.6, installer VIProductVersion 2.3.2.15 → 2.3.2.16 · v2.3.2-fos.8.5.6 released with Windows installer, portable zip and universal macOS DMG
Outstanding / TODO
TODO
Remaining items
Regression fix, cleanup, version bump, extended testing
  • Color patch FlowErrorNegativeSpacing with mixed nozzle sizes — not yet diagnosed
  • Painting erase warning on OW change — implemented but commented out, pending testing
  • PRP dropdown width not adaptive in mixed nozzle mode
  • Nozzle-1 independent PRP selector not built — nozzle 1 is still the main Process preset (carried from 8.5.x)
  • initial_layer_line_width still global, not per-nozzle (carried from 8.5.x)
  • _u8L → wxString mojibake trap may exist wherever a translated _u8L is passed to a wxString-only sink — only the nozzle-verify overlay was audited (8.5.4)
  • Modifier/layer fallback icon reads "Global" though it semantically means "inherit parent" (shared icon) — revisit if per-type labels are wanted
  • Per-nozzle FLP — full architecture deferred: requires per-slot speed overrides in PRP (OW/IW/infill/top surface speeds per nozzle)
  • IW dropdown incorrect on absolute first launch — low priority, clicking refreshes
  • GitHub wiki: mixed nozzle OW/IW behavior vs stock OrcaSlicer · wiki started in closed environment
  • Curved surface color patch regression — verify sphere/torus/cone CL loops; if broken, bisect between 48c601c3b1 and current
  • Color patch CL=1 seam artifacts at arc endpoints (nearest/aligned seam) — workaround: use random seam
  • PRP filtering by actual OW nozzle — deferred until accumulator layer height sync implemented
  • Accumulator layer height sync (NozzleLayerPlanner) — not yet started
  • Drag-and-drop STL after fresh install fails — workaround: run OrcaSlicer once; fix: investigate installer.nsi shell registration
  • Dark mode nozzle color bars — not yet tested
  • Thick internal bridges: layer-height-aware clamp on bridge extrusion height (upstream Orca behavior, extreme under mixed nozzles — see fos.8.2 known issue)
  • Support layer height clamp for large nozzles — support still follows nozzle 1's layer height; a 0.8mm nozzle can be asked to print 0.1mm support layers (new feature, deferred — see fos.8.4 known issue)
  • Ramming-to-wipe Y alignment uses tower structure width — up to ~0.4mm off with per-tool widths; cosmetic (see fos.8.4 known issue)
  • outer_wall_loops: no hard UI cap (label turns red as warning — accepted)
  • CL slider max hardcoded to 10 in UI — increase if needed
  • Per-object CL not exposed in Objects tab — only via gizmo
  • Toolhead UID identity system — each toolhead needs factory-burned UID; blocked by hardware; software architecture planned (local registry JSON, PTP stores UID arrangement)
  • Per-nozzle hardened steel toggle — allow marking individual nozzles as HS vs standard
2026年5月15日 — 开发启动
初始化
架构与代码库决策
设计目标、硬件约束、代码库选择
重大决策
  • wall[0] = 0.2mm 外墙挤出口;wall[1+] = 0.4mm 内壁挤出口
  • 颜色或材料不同时最少 2 条总墙壁;否则允许 1 条
  • 仅在 wall[0] 和 wall[1] 之间进行坡度补偿间隙
  • 累加器同步:当 0.2 层累计高度超过 0.4 挤出口最大层高时触发 0.4 挤出口
  • 颜色到挤出口优先级:颜色匹配 → 最小挤出口=外墙 → 挤出机 0 兜底
  • 颜色深度:最小值=挤出口直径;薄截面=完全渗透或 50/50 分割
  • 每区域挤出口覆盖:global=YES 遵循混合逻辑;global=NO 单挤出口拥有整个区域
  • 优先级层次已锁定:CL(着色区域循环数)> OW(外墙)> IW(内壁)
  • 最初基于 OrcaSlicer-FullSpectrum 分支,后因 Filament for Features UI 更清晰改用 Snapmaker Orca
  • FullSpectrum 存在 resolved_model_config_for_tab 过滤 sparse_infill_filament 的问题,无法干净修复
  • 需要 VS2019 · CMake 3.29 · 创建脚本:build_and_run.bat · build.ps1 含计时功能
  • 系统路径中的 Strawberry Perl 会导致 CMake 版本冲突 — 修复:覆盖系统路径
  • DLL 策略:从已安装的 Snapmaker_Orca\ 复制所有文件,除了 Snapmaker_Orca.dll
  • PrintConfig.hpp 必须为 UTF-8(非 ANSI)— 第一行有 // -*- coding: utf-8 -*-;在 VS2019 编辑后需运行编码修复脚本
  • 禁止对 PrintConfig.cpp 运行编码转换 — ANSI→UTF-8 会损坏中文字符
第一阶段 — 配置方案与流水线框架(ALH=自适应层高)
第1a阶段
新文件 — NozzleLayerPlanner、ColorDepthProcessor、NozzleVerifyDialog
核心逻辑类,全部构建成功
  • SyncPoint 结构体:z、accumulated_height、accumulated_layers、gap_offset、expanding、outer_wall_extruder
  • plan() 消耗 ALH 层高数据,生成同步点
  • 二分查找:sync_point_at()、outer_wall_extruder_at()、gap_offset_at()
  • apply_depth():逐层 ClipperUtils 偏移 — 已构建但尚未正确连接(见第五阶段)
  • detect_thin_sections():存根 · ThinSectionBehavior:BleedThrough | Split50_50
  • inward_offset():使用 scaled() 进行正确的 Clipper 单位转换
  • 显示工作头 T1–T4 挤出口尺寸、材料名称、颜色色块
  • 验证按钮连接至 MainFrame 打印按钮门控 · 重新切片时调用 update_extruders()
里程碑
  • 3 个文件均已注册至 CMakeLists,构建零错误
第1b–d阶段
PrintConfig 字段 + 流水线钩子 + Tab UI
新配置字段、PrintObject、PerimeterGenerator、多材料标签页
  • outer_wall_loops (coInt) · outer_wall_layer_height_max (coFloat) · outer_wall_seam_position (coEnum)
  • has_mixed_nozzle_sizes (coBool) · filament_color_depth (coFloat,占位符)
  • 多材料标签页中新增"混合挤出口"选项组:outer_wall_loops、outer_wall_layer_height_max、outer_wall_seam_position
里程碑
  • 应用启动 · 多材料标签稳定 · 混合挤出口组可见 · 切片无错误
第二阶段 — 自动检测 · 2026年5月16日
第2阶段
PresetBundle 自动检测
has_mixed_nozzle_sizes 自动从 nozzle_diameter 向量计算
  • 在 full_fff_config() 末尾计算 has_mixed_nozzle_sizes:任意值与 values[0] 差值 > 1e-4 → mixed=true
  • 永远不由用户设置 — 始终在配置组装时推导
  • 混合模式下 outer_wall_loops 封顶为 wall_loops(带空值检查)
里程碑
  • 混合挤出口自动检测 · 切片时 outer_wall_loops 自动封顶
第三阶段 — 代码库切换 + 挤出口验证对话框 + 打印门控 — 5月16–17日
第3a阶段
从 FullSpectrum 切换至 Snapmaker Orca 分支
更干净的基础,Filament for Features 已可用
重大决策
  • FullSpectrum 存在无法解决的 resolved_model_config_for_tab 过滤问题 — sparse_infill_filament 无法在多材料标签中显示
  • Snapmaker Orca 是实际目标硬件 — 更干净的起点
  • DLL 变通方案:使用已安装的 Snapmaker_Orca DLL,除了 Snapmaker_Orca.dll → 保存至 %USERPROFILE%\SnapmakerOrcaDLLs\
第3b阶段
NozzleVerifyDialog 完整连接 + 打印门控
切片后显示对话框,打印按钮需验证后才可用
  • on_slicing_completed() 中启用混合挤出口检测
  • 耗材颜色从 full_config() filament_colour 向量读取(按值存储,避免悬空指针)
  • 重新切片时调用 update_extruders() 刷新材料名称和颜色
  • get_enable_print_status():添加挤出口验证门控 · get_enable_slice_status():不添加门控
里程碑
  • 对话框显示工作头 T1–T4 挤出口尺寸、材料名称、耗材颜色 · 打印按钮需点击验证后才可用
第四阶段 — OW/IW 挤出路径分离 + UI + 逐挤出口线宽 — 5月17日 ✅(OW=外墙,IW=内壁)
第4a阶段
inner_wall_filament 字段 + UI
内壁新增挤出机选择器,下拉列表已注册
  • inner_wall_filament (coInt):gui_type=i_enum_open,min=1,默认值=1 · wall_loops 标签 → "All walls" · wall_filament 标签 → "Outer wall"
  • 添加至 Filament for Features 组 · 注册至 dynamic_filament_list_1_based · 添加至 toggle_line 循环
  • fdm_process_U1.json:"inner_wall_filament":"2"
里程碑
  • 内壁下拉列表显示挤出机列表 · 默认挤出机 2
第4b–c阶段
PrintRegion 挤出路径规划 + GCode.cpp 周界分割
frExternalPerimeter → wall_filament · frPerimeter → inner_wall_filament
  • extruder():frExternalPerimeter → wall_filament;frPerimeter → inner_wall_filament
  • 仅在该层存在实际 erPerimeter 实体时才添加 inner_wall_filament — 防止锥顶处产生空换料动作
  • 若为 PERIMETERS 且内外壁耗材不同且 can_sort():erExternalPerimeter → 外墙挤出机;erPerimeter → 内壁挤出机
  • 直接推入 reg.perimeters — 避免拥有临时集合(堆损坏修复)
里程碑
  • 外墙白色(挤出机 1),内壁黄色(挤出机 2) · 无崩溃
第4d–f阶段
OW>1、逐挤出口线宽、UI 润色
outer_wall_loops 驱动循环标记 · 线宽按挤出机出口直径重新缩放
  • classic 路径:is_external = loop.depth < std::max(1, outer_wall_loops)
  • Arachne 路径:is_external = (int)extrusion->inset_idx < std::max(1, outer_wall_loops)
  • PrintRegion.cpp flow():比例 = config_width / nozzle[0];重新缩放 = 比例 × 实际挤出口直径
  • fdm_process_U1.json:所有线宽转换为百分比格式
  • outer_wall_loops 标签当值 > wall_loops 时变红(与橙色"已修改"状态有区别)
  • U1 挤出机出口直径:同步对话框已移除;每个挤出口独立写入 nozzle_diameter[i]
里程碑
  • 颜色色块与侧边栏匹配 · 每个挤出口可独立设置 · outer_wall_loops 标签验证正常
第五阶段 — 着色区域流水线 — 5月18–28日 ✅ 已完成
第5a阶段
着色区域 — 凸面与凹面均已正确工作 ✅
已提交 ba3d32bc68 — 涂色面单条红色循环,内核正确
  • 平面(立方体):涂色面仅 1 条红线,内部白色循环+填充正确 ✅
  • 凸曲面(球体):正确 ✅
  • 凹面(环形内孔):正确单条弧线 ✅ — 修复方案:用 region slices 构建 full_model(layer.lslices 在分割时为空)
  • 相邻涂色面(同挤出口):连续红色,无白色接缝 ✅
  • MMU 工具面板中的 CL 滑块 UI ✅
  • GCode 已验证:T3 每层打印正确循环数 ✅
  • Layer.hpp:color_patch_regions(vector)— 每挤出口每层的壳带
  • PrintObjectSlice.cpp apply_mm_segmentation():shell_strip = intersection(full_model 边界带, stolen_expanded);内核归还至基础区域
  • PerimeterGenerator:loop_number 覆盖为 patch_loops-1;is_color_patch 抑制填充
  • color_patch_loops 默认值:{1,1,1,1}(适配 U1 四工作头)
第5b阶段
着色区域流水线 — 已完成 ✅
所有 CL 值、所有面类型、逐对象设置、原始模式已验证
  • 原始模式:GCode 输出与原版 OrcaSlicer 完全一致 — 已通过 GCode 对比验证 ✅
  • 着色区域模式:外层 N 条循环以涂色耗材打印;内壁循环 + 填充使用基础耗材 ✅
  • 全包裹,所有 CL 值:CL=1 至 10+ 均正确 ✅
  • 局部涂色,所有 CL 值:CL=1 至 10+ 均正确,包括奇数 CL ✅
  • 混合挤出口尺寸:每个涂色挤出口使用各自的流量参数计算循环宽度 ✅
  • 逐对象 CL:打印板上每个实体可独立设置各挤出口的 CL 值 ✅
  • 填充抑制:着色区域内无填充 — 仅打印 CL 圈 ✅
  • 多涂色:T2、T3、T4 以不同 CL 涂在同一实体上均正常 ✅
  • 工程文件持久化:逐对象 CL 设置可随 3MF 正确保存/读取 ✅
  • T1(黑色)= 基础耗材,即模型区域的 wall_filament
  • T2/T3/T4 = 涂色耗材 — 用户在 T1 实体上涂色
  • color_patch_enabled[ext_idx]=false → 原始模式(直通原版流水线)
  • color_patch_enabled[ext_idx]=true 且 color_patch_loops[ext_idx]>0 → 着色区域模式
  • color_patch_loops 默认值已改为 {0,0,0,0} — 原始模式为默认行为
里程碑
第5c阶段
最终架构实现
关键文件、逐对象 CL 流程、循环宽度公式、配置传递
  • color_patch_loops:GCodeConfig 中的 coInts,默认 {0,0,0,0}
  • color_patch_enabled:GCodeConfig 中的 coBools,默认 {false,false,false,false}
  • 两者均加入 posSlice 失效(Print.cpp)和 posPerimeters 失效(PrintObject.cpp)
  • 门控:if (cp_enabled && patch_loops > 0) — 两者均为真时才走 FOS 流水线
  • 逐对象读取:优先读取 model_object->config,否则回退至全局 print()->config()
  • 循环宽度 = ext_width/2 + (CL>1 ? ext_sp2 : 0) + (CL>2 ? (CL-2)×perim_sp : 0) + perim_width/2
  • 使用 target_lr(涂色挤出口的区域)获取流量参数 — 支持混合挤出口尺寸
  • shell_strip 分别存入 color_patch_regions[ext_idx] 和 color_patch_loops_effective[ext_idx]
  • mine 减法:仅减去 shell_strip(非完整 stolen)— T1 保留内部区域
  • color_patch_regions:vector — 每挤出口的壳带
  • color_patch_loops_effective:vector — 每挤出口经逐对象解析后的 CL 值
  • loop_number = patch_loops-1,在所有调整之后设置(覆盖 alternate_extra_wall 等)
  • last 覆盖为 color_patch_regions[wall_ext] 裁剪至当前面
  • 内壁偏移使用 offset_ex(非 offset2_ex)— 防止薄楔形坍塌
  • 着色区域跳过间隙检测(is_cp_region_gap)
  • 着色区域抑制填充(is_cp_region)
  • 所有 CL 圈通过 cp_loops lambda 获得 erExternalPerimeter 角色
  • 强制使用 process_classic(非 Arachne)处理着色区域
  • inner_wall_filament 已加入涂色区域配置赋值 — 原先缺失,导致内壁打印为黑色
  • cfg.wall_filament、cfg.inner_wall_filament、cfg.solid_infill_filament、cfg.sparse_infill_filament 均设置为 painted_extruder_id
  • 原始按钮(左):将 color_patch_enabled[ext_idx]=false 写入 model_object->config
  • 着色区域按钮(右):将 color_patch_enabled[ext_idx]=true 写入,并确保 color_patch_loops > 0
  • CL 滑块读写 model_object->config 中的 color_patch_loops[ext_idx]
  • on_config_change 必须传入完整预设配置 — 传局部配置无法正确传递
  • 重新切片通过 wxGetApp().plater()->update() 触发
第六阶段 — 应用重命名与品牌更新 — 5月28日 ✅
第6阶段
重命名为 FOrcaSlicer — Flexible OrcaSlicer + Fork-a-Slicer
可执行文件、DLL、安装程序、图标、Web UI、启动画面、README 全部更新 · v2.3.2-fos.1 已发布
重大决策
  • version.inc:SLIC3R_APP_NAME "FOrcaSlicer" · SLIC3R_APP_KEY 必须保留 "Snapmaker_Orca" — 翻译 .mo 文件以 Snapmaker_Orca.mo 命名,更改会导致崩溃
  • src/CMakeLists.txt:OUTPUT_NAME "FOrcaSlicer"(可执行文件)· OUTPUT_NAME "FOrcaSlicer-core"(DLL)— DLL 命名为 -core 以避免 CMake 链接器与可执行文件基础名冲突
  • src/Snapmaker_Orca_app_msvc.cpp:加载 FOrcaSlicer-core.dll
  • src/slic3r/GUI/Plater.cpp + GUI_App.cpp:标题栏已更新 · 跳过启动向导(需要 Snapmaker 账号登录)
  • installer.nsi:所有引用已更新 · VIProductVersion "2.3.2.1"(Windows 资源格式仅支持整数)
  • src/FOrcaSlicer.ico:由 FOrcaSlicer_Icon.png 生成的 7 尺寸 ICO 文件
  • resources/web + images:启动画面 SVG、logo.png、Flutter Web 标题、引导页欢迎文字全部更新
  • 版本号:2.3.2-fos.1 — 基于 Snapmaker OrcaSlicer 提交 48c601c3b1(其未发布的 2.3.2 开发分支)
  • version.inc 中的版本正则仅解析 X.X.X — -fos.1 后缀在运行时被安全忽略
  • 构建:cmake --build . --config Release --target ALL_BUILD -- -m
  • 安装:cmake --build . --config Release --target INSTALL -- -m
  • 安装程序:makensis.exe /DVERSION=2.3.2-fos.1 installer.nsi
  • 便携版:Compress-Archive -Path build\FOrcaSlicer\* -DestinationPath FOrcaSlicer_Windows_Portable_V2.3.2-fos.1.zip
  • PerimeterGenerator.cpp 必须以 UTF-8(无 BOM)LF 换行保存 — 否则编码检查器以退出码 -2 报错
里程碑
  • GitHub 发布 v2.3.2 · FOrcaSlicer_Windows_Installer_V2.3.2-fos.1.exe + 便携版压缩包
第七阶段 — 着色区域顶/底面实心填充 — 5月28–30日 ✅ 已完成
第7a阶段
着色区域顶/底面实心填充 — 设计与迭代
将着色区域流水线扩展至涂色顶/底面 — 以涂色耗材打印实心填充
重大决策
  • 侧壁:仅 CL 圈(shell_strip)— 现有行为不变
  • 涂色区域结束处的顶/底层:涂色挤出口获得完整 stolen 区域,按原始模式切片(自然壁圈 + 实心填充)
  • 检测方式:检查实体顶/底壳区域边界是否与涂色区域结束处重合 — 每层单次交集检查
  • 基础挤出口:顶/底层减去完整 stolen;侧壁层仅减去 shell_strip
  • discover_vertical_shells 阶梯化:若涂色挤出口 dst 在顶/底层使用完整 stolen,discover_vertical_shells 会将较大区域向下传播至壁层,产生阶梯伪影。修复:涂色挤出口 dst 始终使用 shell_strip;实心填充来自 fill_expolygons 覆盖完整暴露区域
  • fill_expolygons 裁剪:Layer.cpp 多区域路径中 fill_surfaces 被 fill_expolygons 裁剪。若 fill_expolygons 仅为 shell_strip,实心填充面会被裁掉。修复:顶/底 CP 层的 dst 包含 stolen 的暴露顶/底部分,使 fill_expolygons 覆盖完整顶面
  • 暴露区域分割:涂色区域同时跨越顶面和侧面(穿越边缘)的层,stolen 被分割 — 暴露部分获得完整 dst;侧面部分仅获得 shell_strip
  • CL 滑块触发重新切片修复:wxGetApp().plater()->update() 单独调用不会标记实体为脏数据。修复:同时调用 notify_instance_update(obj_idx, 0) + post_event(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)
  • ❌ 仅凭 top_shell_layers / bottom_shell_layers 配置检测顶/底 → 局部侧面涂色时误报
  • ❌ 跨层面积/形状一致性检查 → 速度太慢,并行循环中出现内存溢出错误
  • ❌ 从当前层向上/向下扫描 → 同样有内存溢出风险;已替换为单次边界交集检查
  • fos_near_obj_top = (layer_id >= total_layers - top_shells)
  • fos_near_obj_bottom = (layer_id < bot_shells)
  • 若接近边界:检查该边界层的 stolen 是否与实体边界相交
  • 利用上/下层的分割数据将 stolen 拆分为暴露部分与侧面部分
第7b阶段
最终状态 — 随 v2.3.2-fos.1 发布
壁层 + 顶/底层行为均正确 · 已知限制已记录
  • 涂色挤出口获得 shell_strip → PerimeterGenerator 仅生成 CL 圈
  • color_patch_regions 存储 shell_strip 供 PerimeterGenerator 使用
  • 基础挤出口从 mine 中减去 shell_strip → 保留涂色区域内部
  • 涂色挤出口获得 stolen 的暴露部分作为 dst → fill_expolygons 覆盖完整顶/底面
  • color_patch_regions 不填充 → PerimeterGenerator 按原始模式运行(自然壁圈 + 实心填充)
  • color_patch_is_top_bottom 标志 → PerimeterGenerator 抑制稀疏填充,允许实心填充
  • 基础挤出口从 mine 中减去完整 segmented.expolygons → 让出完整涂色区域
  • PrintObjectSlice.cpp:着色区域流水线、顶/底检测、mine 减法、dst 赋值
  • PerimeterGenerator.cpp:CP 区域填充抑制、顶/底实心填充、top_fills 防护
  • PerimeterGenerator.hpp + Layer.hpp:新增 color_patch_is_top_bottom 指针/向量
  • LayerRegion.cpp:将 color_patch_is_top_bottom 传递至 PerimeterGenerator
  • 曲面(球体、锥体、圆柱):着色区域按原始楔形方式处理 — 回归原因未知,暂不调查;U1 主要使用场景为平面实体,优先级较低
  • UI 同步问题:偶尔需要切换"原始 → 着色区域"才能同步配置;发生频率低,变通方法简单
  • 顶/底壳边界阶梯:与原版 OrcaSlicer 多材料 discover_vertical_shells 传播行为一致 — 非回归,已接受
里程碑
第7c阶段
CL 滑块 / 模式按钮触发重新切片修复 — 5月30日 ✅
发现并修复两处根本原因 — 配置时间戳 + 失效路径
  • ModelObject::config.set_key_value() 不会更新配置时间戳 → PrintApply.cpp 检测不到变化 → 返回 APPLY_STATUS_UNCHANGED → 不触发重新切片
  • color_patch_loops 和 color_patch_enabled 存储在 ModelObject::config(逐对象 DynamicConfig)中,但不是 PrintObjectConfig 的字段 → 即使时间戳更新,object_config_from_model_object 也不会将其纳入传递给 invalidate_state_by_config_options 的差异集
  • GLGizmoMmuSegmentation.cpp:在每次对 color_patch 键调用 set_key_value 后调用 mo->config.touch() — 共 5 处:原始按钮、着色区域按钮 ×2、CL 滑块、拖拽输入
  • PrintApply.cpp:当 object_config_changed 为真时,手动检查逐对象 DynamicConfig 中 color_patch_loops 或 color_patch_enabled 是否发生变化,并在调用 invalidate_state_by_config_options 前将其追加至差异集
  • PrintObject.cpp:将 color_patch 键的失效步骤从 posPerimeters 改为 posSlice — 仅周界失效在已失效时返回 false,阻止了重新切片的触发
  • GLGizmoMmuSegmentation.cpp · PrintApply.cpp · PrintObject.cpp
里程碑
  • CL 滑块与模式按钮的更改现可可靠地触发重新切片
第7d阶段
混合挤出口 + 仅外壳着色热修复 — 5月30日 ✅
三项修复随 v2.3.2-fos.1 发布
  • has_mixed_nozzle_sizes 标志已定义但从未计算 — 始终为 false
  • 在 normalize_fdm_1() 中新增计算:遍历 nozzle_diameter 值,若任意值与第一个不同则将标志设为 true
  • 此修复激活了 PerimeterGenerator 中第二阶段的代码路径,使其读取混合挤出口几何结构的 outer_wall_loops
  • 着色区域 CL=1(loop_number=0)时,i0_half_width 使用 perimeter_spacing/2 — 对大挤出口(0.6/0.8mm)而言偏移量过大,内缩超出 shell_strip 宽度 → 偏移结果为空 → 着色区域缺失
  • 修复:CL=1 时使用 ext_perimeter_width/2,CL≥2 时使用 perimeter_spacing/2。条件:(is_cp_region && loop_number > 0)
  • 当所选涂色挤出口为外墙挤出口时,仅外壳着色模式无意义 — 仅会复制外墙行为
  • 防护:将 m_selected_extruder_idx 与打印配置中的 wall_filament-1 比对。若匹配:自动切换至原始模式,仅外壳着色按钮视觉禁用,提示说明原因
  • 注意:此 ImGui 版本不支持 ImGui::BeginDisabled — 改用样式颜色覆盖 + && !is_ow_extruder 条件实现
  • 新建实体上偶发的仅外壳着色配置同步问题(预先存在)— 切换"原始 → 仅外壳着色"可解决
  • 混合挤出口 + 仅外壳着色在多功能挤出口(OW/IW/填充各不同)场景下尚未完整验证 — 当前建议作为独立工作流处理
里程碑
  • 三项修复均随 v2.3.2-fos.1 发布
第八阶段 — 外部预设崩溃修复 + 混合挤出口完善 — 5月31日 ✅ · v2.3.2-fos.2 已发布
第8a阶段
外部预设崩溃修复 — 7 项问题已解决
使用 Bambu/通用预设全新安装不再崩溃 · 系统预设自动同步已禁用
  • 全新安装从 Snapmaker 服务器下载 2.3.14 预设 — 与 FOrcaSlicer 2.3.2 代码库不兼容
  • 外部预设中缺少 FOrcaSlicer 专属配置选项 → UI 读取时产生空指针崩溃
  • 症状:在外部预设下点击多材料标签页时,DynamicConfig::opt_int() 发生访问违规崩溃
  • Preset.cpp Preset::normalize():在 handle_legacy_sla() 前为 outer_wall_loops、outer_wall_layer_height_max、outer_wall_seam_position、color_patch_loops、color_patch_enabled、inner_wall_filament 添加默认值
  • Tab.cpp TabPrint::reload_config():在 Tab::reload_config() 前添加相同的默认值
  • OptionsGroup.cpp get_config_value() + get_config_value2():为所有 coInt/coBool/coInts/coBools/coEnum/coEnums 分支添加空值防护 — 返回 0/false 而非崩溃
  • 症状:添加 Snapmaker U1 时,Sidebar::on_filaments_change() 在 choices.back() 处访问违规崩溃
  • 根本原因:侧边栏耗材下拉框初始化前被调用时 choices 向量为空
  • 修复:int last_selection = choices.empty() ? 0 : choices.back()->GetSelection()
  • 症状:未选择实体时点击涂色工具崩溃
  • 修复:所有 4 处 FOS 新增的 notify_instance_update 块中,对 mo_notify 添加空值检查后再访问模型对象
  • 症状:0.2mm 挤出口第二次启动时切片失败,提示"线宽过小(skin_infill_line_width)"
  • 根本原因:validate_extrusion_width 中 extrusion_width_min <= layer_height 在两者均为 0.2mm 时触发 — <= 应改为 <
  • 症状:加载 2.3.14 预设时启动崩溃,Unknown option exception: internal_solid_infill_acceleration
  • 根本原因:该选项在 GCode.cpp 和 Print.cpp 中使用但从未注册;同时存在复制粘贴错误,skeleton_infill_line_width 被赋予了错误的标签和提示文字
  • 修复:将 internal_solid_infill_acceleration 注册为独立 coFloatOrPercent 条目;恢复 skeleton_infill_line_width 的正确标签和提示文字
  • sync_system_preset 默认值从 true 改为 false — 防止全新安装时下载不兼容的 2.3.14 预设
  • 快捷方式从 "Snapmaker Orca.lnk" 重命名为 "FOrcaSlicer.lnk"
里程碑
  • 使用任意打印机预设全新安装不再崩溃 · 系统预设自动同步默认禁用
第8b阶段
混合挤出口 + 仅外壳着色完善
SLIC3R_APP_KEY 重命名 · inner_wall_filament 修复 · CL=1 大挤出口精细化
  • 文件:src/common_func/common_func.hpp + version.inc
  • 应用数据现存储于 %APPDATA%\FOrcaSlicer\ 而非 %APPDATA%\Snapmaker_Orca\
  • 所有语言 .mo 文件已复制为 FOrcaSlicer.mo — 无需额外操作
  • 制作安装程序前的预备步骤:将所有语言目录中的 Snapmaker_Orca.mo 复制为 FOrcaSlicer.mo
  • 缺少 def->mode = comAdvanced 和 def->set_default_value(new ConfigOptionInt(1)) — 导致首次启动时内壁下拉框显示异常
  • 已补充两项,与 wall_filament 定义保持一致
  • 对第7d阶段修复的进一步优化:CL=1 着色区域的 i0_half_width 改用 ext_perimeter_spacing/2(而非 ext_perimeter_width/2)
  • spacing 略小于 width — 使 CL=1 单圈在所有层高下均能在 shell_strip 内正确生成,适用于大挤出口(0.6mm、0.8mm)
  • 条件:(is_cp_region && loop_number > 0) ? perimeter_spacing/2 : (is_cp_region ? ext_perimeter_spacing/2 : ext_perimeter_width/2)
  • 混用不同尺寸挤出口时仅外壳着色产生 FlowErrorNegativeSpacing(如 T1=0.2mm,T3=0.6mm)— 根本原因尚未诊断;排查方向:PrintObjectSlice.cpp shell_strip 计算 + LayerRegion.cpp 流量设置
  • 绝对首次启动时内壁下拉框可能显示异常 — 点击可刷新;根本原因是 dynamic_filament_list_1_based 在标签页控件首次创建时尚未填充
  • outer_wall_layer_height_max 字段已在 UI 中显示,但 NozzleLayerPlanner 累加器逻辑尚未实现 — 该字段目前对切片无效
第8c阶段
top_surface_line_width .3mf 读取修复 + v2.3.2-fos.2 发布
百分比线宽从 .3mf 读取时变为绝对 mm 值 · fos.2 包含第八阶段全部修复
  • 症状:重新打开已保存的 .3mf 文件时,top_surface_line_width 读取为 105mm 而非 105%
  • 根本原因:线宽值序列化/反序列化至 .3mf 时百分比标志未被保留 — 以浮点数存储,% 标记丢失
  • 仅外壳着色且CL=1时,若接缝位置设为"最近"或"对齐",弧线端点处出现较大缺口 — 使用随机接缝位置可避免
  • 涂色顶/底面边界遵循原版 OrcaSlicer 壳布线逻辑 — 角分线(法线方向)分割方案计划在后续版本实现
  • 混合挤出口第二阶段(每挤出口独立周界生成器)尚未实现 — 预设中线宽仍使用绝对 mm 值
  • 绝对首次启动时内壁下拉框可能显示异常 — 点击可刷新
  • 动态调整内壁层高从而同步外墙层高尚未实现
里程碑
  • v2.3.2-fos.2 已发布 · FOrcaSlicer_Windows_Installer_V2.3.2-fos.2.exe + 便携版压缩包
第九阶段 — 挤出口面板重设计 + 混合挤出口间距 + 稳定性 — 6月3日 · v2.3.2-fos.3/fos.4
第9a阶段
混合挤出口 OW/IW 间距修复 + 流量封顶 + 稳定性
正确的外墙至内壁过渡间距 · Flow.cpp 崩溃防护 · 空值防护
  • 外墙循环现使用 ext_perimeter_spacing,内壁循环使用 perimeter_spacing
  • 外墙至内壁边界的正确过渡间距:0.5 × (ext_perimeter_spacing + perimeter_spacing)
  • 仅当 wall_filament != inner_wall_filament 时激活(fos_ow_iw_split 标志)
  • with_spacing() 在宽度 < 高度时现改为封顶而非抛出异常
  • 防止仅外壳着色 + 混合挤出口在较高层高时崩溃
  • 源文件中 top_surface_line_width 修正为 "105%"(原为 "105",缺少 % 标记)
  • 注意:百分比线宽会破坏混合挤出口流量计算 — U1 打印流程预设中所有线宽保留为绝对 mm 值
  • Tab.cpp:outer_wall_loops 与 wall_loops 的颜色比较现对空配置选项添加防护
  • OptionsGroup.cpp:在 get_config_value 的 coInt 分支中添加额外空值防护
  • Tab.cpp:outer_wall_layer_height_max 选项行及切换已注释隐藏 — 待累加器同步实现后恢复
里程碑
  • v2.3.2-fos.3 随间距修复和流量封顶一同发布
第9b阶段
挤出口直径面板重设计(fos.4)
两行两列 UI · 混直径模式切换 · 自动检测 · 打印流程预设刷新链
重大决策
  • 打印机预设简称 PTP · 打印流程预设简称 PRP
  • 两行两列布局取代原标签页,与耗材面板样式一致
  • 混直径模式切换开关(SwitchButton,默认关闭)— 加载 PTP 时若各挤出口直径不同则自动启用混合模式
  • 每行颜色条:0.2mm=白色,0.4mm=10%灰,0.6mm=20%灰,0.8mm=30%灰 · 标签格式:"N: X.Xmm"
  • 打印流程预设下拉框上方新增提示文字:打印流程预设按挤出口 1 直径过滤,您设置的层高决定实际切片结果
  • 更改任意挤出口 → 确认对话框(同步/取消)→ 所有挤出口同步至所选直径 → 切换至匹配系统预设 → 打印流程预设刷新
  • 各挤出口独立 — 外墙挤出口变更时更新自定义 PTP 的继承关系 → 打印流程预设刷新
  • 在各挤出口直径不同时关闭混合模式 → 确认 → 全部同步至挤出口 1 → 切换系统预设
  • update_nozzle_settings(switch_machine=true) → 从 wall_filament 读取外墙挤出口索引 → 更新 inherits → update_compatible(Always) → CallAfter → update_presets + load_current_preset
  • is_compatible_with_parent_printer() checks active_printer.preset.inherits() against compatible_printers list
  • 自定义 PTP 需要正确的 inherits:根据外墙挤出口直径设为 "Snapmaker U1 (X.X nozzle)"
  • 打印流程预设按挤出口 1 直径过滤,而非实际外墙挤出口 — 延迟至累加器层高同步实现后处理
  • 深色模式:挤出口颜色条(白/灰调)尚未测试 — 可能需要颜色反转
里程碑
  • v2.3.2-fos.4 已发布 · installer.nsi VIProductVersion 2.3.2.4
第十阶段 — 外墙挤出口 → 模型颜色同步 + 涂色擦除警告 — 6月4日 · v2.3.2-fos.5
第10a阶段
挤出口直径标题图标
新增 nozzle_diameter.svg · Plater.cpp 中接入 ScalableButton
  • 新增 nozzle_diameter.svg 至 resources/images/ — 灰色顶部弧线、绿色侧线、绿色描边外椭圆、绿色填充内椭圆代表挤出口尖端
  • SVG:width="16" height="16" viewBox="0 0 16 16",内联属性,stroke-width="0.9"
  • Plater.cpp:取消注释 nozzle_icon ScalableButton,图标键更新为 "nozzle_diameter",接入 nozzle_title_sizer
第10b阶段
外墙挤出口 → 模型预览颜色同步
3D 视口和涂色工具现正确反映外墙挤出口颜色
  • GL 体积颜色:vol->extruder_id → filament_colour[extruder_id-1];未涂色实体的 extruder_id 始终为 1
  • simple_render(切片后的壳体/缩略图)将 model_volume->extruder_id() 硬编码为基础颜色索引
  • 涂色工具 ebt_colors[0](未涂色面的基础颜色)同样使用 mv->extruder_id()-1
  • 重大决策:禁止设置 mo->config["extruder"] — 会通过 object_config_from_model_object 覆盖各功能耗材分配,破坏切片。颜色覆盖仅为纯视觉效果。
  • Tab.cpp on_value_change:wall_filament 变更时,通过 CallAfter 直接修补所有非修改体 GL 体积的 vol->extruder_id → update_volumes_colors_by_extruder(),不修改 mo->config
  • GLCanvas3D.cpp reload_scene:update_volumes_colors_by_extruder() 后重新读取 wall_filament,若 OW > 1 则重新应用 extruder_id 覆盖 — 确保颜色在每次 reload_scene 后持久保留
  • Tab.cpp · GLCanvas3D.cpp · Plater.cpp · resources/images/nozzle_diameter.svg
里程碑
  • v2.3.2-fos.5 已发布 · VIProductVersion 2.3.2.5
第10c阶段
外墙挤出口变更时的多材料涂色擦除警告
已实现但暂时注释 — 待测试后启用
  • wall_filament 变更时:检查所有实体上 mv->is_mm_painted()
  • 若有涂色:弹出 MessageDialog — "更改外墙挤出口将擦除所有实体上的多材料涂色。"
  • 确认:对所有模型部件体积调用 mmu_segmentation_facets.reset() → plater()->update()
  • 取消:从 get_selected_preset().config 读取旧值,通过 m_config_manipulation.apply() 还原,提前返回 — 下拉框恢复原值
  • 当前已注释 — 启用前需更多测试
第十一阶段 — 外墙颜色全路径修复 + 右键旋转 + 启动修复 — 6月4日 · v2.3.2-fos.6
第11a阶段
外墙颜色覆盖 — 所有渲染路径完整修复
3DScene.cpp 核心修复 · 涂色工具基础颜色 · 缩略图渲染 · data_changed 追踪
  • simple_render(壳体/缩略图)仍将 extruder_id() 硬编码为基础颜色
  • 涂色工具 ebt_colors[0] 使用 mv->extruder_id()-1,而非外墙挤出口
  • 3DScene.cpp update_colors_by_extruder:从配置读取 wall_filament,OW > 1 时对所有模型体积使用外墙挤出口索引
  • 3DScene.cpp simple_render:新增 ow_extruder 参数;idx==0 基础颜色在 OW > 1 时使用外墙挤出口
  • 3DScene.cpp render / render_with_outline:向 simple_render 传入外墙挤出口
  • GLCanvas3D.cpp render_thumbnail_internal:向 simple_render 传入外墙挤出口(拾取路径与非拾取路径均处理)
  • GLGizmoMmuSegmentation.cpp init_model_triangle_selectors:ebt_colors[0] 使用外墙挤出口颜色作为基础
  • GLGizmoMmuSegmentation.cpp update_triangle_selectors_colors + data_changed:静态变量 last_ow_ext_idx 追踪 wall_filament 变更,变更时触发颜色更新
里程碑
  • 模型颜色、缩略图和涂色工具基础颜色均正确反映外墙挤出口分配
第11b阶段
右键拖拽旋转视图 + 启动修复 + 打印流程预设保存后刷新
右键拖拽 → 旋转 · 启动时混直径模式自动检测 · 保存新打印机预设后刷新打印流程预设
  • is_camera_rotate:新增 evt.RightIsDown() — 右键拖拽现与左键拖拽一样执行旋转
  • is_camera_pan:移除右键拖拽 — 平移现仅通过中键执行
  • load_current_presets 调用 update_nozzle_settings() 时未传入 switch_machine=true — 重启后混直径模式自动检测从未触发
  • 修复:update_nozzle_settings() → update_nozzle_settings(true)
  • 保存新 PTP 后,打印流程预设停留在错误的挤出口直径,直到手动重新选择 PTP
  • 修复:save_preset 中 update_presets_from_to 之后,当 m_type == TYPE_PRINTER 时调用 sidebar().update_nozzle_settings(true)
里程碑
  • v2.3.2-fos.6 已发布 · VIProductVersion 2.3.2.6
第十二阶段 — 挤出口验证面板 + 鼠标配置 + GCode 图例 — 6月5日 · v2.3.2-fos.7
第12a阶段
挤出口验证集成至 GCode 图例面板
浮动对话框替换为耗材图例内嵌面板 · 打印按钮门控保留
重大决策
  • 混合挤出口切片完成后,耗材图例面板顶部(耗材列表上方)出现"挤出口验证"区域
  • 两行网格:上行显示挤出口 1–N 标签,下行显示带颜色矩形的直径值(0.2mm=白色,0.4mm=10%灰,0.6mm=20%灰,0.8mm=30%灰)
  • 绿色"验证挤出口尺寸"按钮 — 点击前打印按钮被锁定;点击后变为灰色"[OK]"状态
  • 重新切片后按钮重置为绿色"验证挤出口尺寸"
  • 浮动 NozzleVerifyDialog 已注释(保留供后续参考)
  • GCodeViewer.cpp — EViewType::ColorPrint case 中的 FOS 挤出口验证块
  • Plater.cpp — verify_nozzle_sizes()、is_nozzle_verify_required()、is_nozzle_verified()、重新切片时重置
  • MainFrame.cpp — reset_nozzle_verified()、set_nozzle_verified_only()
第12b阶段
偏好设置中的鼠标按键分配 + GCode 图例默认值
设置 → 通用:逐按键旋转/平移/不分配 · 耗材视图为默认 · 外墙在线型图例中排首位
重大决策
  • 设置 → 通用("使用自由相机"下方):三行表格 — 左键拖拽 / 中键拖拽 / 右键拖拽,每行含单选按钮:旋转 | 平移 | 不分配
  • 默认值:左键=旋转,中键=平移,右键=旋转
  • 配置键:mouse_left_btn_fn、mouse_middle_btn_fn、mouse_right_btn_fn(值:"rotate" | "pan" | "none")
  • GLCanvas3D.cpp is_camera_rotate + is_camera_pan 读取新配置键
  • ColorPrint 移至 view_type_items 列表首位
  • m_view_type 默认值改为 EViewType::ColorPrint
  • sort_remove_duplicates(m_roles) 后交换 erExternalPerimeter 与 erPerimeter,使外墙显示在内壁之前
里程碑
  • v2.3.2-fos.7 已发布 · VIProductVersion 2.3.2.7
第十三阶段 — 混合挤出口逐槽耗材配置文件(FLP=耗材预设)过滤 — 6月7–8日 · v2.3.2-fos.8 / fos.8.1
第13a阶段
逐挤出口耗材配置文件过滤 — 各槽独立体积流速
挤出口 3/4(0.2mm)现自动选择 0.2mm 耗材配置 · 1.6mm³/s 而非 12mm³/s
重大决策
  • PresetComboBoxes.cpp 耗材列表过滤对所有槽均使用 nozzle_diameter[0]
  • Preset.cpp update_compatible_internal 仅通过父打印机继承关系标记耗材列表兼容性 — 当打印机继承自 0.4mm 父级时,0.2mm 耗材列表被标记为不兼容
  • PresetBundle.cpp update_compatible 自动选择逻辑忽略各槽独立的挤出口直径
  • 别名冲突:"Generic PLA" 与 "Generic PLA @U1 0.2 nozzle" 共享别名 "Generic PLA" — map 键冲突导致显示/选择错误的预设
  • update_compatible_internal:FOS 块在混合挤出口打印机配置中,若 compatible_printers 匹配任意挤出口直径则标记耗材列表为兼容
  • 仅限 TYPE_FILAMENT · 使用 cp->values 的向量副本避免指针失效
  • update_compatible:在 select_other_filament_if_incompatible 门控之后添加无条件 FOS 块
  • 遍历所有耗材槽,将 compatible_printers 与各槽挤出口直径比对
  • 不匹配时:first_compatible lambda 按挤出口匹配(必要)+ 相同别名(+1000)+ 相同材料类型(+10)评分
  • PlaterPresetComboBox::update() 中逐槽显示过滤:按槽挤出口直径 + 打印机型号过滤
  • 混直径模式下 get_preset_name 返回完整预设名称
  • 为缺少挤出口后缀的预设追加 @U1 X.X nozzle — 保持命名一致性
里程碑
  • 槽 1/2(0.4mm):Generic PLA @U1 0.4 nozzle,12mm³/s ✓ · 槽 3/4(0.2mm):自动选择 Generic PLA @U1 0.2 nozzle,1.6mm³/s ✓ · v2.3.2-fos.8 已发布
第13b阶段
热修复 — FLP 下拉框锁死 + 预设重新选择时崩溃 · v2.3.2-fos.8.1
fos.8 get_preset_name 构造的虚假名称导致 0.4mm 槽下拉框卡死 · 空指针崩溃已修复
  • fos.8 get_preset_name 构造了"Generic PLA @U1 0.4 nozzle"虚假名称 — 预设包中实际不存在该名称;破坏 system_presets 的 map 键匹配 → drop_down 状态卡死,ComboBox 无法重新打开。0.2mm 槽不受影响,因为"Generic PLA @U1 0.2 nozzle"是真实存在的预设名称。
  • DynamicFilamentList::update 和 DynamicFilamentList1Based::update 调用 find_preset()->get_filament_type() 时缺少空值检查 — 预设选择状态切换期间 find_preset() 返回 nullptr 导致崩溃
  • on_select_preset 中 combo->update() 在选择事件期间同步调用 — 可能导致下拉框状态损坏
  • DynamicFilamentList::update 和 DynamicFilamentList1Based::update 中添加空值防护 — 调用 get_filament_type() 前先检查 find_preset() 返回值
  • on_select_preset 中通过 wxTheApp->CallAfter 延迟执行 combo->update() — 避免选择事件处理期间损坏下拉框状态
  • 简化 get_preset_name:混直径模式下直接返回 preset.name,不再构造虚假后缀;0.4mm 基础预设原样显示(如"Generic PLA"),非 0.4mm 预设的实际名称中已含挤出口后缀
  • switch_to_tab 中添加 FOS 后缀剥离:若显示名称在预设包中找不到,通过正则表达式剥离 @U1 X.X nozzle 后缀再进行别名查找
里程碑
  • 0.4mm 槽 FLP 下拉框可正常重新打开 · 重新选择、多材料标签页、切片后均无崩溃 · fos.8 逐挤出口过滤功能保持完整 · v2.3.2-fos.8.1 已发布
第13c阶段 — 内壁耗材 + 外墙圈数保存/加载修复 — 7月12日 · v2.3.2-fos.8.2
第13c阶段
修复 — 内壁耗材 + 外墙圈数在工程保存/加载后丢失 · v2.3.2-fos.8.2
两个 FOS 键均未注册到 s_Preset_print_options · 工程重新加载时被 apply_only 过滤丢弃
重大决策
  • v2.3.2-fos.8.2 定为 FOrcaSlicer 首个公开发布版本
  • 内壁耗材:已保存的分配(如 N3)重新加载后变回外墙挤出口 — 导出的 G-code 将内圈周界路由到外墙工具
  • 外墙圈数:已保存的数量在重新加载时被丢弃,静默恢复为默认值
  • 两项设置在会话内均正常生效 — 仅在关闭并重新打开工程后才暴露丢失,或从重新加载工程的 G-code 配置头中缺失这两个键可见
  • inner_wall_filament 与 outer_wall_loops 均已在 PrintConfig.cpp 中定义、在 Preset.cpp 中有 set_key_value 默认值,但都未列入 s_Preset_print_options — 即打印预设有效键的注册表
  • 工程加载时 PresetCollection::load_external_preset 通过 apply_only(combined_config, print_options) 重建配置 — 不在 print_options 中的键会被过滤;两个键在保存时写入了工程文件,但在加载时尚未到达预设即被丢弃
  • wall_filament / sparse_infill_filament / solid_infill_filament 不受影响,因为三者均已注册 — 仅两个 FOS 新增键缺失,这就是该 bug 看起来具有选择性的原因
  • 在 s_Preset_print_options 中添加 "outer_wall_loops"(位于 "wall_loops" 之后)和 "inner_wall_filament"(位于 "wall_filament" 之后)
  • installer.nsi:VIProductVersion 提升至 2.3.2.8(自 fos.7 起一直滞留在 2.3.2.7)· 未改动任何切片代码
  • 厚内部桥接:内部桥接以 nozzle_diameter × sqrt(bridge_flow) 的高度挤出,未对层高做钳制 — 混合挤出口下可能极端(0.8mm 挤出口 + 0.1mm 层高 → 单条 0.8mm 高挤出,为层高 8 倍,与下方稀疏填充重叠)。属上游 OrcaSlicer 行为而非 FOS 特有 — 原版 Orca 统一 0.8mm 挤出口 + 0.24mm 层高也出现同样现象。变通方法:当实心填充挤出口远大于层高时禁用厚内部桥接;层高感知钳制在未来版本考虑中
里程碑
  • 内壁耗材可在保存/加载后保留(保存 IW=N3 重新加载仍为 N3,G-code 将内圈周界路由到该工具)✓ · 外墙圈数可在保存/加载后保留 ✓ · 重新加载工程的 G-code 配置头现包含这两个键 ✓ · 两个字段恢复重置圆点与预设基线跟踪 ✓ · v2.3.2-fos.8.2 已发布
第13d阶段 — 厂商配置文件刷新 + 重复配置清理 — 7月13日 · v2.3.2-fos.8.3
第13d阶段
修复 — 修正后的 Snapmaker 配置文件从未送达已安装机器 · v2.3.2-fos.8.3
预设包版本号滞留导致缓存配置冻结在损坏的 105% 状态 · 20 个重名配置文件 · About 对话框 FOrcaSlicer 品牌化
  • 同一工程文件在两台电脑上产生不同的线宽 — 升级安装的机器在质量标签页中显示百分比与毫米混杂,其中顶面线宽为裸"105"(无 % 号,即 105 毫米)
  • 仅 0.4mm 挤出口预设受影响;0.2/0.6/0.8 正常 · 全新安装也正常 — 这正是问题难以定位的原因
  • 并非表面问题:线宽为 coFloatOrPercent 类型且 ratio_over = nozzle_diameter,存储的 105% 在每个挤出口上解析为不同的物理宽度(0.2 挤出口 0.21mm,0.8 挤出口 0.84mm)— 混合挤出口下会静默改变实际打印结果
  • U1 打印预设经历三次修订:df0a7e9d58(毫米 → 105%/110%,顶面线宽变成裸 105)、03a404bda3(修复 105 → 105%)、7b5c73e1af(全部改回绝对毫米值)— 修正后的毫米值此后一直随 resources/profiles/ 打包发布
  • PresetUpdater::check_installed_vendor_profiles 仅在资源包声明更新版本时才刷新 %APPDATA%/FOrcaSlicer/system/<Vendor>/ — Snapmaker.json 在历次 FOS 配置修改中始终停留在"02.02.46.02",检查从未触发,机器一直保留最初安装的配置(冻结在 df0a7e9d58 状态)
  • 每次配置修复都被如实打入安装包,然后在升级时被静默忽略
  • 20 个打印配置文件与真实同级文件声明了相同的"name"键 → 预设绑定不确定:19 个"* copy.json"文件(0.2/0.6/0.8 预设)+ "0.20 Standard @Snapmaker U1 (0.4 nozzle)_old.json"
  • _old.json 正是 0.4 挤出口的重名冲突 — 这就是 0.4 出问题的原因;它是更早更瘦的旧修订版,且未被 Snapmaker.json 索引引用(此前的清理提交 eeebb38a47 / 710caf2ac1 仅在开发分支,从未发布)
  • Snapmaker.json 预设包版本 02.02.46.02 → 02.02.47.00 — Semver::parse 仅解析 3 段版本号(第 4 段对比较不可见),将 .02 改为 .03 毫无作用;提升补丁段(46 → 47)才使 2.2.46 < 2.2.47 成立并触发刷新 · 同时使 FOS 版本号领先于 Snapmaker 官方的 02.02.46.x,官方在线更新器不会将其覆盖
  • 删除上述 20 个重名配置文件 — install_bundles_rsrc 在重新复制前对厂商文件夹调用 remove_all(),因此版本提升既送达修正配置,也将陈旧文件从已安装机器上清除
  • libslic3r.h SLIC3R_APP_FULL_NAME "Snapmaker Orca" → "FOrcaSlicer" — 驱动帮助 > 关于菜单项、关于对话框标题、错误标题和 MessageDialog 标题;仅影响显示(路径由 SLIC3R_APP_KEY 驱动,已是 FOrcaSlicer)
  • common_func.hpp:新增 FOS_VERSION "2.3.2-fos.8.3" — 刻意与 Snapmaker_VERSION 分离(该宏写入 3mf 元数据、工程设置及 user_backup 文件夹名,改动会破坏工程往返并孤立现有备份)
  • AboutDialog.cpp:版本行显示"FOrcaSlicer 2.3.2-fos.8.3",下方标注"Based on Snapmaker Orca 2.3.2 / Orca Slicer" · 修复版权符号乱码 — 窄字符串字面量中的原始 U+00A9 按当前区域设置解码(GBK 代码页下渲染为汉字);现改为纯 ASCII,通过 wxString::FromUTF8 显式解码
  • 未改动任何切片代码 · 升级说明:修正配置在 8.3 首次启动时自动应用;自定义用户预设不受影响 — 仅替换 %APPDATA%\FOrcaSlicer\system\Snapmaker\ 下的系统预设包
里程碑
  • 升级至 8.3 刷新缓存的厂商预设包 — 所有 U1 线宽在每个挤出口上均以绝对毫米值加载 ✓ · 20 个重名配置从已安装机器上移除 ✓ · Snapmaker 配置树中不再有任何百分比线宽和重名 ✓ · 同一工程在不同机器上产生一致线宽 ✓ · 帮助 > 关于显示"About FOrcaSlicer",对话框显示 FOrcaSlicer 2.3.2-fos.8.3 且版权符号正确 ✓ · v2.3.2-fos.8.3 已发布
第13e阶段 — 支撑、树状支撑 + 擦料塔均按 1 号挤出口取尺寸 — 7月14日 · v2.3.2-fos.8.4
第13e阶段
修复 — 支撑、树状支撑与擦料塔的几何尺寸全部按 1 号挤出口计算 · v2.3.2-fos.8.4
单一主题:三个功能无论由哪个工具打印均按 1 号挤出口取尺寸 · 统一口径机器完全不受影响
  • 在 0.2/0.4/0.6/0.8 机器上将支撑分配给 2 号耗材:切片器选对了挤出口,却让它铺 0.22mm 的线 — 那是 1 号挤出口的线宽;0.4mm 口径被要求挤出比自身还细的线条
  • 擦料塔更严重:所有工具均以 1.0mm 擦拭、2.0mm 冲料 — 包括 0.2mm 挤出口,达到自身口径的 5 倍和 10 倍
  • 这不是舍入误差 — 而是挤出口物理上无法铺出的挤出线
  • 打印预设中的绝对线宽是按 1 号挤出口(预设解析时的参考挤出口)制定的;由其他工具打印的功能必须按比例重新推导线宽。PrintRegion::flow() 一直对外墙/内壁/填充/实心填充/顶面实心填充这样做 — 但支撑角色不在该列表中,且支撑根本不经过 PrintRegion::flow():它走 Flow.cpp 中的自由函数,选对了挤出口却传入未调整的线宽
  • 树状支撑还有第二处错误:support_extrusion_width(决定树状支撑主体尺寸)从 support_interface_filament 的挤出口读取,而非主体自己的 · 另有两处硬编码 nozzle_diameter.get_at(0),将分支间距和碰撞解析直接钉死在 1 号挤出口
  • 擦料塔只保留一个标量并反复覆盖:WipeTower2::m_perimeter_width 在 set_extruder() 内赋值、每个耗材调用一次 — 最后配置的耗材因迭代顺序的偶然而胜出(0.8 × 1.25 = 1.0mm,人人如此);代码甚至明说"此后假定所有挤出机口径相同" · 而 m_filpar[idx].nozzle_diameter 一直被填充却从未被读取
  • 新增 fos_width_for_nozzle() — 1 号挤出口 → 实际打印挤出口的比例换算规则,导出后支撑、SupportParameters 与 TreeSupport 以完全一致的方式推导线宽,而非三套各自的算法;百分比线宽原样通过(get_abs_value 本就按传入挤出口取比例) · support_material_flow、support_material_1st_layer_flow、support_material_interface_flow 均改经此函数
  • SupportParameters:support_extrusion_width 改从 support_filament 读取(原为 support_interface_filament)并按比例推导 — 它决定支撑主体尺寸,此前从错误的工具取尺寸
  • TreeSupport:两处硬编码 nozzle_diameter.get_at(0) 改为支撑耗材的挤出口并按比例推导 — 二者驱动碰撞解析与分支间距;由 0.6mm 工具打印的树曾按 0.2mm 尺寸构建
  • 结构线宽(占地、边距、裙边间距、塔周界 + 稀疏填充)绑定到 wipe_tower_filament — 真正打印它们的工具(set_extruder 将其标记为唯一非水溶性耗材,first_toolchange_to_nonsoluble 因此将 finish_layer 落在其上);此前取决于哪次 set_extruder 调用最后运行
  • 擦拭与冲料现按各工具自身的线宽和流量挤出,取自 m_filpar[tool].nozzle_diameter
  • 深度规划同步跟进:plan_toolchange() 与 save_on_last_wipe() 用同一套逐工具线宽预留深度 — 线宽与间距必须同步调整,否则冲料块会过满/欠满、预留深度与实际挤出线不再吻合 · 冲料的 E 值按体积计算(ramming_speed × 时间 / 耗材截面积),仅线条长度变化,清料体积不受影响
  • 支撑/筏底、支撑/筏接触面与擦料塔提供"默认"选项 = 当前已装载的耗材打印 — 混合口径下这不是有效选择且无法修复:支撑路径在切片时以单一流量一次性生成,而工具直到 G-code 导出才确定 — ToolOrdering 在已生成的挤出线上直接改派工具,无任何重新渲染;为 0.2mm 口径构建的路径可能交给 0.8mm 口径
  • 口径不一致时:三个下拉框显示"Empty"而非"默认",且切片器拒绝切片直至选定耗材,并指明哪个下拉框为空 · 刻意不自动填充 1 号耗材 — 静默落到 1 号挤出口正是本次要修的 bug · 统一口径机器上"默认"行为与以前完全一致
  • FOS_VERSION → 2.3.2-fos.8.4 · 安装包 VIProductVersion 2.3.2.8 → 2.3.2.9 · 升级说明:使用混合口径且启用支撑/擦料塔的用户首次切片时需选定耗材;8.4 之前保存的工程打开后这些项可能为空,切片器会指明缺哪一项
  • 支撑层高仍跟随 1 号挤出口 — 与 8.x 规则(所有挤出口跟随 1 号挤出口的打印设置)一致,但 0.8mm 挤出口可能被要求打印 0.1mm 支撑层;层高钳制属新功能而非修复 — 延后
  • 冲料块与擦拭线对齐:将冲料块对齐到首条擦拭线的 Y 偏移仍使用塔的结构线宽 — 逐工具线宽下偏差可达约 0.4mm;仅外观问题,不影响清料体积和塔体几何
里程碑
  • G-code 验证(0.2/0.4/0.6/0.8,支撑主体耗材 3、接触面耗材 2、擦料塔耗材 2):支撑 0.22 → 0.66mm(0.6 工具)✓ · 接触面 0.22 → 0.44mm(0.4 工具)✓ · 塔擦拭 1.0×4 → 0.25/0.5/0.75/1.0 ✓ · 冲料 2.0×4 → 0.5/1.0/1.5/2.0 ✓ · 冲料块保持在塔壁之内 ✓ · 树状支撑分支间距随支撑挤出口缩放 ✓ · 统一口径切片完全不变(0.6×4:支撑同为 0.62mm、塔线宽同为 0.75/1.5、挤出线数量一致)✓ · v2.3.2-fos.8.4 已发布
第十四阶段 — 逐挤出口线宽与速度 — 7月14日 · v2.3.2-fos.8.5
第14阶段
逐挤出口线宽与速度 · v2.3.2-fos.8.5
每个打印特征按实际打印它的挤出口的线宽与速度打印 — 不再全部跟随 1 号挤出口 · 逐挤出口擦料塔线宽 · 层高由实际使用的最小挤出口限定
  • 混合口径打印机上,每个打印特征的线宽与速度现取自实际打印它的挤出口 — 在质量和速度页的逐挤出口标签(挤出口 1–4)中设置;分配跟随打印流程耗材下拉框(外墙、内壁、填充、实心填充、支撑)
  • 线宽:外墙、内壁、顶面、稀疏填充、内部实心填充、支撑 · 速度:外墙、内壁、小周界、稀疏填充、内部实心填充、顶面、间隙填充、熨烫、支撑、支撑接触面
  • 在挤出口 2–4 标签中输入的数值立即生效 — 无需先重新加载预设
  • 每个挤出口新增擦料塔线宽字段(质量页)— 填 0 为自动(该挤出口直径 × 1.25),或手动设值;每个工具按自身线宽擦拭和冲料,塔壁与占地使用擦料塔耗材的数值
  • 此前整座塔使用最后配置的工具的单一线宽 — 0.2 挤出口可能在铺 1.0mm 的塔线
  • 层高与首层层高仍为单一全局设置,显示在挤出口标签下方 — 自动限制在实际使用的最小挤出口可打印的范围内(不能让 0.2mm 口径铺 0.6mm 的层);限制跟随分配变化,只用大口径时范围随之放宽
  • 限制也计入对象自身的耗材 — 若层高上限看起来过低,请检查对象列表中分配给对象的耗材,而不只是打印流程下拉框
里程碑
  • 已在 0.2/0.4/0.6/0.8 混合配置及 0.6 统一口径回归中通过 G-code 验证 — 统一口径打印机完全不受影响,输出与之前一致 ✓ · 尚未实机打印测试 — 欢迎反馈混合口径模型的实际打印问题 · v2.3.2-fos.8.5 已发布
第14a阶段 — 热修复:涂色区域线宽 + 1 号挤出口预设 — 7月17日 · v2.3.2-fos.8.5.1
第14a阶段
8.5 热修复 — 涂色区域应用逐挤出口线宽 + 1 号挤出口保留自身预设 · v2.3.2-fos.8.5.1
两处修复,无新功能
  • 混合口径打印机上,通过涂色(色块 / 分割)分配给某挤出口的表面现按该挤出口的线宽打印 — 与通过打印流程特征下拉框分配的表面一致
  • 在 8.5 中,涂色区域无论由哪个挤出口铺设都按基础区域的线宽打印:0.2mm 口径可能被推到约 0.4mm,0.8mm 口径被压到约 0.4mm;特征下拉框分配(外墙、内壁、填充、实心填充)不受影响 — 仅涂色区域受影响。此修复恢复了 8.5 之前的行为
  • 修复后,涂色的 0.2/0.4/0.6/0.8 模型每个挤出口约铺 0.22/0.42/0.62/0.82 — 大口径上偶见较宽线条为正常的间隙填充
  • 1 号挤出口的打印流程预设现跟随 1 号挤出口自身的直径 · 此前它被绑定到打印外墙的那个挤出口:将外墙分配给其他挤出口 — 或涂色使外墙耗材非 1 号 — 会将 1 号挤出口的预设锁定到该挤出口的直径,并在重新加载时弹回
  • 1 号挤出口现无论外墙分配如何都保留自身预设;挤出口 2–4 一如既往保留各自预设
里程碑
  • 统一口径打印机完全不受影响,输出与之前一致 ✓ · 若不在混合口径机器上涂色或重新分配外墙,这些修复对你毫无影响 · v2.3.2-fos.8.5.1 已发布
第14b阶段 — 热修复:全新安装添加 U1 PTP 时崩溃 — 7月19日 · v2.3.2-fos.8.5.2
第14b阶段
8.5.1 热修复 — 全新安装时添加 Snapmaker U1 崩溃 · v2.3.2-fos.8.5.2
逐挤出口标签页中指向已扩容 std::vector 的裸指针失效 · 单一崩溃修复,无新功能
  • 全新安装时添加 Snapmaker U1(混合口径 PTP)立即崩溃 — 硬性访问违例,无对话框、无错误提示
  • 逐挤出口标签页中长期存在的潜伏 bug,仅在全新安装路径上暴露;与 8.5.1 的修复无关
  • 堆栈:TabPrint::fos_reload_slot_config(Tab.cpp ~3517)→ ConfigOptionsGroup::reload_config(OptionsGroup.cpp ~750)→ get_config_value → 在 config.def() 处崩溃(~945)
  • 逐挤出口槽位的 optgroup 使用指向 m_fos_slot_configs std::vector 的裸指针构建(&m_fos_slot_configs[n])· 标签页构建与 fos_reload_slot_config 在三处调用 resize(nozzle_count) — 当 resize 使 vector 增长时会重新分配缓冲区,令所有 optgroup 的 m_config 变为悬空;下一次 reload_config() 读取已释放的 config 并崩溃
  • 全新安装时添加 U1 会使槽位 vector 增长(1 → 4)— 正是这次重新分配触发了崩溃;已有配置文件的 vector 已具备所需大小,因此不复现,故表现为"仅全新安装"
  • 已排除(记录在此以免重复排查):"contains incorrect keys: color_patch_enabled, inner_wall_filament..." 的日志刷屏是良性的 — 这些是打印流程/对象作用域的键,加载器正确地将其从机器预设中剥离,与崩溃无关 · 最初对 get_config_value 中 opt 解引用添加的空值防护是对崩溃行的误读(故障点是第 945 行的 config 访问,发生在任何 opt 解引用之前)
  • Tab.hpp:m_fos_slot_configs 由 std::vector 改为 std::deque — deque 在尾部 resize/增长时保持已有元素地址稳定,optgroup 的裸指针因此永不悬空;已确认容器无连续内存假设(仅取单元素地址,无 .data()/.reserve()/.capacity()),可直接替换,一处改动覆盖全部三个 resize 调用点
  • OptionsGroup.cpp:在 get_config_value 与 get_config_value2 中添加空值防护 — 若 config.def()->get(opt_key) 返回空则返回空值而非解引用;这是针对另一处潜伏空指针解引用的防御性加固,并非本次崩溃的修复
  • FOS_VERSION 2.3.2-fos.8.5.1 → 2.3.2-fos.8.5.2 · 安装包 VIProductVersion 2.3.2.11 → 2.3.2.12 · 8.5.1 的全部内容保持不变
  • 发布版日志在硬性崩溃时会写入中断且不含堆栈。最佳方式:在构建机上用 Visual Studio — 将 %APPDATA%\FOrcaSlicer 改名以强制全新配置,带符号在调试器下运行并复现 · WER 本地转储(HKLM LocalDumps,DumpType=2)→ 生成 .dmp,用匹配的 PDB 在 VS 中打开 · 快速定位:事件查看器 → 应用程序错误 → 故障模块 + 偏移量
里程碑
  • 已在全新配置下复现(删除 %APPDATA%\FOrcaSlicer 后添加 U1 PTP)→ 出现上述堆栈的硬性崩溃;应用 deque 修复并重新构建后,同一路径 → 不再崩溃,已由用户确认 ✓ · v2.3.2-fos.8.5.2 已发布
第14c阶段 — 热修复:逐挤出口线宽在创建 PTP 时即生效 — 7月20日 · v2.3.2-fos.8.5.3
第14c阶段
8.5.2 热修复 — 逐挤出口线宽在创建混合口径 PTP 时即刻生效 · v2.3.2-fos.8.5.3
optgroup 惰性构建导致首次切片在打开质量页前对所有挤出口都用 1 号线宽 · 单一修复,无新功能
  • 混合口径打印机上,创建新的打印类型配置(PTP)时已为每个挤出口分配了正确的打印流程预设,但切片仍对所有挤出口使用 1 号挤出口的线宽,直到你打开质量页(或重新选择某挤出口的打印流程预设)
  • 涂色区域表现相同 — 涂色挤出口的线宽在访问质量页前不生效;这解释了跨机器差异:一台安装访问过质量页,另一台没有
  • update_prp_nozzle_rows(ParamsPanel.cpp)为每个槽位自动选择正确的逐挤出口 PRP,随后调用 fos_reload_slot_config(i) 以解析 fos_nozzle_*_line_width 数组 — 但 fos_reload_slot_config 在 m_fos_slot_optgroups[slot_idx].empty() 时提前返回,而逐挤出口标签页的 optgroup 是惰性构建的,仅在首次打开质量页时才创建
  • 因此新建的 PTP 上槽位配置从未加载;fos_resolve_nozzle_arrays 使数组一直镜像自槽位 0(1 号挤出口)。打开质量页构建 optgroup 后重载才终于运行,线宽随即修正 — 探针显示 RESOLVE 卡在 ow=0.220×4(nd=0.2,0.4,0.6,0.8),打开质量页后才达到 0.220,0.420,0.620,0.820
  • 已排除:s21 的"N1 驱逐"修复并非本机制 — 探针显示 1 号挤出口 / 槽位 0 始终正确、从未被驱逐;这是不同的机制(PTP 创建时的 optgroup 时序)
  • 首次修复移除了提前返回,使加载+解析在 optgroup 为空时也运行 — 修好了创建路径,却令正常使用回归:点击某个逐挤出口标签会使所有线条塌缩为 0.2。该 bail 是承重的 — 正常使用中打印标签页会不断被销毁重建,触发单独的 fos_reload_slot_config(0),此时槽位 1..N 短暂为空;bail 阻止 fos_resolve_nozzle_arrays 在这一部分快照上运行。已完整还原
  • 教训:optgroup 为空的 bail 不只是 UI 防护 — 它保护已解析的数组不被标签页销毁/重建期间的单槽解析破坏。任何创建时的填充都必须先加载完整集合、再解析恰好一次
  • 新增 TabPrint::fos_populate_all_slots(bool mixed_active) — 从各自来源加载每个槽位配置(槽位 0 取自 *m_config;槽位 1..N-1 取自各挤出口标签存储的 selected_preset_name(),该选择在 optgroup 销毁后仍保留),随后对完整集合调用一次 fos_resolve_nozzle_arrays();不依赖 optgroup、不触发 UI 重载 · 统一模式镜像 N1 · 逐槽变更防护保留槽位手动编辑
  • ParamsPanel.cpp:在 update_prp_nozzle_rows 的逐槽自动选择循环之后调用 fos_populate_all_slots(mixed_active) — 即创建/口径变更路径 · fos_reload_slot_config 的 optgroup 为空 bail 保持不变,churn 路径仍受保护
  • FOS_VERSION 2.3.2-fos.8.5.2 → 2.3.2-fos.8.5.3 · 安装包 VIProductVersion 2.3.2.12 → 2.3.2.13 · 8.5.2/8.5.1 的全部内容保持不变
里程碑
  • G-code(cube_build_3.gcode)刀路 ;WIDTH: 按活动工具标记 — T0=0.22、T1=0.45、T2=0.62、T3 约 0.82;每个挤出口挤出自身线宽,配置块 fos_nozzle_outer_wall_line_width = 0.22,0.42,0.62,0.82 · 无需访问质量页创建即正确;点击 2 号挤出口标签不再塌缩;统一模式正确镜像 N1 ✓ · v2.3.2-fos.8.5.3 已发布
第14d阶段 — 热修复:全局特征耗材回归 + "Global" 标签 + CJK/i18n — 7月21日 · v2.3.2-fos.8.5.4
第14d阶段
8.5.3 热修复 — 全局特征对应耗材切片回归 + 单对象 "Global" 标签 + imgui CJK + zh_CN 整理 · v2.3.2-fos.8.5.4
对象被强制设为 extruder=1 覆盖了所有逐特征分配 · 四处修复,无新功能
  • 对象被存储为强制 extruder=1 · PrintObject apply_to_print_region_config 第 1 步填充实质上执行:if (extruder != 0) { sparse_infill = solid_infill = wall = inner_wall = extruder; } — 因此非零的对象 extruder 会用该单一耗材覆盖所有全局逐特征分配
  • 全局设置 OW=1/IW=2/填充=3/实心=4 时,四者全部塌缩为 1 号耗材,擦拭塔消失(仅用到一种耗材)
  • 修复:保持对象 extruder = 0(未分配),从而跳过覆盖分支,特征从全局分配解析 · Model.cpp(3 处 ConfigOptionInt(1) → (0))、GUI_ObjectList.cpp(update_filament_values_for_items + 添加网格路径改回 0)· 同时恢复 OrcaSlicer 的单对象模型(对象显示颜色仅由对象级耗材决定;移除了误导性的逐特征外墙颜色预览)
  • extruder=0 此前显示为 "default",容易误解 — 它是回退/全局状态(特征回退到全局分配),并非字面意义的默认值 · 现显示为 "Global"(全局),复用已有 msgid 并与 全局/对象 模式切换保持一致
  • 六处,均带 // FOS: 标记:wxExtensions.cpp get_default_extruder_color_icon(共享图标覆盖编辑器下拉行 + 修改器/层图标)、ExtraRenderers.cpp 下拉回退行、ObjectDataViewModel.cpp 对象/修改器/层显示字符串 · 对修改器部件 / 层范围而言 0 意为"继承父级",但为同一共享图标,故均显示 "Global"
  • 乱码按钮:ImGuiWrapper::button() 仅有 wxString 重载;imgui.button(_u8L("...")) 传入 UTF-8 std::string,命中隐式 wxString(std::string) ANSI/Latin-1 构造函数 → 翻译后的 CJK 字节被错误解码 → 乱码(imgui.title 直接接受 std::string,故相邻标题显示正常)· 修复:两个按钮 _u8L → _L("验证出口尺寸"、"出口尺寸已验证")
  • 英文出口标签:出口 1..N 表头使用原始 sprintf("Nozzle %d") 未翻译 → 修复为 wxString::Format(_L("Nozzle %d")) → 出口 %d
  • 通用陷阱(已记录):任何翻译后的 _u8L 传入仅接受 wxString 的接收端都会经隐式 std::string → wxString ANSI 构造函数产生乱码 · 规则:wxString 接收端用 _L,仅 std::string/char* 接收端用 _u8L — 本轮仅审计了验证出口叠加层
  • 大范围 zh_CN 整理:耗材丝 → 耗材(137 处,含一处 耗材丝丝 重复)、default → 默认(原为 缺省)、Global → 全局、Filament for Features → 特征对应的耗材、Wipe tower → 擦拭塔、Ooze prevention → 预防渗出、新增 Preheat delta temperature → 预热温差(该 msgid 从未被提取到 .po)、移除字段标签中残留的英文 "Skirt"/"Brim"(Brim 表头 → 加固边沿);zh_TW 未改动
  • 管线根本原因(探针确认):应用加载 AddCatalog(SLIC3R_APP_KEY) = FOrcaSlicer.mo,但 gettext_po_to_mo 输出的是 Snapmaker_Orca.mo(名称错误)且调用裸 msgfmt(不在 Windows PATH 上 → 目标始终失败)— 因此运行时 zh_CN 目录冻结在手工制作的 5 月 17 日 .mo 上,.po 的修改从未到达应用
  • 修复:gettext_po_to_mo 现调用随附的 tools/msgfmt.exe 并输出 ${SLIC3R_APP_KEY}.mo = FOrcaSlicer.mo;同级 gettext_merge_po_with_pot 使用 tools/msgmerge.exe · 已验证构建 gettext_po_to_mo 目标可经官方管线从 .po 重新生成 FOrcaSlicer.mo · 所有 *.mo 已被 gitignore,目录现可从源文件复现(此前是全新构建即消失的手工产物)
里程碑
  • 全局特征对应耗材正确解析(OW=1/IW=2/填充=3/实心=4 不再塌缩,擦拭塔恢复)✓ · 单对象无耗材单元格显示 "Global"/全局 ✓ · 验证出口叠加层按钮与出口 1–4 表头正确显示中文 ✓ · zh_CN 翻译在全新构建后依然生效 ✓ · 单口径打印机不受影响 · FOS_VERSION → 2.3.2-fos.8.5.4,安装包 VIProductVersion 2.3.2.13 → 2.3.2.14 · v2.3.2-fos.8.5.4 已发布
第14e阶段 — 对象耗材以外墙为准 + 移除默认耗材回退 + PRP 缩放修复 — 7月22日 · v2.3.2-fos.8.5.5
第14e阶段
对象耗材现以其外墙为准 · 移除默认/全局回退 · 修复 PRP 面板 DPI 缩放 · v2.3.2-fos.8.5.5
对象始终携带具体耗材 — 不再有不可见的默认值
  • 对象耗材现由其外墙(wall_filament)贯穿定义 — 单对象耗材列与三维对象颜色都显示外墙耗材(即打印件上实际所见),不再显示单独的对象"默认";旧的"全局"标签已移除
  • 所有入口保持同步并写入同一个值:右键菜单、内联耗材下拉框、对象表格下拉框、外墙耗材选择器、三维预览 · 以一个共享的特征组作为唯一真值源;apply_to_print_region_config 中第 1 步的对象 extruder 覆盖已删除,特征改由第 2 步复制循环解析
  • 右键"更改默认耗材" → "统一对象耗材" — 一次性将所选对象的外墙、内墙、稀疏填充与实心填充设为同一耗材(即"整个对象用耗材 N 打印"的快捷操作);对象表格下拉框执行相同操作 · 两个菜单字符串均已翻译为简体中文
  • 支撑底层/接触面与擦拭塔保留各自的耗材控件 — 混合口径打印机上它们仍必须为具体耗材(限制不变)
  • 对象列表耗材列对对象/模型零件不再提供"全局"行 — 它们列出耗材 1..N 并默认为耗材 1;参数修改器与层高范围保留继承行(对它们而言 0 = 继承仍有意义)
  • 特征耗材下拉框(外墙、内墙、稀疏填充、实心填充)仅列出耗材;若某特征仍为 0 则用耗材 1 打印 · 四个特征耗材现默认 1 / 最小 1(原为 0/0),clamp_feature_extruder 保留作为处理遗留/手改配置中 0 值的崩溃防护
  • 外墙、内墙、顶面、稀疏填充、实心填充与支撑线宽提示现说明将值设为 0 时使用默认线宽(说明既有行为,使 0 读作"继承");中文提示已更新
  • 在两台不同缩放(如 100% + 150%)显示器上,出口 2–4 的 PRP 行停留在构建时缩放而出口 1 跟随当前显示器 — 其下拉框、保存/搜索按钮与行间距显得过大。根本原因:出口 2–4 是 TabPrintNozzle 标签,会将自身从 tabs_list 移除、不在任何 msw_rescale 路径上 · 修复:新增 TabPrintNozzle::fos_rescale_row(仅缩放其拥有的下拉框/按钮/位图/行高 — 对出口标签调用完整 Tab::msw_rescale 是未定义行为)并新增 TabPrint::msw_rescale 覆盖以处理逐出口标签页槽位 optgroup;不再需要切换标签修正间距
  • 另自 8.5.4 起:修复了在较高层高下对混合口径对象切片时误报"线宽过小"并阻止切片的问题 · FOS_VERSION → 2.3.2-fos.8.5.5,安装包 VIProductVersion 2.3.2.14 → 2.3.2.15
里程碑
  • 对象身份在耗材列、三维着色、菜单、所有下拉框与预览中均以外墙为准 ✓ · 在对象/特征已携带具体耗材之处移除了默认/全局行 ✓ · PRP 行与逐出口标签页随当前显示器缩放 ✓ · 修复误报线宽过小阻止切片 ✓ · 单口径打印机不受影响 · v2.3.2-fos.8.5.5 已发布
第十五阶段 — 首个 macOS 版本 · 跨平台发布 — 7月27日 · v2.3.2-fos.8.5.6
第15阶段
首个 macOS 版本 — 通用 DMG、完成 macOS 上的重命名、统一用户数据 · v2.3.2-fos.8.5.6
切片行为无任何变化 — Windows 切片输出与 8.5.5 完全一致
重大决策
  • v2.3.2-fos.8.5.6 是首个提供 macOS 版本的发布 — FOrcaSlicer 现已跨平台(Windows + macOS)
  • 提供由 GitHub Actions 从源码构建的 macOS DMG — 通用二进制(主程序、崩溃处理器与 Sentry 库均同时包含 x86_64 + arm64 切片),在 Intel 与 Apple 芯片上原生运行;最低 macOS 12.0(Monterey),已在 Intel 与 Apple 芯片(M4 Max,macOS 26)上测试
  • 未签名、未公证(无 Apple 开发者证书)— 首次启动会被拒绝并提示"Apple 无法检查其是否包含恶意软件";执行一次以清除隔离标记:xattr -dr com.apple.quarantine "/Applications/FOrcaSlicer.app",或前往 系统设置 > 隐私与安全性 > 仍要打开(Control 点按绕过方式已在 macOS 15 中移除)
  • 8.x 的重命名覆盖了 Windows 的应用名/图标/可执行文件;若干 macOS 专有代码路径不在其范围内且从未被执行(此前无 macOS 版本)。现已全部正确:应用包与可执行文件均为 FOrcaSlicer(非 "Snapmaker Orca");FOrcaSlicer 图标在各处显示,包括 Dock 图标(此前应用运行时会变回 Snapmaker 图标);语言列表恢复 — mac 构建曾以旧名生成翻译文件,导致应用找不到 FOrcaSlicer.mo 而无任何语言,现约 20 种语言均可加载
  • 若干路径仍硬编码为 Snapmaker_Orca,导致 FOrcaSlicer 同时读写自己的目录与 Snapmaker Orca 的目录;现所有路径均由应用标识键派生,FOrcaSlicer 与同时安装的任何其他 Orca 系列切片器完全独立
  • 现均从 FOrcaSlicer 自身目录读取:启动时读取的配置/网页版本文件、语言/区域检测(读取 FOrcaSlicer.conf,原为 Snapmaker_Orca.conf)、崩溃报告数据(FOrcaSlicer/SentryData)· Windows 用户:无需操作、不丢失任何设置 — 预设/配置文件一直位于 FOrcaSlicer 目录
  • 两个 GitHub Actions 工作流:通用发布构建(依赖矩阵 → 通用构建,DMG + dSYM)与单架构 x86_64 快速测试 · 修复三处各需一次失败运行才发现的 CI 缺陷:固定 CMake 3.31.6(brew 提供的 CMake 4.x 移除了 cmake_minimum_required <3.5 兼容);在任何缓存操作前卸载 Homebrew zstd(作业中途移除 zstd 会改变缓存压缩方式,导致每次查找都未命中);在检出后一次性计算依赖缓存键(已恢复的依赖前缀会改变下一次恢复的 hashFiles())· 必需步骤顺序:检出 → 移除 zstd → 计算依赖缓存键 → 缓存操作 → 其余
  • 添加 U1 PTP 后的出口标签:逐出口标签页仅显示一个标签,切换到其他页再切回即可填充其余标签 — 仅外观,预设创建正确
  • macOS 深色模式首次启动:标签页与出口直径标签可能显示为浅底浅字,切换一次系统外观即可修正 — 应用在窗口创建前采样外观且不再复检;仅外观,仅 macOS
  • macOS 版本未签名、未公证(见上方隔离说明)
里程碑
  • 通用 DMG 经验证确为通用(CI 审计:三个 Mach-O 文件均包含 x86_64 + arm64;其余全部静态链接)✓ · 在 Intel 与 Apple 芯片(M4 Max,macOS 26)上原生启动 ✓ · 全新 Mac 仅创建 ~/Library/Application Support/FOrcaSlicer/,无 Snapmaker_Orca 目录 ✓ · Windows 数据已统一,不再写入 Snapmaker_Orca ✓ · FOS_VERSION → 2.3.2-fos.8.5.6,安装包 VIProductVersion 2.3.2.15 → 2.3.2.16 · v2.3.2-fos.8.5.6 已发布,含 Windows 安装包、便携版 zip 与通用 macOS DMG
待完成 / TODO
TODO
待处理事项
回归修复、代码清理、待实现功能
  • 混合挤出口尺寸下仅外壳着色 FlowErrorNegativeSpacing — 尚未诊断
  • 外墙挤出口变更时的涂色擦除警告 — 已实现但注释,待测试后启用
  • 打印流程预设下拉框宽度在混直径模式下不自适应
  • 1 号挤出口的独立打印流程预设选择器尚未实现 — 1 号挤出口仍为主打印流程预设(自 8.5.x 延续)
  • initial_layer_line_width(首层线宽)仍为全局设置,非逐挤出口(自 8.5.x 延续)
  • _u8L → wxString 乱码陷阱可能存在于任何将翻译后的 _u8L 传入仅接受 wxString 接收端之处 — 本轮仅审计了验证出口叠加层(8.5.4)
  • 修改器/层的回退图标显示 "Global",但语义为"继承父级"(共享图标)— 若需要逐类型标签则重新审视
  • 逐挤出口耗材列表完整架构延后 — 需要在 PRP 中实现逐槽速度覆盖(各挤出口的外墙/内壁/填充/顶面速度)
  • 绝对首次启动时内壁下拉框显示异常 — 优先级低,点击可刷新
  • GitHub Wiki:说明混合挤出口外墙/内壁行为与原版 OrcaSlicer 的区别 · wiki 已在封闭环境中启动
  • 曲面着色区域回归 — 验证球体/环形/锥体 CL 路径是否正常;如有问题,在 48c601c3b1 与当前版本之间进行二分排查
  • 仅外壳着色 CL=1 弧线端点接缝伪影(最近/对齐接缝位置)— 变通方法:使用随机接缝
  • 按实际外墙挤出口过滤打印流程预设 — 延迟至累加器层高同步实现
  • 累加器层高同步(NozzleLayerPlanner)— 尚未开始
  • 全新安装后拖拽 STL 文件失败 — 变通方法:先运行一次 OrcaSlicer 再切换;修复方向:排查 installer.nsi 的 shell 注册
  • 深色模式挤出口颜色条 — 尚未测试
  • 厚内部桥接:为桥接挤出高度添加层高感知钳制(上游 Orca 行为,混合挤出口下尤为极端 — 见 fos.8.2 已知问题)
  • 大口径挤出口的支撑层高钳制 — 支撑仍跟随 1 号挤出口的层高;0.8mm 挤出口可能被要求打印 0.1mm 支撑层(属新功能,延后 — 见 fos.8.4 已知问题)
  • 冲料块与擦拭线的 Y 对齐仍用塔结构线宽 — 逐工具线宽下偏差可达约 0.4mm;仅外观问题(见 fos.8.4 已知问题)
  • outer_wall_loops:UI 无硬性上限(标签变红作为警告 — 已接受)
  • CL 滑块最大值在 UI 中硬编码为 10 — 如有需要可调高
  • 逐对象 CL 仅可通过操作工具设置,未在对象属性面板(Objects 标签)中开放
  • 工作头 UID 身份识别系统 — 每个工作头需要出厂烧录 UID;受硬件阻塞;软件架构已规划(本地注册表 JSON,PTP 存储 UID 排列)
  • 逐挤出口钢化钢(HS)切换 — 允许单独标记各挤出口为 HS 或标准型