Compare commits

..
21 changed files with 234 additions and 334 deletions
-3
View File
@@ -70,9 +70,6 @@ if (POLICY CMP0092)
cmake_policy(SET CMP0092 NEW)
endif ()
# project() reads this, so set it first.
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/ClangClShowIncludes.cmake")
project(OrcaSlicer)
# Backward compatibility for old CMake versions
-10
View File
@@ -1,10 +0,0 @@
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
# depfile, so a cache hit writes the object and no depfile, and Ninja then records
# no headers for that object. ccache reproduces /showIncludes output on a hit.
foreach (_lang C CXX)
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
endif ()
endforeach ()
-10
View File
@@ -38,14 +38,6 @@ if(POLICY CMP0135) # DOWNLOAD_EXTRACT_TIMESTAMP
cmake_policy(SET CMP0135 NEW)
endif()
# project() reads this, so set it first. scripts/flatpak/make_deps_tar.sh packs deps/
# without cmake/, so the file is missing in a Flatpak build.
set(_rules_override "${CMAKE_CURRENT_LIST_DIR}/../cmake/modules/ClangClShowIncludes.cmake")
if (EXISTS "${_rules_override}")
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${_rules_override}")
endif ()
unset(_rules_override)
project(OrcaSlicer-deps)
# Backward compatibility for old CMake versions
@@ -228,7 +220,6 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
-DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_USER_MAKE_RULES_OVERRIDE:STRING=${CMAKE_USER_MAKE_RULES_OVERRIDE}
-DCMAKE_TOOLCHAIN_FILE:STRING=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS}
-DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS}
@@ -276,7 +267,6 @@ else()
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_USER_MAKE_RULES_OVERRIDE:STRING=${CMAKE_USER_MAKE_RULES_OVERRIDE}
-DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch}
"${_configs_line}"
+57 -23
View File
@@ -30,7 +30,7 @@ var USER_MODE = "simple";
var MODE_RANK = { simple: 0, advanced: 1, expert: 2, develop: 3 };
// Search ranking weights: every contiguous match must outrank every fuzzy one regardless of field,
// and title must outrank group, which outranks source.
// and title must outrank group/category, which outranks source.
var SCORE_CONTIGUOUS = 100000;
var SCORE_TITLE = 2000;
var SCORE_GROUP = 1000;
@@ -118,6 +118,18 @@ function sourceNorm(a) {
a._sn = NormText(a.source || "", false);
return a._sn;
}
function pluginCategoryNorm(a) {
if (a._pcn === undefined)
a._pcn = a.kind === "plugin" ? NormText(T("sd_plugins", "Plugins"), false) : "";
return a._pcn;
}
// Plugin type is searchable metadata, so typing "plugin" can find runnable plugin actions even
// when neither their capability nor plugin name contains that word. Keep both category forms.
function pluginTypeNorm(a) {
if (a._pn === undefined)
a._pn = a.kind === "plugin" ? NormText("plugin plugins", false) : "";
return a._pn;
}
// Search-only alias for the descriptive name when the title differs (e.g. "Reverse on even" vs
// "Overhang reversal"). Never rendered, so no highlight ranges.
function fullNorm(a) {
@@ -152,7 +164,7 @@ function fieldMatchScore(norm, needle, wwRe) {
// Split a query into normalized (folded+lowercased) whitespace-separated tokens. Empty for a blank
// query. These drive the multi-token path: every token must match some field, but different tokens
// may match different fields (the title, the group, or the source breadcrumb).
// may match different fields (the title, group, source breadcrumb, or plugin kind).
function queryTokens(query) {
var norm = NormText(String(query || "").trim(), false);
return norm ? norm.split(/\s+/).filter(Boolean) : [];
@@ -170,14 +182,19 @@ function tokenMatch(a, token, wwRe) {
var g = fieldMatchScore(groupNorm(a), token, wwRe);
var s = fieldMatchScore(sourceNorm(a), token, wwRe);
var f = fieldMatchScore(fullNorm(a), token, wwRe);
if (!t && !g && !s && !f) return null;
var p = fieldMatchScore(pluginCategoryNorm(a), token, wwRe);
var typeMatch = fieldMatchScore(pluginTypeNorm(a), token, wwRe);
if (!t && !g && !s && !f && !p && !typeMatch) return null;
var score = Math.max(
t ? (t.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_TITLE + t.score : -Infinity,
g ? (g.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + g.score : -Infinity,
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.score : -Infinity,
f ? (f.contiguous ? SCORE_CONTIGUOUS : 0) + f.score : -Infinity
f ? (f.contiguous ? SCORE_CONTIGUOUS : 0) + f.score : -Infinity,
p ? (p.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + p.score : -Infinity,
typeMatch ? (typeMatch.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + typeMatch.score : -Infinity
);
return { score: score, title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null };
return { score: score, title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null,
plugin: p ? p.ranges : null };
}
// Merge per-field token ranges into sorted, coalesced ranges for highlighting. Overlapping or adjacent
@@ -201,8 +218,8 @@ function mergeRanges(ranges) {
}
// Combine per-field scores into one value, or null when nothing matched.
// Ranking: contiguous > fuzzy, then title > group > source/full alias, then start/gaps.
function scoreFields(t, g, s, f) {
// Ranking: contiguous > fuzzy, then title > group/category > source/full alias, then start/gaps.
function scoreFields(t, g, s, f, p, typeMatch) {
var best = null;
function consider(m, weight) {
if (!m) return;
@@ -213,6 +230,8 @@ function scoreFields(t, g, s, f) {
consider(g, SCORE_GROUP);
consider(s, 0);
consider(f, 0);
consider(p, SCORE_GROUP);
consider(typeMatch, SCORE_GROUP);
return best;
}
@@ -226,7 +245,8 @@ function scoreFields(t, g, s, f) {
// - tokens: every whitespace-separated word must match SOME field, but different words may match
// different fields. This is what lets "speed acceleration inner" find "Inner wall" whose path is
// "Process : Speed : Acceleration" (title + source breadcrumb together).
// full_label is searchable too but never highlighted, since it is not rendered.
// full_label and canonical plugin kind are searchable aliases. The visible category is also searchable
// so its own match can be highlighted in plugin rows.
// A phrase match always outranks a distributed token match.
function searchActions(actions, query) {
var q = (query || "").trim();
@@ -251,26 +271,31 @@ function searchActions(actions, query) {
var g = fieldMatchScore(groupNorm(a), searchNeedle, wwRe);
var s = fieldMatchScore(sourceNorm(a), searchNeedle, wwRe);
var f = fieldMatchScore(fullNorm(a), searchNeedle, wwRe);
var phrase = scoreFields(t, g, s, f);
var p = fieldMatchScore(pluginCategoryNorm(a), searchNeedle, wwRe);
var typeMatch = fieldMatchScore(pluginTypeNorm(a), searchNeedle, wwRe);
var phrase = scoreFields(t, g, s, f, p, typeMatch);
var score, ranges;
if (phrase !== null) {
score = phrase + SCORE_PHRASE;
ranges = { title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null };
ranges = { title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null,
plugin: p ? p.ranges : null };
} else {
// Require every token; a token that matches nothing drops the action immediately. Ranges
// from all matching tokens are merged per field so each matched word highlights.
var sum = 0, titleR = null, groupR = null, sourceR = null, all = true;
for (var k = 0; k < searchTokens.length; k++) {
var m = tokenMatch(a, searchTokens[k], searchTokenRes[k]);
var sum = 0, titleR = null, groupR = null, sourceR = null, pluginR = null, all = true;
for (var tokenIndex = 0; tokenIndex < searchTokens.length; tokenIndex++) {
var m = tokenMatch(a, searchTokens[tokenIndex], searchTokenRes[tokenIndex]);
if (!m) { all = false; break; }
sum += m.score;
if (m.title) (titleR || (titleR = [])).push(m.title);
if (m.group) (groupR || (groupR = [])).push(m.group);
if (m.source) (sourceR || (sourceR = [])).push(m.source);
if (m.plugin) (pluginR || (pluginR = [])).push(m.plugin);
}
if (!all) continue;
score = sum;
ranges = { title: mergeRanges(titleR), group: mergeRanges(groupR), source: mergeRanges(sourceR) };
ranges = { title: mergeRanges(titleR), group: mergeRanges(groupR), source: mergeRanges(sourceR),
plugin: mergeRanges(pluginR) };
}
// Ranges are per-field against the ACTUAL text drawn: title for the row-name, and group (or
// source when group is empty) for the eyebrow - so highlight offsets stay aligned to the label.
@@ -278,6 +303,7 @@ function searchActions(actions, query) {
title: ranges.title,
group: ranges.group,
source: ranges.source,
plugin: ranges.plugin,
useEyebrowGroup: !!(a.group)
};
scored.push({ a: a, s: score });
@@ -390,7 +416,7 @@ function favDigitFromEvent(e) {
function resultCountText(total, shown, query) {
return (query || "").trim() ?
T("sd_result_count", "Showing %s of %s actions", shown, total) :
T("sd_result_count", "Showing %s actions", shown) :
T("sd_result_count_all", "%s actions", total);
}
@@ -461,6 +487,12 @@ function actionCategory(a) {
return cat || T("sd_other", "Other");
}
function actionEyebrow(a, typedQuery, isRecent) {
if (a && a.kind === "plugin" && (String(typedQuery || "").trim() || isRecent))
return T("sd_plugins", "Plugins");
return (a && (a.group || a.source)) || "";
}
// Stable-bucket actions by category, then order the groups alphabetically. Within a group the incoming
// (frecency) order is kept. Pure so the node-vm test can exercise grouping.
function groupActions(list) {
@@ -711,7 +743,7 @@ window.HandleStudio = function (payload) {
builtKey = "";
if (qEl) {
qEl.value = "";
qEl.placeholder = T("sd_search_n", "Search %s actions", ACTIONS.length);
qEl.placeholder = T("sd_search", "Search actions");
syncClearButton();
}
render({ resize: true, resetScroll: true });
@@ -931,12 +963,14 @@ function beginRow(item, i, mono, ariaLabel) {
function renderActionRow(a, i) {
var on = FAVS.indexOf(a.id) !== -1;
var shell = beginRow(a, i, false, actionLabel(a, ACTIONS));
var mi = matchIndex[a.id];
// The eyebrow shows group when present, else source. Highlight with the ranges of whichever of the
// two the eyebrow actually renders (so a "Recent Projects"/"Object" header match lights up like a
// setting path does - the offsets are computed against the same string we are marking).
var eyebrow = a.group || a.source;
var eyebrowMatch = mi ? (mi.useEyebrowGroup ? mi.group : mi.source) : null;
var typedQuery = String(query || "").trim();
var isRecent = !typedQuery && i < RECENTS.length && RECENTS[i].id === a.id;
var mi = typedQuery ? matchIndex[a.id] : null;
// Plugin search/recent rows show their category; other rows show their group or source breadcrumb.
// Use match ranges from the visible field so category and breadcrumb highlights stay aligned.
var showPluginCategory = a.kind === "plugin" && (typedQuery || isRecent);
var eyebrow = actionEyebrow(a, query, isRecent);
var eyebrowMatch = mi ? (showPluginCategory ? mi.plugin : (mi.useEyebrowGroup ? mi.group : mi.source)) : null;
shell.left.insertBefore(markedText("row-eyebrow", eyebrow, eyebrowMatch), shell.line);
shell.line.appendChild(markedText("row-name", a.title, mi ? mi.title : null));
var badge = modeBadge(a, USER_MODE);
@@ -1376,7 +1410,7 @@ function exitPhase() {
// It survives a second-phase exit (which never goes through exitPhase from the commands view),
// so without a reset the cached empty-query key would skip the rebuild and leave stale content.
builtKey = "";
qEl.placeholder = T("sd_search_n", "Search %s actions", ACTIONS.length);
qEl.placeholder = T("sd_search", "Search actions");
render({ resize: true, resetScroll: true });
qEl.focus();
}
@@ -82,6 +82,20 @@ assert.equal(ctx.searchActions(pool, "layer").length >= 2, true,
assert.equal(ctx.searchActions(pool, "surface")[0].id, "c2",
"a later-but-precise match still ranks by relevance, not by pool type");
const pluginPool = [
{ id: "plugin-action", title: "Optimize G-code", source: "Gcode Optimizer", group: "", kind: "plugin" },
{ id: "command-action", title: "Open Preferences", source: "OrcaSlicer", group: "Commands", kind: "command" }
];
assert.deepEqual(ctx.searchActions(pluginPool, "plugin").map(function (a) { return a.id; }), ["plugin-action"],
"the plugin kind makes runnable plugin actions searchable by plugin");
assert.deepEqual(ctx.searchActions(pluginPool, "plugins").map(function (a) { return a.id; }), ["plugin-action"],
"the plural Plugins category also finds plugin actions");
ctx.searchActions(pluginPool, "plugins");
assert.deepEqual(ctx.matchIndex["plugin-action"].plugin, [[0, 7]],
"a category match highlights the visible Plugins label");
assert.deepEqual(ctx.searchActions(pluginPool, "plugin optimize").map(function (a) { return a.id; }), ["plugin-action"],
"plugin kind can match one token while the action title matches another");
// A perfect match (the needle as one contiguous run) outranks a fuzzy match of the same field - and a
// contiguous GROUP/header hit ("Recent Projects") beats a scattered fuzzy TITLE hit ("Retraction Length"),
// which is what the old flat title-bonus ranking got backwards.
@@ -188,6 +202,12 @@ assert.equal(ctx.actionCategory({ id: "s", group: "", source: "Filament : Coolin
"a Filament setting groups under Filament");
assert.equal(ctx.actionCategory({ id: "plugin_script_action:Foo:bar.py", group: "", source: "Gcode Optimizer", kind: "plugin" }), "Plugins",
"every plugin shares one Plugins header");
assert.equal(ctx.actionEyebrow({ group: "", source: "Gcode Optimizer", kind: "plugin" }, "plugin"), "Plugins",
"typed results show only the Plugins category");
assert.equal(ctx.actionEyebrow({ group: "", source: "Gcode Optimizer", kind: "plugin" }, "", true), "Plugins",
"recent plugin actions show only the Plugins category");
assert.equal(ctx.actionEyebrow({ group: "", source: "Gcode Optimizer", kind: "plugin" }, ""), "Gcode Optimizer",
"the unfiltered plugin section keeps the source name on non-recent rows");
assert.equal(ctx.actionCategory({ id: "x", group: "", source: "", kind: "command" }), "Other",
"a category-less action falls back to Other");
@@ -436,4 +456,9 @@ assert.equal(ctx.stateFromPayload({}).tooltipExpanded, true, "expansion defaults
assert.equal(ctx.stateFromPayload({ tooltip_expanded: false }).tooltipExpanded, false, "a collapsed payload is honored");
assert.equal(ctx.stateFromPayload({ tooltip_expanded: true }).tooltipExpanded, true, "an expanded payload is honored");
// resultCountText: a search counts the shown matches only ("Showing N actions"); the total is used
// solely for the empty-query count.
assert.equal(ctx.resultCountText(100, 3, "lay"), "Showing 3 actions", "a search reports the shown match count only");
assert.equal(ctx.resultCountText(100, 100, ""), "100 actions", "an empty query reports the total");
console.log("ok");
+5 -31
View File
@@ -5527,28 +5527,12 @@ int CLI::run(int argc, char **argv)
//add the virtual object into unselect list if has
partplate_list.preprocess_exclude_areas(unselected, enable_wrapping_detect);
// Filament ids given on the command line size the tower for STL input. A project
// records its filament use per plate, so count there and keep its tower positions.
const int plate_count = partplate_list.get_plate_count();
const bool from_project = used_filament_set.empty();
std::vector<int> plate_filament_counts(plate_count, static_cast<int>(used_filament_set.size()));
if (from_project)
for (int plate_index = 0; plate_index < plate_count; ++plate_index)
plate_filament_counts[plate_index] = static_cast<int>(partplate_list.get_plate(plate_index)->get_extruders_under_cli(true, m_print_config).size());
// A project only gets a tower the slicer will print: the prime tower enabled, and not
// a by-object print unless a smooth timelapse needs it, as the per-plate arrange decides.
const bool project_tower_allowed = m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value &&
(is_smooth_timelapse || !arrange_cfg.is_seq_print);
const auto plate_needs_wipe_tower = [from_project, project_tower_allowed, is_smooth_timelapse](int filament_count) {
if (!from_project)
return filament_count > 0;
return project_tower_allowed && (filament_count > 1 || (filament_count > 0 && is_smooth_timelapse));
};
const int max_filament_count = plate_count > 0 ? *std::max_element(plate_filament_counts.begin(), plate_filament_counts.end()) : 0;
if (plate_needs_wipe_tower(max_filament_count))
if (used_filament_set.size() > 0)
{
//prepare the wipe tower
int plate_count = partplate_list.get_plate_count();
int extruder_size = used_filament_set.size();
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
// This margin only pre-adjusts the default away from the near edges;
// estimate_wipe_tower_polygon below computes the real clamped position.
@@ -5584,11 +5568,7 @@ int CLI::run(int argc, char **argv)
for (int bedid = 0; bedid < MAX_PLATE_COUNT; bedid++) {
int plate_index_valid = std::min(bedid, plate_count - 1);
// Overflow beds may receive objects from any plate, so size them for the busiest one.
const int extruder_size = bedid < plate_count ? plate_filament_counts[bedid] : max_filament_count;
if (!plate_needs_wipe_tower(extruder_size))
continue;
if (bedid < plate_count && !from_project) {
if (bedid < plate_count) {
wipe_x_option->set_at(&wt_x_opt, plate_index_valid, 0);
wipe_y_option->set_at(&wt_y_opt, plate_index_valid, 0);
}
@@ -7044,12 +7024,6 @@ int CLI::run(int argc, char **argv)
}
}
sliced_info.sliced_plates.push_back(sliced_plate_info);
} catch (const Slic3r::SlicingErrors &exs) {
const std::string message = print_fff ? print_fff->slicing_errors_message(exs) : std::string(exs.what());
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate " << index+1 << ": " << message;
boost::nowide::cerr << message << std::endl;
record_exit_reson(outfile_dir, CLI_SLICING_ERROR, index+1, message, sliced_info);
flush_and_exit(CLI_SLICING_ERROR);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate "<<index+1 << std::endl;
boost::nowide::cerr << ex.what() << std::endl;
+38 -55
View File
@@ -636,8 +636,6 @@ static std::vector<std::pair<size_t, size_t>> get_segments(const ColoredLines &p
return segments;
}
static std::vector<PaintedLine> filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector<PaintedLine> &painted_lines)
{
const int filter_eps_value = scale_(0.1f);
@@ -690,29 +688,15 @@ static std::vector<std::vector<PaintedLine>> post_process_painted_lines(const st
if (painted_lines.empty())
return {};
// The painted lines were appended by parallel workers, so their order is arbitrary. The sort must
// therefore be a total order: two projections of the same span from facets of different colours
// tie on every geometric key, and whichever sorts first wins the span in filter_painted_lines().
// The colour and the end points break such ties so the result does not depend on scheduling.
auto comp = [&contours](const PaintedLine &first, const PaintedLine &second) {
if (first.contour_idx != second.contour_idx)
return first.contour_idx < second.contour_idx;
if (first.line_idx != second.line_idx)
return first.line_idx < second.line_idx;
const Point start_p = contours[first.contour_idx].segment_start(first.line_idx);
const double first_dist = (first.projected_line.a - start_p).cast<double>().squaredNorm();
const double second_dist = (second.projected_line.a - start_p).cast<double>().squaredNorm();
if (first_dist != second_dist)
return first_dist < second_dist;
const double first_len = (first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm();
const double second_len = (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm();
if (first_len != second_len)
return first_len < second_len;
if (first.color != second.color)
return first.color < second.color;
if (first.projected_line.a != second.projected_line.a)
return first.projected_line.a < second.projected_line.a;
return first.projected_line.b < second.projected_line.b;
Point first_start_p = contours[first.contour_idx].segment_start(first.line_idx);
return first.contour_idx < second.contour_idx ||
(first.contour_idx == second.contour_idx &&
(first.line_idx < second.line_idx ||
(first.line_idx == second.line_idx &&
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() < (second.projected_line.a - first_start_p).cast<double>().squaredNorm() ||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() == (second.projected_line.a - first_start_p).cast<double>().squaredNorm() &&
(first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm())))));
};
std::sort(painted_lines.begin(), painted_lines.end(), comp);
@@ -1216,12 +1200,15 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
const size_t num_layers = input_expolygons.size();
const ConstLayerPtrsAdaptor layers = print_object.layers();
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
int max_top_layers = 0;
int max_bottom_layers = 0;
int granularity = 1;
for (size_t i = 0; i < print_object.num_printing_regions(); ++ i) {
const PrintRegionConfig &config = print_object.printing_region(i).config();
max_top_layers = std::max(max_top_layers, config.top_shell_layers.value);
max_bottom_layers = std::max(max_bottom_layers, config.bottom_shell_layers.value);
granularity = std::max(granularity, std::max(config.top_shell_layers.value, config.bottom_shell_layers.value) - 1);
}
// Project upwards pointing painted triangles over top surfaces,
@@ -1340,16 +1327,14 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_facets_states);
triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers));
triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers));
triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
// BBS: the painted top / bottom surfaces are also projected onto the shell layers below / above them.
// Each layer only writes the projections it produced, keyed by the layer they land on, so the
// parallel loop shares nothing; they are gathered per target layer afterwards, in source-layer
// order, which keeps the result independent of how the layers were scheduled.
using ShellProjections = std::vector<std::pair<size_t, ExPolygons>>; // (target layer, projection)
std::vector<std::vector<ShellProjections>> shell_triangles_by_color_bottom(num_facets_states, std::vector<ShellProjections>(num_layers));
std::vector<std::vector<ShellProjections>> shell_triangles_by_color_top(num_facets_states, std::vector<ShellProjections>(num_layers));
// BBS: use shell_triangles_by_color_bottom & shell_triangles_by_color_top to save the top and bottom embedded layers's color information
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_bottom(num_facets_states);
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_top(num_facets_states);
shell_triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
shell_triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
struct LayerColorStat {
// Number of regions for a queried color.
@@ -1393,9 +1378,11 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
return out;
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
throw_on_cancel_callback();
@@ -1405,7 +1392,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
// Clean up thin projections. They are not printable anyways.
top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx], top_ex);
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
@@ -1416,7 +1403,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
shell_triangles_by_color_top[color_idx][layer_idx].emplace_back(size_t(last_idx), std::move(last));
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
}
@@ -1425,7 +1412,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
// Clean up thin projections. They are not printable anyways.
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx], bottom_ex);
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
@@ -1436,7 +1423,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
shell_triangles_by_color_bottom[color_idx][layer_idx].emplace_back(last_idx, std::move(last));
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
}
@@ -1444,28 +1431,19 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
}
});
// Gather the shell projections per target layer, walking the source layers in order.
std::vector<std::vector<ExPolygons>> shell_top_by_layer(num_facets_states, std::vector<ExPolygons>(num_layers));
std::vector<std::vector<ExPolygons>> shell_bottom_by_layer(num_facets_states, std::vector<ExPolygons>(num_layers));
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
for (auto &[target, projection] : shell_triangles_by_color_top[color_idx][layer_idx])
append(shell_top_by_layer[color_idx][target], std::move(projection));
for (auto &[target, projection] : shell_triangles_by_color_bottom[color_idx][layer_idx])
append(shell_bottom_by_layer[color_idx][target], std::move(projection));
}
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_facets_states);
triangles_by_color_merged.assign(num_facets_states, std::vector<ExPolygons>(num_layers));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &throw_on_cancel_callback,
&shell_top_by_layer, &shell_bottom_by_layer](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
throw_on_cancel_callback();
ExPolygons painted_exploys;
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
self = union_ex(self);
append(painted_exploys, self);
@@ -1477,8 +1455,13 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
auto top_area = diff_ex(union_ex(shell_top_by_layer[color_idx][layer_idx]), painted_exploys);
auto bottom_area = diff_ex(union_ex(shell_bottom_by_layer[color_idx][layer_idx]), painted_exploys);
auto top_area = diff_ex(union_ex(shell_triangles_by_color_top[color_idx][layer_idx],
shell_triangles_by_color_top[color_idx][layer_idx + num_layers]),
painted_exploys);
auto bottom_area = diff_ex(union_ex(shell_triangles_by_color_bottom[color_idx][layer_idx],
shell_triangles_by_color_bottom[color_idx][layer_idx + num_layers]),
painted_exploys);
append(self, top_area);
append(self, bottom_area);
-19
View File
@@ -1705,25 +1705,6 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
//BBS: refine seq-print validation logic
// The exception's own message is just "Errors"; the detail is in the per-object errors,
// whose object id is the PrintObject's.
std::string Print::slicing_errors_message(const SlicingErrors &errors) const
{
std::string message;
for (const SlicingError &error : errors.errors_) {
std::string object_name;
for (const PrintObject *object : m_objects)
if (object->id().id == error.objectId()) {
object_name = object->model_object()->name;
break;
}
if (!message.empty())
message += "\n";
message += object_name.empty() ? std::string(error.what()) : object_name + ": " + error.what();
}
return message;
}
StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
{
auto add_warning = [warnings](StringObjectException w) {
-4
View File
@@ -30,8 +30,6 @@
namespace Slic3r {
class SlicingErrors;
class GCode;
class Layer;
class ModelObject;
@@ -969,8 +967,6 @@ public:
// Returns an empty string if valid, otherwise returns an error message.
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
// The per-object messages of a SlicingErrors, each prefixed with its object's name.
std::string slicing_errors_message(const SlicingErrors &errors) const;
double skirt_first_layer_height() const;
Flow brim_flow() const;
Flow skirt_flow() const;
-17
View File
@@ -1062,23 +1062,6 @@ inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
}
}
);
// As in slice_make_lines(): the facet loop is parallel, so the per-slab line order depends on
// thread scheduling, and make_slab_loops() derives loop order and start vertices from it.
// Sort canonically; edge_type and flags only break ties, std::sort being unstable.
auto sort_canonically = [](std::vector<IntersectionLines> &lines_per_slab) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, lines_per_slab.size()),
[&lines_per_slab](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i)
std::sort(lines_per_slab[i].begin(), lines_per_slab[i].end(), [](const IntersectionLine &l, const IntersectionLine &r) {
return std::make_tuple(l.edge_a_id, l.edge_b_id, l.a_id, l.b_id, l.a.x(), l.a.y(), l.b.x(), l.b.y(), l.edge_type, l.flags) <
std::make_tuple(r.edge_a_id, r.edge_b_id, r.a_id, r.b_id, r.a.x(), r.a.y(), r.b.x(), r.b.y(), r.edge_type, r.flags);
});
});
};
for (SlabLines *slab_lines : { &lines_top, &lines_bottom }) {
sort_canonically(slab_lines->at_slice);
sort_canonically(slab_lines->between_slices);
}
return out;
}
-13
View File
@@ -2354,13 +2354,6 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
render_thumbnail(thumbnail_data, w, h, thumbnail_params, model_objects, m_volumes, camera_type, camera_view_angle_type, for_picking, ban_light);
}
bool GLCanvas3D::_set_shown_canvas_current()
{
// Thumbnails also render outside render(), where another library's GL context (e.g. WebKitGTK's) can be current.
// Inside render(), the shown canvas is the one already bound.
return wxGetApp().plater()->get_current_canvas3D()->_set_current();
}
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
unsigned int w,
unsigned int h,
@@ -2372,9 +2365,6 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
bool for_picking,
bool ban_light)
{
if (!_set_shown_canvas_current())
return;
GLShaderProgram* shader = nullptr;
if (for_picking)
shader = wxGetApp().get_shader("flat");
@@ -2413,9 +2403,6 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_d
bool for_picking,
bool ban_light)
{
if (!_set_shown_canvas_current())
return;
GLShaderProgram *shader = wxGetApp().get_shader("thumbnail");
switch (OpenGLManager::get_framebuffers_type()) {
case OpenGLManager::EFramebufferType::Arb: {
-1
View File
@@ -1320,7 +1320,6 @@ private:
bool _init_collapse_toolbar();
bool _set_current();
bool _set_shown_canvas_current();
void _resize(unsigned int w, unsigned int h);
//BBS: add part plate related logic
+68 -44
View File
@@ -9,6 +9,8 @@
#include "Plater.hpp"
#include "Widgets/WebViewHostDialog.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include <algorithm>
#include <wx/dcmemory.h>
@@ -82,39 +84,38 @@ nlohmann::json speed_dial_ui_strings()
{"shortcut_alt", alt},
{"shortcut_ctrl", ctrl},
{"sd_search", _u8L("Search actions")},
{"sd_clear", _u8L("Clear")},
{"sd_search_n", _u8L("Search %s actions")},
{"sd_recent", _u8L("Recent")},
{"sd_plugins", _u8L("Plugins")},
{"sd_other", _u8L("Other")},
{"sd_no_match_total", _u8L("No actions match (Total: %s)")},
{"sd_no_actions", _u8L("No actions yet")},
{"sd_no_tabs_match", _u8L("No tabs match")},
{"sd_no_tabs", _u8L("No tabs")},
{"sd_result_count", _u8L("Showing %s of %s actions")},
{"sd_search", _u8L("Search actions")},
{"sd_clear", _u8L("Clear")},
{"sd_recent", _u8L("Recent")},
{"sd_plugins", _u8L("Plugins")},
{"sd_other", _u8L("Other")},
{"sd_no_match_total", _u8L("No actions match (Total: %s)")},
{"sd_no_actions", _u8L("No actions yet")},
{"sd_no_tabs_match", _u8L("No tabs match")},
{"sd_no_tabs", _u8L("No tabs")},
{"sd_result_count", _u8L("Showing %s actions")},
{"sd_result_count_all", _u8L("%s actions")},
{"sd_tab_count", _u8L("%s tabs")},
{"sd_tab_count", _u8L("%s tabs")},
{"sd_tab_match_count", _u8L("%s matches")},
{"sd_favs_full", _u8L("Favourites are full (%s max)")},
{"sd_go_to_pct", _u8L("Go to %s%% of the layer range")},
{"sd_enter_pct", _u8L("Enter a layer percentage (0-100)")},
{"sd_go_layer_ph", _u8L("Go to layer %% (0-100)")},
{"sd_go_tab_ph", _u8L("Go to tab")},
{"sd_fav_slot", _u8L("Favourite %s (%s)")},
{"sd_pin_fav", _u8L("Pin to favourites (%s)")},
{"sd_unpin_fav", _u8L("Unpin from favourites (%s)")},
{"sd_remove_fav", _u8L("Remove from favourites")},
{"sd_move_left", _u8L("Move left")},
{"sd_move_right", _u8L("Move right")},
{"sd_unpin", _u8L("Unpin")},
{"sd_mode_advanced", _u8L("Advanced")},
{"sd_mode_expert", _u8L("Expert")},
{"sd_mode_develop", _u8L("Developer")},
{"sd_wiki_f1", _u8L("Wiki (F1)")},
{"sd_no_wiki", _u8L("No wiki page for this action")},
{"sd_show_details", _u8L("Show details")},
{"sd_hide_details", _u8L("Hide details")},
{"sd_favs_full", _u8L("Favourites are full (%s max)")},
{"sd_go_to_pct", _u8L("Go to %s%% of the layer range")},
{"sd_enter_pct", _u8L("Enter a layer percentage (0-100)")},
{"sd_go_layer_ph", _u8L("Go to layer %% (0-100)")},
{"sd_go_tab_ph", _u8L("Go to tab")},
{"sd_fav_slot", _u8L("Favourite %s (%s)")},
{"sd_pin_fav", _u8L("Pin to favourites (%s)")},
{"sd_unpin_fav", _u8L("Unpin from favourites (%s)")},
{"sd_remove_fav", _u8L("Remove from favourites")},
{"sd_move_left", _u8L("Move left")},
{"sd_move_right", _u8L("Move right")},
{"sd_unpin", _u8L("Unpin")},
{"sd_mode_advanced", _u8L("Advanced")},
{"sd_mode_expert", _u8L("Expert")},
{"sd_mode_develop", _u8L("Developer")},
{"sd_wiki_f1", _u8L("Wiki (F1)")},
{"sd_no_wiki", _u8L("No wiki page for this action")},
{"sd_show_details", _u8L("Show details")},
{"sd_hide_details", _u8L("Hide details")},
};
}
@@ -178,6 +179,7 @@ void SpeedDialWebDialog::request_show()
if (IsShown()) {
Raise();
focus_webview(browser(), m_page_ready);
repaint_webview();
return;
}
@@ -189,6 +191,7 @@ void SpeedDialWebDialog::request_show()
// Grab focus now and again on wxEVT_ACTIVATE; grabbing directly on the WebKit widget is
// what makes typing reach the search field immediately on open.
focus_webview(browser(), m_page_ready);
repaint_webview();
}
void SpeedDialWebDialog::on_script_message(const nlohmann::json& payload)
@@ -216,7 +219,7 @@ void SpeedDialWebDialog::handle_web_command(const nlohmann::json& payload)
// set_favourite() refuses once the bar hits kFavLimit; tell the page so it can undo the
// pin and show a "favourites are full" hint instead of silently losing the favourite.
const std::string fav_id = payload.value("id", "");
const bool ok = wxGetApp().action_registry().set_favourite(fav_id, payload.value("fav", false));
const bool ok = wxGetApp().action_registry().set_favourite(fav_id, payload.value("fav", false));
if (!ok)
call_web_handler({{"command", "favourite_full"}, {"limit", (int) ActionRegistry::kFavLimit}, {"id", fav_id}});
} else if (command == "reorder_favourites") {
@@ -265,14 +268,36 @@ void SpeedDialWebDialog::resize_to_content(int height)
Layout();
#ifdef __WXOSX__
// WKWebView can lag the dialog's new client size; force the viewport to match so the page is
// never painted (and clipped by the rounded layer) below the footer.
if (wxWebView* wv = browser()) {
const wxSize client = GetClientSize();
if (wv->GetSize() != client)
wv->SetSize(client);
}
// never painted (and clipped by the rounded layer) below the footer. Unconditional: on a
// re-open the size is often unchanged, and skipping the sync leaves the fresh render unpainted.
if (wxWebView* wv = browser())
wv->SetSize(GetClientSize());
#endif
apply_rounded_shape();
// A re-open re-renders at (usually) the same size, so nothing above may generate damage.
// Repaint explicitly so the newly rendered list is shown without needing user input.
repaint_webview();
}
void SpeedDialWebDialog::repaint_webview()
{
wxWebView* wv = browser();
if (!wv)
return;
// Portable invalidate; the platform blocks below reach the widget/layer that actually paints.
wv->Refresh();
#ifdef __WXOSX__
if (void* nb = wv->GetNativeBackend())
WKWebView_force_display(nb);
wv->Update();
#elif defined(__linux__)
// WebKitGTK's WebKitWebView owns its own GdkWindow, so invalidating the wxWebView wrapper
// (the GtkScrolledWindow) does not redraw it.
if (void* nb = wv->GetNativeBackend())
gtk_widget_queue_draw((GtkWidget*) nb);
#else
wv->Update();
#endif
}
// Rounded corners: the webview paints an opaque rectangle, so round the whole top-level window.
@@ -331,8 +356,8 @@ void SpeedDialWebDialog::run_action(const std::string& id, const std::string& ti
return;
// Only plugin actions get the "Run plugin?" confirm. Built-in commands act immediately.
const bool ask = a->kind == AppActionKind::Plugin && reg.should_ask(id);
const std::string atitle = a->title();
const bool ask = a->kind == AppActionKind::Plugin && reg.should_ask(id);
const std::string atitle = a->title();
const ConfigOptionMode required = a->required_mode;
// Settings the current mode hides require a switch first. Ask while the dial is still up; a
@@ -341,16 +366,15 @@ void SpeedDialWebDialog::run_action(const std::string& id, const std::string& ti
const wxString setting = title.empty() ? from_u8(atitle) : from_u8(title);
if (required == comDevelop) {
RichMessageDialog dlg(wxGetApp().mainframe,
wxString::Format(_L("\"%s\" is a Developer setting. Enable Developer mode to edit it?"),
setting),
wxString::Format(_L("\"%s\" is a Developer setting. Enable Developer mode to edit it?"), setting),
_L("Developer setting"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK)
return;
wxGetApp().enable_developer_mode();
} else {
RichMessageDialog dlg(wxGetApp().mainframe,
wxString::Format(_L("\"%s\" is a %s setting. Switch from %s mode to %s mode to edit it?"),
setting, mode_label(required), mode_label(wxGetApp().get_mode()), mode_label(required)),
wxString::Format(_L("\"%s\" is a %s setting. Switch from %s mode to %s mode to edit it?"), setting,
mode_label(required), mode_label(wxGetApp().get_mode()), mode_label(required)),
_L("Switch settings mode"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK)
return;
+4
View File
@@ -28,6 +28,10 @@ private:
void send_actions();
void search_tabs();
void apply_rounded_shape();
// Forces the webview to repaint after it is mapped / re-rendered. The popup is transparent and
// chrome-less, so a missed frame leaves it blank until input; platform-specific because the
// widget that actually paints is not always the wxWebView wrapper.
void repaint_webview();
void on_dpi_changed(const wxRect& suggested_rect) override;
bool m_page_ready{false};
-8
View File
@@ -7184,14 +7184,6 @@ void Tab::activate_selected_page(std::function<void()> throw_if_canceled)
if (!m_active_page)
return;
#ifdef __WXGTK__
// Builds the page off screen, since GTK crashes when it desensitizes a multiline text view
// that was built on screen and hidden before its first size allocation.
const bool hide_view = m_active_page->build_pending() && m_page_view->IsShown();
if (hide_view)
m_page_view->Hide();
ScopeGuard show_view([this, hide_view] { if (hide_view) m_page_view->Show(); });
#endif
m_active_page->activate(m_mode, throw_if_canceled);
update_changed_ui();
update_description_lines();
+1
View File
@@ -12,6 +12,7 @@ extern double mac_max_scaling_factor();
extern void set_miniaturizable(void * window);
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &));
void WKWebView_setTransparentBackground(void * web);
void WKWebView_force_display(void * web);
void set_tag_when_enter_full_screen(bool isfullscreen);
void set_title_colour_after_set_title(void * window);
void initGestures(void * view, wxEvtHandler * handler);
+14
View File
@@ -102,6 +102,20 @@ void WKWebView_setTransparentBackground(void * web)
[webView registerForDraggedTypes: @[NSFilenamesPboardType]];
}
// Force a WKWebView to re-lay-out and repaint. Needed for chrome-less popups: the window is
// transparent, so a WKWebView whose layer has no pending frame leaves the whole window invisible
// until the user generates input (scroll/arrow). setNeedsDisplay alone does not always reach the
// web-content layer, so flag layout and both the view and its layer.
void WKWebView_force_display(void * web)
{
WKWebView * webView = (WKWebView*)web;
if (!webView)
return;
[webView setNeedsLayout:YES];
[webView setNeedsDisplay:YES];
[[webView layer] setNeedsDisplay];
}
void openFolderForFile(wxString const & file)
{
NSArray *fileURLs = [NSArray arrayWithObjects:wxCFStringRef(file).AsNSString(), /* ... */ nil];
+22 -19
View File
@@ -1098,32 +1098,35 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
const auto enabled_vendors = app_config->vendors();
std::set<std::string> bundles;
// Orca: always install filament library
bundles.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
// A vendor is named by its profile or, where the build ships preset caches
// instead of the raw profile JSONs, by its cache alone.
for (const std::string &vendor_name : vendor_names_in(rsrc_path)) {
// enabled_vendors lists the vendors whose printer models the user picked, and
// neither of these two is ever in it.
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE)
|| (vendor_name == PresetBundle::ORCA_FILAMENT_LIBRARY)
if (bundles.find(vendor_name) != bundles.end())continue;
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) // always update configs from resource to vendor for ORCA_DEFAULT_BUNDLE
|| (enabled_vendors.find(vendor_name) != enabled_vendors.end());
if (is_vendor_installed(vendor_name)) {
if (is_vendor_enabled) {
// Orca: whichever form of the vendor resources ships at the newer
// version is the one installing lays down, and the one to judge
// what is installed against.
Semver resource_ver = resource_vendor_version(vendor_name);
// Orca: a vendor installed as a preset cache has no profile
// beside it; the version it was installed at is in the cache.
Semver vendor_ver = installed_vendor_version(vendor_name);
if (enabled_config_update) {
if (is_vendor_enabled) {
// Orca: whichever form of the vendor resources ships at the newer
// version is the one installing lays down, and the one to judge
// what is installed against.
Semver resource_ver = resource_vendor_version(vendor_name);
// Orca: a vendor installed as a preset cache has no profile
// beside it; the version it was installed at is in the cache.
Semver vendor_ver = installed_vendor_version(vendor_name);
if (vendor_ver < resource_ver) {
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor " << vendor_name << " newer version "
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
bundles.insert(vendor_name);
if (vendor_ver < resource_ver) {
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor " << vendor_name << " newer version "
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
bundles.insert(vendor_name);
}
} else {
// need to be removed because not installed
remove_installed_vendor(vendor_name);
}
} else {
// need to be removed because not installed
remove_installed_vendor(vendor_name);
}
} else if (is_vendor_enabled) {
bundles.insert(vendor_name);
-26
View File
@@ -505,29 +505,3 @@ TEST_CASE("Sequential printing publishes the nozzle group result", "[Print][Mult
CHECK(gcode.find("; SEQ-ND-OK") != std::string::npos);
}
}
TEST_CASE("Slicing errors are reported per object with the object's name", "[Print]")
{
Print print;
Model model;
init_print({Slic3r::Test::cube(20.)}, print, model);
// Lift the cube off the bed: its first layer is empty, which G-code export reports per object.
ModelObject *object = model.objects.front();
object->name = "floating cube";
object->instances.front()->set_offset(object->instances.front()->get_offset() + Vec3d(0., 0., 2.));
print.apply(model, DynamicPrintConfig::full_print_config());
print.set_status_silent();
ScopedTemporaryFile temp(".gcode");
std::string message;
try {
print.process();
print.export_gcode(temp.string(), nullptr, nullptr);
FAIL("slicing did not report the empty first layer");
} catch (const SlicingErrors &errors) {
REQUIRE(errors.errors_.size() == 1);
message = print.slicing_errors_message(errors);
}
CHECK(message.rfind("floating cube: ", 0) == 0);
CHECK(message.find("empty first layer") != std::string::npos);
}
-1
View File
@@ -36,7 +36,6 @@ add_executable(${_TEST_NAME}_tests
test_stl.cpp
test_triangle_selector.cpp
test_meshboolean.cpp
test_trianglemesh_slicer.cpp
test_marchingsquares.cpp
test_lay_on_face.cpp
test_model.cpp
@@ -1,50 +0,0 @@
#include <catch2/catch_all.hpp>
#include <tbb/global_control.h>
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/TriangleMeshSlicer.hpp"
using namespace Slic3r;
// The slab slicer collects each slab's intersection lines from a parallel loop over the facets.
// Its loops, and therefore the projected polygons, are derived from the order of those lines, so
// the order has to be canonical or the same mesh projects to different polygons run to run.
// The single-threaded projection is the reference; every multi-threaded run must reproduce it
// exactly, vertex order included.
TEST_CASE("Slab slicing projects the same polygons whatever the thread schedule", "[TriangleMeshSlicer]")
{
// A dense sphere, tilted so no facet is axis aligned: thousands of upward and downward
// facing facets spread over every slab.
indexed_triangle_set mesh = its_make_sphere(10., 0.05);
Transform3d trafo = Transform3d::Identity();
trafo.rotate(Eigen::AngleAxisd(0.37, Vec3d(0.3, 0.5, 1.).normalized()));
trafo.translate(Vec3d(1., 2., 0.));
std::vector<float> zs;
for (float z = -9.7f; z < 9.7f; z += 0.2f)
zs.emplace_back(z);
auto project = [&mesh, &trafo, &zs]() {
std::vector<Polygons> top, bottom;
slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, nullptr, []{});
return std::make_pair(std::move(top), std::move(bottom));
};
std::pair<std::vector<Polygons>, std::vector<Polygons>> reference;
{
tbb::global_control single_thread(tbb::global_control::max_allowed_parallelism, 1);
reference = project();
}
REQUIRE(reference.first.size() == zs.size());
REQUIRE(std::any_of(reference.first.begin(), reference.first.end(), [](const Polygons &p) { return !p.empty(); }));
REQUIRE(std::any_of(reference.second.begin(), reference.second.end(), [](const Polygons &p) { return !p.empty(); }));
for (int run = 0; run < 3; ++run) {
DYNAMIC_SECTION("multi-threaded run " << run)
{
auto parallel = project();
CHECK(parallel.first == reference.first);
CHECK(parallel.second == reference.second);
}
}
}