update slic3r

This commit is contained in:
QIDI TECH
2025-08-04 16:30:53 +08:00
parent 8d4d60ec48
commit 661b112a68
287 changed files with 22250 additions and 7322 deletions

View File

@@ -194,7 +194,7 @@ void ArrangeJob::prepare_selected() {
void ArrangeJob::prepare_all() {
clear_input();
PartPlateList& plate_list = m_plater->get_partplate_list();
PartPlateList& plate_list = m_plater->get_partplate_list();
for (size_t i = 0; i < plate_list.get_plate_count(); i++) {
PartPlate* plate = plate_list.get_plate(i);
bool same_as_global_print_seq = true;
@@ -798,7 +798,7 @@ void ArrangeJob::finalize()
// Move the unprintable items to the last virtual bed.
// Note ap.apply() moves relatively according to bed_idx, so we need to subtract the orignal bed_idx
for (ArrangePolygon& ap : m_unprintable) {
ap.bed_idx = beds + 1;
ap.bed_idx = -1;
plate_list.postprocess_arrange_polygon(ap, true);
ap.apply();
@@ -836,6 +836,7 @@ void ArrangeJob::finalize()
NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging canceled."));
}
Job::finalize();
m_plater->m_arrange_running.store(false);
}

View File

@@ -0,0 +1,222 @@
#include "CreateFontNameImageJob.hpp"
// rasterization of ExPoly
#include "libslic3r/SLA/AGGRaster.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
// ability to request new frame after finish rendering
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "wx/fontenum.h"
#include <boost/log/trivial.hpp>
using namespace Slic3r;
using namespace Slic3r::GUI;
const std::string CreateFontImageJob::default_text = "AaBbCc123";
CreateFontImageJob::CreateFontImageJob(FontImageData &&input)
: m_input(std::move(input))
{
assert(wxFontEnumerator::IsValidFacename(m_input.font_name));
assert(m_input.gray_level > 0 && m_input.gray_level < 255);
assert(m_input.texture_id != 0);
}
void CreateFontImageJob::process(Ctl &ctl)
{
auto font_file_with_cache = Slic3r::GUI::BackupFonts::gener_font_with_cache(m_input.font_name, m_input.encoding);
if (!font_file_with_cache.has_value()) {
return;
}
// use only first line of text
std::string& text = m_input.text;
if (text.empty())
text = default_text; // copy
size_t enter_pos = text.find('\n');
if (enter_pos < text.size()) {
// text start with enter
if (enter_pos == 0)
return;
// exist enter, soo delete all after enter
text = text.substr(0, enter_pos);
}
std::function<bool()> was_canceled = [&ctl, cancel = m_input.cancel]() -> bool {
if (ctl.was_canceled()) return true;
if (cancel->load()) return true;
return false;
};
auto ft_fn = []() {
return Slic3r::GUI::BackupFonts::backup_fonts;
};
FontProp fp; // create default font parameters
auto &ff = *font_file_with_cache.font_file;
double standard_scale = get_text_shape_scale(fp, ff);
bool support_backup_fonts = GUI::wxGetApp().app_config->get_bool("support_backup_fonts");
EmbossShape emboss_shape;
ExPolygons shapes = support_backup_fonts ? Emboss::text2shapes(emboss_shape, font_file_with_cache, text.c_str(), fp, was_canceled, ft_fn, standard_scale):
Emboss::text2shapes(emboss_shape, font_file_with_cache, text.c_str(), fp, was_canceled);
m_input.generate_origin_text = true;
if (shapes.empty()) {// select some character from font e.g. default text
m_input.generate_origin_text = false;
shapes = Emboss::text2shapes(emboss_shape, font_file_with_cache, default_text.c_str(), fp, was_canceled, ft_fn, standard_scale);
}
if (shapes.empty()) {
m_input.cancel->store(true);
return;
}
// normalize height of font
BoundingBox bounding_box;
for (const ExPolygon &shape : shapes)
bounding_box.merge(BoundingBox(shape.contour.points));
if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1) {
m_input.cancel->store(true);
return;
}
double scale = m_input.size.y() / (double) bounding_box.size().y();
BoundingBoxf bb2(bounding_box.min.cast<double>(),
bounding_box.max.cast<double>());
bb2.scale(scale);
Vec2d size_f = bb2.size();
m_tex_size = Point(std::ceil(size_f.x()), std::ceil(size_f.y()));
// crop image width
if (m_tex_size.x() > m_input.size.x())
m_tex_size.x() = m_input.size.x();
if (m_tex_size.y() > m_input.size.y())
m_tex_size.y() = m_input.size.y();
// Set up result
unsigned bit_count = 4; // RGBA
m_result = std::vector<unsigned char>(m_tex_size.x() * m_tex_size.y() * bit_count, {255});
sla::Resolution resolution(m_tex_size.x(), m_tex_size.y());
double pixel_dim = SCALING_FACTOR / scale;
sla::PixelDim dim(pixel_dim, pixel_dim);
double gamma = 1.;
std::unique_ptr<sla::RasterBase> raster = sla::create_raster_grayscale_aa(resolution, dim, gamma);
for (ExPolygon &shape : shapes)
shape.translate(-bounding_box.min);
for (const ExPolygon &shape : shapes)
raster->draw(shape);
// copy rastered data to pixels
sla::RasterEncoder encoder =
[&pix = m_result, w = m_tex_size.x(), h = m_tex_size.y(),
gray_level = m_input.gray_level]
(const void *ptr, size_t width, size_t height, size_t num_components) {
size_t size {static_cast<size_t>(w*h)};
const unsigned char *ptr2 = (const unsigned char *) ptr;
for (size_t x = 0; x < width; ++x)
for (size_t y = 0; y < height; ++y) {
size_t index = y*w + x;
assert(index < size);
if (index >= size) continue;
pix[3+4*index] = ptr2[y * width + x] / gray_level;
}
return sla::EncodedRaster();
};
raster->encode(encoder);
}
void CreateFontImageJob::finalize(bool canceled, std::exception_ptr &)
{
if (m_input.count_opened_font_files)
--(*m_input.count_opened_font_files);
if (canceled || m_input.cancel->load()) return;
*m_input.is_created = true;
// Exist result bitmap with preview?
// (not valid input. e.g. not loadable font)
if (m_result.empty()) {
// TODO: write text cannot load into texture
m_result = std::vector<unsigned char>(m_tex_size.x() * m_tex_size.y() * 4, {255});
}
// upload texture on GPU
const GLenum target = GL_TEXTURE_2D;
glsafe(::glBindTexture(target, m_input.texture_id));
GLsizei w = m_tex_size.x(), h = m_tex_size.y();
GLint xoffset = 0; // align to left
auto texture_w = m_input.size.x();
GLint yoffset = m_input.size.y() * m_input.index;
// clear rest of texture
std::vector<unsigned char> empty_data(texture_w * m_tex_size.y() * 4, {0});
glsafe(::glTexSubImage2D(target, m_input.level, 0, yoffset, texture_w, h, m_input.format, m_input.type, empty_data.data()));
if (m_input.generate_origin_text) { // valid texture
glsafe(::glTexSubImage2D(target, m_input.level, xoffset, yoffset, w, h, m_input.format, m_input.type, m_result.data()));
}
// bind default texture
GLuint no_texture_id = 0;
glsafe(::glBindTexture(target, no_texture_id));
// show rendered texture
wxGetApp().plater()->canvas3D()->schedule_extra_frame(0);
BOOST_LOG_TRIVIAL(info)
<< "Generate Preview font('" << m_input.font_name << "' id:" << m_input.index << ") "
<< "with text: '" << m_input.text << "' "
<< "texture_size " << m_input.size.x() << " x " << m_input.size.y();
}
std::vector<Slic3r::Emboss::FontFileWithCache> Slic3r::GUI::BackupFonts::backup_fonts;
void Slic3r::GUI::BackupFonts::generate_backup_fonts() {
auto language = wxGetApp().app_config->get("language");
auto custom_back_font_name = wxGetApp().app_config->get("custom_back_font_name");
if (backup_fonts.empty()) {
size_t idx = language.find('_');
std::string lang = (idx == std::string::npos) ? language : language.substr(0, idx);
std::vector<wxString> font_names;
#ifdef _WIN32
font_names.emplace_back(wxString(L"宋体"));//chinese confirm
font_names.emplace_back(wxString::FromUTF8("MS Gothic")); // Japanese
font_names.emplace_back(wxString::FromUTF8("NanumGothic")); // Korean
font_names.emplace_back(wxString::FromUTF8("Arial")); // Arabic
#endif
#ifdef __APPLE__
font_names.emplace_back(wxString::FromUTF8("Songti SC"));//chinese confirm
font_names.emplace_back(wxString::FromUTF8("SimSong")); // Japanese//mac special
font_names.emplace_back(wxString::FromUTF8("Nanum Gothic")); // Korean//mac need space
font_names.emplace_back(wxString::FromUTF8("Arial")); // Arabic
#endif
#ifdef __linux__
font_names.emplace_back(wxString(L"宋体")); // chinese confirm
font_names.emplace_back(wxString::FromUTF8("MS Gothic")); // Japanese
font_names.emplace_back(wxString::FromUTF8("NanumGothic")); // Korean
font_names.emplace_back(wxString::FromUTF8("Arial")); // Arabic
#endif
if (!custom_back_font_name.empty()) {
font_names.emplace_back(wxString::FromUTF8(custom_back_font_name));
}
for (int i = 0; i < font_names.size(); i++) {
backup_fonts.emplace_back(gener_font_with_cache(font_names[i], wxFontEncoding::wxFONTENCODING_SYSTEM));
}
}
}
Slic3r::Emboss::FontFileWithCache Slic3r::GUI::BackupFonts::gener_font_with_cache(const wxString &font_name, const wxFontEncoding &encoding)
{
Emboss::FontFileWithCache font_file_with_cache;
if (!wxFontEnumerator::IsValidFacename(font_name))
return font_file_with_cache;
// Select font
wxFont wx_font(wxFontInfo().FaceName(font_name).Encoding(encoding));
if (!wx_font.IsOk()) return font_file_with_cache;
std::unique_ptr<Emboss::FontFile> font_file = WxFontUtils::create_font_file(wx_font);
if (font_file == nullptr)
return font_file_with_cache;
font_file_with_cache = Emboss::FontFileWithCache(std::move(font_file));
return font_file_with_cache;
}

View File

@@ -0,0 +1,89 @@
#ifndef slic3r_CreateFontNameImageJob_hpp_
#define slic3r_CreateFontNameImageJob_hpp_
#include <vector>
#include <string>
#include <GL/glew.h>
#include <wx/string.h>
#include <wx/fontenc.h>
#include "JobNew.hpp"
#include "libslic3r/Point.hpp" // Vec2i32
#include "libslic3r/Emboss.hpp"
namespace Slic3r::GUI {
/// <summary>
/// Keep data for rasterization of text by font face
/// </summary>
struct FontImageData
{
// Text to rasterize
std::string text;
// Define font face
wxString font_name;
wxFontEncoding encoding;
// texture for copy result to
// texture MUST BE initialized
GLuint texture_id;
// Index of face name, define place in texture
size_t index;
// Height of each text
// And Limit for width
Vec2i32 size; // in px
// bigger value create darker image
// divide value 255
unsigned char gray_level = 5;
// texture meta data
GLenum format = GL_ALPHA, type = GL_UNSIGNED_BYTE;
GLint level = 0;
// prevent opening too much files
// it is decreased in finalize phase
unsigned int *count_opened_font_files = nullptr;
std::shared_ptr<std::atomic<bool>> cancel = nullptr;
std::shared_ptr<bool> is_created = nullptr;
bool generate_origin_text = false;
};
class BackupFonts
{
public:
static void generate_backup_fonts();
static Slic3r::Emboss::FontFileWithCache gener_font_with_cache(const wxString &font_name, const wxFontEncoding& encoding);
static std::vector<Slic3r::Emboss::FontFileWithCache> backup_fonts;
};
/// <summary>
/// Create image for face name
/// </summary>
class CreateFontImageJob : public JobNew
{
FontImageData m_input;
std::vector<unsigned char> m_result;
Point m_tex_size;
public:
CreateFontImageJob(FontImageData &&input);
/// <summary>
/// Rasterize text into image (result)
/// </summary>
/// <param name="ctl">Check for cancelation</param>
void process(Ctl &ctl) override;
/// <summary>
/// Copy image data into OpenGL texture
/// </summary>
/// <param name="canceled"></param>
/// <param name=""></param>
void finalize(bool canceled, std::exception_ptr &) override;
/// <summary>
/// Text used for generate preview for empty text
/// and when no glyph for given m_input.text
/// </summary>
static const std::string default_text;
};
} // namespace Slic3r::GUI
#endif // slic3r_CreateFontNameImageJob_hpp_

View File

@@ -0,0 +1,158 @@
#include "CreateFontStyleImagesJob.hpp"
#include "CreateFontNameImageJob.hpp"
// rasterization of ExPoly
#include "libslic3r/SLA/AGGRaster.hpp"
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
// ability to request new frame after finish rendering
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
using namespace Slic3r;
using namespace Slic3r::Emboss;
using namespace Slic3r::GUI;
using namespace Slic3r::GUI::Emboss;
CreateFontStyleImagesJob::CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input)
: m_input(std::move(input)), m_width(0), m_height(0)
{
assert(m_input.result != nullptr);
assert(!m_input.styles.empty());
assert(!m_input.text.empty());
assert(m_input.max_size.x() > 1);
assert(m_input.max_size.y() > 1);
assert(m_input.ppm > 1e-5);
}
void CreateFontStyleImagesJob::process(Ctl &ctl)
{
// create shapes and calc size (bounding boxes)
std::vector<ExPolygons> name_shapes(m_input.styles.size());
std::vector<double> scales(m_input.styles.size());
m_images = std::vector<StyleManager::StyleImage>(m_input.styles.size());
auto was_canceled = []() { return false; };
bool support_backup_fonts = GUI::wxGetApp().app_config->get_bool("support_backup_fonts");
auto ft_fn = []() { return Slic3r::GUI::BackupFonts::backup_fonts; };
for (auto &item : m_input.styles) {
size_t index = &item - &m_input.styles.front();
ExPolygons &shapes = name_shapes[index];
EmbossShape emboss_shape;
auto & ff = *item.font.font_file;
double standard_scale = get_text_shape_scale(item.prop, ff);
shapes = support_backup_fonts ? text2shapes(emboss_shape, item.font, m_input.text.c_str(), item.prop, was_canceled, ft_fn, standard_scale)
:text2shapes(emboss_shape, item.font, m_input.text.c_str(), item.prop, was_canceled);
// create image description
StyleManager::StyleImage &image = m_images[index];
BoundingBox &bounding_box = image.bounding_box;
for (ExPolygon &shape : shapes)
bounding_box.merge(BoundingBox(shape.contour.points));
for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min);
// calculate conversion from FontPoint to screen pixels by size of font
double scale = get_text_shape_scale(item.prop, *item.font.font_file) * m_input.ppm *0.5;
scales[index] = scale;
//double scale = font_prop.size_in_mm * SCALING_FACTOR;
BoundingBoxf bb2(bounding_box.min.cast<double>(),
bounding_box.max.cast<double>());
bb2.scale(scale);
image.tex_size.x = std::ceil(bb2.max.x() - bb2.min.x());
image.tex_size.y = std::ceil(bb2.max.y() - bb2.min.y());
// crop image width
if (image.tex_size.x > m_input.max_size.x())
image.tex_size.x = m_input.max_size.x();
// crop image height
if (image.tex_size.y > m_input.max_size.y())
image.tex_size.y = m_input.max_size.y();
}
// arrange bounding boxes
int offset_y = 0;
m_width = 0;
for (StyleManager::StyleImage &image : m_images) {
image.offset.y() = offset_y;
offset_y += image.tex_size.y+1;
if (m_width < image.tex_size.x)
m_width = image.tex_size.x;
}
m_height = offset_y;
for (StyleManager::StyleImage &image : m_images) {
const Point &o = image.offset;
const ImVec2 &s = image.tex_size;
image.uv0 = ImVec2(o.x() / (double) m_width,
o.y() / (double) m_height);
image.uv1 = ImVec2((o.x() + s.x) / (double) m_width,
(o.y() + s.y) / (double) m_height);
}
// Set up result
m_pixels = std::vector<unsigned char>(4 * m_width * m_height, {255});
// upload sub textures
for (StyleManager::StyleImage &image : m_images) {
sla::Resolution resolution(image.tex_size.x, image.tex_size.y);
size_t index = &image - &m_images.front();
double pixel_dim = SCALING_FACTOR / scales[index];
sla::PixelDim dim(pixel_dim, pixel_dim);
double gamma = 1.;
std::unique_ptr<sla::RasterBase> r =
sla::create_raster_grayscale_aa(resolution, dim, gamma);
for (const ExPolygon &shape : name_shapes[index]) r->draw(shape);
// copy rastered data to pixels
sla::RasterEncoder encoder = [&offset = image.offset, &pix = m_pixels, w=m_width,h=m_height]
(const void *ptr, size_t width, size_t height, size_t num_components) {
// bigger value create darker image
unsigned char gray_level = 1;
size_t size {static_cast<size_t>(w*h)};
assert((offset.x() + width) <= (size_t)w);
assert((offset.y() + height) <= (size_t)h);
const unsigned char *ptr2 = (const unsigned char *) ptr;
for (size_t x = 0; x < width; ++x)
for (size_t y = 0; y < height; ++y) {
size_t index = (offset.y() + y)*w + offset.x() + x;
assert(index < size);
if (index >= size) continue;
pix[4*index+3] = ptr2[y * width + x] / gray_level;
}
return sla::EncodedRaster();
};
r->encode(encoder);
}
}
void CreateFontStyleImagesJob::finalize(bool canceled, std::exception_ptr &)
{
if (canceled) return;
// upload texture on GPU
GLuint tex_id;
GLenum target = GL_TEXTURE_2D, format = GL_RGBA, type = GL_UNSIGNED_BYTE;
GLint level = 0, border = 0;
glsafe(::glGenTextures(1, &tex_id));
glsafe(::glBindTexture(target, tex_id));
glsafe(::glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(::glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GLint w = m_width, h = m_height;
glsafe(::glTexImage2D(target, level, GL_RGBA, w, h, border, format, type,
(const void *) m_pixels.data()));
// set up texture id
void *texture_id = (void *) (intptr_t) tex_id;
for (StyleManager::StyleImage &image : m_images)
image.texture_id = texture_id;
// move to result
m_input.result->styles = std::move(m_input.styles);
m_input.result->images = std::move(m_images);
// bind default texture
GLuint no_texture_id = 0;
glsafe(::glBindTexture(target, no_texture_id));
// show rendered texture
wxGetApp().plater()->canvas3D()->schedule_extra_frame(0);
}

View File

@@ -0,0 +1,36 @@
#ifndef slic3r_CreateFontStyleImagesJob_hpp_
#define slic3r_CreateFontStyleImagesJob_hpp_
#include <vector>
#include <string>
#include <libslic3r/Emboss.hpp>
#include "slic3r/Utils/EmbossStyleManager.hpp"
#include "JobNew.hpp"
namespace Slic3r::GUI::Emboss {
/// <summary>
/// Create texture with name of styles written by its style
/// NOTE: Access to glyph cache is possible only from job
/// </summary>
class CreateFontStyleImagesJob : public JobNew
{
StyleManager::StyleImagesData m_input;
// Output data
// texture size
int m_width, m_height;
// texture data
std::vector<unsigned char> m_pixels;
// descriptors of sub textures
std::vector<StyleManager::StyleImage> m_images;
public:
CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &) override;
};
} // namespace Slic3r::GUI
#endif // slic3r_CreateFontStyleImagesJob_hpp_

View File

@@ -3,6 +3,7 @@
#include <type_traits>
#include <boost/log/trivial.hpp>
//
#include <libslic3r/Line.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/Format/OBJ.hpp> // load_obj for default mesh
#include <libslic3r/CutSurface.hpp> // use surface cuts
@@ -25,7 +26,7 @@
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/Jobs/Worker.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <wx/regex.h>
// #define EXECUTE_UPDATE_ON_MAIN_THREAD // debug execution on main thread
using namespace Slic3r::Emboss;
namespace Slic3r {
@@ -45,8 +46,10 @@ public:
bool was_canceled(const JobNew::Ctl &ctl, const DataBase &base)
{
if (base.cancel->load()) return true;
return ctl.was_canceled();
if (base.cancel->load())
return true;
auto flag = ctl.was_canceled();
return flag;
}
bool _finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input)
@@ -178,6 +181,45 @@ bool is_valid(ModelVolumeType volume_type)
return false;
}
void recreate_model_volume(ModelObject *model_object, int volume_idx, const TriangleMesh &mesh, Geometry::Transformation &text_tran, TextInfo &text_info)
{
wxGetApp() .plater()->take_snapshot("Modify Text");
ModelVolume *model_volume = model_object->volumes[volume_idx];
ModelVolume *new_model_volume = model_object->add_volume(mesh, false);
new_model_volume->calculate_convex_hull();
new_model_volume->set_transformation(text_tran.get_matrix());
new_model_volume->set_text_info(text_info);
new_model_volume->name = model_volume->name;
new_model_volume->set_type(model_volume->type());
new_model_volume->config.apply(model_volume->config);
std::swap(model_object->volumes[volume_idx], model_object->volumes.back());
model_object->delete_volume(model_object->volumes.size() - 1);
model_object->invalidate_bounding_box();
wxGetApp().plater()->update();
}
void create_text_volume(Slic3r::ModelObject *model_object, const TriangleMesh &mesh, Geometry::Transformation &text_tran, TextInfo &text_info)
{
wxGetApp().plater()->take_snapshot("create_text_volume");
ModelVolume *new_model_volume = model_object->add_volume(mesh, false);
new_model_volume->calculate_convex_hull();
new_model_volume->set_transformation(text_tran.get_matrix());
new_model_volume->set_text_info(text_info);
if (model_object->config.option("extruder")) {
new_model_volume->config.set_key_value("extruder", new ConfigOptionInt(model_object->config.extruder()));
} else {
new_model_volume->config.set_key_value("extruder", new ConfigOptionInt(1));
model_object->config.set_key_value("extruder", new ConfigOptionInt(1));
}
new_model_volume->name = "text_shape";
model_object->invalidate_bounding_box();
wxGetApp().plater()->update();
}
bool check(unsigned char gizmo_type) { return gizmo_type == (unsigned char) GLGizmosManager::Svg; }
bool check(const CreateVolumeParams &input)
@@ -339,6 +381,7 @@ void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const Da
// update volume
volume->set_mesh(std::move(mesh));
volume->calculate_convex_hull();
volume->set_new_unique_id();
volume->calculate_convex_hull();
@@ -467,12 +510,16 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
// 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->get_view3D_canvas3D();
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != m_input.gizmo_type) // GLGizmosManager::EType::svg
manager.open_gizmo(m_input.gizmo_type);
if (canvas) {
GLGizmosManager& manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != m_input.gizmo_type) { // GLGizmosManager::EType::svg
const auto svg_item_name = GLGizmosManager::convert_gizmo_type_to_string(static_cast<GLGizmosManager::EType>(m_input.gizmo_type));
canvas->force_main_toolbar_left_action(canvas->get_main_toolbar_item_id(svg_item_name));
}
// redraw scene
canvas->reload_scene(true);
// redraw scene
canvas->reload_scene(true);
}
}
CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input) : m_input(std::move(input)) {}
@@ -495,7 +542,7 @@ CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input) : m_input(std::move(i
void CreateVolumeJob::process(Ctl &ctl)
{
if (!check(m_input))
throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
throw JobException("Bad input data for EmbossCreateVolumeJob.");
m_result = create_mesh(*m_input.base);
}
@@ -716,7 +763,7 @@ void create_volume(
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));
volume->config.set_key_value("extruder", new ConfigOptionInt(1));
// do not allow model reload from disk
volume->source.is_from_builtin_objects = true;
@@ -799,10 +846,15 @@ OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform
}
indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input)
{
return cut_surface_to_its(shapes, input.shape.scale, tr, sources, input);
}
indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, float scale, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input)
{
assert(!sources.empty());
BoundingBox bb = get_extents(shapes);
double shape_scale = input.shape.scale;
double shape_scale = scale;
const SurfaceVolumeData::ModelSource *biggest = &sources.front();
@@ -828,7 +880,8 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor
s_to_itss[source_index] = its_index;
itss.emplace_back(std::move(its));
}
if (itss.empty()) return {};
if (itss.empty())
return {};
Transform3d tr_inv = biggest->tr.inverse();
Transform3d cut_projection_tr = tr_inv * tr;
@@ -837,8 +890,10 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
for (const SurfaceVolumeData::ModelSource &s : sources) {
itss_index = s_to_itss[&s - &sources.front()];
if (itss_index == std::numeric_limits<size_t>::max()) continue;
if (&s == biggest) continue;
if (itss_index == std::numeric_limits<size_t>::max())
continue;
if (&s == biggest)
continue;
Transform3d tr = s.tr * tr_inv;
bool fix_reflected = true;
@@ -864,7 +919,8 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor
shapes_data = shapes; // copy
for (ExPolygon &shape : shapes_data) {
shape.contour.reverse();
for (Slic3r::Polygon &hole : shape.holes) hole.reverse();
for (Slic3r::Polygon &hole : shape.holes)
hole.reverse();
}
shapes_ptr = &shapes_data;
}
@@ -1037,7 +1093,11 @@ const GLVolume *find_closest(const Selection &selection, const Vec2d &screen_cen
double center_sq_distance = std::numeric_limits<double>::max();
for (unsigned int id : indices) {
const GLVolume *gl_volume = selection.get_volume(id);
if (const ModelVolume *volume = get_model_volume(*gl_volume, objects); volume == nullptr || !volume->is_model_part()) continue;
const ModelVolume *volume = get_model_volume(*gl_volume, objects);
if (volume == nullptr || !volume->is_model_part()) continue;
if (volume->is_text()) {
continue;
}
Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume);
Vec2d c = hull.centroid().cast<double>();
Vec2d d = c - screen_center;
@@ -1198,6 +1258,767 @@ SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, s
return result;
}
const GLVolume *find_glvoloume_render_screen_cs(const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center)
{
return find_closest(selection, screen_center, camera, objects, closest_center);
}
} // namespace Slic3r::GUI
} // namespace Slic3r
ProjectTransform calc_project_tran(DataBase &input, double real_scale)
{
double scale = real_scale; // 1e-6
double depth = (input.shape.projection.depth + input.shape.projection.embeded_depth) / scale;
auto projectZ = std::make_unique<ProjectZ>(depth);
float offset = input.is_outside ? -SAFE_SURFACE_OFFSET : (SAFE_SURFACE_OFFSET - input.shape.projection.depth);
if (input.from_surface.has_value()) offset += *input.from_surface;
Transform3d tr = Eigen::Translation<double, 3>(0., 0., static_cast<double>(offset)) * Eigen::Scaling(scale);
ProjectTransform project_tr(std::move(projectZ), tr);
return project_tr;
}
void create_all_char_mesh(DataBase &input, std::vector<TriangleMesh> &result, EmbossShape &shape)
{
shape = input.create_shape();//this will call letter2shapes
if (shape.shapes_with_ids.empty())
return;
result.clear();
auto first_project_tr = calc_project_tran(input, input.shape.scale);
TextConfiguration text_configuration = input.get_text_configuration();
bool support_backup_fonts = std::any_of(shape.text_scales.begin(), shape.text_scales.end(), [](float x) { return x > 0; });
const char *text = input.get_text_configuration().text.c_str();
wxString input_text = wxString::FromUTF8(input.get_text_configuration().text);
wxRegEx re("^ +$");
bool is_all_space = re.Matches(input_text);
if (is_all_space) { return; }
if (input_text.size() != shape.shapes_with_ids.size()) {
BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< "error: input_text.size() != shape.shapes_with_ids.size()";
}
for (int i = 0; i < shape.shapes_with_ids.size(); i++) {
auto &temp_shape = shape.shapes_with_ids[i];
if (input_text[i] == ' ') {
result.emplace_back(TriangleMesh());
continue;
}
if (temp_shape.expoly.empty())
continue;
if (support_backup_fonts) {
if (i < shape.text_scales.size() && shape.text_scales[i] > 0) {
auto temp_scale = shape.text_scales[i];
auto temp_project_tr = calc_project_tran(input, temp_scale);
TriangleMesh mesh(polygons2model(temp_shape.expoly, temp_project_tr));
result.emplace_back(mesh);
} else {
TriangleMesh mesh(polygons2model(temp_shape.expoly, first_project_tr));
result.emplace_back(mesh);
}
} else {
TriangleMesh mesh(polygons2model(temp_shape.expoly, first_project_tr));
result.emplace_back(mesh);
}
}
}
float get_single_char_width(const std::vector<TriangleMesh> &chars_mesh_result)
{
for (int i = 0; i < chars_mesh_result.size(); ++i) {
auto box = chars_mesh_result[i].bounding_box();
auto box_size = box.size();
auto half_x_length = box_size[0] / 2.0f;
if (half_x_length > 0.01) {
return half_x_length;
}
}
return 0.f;
}
bool calc_text_lengths(std::vector<double> &text_lengths, const std::vector<TriangleMesh> &chars_mesh_result)
{
text_lengths.clear();
auto single_char_width = get_single_char_width(chars_mesh_result);
if (single_char_width < 0.01) { return false; }
for (int i = 0; i < chars_mesh_result.size(); ++i) {
auto box = chars_mesh_result[i].bounding_box();
auto box_size = box.size();
auto half_x_length = box_size[0] / 2.0f;
if (half_x_length < 0.01) {
text_lengths.emplace_back(single_char_width + 1);
} else {
text_lengths.emplace_back(half_x_length + 1);
}
}
return true;
}
void calc_position_points(std::vector<Vec3d> &position_points, std::vector<double> &text_lengths, float text_gap, const Vec3d &temp_pos_dir)
{
auto text_num = text_lengths.size();
if (text_num == 0) {
throw JobException("calc_position_points fail.");
return;
}
if (position_points.size() != text_lengths.size()) { position_points.resize(text_num); }
auto pos_dir = temp_pos_dir.normalized();
if (text_num % 2 == 1) {
position_points[text_num / 2] = Vec3d::Zero();
for (int i = 0; i < text_num / 2; ++i) {
double left_gap = text_lengths[text_num / 2 - i - 1] + text_gap + text_lengths[text_num / 2 - i];
if (left_gap < 0)
left_gap = 0;
double right_gap = text_lengths[text_num / 2 + i + 1] + text_gap + text_lengths[text_num / 2 + i];
if (right_gap < 0)
right_gap = 0;
position_points[text_num / 2 - 1 - i] = position_points[text_num / 2 - i] - left_gap * pos_dir;
position_points[text_num / 2 + 1 + i] = position_points[text_num / 2 + i] + right_gap * pos_dir;
}
} else {
for (int i = 0; i < text_num / 2; ++i) {
double left_gap = i == 0 ? (text_lengths[text_num / 2 - i - 1] + text_gap / 2) : (text_lengths[text_num / 2 - i - 1] + text_gap + text_lengths[text_num / 2 - i]);
if (left_gap < 0)
left_gap = 0;
double right_gap = i == 0 ? (text_lengths[text_num / 2 + i] + text_gap / 2) : (text_lengths[text_num / 2 + i] + text_gap + text_lengths[text_num / 2 + i - 1]);
if (right_gap < 0)
right_gap = 0;
if (i == 0) {
position_points[text_num / 2 - 1 - i] = Vec3d::Zero() - left_gap * pos_dir;
position_points[text_num / 2 + i] = Vec3d::Zero() + right_gap * pos_dir;
continue;
}
position_points[text_num / 2 - 1 - i] = position_points[text_num / 2 - i] - left_gap * pos_dir;
position_points[text_num / 2 + i] = position_points[text_num / 2 + i - 1] + right_gap * pos_dir;
}
}
}
GenerateTextJob::GenerateTextJob(InputInfo &&input) : m_input(std::move(input)) {}
std::vector<Vec3d> GenerateTextJob::debug_cut_points_in_world;
void GenerateTextJob::process(Ctl &ctl)
{
auto canceled = was_canceled(ctl, *m_input.m_data_update.base);
if (canceled)
return;
create_all_char_mesh(*m_input.m_data_update.base, m_input.m_chars_mesh_result, m_input.m_text_shape);
if (m_input.m_chars_mesh_result.empty()) {
return;
}
if (!update_text_positions(m_input)) {
throw JobException("update_text_positions fail.");
}
if (!generate_text_points(m_input))
throw JobException("generate_text_volume fail.");
GenerateTextJob::debug_cut_points_in_world = m_input.m_cut_points_in_world;
if (m_input.use_surface) {
if (m_input.m_text_shape.shapes_with_ids.empty())
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
}
generate_mesh_according_points(m_input);
}
void GenerateTextJob::finalize(bool canceled, std::exception_ptr &eptr)
{
if (canceled || eptr)
return;
if (m_input.first_generate) {
create_text_volume(m_input.mo, m_input.m_final_text_mesh, m_input.m_final_text_tran_in_object, m_input.text_info);
auto model_object = m_input.mo;
m_input.m_volume_idx;
auto volume_idx = model_object->volumes.size() - 1;
ModelVolume * model_volume = model_object->volumes[volume_idx];
auto add_to_selection = [model_volume](const ModelVolume *vol) { return vol == model_volume; };
ObjectList * obj_list = wxGetApp().obj_list();
int object_idx = wxGetApp().plater()->get_selected_object_idx();
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection);
if (!sel.IsEmpty()) {
obj_list->select_item(sel.front());
}
obj_list->selection_changed();
GLCanvas3D * canvas = wxGetApp().plater()->get_view3D_canvas3D();
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::EType::Text) {
manager.open_gizmo(GLGizmosManager::EType::Text);
}
} else {
recreate_model_volume(m_input.mo, m_input.m_volume_idx, m_input.m_final_text_mesh, m_input.m_final_text_tran_in_object, m_input.text_info);
}
}
bool GenerateTextJob::update_text_positions(InputInfo &input_info)
{
if (input_info.m_chars_mesh_result.size() == 0) {
input_info.m_position_points.clear();
return false;
}
std::vector<double> text_lengths;
if (!calc_text_lengths(text_lengths, input_info.m_chars_mesh_result)) {
return false;
}
int text_num = input_info.m_chars_mesh_result.size(); // FIX by BBS 20250109
input_info.m_position_points.clear();
input_info.m_normal_points.clear();
/*auto mouse_position_world = m_text_position_in_world.cast<double>();
auto mouse_normal_world = m_text_normal_in_world.cast<double>();*/
input_info.m_position_points.resize(text_num);
input_info.m_normal_points.resize(text_num);
input_info.text_lengths = text_lengths;
input_info.m_surface_type = GenerateTextJob::SurfaceType::None;
if (input_info.text_surface_type == TextInfo::TextType::HORIZONAL) {
input_info.use_surface = false;
Vec3d mouse_normal_world = input_info.m_text_normal_in_world.cast<double>();
Vec3d world_pos_dir = input_info.m_cut_plane_dir_in_world.cross(mouse_normal_world);
auto inv_ = (input_info.m_model_object_in_world_tran.get_matrix_no_offset() * input_info.m_text_tran_in_object.get_matrix_no_offset()).inverse();
Vec3d pos_dir = Vec3d::UnitX();
auto mouse_normal_local = inv_ * mouse_normal_world;
mouse_normal_local.normalize();
calc_position_points(input_info.m_position_points, text_lengths, input_info.m_text_gap, pos_dir);
for (int i = 0; i < text_num; ++i) {
input_info.m_normal_points[i] = mouse_normal_local;
}
return true;
}
input_info.m_surface_type = GenerateTextJob::SurfaceType::Surface;
return true;
}
bool GenerateTextJob::generate_text_points(InputInfo &input_info)
{
if (input_info.m_surface_type == GenerateTextJob::SurfaceType::None) {
return true;
}
auto &m_text_tran_in_object = input_info.m_text_tran_in_object;
auto mo = input_info.mo;
auto &m_volume_idx = input_info.m_volume_idx;
auto &m_position_points = input_info.m_position_points;
auto &m_normal_points = input_info.m_normal_points;
auto &m_cut_points_in_world = input_info.m_cut_points_in_world;
std::vector<Vec3d> m_cut_points_in_local;
auto &m_text_cs_to_world_tran = input_info.m_text_tran_in_world.get_matrix();
auto &m_chars_mesh_result = input_info.m_chars_mesh_result;
auto &m_text_position_in_world = input_info.m_text_position_in_world;
auto &m_text_normal_in_world = input_info.m_text_normal_in_world;
auto &m_text_gap = input_info.m_text_gap;
auto &m_model_object_in_world_tran = input_info.m_model_object_in_world_tran;
auto &text_lengths = input_info.text_lengths;
//auto hit_mesh_id = input_info.hit_mesh_id;//m_rr.mesh_id
//calc
TriangleMesh slice_meshs;
int mesh_index = 0;
for (int i = 0; i < mo->volumes.size(); ++i) {
ModelVolume *mv = mo->volumes[i];
if (m_volume_idx == i) {
continue;
}
if (mv->is_text()) {
continue;
}
if (mv->is_model_part()) {
TriangleMesh vol_mesh(mv->mesh());
vol_mesh.transform(mv->get_matrix());
slice_meshs.merge(vol_mesh);
mesh_index++;
}
}
auto text_tran_in_object = m_text_tran_in_object; // important
input_info.slice_mesh = slice_meshs;
auto rotate_tran = Geometry::assemble_transform(Vec3d::Zero(), {-0.5 * M_PI, 0.0, 0.0});
MeshSlicingParams slicing_params;
auto cut_tran = (text_tran_in_object.get_matrix() * rotate_tran);
slicing_params.trafo = cut_tran.inverse();
// for debug
// its_write_obj(slice_meshs.its, "D:/debug_files/mesh.obj");
// generate polygons
const Polygons temp_polys = slice_mesh(slice_meshs.its, 0, slicing_params);
Vec3d scale_click_pt(scale_(0), scale_(0), 0);
// for debug
// export_regions_to_svg(Point(scale_pt.x(), scale_pt.y()), temp_polys);
Polygons polys = union_(temp_polys);
auto point_in_line_rectange = [](const Line &line, const Point &point, double &distance) {
distance = line.distance_to(point);
return distance < line.length() / 2;
};
int index = 0;
double min_distance = 1e12;
Polygon hit_ploy;
for (const Polygon poly : polys) {
if (poly.points.size() == 0)
continue;
Lines lines = poly.lines();
for (int i = 0; i < lines.size(); ++i) {
Line line = lines[i];
double distance = min_distance;
if (point_in_line_rectange(line, Point(scale_click_pt.x(), scale_click_pt.y()), distance)) {
if (distance < min_distance) {
min_distance = distance;
index = i;
hit_ploy = poly;
}
}
}
}
if (hit_ploy.points.size() == 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("Text: the hit polygon is null,") << "x:" << m_text_position_in_world.x() << ",y:" << m_text_position_in_world.y()
<< ",z:" << m_text_position_in_world.z();
throw JobException("The hit polygon is null,please try to regenerate after adjusting text position.");
return false;
}
int text_num = m_chars_mesh_result.size();
auto world_tran = m_model_object_in_world_tran * text_tran_in_object;
m_cut_points_in_world.clear();
m_cut_points_in_world.reserve(hit_ploy.points.size());
m_cut_points_in_local.clear();
m_cut_points_in_local.reserve(hit_ploy.points.size());
for (int i = 0; i < hit_ploy.points.size(); ++i) {
m_cut_points_in_local.emplace_back(rotate_tran * Vec3d(unscale_(hit_ploy.points[i].x()), unscale_(hit_ploy.points[i].y()), 0)); // m_text_cs_to_world_tran *
m_cut_points_in_world.emplace_back(world_tran.get_matrix() * m_cut_points_in_local.back());
}
Slic3r::Polygon_3D new_polygon(m_cut_points_in_local);
m_position_points.resize(text_num);
if (text_num % 2 == 1) {
m_position_points[text_num / 2] = Vec3d::Zero();
std::vector<Line_3D> lines = new_polygon.get_lines();
Line_3D line = lines[index];
auto min_dist = 1e6;
{// Find the nearest tangent point
for (int i = 0; i < lines.size(); i++) {
Line_3D temp_line = lines[i];
Vec3d intersection_pt;
float proj_length;
auto pt = Vec3d::Zero();
Linef3::get_point_projection_to_line(pt, temp_line.a, temp_line.vector(), intersection_pt, proj_length);
auto dist = (intersection_pt - pt).norm();
if (min_dist > dist) {
min_dist = dist;
m_position_points[text_num / 2] = intersection_pt;
}
}
}
{
int index1 = index;
double left_length = (Vec3d::Zero() - line.a).cast<double>().norm();
int left_num = text_num / 2;
while (left_num > 0) {
double gap_length = (text_lengths[left_num] + m_text_gap + text_lengths[left_num - 1]);
if (gap_length < 0) gap_length = 0;
while (gap_length > left_length) {
gap_length -= left_length;
if (index1 == 0)
index1 = lines.size() - 1;
else
--index1;
left_length = lines[index1].length();
}
Vec3d direction = lines[index1].vector();
direction.normalize();
double distance_to_a = (left_length - gap_length);
Line_3D new_line = lines[index1];
double norm_value = direction.cast<double>().norm();
double deta_x = distance_to_a * direction.x() / norm_value;
double deta_y = distance_to_a * direction.y() / norm_value;
double deta_z = distance_to_a * direction.z() / norm_value;
Vec3d new_pos = new_line.a + Vec3d(deta_x, deta_y, deta_z);
left_num--;
m_position_points[left_num] = new_pos;
left_length = distance_to_a;
}
}
{
int index2 = index;
double right_length = (line.b - Vec3d::Zero()).cast<double>().norm();
int right_num = text_num / 2;
while (right_num > 0) {
double gap_length = (text_lengths[text_num - right_num] + m_text_gap + text_lengths[text_num - right_num - 1]);
if (gap_length < 0) gap_length = 0;
while (gap_length > right_length) {
gap_length -= right_length;
if (index2 == lines.size() - 1)
index2 = 0;
else
++index2;
right_length = lines[index2].length();
}
Line_3D line2 = lines[index2];
line2.reverse();
Vec3d direction = line2.vector();
direction.normalize();
double distance_to_b = (right_length - gap_length);
Line_3D new_line = lines[index2];
double norm_value = direction.cast<double>().norm();
double deta_x = distance_to_b * direction.x() / norm_value;
double deta_y = distance_to_b * direction.y() / norm_value;
double deta_z = distance_to_b * direction.z() / norm_value;
Vec3d new_pos = new_line.b + Vec3d(deta_x, deta_y, deta_z);
m_position_points[text_num - right_num] = new_pos;
right_length = distance_to_b;
right_num--;
}
}
} else {
for (int i = 0; i < text_num / 2; ++i) {
std::vector<Line_3D> lines = new_polygon.get_lines();
Line_3D line = lines[index];
{
int index1 = index;
double left_length = (Vec3d::Zero() - line.a).cast<double>().norm();
int left_num = text_num / 2;
for (int i = 0; i < text_num / 2; ++i) {
double gap_length = 0;
if (i == 0) {
gap_length = m_text_gap / 2 + text_lengths[text_num / 2 - 1 - i];
} else {
gap_length = text_lengths[text_num / 2 - i] + m_text_gap + text_lengths[text_num / 2 - 1 - i];
}
if (gap_length < 0) gap_length = 0;
while (gap_length > left_length) {
gap_length -= left_length;
if (index1 == 0)
index1 = lines.size() - 1;
else
--index1;
left_length = lines[index1].length();
}
Vec3d direction = lines[index1].vector();
direction.normalize();
double distance_to_a = (left_length - gap_length);
Line_3D new_line = lines[index1];
double norm_value = direction.cast<double>().norm();
double deta_x = distance_to_a * direction.x() / norm_value;
double deta_y = distance_to_a * direction.y() / norm_value;
double deta_z = distance_to_a * direction.z() / norm_value;
Vec3d new_pos = new_line.a + Vec3d(deta_x, deta_y, deta_z);
m_position_points[text_num / 2 - 1 - i] = new_pos;
left_length = distance_to_a;
}
}
{
int index2 = index;
double right_length = (line.b - Vec3d::Zero()).cast<double>().norm();
int right_num = text_num / 2;
double gap_length = 0;
for (int i = 0; i < text_num / 2; ++i) {
double gap_length = 0;
if (i == 0) {
gap_length = m_text_gap / 2 + text_lengths[text_num / 2 + i];
} else {
gap_length = text_lengths[text_num / 2 + i] + m_text_gap + text_lengths[text_num / 2 + i - 1];
}
if (gap_length < 0) gap_length = 0;
while (gap_length > right_length) {
gap_length -= right_length;
if (index2 == lines.size() - 1)
index2 = 0;
else
++index2;
right_length = lines[index2].length();
}
Line_3D line2 = lines[index2];
line2.reverse();
Vec3d direction = line2.vector();
direction.normalize();
double distance_to_b = (right_length - gap_length);
Line_3D new_line = lines[index2];
double norm_value = direction.cast<double>().norm();
double deta_x = distance_to_b * direction.x() / norm_value;
double deta_y = distance_to_b * direction.y() / norm_value;
double deta_z = distance_to_b * direction.z() / norm_value;
Vec3d new_pos = new_line.b + Vec3d(deta_x, deta_y, deta_z);
m_position_points[text_num / 2 + i] = new_pos;
right_length = distance_to_b;
}
}
}
}
std::vector<double> mesh_values(m_position_points.size(), 1e9);
m_normal_points.resize(m_position_points.size());
auto point_in_triangle_delete_area = [](const Vec3d &point, const Vec3d &point0, const Vec3d &point1, const Vec3d &point2) {
Vec3d p0_p = point - point0;
Vec3d p0_p1 = point1 - point0;
Vec3d p0_p2 = point2 - point0;
Vec3d p_p0 = point0 - point;
Vec3d p_p1 = point1 - point;
Vec3d p_p2 = point2 - point;
double s = p0_p1.cross(p0_p2).norm();
double s0 = p_p0.cross(p_p1).norm();
double s1 = p_p1.cross(p_p2).norm();
double s2 = p_p2.cross(p_p0).norm();
return abs(s0 + s1 + s2 - s);
};
bool is_mirrored = (m_model_object_in_world_tran * text_tran_in_object).is_left_handed();
slice_meshs.transform(text_tran_in_object.get_matrix().inverse());
TriangleMesh& mesh = slice_meshs;
std::vector<int> debug_incides;
debug_incides.resize(m_position_points.size());
for (int i = 0; i < m_position_points.size(); ++i) {
int debug_index = 0;
for (auto indice : mesh.its.indices) {
stl_vertex stl_point0 = mesh.its.vertices[indice[0]];
stl_vertex stl_point1 = mesh.its.vertices[indice[1]];
stl_vertex stl_point2 = mesh.its.vertices[indice[2]];
Vec3d point0 = stl_point0.cast<double>();
Vec3d point1 = stl_point1.cast<double>();
Vec3d point2 = stl_point2.cast<double>();
double abs_area = point_in_triangle_delete_area(m_position_points[i], point0, point1, point2);
if (mesh_values[i] > abs_area) {
mesh_values[i] = abs_area;
debug_incides[i] = debug_index;
Vec3d s1 = point1 - point0;
Vec3d s2 = point2 - point0;
m_normal_points[i] = s1.cross(s2);
m_normal_points[i].normalize();
if (is_mirrored) {
m_normal_points[i] = -m_normal_points[i];
}
}
debug_index++;
}
}
return true;
}
Geometry::Transformation GenerateTextJob::get_sub_mesh_tran(const Vec3d &position, const Vec3d &normal, const Vec3d &text_up_dir, float embeded_depth)
{
double phi;
Vec3d rotation_axis;
Matrix3d rotation_matrix;
Geometry::rotation_from_two_vectors(Vec3d::UnitZ(), normal, rotation_axis, phi, &rotation_matrix);
Geometry::Transformation local_tran;
Transform3d temp_tran0(Transform3d::Identity());
temp_tran0.rotate(Eigen::AngleAxisd(phi, rotation_axis.normalized()));
auto project_on_plane = [](const Vec3d &dir, const Vec3d &plane_normal) -> Vec3d { return dir - (plane_normal.dot(dir) * plane_normal.dot(plane_normal)) * plane_normal; };
Vec3d old_text_dir = Vec3d::UnitY();
old_text_dir = rotation_matrix * old_text_dir;
Vec3d new_text_dir = project_on_plane(text_up_dir, normal);
new_text_dir.normalize();
Geometry::rotation_from_two_vectors(old_text_dir, new_text_dir, rotation_axis, phi, &rotation_matrix);
if (abs(phi - PI) < EPSILON)
rotation_axis = normal;
Transform3d temp_tran1(Transform3d::Identity());
temp_tran1.rotate(Eigen::AngleAxisd(phi, rotation_axis.normalized()));
local_tran.set_matrix(temp_tran1 * temp_tran0);
Vec3d offset = position - embeded_depth * normal;
local_tran.set_offset(offset);
return local_tran;
}
void GenerateTextJob::get_text_mesh(TriangleMesh &result_mesh, std::vector<TriangleMesh> &chars_mesh, int i, Geometry::Transformation &local_tran)
{
if (chars_mesh.size() == 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("check error:get_text_mesh");
}
TriangleMesh mesh = chars_mesh[i]; // m_cur_font_name
auto box = mesh.bounding_box();
mesh.translate(-box.center().x(), 0, 0);
mesh.transform(local_tran.get_matrix());
result_mesh = mesh; // mesh in object cs
}
void GenerateTextJob::get_text_mesh(TriangleMesh & result_mesh,
EmbossShape & text_shape,
BoundingBoxes & line_qds,
SurfaceVolumeData::ModelSources &input_ms_es,
DataBase & input_db,
int i,
Geometry::Transformation &mv_tran,
Geometry::Transformation &local_tran_to_object_cs,
TriangleMesh & slice_mesh)
{
ExPolygons glyph_shape = text_shape.shapes_with_ids[i].expoly;
const BoundingBox &glyph_bb = line_qds[i];
Point offset(-glyph_bb.center().x(), 0);
for (ExPolygon &s : glyph_shape) {
s.translate(offset);
}
auto modify = local_tran_to_object_cs.get_matrix();
Transform3d tr = mv_tran.get_matrix() * modify;
float text_scale = input_db.shape.scale;
if (i < text_shape.text_scales.size() && text_shape.text_scales[i] > 0) {
text_scale = text_shape.text_scales[i];
}
indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, text_scale, tr, input_ms_es, input_db);
if (glyph_its.empty()) {
BOOST_LOG_TRIVIAL(info) << boost::format("check error:get_text_mesh");
}
// move letter in volume on the right position
its_transform(glyph_its, modify);
// Improve: union instead of merge
//its_merge(result, std::move(glyph_its));
result_mesh = TriangleMesh(glyph_its);
}
void GenerateTextJob::generate_mesh_according_points(InputInfo &input_info)
{
auto &m_position_points = input_info.m_position_points;
auto &m_normal_points = input_info.m_normal_points;
auto &m_cut_plane_dir_in_world = input_info.m_cut_plane_dir_in_world;
auto &m_chars_mesh_result = input_info.m_chars_mesh_result;
auto &m_embeded_depth = input_info.m_embeded_depth;
auto &m_thickness = input_info.m_thickness;
auto &m_model_object_in_world_tran = input_info.m_model_object_in_world_tran;
auto &m_text_tran_in_object = input_info.m_text_tran_in_object;
auto &mesh = input_info.m_final_text_mesh;
mesh.clear();
auto text_tran_in_object = m_text_tran_in_object; // important
auto inv_text_cs_in_object_no_offset = (m_model_object_in_world_tran.get_matrix_no_offset() * text_tran_in_object.get_matrix_no_offset()).inverse();
ExPolygons ex_polygons;
std::vector<BoundingBoxes> qds;
int line_idx = 0;
SurfaceVolumeData::ModelSources ms_es;
DataBase input_db("", std::make_shared<std::atomic<bool>>(false));
if (input_info.use_surface) {
EmbossShape &es = input_info.m_text_shape;
if (es.shapes_with_ids.empty())
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
size_t count_lines = 1; // input1.text_lines.size();
qds = create_line_bounds(es.shapes_with_ids, count_lines);
if (qds.empty()) {
return;
}
SurfaceVolumeData::ModelSource ms;
ms.mesh = std::make_shared<const TriangleMesh> (input_info.slice_mesh);
if (ms.mesh->empty()) {
return;
}
ms_es.push_back(ms);
input_db.is_outside = input_info.is_outside;
input_db.shape.projection.depth = m_thickness + m_embeded_depth;
input_db.shape.scale = input_info.shape_scale;
}
auto cut_plane_dir = inv_text_cs_in_object_no_offset * m_cut_plane_dir_in_world;
for (int i = 0; i < m_position_points.size(); ++i) {
auto position = m_position_points[i];
auto normal = m_normal_points[i];
TriangleMesh sub_mesh;
auto local_tran = get_sub_mesh_tran(position, normal, cut_plane_dir, m_embeded_depth);
if (input_info.use_surface) {
get_text_mesh(sub_mesh, input_info.m_text_shape, qds[line_idx], ms_es, input_db, i, text_tran_in_object, local_tran, input_info.slice_mesh);
}
else {
get_text_mesh(sub_mesh, m_chars_mesh_result, i, local_tran);
}
mesh.merge(sub_mesh);
}
if (mesh.its.empty()){
throw JobException(_u8L("Text mesh ie empty.").c_str());
return;
}
//ASCENT_CENTER = 1 / 2.5;// mesh.translate(Vec3f(0, -center.y(), 0)); // align vertical center
}
CreateObjectTextJob::CreateObjectTextJob(CreateTextInput &&input) : m_input(std::move(input)) {}
void CreateObjectTextJob::process(Ctl &ctl) {
create_all_char_mesh(*m_input.base, m_input.m_chars_mesh_result, m_input.m_text_shape);
if (m_input.m_chars_mesh_result.empty()) {
return;
}
std::vector<double> text_lengths;
calc_text_lengths(text_lengths, m_input.m_chars_mesh_result);
calc_position_points(m_input.m_position_points, text_lengths, m_input.text_info.m_text_gap, Vec3d(1, 0, 0));
}
void CreateObjectTextJob::finalize(bool canceled, std::exception_ptr &eptr) {
if (canceled || eptr) return;
if (m_input.m_position_points.empty())
return create_message("Can't create empty object.");
TriangleMesh final_mesh;
for (int i = 0; i < m_input.m_position_points.size();i++) {
TriangleMesh sub_mesh;
auto position = m_input.m_position_points[i];
auto local_tran = GenerateTextJob::get_sub_mesh_tran(position, Vec3d::UnitZ(), Vec3d(0, 1, 0), m_input.text_info.m_embeded_depth);
GenerateTextJob::get_text_mesh(sub_mesh, m_input.m_chars_mesh_result, i, local_tran);
final_mesh.merge(sub_mesh);
}
GUI_App &app = wxGetApp();
Plater * plater = app.plater();
plater->take_snapshot("Add text object on plate");
auto center = plater->get_partplate_list().get_curr_plate()->get_bounding_box().center();
Model &model = plater->model();
{
// INFO: inspiration for create object is from ObjectList::load_mesh_object()
ModelObject *new_object = model.add_object();
new_object->name = _u8L("Text");
new_object->add_instance(); // each object should have at list one instance
new_object->invalidate_bounding_box();
ModelVolume *new_volume = new_object->add_volume(std::move(final_mesh), false);
new_volume->calculate_convex_hull();
new_volume->name = _u8L("Text");
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(1));
m_input.text_info.m_surface_type = TextInfo::TextType ::HORIZONAL;
new_volume->set_text_info(m_input.text_info);
// write emboss data into volume
m_input.base->write(*new_volume);
// set transformation
Slic3r::Geometry::Transformation tr;
tr.set_offset(center);
new_object->instances.front()->set_transformation(tr);
new_object->ensure_on_bed();
Slic3r::save_object_mesh(*new_object);
new_object->get_model()->set_assembly_pos(new_object);
// 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->get_view3D_canvas3D();
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::EType::Text)
manager.open_gizmo(GLGizmosManager::EType::Text);
// redraw scene
canvas->reload_scene(true);
}
}}} // namespace Slic3r

