Port Emboss & SVG gizmo from PrusaSlicer (#2819)

* Rework UI jobs to make them more understandable and flexible.

* Update Orca specific jobs

* Fix progress issue

* Fix dark mode and window radius

* Update cereal version from 1.2.2 to 1.3.0

(cherry picked from commit prusa3d/PrusaSlicer@057232a275)

* Initial port of Emboss gizmo

* Bump up CGAL version to 5.4

(cherry picked from commit prusa3d/PrusaSlicer@1bf9dee3e7)

* Fix text rotation

* Fix test dragging

* Add text gizmo to right click menu

* Initial port of SVG gizmo

* Fix text rotation

* Fix Linux build

* Fix "from surface"

* Fix -90 rotation

* Fix icon path

* Fix loading font with non-ascii name

* Fix storing non-utf8 font descriptor in 3mf file

* Fix filtering with non-utf8 characters

* Emboss: Use Orca style input dialog

* Fix build on macOS

* Fix tooltip color in light mode

* InputText: fixed incorrect padding when FrameBorder > 0. (ocornut/imgui#4794, ocornut/imgui#3781)
InputTextMultiline: fixed vertical tracking with large values of FramePadding.y. (ocornut/imgui#3781, ocornut/imgui#4794)

(cherry picked from commit ocornut/imgui@072caa4a90)
(cherry picked from commit ocornut/imgui@bdd2a94315)

* SVG: Use Orca style input dialog

* Fix job progress update

* Fix crash when select editing text in preview screen

* Use Orca checkbox style

* Fix issue that toolbar icons are kept regenerated

* Emboss: Fix text & icon alignment

* SVG: Fix text & icon alignment

* Emboss: fix toolbar icon mouse hover state

* Add a simple subtle outline effect by drawing back faces using wireframe mode

* Disable selection outlines

* Show outline in white if the model color is too dark

* Make the outline algorithm more reliable

* Enable cull face, which fix render on Linux

* Fix `disable_cullface`

* Post merge fix

* Optimize selection rendering

* Fix scale gizmo

* Emboss: Fix text rotation if base object is scaled

* Fix volume synchronize

* Fix emboss rotation

* Emboss: Fix advance toggle

* Fix text position after reopened the project

* Make font style preview darker

* Make font style preview selector height shorter

---------

Co-authored-by: tamasmeszaros <meszaros.q@gmail.com>
Co-authored-by: ocornut <omarcornut@gmail.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
Noisyfox 2023-12-09 22:46:18 +08:00 committed by GitHub
parent 7a8e1929ee
commit 933aa3050b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
197 changed files with 27190 additions and 2454 deletions

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2017 - 2023 Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "AppConfig.hpp"
@ -43,6 +47,7 @@ static const std::string MODELS_STR = "models";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
const std::string AppConfig::SECTION_EMBOSS_STYLE = "font";
std::string AppConfig::get_language_code()
{

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2017 - 2023 Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_AppConfig_hpp_
#define slic3r_AppConfig_hpp_
@ -287,6 +291,7 @@ public:
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
static const std::string SECTION_EMBOSS_STYLE;
private:
template<typename T>

View file

@ -1,3 +1,8 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Enrico Turri @enricoturri1966
///|/ Copyright (c) Slic3r 2014 - 2015 Alessandro Ranellucci @alranel
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_BoundingBox_hpp_
#define slic3r_BoundingBox_hpp_
@ -222,6 +227,8 @@ public:
friend BoundingBox get_extents_rotated(const Points &points, double angle);
};
using BoundingBoxes = std::vector<BoundingBox>;
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
{
public:

View file

@ -66,9 +66,15 @@ set(lisbslic3r_sources
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
Emboss.cpp
Emboss.hpp
EmbossShape.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonSerialize.hpp
ExPolygonsIndex.cpp
ExPolygonsIndex.hpp
Extruder.cpp
Extruder.hpp
ExtrusionEntity.cpp
@ -224,6 +230,8 @@ set(lisbslic3r_sources
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp
NSVGUtils.cpp
NSVGUtils.hpp
ObjectID.cpp
ObjectID.hpp
PerimeterGenerator.cpp
@ -318,6 +326,7 @@ set(lisbslic3r_sources
Technologies.hpp
Tesselate.cpp
Tesselate.hpp
TextConfiguration.hpp
TriangleMesh.cpp
TriangleMesh.hpp
TriangleMeshSlicer.cpp
@ -329,6 +338,8 @@ set(lisbslic3r_sources
Utils.hpp
Time.cpp
Time.hpp
Timer.cpp
Timer.hpp
Thread.cpp
Thread.hpp
TriangleSelector.cpp
@ -457,8 +468,13 @@ cmake_policy(SET CMP0011 NEW)
find_package(CGAL REQUIRED)
cmake_policy(POP)
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
TryCatchSignal.cpp)
add_library(libslic3r_cgal STATIC
CutSurface.hpp CutSurface.cpp
IntersectionPoints.hpp IntersectionPoints.cpp
MeshBoolean.hpp MeshBoolean.cpp
TryCatchSignal.hpp TryCatchSignal.cpp
Triangulation.hpp Triangulation.cpp
)
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options

View file

@ -272,8 +272,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
// Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
template<typename PathsProvider>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType endType = ClipperLib::etClosedPolygon)
{
ClipperLib::ClipperOffset co;
ClipperLib::Paths out;
@ -354,16 +354,16 @@ TResult clipper_union(
// Perform union of input polygons using the positive rule, convert to ExPolygons.
//FIXME is there any benefit of not doing the boolean / using pftEvenOdd?
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
inline ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
{
return PolyTreeToExPolygons(clipper_union<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
template<typename PathsProvider>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type = ClipperLib::etOpenButt)
{
assert(offset > 0);
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
return raw_offset<PathsProvider>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit, end_type);
}
template<class TResult, typename PathsProvider>
@ -418,10 +418,17 @@ Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, Cli
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); }
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); }
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); }
Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type, double miter_limit){
assert(line_width > 1.f); return to_polygons(clipper_union<ClipperLib::Paths>(
raw_offset(ClipperUtils::SinglePathProvider(polygon.points), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
Polygons contour_to_polygons(const Polygons &polygons, const float line_width, ClipperLib::JoinType join_type, double miter_limit){
assert(line_width > 1.f); return to_polygons(clipper_union<ClipperLib::Paths>(
raw_offset(ClipperUtils::PolygonsProvider(polygons), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
// returns number of expolygons collected (0 or 1).
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
@ -795,6 +802,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
// BBS

View file

@ -22,6 +22,9 @@ namespace Slic3r {
static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt;
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
@ -335,8 +338,8 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp
// offset Polylines
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
@ -355,6 +358,10 @@ inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float
return offset_ex(temp, delta, joinType, miterLimit);
}
// convert stroke to path by offsetting of contour
Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }
@ -539,6 +546,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2);
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_ = false);

View file

@ -61,6 +61,7 @@ public:
static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; }
static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; }
static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; }
static const ColorRGB ORCA() { return {0.0f, 150.f / 255.0f, 136.0f / 255}; }
static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; }
static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; }

4086
src/libslic3r/CutSurface.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,78 @@
///|/ Copyright (c) Prusa Research 2022 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_CutSurface_hpp_
#define slic3r_CutSurface_hpp_
#include <vector>
#include <admesh/stl.h> // indexed_triangle_set
#include "ExPolygon.hpp"
#include "Emboss.hpp" // IProjection
namespace Slic3r{
/// <summary>
/// Represents cutted surface from object
/// Extend index triangle set by outlines
/// </summary>
struct SurfaceCut : public indexed_triangle_set
{
// vertex indices(index to mesh vertices)
using Index = unsigned int;
using Contour = std::vector<Index>;
using Contours = std::vector<Contour>;
// list of circulated open surface
Contours contours;
};
/// <summary>
/// Cut surface shape from models.
/// </summary>
/// <param name="shapes">Multiple shape to cut from model</param>
/// <param name="models">Multi mesh to cut, need to be in same coordinate system</param>
/// <param name="projection">Define transformation 2d shape into 3d</param>
/// <param name="projection_ratio">Define ideal ratio between front and back projection to cut
/// 0 .. means use closest to front projection
/// 1 .. means use closest to back projection
/// value from <0, 1>
/// </param>
/// <returns>Cutted surface from model</returns>
SurfaceCut cut_surface(const ExPolygons &shapes,
const std::vector<indexed_triangle_set> &models,
const Emboss::IProjection &projection,
float projection_ratio);
/// <summary>
/// Create model from surface cuts by projection
/// </summary>
/// <param name="cut">Surface from model with outlines</param>
/// <param name="projection">Way of emboss</param>
/// <returns>Mesh</returns>
indexed_triangle_set cut2model(const SurfaceCut &cut,
const Emboss::IProject3d &projection);
/// <summary>
/// Separate (A)rea (o)f (I)nterest .. AoI from model
/// NOTE: Only 2d filtration, do not filtrate by Z coordinate
/// </summary>
/// <param name="its">Input model</param>
/// <param name="bb">Bounding box to project into space</param>
/// <param name="projection">Define tranformation of BB into space</param>
/// <returns>Triangles lay at least partialy inside of projected Bounding box</returns>
indexed_triangle_set its_cut_AoI(const indexed_triangle_set &its,
const BoundingBox &bb,
const Emboss::IProjection &projection);
/// <summary>
/// Separate triangles by mask
/// </summary>
/// <param name="its">Input model</param>
/// <param name="mask">Mask - same size as its::indices</param>
/// <returns>Copy of indices by mask(with their vertices)</returns>
indexed_triangle_set its_mask(const indexed_triangle_set &its, const std::vector<bool> &mask);
bool corefine_test(const std::string &model_path, const std::string &shape_path);
} // namespace Slic3r
#endif // slic3r_CutSurface_hpp_

View file

@ -217,7 +217,7 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan
auto& obj_instance = object->instances[i];
const double rot_z = obj_instance->get_rotation().z();
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix(false, false, true));
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
// add respect to mirroring
if (obj_instance->is_left_handed())
inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
@ -320,7 +320,7 @@ const ModelObjectPtrs& Cut::perform_with_plane()
// except for translation and Z-rotation on instances, which are preserved
// in the transformation matrix and not applied to the mesh transform.
const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix(true);
const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
const Transformation cut_transformation = Transformation(m_cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());

2185
src/libslic3r/Emboss.cpp Normal file

File diff suppressed because it is too large Load diff

477
src/libslic3r/Emboss.hpp Normal file
View file

@ -0,0 +1,477 @@
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_Emboss_hpp_
#define slic3r_Emboss_hpp_
#include <vector>
#include <set>
#include <optional>
#include <memory>
#include <admesh/stl.h> // indexed_triangle_set
#include "Polygon.hpp"
#include "ExPolygon.hpp"
#include "EmbossShape.hpp" // ExPolygonsWithIds
#include "BoundingBox.hpp"
#include "TextConfiguration.hpp"
namespace Slic3r {
/// <summary>
/// class with only static function add ability to engraved OR raised
/// text OR polygons onto model surface
/// </summary>
namespace Emboss
{
static const float UNION_DELTA = 50.0f; // [approx in nano meters depends on volume scale]
static const unsigned UNION_MAX_ITERATIN = 10; // [count]
/// <summary>
/// Collect fonts registred inside OS
/// </summary>
/// <returns>OS registred TTF font files(full path) with names</returns>
EmbossStyles get_font_list();
#ifdef _WIN32
EmbossStyles get_font_list_by_register();
EmbossStyles get_font_list_by_enumeration();
EmbossStyles get_font_list_by_folder();
#endif
/// <summary>
/// OS dependent function to get location of font by its name descriptor
/// </summary>
/// <param name="font_face_name">Unique identificator for font</param>
/// <returns>File path to font when found</returns>
std::optional<std::wstring> get_font_path(const std::wstring &font_face_name);
// description of one letter
struct Glyph
{
// NOTE: shape is scaled by SHAPE_SCALE
// to be able store points without floating points
ExPolygons shape;
// values are in font points
int advance_width=0, left_side_bearing=0;
};
// cache for glyph by unicode
using Glyphs = std::map<int, Glyph>;
/// <summary>
/// keep information from file about font
/// (store file data itself)
/// + cache data readed from buffer
/// </summary>
struct FontFile
{
// loaded data from font file
// must store data size for imgui rasterization
// To not store data on heap and To prevent unneccesary copy
// data are stored inside unique_ptr
std::unique_ptr<std::vector<unsigned char>> data;
struct Info
{
// vertical position is "scale*(ascent - descent + lineGap)"
int ascent, descent, linegap;
// for convert font units to pixel
int unit_per_em;
};
// info for each font in data
std::vector<Info> infos;
FontFile(std::unique_ptr<std::vector<unsigned char>> data,
std::vector<Info> &&infos)
: data(std::move(data)), infos(std::move(infos))
{
assert(this->data != nullptr);
assert(!this->data->empty());
}
bool operator==(const FontFile &other) const {
if (data->size() != other.data->size())
return false;
//if(*data != *other.data) return false;
for (size_t i = 0; i < infos.size(); i++)
if (infos[i].ascent != other.infos[i].ascent ||
infos[i].descent == other.infos[i].descent ||
infos[i].linegap == other.infos[i].linegap)
return false;
return true;
}
};
/// <summary>
/// Add caching for shape of glyphs
/// </summary>
struct FontFileWithCache
{
// Pointer on data of the font file
std::shared_ptr<const FontFile> font_file;
// Cache for glyph shape
// IMPORTANT: accessible only in plater job thread !!!
// main thread only clear cache by set to another shared_ptr
std::shared_ptr<Emboss::Glyphs> cache;
FontFileWithCache() : font_file(nullptr), cache(nullptr) {}
explicit FontFileWithCache(std::unique_ptr<FontFile> font_file)
: font_file(std::move(font_file))
, cache(std::make_shared<Emboss::Glyphs>())
{}
bool has_value() const { return font_file != nullptr && cache != nullptr; }
};
/// <summary>
/// Load font file into buffer
/// </summary>
/// <param name="file_path">Location of .ttf or .ttc font file</param>
/// <returns>Font object when loaded.</returns>
std::unique_ptr<FontFile> create_font_file(const char *file_path);
// data = raw file data
std::unique_ptr<FontFile> create_font_file(std::unique_ptr<std::vector<unsigned char>> data);
#ifdef _WIN32
// fix for unknown pointer HFONT is replaced with "void *"
void * can_load(void* hfont);
std::unique_ptr<FontFile> create_font_file(void * hfont);
#endif // _WIN32
/// <summary>
/// convert letter into polygons
/// </summary>
/// <param name="font">Define fonts</param>
/// <param name="font_index">Index of font in collection</param>
/// <param name="letter">One character defined by unicode codepoint</param>
/// <param name="flatness">Precision of lettter outline curve in conversion to lines</param>
/// <returns>inner polygon cw(outer ccw)</returns>
std::optional<Glyph> letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness);
/// <summary>
/// Convert text into polygons
/// </summary>
/// <param name="font">Define fonts + cache, which could extend</param>
/// <param name="text">Characters to convert</param>
/// <param name="font_prop">User defined property of the font</param>
/// <param name="was_canceled">Way to interupt processing</param>
/// <returns>Inner polygon cw(outer ccw)</returns>
HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function<bool()> &was_canceled = []() {return false;});
ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled = []() {return false;});
const unsigned ENTER_UNICODE = static_cast<unsigned>('\n');
/// Sum of character '\n'
unsigned get_count_lines(const std::wstring &ws);
unsigned get_count_lines(const std::string &text);
unsigned get_count_lines(const ExPolygonsWithIds &shape);
/// <summary>
/// Fix duplicit points and self intersections in polygons.
/// Also try to reduce amount of points and remove useless polygon parts
/// </summary>
/// <param name="is_non_zero">Fill type ClipperLib::pftNonZero for overlapping otherwise </param>
/// <param name="max_iteration">Look at heal_expolygon()::max_iteration</param>
/// <returns>Healed shapes with flag is fully healed</returns>
HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10);
/// <summary>
/// NOTE: call Slic3r::union_ex before this call
///
/// Heal (read: Fix) issues in expolygons:
/// - self intersections
/// - duplicit points
/// - points close to line segments
/// </summary>
/// <param name="shape">In/Out shape to heal</param>
/// <param name="max_iteration">Heal could create another issue,
/// After healing it is checked again until shape is good or maximal count of iteration</param>
/// <returns>True when shapes is good otherwise False</returns>
bool heal_expolygons(ExPolygons &shape, unsigned max_iteration = 10);
/// <summary>
/// Divide line segments in place near to point
/// (which could lead to self intersection due to preccision)
/// Remove same neighbors
/// Note: Possible part of heal shape
/// </summary>
/// <param name="expolygons">Expolygon to edit</param>
/// <param name="distance">(epsilon)Euclidean distance from point to line which divide line</param>
/// <returns>True when some division was made otherwise false</returns>
bool divide_segments_for_close_point(ExPolygons &expolygons, double distance);
/// <summary>
/// Use data from font property to modify transformation
/// </summary>
/// <param name="angle">Z-rotation as angle to Y axis</param>
/// <param name="distance">Z-move as surface distance</param>
/// <param name="transformation">In / Out transformation to modify by property</param>
void apply_transformation(const std::optional<float> &angle, const std::optional<float> &distance, Transform3d &transformation);
/// <summary>
/// Read information from naming table of font file
/// search for italic (or oblique), bold italic (or bold oblique)
/// </summary>
/// <param name="font">Selector of font</param>
/// <param name="font_index">Index of font in collection</param>
/// <returns>True when the font description contains italic/obligue otherwise False</returns>
bool is_italic(const FontFile &font, unsigned int font_index);
/// <summary>
/// Create unique character set from string with filtered from text with only character from font
/// </summary>
/// <param name="text">Source vector of glyphs</param>
/// <param name="font">Font descriptor</param>
/// <param name="font_index">Define font in collection</param>
/// <param name="exist_unknown">True when text contain glyph unknown in font</param>
/// <returns>Unique set of character from text contained in font</returns>
std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr);
/// <summary>
/// Calculate scale for glyph shape convert from shape points to mm
/// </summary>
/// <param name="fp">Property of font</param>
/// <param name="ff">Font data</param>
/// <returns>Conversion to mm</returns>
double get_text_shape_scale(const FontProp &fp, const FontFile &ff);
/// <summary>
/// getter of font info by collection defined in prop
/// </summary>
/// <param name="font">Contain infos about all fonts(collections) in file</param>
/// <param name="prop">Index of collection</param>
/// <returns>Ascent, descent, line gap</returns>
const FontFile::Info &get_font_info(const FontFile &font, const FontProp &prop);
/// <summary>
/// Read from font file and properties height of line with spacing
/// </summary>
/// <param name="font">Infos for collections</param>
/// <param name="prop">Collection index + Additional line gap</param>
/// <returns>Line height with spacing in scaled font points (same as ExPolygons)</returns>
int get_line_height(const FontFile &font, const FontProp &prop);
/// <summary>
/// Calculate Vertical align
/// </summary>
/// <param name="align">Top | Center | Bottom</param>
/// <param name="count_lines"></param>
/// <returns>Return align Y offset in mm</returns>
double get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp);
/// <summary>
/// Project spatial point
/// </summary>
class IProject3d
{
public:
virtual ~IProject3d() = default;
/// <summary>
/// Move point with respect to projection direction
/// e.g. Orthogonal projection will move with point by direction
/// e.g. Spherical projection need to use center of projection
/// </summary>
/// <param name="point">Spatial point coordinate</param>
/// <returns>Projected spatial point</returns>
virtual Vec3d project(const Vec3d &point) const = 0;
};
/// <summary>
/// Project 2d point into space
/// Could be plane, sphere, cylindric, ...
/// </summary>
class IProjection : public IProject3d
{
public:
virtual ~IProjection() = default;
/// <summary>
/// convert 2d point to 3d points
/// </summary>
/// <param name="p">2d coordinate</param>
/// <returns>
/// first - front spatial point
/// second - back spatial point
/// </returns>
virtual std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const = 0;
/// <summary>
/// Back projection
/// </summary>
/// <param name="p">Point to project</param>
/// <param name="depth">[optional] Depth of 2d projected point. Be careful number is in 2d scale</param>
/// <returns>Uprojected point when it is possible</returns>
virtual std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const = 0;
};
/// <summary>
/// Create triangle model for text
/// </summary>
/// <param name="shape2d">text or image</param>
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
/// <returns>Projected shape into space</returns>
indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection);
/// <summary>
/// Suggest wanted up vector of embossed text by emboss direction
/// </summary>
/// <param name="normal">Normalized vector of emboss direction in world</param>
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
/// <returns>Wanted up vector</returns>
Vec3d suggest_up(const Vec3d normal, double up_limit = 0.9);
/// <summary>
/// By transformation calculate angle between suggested and actual up vector
/// </summary>
/// <param name="tr">Transformation of embossed volume in world</param>
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
/// <returns>Rotation of suggested up-vector[in rad] in the range [-Pi, Pi], When rotation is not zero</returns>
std::optional<float> calc_up(const Transform3d &tr, double up_limit = 0.9);
/// <summary>
/// Create transformation for emboss text object to lay on surface point
/// </summary>
/// <param name="position">Position of surface point</param>
/// <param name="normal">Normal of surface point</param>
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
/// <returns>Transformation onto surface point</returns>
Transform3d create_transformation_onto_surface(
const Vec3d &position, const Vec3d &normal, double up_limit = 0.9);
class ProjectZ : public IProjection
{
public:
explicit ProjectZ(double depth) : m_depth(depth) {}
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
Vec3d project(const Vec3d &point) const override;
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
double m_depth;
};
class ProjectScale : public IProjection
{
std::unique_ptr<IProjection> core;
double m_scale;
public:
ProjectScale(std::unique_ptr<IProjection> core, double scale)
: core(std::move(core)), m_scale(scale)
{}
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override
{
auto res = core->create_front_back(p);
return std::make_pair(res.first * m_scale, res.second * m_scale);
}
Vec3d project(const Vec3d &point) const override{
return core->project(point);
}
std::optional<Vec2d> unproject(const Vec3d &p, double *depth = nullptr) const override {
auto res = core->unproject(p / m_scale, depth);
if (depth != nullptr) *depth *= m_scale;
return res;
}
};
class ProjectTransform : public IProjection
{
std::unique_ptr<IProjection> m_core;
Transform3d m_tr;
Transform3d m_tr_inv;
double z_scale;
public:
ProjectTransform(std::unique_ptr<IProjection> core, const Transform3d &tr) : m_core(std::move(core)), m_tr(tr)
{
m_tr_inv = m_tr.inverse();
z_scale = (m_tr.linear() * Vec3d::UnitZ()).norm();
}
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override
{
auto [front, back] = m_core->create_front_back(p);
return std::make_pair(m_tr * front, m_tr * back);
}
Vec3d project(const Vec3d &point) const override{
return m_core->project(point);
}
std::optional<Vec2d> unproject(const Vec3d &p, double *depth = nullptr) const override {
auto res = m_core->unproject(m_tr_inv * p, depth);
if (depth != nullptr)
*depth *= z_scale;
return res;
}
};
class OrthoProject3d : public Emboss::IProject3d
{
// size and direction of emboss for ortho projection
Vec3d m_direction;
public:
OrthoProject3d(Vec3d direction) : m_direction(direction) {}
Vec3d project(const Vec3d &point) const override{ return point + m_direction;}
};
class OrthoProject: public Emboss::IProjection {
Transform3d m_matrix;
// size and direction of emboss for ortho projection
Vec3d m_direction;
Transform3d m_matrix_inv;
public:
OrthoProject(Transform3d matrix, Vec3d direction)
: m_matrix(matrix), m_direction(direction), m_matrix_inv(matrix.inverse())
{}
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
Vec3d project(const Vec3d &point) const override;
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
};
/// <summary>
/// Define polygon for draw letters
/// </summary>
struct TextLine
{
// slice of object
Polygon polygon;
// point laying on polygon closest to zero
PolygonPoint start;
// offset of text line in volume mm
float y;
};
using TextLines = std::vector<TextLine>;
/// <summary>
/// Sample slice polygon by bounding boxes centers
/// slice start point has shape_center_x coor
/// </summary>
/// <param name="slice">Polygon and start point[Slic3r scaled milimeters]</param>
/// <param name="bbs">Bounding boxes of letter on one line[in font scales]</param>
/// <param name="scale">Scale for bbs (after multiply bb is in milimeters)</param>
/// <returns>Sampled polygon by bounding boxes</returns>
PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale);
/// <summary>
/// Calculate angle for polygon point
/// </summary>
/// <param name="distance">Distance for found normal in point</param>
/// <param name="polygon_point">Select point on polygon</param>
/// <param name="polygon">Polygon know neighbor of point</param>
/// <returns>angle(atan2) of normal in polygon point</returns>
double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon);
std::vector<double> calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon);
} // namespace Emboss
///////////////////////
// Move to ExPolygonsWithIds Utils
void translate(ExPolygonsWithIds &e, const Point &p);
BoundingBox get_extents(const ExPolygonsWithIds &e);
void center(ExPolygonsWithIds &e);
// delta .. safe offset before union (use as boolean close)
// NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve)
ExPolygons union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration);
} // namespace Slic3r
#endif // slic3r_Emboss_hpp_

