Compare commits

..
Author SHA1 Message Date
Hanif Koh 1acec9f88f Offer the Sliced Objects as a Solid Model While Dragging
The G-code preview draws every toolpath segment as an instanced box, and its frame cost is
linear in the number of segments drawn. On a plate of large objects that is enough that
dragging the camera or a preview slider cannot keep up, and no amount of per-segment work
changes that; only drawing fewer segments does.

The objects themselves are cheaper: a mesh costs its triangles once, however many layers it
has, and the preview already loads the objects as shells for its translucent ghost. A new
preference, "Only render solid model when dragging" (off by default), draws those shells
opaque in their filament colours instead of the toolpaths while the user drags.

The visible layer range still holds. The shells are cut at the range's top and bottom through
the gouraud shader's z range, and libvgcode keeps a second index buffer holding just the
range's bottom and top layers, filled in the same walk as the full one, which is drawn
afterwards so that it caps the cut with what was really printed there. Switching between the
two sets is a buffer binding, never a rebuild. The prime tower is added to the shells from its
sliced mesh while the preference is on, positioned as the print placed it; it keeps its opaque
colour and so stays out of the translucent ghost. Supports have no mesh and are not shown.

Dragging is the camera, the navigator or either slider being held; a slider reports it from
ImGui's active id, since its dirty flag is raised and consumed inside one frame. A wheel step
holds the solid model for a 150 ms settle time, with the frame that restores the toolpaths
scheduled for when it runs out. The switch is decided at the top of the frame, before the
cached scene is consulted, and a frame that switches redraws the scene. A drag cut short by
focus or capture loss is ended explicitly, and a button release wakes the idle loop, since on
some platforms no idle event follows it until the next input.

With the preference off, nothing is built and the preview is unchanged.
2026-09-22 14:52:49 +08:00
Hanif Koh 770e4feac5 Draw the Preview's Toolpath Segments From an Index Buffer
The preview's frame cost is dominated by one call: a single instanced draw of
every visible toolpath segment. On a tall multi-filament print the wipe tower
supplies most of those segments, which is why the preview of a large tower is
slow and why shrinking the layer range speeds it up again.

That draw is not fill bound. Shrinking the model to about a fortieth of its
screen area moved the frame from 419 ms to 401 ms, so the cost is per segment,
not per pixel, and it is paid in the vertex shader: five texelFetch calls plus
several cross/normalize per invocation.

Each segment is a box of eight corners, but it was submitted with
glDrawArraysInstanced over a 24 entry array, so every corner was transformed
once per triangle that touches it and the shader ran 24 times per segment. The
same 24 entries are now an element buffer over the eight distinct corners, which
lets the post-transform cache reuse them and drops the shader to 8 runs per
segment. The triangles, their winding and the vertex_id each corner receives are
unchanged.

Measured over 100 frames on the 636-layer, 351k-vertex three-filament fixture,
the segment draw goes from 381 ms to 322 ms per frame. That is a software
rasterizer, where triangle setup dominates and understates the win; the drop in
shader invocations is the transferable part.

Verified by loading the same project in this build and in a build of the parent
commit and comparing the canvas across three states - the default view, a
rotated camera, and a reduced layer range: pixel identical in all three. The
rotated case matters because the shader picks its corner offsets from the camera
direction. The only pixels that differ anywhere on screen are in the G-code text
panel, which prints a per-process object id that varies between any two runs.
2026-09-22 14:52:49 +08:00
Hanif Koh d54abc874f Answer the Preview's Per-Frame Time Query From a Cached Sum
The G-code preview's cost is linear in the number of toolpath vertices, and on a
tall multi-filament print the wipe tower dominates that count: it emits a roughly
constant 160-180 moves on every layer whatever the object is, measured at 57-61%
of all moves on a three-filament print.

Four places scanned or allocated across the whole vertex array. None of them
needed to.

get_estimated_time_at re-accumulated the estimated time from vertex 0 on every
call, and its caller is the tool marker tooltip, which ImGui re-renders every
frame while the properties panel is unfolded. It now reads a running sum built
during load from the total the load loop was already keeping, so the value is
the same addition in the same order and the float result is unchanged. At the
351k vertices of a 636-layer test print this drops the call from 238us to
0.006us.

update_view_full_range walked from vertex 0 to find where the layer range starts,
on every slider tick. It now starts at the first vertex of that layer. The index
is derived from the vertices rather than from Layers::Item::range, because
Layers::update folds a vertex whose layer_id arrives out of order into whichever
bucket is open, which makes that range the wrong answer in general; the index
costs four bytes per layer, not per vertex.

update_colors_texture allocated one float per vertex of the whole print on every
slider tick. It now reuses a buffer.

render_legend fetched the layer Zs and the per-layer times from inside loops over
the custom G-code items, and built whole vectors only to test them for emptiness.
The times are hoisted, the Zs are built lazily so a print with no colour change
does not pay for them at all, and the emptiness tests use the existing counters.

