Emboss on job SVG and Text together

This commit is contained in:
Filip Sykala - NTB T15p 2023-03-14 19:56:52 +01:00
parent 07181b4f37
commit b6d84fb276
6 changed files with 695 additions and 528 deletions

View File

@ -14,7 +14,6 @@
#include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/EmbossGui.hpp"
// TODO: remove include
#include "libslic3r/SVG.hpp" // debug store
@ -108,14 +107,6 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
// Private namespace with helper function for create volume
namespace priv {
/// <summary>
/// Check if volume type is possible use for new text volume
/// </summary>
/// <param name="volume_type">Type</param>
/// <returns>True when allowed otherwise false</returns>
static bool is_valid(ModelVolumeType volume_type);
/// <summary>
/// Prepare data for emboss
/// </summary>
@ -134,7 +125,7 @@ struct TextDataBase : public DataBase
TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration);
// Create shape from text + font configuration
EmbossShape &create_shape() override;
void write(ModelVolume &volume) const override;
void write(ModelVolume &volume) const override;
// private:
// Keep pointer on Data of font (glyph shapes)
@ -143,31 +134,6 @@ struct TextDataBase : public DataBase
TextConfiguration text_configuration;
};
/// <summary>
/// Start job for add object with text into scene
/// </summary>
/// <param name="emboss_data">Define params of text</param>
/// <param name="coor">Screen coordinat, where to create new object laying on bed</param>
static void start_create_object_job(std::unique_ptr<DataBase> emboss_data, const Vec2d &coor);
/// <summary>
/// Start job for add new volume on surface of object defined by screen coor
/// </summary>
/// <param name="emboss_data">Define params of text</param>
/// <param name="volume_type">Emboss / engrave</param>
/// <param name="screen_coor">Mouse position which define position</param>
/// <param name="gl_volume">Volume to find surface for create</param>
/// <param name="raycaster">Ability to ray cast to model</param>
/// <param name="canvas">Contain already used scene RayCasters</param>
/// <returns>True when start creation, False when there is no hit surface by screen coor</returns>
static bool start_create_volume_on_surface_job(const FontProp &fp,
std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLCanvas3D &canvas);
// Loaded icons enum
// Have to match order of files in function GLGizmoEmboss::init_icons()
enum class IconType : unsigned {
@ -194,69 +160,26 @@ const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType typ
static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false);
} // namespace priv
void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos)
bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos)
{
if (!priv::is_valid(volume_type)) return;
m_style_manager.discard_style_changes();
set_default_text();
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent);
if (gl_volume == nullptr)
// object is not under mouse position soo create object on plater
return priv::start_create_object_job(std::move(emboss_data), mouse_pos);
set_default_text();
DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
Plater *plater_ptr = wxGetApp().plater();
const FontProp &fp = m_style_manager.get_style().prop;
if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, mouse_pos, *gl_volume, m_raycast_manager, m_parent))
return create_volume(volume_type);
return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Emboss, mouse_pos, fp.distance, fp.angle);
}
// Designed for create volume without information of mouse in scene
void GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
{
if (!priv::is_valid(volume_type)) return;
m_style_manager.discard_style_changes();
set_default_text();
// select position by camera position and view direction
const Selection &selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
Size s = m_parent.get_canvas_size();
Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.);
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
const ModelObjectPtrs &objects = selection.get_model()->objects;
// No selected object so create new object
if (selection.is_empty() || object_idx < 0 || static_cast<size_t>(object_idx) >= objects.size()) {
// create Object on center of screen
// when ray throw center of screen not hit bed it create object on center of bed
priv::start_create_object_job(std::move(emboss_data), screen_center);
return;
}
// create volume inside of selected object
Vec2d coor;
const Camera &camera = wxGetApp().plater()->get_camera();
const GLVolume *gl_volume = find_closest(selection, screen_center, camera, objects, &coor);
FontProp &fp = m_style_manager.get_style().prop;
if (gl_volume == nullptr) {
return priv::start_create_object_job(std::move(emboss_data), screen_center);
} else if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, coor, *gl_volume, m_raycast_manager, m_parent)){
// In centroid of convex hull is not hit with object. e.g. torid
// soo create transfomation on border of object
// unique pointer is already destoyed need to create again
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
// there is no point on surface so no use of surface will be applied
if (emboss_data->shape.use_surface)
emboss_data->shape.use_surface = false;
const ModelObject *object = get_model_object(*gl_volume, objects);
Worker &worker = wxGetApp().plater()->get_ui_job_worker();
Transform3d volume_trmat = create_volume_transformation(*gl_volume, objects, fp.size_in_mm, fp.emboss);
start_create_volume_job(worker, *object, volume_trmat, std::move(emboss_data), volume_type);
}
Plater *plater_ptr = wxGetApp().plater();
const FontProp &fp = m_style_manager.get_style().prop;
return start_create_volume_without_position(plater_ptr, std::move(emboss_data),
volume_type, m_raycast_manager, GLGizmosManager::Emboss, fp.distance, fp.angle);
}
bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event)
@ -1057,7 +980,7 @@ bool GLGizmoEmboss::process()
if (use_surface) {
// Model to cut surface from.
SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume);
SurfaceVolumeData::ModelSources sources = create_volume_sources(*m_volume);
if (sources.empty()) return false;
Transform3d text_tr = m_volume->get_matrix();
@ -3254,17 +3177,6 @@ bool GLGizmoEmboss::is_text_object(const ModelVolume *text) {
// priv namespace implementation
///////////////
bool priv::is_valid(ModelVolumeType volume_type)
{
if (volume_type == ModelVolumeType::MODEL_PART ||
volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
volume_type == ModelVolumeType::PARAMETER_MODIFIER)
return true;
BOOST_LOG_TRIVIAL(error) << "Can't create embossed text with this type: " << (int) volume_type;
return false;
}
priv::TextDataBase::TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration)
: DataBase(std::move(parent)), font_file(font_file) /* copy */, text_configuration(std::move(text_configuration))
{
@ -3351,50 +3263,6 @@ std::unique_ptr<DataBase> priv::create_emboss_data_base(const std::string &text,
return std::make_unique<TextDataBase>(std::move(base), font, std::move(tc));
}
void priv::start_create_object_job(std::unique_ptr<DataBase> emboss_data, const Vec2d &coor) {
Plater *plater = wxGetApp().plater();
const Camera &camera = plater->get_camera();
const Pointfs &bed_shape = plater->build_volume().bed_shape();
Worker &worker = plater->get_ui_job_worker();
DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape};
auto job = std::make_unique<CreateObjectJob>(std::move(data));
queue_job(worker, std::move(job));
}
bool priv::start_create_volume_on_surface_job(const FontProp &fp,
std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLCanvas3D &canvas)
{
const ModelObjectPtrs &objects = canvas.get_model()->objects;
const ModelVolume* volume = get_model_volume(gl_volume, objects);
const ModelInstance *instance = get_model_instance(gl_volume, objects);
if (volume == nullptr || instance == nullptr ||
volume->get_object() == nullptr) {
// weird situation
assert(false);
return false;
}
Plater* plater = wxGetApp().plater();
const Camera &camera = plater->get_camera();
std::optional<Transform3d> transform = create_volume_transformation_on_surface(
screen_coor, camera, *volume, *instance, raycaster, canvas, fp.distance, fp.angle);
if (!transform.has_value()) {
// When model is broken. It could appear that hit miss the object.
// So add part near by in simmilar manner as right panel do
return false;
}
// Try to cast ray into scene and find object for add volume
Worker &worker = plater->get_ui_job_worker();
return start_create_volume_job(worker, *volume->get_object(), *transform, std::move(emboss_data), volume_type);
}
ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size)
{
const Selection::IndicesList indices = selection.get_volume_idxs();

View File

@ -39,13 +39,13 @@ public:
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
/// <param name="mouse_pos">Define position of new volume</param>
void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos);
bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos);
/// <summary>
/// Create new text without given position
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
void create_volume(ModelVolumeType volume_type);
bool create_volume(ModelVolumeType volume_type);
protected:
bool on_init() override;