View file

@ -0,0 +1,143 @@
#ifndef slic3r_EmbossShape_hpp_
#define slic3r_EmbossShape_hpp_
#include <string>
#include <optional>
#include <memory> // unique_ptr
#include <cereal/cereal.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/optional.hpp>
#include <cereal/archives/binary.hpp>
#include "Point.hpp" // Transform3d
#include "ExPolygon.hpp"
#include "ExPolygonSerialize.hpp"
#include "nanosvg/nanosvg.h" // NSVGimage
namespace Slic3r {
struct EmbossProjection{
// Emboss depth, Size in local Z direction
double depth = 1.; // [in loacal mm]
// NOTE: User should see and modify mainly world size not local
// Flag that result volume use surface cutted from source objects
bool use_surface = false;
bool operator==(const EmbossProjection &other) const {
return depth == other.depth && use_surface == other.use_surface;
}
// undo / redo stack recovery
template<class Archive> void serialize(Archive &ar) { ar(depth, use_surface); }
};
// Extend expolygons with information whether it was successfull healed
struct HealedExPolygons{
ExPolygons expolygons;
bool is_healed;
operator ExPolygons&() { return expolygons; }
};
// Help structure to identify expolygons grups
// e.g. emboss -> per glyph -> identify character
struct ExPolygonsWithId
{
// Identificator for shape
// In text it separate letters and the name is unicode value of letter
// Is svg it is id of path
unsigned id;
// shape defined by integer point contain only lines
// Curves are converted to sequence of lines
ExPolygons expoly;
// flag whether expolygons are fully healed(without duplication)
bool is_healed = true;
};
using ExPolygonsWithIds = std::vector<ExPolygonsWithId>;
/// <summary>
/// Contain plane shape information to be able emboss it and edit it
/// </summary>
struct EmbossShape
{
// shapes to to emboss separately over surface
ExPolygonsWithIds shapes_with_ids;
// Only cache for final shape
// It is calculated from ExPolygonsWithIds
// Flag is_healed --> whether union of shapes is healed
// Healed mean without selfintersection and point duplication
HealedExPolygons final_shape;
// scale of shape, multiplier to get 3d point in mm from integer shape
double scale = SCALING_FACTOR;
// Define how to emboss shape
EmbossProjection projection;
// !!! Volume stored in .3mf has transformed vertices.
// (baked transformation into vertices position)
// Only place for fill this is when load from .3mf
// This is correction for volume transformation
// Stored_Transform3d * fix_3mf_tr = Transform3d_before_store_to_3mf
std::optional<Slic3r::Transform3d> fix_3mf_tr;
struct SvgFile {
// File(.svg) path on local computer
// When empty can't reload from disk
std::string path;
// File path into .3mf(.zip)
// When empty svg is not stored into .3mf file yet.
// and will create dialog to delete private data on save.
std::string path_in_3mf;
// Loaded svg file data.
// !!! It is not serialized on undo/redo stack
std::shared_ptr<NSVGimage> image = nullptr;
// Loaded string data from file
std::shared_ptr<std::string> file_data = nullptr;
template<class Archive> void save(Archive &ar) const {
// Note: image is only cache it is not neccessary to store
// Store file data as plain string
assert(file_data != nullptr);
ar(path, path_in_3mf, (file_data != nullptr) ? *file_data : std::string(""));
}
template<class Archive> void load(Archive &ar) {
// for restore shared pointer on file data
std::string file_data_str;
ar(path, path_in_3mf, file_data_str);
if (!file_data_str.empty())
file_data = std::make_unique<std::string>(file_data_str);
}
};
// When embossing shape is made by svg file this is source data
std::optional<SvgFile> svg_file;
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const
{
// final_shape is not neccessary to store - it is only cache
ar(shapes_with_ids, final_shape, scale, projection, svg_file);
cereal::save(ar, fix_3mf_tr);
}
template<class Archive> void load(Archive &ar)
{
ar(shapes_with_ids, final_shape, scale, projection, svg_file);
cereal::load(ar, fix_3mf_tr);
}
};
} // namespace Slic3r
// Serialization through the Cereal library
namespace cereal {
template<class Archive> void serialize(Archive &ar, Slic3r::ExPolygonsWithId &o) { ar(o.id, o.expoly, o.is_healed); }
template<class Archive> void serialize(Archive &ar, Slic3r::HealedExPolygons &o) { ar(o.expolygons, o.is_healed); }
}; // namespace cereal
#endif // slic3r_EmbossShape_hpp_

View file

@ -1,3 +1,15 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas
///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard
///|/ Copyright (c) 2014 Petr Ledvina @ledvinap
///|/
///|/ ported from lib/Slic3r/ExPolygon.pm:
///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2012 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Exception.hpp"
@ -475,6 +487,24 @@ bool has_duplicate_points(const ExPolygons &expolys)
#endif
}
bool remove_same_neighbor(ExPolygons &expolygons)
{
if (expolygons.empty())
return false;
bool remove_from_holes = false;
bool remove_from_contour = false;
for (ExPolygon &expoly : expolygons) {
remove_from_contour |= remove_same_neighbor(expoly.contour);
remove_from_holes |= remove_same_neighbor(expoly.holes);
}
// Removing of expolygons without contour
if (remove_from_contour)
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(),
[](const ExPolygon &p) { return p.contour.points.size() <= 2; }),
expolygons.end());
return remove_from_holes || remove_from_contour;
}
bool remove_sticks(ExPolygon &poly)
{
return remove_sticks(poly.contour) || remove_sticks(poly.holes);

View file

@ -1,3 +1,14 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Pavel Mikuš @Godrak, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Tomáš Mészáros @tamasmeszaros
///|/ Copyright (c) 2016 Sakari Kapanen @Flannelhead
///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
///|/
///|/ ported from lib/Slic3r/ExPolygon.pm:
///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2012 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_ExPolygon_hpp_
#define slic3r_ExPolygon_hpp_
@ -142,19 +153,6 @@ inline Lines to_lines(const ExPolygons &src)
return lines;
}
inline Points to_points(const ExPolygons& src)
{
Points points;
size_t count = count_points(src);
points.reserve(count);
for (const ExPolygon& expolygon : src) {
append(points, expolygon.contour.points);
for (const Polygon& hole : expolygon.holes)
append(points, hole.points);
}
return points;
}
// Line is from point index(see to_points) to next point.
// Next point of last point in polygon is first polygon point.
inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0)
@ -205,6 +203,20 @@ inline Linesf to_unscaled_linesf(const ExPolygons &src)
return lines;
}
inline Points to_points(const ExPolygons &src)
{
Points points;
size_t count = count_points(src);
points.reserve(count);
for (const ExPolygon &expolygon : src) {
append(points, expolygon.contour.points);
for (const Polygon &hole : expolygon.holes)
append(points, hole.points);
}
return points;
}
inline Polylines to_polylines(const ExPolygon &src)
{
Polylines polylines;
@ -371,6 +383,11 @@ inline Points to_points(const ExPolygon &expoly)
return out;
}
inline void translate(ExPolygons &expolys, const Point &p) {
for (ExPolygon &expoly : expolys)
expoly.translate(p);
}
inline void polygons_append(Polygons &dst, const ExPolygon &src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
@ -464,6 +481,9 @@ std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
bool has_duplicate_points(const ExPolygon &expoly);
bool has_duplicate_points(const ExPolygons &expolys);
// Return True when erase some otherwise False.
bool remove_same_neighbor(ExPolygons &expolys);
bool remove_sticks(ExPolygon &poly);
void keep_largest_contour_only(ExPolygons &polygons);

View file

@ -0,0 +1,28 @@
#ifndef slic3r_ExPolygonSerialize_hpp_
#define slic3r_ExPolygonSerialize_hpp_
#include "ExPolygon.hpp"
#include "Point.hpp" // Cereal serialization of Point
#include <cereal/cereal.hpp>
#include <cereal/types/vector.hpp>
/// <summary>
/// External Cereal serialization of ExPolygons
/// </summary>
// Serialization through the Cereal library
#include <cereal/access.hpp>
namespace cereal {
template<class Archive>
void serialize(Archive &archive, Slic3r::Polygon &polygon) {
archive(polygon.points);
}
template<class Archive>
void serialize(Archive &archive, Slic3r::ExPolygon &expoly) {
archive(expoly.contour, expoly.holes);
}
} // namespace Slic3r
#endif // slic3r_ExPolygonSerialize_hpp_

View file

@ -0,0 +1,82 @@
#include "ExPolygonsIndex.hpp"
using namespace Slic3r;
// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound
ExPolygonsIndices::ExPolygonsIndices(const ExPolygons &shapes)
{
// prepare offsets
m_offsets.reserve(shapes.size());
uint32_t offset = 0;
for (const ExPolygon &shape : shapes) {
assert(!shape.contour.points.empty());
std::vector<uint32_t> shape_offsets;
shape_offsets.reserve(shape.holes.size() + 1);
shape_offsets.push_back(offset);
offset += shape.contour.points.size();
for (const Polygon &hole: shape.holes) {
shape_offsets.push_back(offset);
offset += hole.points.size();
}
m_offsets.push_back(std::move(shape_offsets));
}
m_count = offset;
}
uint32_t ExPolygonsIndices::cvt(const ExPolygonsIndex &id) const
{
assert(id.expolygons_index < m_offsets.size());
const std::vector<uint32_t> &shape_offset = m_offsets[id.expolygons_index];
assert(id.polygon_index < shape_offset.size());
uint32_t res = shape_offset[id.polygon_index] + id.point_index;
assert(res < m_count);
return res;
}
ExPolygonsIndex ExPolygonsIndices::cvt(uint32_t index) const
{
assert(index < m_count);
ExPolygonsIndex result{0, 0, 0};
// find expolygon index
auto fn = [](const std::vector<uint32_t> &offsets, uint32_t index) { return offsets[0] < index; };
auto it = std::lower_bound(m_offsets.begin() + 1, m_offsets.end(), index, fn);
result.expolygons_index = it - m_offsets.begin();
if (it == m_offsets.end() || it->at(0) != index) --result.expolygons_index;
// find polygon index
const std::vector<uint32_t> &shape_offset = m_offsets[result.expolygons_index];
auto it2 = std::lower_bound(shape_offset.begin() + 1, shape_offset.end(), index);
result.polygon_index = it2 - shape_offset.begin();
if (it2 == shape_offset.end() || *it2 != index) --result.polygon_index;
// calculate point index
uint32_t polygon_offset = shape_offset[result.polygon_index];
assert(index >= polygon_offset);
result.point_index = index - polygon_offset;
return result;
}
bool ExPolygonsIndices::is_last_point(const ExPolygonsIndex &id) const {
assert(id.expolygons_index < m_offsets.size());
const std::vector<uint32_t> &shape_offset = m_offsets[id.expolygons_index];
assert(id.polygon_index < shape_offset.size());
uint32_t index = shape_offset[id.polygon_index] + id.point_index;
assert(index < m_count);
// next index
uint32_t next_point_index = index + 1;
uint32_t next_poly_index = id.polygon_index + 1;
uint32_t next_expoly_index = id.expolygons_index + 1;
// is last expoly?
if (next_expoly_index == m_offsets.size()) {
// is last expoly last poly?
if (next_poly_index == shape_offset.size())
return next_point_index == m_count;
} else {
// (not last expoly) is expoly last poly?
if (next_poly_index == shape_offset.size())
return next_point_index == m_offsets[next_expoly_index][0];
}
// Not last polygon in expolygon
return next_point_index == shape_offset[next_poly_index];
}
uint32_t ExPolygonsIndices::get_count() const { return m_count; }

View file

@ -0,0 +1,74 @@
#ifndef slic3r_ExPolygonsIndex_hpp_
#define slic3r_ExPolygonsIndex_hpp_
#include "ExPolygon.hpp"
namespace Slic3r {
/// <summary>
/// Index into ExPolygons
/// Identify expolygon, its contour (or hole) and point
/// </summary>
struct ExPolygonsIndex
{
// index of ExPolygons
uint32_t expolygons_index;
// index of Polygon
// 0 .. contour
// N .. hole[N-1]
uint32_t polygon_index;
// index of point in polygon
uint32_t point_index;
bool is_contour() const { return polygon_index == 0; }
bool is_hole() const { return polygon_index != 0; }
uint32_t hole_index() const { return polygon_index - 1; }
};
/// <summary>
/// Keep conversion from ExPolygonsIndex to Index and vice versa
/// ExPolygonsIndex .. contour(or hole) point from ExPolygons
/// Index .. continous number
///
/// index is used to address lines and points as result from function
/// Slic3r::to_lines, Slic3r::to_points
/// </summary>
class ExPolygonsIndices
{
std::vector<std::vector<uint32_t>> m_offsets;
// for check range of index
uint32_t m_count; // count of points
public:
ExPolygonsIndices(const ExPolygons &shapes);
/// <summary>
/// Convert to one index number
/// </summary>
/// <param name="id">Compose of adress into expolygons</param>
/// <returns>Index</returns>
uint32_t cvt(const ExPolygonsIndex &id) const;
/// <summary>
/// Separate to multi index
/// </summary>
/// <param name="index">adress into expolygons</param>
/// <returns></returns>
ExPolygonsIndex cvt(uint32_t index) const;
/// <summary>
/// Check whether id is last point in polygon
/// </summary>
/// <param name="id">Identify point in expolygon</param>
/// <returns>True when id is last point in polygon otherwise false</returns>
bool is_last_point(const ExPolygonsIndex &id) const;
/// <summary>
/// Count of points in expolygons
/// </summary>
/// <returns>Count of points in expolygons</returns>
uint32_t get_count() const;
};
} // namespace Slic3r
#endif // slic3r_ExPolygonsIndex_hpp_

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "SL1.hpp"
#include "GCode/ThumbnailData.hpp"
#include "libslic3r/Time.hpp"
@ -442,8 +446,8 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print)
std::unique_ptr<sla::RasterBase> SL1Archive::create_raster() const
{
sla::RasterBase::Resolution res;
sla::RasterBase::PixelDim pxdim;
sla::Resolution res;
sla::PixelDim pxdim;
std::array<bool, 2> mirror;
double w = m_cfg.display_width.getFloat();
@ -464,8 +468,8 @@ std::unique_ptr<sla::RasterBase> SL1Archive::create_raster() const
std::swap(pw, ph);
}
res = sla::RasterBase::Resolution{pw, ph};
pxdim = sla::RasterBase::PixelDim{w / pw, h / ph};
res = sla::Resolution{pw, ph};
pxdim = sla::PixelDim{w / pw, h / ph};
sla::RasterBase::Trafo tr{orientation, mirror};
double gamma = m_cfg.gamma_correction.getFloat();

View file

