mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-06 05:38:54 +08:00
Unify scale and suggest tesselation tolerance for svg curves
This commit is contained in:
parent
fcdaf6acbf
commit
c63f9f3e11
@ -32,6 +32,29 @@ NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units,
|
|||||||
return {image, ::nsvgDelete};
|
return {image, ::nsvgDelete};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool save(const NSVGimage &image, const std::string &svg_file_path)
|
||||||
|
{
|
||||||
|
//BoundingBox bb = get_extents(to_polygons(image));
|
||||||
|
FILE * file = boost::nowide::fopen(svg_file_path.c_str(), "w");
|
||||||
|
if (file == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fprintf(file, "<svg xmlns=\"http://www.w3.org/2000/svg\">\n");
|
||||||
|
for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
|
||||||
|
std::string d = "M "; // move on start point
|
||||||
|
for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
|
||||||
|
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
|
||||||
|
for (size_t i = 0; i < path_size; i += 3) {
|
||||||
|
const float *p = &path->pts[i * 2];
|
||||||
|
d += std::to_string(p[0]) + "," + std::to_string(p[1]) + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d += "Z"; // closed path
|
||||||
|
fprintf(file, " <path fill=\"#D2D2D2\" d=\"%s\" />\n", d.c_str());
|
||||||
|
}
|
||||||
|
fprintf(file, "</svg>\n");
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -48,6 +71,41 @@ bool is_useable(const NSVGshape &shape)
|
|||||||
|
|
||||||
Point::coord_type to_coor(float val, float scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
|
Point::coord_type to_coor(float val, float scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) {
|
||||||
|
// f .. first
|
||||||
|
// s .. second
|
||||||
|
auto det = [](const Vec2f &f, const Vec2f &s) {
|
||||||
|
return std::fabs(f.x() * s.y() - f.y() * s.x());
|
||||||
|
};
|
||||||
|
|
||||||
|
Vec2f pd = (p4 - p1);
|
||||||
|
Vec2f pd2 = (p2 - p4);
|
||||||
|
float d2 = det(pd2, pd);
|
||||||
|
Vec2f pd3 = (p3 - p4);
|
||||||
|
float d3 = det(pd3, pd);
|
||||||
|
float d23 = d2 + d3;
|
||||||
|
|
||||||
|
return (d23 * d23) >= tessTol * pd.squaredNorm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
|
||||||
|
bool is_line(const float *p, float precision = 1e-4){
|
||||||
|
//Vec2f p1(p[0], p[1]);
|
||||||
|
//Vec2f p2(p[2], p[3]);
|
||||||
|
//Vec2f p3(p[4], p[5]);
|
||||||
|
//Vec2f p4(p[6], p[7]);
|
||||||
|
float dx_3 = (p[6] - p[0]) / 3.f;
|
||||||
|
float dy_3 = (p[7] - p[1]) / 3.f;
|
||||||
|
|
||||||
|
return
|
||||||
|
is_approx(p[2], p[0] + dx_3, precision) &&
|
||||||
|
is_approx(p[4], p[6] - dx_3, precision) &&
|
||||||
|
is_approx(p[3], p[1] + dy_3, precision) &&
|
||||||
|
is_approx(p[5], p[7] - dy_3, precision);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert cubic curve to lines
|
/// Convert cubic curve to lines
|
||||||
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
|
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
|
||||||
@ -60,26 +118,11 @@ Point::coord_type to_coor(float val, float scale) { return static_cast<Point::co
|
|||||||
/// <param name="p3">Curve point</param>
|
/// <param name="p3">Curve point</param>
|
||||||
/// <param name="p4">Curve point</param>
|
/// <param name="p4">Curve point</param>
|
||||||
/// <param name="level">Actual depth of recursion</param>
|
/// <param name="level">Actual depth of recursion</param>
|
||||||
/// <param name="scale">Scale of point - multiplicator
|
void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level)
|
||||||
/// NOTE: increase preccission by number greater than 1.</param>
|
|
||||||
void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level, float scale)
|
|
||||||
{
|
{
|
||||||
// f .. first
|
if (!need_flattening(tessTol, p1, p2, p3, p4)) {
|
||||||
// s .. second
|
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
|
||||||
auto det = [](const Vec2f &f, const Vec2f &s) {
|
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
|
||||||
return std::fabs(f.x() * s.y() - f.y() * s.x());
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec2f pd = p4 - p1;
|
|
||||||
Vec2f pd2 = p2 - p4;
|
|
||||||
float d2 = det(pd2, pd);
|
|
||||||
Vec2f pd3 = p3 - p4;
|
|
||||||
float d3 = det(pd3, pd);
|
|
||||||
float d23 = d2 + d3;
|
|
||||||
|
|
||||||
if ((d23 * d23) < tessTol * pd.squaredNorm()) {
|
|
||||||
Point::coord_type x = to_coor(p4.x(), scale);
|
|
||||||
Point::coord_type y = to_coor(p4.y(), scale);
|
|
||||||
polygon.points.emplace_back(x, y);
|
polygon.points.emplace_back(x, y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -94,8 +137,8 @@ void flatten_cubic_bez(Polygon &polygon, float tessTol, const Vec2f& p1, const V
|
|||||||
Vec2f p123 = (p12 + p23) * 0.5f;
|
Vec2f p123 = (p12 + p23) * 0.5f;
|
||||||
Vec2f p234 = (p23 + p34) * 0.5f;
|
Vec2f p234 = (p23 + p34) * 0.5f;
|
||||||
Vec2f p1234 = (p123 + p234) * 0.5f;
|
Vec2f p1234 = (p123 + p234) * 0.5f;
|
||||||
flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level, scale);
|
flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level);
|
||||||
flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level, scale);
|
flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float scale)
|
Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float scale)
|
||||||
@ -110,11 +153,18 @@ Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float s
|
|||||||
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
|
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
|
||||||
for (size_t i = 0; i < path_size; i += 3) {
|
for (size_t i = 0; i < path_size; i += 3) {
|
||||||
const float *p = &path->pts[i * 2];
|
const float *p = &path->pts[i * 2];
|
||||||
|
if (is_line(p)) {
|
||||||
|
// point p4
|
||||||
|
Point::coord_type x = to_coor(p[6], scale);
|
||||||
|
Point::coord_type y = to_coor(p[7], scale);
|
||||||
|
polygon.points.emplace_back(x, y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Vec2f p1(p[0], p[1]);
|
Vec2f p1(p[0], p[1]);
|
||||||
Vec2f p2(p[2], p[3]);
|
Vec2f p2(p[2], p[3]);
|
||||||
Vec2f p3(p[4], p[5]);
|
Vec2f p3(p[4], p[5]);
|
||||||
Vec2f p4(p[6], p[7]);
|
Vec2f p4(p[6], p[7]);
|
||||||
flatten_cubic_bez(polygon, tessTol, p1, p2, p3, p4, max_level, scale);
|
flatten_cubic_bez(polygon, tessTol, p1 * scale, p2 * scale, p3 * scale, p4 * scale, max_level);
|
||||||
}
|
}
|
||||||
if (path->closed && !polygon.empty()) {
|
if (path->closed && !polygon.empty()) {
|
||||||
polygons.push_back(polygon);
|
polygons.push_back(polygon);
|
||||||
@ -123,6 +173,8 @@ Polygons to_polygons(NSVGpath *first_path, float tessTol, int max_level, float s
|
|||||||
}
|
}
|
||||||
if (!polygon.empty())
|
if (!polygon.empty())
|
||||||
polygons.push_back(polygon);
|
polygons.push_back(polygon);
|
||||||
|
|
||||||
|
remove_same_neighbor(polygons);
|
||||||
return polygons;
|
return polygons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ Polygons to_polygons(const NSVGimage &image, float tessTol = 10., int max_leve
|
|||||||
ExPolygons to_expolygons(const NSVGimage &image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true);
|
ExPolygons to_expolygons(const NSVGimage &image, float tessTol = 10., int max_level = 10, float scale = 1.f, bool is_y_negative = true);
|
||||||
|
|
||||||
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
|
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
|
||||||
NSVGimage_ptr nsvgParseFromFile(const std::string& filename, const char *units = "mm", float dpi = 96.0f);
|
NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f);
|
||||||
|
bool save(const NSVGimage &image, const std::string &svg_file_path);
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
#endif // slic3r_NSVGUtils_hpp_
|
#endif // slic3r_NSVGUtils_hpp_
|
||||||
|
@ -47,9 +47,6 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent)
|
|||||||
// Private functions to create emboss volume
|
// Private functions to create emboss volume
|
||||||
namespace{
|
namespace{
|
||||||
|
|
||||||
// Default scale of SVG image
|
|
||||||
static constexpr double DEFAULT_SCALE = 1e-2;
|
|
||||||
|
|
||||||
// Variable keep limits for variables
|
// Variable keep limits for variables
|
||||||
const struct Limits
|
const struct Limits
|
||||||
{
|
{
|
||||||
@ -64,13 +61,20 @@ const struct Limits
|
|||||||
/// <returns>File path to svg</returns>
|
/// <returns>File path to svg</returns>
|
||||||
std::string choose_svg_file();
|
std::string choose_svg_file();
|
||||||
|
|
||||||
|
constexpr double get_tesselation_tolerance(double scale){
|
||||||
|
constexpr double tesselation_tolerance_in_mm = .1; //8e-2;
|
||||||
|
constexpr double tesselation_tolerance_scaled = (tesselation_tolerance_in_mm*tesselation_tolerance_in_mm) / SCALING_FACTOR / SCALING_FACTOR;
|
||||||
|
return tesselation_tolerance_scaled / scale / scale;
|
||||||
|
}
|
||||||
|
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, double tesselation_tolerance);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG.
|
/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG.
|
||||||
/// Than let select contour
|
/// Than let select contour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filepath">SVG file path, when empty promt user to select one</param>
|
/// <param name="filepath">SVG file path, when empty promt user to select one</param>
|
||||||
/// <returns>EmbossShape to create</returns>
|
/// <returns>EmbossShape to create</returns>
|
||||||
EmbossShape select_shape(std::string_view filepath = "");
|
EmbossShape select_shape(std::string_view filepath = "", double tesselation_tolerance_in_mm = get_tesselation_tolerance(1.));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create new embos data
|
/// Create new embos data
|
||||||
@ -114,6 +118,8 @@ enum class IconType : unsigned {
|
|||||||
change_file_hover,
|
change_file_hover,
|
||||||
bake,
|
bake,
|
||||||
bake_hover,
|
bake_hover,
|
||||||
|
save,
|
||||||
|
save_hover,
|
||||||
lock,
|
lock,
|
||||||
lock_hover,
|
lock_hover,
|
||||||
unlock,
|
unlock,
|
||||||
@ -392,6 +398,8 @@ IconManager::Icons init_icons(IconManager &mng, const GuiCfg &cfg)
|
|||||||
{"open.svg", size, IconManager::RasterType::color}, // changhe_file_hovered
|
{"open.svg", size, IconManager::RasterType::color}, // changhe_file_hovered
|
||||||
{"burn.svg", size, IconManager::RasterType::white_only_data}, // bake_file
|
{"burn.svg", size, IconManager::RasterType::white_only_data}, // bake_file
|
||||||
{"burn.svg", size, IconManager::RasterType::color}, // bake_hovered
|
{"burn.svg", size, IconManager::RasterType::color}, // bake_hovered
|
||||||
|
{"save.svg", size, IconManager::RasterType::white_only_data}, // save
|
||||||
|
{"save.svg", size, IconManager::RasterType::color}, // save_hovered
|
||||||
{"lock_closed.svg", size, IconManager::RasterType::white_only_data}, // lock
|
{"lock_closed.svg", size, IconManager::RasterType::white_only_data}, // lock
|
||||||
{"lock_open_f.svg", size, IconManager::RasterType::white_only_data}, // lock_hovered
|
{"lock_open_f.svg", size, IconManager::RasterType::white_only_data}, // lock_hovered
|
||||||
{"lock_open.svg", size, IconManager::RasterType::white_only_data}, // unlock
|
{"lock_open.svg", size, IconManager::RasterType::white_only_data}, // unlock
|
||||||
@ -567,6 +575,13 @@ NSVGimage* init_image(EmbossShape::SvgFile &svg_file) {
|
|||||||
// init svg image
|
// init svg image
|
||||||
svg_file.image = nsvgParseFromFile(svg_file.path);
|
svg_file.image = nsvgParseFromFile(svg_file.path);
|
||||||
|
|
||||||
|
if (svg_file.image.get() == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Disable stroke
|
||||||
|
for (NSVGshape *shape = svg_file.image->shapes; shape != NULL; shape = shape->next)
|
||||||
|
shape->stroke.type = 0;
|
||||||
|
|
||||||
return svg_file.image.get();
|
return svg_file.image.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,7 +599,7 @@ bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px)
|
|||||||
// GLTexture::load_from_svg_file(filepath, false, false, false, max_size_px);
|
// GLTexture::load_from_svg_file(filepath, false, false, false, max_size_px);
|
||||||
|
|
||||||
// NOTE: Can not use es.shape --> it is aligned and one need offset in svg
|
// NOTE: Can not use es.shape --> it is aligned and one need offset in svg
|
||||||
ExPolygons shape = to_expolygons(*image);
|
Polygons shape = to_polygons(*image);
|
||||||
if (shape.empty())
|
if (shape.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -618,7 +633,7 @@ bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px)
|
|||||||
float tx = static_cast<float>(-bb.min.x() * scale);
|
float tx = static_cast<float>(-bb.min.x() * scale);
|
||||||
float ty = static_cast<float>(bb.max.y() * scale); // Reverse direction of y
|
float ty = static_cast<float>(bb.max.y() * scale); // Reverse direction of y
|
||||||
int stride = texture.width * channels_count;
|
int stride = texture.width * channels_count;
|
||||||
nsvgRasterizeXY(rast, image, tx, ty, scale, scale, data.data(), texture.width, texture.height, stride);
|
nsvgRasterize(rast, image, tx, ty, scale, data.data(), texture.width, texture.height, stride);
|
||||||
|
|
||||||
// fill by monotone color
|
// fill by monotone color
|
||||||
std::vector<unsigned char> fill_color = {201, 201, 201}; // RGB and keep same alpha
|
std::vector<unsigned char> fill_color = {201, 201, 201}; // RGB and keep same alpha
|
||||||
@ -629,6 +644,8 @@ bool init_texture(Texture &texture, ModelVolume &mv, unsigned max_size_px)
|
|||||||
|
|
||||||
// sends data to gpu
|
// sends data to gpu
|
||||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||||
|
if (texture.id != 0)
|
||||||
|
glsafe(::glDeleteTextures(1, &texture.id));
|
||||||
glsafe(::glGenTextures(1, &texture.id));
|
glsafe(::glGenTextures(1, &texture.id));
|
||||||
glsafe(::glBindTexture(GL_TEXTURE_2D, texture.id));
|
glsafe(::glBindTexture(GL_TEXTURE_2D, texture.id));
|
||||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) texture.width, (GLsizei) texture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void *) data.data()));
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) texture.width, (GLsizei) texture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void *) data.data()));
|
||||||
@ -684,10 +701,19 @@ void GLGizmoSVG::set_volume_by_selection()
|
|||||||
m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
|
m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
|
||||||
m_distance = calc_distance(*gl_volume, m_raycast_manager, m_parent);
|
m_distance = calc_distance(*gl_volume, m_raycast_manager, m_parent);
|
||||||
|
|
||||||
|
m_shape_bb = get_extents(m_volume_shape.shapes_with_ids);
|
||||||
|
|
||||||
// calculate scale for height and depth inside of scaled object instance
|
// calculate scale for height and depth inside of scaled object instance
|
||||||
calculate_scale();
|
calculate_scale();
|
||||||
}
|
}
|
||||||
|
namespace {
|
||||||
|
void delete_texture(Texture& texture){
|
||||||
|
if (texture.id != 0) {
|
||||||
|
glsafe(::glDeleteTextures(1, &texture.id));
|
||||||
|
texture.id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
void GLGizmoSVG::reset_volume()
|
void GLGizmoSVG::reset_volume()
|
||||||
{
|
{
|
||||||
if (m_volume == nullptr)
|
if (m_volume == nullptr)
|
||||||
@ -697,11 +723,7 @@ void GLGizmoSVG::reset_volume()
|
|||||||
m_volume_id.id = 0;
|
m_volume_id.id = 0;
|
||||||
m_volume_shape.shapes_with_ids.clear();
|
m_volume_shape.shapes_with_ids.clear();
|
||||||
m_filename_preview.clear();
|
m_filename_preview.clear();
|
||||||
|
delete_texture(m_texture);
|
||||||
if (m_texture.id != 0) {
|
|
||||||
glsafe(::glDeleteTextures(1, &m_texture.id));
|
|
||||||
m_texture.id = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSVG::calculate_scale() {
|
void GLGizmoSVG::calculate_scale() {
|
||||||
@ -721,6 +743,7 @@ void GLGizmoSVG::calculate_scale() {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
calc(Vec3d::UnitX(), m_scale_width);
|
||||||
calc(Vec3d::UnitY(), m_scale_height);
|
calc(Vec3d::UnitY(), m_scale_height);
|
||||||
calc(Vec3d::UnitZ(), m_scale_depth);
|
calc(Vec3d::UnitZ(), m_scale_depth);
|
||||||
}
|
}
|
||||||
@ -797,7 +820,42 @@ void GLGizmoSVG::draw_window()
|
|||||||
draw_model_type();
|
draw_model_type();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace {
|
||||||
|
size_t count(const NSVGshape &shape){
|
||||||
|
size_t res = 0;
|
||||||
|
for (const NSVGshape *shape_ptr = &shape; shape_ptr != NULL; shape_ptr = shape_ptr->next)
|
||||||
|
++res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(const ExPolygonsWithIds& shapes_with_ids, unsigned max_size)
|
||||||
|
{
|
||||||
|
ImVec2 actual_pos = ImGui::GetCursorPos();
|
||||||
|
// draw shapes
|
||||||
|
BoundingBox bb;
|
||||||
|
for (const ExPolygonsWithId &shape : shapes_with_ids)
|
||||||
|
bb.merge(get_extents(shape.expoly));
|
||||||
|
|
||||||
|
Point bb_size = bb.size();
|
||||||
|
double scale = max_size / (double) std::max(bb_size.x(), bb_size.y());
|
||||||
|
ImVec2 win_offset = ImGui::GetWindowPos();
|
||||||
|
Point offset(win_offset.x + actual_pos.x, win_offset.y + actual_pos.y);
|
||||||
|
offset += bb_size / 2 * scale;
|
||||||
|
auto draw_polygon = [&scale, offset](Slic3r::Polygon p) {
|
||||||
|
p.scale(scale, -scale); // Y mirror
|
||||||
|
p.translate(offset);
|
||||||
|
ImGuiWrapper::draw(p);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const ExPolygonsWithId &shape : shapes_with_ids) {
|
||||||
|
for (const ExPolygon &expoly : shape.expoly) {
|
||||||
|
draw_polygon(expoly.contour);
|
||||||
|
for (const Slic3r::Polygon &hole : expoly.holes)
|
||||||
|
draw_polygon(hole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
bool GLGizmoSVG::draw_preview(){
|
bool GLGizmoSVG::draw_preview(){
|
||||||
assert(m_volume->emboss_shape.has_value());
|
assert(m_volume->emboss_shape.has_value());
|
||||||
if (!m_volume->emboss_shape.has_value()) {
|
if (!m_volume->emboss_shape.has_value()) {
|
||||||
@ -810,10 +868,16 @@ bool GLGizmoSVG::draw_preview(){
|
|||||||
if (m_texture.id == 0)
|
if (m_texture.id == 0)
|
||||||
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
|
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
|
||||||
|
|
||||||
|
::draw(m_volume_shape.shapes_with_ids, m_gui_cfg->texture_max_size_px);
|
||||||
|
|
||||||
if (m_texture.id != 0) {
|
if (m_texture.id != 0) {
|
||||||
ImTextureID id = (void *) static_cast<intptr_t>(m_texture.id);
|
ImTextureID id = (void *) static_cast<intptr_t>(m_texture.id);
|
||||||
ImVec2 s(m_texture.width, m_texture.height);
|
ImVec2 s(m_texture.width, m_texture.height);
|
||||||
ImGui::Image(id, s);
|
ImGui::Image(id, s);
|
||||||
|
if(ImGui::IsItemHovered()){
|
||||||
|
size_t count_shapes = count(*m_volume->emboss_shape->svg_file.image->shapes);
|
||||||
|
ImGui::SetTooltip("%d count shapes", count_shapes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_filename_preview.empty()){
|
if (m_filename_preview.empty()){
|
||||||
@ -837,6 +901,8 @@ bool GLGizmoSVG::draw_preview(){
|
|||||||
ImGui::SetTooltip("%s", tooltip.c_str());
|
ImGui::SetTooltip("%s", tooltip.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool file_changed = false;
|
||||||
|
|
||||||
// Re-Load button
|
// Re-Load button
|
||||||
bool can_reload = !m_volume_shape.svg_file.path.empty();
|
bool can_reload = !m_volume_shape.svg_file.path.empty();
|
||||||
if (can_reload) {
|
if (can_reload) {
|
||||||
@ -845,9 +911,7 @@ bool GLGizmoSVG::draw_preview(){
|
|||||||
if (!boost::filesystem::exists(m_volume_shape.svg_file.path)) {
|
if (!boost::filesystem::exists(m_volume_shape.svg_file.path)) {
|
||||||
m_volume_shape.svg_file.path.clear();
|
m_volume_shape.svg_file.path.clear();
|
||||||
} else {
|
} else {
|
||||||
m_volume_shape.shapes_with_ids = select_shape(m_volume_shape.svg_file.path).shapes_with_ids;
|
file_changed = true;
|
||||||
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
|
|
||||||
process();
|
|
||||||
}
|
}
|
||||||
} else if (ImGui::IsItemHovered())
|
} else if (ImGui::IsItemHovered())
|
||||||
ImGui::SetTooltip("%s", _u8L("Re-load SVG file from disk.").c_str());
|
ImGui::SetTooltip("%s", _u8L("Re-load SVG file from disk.").c_str());
|
||||||
@ -855,13 +919,24 @@ bool GLGizmoSVG::draw_preview(){
|
|||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (clickable(get_icon(m_icons, IconType::change_file), get_icon(m_icons, IconType::change_file_hover))) {
|
if (clickable(get_icon(m_icons, IconType::change_file), get_icon(m_icons, IconType::change_file_hover))) {
|
||||||
m_volume_shape.shapes_with_ids = select_shape().shapes_with_ids;
|
std::string new_path = choose_svg_file();
|
||||||
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
|
if (!new_path.empty()) {
|
||||||
process();
|
file_changed = true;
|
||||||
|
m_volume_shape.svg_file.path = new_path;
|
||||||
|
}
|
||||||
} else if (ImGui::IsItemHovered()) {
|
} else if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("%s", _u8L("Change to another .svg file").c_str());
|
ImGui::SetTooltip("%s", _u8L("Change to another .svg file").c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file_changed) {
|
||||||
|
double tes_tol = get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)));
|
||||||
|
EmbossShape es = select_shape(m_volume_shape.svg_file.path, tes_tol);
|
||||||
|
m_volume_shape.svg_file.image = es.svg_file.image;
|
||||||
|
m_volume_shape.shapes_with_ids = es.shapes_with_ids;
|
||||||
|
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (clickable(get_icon(m_icons, IconType::bake), get_icon(m_icons, IconType::bake_hover))) {
|
if (clickable(get_icon(m_icons, IconType::bake), get_icon(m_icons, IconType::bake_hover))) {
|
||||||
m_volume->emboss_shape.reset();
|
m_volume->emboss_shape.reset();
|
||||||
@ -870,6 +945,13 @@ bool GLGizmoSVG::draw_preview(){
|
|||||||
} else if (ImGui::IsItemHovered()) {
|
} else if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("%s", _u8L("Bake to uneditable part and save copyright of svg").c_str());
|
ImGui::SetTooltip("%s", _u8L("Bake to uneditable part and save copyright of svg").c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (clickable(get_icon(m_icons, IconType::save), get_icon(m_icons, IconType::save_hover))) {
|
||||||
|
Slic3r::save(*m_volume_shape.svg_file.image, "C:/data/temp/saved.svg");
|
||||||
|
} else if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("%s", _u8L("Save as svg file").c_str());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,9 +965,16 @@ void GLGizmoSVG::draw_depth()
|
|||||||
double &value = m_volume_shape.projection.depth;
|
double &value = m_volume_shape.projection.depth;
|
||||||
if (use_inch) {
|
if (use_inch) {
|
||||||
const char *size_format = "%.2f in";
|
const char *size_format = "%.2f in";
|
||||||
double value_inch = value * ObjectManipulation::mm_to_in;
|
double value_inch = value * ObjectManipulation::mm_to_in * m_scale_depth.value_or(1.f);
|
||||||
if (ImGui::InputDouble("##depth", &value_inch, 1., 10., size_format)) {
|
if (ImGui::InputDouble("##depth", &value_inch, 1., 10., size_format)) {
|
||||||
value = value_inch * ObjectManipulation::in_to_mm;
|
value = value_inch * ObjectManipulation::in_to_mm / m_scale_depth.value_or(1.f);
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
} else if (m_scale_depth.has_value()) {
|
||||||
|
const char *size_format = "%.1f mm";
|
||||||
|
double value_mm = value * (*m_scale_depth);
|
||||||
|
if (ImGui::InputDouble("##depth", &value_mm, 1., 10., size_format)) {
|
||||||
|
value = value_mm / (*m_scale_depth);
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -903,15 +992,14 @@ void GLGizmoSVG::draw_size()
|
|||||||
|
|
||||||
bool use_inch = wxGetApp().app_config->get_bool("use_inches");
|
bool use_inch = wxGetApp().app_config->get_bool("use_inches");
|
||||||
|
|
||||||
// TODO: cache it
|
Point size = m_shape_bb.size();
|
||||||
BoundingBox bb = get_extents(m_volume_shape.shapes_with_ids);
|
double width = size.x() * m_volume_shape.scale * m_scale_width.value_or(1.f);
|
||||||
Point size = bb.size();
|
|
||||||
|
|
||||||
double width = size.x() * m_volume_shape.scale;
|
|
||||||
if (use_inch) width *= ObjectManipulation::mm_to_in;
|
if (use_inch) width *= ObjectManipulation::mm_to_in;
|
||||||
double height = size.y() * m_volume_shape.scale;
|
double height = size.y() * m_volume_shape.scale * m_scale_height.value_or(1.f);
|
||||||
if (use_inch) height *= ObjectManipulation::mm_to_in;
|
if (use_inch) height *= ObjectManipulation::mm_to_in;
|
||||||
|
|
||||||
|
std::optional<Vec3d> new_absolute_scale;
|
||||||
|
|
||||||
if (m_keep_ratio) {
|
if (m_keep_ratio) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::setprecision(2) << width << " x " << height << " " << (use_inch ? "in" : "mm");
|
ss << std::setprecision(2) << width << " x " << height << " " << (use_inch ? "in" : "mm");
|
||||||
@ -919,12 +1007,15 @@ void GLGizmoSVG::draw_size()
|
|||||||
ImGui::SameLine(m_gui_cfg->input_offset);
|
ImGui::SameLine(m_gui_cfg->input_offset);
|
||||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||||
|
|
||||||
|
// convert to float for slider
|
||||||
float width_f = width;
|
float width_f = width;
|
||||||
if (m_imgui->slider_float("##width_size_slider", &width_f, 5.f, 100.f, ss.str().c_str())) {
|
if (m_imgui->slider_float("##width_size_slider", &width_f, 5.f, 100.f, ss.str().c_str())) {
|
||||||
if (use_inch)
|
double width_ratio = width_f / width;
|
||||||
width_f *= ObjectManipulation::in_to_mm;
|
if (std::fabs(width_ratio - 1.) > 1e-4) {
|
||||||
m_volume_shape.scale = width_f / size.x();
|
m_scale_width = m_scale_width.value_or(1.f) * width_ratio;
|
||||||
process();
|
m_scale_height = m_scale_height.value_or(1.f) * width_ratio;
|
||||||
|
new_absolute_scale = Vec3d(*m_scale_width, *m_scale_height, 1.);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGuiInputTextFlags flags = 0;
|
ImGuiInputTextFlags flags = 0;
|
||||||
@ -939,49 +1030,69 @@ void GLGizmoSVG::draw_size()
|
|||||||
|
|
||||||
ImGui::SameLine(m_gui_cfg->input_offset);
|
ImGui::SameLine(m_gui_cfg->input_offset);
|
||||||
ImGui::SetNextItemWidth(input_width);
|
ImGui::SetNextItemWidth(input_width);
|
||||||
|
double prev_width = width;
|
||||||
if (ImGui::InputDouble("##width", &width, step, fast_step, size_format, flags)) {
|
if (ImGui::InputDouble("##width", &width, step, fast_step, size_format, flags)) {
|
||||||
if (use_inch)
|
double width_ratio = width / prev_width;
|
||||||
width *= ObjectManipulation::in_to_mm;
|
m_scale_width = m_scale_width.value_or(1.f) * width_ratio;
|
||||||
m_volume_shape.scale = width / size.x();
|
new_absolute_scale = Vec3d(*m_scale_width, m_scale_height.value_or(1.f), 1.);
|
||||||
process();
|
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
ImGui::SetTooltip("%s", _u8L("Width of SVG.").c_str());
|
ImGui::SetTooltip("%s", "Width of SVG.");
|
||||||
|
|
||||||
ImGui::SameLine(second_offset);
|
ImGui::SameLine(second_offset);
|
||||||
ImGui::SetNextItemWidth(input_width);
|
ImGui::SetNextItemWidth(input_width);
|
||||||
|
double prev_height = height;
|
||||||
if (ImGui::InputDouble("##height", &height, step, fast_step, size_format, flags)) {
|
if (ImGui::InputDouble("##height", &height, step, fast_step, size_format, flags)) {
|
||||||
if (use_inch)
|
double height_ratio = height / prev_height;
|
||||||
height *= ObjectManipulation::in_to_mm;
|
m_scale_height = m_scale_height.value_or(1.f) * height_ratio;
|
||||||
m_volume_shape.scale = height / size.y();
|
new_absolute_scale = Vec3d(m_scale_width.value_or(1.f), *m_scale_height, 1.);
|
||||||
process();
|
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
ImGui::SetTooltip("%s", _u8L("Height of SVG.").c_str());
|
ImGui::SetTooltip("%s", "Height of SVG.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock on ratio m_keep_ratio
|
// Lock on ratio m_keep_ratio
|
||||||
//ImGui::SameLine(m_gui_cfg->lock_offset);
|
ImGui::SameLine(m_gui_cfg->lock_offset);
|
||||||
//const IconManager::Icon &icon = get_icon(m_icons, m_keep_ratio ? IconType::lock : IconType::unlock);
|
const IconManager::Icon &icon = get_icon(m_icons, m_keep_ratio ? IconType::lock : IconType::unlock);
|
||||||
//const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_ratio ? IconType::lock_hover : IconType::unlock_hover);
|
const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_ratio ? IconType::lock_hover : IconType::unlock_hover);
|
||||||
//if (button(icon, icon_hover, icon))
|
if (button(icon, icon_hover, icon))
|
||||||
// m_keep_ratio = !m_keep_ratio;
|
m_keep_ratio = !m_keep_ratio;
|
||||||
//if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
// ImGui::SetTooltip("%s", (m_keep_ratio ?
|
ImGui::SetTooltip("%s", (m_keep_ratio ?
|
||||||
// _u8L("Free set of width and height value."):
|
_u8L("Free set of width and height value."):
|
||||||
// _u8L("Keep same ratio of width to height.")
|
_u8L("Keep same ratio of width to height.")
|
||||||
// ).c_str());
|
).c_str());
|
||||||
|
|
||||||
|
|
||||||
// reset button
|
// reset button
|
||||||
bool can_reset = !is_approx(m_volume_shape.scale, DEFAULT_SCALE);
|
bool can_reset = m_scale_width.has_value() || m_scale_height.has_value() || m_scale_depth.has_value();
|
||||||
if (can_reset) {
|
if (can_reset) {
|
||||||
if (reset_button(m_icons)) {
|
if (reset_button(m_icons)) {
|
||||||
m_volume_shape.scale = DEFAULT_SCALE;
|
new_absolute_scale = Vec3d(1., 1., 1.);
|
||||||
process();
|
|
||||||
} else if (ImGui::IsItemHovered())
|
} else if (ImGui::IsItemHovered())
|
||||||
ImGui::SetTooltip("%s", _u8L("Reset scale to loaded one from the SVG").c_str());
|
ImGui::SetTooltip("%s", _u8L("Reset scale to loaded one from the SVG").c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new_absolute_scale.has_value()){
|
||||||
|
Selection &selection = m_parent.get_selection();
|
||||||
|
selection.setup_cache();
|
||||||
|
TransformationType type = m_volume->is_the_only_one_part() ?
|
||||||
|
TransformationType::Instance_Absolute_Independent :
|
||||||
|
TransformationType::Local_Absolute_Independent;
|
||||||
|
selection.scale(*new_absolute_scale, type);
|
||||||
|
m_parent.do_scale(L("Resize"));
|
||||||
|
wxGetApp().obj_manipul()->set_dirty();
|
||||||
|
// should be the almost same
|
||||||
|
calculate_scale();
|
||||||
|
|
||||||
|
NSVGimage *img = m_volume_shape.svg_file.image.get();
|
||||||
|
assert(img != NULL);
|
||||||
|
if (img != NULL){
|
||||||
|
double tes_tol = get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)));
|
||||||
|
m_volume_shape.shapes_with_ids = create_shape_with_ids(*img, tes_tol);
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSVG::draw_use_surface()
|
void GLGizmoSVG::draw_use_surface()
|
||||||
@ -1275,14 +1386,14 @@ GuiCfg create_gui_configuration() {
|
|||||||
|
|
||||||
float lock_width = cfg.icon_width + 3 * space;
|
float lock_width = cfg.icon_width + 3 * space;
|
||||||
tr.depth = _u8L("Depth");
|
tr.depth = _u8L("Depth");
|
||||||
tr.size = _u8L("Width / Height");
|
tr.size = _u8L("Size");
|
||||||
tr.use_surface = _u8L("Use surface");
|
tr.use_surface = _u8L("Use surface");
|
||||||
tr.distance = _u8L("From surface");
|
tr.distance = _u8L("From surface");
|
||||||
tr.rotation = _u8L("Rotation");
|
tr.rotation = _u8L("Rotation");
|
||||||
tr.reflection = _u8L("Reflection");
|
tr.reflection = _u8L("Reflection");
|
||||||
float max_tr_width = std::max({
|
float max_tr_width = std::max({
|
||||||
ImGui::CalcTextSize(tr.depth.c_str()).x,
|
ImGui::CalcTextSize(tr.depth.c_str()).x,
|
||||||
ImGui::CalcTextSize(tr.size.c_str()).x,
|
ImGui::CalcTextSize(tr.size.c_str()).x + lock_width,
|
||||||
ImGui::CalcTextSize(tr.use_surface.c_str()).x,
|
ImGui::CalcTextSize(tr.use_surface.c_str()).x,
|
||||||
ImGui::CalcTextSize(tr.distance.c_str()).x,
|
ImGui::CalcTextSize(tr.distance.c_str()).x,
|
||||||
ImGui::CalcTextSize(tr.rotation.c_str()).x + lock_width,
|
ImGui::CalcTextSize(tr.rotation.c_str()).x + lock_width,
|
||||||
@ -1345,7 +1456,24 @@ void translate(ExPolygons &expolys, const Point &p) {
|
|||||||
expoly.translate(p);
|
expoly.translate(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbossShape select_shape(std::string_view filepath)
|
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, double tesselation_tolerance)
|
||||||
|
{
|
||||||
|
int max_level = 10;
|
||||||
|
bool is_y_negative = true;
|
||||||
|
ExPolygons expoly = to_expolygons(image, tesselation_tolerance, max_level, 1.0/SCALING_FACTOR, is_y_negative);
|
||||||
|
if (expoly.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// SVG is used as centered
|
||||||
|
// Do not disturb user by settings of pivot position
|
||||||
|
BoundingBox bb = get_extents(expoly);
|
||||||
|
translate(expoly, -bb.center());
|
||||||
|
|
||||||
|
unsigned id = 0;
|
||||||
|
return {{id, expoly}};
|
||||||
|
}
|
||||||
|
|
||||||
|
EmbossShape select_shape(std::string_view filepath, double tesselation_tolerance)
|
||||||
{
|
{
|
||||||
EmbossShape shape;
|
EmbossShape shape;
|
||||||
shape.projection.depth = 10.;
|
shape.projection.depth = 10.;
|
||||||
@ -1370,33 +1498,21 @@ EmbossShape select_shape(std::string_view filepath)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
shape.svg_file.image = nsvgParseFromFile(shape.svg_file.path);
|
init_image(shape.svg_file);
|
||||||
if (shape.svg_file.image.get() == NULL) {
|
if (shape.svg_file.image.get() == NULL) {
|
||||||
show_error(nullptr, GUI::format(_u8L("Nano SVG parser can't load from file(%1%)."), shape.svg_file.path));
|
show_error(nullptr, GUI::format(_u8L("Nano SVG parser can't load from file(%1%)."), shape.svg_file.path));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
shape.scale = DEFAULT_SCALE; // loaded in mm
|
// Set default and unchanging scale
|
||||||
constexpr float tesselation_tolerance = 1e-2f;
|
shape.scale = SCALING_FACTOR;
|
||||||
int max_level = 10;
|
shape.shapes_with_ids = create_shape_with_ids(*shape.svg_file.image, tesselation_tolerance);
|
||||||
float scale = static_cast<float>(1 / shape.scale);
|
|
||||||
bool is_y_negative = true;
|
|
||||||
ExPolygons expoly = to_expolygons(*shape.svg_file.image, tesselation_tolerance, max_level, scale, is_y_negative);
|
|
||||||
|
|
||||||
// Must contain some shapes !!!
|
// Must contain some shapes !!!
|
||||||
if (expoly.empty()) {
|
if (shape.shapes_with_ids.empty()) {
|
||||||
show_error(nullptr, GUI::format(_u8L("SVG file(%1%) do NOT contain path to be able embossed."), shape.svg_file.path));
|
show_error(nullptr, GUI::format(_u8L("SVG file(%1%) do NOT contain path to be able embossed."), shape.svg_file.path));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVG is used as centered
|
|
||||||
// Do not disturb user by settings of pivot position
|
|
||||||
BoundingBox bb = get_extents(expoly);
|
|
||||||
translate(expoly, -bb.center());
|
|
||||||
|
|
||||||
unsigned id = 0;
|
|
||||||
shape.shapes_with_ids = {{id, expoly}};
|
|
||||||
|
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ private:
|
|||||||
// When true keep up vector otherwise relative rotation
|
// When true keep up vector otherwise relative rotation
|
||||||
bool m_keep_up = true;
|
bool m_keep_up = true;
|
||||||
|
|
||||||
|
// Keep size aspect ratio when True.
|
||||||
bool m_keep_ratio = true;
|
bool m_keep_ratio = true;
|
||||||
|
|
||||||
// setted only when wanted to use - not all the time
|
// setted only when wanted to use - not all the time
|
||||||
@ -168,6 +168,7 @@ private:
|
|||||||
std::optional<SurfaceDrag> m_surface_drag;
|
std::optional<SurfaceDrag> m_surface_drag;
|
||||||
|
|
||||||
// For volume on scaled objects
|
// For volume on scaled objects
|
||||||
|
std::optional<float> m_scale_width;
|
||||||
std::optional<float> m_scale_height;
|
std::optional<float> m_scale_height;
|
||||||
std::optional<float> m_scale_depth;
|
std::optional<float> m_scale_depth;
|
||||||
void calculate_scale();
|
void calculate_scale();
|
||||||
@ -175,6 +176,10 @@ private:
|
|||||||
// keep SVG data rendered on GPU
|
// keep SVG data rendered on GPU
|
||||||
Texture m_texture;
|
Texture m_texture;
|
||||||
|
|
||||||
|
// bounding box of shape
|
||||||
|
// Note: Scaled mm to int value by m_volume_shape.scale
|
||||||
|
BoundingBox m_shape_bb;
|
||||||
|
|
||||||
std::string m_filename_preview;
|
std::string m_filename_preview;
|
||||||
|
|
||||||
IconManager m_icon_manager;
|
IconManager m_icon_manager;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user