View File

@ -9,6 +9,7 @@
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/CameraUtils.hpp"
#include "slic3r/GUI/Jobs/EmbossJob.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "libslic3r/Point.hpp"
@ -32,11 +33,11 @@
using namespace Slic3r;
using namespace Slic3r::Emboss;
using namespace Slic3r::GUI;
using namespace Slic3r::GUI::Emboss;
namespace priv {
// Variable keep limits for variables
static const struct Limits
{
static const struct Limits{
MinMax<float> emboss{0.01f, 1e4f}; // in mm
// distance text object from surface
MinMax<float> angle{-180.f, 180.f}; // in degrees
@ -55,20 +56,34 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent)
// Private functions to create emboss volume
namespace priv {
/// <summary>
/// Check if volume type is possible use for new text volume
/// </summary>
/// <param name="volume_type">Type</param>
/// <returns>True when allowed otherwise false</returns>
static bool is_valid(ModelVolumeType volume_type);
/// <summary>
/// Open file dialog with svg files
/// </summary>
/// <returns>File path to svg</returns>
static std::string choose_svg_file();
/// <summary>
/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG.
/// Than let select contour
/// </summary>
/// <returns>EmbossShape to create</returns>
static EmbossShape select_shape();
/// <summary>
/// Create new embos data
/// </summary>
/// <param name="cancel">Cancel for previous job</param>
/// <returns>Base data for emboss SVG</returns>
static DataBasePtr create_emboss_data_base(std::shared_ptr<std::atomic<bool>> &cancel);
/// <summary>
/// Create symbol '?' as default shape
/// without source file
/// with size 2cm
/// </summary>
/// <returns>Default shape to emboss</returns>
static ExPolygons default_shape();
/// <summary>
/// Separate file name from file path.
/// String after last delimiter and before last point
@ -77,53 +92,39 @@ static std::string choose_svg_file();
/// <returns>File name without directory path</returns>
static std::string get_file_name(const std::string &file_path);
/// <summary>
/// Create volume name from shape information
/// </summary>
/// <param name="shape">File path</param>
/// <returns>Name for volume</returns>
static std::string volume_name(const EmbossShape& shape);
} // namespace priv
void GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos){
std::string path = priv::choose_svg_file();
if (path.empty()) return;
create_volume(path, volume_type, mouse_pos);
}
void GLGizmoSVG::create_volume(ModelVolumeType volume_type) {
std::string path = priv::choose_svg_file();
if (path.empty()) return;
create_volume(path, volume_type);
bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos)
{
DataBasePtr base = priv::create_emboss_data_base(m_job_cancel);
Plater *plater_ptr = wxGetApp().plater();
return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg, mouse_pos);
}
void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos) {
std::string name = priv::get_file_name(svg_file_path);
NSVGimage *image = nsvgParseFromFile(svg_file_path.c_str(), "mm", 96.0f);
ExPolygons polys = NSVGUtils::to_ExPolygons(image);
nsvgDelete(image);
BoundingBox bb;
for (const auto &p : polys)
bb.merge(p.contour.points);
double scale = 1e-4;
auto project = std::make_unique<ProjectScale>(std::make_unique<ProjectZ>(10 / scale), scale);
indexed_triangle_set its = polygons2model(polys, *project);
// add volume
bool GLGizmoSVG::create_volume(ModelVolumeType volume_type)
{
DataBasePtr base = priv::create_emboss_data_base(m_job_cancel);
Plater *plater_ptr = wxGetApp().plater();
return start_create_volume_without_position(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg);
}
void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type) {
bool GLGizmoSVG::is_svg(const ModelVolume &volume) {
return volume.emboss_shape.has_value();
}
bool GLGizmoSVG::is_svg(const ModelVolume *volume) {
if (volume == nullptr)
return false;
return volume->emboss_shape.has_value();
}
bool GLGizmoSVG::is_svg_object(const ModelVolume *volume) {
if (volume == nullptr) return false;
if (!volume->emboss_shape.has_value()) return false;
if (volume->type() != ModelVolumeType::MODEL_PART) return false;
for (const ModelVolume *v : volume->get_object()->volumes) {
if (v == volume) continue;
bool GLGizmoSVG::is_svg_object(const ModelVolume &volume) {
if (!volume.emboss_shape.has_value()) return false;
if (volume.type() != ModelVolumeType::MODEL_PART) return false;
for (const ModelVolume *v : volume.get_object()->volumes) {
if (v->id() == volume.id()) continue;
if (v->type() == ModelVolumeType::MODEL_PART) return false;
}
return true;
@ -423,7 +424,7 @@ void GLGizmoSVG::set_volume_by_selection()
ImGuiWrapper::left_inputs();
// is valid svg volume?
if (!is_svg(volume))
if (!is_svg(*volume))
return reset_volume();
// cancel previous job
@ -527,8 +528,8 @@ void GLGizmoSVG::close()
{
// close gizmo == open it again
auto& mng = m_parent.get_gizmos_manager();
if (mng.get_current_type() == GLGizmosManager::Emboss)
mng.open_gizmo(GLGizmosManager::Emboss);
if (mng.get_current_type() == GLGizmosManager::Svg)
mng.open_gizmo(GLGizmosManager::Svg);
}
void GLGizmoSVG::draw_window()
@ -543,7 +544,8 @@ void GLGizmoSVG::draw_window()
void GLGizmoSVG::draw_model_type()
{
bool is_last_solid_part = is_svg_object(m_volume);
assert(m_volume != nullptr);
bool is_last_solid_part = is_svg_object(*m_volume);
std::string title = _u8L("SVG is to object");
if (is_last_solid_part) {
ImVec4 color{.5f, .5f, .5f, 1.f};
@ -561,7 +563,7 @@ void GLGizmoSVG::draw_model_type()
if (ImGui::RadioButton(_u8L("Added").c_str(), type == part))
new_type = part;
else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str());
ImGui::SetTooltip("%s", _u8L("Change to object's part.").c_str());
ImGui::SameLine();
std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object.");
@ -571,11 +573,16 @@ void GLGizmoSVG::draw_model_type()
if (is_last_solid_part)
ImGui::SetTooltip("%s", last_solid_part_hint.c_str());
else if (type != negative)
ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str());
ImGui::SetTooltip("%s", _u8L("Change to negative volume.").c_str());
}
// In simple mode are not modifiers
if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) {
GUI_App &app = wxGetApp();
Plater *plater = app.plater();
// SLA printers do not use modifiers
bool is_sla = plater->printer_technology() == ptSLA;
// In simple mode are not modifiers ?!?
bool is_simple = app.get_mode() == ConfigOptionMode::comSimple;
if (!is_sla && !is_simple) {
ImGui::SameLine();
if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier))
new_type = modifier;
@ -583,13 +590,11 @@ void GLGizmoSVG::draw_model_type()
if (is_last_solid_part)
ImGui::SetTooltip("%s", last_solid_part_hint.c_str());
else if (type != modifier)
ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str());
ImGui::SetTooltip("%s", _u8L("Change to modifier.").c_str());
}
}
if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) {
GUI_App &app = wxGetApp();
Plater * plater = app.plater();
Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction);
m_volume->set_type(*new_type);
@ -612,8 +617,8 @@ void GLGizmoSVG::draw_model_type()
// NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false)
// which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed
auto &mng = m_parent.get_gizmos_manager();
if (mng.get_current_type() != GLGizmosManager::Emboss)
mng.open_gizmo(GLGizmosManager::Emboss);
if (mng.get_current_type() != GLGizmosManager::Svg)
mng.open_gizmo(GLGizmosManager::Svg);
// TODO: select volume back - Ask @Sasa
}
}
@ -623,17 +628,6 @@ void GLGizmoSVG::draw_model_type()
// priv namespace implementation
///////////////
bool priv::is_valid(ModelVolumeType volume_type)
{
if (volume_type == ModelVolumeType::MODEL_PART ||
volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
volume_type == ModelVolumeType::PARAMETER_MODIFIER )
return true;
BOOST_LOG_TRIVIAL(error) << "Can't create embossed SVG with this type: " << (int) volume_type;
return false;
}
std::string priv::get_file_name(const std::string &file_path)
{
if (file_path.empty())
@ -660,6 +654,14 @@ std::string priv::get_file_name(const std::string &file_path)
return file_path.substr(offset, count);
}
std::string priv::volume_name(const EmbossShape &shape)
{
std::string file_name = get_file_name(shape.svg_file_path);
if (!file_name.empty())
return file_name;
return "SVG shape";
}
std::string priv::choose_svg_file()
{
wxWindow* parent = nullptr;
@ -689,5 +691,57 @@ std::string priv::choose_svg_file()
return path;
}
ExPolygons priv::default_shape() {
std::string file = Slic3r::resources_dir() + "/icons/question.svg";
NSVGimage *image = nsvgParseFromFile(file.c_str(), "px", 96.0f);
ExPolygons shape = NSVGUtils::to_ExPolygons(image);
nsvgDelete(image);
return shape;
}
EmbossShape priv::select_shape() {
EmbossShape shape;
shape.depth = 10.;
shape.distance = 0;
shape.use_surface = false;
shape.svg_file_path = choose_svg_file();
if (shape.svg_file_path.empty())
return {};
NSVGimage *image = nsvgParseFromFile(shape.svg_file_path.c_str(), "mm", 96.0f);
ScopeGuard sg([image]() { nsvgDelete(image); });
shape.shapes = NSVGUtils::to_ExPolygons(image);
// TODO: get scale from file
shape.scale = 1.;
// Must contain some shapes !!!
if (shape.shapes.empty())
shape.shapes = default_shape();
return shape;
}
DataBasePtr priv::create_emboss_data_base(std::shared_ptr<std::atomic<bool>> &cancel) {
EmbossShape shape = priv::select_shape();
if (shape.shapes.empty())
// canceled selection of SVG file
return nullptr;
// Cancel previous Job, when it is in process
// worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs
// Cancel only EmbossUpdateJob no others
if (cancel != nullptr)
cancel->store(true);
// create new shared ptr to cancel new job
cancel = std::make_shared<std::atomic<bool>>(false);
std::string name = priv::volume_name(shape);
return std::make_unique<DataBase>(name, cancel /*copy*/, std::move(shape));
}
// any existing icon filename to not influence GUI
const std::string GLGizmoSVG::M_ICON_FILENAME = "cut.svg";