@ -23,6 +23,9 @@
#include <stdexcept>
#include <iomanip>
#include <boost/assign.hpp>
#include <boost/bimap.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/predicate.hpp>
@ -48,6 +51,13 @@ namespace pt = boost::property_tree;
#include <Eigen/Dense>
#include "miniz_extension.hpp"
#include "nlohmann/json.hpp"
#include "TextConfiguration.hpp"
#include "EmbossShape.hpp"
#include "ExPolygonSerialize.hpp"
#include "NSVGUtils.hpp"
#include <fast_float/fast_float.h>
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
@ -211,7 +221,7 @@ static constexpr const char* ASSEMBLE_ITEM_TAG = "assemble_item";
static constexpr const char* SLICE_HEADER_TAG = "header";
static constexpr const char* SLICE_HEADER_ITEM_TAG = "header_item";
// text_info
// Deprecated: text_info
static constexpr const char* TEXT_INFO_TAG = "text_info";
static constexpr const char* TEXT_ATTR = "text";
static constexpr const char* FONT_NAME_ATTR = "font_name";
@ -325,6 +335,42 @@ static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed";
static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed";
static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges";
// Store / load of TextConfiguration
static constexpr const char *TEXT_TAG = "slic3rpe:text";
static constexpr const char *TEXT_DATA_ATTR = "text";
// TextConfiguration::EmbossStyle
static constexpr const char *STYLE_NAME_ATTR = "style_name";
static constexpr const char *FONT_DESCRIPTOR_ATTR = "font_descriptor";
static constexpr const char *FONT_DESCRIPTOR_TYPE_ATTR = "font_descriptor_type";
// TextConfiguration::FontProperty
static constexpr const char *CHAR_GAP_ATTR = "char_gap";
static constexpr const char *LINE_GAP_ATTR = "line_gap";
static constexpr const char *LINE_HEIGHT_ATTR = "line_height";
static constexpr const char *BOLDNESS_ATTR = "boldness";
static constexpr const char *SKEW_ATTR = "skew";
static constexpr const char *PER_GLYPH_ATTR = "per_glyph";
static constexpr const char *HORIZONTAL_ALIGN_ATTR = "horizontal";
static constexpr const char *VERTICAL_ALIGN_ATTR = "vertical";
static constexpr const char *COLLECTION_NUMBER_ATTR = "collection";
static constexpr const char *FONT_FAMILY_ATTR = "family";
static constexpr const char *FONT_FACE_NAME_ATTR = "face_name";
static constexpr const char *FONT_STYLE_ATTR = "style";
static constexpr const char *FONT_WEIGHT_ATTR = "weight";
// Store / load of EmbossShape
static constexpr const char *SHAPE_TAG = "slic3rpe:shape";
static constexpr const char *SHAPE_SCALE_ATTR = "scale";
static constexpr const char *UNHEALED_ATTR = "unhealed";
static constexpr const char *SVG_FILE_PATH_ATTR = "filepath";
static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf";
// EmbossProjection
static constexpr const char *DEPTH_ATTR = "depth";
static constexpr const char *USE_SURFACE_ATTR = "use_surface";
// static constexpr const char *FIX_TRANSFORMATION_ATTR = "transform";
const unsigned int BBS_VALID_OBJECT_TYPES_COUNT = 2;
const char* BBS_VALID_OBJECT_TYPES[] =
@ -718,8 +764,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
MetadataList metadata;
RepairedMeshErrors mesh_stats;
ModelVolumeType part_type;
TextInfo text_info;
std::optional<TextConfiguration> text_configuration;
std::optional<EmbossShape> shape_configuration;
VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id, ModelVolumeType type = ModelVolumeType::MODEL_PART)
: first_triangle_id(first_triangle_id)
, last_triangle_id(last_triangle_id)
@ -770,6 +816,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
/*typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;*/
using PathToEmbossShapeFileMap = std::map<std::string, std::shared_ptr<std::string>>;
struct ObjectImporter
{
@ -962,6 +1009,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
IdToLayerConfigRangesMap m_layer_config_ranges;
/*IdToSlaSupportPointsMap m_sla_support_points;
IdToSlaDrainHolesMap m_sla_drain_holes;*/
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
std::string m_curr_metadata_name;
std::string m_curr_characters;
std::string m_name;
@ -1015,6 +1063,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
// add backup & restore logic
bool _load_model_from_file(std::string filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn = nullptr,
BBLProject* project = nullptr, int plate_id = 0);
bool _is_svg_shape_file(const std::string &filename) const;
bool _extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)>, bool restore = false);
bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
@ -1035,6 +1084,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
void _extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
// handlers to parse the .model file
void _handle_start_model_xml_element(const char* name, const char** attributes);
@ -1090,6 +1140,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
bool _handle_start_metadata(const char** attributes, unsigned int num_attributes);
bool _handle_end_metadata();
bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes);
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);
bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
@ -1141,7 +1194,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _generate_current_object_list(std::vector<Component> &sub_objects, Id object_id, IdToCurrentObjectMap& current_objects);
bool _generate_volumes_new(ModelObject& object, const std::vector<Component> &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
//bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
// callbacks to parse the .model file
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
@ -1757,6 +1810,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return false;
}
}
else if (_is_svg_shape_file(name)) {
_extract_embossed_svg_shape_file(name, archive, stat);
}
else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) {
m_parsing_slice_info = true;
//extract slice info from archive
@ -2156,6 +2212,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
bool _BBS_3MF_Importer::_is_svg_shape_file(const std::string &name) const {
return boost::starts_with(name, MODEL_FOLDER) && boost::ends_with(name, ".svg");
}
bool _BBS_3MF_Importer::_extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)> extract, bool restore)
{
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
@ -2870,6 +2930,32 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
}*/
void _BBS_3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat){
assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end());
auto file = std::make_unique<std::string>(stat.m_uncomp_size, '\0');
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void *) file->data(), stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading svg shape for emboss");
return;
}
// store for case svg is loaded before volume
m_path_to_emboss_shape_files[filename] = std::move(file);
// find embossed volume, for case svg is loaded after volume
for (const ModelObject* object : m_model->objects)
for (ModelVolume *volume : object->volumes) {
std::optional<EmbossShape> &es = volume->emboss_shape;
if (!es.has_value())
continue;
std::optional<EmbossShape::SvgFile> &svg = es->svg_file;
if (!svg.has_value())
continue;
if (filename.compare(svg->path_in_3mf) == 0)
svg->file_data = m_path_to_emboss_shape_files[filename];
}
}
void _BBS_3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
{
//BBS: add plate tree related logic
@ -3066,6 +3152,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
res = _handle_start_config_volume_mesh(attributes, num_attributes);
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_start_config_metadata(attributes, num_attributes);
else if (::strcmp(SHAPE_TAG, name) == 0)
res = _handle_start_shape_configuration(attributes, num_attributes);
else if (::strcmp(TEXT_TAG, name) == 0)
res = _handle_start_text_configuration(attributes, num_attributes);
else if (::strcmp(PLATE_TAG, name) == 0)
res = _handle_start_config_plater(attributes, num_attributes);
else if (::strcmp(INSTANCE_TAG, name) == 0)
@ -3632,6 +3722,104 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
struct TextConfigurationSerialization
{
public:
TextConfigurationSerialization() = delete;
using TypeToName = boost::bimap<EmbossStyle::Type, std::string_view>;
static const TypeToName type_to_name;
using HorizontalAlignToName = boost::bimap<FontProp::HorizontalAlign, std::string_view>;
static const HorizontalAlignToName horizontal_align_to_name;
using VerticalAlignToName = boost::bimap<FontProp::VerticalAlign, std::string_view>;
static const VerticalAlignToName vertical_align_to_name;
static EmbossStyle::Type get_type(std::string_view type) {
const auto& to_type = TextConfigurationSerialization::type_to_name.right;
auto type_item = to_type.find(type);
assert(type_item != to_type.end());
if (type_item == to_type.end()) return EmbossStyle::Type::undefined;
return type_item->second;
}
static std::string_view get_name(EmbossStyle::Type type) {
const auto& to_name = TextConfigurationSerialization::type_to_name.left;
auto type_name = to_name.find(type);
assert(type_name != to_name.end());
if (type_name == to_name.end()) return "unknown type";
return type_name->second;
}
static void to_xml(std::stringstream &stream, const TextConfiguration &tc);
static std::optional<TextConfiguration> read(const char **attributes, unsigned int num_attributes);
static EmbossShape read_old(const char **attributes, unsigned int num_attributes);
};
bool _BBS_3MF_Importer::_handle_start_text_configuration(const char **attributes, unsigned int num_attributes)
{
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
if (object == m_objects_metadata.end()) {
add_error("Can not assign volume mesh to a valid object");
return false;
}
if (object->second.volumes.empty()) {
add_error("Can not assign mesh to a valid volume");
return false;
}
ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back();
volume.text_configuration = TextConfigurationSerialization::read(attributes, num_attributes);
if (!volume.text_configuration.has_value())
return false;
// Is 3mf version with shapes?
if (volume.shape_configuration.has_value())
return true;
// Back compatibility for 3mf version without shapes
volume.shape_configuration = TextConfigurationSerialization::read_old(attributes, num_attributes);
return true;
}
// Definition of read/write method for EmbossShape
static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive);
static std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes);
bool _BBS_3MF_Importer::_handle_start_shape_configuration(const char **attributes, unsigned int num_attributes)
{
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
if (object == m_objects_metadata.end()) {
add_error("Can not assign volume mesh to a valid object");
return false;
}
auto &volumes = object->second.volumes;
if (volumes.empty()) {
add_error("Can not assign mesh to a valid volume");
return false;
}
ObjectMetadata::VolumeMetadata &volume = volumes.back();
volume.shape_configuration = read_emboss_shape(attributes, num_attributes);
if (!volume.shape_configuration.has_value())
return false;
// Fill svg file content into shape_configuration
std::optional<EmbossShape::SvgFile> &svg = volume.shape_configuration->svg_file;
if (!svg.has_value())
return true; // do not contain svg file
const std::string &path = svg->path_in_3mf;
if (path.empty())
return true; // do not contain svg file
auto it = m_path_to_emboss_shape_files.find(path);
if (it == m_path_to_emboss_shape_files.end())
return true; // svg file is not loaded yet
svg->file_data = it->second;
return true;
}
bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
{
static const unsigned int MAX_RECURSIONS = 10;
@ -4169,6 +4357,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
ObjectMetadata::VolumeMetadata &volume = object->second.volumes[m_curr_config.volume_id];
if (volume.text_configuration.has_value()) {
add_error("Both text_info and text_configuration found, ignore legacy text_info");
return true;
}
// TODO: Orca: support legacy text info
/*
TextInfo text_info;
text_info.m_text = xml_unescape(bbs_get_attribute_value_string(attributes, num_attributes, TEXT_ATTR));
text_info.m_font_name = bbs_get_attribute_value_string(attributes, num_attributes, FONT_NAME_ATTR);
@ -4196,7 +4391,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (!hit_normal.empty())
text_info.m_rr.normal = get_vec3_from_string(hit_normal);
volume.text_info = text_info;
volume.text_info = text_info;*/
return true;
}
@ -4473,9 +4668,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
volume->set_type(volume_data->part_type);
if (!volume_data->text_info.m_text.empty())
volume->set_text_info(volume_data->text_info);
if (auto &es = volume_data->shape_configuration; es.has_value())
volume->emboss_shape = std::move(es);
if (auto &tc = volume_data->text_configuration; tc.has_value())
volume->text_configuration = std::move(tc);
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data->metadata) {
@ -4519,7 +4716,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
/*
bool _BBS_3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
{
if (!object.volumes.empty()) {
@ -4667,7 +4864,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
*/
void XMLCALL _BBS_3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes)
{
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
@ -5242,6 +5439,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
bool save_model_to_file(StoreParams& store_params);
// add backup logic
bool save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id);
static void add_transformation(std::stringstream &stream, const Transform3d &tr);
private:
//BBS: add plate data related logic
@ -6687,6 +6885,16 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return flush(output_buffer, true);
}
void _BBS_3MF_Exporter::add_transformation(std::stringstream &stream, const Transform3d &tr)
{
for (unsigned c = 0; c < 4; ++c) {
for (unsigned r = 0; r < 3; ++r) {
stream << tr(r, c);
if (r != 2 || c != 3) stream << " ";
}
}
}
bool _BBS_3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const
{
// This happens for empty projects
@ -6707,13 +6915,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (!item.path.empty())
stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path);
stream << "\" " << TRANSFORM_ATTR << "=\"";
for (unsigned c = 0; c < 4; ++c) {
for (unsigned r = 0; r < 3; ++r) {
stream << item.transform(r, c);
if (r != 2 || c != 3)
stream << " ";
}
}
add_transformation(stream, item.transform);
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n";
}
@ -7041,38 +7243,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
void _add_text_info_to_archive(std::stringstream& stream, const TextInfo& text_info) {
stream << " <" << TEXT_INFO_TAG << " ";
stream << TEXT_ATTR << "=\"" << xml_escape(text_info.m_text) << "\" ";
stream << FONT_NAME_ATTR << "=\"" << text_info.m_font_name << "\" ";
stream << FONT_INDEX_ATTR << "=\"" << text_info.m_curr_font_idx << "\" ";
stream << FONT_SIZE_ATTR << "=\"" << text_info.m_font_size << "\" ";
stream << THICKNESS_ATTR << "=\"" << text_info.m_thickness << "\" ";
stream << EMBEDED_DEPTH_ATTR << "=\"" << text_info.m_embeded_depth << "\" ";
stream << ROTATE_ANGLE_ATTR << "=\"" << text_info.m_rotate_angle << "\" ";
stream << TEXT_GAP_ATTR << "=\"" << text_info.m_text_gap << "\" ";
stream << BOLD_ATTR << "=\"" << (text_info.m_bold ? 1 : 0) << "\" ";
stream << ITALIC_ATTR << "=\"" << (text_info.m_italic ? 1 : 0) << "\" ";
stream << SURFACE_TEXT_ATTR << "=\"" << (text_info.m_is_surface_text ? 1 : 0) << "\" ";
stream << KEEP_HORIZONTAL_ATTR << "=\"" << (text_info.m_keep_horizontal ? 1 : 0) << "\" ";
stream << HIT_MESH_ATTR << "=\"" << text_info.m_rr.mesh_id << "\" ";
stream << HIT_POSITION_ATTR << "=\"";
add_vec3(stream, text_info.m_rr.hit);
stream << "\" ";
stream << HIT_NORMAL_ATTR << "=\"";
add_vec3(stream, text_info.m_rr.normal);
stream << "\" ";
stream << "/>\n";
}
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx, bool save_gcode, bool use_loaded_id)
{
std::stringstream stream;
@ -7170,9 +7340,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n";
}
const TextInfo &text_info = volume->get_text_info();
if (!text_info.m_text.empty())
_add_text_info_to_archive(stream, text_info);
if (const std::optional<EmbossShape> &es = volume->emboss_shape;
es.has_value())
to_xml(stream, *es, *volume, archive);
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
tc.has_value())
TextConfigurationSerialization::to_xml(stream, *tc);
// stores mesh's statistics
const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors;
@ -8163,4 +8337,335 @@ SaveObjectGaurd::~SaveObjectGaurd()
_BBS_Backup_Manager::get().pop_object_gaurd();
}
namespace{
// Conversion with bidirectional map
// F .. first, S .. second
template<typename F, typename S>
F bimap_cvt(const boost::bimap<F, S> &bmap, S s, const F & def_value) {
const auto &map = bmap.right;
auto found_item = map.find(s);
// only for back and forward compatibility
assert(found_item != map.end());
if (found_item == map.end())
return def_value;
return found_item->second;
}
template<typename F, typename S>
S bimap_cvt(const boost::bimap<F, S> &bmap, F f, const S &def_value)
{
const auto &map = bmap.left;
auto found_item = map.find(f);
// only for back and forward compatibility
assert(found_item != map.end());
if (found_item == map.end())
return def_value;
return found_item->second;
}
} // namespace
/// <summary>
/// TextConfiguration serialization
/// </summary>
const TextConfigurationSerialization::TypeToName TextConfigurationSerialization::type_to_name =
boost::assign::list_of<TypeToName::relation>
(EmbossStyle::Type::file_path, "file_name")
(EmbossStyle::Type::wx_win_font_descr, "wxFontDescriptor_Windows")
(EmbossStyle::Type::wx_lin_font_descr, "wxFontDescriptor_Linux")
(EmbossStyle::Type::wx_mac_font_descr, "wxFontDescriptor_MacOsX");
const TextConfigurationSerialization::HorizontalAlignToName TextConfigurationSerialization::horizontal_align_to_name =
boost::assign::list_of<HorizontalAlignToName::relation>
(FontProp::HorizontalAlign::left, "left")
(FontProp::HorizontalAlign::center, "center")
(FontProp::HorizontalAlign::right, "right");
const TextConfigurationSerialization::VerticalAlignToName TextConfigurationSerialization::vertical_align_to_name =
boost::assign::list_of<VerticalAlignToName::relation>
(FontProp::VerticalAlign::top, "top")
(FontProp::VerticalAlign::center, "middle")
(FontProp::VerticalAlign::bottom, "bottom");
void TextConfigurationSerialization::to_xml(std::stringstream &stream, const TextConfiguration &tc)
{
stream << " <" << TEXT_TAG << " ";
stream << TEXT_DATA_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(tc.text) << "\" ";
// font item
const EmbossStyle &style = tc.style;
stream << STYLE_NAME_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(style.name) << "\" ";
stream << FONT_DESCRIPTOR_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(style.path) << "\" ";
constexpr std::string_view dafault_type{"undefined"};
std::string_view style_type = bimap_cvt(type_to_name, style.type, dafault_type);
stream << FONT_DESCRIPTOR_TYPE_ATTR << "=\"" << style_type << "\" ";
// font property
const FontProp &fp = tc.style.prop;
if (fp.char_gap.has_value())
stream << CHAR_GAP_ATTR << "=\"" << *fp.char_gap << "\" ";
if (fp.line_gap.has_value())
stream << LINE_GAP_ATTR << "=\"" << *fp.line_gap << "\" ";
stream << LINE_HEIGHT_ATTR << "=\"" << fp.size_in_mm << "\" ";
if (fp.boldness.has_value())
stream << BOLDNESS_ATTR << "=\"" << *fp.boldness << "\" ";
if (fp.skew.has_value())
stream << SKEW_ATTR << "=\"" << *fp.skew << "\" ";
if (fp.per_glyph)
stream << PER_GLYPH_ATTR << "=\"" << 1 << "\" ";
stream << HORIZONTAL_ALIGN_ATTR << "=\"" << bimap_cvt(horizontal_align_to_name, fp.align.first, dafault_type) << "\" ";
stream << VERTICAL_ALIGN_ATTR << "=\"" << bimap_cvt(vertical_align_to_name, fp.align.second, dafault_type) << "\" ";
if (fp.collection_number.has_value())
stream << COLLECTION_NUMBER_ATTR << "=\"" << *fp.collection_number << "\" ";
// font descriptor
if (fp.family.has_value())
stream << FONT_FAMILY_ATTR << "=\"" << *fp.family << "\" ";
if (fp.face_name.has_value())
stream << FONT_FACE_NAME_ATTR << "=\"" << *fp.face_name << "\" ";
if (fp.style.has_value())
stream << FONT_STYLE_ATTR << "=\"" << *fp.style << "\" ";
if (fp.weight.has_value())
stream << FONT_WEIGHT_ATTR << "=\"" << *fp.weight << "\" ";
stream << "/>\n"; // end TEXT_TAG
}
namespace {
FontProp::HorizontalAlign read_horizontal_align(const char **attributes, unsigned int num_attributes, const TextConfigurationSerialization::HorizontalAlignToName& horizontal_align_to_name){
std::string horizontal_align_str = bbs_get_attribute_value_string(attributes, num_attributes, HORIZONTAL_ALIGN_ATTR);
// Back compatibility
// PS 2.6.0 do not have align
if (horizontal_align_str.empty())
return FontProp::HorizontalAlign::center;
// Back compatibility
// PS 2.6.1 store indices(0|1|2) instead of text for align
if (horizontal_align_str.length() == 1) {
int horizontal_align_int = 0;
if(boost::spirit::qi::parse(horizontal_align_str.c_str(), horizontal_align_str.c_str() + 1, boost::spirit::qi::int_, horizontal_align_int))
return static_cast<FontProp::HorizontalAlign>(horizontal_align_int);
}
return bimap_cvt(horizontal_align_to_name, std::string_view(horizontal_align_str), FontProp::HorizontalAlign::center);
}
FontProp::VerticalAlign read_vertical_align(const char **attributes, unsigned int num_attributes, const TextConfigurationSerialization::VerticalAlignToName& vertical_align_to_name){
std::string vertical_align_str = bbs_get_attribute_value_string(attributes, num_attributes, VERTICAL_ALIGN_ATTR);
// Back compatibility
// PS 2.6.0 do not have align
if (vertical_align_str.empty())
return FontProp::VerticalAlign::center;
// Back compatibility
// PS 2.6.1 store indices(0|1|2) instead of text for align
if (vertical_align_str.length() == 1) {
int vertical_align_int = 0;
if(boost::spirit::qi::parse(vertical_align_str.c_str(), vertical_align_str.c_str() + 1, boost::spirit::qi::int_, vertical_align_int))
return static_cast<FontProp::VerticalAlign>(vertical_align_int);
}
return bimap_cvt(vertical_align_to_name, std::string_view(vertical_align_str), FontProp::VerticalAlign::center);
}
} // namespace
std::optional<TextConfiguration> TextConfigurationSerialization::read(const char **attributes, unsigned int num_attributes)
{
FontProp fp;
int char_gap = bbs_get_attribute_value_int(attributes, num_attributes, CHAR_GAP_ATTR);
if (char_gap != 0) fp.char_gap = char_gap;
int line_gap = bbs_get_attribute_value_int(attributes, num_attributes, LINE_GAP_ATTR);
if (line_gap != 0) fp.line_gap = line_gap;
float boldness = bbs_get_attribute_value_float(attributes, num_attributes, BOLDNESS_ATTR);
if (std::fabs(boldness) > std::numeric_limits<float>::epsilon())
fp.boldness = boldness;
float skew = bbs_get_attribute_value_float(attributes, num_attributes, SKEW_ATTR);
if (std::fabs(skew) > std::numeric_limits<float>::epsilon())
fp.skew = skew;
int per_glyph = bbs_get_attribute_value_int(attributes, num_attributes, PER_GLYPH_ATTR);
if (per_glyph == 1) fp.per_glyph = true;
fp.align = FontProp::Align(
read_horizontal_align(attributes, num_attributes, horizontal_align_to_name),
read_vertical_align(attributes, num_attributes, vertical_align_to_name));
int collection_number = bbs_get_attribute_value_int(attributes, num_attributes, COLLECTION_NUMBER_ATTR);
if (collection_number > 0) fp.collection_number = static_cast<unsigned int>(collection_number);
fp.size_in_mm = bbs_get_attribute_value_float(attributes, num_attributes, LINE_HEIGHT_ATTR);
std::string family = bbs_get_attribute_value_string(attributes, num_attributes, FONT_FAMILY_ATTR);
if (!family.empty()) fp.family = family;
std::string face_name = bbs_get_attribute_value_string(attributes, num_attributes, FONT_FACE_NAME_ATTR);
if (!face_name.empty()) fp.face_name = face_name;
std::string style = bbs_get_attribute_value_string(attributes, num_attributes, FONT_STYLE_ATTR);
if (!style.empty()) fp.style = style;
std::string weight = bbs_get_attribute_value_string(attributes, num_attributes, FONT_WEIGHT_ATTR);
if (!weight.empty()) fp.weight = weight;
std::string style_name = bbs_get_attribute_value_string(attributes, num_attributes, STYLE_NAME_ATTR);
std::string font_descriptor = bbs_get_attribute_value_string(attributes, num_attributes, FONT_DESCRIPTOR_ATTR);
std::string type_str = bbs_get_attribute_value_string(attributes, num_attributes, FONT_DESCRIPTOR_TYPE_ATTR);
EmbossStyle::Type type = bimap_cvt(type_to_name, std::string_view{type_str}, EmbossStyle::Type::undefined);
std::string text = bbs_get_attribute_value_string(attributes, num_attributes, TEXT_DATA_ATTR);
EmbossStyle es{style_name, std::move(font_descriptor), type, std::move(fp)};
return TextConfiguration{std::move(es), std::move(text)};
}
EmbossShape TextConfigurationSerialization::read_old(const char **attributes, unsigned int num_attributes)
{
EmbossShape es;
std::string fix_tr_mat_str = bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR);
if (!fix_tr_mat_str.empty())
es.fix_3mf_tr = bbs_get_transform_from_3mf_specs_string(fix_tr_mat_str);
if (bbs_get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR) == 1)
es.projection.use_surface = true;
es.projection.depth = bbs_get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR);
int use_surface = bbs_get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
if (use_surface == 1)
es.projection.use_surface = true;
return es;
}
namespace {
Transform3d create_fix(const std::optional<Transform3d> &prev, const ModelVolume &volume)
{
// IMPROVE: check if volume was modified (translated, rotated OR scaled)
// when no change do not calculate transformation only store original fix matrix
// Create transformation used after load actual stored volume
// Orca: do not bake volume transformation into meshes
// const Transform3d &actual_trmat = volume.get_matrix();
const Transform3d& actual_trmat = Transform3d::Identity();
const auto &vertices = volume.mesh().its.vertices;
Vec3d min = actual_trmat * vertices.front().cast<double>();
Vec3d max = min;
for (const Vec3f &v : vertices) {
Vec3d vd = actual_trmat * v.cast<double>();
for (size_t i = 0; i < 3; ++i) {
if (min[i] > vd[i])
min[i] = vd[i];
if (max[i] < vd[i])
max[i] = vd[i];
}
}
Vec3d center = (max + min) / 2;
Transform3d post_trmat = Transform3d::Identity();
post_trmat.translate(center);
Transform3d fix_trmat = actual_trmat.inverse() * post_trmat;
if (!prev.has_value())
return fix_trmat;
// check whether fix somehow differ previous
if (fix_trmat.isApprox(Transform3d::Identity(), 1e-5))
return *prev;
return *prev * fix_trmat;
}
bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive){
if (svg.path_in_3mf.empty())
return true; // EmbossedText OR unwanted store .svg file into .3mf (protection of copyRight)
if (!svg.path.empty())
stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path) << "\" ";
stream << SVG_FILE_PATH_IN_3MF_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path_in_3mf) << "\" ";
std::shared_ptr<std::string> file_data = svg.file_data;
assert(file_data != nullptr);
if (file_data == nullptr && !svg.path.empty())
file_data = read_from_disk(svg.path);
if (file_data == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "Can't write svg file no filedata";
return false;
}
const std::string &file_data_str = *file_data;
return mz_zip_writer_add_mem(&archive, svg.path_in_3mf.c_str(),
(const void *) file_data_str.c_str(), file_data_str.size(), MZ_DEFAULT_COMPRESSION);
}
} // namespace
void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive)
{
stream << " <" << SHAPE_TAG << " ";
if (es.svg_file.has_value())
if(!to_xml(stream, *es.svg_file, volume, archive))
BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf";
stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" ";
if (!es.final_shape.is_healed)
stream << UNHEALED_ATTR << "=\"" << 1 << "\" ";
// projection
const EmbossProjection &p = es.projection;
stream << DEPTH_ATTR << "=\"" << p.depth << "\" ";
if (p.use_surface)
stream << USE_SURFACE_ATTR << "=\"" << 1 << "\" ";
// FIX of baked transformation
Transform3d fix = create_fix(es.fix_3mf_tr, volume);
stream << TRANSFORM_ATTR << "=\"";
_BBS_3MF_Exporter::add_transformation(stream, fix);
stream << "\" ";
stream << "/>\n"; // end SHAPE_TAG
}
std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes) {
double scale = bbs_get_attribute_value_float(attributes, num_attributes, SHAPE_SCALE_ATTR);
int unhealed = bbs_get_attribute_value_int(attributes, num_attributes, UNHEALED_ATTR);
bool is_healed = unhealed != 1;
EmbossProjection projection;
projection.depth = bbs_get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR);
if (is_approx(projection.depth, 0.))
projection.depth = 10.;
int use_surface = bbs_get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
if (use_surface == 1)
projection.use_surface = true;
std::optional<Transform3d> fix_tr_mat;
std::string fix_tr_mat_str = bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR);
if (!fix_tr_mat_str.empty()) {
fix_tr_mat = bbs_get_transform_from_3mf_specs_string(fix_tr_mat_str);
}
std::string file_path = bbs_get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_ATTR);
std::string file_path_3mf = bbs_get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_IN_3MF_ATTR);
// MayBe: store also shapes to not store svg
// But be carefull curve will be lost -> scale will not change sampling
// shapes could be loaded from SVG
ExPolygonsWithIds shapes;
// final shape could be calculated from shapes
HealedExPolygons final_shape;
final_shape.is_healed = is_healed;
EmbossShape::SvgFile svg{file_path, file_path_3mf};
return EmbossShape{std::move(shapes), std::move(final_shape), scale, std::move(projection), std::move(fix_tr_mat), std::move(svg)};
}
} // namespace Slic3r

