mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 13:39:00 +08:00
ReWork DataBase for embossing to prepare for SVG.
Remove dependency on Emboss inside of Object list Hide variable SHAPE_SCALE to one .cpp file only
This commit is contained in:
parent
24c791b29d
commit
c2f348ee19
@ -449,16 +449,21 @@ ProjectionDistances choose_best_distance(
|
||||
/// </summary>
|
||||
/// <param name="best_distances">For each point selected closest distance</param>
|
||||
/// <param name="patches">All patches</param>
|
||||
/// <param name="shapes">All patches</param>
|
||||
/// <param name="shapes">Shape to cut</param>
|
||||
/// <param name="shapes_bb">Bound of shapes</param>
|
||||
/// <param name="s2i"></param>
|
||||
/// <param name="cutAOIs"></param>
|
||||
/// <param name="meshes"></param>
|
||||
/// <param name="projection"></param>
|
||||
/// <returns>Mask of used patch</returns>
|
||||
std::vector<bool> select_patches(const ProjectionDistances &best_distances,
|
||||
const SurfacePatches &patches,
|
||||
|
||||
const ExPolygons &shapes,
|
||||
const ExPolygonsIndices &s2i,
|
||||
const VCutAOIs &cutAOIs,
|
||||
const CutMeshes &meshes,
|
||||
const Project &projection);
|
||||
const ExPolygons &shapes,
|
||||
const BoundingBox &shapes_bb,
|
||||
const ExPolygonsIndices &s2i,
|
||||
const VCutAOIs &cutAOIs,
|
||||
const CutMeshes &meshes,
|
||||
const Project &projection);
|
||||
|
||||
/// <summary>
|
||||
/// Merge two surface cuts together
|
||||
@ -601,8 +606,7 @@ SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes,
|
||||
// Use only outline points
|
||||
// for each point select best projection
|
||||
priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches);
|
||||
std::vector<bool> use_patch = priv::select_patches(best_projection, patches,
|
||||
shapes, s2i,model_cuts, cgal_models, projection);
|
||||
std::vector<bool> use_patch = priv::select_patches(best_projection, patches, shapes, shapes_bb, s2i, model_cuts, cgal_models, projection);
|
||||
SurfaceCut result = merge_patches(patches, use_patch);
|
||||
//*/
|
||||
|
||||
@ -3194,15 +3198,17 @@ bool priv::is_over_whole_expoly(const CutAOI &cutAOI,
|
||||
|
||||
std::vector<bool> priv::select_patches(const ProjectionDistances &best_distances,
|
||||
const SurfacePatches &patches,
|
||||
|
||||
const ExPolygons &shapes,
|
||||
const ExPolygonsIndices &s2i,
|
||||
const VCutAOIs &cutAOIs,
|
||||
const CutMeshes &meshes,
|
||||
const Project &projection)
|
||||
const ExPolygons &shapes,
|
||||
const BoundingBox &shapes_bb,
|
||||
const ExPolygonsIndices &s2i,
|
||||
const VCutAOIs &cutAOIs,
|
||||
const CutMeshes &meshes,
|
||||
const Project &projection)
|
||||
{
|
||||
// extension to cover numerical mistake made by back projection patch from 3d to 2d
|
||||
const float extend_delta = 5.f / Emboss::SHAPE_SCALE; // [Font points scaled by Emboss::SHAPE_SCALE]
|
||||
// Calculated as one percent of average size(width and height)
|
||||
Point s = shapes_bb.size();
|
||||
const float extend_delta = (s.x() + s.y())/ float(2 * 100);
|
||||
|
||||
// vector of patches for shape
|
||||
std::vector<std::vector<uint32_t>> used_shapes_patches(shapes.size());
|
||||
|
@ -19,6 +19,10 @@
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value
|
||||
// stored in fonts (to be able represents curve by sequence of lines)
|
||||
static constexpr double SHAPE_SCALE = 0.001; // SCALING_FACTOR promile is fine enough
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Emboss;
|
||||
using fontinfo_opt = std::optional<stbtt_fontinfo>;
|
||||
@ -1358,12 +1362,12 @@ std::string Emboss::create_range_text(const std::string &text,
|
||||
return boost::nowide::narrow(ws);
|
||||
}
|
||||
|
||||
double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff)
|
||||
double Emboss::get_text_shape_scale(const FontProp &fp, const FontFile &ff)
|
||||
{
|
||||
const auto &cn = fp.collection_number;
|
||||
const std::optional<unsigned int> &cn = fp.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
int unit_per_em = ff.infos[font_index].unit_per_em;
|
||||
double scale = fp.size_in_mm / unit_per_em;
|
||||
int unit_per_em = ff.infos[font_index].unit_per_em;
|
||||
double scale = fp.size_in_mm / unit_per_em;
|
||||
// Shape is scaled for store point coordinate as integer
|
||||
return scale * SHAPE_SCALE;
|
||||
}
|
||||
@ -1523,10 +1527,7 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
|
||||
|
||||
std::pair<Vec3d, Vec3d> Emboss::ProjectZ::create_front_back(const Point &p) const
|
||||
{
|
||||
Vec3d front(
|
||||
p.x() * SHAPE_SCALE,
|
||||
p.y() * SHAPE_SCALE,
|
||||
0.);
|
||||
Vec3d front(p.x(), p.y(), 0.);
|
||||
return std::make_pair(front, project(front));
|
||||
}
|
||||
|
||||
@ -1538,8 +1539,7 @@ Vec3d Emboss::ProjectZ::project(const Vec3d &point) const
|
||||
}
|
||||
|
||||
std::optional<Vec2d> Emboss::ProjectZ::unproject(const Vec3d &p, double *depth) const {
|
||||
if (depth != nullptr) *depth /= SHAPE_SCALE;
|
||||
return Vec2d(p.x() / SHAPE_SCALE, p.y() / SHAPE_SCALE);
|
||||
return Vec2d(p.x(), p.y());
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,10 +18,6 @@ namespace Slic3r {
|
||||
/// </summary>
|
||||
namespace Emboss
|
||||
{
|
||||
// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value
|
||||
// stored in fonts (to be able represents curve by sequence of lines)
|
||||
static constexpr double SHAPE_SCALE = 0.001; // SCALING_FACTOR promile is fine enough
|
||||
|
||||
/// <summary>
|
||||
/// Collect fonts registred inside OS
|
||||
/// </summary>
|
||||
@ -220,7 +216,7 @@ namespace Emboss
|
||||
/// <param name="fp">Property of font</param>
|
||||
/// <param name="ff">Font data</param>
|
||||
/// <returns>Conversion to mm</returns>
|
||||
double get_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||
double get_text_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||
|
||||
/// <summary>
|
||||
/// Project spatial point
|
||||
|
@ -18,13 +18,14 @@ namespace Slic3r {
|
||||
struct EmbossShape
|
||||
{
|
||||
// shape defined by integer point consist only by lines not curves
|
||||
ExPolygons shape;
|
||||
ExPolygons shapes;
|
||||
|
||||
// scale of shape, multiplier to get 3d point in mm from integer shape
|
||||
double shape_scale;
|
||||
double scale;
|
||||
|
||||
// Emboss depth
|
||||
double depth; // [in world mm]
|
||||
// Emboss depth, Size in local Z direction
|
||||
double depth; // [in loacal mm]
|
||||
// NOTE: User should see and modify mainly world size not local
|
||||
|
||||
// Flag that result volume use surface cutted from source objects
|
||||
bool use_surface = false;
|
||||
@ -32,6 +33,7 @@ struct EmbossShape
|
||||
// distance from surface point
|
||||
// used for move over model surface
|
||||
// When not set value is zero and is not stored
|
||||
// NOTE: Can't be used together with use_surface
|
||||
std::optional<float> distance; // [in mm]
|
||||
|
||||
// !!! Volume stored in .3mf has transformed vertices.
|
||||
@ -42,18 +44,19 @@ struct EmbossShape
|
||||
std::optional<Slic3r::Transform3d> fix_3mf_tr;
|
||||
|
||||
// file(.svg) path to source of shape
|
||||
// When empty can't reload from disk
|
||||
std::string svg_file_path;
|
||||
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
ar(shape, shape_scale, depth, use_surface, svg_file_path);
|
||||
ar(shapes, scale, depth, use_surface, svg_file_path);
|
||||
cereal::save(ar, distance);
|
||||
cereal::save(ar, fix_3mf_tr);
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
ar(shape, shape_scale, depth, use_surface, svg_file_path);
|
||||
ar(shapes, scale, depth, use_surface, svg_file_path);
|
||||
cereal::load(ar, distance);
|
||||
cereal::load(ar, fix_3mf_tr);
|
||||
}
|
||||
|
@ -1833,12 +1833,7 @@ void ObjectList::load_shape_object_from_gallery(const wxArrayString& input_files
|
||||
wxGetApp().mainframe->update_title();
|
||||
}
|
||||
|
||||
void ObjectList::load_mesh_object(
|
||||
const TriangleMesh & mesh,
|
||||
const std::string & name,
|
||||
bool center,
|
||||
const TextConfiguration *text_config /* = nullptr*/,
|
||||
const Transform3d * transformation /* = nullptr*/)
|
||||
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center)
|
||||
{
|
||||
PlaterAfterLoadAutoArrange plater_after_load_auto_arrange;
|
||||
// Add mesh to model as a new object
|
||||
@ -1848,7 +1843,6 @@ void ObjectList::load_mesh_object(
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
std::vector<size_t> object_idxs;
|
||||
ModelObject* new_object = model.add_object();
|
||||
new_object->name = name;
|
||||
new_object->add_instance(); // each object should have at list one instance
|
||||
@ -1856,31 +1850,24 @@ void ObjectList::load_mesh_object(
|
||||
ModelVolume* new_volume = new_object->add_volume(mesh);
|
||||
new_object->sort_volumes(wxGetApp().app_config->get_bool("order_volumes"));
|
||||
new_volume->name = name;
|
||||
if (text_config)
|
||||
new_volume->text_configuration = *text_config;
|
||||
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
new_object->invalidate_bounding_box();
|
||||
if (transformation) {
|
||||
assert(!center);
|
||||
Slic3r::Geometry::Transformation tr(*transformation);
|
||||
new_object->instances[0]->set_transformation(tr);
|
||||
} else {
|
||||
auto bb = mesh.bounding_box();
|
||||
new_object->translate(-bb.center());
|
||||
new_object->instances[0]->set_offset(
|
||||
center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) :
|
||||
bb.center());
|
||||
}
|
||||
|
||||
auto bb = mesh.bounding_box();
|
||||
new_object->translate(-bb.center());
|
||||
new_object->instances[0]->set_offset(
|
||||
center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) :
|
||||
bb.center());
|
||||
|
||||
new_object->ensure_on_bed();
|
||||
|
||||
object_idxs.push_back(model.objects.size() - 1);
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
paste_objects_into_list(object_idxs);
|
||||
|
||||
paste_objects_into_list({model.objects.size() - 1});
|
||||
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
|
@ -27,7 +27,6 @@ class ModelConfig;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class TriangleMesh;
|
||||
struct TextConfiguration;
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
// FIXME: broken build on mac os because of this is missing:
|
||||
@ -258,8 +257,7 @@ public:
|
||||
void load_shape_object(const std::string &type_name);
|
||||
void load_shape_object_from_gallery();
|
||||
void load_shape_object_from_gallery(const wxArrayString& input_files);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true,
|
||||
const TextConfiguration* text_config = nullptr, const Transform3d* transformation = nullptr);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true);
|
||||
bool del_object(const int obj_idx);
|
||||
bool del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||
|
@ -107,6 +107,67 @@ 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
|
||||
@ -122,7 +183,8 @@ static bool is_valid(ModelVolumeType volume_type);
|
||||
/// <param name="style_manager">Keep actual selected style</param>
|
||||
/// <param name="cancel">Cancel for previous job</param>
|
||||
/// <returns>Base data for emboss text</returns>
|
||||
static DataBase create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>> &cancel);
|
||||
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
|
||||
@ -133,7 +195,7 @@ static DataBase create_emboss_data_base(const std::string &text, StyleManager &s
|
||||
/// <param name="volume_type">Type of volume</param>
|
||||
static void start_create_volume_job(const ModelObject *object,
|
||||
const Transform3d volume_trmat,
|
||||
DataBase &emboss_data,
|
||||
std::unique_ptr<DataBase> emboss_data,
|
||||
ModelVolumeType volume_type);
|
||||
|
||||
/// <summary>
|
||||
@ -146,7 +208,7 @@ 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(DataBase &emboss_data,
|
||||
static bool start_create_volume_on_surface_job(std::unique_ptr<DataBase> emboss_data,
|
||||
ModelVolumeType volume_type,
|
||||
const Vec2d &screen_coor,
|
||||
const GLVolume *gl_volume,
|
||||
@ -174,7 +236,7 @@ static void find_closest_volume(const Selection &selection,
|
||||
/// </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(DataBase &emboss_data, const Vec2d &coor);
|
||||
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()
|
||||
@ -209,17 +271,17 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous
|
||||
set_default_text();
|
||||
|
||||
GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent);
|
||||
DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
|
||||
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(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) {
|
||||
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 {
|
||||
// object is not under mouse position soo create object on plater
|
||||
priv::start_create_object_job(emboss_data, mouse_pos);
|
||||
priv::start_create_object_job(std::move(emboss_data), mouse_pos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,13 +298,13 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
|
||||
|
||||
Size s = m_parent.get_canvas_size();
|
||||
Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.);
|
||||
DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
|
||||
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(emboss_data, screen_center);
|
||||
priv::start_create_object_job(std::move(emboss_data), screen_center);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -252,15 +314,18 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
|
||||
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(emboss_data, screen_center);
|
||||
} else if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager, m_parent)) {
|
||||
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
|
||||
// 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
|
||||
FontProp &prop = emboss_data.text_configuration.style.prop;
|
||||
if (prop.use_surface)
|
||||
prop.use_surface = false;
|
||||
if (fp.use_surface)
|
||||
fp.use_surface = false;
|
||||
|
||||
// Transformation is inspired add generic volumes in ObjectList::load_generic_subobject
|
||||
const ModelObject *obj = objects[vol->object_idx()];
|
||||
@ -268,11 +333,11 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
|
||||
// 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 - prop.size_in_mm / 2, // under
|
||||
prop.emboss / 2 - instance_bb.size().z() / 2 // lay on bed
|
||||
- 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, emboss_data, volume_type);
|
||||
priv::start_create_volume_job(obj, volume_trmat, std::move(emboss_data), volume_type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1064,8 +1129,10 @@ bool GLGizmoEmboss::process()
|
||||
|
||||
std::unique_ptr<Job> job = nullptr;
|
||||
|
||||
// check cutting from source mesh
|
||||
bool &use_surface = data.text_configuration.style.prop.use_surface;
|
||||
// check cutting from source mesh
|
||||
// TODO: do it better way
|
||||
FontProp &fp = static_cast<TextDataBase *>(data.base.get())->text_configuration.style.prop;
|
||||
bool &use_surface = fp.use_surface;
|
||||
bool is_object = m_volume->get_object()->volumes.size() == 1;
|
||||
if (use_surface && is_object)
|
||||
use_surface = false;
|
||||
@ -3271,15 +3338,16 @@ bool GLGizmoEmboss::is_text_object(const ModelVolume *text) {
|
||||
|
||||
bool priv::is_valid(ModelVolumeType volume_type)
|
||||
{
|
||||
if (volume_type == ModelVolumeType::MODEL_PART || volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
|
||||
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;
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't create embossed text with this type: " << (int) volume_type;
|
||||
return false;
|
||||
}
|
||||
|
||||
DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>>& cancel)
|
||||
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
|
||||
std::string volume_name = text; // copy
|
||||
@ -3297,7 +3365,6 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st
|
||||
// volume must store valid path
|
||||
assert(style_manager.get_wx_font().IsOk());
|
||||
assert(es.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0);
|
||||
TextConfiguration tc{es, text};
|
||||
|
||||
// Cancel previous Job, when it is in process
|
||||
// worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs
|
||||
@ -3306,10 +3373,14 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st
|
||||
cancel->store(true);
|
||||
// create new shared ptr to cancel new job
|
||||
cancel = std::make_shared<std::atomic<bool>>(false);
|
||||
return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), tc, volume_name, cancel};
|
||||
|
||||
DataBase base(volume_name, cancel);
|
||||
FontFileWithCache &font = style_manager.get_font_file_with_cache();
|
||||
TextConfiguration tc{es, text};
|
||||
return std::make_unique<TextDataBase>(std::move(base), font, std::move(tc));
|
||||
}
|
||||
|
||||
void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor)
|
||||
void priv::start_create_object_job(std::unique_ptr<DataBase> emboss_data, const Vec2d &coor)
|
||||
{
|
||||
// start creation of new object
|
||||
Plater *plater = wxGetApp().plater();
|
||||
@ -3317,12 +3388,13 @@ void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor)
|
||||
const Pointfs &bed_shape = plater->build_volume().bed_shape();
|
||||
|
||||
// can't create new object with distance from surface
|
||||
FontProp &prop = emboss_data.text_configuration.style.prop;
|
||||
if (prop.distance.has_value()) prop.distance.reset();
|
||||
// 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 (prop.use_surface)
|
||||
prop.use_surface = false;
|
||||
if (fp.use_surface)
|
||||
fp.use_surface = false;
|
||||
|
||||
// Transform3d volume_tr = priv::create_transformation_on_bed(mouse_pos, camera, bed_shape, prop.emboss / 2);
|
||||
DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape};
|
||||
@ -3333,10 +3405,13 @@ void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor)
|
||||
|
||||
void priv::start_create_volume_job(const ModelObject *object,
|
||||
const Transform3d volume_trmat,
|
||||
DataBase &emboss_data,
|
||||
std::unique_ptr<DataBase> emboss_data,
|
||||
ModelVolumeType volume_type)
|
||||
{
|
||||
bool &use_surface = emboss_data.text_configuration.style.prop.use_surface;
|
||||
// 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.
|
||||
@ -3348,7 +3423,7 @@ void priv::start_create_volume_job(const ModelObject *object,
|
||||
// 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(emboss_data), std::move(sfvd), volume_type, object->id()};
|
||||
CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(emboss_data), volume_type, object->id()};
|
||||
job = std::make_unique<CreateSurfaceVolumeJob>(std::move(surface_data));
|
||||
}
|
||||
}
|
||||
@ -3363,8 +3438,12 @@ void priv::start_create_volume_job(const ModelObject *object,
|
||||
queue_job(worker, std::move(job));
|
||||
}
|
||||
|
||||
bool priv::start_create_volume_on_surface_job(
|
||||
DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster, GLCanvas3D& canvas)
|
||||
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,
|
||||
RaycastManager &raycaster,
|
||||
GLCanvas3D &canvas)
|
||||
{
|
||||
assert(gl_volume != nullptr);
|
||||
if (gl_volume == nullptr) return false;
|
||||
@ -3395,11 +3474,14 @@ bool priv::start_create_volume_on_surface_job(
|
||||
|
||||
// Create result volume transformation
|
||||
Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit);
|
||||
const FontProp &font_prop = emboss_data.text_configuration.style.prop;
|
||||
apply_transformation(font_prop, surface_trmat);
|
||||
|
||||
// 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, emboss_data, volume_type);
|
||||
start_create_volume_job(obj, volume_trmat, std::move(emboss_data), volume_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -41,9 +41,6 @@ static const struct Limits
|
||||
// distance text object from surface
|
||||
MinMax<float> angle{-180.f, 180.f}; // in degrees
|
||||
} limits;
|
||||
|
||||
static std::string choose_svg_file();
|
||||
static std::string get_file_name(const std::string &file_path);
|
||||
} // namespace priv
|
||||
|
||||
GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent)
|
||||
@ -56,6 +53,34 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent)
|
||||
m_rotate_gizmo.set_force_local_coordinate(true);
|
||||
}
|
||||
|
||||
// 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>
|
||||
/// Separate file name from file path.
|
||||
/// String after last delimiter and before last point
|
||||
/// </summary>
|
||||
/// <param name="file_path">path return by file dialog</param>
|
||||
/// <returns>File name without directory path</returns>
|
||||
static std::string get_file_name(const std::string &file_path);
|
||||
|
||||
|
||||
} // namespace priv
|
||||
|
||||
|
||||
void GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos){
|
||||
std::string path = priv::choose_svg_file();
|
||||
if (path.empty()) return;
|
||||
@ -594,30 +619,70 @@ 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())
|
||||
return file_path;
|
||||
|
||||
size_t pos_last_delimiter = file_path.find_last_of("/\\");
|
||||
size_t pos_point = file_path.find_last_of('.');
|
||||
size_t offset = pos_last_delimiter + 1;
|
||||
size_t count = pos_point - pos_last_delimiter - 1;
|
||||
if (pos_last_delimiter == std::string::npos) {
|
||||
// should not happend that in path is not delimiter
|
||||
assert(false);
|
||||
pos_last_delimiter = 0;
|
||||
}
|
||||
|
||||
size_t pos_point = file_path.find_last_of('.');
|
||||
if (pos_point == std::string::npos ||
|
||||
pos_point < pos_last_delimiter // last point is inside of directory path
|
||||
) {
|
||||
// there is no extension
|
||||
assert(false);
|
||||
pos_point = file_path.size();
|
||||
}
|
||||
|
||||
size_t offset = pos_last_delimiter + 1; // result should not contain last delimiter ( +1 )
|
||||
size_t count = pos_point - pos_last_delimiter - 1; // result should not contain extension point ( -1 )
|
||||
return file_path.substr(offset, count);
|
||||
}
|
||||
|
||||
std::string priv::choose_svg_file()
|
||||
{
|
||||
wxArrayString input_files;
|
||||
wxWindow* parent = nullptr;
|
||||
wxString message = _L("Choose SVG file for emboss:");
|
||||
wxString defaultDir = wxEmptyString;
|
||||
wxString selectedFile = wxEmptyString;
|
||||
|
||||
wxFileDialog dialog(nullptr, _L("Choose SVG file:"), defaultDir,
|
||||
selectedFile, file_wildcards(FT_SVG),
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files);
|
||||
if (input_files.IsEmpty())
|
||||
wxString wildcard = file_wildcards(FT_SVG);
|
||||
long style = wxFD_OPEN | wxFD_FILE_MUST_EXIST;
|
||||
wxFileDialog dialog(parent, message, defaultDir, selectedFile, wildcard, style);
|
||||
if (dialog.ShowModal() != wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SVG file for emboss was NOT selected.";
|
||||
return {};
|
||||
}
|
||||
|
||||
wxArrayString input_files;
|
||||
dialog.GetPaths(input_files);
|
||||
if (input_files.IsEmpty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result is empty.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (input_files.size() != 1)
|
||||
return {};
|
||||
BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result contain multiple files but only first is used.";
|
||||
|
||||
auto &input_file = input_files.front();
|
||||
std::string path = std::string(input_file.c_str());
|
||||
|
@ -37,7 +37,7 @@ static void call_after_if_active(std::function<void()> fn, GUI_App* app = &wxGet
|
||||
});
|
||||
}
|
||||
|
||||
static std::set<ObjectID> get_volume_ids(const Selection &selection)
|
||||
static std::set<ObjectID> get_selected_volume_ids(const Selection &selection)
|
||||
{
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
@ -64,20 +64,6 @@ static std::set<ObjectID> get_volume_ids(const Selection &selection)
|
||||
return result;
|
||||
}
|
||||
|
||||
// return ModelVolume from selection by object id
|
||||
static ModelVolume *get_volume(const ObjectID &id, const Selection &selection) {
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
for (auto volume_id : volume_ids) {
|
||||
const GLVolume *selected_volume = selection.get_volume(volume_id);
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
ModelObject *obj = model_objects[cid.object_id];
|
||||
ModelVolume *volume = obj->volumes[cid.volume_id];
|
||||
if (id == volume->id()) return volume;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::string create_volumes_name(const std::set<ObjectID>& ids, const Selection &selection){
|
||||
assert(!ids.empty());
|
||||
std::string name;
|
||||
@ -88,7 +74,7 @@ static std::string create_volumes_name(const std::set<ObjectID>& ids, const Sele
|
||||
else
|
||||
name += " + ";
|
||||
|
||||
const ModelVolume *volume = get_volume(id, selection);
|
||||
const ModelVolume *volume = get_selected_volume(id, selection);
|
||||
assert(volume != nullptr);
|
||||
name += volume->name;
|
||||
}
|
||||
@ -181,7 +167,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
{
|
||||
create_gui_cfg();
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
auto act_volume_ids = get_volume_ids(selection);
|
||||
auto act_volume_ids = get_selected_volume_ids(selection);
|
||||
if (act_volume_ids.empty()) {
|
||||
stop_worker_thread_request();
|
||||
close();
|
||||
@ -472,7 +458,7 @@ void GLGizmoSimplify::process()
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
State::Data its;
|
||||
for (const auto &id : m_volume_ids) {
|
||||
const ModelVolume *volume = get_volume(id, selection);
|
||||
const ModelVolume *volume = get_selected_volume(id, selection);
|
||||
its[id] = std::make_unique<indexed_triangle_set>(volume->mesh().its); // copy
|
||||
}
|
||||
|
||||
@ -549,7 +535,7 @@ void GLGizmoSimplify::apply_simplify() {
|
||||
for (const auto &item: m_state.result) {
|
||||
const ObjectID &id = item.first;
|
||||
const indexed_triangle_set &its = *item.second;
|
||||
ModelVolume *volume = get_volume(id, selection);
|
||||
ModelVolume *volume = get_selected_volume(id, selection);
|
||||
assert(volume != nullptr);
|
||||
ModelObject *obj = volume->get_object();
|
||||
|
||||
@ -725,7 +711,7 @@ void GLGizmoSimplify::on_render()
|
||||
const Selection & selection = m_parent.get_selection();
|
||||
|
||||
// Check that the GLVolume still belongs to the ModelObject we work on.
|
||||
if (m_volume_ids != get_volume_ids(selection)) return;
|
||||
if (m_volume_ids != get_selected_volume_ids(selection)) return;
|
||||
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
const Selection::IndicesList &volume_idxs = selection.get_volume_idxs();
|
||||
|
@ -46,10 +46,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
|
||||
for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min);
|
||||
|
||||
// calculate conversion from FontPoint to screen pixels by size of font
|
||||
const auto &cn = item.prop.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
double unit_per_em = item.font.font_file->infos[font_index].unit_per_em;
|
||||
double scale = item.prop.size_in_mm / unit_per_em * SHAPE_SCALE * m_input.ppm;
|
||||
double scale = get_text_shape_scale(item.prop, *item.font.font_file);
|
||||
scales[index] = scale;
|
||||
|
||||
//double scale = font_prop.size_in_mm * SCALING_FACTOR;
|
||||
|
@ -40,8 +40,6 @@ bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surfac
|
||||
bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||
bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||
|
||||
template<typename Fnc> static ExPolygons create_shape(DataBase &input, Fnc was_canceled);
|
||||
|
||||
// <summary>
|
||||
/// Try to create mesh from text
|
||||
/// </summary>
|
||||
@ -68,6 +66,13 @@ static TriangleMesh create_default_mesh();
|
||||
/// <param name="mesh">Transformation of volume</param>
|
||||
static void update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d *tr = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Update name in right panel
|
||||
/// </summary>
|
||||
/// <param name="obj_list">Right panel data</param>
|
||||
/// <param name="volume">Volume with just changed name</param>
|
||||
static void update_name_in_list(const ObjectList &object_list, const ModelVolume &volume);
|
||||
|
||||
/// <summary>
|
||||
/// Add new volume to object
|
||||
/// </summary>
|
||||
@ -109,7 +114,7 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr
|
||||
/// <summary>
|
||||
/// Cut surface into triangle mesh
|
||||
/// </summary>
|
||||
/// <param name="input1">(can't be const - cache of font)</param>
|
||||
/// <param name="base">(can't be const - cache of font)</param>
|
||||
/// <param name="input2">SurfaceVolume data</param>
|
||||
/// <param name="was_canceled">Check to interupt execution</param>
|
||||
/// <returns>Extruded object from cuted surace</returns>
|
||||
@ -122,50 +127,49 @@ static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &in
|
||||
class JobException : public std::runtime_error {
|
||||
public: JobException(const char* message):runtime_error(message){}};
|
||||
|
||||
auto was_canceled(Job::Ctl &ctl, DataBase &base){
|
||||
return [&ctl, &cancel = base.cancel]() -> bool {
|
||||
if (cancel->load())
|
||||
return true;
|
||||
return ctl.was_canceled();
|
||||
};
|
||||
}
|
||||
|
||||
}// namespace priv
|
||||
|
||||
/////////////////
|
||||
/// Create Volume
|
||||
CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input)
|
||||
: m_input(std::move(input))
|
||||
{
|
||||
assert(priv::check(m_input, true));
|
||||
}
|
||||
CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input): m_input(std::move(input)){ assert(priv::check(m_input, true)); }
|
||||
|
||||
void CreateVolumeJob::process(Ctl &ctl) {
|
||||
if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
|
||||
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
|
||||
m_result = priv::create_mesh(m_input, was_canceled, ctl);
|
||||
if (!priv::check(m_input))
|
||||
throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
|
||||
|
||||
m_result = priv::create_mesh(*m_input.base, priv::was_canceled(ctl, *m_input.base), ctl);
|
||||
// center result
|
||||
Vec3f c = m_result.bounding_box().center().cast<float>();
|
||||
if (!c.isApprox(Vec3f::Zero())) m_result.translate(-c);
|
||||
}
|
||||
|
||||
void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||
if (!priv::finalize(canceled, eptr, m_input))
|
||||
if (!priv::finalize(canceled, eptr, *m_input.base))
|
||||
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);
|
||||
priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base);
|
||||
}
|
||||
|
||||
|
||||
/////////////////
|
||||
/// Create Object
|
||||
CreateObjectJob::CreateObjectJob(DataCreateObject &&input)
|
||||
: m_input(std::move(input))
|
||||
{
|
||||
assert(priv::check(m_input));
|
||||
}
|
||||
CreateObjectJob::CreateObjectJob(DataCreateObject &&input): m_input(std::move(input)){ assert(priv::check(m_input)); }
|
||||
|
||||
void CreateObjectJob::process(Ctl &ctl)
|
||||
{
|
||||
if (!priv::check(m_input))
|
||||
throw std::runtime_error("Bad input data for EmbossCreateObjectJob.");
|
||||
|
||||
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
|
||||
m_result = priv::create_mesh(m_input, was_canceled, ctl);
|
||||
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;
|
||||
|
||||
// Create new object
|
||||
@ -184,7 +188,9 @@ void CreateObjectJob::process(Ctl &ctl)
|
||||
// mouse pose is out of build plate so create object in center of plate
|
||||
bed_coor = bed.centroid().cast<double>();
|
||||
|
||||
double z = m_input.text_configuration.style.prop.emboss / 2;
|
||||
// TODO: need TextConfiguration refactor to work !!!
|
||||
double z = m_input.base->shape.depth / 2;
|
||||
|
||||
Vec3d offset(bed_coor.x(), bed_coor.y(), z);
|
||||
offset -= m_result.center();
|
||||
Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z());
|
||||
@ -193,29 +199,49 @@ void CreateObjectJob::process(Ctl &ctl)
|
||||
|
||||
void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
if (!priv::finalize(canceled, eptr, m_input))
|
||||
if (!priv::finalize(canceled, eptr, *m_input.base))
|
||||
return;
|
||||
|
||||
// only for sure
|
||||
if (m_result.empty())
|
||||
return priv::create_message(_u8L("Can't create empty object."));
|
||||
|
||||
GUI_App &app = wxGetApp();
|
||||
Plater *plater = app.plater();
|
||||
ObjectList *obj_list = app.obj_list();
|
||||
GLCanvas3D *canvas = plater->canvas3D();
|
||||
|
||||
GUI_App &app = wxGetApp();
|
||||
Plater *plater = app.plater();
|
||||
plater->take_snapshot(_L("Add Emboss text object"));
|
||||
|
||||
// Create new object and change selection
|
||||
bool center = false;
|
||||
obj_list->load_mesh_object(std::move(m_result), m_input.volume_name,
|
||||
center, &m_input.text_configuration,
|
||||
&m_transformation);
|
||||
Model& model = plater->model();
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
{
|
||||
// INFO: inspiration for create object is from ObjectList::load_mesh_object()
|
||||
ModelObject *new_object = model.add_object();
|
||||
new_object->name = m_input.base->volume_name;
|
||||
new_object->add_instance(); // each object should have at list one instance
|
||||
|
||||
ModelVolume *new_volume = new_object->add_volume(std::move(m_result));
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
// write emboss data into volume
|
||||
m_input.base->write(*new_volume);
|
||||
|
||||
// set transformation
|
||||
Slic3r::Geometry::Transformation tr(m_transformation);
|
||||
new_object->instances.front()->set_transformation(tr);
|
||||
new_object->ensure_on_bed();
|
||||
|
||||
// Actualize right panel and set inside of selection
|
||||
app.obj_list()->paste_objects_into_list({model.objects.size() - 1});
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
// When add new object selection is empty.
|
||||
// When cursor move and no one object is selected than
|
||||
// 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);
|
||||
@ -226,22 +252,15 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
|
||||
/////////////////
|
||||
/// Update Volume
|
||||
UpdateJob::UpdateJob(DataUpdate&& input)
|
||||
: m_input(std::move(input))
|
||||
{
|
||||
assert(priv::check(m_input, true));
|
||||
}
|
||||
UpdateJob::UpdateJob(DataUpdate&& input): m_input(std::move(input)){ assert(priv::check(m_input, true)); }
|
||||
|
||||
void UpdateJob::process(Ctl &ctl)
|
||||
{
|
||||
if (!priv::check(m_input))
|
||||
throw std::runtime_error("Bad input data for EmbossUpdateJob.");
|
||||
|
||||
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
|
||||
if (cancel->load()) return true;
|
||||
return ctl.was_canceled();
|
||||
};
|
||||
m_result = priv::try_create_mesh(m_input, was_canceled);
|
||||
auto was_canceled = priv::was_canceled(ctl, *m_input.base);
|
||||
m_result = priv::try_create_mesh(*m_input.base, was_canceled);
|
||||
if (was_canceled()) return;
|
||||
if (m_result.its.empty())
|
||||
throw priv::JobException(_u8L("Created text volume is empty. Change text or font.").c_str());
|
||||
@ -253,7 +272,7 @@ void UpdateJob::process(Ctl &ctl)
|
||||
|
||||
void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
if (!priv::finalize(canceled, eptr, m_input))
|
||||
if (!priv::finalize(canceled, eptr, *m_input.base))
|
||||
return;
|
||||
priv::update_volume(std::move(m_result), m_input);
|
||||
}
|
||||
@ -299,16 +318,14 @@ CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input)
|
||||
void CreateSurfaceVolumeJob::process(Ctl &ctl) {
|
||||
if (!priv::check(m_input))
|
||||
throw std::runtime_error("Bad input data for CreateSurfaceVolumeJob.");
|
||||
// check cancelation of process
|
||||
auto was_canceled = [&ctl]() -> bool { return ctl.was_canceled(); };
|
||||
m_result = priv::cut_surface(m_input, m_input, was_canceled);
|
||||
m_result = priv::cut_surface(*m_input.base, m_input, priv::was_canceled(ctl, *m_input.base));
|
||||
}
|
||||
|
||||
void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||
if (!priv::finalize(canceled, eptr, m_input))
|
||||
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.text_tr, m_input);
|
||||
m_input.volume_type, m_input.transform, *m_input.base);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
@ -323,23 +340,17 @@ void UpdateSurfaceVolumeJob::process(Ctl &ctl)
|
||||
{
|
||||
if (!priv::check(m_input))
|
||||
throw std::runtime_error("Bad input data for UseSurfaceJob.");
|
||||
|
||||
// check cancelation of process
|
||||
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
|
||||
if (cancel->load()) return true;
|
||||
return ctl.was_canceled();
|
||||
};
|
||||
m_result = priv::cut_surface(m_input, m_input, was_canceled);
|
||||
m_result = priv::cut_surface(*m_input.base, m_input, priv::was_canceled(ctl, *m_input.base));
|
||||
}
|
||||
|
||||
void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
if (!priv::finalize(canceled, eptr, m_input))
|
||||
if (!priv::finalize(canceled, eptr, *m_input.base))
|
||||
return;
|
||||
|
||||
// when start using surface it is wanted to move text origin on surface of model
|
||||
// also when repeteadly move above surface result position should match
|
||||
Transform3d *tr = &m_input.text_tr;
|
||||
Transform3d *tr = &m_input.transform;
|
||||
priv::update_volume(std::move(m_result), m_input, tr);
|
||||
}
|
||||
|
||||
@ -348,23 +359,25 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface)
|
||||
{
|
||||
bool res = true;
|
||||
if (check_fontfile) {
|
||||
assert(input.font_file.has_value());
|
||||
res &= input.font_file.has_value();
|
||||
}
|
||||
assert(!input.text_configuration.fix_3mf_tr.has_value());
|
||||
res &= !input.text_configuration.fix_3mf_tr.has_value();
|
||||
assert(!input.text_configuration.text.empty());
|
||||
res &= !input.text_configuration.text.empty();
|
||||
//if (check_fontfile) {
|
||||
// assert(input.font_file.has_value());
|
||||
// res &= input.font_file.has_value();
|
||||
//}
|
||||
//assert(!input.text_configuration.fix_3mf_tr.has_value());
|
||||
//res &= !input.text_configuration.fix_3mf_tr.has_value();
|
||||
//assert(!input.text_configuration.text.empty());
|
||||
//res &= !input.text_configuration.text.empty();
|
||||
assert(!input.volume_name.empty());
|
||||
res &= !input.volume_name.empty();
|
||||
assert(input.text_configuration.style.prop.use_surface == use_surface);
|
||||
res &= input.text_configuration.style.prop.use_surface == use_surface;
|
||||
//assert(input.text_configuration.style.prop.use_surface == use_surface);
|
||||
//res &= input.text_configuration.style.prop.use_surface == use_surface;
|
||||
return res;
|
||||
}
|
||||
bool priv::check(const DataCreateVolume &input, bool is_main_thread) {
|
||||
bool check_fontfile = false;
|
||||
bool res = check((DataBase) input, check_fontfile);
|
||||
assert(input.base != nullptr);
|
||||
bool res = input.base != nullptr;
|
||||
res &= check(*input.base, check_fontfile);
|
||||
assert(input.volume_type != ModelVolumeType::INVALID);
|
||||
res &= input.volume_type != ModelVolumeType::INVALID;
|
||||
assert(input.object_id.id >= 0);
|
||||
@ -373,7 +386,9 @@ bool priv::check(const DataCreateVolume &input, bool is_main_thread) {
|
||||
}
|
||||
bool priv::check(const DataCreateObject &input) {
|
||||
bool check_fontfile = false;
|
||||
bool res = check((DataBase) input, check_fontfile);
|
||||
assert(input.base != nullptr);
|
||||
bool res = input.base != nullptr;
|
||||
res &= check(*input.base, check_fontfile);
|
||||
assert(input.screen_coor.x() >= 0.);
|
||||
res &= input.screen_coor.x() >= 0.;
|
||||
assert(input.screen_coor.y() >= 0.);
|
||||
@ -384,66 +399,49 @@ bool priv::check(const DataCreateObject &input) {
|
||||
}
|
||||
bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){
|
||||
bool check_fontfile = true;
|
||||
bool res = check((DataBase) input, check_fontfile, use_surface);
|
||||
assert(input.base != nullptr);
|
||||
bool res = input.base != nullptr;
|
||||
res &= check(*input.base, check_fontfile, use_surface);
|
||||
assert(input.volume_id.id >= 0);
|
||||
res &= input.volume_id.id >= 0;
|
||||
if (is_main_thread)
|
||||
assert(get_volume(wxGetApp().model().objects, input.volume_id) != nullptr);
|
||||
assert(input.cancel != nullptr);
|
||||
res &= input.cancel != nullptr;
|
||||
assert(input.base->cancel != nullptr);
|
||||
res &= input.base->cancel != nullptr;
|
||||
if (is_main_thread)
|
||||
assert(!input.cancel->load());
|
||||
assert(!input.base->cancel->load());
|
||||
return res;
|
||||
}
|
||||
bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread)
|
||||
{
|
||||
bool use_surface = true;
|
||||
bool res = check((DataBase)input, is_main_thread, use_surface);
|
||||
assert(input.base != nullptr);
|
||||
bool res = input.base != nullptr;
|
||||
res &= check(*input.base, is_main_thread, use_surface);
|
||||
assert(!input.sources.empty());
|
||||
res &= !input.sources.empty();
|
||||
return res;
|
||||
}
|
||||
bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){
|
||||
bool use_surface = true;
|
||||
bool res = check((DataUpdate)input, is_main_thread, use_surface);
|
||||
assert(input.base != nullptr);
|
||||
bool res = input.base != nullptr;
|
||||
res &= check(*input.base, is_main_thread, use_surface);
|
||||
assert(!input.sources.empty());
|
||||
res &= !input.sources.empty();
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename Fnc>
|
||||
ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) {
|
||||
FontFileWithCache &font = input.font_file;
|
||||
const TextConfiguration &tc = input.text_configuration;
|
||||
const char *text = tc.text.c_str();
|
||||
const FontProp &prop = tc.style.prop;
|
||||
|
||||
assert(font.has_value());
|
||||
if (!font.has_value())
|
||||
return {};
|
||||
|
||||
return text2shapes(font, text, prop, was_canceled);
|
||||
}
|
||||
|
||||
template<typename Fnc>
|
||||
TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled)
|
||||
TriangleMesh priv::try_create_mesh(DataBase &base, Fnc was_canceled)
|
||||
{
|
||||
ExPolygons shapes = priv::create_shape(input, was_canceled);
|
||||
if (shapes.empty()) return {};
|
||||
if (was_canceled()) return {};
|
||||
|
||||
const FontProp &prop = input.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 = input.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;
|
||||
const EmbossShape& shape = base.create_shape();
|
||||
if (shape.shapes.empty()) return {};
|
||||
float depth = shape.depth / shape.scale;
|
||||
auto projectZ = std::make_unique<ProjectZ>(depth);
|
||||
ProjectScale project(std::move(projectZ), scale);
|
||||
ProjectScale project(std::move(projectZ), shape.scale);
|
||||
if (was_canceled()) return {};
|
||||
return TriangleMesh(polygons2model(shapes, project));
|
||||
return TriangleMesh(polygons2model(shape.shapes, project));
|
||||
}
|
||||
|
||||
template<typename Fnc>
|
||||
@ -451,11 +449,8 @@ TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl)
|
||||
{
|
||||
// It is neccessary to create some shape
|
||||
// Emboss text window is opened by creation new emboss text object
|
||||
TriangleMesh result;
|
||||
if (input.font_file.has_value()) {
|
||||
result = try_create_mesh(input, was_canceled);
|
||||
if (was_canceled()) return {};
|
||||
}
|
||||
TriangleMesh result = try_create_mesh(input, was_canceled);
|
||||
if (was_canceled()) return {};
|
||||
|
||||
if (result.its.empty()) {
|
||||
result = priv::create_default_mesh();
|
||||
@ -483,16 +478,13 @@ TriangleMesh priv::create_default_mesh()
|
||||
return triangle_mesh;
|
||||
}
|
||||
|
||||
void UpdateJob::update_volume(ModelVolume *volume,
|
||||
TriangleMesh &&mesh,
|
||||
const TextConfiguration &text_configuration,
|
||||
const std::string &volume_name)
|
||||
void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base)
|
||||
{
|
||||
// check inputs
|
||||
bool is_valid_input =
|
||||
volume != nullptr &&
|
||||
!mesh.empty() &&
|
||||
!volume_name.empty();
|
||||
!base.volume_name.empty();
|
||||
assert(is_valid_input);
|
||||
if (!is_valid_input) return;
|
||||
|
||||
@ -501,24 +493,17 @@ void UpdateJob::update_volume(ModelVolume *volume,
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
volume->get_object()->invalidate_bounding_box();
|
||||
volume->text_configuration = text_configuration;
|
||||
|
||||
// discard information about rotation, should not be stored in volume
|
||||
volume->text_configuration->style.prop.angle.reset();
|
||||
// write data from base into volume
|
||||
base.write(*volume);
|
||||
|
||||
GUI_App &app = wxGetApp(); // may be move to input
|
||||
GLCanvas3D *canvas = app.plater()->canvas3D();
|
||||
const Selection &selection = canvas->get_selection();
|
||||
const GLVolume *gl_volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
int object_idx = gl_volume->object_idx();
|
||||
|
||||
if (volume->name != volume_name) {
|
||||
volume->name = volume_name;
|
||||
|
||||
// update volume name in right panel( volume / object name)
|
||||
int volume_idx = gl_volume->volume_idx();
|
||||
ObjectList *obj_list = app.obj_list();
|
||||
obj_list->update_name_in_list(object_idx, volume_idx);
|
||||
GUI_App &app = wxGetApp(); // may be move to input
|
||||
if (volume->name != base.volume_name) {
|
||||
volume->name = base.volume_name;
|
||||
|
||||
ObjectList *obj_list = app.obj_list();
|
||||
if (obj_list != nullptr)
|
||||
priv::update_name_in_list(*obj_list, *volume);
|
||||
}
|
||||
|
||||
// When text is object.
|
||||
@ -528,6 +513,8 @@ void UpdateJob::update_volume(ModelVolume *volume,
|
||||
volume->get_object()->ensure_on_bed();
|
||||
|
||||
// redraw scene
|
||||
GLCanvas3D *canvas = app.plater()->canvas3D();
|
||||
|
||||
bool refresh_immediately = false;
|
||||
canvas->reload_scene(refresh_immediately);
|
||||
|
||||
@ -535,21 +522,53 @@ void UpdateJob::update_volume(ModelVolume *volume,
|
||||
canvas->post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
|
||||
void priv::update_name_in_list(const ObjectList &object_list, const ModelVolume &volume)
|
||||
{
|
||||
const ModelObjectPtrs *objects_ptr = object_list.objects();
|
||||
if (objects_ptr == nullptr)
|
||||
return;
|
||||
|
||||
const ModelObjectPtrs &objects = *objects_ptr;
|
||||
const ModelObject *object = volume.get_object();
|
||||
const ObjectID& object_id = object->id();
|
||||
|
||||
// search for index of object
|
||||
int object_index = -1;
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
if (objects[i]->id() == object_id) {
|
||||
object_index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
const ModelVolumePtrs volumes = object->volumes;
|
||||
const ObjectID& volume_id = volume.id();
|
||||
|
||||
// search for index of volume
|
||||
int volume_index = -1;
|
||||
for (size_t i = 0; i < volumes.size(); ++i)
|
||||
if (volumes[i]->id() == volume_id) {
|
||||
volume_index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (object_index < 0 || volume_index < 0)
|
||||
return;
|
||||
|
||||
object_list.update_name_in_list(object_index, volume_index);
|
||||
}
|
||||
|
||||
void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d* tr)
|
||||
{
|
||||
// for sure that some object will be created
|
||||
if (mesh.its.empty())
|
||||
return create_message("Empty mesh can't be created.");
|
||||
|
||||
Plater *plater = wxGetApp().plater();
|
||||
GLCanvas3D *canvas = plater->canvas3D();
|
||||
Plater *plater = wxGetApp().plater();
|
||||
// Check gizmo is still open otherwise job should be canceled
|
||||
assert(plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss ||
|
||||
plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Svg);
|
||||
|
||||
// Check emboss gizmo is still open
|
||||
GLGizmosManager &manager = canvas->get_gizmos_manager();
|
||||
if (manager.get_current_type() != GLGizmosManager::Emboss)
|
||||
return;
|
||||
|
||||
std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text);
|
||||
std::string snap_name = GUI::format(_L("Change: %1%"), data.base->volume_name);
|
||||
Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction);
|
||||
ModelVolume *volume = get_volume(plater->model().objects, data.volume_id);
|
||||
|
||||
@ -567,7 +586,7 @@ void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3
|
||||
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
|
||||
}
|
||||
|
||||
UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name);
|
||||
UpdateJob::update_volume(volume, std::move(mesh), *data.base);
|
||||
}
|
||||
|
||||
void priv::create_volume(
|
||||
@ -609,20 +628,15 @@ void priv::create_volume(
|
||||
volume->set_mesh(std::move(mesh));
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
|
||||
// set a default extruder value, since user can't add it manually
|
||||
volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
|
||||
// do not allow model reload from disk
|
||||
volume->source.is_from_builtin_objects = true;
|
||||
|
||||
volume->name = data.volume_name; // copy
|
||||
volume->text_configuration = data.text_configuration; // copy
|
||||
|
||||
// discard information about rotation, should not be stored in volume
|
||||
volume->text_configuration->style.prop.angle.reset();
|
||||
|
||||
volume->name = data.volume_name; // copy
|
||||
volume->set_transformation(trmat);
|
||||
data.write(*volume);
|
||||
|
||||
// update printable state on canvas
|
||||
if (type == ModelVolumeType::MODEL_PART) {
|
||||
@ -635,7 +649,7 @@ void priv::create_volume(
|
||||
// change name of volume in right panel
|
||||
// select only actual volume
|
||||
// when new volume is created change selection to this volume
|
||||
auto add_to_selection = [volume](const ModelVolume *vol) { return vol == volume; };
|
||||
auto add_to_selection = [volume](const ModelVolume *vol) { return vol == volume; };
|
||||
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection);
|
||||
if (!sel.IsEmpty()) obj_list->select_item(sel.front());
|
||||
|
||||
@ -698,24 +712,21 @@ OrthoProject3d priv::create_emboss_projection(
|
||||
}
|
||||
|
||||
// input can't be const - cache of font
|
||||
TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
|
||||
TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
|
||||
{
|
||||
ExPolygons shapes = create_shape(input1, was_canceled);
|
||||
EmbossShape& emboss_shape = base.create_shape();
|
||||
ExPolygons& shapes = emboss_shape.shapes;
|
||||
if (shapes.empty())
|
||||
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
|
||||
|
||||
if (was_canceled()) return {};
|
||||
|
||||
// Define alignment of text - left, right, center, top bottom, ....
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
Point projection_center = bb.center();
|
||||
for (ExPolygon &shape : shapes) shape.translate(-projection_center);
|
||||
bb.translate(-projection_center);
|
||||
|
||||
const FontFile &ff = *input1.font_file.font_file;
|
||||
const FontProp &fp = input1.text_configuration.style.prop;
|
||||
double shape_scale = get_shape_scale(fp, ff);
|
||||
|
||||
const SurfaceVolumeData::ModelSources &sources = input2.sources;
|
||||
const SurfaceVolumeData::ModelSource *biggest = nullptr;
|
||||
|
||||
@ -726,9 +737,9 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
|
||||
itss.reserve(sources.size());
|
||||
for (const SurfaceVolumeData::ModelSource &s : sources) {
|
||||
Transform3d mesh_tr_inv = s.tr.inverse();
|
||||
Transform3d cut_projection_tr = mesh_tr_inv * input2.text_tr;
|
||||
Transform3d cut_projection_tr = mesh_tr_inv * input2.transform;
|
||||
std::pair<float, float> z_range{0., 1.};
|
||||
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
|
||||
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_shape.scale, z_range);
|
||||
// copy only part of source model
|
||||
indexed_triangle_set its = its_cut_AoI(s.mesh->its, bb, cut_projection);
|
||||
if (its.indices.empty()) continue;
|
||||
@ -745,7 +756,7 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
|
||||
throw JobException(_u8L("There is no volume in projection direction.").c_str());
|
||||
|
||||
Transform3d tr_inv = biggest->tr.inverse();
|
||||
Transform3d cut_projection_tr = tr_inv * input2.text_tr;
|
||||
Transform3d cut_projection_tr = tr_inv * input2.transform;
|
||||
|
||||
size_t itss_index = s_to_itss[biggest - &sources.front()];
|
||||
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
|
||||
@ -767,10 +778,10 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
|
||||
Transform3d emboss_tr = cut_projection_tr.inverse();
|
||||
BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr);
|
||||
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
|
||||
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
|
||||
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_shape.scale, z_range);
|
||||
float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension);
|
||||
|
||||
bool is_text_reflected = Slic3r::has_reflection(input2.text_tr);
|
||||
bool is_text_reflected = Slic3r::has_reflection(input2.transform);
|
||||
if (is_text_reflected) {
|
||||
// revert order of points in expolygons
|
||||
// CW --> CCW
|
||||
@ -794,8 +805,8 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
|
||||
if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str());
|
||||
if (was_canceled()) return {};
|
||||
|
||||
// !! Projection needs to transform cut
|
||||
OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, emboss_tr, cut);
|
||||
// !! Projection needs to transform cut
|
||||
OrthoProject3d projection = create_emboss_projection(input2.is_outside, emboss_shape.depth, emboss_tr, cut);
|
||||
indexed_triangle_set new_its = cut2model(cut, projection);
|
||||
assert(!new_its.empty());
|
||||
if (was_canceled()) return {};
|
||||
|
@ -4,7 +4,8 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <libslic3r/Emboss.hpp>
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
#include "libslic3r/EmbossShape.hpp"
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "Job.hpp"
|
||||
@ -17,20 +18,43 @@ class TriangleMesh;
|
||||
namespace Slic3r::GUI::Emboss {
|
||||
|
||||
/// <summary>
|
||||
/// Base data holder for embossing
|
||||
/// Base data hold data for create emboss shape
|
||||
/// </summary>
|
||||
struct DataBase
|
||||
class DataBase
|
||||
{
|
||||
// Keep pointer on Data of font (glyph shapes)
|
||||
Slic3r::Emboss::FontFileWithCache font_file;
|
||||
// font item is not used for create object
|
||||
TextConfiguration text_configuration;
|
||||
// new volume name created from text
|
||||
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)
|
||||
: volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape))
|
||||
{}
|
||||
virtual ~DataBase() {}
|
||||
|
||||
/// <summary>
|
||||
/// Create shape
|
||||
/// e.g. Text extract glyphs from font
|
||||
/// Not 'const' function because it could modify shape
|
||||
/// </summary>
|
||||
virtual EmbossShape &create_shape() { return shape; };
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
};
|
||||
|
||||
// new volume name
|
||||
std::string volume_name;
|
||||
|
||||
// flag that job is canceled
|
||||
// for time after process.
|
||||
std::shared_ptr<std::atomic<bool>> cancel;
|
||||
|
||||
// shape to emboss
|
||||
EmbossShape shape;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -38,8 +62,11 @@ struct DataBase
|
||||
/// Volume is created on the surface of existing volume in object.
|
||||
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
||||
/// </summary>
|
||||
struct DataCreateVolume : public DataBase
|
||||
struct DataCreateVolume
|
||||
{
|
||||
// Hold data about shape
|
||||
std::unique_ptr<DataBase> base;
|
||||
|
||||
// define embossed volume type
|
||||
ModelVolumeType volume_type;
|
||||
|
||||
@ -71,8 +98,11 @@ public:
|
||||
/// Object is placed on bed under screen coor
|
||||
/// OR to center of scene when it is out of bed shape
|
||||
/// </summary>
|
||||
struct DataCreateObject : public DataBase
|
||||
struct DataCreateObject
|
||||
{
|
||||
// Hold data about shape
|
||||
std::unique_ptr<DataBase> base;
|
||||
|
||||
// define position on screen where to create object
|
||||
Vec2d screen_coor;
|
||||
|
||||
@ -101,8 +131,11 @@ public:
|
||||
/// <summary>
|
||||
/// Hold neccessary data to update embossed text object in job
|
||||
/// </summary>
|
||||
struct DataUpdate : public DataBase
|
||||
struct DataUpdate
|
||||
{
|
||||
// Hold data about shape
|
||||
std::unique_ptr<DataBase> base;
|
||||
|
||||
// unique identifier of volume to change
|
||||
ObjectID volume_id;
|
||||
};
|
||||
@ -140,18 +173,14 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="volume">Volume to be updated</param>
|
||||
/// <param name="mesh">New Triangle mesh for volume</param>
|
||||
/// <param name="text_configuration">Parametric description of volume</param>
|
||||
/// <param name="volume_name">Name of volume</param>
|
||||
static void update_volume(ModelVolume *volume,
|
||||
TriangleMesh &&mesh,
|
||||
const TextConfiguration &text_configuration,
|
||||
const std::string &volume_name);
|
||||
/// <param name="base">Data to write into volume</param>
|
||||
static void update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base);
|
||||
};
|
||||
|
||||
struct SurfaceVolumeData
|
||||
{
|
||||
// Transformation of text volume inside of object
|
||||
Transform3d text_tr;
|
||||
// Transformation of volume inside of object
|
||||
Transform3d transform;
|
||||
|
||||
// Define projection move
|
||||
// True (raised) .. move outside from surface
|
||||
@ -172,7 +201,10 @@ struct SurfaceVolumeData
|
||||
/// <summary>
|
||||
/// Hold neccessary data to create(cut) volume from surface object in job
|
||||
/// </summary>
|
||||
struct CreateSurfaceVolumeData : public DataBase, public SurfaceVolumeData{
|
||||
struct CreateSurfaceVolumeData : public SurfaceVolumeData{
|
||||
// Hold data about shape
|
||||
std::unique_ptr<DataBase> base;
|
||||
|
||||
// define embossed volume type
|
||||
ModelVolumeType volume_type;
|
||||
|
||||
|
@ -3435,5 +3435,30 @@ const GLVolume *get_selected_gl_volume(const Selection &selection)
|
||||
return selection.get_volume(volume_idx);
|
||||
}
|
||||
|
||||
ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection) {
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
for (auto id : volume_ids) {
|
||||
const GLVolume *selected_volume = selection.get_volume(id);
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
ModelObject *obj = model_objects[cid.object_id];
|
||||
ModelVolume *volume = obj->volumes[cid.volume_id];
|
||||
if (volume_id == volume->id())
|
||||
return volume;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection) {
|
||||
const ModelObjectPtrs &objects = selection.get_model()->objects;
|
||||
for (const ModelObject *object : objects) {
|
||||
for (ModelVolume *volume : object->volumes) {
|
||||
if (volume->id() == volume_id)
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -18,6 +18,7 @@ class Shader;
|
||||
class Model;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class ObjectID;
|
||||
class GLVolume;
|
||||
class GLArrow;
|
||||
class GLCurvedArrow;
|
||||
@ -524,9 +525,12 @@ private:
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
};
|
||||
|
||||
ModelVolume *get_selected_volume(const Selection &selection);
|
||||
ModelVolume *get_selected_volume (const Selection &selection);
|
||||
const GLVolume *get_selected_gl_volume(const Selection &selection);
|
||||
|
||||
ModelVolume *get_selected_volume (const ObjectID &volume_id, const Selection &selection);
|
||||
ModelVolume *get_volume (const ObjectID &volume_id, const Selection &selection);
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -23,7 +23,8 @@ TEST_CASE("Cut character from surface", "[]")
|
||||
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.translate(Vec3d(0., 0., -z_depth));
|
||||
tr.scale(Emboss::SHAPE_SCALE);
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
tr.scale(text_shape_scale);
|
||||
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
|
||||
|
||||
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
|
||||
@ -158,7 +159,7 @@ TEST_CASE("CutSurface in 3mf", "[Emboss]")
|
||||
|
||||
FontProp fp = tc.style.prop;
|
||||
ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp);
|
||||
double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file);
|
||||
double shape_scale = Emboss::get_text_shape_scale(fp, *ff.font_file);
|
||||
|
||||
Emboss::OrthoProject projection = create_projection_for_cut(
|
||||
cut_projection_tr, shape_scale, get_extents(shapes), z_range);
|
||||
|
@ -258,7 +258,9 @@ TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]")
|
||||
auto a = heal_and_check(polygons);
|
||||
|
||||
Polygons scaled_shape = polygons; // copy
|
||||
scale(scaled_shape, 1 / Emboss::SHAPE_SCALE);
|
||||
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
scale(scaled_shape, 1 / text_shape_scale);
|
||||
auto b = heal_and_check(scaled_shape);
|
||||
|
||||
// different scale
|
||||
@ -468,7 +470,8 @@ TEST_CASE("Cut surface", "[]")
|
||||
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.translate(Vec3d(0., 0., -z_depth));
|
||||
tr.scale(Emboss::SHAPE_SCALE);
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
tr.scale(text_shape_scale);
|
||||
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
|
||||
|
||||
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
|
||||
@ -527,8 +530,8 @@ TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]")
|
||||
TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]")
|
||||
{
|
||||
EmbossShape emboss;
|
||||
emboss.shape = {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}};
|
||||
emboss.shape_scale = 2.;
|
||||
emboss.shapes = {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}};
|
||||
emboss.scale = 2.;
|
||||
emboss.depth = 5.;
|
||||
emboss.use_surface = true;
|
||||
emboss.distance = 3.f;
|
||||
@ -549,8 +552,8 @@ TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]")
|
||||
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
|
||||
iarchive(emboss_loaded);
|
||||
}
|
||||
CHECK(emboss.shape == emboss_loaded.shape);
|
||||
CHECK(emboss.shape_scale == emboss_loaded.shape_scale);
|
||||
CHECK(emboss.shapes == emboss_loaded.shapes);
|
||||
CHECK(emboss.scale == emboss_loaded.scale);
|
||||
CHECK(emboss.depth == emboss_loaded.depth);
|
||||
CHECK(emboss.use_surface == emboss_loaded.use_surface);
|
||||
CHECK(emboss.distance == emboss_loaded.distance);
|
||||
|
Loading…
x
Reference in New Issue
Block a user