View File

@ -37,29 +37,29 @@ public:
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
/// <param name="mouse_pos">Define position of new volume</param>
void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos);
void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog
/// <returns>True on succesfull start creation otherwise False</returns>
bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog
/// <summary>
/// Create new text without given position
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type);
void create_volume(ModelVolumeType volume_type); // first open file dialog
/// <returns>True on succesfull start creation otherwise False</returns>
bool create_volume(ModelVolumeType volume_type); // first open file dialog
/// <summary>
/// Check whether volume is object containing only emboss volume
/// </summary>
/// <param name="volume">Pointer to volume</param>
/// <returns>True when object otherwise False</returns>
static bool is_svg_object(const ModelVolume *volume);
static bool is_svg_object(const ModelVolume &volume);
/// <summary>
/// Check whether volume has emboss data
/// </summary>
/// <param name="volume">Pointer to volume</param>
/// <returns>True when constain emboss data otherwise False</returns>
static bool is_svg(const ModelVolume *volume);
static bool is_svg(const ModelVolume &volume);
protected:
bool on_init() override;

View File

@ -5,6 +5,7 @@
#include <libslic3r/Model.hpp>
#include <libslic3r/Format/OBJ.hpp> // load_obj for default mesh
#include <libslic3r/CutSurface.hpp> // use surface cuts
#include <libslic3r/BuildVolume.hpp> // create object
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
@ -27,6 +28,120 @@ using namespace Slic3r::Emboss;
using namespace Slic3r::GUI;
using namespace Slic3r::GUI::Emboss;
// Private implementation for create volume and objects jobs
namespace Slic3r::GUI::Emboss {
/// <summary>
/// Hold neccessary data to create ModelVolume in job
/// Volume is created on the surface of existing volume in object.
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary>
struct DataCreateVolume
{
// Hold data about shape
DataBasePtr base;
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
// new created volume transformation
std::optional<Transform3d> trmat;
// Define which gizmo open on the success
GLGizmosManager::EType gizmo;
};
/// <summary>
/// Create new TextVolume on the surface of ModelObject
/// Should not be stopped
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary>
class CreateVolumeJob : public Job
{
DataCreateVolume m_input;
TriangleMesh m_result;
public:
CreateVolumeJob(DataCreateVolume &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to create ModelObject in job
/// Object is placed on bed under screen coor
/// OR to center of scene when it is out of bed shape
/// </summary>
struct DataCreateObject
{
// Hold data about shape
DataBasePtr base;
// define position on screen where to create object
Vec2d screen_coor;
// projection property
Camera camera;
// shape of bed in case of create volume on bed
std::vector<Vec2d> bed_shape;
// Define which gizmo open on the success
GLGizmosManager::EType gizmo;
};
/// <summary>
/// Create new TextObject on the platter
/// Should not be stopped
/// </summary>
class CreateObjectJob : public Job
{
DataCreateObject m_input;
TriangleMesh m_result;
Transform3d m_transformation;
public:
CreateObjectJob(DataCreateObject &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to create(cut) volume from surface object in job
/// </summary>
struct CreateSurfaceVolumeData : public SurfaceVolumeData
{
// Hold data about shape
DataBasePtr base;
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
// Define which gizmo open on the success
GLGizmosManager::EType gizmo;
};
/// <summary>
/// Cut surface from object and create cutted volume
/// Should not be stopped
/// </summary>
class CreateSurfaceVolumeJob : public Job
{
CreateSurfaceVolumeData m_input;
TriangleMesh m_result;
public:
CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
} // namespace Slic3r::GUI::Emboss
// private namespace
namespace priv{
// create sure that emboss object is bigger than source object [in mm]
@ -37,12 +152,13 @@ constexpr float safe_extension = 1.0f;
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false);
bool check(const DataCreateVolume &input, bool is_main_thread = false);
bool check(const DataCreateObject &input);
bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false);
bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
static bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false);
static bool check(GLGizmosManager::EType gizmo);
static bool check(const DataCreateVolume &input, bool is_main_thread = false);
static bool check(const DataCreateObject &input);
static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false);
static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
// <summary>
/// Try to create mesh from text
@ -85,8 +201,9 @@ static void update_name_in_list(const ObjectList &object_list, const ModelVolume
/// <param name="type">Type of new volume</param>
/// <param name="trmat">Transformation of volume inside of object</param>
/// <param name="data">Text configuration and New VolumeName</param>
static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id,
const ModelVolumeType type, const Transform3d trmat, const DataBase &data);
/// <param name="gizmo">Gizmo to open</param>
static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, const ModelVolumeType type,
const std::optional<Transform3d>& trmat, const DataBase &data, GLGizmosManager::EType gizmo);
/// <summary>
/// Select Volume from objects
@ -124,6 +241,14 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr
/// <returns>Extruded object from cuted surace</returns>
static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, std::function<bool()> was_canceled);
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </summary>
/// <param name="volumes">Source object volumes for cut surface from</param>
/// <param name="text_volume_id">Source volume id</param>
/// <returns>Source data for cut surface from</returns>
static SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
static void create_message(const std::string &message); // only in finalize
static bool process(std::exception_ptr &eptr);
static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input);
@ -141,6 +266,11 @@ auto was_canceled(Job::Ctl &ctl, DataBase &base){
}// namespace priv
void Slic3r::GUI::Emboss::DataBase::write(ModelVolume &volume) const{
volume.name = volume_name;
volume.emboss_shape = shape;
}
/////////////////
/// Create Volume
CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input): m_input(std::move(input)){ assert(priv::check(m_input, true)); }
@ -159,7 +289,7 @@ void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
return;
if (m_result.its.empty())
return priv::create_message(_u8L("Can't create empty volume."));
priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base);
priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base, m_input.gizmo);
}
@ -194,7 +324,7 @@ void CreateObjectJob::process(Ctl &ctl)
bed_shape_.reserve(m_input.bed_shape.size());
for (const Vec2d &p : m_input.bed_shape)
bed_shape_.emplace_back(p.cast<int>());
Polygon bed(bed_shape_);
Slic3r::Polygon bed(bed_shape_);
if (!bed.contains(bed_coor.cast<int>()))
// mouse pose is out of build plate so create object in center of plate
bed_coor = bed.centroid().cast<double>();
@ -254,8 +384,8 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
// Manager::reset_all() So Gizmo could be closed before end of creation object
GLCanvas3D *canvas = plater->canvas3D();
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::Emboss)
manager.open_gizmo(GLGizmosManager::Emboss);
if (manager.get_current_type() != m_input.gizmo)
manager.open_gizmo(m_input.gizmo);
// redraw scene
canvas->reload_scene(true);
@ -306,7 +436,7 @@ void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
if (!priv::finalize(canceled, eptr, *m_input.base))
return;
priv::create_volume(std::move(m_result), m_input.object_id,
m_input.volume_type, m_input.transform, *m_input.base);
m_input.volume_type, m_input.transform, *m_input.base, m_input.gizmo);
}
/////////////////
@ -335,143 +465,203 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
priv::update_volume(std::move(m_result), m_input, tr);
}
namespace Slic3r::GUI::Emboss {
namespace priv {
/// <summary>
/// Check if volume type is possible use for new text volume
/// </summary>
/// <param name="volume_type">Type</param>
/// <returns>True when allowed otherwise false</returns>
static bool is_valid(ModelVolumeType volume_type);
/// <summary>
/// Start job for add new volume to object with given transformation
/// </summary>
/// <param name="worker">Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker()</param>
/// <param name="object">Define where to add</param>
/// <param name="volume_tr">Wanted volume transformation, when not set will be calculated after creation to be near the object</param>
/// <param name="data">Define what to emboss - shape</param>
/// <param name="volume_type">Type of volume: Part, negative, modifier</param>
/// <param name="gizmo">Define which gizmo open on the success</param>
/// <returns>Nullptr when job is sucessfully add to worker otherwise return data to be processed different way</returns>
static bool start_create_volume_job(Worker &worker, const ModelObject &object, const std::optional<Transform3d>& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo);
/// <summary>
/// Find volume in selected objects with closest convex hull to screen center.
/// </summary>
/// <param name="selection">Define where to search for closest</param>
/// <param name="screen_center">Canvas center(dependent on camera settings)</param>
/// <param name="objects">Actual objects</param>
/// <param name="closest_center">OUT: coordinate of controid of closest volume</param>
/// <returns>closest volume when exists otherwise nullptr</returns>
static const GLVolume *find_closest(
const Selection &selection, const Vec2d &screen_center,
const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center);
/// <summary>
/// Start job for add object with text into scene
/// </summary>
/// <param name="plater">Contain worker and build shape</param>
/// <param name="emboss_data">Define params of text</param>
/// <param name="coor">Screen coordinat, where to create new object laying on bed</param>
/// <param name="gizmo">Define which gizmo open on the success</param>
/// <returns>True when can add job to worker otherwise FALSE</returns>
static bool start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo);
/// <summary>
/// Start job to create volume on the surface of object
/// </summary>
/// <param name="plater">scene RayCasters + Objects + Camera + worker</param>
/// <param name="data">Describe what to emboss</param>
/// <param name="volume_type">Type of new volume</param>
/// <param name="screen_coor">Where to add</param>
/// <param name="gl_volume">Surface point is belonge to</param>
/// <param name="raycaster">For project on surface</param>
/// <param name="gizmo">Define which gizmo open on the success</param>
/// <param name="distance">Distance from surface</param>
/// <param name="angle">Angle around emboss direction</param>
/// <param name="success">True on add job to worker otherwise false</param>
/// <returns>Nullptr when job is sucessfully add to worker otherwise return data to be processed different way</returns>
static DataBasePtr start_create_volume_on_surface_job(Plater &plater,
DataBasePtr data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLGizmosManager::EType gizmo,
const std::optional<float> &distance,
const std::optional<float> &angle,
bool &success);
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id)
{
SurfaceVolumeData::ModelSources result;
result.reserve(volumes.size() - 1);
for (const ModelVolume *v : volumes) {
if (text_volume_id.has_value() && v->id().id == *text_volume_id)
continue;
// skip modifiers and negative volumes, ...
if (!v->is_model_part())
continue;
const TriangleMesh &tm = v->mesh();
if (tm.empty())
continue;
if (tm.its.empty())
continue;
result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()});
}
return result;
}
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume)
namespace Slic3r::GUI::Emboss {
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_volume)
{
if (text_volume == nullptr)
return {};
if (!text_volume->text_configuration.has_value())
return {};
const ModelVolumePtrs &volumes = text_volume->get_object()->volumes;
const ModelVolumePtrs &volumes = text_volume.get_object()->volumes;
// no other volume in object
if (volumes.size() <= 1)
return {};
return create_sources(volumes, text_volume->id().id);
return priv::create_sources(volumes, text_volume.id().id);
}
bool start_create_volume_job(
Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type)
bool start_create_volume(Plater *plater_ptr,
DataBasePtr data,
ModelVolumeType volume_type,
RaycastManager &raycaster,
unsigned char gizmo,
const Vec2d &mouse_pos,
const std::optional<float> &distance,
const std::optional<float> &angle)
{
bool &use_surface = data->shape.use_surface;
std::unique_ptr<GUI::Job> job;
if (use_surface) {
// Model to cut surface from.
SurfaceVolumeData::ModelSources sources = create_sources(object.volumes);
if (sources.empty()) {
use_surface = false;
} else {
bool is_outside = volume_type == ModelVolumeType::MODEL_PART;
// check that there is not unexpected volume type
assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER);
SurfaceVolumeData sfvd{volume_tr, is_outside, std::move(sources)};
CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id()};
job = std::make_unique<CreateSurfaceVolumeJob>(std::move(surface_data));
}
}
if (!use_surface) {
// create volume
DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr};
job = std::make_unique<CreateVolumeJob>(std::move(create_volume_data));
}
return queue_job(worker, std::move(job));
if (!priv::is_valid(volume_type))
return false;
assert(plater_ptr);
if (plater_ptr == nullptr)
return false;
Plater &plater = *plater_ptr;
GLCanvas3D *canvas_ptr = plater.get_current_canvas3D();
assert(canvas_ptr);
if (canvas_ptr == nullptr)
return false;
GLGizmosManager::EType gizmo_type = static_cast<GLGizmosManager::EType>(gizmo);
GLVolume *gl_volume = get_first_hovered_gl_volume(*canvas_ptr);
if (gl_volume == nullptr)
// object is not under mouse position soo create object on plater
return priv::start_create_object_job(plater, std::move(data), mouse_pos, gizmo_type);
bool success = true;
DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data),
volume_type, mouse_pos, *gl_volume, raycaster, gizmo_type, distance, angle, success);
// Is successfull created job for add volume on surface?
if (success)
return true;
// not success and consume data
if (data2 == nullptr)
return false;
// Can't create on coordinate try to create somewhere
return start_create_volume_without_position(plater_ptr, std::move(data2), volume_type, raycaster, gizmo, distance, angle);
}
std::optional<Transform3d> create_volume_transformation_on_surface(const Vec2d &screen_coor,
const Camera &camera,
const ModelVolume &volume,
const ModelInstance &instance,
RaycastManager &raycaster,
GLCanvas3D &canvas,
const std::optional<float> &distance,
const std::optional<float> &angle)
bool start_create_volume_without_position(Plater *plater_ptr,
DataBasePtr data,
ModelVolumeType volume_type,
RaycastManager &raycaster,
unsigned char gizmo,
const std::optional<float> &distance,
const std::optional<float> &angle)
{
auto cond = RaycastManager::AllowVolumes({volume.id().id});
RaycastManager::Meshes meshes = create_meshes(canvas, cond);
raycaster.actualize(instance, &cond, &meshes);
if (!priv::is_valid(volume_type))
return false;
std::optional<RaycastManager::Hit> hit = ray_from_camera(raycaster, screen_coor, camera, &cond);
assert(plater_ptr);
if (plater_ptr == nullptr)
return false;
Plater &plater = *plater_ptr;
// context menu for add text could be open only by right click on an
// object. After right click, object is selected and object_idx is set
// also hit must exist. But there is options to add text by object list
if (!hit.has_value())
return {};
GLCanvas3D *canvas_ptr = plater.get_current_canvas3D();
assert(canvas_ptr);
if (canvas_ptr == nullptr)
return false;
const GLCanvas3D &canvas = *canvas_ptr;
// Create result volume transformation
Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit);
// select position by camera position and view direction
const Selection &selection = canvas.get_selection();
int object_idx = selection.get_object_idx();
apply_transformation(angle, distance, surface_trmat);
Size s = canvas.get_canvas_size();
Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.);
const ModelObjectPtrs &objects = selection.get_model()->objects;
return instance.get_matrix().inverse() * surface_trmat;
}
GLGizmosManager::EType gizmo_type = static_cast<GLGizmosManager::EType>(gizmo);
Transform3d create_volume_transformation(const GLVolume &gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth)
{
// Transformation is inspired add generic volumes in ObjectList::load_generic_subobject
const ModelObject *obj = objects[gl_volume.object_idx()];
BoundingBoxf3 instance_bb = obj->instance_bounding_box(gl_volume.instance_idx());
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
Transform3d tr = gl_volume.get_instance_transformation().get_matrix_no_offset().inverse();
Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created
- instance_bb.size().y() / 2 - volume_height / 2, // under
volume_depth / 2 - instance_bb.size().z() / 2 // lay on bed
);
return tr * Eigen::Translation3d(offset_tr);
}
// No selected object so create new object
if (selection.is_empty() || object_idx < 0 ||
static_cast<size_t>(object_idx) >= objects.size())
// create Object on center of screen
// when ray throw center of screen not hit bed it create object on center of bed
return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type);
const GLVolume *find_closest(
const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center)
{
assert(closest_center != nullptr);
const GLVolume * closest = nullptr;
const Selection::IndicesList &indices = selection.get_volume_idxs();
assert(!indices.empty()); // no selected volume
if (indices.empty())
return closest;
// create volume inside of selected object
Vec2d coor;
const Camera &camera = wxGetApp().plater()->get_camera();
const GLVolume *gl_volume = priv::find_closest(selection, screen_center, camera, objects, &coor);
double center_sq_distance = std::numeric_limits<double>::max();
for (unsigned int id : indices) {
const GLVolume *gl_volume = selection.get_volume(id);
const ModelVolume *volume = get_model_volume(*gl_volume, objects);
if (!volume->is_model_part())
continue;
Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume);
Vec2d c = hull.centroid().cast<double>();
Vec2d d = c - screen_center;
bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y());
if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance))
continue;
if (gl_volume == nullptr)
return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type);
bool success = true;
DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data),
volume_type, coor, *gl_volume, raycaster, gizmo_type, distance, angle, success);
double distance = d.squaredNorm();
if (center_sq_distance < distance)
continue;
center_sq_distance = distance;
// Is successfull created job for add volume on surface?
if (success)
return true;
*closest_center = c;
closest = gl_volume;
}
return closest;
// not success and consume data
if (data2 == nullptr)
return false;
// In centroid of convex hull is not hit with object. e.g. torid
// soo create transfomation on border of object
// there is no point on surface so no use of surface will be applied
if (data2->shape.use_surface)
data2->shape.use_surface = false;
Worker &worker = plater.get_ui_job_worker();
const ModelObject *object = get_model_object(*gl_volume, objects);
if (object == nullptr)
return false;
return priv::start_create_volume_job(worker, *object, {}, std::move(data2), volume_type, gizmo_type);
}
} // namespace Slic3r::GUI::Emboss
@ -495,6 +685,13 @@ bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface)
//res &= input.text_configuration.style.prop.use_surface == use_surface;
return res;
}
bool priv::check(GLGizmosManager::EType gizmo)
{
assert(gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg);
return gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg;
}
bool priv::check(const DataCreateVolume &input, bool is_main_thread) {
bool check_fontfile = false;
assert(input.base != nullptr);
@ -504,6 +701,7 @@ bool priv::check(const DataCreateVolume &input, bool is_main_thread) {
res &= input.volume_type != ModelVolumeType::INVALID;
assert(input.object_id.id >= 0);
res &= input.object_id.id >= 0;
res &= check(input.gizmo);
return res;
}
bool priv::check(const DataCreateObject &input) {
@ -517,6 +715,7 @@ bool priv::check(const DataCreateObject &input) {
res &= input.screen_coor.y() >= 0.;
assert(input.bed_shape.size() >= 3); // at least triangle
res &= input.bed_shape.size() >= 3;
res &= check(input.gizmo);
return res;
}
bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){
@ -542,6 +741,7 @@ bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread)
res &= check(*input.base, is_main_thread, use_surface);
assert(!input.sources.empty());
res &= !input.sources.empty();
res &= check(input.gizmo);
return res;
}
bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){
@ -711,9 +911,12 @@ void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3
UpdateJob::update_volume(volume, std::move(mesh), *data.base);
}
void priv::create_volume(
TriangleMesh &&mesh, const ObjectID& object_id,
const ModelVolumeType type, const Transform3d trmat, const DataBase &data)
void priv::create_volume(TriangleMesh &&mesh,
const ObjectID &object_id,
const ModelVolumeType type,
const std::optional<Transform3d> &trmat,
const DataBase &data,
GLGizmosManager::EType gizmo)
{
GUI_App &app = wxGetApp();
Plater *plater = app.plater();
@ -741,6 +944,13 @@ void priv::create_volume(
plater->take_snapshot(_L("Add Emboss text Volume"));
BoundingBoxf3 instance_bb;
if (!trmat.has_value()) {
// used for align to instance
size_t instance_index = 0; // must exist
instance_bb = obj->instance_bounding_box(instance_index);
}
// NOTE: be carefull add volume also center mesh !!!
// So first add simple shape(convex hull is also calculated)
ModelVolume *volume = obj->add_volume(make_cube(1., 1., 1.), type);
@ -757,7 +967,24 @@ void priv::create_volume(
volume->source.is_from_builtin_objects = true;
volume->name = data.volume_name; // copy
volume->set_transformation(trmat);
if (trmat.has_value()) {
volume->set_transformation(*trmat);
} else {
assert(!data.shape.use_surface);
// Create transformation for volume near from object(defined by glVolume)
// Transformation is inspired add generic volumes in ObjectList::load_generic_subobject
Vec3d volume_size = volume->mesh().bounding_box().size();
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created
-instance_bb.size().y() / 2 - volume_size.y() / 2, // under
volume_size.z() / 2 - instance_bb.size().z() / 2); // lay on bed
// use same instance as for calculation of instance_bounding_box
Transform3d tr = obj->instances.front()->get_transformation().get_matrix_no_offset().inverse();
Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr);
volume->set_transformation(volume_trmat);
}
data.write(*volume);
// update printable state on canvas
@ -935,6 +1162,26 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2,
return TriangleMesh(std::move(new_its));
}
SurfaceVolumeData::ModelSources priv::create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id)
{
SurfaceVolumeData::ModelSources result;
result.reserve(volumes.size() - 1);
for (const ModelVolume *v : volumes) {
if (text_volume_id.has_value() && v->id().id == *text_volume_id)
continue;
// skip modifiers and negative volumes, ...
if (!v->is_model_part())
continue;
const TriangleMesh &tm = v->mesh();
if (tm.empty())
continue;
if (tm.its.empty())
continue;
result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()});
}
return result;
}
bool priv::process(std::exception_ptr &eptr) {
if (!eptr) return false;
try {
@ -956,6 +1203,141 @@ bool priv::finalize(bool canceled, std::exception_ptr &eptr, const DataBase &inp
return !process(eptr);
}
bool priv::is_valid(ModelVolumeType volume_type)
{
if (volume_type == ModelVolumeType::MODEL_PART ||
volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
volume_type == ModelVolumeType::PARAMETER_MODIFIER )
return true;
BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type;
return false;
}
bool priv::start_create_volume_job(
Worker &worker, const ModelObject &object, const std::optional<Transform3d>& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo)
{
bool &use_surface = data->shape.use_surface;
std::unique_ptr<GUI::Job> job;
if (use_surface) {
// Model to cut surface from.
SurfaceVolumeData::ModelSources sources = create_sources(object.volumes);
if (sources.empty() || !volume_tr.has_value()) {
use_surface = false;
} else {
bool is_outside = volume_type == ModelVolumeType::MODEL_PART;
// check that there is not unexpected volume type
assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER);
SurfaceVolumeData sfvd{*volume_tr, is_outside, std::move(sources)};
CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id(), gizmo};
job = std::make_unique<CreateSurfaceVolumeJob>(std::move(surface_data));
}
}
if (!use_surface) {
// create volume
DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr, gizmo};
job = std::make_unique<CreateVolumeJob>(std::move(create_volume_data));
}
return queue_job(worker, std::move(job));
}
const GLVolume * priv::find_closest(
const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center)
{
assert(closest_center != nullptr);
const GLVolume *closest = nullptr;
const Selection::IndicesList &indices = selection.get_volume_idxs();
assert(!indices.empty()); // no selected volume
if (indices.empty())
return closest;
double center_sq_distance = std::numeric_limits<double>::max();
for (unsigned int id : indices) {
const GLVolume *gl_volume = selection.get_volume(id);
const ModelVolume *volume = get_model_volume(*gl_volume, objects);
if (!volume->is_model_part())
continue;
Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume);
Vec2d c = hull.centroid().cast<double>();
Vec2d d = c - screen_center;
bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y());
if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance))
continue;
double distance = d.squaredNorm();
if (center_sq_distance < distance)
continue;
center_sq_distance = distance;
*closest_center = c;
closest = gl_volume;
}
return closest;
}
bool priv::start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo)
{
const Camera &camera = plater.get_camera();
const Pointfs &bed_shape = plater.build_volume().bed_shape();
DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape, gizmo};
auto job = std::make_unique<CreateObjectJob>(std::move(data));
Worker &worker = plater.get_ui_job_worker();
return queue_job(worker, std::move(job));
}
DataBasePtr priv::start_create_volume_on_surface_job(Plater &plater,
DataBasePtr data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLGizmosManager::EType gizmo,
const std::optional<float> &distance,
const std::optional<float> &angle,
bool &success)
{
success = false;
const ModelObjectPtrs &objects = plater.model().objects;
const ModelVolume* volume = get_model_volume(gl_volume, objects);
const ModelInstance *instance = get_model_instance(gl_volume, objects);
if (volume == nullptr || instance == nullptr ||
volume->get_object() == nullptr) {
// weird situation
assert(false);
return data;
}
const Camera &camera = plater.get_camera();
GLCanvas3D &canvas = *plater.get_current_canvas3D();
auto cond = RaycastManager::AllowVolumes({volume->id().id});
RaycastManager::Meshes meshes = create_meshes(canvas, cond);
raycaster.actualize(*instance, &cond, &meshes);
std::optional<RaycastManager::Hit> hit = ray_from_camera(raycaster, screen_coor, camera, &cond);
// context menu for add text could be open only by right click on an
// object. After right click, object is selected and object_idx is set
// also hit must exist. But there is options to add text by object list
if (!hit.has_value())
// When model is broken. It could appear that hit miss the object.
// So add part near by in simmilar manner as right panel do
return data;
// Create result volume transformation
Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit);
apply_transformation(angle, distance, surface_trmat);
Transform3d transform = instance->get_matrix().inverse() * surface_trmat;
// Try to cast ray into scene and find object for add volume
Worker &worker = plater.get_ui_job_worker();
success = priv::start_create_volume_job(worker, *volume->get_object(), transform, std::move(data), volume_type, gizmo);
return nullptr;
}
#include <wx/msgdlg.h>