View file

@ -397,42 +397,12 @@ Transform3d scale_transform(const Vec3d& scale)
Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>& rotation_matrix)
{
// reference: http://www.gregslabaugh.net/publications/euler.pdf
Vec3d angles1 = Vec3d::Zero();
Vec3d angles2 = Vec3d::Zero();
// BBS: rotation_matrix(2, 0) may be slighterly larger than 1 due to numerical accuracy
if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5 || std::abs(rotation_matrix(2, 0))>1) {
angles1.z() = 0.0;
if (rotation_matrix(2, 0) < 0.0) { // == -1.0
angles1.y() = 0.5 * double(PI);
angles1.x() = angles1.z() + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2));
}
else { // == 1.0
angles1.y() = - 0.5 * double(PI);
angles1.x() = - angles1.y() + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2));
}
angles2 = angles1;
}
else {
angles1.y() = -::asin(rotation_matrix(2, 0));
const double inv_cos1 = 1.0 / ::cos(angles1.y());
angles1.x() = ::atan2(rotation_matrix(2, 1) * inv_cos1, rotation_matrix(2, 2) * inv_cos1);
angles1.z() = ::atan2(rotation_matrix(1, 0) * inv_cos1, rotation_matrix(0, 0) * inv_cos1);
angles2.y() = double(PI) - angles1.y();
const double inv_cos2 = 1.0 / ::cos(angles2.y());
angles2.x() = ::atan2(rotation_matrix(2, 1) * inv_cos2, rotation_matrix(2, 2) * inv_cos2);
angles2.z() = ::atan2(rotation_matrix(1, 0) * inv_cos2, rotation_matrix(0, 0) * inv_cos2);
}
// The following euristic is the best found up to now (in the sense that it works fine with the greatest number of edge use-cases)
// but there are other use-cases were it does not
// We need to improve it
const double min_1 = angles1.cwiseAbs().minCoeff();
const double min_2 = angles2.cwiseAbs().minCoeff();
const bool use_1 = (min_1 < min_2) || (is_approx(min_1, min_2) && (angles1.norm() <= angles2.norm()));
return use_1 ? angles1 : angles2;
// The extracted "rotation" is a triplet of numbers such that Geometry::rotation_transform
// returns the original transform. Because of the chosen order of rotations, the triplet
// is not equivalent to Euler angles in the usual sense.
Vec3d angles = rotation_matrix.eulerAngles(2,1,0);
std::swap(angles(0), angles(2));
return angles;
}
Vec3d extract_euler_angles(const Transform3d& transform)
@ -446,14 +416,6 @@ Vec3d extract_euler_angles(const Transform3d& transform)
return extract_euler_angles(m);
}
static Transform3d extract_rotation_matrix(const Transform3d& trafo)
{
Matrix3d rotation;
Matrix3d scale;
trafo.computeRotationScaling(&rotation, &scale);
return Transform3d(rotation);
}
void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix)
{
double epsilon = 1e-5;
@ -488,42 +450,65 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl
}
}
bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const
Transform3d Transformation::get_offset_matrix() const
{
return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror);
return translation_transform(get_offset());
}
void Transformation::Flags::set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror)
static Transform3d extract_rotation_matrix(const Transform3d& trafo)
{
this->dont_translate = dont_translate;
this->dont_rotate = dont_rotate;
this->dont_scale = dont_scale;
this->dont_mirror = dont_mirror;
Matrix3d rotation;
Matrix3d scale;
trafo.computeRotationScaling(&rotation, &scale);
return Transform3d(rotation);
}
Transformation::Transformation()
static Transform3d extract_scale(const Transform3d& trafo)
{
reset();
Matrix3d rotation;
Matrix3d scale;
trafo.computeRotationScaling(&rotation, &scale);
return Transform3d(scale);
}
Transformation::Transformation(const Transform3d& transform)
static std::pair<Transform3d, Transform3d> extract_rotation_scale(const Transform3d& trafo)
{
set_from_transform(transform);
Matrix3d rotation;
Matrix3d scale;
trafo.computeRotationScaling(&rotation, &scale);
return { Transform3d(rotation), Transform3d(scale) };
}
void Transformation::set_offset(const Vec3d& offset)
static bool contains_skew(const Transform3d& trafo)
{
set_offset(X, offset.x());
set_offset(Y, offset.y());
set_offset(Z, offset.z());
Matrix3d rotation;
Matrix3d scale;
trafo.computeRotationScaling(&rotation, &scale);
if (scale.isDiagonal())
return false;
if (scale.determinant() >= 0.0)
return true;
// the matrix contains mirror
const Matrix3d ratio = scale.cwiseQuotient(trafo.matrix().block<3,3>(0,0));
auto check_skew = [&ratio](int i, int j, bool& skew) {
if (!std::isnan(ratio(i, j)) && !std::isnan(ratio(j, i)))
skew |= std::abs(ratio(i, j) * ratio(j, i) - 1.0) > EPSILON;
};
bool has_skew = false;
check_skew(0, 1, has_skew);
check_skew(0, 2, has_skew);
check_skew(1, 2, has_skew);
return has_skew;
}
void Transformation::set_offset(Axis axis, double offset)
Vec3d Transformation::get_rotation() const
{
if (m_offset(axis) != offset) {
m_offset(axis) = offset;
m_dirty = true;
}
return extract_euler_angles(extract_rotation_matrix(m_matrix));
}
Transform3d Transformation::get_rotation_matrix() const
@ -533,9 +518,9 @@ Transform3d Transformation::get_rotation_matrix() const
void Transformation::set_rotation(const Vec3d& rotation)
{
set_rotation(X, rotation.x());
set_rotation(Y, rotation.y());
set_rotation(Z, rotation.z());
const Vec3d offset = get_offset();
m_matrix = rotation_transform(rotation) * extract_scale(m_matrix);
m_matrix.translation() = offset;
}
void Transformation::set_rotation(Axis axis, double rotation)
@ -544,32 +529,88 @@ void Transformation::set_rotation(Axis axis, double rotation)
if (is_approx(std::abs(rotation), 2.0 * double(PI)))
rotation = 0.0;
if (m_rotation(axis) != rotation) {
m_rotation(axis) = rotation;
m_dirty = true;
}
auto [curr_rotation, scale] = extract_rotation_scale(m_matrix);
Vec3d angles = extract_euler_angles(curr_rotation);
angles[axis] = rotation;
const Vec3d offset = get_offset();
m_matrix = rotation_transform(angles) * scale;
m_matrix.translation() = offset;
}
Vec3d Transformation::get_scaling_factor() const
{
const Transform3d scale = extract_scale(m_matrix);
return { std::abs(scale(0, 0)), std::abs(scale(1, 1)), std::abs(scale(2, 2)) };
}
Transform3d Transformation::get_scaling_factor_matrix() const
{
Transform3d scale = extract_scale(m_matrix);
scale(0, 0) = std::abs(scale(0, 0));
scale(1, 1) = std::abs(scale(1, 1));
scale(2, 2) = std::abs(scale(2, 2));
return scale;
}
void Transformation::set_scaling_factor(const Vec3d& scaling_factor)
{
set_scaling_factor(X, scaling_factor.x());
set_scaling_factor(Y, scaling_factor.y());
set_scaling_factor(Z, scaling_factor.z());
assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0);
const Vec3d offset = get_offset();
m_matrix = extract_rotation_matrix(m_matrix) * scale_transform(scaling_factor);
m_matrix.translation() = offset;
}
void Transformation::set_scaling_factor(Axis axis, double scaling_factor)
{
if (m_scaling_factor(axis) != std::abs(scaling_factor)) {
m_scaling_factor(axis) = std::abs(scaling_factor);
m_dirty = true;
}
assert(scaling_factor > 0.0);
auto [rotation, scale] = extract_rotation_scale(m_matrix);
scale(axis, axis) = scaling_factor;
const Vec3d offset = get_offset();
m_matrix = rotation * scale;
m_matrix.translation() = offset;
}
Vec3d Transformation::get_mirror() const
{
const Transform3d scale = extract_scale(m_matrix);
return { scale(0, 0) / std::abs(scale(0, 0)), scale(1, 1) / std::abs(scale(1, 1)), scale(2, 2) / std::abs(scale(2, 2)) };
}
Transform3d Transformation::get_mirror_matrix() const
{
Transform3d scale = extract_scale(m_matrix);
scale(0, 0) = scale(0, 0) / std::abs(scale(0, 0));
scale(1, 1) = scale(1, 1) / std::abs(scale(1, 1));
scale(2, 2) = scale(2, 2) / std::abs(scale(2, 2));
return scale;
}
void Transformation::set_mirror(const Vec3d& mirror)
{
set_mirror(X, mirror.x());
set_mirror(Y, mirror.y());
set_mirror(Z, mirror.z());
Vec3d copy(mirror);
const Vec3d abs_mirror = copy.cwiseAbs();
for (int i = 0; i < 3; ++i) {
if (abs_mirror(i) == 0.0)
copy(i) = 1.0;
else if (abs_mirror(i) != 1.0)
copy(i) /= abs_mirror(i);
}
auto [rotation, scale] = extract_rotation_scale(m_matrix);
const Vec3d curr_scales = { scale(0, 0), scale(1, 1), scale(2, 2) };
const Vec3d signs = curr_scales.cwiseProduct(copy);
if (signs[0] < 0.0) scale(0, 0) = -scale(0, 0);
if (signs[1] < 0.0) scale(1, 1) = -scale(1, 1);
if (signs[2] < 0.0) scale(2, 2) = -scale(2, 2);
const Vec3d offset = get_offset();
m_matrix = rotation * scale;
m_matrix.translation() = offset;
}
void Transformation::set_mirror(Axis axis, double mirror)
@ -580,75 +621,61 @@ void Transformation::set_mirror(Axis axis, double mirror)
else if (abs_mirror != 1.0)
mirror /= abs_mirror;
if (m_mirror(axis) != mirror) {
m_mirror(axis) = mirror;
m_dirty = true;
}
auto [rotation, scale] = extract_rotation_scale(m_matrix);
const double curr_scale = scale(axis, axis);
const double sign = curr_scale * mirror;
if (sign < 0.0) scale(axis, axis) = -scale(axis, axis);
const Vec3d offset = get_offset();
m_matrix = rotation * scale;
m_matrix.translation() = offset;
}
void Transformation::set_from_transform(const Transform3d& transform)
bool Transformation::has_skew() const
{
// offset
set_offset(transform.matrix().block(0, 3, 3, 1));
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> m3x3 = transform.matrix().block(0, 0, 3, 3);
// mirror
// it is impossible to reconstruct the original mirroring factors from a matrix,
// we can only detect if the matrix contains a left handed reference system
// in which case we reorient it back to right handed by mirroring the x axis
Vec3d mirror = Vec3d::Ones();
if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) {
mirror.x() = -1.0;
// remove mirror
m3x3.col(0) *= -1.0;
}
set_mirror(mirror);
// scale
set_scaling_factor(Vec3d(m3x3.col(0).norm(), m3x3.col(1).norm(), m3x3.col(2).norm()));
// remove scale
m3x3.col(0).normalize();
m3x3.col(1).normalize();
m3x3.col(2).normalize();
// rotation
set_rotation(extract_euler_angles(m3x3));
// forces matrix recalculation matrix
m_matrix = get_matrix();
// // debug check
// if (!m_matrix.isApprox(transform))
// std::cout << "something went wrong in extracting data from matrix" << std::endl;
return contains_skew(m_matrix);
}
void Transformation::reset()
{
m_offset = Vec3d::Zero();
m_rotation = Vec3d::Zero();
m_scaling_factor = Vec3d::Ones();
m_mirror = Vec3d::Ones();
m_matrix = Transform3d::Identity();
m_dirty = false;
}
const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const
void Transformation::reset_rotation()
{
if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) {
m_matrix = Geometry::assemble_transform(
dont_translate ? Vec3d::Zero() : m_offset,
dont_rotate ? Vec3d::Zero() : m_rotation,
dont_scale ? Vec3d::Ones() : m_scaling_factor,
dont_mirror ? Vec3d::Ones() : m_mirror
);
const Geometry::TransformationSVD svd(*this);
m_matrix = get_offset_matrix() * Transform3d(svd.v * svd.s * svd.v.transpose()) * svd.mirror_matrix();
}
m_flags.set(dont_translate, dont_rotate, dont_scale, dont_mirror);
m_dirty = false;
}
void Transformation::reset_scaling_factor()
{
const Geometry::TransformationSVD svd(*this);
m_matrix = get_offset_matrix() * Transform3d(svd.u) * Transform3d(svd.v.transpose()) * svd.mirror_matrix();
}
return m_matrix;
void Transformation::reset_skew()
{
auto new_scale_factor = [](const Matrix3d& s) {
return pow(s(0, 0) * s(1, 1) * s(2, 2), 1. / 3.); // scale average
};
const Geometry::TransformationSVD svd(*this);
m_matrix = get_offset_matrix() * Transform3d(svd.u) * scale_transform(new_scale_factor(svd.s)) * Transform3d(svd.v.transpose()) * svd.mirror_matrix();
}
Transform3d Transformation::get_matrix_no_offset() const
{
Transformation copy(*this);
copy.reset_offset();
return copy.get_matrix();
}
Transform3d Transformation::get_matrix_no_scaling_factor() const
{
Transformation copy(*this);
copy.reset_scaling_factor();
return copy.get_matrix();
}
Transformation Transformation::operator * (const Transformation& other) const
@ -663,7 +690,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
if (instance_transformation.is_scaling_uniform()) {
// No need to run the non-linear least squares fitting for uniform scaling.
// Just set the inverse.
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
out.set_matrix(instance_transformation.get_matrix_no_offset().inverse());
}
else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) {
// Anisotropic scaling, rotation by multiples of ninety degrees.
@ -712,6 +739,53 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
return out;
}
TransformationSVD::TransformationSVD(const Transform3d& trafo)
{
const auto &m0 = trafo.matrix().block<3, 3>(0, 0);
mirror = m0.determinant() < 0.0;
Matrix3d m;
if (mirror)
m = m0 * Eigen::DiagonalMatrix<double, 3, 3>(-1.0, 1.0, 1.0);
else
m = m0;
const Eigen::JacobiSVD<Matrix3d> svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV);
u = svd.matrixU();
v = svd.matrixV();
s = svd.singularValues().asDiagonal();
scale = !s.isApprox(Matrix3d::Identity());
anisotropic_scale = ! is_approx(s(0, 0), s(1, 1)) || ! is_approx(s(1, 1), s(2, 2));
rotation = !v.isApprox(u);
if (anisotropic_scale) {
rotation_90_degrees = true;
for (int i = 0; i < 3; ++i) {
const Vec3d row = v.row(i).cwiseAbs();
const size_t num_zeros = is_approx(row[0], 0.) + is_approx(row[1], 0.) + is_approx(row[2], 0.);
const size_t num_ones = is_approx(row[0], 1.) + is_approx(row[1], 1.) + is_approx(row[2], 1.);
if (num_zeros != 2 || num_ones != 1) {
rotation_90_degrees = false;
break;
}
}
// Detect skew by brute force: check if the axes are still orthogonal after transformation
const Matrix3d trafo_linear = trafo.linear();
const std::array<Vec3d, 3> axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
std::array<Vec3d, 3> transformed_axes;
for (int i = 0; i < 3; ++i) {
transformed_axes[i] = trafo_linear * axes[i];
}
skew = std::abs(transformed_axes[0].dot(transformed_axes[1])) > EPSILON ||
std::abs(transformed_axes[1].dot(transformed_axes[2])) > EPSILON ||
std::abs(transformed_axes[2].dot(transformed_axes[0])) > EPSILON;
// This following old code does not work under all conditions. The v matrix can become non diagonal (see SPE-1492)
// skew = ! rotation_90_degrees;
} else
skew = false;
}
// For parsing a transformation matrix from 3MF / AMF.
Transform3d transform3d_from_string(const std::string& transform_str)
{

View file

@ -51,9 +51,9 @@ enum Orientation
static inline Orientation orient(const Point &a, const Point &b, const Point &c)
{
static_assert(sizeof(coord_t) * 2 == sizeof(int64_t), "orient works with 32 bit coordinates");
int64_t u = int64_t(b(0)) * int64_t(c(1)) - int64_t(b(1)) * int64_t(c(0));
int64_t v = int64_t(a(0)) * int64_t(c(1)) - int64_t(a(1)) * int64_t(c(0));
int64_t w = int64_t(a(0)) * int64_t(b(1)) - int64_t(a(1)) * int64_t(b(0));
int64_t u = int64_t(b.x()) * int64_t(c.y()) - int64_t(b.y()) * int64_t(c.x());
int64_t v = int64_t(a.x()) * int64_t(c.y()) - int64_t(a.y()) * int64_t(c.x());
int64_t w = int64_t(a.x()) * int64_t(b.y()) - int64_t(a.y()) * int64_t(b.x());
int64_t d = u - v + w;
return (d > 0) ? ORIENTATION_CCW : ((d == 0) ? ORIENTATION_COLINEAR : ORIENTATION_CW);
}
@ -322,6 +322,13 @@ template<typename T> T angle_to_0_2PI(T angle)
return angle;
}
template<typename T> void to_range_pi_pi(T &angle){
if (angle > T(PI) || angle <= -T(PI)) {
int count = static_cast<int>(std::round(angle / (2 * PI)));
angle -= static_cast<T>(count * 2 * PI);
assert(angle <= T(PI) && angle > -T(PI));
}
}
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
@ -404,66 +411,67 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d &rotation_axis, doubl
class Transformation
{
struct Flags
{
bool dont_translate{ true };
bool dont_rotate{ true };
bool dont_scale{ true };
bool dont_mirror{ true };
bool needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const;
void set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror);
};
Vec3d m_offset{ Vec3d::Zero() }; // In unscaled coordinates
Vec3d m_rotation{ Vec3d::Zero() }; // Rotation around the three axes, in radians around mesh center point
Vec3d m_scaling_factor{ Vec3d::Ones() }; // Scaling factors along the three axes
Vec3d m_mirror{ Vec3d::Ones() }; // Mirroring along the three axes
mutable Transform3d m_matrix{ Transform3d::Identity() };
mutable Flags m_flags;
mutable bool m_dirty{ false };
Transform3d m_matrix{ Transform3d::Identity() };
public:
Transformation();
explicit Transformation(const Transform3d& transform);
Transformation() = default;
explicit Transformation(const Transform3d& transform) : m_matrix(transform) {}
//BBS: add get dirty function
bool is_dirty() { return m_dirty; }
Vec3d get_offset() const { return m_matrix.translation(); }
double get_offset(Axis axis) const { return get_offset()[axis]; }
const Vec3d& get_offset() const { return m_offset; }
double get_offset(Axis axis) const { return m_offset(axis); }
Transform3d get_offset_matrix() const;
void set_offset(const Vec3d& offset);
void set_offset(Axis axis, double offset);
void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; }
void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; }
const Vec3d& get_rotation() const { return m_rotation; }
double get_rotation(Axis axis) const { return m_rotation(axis); }
Vec3d get_rotation() const;
double get_rotation(Axis axis) const { return get_rotation()[axis]; }
Transform3d get_rotation_matrix() const;
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);
const Vec3d& get_scaling_factor() const { return m_scaling_factor; }
double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); }
Vec3d get_scaling_factor() const;
double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; }
Transform3d get_scaling_factor_matrix() const;
bool is_scaling_uniform() const {
const Vec3d scale = get_scaling_factor();
return std::abs(scale.x() - scale.y()) < 1e-8 && std::abs(scale.x() - scale.z()) < 1e-8;
}
void set_scaling_factor(const Vec3d& scaling_factor);
void set_scaling_factor(Axis axis, double scaling_factor);
bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; }
const Vec3d& get_mirror() const { return m_mirror; }
double get_mirror(Axis axis) const { return m_mirror(axis); }
bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; }
Vec3d get_mirror() const;
double get_mirror(Axis axis) const { return get_mirror()[axis]; }
Transform3d get_mirror_matrix() const;
bool is_left_handed() const {
return m_matrix.linear().determinant() < 0;
}
void set_mirror(const Vec3d& mirror);
void set_mirror(Axis axis, double mirror);
void set_from_transform(const Transform3d& transform);
bool has_skew() const;
void reset();
void reset_offset() { set_offset(Vec3d::Zero()); }
void reset_rotation();
void reset_scaling_factor();
void reset_mirror() { set_mirror(Vec3d::Ones()); }
void reset_skew();
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const;
const Transform3d& get_matrix() const { return m_matrix; }
Transform3d get_matrix_no_offset() const;
Transform3d get_matrix_no_scaling_factor() const;
void set_matrix(const Transform3d& transform) { m_matrix = transform; }
Transformation operator * (const Transformation& other) const;
@ -474,19 +482,43 @@ public:
// BBS: backup use this compare
friend bool operator==(Transformation const& l, Transformation const& r) {
return l.m_offset == r.m_offset && l.m_rotation == r.m_rotation && l.m_scaling_factor == r.m_scaling_factor && l.m_mirror == r.m_mirror;
return l.m_matrix.isApprox(r.m_matrix);
}
friend bool operator!=(Transformation const &l, Transformation const &r)
{
return !(l == r);
}
private:
friend class cereal::access;
template<class Archive> void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); }
explicit Transformation(int) : m_dirty(true) {}
template <class Archive> static void load_and_construct(Archive &ar, cereal::construct<Transformation> &construct)
{
// Calling a private constructor with special "int" parameter to indicate that no construction is necessary.
construct(1);
ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror);
}
template<class Archive> void serialize(Archive& ar) { ar(m_matrix); }
explicit Transformation(int) {}
template <class Archive> static void load_and_construct(Archive& ar, cereal::construct<Transformation>& construct)
{
// Calling a private constructor with special "int" parameter to indicate that no construction is necessary.
construct(1);
ar(construct.ptr()->m_matrix);
}
};
struct TransformationSVD
{
Matrix3d u{ Matrix3d::Identity() };
Matrix3d s{ Matrix3d::Identity() };
Matrix3d v{ Matrix3d::Identity() };
bool mirror{ false };
bool scale{ false };
bool anisotropic_scale{ false };
bool rotation{ false };
bool rotation_90_degrees{ false };
bool skew{ false };
explicit TransformationSVD(const Transformation& trafo) : TransformationSVD(trafo.get_matrix()) {}
explicit TransformationSVD(const Transform3d& trafo);
Eigen::DiagonalMatrix<double, 3, 3> mirror_matrix() const { return Eigen::DiagonalMatrix<double, 3, 3>(this->mirror ? -1. : 1., 1., 1.); }
};
// For parsing a transformation matrix from 3MF / AMF.

