clean up comments

This commit is contained in:
SoftFever
2026-08-23 22:43:41 +08:00
parent 7934814077
commit 2b1499a087
33 changed files with 127 additions and 193 deletions
+2 -2
View File
@@ -38,8 +38,8 @@ extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_color
extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr, ObjParser::MtlData *out_mtl = nullptr);
struct TexturedMesh;
// Build a TexturedMesh (vertices + per-face UVs + decoded texture images) from a parsed OBJ
// plus its material table, so the texture-to-color importer can sample face colours.
// Build a TexturedMesh (vertices + per-face UVs + the texture files named by map_Kd) from a
// parsed OBJ plus its material table, so the texture-to-color importer can sample face colours.
extern bool obj_to_textured_mesh(
const ObjInfo& obj_info,
const indexed_triangle_set& its,
+3 -4
View File
@@ -6106,8 +6106,7 @@ LayerResult GCode::process_layer(
// A mixed-color slot is absent from layer_tools.extruders by design: resolve_mixed_filaments()
// replaced it with its physical components. Its geometry is still keyed under the slot in
// by_extruder though, and the sublayer emitter looks the plan up by slot id, so append the
// slots here. Appended (not merged) so the existing order is untouched, and empty for every
// configuration without sublayer splitting.
// slots here. Appending rather than merging leaves the flush-optimized order untouched.
std::vector<unsigned int> plan_filaments = layer_tools.extruders;
for (const auto &grp : layer_tools.mixed_sub_layer_groups)
if (std::find(plan_filaments.begin(), plan_filaments.end(), grp.mixed_slot_0based) == plan_filaments.end())
@@ -6593,8 +6592,8 @@ LayerResult GCode::process_layer(
// Mixed-color sublayer extrusion: if this extruder is a component of a mixed sublayer
// group, extrude the mixed slot's geometry at the appropriate sub-Z with scaled flow.
// Ported from BambuStudio's 混色耗材 feature; adapted to Orca's InstanceVisit-based
// instance loop and its finer-grained per-role region filament options.
// Ported from BambuStudio and adapted to Orca's instance loop and its finer-grained
// per-role region filament options.
for (const auto &grp : layer_tools.mixed_sub_layer_groups) {
int sub_idx = -1;
for (size_t k = 0; k < grp.components_0based.size(); ++k) {
+4 -7
View File
@@ -91,11 +91,9 @@ bool check_filament_printable_after_group(const std::vector<unsigned int> &used_
}
// Return a zero based extruder from the region, or extruder_override if overriden.
// The region accessors below resolve mixed-color slots to the physical filament chosen for
// this layer. Without sub-layer splitting a mixed slot is realized by alternating whole layers
// (deficit round-robin, see resolve_mixed_filaments), so a region asking "which filament?" must
// get the resolved physical one, not the virtual slot id. resolve_mixed() is identity when the
// slot is not mixed, so this is a no-op for every non-mixed setup.
// The region accessors below resolve mixed-color slots to the physical filament chosen for this
// layer by resolve_mixed_filaments(), because a virtual slot id is never a real tool. resolve_mixed()
// returns its argument unchanged for every filament that is not a mixed slot.
unsigned int LayerTools::wall_extruder_id(const PrintRegion &region) const
{
assert(region.config().outer_wall_filament_id.value > 0);
@@ -2522,8 +2520,7 @@ void ToolOrdering::resolve_mixed_filaments(const PrintConfig &config)
// - untagged region (modifier / painted / etc.) -> per_object_gradient[obj]
// Populating both keeps the per-object run state correct even when per-volume
// takes over for the same (slot, obj), and lets untagged geometry (which is
// explicitly NOT split per-volume in v1 per the design doc) keep its legacy
// per-object gradient ratios.
// never split per-volume) keep its per-object gradient ratios.
if (grp.is_gradient) {
auto vol_runs_slot_it = per_vol_runs.find(ext);
if (vol_runs_slot_it != per_vol_runs.end()) {
+4 -5
View File
@@ -309,9 +309,8 @@ Model Model::read_from_file(const std::string&
ObjParser::MtlData mtl_data;
result = load_obj(input_file.c_str(), &model, obj_info, message, nullptr, &mtl_data);
if (result && obj_info.has_uv_png && !obj_info.uvs.empty() && !model.objects.empty()) {
// Textured OBJ: hand the mesh + materials to the texture-to-color importer instead
// of the flat per-face colour dialog. Replaces Orca's previous "not implemented"
// placeholder for this branch.
// Textured OBJ: hand the mesh + materials to the texture-to-color importer
// instead of the flat per-face colour dialog.
auto tex_mesh = std::make_shared<TexturedMesh>();
std::string obj_dir = boost::filesystem::path(input_file).parent_path().string();
if (obj_to_textured_mesh(obj_info,
@@ -322,7 +321,7 @@ Model Model::read_from_file(const std::string&
}
else if (result && !model.objects.empty() && !model.objects.back()->volumes.empty()) {
// Vertex-colour and MTL face-colour OBJs also go through the texture-to-color
// importer (as precomputed per-face colors) instead of the legacy flat
// importer (as precomputed per-face colors) instead of the flat
// per-face colour dialog, matching the uv_png branch above.
auto build_tex_mesh_geometry = [&]() {
auto tex_mesh = std::make_shared<TexturedMesh>();
@@ -374,7 +373,7 @@ Model Model::read_from_file(const std::string&
else if (boost::algorithm::iends_with(input_file, ".glb") ||
boost::algorithm::iends_with(input_file, ".gltf") ||
boost::algorithm::iends_with(input_file, ".fbx")) {
// These formats always carry material/texture data, so they go through the textured
// These formats can carry material/texture data, so they go through the textured
// import path: the geometry becomes a normal object and the texture is handed to the
// texture-to-color dialog via Model::texture_mesh.
auto tex_mesh = std::make_shared<TexturedMesh>();
+12 -23
View File
@@ -2715,19 +2715,13 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
preset.set_visible_from_appconfig(config);
}
// Mixed-color filament metadata is project state, carried in the 3mf's project_settings.config.
// BambuStudio also snapshots it in the app config so the last session's mixes are back before any
// project is opened; there the filament list itself is a single global snapshot, so the mixed
// arrays live next to it in the global "presets" section. Orca's per-printer preset memory instead
// rebuilds the filament list from the selected printer's snapshot (filament_%02u/filament_colors)
// on startup AND on every printer selection — so the mixed arrays, whose component ids are 1-based
// indices into exactly that list, must live in the same per-printer snapshot or they end up
// describing a list they were never saved against (and previously got reset on every printer
// select, losing the mixes over a restart).
// Missing keys clear the arrays: a printer with no stored mixes must not inherit another's.
// fallback_to_global additionally reads the legacy shared "presets" keys (the old format) so a
// config saved by an earlier build still restores at startup; export_selections clears that
// section on the next save.
// Mixed-color filament metadata is project state saved in the 3mf, also mirrored into the app
// config so the last session's mixes are back before any project is opened. It is kept in the
// per-printer snapshot next to the filament list it indexes (filament_%02u/filament_colors),
// because that list is rebuilt on every printer selection and the component ids are 1-based
// indices into exactly that list. Missing keys clear the arrays, so one printer never inherits
// another's mixes; fallback_to_global also reads the shared "presets" keys an older config
// layout used, which export_selections drops on the next save.
static void load_mixed_filament_settings(DynamicPrintConfig &project_config, AppConfig &config,
const std::string &printer_name, size_t n_filaments,
bool fallback_to_global)
@@ -3162,12 +3156,9 @@ void PresetBundle::export_selections(AppConfig &config)
"|");
config.set_printer_setting(printer_name, "flush_multiplier", flush_multiplier_str);
// Mixed-color filament metadata: stored in the per-printer snapshot next to the filament
// list it indexes (filament_%02u / filament_colors), so each printer's remembered config
// round-trips its own mixes and re-applying a snapshot never leaves the arrays describing a
// different list (see load_mixed_filament_settings). Bools are ','-joined; the
// component/ratio/range strings are '|'-joined; the gradient curve is escaped instead,
// because its values contain '|'.
// Mixed-color filament metadata goes into the per-printer snapshot next to the filament list
// it indexes (see load_mixed_filament_settings). Bools are ','-joined and the component, ratio
// and range strings '|'-joined; the gradient curve is escaped instead, as it contains '|'.
auto join_bools = [](const std::vector<unsigned char> &vals) {
std::string s;
for (size_t i = 0; i < vals.size(); ++i) {
@@ -3227,8 +3218,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
ams_multi_color_filment.resize(n);
// Mixed-color metadata is a parallel per-filament array set, so it has to grow and shrink
// with the filament count exactly like filament_colour above. Missing this leaves the
// arrays short and every lookup of a newly created slot reads past the end.
// with the filament count exactly like filament_colour above.
if (auto* opt = project_config.option<ConfigOptionBools>("filament_is_mixed"))
opt->values.resize(n, false);
if (auto* opt = project_config.option<ConfigOptionStrings>("filament_mixed_components"))
@@ -3285,8 +3275,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
ams_multi_color_filment.resize(n);
// Mixed-color metadata is a parallel per-filament array set, so it has to grow and shrink
// with the filament count exactly like filament_colour above. Missing this leaves the
// arrays short and every lookup of a newly created slot reads past the end.
// with the filament count exactly like filament_colour above.
if (auto* opt = project_config.option<ConfigOptionBools>("filament_is_mixed"))
opt->values.resize(n, false);
if (auto* opt = project_config.option<ConfigOptionStrings>("filament_mixed_components"))
+3 -6
View File
@@ -2615,12 +2615,9 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
print_object_instances_ordering = sort_object_instances_by_model_order(*this);
// A mixed slot is virtual; only its components reach a nozzle. These per-object orderings
// are unsorted (no resolve_mixed_filaments), so expand the slots here for the grouping, the
// unprintable sets and the slice-used lists. No-op without mixed filaments.
// Orca: the slice-used lists stay sourced from these expanded lists rather than from the
// sorted orderings (which may add the wipe-tower filament or seed dontcare layers
// differently), so prints without mixed filaments keep their used-filament set; the
// first-layer set therefore lists every component of a mixed slot, not just the one layer 0
// resolves to.
// unprintable sets and the slice-used lists. Because the expansion happens here rather than
// on the sorted orderings, the first-layer used set lists every component of a mixed slot,
// not just the one layer 0 resolves to. No-op without mixed filaments.
const auto &is_mixed = m_config.filament_is_mixed.values;
const auto &comp_strs = m_config.filament_mixed_components.values;
const bool has_mixed = has_any_mixed_filament(is_mixed);
+2 -2
View File
@@ -1931,8 +1931,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
for (const ModelVolume *volume : volumes) {
const std::vector<bool> &volume_used_facet_states = volume->mmu_segmentation_facets.get_data().used_states;
// Sizes may legitimately differ: paint data stored before the state range was
// extended carries a shorter used_states vector. Merge over the common prefix.
// Paint data saved before the painted state range was extended deserializes a
// shorter used_states vector, so merge over the common prefix.
for (size_t state_idx = 0; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx)
used_facet_states[state_idx] |= volume_used_facet_states[state_idx];
}
@@ -639,9 +639,8 @@ static bool repair_cluster_smooth(
{
TriangleMesh stats_mesh(static_cast<const indexed_triangle_set&>(mesh));
const auto& stats = stats_mesh.stats();
// Orca's TriangleMeshStats defines manifold() as open_edges == 0 and does not track
// non-manifold edges/vertices separately, so BBS's "!manifold() || has_open_edges()"
// collapses to this single test and the extra counters drop out of the log.
// Orca's TriangleMeshStats only counts open edges: manifold() is open_edges == 0, and
// there are no separate non-manifold edge/vertex counters to test or log here.
if (!stats.manifold()) {
BOOST_LOG_TRIVIAL(info) << log_prefix << ": mesh has non-manifold geometry or open boundaries, open_edges="
<< stats.open_edges;
+3 -4
View File
@@ -64,10 +64,9 @@ static constexpr double LARGE_BED_THRESHOLD = 2147;
// Orca: maximum number of extruders is 64. For SEMM printers, it defines maximum filament number.
static constexpr size_t MAXIMUM_EXTRUDER_NUMBER = 64;
// Orca: how many filament slots syncing an AMS setup may create. This used to follow
// EnforcerBlockerType::ExtruderMax, which capped it at the number of paintable filaments; that
// limit has since been raised to 32, so the value is pinned here to keep AMS sync behaving as
// before for projects that use no mixed-colour filaments.
// Orca: how many filament slots syncing an AMS setup may create. This was derived from
// EnforcerBlockerType::ExtruderMax, but that cap now covers 32 paintable filaments, so the AMS
// limit is pinned here to keep sync behaving as it does for projects without mixed-color filaments.
static constexpr size_t MAXIMUM_AMS_SYNC_FILAMENT_NUMBER = 16;
// Orca: maximum line width is 5 times the nozzle diameter
+3 -7
View File
@@ -577,13 +577,9 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
}
// BBS
// A filament override naming a slot that no longer exists is stale and falls back to the
// plater's value. Support and the wipe tower are additionally restricted to physical filaments:
// the engine consumes those keys directly, with no per-layer mixed resolution, so a virtual
// slot there would reach the G-code unresolved. The per-feature keys have no such restriction —
// LayerTools::extruder() and its siblings resolve a mixed slot to the physical filament chosen
// for each layer. The sidebar dropdowns already hide mixed slots for the restricted keys
// (Plater.cpp DynamicFilamentList); this reset covers values loaded from projects.
// Reset filament overrides pointing at a slot that no longer exists. Support and the wipe
// tower additionally reject mixed slots: the engine consumes those keys directly, so a virtual
// slot would reach the G-code unresolved, while the per-feature keys are resolved per layer.
static const char* physical_only_keys[] = { "support_filament", "support_interface_filament", "wipe_tower_filament" };
static const char* feature_keys[] = { "outer_wall_filament_id", "inner_wall_filament_id",
"sparse_infill_filament_id", "internal_solid_filament_id",
+1 -2
View File
@@ -131,8 +131,7 @@ void fill_gradient_ramp_rect(wxDC& dc, const wxRect& rect, const std::vector<wxC
dc.SetPen(*wxTRANSPARENT_PEN);
for (int y = 0; y < rect.height; ++y) {
// Row 0 is the top of the rect and so takes the ramp's last entry, the model's top.
// Mapping over height - 1 keeps both ends of the ramp on screen; a swatch is often
// shorter than the ramp is long, so truncating either end would be visible.
// Mapping over height - 1 puts both ends of the ramp on screen even in a short swatch.
const double t = (rect.height > 1) ? (double) (rect.height - 1 - y) / (rect.height - 1) : 0.5;
dc.SetBrush(wxBrush(ramp[static_cast<size_t>(t * (ramp.size() - 1) + 0.5)]));
dc.DrawRectangle(rect.x, rect.y + y, rect.width, 1);
+5 -7
View File
@@ -38,19 +38,17 @@ wxColour blend_n_colors(const std::vector<wxColour>& cols, const std::vector<dou
// Sample a gradient mixed filament the way the slicer builds it: t runs 0..1 over the
// model's height, the curve gives the first component's ratio at t, and the two
// components are blended at that ratio. Entry 0 is the bottom of the model, the last
// entry its top. Blending goes through blend_n_colors, so measured mixes and the
// reserved [kGradientMinRatio, kGradientMaxRatio] band are both respected — a plain
// two-endpoint fade is neither.
// components are blended at that ratio through blend_n_colors. Entry 0 is the bottom
// of the model, the last entry its top.
std::vector<wxColour> sample_gradient_ramp(const wxColour& first,
const wxColour& second,
const Slic3r::GradientCurve& curve,
int steps);
// Same ramp for a project config slot, resolving components, colours and curve (or the
// linear gradient_range fallback) from cfg. Empty unless the slot is a two-component
// gradient mixed filament, which is what gates every caller to mixed slots only.
// steps is the ramp's resolution; pass the destination's height in pixels.
// linear gradient_range fallback) from cfg. Returns empty for any slot that is not a
// two-component gradient mixed filament. steps is the ramp's resolution; pass the
// destination's height in pixels.
std::vector<wxColour> mixed_gradient_ramp(const Slic3r::DynamicPrintConfig& cfg, size_t slot, int steps);
// Fill rect with a ramp, ramp.front() along the bottom edge.
+3 -4
View File
@@ -9681,10 +9681,9 @@ void GLCanvas3D::_render_paint_toolbar() const
}
}
}
// ORCA: the loop above only produces a label for a slot whose preset is found in the preset
// collection, while the render loop below iterates extruder_num (= colour count). Pad the
// label arrays so a slot without a matching preset cannot index past them — reading a garbage
// std::string here crashes in ImGui::CalcTextSize (strlen).
// ORCA: the loop above only labels a slot whose preset was found in the preset collection,
// while the render loop below iterates extruder_num. Pad the label arrays so a slot without a
// matching preset cannot index past them; a garbage std::string crashes ImGui::CalcTextSize.
while (int(filament_text_first_line.size()) < extruder_num) {
filament_text_first_line.emplace_back();
filament_text_second_line.emplace_back();
+3 -3
View File
@@ -8906,9 +8906,9 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch
auto* nozzle_diameter = edited_printer_preset.config.option<ConfigOptionFloats>("nozzle_diameter");
if (nozzle_diameter) {
// Mixed-color slots are virtual filaments kept at the tail of the list, so they have no
// nozzle of their own. Sizing to the nozzle count alone truncates them away — and this
// runs right after a project is loaded, so it would silently drop the project's mixes
// and then let update_extruder_count() strip every painted facet above the new count.
// nozzle of their own. Sizing to the nozzle count alone would silently drop the mixes of
// a just-loaded project, and update_extruder_count() would then strip the facets painted
// with them.
preset_bundle->set_num_filaments(nozzle_diameter->values.size() + preset_bundle->num_mixed_filaments());
}
}
+2 -2
View File
@@ -3234,8 +3234,8 @@ void ObjectList::merge(bool to_multipart_object)
void ObjectList::layers_editing()
{
// Height ranges give each range its own layer height, varying the mixed sub-layer heights just
// like an adaptive profile; sibling of the on_action_layersediting/ConfigManipulation warnings,
// sharing the same do-not-show-again flag.
// like an adaptive profile, so this raises the same warning as variable layer height and shares
// its do-not-show-again flag.
const auto& print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
if (print_config.opt_bool("enable_mixed_color_sublayer")) {
if (wxGetApp().app_config->get("no_warn_mixed_sublayer_variable_layer") != "1") {
@@ -326,10 +326,9 @@ bool GLGizmoMmuSegmentation::draw_color_button(int idx, const char* id_str, cons
bool dark_tone = gradient ? (*gradient)[gradient->size() / 2].GetLuminance() < 0.51 :
(0.299f * color.r() + 0.587f * color.g() + 0.114f * color.b()) < 0.51f; // matching values used by wxWidgets with clr.GetLuminance() < 0.51
// Paint a gradient mixed filament's fade before the button and keep the button transparent, so the
// slot number and the frame below stay on top of it. The bands cannot round their corners, so the
// fade is drawn at the frame's inset and the frame masks it into the same shape a plain color slot
// gets.
// Paint a gradient mixed filament's fade before the button and keep the button transparent, so
// the slot number and the frame below stay on top of it. The bands cannot round their corners,
// so the fade is inset to the frame, which masks it into the shape a plain color slot gets.
if (gradient) {
ImGuiWrapper::draw_gradient_ramp(draw_list, {pos.x + frame_inset * scale, pos.y + frame_inset * scale},
{pos.x + size.x - frame_inset * scale, pos.y + size.y - frame_inset * scale}, *gradient);
@@ -778,7 +777,7 @@ void GLGizmoMmuSegmentation::update_triangle_selectors_colors()
TriangleSelectorPatch* selector = dynamic_cast<TriangleSelectorPatch*>(m_triangle_selectors[i].get());
int extruder_idx = m_volumes_extruder_idxs[i];
int extruder_color_idx = std::max(0, extruder_idx - 1);
// As above: a mixed-color slot can index past the physical colour list.
// A mixed-color slot can index past the physical colour list; fall back to the first colour.
if (extruder_color_idx >= (int)m_extruders_colors.size())
extruder_color_idx = 0;
std::vector<ColorRGBA> ebt_colors;
@@ -142,7 +142,7 @@ private:
// ORCA
bool draw_color_button(int idx, const char* id_str, const ColorRGBA& color, ColorRGBA& map_color, bool active, float scale);
// Gradient ramp of a filament slot, or nullptr when the slot is a plain single color
// filament, so callers can index into what they get back freely.
// filament. A non-null result is never empty.
const std::vector<wxColour>* gradient_of(int idx) const
{
return idx >= 0 && idx < (int) m_gradient_ramps.size() && !m_gradient_ramps[idx].empty() ? &m_gradient_ramps[idx] : nullptr;
+2 -2
View File
@@ -998,8 +998,8 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
keyCode = keyCode- WXK_NUMPAD0+'0';
}
if (keyCode >= '0' && keyCode <= '9') {
// The paint palette now reaches EXTRUDERS_LIMIT (mixed-color filaments share
// the same slots), so any leading digit that can start a valid two-digit
// The paint palette reaches EXTRUDERS_LIMIT slots (mixed-color filaments take
// ordinary slots too), so any leading digit that can start a valid two-digit
// number waits briefly for a second one.
const int digit = keyCode - '0';
const int shortcut_max = int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT);
+11 -19
View File
@@ -19,7 +19,7 @@ namespace GUI {
wxDEFINE_EVENT(wxEVT_GRADIENT_CURVE_CHANGED, wxCommandEvent);
namespace {
// Layout (Figma "Property 1=Default", 214.06 x 179.63 px reference).
// Layout ratios of the plot rect within the widget, taken from a 214 x 180 px reference drawing.
// Plot rect occupies the upper-left region; right + bottom margins host axis arrows / labels.
constexpr double kPlotLeftRatio = 0.0316;
constexpr double kPlotRightRatio = 0.6766;
@@ -37,7 +37,7 @@ constexpr int kStrokeAxis = 2; // axis line width (px, no DPI scaling -
constexpr int kAxisArrowHalf = 5; // half-base of the axis arrow triangle (DIP)
constexpr int kAxisArrowLen = 10; // length of the axis arrow triangle (DIP)
// Light-mode design tokens from Figma. Resolved through StateColor::darkModeColorFor()
// Light-mode design tokens. Resolved through StateColor::darkModeColorFor()
// at paint time so the editor follows the app theme (#EEEEEE -> #4C4C55, #6B6B6B ->
// #818183, #262E30 -> #EFEFF0, *wxWHITE -> #2D2D31). Don't read these directly in paint;
// always go through the resolved locals declared at the top of on_paint().
@@ -46,11 +46,9 @@ const wxColour kAxisColor (107, 107, 107); // #6B6B6B grey 700
const wxColour kLabelMuted (107, 107, 107); // #6B6B6B grey 700
const wxColour kLabelStrong ( 38, 46, 48); // #262E30 grey 900
// LAB (DeltaE76) threshold for "curve color is too close to the background". Below this
// we paint a subtle axis-color outline so the curve doesn't visually vanish; above this
// we draw the curve plain. ~15 is "perceptible but still close", looser than the strict
// 5.0 used by FlushPredict::is_similar_color but loose enough that a pastel pink on white
// or a charcoal on #2B2B2B still triggers an outline.
// LAB (DeltaE76) threshold for "curve color is too close to the background": below it the curve
// gets a subtle outline so it does not visually vanish, otherwise it is drawn plain. Looser than
// the 5.0 of FlushPredict::is_similar_color, so a pastel pink on white still gets an outline.
constexpr float kBgSimilarThreshold = 15.0f;
constexpr int kOutlineExtraDip = 2;
} // namespace
@@ -65,8 +63,6 @@ GradientCurveEditor::GradientCurveEditor(wxWindow* parent,
SetBackgroundStyle(wxBG_STYLE_PAINT);
SetBackgroundColour(wxGetApp().get_window_default_clr());
// Wide enough so the X-axis "Material Ratio" label fits past the arrow tip without overlap.
// 260 (was 240): adds room for the "Material Ratio" label that gets shifted right by the
// longer axis arrow; the hosting MixedFilamentDialog grows to 470 DIP to accommodate.
SetMinSize(FromDIP(wxSize(260, 200)));
reset_to_linear(0.10, 0.90);
@@ -456,10 +452,9 @@ void GradientCurveEditor::on_paint(wxPaintEvent& /*evt*/)
return poly;
};
// Only the geometry goes through the graphics context: dc.DrawLines() takes integer
// wxPoint and would quantize the curve back to whole pixels. The pen is still set on
// the dc, which forwards it to this same context while keeping the dc's own cached
// state in sync, so later dc drawing does not inherit the curve's pen.
// Only the geometry goes through the graphics context: dc.DrawLines() takes integer wxPoint
// and would quantize the curve back to whole pixels. The pen is still set on the dc, which
// forwards it here while keeping its own cached state in sync for later dc drawing.
auto draw_polyline = [&](const std::vector<wxPoint2DDouble>& poly, const wxColour& col, int stroke_dip) {
dc.SetPen(wxPen(col, FromDIP(stroke_dip)));
gc->StrokeLines(poly.size(), poly.data());
@@ -552,12 +547,9 @@ void GradientCurveEditor::on_left_down(wxMouseEvent& evt)
// 4) Selected curve line body hit -> insert a new anchor at cursor x (snapped
// to the current smooth curve so the initial click is visually invisible)
// and immediately enter Anchor drag mode. PS Curves style: the drag-bend
// interaction has no separate "bend without anchor" mode; pressing and
// dragging on the line is equivalent to clicking to add then dragging the
// fresh anchor. Trades the previous (failed) "no anchor on drag" promise
// for genuine cursor tracking, since a single cubic between two existing
// anchors mathematically cannot put its peak under an off-center cursor.
// and immediately enter Anchor drag mode. Bending the segment without
// inserting an anchor is not an option: a single cubic between two existing
// anchors cannot put its peak under an off-center cursor.
double nx = 0, dummy = 0;
px_to_data(pos.x, pos.y, nx, dummy);
if (nx <= 0.0 || nx >= 1.0 || seg < 0) {
+6 -8
View File
@@ -894,9 +894,9 @@ wxBoxSizer* MixedFilamentDialog::create_ratio_slider()
on_ratio_changed(std::max(MIN_COMPONENT_RATIO, std::min(100 - MIN_COMPONENT_RATIO, new_ratio)));
});
// Release whenever the capture is held, not only when the drag flag is set:
// the flag can be cleared behind our back, and a capture that outlives the
// widget wedges mouse input for the whole application.
// Key the release off the capture itself, not off the drag flag: the two can fall out of
// sync (a lost capture clears the flag on its own), and a capture that outlives the widget
// wedges mouse input for the whole application.
m_ratio_bar->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent&) {
m_ratio_dragging = false;
if (m_ratio_bar->HasCapture())
@@ -1492,11 +1492,9 @@ void MixedFilamentDialog::on_ratio_changed(int new_ratio_a)
void MixedFilamentDialog::on_gradient_toggled()
{
// Orca: the engine only produces a gradient when the print profile's
// "enable_mixed_color_sublayer" option is on (ToolOrdering::resolve_mixed_filaments
// falls back to whole-layer round-robin without it, and BBS leaves users to find the
// option themselves). Offer to switch it on so the gradient the user just enabled
// actually shows up in the sliced result. Keep this block on future BBS syncs.
// Orca: a gradient is only sliced when the print profile's "enable_mixed_color_sublayer"
// option is on; without it ToolOrdering picks a single component per whole layer. Offer to
// turn the option on instead of silently ignoring the gradient the user just enabled.
bool checked = m_chk_gradient->GetValue();
if (checked) {
+2 -7
View File
@@ -2048,13 +2048,8 @@ bool PartPlate::check_tpu_printable_status(const DynamicPrintConfig & config, co
}
// A mixed-color filament alternates between its components constantly. On a single-nozzle
// printer every one of those switches is a full filament change plus a purge, so warn the
// user before they commit to it. Printers with more than one nozzle can keep the components
// loaded simultaneously and are not affected.
//
// BBS additionally excludes its H2C/H2D/X2D models by name; those are multi-nozzle machines
// already ruled out by the nozzle_diameter test above, so the name check is dropped here
// rather than carried over as a Bambu-specific special case.
// printer every one of those switches is a full filament change plus a purge, so warn before
// slicing. Multi-nozzle printers keep the components loaded at once and are not affected.
bool PartPlate::check_single_extruder_mixed_filament_risk(const DynamicPrintConfig &config, std::string &warning_text) const
{
warning_text.clear();
+1 -2
View File
@@ -473,8 +473,7 @@ PlateSettingsDialog::PlateSettingsDialog(wxWindow* parent, const wxString& title
m_sizer_main->Add(m_other_layers_seq_panel, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(30));
// A mixed-color slot resolves to a different physical filament per layer, so a user-defined
// filament order cannot be honoured. Disable the choice and say why. BBS puts this warning
// inside its button sizer; Orca builds the buttons with DialogButtons, so it gets its own row.
// filament order cannot be honoured; grey out the choice and explain that in the dialog.
{
auto &proj_cfg = wxGetApp().preset_bundle->project_config;
auto *is_mixed_opt = proj_cfg.option<ConfigOptionBools>("filament_is_mixed");
+14 -22
View File
@@ -3876,10 +3876,8 @@ void Sidebar::change_top_border_for_mode_sizer(bool increase_border)
// ---- Mixed-color filament sidebar support ----
// Ported from BambuStudio's 混色耗材 feature. BBS hosts these widgets in an
// m_filament_area_wrapper that Orca's sidebar has no counterpart for, so the mixed
// section is parented to p->scrolled and sized with Orca's own row-height preference
// (filaments_area_preferred_count) rather than BBS's fixed 3-row / 12-filament cap.
// The mixed rows get their own scroll area, capped by Orca's filaments_area_preferred_count
// row budget rather than BBS's fixed 3-row / 12-filament limit.
void Sidebar::recalc_filament_scroll_sizes()
{
if (!p->m_mixed_scroll_area || !p->m_mixed_scroll_area->GetSizer())
@@ -4102,8 +4100,8 @@ void Sidebar::update_mixed_filament_list()
unsigned int mix_num = (unsigned int)(cfg_idx + 1);
// The swatch fades bottom to top over the model's height, sampled the same way
// the slicer builds the sublayers, so it matches the editor's Effect Preview. It
// comes back empty for every slot that is not a two component gradient mix.
// the slicer builds the sublayers, so it matches the editor's Effect Preview. The
// ramp comes back empty for every slot that is not a two component gradient mix.
const int swatch_sz = FromDIP(20);
const std::vector<wxColour> gradient_ramp = mixed_gradient_ramp(project_config, cfg_idx, swatch_sz);
@@ -4642,9 +4640,8 @@ static bool create_mixed_filament_from_result(
multi_colour_opt->values[new_idx] = mixed_color;
}
// set_num_filaments() above is what grows these parallel arrays. Guard the writes anyway,
// matching the gradient writes below, so a sizing bug degrades into a no-op rather than a
// heap overwrite.
// set_num_filaments() above already grows these parallel arrays; the writes are still
// size-guarded so a sizing bug degrades into a no-op rather than a heap overwrite.
{
auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
while (is_mixed_opt->values.size() <= new_idx) is_mixed_opt->values.push_back(false);
@@ -14071,11 +14068,9 @@ bool Plater::priv::can_layers_editing() const
void Plater::priv::on_action_layersediting(SimpleEvent&)
{
// Sub-layer splitting divides each layer by the mix ratio, so an adaptive layer profile makes
// those sub-layer heights vary and degrades the blend. ConfigManipulation warns when the
// option is switched on with a variable profile already present; this is the other direction,
// warning when variable layer editing is switched on while the option is active. All three
// sites (with ObjectList::layers_editing for height ranges) honour the same do-not-show-again flag.
// Sub-layer splitting divides each layer by the mix ratio, so a variable layer height profile
// makes those sub-layer heights uneven and degrades the blend. ConfigManipulation warns for the
// opposite order, when the option is switched on while a variable profile already exists.
if (!view3D->is_layers_editing_enabled()) {
const auto& print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
if (print_config.opt_bool("enable_mixed_color_sublayer")) {
@@ -19988,14 +19983,11 @@ std::vector<std::string> Plater::get_filament_color_render_type() const
const std::vector<std::vector<wxColour>>& Plater::get_filament_gradient_ramps() const
{
// Sampling a ramp walks the measured-blend recipe table once per step, and the paint toolbar
// asks for the ramps on every rendered frame, so they are cached against the config values
// they are built from and resampled only when one of those actually changes.
//
// The cache cannot live on the Plater: the extruder icons ask for the ramps from inside
// MenuFactory::init(), which runs while this Plater is still being constructed, so `this` is
// not usable yet. Everything the ramps are built from is global anyway, and there is one
// Plater per process, which is the same reasoning behind the icons' own static BitmapCache.
// Sampling a ramp walks the measured-blend recipe table once per step and the paint toolbar
// asks for the ramps every rendered frame, so they are cached against the config values they
// are built from. The cache is static rather than a Plater member because the extruder icons
// ask for the ramps from MenuFactory::init(), which runs while this Plater is still inside its
// own constructor, so wxGetApp().plater_ is not assigned yet.
static std::string s_ramps_key;
static std::vector<std::vector<wxColour>> s_ramps;
+2 -2
View File
@@ -2577,7 +2577,7 @@ void SyncAmsInfoDialog::reset_and_sync_ams_list()
m_filaments.clear();
// Mixed-color slots are virtual: they never occupy a tray, so they must not appear as
// AMS sync targets. Look the flags up once and skip those slots in the loop below.
// AMS sync targets.
auto* is_mixed_opt = preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed");
bool use_double_extruder = get_is_double_extruder();
@@ -2801,7 +2801,7 @@ void SyncAmsInfoDialog::generate_override_fix_ams_list()
m_fix_filaments.clear();
// Mixed-color slots are virtual: they never occupy a tray, so they must not appear as
// AMS sync targets. Look the flags up once and skip those slots in the loop below.
// AMS sync targets.
auto* is_mixed_opt = preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed");
bool use_double_extruder = get_is_double_extruder();
+6 -9
View File
@@ -1368,11 +1368,9 @@ void TexturePreviewCanvas::ensure_gl_ready()
{
if (m_gl_initialized) return;
// BBS loads GL entry points here with GLEW. Orca uses glad and centralises loading in
// OpenGLManager, which has already run by the time any canvas is realized, so just
// verify the loader is up and drain any stale error state.
// glad leaves unresolved entry points as null pointers, so this is a cheap guard against
// painting before OpenGLManager::init_gl() has run.
// BBS loads the GL entry points here with GLEW; Orca loads them centrally in
// OpenGLManager, so only check that this has already happened (glad leaves unresolved
// entry points null) and drain any stale error state.
if (glGetString == nullptr) {
BOOST_LOG_TRIVIAL(error) << "TexturePreviewCanvas: OpenGL functions are not loaded yet";
return;
@@ -2436,10 +2434,9 @@ void TextureImportDialog::start_computation(bool auto_color, bool initial)
settings.target_colors_num = auto_color ? 0 : (size_t)m_param_color_count;
settings.smooth_weight = m_param_smooth / 10.0;
settings.mesh_repair_decision = m_mesh_repair_decision;
// BBS repairs the mesh through the Windows 3D SDK, which only exists on Windows and only
// when the SDK is present at build time. Orca already ships a CGAL-based repair
// (MeshBoolean::cgal::repair) that works on all three platforms, so use that instead
// this makes the repair path available on Linux and macOS too.
// BBS repairs the mesh through the Windows 3D SDK, which is only available on Windows
// builds that ship the SDK. Orca's CGAL-based repair (MeshBoolean::cgal::repair) works
// on all three platforms, so use that instead.
settings.mesh_repair_callback = [](const indexed_triangle_set& mesh,
indexed_triangle_set& repaired_mesh,
std::function<void(const char*, unsigned)> progress_callback,
+1 -2
View File
@@ -360,8 +360,7 @@ void DropDown::render(wxDC &dc)
for (int i = 0; i < items.size(); ++i) {
auto &item = items[i];
int states2 = states;
// Dimmed items stay selectable but render greyed out (used by the mixed-filament
// dialog to show components that are already consumed by another mix).
// Dimmed items render greyed out but stay selectable, so they cannot reuse the disabled state.
bool is_dimmed = (item.style & DD_ITEM_STYLE_DIMMED) != 0;
if ((item.style & DD_ITEM_STYLE_DISABLED) != 0)
states2 &= ~StateColor::Enabled;
+3 -4
View File
@@ -261,10 +261,9 @@ static std::vector<float> MatrixFlatten(const WipingDialog::VolumeMatrix& matrix
return vec;
}
// Mixed-color slots are virtual: they are never loaded into a tray and so have no flushing
// volumes of their own. The dialog therefore shows only the physical filaments, which means
// converting between the full config matrix (indexed by config slot) and a dense physical
// sub-matrix (indexed by row/column in the table).
// Mixed-color slots are virtual and have no flushing volumes, so the dialog shows only the
// physical filaments. That means converting between the full config matrix (indexed by config
// slot) and a dense physical sub-matrix (indexed by row/column in the table).
static std::vector<double> extract_physical_sub_matrix(
const std::vector<double>& full_matrix, size_t full_n,
const std::vector<size_t>& indices)
+3 -4
View File
@@ -37,7 +37,7 @@ DynamicPrintConfig mixed_config(bool sublayer_on, const char *ratios = "0.6,0.4"
return config;
}
// Total sub-layer groups and per-layer DRR resolutions across the whole tool ordering.
// Total sub-layer groups and per-layer mixed-filament resolutions across the whole tool ordering.
void count_mixed(ToolOrdering &to, size_t &groups, size_t &resolutions)
{
groups = resolutions = 0;
@@ -139,9 +139,8 @@ TEST_CASE("Whole-layer mixing emits only the nominal layer height", "[MixedFilam
TEST_CASE("By-object prints without mixed filaments keep their used-filament set", "[MixedFilament]")
{
// Regression guard for the mixed gate: with no mixed slot the by-object bookkeeping must
// be untouched by this change. Object 2 prints with filament 2, so both filaments are used
// and no mixed filament is reported.
// With no mixed slot the by-object bookkeeping stays plain: object 2 prints with filament 2,
// so both filaments are used and no mixed filament is reported.
DynamicPrintConfig config = multifilament_config(2, {{"print_sequence", "by object"}});
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides{ {}, { {"extruder", "2"} } };
+2 -4
View File
@@ -501,10 +501,8 @@ SCENARIO("Nozzle-group metadata .3mf round-trip", "[3mf][MultiNozzle]") {
// A mixed-color filament occupies an ordinary filament slot, and painting with it stores an
// ordinary extruder state a project saved by BambuStudio encodes filament 5 of a 5-slot setup
// as paint state 5, with the mix described by the parallel filament_mixed_* project arrays. This
// pins both halves of that contract at the .3mf layer: the project keys and the painted states
// must come back exactly as written.
// ordinary extruder state: a project saved by BambuStudio encodes filament 5 of a 5-slot setup
// as paint state 5, with the mix described by the parallel filament_mixed_* project arrays.
SCENARIO("Mixed-color filament setup and painting round-trip through a .3mf", "[3mf][MixedFilament]") {
GIVEN("a painted model whose project config describes a mixed filament in the last slot") {
Model model;
+5 -6
View File
@@ -101,10 +101,9 @@ TEST_CASE("check_mixed_filament_type_consistency flags mismatched component type
TEST_CASE("a support-flagged component reads as its own filament type for the consistency check", "[FilamentMixer]")
{
// Sidebar::update_mixed_filament_list and Sidebar::has_broken_mixed_filament derive each
// component's type through DynamicPrintConfig::get_filament_type, which folds the
// filament_is_support flag into the type — so toggling that flag alone changes the verdict
// and Plater::on_config_change has to refresh the mixed list on filament_is_support too.
// The sidebar derives each component's type through DynamicPrintConfig::get_filament_type,
// which folds filament_is_support into the type, so toggling that flag alone flips the
// verdict and the mixed filament list has to be refreshed on filament_is_support too.
DynamicPrintConfig plain_pla;
plain_pla.set_key_value("filament_type", new ConfigOptionStrings({"PLA"}));
plain_pla.set_key_value("filament_is_support", new ConfigOptionBools({false}));
@@ -193,8 +192,8 @@ TEST_CASE("blend_color_multi weights components", "[FilamentMixer]")
}
SECTION("Mixing a color with itself stays close to that color") {
// The mixer is a degree-4 polynomial fit of pigment behaviour, so a round trip through
// it is near-identity rather than exact (the model documents a mean Delta-E around 2).
// The mixer is a degree-4 polynomial fit of pigment behaviour, so mixing a color with
// itself lands near it rather than exactly on it; allow a small per-channel drift.
std::string mixed = blend_color_multi({"#123456", "#123456"}, {1, 1});
REQUIRE(mixed.size() == 7);
auto comp = [](const std::string &hex, int i) {
+7 -10
View File
@@ -614,12 +614,10 @@ TEST_CASE("set_num_filaments keeps mixed-color arrays in step with the filament
}
}
// A mix is described by 1-based indices into the project's filament list. Orca's per-printer
// preset memory rebuilds that list from the selected printer's snapshot (filament_%02u /
// filament_colors) at startup and on every printer selection, so the mixed arrays must be stored
// in the SAME per-printer snapshot: kept globally (as BambuStudio does — its filament list is a
// single global snapshot too) they end up indexing a list they were never saved against, and used
// to be reset on every printer selection instead, losing the mixes over an app restart.
// A mix is described by 1-based indices into the project's filament list, which Orca rebuilds
// from the selected printer's snapshot (filament_%02u / filament_colors) at startup and on every
// printer selection. Held anywhere but that same per-printer snapshot, the mixed arrays end up
// indexing a filament list they were never saved against.
TEST_CASE("Mixed-color filament metadata is snapshotted per printer, with its filament list", "[Preset][Bundle][FilamentMixer]")
{
PresetBundle bundle;
@@ -674,10 +672,9 @@ TEST_CASE("A multi-point gradient curve survives the app-config snapshot", "[Pre
}
// A multi-tool printer sizes the filament list from its nozzle count. Mixed-color slots are extra
// virtual filaments at the tail of that list with no nozzle of their own, so the sync has to add
// them on top. Sizing to the nozzle count alone truncates them — and because that sync runs right
// after a project is loaded, it silently drops the project's mixes and then lets the filament-count
// change strip every painted facet above the new count.
// virtual filaments at the tail of that list with no nozzle of their own, so the count has to
// allow for them: sizing to the nozzle count alone drops the project's mixes and strips every
// painted facet above the new count.
TEST_CASE("Sizing the filament list to a multi-tool nozzle count keeps mixed slots", "[Preset][Bundle][FilamentMixer]")
{
// The 5-slot layout of a 4-tool project carrying one mix of filaments 2 and 3.
+1 -1
View File
@@ -99,7 +99,7 @@ TEST_CASE("Extruder states above 17 are encoded in a second nibble", "[TriangleS
}
// Model.cpp writes these hex strings into the 3MF for colored mesh imports; the selector must
// decode exactly the states that table assigns to them.
// decode exactly the states CONST_FILAMENTS assigns to them.
TEST_CASE("Extruder states match the CONST_FILAMENTS hex encoding", "[TriangleSelector]")
{
struct Case { const char *hex; int state; };
@@ -140,8 +140,8 @@ TEST_CASE("recompute_mixed_slot_colors honours the configured ratios and is idem
// --- mixed_gradient_ramp / sample_gradient_ramp -----------------------------------------
//
// The ramp is what every mixed filament swatch is drawn from, so these pin the three things
// a plain two-endpoint fade got wrong: the reserved ratio band, the component order, and the
// custom curve.
// a plain fade between two endpoint colours cannot express: the reserved ratio band, the
// component order, and the custom curve.
namespace {
@@ -169,7 +169,7 @@ TEST_CASE("mixed_gradient_ramp runs bottom to top and never reaches a pure compo
REQUIRE(ramp.size() == 16);
// Neither end is the pure component colour - the slicer clamps the blend to
// [kGradientMinRatio, kGradientMaxRatio], which is exactly what a two-endpoint fade missed.
// [kGradientMinRatio, kGradientMaxRatio], which a fade between the pure colours would ignore.
REQUIRE(ramp.front() != wxColour(255, 0, 0));
REQUIRE(ramp.back() != wxColour(0, 0, 255));
@@ -188,7 +188,7 @@ TEST_CASE("mixed_gradient_ramp follows the range's direction rather than the com
REQUIRE(falling.size() == 16);
// "0.1,0.9" starts blue-heavy at the bottom; "0.9,0.1" starts red-heavy. Reversing the
// range must reverse the ramp, which HSV-sorted endpoint colours could not express.
// range must reverse the ramp, which endpoint colours ordered by HSV cannot express.
REQUIRE(int(rising.front().Blue()) > int(rising.front().Red()));
REQUIRE(int(falling.front().Red()) > int(falling.front().Blue()));
require_same_rgb(rising.front(), falling.back());