View File

@ -7,8 +7,7 @@
#include "libslic3r/Emboss.hpp"
#include "libslic3r/EmbossShape.hpp"
#include "libslic3r/Point.hpp" // Transform3d
#include "slic3r/Utils/RaycastManager.hpp"
#include "libslic3r/ObjectID.hpp"
#include "slic3r/GUI/Jobs/EmbossJob.hpp" // Emboss::DataBase
#include "slic3r/GUI/Camera.hpp"
@ -17,16 +16,12 @@
// forward declarations
namespace Slic3r {
class GLVolume;
class ModelVolume;
class ModelObject;
class TriangleMesh;
typedef std::vector<ModelObject *> ModelObjectPtrs;
typedef std::vector<ModelVolume *> ModelVolumePtrs;
class ModelVolume;
enum class ModelVolumeType : int;
namespace GUI {
class Selection;
class RaycastManager;
class Worker;
class Plater;
}}
namespace Slic3r::GUI::Emboss {
@ -38,7 +33,7 @@ class DataBase
{
public:
DataBase(std::string volume_name, std::shared_ptr<std::atomic<bool>> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {}
DataBase(std::string volume_name, std::shared_ptr<std::atomic<bool>> cancel, EmbossShape shape)
DataBase(std::string volume_name, std::shared_ptr<std::atomic<bool>> cancel, EmbossShape&& shape)
: volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape))
{}
virtual ~DataBase() {}
@ -54,11 +49,7 @@ public:
/// Write data how to reconstruct shape to volume
/// </summary>
/// <param name="volume">Data object for store emboss params</param>
virtual void write(ModelVolume &volume) const
{
volume.name = volume_name;
volume.emboss_shape = shape;
};
virtual void write(ModelVolume &volume) const;
// new volume name
std::string volume_name;
@ -70,77 +61,7 @@ public:
// shape to emboss
EmbossShape shape;
};
/// <summary>
/// Hold neccessary data to create ModelVolume in job
/// Volume is created on the surface of existing volume in object.
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary>
struct DataCreateVolume
{
// Hold data about shape
std::unique_ptr<DataBase> base;
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
// new created volume transformation
Transform3d trmat;
};
/// <summary>
/// Create new TextVolume on the surface of ModelObject
/// Should not be stopped
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary>
class CreateVolumeJob : public Job
{
DataCreateVolume m_input;
TriangleMesh m_result;
public:
CreateVolumeJob(DataCreateVolume&& input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to create ModelObject in job
/// Object is placed on bed under screen coor
/// OR to center of scene when it is out of bed shape
/// </summary>
struct DataCreateObject
{
// Hold data about shape
std::unique_ptr<DataBase> base;
// define position on screen where to create object
Vec2d screen_coor;
// projection property
Camera camera;
// shape of bed in case of create volume on bed
std::vector<Vec2d> bed_shape;
};
/// <summary>
/// Create new TextObject on the platter
/// Should not be stopped
/// </summary>
class CreateObjectJob : public Job
{
DataCreateObject m_input;
TriangleMesh m_result;
Transform3d m_transformation;
public:
CreateObjectJob(DataCreateObject&& input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
using DataBasePtr = std::unique_ptr<DataBase>;
/// <summary>
/// Hold neccessary data to update embossed text object in job
@ -148,7 +69,7 @@ public:
struct DataUpdate
{
// Hold data about shape
std::unique_ptr<DataBase> base;
DataBasePtr base;
// unique identifier of volume to change
ObjectID volume_id;
@ -212,35 +133,6 @@ struct SurfaceVolumeData
ModelSources sources;
};
/// <summary>
/// Hold neccessary data to create(cut) volume from surface object in job
/// </summary>
struct CreateSurfaceVolumeData : public SurfaceVolumeData{
// Hold data about shape
std::unique_ptr<DataBase> base;
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
};
/// <summary>
/// Cut surface from object and create cutted volume
/// Should not be stopped
/// </summary>
class CreateSurfaceVolumeJob : public Job
{
CreateSurfaceVolumeData m_input;
TriangleMesh m_result;
public:
CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to update embossed text object in job
/// </summary>
@ -264,72 +156,43 @@ public:
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </summary>
/// <param name="volumes">Source object volumes for cut surface from</param>
/// <param name="text_volume_id">Source volume id</param>
/// <param name="volume">Define embossed volume</param>
/// <returns>Source data for cut surface from</returns>
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume);
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// Create new volume on position of mouse cursor
/// </summary>
/// <param name="text_volume">Define text in object</param>
/// <returns>Source data for cut surface from</returns>
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume);
/// <param name="plater_ptr">canvas + camera + bed shape + </param>
/// <param name="data">Shape of emboss</param>
/// <param name="volume_type">New created volume type</param>
/// <param name="raycaster">Knows object in scene</param>
/// <param name="gizmo">Define which gizmo open on the success - enum GLGizmosManager::EType</param>
/// <param name="mouse_pos">Define position where to create volume</param>
/// <param name="distance">Wanted additionl move in Z(emboss) direction of new created volume</param>
/// <param name="angle">Wanted additionl rotation around Z of new created volume</param>
/// <returns>True on success otherwise False</returns>
bool start_create_volume(Plater *plater_ptr,
DataBasePtr data,
ModelVolumeType volume_type,
RaycastManager &raycaster,
unsigned char gizmo,
const Vec2d &mouse_pos,
const std::optional<float> &distance = {},
const std::optional<float> &angle = {});
using DataBasePtr = std::unique_ptr<DataBase>;
/// <summary>
/// Start job for add new volume to object with given transformation
/// Same as previous function but without mouse position
/// Need to suggest position or put near the selection
/// </summary>
/// <param name="worker">Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker()</param>
/// <param name="object">Define where to add</param>
/// <param name="volume_tr">Wanted volume transformation</param>
/// <param name="data">Define what to emboss - shape</param>
/// <param name="volume_type">Type of volume: Part, negative, modifier</param>
/// <return>True on success otherwise false</return>
bool start_create_volume_job(Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type);
/// <summary>
/// Start job for add new volume on surface of object defined by screen coor
/// </summary>
/// <param name="screen_coor">Mouse position which define position</param>
/// <param name="volume">Volume to find surface for create</param>
/// <param name="instance">Instance to find surface for create</param>
/// <param name="raycaster">Ability to ray cast to model</param>
/// <param name="canvas">Contain already used scene RayCasters</param>
/// <param name="angle">Initial z move</param>
/// <param name="angle">Initial z rotation</param>
/// <returns>Volume transformation otherwise there is no hit surface by screen coor</returns>
std::optional<Transform3d> create_volume_transformation_on_surface(const Vec2d &screen_coor,
const Camera &camera,
const ModelVolume &volume,
const ModelInstance &instance,
RaycastManager &raycaster,
GLCanvas3D &canvas,
const std::optional<float> &distance = {},
const std::optional<float> &angle = {});
/// <summary>
/// Create transformation for volume near from object(defined by glVolume)
/// </summary>
/// <param name="gl_volume">Define object</param>
/// <param name="objects">All objects</param>
/// <param name="volume_height">Y Size of embossed volume [mm in instance]</param>
/// <param name="volume_depth">Z size of embossed volume - emboss depth[mm in instance]</param>
/// <returns>Transformation for new created volume</returns>
Transform3d create_volume_transformation(const GLVolume& gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth);
/// <summary>
/// Find volume in selected objects with closest convex hull to screen center.
/// </summary>
/// <param name="selection">Define where to search for closest</param>
/// <param name="screen_center">Canvas center(dependent on camera settings)</param>
/// <param name="objects">Actual objects</param>
/// <param name="closest_center">OUT: coordinate of controid of closest volume</param>
/// <returns>closest volume when exists otherwise nullptr</returns>
const GLVolume *find_closest(
const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center);
bool start_create_volume_without_position(Plater *plater_ptr,
DataBasePtr data,
ModelVolumeType volume_type,
RaycastManager &raycaster,
unsigned char gizmo,
const std::optional<float> &distance = {},
const std::optional<float> &angle = {});
} // namespace Slic3r::GUI
#endif // slic3r_EmbossJob_hpp_