View file

@ -0,0 +1,49 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "IntersectionPoints.hpp"
#include <libslic3r/AABBTreeLines.hpp>
//NOTE: using CGAL SweepLines is slower !!! (example in git history)
namespace {
using namespace Slic3r;
IntersectionsLines compute_intersections(const Lines &lines)
{
if (lines.size() < 3)
return {};
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
IntersectionsLines result;
for (uint32_t li = 0; li < lines.size()-1; ++li) {
const Line &l = lines[li];
auto intersections = AABBTreeLines::get_intersections_with_line<false, Point, Line>(lines, tree, l);
for (const auto &[p, node_index] : intersections) {
if (node_index - 1 <= li)
continue;
if (const Line &l_ = lines[node_index];
l_.a == l.a ||
l_.a == l.b ||
l_.b == l.a ||
l_.b == l.b )
// it is duplicit point not intersection
continue;
// NOTE: fix AABBTree to compute intersection with double preccission!!
Vec2d intersection_point = p.cast<double>();
result.push_back(IntersectionLines{li, static_cast<uint32_t>(node_index), intersection_point});
}
}
return result;
}
} // namespace
namespace Slic3r {
IntersectionsLines get_intersections(const Lines &lines) { return compute_intersections(lines); }
IntersectionsLines get_intersections(const Polygon &polygon) { return compute_intersections(to_lines(polygon)); }
IntersectionsLines get_intersections(const Polygons &polygons) { return compute_intersections(to_lines(polygons)); }
IntersectionsLines get_intersections(const ExPolygon &expolygon) { return compute_intersections(to_lines(expolygon)); }
IntersectionsLines get_intersections(const ExPolygons &expolygons) { return compute_intersections(to_lines(expolygons)); }
}

View file

@ -0,0 +1,27 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_IntersectionPoints_hpp_
#define slic3r_IntersectionPoints_hpp_
#include "ExPolygon.hpp"
namespace Slic3r {
struct IntersectionLines {
uint32_t line_index1;
uint32_t line_index2;
Vec2d intersection;
};
using IntersectionsLines = std::vector<IntersectionLines>;
// collect all intersecting points
IntersectionsLines get_intersections(const Lines &lines);
IntersectionsLines get_intersections(const Polygon &polygon);
IntersectionsLines get_intersections(const Polygons &polygons);
IntersectionsLines get_intersections(const ExPolygon &expolygon);
IntersectionsLines get_intersections(const ExPolygons &expolygons);
} // namespace Slic3r
#endif // slic3r_IntersectionPoints_hpp_

View file

@ -1368,7 +1368,7 @@ indexed_triangle_set ModelObject::raw_indexed_triangle_set() const
size_t j = out.indices.size();
append(out.vertices, v->mesh().its.vertices);
append(out.indices, v->mesh().its.indices);
auto m = v->get_matrix();
const Transform3d& m = v->get_matrix();
for (; i < out.vertices.size(); ++ i)
out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval();
if (v->is_left_handed()) {
@ -1410,7 +1410,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
if (this->instances.empty())
throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances");
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
const Transform3d inst_matrix = this->instances.front()->get_transformation().get_matrix_no_offset();
for (const ModelVolume *v : this->volumes)
if (v->is_model_part())
m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
@ -1423,14 +1423,18 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
return instance_bounding_box(*this->instances[instance_idx], dont_translate);
}
BoundingBoxf3 ModelObject::instance_bounding_box(const ModelInstance &instance, bool dont_translate) const {
BoundingBoxf3 bbox;
const auto& inst_mat = instance.get_transformation().get_matrix(dont_translate);
for (auto vol : this->volumes) {
if (vol->is_model_part())
bbox.merge(vol->mesh().transformed_bounding_box(inst_mat * vol->get_matrix()));
BoundingBoxf3 ModelObject::instance_bounding_box(const ModelInstance &instance, bool dont_translate) const
{
BoundingBoxf3 bb;
const Transform3d inst_matrix = dont_translate ?
instance.get_transformation().get_matrix_no_offset() :
instance.get_transformation().get_matrix();
for (ModelVolume *v : this->volumes) {
if (v->is_model_part())
bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
}
return bbox;
return bb;
}
@ -1438,7 +1442,9 @@ BoundingBoxf3 ModelObject::instance_bounding_box(const ModelInstance &instance,
BoundingBoxf3 ModelObject::instance_convex_hull_bounding_box(size_t instance_idx, bool dont_translate) const
{
BoundingBoxf3 bb;
const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate);
const Transform3d& inst_matrix = dont_translate ?
this->instances[instance_idx]->get_transformation().get_matrix_no_offset() :
this->instances[instance_idx]->get_transformation().get_matrix();
for (ModelVolume *v : this->volumes)
{
if (v->is_model_part())
@ -1739,6 +1745,25 @@ void ModelObject::clone_for_cut(ModelObject **obj)
(*obj)->input_file.clear();
}
bool ModelVolume::is_the_only_one_part() const
{
if (m_type != ModelVolumeType::MODEL_PART)
return false;
if (object == nullptr)
return false;
for (const ModelVolume *v : object->volumes) {
if (v == nullptr)
continue;
// is this volume?
if (v->id() == this->id())
continue;
// exist another model part in object?
if (v->type() == ModelVolumeType::MODEL_PART)
return false;
}
return true;
}
void ModelVolume::reset_extra_facets()
{
this->supported_facets.reset();
@ -1759,67 +1784,6 @@ static void invalidate_translations(ModelObject* object, const ModelInstance* sr
}
}
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero())
{
using namespace Geometry;
// Reset instance transformation except offset and Z-rotation
for (size_t i = 0; i < object->instances.size(); ++i) {
auto& obj_instance = object->instances[i];
Geometry::Transformation instance_transformation_copy = obj_instance->get_transformation();
instance_transformation_copy.set_offset(Vec3d(0, 0, 0));
if (object->volumes.size() == 1) {
instance_transformation_copy.set_offset(-object->volumes[0]->get_offset());
}
if (i == src_instance_idx && object->volumes.size() == 1)
invalidate_translations(object, obj_instance);
const Vec3d offset = obj_instance->get_offset();
const double rot_z = obj_instance->get_rotation().z();
obj_instance->set_transformation(Transformation());
const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() :
rotation_transform(obj_instance->get_rotation()) * local_displace;
obj_instance->set_offset(offset + displace);
Vec3d rotation = Vec3d::Zero();
if (!flip && !place_on_cut) {
if ( i != src_instance_idx)
rotation[Z] = rot_z;
}
else {
Transform3d rotation_matrix = Transform3d::Identity();
if (flip)
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
if (place_on_cut)
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_matrix(true, false, true, true).inverse();
if (i != src_instance_idx)
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
rotation = Transformation(rotation_matrix).get_rotation();
}
obj_instance->set_rotation(rotation);
// update the assemble matrix
const Transform3d &assemble_matrix = obj_instance->get_assemble_transformation().get_matrix();
const Transform3d &instance_inverse_matrix = instance_transformation_copy.get_matrix().inverse();
Transform3d new_instance_inverse_matrix = instance_inverse_matrix * obj_instance->get_transformation().get_matrix(true).inverse();
if (place_on_cut) { // reset the rotation of cut plane
new_instance_inverse_matrix = new_instance_inverse_matrix * Transformation(cut_matrix).get_matrix(true, false, true, true).inverse();
}
Transform3d new_assemble_transform = assemble_matrix * new_instance_inverse_matrix;
obj_instance->set_assemble_from_transform(new_assemble_transform);
}
}
void ModelObject::split(ModelObjectPtrs* new_objects)
{
std::vector<TriangleMesh> all_meshes;
@ -1833,6 +1797,10 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
if (volume->type() != ModelVolumeType::MODEL_PART)
continue;
// splited volume should not be text object
if (volume->text_configuration.has_value())
volume->text_configuration.reset();
if (!is_multi_volume_object) {
//BBS: not multi volume object, then split mesh.
std::vector<TriangleMesh> volume_meshes = volume->mesh().split();
@ -1912,7 +1880,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
for (ModelInstance* model_instance : new_object->instances)
{
Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
const Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset();
model_instance->set_offset(model_instance->get_offset() + shift);
//BBS: add assemble_view related logic
@ -1920,7 +1888,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
instance_transformation_copy.set_offset(-new_vol->get_offset());
const Transform3d &assemble_matrix = model_instance->get_assemble_transformation().get_matrix();
const Transform3d &instance_inverse_matrix = instance_transformation_copy.get_matrix().inverse();
Transform3d new_instance_inverse_matrix = instance_inverse_matrix * model_instance->get_transformation().get_matrix(true).inverse();
Transform3d new_instance_inverse_matrix = instance_inverse_matrix * model_instance->get_transformation().get_matrix_no_offset().inverse();
Transform3d new_assemble_transform = assemble_matrix * new_instance_inverse_matrix;
model_instance->set_assemble_from_transform(new_assemble_transform);
model_instance->set_offset_to_assembly(new_vol->get_offset());
@ -2037,8 +2005,14 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
// Adjust the meshes.
// Transformation to be applied to the meshes.
Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0);
Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
Geometry::Transformation reference_trafo_mod = reference_trafo;
reference_trafo_mod.reset_offset();
if (uniform_scaling)
reference_trafo_mod.reset_scaling_factor();
if (!has_mirrorring)
reference_trafo_mod.reset_mirror();
Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0);
Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
for (ModelVolume *model_volume : this->volumes) {
const Geometry::Transformation volume_trafo = model_volume->get_transformation();
bool volume_left_handed = volume_trafo.is_left_handed();
@ -2047,9 +2021,15 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON;
double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.;
// Transform the mesh.
Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0);
Geometry::Transformation volume_trafo_mod = volume_trafo;
volume_trafo_mod.reset_offset();
if (volume_uniform_scaling)
volume_trafo_mod.reset_scaling_factor();
if (!volume_has_mirrorring)
volume_trafo_mod.reset_mirror();
Eigen::Matrix3d volume_trafo_3x3 = volume_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0);
// Following method creates a new shared_ptr<TriangleMesh>
model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
// Reset the rotation, scaling and mirroring.
model_volume->set_rotation(Vec3d(0., 0., 0.));
model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor));
@ -2094,7 +2074,7 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
double min_z = DBL_MAX;
const ModelInstance* inst = instances[instance_idx];
const Transform3d& mi = inst->get_matrix(true);
const Transform3d mi = inst->get_matrix_no_offset();
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
@ -2130,7 +2110,7 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const
double max_z = -DBL_MAX;
const ModelInstance* inst = instances[instance_idx];
const Transform3d& mi = inst->get_matrix(true);
const Transform3d mi = inst->get_matrix_no_offset();
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
@ -2553,6 +2533,10 @@ size_t ModelVolume::split(unsigned int max_extruders)
if (meshes.size() <= 1)
return 1;
// splited volume should not be text object
if (text_configuration.has_value())
text_configuration.reset();
size_t idx = 0;
size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
const std::string name = this->name;
@ -2723,44 +2707,17 @@ void ModelVolume::convert_from_meters()
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
{
mesh->transform(get_matrix(dont_translate));
}
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const
{
// Rotate around mesh origin.
TriangleMesh copy(mesh);
copy.transform(get_matrix(true, false, true, true));
BoundingBoxf3 bbox = copy.bounding_box();
if (!empty(bbox)) {
// Scale the bounding box along the three axes.
for (unsigned int i = 0; i < 3; ++i)
{
if (std::abs(get_scaling_factor((Axis)i)-1.0) > EPSILON)
{
bbox.min(i) *= get_scaling_factor((Axis)i);
bbox.max(i) *= get_scaling_factor((Axis)i);
}
}
// Translate the bounding box.
if (! dont_translate) {
bbox.min += get_offset();
bbox.max += get_offset();
}
}
return bbox;
mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix());
}
BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
{
return bbox.transformed(get_matrix(dont_translate));
return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix());
}
Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const
{
return get_matrix(dont_translate) * v;
return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v;
}
void ModelInstance::transform_polygon(Polygon* polygon) const
@ -2952,7 +2909,7 @@ Polygon ModelInstance::convex_hull_2d()
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": name %1%, is_valid %2%")% this->object->name.c_str()% convex_hull.is_valid();
//if (!convex_hull.is_valid())
{ // this logic is not working right now, as moving instance doesn't update convex_hull
const Transform3d& trafo_instance = get_matrix(false);
const Transform3d& trafo_instance = get_matrix();
convex_hull = get_object()->convex_hull_2d(trafo_instance);
}
//int size = convex_hull.size();

View file

