mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-02 09:58:41 +03:00
update slic3r
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
222
src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp
Normal file
222
src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp
Normal 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;
|
||||
}
|
||||
|
||||
89
src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp
Normal file
89
src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp
Normal 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_
|
||||
158
src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp
Normal file
158
src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp
Normal 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);
|
||||
}
|
||||
36
src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp
Normal file
36
src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp
Normal 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_
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user