No rendering behaviour changes. Verified by loading the same project in this
build and a build of the parent commit under Xvfb and comparing frames across
five interaction states, including the unfolded tooltip whose Time row is the
output of the function that changed: pixel identical.
2026-09-22 14:52:49 +08:00
44 changed files with 581 additions and 872 deletions
+62
View File
@@ -0,0 +1,62 @@
# G-code preview while dragging
The sliced preview draws every toolpath segment of the plate as an instanced box. On a large
plate that is tens of millions of segments, and the frame is GPU-bound: the cost is the number of
instances drawn, not anything the CPU does per frame. Dragging the camera over such a plate cannot
keep up. With the `preview_solid_model_while_dragging` preference (*Graphics > G-code Preview*,
off by default), the preview draws the sliced objects as solid meshes while the user drags and
puts the toolpaths back when they let go. A mesh costs its triangles once, however many layers it
has.
`GCodeViewer` draws the solid model, libvgcode (`src/libvgcode`) keeps the toolpaths that cap it,
and `GLCanvas3D` decides when the user is dragging. The OpenGL ES path ignores the preference.
## The solid model
The preview already loads the sliced objects as shells for its translucent ghost.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colours, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The shells hold only the
objects, so while the preference is on the prime tower is added from its sliced mesh, positioned
as the print placed it. It is added or removed on its own when the preference changes, without
reloading the objects, keeps its opaque colour so that it never appears among the translucent
shells, and stays out of their bounding box. Supports have no mesh and are not shown.
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
only the end layers.
## End-layer set
The cut faces of the solid model are capped with what was really printed there: the toolpaths of
the bottom and top layers of the visible range. While the preference is on,
`ViewerImpl::update_enabled_entities()` fills a second, **reduced** index buffer holding just those
two layers, in the same walk that fills the full one. Building both together is what makes
switching free: starting or ending a drag is a buffer binding, never a rebuild.
## Deciding that the user is dragging
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the
canvas decides whether to reuse its cached scene, so that the switch lands in that frame. Dragging
is the camera, the navigator or either slider being held; a slider reports this from ImGui's active
id rather than its dirty flag, which is raised and consumed inside one frame. A wheel step has no
duration, so it holds the solid model for a 150 ms settle time instead, and the frame that restores
the toolpaths is scheduled for when that time runs out, since the render timer only wakes the idle
loop. A drag cut short by focus or capture loss is ended explicitly, and a button release wakes the
idle loop, because on some platforms nothing else would until the next input.
## Reused scene frames
`GLCanvas3D` keeps its last scene pass for frames that only rebuild the overlay (`SceneCache`). Its
key covers the canvas size, the camera and hover state, not what the toolpath sets draw, so a frame
that reuses the scene must never be one on which the solid model is switched.
`_update_preview_interaction()` therefore reports whether the bound set changed, and a frame on
which it did redraws the scene. The canvas neither captures nor reuses the scene while the user
drags, so no solid-model frame outlives a drag, and the frame that ends a wheel's settle time is
requested as a full frame.
## Per-frame lookups
The segment template draws its box from 8 corners through an index buffer, so the vertex shader
runs at most once per corner. `get_estimated_time_at()`, which the tool marker tooltip calls every
frame, starts from the running time at the first vertex of the vertex's layer, kept per layer at
load, and adds only that layer's vertices. The sum runs in vertex order, so it matches a full
accumulation exactly while costing memory per layer rather than per vertex.
+4
View File
@@ -205,6 +205,10 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
// draw the sliced objects instead of their toolpaths while the user drags the preview
if (get("preview_solid_model_while_dragging").empty())
set_bool("preview_solid_model_while_dragging", false);
// ORCA: darken the layers the preview layer slider is not scrubbed to
if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false);
+5 -46
View File
@@ -8,7 +8,6 @@
#include "I18N.hpp"
#include "GCode.hpp"
#include "Exception.hpp"
#include "LifecycleEvents.hpp"
#include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp"
#include "Geometry/ConvexHull.hpp"
@@ -2477,14 +2476,6 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
m_writer.set_is_bbl_machine(print->is_BBL_printer());
print->set_started(psGCodeExport);
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = path;
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
// check if any custom gcode contains keywords used by the gcode processor to
// produce time estimation and gcode toolpaths
std::vector<std::pair<std::string, std::string>> validation_res = DoExport::validate_custom_gcode(*print);
@@ -2520,22 +2511,12 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
m_processor.set_print(print);
GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor);
if (! file.is_open()) {
std::string err_msg = std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n";
BOOST_LOG_TRIVIAL(error) << err_msg << std::endl;
BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n" << std::endl;
if (!fs::exists(folder)) {
//fs::create_directory(folder);
std::string add_err_msg = "the parent path " + folder.string() +" is not there!!!";
BOOST_LOG_TRIVIAL(error) << add_err_msg << std::endl;
err_msg += add_err_msg;
BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() +" is not there!!!" << std::endl;
}
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\n" + err_msg;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(err_msg);
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
}
try {
@@ -2546,18 +2527,11 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
boost::nowide::remove(path_tmp.c_str());
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
}
} catch (std::exception &ex) {
} catch (std::exception & /* ex */) {
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
// Close and remove the file.
file.close();
boost::nowide::remove(path_tmp.c_str());
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\n" + ex.what();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw;
}
file.close();
@@ -2663,13 +2637,6 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
std::error_code ret = rename_file(path_tmp, path);
if (ret) {
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' +
"Is " + path_tmp + " locked?" + '\n');
@@ -2680,15 +2647,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
print->set_done(psGCodeExport);
{
LifecycleEventContext ctx;
ctx.name = std::to_string(print->model().id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = path;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
// Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT /
// M486), which is driven by exclude_object for every printer
if(result != nullptr)
-181
View File
@@ -1,181 +0,0 @@
#pragma once
// LifecycleEvents.hpp
// --------------------
// Application lifecycle events (project, slicing, plate editing, preset, printer connection, and
// job activity) that other subsystems -- chiefly the plugin layer above libslic3r -- may want to
// observe. Lives in libslic3r rather than the plugin layer because some events fire from inside
// the slicing engine itself; see fire_lifecycle_event() below.
#include <functional>
#include <string>
#include <utility>
namespace Slic3r
{
enum class LifecycleEvent {
// Project (3mf)
NewProject,
ProjectOpened,
ProjectBeforeSave,
ProjectAfterSave,
ProjectClosed,
ProjectDirtyChanged,
// Slicing pipeline
SliceStarted,
SliceGeometryFinished,
GCodeExportStarted,
GCodeExportFinished,
SlicingJobComplete,
// Plate/model editing
ObjectAdded,
ObjectDeleted,
ObjectTransformed,
ObjectChanged,
ObjectRenamed,
PlateCreated,
PlateDeleted,
PlateSelected,
PlateRenamed,
// Preset
PresetSelected,
PresetSaved,
// Printer/device
PrintStateChanged,
DeviceOnlineChanged,
DeviceDiscovered,
DeviceSelected,
DeviceConnected,
DeviceDisconnected,
UploadStarted,
UploadFinished,
// Print/send jobs
PrintJobStarted,
PrintJobFinished,
SendJobStarted,
SendJobFinished,
};
// Scoped so callers must qualify (LifecycleEvtCode::Error, not ERROR) -- ERROR/OK collide with
// Windows macros (wingdi.h) as unqualified names.
enum class LifecycleEvtCode { Ok, Error, Warn };
struct LifecycleEventContext
{
// The primary subject identifier for the event. This is event-specific (for example, a
// project/output path, preset name, device id, or object name), must not contain status or
// prose, and may be empty when the event has no single subject.
std::string name;
// Outcome of the operation represented by the event. For state-change and start events,
// Ok means that the event occurred; it does not imply that a future operation succeeded.
LifecycleEvtCode code = LifecycleEvtCode::Ok;
// Optional human-readable detail or diagnostic text. It is not a stable parsing contract;
// machine-readable data should be represented by a dedicated field or event instead.
std::string msg;
// Stable subject/object identifier, when the source model provides one.
std::string id;
// Previous value for rename and other before/after events.
std::string previous_name;
// Device identifier for printer and job events.
std::string device_id;
// Job identifier when the originating queue/task provides one.
std::string job_id;
// Source subsystem or operation detail, suitable for filtering but not guaranteed to be
// exhaustive across versions.
std::string source;
// Plate, object, or volume index when the source uses an index rather than a stable id.
int index = -1;
// Aggregate project dirty state for ProjectDirtyChanged.
bool dirty = false;
};
inline std::string lifecycle_event_to_string(LifecycleEvent event)
{
switch (event) {
case LifecycleEvent::NewProject: return "NewProject";
case LifecycleEvent::ProjectOpened: return "ProjectOpened";
case LifecycleEvent::ProjectBeforeSave: return "ProjectBeforeSave";
case LifecycleEvent::ProjectAfterSave: return "ProjectAfterSave";
case LifecycleEvent::ProjectClosed: return "ProjectClosed";
case LifecycleEvent::ProjectDirtyChanged: return "ProjectDirtyChanged";
case LifecycleEvent::SliceStarted: return "SliceStarted";
case LifecycleEvent::SliceGeometryFinished: return "SliceGeometryFinished";
case LifecycleEvent::GCodeExportStarted: return "GCodeExportStarted";
case LifecycleEvent::GCodeExportFinished: return "GCodeExportFinished";
case LifecycleEvent::SlicingJobComplete: return "SlicingJobComplete";
case LifecycleEvent::ObjectAdded: return "ObjectAdded";
case LifecycleEvent::ObjectDeleted: return "ObjectDeleted";
case LifecycleEvent::ObjectTransformed: return "ObjectTransformed";
case LifecycleEvent::ObjectChanged: return "ObjectChanged";
case LifecycleEvent::ObjectRenamed: return "ObjectRenamed";
case LifecycleEvent::PlateCreated: return "PlateCreated";
case LifecycleEvent::PlateDeleted: return "PlateDeleted";
case LifecycleEvent::PlateSelected: return "PlateSelected";
case LifecycleEvent::PlateRenamed: return "PlateRenamed";
case LifecycleEvent::PresetSelected: return "PresetSelected";
case LifecycleEvent::PresetSaved: return "PresetSaved";
case LifecycleEvent::PrintStateChanged: return "PrintStateChanged";
case LifecycleEvent::DeviceOnlineChanged: return "DeviceOnlineChanged";
case LifecycleEvent::DeviceDiscovered: return "DeviceDiscovered";
case LifecycleEvent::DeviceSelected: return "DeviceSelected";
case LifecycleEvent::DeviceConnected: return "DeviceConnected";
case LifecycleEvent::DeviceDisconnected: return "DeviceDisconnected";
case LifecycleEvent::UploadStarted: return "UploadStarted";
case LifecycleEvent::UploadFinished: return "UploadFinished";
case LifecycleEvent::PrintJobStarted: return "PrintJobStarted";
case LifecycleEvent::PrintJobFinished: return "PrintJobFinished";
case LifecycleEvent::SendJobStarted: return "SendJobStarted";
case LifecycleEvent::SendJobFinished: return "SendJobFinished";
default: return "Unknown";
}
}
inline std::string lifecycle_evt_code_to_string(LifecycleEvtCode code)
{
switch (code) {
case LifecycleEvtCode::Ok: return "Ok";
case LifecycleEvtCode::Error: return "Error";
case LifecycleEvtCode::Warn: return "Warn";
default: return "Unknown";
}
}
// Global cross-layer seam (mirrors ConfigBase::set_resolve_capability_fn): any libslic3r code can
// fire a lifecycle event without depending on the plugin layer above it, which installs the
// dispatcher here at startup. Not tied to Print/GCode specifically, since nothing here should
// require callers to hold a Print& just to report an event.
using LifecycleHookFn = std::function<void(LifecycleEvent, const LifecycleEventContext&)>;
inline LifecycleHookFn& lifecycle_hook_fn()
{
static LifecycleHookFn fn;
return fn;
}
inline void set_lifecycle_hook_fn(LifecycleHookFn fn) { lifecycle_hook_fn() = std::move(fn); }
inline void fire_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx)
{
if (const LifecycleHookFn& fn = lifecycle_hook_fn(); fn)
fn(event, ctx);
}
}
-10
View File
@@ -47,7 +47,6 @@
#include <boost/log/trivial.hpp>
#include "libslic3r.h"
#include "LifecycleEvents.hpp"
#include "Utils.hpp"
#include "Time.hpp"
#include "PlaceholderParser.hpp"
@@ -2973,7 +2972,6 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
// 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config.
auto it = this->find_preset_internal(new_name);
const bool preset_existed = (it != m_presets.end() && it->name == new_name);
if (it != m_presets.end() && it->name == new_name) {
// Preset with the same name found.
Preset &preset = *it;
@@ -3081,14 +3079,6 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
this->get_selected_preset().save(&(parent_preset->config));
else
this->get_selected_preset().save(nullptr);
{
LifecycleEventContext ctx;
ctx.name = new_name;
ctx.msg = preset_existed ? "overwrite" : "new";
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::PresetSaved, ctx);
}
}
// A detached standalone preset for the Full Publish receiver: create a user preset holding
+1 -39
View File
@@ -14,7 +14,6 @@
#include "Flow.hpp"
#include "Geometry/ConvexHull.hpp"
#include "I18N.hpp"
#include "LifecycleEvents.hpp"
#include "ShortestPath.hpp"
#include "Thread.hpp"
#include "Time.hpp"
@@ -2695,13 +2694,6 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (m_objects.empty())
return;
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
}
for (PrintObject *obj : m_objects)
obj->clear_shared_object();
@@ -3320,13 +3312,6 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
}
}
// G-code export process, running at a background thread.
@@ -4926,14 +4911,6 @@ void Print::set_gcode_file_invalidated()
//BBS: add gcode file preload logic
void Print::export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
{
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = file;
fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx);
}
try {
GCodeProcessor processor;
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
@@ -4953,28 +4930,13 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
*result = std::move(processor.extract_result());
result->filament_change_sequence = filament_seq_loaded;
result->nozzle_change_sequence = nozzle_seq_loaded;
} catch (std::exception &ex) {
} catch (std::exception & /* ex */) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Error;
ctx.msg = file + "\n" + ex.what();
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
throw Slic3r::RuntimeError(
std::string("Failed to process the G-code file ") + file + " from previous 3mf\n");
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": process the G-code file %1% successfully")%file.c_str();
{
LifecycleEventContext ctx;
ctx.name = std::to_string(m_model.id().id);
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = file;
fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx);
}
}
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
+9
View File
@@ -98,6 +98,15 @@ public:
//
bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value);
//
// The reduced set holds only the bottom and top layers of the visible range, for a caller that
// draws the print itself some other way while the user drags. While enabled it is built
// alongside the full set, so set_reduced_detail() rebuilds nothing. Ignored on the OpenGL ES path.
//
void set_reduced_detail_enabled(bool value);
bool is_reduced_detail_enabled() const;
void set_reduced_detail(bool value);
bool is_reduced_detail() const;
float get_dim_previous_layers_brightness() const;
void set_dim_previous_layers_brightness(float value);
//
+20 -4
View File
@@ -15,7 +15,12 @@ namespace libvgcode {
//| 2--0-------5--7 |
//| \ | | / |
//| 3-------4 |
static constexpr const std::array<uint8_t, 24> VERTEX_DATA = {
// The eight corners the vertex shader knows how to place. Each is sent once and
// referenced by INDEX_DATA below, so the post-transform cache can reuse it across
// the triangles that share it: the shader runs 8 times per segment instead of 24.
static constexpr const std::array<uint8_t, 8> VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 };
static constexpr const std::array<uint8_t, 24> INDEX_DATA = {
0, 1, 2, // front spike
0, 2, 3, // front spike
0, 3, 4, // right/bottom body
@@ -31,7 +36,7 @@ void SegmentTemplate::init()
if (m_vao_id != 0)
return;
m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t);
m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t);
int curr_vertex_array;
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
@@ -51,12 +56,22 @@ void SegmentTemplate::init()
glsafe(glVertexAttribIPointer(0, 1, GL_UNSIGNED_BYTE, 0, (const void*)0));
#endif // ENABLE_OPENGL_ES
// The element buffer binding is part of the vao state, so it is left bound here
// and restored together with the vao.
glsafe(glGenBuffers(1, &m_ibo_id));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer));
glsafe(glBindVertexArray(curr_vertex_array));
}
void SegmentTemplate::shutdown()
{
if (m_ibo_id != 0) {
glsafe(glDeleteBuffers(1, &m_ibo_id));
m_ibo_id = 0;
}
if (m_vbo_id != 0) {
glsafe(glDeleteBuffers(1, &m_vbo_id));
m_vbo_id = 0;
@@ -71,14 +86,15 @@ void SegmentTemplate::shutdown()
void SegmentTemplate::render(size_t count)
{
if (m_vao_id == 0 || m_vbo_id == 0 || count == 0)
if (m_vao_id == 0 || m_vbo_id == 0 || m_ibo_id == 0 || count == 0)
return;
int curr_vertex_array;
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
glsafe(glBindVertexArray(m_vao_id));
glsafe(glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast<GLsizei>(VERTEX_DATA.size()), static_cast<GLsizei>(count)));
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_BYTE,
nullptr, static_cast<GLsizei>(count)));
glsafe(glBindVertexArray(curr_vertex_array));
}
+1
View File
@@ -40,6 +40,7 @@ private:
//
unsigned int m_vao_id{ 0 };
unsigned int m_vbo_id{ 0 };
unsigned int m_ibo_id{ 0 };
//
// Size of the data sent to gpu, in bytes.
//
+4
View File
@@ -25,6 +25,10 @@ struct Settings
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
float dim_previous_layers_brightness{ 0.4f };
bool spiral_vase_mode{ false };
// whether the reduced set (the visible range's end layers) is built, and whether it is drawn.
// Ignored on the OpenGL ES path.
bool reduced_detail_enabled{ false };
bool reduced_detail{ false };
//
// Required update flags
//
+20
View File
@@ -77,6 +77,26 @@ bool Viewer::is_dim_previous_layers() const
return m_impl->is_dim_previous_layers();
}
void Viewer::set_reduced_detail(bool value)
{
m_impl->set_reduced_detail(value);
}
bool Viewer::is_reduced_detail() const
{
return m_impl->is_reduced_detail();
}
void Viewer::set_reduced_detail_enabled(bool value)
{
m_impl->set_reduced_detail_enabled(value);
}
bool Viewer::is_reduced_detail_enabled() const
{
return m_impl->is_reduced_detail_enabled();
}
void Viewer::set_dim_previous_layers(bool value)
{
m_impl->set_dim_previous_layers(value);
+125 -11
View File
@@ -875,6 +875,12 @@ void ViewerImpl::reset()
m_travels_time = { 0.0f, 0.0f };
m_vertices.clear();
m_vertices_colors.clear();
// swap rather than clear: these are sized by the print, and a reset means the memory
// should go back, not sit reserved until the next load
for (std::vector<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
std::vector<float>().swap(m_colors_scratch);
m_valid_lines_bitset.clear();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
@@ -885,9 +891,15 @@ void ViewerImpl::reset()
#else
m_enabled_segments_count = 0;
m_enabled_options_count = 0;
m_enabled_segments_reduced_count = 0;
m_enabled_options_reduced_count = 0;
m_settings_used_for_ranges = std::nullopt;
delete_textures(m_enabled_options_reduced_tex_id);
delete_buffers(m_enabled_options_reduced_buf_id);
delete_textures(m_enabled_segments_reduced_tex_id);
delete_buffers(m_enabled_segments_reduced_buf_id);
delete_textures(m_enabled_options_tex_id);
delete_buffers(m_enabled_options_buf_id);
delete_textures(m_enabled_segments_tex_id);
@@ -1048,6 +1060,37 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
v.layer_duration = m_layers.get_layer_time(m_settings.time_mode, static_cast<size_t>(v.layer_id));
}
// Index of the first vertex of each layer, walked back to front so that a layer with no
// vertex of its own inherits the next layer's index and the array stays non-decreasing.
if (!m_layers.empty()) {
const uint32_t vertices_count = static_cast<uint32_t>(m_vertices.size());
m_layer_first_vertex.assign(m_layers.count(), vertices_count);
for (uint32_t i = vertices_count; i > 0; --i) {
const uint32_t layer_id = m_vertices[i - 1].layer_id;
if (layer_id < m_layer_first_vertex.size())
m_layer_first_vertex[layer_id] = i - 1;
}
for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i)
m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]);
// the running time at each layer's first vertex, summed in vertex order so that
// get_estimated_time_at() matches a full accumulation exactly
std::array<float, TIME_MODES_COUNT> running{};
for (std::vector<float>& times : m_layer_start_times)
times.assign(m_layer_first_vertex.size(), 0.0f);
size_t layer = 0;
for (size_t i = 0; i <= m_vertices.size(); ++i) {
for (; layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] == i; ++layer) {
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
m_layer_start_times[j][layer] = running[j];
}
if (i < m_vertices.size()) {
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
running[j] += m_vertices[i].times[j];
}
}
}
if (!m_layers.empty())
m_layers.set_view_range(0, static_cast<uint32_t>(m_layers.count()) - 1);
@@ -1116,6 +1159,17 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
glsafe(glGenTextures(1, &m_enabled_options_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
// create (but do not fill) the reduced counterparts of the two buffers above
glsafe(glGenBuffers(1, &m_enabled_segments_reduced_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
glsafe(glGenTextures(1, &m_enabled_segments_reduced_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_id));
glsafe(glGenBuffers(1, &m_enabled_options_reduced_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glGenTextures(1, &m_enabled_options_reduced_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
#endif // ENABLE_OPENGL_ES
@@ -1134,6 +1188,14 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_segments;
std::vector<uint32_t> enabled_options;
#ifndef ENABLE_OPENGL_ES
// the reduced set is filled by the same walk, so switching to it costs no rebuild. It keeps the
// bottom and top layers of the visible range: the surfaces the range cuts open
const bool build_reduced = m_settings.reduced_detail_enabled;
std::vector<uint32_t> enabled_segments_reduced;
std::vector<uint32_t> enabled_options_reduced;
const Interval& layers_range = m_layers.get_view_range();
#endif // ENABLE_OPENGL_ES
Interval range = m_view_range.get_visible();
// when top layer only visualization is enabled, we need to render
@@ -1181,6 +1243,11 @@ void ViewerImpl::update_enabled_entities()
enabled_options.push_back(static_cast<uint32_t>(i));
else
enabled_segments.push_back(static_cast<uint32_t>(i));
#ifndef ENABLE_OPENGL_ES
if (build_reduced && (v.layer_id == layers_range[0] || v.layer_id == layers_range[1]))
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
#endif // ENABLE_OPENGL_ES
}
#ifdef ENABLE_OPENGL_ES
@@ -1209,6 +1276,21 @@ void ViewerImpl::update_enabled_entities()
else
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
m_enabled_segments_reduced_count = enabled_segments_reduced.size();
m_enabled_options_reduced_count = enabled_options_reduced.size();
if (build_reduced) {
assert(m_enabled_segments_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_segments_reduced.size() * sizeof(uint32_t),
enabled_segments_reduced.empty() ? nullptr : enabled_segments_reduced.data(), GL_STATIC_DRAW));
assert(m_enabled_options_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_options_reduced.size() * sizeof(uint32_t),
enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW));
}
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
@@ -1261,7 +1343,10 @@ void ViewerImpl::update_colors_texture()
// Based on current settings and slider position, we might want to render some
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
std::vector<float> colors(m_vertices_colors.size());
// Reused across calls: this runs on every slider tick, and the allocation alone is
// 4 bytes per vertex of the whole print each time.
std::vector<float>& colors = m_colors_scratch;
colors.resize(m_vertices_colors.size());
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
@@ -1384,6 +1469,14 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture();
}
void ViewerImpl::set_reduced_detail_enabled(bool value)
{
if (m_settings.reduced_detail_enabled == value)
return;
m_settings.reduced_detail_enabled = value;
m_settings.update_enabled_entities = true;
}
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
void ViewerImpl::set_dim_previous_layers(bool value)
{
@@ -1516,8 +1609,19 @@ void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::valu
float ViewerImpl::get_estimated_time_at(size_t id) const
{
return std::accumulate(m_vertices.begin(), m_vertices.begin() + id + 1, 0.0f,
[this](float a, const PathVertex& v) { return a + v.times[static_cast<size_t>(m_settings.time_mode)]; });
const size_t mode = static_cast<size_t>(m_settings.time_mode);
if (mode >= TIME_MODES_COUNT || id >= m_vertices.size())
return 0.0f;
size_t first = 0;
float time = 0.0f;
const size_t layer = static_cast<size_t>(m_vertices[id].layer_id);
if (layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] <= id) {
first = m_layer_first_vertex[layer];
time = m_layer_start_times[mode][layer];
}
for (size_t i = first; i <= id; ++i)
time += m_vertices[i].times[mode];
return time;
}
Color ViewerImpl::get_vertex_color(const PathVertex& v) const
@@ -1722,6 +1826,10 @@ size_t ViewerImpl::get_used_cpu_memory() const
ret += sizeof(m_extrusion_roles_colors);
ret += sizeof(m_options_colors);
ret += STDVEC_MEMSIZE(m_vertices, PathVertex);
for (const std::vector<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
ret += STDVEC_MEMSIZE(m_colors_scratch, float);
ret += m_valid_lines_bitset.size_in_bytes_cpu();
ret += m_height_range.size_in_bytes_cpu();
ret += m_width_range.size_in_bytes_cpu();
@@ -1787,7 +1895,11 @@ void ViewerImpl::update_view_full_range()
const bool travels_visible = m_settings.options_visibility[size_t(EOptionType::Travels)];
const bool wipes_visible = m_settings.options_visibility[size_t(EOptionType::Wipes)];
// every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the loop
// below would skip all of them anyway
auto first_it = m_vertices.begin();
if (layers_range[0] < m_layer_first_vertex.size())
first_it += m_layer_first_vertex[layers_range[0]];
while (first_it != m_vertices.end() &&
(first_it->layer_id < layers_range[0] || !is_visible(*first_it, m_settings))) {
++first_it;
@@ -1974,7 +2086,8 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
#ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_segments_count() == 0)
#else
if (m_enabled_segments_count == 0)
const ActiveSet segments = active_segments();
if (segments.count == 0)
#endif // ENABLE_OPENGL_ES
return;
@@ -2033,10 +2146,10 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, segments.tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, segments.buf_id));
m_segment_template.render(m_enabled_segments_count);
m_segment_template.render(segments.count);
#endif // ENABLE_OPENGL_ES
if (curr_cull_face)
@@ -2062,7 +2175,8 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
#ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_options_count() == 0)
#else
if (m_enabled_options_count == 0)
const ActiveSet options = active_options();
if (options.count == 0)
#endif // ENABLE_OPENGL_ES
return;
@@ -2120,10 +2234,10 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, options.tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, options.buf_id));
m_option_template.render(m_enabled_options_count);
m_option_template.render(options.count);
#endif // ENABLE_OPENGL_ES
if (!curr_cull_face)
+55
View File
@@ -91,6 +91,19 @@ public:
// 0.0 = black
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
void set_dim_previous_layers(bool value);
//
// Draw from the reduced set; it is already built, so this is just a buffer binding.
//
void set_reduced_detail(bool value) {
#ifdef ENABLE_OPENGL_ES
// no reduced set is built on OpenGL ES
value = false;
#endif // ENABLE_OPENGL_ES
m_settings.reduced_detail = value;
}
bool is_reduced_detail() const { return m_settings.reduced_detail; }
bool is_reduced_detail_enabled() const { return m_settings.reduced_detail_enabled; }
void set_reduced_detail_enabled(bool value);
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
void set_dim_previous_layers_brightness(float value);
@@ -234,6 +247,20 @@ private:
//
std::array<float, TIME_MODES_COUNT> m_total_time{ 0.0f, 0.0f };
//
// Running sum of the vertex estimated times at each layer's first vertex, for each time mode,
// so that get_estimated_time_at() only accumulates the vertices of one layer.
//
std::array<std::vector<float>, TIME_MODES_COUNT> m_layer_start_times;
//
// For each layer L, the index of the first vertex whose layer_id is >= L (m_vertices.size()
// if there is none). Derived from the vertices, so it stays exact whatever order they arrive in.
//
std::vector<uint32_t> m_layer_first_vertex;
//
// Scratch buffer for update_colors_texture(), kept alive across slider steps
//
std::vector<float> m_colors_scratch;
//
// Detected travel moves times
//
std::array<float, TIME_MODES_COUNT> m_travels_time{ 0.0f, 0.0f };
@@ -460,6 +487,15 @@ private:
unsigned int m_enabled_options_tex_id{ 0 };
size_t m_enabled_options_count{ 0 };
//
// OpenGL buffers to store the reduced set drawn while Settings::reduced_detail is set
//
unsigned int m_enabled_segments_reduced_buf_id{ 0 };
unsigned int m_enabled_segments_reduced_tex_id{ 0 };
size_t m_enabled_segments_reduced_count{ 0 };
unsigned int m_enabled_options_reduced_buf_id{ 0 };
unsigned int m_enabled_options_reduced_tex_id{ 0 };
size_t m_enabled_options_reduced_count{ 0 };
//
// Caches for size of data sent to gpu, in bytes
//
size_t m_positions_tex_size{ 0 };
@@ -467,6 +503,25 @@ private:
size_t m_colors_tex_size{ 0 };
size_t m_enabled_segments_tex_size{ 0 };
size_t m_enabled_options_tex_size{ 0 };
// The set the next draw reads from: the reduced one while dragging, if one is built.
bool use_reduced_set() const { return m_settings.reduced_detail && m_settings.reduced_detail_enabled; }
struct ActiveSet
{
size_t count{ 0 };
unsigned int buf_id{ 0 };
unsigned int tex_id{ 0 };
};
ActiveSet active_segments() const {
if (use_reduced_set())
return { m_enabled_segments_reduced_count, m_enabled_segments_reduced_buf_id, m_enabled_segments_reduced_tex_id };
return { m_enabled_segments_count, m_enabled_segments_buf_id, m_enabled_segments_tex_id };
}
ActiveSet active_options() const {
if (use_reduced_set())
return { m_enabled_options_reduced_count, m_enabled_options_reduced_buf_id, m_enabled_options_reduced_tex_id };
return { m_enabled_options_count, m_enabled_options_buf_id, m_enabled_options_tex_id };
}
#endif // ENABLE_OPENGL_ES
void update_view_full_range();
+2 -25
View File
@@ -10,7 +10,6 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Time.hpp"
@@ -356,10 +355,7 @@ namespace Slic3r
obj->bind_state = "free";
obj->last_alive = Slic3r::Utils::get_current_time_utc();
// Route through set_online_state() (rather than writing m_is_online directly) so the
// DeviceOnlineChanged lifecycle event fires consistently; same effective value/behavior
// here since the object was already online in the common case.
obj->set_online_state(true);
obj->m_is_online = true;
obj->set_dev_name(dev_name);
/* if (!obj->dev_ip.empty()) {
Slic3r::GUI::wxGetApp().app_config->set_str("ip_address", obj->dev_id, obj->dev_ip);
@@ -377,10 +373,6 @@ namespace Slic3r
obj->bind_sec_link = sec_link;
obj->dev_connection_name = connection_name;
obj->bind_ssdp_version = ssdp_version;
// Discovery establishes the initial reachability state. Do not report it as an
// online transition; DeviceDiscovered below is the lifecycle event for a new
// device. Subsequent updates route through set_online_state(), so a known device
// still emits DeviceOnlineChanged when its reachability actually changes.
obj->m_is_online = true;
//load access code
@@ -397,15 +389,6 @@ namespace Slic3r
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " New Machine, dev_id= " << dev_id
<< ", ip = " << dev_ip <<", printer_name = " << dev_name
<< ", con_type= " << connect_type <<", signal= " << printer_signal << ", bind_state= " << bind_state;
// First discovery of a genuinely new device (not a periodic SSDP/heartbeat update to
// an already-known one, which is handled in the branch above).
{
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx);
}
}
update_local_machine(*obj);
}
@@ -1001,14 +984,8 @@ namespace Slic3r
}
void DeviceManager::OnSelectedMachineChanged(const std::string& /*pre_dev_id*/,
const std::string& new_dev_id)
const std::string& /*new_dev_id*/)
{
{
LifecycleEventContext ctx;
ctx.name = new_dev_id; // empty string is a valid deselection
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceSelected, ctx);
}
if (MachineObject* obj_ = get_selected_machine()) {
GUI::wxGetApp().sidebar().update_sync_status(obj_);
if(m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription)
+1 -27
View File
@@ -5,7 +5,6 @@
#include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "GuiColor.hpp"
@@ -2627,15 +2626,7 @@ void MachineObject::reset()
void MachineObject::set_print_state(std::string status)
{
const bool changed = (print_status != status);
print_status = status;
if (changed) {
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = print_status;
fire_lifecycle_event(LifecycleEvent::PrintStateChanged, ctx);
}
}
int MachineObject::connect(bool use_openssl)
@@ -2657,10 +2648,6 @@ int MachineObject::connect(bool use_openssl)
int MachineObject::disconnect()
{
if (m_agent) {
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
return m_agent->disconnect_printer();
}
return -1;
@@ -2691,16 +2678,8 @@ bool MachineObject::is_connecting()
void MachineObject::set_online_state(bool on_off)
{
const bool changed = (m_is_online != on_off);
m_is_online = on_off;
if (!on_off) m_active_state = NotActive;
if (changed) {
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
ctx.msg = on_off ? "online" : "offline";
fire_lifecycle_event(LifecycleEvent::DeviceOnlineChanged, ctx);
}
}
bool MachineObject::is_info_ready(bool check_version) const
@@ -4622,13 +4601,8 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
try {
if (j.contains("event")) {
if (j["event"].contains("event")) {
if (j["event"]["event"].get<std::string>() == "client.disconnected") {
if (j["event"]["event"].get<std::string>() == "client.disconnected")
set_online_state(false);
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
}
else if (j["event"]["event"].get<std::string>() == "client.connected")
set_online_state(true);
}
+141 -5
View File
@@ -1177,6 +1177,8 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range();
read_solid_model_preference();
// ORCA: darken the layers the preview layer slider is not scrubbed to
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
@@ -1589,6 +1591,7 @@ void GCodeViewer::reset_shell()
{
m_shells.volumes.clear();
m_shells.print_id = -1;
m_shells.with_wipe_tower = false;
m_shell_bounding_box = BoundingBoxf3();
}
@@ -1625,9 +1628,14 @@ void GCodeViewer::reset()
void GCodeViewer::render_scene(int canvas_width, int canvas_height)
{
glsafe(::glEnable(GL_DEPTH_TEST));
render_shells(canvas_width, canvas_height);
// while dragging with the solid model on, the objects stand in for their toolpaths, cut to the
// visible layer range; the toolpath set then holds only the range's bottom and top layers
if (m_viewer.is_reduced_detail())
render_solid_model(canvas_width, canvas_height);
else
render_shells(canvas_width, canvas_height);
if (m_viewer.get_extrusion_roles().empty())
if (m_viewer.get_extrusion_roles_count() == 0)
return;
render_toolpaths();
@@ -1925,6 +1933,41 @@ void GCodeViewer::update_layers_slider_mode()
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
}
void GCodeViewer::set_interacting(bool interacting)
{
// with no shells to stand in for the toolpaths, the solid model would leave only the end layers
m_viewer.set_reduced_detail(m_solid_model_while_dragging && interacting && !m_shells.volumes.empty());
}
void GCodeViewer::set_solid_model_while_dragging(bool value)
{
const bool was_enabled = m_solid_model_while_dragging;
m_solid_model_while_dragging = value;
m_viewer.set_reduced_detail_enabled(value);
reload_shells_if_solid_model_changed(was_enabled);
}
void GCodeViewer::read_solid_model_preference()
{
m_solid_model_while_dragging = get_app_config()->get_bool("preview_solid_model_while_dragging");
m_viewer.set_reduced_detail_enabled(m_solid_model_while_dragging);
}
void GCodeViewer::reload_shells_if_solid_model_changed(bool was_enabled)
{
if (was_enabled == m_solid_model_while_dragging || m_shells.print_id == -1)
return;
// only the prime tower comes and goes with the mode: a full reload would drop the shells
// whenever the print has moved on since they were loaded, leaving the solid model nothing to draw
if (wxGetApp().plater() == nullptr)
return;
// the shells are loaded from the current plate's print, which is not the plater's own
const Print& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
if (static_cast<int>(print.id().id) != m_shells.print_id)
return;
update_shell_wipe_tower(print, m_gl_data_initialized);
}
void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)
{
m_viewer.set_layers_view_range(static_cast<uint32_t>(layers_z_range[0]), static_cast<uint32_t>(layers_z_range[1]));
@@ -2249,7 +2292,11 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
// the shells can load before the first G-code does, so the preferences are read here as well
read_solid_model_preference();
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
// the prime tower comes and goes on its own, without reloading the objects
update_shell_wipe_tower(print, initialized);
//BBS: update force previewing logic
if (force_previewing)
m_shells.previewing = force_previewing;
@@ -2358,10 +2405,45 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
m_shells.print_id = print.id().id;
m_shells.print_modify_count = print.get_modified_count();
m_shells.previewing = true;
update_shell_wipe_tower(print, initialized);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%")
% m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size();
}
// The prime tower as it was sliced, so that the solid model shows what the print shows. It keeps its
// opaque colour, so it never appears among the translucent shells, and stays out of their bounding box.
void GCodeViewer::update_shell_wipe_tower(const Print& print, bool initialized)
{
const bool with_wipe_tower = m_solid_model_while_dragging && print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data;
if (with_wipe_tower == m_shells.with_wipe_tower)
return;
m_shells.with_wipe_tower = with_wipe_tower;
GLVolumePtrs& volumes = m_shells.volumes.volumes;
if (!with_wipe_tower) {
volumes.erase(std::remove_if(volumes.begin(), volumes.end(), [](GLVolume* volume) {
if (!volume->is_wipe_tower)
return false;
delete volume;
return true;
}), volumes.end());
return;
}
const PrintConfig& config = print.config();
const int plate_idx = print.get_plate_index();
const Vec3d plate_origin = print.get_plate_origin();
const float x = static_cast<float>(config.wipe_tower_x.get_at(plate_idx) + plate_origin.x());
const float y = static_cast<float>(config.wipe_tower_y.get_at(plate_idx) + plate_origin.y());
const size_t first_new = volumes.size();
m_shells.volumes.load_real_wipe_tower_preview(1000 + plate_idx, x, y, print.wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
print.wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh, true,
static_cast<float>(config.wipe_tower_rotation_angle), false, initialized);
for (size_t i = first_new; i < volumes.size(); ++i) {
volumes[i]->zoom_to_volumes = false;
volumes[i]->force_native_color = true;
volumes[i]->set_render_color();
}
}
void GCodeViewer::render_toolpaths()
{
const Camera& camera = wxGetApp().plater()->get_camera();
@@ -2562,6 +2644,50 @@ void GCodeViewer::render_shells(int canvas_width, int canvas_height)
glsafe(::glDepthMask(GL_TRUE));
}
// The sliced objects and the prime tower drawn opaque, in their filament colours, cut to the
// visible layer range by the shader's z range. The toolpaths of the range's bottom and top layers
// are drawn afterwards and cap the cut.
void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
{
if (m_shells.volumes.empty())
return;
// gouraud_light has no z range, so it could not cut the model
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
if (shader == nullptr)
return;
const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
const float z_top = m_viewer.get_layer_z(layers[1]) - m_z_offset + 0.001f;
const float z_bottom = (layers[0] > 0) ? m_viewer.get_layer_z(layers[0] - 1) - m_z_offset - 0.001f : -FLT_MAX;
std::vector<float> alphas;
alphas.reserve(m_shells.volumes.volumes.size());
for (GLVolume* volume : m_shells.volumes.volumes) {
alphas.push_back(volume->color.a());
volume->color.a(1.0f);
volume->set_render_color();
}
m_shells.volumes.set_z_range(z_bottom, z_top);
// gouraud also clips by this plane, which nothing else sets on the shells
m_shells.volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data());
shader->start_using();
// the 3D view leaves its shadow settings on the shared program
shader->set_uniform("shadow_intensity", 0.0f);
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("z_far", camera.get_far_z());
shader->set_uniform("z_near", camera.get_near_z());
m_shells.volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height});
shader->stop_using();
m_shells.volumes.set_z_range(-FLT_MAX, FLT_MAX);
size_t k = 0;
for (GLVolume* volume : m_shells.volumes.volumes) {
volume->color.a(alphas[k++]);
volume->set_render_color();
}
}
//BBS
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
if (!show)
@@ -3426,6 +3552,12 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
std::vector<std::pair<ColorRGBA, std::pair<double, double>>> ret;
ret.reserve(custom_gcode_per_print_z.size());
// Loop invariant, but built lazily: this lambda runs once per extruder on every frame
// and most prints reach neither colour change below, so fetching it up front would cost
// more than the per-item fetch it replaces.
std::vector<float> zs;
bool zs_built = false;
for (const auto& item : custom_gcode_per_print_z) {
if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
continue;
@@ -3433,7 +3565,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
if (item.type != ColorChange)
continue;
const std::vector<float> zs = m_viewer.get_layers_zs();
if (!zs_built) {
zs = m_viewer.get_layers_zs();
zs_built = true;
}
auto lower_b = std::lower_bound(zs.begin(), zs.end(),
static_cast<float>(item.print_z - epsilon()));
if (lower_b == zs.end())
@@ -4582,6 +4717,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
// ORCA: Get layer Zs as doubles
std::vector<double> layer_zs = get_layers_zs();
// loop invariant, same reason as the layer Zs above
const std::vector<float> layer_times = m_viewer.get_layers_estimated_times();
for (Slic3r::CustomGCode::Item custom_gcode : custom_gcode_per_print_z) {
ImGui::Dummy({window_padding, window_padding});
@@ -4601,7 +4738,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
imgui.text(buf);
ImGui::SameLine(max_len * 1.5);
std::vector<float> layer_times = m_viewer.get_layers_estimated_times();
float custom_gcode_time = 0;
if (layer > 0)
{
@@ -4650,7 +4786,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
std::string print_str = _u8L("Model printing time");
std::string total_str = _u8L("Total time");
float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x;
if (m_viewer.get_layers_estimated_times().empty())
if (m_viewer.get_layers_count() == 0)
max_len += ImGui::CalcTextSize(total_str.c_str()).x;
else {
if (m_viewer.get_view_type() == libvgcode::EViewType::FeatureType)
+15 -1
View File
@@ -174,6 +174,8 @@ public:
int print_id{-1};
int print_modify_count{-1};
bool previewing{false};
// the prime tower was loaded with the objects, for the solid model
bool with_wipe_tower{false};
};
//BBS
ConflictResultOpt m_conflict_result;
@@ -233,6 +235,13 @@ private:
bool m_legend_visible{ true };
bool m_legend_enabled{ true };
// while dragging, the sliced objects are drawn as solid shapes instead of toolpaths
bool m_solid_model_while_dragging{ false };
void read_solid_model_preference();
void render_solid_model(int canvas_width, int canvas_height);
// the prime tower is only among the shells for the solid model, so it is added or removed when that changes
void reload_shells_if_solid_model_changed(bool was_enabled);
void update_shell_wipe_tower(const Print& print, bool initialized);
float m_legend_height;
PrintEstimatedStatistics m_print_statistics;
@@ -283,7 +292,7 @@ public:
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
bool has_data() const { return !m_viewer.get_extrusion_roles().empty(); }
bool has_data() const { return m_viewer.get_extrusion_roles_count() != 0; }
bool can_export_toolpaths() const;
std::vector<int> get_plater_extruder();
@@ -345,6 +354,11 @@ public:
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
// while the user drags the camera or a slider, draw the solid model, if the preference asks for it
void set_interacting(bool interacting);
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
void set_solid_model_while_dragging(bool value);
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
+58 -28
View File
@@ -43,7 +43,6 @@
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include <slic3r/GUI/GUI_Utils.hpp>
@@ -2055,6 +2054,11 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
const bool overlay_tick = m_fps_overlay_tick;
m_fps_overlay_tick = false;
// Whether the preview draws the solid model is decided before the cached scene is consulted,
// since switching changes what the scene pass draws.
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data() && _update_preview_interaction())
scene_dirty = true;
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
// below once it is known whether the frame differs from the one on screen.
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
@@ -3234,9 +3238,16 @@ void GLCanvas3D::bind_event_handlers()
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
// a drag cut short never sees its button release, which would leave the solid model drawn
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
render();
evt.Skip();
});
m_canvas->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
});
m_event_handlers_bound = true;
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
@@ -3309,6 +3320,17 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_overlay_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
// the render timer only wakes the idle loop; the frame that puts the preview's toolpaths back
// after a wheel burst has to be asked for here, once the settle time is really up
if (m_preview_settle_pending) {
const auto now = std::chrono::steady_clock::now();
if (now >= m_preview_interaction_until) {
m_preview_settle_pending = false;
m_dirty = true;
}
else // the timer fired early
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
}
if (!m_dirty && !m_overlay_dirty)
return;
@@ -3839,6 +3861,10 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return;
}
// only a wheel the panels did not take moves the camera
if (m_canvas_type == CanvasPreview)
note_preview_interaction();
#ifdef __WXMSW__
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
// if the event is not allowed to be passed further.
@@ -3939,6 +3965,11 @@ void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
wxWakeUpIdle();
}
void GLCanvas3D::note_preview_interaction()
{
m_preview_interaction_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(150);
}
void GLCanvas3D::schedule_extra_frame(int milliseconds)
{
// Schedule idle event right now
@@ -5027,16 +5058,8 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
//BBS: nofity object list to update
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
if (object_moved) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "moved";
if (done.size() == 1)
ctx.name = m_model->objects[done.begin()->first]->name;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
if (object_moved)
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
}
// BBS: support wipe-tower for multi-plates
for (int plate_id = 0; plate_id < wipe_tower_origins.size(); plate_id++) {
@@ -5157,16 +5180,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
//BBS: nofity object list to update
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
if (!done.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "rotated";
if (done.size() == 1)
ctx.name = m_model->objects[done.begin()->first]->name;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
}
m_dirty = true;
}
@@ -5257,16 +5272,8 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
//BBS: notify object info update
wxGetApp().plater()->show_object_info();
if (!done.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "scaled";
if (done.size() == 1)
ctx.name = m_model->objects[done.begin()->first]->name;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
}
m_dirty = true;
}
@@ -5579,6 +5586,9 @@ void GLCanvas3D::mouse_up_cleanup()
m_mouse.ignore_left_up = false;
m_mouse.ignore_right_up = false;
m_dirty = true;
// the frame that follows a release puts the preview's toolpaths back, and on some platforms
// no idle event follows a button release until the next input
wxWakeUpIdle();
if (m_canvas->HasCapture())
m_canvas->ReleaseMouse();
@@ -8763,6 +8773,26 @@ void GLCanvas3D::_render_wireframe_overlay()
shader->stop_using();
}
// The solid model is drawn while the camera, the navigator or either slider is dragged. A wheel
// step has no duration, so it holds the solid model for a settle time instead, and the frame that
// restores the toolpaths is scheduled for when that time runs out. Returns whether what the scene
// pass draws changed, since a frame that reuses the cached scene would hide the change.
bool GLCanvas3D::_update_preview_interaction()
{
IMSlider* layers_slider = m_gcode_viewer.get_layers_slider();
IMSlider* moves_slider = m_gcode_viewer.get_moves_slider();
const auto now = std::chrono::steady_clock::now();
const bool settling = now < m_preview_interaction_until;
const bool dragging = m_mouse.dragging || m_navigator_dragging || layers_slider->is_dragging() || moves_slider->is_dragging();
const bool was_reduced = m_gcode_viewer.is_reduced_detail();
m_gcode_viewer.set_interacting(dragging || settling);
if (settling && !dragging && m_gcode_viewer.is_reduced_detail()) {
m_preview_settle_pending = true;
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
}
return m_gcode_viewer.is_reduced_detail() != was_reduced;
}
//BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{
+9
View File
@@ -649,6 +649,10 @@ private:
ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false };
// until when a wheel step keeps the preview's solid model drawn
std::chrono::time_point<std::chrono::steady_clock> m_preview_interaction_until{};
// whether the frame that restores the toolpaths once that time is up is still owed
bool m_preview_settle_pending{ false };
//BBS:add plate related logic
mutable std::vector<int> m_hover_volume_idxs;
@@ -1215,6 +1219,8 @@ public:
void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
void request_extra_frame() { m_extra_frame_requested = true; }
// a wheel step is over before the next frame, so it holds the preview's solid model for a settle time
void note_preview_interaction();
void schedule_extra_frame(int milliseconds);
@@ -1361,6 +1367,9 @@ private:
//BBS: GUI refactor: add canvas size as parameters
void _render_gcode(int canvas_width, int canvas_height);
void _render_gcode_overlay(int canvas_width, int canvas_height);
// decides whether the preview draws its solid model this frame and returns whether what the scene
// pass draws changed; runs before the cached scene is consulted
bool _update_preview_interaction();
//BBS: render a plane for assemble
void _render_plane() const;
void _render_selection();
-22
View File
@@ -2182,11 +2182,6 @@ void GUI_App::init_networking_callbacks()
obj->command_get_access_code();
if (m_agent)
m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
LifecycleEventContext ctx;
ctx.name = obj->get_dev_id();
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceConnected, ctx);
}
});
});
@@ -2225,11 +2220,6 @@ void GUI_App::init_networking_callbacks()
obj->command_get_version();
event.SetInt(0);
event.SetString(obj->get_dev_id());
LifecycleEventContext ctx;
ctx.name = obj->get_dev_id();
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceConnected, ctx);
} else if (state == ConnectStatus::ConnectStatusFailed) {
// Orca: only update status if same device id
if (m_device_manager->selected_machine != dev_id) return;
@@ -2245,22 +2235,10 @@ void GUI_App::init_networking_callbacks()
wxGetApp().show_dialog(text);
}
event.SetInt(-1);
{
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
}
} else if (state == ConnectStatus::ConnectStatusLost) {
m_device_manager->set_selected_machine("");
event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = lost";
LifecycleEventContext ctx;
ctx.name = dev_id;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::DeviceDisconnected, ctx);
} else {
event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = " << state;
+1 -39
View File
@@ -1,6 +1,5 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_Factories.hpp"
//#include "GUI_ObjectLayers.hpp"
@@ -11,7 +10,6 @@
#include "BitmapComboBox.hpp"
#include "MainFrame.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "OptionsGroup.hpp"
#include "Tab.hpp"
@@ -1161,39 +1159,17 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
if (m_objects_model->GetItemType(item) & itObject) {
std::string name = m_objects_model->GetName(item).ToUTF8().data();
if (obj->name != name) {
const std::string previous_name = obj->name;
obj->name = name;
// if object has just one volume, rename this volume too
if (obj->volumes.size() == 1)
obj->volumes[0]->name = obj->name;
Slic3r::save_object_mesh(*obj);
LifecycleEventContext ctx;
ctx.name = name;
ctx.previous_name = previous_name;
ctx.id = std::to_string(obj->id().id);
ctx.index = obj_idx;
ctx.source = "object";
fire_lifecycle_event(LifecycleEvent::ObjectRenamed, ctx);
}
return;
}
if (volume_id < 0) return;
std::string name = m_objects_model->GetName(item).ToUTF8().data();
if (obj->volumes[volume_id]->name == name)
return;
const std::string previous_name = obj->volumes[volume_id]->name;
obj->volumes[volume_id]->name = name;
LifecycleEventContext ctx;
ctx.name = name;
ctx.previous_name = previous_name;
ctx.id = std::to_string(obj->volumes[volume_id]->id().id);
ctx.index = volume_id;
ctx.source = "volume";
fire_lifecycle_event(LifecycleEvent::ObjectRenamed, ctx);
obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
}
void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const
@@ -3543,14 +3519,7 @@ void ObjectList::delete_all_connectors_for_object(int obj_idx)
obj->delete_connectors();
if (obj->volumes.empty() || !obj->has_solid_mesh()) {
const std::string deleted_obj_name = obj->name;
model.delete_object(idx);
{
Slic3r::LifecycleEventContext ctx;
ctx.name = deleted_obj_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx);
}
m_objects_model->Delete(m_objects_model->GetItemById(idx));
continue;
}
@@ -4119,13 +4088,6 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed,
const auto item = m_objects_model->AddObject(model_object, warning_bitmap, model_object->is_cut());
Expand(m_objects_model->GetParent(item));
{
Slic3r::LifecycleEventContext ctx;
ctx.name = model_object->name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectAdded, ctx);
}
if (!do_info_update)
return;
+7
View File
@@ -483,6 +483,11 @@ void IMSlider::draw_background_and_groove(const ImRect& bg_rect, const ImRect& g
ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth());
}
bool IMSlider::is_dragging() const
{
return GImGui != nullptr && m_imgui_id != 0 && GImGui->ActiveId == m_imgui_id && GImGui->IO.MouseDown[0];
}
bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
@@ -491,6 +496,7 @@ bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int
ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size);
@@ -883,6 +889,7 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size);
+5
View File
@@ -118,6 +118,9 @@ public:
//BBS update scroll value changed
bool is_dirty() { return m_dirty; }
// whether the mouse is holding this slider's handle, read from ImGui's active id rather than
// from the dirty flag, which is raised and consumed inside a single frame
bool is_dragging() const;
void set_as_dirty(bool dirty = true) { m_dirty = dirty; }
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
void reset_post_tick_event(bool val = false) {
@@ -182,6 +185,8 @@ private:
int m_higher_value;
int m_one_layer_value; // ORCA
bool m_dirty = false;
// the ImGui id of the slider widget, as of its last render
unsigned int m_imgui_id = 0;
bool m_render_as_disabled{ false };
-8
View File
@@ -13,7 +13,6 @@
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libnest2d/common.hpp"
@@ -699,13 +698,6 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y)) % ap.name;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "arranged";
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
}
m_plater->update();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
-8
View File
@@ -6,7 +6,6 @@
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libnest2d/common.hpp"
#include <numeric>
@@ -348,13 +347,6 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
m_plater->arrange();
}
m_plater->update();
{
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "arranged";
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
}
}
m_plater->mark_plate_toolbar_image_dirty();
-7
View File
@@ -5,7 +5,6 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -255,12 +254,6 @@ void OrientJob::finalize(bool canceled, std::exception_ptr &eptr)
mesh.apply();
}
if (!m_selected.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
ctx.msg = "auto_oriented";
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx);
}
m_plater->update();
-22
View File
@@ -1,5 +1,4 @@
#include "PrintJob.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -134,13 +133,6 @@ wxString PrintJob::get_http_error_msg(unsigned int status, std::string body)
void PrintJob::process(Ctl &ctl)
{
LifecycleEventContext start_ctx;
start_ctx.name = m_project_name;
start_ctx.device_id = m_dev_id;
start_ctx.source = "print_job";
fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx);
m_lifecycle_started = true;
/* display info */
std::string msg;
int curr_percent = 10;
@@ -694,7 +686,6 @@ void PrintJob::process(Ctl &ctl)
}
wxQueueEvent(m_plater, evt);
m_job_finished = true;
m_lifecycle_success = true;
}
}
@@ -707,19 +698,6 @@ void PrintJob::finalize(bool canceled, std::exception_ptr &eptr) {
eptr = std::current_exception();
}
if (m_lifecycle_started && !m_lifecycle_finished) {
LifecycleEventContext finish_ctx;
finish_ctx.name = m_project_name;
finish_ctx.device_id = m_dev_id;
finish_ctx.source = "print_job";
finish_ctx.code = canceled ? LifecycleEvtCode::Warn :
(eptr || !m_lifecycle_success ? LifecycleEvtCode::Error : LifecycleEvtCode::Ok);
finish_ctx.msg = canceled ? "cancelled" : (eptr ? "exception" :
(m_lifecycle_success ? "" : "failed"));
fire_lifecycle_event(LifecycleEvent::PrintJobFinished, finish_ctx);
m_lifecycle_finished = true;
}
if (canceled || eptr)
return;
}
-3
View File
@@ -43,9 +43,6 @@ class PrintJob : public Job
std::function<void()> m_success_fun{nullptr};
std::string m_dev_id;
bool m_job_finished{ false };
bool m_lifecycle_started{ false };
bool m_lifecycle_finished{ false };
bool m_lifecycle_success{ false };
int m_print_job_completed_id = 0;
wxString m_completed_evt_data;
std::function<void()> m_enter_ip_address_fun_fail{ nullptr };
-21
View File
@@ -1,5 +1,4 @@
#include "SendJob.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "libslic3r/MTUtils.hpp"
@@ -149,13 +148,6 @@ void SendJob::process(Ctl &ctl)
}
}
LifecycleEventContext start_ctx;
start_ctx.name = m_project_name;
start_ctx.device_id = m_dev_id;
start_ctx.source = "send_job";
fire_lifecycle_event(LifecycleEvent::SendJobStarted, start_ctx);
m_lifecycle_started = true;
int total_plate_num = m_plater->get_partplate_list().get_plate_count();
PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx);
@@ -432,19 +424,6 @@ void SendJob::finalize(bool canceled, std::exception_ptr &eptr)
eptr = std::current_exception();
}
if (m_lifecycle_started && !m_lifecycle_finished) {
LifecycleEventContext finish_ctx;
finish_ctx.name = m_project_name;
finish_ctx.device_id = m_dev_id;
finish_ctx.source = "send_job";
finish_ctx.code = canceled ? LifecycleEvtCode::Warn :
(eptr ? LifecycleEvtCode::Error : (m_job_finished ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error));
finish_ctx.msg = canceled ? "cancelled" : (eptr ? "exception" :
(m_job_finished ? "" : "failed"));
fire_lifecycle_event(LifecycleEvent::SendJobFinished, finish_ctx);
m_lifecycle_finished = true;
}
if (canceled || eptr)
return;
}
-2
View File
@@ -22,8 +22,6 @@ class SendJob : public Job
PrintPrepareData job_data;
std::string m_dev_id;
bool m_job_finished{ false };
bool m_lifecycle_started{ false };
bool m_lifecycle_finished{ false };
int m_print_job_completed_id = 0;
bool m_is_check_mode{false};
bool m_check_and_continue{false};
+1 -34
View File
@@ -29,7 +29,6 @@
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
@@ -2627,20 +2626,11 @@ void PartPlate::set_plate_name(const std::string& name)
if (boost::equals(m_name, name))
return;
const std::string previous_name = m_name;
m_name = name;
if (m_print != nullptr)
m_print->set_plate_name(name);
invalidate_plate_name_texture();
if (m_plater != nullptr && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = name;
ctx.previous_name = previous_name;
ctx.index = m_plate_index;
fire_lifecycle_event(LifecycleEvent::PlateRenamed, ctx);
}
}
//get the print's object, result and index
@@ -4751,16 +4741,9 @@ int PartPlateList::create_plate(bool adjust_position)
if (m_plater) {
// In GUI mode
wxGetApp().obj_list()->on_plate_added(plate);
wxGetApp().obj_list()->on_plate_added(plate);
}
if (m_plater != nullptr && m_intialized && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = plate->get_plate_name();
ctx.index = new_index;
fire_lifecycle_event(LifecycleEvent::PlateCreated, ctx);
}
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":created a new plate %1%") % new_index;
return new_index;
}
@@ -4859,7 +4842,6 @@ int PartPlateList::delete_plate(int index)
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":plate %1%, has an invalid index %2%") % index % plate->get_index();
return -1;
}
const std::string plate_name = plate->get_plate_name();
if (m_plater) {
// In GUI mode
@@ -4942,13 +4924,6 @@ int PartPlateList::delete_plate(int index)
delete plate;
if (m_plater != nullptr && m_intialized && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = plate_name;
ctx.index = index;
fire_lifecycle_event(LifecycleEvent::PlateDeleted, ctx);
}
// FIX: context of BackgroundSliceProcess and gcode preview need to be updated before ObjectList::reload_all_plates().
#if 0
if (m_plater != nullptr) {
@@ -5057,7 +5032,6 @@ int PartPlateList::select_plate(int index)
if (m_plate_list.empty() || index >= m_plate_list.size()) {
return -1;
}
const int previous_index = m_current_plate;
// BBS: erase unnecessary snapshot
if (get_curr_plate_index() != index && m_intialized) {
@@ -5084,13 +5058,6 @@ int PartPlateList::select_plate(int index)
//wxQueueEvent(m_plater, new SimpleEvent(EVT_GLCANVAS_PLATE_SELECT));
}
if (previous_index != index && m_intialized && m_plater != nullptr && !m_plater->is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = m_plate_list[index]->get_plate_name();
ctx.index = index;
fire_lifecycle_event(LifecycleEvent::PlateSelected, ctx);
}
return 0;
}
+13 -105
View File
@@ -57,7 +57,6 @@
#include <wx/aui/aui.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/DRC.hpp"
#include "libslic3r/Format/STEP.hpp"
@@ -10145,14 +10144,7 @@ void Plater::priv::remove(size_t obj_idx)
view3D->enable_layers_editing(false);
m_worker.cancel_all();
std::string obj_name = (obj_idx < model.objects.size()) ? model.objects[obj_idx]->name : std::to_string(obj_idx);
model.delete_object(obj_idx);
{
Slic3r::LifecycleEventContext ctx;
ctx.name = obj_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx);
}
//BBS: notify partplate the instance removed
partplate_list.notify_instance_removed(obj_idx, -1);
update();
@@ -10185,14 +10177,7 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immedia
if (obj->is_cut())
sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx);
std::string obj_name = obj->name;
model.delete_object(obj_idx);
{
Slic3r::LifecycleEventContext ctx;
ctx.name = obj_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx);
}
//BBS: notify partplate the instance removed
partplate_list.notify_instance_removed(obj_idx, -1);
@@ -10238,22 +10223,10 @@ void Plater::priv::delete_all_objects_from_model()
void Plater::priv::reset(bool apply_presets_change)
{
// TakeSnapshot below and load_current_presets() further down each re-evaluate the
// aggregate dirty flag against a baseline that hasn't been reset yet, so they can toggle
// is_dirty() back and forth several times before it settles; coalesce those into one event.
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(dirty_state);
Plater::TakeSnapshot snapshot(q, _u8L("Reset Project"), UndoRedo::SnapshotType::ProjectSeparator);
clear_warnings();
const std::string closed_project_name = into_u8(get_project_filename());
if (!closed_project_name.empty() || !model.objects.empty()) {
Slic3r::LifecycleEventContext ctx;
ctx.name = closed_project_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectClosed, ctx);
}
// A new project must not inherit the previous project's published selection (Feature A/B).
m_has_pending_published = false;
m_pending_published_keys.clear();
@@ -12681,15 +12654,11 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
notification_manager->set_slicing_progress_export_possible();
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
const std::string lifecycle_job_name = this->background_process.fff_print() ?
this->background_process.fff_print()->output_filename() : std::string();
this->background_process.reset_export();
// This bool stops showing export finished notification even when process_completed_with_error is false
bool has_error = false;
std::string lifecycle_error_msg;
if (evt.error()) {
auto message = evt.format_error_message();
lifecycle_error_msg = message.first;
if (evt.critical_error()) {
if (q->m_tracking_popup_menu) {
// We don't want to pop-up a message box when tracking a pop-up menu.
@@ -12727,14 +12696,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
is_finished = true;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = lifecycle_job_name;
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
}
//BBS: set the current plater's slice result to valid
if (!this->background_process.empty())
this->background_process.get_current_plate()->update_slice_result_valid_state(evt.success());
@@ -15268,33 +15229,21 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
//get_partplate_list().reinit();
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
//p->preview->update_gcode_result(p->partplate_list.get_current_slice_result());
if (!silent) {
Slic3r::LifecycleEventContext ctx;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::NewProject, ctx);
}
{
// Same rationale as in Plater::priv::reset(): the whole reset + preset-reload +
// baseline-reset sequence below settles into its final dirty state only once it
// completes, so hold notifications until then to avoid firing on transient flips.
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(p->dirty_state);
reset(transfer_preset_changes);
reset_project_dirty_after_save();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
update_project_dirty_from_presets();
reset(transfer_preset_changes);
reset_project_dirty_after_save();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
update_project_dirty_from_presets();
//reset project
p->project.reset();
//set project name
if (project_name.empty())
p->set_project_name(_L("Untitled"));
else
p->set_project_name(project_name);
//reset project
p->project.reset();
//set project name
if (project_name.empty())
p->set_project_name(_L("Untitled"));
else
p->set_project_name(project_name);
Plater::TakeSnapshot snapshot(this, "New Project", UndoRedo::SnapshotType::ProjectSeparator);
}
Plater::TakeSnapshot snapshot(this, "New Project", UndoRedo::SnapshotType::ProjectSeparator);
Model m;
model().load_from(m); // new id avoid same path name
@@ -15412,13 +15361,6 @@ void Plater::load_project(wxString const& filename2,
p->set_project_name(_L("Untitled"));
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = into_u8(load_restore ? originfile : filename);
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectOpened, ctx);
}
} else {
if (using_exported_file()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " using ecported set project filename: " << filename;
@@ -15490,23 +15432,11 @@ int Plater::save_project(bool saveAs)
if (full_pathnames) {
save_strategy = save_strategy | SaveStrategy::FullPathSources;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = into_u8(filename);
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectBeforeSave, ctx);
}
if (export_3mf(into_path(filename), save_strategy) < 0) {
MessageDialog(this, _L("Failed to save the project.\nPlease check whether the folder exists online or if other programs have the project file open."),
_L("Save project"), wxOK | wxICON_WARNING).ShowModal();
return wxID_CANCEL;
}
{
Slic3r::LifecycleEventContext ctx;
ctx.name = into_u8(filename);
ctx.code = Slic3r::LifecycleEvtCode::Ok;
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectAfterSave, ctx);
}
Slic3r::remove_backup(model(), false);
@@ -20826,14 +20756,6 @@ void Plater::changed_object(ModelObject &object){
// Check outside bed
get_current_canvas3D()->requires_check_outside_state();
if (!is_loading_project()) {
LifecycleEventContext ctx;
ctx.name = object.name;
ctx.id = std::to_string(object.id().id);
ctx.source = "geometry";
fire_lifecycle_event(LifecycleEvent::ObjectChanged, ctx);
}
}
void Plater::changed_object(int obj_idx)
@@ -20870,20 +20792,6 @@ void Plater::changed_objects(const std::vector<size_t>& object_idxs)
// update print
this->p->schedule_background_process();
if (!is_loading_project()) {
for (size_t obj_idx : object_idxs) {
if (obj_idx >= p->model.objects.size() || p->model.objects[obj_idx] == nullptr)
continue;
LifecycleEventContext ctx;
ctx.name = p->model.objects[obj_idx]->name;
ctx.id = std::to_string(p->model.objects[obj_idx]->id().id);
ctx.index = static_cast<int>(obj_idx);
ctx.source = "geometry";
fire_lifecycle_event(LifecycleEvent::ObjectChanged, ctx);
}
}
}
void Plater::schedule_background_process(bool schedule/* = true*/)
+19
View File
@@ -1049,6 +1049,16 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
}
}
// ORCA: apply the preview dimming change immediately to the currently loaded preview
// apply the solid model preference immediately to the currently loaded preview
else if (param == "preview_solid_model_while_dragging") {
if (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_solid_model_while_dragging(app_config->get_bool(param));
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
}
else if (param == "preview_dim_previous_layers") {
if (m_dim_previous_layers_brightness_input)
m_dim_previous_layers_brightness_input->Enable(app_config->get_bool(param));
@@ -2019,6 +2029,15 @@ void PreferencesDialog::create_items()
//// GRAPHICS > G-code Preview
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
auto item_solid_model_while_dragging = create_item_checkbox(
_L("Only render solid model when dragging"),
_L("While dragging the camera or a preview slider, or zooming with the mouse wheel, draw the sliced objects and the prime tower as solid shapes "
"in their filament colours instead of toolpaths, so that large prints stay responsive. They are cut to the visible layer range, with its bottom "
"and top layers drawn as toolpaths. Supports are not shown. The toolpaths are restored as soon as you let go."),
"preview_solid_model_while_dragging"
);
g_sizer->Add(item_solid_model_while_dragging);
auto item_dim_previous_layers = create_item_checkbox(
_L("Dim lower layers"),
_L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),
+1 -46
View File
@@ -6,7 +6,6 @@
#include "MainFrame.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include <boost/algorithm/string/predicate.hpp>
@@ -18,23 +17,13 @@ namespace GUI {
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
{
const bool was_dirty = is_dirty();
m_plater_dirty = dirty;
notify_dirty_change(was_dirty, "undo_redo");
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::set_plater_dirty(bool is_dirty)
{
const bool was_dirty = this->is_dirty();
m_plater_dirty = is_dirty;
notify_dirty_change(was_dirty, "plater");
}
void ProjectDirtyStateManager::update_from_presets()
{
const bool was_dirty = is_dirty();
m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new
GUI_App &app = wxGetApp();
@@ -56,53 +45,18 @@ void ProjectDirtyStateManager::update_from_presets()
}
m_presets_dirty |= app.has_unsaved_preset_changes();
m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config;
notify_dirty_change(was_dirty, "presets");
app.mainframe->update_title();
}
void ProjectDirtyStateManager::reset_after_save()
{
const bool was_dirty = is_dirty();
this->reset_initial_presets();
m_plater_dirty = false;
m_presets_dirty = false;
m_project_config_dirty = false;
notify_dirty_change(was_dirty, "save");
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::notify_dirty_change(bool was_dirty, const char *source)
{
if (m_suppress_depth > 0)
return;
const bool dirty = is_dirty();
if (was_dirty == dirty)
return;
LifecycleEventContext ctx;
ctx.code = LifecycleEvtCode::Ok;
ctx.dirty = dirty;
ctx.source = source;
fire_lifecycle_event(LifecycleEvent::ProjectDirtyChanged, ctx);
}
void ProjectDirtyStateManager::begin_suppress_notifications()
{
if (m_suppress_depth == 0)
m_suppress_entry_dirty = is_dirty();
++m_suppress_depth;
}
void ProjectDirtyStateManager::end_suppress_notifications()
{
assert(m_suppress_depth > 0);
if (m_suppress_depth > 0)
--m_suppress_depth;
if (m_suppress_depth == 0)
notify_dirty_change(m_suppress_entry_dirty, "batch");
}
void ProjectDirtyStateManager::reset_initial_presets()
{
m_initial_presets.fill(std::string{});
@@ -214,3 +168,4 @@ void ProjectDirtyStateManager::render_debug_window() const
} // namespace GUI
} // namespace Slic3r
+1 -22
View File
@@ -14,42 +14,21 @@ public:
void reset_after_save();
void reset_initial_presets();
void set_plater_dirty(bool is_dirty);
void set_plater_dirty(bool is_dirty) { m_plater_dirty = is_dirty; }
bool is_dirty() const { return m_plater_dirty || m_project_config_dirty || m_presets_dirty; }
bool is_presets_dirty() const { return m_presets_dirty; }
// RAII guard coalescing dirty-state updates: while any guard is alive, ProjectDirtyChanged
// notifications are held back; when the outermost guard is destroyed, at most one
// notification fires, reflecting only the net change across the whole guarded scope.
class NotificationSuppressor
{
public:
explicit NotificationSuppressor(ProjectDirtyStateManager &owner) : m_owner(owner) { m_owner.begin_suppress_notifications(); }
~NotificationSuppressor() { m_owner.end_suppress_notifications(); }
NotificationSuppressor(const NotificationSuppressor &) = delete;
NotificationSuppressor &operator=(const NotificationSuppressor &) = delete;
private:
ProjectDirtyStateManager &m_owner;
};
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_debug_window() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
private:
void notify_dirty_change(bool was_dirty, const char *source);
void begin_suppress_notifications();
void end_suppress_notifications();
// Does the Undo / Redo stack indicate the project is dirty?
bool m_plater_dirty { false };
// Do the presets indicate the project is dirty?
bool m_presets_dirty { false };
// Is the project config dirty?
bool m_project_config_dirty { false };
// NotificationSuppressor nesting depth and the dirty state observed when the outermost guard began.
int m_suppress_depth { 0 };
bool m_suppress_entry_dirty { false };
// Keeps track of preset names selected at the time of last project save.
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
DynamicPrintConfig m_initial_project_config;
-15
View File
@@ -38,7 +38,6 @@
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/plugin/PluginConfig.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
@@ -6965,20 +6964,6 @@ bool Tab::select_preset(
}
load_current_preset();
{
Slic3r::LifecycleEventContext ctx;
ctx.name = preset_name;
ctx.code = Slic3r::LifecycleEvtCode::Ok;
switch (m_type) {
case Preset::TYPE_PRINT: ctx.msg = "print"; break;
case Preset::TYPE_SLA_PRINT: ctx.msg = "sla_print"; break;
case Preset::TYPE_FILAMENT: ctx.msg = "filament"; break;
case Preset::TYPE_SLA_MATERIAL: ctx.msg = "sla_material"; break;
case Preset::TYPE_PRINTER: ctx.msg = "printer"; break;
default: break;
}
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::PresetSelected, ctx);
}
if (delete_third_printer) {
wxGetApp().CallAfter([filament_presets, process_presets]() {
-18
View File
@@ -1,7 +1,6 @@
#include "TaskManager.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "nlohmann/json.hpp"
#include "MainFrame.hpp"
#include "GUI_App.hpp"
@@ -211,13 +210,6 @@ int TaskManager::schedule(TaskStateInfo* task)
assert(task->state() == TaskState::TS_PENDING);
task->set_state(TaskState::TS_SENDING);
LifecycleEventContext start_ctx;
start_ctx.name = task->params().project_name;
start_ctx.device_id = task->params().dev_id;
start_ctx.job_id = std::to_string(task->task_info_id);
start_ctx.source = "task_manager";
fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx);
BOOST_LOG_TRIVIAL(trace) << "task_manager: schedule a task to dev_id = " << task->params().dev_id;
boost::thread* new_sending_thread = new boost::thread();
*new_sending_thread = Slic3r::create_thread(
@@ -245,16 +237,6 @@ int TaskManager::schedule(TaskStateInfo* task)
task->set_state(TaskState::TS_SEND_CANCELED);
}
}
LifecycleEventContext finish_ctx;
finish_ctx.name = task->params().project_name;
finish_ctx.device_id = task->params().dev_id;
finish_ctx.job_id = std::to_string(task->task_info_id);
finish_ctx.source = "task_manager";
finish_ctx.code = result == 0 ? LifecycleEvtCode::Ok :
(task->is_canceled() ? LifecycleEvtCode::Warn : LifecycleEvtCode::Error);
finish_ctx.msg = result == 0 ? "" : (task->is_canceled() ? "cancelled" : "failed");
fire_lifecycle_event(LifecycleEvent::PrintJobFinished, finish_ctx);
/* remove from sending task list */
m_scedule_mutex.lock();
-18
View File
@@ -24,7 +24,6 @@
#include "CrealityPrint.hpp"
#include "../GUI/PrintHostDialogs.hpp"
#include "../GUI/MainFrame.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "Obico.hpp"
#include "Flashforge.hpp"
#include "SimplyPrint.hpp"
@@ -361,29 +360,12 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
emit_progress(0); // Indicate the upload is starting
// Captured before upload_data is moved into upload() below.
const std::string upload_filename = the_job.upload_data.source_path.filename().string();
{
LifecycleEventContext ctx;
ctx.name = upload_filename;
ctx.code = LifecycleEvtCode::Ok;
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
}
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) { this->error_fn(std::move(error)); },
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
);
{
LifecycleEventContext ctx;
ctx.name = upload_filename;
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
}
if (success) {
emit_progress(100);
if (the_job.switch_to_device_tab) {
-13
View File
@@ -7,7 +7,6 @@
#include "libslic3r/Config.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/LifecycleEvents.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r_version.h"
@@ -47,16 +46,6 @@ void install_capability_resolver()
});
}
// Global libslic3r-side seam (Slic3r::fire_lifecycle_event, in libslic3r/LifecycleEvents.hpp):
// broadcasts to every loaded, enabled capability regardless of type, unlike the SlicingPipeline
// hook below which only targets picker-selected SlicingPipeline capabilities.
void install_lifecycle_event_hook()
{
set_lifecycle_hook_fn([](LifecycleEvent event, const LifecycleEventContext& ctx) {
PluginManager::instance().dispatch_lifecycle_event(event, ctx);
});
}
// Print::process() fires this hook at each pipeline seam on the slicing worker
// thread; here we run the picker-selected SlicingPipeline capabilities. Per
// capability we acquire the GIL, honor cancellation, and convert a plugin
@@ -133,14 +122,12 @@ void install()
{
install_capability_resolver();
install_slicing_pipeline_hook();
install_lifecycle_event_hook();
}
void uninstall()
{
ConfigBase::set_resolve_capability_fn(nullptr);
Print::set_slicing_pipeline_hook_fn(nullptr);
set_lifecycle_hook_fn(nullptr);
}
} // namespace Slic3r::plugin_hooks
-21
View File
@@ -1,6 +1,5 @@
#include "PluginManager.hpp"
#include <exception>
#include <libslic3r/Utils.hpp>
#include <memory>
#include <pybind11/embed.h>
@@ -2090,24 +2089,4 @@ ExecutionResult PluginManager::run_script_capability(const std::string& plugin_k
return result;
}
void PluginManager::dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx) {
for (const auto& cap : get_plugin_capabilities()) {
if (!cap || !cap->is_enabled()) continue;
try {
cap->on_lifecycle_event(evt, ctx);
} catch (const std::exception& ex) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin '" << cap->audit_plugin_key() << "/" << cap->name()
<< "' on_lifecycle_event(" << lifecycle_event_to_string(evt) << ") threw: " << ex.what()
<< " [ctx name='" << ctx.name << "', code=" << lifecycle_evt_code_to_string(ctx.code)
<< ", msg='" << ctx.msg << "']";
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin '" << cap->audit_plugin_key() << "/" << cap->name()
<< "' on_lifecycle_event(" << lifecycle_event_to_string(evt)
<< ") threw a non-standard exception"
<< " [ctx name='" << ctx.name << "', code=" << lifecycle_evt_code_to_string(ctx.code)
<< ", msg='" << ctx.msg << "']";
}
}
}
} // namespace Slic3r
+1 -3
View File
@@ -8,7 +8,6 @@
#include <condition_variable>
#include <functional>
#include <libslic3r/Config.hpp>
#include <libslic3r/LifecycleEvents.hpp>
#include <map>
#include <memory>
#include <mutex>
@@ -21,6 +20,7 @@
#include <pybind11/embed.h>
#include "CloudPluginService.hpp"
#include "PluginFsUtils.hpp"
#include "PluginDescriptor.hpp"
#include "PluginLoader.hpp"
#include "PluginConfig.hpp"
@@ -210,8 +210,6 @@ public:
ExecutionResult run_script_capability(const std::string& plugin_key, const std::string& capability_name, std::string& error);
void dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx);
private:
PluginManager() = default;
PluginManager(const PluginManager&) = delete;
-5
View File
@@ -132,11 +132,6 @@ public:
{
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
}
void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) override
{
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_lifecycle_event, event, ctx);
}
};
class PyPluginInterfaceTrampoline : public PyPluginCommonTrampoline<PluginCapabilityInterface>
-59
View File
@@ -377,62 +377,6 @@ void bind_python_api(pybind11::module_& m)
.value("FatalError", PluginResult::FatalError)
.export_values();
py::enum_<LifecycleEvent>(m, "LifecycleEvent", "Application lifecycle moment passed to on_lifecycle_event")
.value("NewProject", LifecycleEvent::NewProject)
.value("ProjectOpened", LifecycleEvent::ProjectOpened)
.value("ProjectBeforeSave", LifecycleEvent::ProjectBeforeSave)
.value("ProjectAfterSave", LifecycleEvent::ProjectAfterSave)
.value("ProjectClosed", LifecycleEvent::ProjectClosed)
.value("ProjectDirtyChanged", LifecycleEvent::ProjectDirtyChanged)
.value("SliceStarted", LifecycleEvent::SliceStarted)
.value("SliceGeometryFinished", LifecycleEvent::SliceGeometryFinished)
.value("GCodeExportStarted", LifecycleEvent::GCodeExportStarted)
.value("GCodeExportFinished", LifecycleEvent::GCodeExportFinished)
.value("SlicingJobComplete", LifecycleEvent::SlicingJobComplete)
.value("ObjectAdded", LifecycleEvent::ObjectAdded)
.value("ObjectDeleted", LifecycleEvent::ObjectDeleted)
.value("ObjectTransformed", LifecycleEvent::ObjectTransformed)
.value("ObjectChanged", LifecycleEvent::ObjectChanged)
.value("ObjectRenamed", LifecycleEvent::ObjectRenamed)
.value("PlateCreated", LifecycleEvent::PlateCreated)
.value("PlateDeleted", LifecycleEvent::PlateDeleted)
.value("PlateSelected", LifecycleEvent::PlateSelected)
.value("PlateRenamed", LifecycleEvent::PlateRenamed)
.value("PresetSelected", LifecycleEvent::PresetSelected)
.value("PresetSaved", LifecycleEvent::PresetSaved)
.value("PrintStateChanged", LifecycleEvent::PrintStateChanged)
.value("DeviceOnlineChanged", LifecycleEvent::DeviceOnlineChanged)
.value("DeviceDiscovered", LifecycleEvent::DeviceDiscovered)
.value("DeviceSelected", LifecycleEvent::DeviceSelected)
.value("DeviceConnected", LifecycleEvent::DeviceConnected)
.value("DeviceDisconnected", LifecycleEvent::DeviceDisconnected)
.value("UploadStarted", LifecycleEvent::UploadStarted)
.value("UploadFinished", LifecycleEvent::UploadFinished)
.value("PrintJobStarted", LifecycleEvent::PrintJobStarted)
.value("PrintJobFinished", LifecycleEvent::PrintJobFinished)
.value("SendJobStarted", LifecycleEvent::SendJobStarted)
.value("SendJobFinished", LifecycleEvent::SendJobFinished)
.export_values();
py::enum_<LifecycleEvtCode>(m, "LifecycleEvtCode", "Outcome code accompanying a LifecycleEventContext")
.value("Ok", LifecycleEvtCode::Ok)
.value("Error", LifecycleEvtCode::Error)
.value("Warn", LifecycleEvtCode::Warn)
.export_values();
py::class_<LifecycleEventContext>(m, "LifecycleEventContext", "Payload accompanying a LifecycleEvent")
.def(py::init<>())
.def_readonly("name", &LifecycleEventContext::name)
.def_readonly("code", &LifecycleEventContext::code)
.def_readonly("msg", &LifecycleEventContext::msg)
.def_readonly("id", &LifecycleEventContext::id)
.def_readonly("previous_name", &LifecycleEventContext::previous_name)
.def_readonly("device_id", &LifecycleEventContext::device_id)
.def_readonly("job_id", &LifecycleEventContext::job_id)
.def_readonly("source", &LifecycleEventContext::source)
.def_readonly("index", &LifecycleEventContext::index)
.def_readonly("dirty", &LifecycleEventContext::dirty);
py::class_<PluginContext>(m, "PluginContext", "Context shared with plugin entry points")
.def(py::init<>())
.def_readwrite("orca_version", &PluginContext::orca_version);
@@ -457,9 +401,6 @@ void bind_python_api(pybind11::module_& m)
.def("get_type", &PluginCapabilityInterface::get_type)
.def("on_load", &PluginCapabilityInterface::on_load)
.def("on_unload", &PluginCapabilityInterface::on_unload)
.def("on_lifecycle_event", &PluginCapabilityInterface::on_lifecycle_event,
"Override to react to an application lifecycle moment (LifecycleEvent) and its\n"
"LifecycleEventContext payload. Available on every capability type.")
.def("has_config_ui", &PluginCapabilityInterface::has_config_ui,
"Override to return True to replace the host's default JSON editor with your own HTML\n"
"UI, returned by get_config_ui(). Every capability is configurable and appears in the\n"
@@ -10,8 +10,6 @@
#include <nlohmann/json.hpp>
#include <pybind11/embed.h>
#include <libslic3r/LifecycleEvents.hpp>
namespace Slic3r {
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
@@ -169,8 +167,6 @@ public:
virtual void on_unload() {}
virtual void on_cancelled() {}
virtual void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) { (void) event; (void) ctx; }
// ── C++-only host state, never exposed to Python. Set by the loader at materialization. ──
//
// The capability owns its own identity and enable flag: they are read once under the GIL, live