@ -26,6 +26,8 @@
#include "CustomGCode.hpp"
#include "calib.hpp"
#include "enum_bitmask.hpp"
#include "TextConfiguration.hpp"
#include "EmbossShape.hpp"
//BBS: add bbs 3mf
#include "Format/bbs_3mf.hpp"
@ -41,6 +43,7 @@
#include <vector>
#include <algorithm>
#include <functional>
#include <optional>
namespace cereal {
class BinaryInputArchive;
@ -779,38 +782,6 @@ private:
friend class ModelVolume;
};
struct RaycastResult
{
Vec2d mouse_position = Vec2d::Zero();
int mesh_id = -1;
Vec3f hit = Vec3f::Zero();
Vec3f normal = Vec3f::Zero();
template<typename Archive> void serialize(Archive &ar) { ar(mouse_position, mesh_id, hit, normal); }
};
struct TextInfo
{
std::string m_font_name;
float m_font_size = 16.f;
int m_curr_font_idx = 0;
bool m_bold = true;
bool m_italic = false;
float m_thickness = 2.f;
float m_embeded_depth = 0.f;
float m_rotate_angle = 0;
float m_text_gap = 0.f;
bool m_is_surface_text = false;
bool m_keep_horizontal = false;
std::string m_text;
RaycastResult m_rr;
template<typename Archive> void serialize(Archive &ar) {
ar(m_font_name, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_is_surface_text, m_keep_horizontal, m_text, m_rr);
}
};
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
// ModelVolume instances are owned by a ModelObject.
class ModelVolume final : public ObjectBase
@ -829,7 +800,7 @@ public:
bool is_converted_from_meters{ false };
bool is_from_builtin_objects{ false };
template<class Archive> void serialize(Archive& ar) {
template<class Archive> void serialize(Archive& ar) {
//FIXME Vojtech: Serialize / deserialize only if the Source is set.
// likely testing input_file or object_idx would be sufficient.
ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects);
@ -883,7 +854,8 @@ public:
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
// Configuration parameters specific to an object model geometry or a modifier volume,
const std::shared_ptr<const TriangleMesh>& get_mesh_shared_ptr() const { return m_mesh; }
// Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings.
ModelConfigObject config;
@ -903,6 +875,14 @@ public:
// List of exterior faces
FacetsAnnotation exterior_facets;
// Is set only when volume is Embossed Text type
// Contain information how to re-create volume
std::optional<TextConfiguration> text_configuration;
// Is set only when volume is Embossed Shape
// Contain 2d information about embossed shape to be editabled
std::optional<EmbossShape> emboss_shape;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
@ -913,6 +893,9 @@ public:
bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_text() const { return text_configuration.has_value(); }
bool is_svg() const { return emboss_shape.has_value() && !text_configuration.has_value(); }
bool is_the_only_one_part() const; // behave like an object
t_model_material_id material_id() const { return m_material_id; }
void set_material_id(t_model_material_id material_id);
void reset_extra_facets();
@ -968,15 +951,16 @@ public:
const Geometry::Transformation& get_transformation() const { return m_transformation; }
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); }
void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); }
Vec3d get_offset() const { return m_transformation.get_offset(); }
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
Vec3d get_rotation() const { return m_transformation.get_rotation(); }
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
@ -988,7 +972,7 @@ public:
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
Vec3d get_mirror() const { return m_transformation.get_mirror(); }
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
@ -997,10 +981,8 @@ public:
void convert_from_imperial_units();
void convert_from_meters();
void set_text_info(const TextInfo& text_info) { m_text_info = text_info; }
const TextInfo& get_text_info() const { return m_text_info; }
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
const Transform3d& get_matrix() const { return m_transformation.get_matrix(); }
Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); }
void set_new_unique_id() {
ObjectBase::set_new_unique_id();
@ -1044,8 +1026,6 @@ private:
mutable Polygon m_cached_2d_polygon; //BBS, used for convex_hell_2d acceleration
Geometry::Transformation m_transformation;
TextInfo m_text_info;
//BBS: add convex_hell_2d related logic
void calculate_convex_hull_2d(const Geometry::Transformation &transformation) const;
@ -1100,10 +1080,10 @@ private:
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
m_text_info(other.m_text_info)
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
@ -1119,11 +1099,12 @@ private:
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) :
name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation),
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
@ -1135,10 +1116,10 @@ private:
assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id());
this->config.set_new_unique_id();
if (mesh.facets_count() > 1)
if (m_mesh->facets_count() > 1)
calculate_convex_hull();
assert(this->config.id().valid());
assert(this->config.id() != other.config.id());
assert(this->config.id().valid());
assert(this->config.id() != other.config.id());
assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
@ -1165,9 +1146,8 @@ private:
// BBS: add backup, check modify
bool mesh_changed = false;
auto tr = m_transformation;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info);
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
mesh_changed |= !(tr == m_transformation);
if (mesh_changed) m_transformation.get_matrix(true, true, true, true); // force dirty
auto t = supported_facets.timestamp();
cereal::load_by_value(ar, supported_facets);
mesh_changed |= t != supported_facets.timestamp();
@ -1178,6 +1158,8 @@ private:
cereal::load_by_value(ar, mmu_segmentation_facets);
mesh_changed |= t != mmu_segmentation_facets.timestamp();
cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration);
cereal::load(ar, emboss_shape);
assert(m_mesh);
if (has_convex_hull) {
cereal::load_optional(ar, m_convex_hull);
@ -1191,11 +1173,13 @@ private:
}
template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info);
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);
cereal::save(ar, emboss_shape);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
}
@ -1259,7 +1243,7 @@ public:
}
void set_assemble_from_transform(Transform3d& transform) {
m_assemble_initialized = true;
m_assemble_transformation.set_from_transform(transform);
m_assemble_transformation.set_matrix(transform);
}
void set_assemble_offset(const Vec3d& offset) { m_assemble_transformation.set_offset(offset); }
void rotate_assemble(double angle, const Vec3d& axis) {
@ -1270,13 +1254,13 @@ public:
void set_offset_to_assembly(const Vec3d& offset) { m_offset_to_assembly = offset; }
Vec3d get_offset_to_assembly() const { return m_offset_to_assembly; }
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
Vec3d get_offset() const { return m_transformation.get_offset(); }
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
Vec3d get_rotation() const { return m_transformation.get_rotation(); }
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
@ -1284,38 +1268,36 @@ public:
// BBS
void rotate(Matrix3d rotation_matrix) {
// note: must remove scaling from transformation, otherwise auto-orientation with scaled objects will have problem
auto R = get_matrix(true,false,true).matrix().block<3, 3>(0, 0);
auto R = m_transformation.get_rotation_matrix().matrix().block<3, 3>(0, 0);
auto R_new = rotation_matrix * R;
auto euler_angles = Geometry::extract_euler_angles(R_new);
set_rotation(euler_angles);
}
const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
Vec3d get_mirror() const { return m_transformation.get_mirror(); }
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
// To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
// Calculate a bounding box of a transformed mesh. To be called on an external mesh.
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
// Transform an external bounding box.
// Transform an external bounding box, thus the resulting bounding box is no more snug.
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
// Transform an external vector.
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
// To be called on an external polygon. It does not translate the polygon, only rotates and scales.
void transform_polygon(Polygon* polygon) const;
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
const Transform3d& get_matrix() const { return m_transformation.get_matrix(); }
Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); }
bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); }
bool is_assemble_initialized() { return m_assemble_initialized; }

547
src/libslic3r/NSVGUtils.cpp Normal file
View file

@ -0,0 +1,547 @@
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "NSVGUtils.hpp"
#include <array>
#include <charconv> // to_chars
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include "ClipperUtils.hpp"
#include "Emboss.hpp" // heal for shape
namespace {
using namespace Slic3r; // Polygon
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
bool is_line(const float *p, float precision = 1e-4f);
// convert curve in path to lines
struct LinesPath{
Polygons polygons;
Polylines polylines; };
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param);
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
} // namespace
namespace Slic3r {
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param)
{
ExPolygonsWithIds result;
size_t shape_id = 0;
for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) {
const NSVGshape &shape = *shape_ptr;
if (!(shape.flags & NSVG_FLAGS_VISIBLE))
continue;
bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE;
bool is_stroke_used =
shape.stroke.type != NSVG_PAINT_NONE &&
shape.strokeWidth > 1e-5f;
if (!is_fill_used && !is_stroke_used)
continue;
const LinesPath lines_path = linearize_path(shape.paths, param);
if (is_fill_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id);
HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param);
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
}
if (is_stroke_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1);
HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param);
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
}
}
// SVG is used as centered
// Do not disturb user by settings of pivot position
center(result);
return result;
}
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams &param)
{
Polygons result;
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue;
if (shape->fill.type == NSVG_PAINT_NONE)
continue;
const LinesPath lines_path = linearize_path(shape->paths, param);
polygons_append(result, lines_path.polygons);
// close polyline to create polygon
polygons_append(result, to_polygons(lines_path.polylines));
}
return result;
}
void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max)
{
for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next)
for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
if (min.x() > path->bounds[0])
min.x() = path->bounds[0];
if (min.y() > path->bounds[1])
min.y() = path->bounds[1];
if (max.x() < path->bounds[2])
max.x() = path->bounds[2];
if (max.y() < path->bounds[3])
max.y() = path->bounds[3];
}
}
NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi)
{
NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi);
return {image, &nsvgDelete};
}
std::unique_ptr<std::string> read_from_disk(const std::string &path)
{
boost::nowide::ifstream fs{path};
if (!fs.is_open())
return nullptr;
std::stringstream ss;
ss << fs.rdbuf();
return std::make_unique<std::string>(ss.str());
}
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float dpi){
// NOTE: nsvg parser consume data from input(char *)
size_t size = file_data.size();
// file data could be big, so it is allocated on heap
std::unique_ptr<char[]> data_copy(new char[size+1]);
memcpy(data_copy.get(), file_data.c_str(), size);
data_copy[size] = '\0'; // data for nsvg must be null terminated
NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi);
return {image, &nsvgDelete};
}
NSVGimage *init_image(EmbossShape::SvgFile &svg_file){
// is already initialized?
if (svg_file.image.get() != nullptr)
return svg_file.image.get();
if (svg_file.file_data == nullptr) {
// chech if path is known
if (svg_file.path.empty())
return nullptr;
svg_file.file_data = read_from_disk(svg_file.path);
if (svg_file.file_data == nullptr)
return nullptr;
}
// init svg image
svg_file.image = nsvgParse(*svg_file.file_data);
if (svg_file.image.get() == NULL)
return nullptr;
return svg_file.image.get();
}
size_t get_shapes_count(const NSVGimage &image)
{
size_t count = 0;
for (NSVGshape * s = image.shapes; s != NULL; s = s->next)
++count;
return count;
}
//void save(const NSVGimage &image, std::ostream &data)
//{
// data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
//
// // tl .. top left
// Vec2f tl(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
// // br .. bottom right
// Vec2f br(std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
// bounds(image, tl, br);
//
// tl.x() = std::floor(tl.x());
// tl.y() = std::floor(tl.y());
//
// br.x() = std::ceil(br.x());
// br.y() = std::ceil(br.y());
// Vec2f s = br - tl;
// Point size = s.cast<Point::coord_type>();
//
// data << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
// << "width=\"" << size.x() << "mm\" "
// << "height=\"" << size.y() << "mm\" "
// << "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n";
// data << "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n";
//
// std::array<char, 128> buffer;
// auto write_point = [&tl, &buffer](std::string &d, const float *p) {
// float x = p[0] - tl.x();
// float y = p[1] - tl.y();
// auto to_string = [&buffer](float f) -> std::string {
// auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
// if (ec != std::errc{})
// return "0";
// return std::string(buffer.data(), ptr);
// };
// d += to_string(x) + "," + to_string(y) + " ";
// };
//
// for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
// enum struct Type { move, line, curve, close }; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// Type type = Type::move;
// std::string d = "M "; // move on start point
// for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
// if (path->npts <= 1)
// continue;
//
// if (type == Type::close) {
// type = Type::move;
// // NOTE: After close must be a space
// d += " M "; // move on start point
// }
// write_point(d, path->pts);
// size_t path_size = static_cast<size_t>(path->npts - 1);
//
// if (path->closed) {
// // Do not use last point in path it is duplicit
// if (path->npts <= 4)
// continue;
// path_size = static_cast<size_t>(path->npts - 4);
// }
//
// for (size_t i = 0; i < path_size; i += 3) {
// const float *p = &path->pts[i * 2];
// if (!::is_line(p)) {
// if (type != Type::curve) {
// type = Type::curve;
// d += "C "; // start sequence of triplets defining curves
// }
// write_point(d, &p[2]);
// write_point(d, &p[4]);
// } else {
//
// if (type != Type::line) {
// type = Type::line;
// d += "L "; // start sequence of line points
// }
// }
// write_point(d, &p[6]);
// }
// if (path->closed) {
// type = Type::close;
// d += "Z"; // start sequence of line points
// }
// }
// if (type != Type::close) {
// //type = Type::close;
// d += "Z"; // closed path
// }
// data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
// }
// data << "</svg>\n";
//}
//
//bool save(const NSVGimage &image, const std::string &svg_file_path)
//{
// std::ofstream file{svg_file_path};
// if (!file.is_open())
// return false;
// save(image, file);
// return true;
//}
} // namespace Slic3r
namespace {
using namespace Slic3r; // Polygon + Vec2f
Point::coord_type to_coor(float val, double 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){
//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>
/// Convert cubic curve to lines
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335
/// </summary>
/// <param name="polygon">Result points</param>
/// <param name="tessTol">Tesselation tolerance</param>
/// <param name="p1">Curve point</param>
/// <param name="p2">Curve point</param>
/// <param name="p3">Curve point</param>
/// <param name="p4">Curve point</param>
/// <param name="level">Actual depth of recursion</param>
void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level)
{
if (!need_flattening(tessTol, p1, p2, p3, p4)) {
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
points.emplace_back(x, y);
return;
}
--level;
if (level == 0)
return;
Vec2f p12 = (p1 + p2) * 0.5f;
Vec2f p23 = (p2 + p3) * 0.5f;
Vec2f p34 = (p3 + p4) * 0.5f;
Vec2f p123 = (p12 + p23) * 0.5f;
Vec2f p234 = (p23 + p34) * 0.5f;
Vec2f p1234 = (p123 + p234) * 0.5f;
flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level);
flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level);
}
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param)
{
LinesPath result;
Polygons &polygons = result.polygons;
Polylines &polylines = result.polylines;
// multiple use of allocated memmory for points between paths
Points points;
for (NSVGpath *path = first_path; path != NULL; path = path->next) {
// Flatten path
Point::coord_type x = to_coor(path->pts[0], param.scale);
Point::coord_type y = to_coor(path->pts[1], param.scale);
points.emplace_back(x, y);
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];
if (is_line(p)) {
// point p4
Point::coord_type xx = to_coor(p[6], param.scale);
Point::coord_type yy = to_coor(p[7], param.scale);
points.emplace_back(xx, yy);
continue;
}
Vec2f p1(p[0], p[1]);
Vec2f p2(p[2], p[3]);
Vec2f p3(p[4], p[5]);
Vec2f p4(p[6], p[7]);
flatten_cubic_bez(points, param.tesselation_tolerance,
p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale,
param.max_level);
}
assert(!points.empty());
if (points.empty())
continue;
if (param.is_y_negative)
for (Point &p : points)
p.y() = -p.y();
if (path->closed) {
polygons.emplace_back(points);
} else {
polylines.emplace_back(points);
}
// prepare for new path - recycle alocated memory
points.clear();
}
remove_same_neighbor(polygons);
remove_same_neighbor(polylines);
return result;
}
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{
Polygons fill = lines_path.polygons; // copy
// close polyline to create polygon
polygons_append(fill, to_polygons(lines_path.polylines));
if (fill.empty())
return {};
// if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO)
bool is_non_zero = true;
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
is_non_zero = false;
return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration);
}
struct DashesParam{
// first dash length
float dash_length = 1.f; // scaled
// is current dash .. true
// is current space .. false
bool is_line = true;
// current index to array
unsigned char dash_index = 0;
static constexpr size_t max_dash_array_size = 8; // limitation of nanosvg strokeDashArray
std::array<float, max_dash_array_size> dash_array; // scaled
unsigned char dash_count = 0; // count of values in array
explicit DashesParam(const NSVGshape &shape, double scale) :
dash_count(shape.strokeDashCount)
{
assert(dash_count > 0);
assert(dash_count <= max_dash_array_size); // limitation of nanosvg strokeDashArray
for (size_t i = 0; i < dash_count; ++i)
dash_array[i] = static_cast<float>(shape.strokeDashArray[i] * scale);
// Figure out dash offset.
float all_dash_length = 0;
for (unsigned char j = 0; j < dash_count; ++j)
all_dash_length += dash_array[j];
if (dash_count%2 == 1) // (shape.strokeDashCount & 1)
all_dash_length *= 2.0f;
// Find location inside pattern
float dash_offset = fmodf(static_cast<float>(shape.strokeDashOffset * scale), all_dash_length);
if (dash_offset < 0.0f)
dash_offset += all_dash_length;
while (dash_offset > dash_array[dash_index]) {
dash_offset -= dash_array[dash_index];
dash_index = (dash_index + 1) % shape.strokeDashCount;
is_line = !is_line;
}
dash_length = dash_array[dash_index] - dash_offset;
}
};
Polylines to_dashes(const Polyline &polyline, const DashesParam& param)
{
Polylines dashes;
Polyline dash; // cache for one dash in dashed line
Point prev_point;
bool is_line = param.is_line;
unsigned char dash_index = param.dash_index;
float dash_length = param.dash_length; // current rest of dash distance
for (const Point &point : polyline.points) {
if (&point == &polyline.points.front()) {
// is first point
prev_point = point; // copy
continue;
}
Point diff = point - prev_point;
float line_segment_length = diff.cast<float>().norm();
while (dash_length < line_segment_length) {
// Calculate intermediate point
float d = dash_length / line_segment_length;
Point move_point = diff * d;
Point intermediate = prev_point + move_point;
// add Dash in stroke
if (is_line) {
if (dash.empty()) {
dashes.emplace_back(Points{prev_point, intermediate});
} else {
dash.append(prev_point);
dash.append(intermediate);
dashes.push_back(dash);
dash.clear();
}
}
diff -= move_point;
line_segment_length -= dash_length;
prev_point = intermediate;
// Advance dash pattern
is_line = !is_line;
dash_index = (dash_index + 1) % param.dash_count;
dash_length = param.dash_array[dash_index];
}
if (is_line)
dash.append(prev_point);
dash_length -= line_segment_length;
prev_point = point; // copy
}
// add last dash
if (is_line){
assert(!dash.empty());
dash.append(prev_point); // prev_point == polyline.points.back()
dashes.push_back(dash);
}
return dashes;
}
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{
// convert stroke to polygon
ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare;
switch (static_cast<NSVGlineJoin>(shape.strokeLineJoin)) {
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break;
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break;
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break;
}
double mitter = shape.miterLimit * param.scale;
if (join_type == ClipperLib::JoinType::jtRound) {
// mitter is used as ArcTolerance
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
mitter = std::pow(param.tesselation_tolerance, 1/3.);
}
float stroke_width = static_cast<float>(shape.strokeWidth * param.scale);
ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt;
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break;
case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break;
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break;
}
Polygons result;
if (shape.strokeDashCount > 0) {
DashesParam params(shape, param.scale);
Polylines dashes;
for (const Polyline &polyline : lines_path.polylines)
polylines_append(dashes, to_dashes(polyline, params));
for (const Polygon &polygon : lines_path.polygons)
polylines_append(dashes, to_dashes(to_polyline(polygon), params));
result = offset(dashes, stroke_width / 2, join_type, mitter, end_type);
} else {
result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter);
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
}
bool is_non_zero = true;
return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration);
}
} // namespace

View file

@ -0,0 +1,86 @@
///|/ Copyright (c) Prusa Research 2021 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_NSVGUtils_hpp_
#define slic3r_NSVGUtils_hpp_
#include <memory>
#include <string>
#include <sstream>
#include "Polygon.hpp"
#include "ExPolygon.hpp"
#include "EmbossShape.hpp" // ExPolygonsWithIds
#include "nanosvg/nanosvg.h" // load SVG file
// Helper function to work with nano svg
namespace Slic3r {
/// <summary>
/// Paramreters for conversion curve from SVG to lines in Polygon
/// </summary>
struct NSVGLineParams
{
// Smaller will divide curve to more lines
// NOTE: Value is in image scale
double tesselation_tolerance = 10.f;
// Maximal depth of recursion for conversion curve to lines
int max_level = 10;
// Multiplicator of point coors
// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer --> Slic3r::Point
double scale = 1. / SCALING_FACTOR;
// Flag wether y is negative, when true than y coor is multiplied by -1
bool is_y_negative = true;
// Is used only with rounded Stroke
double arc_tolerance = 1.;
// Maximal count of heal iteration
unsigned max_heal_iteration = 10;
explicit NSVGLineParams(double tesselation_tolerance):
tesselation_tolerance(tesselation_tolerance),
arc_tolerance(std::pow(tesselation_tolerance, 1/3.))
{}
};
/// <summary>
/// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids
/// </summary>
/// <param name="image">Parsed svg file by NanoSvg</param>
/// <param name="tesselation_tolerance">Smaller will divide curve to more lines
/// NOTE: Value is in image scale</param>
/// <param name="max_level">Maximal depth for conversion curve to lines</param>
/// <param name="scale">Multiplicator of point coors
/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer</param>
/// <returns>Shapes from svg image - fill + stroke</returns>
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param);
// help functions - prepare to be tested
/// <param name="is_y_negative">Flag is y negative, when true than y coor is multiplied by -1</param>
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams &param);
void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max);
// read text data from file
std::unique_ptr<std::string> read_from_disk(const std::string &path);
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f);
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units = "mm", float dpi = 96.0f);
NSVGimage *init_image(EmbossShape::SvgFile &svg_file);
/// <summary>
/// Iterate over shapes and calculate count
/// </summary>
/// <param name="image">Contain pointer to first shape</param>
/// <returns>Count of shapes</returns>
size_t get_shapes_count(const NSVGimage &image);
//void save(const NSVGimage &image, std::ostream &data);
//bool save(const NSVGimage &image, const std::string &svg_file_path);
} // namespace Slic3r
#endif // slic3r_NSVGUtils_hpp_

View file