View File

@@ -49,7 +49,7 @@ public:
/// </summary>
/// <param name="volume">Data object for store emboss params</param>
virtual void write(ModelVolume &volume) const;
virtual TextConfiguration get_text_configuration() { return {}; }
// Define projection move
// True (raised) .. move outside from surface (MODEL_PART)
// False (engraved).. move into object (NEGATIVE_VOLUME)
@@ -176,7 +176,7 @@ struct SurfaceVolumeData
// source volumes
std::shared_ptr<const TriangleMesh> mesh;
// Transformation of volume inside of object
Transform3d tr;
Transform3d tr{Transform3d::Identity()};
};
using ModelSources = std::vector<ModelSource>;
ModelSources sources;
@@ -261,6 +261,30 @@ public:
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
struct CreateTextInput
{
std::vector<TriangleMesh> m_chars_mesh_result;
EmbossShape m_text_shape;
TextInfo text_info;
DataBasePtr base;
std::vector<Vec3d> m_position_points;
};
class CreateObjectTextJob : public JobNew
{
CreateTextInput m_input;
public:
explicit CreateObjectTextJob(CreateTextInput &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
const GLVolume *find_glvoloume_render_screen_cs(const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center);
void create_all_char_mesh(DataBase &input, std::vector<TriangleMesh> &result, EmbossShape &shape);
float get_single_char_width( const std::vector<TriangleMesh> &chars_mesh_result);
bool calc_text_lengths(std::vector<double> &text_lengths,const std::vector<TriangleMesh>& chars_mesh_result);
void calc_position_points(std::vector<Vec3d> &position_points, std::vector<double> &text_lengths, float text_gap, const Vec3d &temp_pos_dir);
struct Texture
{
@@ -304,6 +328,81 @@ public:
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
void recreate_model_volume(Slic3r::ModelObject *model_object, int volume_idx, const TriangleMesh &mesh, Geometry::Transformation &text_tran, TextInfo &text_info);
void create_text_volume(Slic3r::ModelObject *model_object, const TriangleMesh &mesh, Geometry::Transformation &text_tran, TextInfo &text_info);
class GenerateTextJob : public JobNew
{
public:
enum SurfaceType {
None,
Surface,
CharSurface,
};
struct InputInfo
{
Geometry::Transformation m_text_tran_in_object;
Geometry::Transformation m_model_object_in_world_tran;
ModelObject * mo{nullptr};
int m_volume_idx;
int hit_mesh_id;
std::vector<Vec3d> m_position_points;
std::vector<Vec3d> m_normal_points;
std::vector<Vec3d> m_cut_points_in_world;
std::vector<Vec3d> m_cut_points_in_local;
Geometry::Transformation m_text_tran_in_world; // Transform3d m_text_cs_to_world_tran;
//Transform3d m_object_cs_to_world_tran;
std::vector<TriangleMesh> m_chars_mesh_result;
Vec3d m_text_position_in_world;
Vec3f m_text_normal_in_world;
float m_text_gap;
std::vector<double> text_lengths;
Vec3d m_cut_plane_dir_in_world;
float m_thickness = 2.f;
float m_embeded_depth = 0.f;
SurfaceType m_surface_type = SurfaceType::None;
TextInfo::TextType text_surface_type;
TriangleMesh m_final_text_mesh;
Geometry::Transformation m_final_text_tran_in_object;
TextInfo text_info;
TriangleMesh slice_mesh;
EmbossShape m_text_shape;
bool use_surface = false;
float shape_scale;
bool is_outside = true;//bool is_outside = (type == ModelVolumeType::MODEL_PART);
bool first_generate = false;
Emboss::DataUpdate m_data_update;
};
static bool update_text_positions(InputInfo &input_info);
static bool generate_text_points(InputInfo &input_info);
static Geometry::Transformation get_sub_mesh_tran(const Vec3d &position, const Vec3d &normal, const Vec3d &text_up_dir, float embeded_depth);
static void get_text_mesh(TriangleMesh &result_mesh, std::vector<TriangleMesh> &chars_mesh, int i, Geometry::Transformation& local_tran);
static void get_text_mesh(TriangleMesh & result_mesh,
EmbossShape & text_shape,
BoundingBoxes & line_qds,
SurfaceVolumeData::ModelSources& input_ms_es,
DataBase &input_db,
int i,
Geometry::Transformation &mv_tran,
Geometry::Transformation &local_tran_to_object_cs,
TriangleMesh & slice_mesh);
static void generate_mesh_according_points(InputInfo& input_info);
static std::vector<Vec3d> debug_cut_points_in_world;
public:
explicit GenerateTextJob(InputInfo &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
private:
InputInfo m_input;
};
static bool check(unsigned char gizmo_type);
static bool check(const DataBase &input, bool check_fontfile, bool use_surface = false);
static bool check(const CreateVolumeParams &input);
@@ -323,6 +422,7 @@ static TriangleMesh try_create_mesh(DataBase &input);
static TriangleMesh create_mesh(DataBase &input);
static std::vector<TriangleMesh> create_meshs(DataBase &input);
static indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input);
static indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, float scale, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input);
static TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2);
static TriangleMesh cut_surface(DataBase &input1, const SurfaceVolumeData &input2);
static void _update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr);

