Refactor for easier use of emboss shape

This commit is contained in:
Filip Sykala - NTB T15p 2023-03-13 20:03:03 +01:00
parent c2f348ee19
commit 07181b4f37
5 changed files with 368 additions and 304 deletions

View File

@ -21,10 +21,10 @@ struct EmbossShape
ExPolygons shapes;
// scale of shape, multiplier to get 3d point in mm from integer shape
double scale;
double scale = 1.;
// Emboss depth, Size in local Z direction
double depth; // [in loacal mm]
double depth = 1.; // [in loacal mm]
// NOTE: User should see and modify mainly world size not local
// Flag that result volume use surface cutted from source objects

View File

@ -14,6 +14,7 @@
#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
@ -107,67 +108,6 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
// Private namespace with helper function for create volume
namespace priv {
/// <summary>
/// Data for emboss job to create shape
/// </summary>
struct TextDataBase : public DataBase
{
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))
{
assert(this->font_file.has_value());
// partialy fill shape from text configuration
const FontProp &fp = this->text_configuration.style.prop;
shape.depth = fp.emboss;
shape.use_surface = fp.use_surface;
shape.distance = fp.distance;
const FontFile &ff = *this->font_file.font_file;
shape.scale = get_text_shape_scale(fp, ff);
}
/// <summary>
/// Create shape from text and font
/// </summary>
/// <returns>Text shape defined by configuration and font file</returns>
EmbossShape &create_shape() override {
if (!shape.shapes.empty())
return shape;
// create shape by configuration
const char *text = text_configuration.text.c_str();
const FontProp &fp = text_configuration.style.prop;
auto was_canceled = [&c = cancel]() -> bool { return c->load(); };
shape.shapes = text2shapes(font_file, text, fp, was_canceled);
// TEST
const FontProp &prop = text_configuration.style.prop;
const std::optional<unsigned int> &cn = prop.collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
const FontFileWithCache &font = font_file;
assert(font_index < font.font_file->infos.size());
int unit_per_em = font.font_file->infos[font_index].unit_per_em;
float scale = prop.size_in_mm / unit_per_em;
float depth = prop.emboss / scale;
return shape;
}
void write(ModelVolume &volume) const override
{
volume.text_configuration = text_configuration; // copy
// discard information about rotation, should not be stored in volume
volume.text_configuration->style.prop.angle.reset();
DataBase::write(volume);
}
// Keep pointer on Data of font (glyph shapes)
FontFileWithCache font_file;
// font item is not used for create object
TextConfiguration text_configuration;
};
/// <summary>
/// Check if volume type is possible use for new text volume
@ -187,16 +127,28 @@ static std::unique_ptr<DataBase> create_emboss_data_base(
const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>> &cancel);
/// <summary>
/// Start job for add new volume to object with given transformation
/// Data for emboss job to create shape
/// </summary>
/// <param name="object">Define where to add</param>
/// <param name="volume_trmat">Volume transformation</param>
/// <param name="emboss_data">Define text</param>
/// <param name="volume_type">Type of volume</param>
static void start_create_volume_job(const ModelObject *object,
const Transform3d volume_trmat,
std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type);
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;
// private:
// Keep pointer on Data of font (glyph shapes)
FontFileWithCache font_file;
// font item is not used for create object
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
@ -208,36 +160,14 @@ static void start_create_volume_job(const ModelObject *object,
/// <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(std::unique_ptr<DataBase> emboss_data,
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,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLCanvas3D &canvas);
/// <summary>
/// Find volume in selected object with closest convex hull to screen center.
/// Return
/// </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>
/// <param name="closest_volume">OUT: closest volume</param>
static void find_closest_volume(const Selection &selection,
const Vec2d &screen_center,
const Camera &camera,
const ModelObjectPtrs &objects,
Vec2d *closest_center,
const GLVolume **closest_volume);
/// <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);
// Loaded icons enum
// Have to match order of files in function GLGizmoEmboss::init_icons()
enum class IconType : unsigned {
@ -270,19 +200,15 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous
m_style_manager.discard_style_changes();
set_default_text();
GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent);
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
if (gl_volume != nullptr) {
// Try to cast ray into scene and find object for add volume
if (!priv::start_create_volume_on_surface_job(std::move(emboss_data), volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) {
// When model is broken. It could appear that hit miss the object.
// So add part near by in simmilar manner as right panel do
create_volume(volume_type);
}
} else {
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
priv::start_create_object_job(std::move(emboss_data), mouse_pos);
}
return priv::start_create_object_job(std::move(emboss_data), mouse_pos);
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);
}
// Designed for create volume without information of mouse in scene
@ -310,34 +236,26 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
// create volume inside of selected object
Vec2d coor;
const GLVolume *vol = nullptr;
const Camera &camera = wxGetApp().plater()->get_camera();
priv::find_closest_volume(selection, screen_center, camera, objects, &coor, &vol);
if (vol == nullptr) {
priv::start_create_object_job(std::move(emboss_data), screen_center);
} else if (!priv::start_create_volume_on_surface_job(std::move(emboss_data), volume_type, coor, vol, m_raycast_manager, m_parent)) {
// in centroid of convex hull is not hit with object
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);
// TODO: do it better way
FontProp &fp = static_cast<TextDataBase *>(emboss_data.get())->text_configuration.style.prop;
// there is no point on surface so no use of surface will be applied
if (fp.use_surface)
fp.use_surface = false;
if (emboss_data->shape.use_surface)
emboss_data->shape.use_surface = false;
// Transformation is inspired add generic volumes in ObjectList::load_generic_subobject
const ModelObject *obj = objects[vol->object_idx()];
BoundingBoxf3 instance_bb = obj->instance_bounding_box(vol->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 = vol->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 - fp.size_in_mm / 2, // under
fp.emboss / 2 - instance_bb.size().z() / 2 // lay on bed
);
Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr);
priv::start_create_volume_job(obj, volume_trmat, std::move(emboss_data), volume_type);
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);
}
}
@ -3347,6 +3265,59 @@ bool priv::is_valid(ModelVolumeType 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))
{
assert(this->font_file.has_value());
// partialy fill shape from text configuration
const FontProp &fp = this->text_configuration.style.prop;
shape.depth = fp.emboss;
shape.use_surface = fp.use_surface;
shape.distance = fp.distance;
const FontFile &ff = *this->font_file.font_file;
shape.scale = get_text_shape_scale(fp, ff);
}
EmbossShape &priv::TextDataBase::create_shape()
{
if (!shape.shapes.empty())
return shape;
// create shape by configuration
const char *text = text_configuration.text.c_str();
const FontProp &fp = text_configuration.style.prop;
auto was_canceled = [&c = cancel]() -> bool { return c->load(); };
shape.shapes = text2shapes(font_file, text, fp, was_canceled);
// TEST
const FontProp &prop = text_configuration.style.prop;
const std::optional<unsigned int> &cn = prop.collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
const FontFileWithCache &font = font_file;
assert(font_index < font.font_file->infos.size());
int unit_per_em = font.font_file->infos[font_index].unit_per_em;
float scale = prop.size_in_mm / unit_per_em;
float depth = prop.emboss / scale;
return shape;
}
void priv::TextDataBase::write(ModelVolume &volume) const
{
volume.text_configuration = text_configuration; // copy
// discard information about rotation, should not be stored in volume
volume.text_configuration->style.prop.angle.reset();
// only temporary solution
volume.text_configuration->style.prop.use_surface = shape.use_surface;
volume.text_configuration->style.prop.distance = shape.distance;
DataBase::write(volume);
}
std::unique_ptr<DataBase> priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>>& cancel)
{
// create volume_name
@ -3380,143 +3351,48 @@ 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)
{
// start creation of new object
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();
// can't create new object with distance from surface
// TODO: do it better way
FontProp &fp = static_cast<TextDataBase *>(emboss_data.get())->text_configuration.style.prop;
if (fp.distance.has_value()) fp.distance.reset();
// can't create new object with using surface
if (fp.use_surface)
fp.use_surface = false;
// Transform3d volume_tr = priv::create_transformation_on_bed(mouse_pos, camera, bed_shape, prop.emboss / 2);
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));
Worker &worker = plater->get_ui_job_worker();
queue_job(worker, std::move(job));
}
void priv::start_create_volume_job(const ModelObject *object,
const Transform3d volume_trmat,
bool priv::start_create_volume_on_surface_job(const FontProp &fp,
std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type)
{
// TODO: do it better way
FontProp &fp = static_cast<TextDataBase *>(emboss_data.get())->text_configuration.style.prop;
bool &use_surface = fp.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_trmat, is_outside, std::move(sources)};
CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(emboss_data), volume_type, object->id()};
job = std::make_unique<CreateSurfaceVolumeJob>(std::move(surface_data));
}
}
if (!use_surface) {
// create volume
DataCreateVolume data{std::move(emboss_data), volume_type, object->id(), volume_trmat};
job = std::make_unique<CreateVolumeJob>(std::move(data));
}
Plater *plater = wxGetApp().plater();
Worker &worker = plater->get_ui_job_worker();
queue_job(worker, std::move(job));
}
bool priv::start_create_volume_on_surface_job(std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume *gl_volume,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLCanvas3D &canvas)
{
assert(gl_volume != nullptr);
if (gl_volume == nullptr) return false;
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 ModelObjectPtrs &objects = plater->model().objects;
int object_idx = gl_volume->object_idx();
if (object_idx < 0 || static_cast<size_t>(object_idx) >= objects.size()) return false;
ModelObject *obj = objects[object_idx];
size_t vol_id = obj->volumes[gl_volume->volume_idx()]->id().id;
auto cond = RaycastManager::AllowVolumes({vol_id});
RaycastManager::Meshes meshes = create_meshes(canvas, cond);
raycaster.actualize(*obj, &cond, &meshes);
const Camera &camera = plater->get_camera();
std::optional<RaycastManager::Hit> hit = ray_from_camera(raycaster, screen_coor, camera, &cond);
std::optional<Transform3d> transform = create_volume_transformation_on_surface(
screen_coor, camera, *volume, *instance, raycaster, canvas, fp.distance, fp.angle);
// 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())
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;
// priv::reset_skew(hit_to_world);
Transform3d instance = gl_volume->get_instance_transformation().get_matrix();
// Create result volume transformation
Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit);
// TODO: find better way !!!
const FontProp &fp = static_cast<TextDataBase *>(emboss_data.get())->text_configuration.style.prop;
apply_transformation(fp, surface_trmat);
// new transformation in world coor is surface_trmat
Transform3d volume_trmat = instance.inverse() * surface_trmat;
start_create_volume_job(obj, volume_trmat, std::move(emboss_data), volume_type);
return true;
}
void priv::find_closest_volume(const Selection &selection,
const Vec2d &screen_center,
const Camera &camera,
const ModelObjectPtrs &objects,
Vec2d *closest_center,
const GLVolume **closest_volume)
{
assert(closest_center != nullptr);
assert(closest_volume != nullptr);
assert(*closest_volume == nullptr);
const Selection::IndicesList &indices = selection.get_volume_idxs();
assert(!indices.empty()); // no selected volume
if (indices.empty()) return;
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_volume = gl_volume;
}
// 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)