@ -59,65 +59,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
void Point::rotate(double angle, const Point &center)
{
double cur_x = (double)(*this)(0);
double cur_y = (double)(*this)(1);
double s = ::sin(angle);
double c = ::cos(angle);
double dx = cur_x - (double)center(0);
double dy = cur_y - (double)center(1);
(*this)(0) = (coord_t)round( (double)center(0) + c * dx - s * dy );
(*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
}
int Point::nearest_point_index(const Points &points) const
{
PointConstPtrs p;
p.reserve(points.size());
for (Points::const_iterator it = points.begin(); it != points.end(); ++it)
p.push_back(&*it);
return this->nearest_point_index(p);
}
int Point::nearest_point_index(const PointConstPtrs &points) const
{
int idx = -1;
double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough
for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) {
/* If the X distance of the candidate is > than the total distance of the
best previous candidate, we know we don't want it */
double d = sqr<double>((*this)(0) - (*it)->x());
if (distance != -1 && d > distance) continue;
/* If the Y distance of the candidate is > than the total distance of the
best previous candidate, we know we don't want it */
d += sqr<double>((*this)(1) - (*it)->y());
if (distance != -1 && d > distance) continue;
idx = it - points.begin();
distance = d;
if (distance < EPSILON) break;
}
return idx;
}
int Point::nearest_point_index(const PointPtrs &points) const
{
PointConstPtrs p;
p.reserve(points.size());
for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it)
p.push_back(*it);
return this->nearest_point_index(p);
}
bool Point::nearest_point(const Points &points, Point* point) const
{
int idx = this->nearest_point_index(points);
if (idx == -1) return false;
*point = points.at(idx);
return true;
Vec2d cur = this->cast<double>();
double s = ::sin(angle);
double c = ::cos(angle);
auto d = cur - center.cast<double>();
this->x() = fast_round_up<coord_t>(center.x() + c * d.x() - s * d.y());
this->y() = fast_round_up<coord_t>(center.y() + s * d.x() + c * d.y());
}
/* Three points are a counter-clockwise turn if ccw > 0, clockwise if
@ -191,7 +138,7 @@ Point Point::projection_onto(const Line &line) const
return ((line.a - *this).cast<double>().squaredNorm() < (line.b - *this).cast<double>().squaredNorm()) ? line.a : line.b;
}
bool has_duplicate_points(std::vector<Point> &&pts)
bool has_duplicate_points(Points &&pts)
{
std::sort(pts.begin(), pts.end());
for (size_t i = 1; i < pts.size(); ++ i)
@ -200,6 +147,24 @@ bool has_duplicate_points(std::vector<Point> &&pts)
return false;
}
Points collect_duplicates(Points pts /* Copy */)
{
std::sort(pts.begin(), pts.end());
Points duplicits;
const Point *prev = &pts.front();
for (size_t i = 1; i < pts.size(); ++i) {
const Point *act = &pts[i];
if (*prev == *act) {
// duplicit point
if (!duplicits.empty() && duplicits.back() == *act)
continue; // only unique duplicits
duplicits.push_back(*act);
}
prev = act;
}
return duplicits;
}
template<bool IncludeBoundary>
BoundingBox get_extents(const Points &pts)
{
@ -231,6 +196,58 @@ BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
return bbox;
}
int Point::nearest_point_index(const Points &points) const
{
PointConstPtrs p;
p.reserve(points.size());
for (Points::const_iterator it = points.begin(); it != points.end(); ++it)
p.push_back(&*it);
return this->nearest_point_index(p);
}
int Point::nearest_point_index(const PointConstPtrs &points) const
{
int idx = -1;
double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough
for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) {
/* If the X distance of the candidate is > than the total distance of the
best previous candidate, we know we don't want it */
double d = sqr<double>((*this)(0) - (*it)->x());
if (distance != -1 && d > distance) continue;
/* If the Y distance of the candidate is > than the total distance of the
best previous candidate, we know we don't want it */
d += sqr<double>((*this)(1) - (*it)->y());
if (distance != -1 && d > distance) continue;
idx = it - points.begin();
distance = d;
if (distance < EPSILON) break;
}
return idx;
}
int Point::nearest_point_index(const PointPtrs &points) const
{
PointConstPtrs p;
p.reserve(points.size());
for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it)
p.push_back(*it);
return this->nearest_point_index(p);
}
bool Point::nearest_point(const Points &points, Point* point) const
{
int idx = this->nearest_point_index(points);
if (idx == -1) return false;
*point = points.at(idx);
return true;
}
std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf)
{
return stm << pointf(0) << "," << pointf(1);

View file

@ -53,9 +53,9 @@ using Vec3i64 = Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign>;
// Vector types with a double coordinate base type.
using Vec2f = Eigen::Matrix<float, 2, 1, Eigen::DontAlign>;
using Vec3f = Eigen::Matrix<float, 3, 1, Eigen::DontAlign>;
using Vec4f = Eigen::Matrix<float, 4, 1, Eigen::DontAlign>;
using Vec2d = Eigen::Matrix<double, 2, 1, Eigen::DontAlign>;
using Vec3d = Eigen::Matrix<double, 3, 1, Eigen::DontAlign>;
using Vec4f = Eigen::Matrix<float, 4, 1, Eigen::DontAlign>;
using Vec4d = Eigen::Matrix<double, 4, 1, Eigen::DontAlign>;
using Points = std::vector<Point>;
@ -116,9 +116,8 @@ inline Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> perp(cons
}
// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>.
template <typename Derived, typename Derived2>
inline double angle(const Eigen::MatrixBase<Derived>& v1, const Eigen::MatrixBase<Derived2>& v2)
{
template<typename Derived, typename Derived2>
inline double angle(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2) {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector");
auto v1d = v1.template cast<double>();
@ -153,6 +152,29 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + floa
std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
Pointf3s transform(const Pointf3s& points, const Transform3d& t);
/// <summary>
/// Check whether transformation matrix contains odd number of mirroring.
/// NOTE: In code is sometime function named is_left_handed
/// </summary>
/// <param name="transform">Transformation to check</param>
/// <returns>Is positive determinant</returns>
inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; }
/// <summary>
/// Getter on base of transformation matrix
/// </summary>
/// <param name="index">column index</param>
/// <param name="transform">source transformation</param>
/// <returns>Base of transformation matrix</returns>
inline const Vec3d get_base(unsigned index, const Transform3d &transform) { return transform.linear().col(index); }
inline const Vec3d get_x_base(const Transform3d &transform) { return get_base(0, transform); }
inline const Vec3d get_y_base(const Transform3d &transform) { return get_base(1, transform); }
inline const Vec3d get_z_base(const Transform3d &transform) { return get_base(2, transform); }
inline const Vec3d get_base(unsigned index, const Transform3d::LinearPart &transform) { return transform.col(index); }
inline const Vec3d get_x_base(const Transform3d::LinearPart &transform) { return get_base(0, transform); }
inline const Vec3d get_y_base(const Transform3d::LinearPart &transform) { return get_base(1, transform); }
inline const Vec3d get_z_base(const Transform3d::LinearPart &transform) { return get_base(2, transform); }
template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
class Point : public Vec2crd
@ -163,15 +185,16 @@ public:
Point() : Vec2crd(0, 0) {}
Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {}
Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {}
Point(const Point &rhs) { *this = rhs; }
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {}
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {}
// This constructor allows you to construct Point from Eigen expressions
// This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions.
template<typename OtherDerived>
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
template<typename OtherDerived>
static Point new_scale(const Eigen::MatrixBase<OtherDerived> &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
// This method allows you to assign Eigen expressions to MyVectorType
template<typename OtherDerived>
@ -297,16 +320,16 @@ BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
// Test for duplicate points in a vector of points.
// The points are copied, sorted and checked for duplicates globally.
bool has_duplicate_points(std::vector<Point> &&pts);
inline bool has_duplicate_points(const std::vector<Point> &pts)
bool has_duplicate_points(Points &&pts);
inline bool has_duplicate_points(const Points &pts)
{
std::vector<Point> cpy = pts;
Points cpy = pts;
return has_duplicate_points(std::move(cpy));
}
// Test for duplicate points in a vector of points.
// Only successive points are checked for equality.
inline bool has_duplicate_successive_points(const std::vector<Point> &pts)
inline bool has_duplicate_successive_points(const Points &pts)
{
for (size_t i = 1; i < pts.size(); ++ i)
if (pts[i - 1] == pts[i])
@ -316,11 +339,14 @@ inline bool has_duplicate_successive_points(const std::vector<Point> &pts)
// Test for duplicate points in a vector of points.
// Only successive points are checked for equality. Additionally, first and last points are compared for equality.
inline bool has_duplicate_successive_points_closed(const std::vector<Point> &pts)
inline bool has_duplicate_successive_points_closed(const Points &pts)
{
return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back());
}
// Collect adjecent(duplicit points)
Points collect_duplicates(Points pts /* Copy */);
inline bool shorter_then(const Point& p0, const coord_t len)
{
if (p0.x() > len || p0.x() < -len)
@ -341,7 +367,7 @@ namespace int128 {
// To be used by std::unordered_map, std::unordered_multimap and friends.
struct PointHash {
size_t operator()(const Vec2crd &pt) const {
size_t operator()(const Vec2crd &pt) const noexcept {
return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y());
}
};
@ -570,6 +596,27 @@ inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base)
inline Point align_to_grid(Point coord, Point spacing, Point base)
{ return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); }
// MinMaxLimits
template<typename T> struct MinMax { T min; T max;};
template<typename T>
static bool apply(std::optional<T> &val, const MinMax<T> &limit) {
if (!val.has_value()) return false;
return apply<T>(*val, limit);
}
template<typename T>
static bool apply(T &val, const MinMax<T> &limit)
{
if (val > limit.max) {
val = limit.max;
return true;
}
if (val < limit.min) {
val = limit.min;
return true;
}
return false;
}
} // namespace Slic3r
// start Boost

View file

@ -1,3 +1,14 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966
///|/ Copyright (c) Slic3r 2013 - 2015 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2014 Petr Ledvina @ledvinap
///|/
///|/ ported from lib/Slic3r/Polygon.pm:
///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2012 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
@ -454,6 +465,38 @@ bool has_duplicate_points(const Polygons &polys)
#endif
}
bool remove_same_neighbor(Polygon &polygon)
{
Points &points = polygon.points;
if (points.empty())
return false;
auto last = std::unique(points.begin(), points.end());
// remove first and last neighbor duplication
if (const Point &last_point = *(last - 1); last_point == points.front()) {
--last;
}
// no duplicits
if (last == points.end())
return false;
points.erase(last, points.end());
return true;
}
bool remove_same_neighbor(Polygons &polygons)
{
if (polygons.empty())
return false;
bool exist = false;
for (Polygon &polygon : polygons)
exist |= remove_same_neighbor(polygon);
// remove empty polygons
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.points.size() <= 2; }), polygons.end());
return exist;
}
static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3)
{
Point v1 = p2 - p1;

View file

@ -1,3 +1,13 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Oleksandra Iushchenko @YuSanka
///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
///|/
///|/ ported from lib/Slic3r/Polygon.pm:
///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2012 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_Polygon_hpp_
#define slic3r_Polygon_hpp_
@ -5,6 +15,7 @@
#include <vector>
#include <string>
#include "Line.hpp"
#include "Point.hpp"
#include "MultiPoint.hpp"
#include "Polyline.hpp"
@ -112,6 +123,10 @@ inline bool has_duplicate_points(Polygon &&poly) { return has_duplicate_poi
inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_points(poly.points); }
bool has_duplicate_points(const Polygons &polys);
// Return True when erase some otherwise False.
bool remove_same_neighbor(Polygon &polygon);
bool remove_same_neighbor(Polygons &polygons);
inline double total_length(const Polygons &polylines) {
double total = 0;
for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
@ -245,7 +260,19 @@ inline Polylines to_polylines(Polygons &&polys)
return polylines;
}
inline Polygons to_polygons(const std::vector<Points> &paths)
// close polyline to polygon (connect first and last point in polyline)
inline Polygons to_polygons(const Polylines &polylines)
{
Polygons out;
out.reserve(polylines.size());
for (const Polyline &polyline : polylines) {
if (polyline.size())
out.emplace_back(polyline.points);
}
return out;
}
inline Polygons to_polygons(const VecOfPoints &paths)
{
Polygons out;
out.reserve(paths.size());
@ -254,7 +281,7 @@ inline Polygons to_polygons(const std::vector<Points> &paths)
return out;
}
inline Polygons to_polygons(std::vector<Points> &&paths)
inline Polygons to_polygons(VecOfPoints &&paths)
{
Polygons out;
out.reserve(paths.size());
@ -270,6 +297,21 @@ bool polygons_match(const Polygon &l, const Polygon &r);
Polygon make_circle(double radius, double error);
Polygon make_circle_num_segments(double radius, size_t num_segments);
/// <summary>
/// Define point laying on polygon
/// keep index of polygon line and point coordinate
/// </summary>
struct PolygonPoint
{
// index of line inside of polygon
// 0 .. from point polygon[0] to polygon[1]
size_t index;
// Point, which lay on line defined by index
Point point;
};
using PolygonPoints = std::vector<PolygonPoint>;
bool overlaps(const Polygons& polys1, const Polygons& polys2);
} // Slic3r

View file

@ -497,6 +497,33 @@ BoundingBox get_extents(const Polylines &polylines)
return bb;
}
// Return True when erase some otherwise False.
bool remove_same_neighbor(Polyline &polyline) {
Points &points = polyline.points;
if (points.empty())
return false;
auto last = std::unique(points.begin(), points.end());
// no duplicits
if (last == points.end())
return false;
points.erase(last, points.end());
return true;
}
bool remove_same_neighbor(Polylines &polylines){
if (polylines.empty())
return false;
bool exist = false;
for (Polyline &polyline : polylines)
exist |= remove_same_neighbor(polyline);
// remove empty polylines
polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [](const Polyline &p) { return p.points.size() <= 1; }), polylines.end());
return exist;
}
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())

View file

@ -164,6 +164,10 @@ public:
extern BoundingBox get_extents(const Polyline &polyline);
extern BoundingBox get_extents(const Polylines &polylines);
// Return True when erase some otherwise False.
bool remove_same_neighbor(Polyline &polyline);
bool remove_same_neighbor(Polylines &polylines);
inline double total_length(const Polylines &polylines) {
double total = 0;
for (const Polyline &pl : polylines)

View file

@ -839,7 +839,7 @@ void update_volume_bboxes(
layer_range.volumes.emplace_back(*it);
} else
layer_range.volumes.push_back({ model_volume->id(),
transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) });
transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), offset) });
}
} else {
std::vector<std::vector<PrintObjectRegions::VolumeExtents>> volumes_old;
@ -871,7 +871,7 @@ void update_volume_bboxes(
layer_range.volumes.emplace_back(*it);
}
} else {
transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset);
transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), ranges, bboxes, offset);
for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second)
layer_range.volumes.push_back({ model_volume->id(), bbox.first });

View file

@ -1,9 +1,12 @@
///|/ Copyright (c) Prusa Research 2020 - 2022 Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef AGGRASTER_HPP
#define AGGRASTER_HPP
#include <libslic3r/SLA/RasterBase.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/MTUtils.hpp"
// For rasterizing
#include <agg/agg_basics.h>
@ -42,7 +45,7 @@ public:
using TValue = typename TColor::value_type;
using TPixel = typename PixelRenderer::pixel_type;
using TRawBuffer = agg::rendering_buffer;
protected:
Resolution m_resolution;
@ -154,8 +157,8 @@ public:
}
Trafo trafo() const override { return m_trafo; }
Resolution resolution() const override { return m_resolution; }
PixelDim pixel_dimensions() const override
Resolution resolution() const { return m_resolution; }
PixelDim pixel_dimensions() const
{
return {SCALING_FACTOR / m_pxdim_scaled.w_mm,
SCALING_FACTOR / m_pxdim_scaled.h_mm};
@ -187,11 +190,15 @@ class RasterGrayscaleAA : public _RasterGrayscaleAA {
using typename Base::TValue;
public:
template<class GammaFn>
RasterGrayscaleAA(const RasterBase::Resolution &res,
const RasterBase::PixelDim & pd,
const RasterBase::Trafo & trafo,
GammaFn && fn)
: Base(res, pd, trafo, Colors<TColor>::White, Colors<TColor>::Black,
RasterGrayscaleAA(const Resolution &res,
const PixelDim &pd,
const RasterBase::Trafo &trafo,
GammaFn &&fn)
: Base(res,
pd,
trafo,
Colors<TColor>::White,
Colors<TColor>::Black,
std::forward<GammaFn>(fn))
{}
@ -209,10 +216,10 @@ public:
class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA {
public:
RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res,
const RasterBase::PixelDim & pd,
const RasterBase::Trafo & trafo,
double gamma = 1.)
RasterGrayscaleAAGammaPower(const Resolution &res,
const PixelDim &pd,
const RasterBase::Trafo &trafo,
double gamma = 1.)
: RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma))
{}
};

View file

@ -1,3 +1,8 @@
///|/ Copyright (c) Prusa Research 2020 - 2022 Tomáš Mészáros @tamasmeszaros
///|/ Copyright (c) 2022 ole00 @ole00
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef SLARASTER_CPP
#define SLARASTER_CPP
@ -11,11 +16,6 @@
namespace Slic3r { namespace sla {
const RasterBase::TMirroring RasterBase::NoMirror = {false, false};
const RasterBase::TMirroring RasterBase::MirrorX = {true, false};
const RasterBase::TMirroring RasterBase::MirrorY = {false, true};
const RasterBase::TMirroring RasterBase::MirrorXY = {true, true};
EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h,
size_t num_components)
{
@ -68,15 +68,17 @@ EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h,
}
std::unique_ptr<RasterBase> create_raster_grayscale_aa(
const RasterBase::Resolution &res,
const RasterBase::PixelDim & pxdim,
double gamma,
const RasterBase::Trafo & tr)
const Resolution &res,
const PixelDim &pxdim,
double gamma,
const RasterBase::Trafo &tr)
{
std::unique_ptr<RasterBase> rst;
if (gamma > 0)
rst = std::make_unique<RasterGrayscaleAAGammaPower>(res, pxdim, tr, gamma);
else if (std::abs(gamma - 1.) < 1e-6)
rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_none());
else
rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_threshold(.5));

View file

@ -1,3 +1,8 @@
///|/ Copyright (c) Prusa Research 2020 - 2022 Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv
///|/ Copyright (c) 2022 ole00 @ole00
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef SLA_RASTERBASE_HPP
#define SLA_RASTERBASE_HPP
@ -9,7 +14,6 @@
#include <cstdint>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/SLA/Concurrency.hpp>
namespace Slic3r {
@ -31,6 +35,27 @@ public:
const char * extension() const { return m_ext.c_str(); }
};
/// Type that represents a resolution in pixels.
struct Resolution {
size_t width_px = 0;
size_t height_px = 0;
Resolution() = default;
Resolution(size_t w, size_t h) : width_px(w), height_px(h) {}
size_t pixels() const { return width_px * height_px; }
};
/// Types that represents the dimension of a pixel in millimeters.
struct PixelDim {
double w_mm = 1.;
double h_mm = 1.;
PixelDim() = default;
PixelDim(double px_width_mm, double px_height_mm)
: w_mm(px_width_mm), h_mm(px_height_mm)
{}
};
using RasterEncoder =
std::function<EncodedRaster(const void *ptr, size_t w, size_t h, size_t num_components)>;
@ -40,10 +65,10 @@ public:
enum Orientation { roLandscape, roPortrait };
using TMirroring = std::array<bool, 2>;
static const TMirroring NoMirror;
static const TMirroring MirrorX;
static const TMirroring MirrorY;
static const TMirroring MirrorXY;
static const constexpr TMirroring NoMirror = {false, false};
static const constexpr TMirroring MirrorX = {true, false};
static const constexpr TMirroring MirrorY = {false, true};
static const constexpr TMirroring MirrorXY = {true, true};
struct Trafo {
bool mirror_x = false, mirror_y = false, flipXY = false;
@ -63,35 +88,14 @@ public:
Point get_center() const { return {center_x, center_y}; }
};
/// Type that represents a resolution in pixels.
struct Resolution {
size_t width_px = 0;
size_t height_px = 0;
Resolution() = default;
Resolution(size_t w, size_t h) : width_px(w), height_px(h) {}
size_t pixels() const { return width_px * height_px; }
};
/// Types that represents the dimension of a pixel in millimeters.
struct PixelDim {
double w_mm = 1.;
double h_mm = 1.;
PixelDim() = default;
PixelDim(double px_width_mm, double px_height_mm)
: w_mm(px_width_mm), h_mm(px_height_mm)
{}
};
virtual ~RasterBase() = default;
/// Draw a polygon with holes.
virtual void draw(const ExPolygon& poly) = 0;
/// Get the resolution of the raster.
virtual Resolution resolution() const = 0;
virtual PixelDim pixel_dimensions() const = 0;
// virtual Resolution resolution() const = 0;
// virtual PixelDim pixel_dimensions() const = 0;
virtual Trafo trafo() const = 0;
virtual EncodedRaster encode(RasterEncoder encoder) const = 0;
@ -109,10 +113,10 @@ std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes);
// If gamma is zero, thresholding will be performed which disables AA.
std::unique_ptr<RasterBase> create_raster_grayscale_aa(
const RasterBase::Resolution &res,
const RasterBase::PixelDim & pxdim,
double gamma = 1.0,
const RasterBase::Trafo & tr = {});
const Resolution &res,
const PixelDim &pxdim,
double gamma = 1.0,
const RasterBase::Trafo &tr = {});
}} // namespace Slic3r::sla

View file

@ -6,6 +6,7 @@
#include "PrintBase.hpp"
#include "SLA/RasterBase.hpp"
#include "SLA/SupportTree.hpp"
#include "Execution/ExecutionTBB.hpp"
#include "Point.hpp"
#include "MTUtils.hpp"
#include "Zipper.hpp"

View file