View File

@@ -50,7 +50,6 @@ void PrintJob::prepare()
if (&job_data) {
std::string temp_file = Slic3r::resources_dir() + "/check_access_code.txt";
auto check_access_code_path = temp_file.c_str();
BOOST_LOG_TRIVIAL(trace) << "sned_job: check_access_code_path = " << check_access_code_path;
job_data._temp_path = fs::path(check_access_code_path);
}
@@ -140,7 +139,7 @@ wxString PrintJob::get_http_error_msg(unsigned int status, std::string body)
;
}
return wxEmptyString;
}
}
void PrintJob::process()
{
@@ -256,6 +255,7 @@ void PrintJob::process()
params.auto_bed_leveling = this->auto_bed_leveling;
params.auto_flow_cali = this->auto_flow_cali;
params.auto_offset_cali = this->auto_offset_cali;
params.task_ext_change_assist = this->task_ext_change_assist;
if (m_print_type == "from_sdcard_view") {
params.dst_file = m_dst_path;
@@ -299,6 +299,7 @@ void PrintJob::process()
std::regex pattern("_+");
params.project_name = std::regex_replace(mall_model_name, pattern, "_");
params.project_name = truncate_string(params.project_name, 100);
}
catch (...) {}
}
@@ -356,8 +357,6 @@ void PrintJob::process()
}
}
if (params.preset_name.empty() && m_print_type == "from_normal") { params.preset_name = wxString::Format("%s_plate_%d", m_project_name, curr_plate_idx).ToStdString(); }
if (params.project_name.empty()) {params.project_name = m_project_name;}
@@ -383,12 +382,12 @@ void PrintJob::process()
bool is_try_lan_mode = false;
bool is_try_lan_mode_failed = false;
auto update_fn = [this,
auto update_fn = [this,
&is_try_lan_mode,
&is_try_lan_mode_failed,
&msg,
&error_str,
&curr_percent,
&msg,
&error_str,
&curr_percent,
&error_text,
StagePercentPoint
](int stage, int code, std::string info) {
@@ -471,7 +470,7 @@ void PrintJob::process()
return was_canceled();
};
DeviceManager* dev = wxGetApp().getDeviceManager();
MachineObject* obj = dev->get_selected_machine();
@@ -593,7 +592,7 @@ void PrintJob::process()
this->update_status(curr_percent, _L("Sending print job through cloud service"));
result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn);
}
}
}
} else {
if (this->has_sdcard) {
this->update_status(curr_percent, _L("Sending print job over LAN"));
@@ -631,7 +630,7 @@ void PrintJob::process()
if (result != QIDI_NETWORK_ERR_CANCELED) {
this->show_error_info(msg_text, 0, "", "");
}
BOOST_LOG_TRIVIAL(error) << "print_job: failed, result = " << result;
} else {
// wait for printer mqtt ready the same job id

View File

@@ -73,7 +73,7 @@ public:
bool m_is_calibration_task = false;
int m_print_from_sdc_plate_idx = 0;
bool m_local_use_ssl_for_mqtt { true };
bool m_local_use_ssl_for_ftp { true };
bool task_bed_leveling;
@@ -84,12 +84,13 @@ public:
bool cloud_print_only { false };
bool has_sdcard { false };
bool task_use_ams { true };
bool task_ext_change_assist { false };
int auto_bed_leveling{0};
int auto_flow_cali{0};
int auto_offset_cali{0};
void set_print_config(std::string bed_type, bool bed_leveling, bool flow_cali, bool vabration_cali, bool record_timelapse, bool layer_inspect,
void set_print_config(std::string bed_type, bool bed_leveling, bool flow_cali, bool vabration_cali, bool record_timelapse, bool layer_inspect, bool ext_change_assist,
int auto_bed_levelingt,
int auto_flow_calit,
int auto_offset_calit)
@@ -100,10 +101,12 @@ public:
task_vibration_cali = vabration_cali;
task_record_timelapse = record_timelapse;
task_layer_inspect = layer_inspect;
task_ext_change_assist = ext_change_assist;
auto_bed_leveling = auto_bed_levelingt;
auto_flow_cali = auto_flow_calit;
auto_offset_cali = auto_offset_calit;
}
int status_range() const override

View File

@@ -40,7 +40,6 @@ void SendJob::prepare()
if (&job_data) {
std::string temp_file = Slic3r::resources_dir() + "/check_access_code.txt";
auto check_access_code_path = temp_file.c_str();
BOOST_LOG_TRIVIAL(trace) << "sned_job: check_access_code_path = " << check_access_code_path;
job_data._temp_path = fs::path(check_access_code_path);
}
}
@@ -121,8 +120,8 @@ void SendJob::process()
std::string http_body;
// local print access
params.dev_ip = m_dev_ip;
params.username = "qdtp";
@@ -148,7 +147,7 @@ void SendJob::process()
m_job_finished = true;
return;
}
/* display info */
msg = _L("Sending gcode file over LAN");
@@ -316,7 +315,7 @@ void SendJob::process()
else if (params.password.empty())
params.comments = "no_password";
if (!params.password.empty()
if (!params.password.empty()
&& !params.dev_ip.empty()
&& this->has_sdcard) {
// try to send local with record

View File

@@ -62,7 +62,9 @@ void UpgradeNetworkJob::process()
auto path_str = tmp_path.string() + wxString::Format(".%d%s", get_current_pid(), ".tmp").ToStdString();
tmp_path = fs::path(path_str);
BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: save netowrk_plugin to " << tmp_path.string();
#if !QDT_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << "UpgradeNetworkJob: save network_plugin to " << PathSanitizer::sanitize(tmp_path);
#endif
auto cancel_fn = [this]() {
return was_canceled();