View File

@ -16,7 +16,6 @@
#include "libslic3r/Emboss.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TextConfiguration.hpp"
#include <imgui/imgui.h>

View File

@ -14,9 +14,13 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
#include "slic3r/GUI/Selection.hpp"
#include "slic3r/GUI/CameraUtils.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/Jobs/Worker.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/RaycastManager.hpp"
using namespace Slic3r;
using namespace Slic3r::Emboss;
@ -168,6 +172,13 @@ void CreateObjectJob::process(Ctl &ctl)
if (!priv::check(m_input))
throw std::runtime_error("Bad input data for EmbossCreateObjectJob.");
if (m_input.base->shape.distance.has_value())
m_input.base->shape.distance.reset();
// can't create new object with using surface
if (m_input.base->shape.use_surface)
m_input.base->shape.use_surface = false;
auto was_canceled = priv::was_canceled(ctl, *m_input.base);
m_result = priv::create_mesh(*m_input.base, was_canceled, ctl);
if (was_canceled()) return;
@ -277,36 +288,6 @@ void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
priv::update_volume(std::move(m_result), m_input);
}
namespace Slic3r::GUI::Emboss {
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)
{
if (text_volume == nullptr) return {};
if (!text_volume->text_configuration.has_value()) return {};
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);
}
} // namespace Slic3r::GUI::Emboss
/////////////////
/// Create Surface volume
CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input)
@ -354,6 +335,147 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
priv::update_volume(std::move(m_result), m_input, tr);
}
namespace Slic3r::GUI::Emboss {
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)
{
if (text_volume == nullptr)
return {};
if (!text_volume->text_configuration.has_value())
return {};
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);
}
bool start_create_volume_job(
Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type)
{
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));
}
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)
{
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())
return {};
// 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);
return instance.get_matrix().inverse() * surface_trmat;
}
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);
}
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;
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;
}
} // namespace Slic3r::GUI::Emboss
////////////////////////////
/// private namespace implementation
bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface)

View File

@ -6,14 +6,28 @@
#include <string>
#include "libslic3r/Emboss.hpp"
#include "libslic3r/EmbossShape.hpp"
#include "libslic3r/Point.hpp" // Transform3d
#include "slic3r/Utils/RaycastManager.hpp"
#include "slic3r/GUI/Jobs/EmbossJob.hpp" // Emboss::DataBase
#include "slic3r/GUI/Camera.hpp"
#include "Job.hpp"
// forward declarations
namespace Slic3r {
class GLVolume;
class ModelVolume;
class ModelObject;
class TriangleMesh;
}
typedef std::vector<ModelObject *> ModelObjectPtrs;
typedef std::vector<ModelVolume *> ModelVolumePtrs;
namespace GUI {
class Selection;
class RaycastManager;
class Worker;
}}
namespace Slic3r::GUI::Emboss {
@ -232,22 +246,6 @@ public:
/// </summary>
struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData{};
/// <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>
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </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);
/// <summary>
/// Update text volume to use surface from object
/// </summary>
@ -263,6 +261,75 @@ public:
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <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>
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </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);
using DataBasePtr = std::unique_ptr<DataBase>;
/// <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</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);
} // namespace Slic3r::GUI
#endif // slic3r_EmbossJob_hpp_