@ -0,0 +1,191 @@
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_TextConfiguration_hpp_
#define slic3r_TextConfiguration_hpp_
#include <vector>
#include <string>
#include <optional>
#include <cereal/cereal.hpp>
#include <cereal/types/optional.hpp>
#include <cereal/types/string.hpp>
#include <cereal/archives/binary.hpp>
#include "Point.hpp" // Transform3d
namespace Slic3r {
/// <summary>
/// User modifiable property of text style
/// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization
/// </summary>
struct FontProp
{
// define extra space between letters, negative mean closer letter
// When not set value is zero and is not stored
std::optional<int> char_gap; // [in font point]
// define extra space between lines, negative mean closer lines
// When not set value is zero and is not stored
std::optional<int> line_gap; // [in font point]
// positive value mean wider character shape
// negative value mean tiner character shape
// When not set value is zero and is not stored
std::optional<float> boldness; // [in mm]
// positive value mean italic of character (CW)
// negative value mean CCW skew (unItalic)
// When not set value is zero and is not stored
std::optional<float> skew; // [ration x:y]
// Parameter for True Type Font collections
// Select index of font in collection
std::optional<unsigned int> collection_number;
// Distiguish projection per glyph
bool per_glyph;
// NOTE: way of serialize to 3mf force that zero must be default value
enum class HorizontalAlign { left = 0, center, right };
enum class VerticalAlign { top = 0, center, bottom };
using Align = std::pair<HorizontalAlign, VerticalAlign>;
// change pivot of text
// When not set, center is used and is not stored
Align align = Align(HorizontalAlign::center, VerticalAlign::center);
//////
// Duplicit data to wxFontDescriptor
// used for store/load .3mf file
//////
// Height of text line (letters)
// duplicit to wxFont::PointSize
float size_in_mm; // [in mm]
// Additional data about font to be able to find substitution,
// when same font is not installed
std::optional<std::string> family;
std::optional<std::string> face_name;
std::optional<std::string> style;
std::optional<std::string> weight;
/// <summary>
/// Only constructor with restricted values
/// </summary>
/// <param name="line_height">Y size of text [in mm]</param>
/// <param name="depth">Z size of text [in mm]</param>
FontProp(float line_height = 10.f) : size_in_mm(line_height), per_glyph(false)
{}
bool operator==(const FontProp& other) const {
return
char_gap == other.char_gap &&
line_gap == other.line_gap &&
per_glyph == other.per_glyph &&
align == other.align &&
is_approx(size_in_mm, other.size_in_mm) &&
is_approx(boldness, other.boldness) &&
is_approx(skew, other.skew);
}
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const
{
ar(size_in_mm, per_glyph, align.first, align.second);
cereal::save(ar, char_gap);
cereal::save(ar, line_gap);
cereal::save(ar, boldness);
cereal::save(ar, skew);
cereal::save(ar, collection_number);
}
template<class Archive> void load(Archive &ar)
{
ar(size_in_mm, per_glyph, align.first, align.second);
cereal::load(ar, char_gap);
cereal::load(ar, line_gap);
cereal::load(ar, boldness);
cereal::load(ar, skew);
cereal::load(ar, collection_number);
}
};
/// <summary>
/// Style of embossed text
/// (Path + Type) must define how to open font for using on different OS
/// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization
/// </summary>
struct EmbossStyle
{
// Human readable name of style it is shown in GUI
std::string name;
// Define how to open font
// Meaning depend on type
std::string path;
enum class Type;
// Define what is stored in path
Type type { Type::undefined };
// User modification of font style
FontProp prop;
// when name is empty than Font item was loaded from .3mf file
// and potentionaly it is not reproducable
// define data stored in path
// when wx change way of storing add new descriptor Type
enum class Type {
undefined = 0,
// wx font descriptors are platform dependent
// path is font descriptor generated by wxWidgets
wx_win_font_descr, // on Windows
wx_lin_font_descr, // on Linux
wx_mac_font_descr, // on Max OS
// TrueTypeFont file loacation on computer
// for privacy: only filename is stored into .3mf
file_path
};
bool operator==(const EmbossStyle &other) const
{
return
type == other.type &&
prop == other.prop &&
name == other.name &&
path == other.path
;
}
// undo / redo stack recovery
template<class Archive> void serialize(Archive &ar){ ar(name, path, type, prop); }
};
// Emboss style name inside vector is unique
// It is not map beacuse items has own order (view inside of slect)
// It is stored into AppConfig by EmbossStylesSerializable
using EmbossStyles = std::vector<EmbossStyle>;
/// <summary>
/// Define how to create 'Text volume'
/// It is stored into .3mf by TextConfigurationSerialization
/// It is part of ModelVolume optional data
/// </summary>
struct TextConfiguration
{
// Style of embossed text
EmbossStyle style;
// Embossed text value
std::string text = "None";
// undo / redo stack recovery
template<class Archive> void serialize(Archive &ar) { ar(style, text); }
};
} // namespace Slic3r
#endif // slic3r_TextConfiguration_hpp_

25
src/libslic3r/Timer.cpp Normal file
View file

@ -0,0 +1,25 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Timer.hpp"
#include <boost/log/trivial.hpp>
using namespace std::chrono;
Slic3r::Timer::Timer(const std::string &name) : m_name(name), m_start(steady_clock::now()) {}
Slic3r::Timer::~Timer()
{
BOOST_LOG_TRIVIAL(debug) << "Timer '" << m_name << "' spend " <<
duration_cast<milliseconds>(steady_clock::now() - m_start).count() << "ms";
}
namespace Slic3r::Timing {
void TimeLimitAlarm::report_time_exceeded() const {
BOOST_LOG_TRIVIAL(error) << "Time limit exceeded for " << m_limit_exceeded_message << ": " << m_timer.elapsed_seconds() << "s";
}
}

96
src/libslic3r/Timer.hpp Normal file
View file

@ -0,0 +1,96 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef libslic3r_Timer_hpp_
#define libslic3r_Timer_hpp_
#include <string>
#include <chrono>
namespace Slic3r {
/// <summary>
/// Instance of this class is used for measure time consumtion
/// of block code until instance is alive and write result to debug output
/// </summary>
class Timer
{
std::string m_name;
std::chrono::steady_clock::time_point m_start;
public:
/// <summary>
/// name describe timer
/// </summary>
/// <param name="name">Describe timer in consol log</param>
Timer(const std::string& name);
/// <summary>
/// name describe timer
/// </summary>
~Timer();
};
namespace Timing {
// Timing code from Catch2 unit testing library
static inline uint64_t nanoseconds_since_epoch() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
// Timing code from Catch2 unit testing library
class Timer {
public:
void start() {
m_nanoseconds = nanoseconds_since_epoch();
}
uint64_t elapsed_nanoseconds() const {
return nanoseconds_since_epoch() - m_nanoseconds;
}
uint64_t elapsed_microseconds() const {
return elapsed_nanoseconds() / 1000;
}
unsigned int elapsed_milliseconds() const {
return static_cast<unsigned int>(elapsed_microseconds()/1000);
}
double elapsed_seconds() const {
return elapsed_microseconds() / 1000000.0;
}
private:
uint64_t m_nanoseconds = 0;
};
// Emits a Boost::log error if the life time of this timing object exceeds a limit.
class TimeLimitAlarm {
public:
TimeLimitAlarm(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) :
m_time_limit_nanoseconds(time_limit_nanoseconds), m_limit_exceeded_message(limit_exceeded_message) {
m_timer.start();
}
~TimeLimitAlarm() {
auto elapsed = m_timer.elapsed_nanoseconds();
if (elapsed > m_time_limit_nanoseconds)
this->report_time_exceeded();
}
static TimeLimitAlarm new_nanos(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) {
return TimeLimitAlarm(time_limit_nanoseconds, limit_exceeded_message);
}
static TimeLimitAlarm new_milis(uint64_t time_limit_milis, std::string_view limit_exceeded_message) {
return TimeLimitAlarm(uint64_t(time_limit_milis) * 1000000l, limit_exceeded_message);
}
static TimeLimitAlarm new_seconds(uint64_t time_limit_seconds, std::string_view limit_exceeded_message) {
return TimeLimitAlarm(uint64_t(time_limit_seconds) * 1000000000l, limit_exceeded_message);
}
private:
void report_time_exceeded() const;
Timer m_timer;
uint64_t m_time_limit_nanoseconds;
std::string_view m_limit_exceeded_message;
};
} // namespace Catch
} // namespace Slic3r
#endif // libslic3r_Timer_hpp_

View file

@ -0,0 +1,332 @@
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Triangulation.hpp"
#include "IntersectionPoints.hpp"
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/spatial_sort.h>
using namespace Slic3r;
namespace priv{
inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon, const Triangulation::Changes& changes) {
const Points &pts = polygon.points;
uint32_t size = static_cast<uint32_t>(pts.size());
uint32_t last_index = offset + size - 1;
uint32_t prev_index = changes[last_index];
for (uint32_t i = 0; i < size; ++i) {
uint32_t index = changes[offset + i];
// when duplicit points are neighbor
if (prev_index == index) continue;
edges.push_back({prev_index, index});
prev_index = index;
}
offset += size;
}
inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon) {
const Points &pts = polygon.points;
uint32_t size = static_cast<uint32_t>(pts.size());
uint32_t prev_index = offset + size - 1;
for (uint32_t i = 0; i < size; ++i) {
uint32_t index = offset + i;
edges.push_back({prev_index, index});
prev_index = index;
}
offset += size;
}
inline bool has_bidirectional_constrained(
const Triangulation::HalfEdges &constrained)
{
for (const auto &c : constrained) {
auto key = std::make_pair(c.second, c.first);
auto it = std::lower_bound(constrained.begin(), constrained.end(),
key);
if (it != constrained.end() && *it == key) return true;
}
return false;
}
inline bool is_unique(const Points &points) {
Points pts = points; // copy
std::sort(pts.begin(), pts.end());
auto it = std::adjacent_find(pts.begin(), pts.end());
return it == pts.end();
}
inline bool has_self_intersection(
const Points &points,
const Triangulation::HalfEdges &constrained_half_edges)
{
Lines lines;
lines.reserve(constrained_half_edges.size());
for (const auto &he : constrained_half_edges)
lines.emplace_back(points[he.first], points[he.second]);
return !get_intersections(lines).empty();
}
} // namespace priv
//#define VISUALIZE_TRIANGULATION
#ifdef VISUALIZE_TRIANGULATION
#include "admesh/stl.h" // indexed triangle set
static void visualize(const Points &points,
const Triangulation::Indices &indices,
const char *filename)
{
// visualize
indexed_triangle_set its;
its.vertices.reserve(points.size());
for (const Point &p : points) its.vertices.emplace_back(p.x(), p.y(), 0.);
its.indices = indices;
its_write_obj(its, filename);
}
#endif // VISUALIZE_TRIANGULATION
Triangulation::Indices Triangulation::triangulate(const Points &points,
const HalfEdges &constrained_half_edges)
{
assert(!points.empty());
assert(!constrained_half_edges.empty());
// constrained must be sorted
assert(std::is_sorted(constrained_half_edges.begin(),
constrained_half_edges.end()));
// check that there is no duplicit constrained edge
assert(std::adjacent_find(constrained_half_edges.begin(), constrained_half_edges.end()) == constrained_half_edges.end());
// edges can NOT contain bidirectional constrained
assert(!priv::has_bidirectional_constrained(constrained_half_edges));
// check that there is only unique poistion of points
assert(priv::is_unique(points));
assert(!priv::has_self_intersection(points, constrained_half_edges));
// use cgal triangulation
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Vb = CGAL::Triangulation_vertex_base_with_info_2<uint32_t, K>;
using Fb = CGAL::Constrained_triangulation_face_base_2<K>;
using Tds = CGAL::Triangulation_data_structure_2<Vb, Fb>;
using CDT = CGAL::Constrained_Delaunay_triangulation_2<K, Tds, CGAL::Exact_predicates_tag>;
// construct a constrained triangulation
CDT cdt;
{
std::vector<CDT::Vertex_handle> vertices_handle(points.size()); // for constriants
using Point_with_ord = std::pair<CDT::Point, size_t>;
using SearchTrait = CGAL::Spatial_sort_traits_adapter_2
<K, CGAL::First_of_pair_property_map<Point_with_ord> >;
std::vector<Point_with_ord> cdt_points;
cdt_points.reserve(points.size());
size_t ord = 0;
for (const auto &p : points)
cdt_points.emplace_back(std::make_pair(CDT::Point{p.x(), p.y()}, ord++));
SearchTrait st;
CGAL::spatial_sort(cdt_points.begin(), cdt_points.end(), st);
CDT::Face_handle f;
for (const auto& p : cdt_points) {
auto handle = cdt.insert(p.first, f);
handle->info() = p.second;
vertices_handle[p.second] = handle;
f = handle->face();
}
// Constrain the triangulation.
for (const HalfEdge &edge : constrained_half_edges)
cdt.insert_constraint(vertices_handle[edge.first], vertices_handle[edge.second]);
}
auto faces = cdt.finite_face_handles();
// Unmark constrained edges of outside faces.
size_t num_faces = 0;
for (CDT::Face_handle fh : faces) {
for (int i = 0; i < 3; ++i) {
if (!fh->is_constrained(i)) continue;
auto key = std::make_pair(fh->vertex((i + 2) % 3)->info(), fh->vertex((i + 1) % 3)->info());
auto it = std::lower_bound(constrained_half_edges.begin(), constrained_half_edges.end(), key);
if (it == constrained_half_edges.end() || *it != key) continue;
// This face contains a constrained edge and it is outside.
for (int j = 0; j < 3; ++ j)
fh->set_constraint(j, false);
--num_faces;
break;
}
++num_faces;
}
auto inside = [](CDT::Face_handle &fh) {
return fh->neighbor(0) != fh &&
(fh->is_constrained(0) ||
fh->is_constrained(1) ||
fh->is_constrained(2));
};
#ifdef VISUALIZE_TRIANGULATION
std::vector<Vec3i> indices2;
indices2.reserve(num_faces);
for (CDT::Face_handle fh : faces)
if (inside(fh)) indices2.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info());
visualize(points, indices2, "C:/data/temp/triangulation_without_floodfill.obj");
#endif // VISUALIZE_TRIANGULATION
// Propagate inside the constrained regions.
std::vector<CDT::Face_handle> queue;
queue.reserve(num_faces);
for (CDT::Face_handle seed : faces){
if (!inside(seed)) continue;
// Seed fill to neighbor faces.
queue.emplace_back(seed);
while (! queue.empty()) {
CDT::Face_handle fh = queue.back();
queue.pop_back();
for (int i = 0; i < 3; ++i) {
if (fh->is_constrained(i)) continue;
// Propagate along this edge.
fh->set_constraint(i, true);
CDT::Face_handle nh = fh->neighbor(i);
bool was_inside = inside(nh);
// Mark the other side of this edge.
nh->set_constraint(nh->index(fh), true);
if (! was_inside)
queue.push_back(nh);
}
}
}
std::vector<Vec3i> indices;
indices.reserve(num_faces);
for (CDT::Face_handle fh : faces)
if (inside(fh))
indices.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info());
#ifdef VISUALIZE_TRIANGULATION
visualize(points, indices, "C:/data/temp/triangulation.obj");
#endif // VISUALIZE_TRIANGULATION
return indices;
}
Triangulation::Indices Triangulation::triangulate(const Polygon &polygon)
{
const Points &pts = polygon.points;
HalfEdges edges;
edges.reserve(pts.size());
uint32_t offset = 0;
priv::insert_edges(edges, offset, polygon);
std::sort(edges.begin(), edges.end());
return triangulate(pts, edges);
}
Triangulation::Indices Triangulation::triangulate(const Polygons &polygons)
{
size_t count = count_points(polygons);
Points points;
points.reserve(count);
HalfEdges edges;
edges.reserve(count);
uint32_t offset = 0;
for (const Polygon &polygon : polygons) {
Slic3r::append(points, polygon.points);
priv::insert_edges(edges, offset, polygon);
}
std::sort(edges.begin(), edges.end());
return triangulate(points, edges);
}
Triangulation::Indices Triangulation::triangulate(const ExPolygon &expolygon){
ExPolygons expolys({expolygon});
return triangulate(expolys);
}
Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons){
Points pts = to_points(expolygons);
Points d_pts = collect_duplicates(pts);
if (d_pts.empty()) return triangulate(expolygons, pts);
Changes changes = create_changes(pts, d_pts);
Indices indices = triangulate(expolygons, pts, changes);
// reverse map for changes
Changes changes2(changes.size(), std::numeric_limits<uint32_t>::max());
for (size_t i = 0; i < changes.size(); ++i)
changes2[changes[i]] = i;
// convert indices into expolygons indicies
for (Vec3i &t : indices)
for (size_t ti = 0; ti < 3; ti++) t[ti] = changes2[t[ti]];
return indices;
}
Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points &points)
{
assert(count_points(expolygons) == points.size());
// when contain duplicit coordinate in points will not work properly
assert(collect_duplicates(points).empty());
HalfEdges edges;
edges.reserve(points.size());
uint32_t offset = 0;
for (const ExPolygon &expolygon : expolygons) {
priv::insert_edges(edges, offset, expolygon.contour);
for (const Polygon &hole : expolygon.holes)
priv::insert_edges(edges, offset, hole);
}
std::sort(edges.begin(), edges.end());
return triangulate(points, edges);
}
Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes)
{
assert(!points.empty());
assert(count_points(expolygons) == points.size());
assert(changes.size() == points.size());
// IMPROVE: search from end and somehow distiquish that value is not a change
uint32_t count_points = *std::max_element(changes.begin(), changes.end())+1;
Points pts(count_points);
for (size_t i = 0; i < changes.size(); i++)
pts[changes[i]] = points[i];
HalfEdges edges;
edges.reserve(points.size());
uint32_t offset = 0;
for (const ExPolygon &expolygon : expolygons) {
priv::insert_edges(edges, offset, expolygon.contour, changes);
for (const Polygon &hole : expolygon.holes)
priv::insert_edges(edges, offset, hole, changes);
}
std::sort(edges.begin(), edges.end());
return triangulate(pts, edges);
}
Triangulation::Changes Triangulation::create_changes(const Points &points, const Points &duplicits)
{
assert(!duplicits.empty());
assert(duplicits.size() < points.size()/2);
std::vector<uint32_t> duplicit_indices(duplicits.size(), std::numeric_limits<uint32_t>::max());
Changes changes;
changes.reserve(points.size());
uint32_t index = 0;
for (const Point &p: points) {
auto it = std::lower_bound(duplicits.begin(), duplicits.end(), p);
if (it == duplicits.end() || *it != p) {
changes.push_back(index);
++index;
continue;
}
uint32_t &d_index = duplicit_indices[it - duplicits.begin()];
if (d_index == std::numeric_limits<uint32_t>::max()) {
d_index = index;
changes.push_back(index);
++index;
} else {
changes.push_back(d_index);
}
}
return changes;
}

View file

@ -0,0 +1,76 @@
///|/ Copyright (c) Prusa Research 2021 - 2022 Vojtěch Bubník @bubnikv, Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef libslic3r_Triangulation_hpp_
#define libslic3r_Triangulation_hpp_
#include <vector>
#include <set>
#include <libslic3r/Point.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
class Triangulation
{
public:
Triangulation() = delete;
// define oriented connection of 2 vertices(defined by its index)
using HalfEdge = std::pair<uint32_t, uint32_t>;
using HalfEdges = std::vector<HalfEdge>;
using Indices = std::vector<Vec3i>;
/// <summary>
/// Connect points by triangulation to create filled surface by triangles
/// Input points have to be unique
/// Inspiration for make unique points is Emboss::dilate_to_unique_points
/// </summary>
/// <param name="points">Points to connect</param>
/// <param name="edges">Constraint for edges, pair is from point(first) to
/// point(second), sorted lexicographically</param>
/// <returns>Triangles</returns>
static Indices triangulate(const Points &points,
const HalfEdges &half_edges);
static Indices triangulate(const Polygon &polygon);
static Indices triangulate(const Polygons &polygons);
static Indices triangulate(const ExPolygon &expolygon);
static Indices triangulate(const ExPolygons &expolygons);
// Map for convert original index to set without duplication
// from_index<to_index>
using Changes = std::vector<uint32_t>;
/// <summary>
/// Create conversion map from original index into new
/// with respect of duplicit point
/// </summary>
/// <param name="points">input set of points</param>
/// <param name="duplicits">duplicit points collected from points</param>
/// <returns>Conversion map for point index</returns>
static Changes create_changes(const Points &points, const Points &duplicits);
/// <summary>
/// Triangulation for expolygons, speed up when points are already collected
/// NOTE: Not working properly for ExPolygons with multiple point on same coordinate
/// You should check it by "collect_changes"
/// </summary>
/// <param name="expolygons">Input shape to triangulation - define edges</param>
/// <param name="points">Points from expolygons</param>
/// <returns>Triangle indices</returns>
static Indices triangulate(const ExPolygons &expolygons, const Points& points);
/// <summary>
/// Triangulation for expolygons containing multiple points with same coordinate
/// </summary>
/// <param name="expolygons">Input shape to triangulation - define edge</param>
/// <param name="points">Points from expolygons</param>
/// <param name="changes">Changes swap for indicies into points</param>
/// <returns>Triangle indices</returns>
static Indices triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes);
};
} // namespace Slic3r
#endif // libslic3r_Triangulation_hpp_

View file

@ -372,6 +372,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER
}
extern std::string xml_escape(std::string text, bool is_marked = false);
extern std::string xml_escape_double_quotes_attribute_value(std::string text);
extern std::string xml_unescape(std::string text);

View file

@ -1225,6 +1225,34 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/)
return text;
}
// Definition of escape symbols https://www.w3.org/TR/REC-xml/#AVNormalize
// During the read of xml attribute normalization of white spaces is applied
// Soo for not lose white space character it is escaped before store
std::string xml_escape_double_quotes_attribute_value(std::string text)
{
std::string::size_type pos = 0;
for (;;) {
pos = text.find_first_of("\"&<\r\n\t", pos);
if (pos == std::string::npos) break;
std::string replacement;
switch (text[pos]) {
case '\"': replacement = "&quot;"; break;
case '&': replacement = "&amp;"; break;
case '<': replacement = "&lt;"; break;
case '\r': replacement = "&#xD;"; break;
case '\n': replacement = "&#xA;"; break;
case '\t': replacement = "&#x9;"; break;
default: break;
}
text.replace(pos, 1, replacement);
pos += replacement.size();
}
return text;
}
std::string xml_unescape(std::string s)
{
std::string ret;