Compare commits

..
15 changed files with 192 additions and 142 deletions
-3
View File
@@ -70,9 +70,6 @@ if (POLICY CMP0092)
cmake_policy(SET CMP0092 NEW) cmake_policy(SET CMP0092 NEW)
endif () endif ()
# project() reads this, so set it first.
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/ClangClShowIncludes.cmake")
project(OrcaSlicer) project(OrcaSlicer)
# Backward compatibility for old CMake versions # 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) cmake_policy(SET CMP0135 NEW)
endif() 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) project(OrcaSlicer-deps)
# Backward compatibility for old CMake versions # 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_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER} -DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_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_TOOLCHAIN_FILE:STRING=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS}
-DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_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_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
-DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER} -DCMAKE_C_COMPILER_LAUNCHER:STRING=${CMAKE_C_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=${CMAKE_CXX_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 -DBUILD_SHARED_LIBS:BOOL=OFF
${_cmake_osx_arch} ${_cmake_osx_arch}
"${_configs_line}" "${_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 }; 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, // 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_CONTIGUOUS = 100000;
var SCORE_TITLE = 2000; var SCORE_TITLE = 2000;
var SCORE_GROUP = 1000; var SCORE_GROUP = 1000;
@@ -118,6 +118,18 @@ function sourceNorm(a) {
a._sn = NormText(a.source || "", false); a._sn = NormText(a.source || "", false);
return a._sn; 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 // 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. // "Overhang reversal"). Never rendered, so no highlight ranges.
function fullNorm(a) { 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 // 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 // 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) { function queryTokens(query) {
var norm = NormText(String(query || "").trim(), false); var norm = NormText(String(query || "").trim(), false);
return norm ? norm.split(/\s+/).filter(Boolean) : []; return norm ? norm.split(/\s+/).filter(Boolean) : [];
@@ -170,14 +182,19 @@ function tokenMatch(a, token, wwRe) {
var g = fieldMatchScore(groupNorm(a), token, wwRe); var g = fieldMatchScore(groupNorm(a), token, wwRe);
var s = fieldMatchScore(sourceNorm(a), token, wwRe); var s = fieldMatchScore(sourceNorm(a), token, wwRe);
var f = fieldMatchScore(fullNorm(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( var score = Math.max(
t ? (t.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_TITLE + t.score : -Infinity, t ? (t.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_TITLE + t.score : -Infinity,
g ? (g.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + g.score : -Infinity, g ? (g.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + g.score : -Infinity,
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.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 // 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. // Combine per-field scores into one value, or null when nothing matched.
// Ranking: contiguous > fuzzy, then title > group > source/full alias, then start/gaps. // Ranking: contiguous > fuzzy, then title > group/category > source/full alias, then start/gaps.
function scoreFields(t, g, s, f) { function scoreFields(t, g, s, f, p, typeMatch) {
var best = null; var best = null;
function consider(m, weight) { function consider(m, weight) {
if (!m) return; if (!m) return;
@@ -213,6 +230,8 @@ function scoreFields(t, g, s, f) {
consider(g, SCORE_GROUP); consider(g, SCORE_GROUP);
consider(s, 0); consider(s, 0);
consider(f, 0); consider(f, 0);
consider(p, SCORE_GROUP);
consider(typeMatch, SCORE_GROUP);
return best; 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 // - 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 // different fields. This is what lets "speed acceleration inner" find "Inner wall" whose path is
// "Process : Speed : Acceleration" (title + source breadcrumb together). // "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. // A phrase match always outranks a distributed token match.
function searchActions(actions, query) { function searchActions(actions, query) {
var q = (query || "").trim(); var q = (query || "").trim();
@@ -251,26 +271,31 @@ function searchActions(actions, query) {
var g = fieldMatchScore(groupNorm(a), searchNeedle, wwRe); var g = fieldMatchScore(groupNorm(a), searchNeedle, wwRe);
var s = fieldMatchScore(sourceNorm(a), searchNeedle, wwRe); var s = fieldMatchScore(sourceNorm(a), searchNeedle, wwRe);
var f = fieldMatchScore(fullNorm(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; var score, ranges;
if (phrase !== null) { if (phrase !== null) {
score = phrase + SCORE_PHRASE; 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 { } else {
// Require every token; a token that matches nothing drops the action immediately. Ranges // 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. // from all matching tokens are merged per field so each matched word highlights.
var sum = 0, titleR = null, groupR = null, sourceR = null, all = true; var sum = 0, titleR = null, groupR = null, sourceR = null, pluginR = null, all = true;
for (var k = 0; k < searchTokens.length; k++) { for (var tokenIndex = 0; tokenIndex < searchTokens.length; tokenIndex++) {
var m = tokenMatch(a, searchTokens[k], searchTokenRes[k]); var m = tokenMatch(a, searchTokens[tokenIndex], searchTokenRes[tokenIndex]);
if (!m) { all = false; break; } if (!m) { all = false; break; }
sum += m.score; sum += m.score;
if (m.title) (titleR || (titleR = [])).push(m.title); if (m.title) (titleR || (titleR = [])).push(m.title);
if (m.group) (groupR || (groupR = [])).push(m.group); if (m.group) (groupR || (groupR = [])).push(m.group);
if (m.source) (sourceR || (sourceR = [])).push(m.source); if (m.source) (sourceR || (sourceR = [])).push(m.source);
if (m.plugin) (pluginR || (pluginR = [])).push(m.plugin);
} }
if (!all) continue; if (!all) continue;
score = sum; 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 // 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. // 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, title: ranges.title,
group: ranges.group, group: ranges.group,
source: ranges.source, source: ranges.source,
plugin: ranges.plugin,
useEyebrowGroup: !!(a.group) useEyebrowGroup: !!(a.group)
}; };
scored.push({ a: a, s: score }); scored.push({ a: a, s: score });
@@ -390,7 +416,7 @@ function favDigitFromEvent(e) {
function resultCountText(total, shown, query) { function resultCountText(total, shown, query) {
return (query || "").trim() ? 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); T("sd_result_count_all", "%s actions", total);
} }
@@ -461,6 +487,12 @@ function actionCategory(a) {
return cat || T("sd_other", "Other"); 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 // 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. // (frecency) order is kept. Pure so the node-vm test can exercise grouping.
function groupActions(list) { function groupActions(list) {
@@ -711,7 +743,7 @@ window.HandleStudio = function (payload) {
builtKey = ""; builtKey = "";
if (qEl) { if (qEl) {
qEl.value = ""; qEl.value = "";
qEl.placeholder = T("sd_search_n", "Search %s actions", ACTIONS.length); qEl.placeholder = T("sd_search", "Search actions");
syncClearButton(); syncClearButton();
} }
render({ resize: true, resetScroll: true }); render({ resize: true, resetScroll: true });
@@ -931,12 +963,14 @@ function beginRow(item, i, mono, ariaLabel) {
function renderActionRow(a, i) { function renderActionRow(a, i) {
var on = FAVS.indexOf(a.id) !== -1; var on = FAVS.indexOf(a.id) !== -1;
var shell = beginRow(a, i, false, actionLabel(a, ACTIONS)); var shell = beginRow(a, i, false, actionLabel(a, ACTIONS));
var mi = matchIndex[a.id]; var typedQuery = String(query || "").trim();
// The eyebrow shows group when present, else source. Highlight with the ranges of whichever of the var isRecent = !typedQuery && i < RECENTS.length && RECENTS[i].id === a.id;
// two the eyebrow actually renders (so a "Recent Projects"/"Object" header match lights up like a var mi = typedQuery ? matchIndex[a.id] : null;
// setting path does - the offsets are computed against the same string we are marking). // Plugin search/recent rows show their category; other rows show their group or source breadcrumb.
var eyebrow = a.group || a.source; // Use match ranges from the visible field so category and breadcrumb highlights stay aligned.
var eyebrowMatch = mi ? (mi.useEyebrowGroup ? mi.group : mi.source) : null; 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.left.insertBefore(markedText("row-eyebrow", eyebrow, eyebrowMatch), shell.line);
shell.line.appendChild(markedText("row-name", a.title, mi ? mi.title : null)); shell.line.appendChild(markedText("row-name", a.title, mi ? mi.title : null));
var badge = modeBadge(a, USER_MODE); 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), // 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. // so without a reset the cached empty-query key would skip the rebuild and leave stale content.
builtKey = ""; builtKey = "";
qEl.placeholder = T("sd_search_n", "Search %s actions", ACTIONS.length); qEl.placeholder = T("sd_search", "Search actions");
render({ resize: true, resetScroll: true }); render({ resize: true, resetScroll: true });
qEl.focus(); qEl.focus();
} }
@@ -82,6 +82,20 @@ assert.equal(ctx.searchActions(pool, "layer").length >= 2, true,
assert.equal(ctx.searchActions(pool, "surface")[0].id, "c2", assert.equal(ctx.searchActions(pool, "surface")[0].id, "c2",
"a later-but-precise match still ranks by relevance, not by pool type"); "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 // 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"), // 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. // 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"); "a Filament setting groups under Filament");
assert.equal(ctx.actionCategory({ id: "plugin_script_action:Foo:bar.py", group: "", source: "Gcode Optimizer", kind: "plugin" }), "Plugins", assert.equal(ctx.actionCategory({ id: "plugin_script_action:Foo:bar.py", group: "", source: "Gcode Optimizer", kind: "plugin" }), "Plugins",
"every plugin shares one Plugins header"); "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", assert.equal(ctx.actionCategory({ id: "x", group: "", source: "", kind: "command" }), "Other",
"a category-less action falls back to 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: false }).tooltipExpanded, false, "a collapsed payload is honored");
assert.equal(ctx.stateFromPayload({ tooltip_expanded: true }).tooltipExpanded, true, "an expanded 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"); console.log("ok");
+1 -6
View File
@@ -34,10 +34,6 @@ static const char* Segments_Vertex_Shader =
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not // ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
// world_position, so the caster would write a depth the receiver never looks up. // world_position, so the caster would write a depth the receiver never looks up.
"uniform float bias_scale;\n" "uniform float bias_scale;\n"
// draw the instances last to first, top layers before the ones they hide, so that early depth
// rejection discards most of the hidden fragments; set when the camera looks down on the print
"uniform int reverse_order;\n"
"uniform int instance_count;\n"
"in int vertex_id;\n" "in int vertex_id;\n"
"out vec3 color;\n" "out vec3 color;\n"
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n" "// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
@@ -63,8 +59,7 @@ static const char* Segments_Vertex_Shader =
" return top_diffuse + front_diffuse + top_specular;\n" " return top_diffuse + front_diffuse + top_specular;\n"
"}\n" "}\n"
"void main() {\n" "void main() {\n"
" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n" " int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n"
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
" int id_b = id_a + 1;\n" " int id_b = id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n" " vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
" vec3 pos_b = texelFetch(position_tex, id_b).xyz;\n" " vec3 pos_b = texelFetch(position_tex, id_b).xyz;\n"
-11
View File
@@ -763,8 +763,6 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex"); m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex"); m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex"); m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
// ORCA: realistic view // ORCA: realistic view
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map"); m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp"); m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
@@ -2092,15 +2090,6 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data())); glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data())); glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data())); glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
// The segments come in print order, bottom layer first. Seen from above, that is back to front,
// and every hidden fragment is shaded before the one that covers it. Drawing them last to first
// lets the depth test reject the hidden ones instead. The camera looks down when the world's
// up axis points towards it, which is the view matrix's (2, 2) entry being positive.
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
#ifndef ENABLE_OPENGL_ES
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
#endif // ENABLE_OPENGL_ES
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades // ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off. // with the lookup off.
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit)); glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
-2
View File
@@ -362,8 +362,6 @@ private:
int m_uni_segments_height_width_angle_tex_id{ -1 }; int m_uni_segments_height_width_angle_tex_id{ -1 };
int m_uni_segments_colors_tex_id{ -1 }; int m_uni_segments_colors_tex_id{ -1 };
int m_uni_segments_segment_index_tex_id{ -1 }; int m_uni_segments_segment_index_tex_id{ -1 };
int m_uni_segments_reverse_order_id{ -1 };
int m_uni_segments_instance_count_id{ -1 };
int m_uni_segments_shadow_map_id{ -1 }; int m_uni_segments_shadow_map_id{ -1 };
int m_uni_segments_shadow_light_vp_id{ -1 }; int m_uni_segments_shadow_light_vp_id{ -1 };
int m_uni_segments_shadow_intensity_id{ -1 }; int m_uni_segments_shadow_intensity_id{ -1 };
-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); 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, void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
unsigned int w, unsigned int w,
unsigned int h, unsigned int h,
@@ -2372,9 +2365,6 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
bool for_picking, bool for_picking,
bool ban_light) bool ban_light)
{ {
if (!_set_shown_canvas_current())
return;
GLShaderProgram* shader = nullptr; GLShaderProgram* shader = nullptr;
if (for_picking) if (for_picking)
shader = wxGetApp().get_shader("flat"); shader = wxGetApp().get_shader("flat");
@@ -2413,9 +2403,6 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_d
bool for_picking, bool for_picking,
bool ban_light) bool ban_light)
{ {
if (!_set_shown_canvas_current())
return;
GLShaderProgram *shader = wxGetApp().get_shader("thumbnail"); GLShaderProgram *shader = wxGetApp().get_shader("thumbnail");
switch (OpenGLManager::get_framebuffers_type()) { switch (OpenGLManager::get_framebuffers_type()) {
case OpenGLManager::EFramebufferType::Arb: { case OpenGLManager::EFramebufferType::Arb: {
-1
View File
@@ -1320,7 +1320,6 @@ private:
bool _init_collapse_toolbar(); bool _init_collapse_toolbar();
bool _set_current(); bool _set_current();
bool _set_shown_canvas_current();
void _resize(unsigned int w, unsigned int h); void _resize(unsigned int w, unsigned int h);
//BBS: add part plate related logic //BBS: add part plate related logic
+36 -12
View File
@@ -9,6 +9,8 @@
#include "Plater.hpp" #include "Plater.hpp"
#include "Widgets/WebViewHostDialog.hpp" #include "Widgets/WebViewHostDialog.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include <algorithm> #include <algorithm>
#include <wx/dcmemory.h> #include <wx/dcmemory.h>
@@ -84,7 +86,6 @@ nlohmann::json speed_dial_ui_strings()
{"sd_search", _u8L("Search actions")}, {"sd_search", _u8L("Search actions")},
{"sd_clear", _u8L("Clear")}, {"sd_clear", _u8L("Clear")},
{"sd_search_n", _u8L("Search %s actions")},
{"sd_recent", _u8L("Recent")}, {"sd_recent", _u8L("Recent")},
{"sd_plugins", _u8L("Plugins")}, {"sd_plugins", _u8L("Plugins")},
{"sd_other", _u8L("Other")}, {"sd_other", _u8L("Other")},
@@ -92,7 +93,7 @@ nlohmann::json speed_dial_ui_strings()
{"sd_no_actions", _u8L("No actions yet")}, {"sd_no_actions", _u8L("No actions yet")},
{"sd_no_tabs_match", _u8L("No tabs match")}, {"sd_no_tabs_match", _u8L("No tabs match")},
{"sd_no_tabs", _u8L("No tabs")}, {"sd_no_tabs", _u8L("No tabs")},
{"sd_result_count", _u8L("Showing %s of %s actions")}, {"sd_result_count", _u8L("Showing %s actions")},
{"sd_result_count_all", _u8L("%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_tab_match_count", _u8L("%s matches")},
@@ -178,6 +179,7 @@ void SpeedDialWebDialog::request_show()
if (IsShown()) { if (IsShown()) {
Raise(); Raise();
focus_webview(browser(), m_page_ready); focus_webview(browser(), m_page_ready);
repaint_webview();
return; return;
} }
@@ -189,6 +191,7 @@ void SpeedDialWebDialog::request_show()
// Grab focus now and again on wxEVT_ACTIVATE; grabbing directly on the WebKit widget is // 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. // what makes typing reach the search field immediately on open.
focus_webview(browser(), m_page_ready); focus_webview(browser(), m_page_ready);
repaint_webview();
} }
void SpeedDialWebDialog::on_script_message(const nlohmann::json& payload) void SpeedDialWebDialog::on_script_message(const nlohmann::json& payload)
@@ -265,14 +268,36 @@ void SpeedDialWebDialog::resize_to_content(int height)
Layout(); Layout();
#ifdef __WXOSX__ #ifdef __WXOSX__
// WKWebView can lag the dialog's new client size; force the viewport to match so the page is // 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. // never painted (and clipped by the rounded layer) below the footer. Unconditional: on a
if (wxWebView* wv = browser()) { // re-open the size is often unchanged, and skipping the sync leaves the fresh render unpainted.
const wxSize client = GetClientSize(); if (wxWebView* wv = browser())
if (wv->GetSize() != client) wv->SetSize(GetClientSize());
wv->SetSize(client);
}
#endif #endif
apply_rounded_shape(); 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. // Rounded corners: the webview paints an opaque rectangle, so round the whole top-level window.
@@ -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); const wxString setting = title.empty() ? from_u8(atitle) : from_u8(title);
if (required == comDevelop) { if (required == comDevelop) {
RichMessageDialog dlg(wxGetApp().mainframe, RichMessageDialog dlg(wxGetApp().mainframe,
wxString::Format(_L("\"%s\" is a Developer setting. Enable Developer mode to edit it?"), wxString::Format(_L("\"%s\" is a Developer setting. Enable Developer mode to edit it?"), setting),
setting),
_L("Developer setting"), wxOK | wxCANCEL); _L("Developer setting"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK) if (dlg.ShowModal() != wxID_OK)
return; return;
wxGetApp().enable_developer_mode(); wxGetApp().enable_developer_mode();
} else { } else {
RichMessageDialog dlg(wxGetApp().mainframe, RichMessageDialog dlg(wxGetApp().mainframe,
wxString::Format(_L("\"%s\" is a %s setting. Switch from %s mode to %s mode to edit it?"), wxString::Format(_L("\"%s\" is a %s setting. Switch from %s mode to %s mode to edit it?"), setting,
setting, mode_label(required), mode_label(wxGetApp().get_mode()), mode_label(required)), mode_label(required), mode_label(wxGetApp().get_mode()), mode_label(required)),
_L("Switch settings mode"), wxOK | wxCANCEL); _L("Switch settings mode"), wxOK | wxCANCEL);
if (dlg.ShowModal() != wxID_OK) if (dlg.ShowModal() != wxID_OK)
return; return;
+4
View File
@@ -28,6 +28,10 @@ private:
void send_actions(); void send_actions();
void search_tabs(); void search_tabs();
void apply_rounded_shape(); 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; void on_dpi_changed(const wxRect& suggested_rect) override;
bool m_page_ready{false}; bool m_page_ready{false};
+1
View File
@@ -12,6 +12,7 @@ extern double mac_max_scaling_factor();
extern void set_miniaturizable(void * window); extern void set_miniaturizable(void * window);
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &)); void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &));
void WKWebView_setTransparentBackground(void * web); void WKWebView_setTransparentBackground(void * web);
void WKWebView_force_display(void * web);
void set_tag_when_enter_full_screen(bool isfullscreen); void set_tag_when_enter_full_screen(bool isfullscreen);
void set_title_colour_after_set_title(void * window); void set_title_colour_after_set_title(void * window);
void initGestures(void * view, wxEvtHandler * handler); void initGestures(void * view, wxEvtHandler * handler);
+14
View File
@@ -102,6 +102,20 @@ void WKWebView_setTransparentBackground(void * web)
[webView registerForDraggedTypes: @[NSFilenamesPboardType]]; [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) void openFolderForFile(wxString const & file)
{ {
NSArray *fileURLs = [NSArray arrayWithObjects:wxCFStringRef(file).AsNSString(), /* ... */ nil]; NSArray *fileURLs = [NSArray arrayWithObjects:wxCFStringRef(file).AsNSString(), /* ... */ nil];
+7 -4
View File
@@ -1098,15 +1098,17 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
const auto enabled_vendors = app_config->vendors(); const auto enabled_vendors = app_config->vendors();
std::set<std::string> bundles; 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 // A vendor is named by its profile or, where the build ships preset caches
// instead of the raw profile JSONs, by its cache alone. // instead of the raw profile JSONs, by its cache alone.
for (const std::string &vendor_name : vendor_names_in(rsrc_path)) { for (const std::string &vendor_name : vendor_names_in(rsrc_path)) {
// enabled_vendors lists the vendors whose printer models the user picked, and if (bundles.find(vendor_name) != bundles.end())continue;
// neither of these two is ever in it.
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) // always update configs from resource to vendor for ORCA_DEFAULT_BUNDLE
|| (vendor_name == PresetBundle::ORCA_FILAMENT_LIBRARY)
|| (enabled_vendors.find(vendor_name) != enabled_vendors.end()); || (enabled_vendors.find(vendor_name) != enabled_vendors.end());
if (is_vendor_installed(vendor_name)) { if (is_vendor_installed(vendor_name)) {
if (enabled_config_update) {
if (is_vendor_enabled) { if (is_vendor_enabled) {
// Orca: whichever form of the vendor resources ships at the newer // Orca: whichever form of the vendor resources ships at the newer
// version is the one installing lays down, and the one to judge // version is the one installing lays down, and the one to judge
@@ -1125,6 +1127,7 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
// need to be removed because not installed // need to be removed because not installed
remove_installed_vendor(vendor_name); remove_installed_vendor(vendor_name);
} }
}
} else if (is_vendor_enabled) { } else if (is_vendor_enabled) {
bundles.insert(vendor_name); bundles.insert(vendor_name);
} }