mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-11-01 21:21:10 -06:00
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:
parent
7a8e1929ee
commit
933aa3050b
197 changed files with 27190 additions and 2454 deletions
|
|
@ -3,16 +3,19 @@ project(imgui)
|
|||
|
||||
add_library(imgui STATIC
|
||||
imconfig.h
|
||||
imgui.cpp
|
||||
imgui.h
|
||||
imgui_demo.cpp
|
||||
imgui_draw.cpp
|
||||
imgui_internal.h
|
||||
imgui_stdlib.cpp
|
||||
imgui_stdlib.h
|
||||
imgui_tables.cpp
|
||||
imgui_widgets.cpp
|
||||
# imgui STB
|
||||
imstb_rectpack.h
|
||||
imstb_textedit.h
|
||||
imstb_truetype.h
|
||||
imgui_tables.cpp
|
||||
imgui.cpp
|
||||
imgui_demo.cpp
|
||||
imgui_draw.cpp
|
||||
imgui_widgets.cpp
|
||||
)
|
||||
|
||||
if(Boost_FOUND)
|
||||
|
|
|
|||
76
src/imgui/imgui_stdlib.cpp
Normal file
76
src/imgui/imgui_stdlib.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
|
||||
// This is also an example of how you may wrap your own similar types.
|
||||
|
||||
// Compatibility:
|
||||
// - std::string support is only guaranteed to work from C++11.
|
||||
// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
|
||||
|
||||
// Changelog:
|
||||
// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_stdlib.h"
|
||||
|
||||
struct InputTextCallback_UserData
|
||||
{
|
||||
std::string* Str;
|
||||
ImGuiInputTextCallback ChainCallback;
|
||||
void* ChainCallbackUserData;
|
||||
};
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData;
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
||||
{
|
||||
// Resize string callback
|
||||
// If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
|
||||
std::string* str = user_data->Str;
|
||||
IM_ASSERT(data->Buf == str->c_str());
|
||||
str->resize(data->BufTextLen);
|
||||
data->Buf = (char*)str->c_str();
|
||||
}
|
||||
else if (user_data->ChainCallback)
|
||||
{
|
||||
// Forward to user callback, if any
|
||||
data->UserData = user_data->ChainCallbackUserData;
|
||||
return user_data->ChainCallback(data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||
{
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
|
||||
InputTextCallback_UserData cb_user_data;
|
||||
cb_user_data.Str = str;
|
||||
cb_user_data.ChainCallback = callback;
|
||||
cb_user_data.ChainCallbackUserData = user_data;
|
||||
return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
|
||||
}
|
||||
|
||||
bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||
{
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
|
||||
InputTextCallback_UserData cb_user_data;
|
||||
cb_user_data.Str = str;
|
||||
cb_user_data.ChainCallback = callback;
|
||||
cb_user_data.ChainCallbackUserData = user_data;
|
||||
return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data);
|
||||
}
|
||||
|
||||
bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||
{
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
|
||||
InputTextCallback_UserData cb_user_data;
|
||||
cb_user_data.Str = str;
|
||||
cb_user_data.ChainCallback = callback;
|
||||
cb_user_data.ChainCallbackUserData = user_data;
|
||||
return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
|
||||
}
|
||||
22
src/imgui/imgui_stdlib.h
Normal file
22
src/imgui/imgui_stdlib.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
|
||||
// This is also an example of how you may wrap your own similar types.
|
||||
|
||||
// Compatibility:
|
||||
// - std::string support is only guaranteed to work from C++11.
|
||||
// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
|
||||
|
||||
// Changelog:
|
||||
// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ImGui
|
||||
{
|
||||
// ImGui::InputText() with std::string
|
||||
// Because text input needs dynamic resizing, we need to setup a callback to grow the capacity
|
||||
IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||
IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||
IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||
}
|
||||
|
|
@ -4815,8 +4815,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|||
PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
|
||||
PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
||||
PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
|
||||
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
|
||||
bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove);
|
||||
PopStyleVar(2);
|
||||
PopStyleVar(3);
|
||||
PopStyleColor();
|
||||
if (!child_visible)
|
||||
{
|
||||
|
|
@ -5454,7 +5455,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|||
// Test if cursor is vertically visible
|
||||
if (cursor_offset.y - g.FontSize < scroll_y)
|
||||
scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
|
||||
else if (cursor_offset.y - inner_size.y >= scroll_y)
|
||||
else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
|
||||
scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
|
||||
const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
|
||||
scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
4086
src/libslic3r/CutSurface.cpp
Normal file
File diff suppressed because it is too large
Load diff
78
src/libslic3r/CutSurface.hpp
Normal file
78
src/libslic3r/CutSurface.hpp
Normal 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_
|
||||
|
|
@ -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
2185
src/libslic3r/Emboss.cpp
Normal file
File diff suppressed because it is too large
Load diff
477
src/libslic3r/Emboss.hpp
Normal file
477
src/libslic3r/Emboss.hpp
Normal 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_
|
||||
143
src/libslic3r/EmbossShape.hpp
Normal file
143
src/libslic3r/EmbossShape.hpp
Normal 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_
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
28
src/libslic3r/ExPolygonSerialize.hpp
Normal file
28
src/libslic3r/ExPolygonSerialize.hpp
Normal 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_
|
||||
82
src/libslic3r/ExPolygonsIndex.cpp
Normal file
82
src/libslic3r/ExPolygonsIndex.cpp
Normal 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; }
|
||||
74
src/libslic3r/ExPolygonsIndex.hpp
Normal file
74
src/libslic3r/ExPolygonsIndex.hpp
Normal 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_
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
49
src/libslic3r/IntersectionPoints.cpp
Normal file
49
src/libslic3r/IntersectionPoints.cpp
Normal 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)); }
|
||||
}
|
||||
27
src/libslic3r/IntersectionPoints.hpp
Normal file
27
src/libslic3r/IntersectionPoints.hpp
Normal 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_
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
547
src/libslic3r/NSVGUtils.cpp
Normal 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 ¶m);
|
||||
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m);
|
||||
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m);
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m)
|
||||
{
|
||||
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 ¶m)
|
||||
{
|
||||
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 ¶m)
|
||||
{
|
||||
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 ¶m)
|
||||
{
|
||||
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 ¶m)
|
||||
{
|
||||
// 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
|
||||
86
src/libslic3r/NSVGUtils.hpp
Normal file
86
src/libslic3r/NSVGUtils.hpp
Normal 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 ¶m);
|
||||
|
||||
// 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 ¶m);
|
||||
|
||||
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_
|
||||
|
|
@ -59,65 +59,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
|
|||
|
||||
void Point::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
191
src/libslic3r/TextConfiguration.hpp
Normal file
191
src/libslic3r/TextConfiguration.hpp
Normal 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
25
src/libslic3r/Timer.cpp
Normal 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
96
src/libslic3r/Timer.hpp
Normal 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_
|
||||
332
src/libslic3r/Triangulation.cpp
Normal file
332
src/libslic3r/Triangulation.cpp
Normal 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;
|
||||
}
|
||||
76
src/libslic3r/Triangulation.hpp
Normal file
76
src/libslic3r/Triangulation.hpp
Normal 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_
|
||||
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = """; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '\r': replacement = "
"; break;
|
||||
case '\n': replacement = "
"; break;
|
||||
case '\t': replacement = "	"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
text.replace(pos, 1, replacement);
|
||||
pos += replacement.size();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string xml_unescape(std::string s)
|
||||
{
|
||||
std::string ret;
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Gizmos/GLGizmosCommon.hpp
|
||||
GUI/Gizmos/GLGizmoBase.cpp
|
||||
GUI/Gizmos/GLGizmoBase.hpp
|
||||
GUI/Gizmos/GLGizmoEmboss.cpp
|
||||
GUI/Gizmos/GLGizmoEmboss.hpp
|
||||
GUI/Gizmos/GLGizmoMove.cpp
|
||||
GUI/Gizmos/GLGizmoMove.hpp
|
||||
GUI/Gizmos/GLGizmoRotate.cpp
|
||||
|
|
@ -144,6 +146,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Gizmos/GLGizmoPainterBase.hpp
|
||||
GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
GUI/Gizmos/GLGizmoSimplify.hpp
|
||||
GUI/Gizmos/GLGizmoSVG.cpp
|
||||
GUI/Gizmos/GLGizmoSVG.hpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
|
||||
#GUI/Gizmos/GLGizmoFaceDetector.cpp
|
||||
|
|
@ -152,8 +156,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Gizmos/GLGizmoMeasure.hpp
|
||||
GUI/Gizmos/GLGizmoSeam.cpp
|
||||
GUI/Gizmos/GLGizmoSeam.hpp
|
||||
GUI/Gizmos/GLGizmoText.cpp
|
||||
GUI/Gizmos/GLGizmoText.hpp
|
||||
#GUI/Gizmos/GLGizmoText.cpp
|
||||
#GUI/Gizmos/GLGizmoText.hpp
|
||||
GUI/Gizmos/GLGizmoMeshBoolean.cpp
|
||||
GUI/Gizmos/GLGizmoMeshBoolean.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
|
|
@ -194,6 +198,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/GUI_Geometry.hpp
|
||||
GUI/I18N.cpp
|
||||
GUI/I18N.hpp
|
||||
GUI/IconManager.cpp
|
||||
GUI/IconManager.hpp
|
||||
GUI/MainFrame.cpp
|
||||
GUI/MainFrame.hpp
|
||||
GUI/BBLTopbar.cpp
|
||||
|
|
@ -303,6 +309,10 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/RemovableDriveManager.hpp
|
||||
GUI/SendSystemInfoDialog.cpp
|
||||
GUI/SendSystemInfoDialog.hpp
|
||||
GUI/SurfaceDrag.cpp
|
||||
GUI/SurfaceDrag.hpp
|
||||
GUI/TextLines.cpp
|
||||
GUI/TextLines.hpp
|
||||
GUI/PlateSettingsDialog.cpp
|
||||
GUI/PlateSettingsDialog.hpp
|
||||
GUI/ImGuiWrapper.hpp
|
||||
|
|
@ -329,13 +339,21 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/UpdateDialogs.cpp
|
||||
GUI/UpdateDialogs.hpp
|
||||
GUI/Jobs/Job.hpp
|
||||
GUI/Jobs/Job.cpp
|
||||
GUI/Jobs/PlaterJob.hpp
|
||||
GUI/Jobs/PlaterJob.cpp
|
||||
GUI/Jobs/Worker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.cpp
|
||||
GUI/Jobs/BusyCursorJob.hpp
|
||||
GUI/Jobs/PlaterWorker.hpp
|
||||
GUI/Jobs/UpgradeNetworkJob.hpp
|
||||
GUI/Jobs/UpgradeNetworkJob.cpp
|
||||
GUI/Jobs/ArrangeJob.hpp
|
||||
GUI/Jobs/ArrangeJob.cpp
|
||||
GUI/Jobs/CreateFontNameImageJob.cpp
|
||||
GUI/Jobs/CreateFontNameImageJob.hpp
|
||||
GUI/Jobs/CreateFontStyleImagesJob.cpp
|
||||
GUI/Jobs/CreateFontStyleImagesJob.hpp
|
||||
GUI/Jobs/EmbossJob.cpp
|
||||
GUI/Jobs/EmbossJob.hpp
|
||||
GUI/Jobs/OrientJob.hpp
|
||||
GUI/Jobs/OrientJob.cpp
|
||||
GUI/Jobs/RotoptimizeJob.hpp
|
||||
|
|
@ -353,6 +371,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Jobs/BindJob.cpp
|
||||
GUI/Jobs/NotificationProgressIndicator.hpp
|
||||
GUI/Jobs/NotificationProgressIndicator.cpp
|
||||
GUI/Jobs/ThreadSafeQueue.hpp
|
||||
GUI/Jobs/SLAImportDialog.hpp
|
||||
GUI/PhysicalPrinterDialog.hpp
|
||||
GUI/PhysicalPrinterDialog.cpp
|
||||
GUI/ProgressStatusBar.hpp
|
||||
|
|
@ -449,6 +469,10 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/Http.hpp
|
||||
Utils/FixModelByWin10.cpp
|
||||
Utils/FixModelByWin10.hpp
|
||||
Utils/EmbossStyleManager.cpp
|
||||
Utils/EmbossStyleManager.hpp
|
||||
Utils/FontConfigHelp.cpp
|
||||
Utils/FontConfigHelp.hpp
|
||||
Utils/Bonjour.cpp
|
||||
Utils/Bonjour.hpp
|
||||
Utils/FileHelp.cpp
|
||||
|
|
@ -457,6 +481,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/PresetUpdater.hpp
|
||||
Utils/Process.cpp
|
||||
Utils/Process.hpp
|
||||
Utils/RaycastManager.cpp
|
||||
Utils/RaycastManager.hpp
|
||||
Utils/Profile.hpp
|
||||
Utils/UndoRedo.cpp
|
||||
Utils/UndoRedo.hpp
|
||||
|
|
@ -480,8 +506,10 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/PrintHost.hpp
|
||||
Utils/Serial.cpp
|
||||
Utils/Serial.hpp
|
||||
Utils/MKS.hpp
|
||||
Utils/MKS.cpp
|
||||
Utils/MKS.hpp
|
||||
Utils/WxFontUtils.cpp
|
||||
Utils/WxFontUtils.hpp
|
||||
Utils/Duet.cpp
|
||||
Utils/Duet.hpp
|
||||
Utils/FlashAir.cpp
|
||||
|
|
@ -591,7 +619,7 @@ endif ()
|
|||
if (UNIX AND NOT APPLE)
|
||||
find_package(GTK${SLIC3R_GTK} REQUIRED)
|
||||
target_include_directories(libslic3r_gui PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS})
|
||||
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES})
|
||||
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig)
|
||||
|
||||
# We add GStreamer for bambu:/// support.
|
||||
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
|
||||
|
|
|
|||
|
|
@ -940,9 +940,9 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
//BBS: add outline related logic
|
||||
if (with_outline && volume.first->selected)
|
||||
volume.first->render_with_outline(view_matrix * model_matrix);
|
||||
else
|
||||
//if (with_outline && volume.first->selected)
|
||||
// volume.first->render_with_outline(view_matrix * model_matrix);
|
||||
//else
|
||||
volume.first->render();
|
||||
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
|
|
|
|||
|
|
@ -243,14 +243,15 @@ public:
|
|||
|
||||
const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; }
|
||||
void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); }
|
||||
Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); }
|
||||
double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); }
|
||||
|
||||
void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
|
||||
Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
|
||||
double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); }
|
||||
|
||||
void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
|
||||
|
|
@ -262,7 +263,7 @@ public:
|
|||
void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
|
||||
Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
|
||||
double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); }
|
||||
|
||||
void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
|
||||
|
|
@ -270,26 +271,27 @@ public:
|
|||
|
||||
const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; }
|
||||
void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); }
|
||||
Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); }
|
||||
double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); }
|
||||
|
||||
void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
|
||||
Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
|
||||
double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); }
|
||||
|
||||
void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
|
||||
Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
|
||||
double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); }
|
||||
|
||||
void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
|
||||
Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
|
||||
double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); }
|
||||
|
||||
void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#include "MainFrame.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "Jobs/BoostThreadWorker.hpp"
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
#include "Widgets/WebView.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -374,6 +376,8 @@ wxString get_fail_reason(int code)
|
|||
|
||||
m_status_bar = std::make_shared<BBLStatusBarBind>(m_simplebook);
|
||||
|
||||
m_worker = std::make_unique<PlaterWorker<BoostThreadWorker>>(this, m_status_bar, "bind_worker");
|
||||
|
||||
auto button_panel = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, BIND_DIALOG_BUTTON_PANEL_SIZE);
|
||||
button_panel->SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer *m_sizer_button = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
|
@ -513,10 +517,7 @@ wxString get_fail_reason(int code)
|
|||
|
||||
void BindMachineDialog::on_destroy()
|
||||
{
|
||||
if (m_bind_job) {
|
||||
m_bind_job->cancel();
|
||||
m_bind_job->join();
|
||||
}
|
||||
m_worker.get()->cancel_all();
|
||||
}
|
||||
|
||||
void BindMachineDialog::on_close(wxCloseEvent &event)
|
||||
|
|
@ -572,7 +573,7 @@ wxString get_fail_reason(int code)
|
|||
agent->track_update_property("dev_ota_version", m_machine_info->get_ota_version());
|
||||
|
||||
m_simplebook->SetSelection(0);
|
||||
m_bind_job = std::make_shared<BindJob>(m_status_bar, wxGetApp().plater(), m_machine_info->dev_id, m_machine_info->dev_ip, m_machine_info->bind_sec_link);
|
||||
auto m_bind_job = std::make_unique<BindJob>(m_machine_info->dev_id, m_machine_info->dev_ip, m_machine_info->bind_sec_link);
|
||||
|
||||
if (m_machine_info && (m_machine_info->get_printer_series() == PrinterSeries::SERIES_X1)) {
|
||||
m_bind_job->set_improved(false);
|
||||
|
|
@ -582,7 +583,7 @@ wxString get_fail_reason(int code)
|
|||
}
|
||||
|
||||
m_bind_job->set_event_handle(this);
|
||||
m_bind_job->start();
|
||||
replace_job(*m_worker, std::move(m_bind_job));
|
||||
}
|
||||
|
||||
void BindMachineDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "Jobs/BindJob.hpp"
|
||||
#include "BBLStatusBar.hpp"
|
||||
#include "BBLStatusBarBind.hpp"
|
||||
#include "Jobs/Worker.hpp"
|
||||
|
||||
#define BIND_DIALOG_GREY200 wxColour(248, 248, 248)
|
||||
#define BIND_DIALOG_GREY800 wxColour(50, 58, 61)
|
||||
|
|
@ -77,8 +78,8 @@ private:
|
|||
std::shared_ptr<int> m_tocken;
|
||||
|
||||
MachineObject * m_machine_info{nullptr};
|
||||
std::shared_ptr<BindJob> m_bind_job;
|
||||
std::shared_ptr<BBLStatusBarBind> m_status_bar;
|
||||
std::unique_ptr<Worker> m_worker;
|
||||
|
||||
public:
|
||||
BindMachineDialog(Plater *plater = nullptr);
|
||||
|
|
|
|||
|
|
@ -1437,12 +1437,10 @@ void CalibrationPresetPage::on_cali_finished_job()
|
|||
void CalibrationPresetPage::on_cali_cancel_job()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "CalibrationWizard::print_job: enter canceled";
|
||||
if (CalibUtils::print_job) {
|
||||
if (CalibUtils::print_job->is_running()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "calibration_print_job: canceled";
|
||||
CalibUtils::print_job->cancel();
|
||||
}
|
||||
CalibUtils::print_job->join();
|
||||
if (CalibUtils::print_worker) {
|
||||
BOOST_LOG_TRIVIAL(info) << "calibration_print_job: canceled";
|
||||
CalibUtils::print_worker->cancel_all();
|
||||
CalibUtils::print_worker->wait_for_idle();
|
||||
}
|
||||
|
||||
m_sending_panel->reset();
|
||||
|
|
|
|||
|
|
@ -1396,12 +1396,10 @@ void CalibrationFlowCoarseSavePage::on_cali_finished_job()
|
|||
void CalibrationFlowCoarseSavePage::on_cali_cancel_job()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "CalibrationWizard::print_job: enter canceled";
|
||||
if (CalibUtils::print_job) {
|
||||
if (CalibUtils::print_job->is_running()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "calibration_print_job: canceled";
|
||||
CalibUtils::print_job->cancel();
|
||||
}
|
||||
CalibUtils::print_job->join();
|
||||
if (CalibUtils::print_worker) {
|
||||
BOOST_LOG_TRIVIAL(info) << "calibration_print_job: canceled";
|
||||
CalibUtils::print_worker->cancel_all();
|
||||
CalibUtils::print_worker->wait_for_idle();
|
||||
}
|
||||
|
||||
m_sending_panel->reset();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
#include "wxExtensions.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Jobs/BoostThreadWorker.hpp"
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
|
||||
#define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1)
|
||||
|
||||
|
|
@ -59,7 +61,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
|||
m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70)));
|
||||
m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70)));
|
||||
m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70)));
|
||||
|
||||
|
||||
m_worker = std::make_unique<PlaterWorker<BoostThreadWorker>>(this, m_status_bar, "download_worker");
|
||||
|
||||
//mode Download Failed
|
||||
auto m_panel_download_failed = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
|
|
@ -144,7 +147,7 @@ bool DownloadProgressDialog::Show(bool show)
|
|||
{
|
||||
if (show) {
|
||||
m_simplebook_status->SetSelection(0);
|
||||
m_upgrade_job = make_job(m_status_bar);
|
||||
auto m_upgrade_job = make_job();
|
||||
m_upgrade_job->set_event_handle(this);
|
||||
m_status_bar->set_progress(0);
|
||||
Bind(EVT_UPGRADE_NETWORK_SUCCESS, [this](wxCommandEvent& evt) {
|
||||
|
|
@ -182,23 +185,17 @@ bool DownloadProgressDialog::Show(bool show)
|
|||
});
|
||||
|
||||
m_status_bar->set_cancel_callback_fina([this]() {
|
||||
if (m_upgrade_job) {
|
||||
m_upgrade_job->cancel();
|
||||
//EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
m_worker->cancel_all();
|
||||
});
|
||||
m_upgrade_job->start();
|
||||
|
||||
replace_job(*m_worker, std::move(m_upgrade_job));
|
||||
}
|
||||
return DPIDialog::Show(show);
|
||||
}
|
||||
|
||||
void DownloadProgressDialog::on_close(wxCloseEvent& event)
|
||||
{
|
||||
if (m_upgrade_job) {
|
||||
m_upgrade_job->cancel();
|
||||
m_upgrade_job->join();
|
||||
}
|
||||
m_worker.get()->cancel_all();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +205,7 @@ void DownloadProgressDialog::on_dpi_changed(const wxRect &suggested_rect) {}
|
|||
|
||||
void DownloadProgressDialog::update_release_note(std::string release_note, std::string version) {}
|
||||
|
||||
std::shared_ptr<UpgradeNetworkJob> DownloadProgressDialog::make_job(std::shared_ptr<ProgressIndicator> pri) { return std::make_shared<UpgradeNetworkJob>(pri); }
|
||||
std::unique_ptr<UpgradeNetworkJob> DownloadProgressDialog::make_job() { return std::make_unique<UpgradeNetworkJob>(); }
|
||||
|
||||
void DownloadProgressDialog::on_finish() { wxGetApp().restart_networking(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "Widgets/Button.hpp"
|
||||
#include "BBLStatusBar.hpp"
|
||||
#include "BBLStatusBarSend.hpp"
|
||||
#include "Jobs/Worker.hpp"
|
||||
#include "Jobs/UpgradeNetworkJob.hpp"
|
||||
|
||||
class wxBoxSizer;
|
||||
|
|
@ -47,11 +48,11 @@ public:
|
|||
wxSimplebook* m_simplebook_status{nullptr};
|
||||
|
||||
std::shared_ptr<BBLStatusBarSend> m_status_bar;
|
||||
std::shared_ptr<UpgradeNetworkJob> m_upgrade_job { nullptr };
|
||||
std::unique_ptr<Worker> m_worker;
|
||||
wxPanel * m_panel_download;
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<UpgradeNetworkJob> make_job(std::shared_ptr<ProgressIndicator> pri);
|
||||
virtual std::unique_ptr<UpgradeNetworkJob> make_job();
|
||||
virtual void on_finish();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_he
|
|||
std::string layer_time = ImGui::ColorMarkerStart + _u8L("Layer Time: ") + ImGui::ColorMarkerEnd;
|
||||
std::string fanspeed = ImGui::ColorMarkerStart + _u8L("Fan: ") + ImGui::ColorMarkerEnd;
|
||||
std::string temperature = ImGui::ColorMarkerStart + _u8L("Temperature: ") + ImGui::ColorMarkerEnd;
|
||||
const float item_size = imgui.calc_text_size("X: 000.000 ").x;
|
||||
const float item_size = imgui.calc_text_size(std::string_view{"X: 000.000 "}).x;
|
||||
const float item_spacing = imgui.get_item_spacing().x;
|
||||
const float window_padding = ImGui::GetStyle().WindowPadding.x;
|
||||
|
||||
|
|
|
|||
|
|
@ -1588,7 +1588,6 @@ void GLCanvas3D::enable_legend_texture(bool enable)
|
|||
void GLCanvas3D::enable_picking(bool enable)
|
||||
{
|
||||
m_picking_enabled = enable;
|
||||
m_selection.set_mode(Selection::Instance);
|
||||
}
|
||||
|
||||
void GLCanvas3D::enable_moving(bool enable)
|
||||
|
|
@ -2171,7 +2170,17 @@ std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
|
|||
|
||||
void GLCanvas3D::mirror_selection(Axis axis)
|
||||
{
|
||||
m_selection.mirror(axis);
|
||||
TransformationType transformation_type;
|
||||
if (wxGetApp().obj_manipul()->is_local_coordinates())
|
||||
transformation_type.set_local();
|
||||
else if (wxGetApp().obj_manipul()->is_instance_coordinates())
|
||||
transformation_type.set_instance();
|
||||
|
||||
transformation_type.set_relative();
|
||||
|
||||
m_selection.setup_cache();
|
||||
m_selection.mirror(axis, transformation_type);
|
||||
|
||||
do_mirror(L("Mirror Object"));
|
||||
// BBS
|
||||
//wxGetApp().obj_manipul()->set_dirty();
|
||||
|
|
@ -3364,7 +3373,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
|
|||
else
|
||||
displacement = multiplier * direction;
|
||||
|
||||
m_selection.translate(displacement);
|
||||
TransformationType trafo_type;
|
||||
trafo_type.set_relative();
|
||||
m_selection.translate(displacement, trafo_type);
|
||||
m_dirty = true;
|
||||
}
|
||||
);}
|
||||
|
|
@ -4136,7 +4147,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
}
|
||||
}
|
||||
|
||||
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D);
|
||||
TransformationType trafo_type;
|
||||
trafo_type.set_relative();
|
||||
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type);
|
||||
if (current_printer_technology() == ptFFF && (fff_print()->config().print_sequence == PrintSequence::ByObject))
|
||||
update_sequential_clearance();
|
||||
// BBS
|
||||
|
|
@ -4364,6 +4377,40 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
else
|
||||
evt.Skip();
|
||||
|
||||
// Detection of doubleclick on text to open emboss edit window
|
||||
auto type = m_gizmos.get_current_type();
|
||||
if (evt.LeftDClick() && !m_hover_volume_idxs.empty() &&
|
||||
(type == GLGizmosManager::EType::Undefined ||
|
||||
type == GLGizmosManager::EType::Move ||
|
||||
type == GLGizmosManager::EType::Rotate ||
|
||||
type == GLGizmosManager::EType::Scale ||
|
||||
type == GLGizmosManager::EType::Emboss ||
|
||||
type == GLGizmosManager::EType::Svg) ) {
|
||||
for (int hover_volume_id : m_hover_volume_idxs) {
|
||||
const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id];
|
||||
int object_idx = hover_gl_volume.object_idx();
|
||||
if (object_idx < 0 || static_cast<size_t>(object_idx) >= m_model->objects.size()) continue;
|
||||
const ModelObject* hover_object = m_model->objects[object_idx];
|
||||
int hover_volume_idx = hover_gl_volume.volume_idx();
|
||||
if (hover_volume_idx < 0 || static_cast<size_t>(hover_volume_idx) >= hover_object->volumes.size()) continue;
|
||||
const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx];
|
||||
|
||||
if (hover_volume->text_configuration.has_value()) {
|
||||
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
|
||||
if (type != GLGizmosManager::EType::Emboss)
|
||||
m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss);
|
||||
wxGetApp().obj_list()->update_selections();
|
||||
return;
|
||||
} else if (hover_volume->emboss_shape.has_value()) {
|
||||
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
|
||||
if (type != GLGizmosManager::EType::Svg)
|
||||
m_gizmos.open_gizmo(GLGizmosManager::EType::Svg);
|
||||
wxGetApp().obj_list()->update_selections();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_moving)
|
||||
show_sinking_contours();
|
||||
|
||||
|
|
@ -4460,6 +4507,9 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
|||
int instance_idx = v->instance_idx();
|
||||
int volume_idx = v->volume_idx();
|
||||
|
||||
if (volume_idx < 0)
|
||||
continue;
|
||||
|
||||
std::pair<int, int> done_id(object_idx, instance_idx);
|
||||
|
||||
if (0 <= object_idx && object_idx < (int)m_model->objects.size()) {
|
||||
|
|
@ -4469,10 +4519,10 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
|||
ModelObject* model_object = m_model->objects[object_idx];
|
||||
if (model_object != nullptr) {
|
||||
if (selection_mode == Selection::Instance)
|
||||
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||
else if (selection_mode == Selection::Volume) {
|
||||
if (model_object->volumes[volume_idx]->get_offset() != v->get_volume_offset()) {
|
||||
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
||||
if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) {
|
||||
model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
|
|
@ -4564,26 +4614,26 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
|||
Selection::EMode selection_mode = m_selection.get_mode();
|
||||
|
||||
for (const GLVolume* v : m_volumes.volumes) {
|
||||
int object_idx = v->object_idx();
|
||||
const int object_idx = v->object_idx();
|
||||
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
|
||||
continue;
|
||||
|
||||
int instance_idx = v->instance_idx();
|
||||
int volume_idx = v->volume_idx();
|
||||
const int instance_idx = v->instance_idx();
|
||||
const int volume_idx = v->volume_idx();
|
||||
|
||||
if (volume_idx < 0)
|
||||
continue;
|
||||
|
||||
done.insert(std::pair<int, int>(object_idx, instance_idx));
|
||||
|
||||
// Rotate instances/volumes.
|
||||
ModelObject* model_object = m_model->objects[object_idx];
|
||||
if (model_object != nullptr) {
|
||||
if (selection_mode == Selection::Instance) {
|
||||
model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation());
|
||||
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
||||
}
|
||||
if (selection_mode == Selection::Instance)
|
||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||
else if (selection_mode == Selection::Volume) {
|
||||
if (model_object->volumes[volume_idx]->get_rotation() != v->get_volume_rotation()) {
|
||||
model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation());
|
||||
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
||||
if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) {
|
||||
model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
|
|
@ -4645,27 +4695,27 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
|
|||
Selection::EMode selection_mode = m_selection.get_mode();
|
||||
|
||||
for (const GLVolume* v : m_volumes.volumes) {
|
||||
int object_idx = v->object_idx();
|
||||
const int object_idx = v->object_idx();
|
||||
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
|
||||
continue;
|
||||
|
||||
int instance_idx = v->instance_idx();
|
||||
int volume_idx = v->volume_idx();
|
||||
const int instance_idx = v->instance_idx();
|
||||
const int volume_idx = v->volume_idx();
|
||||
|
||||
if (volume_idx < 0)
|
||||
continue;
|
||||
|
||||
done.insert(std::pair<int, int>(object_idx, instance_idx));
|
||||
|
||||
// Rotate instances/volumes
|
||||
ModelObject* model_object = m_model->objects[object_idx];
|
||||
if (model_object != nullptr) {
|
||||
if (selection_mode == Selection::Instance) {
|
||||
model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor());
|
||||
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
||||
}
|
||||
if (selection_mode == Selection::Instance)
|
||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||
else if (selection_mode == Selection::Volume) {
|
||||
if (model_object->volumes[volume_idx]->get_scaling_factor() != v->get_volume_scaling_factor()) {
|
||||
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
||||
model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor());
|
||||
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
||||
if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) {
|
||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||
model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
|
|
@ -4756,10 +4806,10 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
|
|||
ModelObject* model_object = m_model->objects[object_idx];
|
||||
if (model_object != nullptr) {
|
||||
if (selection_mode == Selection::Instance)
|
||||
model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror());
|
||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||
else if (selection_mode == Selection::Volume) {
|
||||
if (model_object->volumes[volume_idx]->get_mirror() != v->get_volume_mirror()) {
|
||||
model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror());
|
||||
if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) {
|
||||
model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
|
|
@ -5210,6 +5260,14 @@ bool GLCanvas3D::is_object_sinking(int object_idx) const
|
|||
return false;
|
||||
}
|
||||
|
||||
void GLCanvas3D::apply_retina_scale(Vec2d &screen_coordinate) const
|
||||
{
|
||||
#if ENABLE_RETINA_GL
|
||||
double scale = static_cast<double>(m_retina_helper->get_scale_factor());
|
||||
screen_coordinate *= scale;
|
||||
#endif // ENABLE_RETINA_GL
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_shown_on_screen() const
|
||||
{
|
||||
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
|
||||
|
|
@ -7148,12 +7206,14 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
|
|||
|
||||
auto* m_notification = wxGetApp().plater()->get_notification_manager();
|
||||
m_notification->set_scale(sc);
|
||||
m_gizmos.set_overlay_scale(sc);
|
||||
#else
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
m_main_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size * scale);
|
||||
m_assemble_view_toolbar.set_icons_size(size);
|
||||
m_separator_toolbar.set_icons_size(size);
|
||||
collapse_toolbar.set_icons_size(size / 2.0);
|
||||
m_gizmos.set_overlay_icon_size(size);
|
||||
#endif // ENABLE_RETINA_GL
|
||||
|
||||
// Update collapse toolbar
|
||||
|
|
@ -7210,31 +7270,6 @@ void GLCanvas3D::_render_overlays()
|
|||
_render_assemble_control();
|
||||
_render_assemble_info();
|
||||
|
||||
// main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed
|
||||
// to correctly place them
|
||||
#if ENABLE_RETINA_GL
|
||||
const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/);
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
m_main_toolbar.set_scale(scale);
|
||||
m_assemble_view_toolbar.set_scale(scale);
|
||||
m_separator_toolbar.set_scale(scale);
|
||||
wxGetApp().plater()->get_collapse_toolbar().set_scale(scale / 2.0);
|
||||
m_gizmos.set_overlay_scale(scale);
|
||||
#else
|
||||
// BBS adjust display scale
|
||||
const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/));
|
||||
const float gizmo_size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale());
|
||||
//const float size = int(GLToolbar::Default_Icons_Size);
|
||||
//const float gizmo_size = int(GLGizmosManager::Default_Icons_Size);
|
||||
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
m_main_toolbar.set_icons_size(gizmo_size);
|
||||
m_assemble_view_toolbar.set_icons_size(gizmo_size);
|
||||
m_separator_toolbar.set_icons_size(gizmo_size);
|
||||
wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size / 2.0);
|
||||
m_gizmos.set_overlay_icon_size(gizmo_size);
|
||||
#endif // ENABLE_RETINA_GL
|
||||
|
||||
_render_separator_toolbar_right();
|
||||
_render_separator_toolbar_left();
|
||||
_render_main_toolbar();
|
||||
|
|
@ -8077,7 +8112,7 @@ void GLCanvas3D::_render_assemble_control() const
|
|||
const float text_size_x = std::max(imgui->calc_text_size(_L("Reset direction")).x + 2 * ImGui::GetStyle().FramePadding.x,
|
||||
std::max(imgui->calc_text_size(_L("Explosion Ratio")).x, imgui->calc_text_size(_L("Section View")).x));
|
||||
const float slider_width = 75.0f;
|
||||
const float value_size = imgui->calc_text_size("3.00").x + text_padding * 2;
|
||||
const float value_size = imgui->calc_text_size(std::string_view{"3.00"}).x + text_padding * 2;
|
||||
const float item_spacing = imgui->get_item_spacing().x;
|
||||
ImVec2 window_padding = ImGui::GetStyle().WindowPadding;
|
||||
|
||||
|
|
@ -9511,5 +9546,108 @@ void GLCanvas3D::GizmoHighlighter::blink()
|
|||
invalidate();
|
||||
}
|
||||
|
||||
const ModelVolume *get_model_volume(const GLVolume &v, const Model &model)
|
||||
{
|
||||
const ModelVolume * ret = nullptr;
|
||||
|
||||
if (v.object_idx() < (int)model.objects.size()) {
|
||||
const ModelObject *obj = model.objects[v.object_idx()];
|
||||
if (v.volume_idx() < (int)obj->volumes.size())
|
||||
ret = obj->volumes[v.volume_idx()];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects)
|
||||
{
|
||||
for (const ModelObject *obj : objects)
|
||||
for (ModelVolume *vol : obj->volumes)
|
||||
if (vol->id() == volume_id)
|
||||
return vol;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObject& object) {
|
||||
if (v.volume_idx() < 0)
|
||||
return nullptr;
|
||||
|
||||
size_t volume_idx = static_cast<size_t>(v.volume_idx());
|
||||
if (volume_idx >= object.volumes.size())
|
||||
return nullptr;
|
||||
|
||||
return object.volumes[volume_idx];
|
||||
}
|
||||
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects)
|
||||
{
|
||||
if (v.object_idx() < 0)
|
||||
return nullptr;
|
||||
size_t objext_idx = static_cast<size_t>(v.object_idx());
|
||||
if (objext_idx >= objects.size())
|
||||
return nullptr;
|
||||
if (objects[objext_idx] == nullptr)
|
||||
return nullptr;
|
||||
return get_model_volume(v, *objects[objext_idx]);
|
||||
}
|
||||
|
||||
GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas) {
|
||||
int hovered_id_signed = canvas.get_first_hover_volume_idx();
|
||||
if (hovered_id_signed < 0)
|
||||
return nullptr;
|
||||
|
||||
size_t hovered_id = static_cast<size_t>(hovered_id_signed);
|
||||
const GLVolumePtrs &volumes = canvas.get_volumes().volumes;
|
||||
if (hovered_id >= volumes.size())
|
||||
return nullptr;
|
||||
|
||||
return volumes[hovered_id];
|
||||
}
|
||||
|
||||
GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas) {
|
||||
const GLVolume *gl_volume = get_selected_gl_volume(canvas.get_selection());
|
||||
if (gl_volume == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
|
||||
for (GLVolume *v : gl_volumes)
|
||||
if (v->composite_id == gl_volume->composite_id)
|
||||
return v;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model) {
|
||||
return get_model_object(gl_volume, model.objects);
|
||||
}
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects) {
|
||||
if (gl_volume.object_idx() < 0)
|
||||
return nullptr;
|
||||
size_t objext_idx = static_cast<size_t>(gl_volume.object_idx());
|
||||
if (objext_idx >= objects.size())
|
||||
return nullptr;
|
||||
return objects[objext_idx];
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model& model) {
|
||||
return get_model_instance(gl_volume, model.objects);
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects) {
|
||||
if (gl_volume.instance_idx() < 0)
|
||||
return nullptr;
|
||||
ModelObject *object = get_model_object(gl_volume, objects);
|
||||
return get_model_instance(gl_volume, *object);
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object) {
|
||||
if (gl_volume.instance_idx() < 0)
|
||||
return nullptr;
|
||||
size_t instance_idx = static_cast<size_t>(gl_volume.instance_idx());
|
||||
if (instance_idx >= object.instances.size())
|
||||
return nullptr;
|
||||
return object.instances[instance_idx];
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -966,6 +966,12 @@ public:
|
|||
Size get_canvas_size() const;
|
||||
Vec2d get_local_mouse_position() const;
|
||||
|
||||
// store opening position of menu
|
||||
std::optional<Vec2d> m_popup_menu_positon; // position of mouse right click
|
||||
void set_popup_menu_position(const Vec2d &position) { m_popup_menu_positon = position; }
|
||||
const std::optional<Vec2d>& get_popup_menu_position() const { return m_popup_menu_positon; }
|
||||
void clear_popup_menu_position() { m_popup_menu_positon.reset(); }
|
||||
|
||||
void set_tooltip(const std::string& tooltip);
|
||||
|
||||
// the following methods add a snapshot to the undo/redo stack, unless the given string is empty
|
||||
|
|
@ -1107,6 +1113,8 @@ public:
|
|||
|
||||
bool is_object_sinking(int object_idx) const;
|
||||
|
||||
void apply_retina_scale(Vec2d &screen_coordinate) const;
|
||||
|
||||
void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
|
||||
|
||||
// Convert the screen space coordinate to an object space coordinate.
|
||||
|
|
@ -1233,6 +1241,21 @@ private:
|
|||
float get_overlay_window_width() { return 0; /*LayersEditing::get_overlay_window_width();*/ }
|
||||
};
|
||||
|
||||
const ModelVolume *get_model_volume(const GLVolume &v, const Model &model);
|
||||
ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects);
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects);
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object);
|
||||
|
||||
GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas);
|
||||
GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas);
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model);
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects);
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model);
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects);
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object);
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2023 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Pavel Mikuš @Godrak, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
|
@ -15,6 +19,8 @@
|
|||
#include "format.hpp"
|
||||
//BBS: add partplate related logic
|
||||
#include "PartPlate.hpp"
|
||||
#include "Gizmos/GLGizmoEmboss.hpp"
|
||||
#include "Gizmos/GLGizmoSVG.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include "slic3r/Utils/FixModelByWin10.hpp"
|
||||
|
|
@ -273,24 +279,28 @@ wxBitmapBundle* SettingsFactory::get_category_bitmap(const std::string& category
|
|||
//-------------------------------------
|
||||
|
||||
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
||||
#ifdef __WINDOWS__
|
||||
const std::vector<std::pair<std::string, std::string>> MenuFactory::ADD_VOLUME_MENU_ITEMS = {
|
||||
static const constexpr std::array<std::pair<const char *, const char *>, 5> ADD_VOLUME_MENU_ITEMS = {{
|
||||
// menu_item Name menu_item bitmap name
|
||||
{L("Add part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
{L("Add support blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER
|
||||
{L("Add support enforcer"), "menu_support_enforcer"} // ~ModelVolumeType::SUPPORT_ENFORCER
|
||||
};
|
||||
#else
|
||||
const std::vector<std::pair<std::string, std::string>> MenuFactory::ADD_VOLUME_MENU_ITEMS = {
|
||||
{L("Add part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
{L("Add support blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER
|
||||
{L("Add support enforcer"), "menu_support_enforcer"} // ~ModelVolumeType::SUPPORT_ENFORCER
|
||||
};
|
||||
{L("Add support enforcer"), "menu_support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER
|
||||
}};
|
||||
|
||||
#endif
|
||||
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
||||
static const constexpr std::array<std::pair<const char *, const char *>, 3> TEXT_VOLUME_ICONS {{
|
||||
// menu_item Name menu_item bitmap name
|
||||
{L("Add text"), "add_text_part"}, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative text"), "add_text_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add text modifier"), "add_text_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
}};
|
||||
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
||||
static const constexpr std::array<std::pair<const char *, const char *>, 3> SVG_VOLUME_ICONS{{
|
||||
{L("Add SVG part"), "svg_part"}, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative SVG"), "svg_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add SVG modifier"), "svg_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
}};
|
||||
|
||||
static Plater* plater()
|
||||
{
|
||||
|
|
@ -428,12 +438,26 @@ std::vector<wxBitmapBundle*> MenuFactory::get_volume_bitmaps()
|
|||
{
|
||||
std::vector<wxBitmapBundle*> volume_bmps;
|
||||
volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size());
|
||||
for (auto item : ADD_VOLUME_MENU_ITEMS){
|
||||
if(!item.second.empty()){
|
||||
//volume_bmps.push_back(create_menu_bitmap(item.second));
|
||||
volume_bmps.push_back(get_bmp_bundle(item.second));
|
||||
}
|
||||
}
|
||||
for (const auto& item : ADD_VOLUME_MENU_ITEMS)
|
||||
volume_bmps.push_back(get_bmp_bundle(item.second));
|
||||
return volume_bmps;
|
||||
}
|
||||
|
||||
std::vector<wxBitmapBundle*> MenuFactory::get_text_volume_bitmaps()
|
||||
{
|
||||
std::vector<wxBitmapBundle*> volume_bmps;
|
||||
volume_bmps.reserve(TEXT_VOLUME_ICONS.size());
|
||||
for (const auto& item : TEXT_VOLUME_ICONS)
|
||||
volume_bmps.push_back(get_bmp_bundle(item.second));
|
||||
return volume_bmps;
|
||||
}
|
||||
|
||||
std::vector<wxBitmapBundle*> MenuFactory::get_svg_volume_bitmaps()
|
||||
{
|
||||
std::vector<wxBitmapBundle *> volume_bmps;
|
||||
volume_bmps.reserve(SVG_VOLUME_ICONS.size());
|
||||
for (const auto &item : SVG_VOLUME_ICONS)
|
||||
volume_bmps.push_back(get_bmp_bundle(item.second));
|
||||
return volume_bmps;
|
||||
}
|
||||
|
||||
|
|
@ -463,19 +487,6 @@ void MenuFactory::append_menu_item_delete(wxMenu* menu)
|
|||
#endif
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
append_menu_item(
|
||||
menu, wxID_ANY, _L("Edit Text"), "", [](wxCommandEvent &) { plater()->edit_text(); }, "", nullptr,
|
||||
[]() { return plater()->can_edit_text(); }, m_parent);
|
||||
#else
|
||||
append_menu_item(
|
||||
menu, wxID_ANY, _L("Edit Text"), "", [](wxCommandEvent &) { plater()->edit_text(); }, "", nullptr,
|
||||
[]() { return plater()->can_edit_text(); }, m_parent);
|
||||
#endif
|
||||
}
|
||||
|
||||
wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) {
|
||||
auto sub_menu = new wxMenu;
|
||||
|
||||
|
|
@ -509,6 +520,10 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
|
|||
},
|
||||
"", menu);
|
||||
}
|
||||
|
||||
append_menu_item_add_text(sub_menu, type);
|
||||
append_menu_item_add_svg(sub_menu, type);
|
||||
|
||||
sub_menu->AppendSeparator();
|
||||
for (auto &item : {L("Cube"), L("Cylinder"), L("Sphere"), L("Cone")}) {
|
||||
append_menu_item(
|
||||
|
|
@ -522,13 +537,69 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
|
|||
return sub_menu;
|
||||
}
|
||||
|
||||
static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType gizmo_type, wxMenu *menu, ModelVolumeType type, bool is_submenu_item) {
|
||||
auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) {
|
||||
const GLCanvas3D *canvas = plater()->canvas3D();
|
||||
const GLGizmosManager &mng = canvas->get_gizmos_manager();
|
||||
GLGizmoBase *gizmo_base = mng.get_gizmo(gizmo_type);
|
||||
|
||||
ModelVolumeType volume_type = type;
|
||||
// no selected object means create new object
|
||||
if (volume_type == ModelVolumeType::INVALID)
|
||||
volume_type = ModelVolumeType::MODEL_PART;
|
||||
|
||||
auto screen_position = canvas->get_popup_menu_position();
|
||||
if (gizmo_type == GLGizmosManager::Emboss) {
|
||||
auto emboss = dynamic_cast<GLGizmoEmboss *>(gizmo_base);
|
||||
assert(emboss != nullptr);
|
||||
if (emboss == nullptr) return;
|
||||
if (screen_position.has_value()) {
|
||||
emboss->create_volume(volume_type, *screen_position);
|
||||
} else {
|
||||
emboss->create_volume(volume_type);
|
||||
}
|
||||
} else if (gizmo_type == GLGizmosManager::Svg) {
|
||||
auto svg = dynamic_cast<GLGizmoSVG *>(gizmo_base);
|
||||
assert(svg != nullptr);
|
||||
if (svg == nullptr) return;
|
||||
if (screen_position.has_value()) {
|
||||
svg->create_volume(volume_type, *screen_position);
|
||||
} else {
|
||||
svg->create_volume(volume_type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER ||
|
||||
type == ModelVolumeType::INVALID // cannot use gizmo without selected object
|
||||
) {
|
||||
wxString item_name = wxString(is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": ") + name;
|
||||
menu->AppendSeparator();
|
||||
const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second;
|
||||
append_menu_item(menu, wxID_ANY, item_name, "", add_, icon_name, menu);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item/* = true*/){
|
||||
append_menu_itemm_add_(_L("Text"), GLGizmosManager::Emboss, menu, type, is_submenu_item);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/){
|
||||
append_menu_itemm_add_(_L("SVG"), GLGizmosManager::Svg, menu, type, is_submenu_item);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
|
||||
{
|
||||
// Update "add" items(delete old & create new) settings popupmenu
|
||||
for (auto& item : ADD_VOLUME_MENU_ITEMS) {
|
||||
const auto settings_id = menu->FindItem(_(item.first));
|
||||
if (settings_id != wxNOT_FOUND)
|
||||
menu->Destroy(settings_id);
|
||||
const wxString item_name = _(item.first);
|
||||
int item_id = menu->FindItem(item_name);
|
||||
if (item_id != wxNOT_FOUND)
|
||||
menu->Destroy(item_id);
|
||||
|
||||
item_id = menu->FindItem(item_name + ": " + _L("Text"));
|
||||
if (item_id != wxNOT_FOUND)
|
||||
menu->Destroy(item_id);
|
||||
}
|
||||
|
||||
for (size_t type = 0; type < ADD_VOLUME_MENU_ITEMS.size(); type++)
|
||||
|
|
@ -992,6 +1063,81 @@ void MenuFactory::append_menu_items_mirror(wxMenu* menu)
|
|||
[]() { return plater()->can_mirror(); }, m_parent);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
|
||||
{
|
||||
wxString name = _L("Edit text");
|
||||
|
||||
auto can_edit_text = []() {
|
||||
if (plater() == nullptr)
|
||||
return false;
|
||||
const Selection& selection = plater()->get_selection();
|
||||
if (selection.volumes_count() != 1)
|
||||
return false;
|
||||
const GLVolume* gl_volume = selection.get_first_volume();
|
||||
if (gl_volume == nullptr)
|
||||
return false;
|
||||
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
|
||||
if (volume == nullptr)
|
||||
return false;
|
||||
return volume->is_text();
|
||||
};
|
||||
|
||||
if (menu != &m_text_part_menu) {
|
||||
const int menu_item_id = menu->FindItem(name);
|
||||
if (menu_item_id != wxNOT_FOUND)
|
||||
menu->Destroy(menu_item_id);
|
||||
if (!can_edit_text())
|
||||
return;
|
||||
}
|
||||
|
||||
wxString description = _L("Ability to change text, font, size, ...");
|
||||
std::string icon = "cog";
|
||||
auto open_emboss = [](const wxCommandEvent &) {
|
||||
GLGizmosManager &mng = plater()->get_view3D_canvas3D()->get_gizmos_manager();
|
||||
if (mng.get_current_type() == GLGizmosManager::Emboss)
|
||||
mng.open_gizmo(GLGizmosManager::Emboss); // close() and reopen - move to be visible
|
||||
mng.open_gizmo(GLGizmosManager::Emboss);
|
||||
};
|
||||
append_menu_item(menu, wxID_ANY, name, description, open_emboss, icon, nullptr, can_edit_text, m_parent);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_edit_svg(wxMenu *menu)
|
||||
{
|
||||
wxString name = _L("Edit SVG");
|
||||
auto can_edit_svg = []() {
|
||||
if (plater() == nullptr)
|
||||
return false;
|
||||
const Selection& selection = plater()->get_selection();
|
||||
if (selection.volumes_count() != 1)
|
||||
return false;
|
||||
const GLVolume* gl_volume = selection.get_first_volume();
|
||||
if (gl_volume == nullptr)
|
||||
return false;
|
||||
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
|
||||
if (volume == nullptr)
|
||||
return false;
|
||||
return volume->is_svg();
|
||||
};
|
||||
|
||||
if (menu != &m_svg_part_menu) {
|
||||
const int menu_item_id = menu->FindItem(name);
|
||||
if (menu_item_id != wxNOT_FOUND)
|
||||
menu->Destroy(menu_item_id);
|
||||
if (!can_edit_svg())
|
||||
return;
|
||||
}
|
||||
|
||||
wxString description = _L("Change SVG source file, projection, size, ...");
|
||||
std::string icon = "cog";
|
||||
auto open_svg = [](const wxCommandEvent &) {
|
||||
GLGizmosManager &mng = plater()->get_view3D_canvas3D()->get_gizmos_manager();
|
||||
if (mng.get_current_type() == GLGizmosManager::Svg)
|
||||
mng.open_gizmo(GLGizmosManager::Svg); // close() and reopen - move to be visible
|
||||
mng.open_gizmo(GLGizmosManager::Svg);
|
||||
};
|
||||
append_menu_item(menu, wxID_ANY, name, description, open_svg, icon, nullptr, can_edit_svg, m_parent);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_invalidate_cut_info(wxMenu *menu)
|
||||
{
|
||||
const wxString menu_name = _L("Invalidate cut info");
|
||||
|
|
@ -1175,6 +1321,34 @@ void MenuFactory::create_part_menu()
|
|||
append_menu_item_per_object_settings(&m_part_menu);
|
||||
}
|
||||
|
||||
void MenuFactory::create_text_part_menu()
|
||||
{
|
||||
wxMenu* menu = &m_text_part_menu;
|
||||
|
||||
append_menu_item_edit_text(menu);
|
||||
append_menu_item_delete(menu);
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
menu->AppendSeparator();
|
||||
append_menu_item_per_object_settings(menu);
|
||||
append_menu_item_change_type(menu);
|
||||
}
|
||||
|
||||
void MenuFactory::create_svg_part_menu()
|
||||
{
|
||||
wxMenu* menu = &m_svg_part_menu;
|
||||
|
||||
append_menu_item_edit_svg(menu);
|
||||
append_menu_item_delete(menu);
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
menu->AppendSeparator();
|
||||
append_menu_item_per_object_settings(menu);
|
||||
append_menu_item_change_type(menu);
|
||||
}
|
||||
|
||||
void MenuFactory::create_bbl_part_menu()
|
||||
{
|
||||
wxMenu* menu = &m_part_menu;
|
||||
|
|
@ -1301,7 +1475,8 @@ void MenuFactory::init(wxWindow* parent)
|
|||
//create_object_menu();
|
||||
create_sla_object_menu();
|
||||
//create_part_menu();
|
||||
|
||||
create_text_part_menu();
|
||||
create_svg_part_menu();
|
||||
create_extra_object_menu();
|
||||
create_bbl_part_menu();
|
||||
create_bbl_assemble_object_menu();
|
||||
|
|
@ -1330,6 +1505,8 @@ wxMenu* MenuFactory::object_menu()
|
|||
append_menu_items_convert_unit(&m_object_menu);
|
||||
append_menu_items_flush_options(&m_object_menu);
|
||||
append_menu_item_invalidate_cut_info(&m_object_menu);
|
||||
append_menu_item_edit_text(&m_object_menu);
|
||||
append_menu_item_edit_svg(&m_object_menu);
|
||||
append_menu_item_change_filament(&m_object_menu);
|
||||
return &m_object_menu;
|
||||
}
|
||||
|
|
@ -1339,6 +1516,8 @@ wxMenu* MenuFactory::sla_object_menu()
|
|||
append_menu_items_convert_unit(&m_sla_object_menu);
|
||||
append_menu_item_settings(&m_sla_object_menu);
|
||||
//update_menu_items_instance_manipulation(mtObjectSLA);
|
||||
append_menu_item_edit_text(&m_sla_object_menu);
|
||||
append_menu_item_edit_svg(&m_object_menu);
|
||||
|
||||
return &m_sla_object_menu;
|
||||
}
|
||||
|
|
@ -1351,6 +1530,22 @@ wxMenu* MenuFactory::part_menu()
|
|||
return &m_part_menu;
|
||||
}
|
||||
|
||||
wxMenu* MenuFactory::text_part_menu()
|
||||
{
|
||||
append_menu_item_change_filament(&m_text_part_menu);
|
||||
append_menu_item_per_object_settings(&m_text_part_menu);
|
||||
|
||||
return &m_text_part_menu;
|
||||
}
|
||||
|
||||
wxMenu *MenuFactory::svg_part_menu()
|
||||
{
|
||||
append_menu_item_change_filament(&m_svg_part_menu);
|
||||
append_menu_item_per_object_settings(&m_svg_part_menu);
|
||||
|
||||
return &m_svg_part_menu;
|
||||
}
|
||||
|
||||
wxMenu* MenuFactory::instance_menu()
|
||||
{
|
||||
return &m_instance_menu;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2022 Oleksandra Iushchenko @YuSanka, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_GUI_Factories_hpp_
|
||||
#define slic3r_GUI_Factories_hpp_
|
||||
|
||||
|
|
@ -47,8 +51,9 @@ struct SettingsFactory
|
|||
class MenuFactory
|
||||
{
|
||||
public:
|
||||
static const std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS;
|
||||
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
|
||||
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
|
||||
static std::vector<wxBitmapBundle*> get_text_volume_bitmaps();
|
||||
static std::vector<wxBitmapBundle*> get_svg_volume_bitmaps();
|
||||
|
||||
MenuFactory();
|
||||
~MenuFactory() = default;
|
||||
|
|
@ -65,6 +70,8 @@ public:
|
|||
wxMenu* object_menu();
|
||||
wxMenu* sla_object_menu();
|
||||
wxMenu* part_menu();
|
||||
wxMenu* text_part_menu();
|
||||
wxMenu* svg_part_menu();
|
||||
wxMenu* instance_menu();
|
||||
wxMenu* layer_menu();
|
||||
wxMenu* multi_selection_menu();
|
||||
|
|
@ -85,6 +92,8 @@ private:
|
|||
|
||||
MenuWithSeparators m_object_menu;
|
||||
MenuWithSeparators m_part_menu;
|
||||
MenuWithSeparators m_text_part_menu;
|
||||
MenuWithSeparators m_svg_part_menu;
|
||||
MenuWithSeparators m_sla_object_menu;
|
||||
MenuWithSeparators m_default_menu;
|
||||
MenuWithSeparators m_instance_menu;
|
||||
|
|
@ -104,6 +113,8 @@ private:
|
|||
void create_object_menu();
|
||||
void create_sla_object_menu();
|
||||
void create_part_menu();
|
||||
void create_text_part_menu();
|
||||
void create_svg_part_menu();
|
||||
//BBS: add part plate related logic
|
||||
void create_plate_menu();
|
||||
//BBS: add bbl object menu
|
||||
|
|
@ -113,6 +124,8 @@ private:
|
|||
void create_bbl_assemble_part_menu();
|
||||
|
||||
wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type);
|
||||
void append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item = true);
|
||||
void append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item = true);
|
||||
void append_menu_items_add_volume(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_layers_editing(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_settings(wxMenu* menu);
|
||||
|
|
@ -128,7 +141,6 @@ private:
|
|||
void append_menu_item_change_extruder(wxMenu* menu);
|
||||
void append_menu_item_set_visible(wxMenu* menu);
|
||||
void append_menu_item_delete(wxMenu* menu);
|
||||
void append_menu_item_edit_text(wxMenu *menu);
|
||||
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);
|
||||
void append_menu_items_convert_unit(wxMenu* menu); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk"
|
||||
void append_menu_items_flush_options(wxMenu* menu);
|
||||
|
|
@ -137,6 +149,8 @@ private:
|
|||
void append_menu_item_merge_parts_to_single_part(wxMenu *menu);
|
||||
void append_menu_items_mirror(wxMenu *menu);
|
||||
void append_menu_item_invalidate_cut_info(wxMenu *menu);
|
||||
void append_menu_item_edit_text(wxMenu *menu);
|
||||
void append_menu_item_edit_svg(wxMenu *menu);
|
||||
|
||||
//void append_menu_items_instance_manipulation(wxMenu *menu);
|
||||
//void update_menu_items_instance_manipulation(MenuType type);
|
||||
|
|
|
|||
|
|
@ -1331,11 +1331,20 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
|
|||
const ItemType type = m_objects_model->GetItemType(item);
|
||||
if (!(type & (itPlate | itObject | itVolume | itInstance)))
|
||||
return;
|
||||
if (type & itVolume) {
|
||||
int obj_idx, vol_idx;
|
||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||
if (obj_idx < 0 || vol_idx < 0)
|
||||
return;
|
||||
const ModelVolume *volume = object(obj_idx)->volumes[vol_idx];
|
||||
|
||||
menu = type & itPlate ? plater->plate_menu() :
|
||||
type & itInstance ? plater->instance_menu() :
|
||||
type & itVolume ? plater->part_menu() :
|
||||
printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu();
|
||||
menu = volume->is_text() ? plater->text_part_menu() :
|
||||
plater->part_menu();
|
||||
}
|
||||
else
|
||||
menu = type & itPlate ? plater->plate_menu() :
|
||||
type & itInstance ? plater->instance_menu() :
|
||||
printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu();
|
||||
plater->SetPlateIndexByRightMenuInLeftUI(-1);
|
||||
if (type & itPlate) {
|
||||
int plate_idx = -1;
|
||||
|
|
@ -1999,7 +2008,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
|
|||
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
|
||||
const GLVolume* v = selection.get_first_volume();
|
||||
const Geometry::Transformation inst_transform = v->get_instance_transformation();
|
||||
const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse();
|
||||
const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse();
|
||||
const Vec3d instance_offset = v->get_instance_offset();
|
||||
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
|
|
@ -2143,7 +2152,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
|||
Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) :
|
||||
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
|
||||
Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset();
|
||||
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
|
||||
new_volume->set_offset(v->get_instance_transformation().get_matrix_no_offset().inverse() * offset);
|
||||
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(model_object);
|
||||
|
|
@ -2273,56 +2282,6 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
|
|||
#endif /* _DEBUG */
|
||||
}
|
||||
|
||||
int ObjectList::load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp)
|
||||
{
|
||||
wxDataViewItem item = GetSelection();
|
||||
// we can add volumes for Object or Instance
|
||||
if (!item || !(m_objects_model->GetItemType(item) & (itObject | itInstance)))
|
||||
return -1;
|
||||
const int obj_idx = m_objects_model->GetObjectIdByItem(item);
|
||||
|
||||
if (obj_idx < 0)
|
||||
return -1;
|
||||
|
||||
// Get object item, if Instance is selected
|
||||
if (m_objects_model->GetItemType(item) & itInstance)
|
||||
item = m_objects_model->GetItemById(obj_idx);
|
||||
|
||||
ModelObject* mo = (*m_objects)[obj_idx];
|
||||
|
||||
Geometry::Transformation instance_transformation = mo->instances[0]->get_transformation();
|
||||
|
||||
// apply the instance transform to all volumes and reset instance transform except the offset
|
||||
apply_object_instance_transfrom_to_all_volumes(mo, !is_temp);
|
||||
|
||||
ModelVolume *mv = mo->add_volume(mesh);
|
||||
mv->name = name.ToStdString();
|
||||
if (!text_info.m_text.empty())
|
||||
mv->set_text_info(text_info);
|
||||
|
||||
if (!is_temp) {
|
||||
std::vector<ModelVolume *> volumes;
|
||||
volumes.push_back(mv);
|
||||
wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume *volume) {
|
||||
return std::find(volumes.begin(), volumes.end(), volume) != volumes.end();
|
||||
});
|
||||
|
||||
wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t) obj_idx);
|
||||
|
||||
if (items.size() > 1) {
|
||||
m_selection_mode = smVolume;
|
||||
m_last_selected_item = wxDataViewItem(nullptr);
|
||||
}
|
||||
select_items(items);
|
||||
|
||||
selection_changed();
|
||||
}
|
||||
|
||||
//BBS: notify partplate the modify
|
||||
notify_instance_updated(obj_idx);
|
||||
return mo->volumes.size() - 1;
|
||||
}
|
||||
|
||||
//BBS
|
||||
bool ObjectList::del_object(const int obj_idx, bool refresh_immediately)
|
||||
{
|
||||
|
|
@ -2614,7 +2573,9 @@ void ObjectList::split()
|
|||
for (const ModelVolume* volume : model_object->volumes) {
|
||||
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name),
|
||||
volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART,
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->is_text(),
|
||||
volume->is_svg(),
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
// add settings to the part, if it has those
|
||||
|
|
@ -3787,6 +3748,8 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st
|
|||
object_item,
|
||||
from_u8(volume->name),
|
||||
volume->type(),
|
||||
volume->is_text(),
|
||||
volume->is_svg(),
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
|
|
@ -5838,6 +5801,8 @@ wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(int obj_idx, s
|
|||
for (const ModelVolume* volume : object->volumes) {
|
||||
wxDataViewItem vol_item = m_objects_model->AddVolumeChild(object_item, from_u8(volume->name),
|
||||
volume->type(),
|
||||
volume->is_text(),
|
||||
volume->is_svg(),
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class ModelConfig;
|
|||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class TriangleMesh;
|
||||
struct TextInfo;
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
// FIXME: broken build on mac os because of this is missing:
|
||||
|
|
@ -287,7 +286,6 @@ public:
|
|||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
|
||||
// BBS
|
||||
void switch_to_object_process();
|
||||
int load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp);
|
||||
bool del_object(const int obj_idx, bool refresh_immediately = true);
|
||||
void del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, David Kocík @kocikdav, Tomáš Mészáros @tamasmeszaros, Vojtěch Král @vojtechkral
|
||||
///|/ Copyright (c) 2022 André Althaus
|
||||
///|/ Copyright (c) 2019 John Drake @foxox
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
//#include "stdlib.h"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
|
@ -65,6 +71,7 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig
|
|||
m_canvas->allow_multisample(OpenGLManager::can_multisample());
|
||||
// XXX: If have OpenGL
|
||||
m_canvas->enable_picking(true);
|
||||
m_canvas->get_selection().set_mode(Selection::Instance);
|
||||
m_canvas->enable_moving(true);
|
||||
// XXX: more config from 3D.pm
|
||||
m_canvas->set_model(model);
|
||||
|
|
|
|||
|
|
@ -252,12 +252,17 @@ void GLGizmoBase::GizmoImguiEnd()
|
|||
|
||||
void GLGizmoBase::GizmoImguiSetNextWIndowPos(float &x, float y, int flag, float pivot_x, float pivot_y)
|
||||
{
|
||||
if (abs(last_input_window_width) > 0.01f) {
|
||||
if (x + last_input_window_width > m_parent.get_canvas_size().get_width()) {
|
||||
if (last_input_window_width > m_parent.get_canvas_size().get_width()) {
|
||||
GizmoImguiSetNextWIndowPos(x, y, last_input_window_width, 0, flag, pivot_x, pivot_y);
|
||||
}
|
||||
|
||||
void GLGizmoBase::GizmoImguiSetNextWIndowPos(float &x, float y, float w, float h, int flag, float pivot_x, float pivot_y)
|
||||
{
|
||||
if (abs(w) > 0.01f) {
|
||||
if (x + w > m_parent.get_canvas_size().get_width()) {
|
||||
if (w > m_parent.get_canvas_size().get_width()) {
|
||||
x = 0;
|
||||
} else {
|
||||
x = m_parent.get_canvas_size().get_width() - last_input_window_width;
|
||||
x = m_parent.get_canvas_size().get_width() - w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@ protected:
|
|||
bool GizmoImguiBegin(const std::string& name, int flags);
|
||||
void GizmoImguiEnd();
|
||||
void GizmoImguiSetNextWIndowPos(float &x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
|
||||
void GizmoImguiSetNextWIndowPos(float &x, float y, float w, float h, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
|
||||
|
||||
void register_grabbers_for_picking();
|
||||
void unregister_grabbers_for_picking();
|
||||
|
|
|
|||
|
|
@ -1769,7 +1769,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center,
|
|||
// respect just to the solid parts for FFF and ignore pad and supports for SLA
|
||||
if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) {
|
||||
|
||||
const auto instance_matrix = volume->get_instance_transformation().get_matrix(true);
|
||||
const auto instance_matrix = volume->get_instance_transformation().get_matrix_no_offset();
|
||||
auto volume_trafo = instance_matrix * volume->get_volume_transformation().get_matrix();
|
||||
ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * volume_trafo));
|
||||
}
|
||||
|
|
@ -2284,13 +2284,13 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
|
|||
render_flip_plane_button(m_connectors_editing && connectors.empty());
|
||||
|
||||
m_imgui->text(m_labels_map["Type"]);
|
||||
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f));
|
||||
ImGuiWrapper::push_radio_style();
|
||||
bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug);
|
||||
type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel);
|
||||
type_changed |= render_connect_type_radio_button(CutConnectorType::Snap);
|
||||
if (type_changed)
|
||||
apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); });
|
||||
ImGui::PopStyleColor(1);
|
||||
ImGuiWrapper::pop_radio_style();
|
||||
|
||||
m_imgui->disabled_begin(m_connector_type != CutConnectorType::Plug);
|
||||
if (type_changed && m_connector_type == CutConnectorType::Dowel) {
|
||||
|
|
@ -2948,7 +2948,7 @@ void GLGizmoCut3D::show_tooltip_information(float x, float y)
|
|||
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
||||
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
||||
|
||||
caption_max += m_imgui->calc_text_size(": ").x + 35.f;
|
||||
caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 35.f;
|
||||
|
||||
float font_size = ImGui::GetFontSize();
|
||||
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
||||
|
|
|
|||
3664
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
Normal file
3664
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
Normal file
File diff suppressed because it is too large
Load diff
240
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
Normal file
240
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_GLGizmoEmboss_hpp_
|
||||
#define slic3r_GLGizmoEmboss_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/IconManager.hpp"
|
||||
#include "slic3r/GUI/SurfaceDrag.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp" // TODO: not needed
|
||||
#include "slic3r/GUI/TextLines.hpp"
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "slic3r/Utils/EmbossStyleManager.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TextConfiguration.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <GL/glew.h>
|
||||
|
||||
class wxFont;
|
||||
namespace Slic3r{
|
||||
class AppConfig;
|
||||
class GLVolume;
|
||||
enum class ModelVolumeType : int;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
class GLGizmoEmboss : public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
explicit GLGizmoEmboss(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id);
|
||||
|
||||
/// <summary>
|
||||
/// Create new embossed text volume by type on position of mouse
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
/// <param name="mouse_pos">Define position of new volume</param>
|
||||
bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos);
|
||||
|
||||
/// <summary>
|
||||
/// Create new text without given position
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
bool create_volume(ModelVolumeType volume_type);
|
||||
|
||||
/// <summary>
|
||||
/// Handle pressing of shortcut
|
||||
/// </summary>
|
||||
void on_shortcut_key();
|
||||
|
||||
/// <summary>
|
||||
/// Mirroring from object manipulation panel
|
||||
/// !! Emboss gizmo must be active
|
||||
/// </summary>
|
||||
/// <param name="axis">Axis for mirroring must be one of {0,1,2}</param>
|
||||
/// <returns>True on success start job otherwise False</returns>
|
||||
bool do_mirror(size_t axis);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Call on change inside of object conatining projected volume
|
||||
/// </summary>
|
||||
/// <param name="job_cancel">Way to stop re_emboss job</param>
|
||||
/// <returns>True on success otherwise False</returns>
|
||||
static bool re_emboss(const ModelVolume &text, std::shared_ptr<std::atomic<bool>> job_cancel = nullptr);
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
void on_render() override;
|
||||
void on_register_raycasters_for_picking() override;
|
||||
void on_unregister_raycasters_for_picking() override;
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
void on_set_state() override;
|
||||
void data_changed(bool is_serializing) override; // selection changed
|
||||
void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); }
|
||||
void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); }
|
||||
void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); }
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
void push_button_style(bool pressed);
|
||||
void pop_button_style();
|
||||
|
||||
/// <summary>
|
||||
/// Rotate by text on dragging rotate grabers
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Information about mouse</param>
|
||||
/// <returns>Propagete normaly return false.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override;
|
||||
std::string get_gizmo_entering_text() const override;
|
||||
std::string get_gizmo_leaving_text() const override;
|
||||
std::string get_action_snapshot_name() const override;
|
||||
|
||||
private:
|
||||
void volume_transformation_changing();
|
||||
void volume_transformation_changed();
|
||||
|
||||
static EmbossStyles create_default_styles();
|
||||
// localized default text
|
||||
bool init_create(ModelVolumeType volume_type);
|
||||
|
||||
void set_volume_by_selection();
|
||||
void reset_volume();
|
||||
|
||||
// create volume from text - main functionality
|
||||
bool process();
|
||||
void close();
|
||||
void draw_window();
|
||||
void draw_text_input();
|
||||
void draw_model_type();
|
||||
void draw_style_list();
|
||||
void draw_delete_style_button();
|
||||
void draw_style_rename_popup();
|
||||
void draw_style_rename_button();
|
||||
void draw_style_save_button(bool is_modified);
|
||||
void draw_style_save_as_popup();
|
||||
void draw_style_add_button();
|
||||
void init_font_name_texture();
|
||||
void draw_font_list_line();
|
||||
void draw_font_list();
|
||||
void draw_height(bool use_inch);
|
||||
void draw_depth(bool use_inch);
|
||||
|
||||
// call after set m_style_manager.get_style().prop.size_in_mm
|
||||
bool set_height();
|
||||
|
||||
bool draw_italic_button();
|
||||
bool draw_bold_button();
|
||||
void draw_advanced();
|
||||
|
||||
bool select_facename(const wxString& facename);
|
||||
|
||||
template<typename T> bool rev_input_mm(const std::string &name, T &value, const T *default_value,
|
||||
const std::string &undo_tooltip, T step, T step_fast, const char *format, bool use_inch, const std::optional<float>& scale) const;
|
||||
|
||||
/// <summary>
|
||||
/// Reversible input float with option to restor default value
|
||||
/// TODO: make more general, static and move to ImGuiWrapper
|
||||
/// </summary>
|
||||
/// <returns>True when value changed otherwise FALSE.</returns>
|
||||
template<typename T> bool rev_input(const std::string &name, T &value, const T *default_value,
|
||||
const std::string &undo_tooltip, T step, T step_fast, const char *format, ImGuiInputTextFlags flags = 0) const;
|
||||
bool rev_checkbox(const std::string &name, bool &value, const bool* default_value, const std::string &undo_tooltip) const;
|
||||
bool rev_slider(const std::string &name, std::optional<int>& value, const std::optional<int> *default_value,
|
||||
const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip) const;
|
||||
bool rev_slider(const std::string &name, std::optional<float>& value, const std::optional<float> *default_value,
|
||||
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const;
|
||||
bool rev_slider(const std::string &name, float &value, const float *default_value,
|
||||
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const;
|
||||
template<typename T, typename Draw> bool revertible(const std::string &name, T &value, const T *default_value,
|
||||
const std::string &undo_tooltip, float undo_offset, Draw draw) const;
|
||||
|
||||
// process mouse event
|
||||
bool on_mouse_for_rotation(const wxMouseEvent &mouse_event);
|
||||
bool on_mouse_for_translate(const wxMouseEvent &mouse_event);
|
||||
void on_mouse_change_selection(const wxMouseEvent &mouse_event);
|
||||
|
||||
// When open text loaded from .3mf it could be written with unknown font
|
||||
bool m_is_unknown_font = false;
|
||||
void create_notification_not_valid_font(const TextConfiguration& tc);
|
||||
void create_notification_not_valid_font(const std::string& text);
|
||||
void remove_notification_not_valid_font();
|
||||
|
||||
struct GuiCfg;
|
||||
std::unique_ptr<const GuiCfg> m_gui_cfg;
|
||||
|
||||
// Is open tree with advanced options
|
||||
bool m_is_advanced_edit_style = false;
|
||||
|
||||
// Keep information about stored styles and loaded actual style to compare with
|
||||
Emboss::StyleManager m_style_manager;
|
||||
|
||||
// pImpl to hide implementation of FaceNames to .cpp file
|
||||
struct Facenames; // forward declaration
|
||||
std::unique_ptr<Facenames> m_face_names;
|
||||
|
||||
// Text to emboss
|
||||
std::string m_text; // Sequence of Unicode UTF8 symbols
|
||||
|
||||
// When true keep up vector otherwise relative rotation
|
||||
bool m_keep_up = true;
|
||||
|
||||
// current selected volume
|
||||
// NOTE: Be carefull could be uninitialized (removed from Model)
|
||||
ModelVolume *m_volume = nullptr;
|
||||
|
||||
// When work with undo redo stack there could be situation that
|
||||
// m_volume point to unexisting volume so One need also objectID
|
||||
ObjectID m_volume_id;
|
||||
|
||||
// True when m_text contain character unknown by selected font
|
||||
bool m_text_contain_unknown_glyph = false;
|
||||
|
||||
// cancel for previous update of volume to cancel finalize part
|
||||
std::shared_ptr<std::atomic<bool>> m_job_cancel = nullptr;
|
||||
|
||||
// Keep information about curvature of text line around surface
|
||||
TextLinesModel m_text_lines;
|
||||
void reinit_text_lines(unsigned count_lines=0);
|
||||
|
||||
// Rotation gizmo
|
||||
GLGizmoRotate m_rotate_gizmo;
|
||||
// Value is set only when dragging rotation to calculate actual angle
|
||||
std::optional<float> m_rotate_start_angle;
|
||||
|
||||
// Keep data about dragging only during drag&drop
|
||||
std::optional<SurfaceDrag> m_surface_drag;
|
||||
|
||||
// Keep old scene triangle data in AABB trees,
|
||||
// all the time it need actualize before use.
|
||||
RaycastManager m_raycast_manager;
|
||||
|
||||
// For text on scaled objects
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> m_scale_depth;
|
||||
void calculate_scale();
|
||||
|
||||
// drawing icons
|
||||
IconManager m_icon_manager;
|
||||
IconManager::VIcons m_icons;
|
||||
void init_icons();
|
||||
|
||||
// only temporary solution
|
||||
static const std::string M_ICON_FILENAME;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GLGizmoEmboss_hpp_
|
||||
|
|
@ -513,7 +513,7 @@ void GLGizmoFdmSupports::show_tooltip_information(float caption_max, float x, fl
|
|||
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
||||
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
||||
|
||||
caption_max += m_imgui->calc_text_size(": ").x + 15.f;
|
||||
caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 15.f;
|
||||
|
||||
float font_size = ImGui::GetFontSize();
|
||||
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
||||
|
|
@ -584,7 +584,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
|
|||
|
||||
++mesh_id;
|
||||
|
||||
const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
|
||||
const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset();
|
||||
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
|
||||
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ void GLGizmoFlatten::update_planes()
|
|||
ch = ch.convex_hull_3d();
|
||||
m_planes.clear();
|
||||
on_unregister_raycasters_for_picking();
|
||||
const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true);
|
||||
const Transform3d &inst_matrix = mo->instances.front()->get_matrix_no_offset();
|
||||
|
||||
// Following constants are used for discarding too small polygons.
|
||||
const float minimal_area = 5.f; // in square mm (world coordinates)
|
||||
|
|
|
|||
|
|
@ -2042,7 +2042,7 @@ void GLGizmoMeasure::show_tooltip_information(float caption_max, float x, float
|
|||
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
||||
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
||||
|
||||
caption_max += m_imgui->calc_text_size(": ").x + 35.f;
|
||||
caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 35.f;
|
||||
|
||||
float font_size = ImGui::GetFontSize();
|
||||
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe
|
|||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_offset(old_volume->get_transformation().get_offset());
|
||||
//Vec3d translate_z = { 0,0, (new_volume->source.mesh_offset - old_volume->source.mesh_offset).z() };
|
||||
//new_volume->translate(new_volume->get_transformation().get_matrix(true) * translate_z);
|
||||
//new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * translate_z);
|
||||
//new_volume->supported_facets.assign(old_volume->supported_facets);
|
||||
//new_volume->seam_facets.assign(old_volume->seam_facets);
|
||||
//new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x
|
|||
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
||||
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
||||
|
||||
caption_max += m_imgui->calc_text_size(": ").x + 15.f;
|
||||
caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 15.f;
|
||||
|
||||
float font_size = ImGui::GetFontSize();
|
||||
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
||||
|
|
@ -408,7 +408,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.f);
|
||||
const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
const float color_button_width = m_imgui->calc_text_size("").x + m_imgui->scaled(1.75f);
|
||||
const float color_button_width = m_imgui->calc_text_size(std::string_view{""}).x + m_imgui->scaled(1.75f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float total_text_max = 0.f;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,15 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data)
|
|||
m_displacement.z() = calc_projection(data);
|
||||
|
||||
Selection &selection = m_parent.get_selection();
|
||||
selection.translate(m_displacement);
|
||||
TransformationType trafo_type;
|
||||
trafo_type.set_relative();
|
||||
switch (wxGetApp().obj_manipul()->get_coordinates_type())
|
||||
{
|
||||
case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
|
||||
case ECoordinatesType::Local: { trafo_type.set_local(); break; }
|
||||
default: { break; }
|
||||
}
|
||||
selection.translate(m_displacement, trafo_type);
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_render()
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
|
|||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse();
|
||||
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix_no_scaling_factor().inverse();
|
||||
|
||||
// BBS
|
||||
ColorRGBA render_color = this->get_cursor_hover_color();
|
||||
|
|
@ -536,8 +536,8 @@ std::vector<GLGizmoPainterBase::ProjectedHeightRange> GLGizmoPainterBase::get_pr
|
|||
mi->get_assemble_transformation().get_matrix() :
|
||||
mi->get_transformation().get_matrix();
|
||||
const Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
|
||||
mi->get_assemble_transformation().get_matrix(true) :
|
||||
mi->get_transformation().get_matrix(true);
|
||||
mi->get_assemble_transformation().get_matrix_no_offset() :
|
||||
mi->get_transformation().get_matrix_no_offset();
|
||||
|
||||
for (int mesh_idx = 0; mesh_idx < part_volumes.size(); mesh_idx++) {
|
||||
if (mesh_idx == m_rr.mesh_id)
|
||||
|
|
@ -605,8 +605,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
|
||||
const Transform3d trafo_matrix_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
|
||||
mi->get_assemble_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true) :
|
||||
mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true);
|
||||
mi->get_assemble_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset() :
|
||||
mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset();
|
||||
const Transform3d trafo_matrix = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
|
||||
mi->get_assemble_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix() :
|
||||
mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
|
||||
|
|
@ -675,8 +675,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
mi->get_assemble_transformation().get_matrix() :
|
||||
mi->get_transformation().get_matrix();
|
||||
Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
|
||||
mi->get_assemble_transformation().get_matrix(true) :
|
||||
mi->get_transformation().get_matrix(true);
|
||||
mi->get_assemble_transformation().get_matrix_no_offset() :
|
||||
mi->get_transformation().get_matrix_no_offset();
|
||||
std::vector<const ModelVolume*> part_volumes;
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
|
|
@ -692,7 +692,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
else {
|
||||
trafo_matrices.emplace_back(instance_trafo* mv->get_matrix());
|
||||
}
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset());
|
||||
part_volumes.push_back(mv);
|
||||
}
|
||||
|
||||
|
|
@ -824,8 +824,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
mi->get_assemble_transformation().get_matrix() :
|
||||
mi->get_transformation().get_matrix();
|
||||
const Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
|
||||
mi->get_assemble_transformation().get_matrix(true) :
|
||||
mi->get_transformation().get_matrix(true);
|
||||
mi->get_assemble_transformation().get_matrix_no_offset() :
|
||||
mi->get_transformation().get_matrix_no_offset();
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
|
|
@ -840,7 +840,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
else {
|
||||
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
|
||||
}
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset());
|
||||
}
|
||||
|
||||
// Now "click" into all the prepared points and spill paint around them.
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
|
@ -28,10 +28,16 @@ const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius
|
|||
const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
|
||||
const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius
|
||||
|
||||
|
||||
GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis)
|
||||
: GLGizmoBase(parent, "", -1)
|
||||
, m_axis(axis)
|
||||
, m_angle(0.0)
|
||||
, m_center(0.0, 0.0, 0.0)
|
||||
, m_radius(0.0f)
|
||||
, m_snap_coarse_in_radius(0.0f)
|
||||
, m_snap_coarse_out_radius(0.0f)
|
||||
, m_snap_fine_in_radius(0.0f)
|
||||
, m_snap_fine_out_radius(0.0f)
|
||||
, m_drag_color(DEFAULT_DRAG_COLOR)
|
||||
, m_highlight_color(DEFAULT_HIGHLIGHT_COLOR)
|
||||
{
|
||||
|
|
@ -94,19 +100,12 @@ bool GLGizmoRotate::on_init()
|
|||
|
||||
void GLGizmoRotate::on_start_dragging()
|
||||
{
|
||||
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
|
||||
m_center = m_custom_center == Vec3d::Zero() ? box.center() : m_custom_center;
|
||||
m_radius = Offset + box.radius();
|
||||
m_snap_coarse_in_radius = m_radius / 3.0f;
|
||||
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
|
||||
m_snap_fine_in_radius = m_radius;
|
||||
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
|
||||
init_data_from_selection(m_parent.get_selection());
|
||||
}
|
||||
|
||||
void GLGizmoRotate::on_dragging(const UpdateData &data)
|
||||
{
|
||||
const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection()));
|
||||
|
||||
const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray));
|
||||
const Vec2d orig_dir = Vec2d::UnitX();
|
||||
const Vec2d new_dir = mouse_pos.normalized();
|
||||
|
||||
|
|
@ -141,16 +140,8 @@ void GLGizmoRotate::on_render()
|
|||
return;
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||
|
||||
if (m_hover_id != 0 && !m_grabbers.front().dragging) {
|
||||
m_center = m_custom_center == Vec3d::Zero() ? box.center() : m_custom_center;
|
||||
m_radius = Offset + box.radius();
|
||||
m_snap_coarse_in_radius = m_radius / 3.0f;
|
||||
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
|
||||
m_snap_fine_in_radius = m_radius;
|
||||
m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth);
|
||||
}
|
||||
if (m_hover_id != 0 && !m_grabbers.front().dragging)
|
||||
init_data_from_selection(selection);
|
||||
|
||||
const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset);
|
||||
m_grabbers.front().center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0);
|
||||
|
|
@ -168,15 +159,14 @@ void GLGizmoRotate::on_render()
|
|||
shader->start_using();
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
Transform3d view_model_matrix = camera.get_view_matrix() * m_grabbers.front().matrix;
|
||||
|
||||
const Transform3d view_model_matrix = camera.get_view_matrix() * m_grabbers.front().matrix;
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
|
||||
const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON;
|
||||
m_old_radius = m_radius;
|
||||
|
||||
ColorRGBA color((m_hover_id != -1) ? m_drag_color : m_highlight_color);
|
||||
const ColorRGBA color = (m_hover_id != -1) ? m_drag_color : m_highlight_color;
|
||||
render_circle(color, radius_changed);
|
||||
if (m_hover_id != -1) {
|
||||
const bool hover_radius_changed = std::abs(m_old_hover_radius - m_radius) > EPSILON;
|
||||
|
|
@ -192,7 +182,23 @@ void GLGizmoRotate::on_render()
|
|||
shader->stop_using();
|
||||
}
|
||||
|
||||
render_grabber(box);
|
||||
render_grabber(m_bounding_box);
|
||||
}
|
||||
|
||||
void GLGizmoRotate::init_data_from_selection(const Selection& selection)
|
||||
{
|
||||
const auto [box, box_trafo] = m_force_local_coordinate ?
|
||||
selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system();
|
||||
m_bounding_box = box;
|
||||
const std::pair<Vec3d, double> sphere = selection.get_bounding_sphere();
|
||||
m_center = sphere.first;
|
||||
m_radius = Offset + sphere.second;
|
||||
m_orient_matrix = box_trafo;
|
||||
m_orient_matrix.translation() = m_center;
|
||||
m_snap_coarse_in_radius = m_radius / 3.0f;
|
||||
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
|
||||
m_snap_fine_in_radius = m_radius;
|
||||
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
|
||||
}
|
||||
|
||||
//BBS: add input window for move
|
||||
|
|
@ -422,12 +428,12 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const
|
|||
{
|
||||
case X:
|
||||
{
|
||||
ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ());
|
||||
ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ());
|
||||
break;
|
||||
}
|
||||
case Y:
|
||||
{
|
||||
ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY());
|
||||
ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
@ -438,13 +444,10 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const
|
|||
}
|
||||
}
|
||||
|
||||
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes())
|
||||
ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret;
|
||||
|
||||
return Geometry::assemble_transform(m_center) * ret;
|
||||
return m_orient_matrix * ret;
|
||||
}
|
||||
|
||||
Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const
|
||||
Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) const
|
||||
{
|
||||
const double half_pi = 0.5 * double(PI);
|
||||
|
||||
|
|
@ -472,8 +475,7 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons
|
|||
}
|
||||
}
|
||||
|
||||
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes())
|
||||
m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse();
|
||||
m = m * Geometry::Transformation(m_orient_matrix).get_matrix_no_offset().inverse();
|
||||
|
||||
m.translate(-m_center);
|
||||
|
||||
|
|
@ -496,19 +498,32 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons
|
|||
//BBS: GUI refactor: add obj manipulation
|
||||
GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, GizmoObjectManipulation* obj_manipulation)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
, m_gizmos({ GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) })
|
||||
//BBS: GUI refactor: add obj manipulation
|
||||
, m_gizmos({
|
||||
GLGizmoRotate(parent, GLGizmoRotate::X),
|
||||
GLGizmoRotate(parent, GLGizmoRotate::Y),
|
||||
GLGizmoRotate(parent, GLGizmoRotate::Z) })
|
||||
//BBS: GUI refactor: add obj manipulation
|
||||
, m_object_manipulation(obj_manipulation)
|
||||
{
|
||||
load_rotoptimize_state();
|
||||
}
|
||||
|
||||
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) {
|
||||
|
||||
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
if (mouse_event.Dragging() && m_dragging) {
|
||||
// Apply new temporary rotations
|
||||
TransformationType transformation_type(
|
||||
TransformationType::World_Relative_Joint);
|
||||
TransformationType transformation_type;
|
||||
if (m_parent.get_selection().is_wipe_tower())
|
||||
transformation_type = TransformationType::World_Relative_Joint;
|
||||
else {
|
||||
switch (wxGetApp().obj_manipul()->get_coordinates_type())
|
||||
{
|
||||
default:
|
||||
case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; }
|
||||
case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; }
|
||||
case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; }
|
||||
}
|
||||
}
|
||||
if (mouse_event.AltDown())
|
||||
transformation_type.set_independent();
|
||||
m_parent.get_selection().rotate(get_rotation(), transformation_type);
|
||||
|
|
@ -669,11 +684,12 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
|||
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
|
||||
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
|
||||
|
||||
if (wxGetApp().plater()->is_any_job_running())
|
||||
if (!wxGetApp().plater()->get_ui_job_worker().is_idle())
|
||||
imgui->disabled_begin(true);
|
||||
|
||||
if ( imgui->button(btn_txt) ) {
|
||||
wxGetApp().plater()->optimize_rotation();
|
||||
replace_job(wxGetApp().plater()->get_ui_job_worker(),
|
||||
std::make_unique<RotoptimizeJob>());
|
||||
}
|
||||
|
||||
imgui->disabled_end();
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@
|
|||
#define slic3r_GLGizmoRotate_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "../Jobs/RotoptimizeJob.hpp"
|
||||
//BBS: add size adjust related
|
||||
#include "GizmoObjectManipulation.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
|
||||
class GLGizmoRotate : public GLGizmoBase
|
||||
{
|
||||
static const float Offset;
|
||||
|
|
@ -36,16 +35,14 @@ public:
|
|||
private:
|
||||
Axis m_axis;
|
||||
double m_angle{ 0.0 };
|
||||
Vec3d m_custom_center{Vec3d::Zero()};
|
||||
Vec3d m_center{ Vec3d::Zero() };
|
||||
float m_radius{ 0.0f };
|
||||
float m_snap_coarse_in_radius{ 0.0f };
|
||||
float m_snap_coarse_out_radius{ 0.0f };
|
||||
float m_snap_fine_in_radius{ 0.0f };
|
||||
float m_snap_fine_out_radius{ 0.0f };
|
||||
|
||||
ColorRGBA m_drag_color;
|
||||
ColorRGBA m_highlight_color;
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
Transform3d m_orient_matrix{ Transform3d::Identity() };
|
||||
|
||||
GLModel m_circle;
|
||||
GLModel m_scale;
|
||||
|
|
@ -62,6 +59,12 @@ private:
|
|||
float m_old_hover_radius{ 0.0f };
|
||||
float m_old_angle{ 0.0f };
|
||||
|
||||
// emboss need to draw rotation gizmo in local coordinate systems
|
||||
bool m_force_local_coordinate{ false };
|
||||
|
||||
ColorRGBA m_drag_color;
|
||||
ColorRGBA m_highlight_color;
|
||||
|
||||
public:
|
||||
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
|
||||
virtual ~GLGizmoRotate() = default;
|
||||
|
|
@ -71,7 +74,8 @@ public:
|
|||
|
||||
std::string get_tooltip() const override;
|
||||
|
||||
void set_center(const Vec3d &point) { m_custom_center = point; }
|
||||
void set_group_id(int group_id) { m_group_id = group_id; }
|
||||
void set_force_local_coordinate(bool use) { m_force_local_coordinate = use; }
|
||||
|
||||
void start_dragging();
|
||||
void stop_dragging();
|
||||
|
|
@ -109,7 +113,9 @@ private:
|
|||
Transform3d local_transform(const Selection& selection) const;
|
||||
|
||||
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
|
||||
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const;
|
||||
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
|
||||
|
||||
void init_data_from_selection(const Selection& selection);
|
||||
};
|
||||
|
||||
class GLGizmoRotate3D : public GLGizmoBase
|
||||
|
|
@ -138,13 +144,6 @@ public:
|
|||
return tooltip;
|
||||
}
|
||||
|
||||
void set_center(const Vec3d &point)
|
||||
{
|
||||
m_gizmos[X].set_center(point);
|
||||
m_gizmos[Y].set_center(point);
|
||||
m_gizmos[Z].set_center(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Postpone to Rotation
|
||||
/// </summary>
|
||||
|
|
@ -164,7 +163,14 @@ protected:
|
|||
for (int i = 0; i < 3; ++i)
|
||||
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
|
||||
}
|
||||
|
||||
void on_enable_grabber(unsigned int id) override {
|
||||
if (id < 3)
|
||||
m_gizmos[id].enable_grabber();
|
||||
}
|
||||
void on_disable_grabber(unsigned int id) override {
|
||||
if (id < 3)
|
||||
m_gizmos[id].disable_grabber();
|
||||
}
|
||||
bool on_is_activable() const override;
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
|
|
|
|||
2239
src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp
Normal file
2239
src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp
Normal file
File diff suppressed because it is too large
Load diff
200
src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp
Normal file
200
src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#ifndef slic3r_GLGizmoSVG_hpp_
|
||||
#define slic3r_GLGizmoSVG_hpp_
|
||||
|
||||
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
|
||||
// which overrides our localization "L" macro.
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/SurfaceDrag.hpp"
|
||||
#include "slic3r/GUI/GLTexture.hpp"
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "slic3r/GUI/IconManager.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r{
|
||||
class ModelVolume;
|
||||
enum class ModelVolumeType : int;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
struct Texture{
|
||||
unsigned id{0};
|
||||
unsigned width{0};
|
||||
unsigned height{0};
|
||||
};
|
||||
|
||||
class GLGizmoSVG : public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
explicit GLGizmoSVG(GLCanvas3D &parent);
|
||||
|
||||
/// <summary>
|
||||
/// Create new embossed text volume by type on position of mouse
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
/// <param name="mouse_pos">Define position of new volume</param>
|
||||
/// <returns>True on succesfull start creation otherwise False</returns>
|
||||
bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog
|
||||
|
||||
/// <summary>
|
||||
/// Create new text without given position
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
/// <returns>True on succesfull start creation otherwise False</returns>
|
||||
bool create_volume(ModelVolumeType volume_type); // first open file dialog
|
||||
|
||||
/// <summary>
|
||||
/// Create volume from already selected svg file
|
||||
/// </summary>
|
||||
/// <param name="svg_file">File path</param>
|
||||
/// <param name="mouse_pos">Position on screen where to create volume</param>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
/// <returns>True on succesfull start creation otherwise False</returns>
|
||||
bool create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART);
|
||||
bool create_volume(std::string_view svg_file, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether volume is object containing only emboss volume
|
||||
/// </summary>
|
||||
/// <param name="volume">Pointer to volume</param>
|
||||
/// <returns>True when object otherwise False</returns>
|
||||
static bool is_svg_object(const ModelVolume &volume);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether volume has emboss data
|
||||
/// </summary>
|
||||
/// <param name="volume">Pointer to volume</param>
|
||||
/// <returns>True when constain emboss data otherwise False</returns>
|
||||
static bool is_svg(const ModelVolume &volume);
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
void on_render() override;
|
||||
void on_register_raycasters_for_picking() override;
|
||||
void on_unregister_raycasters_for_picking() override;
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
bool on_is_activable() const override { return true; }
|
||||
bool on_is_selectable() const override { return false; }
|
||||
void on_set_state() override;
|
||||
void data_changed(bool is_serializing) override; // selection changed
|
||||
void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); }
|
||||
void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); }
|
||||
void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); }
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
|
||||
/// <summary>
|
||||
/// Rotate by text on dragging rotate grabers
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Information about mouse</param>
|
||||
/// <returns>Propagete normaly return false.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override;
|
||||
std::string get_gizmo_entering_text() const override;
|
||||
std::string get_gizmo_leaving_text() const override;
|
||||
std::string get_action_snapshot_name() const override;
|
||||
private:
|
||||
void set_volume_by_selection();
|
||||
void reset_volume();
|
||||
|
||||
// create volume from text - main functionality
|
||||
bool process();
|
||||
void close();
|
||||
void draw_window();
|
||||
void draw_preview();
|
||||
void draw_filename();
|
||||
void draw_depth();
|
||||
void draw_size();
|
||||
void draw_use_surface();
|
||||
void draw_distance();
|
||||
void draw_rotation();
|
||||
void draw_mirroring();
|
||||
void draw_face_the_camera();
|
||||
void draw_model_type();
|
||||
|
||||
// process mouse event
|
||||
bool on_mouse_for_rotation(const wxMouseEvent &mouse_event);
|
||||
bool on_mouse_for_translate(const wxMouseEvent &mouse_event);
|
||||
|
||||
void volume_transformation_changed();
|
||||
|
||||
struct GuiCfg;
|
||||
std::unique_ptr<const GuiCfg> m_gui_cfg;
|
||||
|
||||
// actual selected only one volume - with emboss data
|
||||
ModelVolume *m_volume = nullptr;
|
||||
|
||||
// Is used to edit eboss and send changes to job
|
||||
// Inside volume is current state of shape WRT Volume
|
||||
EmbossShape m_volume_shape; // copy from m_volume for edit
|
||||
|
||||
// same index as volumes in
|
||||
std::vector<std::string> m_shape_warnings;
|
||||
|
||||
// When work with undo redo stack there could be situation that
|
||||
// m_volume point to unexisting volume so One need also objectID
|
||||
ObjectID m_volume_id;
|
||||
|
||||
// cancel for previous update of volume to cancel finalize part
|
||||
std::shared_ptr<std::atomic<bool>> m_job_cancel = nullptr;
|
||||
|
||||
// Rotation gizmo
|
||||
GLGizmoRotate m_rotate_gizmo;
|
||||
std::optional<float> m_angle;
|
||||
std::optional<float> m_distance;
|
||||
|
||||
// Value is set only when dragging rotation to calculate actual angle
|
||||
std::optional<float> m_rotate_start_angle;
|
||||
|
||||
// TODO: it should be accessible by other gizmo too.
|
||||
// May be move to plater?
|
||||
RaycastManager m_raycast_manager;
|
||||
|
||||
// When true keep up vector otherwise relative rotation
|
||||
bool m_keep_up = true;
|
||||
|
||||
// Keep size aspect ratio when True.
|
||||
bool m_keep_ratio = true;
|
||||
|
||||
// Keep data about dragging only during drag&drop
|
||||
std::optional<SurfaceDrag> m_surface_drag;
|
||||
|
||||
// For volume on scaled objects
|
||||
std::optional<float> m_scale_width;
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> m_scale_depth;
|
||||
void calculate_scale();
|
||||
float get_scale_for_tolerance();
|
||||
|
||||
// keep SVG data rendered on GPU
|
||||
Texture m_texture;
|
||||
|
||||
// bounding box of shape
|
||||
// Note: Scaled mm to int value by m_volume_shape.scale
|
||||
BoundingBox m_shape_bb;
|
||||
|
||||
std::string m_filename_preview;
|
||||
|
||||
IconManager m_icon_manager;
|
||||
IconManager::VIcons m_icons;
|
||||
|
||||
// only temporary solution
|
||||
static const std::string M_ICON_FILENAME;
|
||||
};
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GLGizmoSVG_hpp_
|
||||
|
|
@ -48,7 +48,7 @@ std::string GLGizmoScale3D::get_tooltip() const
|
|||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
bool single_instance = selection.is_single_full_instance();
|
||||
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
|
||||
bool single_volume = selection.is_single_volume_or_modifier();
|
||||
|
||||
Vec3f scale = 100.0f * Vec3f::Ones();
|
||||
if (single_instance)
|
||||
|
|
@ -85,13 +85,21 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
|
|||
if (mouse_event.Dragging()) {
|
||||
if (m_dragging) {
|
||||
// Apply new temporary scale factors
|
||||
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
|
||||
Selection& selection = m_parent.get_selection();
|
||||
TransformationType transformation_type;
|
||||
if (selection.is_single_full_instance()) {
|
||||
transformation_type.set_instance();
|
||||
} else if (selection.is_single_volume_or_modifier()) {
|
||||
transformation_type.set_local();
|
||||
}
|
||||
|
||||
transformation_type.set_relative();
|
||||
|
||||
if (mouse_event.AltDown())
|
||||
transformation_type.set_independent();
|
||||
|
||||
Selection& selection = m_parent.get_selection();
|
||||
selection.scale(m_scale, transformation_type);
|
||||
if (mouse_event.CmdDown()) selection.translate(m_offset, true);
|
||||
if (mouse_event.CmdDown()) selection.translate(m_offset, transformation_type);
|
||||
}
|
||||
}
|
||||
return use_grabbers(mouse_event);
|
||||
|
|
@ -100,24 +108,11 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
|
|||
void GLGizmoScale3D::data_changed(bool is_serializing) {
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
bool enable_scale_xyz = selection.is_single_full_instance() ||
|
||||
selection.is_single_volume() ||
|
||||
selection.is_single_modifier();
|
||||
selection.is_single_volume_or_modifier();
|
||||
for (unsigned int i = 0; i < 6; ++i)
|
||||
m_grabbers[i].enabled = enable_scale_xyz;
|
||||
|
||||
if (enable_scale_xyz) {
|
||||
// all volumes in the selection belongs to the same instance, any of
|
||||
// them contains the needed data, so we take the first
|
||||
const GLVolume *volume = selection.get_first_volume();
|
||||
if (selection.is_single_full_instance()) {
|
||||
set_scale(volume->get_instance_scaling_factor());
|
||||
} else if (selection.is_single_volume() ||
|
||||
selection.is_single_modifier()) {
|
||||
set_scale(volume->get_volume_scaling_factor());
|
||||
}
|
||||
} else {
|
||||
set_scale(Vec3d::Ones());
|
||||
}
|
||||
set_scale(Vec3d::Ones());
|
||||
}
|
||||
|
||||
bool GLGizmoScale3D::on_init()
|
||||
|
|
@ -193,7 +188,7 @@ void GLGizmoScale3D::on_render()
|
|||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
bool single_instance = selection.is_single_full_instance();
|
||||
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
|
||||
bool single_volume = selection.is_single_volume_or_modifier();
|
||||
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ void GLGizmoSeam::show_tooltip_information(float caption_max, float x, float y)
|
|||
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
||||
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
||||
|
||||
caption_max += m_imgui->calc_text_size(": ").x + 35.f;
|
||||
caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 35.f;
|
||||
|
||||
float font_size = ImGui::GetFontSize();
|
||||
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
||||
|
|
|
|||
|
|
@ -582,7 +582,7 @@ void GLGizmoSimplify::on_set_state()
|
|||
|
||||
void GLGizmoSimplify::create_gui_cfg() {
|
||||
if (m_gui_cfg.has_value()) return;
|
||||
int space_size = m_imgui->calc_text_size(":MM").x;
|
||||
int space_size = m_imgui->calc_text_size(std::string_view{":MM"}).x;
|
||||
GuiCfg cfg;
|
||||
cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
|
||||
m_imgui->calc_text_size(tr_triangles).x)
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@
|
|||
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoText.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp"
|
||||
|
||||
#include "libslic3r/format.hpp"
|
||||
|
|
@ -152,7 +153,7 @@ void GLGizmosManager::switch_gizmos_icon_filename()
|
|||
case(EType::Seam):
|
||||
gizmo->set_icon_filename(m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg");
|
||||
break;
|
||||
case(EType::Text):
|
||||
case(EType::Emboss):
|
||||
gizmo->set_icon_filename(m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg");
|
||||
break;
|
||||
case(EType::MmuSegmentation):
|
||||
|
|
@ -198,7 +199,8 @@ bool GLGizmosManager::init()
|
|||
m_gizmos.emplace_back(new GLGizmoMeshBoolean(m_parent, m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg", EType::MeshBoolean));
|
||||
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports));
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam));
|
||||
m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text));
|
||||
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Emboss));
|
||||
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation));
|
||||
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure));
|
||||
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify));
|
||||
|
|
@ -251,27 +253,6 @@ bool GLGizmosManager::init_icon_textures()
|
|||
else
|
||||
return false;
|
||||
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/text_B.svg", 20, 20, texture_id))
|
||||
icon_list.insert(std::make_pair((int)IC_TEXT_B, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/text_B_dark.svg", 20, 20, texture_id))
|
||||
icon_list.insert(std::make_pair((int)IC_TEXT_B_DARK, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/text_T.svg", 20, 20, texture_id))
|
||||
icon_list.insert(std::make_pair((int)IC_TEXT_T, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/text_T_dark.svg", 20, 20, texture_id))
|
||||
icon_list.insert(std::make_pair((int)IC_TEXT_T_DARK, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +309,8 @@ void GLGizmosManager::reset_all_states()
|
|||
open_gizmo(current);
|
||||
|
||||
activate_gizmo(Undefined);
|
||||
m_hover = Undefined;
|
||||
// Orca: do not clear hover state, as Emboss gizmo can be used without selection
|
||||
//m_hover = Undefined;
|
||||
}
|
||||
|
||||
bool GLGizmosManager::open_gizmo(EType type)
|
||||
|
|
@ -447,8 +429,6 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
|
|||
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == MmuSegmentation)
|
||||
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Text)
|
||||
return dynamic_cast<GLGizmoText*>(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Measure)
|
||||
return dynamic_cast<GLGizmoMeasure*>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Cut)
|
||||
|
|
@ -609,7 +589,12 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) {
|
|||
// mouse is above toolbar
|
||||
if (mouse_event.LeftDown() || mouse_event.LeftDClick()) {
|
||||
mc.left = true;
|
||||
open_gizmo(gizmo);
|
||||
if (gizmo == Emboss) {
|
||||
GLGizmoBase *gizmo_emboss = m_gizmos[Emboss].get();
|
||||
dynamic_cast<GLGizmoEmboss *>(gizmo_emboss)->on_shortcut_key();
|
||||
} else {
|
||||
open_gizmo(gizmo);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (mouse_event.RightDown()) {
|
||||
|
|
@ -1129,12 +1114,14 @@ void GLGizmosManager::do_render_overlay() const
|
|||
const float v_bottom = v_top + dv - v_offset;
|
||||
|
||||
GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + icons_size_x, top_y - icons_size_y, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
|
||||
if (idx == m_current) {
|
||||
if (idx == m_current
|
||||
// Orca: Show Svg dialog at the same place as emboss gizmo
|
||||
|| (m_current == Svg && idx == Emboss)) {
|
||||
//BBS: GUI refactor: GLToolbar&&Gizmo adjust
|
||||
//render_input_window uses a different coordination(imgui)
|
||||
//1. no need to scale by camera zoom, set {0,0} at left-up corner for imgui
|
||||
//gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top);
|
||||
gizmo->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h);
|
||||
m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h);
|
||||
|
||||
is_render_current = true;
|
||||
}
|
||||
|
|
@ -1337,7 +1324,7 @@ std::string get_name_from_gizmo_etype(GLGizmosManager::EType type)
|
|||
return "FdmSupports";
|
||||
case GLGizmosManager::EType::Seam:
|
||||
return "Seam";
|
||||
case GLGizmosManager::EType::Text:
|
||||
case GLGizmosManager::EType::Emboss:
|
||||
return "Text";
|
||||
case GLGizmosManager::EType::MmuSegmentation:
|
||||
return "Color Painting";
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ public:
|
|||
MeshBoolean,
|
||||
FdmSupports,
|
||||
Seam,
|
||||
// BBS
|
||||
Text,
|
||||
Emboss,
|
||||
Svg,
|
||||
MmuSegmentation,
|
||||
Measure,
|
||||
Simplify,
|
||||
|
|
@ -163,10 +163,6 @@ public:
|
|||
IC_TOOLBAR_RESET_HOVER,
|
||||
IC_TOOLBAR_TOOLTIP,
|
||||
IC_TOOLBAR_TOOLTIP_HOVER,
|
||||
IC_TEXT_B,
|
||||
IC_TEXT_B_DARK,
|
||||
IC_TEXT_T,
|
||||
IC_TEXT_T_DARK,
|
||||
IC_NAME_COUNT,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
|
|
@ -19,7 +23,7 @@
|
|||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#define MAX_NUM 9999.99
|
||||
#define MAX_SIZE "9999.99"
|
||||
#define MAX_SIZE std::string_view{"9999.99"}
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
|
@ -81,7 +85,7 @@ void GizmoObjectManipulation::update_settings_value(const Selection& selection)
|
|||
m_new_rotate_label_string = L("Rotation");
|
||||
m_new_scale_label_string = L("Scale ratios");
|
||||
|
||||
m_world_coordinates = true;
|
||||
m_coordinates_type = ECoordinatesType::World;
|
||||
|
||||
ObjectList* obj_list = wxGetApp().obj_list();
|
||||
if (selection.is_single_full_instance()) {
|
||||
|
|
@ -89,7 +93,7 @@ void GizmoObjectManipulation::update_settings_value(const Selection& selection)
|
|||
const GLVolume* volume = selection.get_first_volume();
|
||||
m_new_position = volume->get_instance_offset();
|
||||
|
||||
if (m_world_coordinates) {
|
||||
if (is_world_coordinates()) {
|
||||
m_new_rotate_label_string = L("Rotate");
|
||||
m_new_rotation = volume->get_instance_rotation() * (180. / M_PI);
|
||||
m_new_size = selection.get_scaled_instance_bounding_box().size();
|
||||
|
|
@ -260,7 +264,15 @@ void GizmoObjectManipulation::change_position_value(int axis, double value)
|
|||
|
||||
Selection& selection = m_glcanvas.get_selection();
|
||||
selection.setup_cache();
|
||||
selection.translate(position - m_cache.position, selection.requires_local_axes());
|
||||
TransformationType trafo_type;
|
||||
trafo_type.set_relative();
|
||||
switch (m_coordinates_type)
|
||||
{
|
||||
case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
|
||||
case ECoordinatesType::Local: { trafo_type.set_local(); break; }
|
||||
default: { break; }
|
||||
}
|
||||
selection.translate(position - m_cache.position, trafo_type);
|
||||
m_glcanvas.do_move(L("Set Position"));
|
||||
|
||||
m_cache.position = position;
|
||||
|
|
@ -278,14 +290,16 @@ void GizmoObjectManipulation::change_rotation_value(int axis, double value)
|
|||
|
||||
Selection& selection = m_glcanvas.get_selection();
|
||||
|
||||
TransformationType transformation_type(TransformationType::World_Relative_Joint);
|
||||
if (selection.is_single_full_instance() || selection.requires_local_axes())
|
||||
transformation_type.set_independent();
|
||||
if (selection.is_single_full_instance() && ! m_world_coordinates) {
|
||||
//FIXME Selection::rotate() does not process absoulte rotations correctly: It does not recognize the axis index, which was changed.
|
||||
// transformation_type.set_absolute();
|
||||
transformation_type.set_local();
|
||||
}
|
||||
TransformationType transformation_type;
|
||||
transformation_type.set_relative();
|
||||
if (selection.is_single_full_instance())
|
||||
transformation_type.set_independent();
|
||||
|
||||
if (is_local_coordinates())
|
||||
transformation_type.set_local();
|
||||
|
||||
if (is_instance_coordinates())
|
||||
transformation_type.set_instance();
|
||||
|
||||
selection.setup_cache();
|
||||
selection.rotate(
|
||||
|
|
@ -336,7 +350,7 @@ void GizmoObjectManipulation::change_size_value(int axis, double value)
|
|||
ref_size = Vec3d(instance_scale[0] * ref_size[0], instance_scale[1] * ref_size[1], instance_scale[2] * ref_size[2]);
|
||||
}
|
||||
else if (selection.is_single_full_instance())
|
||||
ref_size = m_world_coordinates ?
|
||||
ref_size = is_world_coordinates() ?
|
||||
selection.get_unscaled_instance_bounding_box().size() :
|
||||
wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size();
|
||||
|
||||
|
|
@ -355,7 +369,7 @@ void GizmoObjectManipulation::do_scale(int axis, const Vec3d &scale) const
|
|||
TransformationType transformation_type(TransformationType::World_Relative_Joint);
|
||||
if (selection.is_single_full_instance()) {
|
||||
transformation_type.set_absolute();
|
||||
if (! m_world_coordinates)
|
||||
if (! is_world_coordinates())
|
||||
transformation_type.set_local();
|
||||
}
|
||||
|
||||
|
|
@ -364,7 +378,7 @@ void GizmoObjectManipulation::do_scale(int axis, const Vec3d &scale) const
|
|||
scaling_factor = scale(axis) * Vec3d::Ones();
|
||||
|
||||
selection.setup_cache();
|
||||
selection.scale(scaling_factor * 0.01, transformation_type);
|
||||
selection.scale_legacy(scaling_factor * 0.01, transformation_type);
|
||||
m_glcanvas.do_scale(L("Set Scale"));
|
||||
}
|
||||
|
||||
|
|
@ -386,6 +400,51 @@ void GizmoObjectManipulation::on_change(const std::string& opt_key, int axis, do
|
|||
change_size_value(axis, new_value);
|
||||
}
|
||||
|
||||
void GizmoObjectManipulation::set_uniform_scaling(const bool new_value)
|
||||
{
|
||||
const Selection &selection = m_glcanvas.get_selection();
|
||||
if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) {
|
||||
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
|
||||
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
||||
const GLVolume* volume = selection.get_first_volume();
|
||||
// Is the angle close to a multiple of 90 degrees?
|
||||
|
||||
if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
|
||||
// Cannot apply scaling in the world coordinate system.
|
||||
// BBS: remove tilt prompt dialog
|
||||
|
||||
// Bake the rotation into the meshes of the object.
|
||||
wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
|
||||
// Update the 3D scene, selections etc.
|
||||
wxGetApp().plater()->update();
|
||||
// Recalculate cached values at this panel, refresh the screen.
|
||||
this->UpdateAndShow(true);
|
||||
}
|
||||
}
|
||||
m_uniform_scale = new_value;
|
||||
}
|
||||
|
||||
void GizmoObjectManipulation::set_coordinates_type(ECoordinatesType type)
|
||||
{
|
||||
if (wxGetApp().get_mode() == comSimple)
|
||||
type = ECoordinatesType::World;
|
||||
|
||||
if (m_coordinates_type == type)
|
||||
return;
|
||||
|
||||
m_coordinates_type = type;
|
||||
this->UpdateAndShow(true);
|
||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||
canvas->get_gizmos_manager().update_data();
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
|
||||
ECoordinatesType GizmoObjectManipulation::get_coordinates_type() const
|
||||
{
|
||||
return m_coordinates_type;
|
||||
}
|
||||
|
||||
void GizmoObjectManipulation::reset_position_value()
|
||||
{
|
||||
Selection& selection = m_glcanvas.get_selection();
|
||||
|
|
@ -427,7 +486,7 @@ void GizmoObjectManipulation::reset_rotation_value()
|
|||
return;
|
||||
|
||||
// Update rotation at the GLVolumes.
|
||||
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
||||
selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL);
|
||||
selection.synchronize_unselected_volumes();
|
||||
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
|
||||
m_glcanvas.do_rotate(L("Reset Rotation"));
|
||||
|
|
@ -444,30 +503,6 @@ void GizmoObjectManipulation::reset_scale_value()
|
|||
change_scale_value(2, 100.);
|
||||
}
|
||||
|
||||
void GizmoObjectManipulation::set_uniform_scaling(const bool new_value)
|
||||
{
|
||||
const Selection &selection = m_glcanvas.get_selection();
|
||||
if (selection.is_single_full_instance() && m_world_coordinates && !new_value) {
|
||||
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
|
||||
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
||||
const GLVolume* volume = selection.get_first_volume();
|
||||
// Is the angle close to a multiple of 90 degrees?
|
||||
|
||||
if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
|
||||
// Cannot apply scaling in the world coordinate system.
|
||||
// BBS: remove tilt prompt dialog
|
||||
|
||||
// Bake the rotation into the meshes of the object.
|
||||
wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
|
||||
// Update the 3D scene, selections etc.
|
||||
wxGetApp().plater()->update();
|
||||
// Recalculate cached values at this panel, refresh the screen.
|
||||
this->UpdateAndShow(true);
|
||||
}
|
||||
}
|
||||
m_uniform_scale = new_value;
|
||||
}
|
||||
|
||||
static const char* label_values[2][3] = {
|
||||
{ "##position_x", "##position_y", "##position_z"},
|
||||
{ "##rotation_x", "##rotation_y", "##rotation_z"}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include "libslic3r/Point.hpp"
|
||||
#include <float.h>
|
||||
|
||||
#include "slic3r/GUI/GUI_Geometry.hpp"
|
||||
|
||||
//#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -81,8 +83,7 @@ public:
|
|||
Vec3d m_buffered_size;
|
||||
bool m_new_enabled {true};
|
||||
bool m_uniform_scale {true};
|
||||
// Does the object manipulation panel work in World or Local coordinates?
|
||||
bool m_world_coordinates = true;
|
||||
ECoordinatesType m_coordinates_type{ ECoordinatesType::World };
|
||||
|
||||
bool m_show_clear_rotation { false };
|
||||
bool m_show_clear_scale { false };
|
||||
|
|
@ -107,9 +108,12 @@ public:
|
|||
|
||||
void set_uniform_scaling(const bool uniform_scale);
|
||||
bool get_uniform_scaling() const { return m_uniform_scale; }
|
||||
// Does the object manipulation panel work in World or Local coordinates?
|
||||
void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); }
|
||||
bool get_world_coordinates() const { return m_world_coordinates; }
|
||||
|
||||
void set_coordinates_type(ECoordinatesType type);
|
||||
ECoordinatesType get_coordinates_type() const;
|
||||
bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; }
|
||||
bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; }
|
||||
bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; }
|
||||
|
||||
void reset_cache() { m_cache.reset(); }
|
||||
|
||||
|
|
|
|||
410
src/slic3r/GUI/IconManager.cpp
Normal file
410
src/slic3r/GUI/IconManager.cpp
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
#include "IconManager.hpp"
|
||||
#include <cmath>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
#include "libslic3r/Utils.hpp" // ScopeGuard
|
||||
|
||||
#include "3DScene.hpp" // glsafe
|
||||
#include "GL/glew.h"
|
||||
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#include "imgui/imstb_rectpack.h" // distribute rectangles
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
namespace priv {
|
||||
// set shared pointer to point on bad texture
|
||||
static void clear(IconManager::Icons &icons);
|
||||
static const std::vector<std::pair<int, bool>>& get_states(IconManager::RasterType type);
|
||||
static void draw_transparent_icon(const IconManager::Icon &icon); // only help function
|
||||
}
|
||||
|
||||
IconManager::~IconManager() {
|
||||
priv::clear(m_icons);
|
||||
// release opengl texture is made in ~GLTexture()
|
||||
|
||||
if (m_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
}
|
||||
|
||||
namespace {
|
||||
NSVGimage *parse_file(const char * filepath) {
|
||||
FILE *fp = boost::nowide::fopen(filepath, "rb");
|
||||
assert(fp != nullptr);
|
||||
if (fp == nullptr)
|
||||
return nullptr;
|
||||
|
||||
Slic3r::ScopeGuard sg([fp]() { fclose(fp); });
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size_t size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
// Note: +1 is for null termination
|
||||
auto data_ptr = std::make_unique<char[]>(size+1);
|
||||
data_ptr[size] = '\0'; // Must be null terminated.
|
||||
|
||||
size_t readed_size = fread(data_ptr.get(), 1, size, fp);
|
||||
assert(readed_size == size);
|
||||
if (readed_size != size)
|
||||
return nullptr;
|
||||
|
||||
return nsvgParse(data_ptr.get(), "px", 96.0f);
|
||||
}
|
||||
|
||||
void subdata(unsigned char *data, size_t data_stride, const std::vector<unsigned char> &data2, size_t data2_row) {
|
||||
assert(data_stride >= data2_row);
|
||||
for (size_t data2_offset = 0, data_offset = 0;
|
||||
data2_offset < data2.size();
|
||||
data2_offset += data2_row, data_offset += data_stride)
|
||||
::memcpy((void *)(data + data_offset), (const void *)(data2.data() + data2_offset), data2_row);
|
||||
}
|
||||
}
|
||||
|
||||
IconManager::Icons IconManager::init(const InitTypes &input)
|
||||
{
|
||||
assert(!input.empty());
|
||||
if (input.empty())
|
||||
return {};
|
||||
|
||||
// TODO: remove in future
|
||||
if (m_id != 0) {
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
m_id = 0;
|
||||
}
|
||||
|
||||
int total_surface = 0;
|
||||
for (const InitType &i : input)
|
||||
total_surface += i.size.x * i.size.y;
|
||||
const int surface_sqrt = (int)sqrt((float)total_surface) + 1;
|
||||
|
||||
// Start packing
|
||||
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
|
||||
const int TEX_HEIGHT_MAX = 1024 * 32;
|
||||
int width = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512;
|
||||
|
||||
int num_nodes = width;
|
||||
std::vector<stbrp_node> nodes(num_nodes);
|
||||
stbrp_context context;
|
||||
stbrp_init_target(&context, width, TEX_HEIGHT_MAX, nodes.data(), num_nodes);
|
||||
|
||||
ImVector<stbrp_rect> pack_rects;
|
||||
pack_rects.resize(input.size());
|
||||
memset(pack_rects.Data, 0, (size_t) pack_rects.size_in_bytes());
|
||||
for (size_t i = 0; i < input.size(); i++) {
|
||||
const ImVec2 &size = input[i].size;
|
||||
assert(size.x > 1);
|
||||
assert(size.y > 1);
|
||||
pack_rects[i].w = size.x;
|
||||
pack_rects[i].h = size.y;
|
||||
}
|
||||
int pack_rects_res = stbrp_pack_rects(&context, &pack_rects[0], pack_rects.Size);
|
||||
assert(pack_rects_res == 1);
|
||||
if (pack_rects_res != 1)
|
||||
return {};
|
||||
|
||||
ImVec2 tex_size(width, width);
|
||||
for (const stbrp_rect &rect : pack_rects) {
|
||||
float x = rect.x + rect.w;
|
||||
float y = rect.y + rect.h;
|
||||
if(x > tex_size.x) tex_size.x = x;
|
||||
if(y > tex_size.y) tex_size.y = y;
|
||||
}
|
||||
|
||||
Icons result(input.size());
|
||||
for (int i = 0; i < pack_rects.Size; i++) {
|
||||
const stbrp_rect &rect = pack_rects[i];
|
||||
assert(rect.was_packed);
|
||||
if (!rect.was_packed)
|
||||
return {};
|
||||
|
||||
ImVec2 tl(rect.x / tex_size.x, rect.y / tex_size.y);
|
||||
ImVec2 br((rect.x + rect.w) / tex_size.x, (rect.y + rect.h) / tex_size.y);
|
||||
|
||||
assert(input[i].size.x == rect.w);
|
||||
assert(input[i].size.y == rect.h);
|
||||
Icon icon = {input[i].size, tl, br};
|
||||
result[i] = std::make_shared<Icon>(std::move(icon));
|
||||
}
|
||||
|
||||
NSVGrasterizer *rast = nsvgCreateRasterizer();
|
||||
assert(rast != nullptr);
|
||||
if (rast == nullptr)
|
||||
return {};
|
||||
ScopeGuard sg_rast([rast]() { ::nsvgDeleteRasterizer(rast); });
|
||||
|
||||
int channels = 4;
|
||||
int n_pixels = tex_size.x * tex_size.y;
|
||||
// store data for whole texture
|
||||
std::vector<unsigned char> data(n_pixels * channels, {0});
|
||||
|
||||
// initialize original index locations
|
||||
std::vector<size_t> idx(input.size());
|
||||
std::iota(idx.begin(), idx.end(), 0);
|
||||
|
||||
// Group same filename by sort inputs
|
||||
// sort indexes based on comparing values in input
|
||||
std::sort(idx.begin(), idx.end(), [&input](size_t i1, size_t i2) { return input[i1].filepath < input[i2].filepath; });
|
||||
for (size_t j: idx) {
|
||||
const InitType &i = input[j];
|
||||
if (i.filepath.empty())
|
||||
continue; // no file path only reservation of space for texture
|
||||
assert(boost::filesystem::exists(i.filepath));
|
||||
if (!boost::filesystem::exists(i.filepath))
|
||||
continue;
|
||||
assert(boost::algorithm::iends_with(i.filepath, ".svg"));
|
||||
if (!boost::algorithm::iends_with(i.filepath, ".svg"))
|
||||
continue;
|
||||
|
||||
NSVGimage *image = parse_file(i.filepath.c_str());
|
||||
assert(image != nullptr);
|
||||
if (image == nullptr)
|
||||
return {};
|
||||
|
||||
ScopeGuard sg_image([image]() { ::nsvgDelete(image); });
|
||||
|
||||
float svg_scale = i.size.y / image->height;
|
||||
// scale should be same in both directions
|
||||
assert(is_approx(svg_scale, i.size.y / image->width));
|
||||
|
||||
const stbrp_rect &rect = pack_rects[j];
|
||||
int n_pixels = rect.w * rect.h;
|
||||
std::vector<unsigned char> icon_data(n_pixels * channels, {0});
|
||||
::nsvgRasterize(rast, image, 0, 0, svg_scale, icon_data.data(), i.size.x, i.size.y, i.size.x * channels);
|
||||
|
||||
// makes white or gray only data in icon
|
||||
if (i.type == RasterType::white_only_data ||
|
||||
i.type == RasterType::gray_only_data) {
|
||||
unsigned char value = (i.type == RasterType::white_only_data) ? 255 : 127;
|
||||
for (size_t k = 0; k < icon_data.size(); k += channels)
|
||||
if (icon_data[k] != 0 || icon_data[k + 1] != 0 || icon_data[k + 2] != 0) {
|
||||
icon_data[k] = value;
|
||||
icon_data[k + 1] = value;
|
||||
icon_data[k + 2] = value;
|
||||
}
|
||||
}
|
||||
|
||||
int start_offset = (rect.y*tex_size.x + rect.x) * channels;
|
||||
int data_stride = tex_size.x * channels;
|
||||
subdata(data.data() + start_offset, data_stride, icon_data, rect.w * channels);
|
||||
}
|
||||
|
||||
if (m_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||
glsafe(::glGenTextures(1, &m_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint) m_id));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) tex_size.x, (GLsizei) tex_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*) data.data()));
|
||||
|
||||
// bind no texture
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
for (const auto &i : result)
|
||||
i->tex_id = m_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<IconManager::Icons> IconManager::init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type)
|
||||
{
|
||||
assert(!file_paths.empty());
|
||||
assert(size.x >= 1);
|
||||
assert(size.x < 256*16);
|
||||
|
||||
// TODO: remove in future
|
||||
if (!m_icons.empty()) {
|
||||
// not first initialization
|
||||
priv::clear(m_icons);
|
||||
m_icons.clear();
|
||||
m_icons_texture.reset();
|
||||
}
|
||||
|
||||
// only rectangle are supported
|
||||
assert(size.x == size.y);
|
||||
// no subpixel supported
|
||||
unsigned int width = static_cast<unsigned int>(std::abs(std::round(size.x)));
|
||||
assert(size.x == static_cast<float>(width));
|
||||
|
||||
// state order has to match the enum IconState
|
||||
const auto& states = priv::get_states(type);
|
||||
|
||||
bool compress = false;
|
||||
bool is_loaded = m_icons_texture.load_from_svg_files_as_sprites_array(file_paths, states, width, compress);
|
||||
if (!is_loaded || (size_t) m_icons_texture.get_width() < (states.size() * width) ||
|
||||
(size_t) m_icons_texture.get_height() < (file_paths.size() * width)) {
|
||||
// bad load of icons, but all usage of m_icons_texture check that texture is initialized
|
||||
assert(false);
|
||||
m_icons_texture.reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
unsigned count_files = file_paths.size();
|
||||
// count icons per file
|
||||
unsigned count = states.size();
|
||||
// create result
|
||||
std::vector<Icons> result;
|
||||
result.reserve(count_files);
|
||||
|
||||
Icon def_icon;
|
||||
def_icon.tex_id = m_icons_texture.get_id();
|
||||
def_icon.size = size;
|
||||
|
||||
// float beacouse of dividing
|
||||
float tex_height = static_cast<float>(m_icons_texture.get_height());
|
||||
float tex_width = static_cast<float>(m_icons_texture.get_width());
|
||||
|
||||
//for (const auto &f: file_paths) {
|
||||
for (unsigned f = 0; f < count_files; ++f) {
|
||||
// NOTE: there are space between icons
|
||||
unsigned start_y = static_cast<unsigned>(f) * (width + 1) + 1;
|
||||
float y1 = start_y / tex_height;
|
||||
float y2 = (start_y + width) / tex_height;
|
||||
Icons file_icons;
|
||||
file_icons.reserve(count);
|
||||
//for (const auto &s : states) {
|
||||
for (unsigned j = 0; j < count; ++j) {
|
||||
auto icon = std::make_shared<Icon>(def_icon);
|
||||
// NOTE: there are space between icons
|
||||
unsigned start_x = static_cast<unsigned>(j) * (width + 1) + 1;
|
||||
float x1 = start_x / tex_width;
|
||||
float x2 = (start_x + width) / tex_width;
|
||||
icon->tl = ImVec2(x1, y1);
|
||||
icon->br = ImVec2(x2, y2);
|
||||
file_icons.push_back(icon);
|
||||
m_icons.push_back(std::move(icon));
|
||||
}
|
||||
result.emplace_back(std::move(file_icons));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void IconManager::release() {
|
||||
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
|
||||
}
|
||||
|
||||
void priv::clear(IconManager::Icons &icons) {
|
||||
std::string message;
|
||||
for (auto &icon : icons) {
|
||||
// Exist more than this instance of shared ptr?
|
||||
long count = icon.use_count();
|
||||
if (count != 1) {
|
||||
// in existing icon change texture to non existing one
|
||||
icon->tex_id = 0;
|
||||
|
||||
std::string descr =
|
||||
((count > 2) ? (std::to_string(count - 1) + "x") : "") + // count
|
||||
std::to_string(icon->size.x) + "x" + std::to_string(icon->size.y); // resolution
|
||||
if (message.empty())
|
||||
message = descr;
|
||||
else
|
||||
message += ", " + descr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "There is still used icons(" << message << ").";
|
||||
}
|
||||
|
||||
const std::vector<std::pair<int, bool>> &priv::get_states(IconManager::RasterType type) {
|
||||
static std::vector<std::pair<int, bool>> color = {std::make_pair(0, false)};
|
||||
static std::vector<std::pair<int, bool>> white = {std::make_pair(1, false)};
|
||||
static std::vector<std::pair<int, bool>> gray = {std::make_pair(2, false)};
|
||||
static std::vector<std::pair<int, bool>> color_wite_gray = {
|
||||
std::make_pair(1, false), // Activable
|
||||
std::make_pair(0, false), // Hovered
|
||||
std::make_pair(2, false) // Disabled
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case IconManager::RasterType::color: return color;
|
||||
case IconManager::RasterType::white_only_data: return white;
|
||||
case IconManager::RasterType::gray_only_data: return gray;
|
||||
case IconManager::RasterType::color_wite_gray: return color_wite_gray;
|
||||
default: return color;
|
||||
}
|
||||
}
|
||||
|
||||
void priv::draw_transparent_icon(const IconManager::Icon &icon)
|
||||
{
|
||||
// Check input
|
||||
if (!icon.is_valid()) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon.";
|
||||
ImGui::Text("?");
|
||||
return;
|
||||
}
|
||||
|
||||
// size UV texture coors [in texture ratio]
|
||||
ImVec2 size_uv(icon.br.x - icon.tl.x, icon.br.y - icon.tl.y);
|
||||
ImVec2 one_px(size_uv.x / icon.size.x, size_uv.y / icon.size.y);
|
||||
|
||||
// use top left corner of first icon
|
||||
IconManager::Icon icon_px = icon; // copy
|
||||
// reduce uv coors to one pixel
|
||||
icon_px.tl = ImVec2(0, 0);
|
||||
icon_px.br = one_px;
|
||||
draw(icon_px);
|
||||
}
|
||||
|
||||
#include "imgui/imgui_internal.h" //ImGuiWindow
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_col, const ImVec4 &border_col)
|
||||
{
|
||||
// Check input
|
||||
if (!icon.is_valid()) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon.";
|
||||
ImGui::Text("?");
|
||||
return;
|
||||
}
|
||||
ImTextureID id = (void *)static_cast<intptr_t>(icon.tex_id);
|
||||
const ImVec2 &s = (size.x < 1 || size.y < 1) ? icon.size : size;
|
||||
|
||||
// Orca: Align icon center vertically
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
ImGuiContext &g = *GImGui;
|
||||
float cursor_y = window->DC.CursorPos.y;
|
||||
float line_height = ImGui::GetTextLineHeight() + g.Style.FramePadding.y * 2;
|
||||
float offset_y = (line_height - s.y) / 2;
|
||||
window->DC.CursorPos.y += offset_y;
|
||||
|
||||
ImGui::Image(id, s, icon.tl, icon.br, tint_col, border_col);
|
||||
|
||||
// Reset offset
|
||||
window->DC.CursorPosPrevLine.y = cursor_y;
|
||||
}
|
||||
|
||||
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover)
|
||||
{
|
||||
// check of hover
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
float cursor_x = ImGui::GetCursorPosX()
|
||||
- window->DC.GroupOffset.x
|
||||
- window->DC.ColumnsOffset.x;
|
||||
priv::draw_transparent_icon(icon);
|
||||
ImGui::SameLine(cursor_x);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
// redraw image
|
||||
draw(icon_hover);
|
||||
} else {
|
||||
// redraw normal image
|
||||
draw(icon);
|
||||
}
|
||||
return ImGui::IsItemClicked();
|
||||
}
|
||||
|
||||
bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled)
|
||||
{
|
||||
if (disabled) {
|
||||
draw(disable);
|
||||
return false;
|
||||
}
|
||||
return clickable(activ, hover);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
134
src/slic3r/GUI/IconManager.hpp
Normal file
134
src/slic3r/GUI/IconManager.hpp
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#ifndef slic3r_IconManager_hpp_
|
||||
#define slic3r_IconManager_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "imgui/imgui.h" // ImVec2
|
||||
#include "slic3r/GUI/GLTexture.hpp" // texture storage
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
/// <summary>
|
||||
/// Keep texture with icons for UI
|
||||
/// Manage texture live -> create and destruct texture
|
||||
/// by live of icon shared pointers.
|
||||
/// </summary>
|
||||
class IconManager
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Release texture
|
||||
/// Set shared pointers to invalid texture
|
||||
/// </summary>
|
||||
~IconManager();
|
||||
|
||||
/// <summary>
|
||||
/// Define way to convert svg data to raster
|
||||
/// </summary>
|
||||
enum class RasterType: int{
|
||||
color = 1 << 1,
|
||||
white_only_data = 1 << 2,
|
||||
gray_only_data = 1 << 3,
|
||||
color_wite_gray = color | white_only_data | gray_only_data
|
||||
// TODO: add type with backgrounds
|
||||
};
|
||||
|
||||
struct InitType {
|
||||
// path to file with image .. svg
|
||||
std::string filepath;
|
||||
|
||||
// resolution of stored rasterized icon
|
||||
ImVec2 size; // float will be rounded
|
||||
|
||||
// could contain more than one type
|
||||
RasterType type = RasterType::color;
|
||||
// together color, white and gray = color | white_only_data | gray_only_data
|
||||
};
|
||||
using InitTypes = std::vector<InitType>;
|
||||
|
||||
/// <summary>
|
||||
/// Data for render texture with icon
|
||||
/// </summary>
|
||||
struct Icon {
|
||||
// stored texture size
|
||||
ImVec2 size = ImVec2(-1, -1); // [in px] --> unsigned int values stored as float
|
||||
|
||||
// SubTexture UV coordinate in range from 0. to 1.
|
||||
ImVec2 tl; // top left -> uv0
|
||||
ImVec2 br; // bottom right -> uv1
|
||||
|
||||
// OpenGL texture id
|
||||
unsigned int tex_id = 0;
|
||||
bool is_valid() const { return tex_id != 0;}
|
||||
// && size.x > 0 && size.y > 0 && tl.x != br.x && tl.y != br.y;
|
||||
};
|
||||
using Icons = std::vector<std::shared_ptr<Icon> >;
|
||||
// Vector of icons, each vector contain multiple use of a SVG render
|
||||
using VIcons = std::vector<Icons>;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize raster texture on GPU with given images
|
||||
/// NOTE: Have to be called after OpenGL initialization
|
||||
/// </summary>
|
||||
/// <param name="input">Define files and its size with rasterization</param>
|
||||
/// <returns>Rasterized icons stored on GPU,
|
||||
/// Same size and order as input, each item of vector is set of texture in order by RasterType</returns>
|
||||
Icons init(const InitTypes &input);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize multiple icons with same settings for size and type
|
||||
/// NOTE: Have to be called after OpenGL initialization
|
||||
/// </summary>
|
||||
/// <param name="file_paths">Define files with icon</param>
|
||||
/// <param name="size">Size of stored texture[in px], float will be rounded</param>
|
||||
/// <param name="type">Define way to rasterize icon,
|
||||
/// together color, white and gray = RasterType::color | RasterType::white_only_data | RasterType::gray_only_data</param>
|
||||
/// <returns>Rasterized icons stored on GPU,
|
||||
/// Same size and order as file_paths, each item of vector is set of texture in order by RasterType</returns>
|
||||
VIcons init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type = RasterType::color);
|
||||
|
||||
/// <summary>
|
||||
/// Release icons which are hold only by this manager
|
||||
/// May change texture and position of icons.
|
||||
/// </summary>
|
||||
void release();
|
||||
|
||||
private:
|
||||
// keep data stored on GPU
|
||||
GLTexture m_icons_texture;
|
||||
|
||||
unsigned int m_id{ 0 };
|
||||
Icons m_icons;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Draw imgui image with icon
|
||||
/// </summary>
|
||||
/// <param name="icon">Place in texture</param>
|
||||
/// <param name="size">[optional]Size of image, wen zero than use same size as stored texture</param>
|
||||
/// <param name="tint_col">viz ImGui::Image </param>
|
||||
/// <param name="border_col">viz ImGui::Image </param>
|
||||
void draw(const IconManager::Icon &icon,
|
||||
const ImVec2 &size = ImVec2(0, 0),
|
||||
const ImVec4 &tint_col = ImVec4(1, 1, 1, 1),
|
||||
const ImVec4 &border_col = ImVec4(0, 0, 0, 0));
|
||||
|
||||
/// <summary>
|
||||
/// Draw icon which change on hover
|
||||
/// </summary>
|
||||
/// <param name="icon">Draw when no hover</param>
|
||||
/// <param name="icon_hover">Draw when hover</param>
|
||||
/// <returns>True when click, otherwise False</returns>
|
||||
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover);
|
||||
|
||||
/// <summary>
|
||||
/// Use icon as button with 3 states activ hover and disabled
|
||||
/// </summary>
|
||||
/// <param name="activ">Not disabled not hovered image</param>
|
||||
/// <param name="hover">Hovered image</param>
|
||||
/// <param name="disable">Disabled image</param>
|
||||
/// <returns>True when click on enabled, otherwise False</returns>
|
||||
bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled = false);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_IconManager_hpp_
|
||||
|
|
@ -520,9 +520,24 @@ void ImGuiWrapper::render()
|
|||
m_new_frame_open = false;
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::calc_text_size(std::string_view text,
|
||||
bool hide_text_after_double_hash,
|
||||
float wrap_width)
|
||||
{
|
||||
return ImGui::CalcTextSize(text.data(), text.data() + text.length(),
|
||||
hide_text_after_double_hash, wrap_width);
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::calc_text_size(const std::string& text,
|
||||
bool hide_text_after_double_hash,
|
||||
float wrap_width)
|
||||
{
|
||||
return ImGui::CalcTextSize(text.c_str(), NULL, hide_text_after_double_hash, wrap_width);
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text,
|
||||
bool hide_text_after_double_hash,
|
||||
float wrap_width) const
|
||||
float wrap_width)
|
||||
{
|
||||
auto text_utf8 = into_u8(text);
|
||||
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, hide_text_after_double_hash, wrap_width);
|
||||
|
|
@ -585,8 +600,8 @@ bool ImGuiWrapper::bbl_combo_with_filter(const char* label, const std::string& p
|
|||
|
||||
static char pattern_buffer[256] = { 0 };
|
||||
auto simple_match = [](const char* pattern, const char* str) {
|
||||
wxString sub_str = wxString(pattern).Lower();
|
||||
wxString main_str = wxString(str).Lower();
|
||||
wxString sub_str = wxString::FromUTF8(pattern).Lower();
|
||||
wxString main_str = wxString::FromUTF8(str).Lower();
|
||||
return main_str.Find(sub_str);
|
||||
};
|
||||
|
||||
|
|
@ -911,13 +926,13 @@ void ImGuiWrapper::text(const char *label)
|
|||
|
||||
void ImGuiWrapper::text(const std::string &label)
|
||||
{
|
||||
this->text(label.c_str());
|
||||
ImGuiWrapper::text(label.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text(const wxString &label)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
this->text(label_utf8.c_str());
|
||||
ImGuiWrapper::text(label_utf8.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text_colored(const ImVec4& color, const char* label)
|
||||
|
|
@ -927,13 +942,13 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const char* label)
|
|||
|
||||
void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label)
|
||||
{
|
||||
this->text_colored(color, label.c_str());
|
||||
ImGuiWrapper::text_colored(color, label.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
this->text_colored(color, label_utf8.c_str());
|
||||
ImGuiWrapper::text_colored(color, label_utf8.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text_wrapped(const char *label, float wrap_width)
|
||||
|
|
@ -1940,6 +1955,364 @@ ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color)
|
|||
return { color.x, color.y, color.z, color.w };
|
||||
}
|
||||
|
||||
template <typename T, typename Func>
|
||||
static bool input_optional(std::optional<T> &v, Func& f, std::function<bool(const T&)> is_default, const T& def_val)
|
||||
{
|
||||
if (v.has_value()) {
|
||||
if (f(*v)) {
|
||||
if (is_default(*v)) v.reset();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
T val = def_val;
|
||||
if (f(val)) {
|
||||
if (!is_default(val)) v = val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_optional_int(const char * label,
|
||||
std::optional<int>& v,
|
||||
int step,
|
||||
int step_fast,
|
||||
ImGuiInputTextFlags flags,
|
||||
int def_val)
|
||||
{
|
||||
auto func = [&](int &value) {
|
||||
return ImGui::InputInt(label, &value, step, step_fast, flags);
|
||||
};
|
||||
std::function<bool(const int &)> is_default =
|
||||
[def_val](const int &value) -> bool { return value == def_val; };
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_optional_float(const char * label,
|
||||
std::optional<float> &v,
|
||||
float step,
|
||||
float step_fast,
|
||||
const char * format,
|
||||
ImGuiInputTextFlags flags,
|
||||
float def_val)
|
||||
{
|
||||
auto func = [&](float &value) {
|
||||
return ImGui::InputFloat(label, &value, step, step_fast, format, flags);
|
||||
};
|
||||
std::function<bool(const float &)> is_default =
|
||||
[def_val](const float &value) -> bool {
|
||||
return std::fabs(value-def_val) <= std::numeric_limits<float>::epsilon();
|
||||
};
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::drag_optional_float(const char * label,
|
||||
std::optional<float> &v,
|
||||
float v_speed,
|
||||
float v_min,
|
||||
float v_max,
|
||||
const char * format,
|
||||
float power,
|
||||
float def_val)
|
||||
{
|
||||
auto func = [&](float &value) {
|
||||
return ImGui::DragFloat(label, &value, v_speed, v_min, v_max, format, power);
|
||||
};
|
||||
std::function<bool(const float &)> is_default =
|
||||
[def_val](const float &value) -> bool {
|
||||
return std::fabs(value-def_val) <= std::numeric_limits<float>::epsilon();
|
||||
};
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_optional_float(const char *label,
|
||||
std::optional<float> &v,
|
||||
float v_min,
|
||||
float v_max,
|
||||
const char *format,
|
||||
float power,
|
||||
bool clamp,
|
||||
const wxString &tooltip,
|
||||
bool show_edit_btn,
|
||||
float def_val)
|
||||
{
|
||||
auto func = [&](float &value) {
|
||||
return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn);
|
||||
};
|
||||
std::function<bool(const float &)> is_default =
|
||||
[def_val](const float &value) -> bool {
|
||||
return std::fabs(value - def_val) <= std::numeric_limits<float>::epsilon();
|
||||
};
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_optional_int(const char *label,
|
||||
std::optional<int> &v,
|
||||
int v_min,
|
||||
int v_max,
|
||||
const char *format,
|
||||
float power,
|
||||
bool clamp,
|
||||
const wxString &tooltip,
|
||||
bool show_edit_btn,
|
||||
int def_val)
|
||||
{
|
||||
std::optional<float> val;
|
||||
if (v.has_value()) val = static_cast<float>(*v);
|
||||
auto func = [&](float &value) {
|
||||
return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn);
|
||||
};
|
||||
std::function<bool(const float &)> is_default =
|
||||
[def_val](const float &value) -> bool {
|
||||
return std::fabs(value - def_val) < 0.9f;
|
||||
};
|
||||
|
||||
float default_value = static_cast<float>(def_val);
|
||||
if (input_optional(val, func, is_default, default_value)) {
|
||||
if (val.has_value())
|
||||
v = static_cast<int>(std::round(*val));
|
||||
else
|
||||
v.reset();
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
std::optional<ImVec2> ImGuiWrapper::change_window_position(const char *window_name, bool try_to_fix) {
|
||||
ImGuiWindow *window = ImGui::FindWindowByName(window_name);
|
||||
// is window just created
|
||||
if (window == NULL)
|
||||
return {};
|
||||
|
||||
// position of window on screen
|
||||
ImVec2 position = window->Pos;
|
||||
ImVec2 size = window->SizeFull;
|
||||
|
||||
// screen size
|
||||
ImVec2 screen = ImGui::GetMainViewport()->Size;
|
||||
|
||||
std::optional<ImVec2> output_window_offset;
|
||||
if (position.x < 0) {
|
||||
if (position.y < 0)
|
||||
// top left
|
||||
output_window_offset = ImVec2(0, 0);
|
||||
else
|
||||
// only left
|
||||
output_window_offset = ImVec2(0, position.y);
|
||||
} else if (position.y < 0) {
|
||||
// only top
|
||||
output_window_offset = ImVec2(position.x, 0);
|
||||
} else if (screen.x < (position.x + size.x)) {
|
||||
if (screen.y < (position.y + size.y))
|
||||
// right bottom
|
||||
output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y);
|
||||
else
|
||||
// only right
|
||||
output_window_offset = ImVec2(screen.x - size.x, position.y);
|
||||
} else if (screen.y < (position.y + size.y)) {
|
||||
// only bottom
|
||||
output_window_offset = ImVec2(position.x, screen.y - size.y);
|
||||
}
|
||||
|
||||
if (!try_to_fix && output_window_offset.has_value())
|
||||
output_window_offset = ImVec2(-1, -1); // Put on default position
|
||||
|
||||
return output_window_offset;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::left_inputs() {
|
||||
ImGui::ClearActiveID();
|
||||
}
|
||||
|
||||
std::string ImGuiWrapper::trunc(const std::string &text,
|
||||
float width,
|
||||
const char * tail)
|
||||
{
|
||||
float text_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
if (text_width < width) return text;
|
||||
float tail_width = ImGui::CalcTextSize(tail).x;
|
||||
assert(width > tail_width);
|
||||
if (width <= tail_width) return "Error: Can't add tail and not be under wanted width.";
|
||||
float allowed_width = width - tail_width;
|
||||
|
||||
// guess approx count of letter
|
||||
float average_letter_width = calc_text_size(std::string_view("n")).x; // average letter width
|
||||
unsigned count_letter = static_cast<unsigned>(allowed_width / average_letter_width);
|
||||
|
||||
std::string_view text_ = text;
|
||||
std::string_view result_text = text_.substr(0, count_letter);
|
||||
text_width = calc_text_size(result_text).x;
|
||||
if (text_width < allowed_width) {
|
||||
// increase letter count
|
||||
while (count_letter < text.length()) {
|
||||
++count_letter;
|
||||
std::string_view act_text = text_.substr(0, count_letter);
|
||||
text_width = calc_text_size(act_text).x;
|
||||
if (text_width > allowed_width) break;
|
||||
result_text = act_text;
|
||||
}
|
||||
} else {
|
||||
// decrease letter count
|
||||
while (count_letter > 1) {
|
||||
--count_letter;
|
||||
result_text = text_.substr(0, count_letter);
|
||||
text_width = calc_text_size(result_text).x;
|
||||
if (text_width < allowed_width) break;
|
||||
}
|
||||
}
|
||||
return std::string(result_text) + tail;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::escape_double_hash(std::string &text)
|
||||
{
|
||||
// add space between hashes
|
||||
const std::string search = "##";
|
||||
const std::string replace = "# #";
|
||||
size_t pos = 0;
|
||||
while ((pos = text.find(search, pos)) != std::string::npos)
|
||||
text.replace(pos, search.length(), replace);
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::suggest_location(const ImVec2 &dialog_size,
|
||||
const Slic3r::Polygon &interest,
|
||||
const ImVec2 &canvas_size)
|
||||
{
|
||||
// IMPROVE 1: do not select place over menu
|
||||
// BoundingBox top_menu;
|
||||
// GLGizmosManager &gizmo_mng = canvas->get_gizmos_manager();
|
||||
// BoundingBox side_menu; // gizmo_mng.get_size();
|
||||
// BoundingBox left_bottom_menu; // is permanent?
|
||||
// NotificationManager *notify_mng = plater->get_notification_manager();
|
||||
// BoundingBox notifications; // notify_mng->get_size();
|
||||
// m_window_width, m_window_height + position
|
||||
|
||||
// IMPROVE 2: use polygon of interest not only bounding box
|
||||
BoundingBox bb(interest.points);
|
||||
Point center = bb.center(); // interest.centroid();
|
||||
|
||||
// area size
|
||||
Point window_center(canvas_size.x / 2, canvas_size.y / 2);
|
||||
|
||||
// mov on side
|
||||
Point bb_half_size = (bb.max - bb.min) / 2 + Point(1,1);
|
||||
Point diff_center = window_center - center;
|
||||
Vec2d diff_norm(diff_center.x() / (double) bb_half_size.x(),
|
||||
diff_center.y() / (double) bb_half_size.y());
|
||||
if (diff_norm.x() > 1.) diff_norm.x() = 1.;
|
||||
if (diff_norm.x() < -1.) diff_norm.x() = -1.;
|
||||
if (diff_norm.y() > 1.) diff_norm.y() = 1.;
|
||||
if (diff_norm.y() < -1.) diff_norm.y() = -1.;
|
||||
|
||||
Vec2d abs_diff(abs(diff_norm.x()), abs(diff_norm.y()));
|
||||
if (abs_diff.x() < 1. && abs_diff.y() < 1.) {
|
||||
if (abs_diff.x() > abs_diff.y())
|
||||
diff_norm.x() = (diff_norm.x() < 0.) ? (-1.) : 1.;
|
||||
else
|
||||
diff_norm.y() = (diff_norm.y() < 0.) ? (-1.) : 1.;
|
||||
}
|
||||
|
||||
Point half_dialog_size(dialog_size.x / 2., dialog_size.y / 2.);
|
||||
Point move_size = bb_half_size + half_dialog_size;
|
||||
Point offseted_center = center - half_dialog_size;
|
||||
Vec2d offset(offseted_center.x() + diff_norm.x() * move_size.x(),
|
||||
offseted_center.y() + diff_norm.y() * move_size.y());
|
||||
|
||||
// move offset close to center
|
||||
Points window_polygon = {offset.cast<int>(),
|
||||
Point(offset.x(), offset.y() + dialog_size.y),
|
||||
Point(offset.x() + dialog_size.x,
|
||||
offset.y() + dialog_size.y),
|
||||
Point(offset.x() + dialog_size.x, offset.y())};
|
||||
// check that position by Bounding box is not intersecting
|
||||
assert(Slic3r::intersection(interest, Polygon(window_polygon)).empty());
|
||||
|
||||
double allowed_space = 10; // in px
|
||||
double allowed_space_sq = allowed_space * allowed_space;
|
||||
Vec2d move_vec = (center - (offset.cast<int>() + half_dialog_size))
|
||||
.cast<double>();
|
||||
Vec2d result_move(0, 0);
|
||||
do {
|
||||
move_vec = move_vec / 2.;
|
||||
Point move_point = (move_vec + result_move).cast<int>();
|
||||
Points moved_polygon = window_polygon; // copy
|
||||
for (Point &p : moved_polygon) p += move_point;
|
||||
if (Slic3r::intersection(interest, Polygon(moved_polygon)).empty())
|
||||
result_move += move_vec;
|
||||
|
||||
} while (move_vec.squaredNorm() >= allowed_space_sq);
|
||||
offset += result_move;
|
||||
|
||||
return ImVec2(offset.x(), offset.y());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::draw(
|
||||
const Polygon &polygon,
|
||||
ImDrawList * draw_list /* = ImGui::GetOverlayDrawList()*/,
|
||||
ImU32 color /* = ImGui::GetColorU32(COL_ORANGE_LIGHT)*/,
|
||||
float thickness /* = 3.f*/)
|
||||
{
|
||||
// minimal one line consist of 2 points
|
||||
if (polygon.size() < 2) return;
|
||||
// need a place to draw
|
||||
if (draw_list == nullptr) return;
|
||||
|
||||
const Point *prev_point = &polygon.points.back();
|
||||
for (const Point &point : polygon.points) {
|
||||
ImVec2 p1(prev_point->x(), prev_point->y());
|
||||
ImVec2 p2(point.x(), point.y());
|
||||
draw_list->AddLine(p1, p2, color, thickness);
|
||||
prev_point = &point;
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiWrapper::draw_cross_hair(const ImVec2 &position, float radius, ImU32 color, int num_segments, float thickness) {
|
||||
auto draw_list = ImGui::GetOverlayDrawList();
|
||||
draw_list->AddCircle(position, radius, color, num_segments, thickness);
|
||||
auto dirs = {ImVec2{0, 1}, ImVec2{1, 0}, ImVec2{0, -1}, ImVec2{-1, 0}};
|
||||
for (const ImVec2 &dir : dirs) {
|
||||
ImVec2 start(position.x + dir.x * 0.5 * radius, position.y + dir.y * 0.5 * radius);
|
||||
ImVec2 end(position.x + dir.x * 1.5 * radius, position.y + dir.y * 1.5 * radius);
|
||||
draw_list->AddLine(start, end, color, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::contain_all_glyphs(const ImFont *font,
|
||||
const std::string &text)
|
||||
{
|
||||
if (font == nullptr) return false;
|
||||
if (!font->IsLoaded()) return false;
|
||||
const ImFontConfig *fc = font->ConfigData;
|
||||
if (fc == nullptr) return false;
|
||||
if (text.empty()) return true;
|
||||
return is_chars_in_ranges(fc->GlyphRanges, text.c_str());
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::is_char_in_ranges(const ImWchar *ranges,
|
||||
unsigned int letter)
|
||||
{
|
||||
for (const ImWchar *range = ranges; range[0] && range[1]; range += 2) {
|
||||
ImWchar from = range[0];
|
||||
ImWchar to = range[1];
|
||||
if (from <= letter && letter <= to) return true;
|
||||
if (letter < to) return false; // ranges should be sorted
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool ImGuiWrapper::is_chars_in_ranges(const ImWchar *ranges,
|
||||
const char *chars_ptr)
|
||||
{
|
||||
while (*chars_ptr) {
|
||||
unsigned int c = 0;
|
||||
// UTF-8 to 32-bit character need imgui_internal
|
||||
int c_len = ImTextCharFromUtf8(&c, chars_ptr, NULL);
|
||||
chars_ptr += c_len;
|
||||
if (c_len == 0) break;
|
||||
if (!is_char_in_ranges(ranges, c)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
static const ImWchar ranges_keyboard_shortcuts[] =
|
||||
{
|
||||
|
|
@ -2204,20 +2577,20 @@ void ImGuiWrapper::push_combo_style(const float scale)
|
|||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.0f * scale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * scale);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BG_DARK);
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderActive, COL_ORCA);
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, to_ImVec4(to_rgba(ColorRGB::ORCA(), 0.5f)));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, COL_ORCA);
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, COL_ORCA);
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGuiWrapper::COL_WINDOW_BG_DARK);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, {1.00f, 1.00f, 1.00f, 0.0f});
|
||||
} else {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.0f * scale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * scale);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BG);
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderActive, COL_ORCA);
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, to_ImVec4(to_rgba(ColorRGB::ORCA(), 0.5f)));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, COL_ORCA);
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, COL_ORCA);
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGuiWrapper::COL_WINDOW_BG);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, {1.00f, 1.00f, 1.00f, 0.0f});
|
||||
}
|
||||
|
|
@ -2229,6 +2602,20 @@ void ImGuiWrapper::pop_combo_style()
|
|||
ImGui::PopStyleColor(7);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::push_radio_style()
|
||||
{
|
||||
if (m_is_dark_mode) {
|
||||
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f));
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiWrapper::pop_radio_style()
|
||||
{
|
||||
ImGui::PopStyleColor(1);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::init_font(bool compress)
|
||||
{
|
||||
destroy_font();
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@
|
|||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class ColorRGBA;
|
||||
namespace Search {
|
||||
struct OptionViewParameters;
|
||||
} // namespace Search
|
||||
|
|
@ -95,13 +96,19 @@ public:
|
|||
|
||||
float get_font_size() const { return m_font_size; }
|
||||
float get_style_scaling() const { return m_style_scaling; }
|
||||
const ImWchar *get_glyph_ranges() const { return m_glyph_ranges; } // language specific
|
||||
|
||||
void new_frame();
|
||||
void render();
|
||||
|
||||
float scaled(float x) const { return x * m_font_size; }
|
||||
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
|
||||
ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f) const;
|
||||
/// <summary>
|
||||
/// Extend ImGui::CalcTextSize to use string_view
|
||||
/// </summary>
|
||||
static ImVec2 calc_text_size(std::string_view text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
|
||||
static ImVec2 calc_text_size(const std::string& text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
|
||||
static ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
|
||||
ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const;
|
||||
|
||||
ImVec2 get_item_spacing() const;
|
||||
|
|
@ -137,12 +144,12 @@ public:
|
|||
bool bbl_checkbox(const wxString &label, bool &value);
|
||||
bool bbl_radio_button(const char *label, bool active);
|
||||
bool bbl_sliderin(const char *label, int *v, int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0);
|
||||
void text(const char *label);
|
||||
void text(const std::string &label);
|
||||
void text(const wxString &label);
|
||||
void text_colored(const ImVec4& color, const char* label);
|
||||
void text_colored(const ImVec4& color, const std::string& label);
|
||||
void text_colored(const ImVec4& color, const wxString& label);
|
||||
static void text(const char *label);
|
||||
static void text(const std::string &label);
|
||||
static void text(const wxString &label);
|
||||
static void text_colored(const ImVec4& color, const char* label);
|
||||
static void text_colored(const ImVec4& color, const std::string& label);
|
||||
static void text_colored(const ImVec4& color, const wxString& label);
|
||||
void text_wrapped(const char *label, float wrap_width);
|
||||
void text_wrapped(const std::string &label, float wrap_width);
|
||||
void text_wrapped(const wxString &label, float wrap_width);
|
||||
|
|
@ -196,11 +203,109 @@ public:
|
|||
bool want_text_input() const;
|
||||
bool want_any_input() const;
|
||||
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
// Optional inputs are used for set up value inside of an optional, with default value
|
||||
//
|
||||
// Extended function ImGui::InputInt to work with std::optional<int>, when value == def_val optional is released.
|
||||
static bool input_optional_int(const char *label, std::optional<int>& v, int step=1, int step_fast=100, ImGuiInputTextFlags flags=0, int def_val = 0);
|
||||
// Extended function ImGui::InputFloat to work with std::optional<float> value near def_val cause release of optional
|
||||
static bool input_optional_float(const char* label, std::optional<float> &v, float step = 0.0f, float step_fast = 0.0f, const char* format = "%.3f", ImGuiInputTextFlags flags = 0, float def_val = .0f);
|
||||
// Extended function ImGui::DragFloat to work with std::optional<float> value near def_val cause release of optional
|
||||
static bool drag_optional_float(const char* label, std::optional<float> &v, float v_speed, float v_min, float v_max, const char* format, float power, float def_val = .0f);
|
||||
// Extended function ImGuiWrapper::slider_float to work with std::optional<float> value near def_val cause release of optional
|
||||
bool slider_optional_float(const char* label, std::optional<float> &v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true, float def_val = .0f);
|
||||
// Extended function ImGuiWrapper::slider_float to work with std::optional<int>, when value == def_val than optional release its value
|
||||
bool slider_optional_int(const char* label, std::optional<int> &v, int v_min, int v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true, int def_val = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Change position of imgui window
|
||||
/// </summary>
|
||||
/// <param name="window_name">ImGui identifier of window</param>
|
||||
/// <param name="output_window_offset">[output] optional </param>
|
||||
/// <param name="try_to_fix">When True Only move to be full visible otherwise reset position</param>
|
||||
/// <returns>New offset of window for function ImGui::SetNextWindowPos</returns>
|
||||
static std::optional<ImVec2> change_window_position(const char *window_name, bool try_to_fix);
|
||||
|
||||
/// <summary>
|
||||
/// Use ImGui internals to unactivate (lose focus) in input.
|
||||
/// When input is activ it can't change value by application.
|
||||
/// </summary>
|
||||
static void left_inputs();
|
||||
|
||||
/// <summary>
|
||||
/// Truncate text by ImGui draw function to specific width
|
||||
/// NOTE 1: ImGui must be initialized
|
||||
/// NOTE 2: Calculation for actual acive imgui font
|
||||
/// </summary>
|
||||
/// <param name="text">Text to be truncated</param>
|
||||
/// <param name="width">Maximal width before truncate</param>
|
||||
/// <param name="tail">String puted on end of text to be visible truncation</param>
|
||||
/// <returns>Truncated text</returns>
|
||||
static std::string trunc(const std::string &text,
|
||||
float width,
|
||||
const char *tail = " ..");
|
||||
|
||||
/// <summary>
|
||||
/// Escape ## in data by add space between hashes
|
||||
/// Needed when user written text is visualized by ImGui.
|
||||
/// </summary>
|
||||
/// <param name="text">In/Out text to be escaped</param>
|
||||
static void escape_double_hash(std::string &text);
|
||||
|
||||
/// <summary>
|
||||
/// Suggest loacation of dialog window,
|
||||
/// dependent on actual visible thing on platter
|
||||
/// like Gizmo menu size, notifications, ...
|
||||
/// To be near of polygon interest and not over it.
|
||||
/// And also not out of visible area.
|
||||
/// </summary>
|
||||
/// <param name="dialog_size">Define width and height of diaog window</param>
|
||||
/// <param name="interest">Area of interest. Result should be close to it</param>
|
||||
/// <param name="canvas_size">Available space a.k.a GLCanvas3D::get_current_canvas3D()</param>
|
||||
/// <returns>Suggestion for dialog offest</returns>
|
||||
static ImVec2 suggest_location(const ImVec2 &dialog_size,
|
||||
const Slic3r::Polygon &interest,
|
||||
const ImVec2 &canvas_size);
|
||||
|
||||
/// <summary>
|
||||
/// Visualization of polygon
|
||||
/// </summary>
|
||||
/// <param name="polygon">Define what to draw</param>
|
||||
/// <param name="draw_list">Define where to draw it</param>
|
||||
/// <param name="color">Color of polygon</param>
|
||||
/// <param name="thickness">Width of polygon line</param>
|
||||
static void draw(const Polygon &polygon,
|
||||
ImDrawList * draw_list = ImGui::GetOverlayDrawList(),
|
||||
ImU32 color = ImGui::GetColorU32(COL_ORANGE_LIGHT),
|
||||
float thickness = 3.f);
|
||||
|
||||
/// <summary>
|
||||
/// Draw symbol of cross hair
|
||||
/// </summary>
|
||||
/// <param name="position">Center of cross hair</param>
|
||||
/// <param name="radius">Circle radius</param>
|
||||
/// <param name="color">Color of symbol</param>
|
||||
/// <param name="num_segments">Precission of circle</param>
|
||||
/// <param name="thickness">Thickness of Line in symbol</param>
|
||||
static void draw_cross_hair(const ImVec2 &position,
|
||||
float radius = 16.f,
|
||||
ImU32 color = ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .75f)),
|
||||
int num_segments = 0,
|
||||
float thickness = 4.f);
|
||||
|
||||
/// <summary>
|
||||
/// Check that font ranges contain all chars in string
|
||||
/// (rendered Unicodes are stored in GlyphRanges)
|
||||
/// </summary>
|
||||
/// <param name="font">Contain glyph ranges</param>
|
||||
/// <param name="text">Vector of character to check</param>
|
||||
/// <returns>True when all glyphs from text are in font ranges</returns>
|
||||
static bool contain_all_glyphs(const ImFont *font, const std::string &text);
|
||||
static bool is_chars_in_ranges(const ImWchar *ranges, const char *chars_ptr);
|
||||
static bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter);
|
||||
|
||||
bool requires_extra_frame() const { return m_requires_extra_frame; }
|
||||
void set_requires_extra_frame() { m_requires_extra_frame = true; }
|
||||
void reset_requires_extra_frame() { m_requires_extra_frame = false; }
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
|
||||
void disable_background_fadeout_animation();
|
||||
|
||||
|
|
@ -248,6 +353,8 @@ public:
|
|||
static void pop_button_disable_style();
|
||||
static void push_combo_style(const float scale);
|
||||
static void pop_combo_style();
|
||||
static void push_radio_style();
|
||||
static void pop_radio_style();
|
||||
|
||||
//BBS
|
||||
static int TOOLBAR_WINDOW_FLAGS;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "ArrangeJob.hpp"
|
||||
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
|
|
@ -509,21 +513,12 @@ void ArrangeJob::check_unprintable()
|
|||
}
|
||||
}
|
||||
|
||||
void ArrangeJob::on_exception(const std::exception_ptr &eptr)
|
||||
void ArrangeJob::process(Ctl &ctl)
|
||||
{
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (libnest2d::GeometryException &) {
|
||||
show_error(m_plater, _(L("Arrange failed. "
|
||||
"Found some exceptions when processing object geometries.")));
|
||||
} catch (std::exception &) {
|
||||
PlaterJob::on_exception(eptr);
|
||||
}
|
||||
}
|
||||
static const auto arrangestr = _u8L("Arranging");
|
||||
ctl.update_status(0, arrangestr);
|
||||
ctl.call_on_main_thread([this]{ prepare(); }).wait();;
|
||||
|
||||
void ArrangeJob::process()
|
||||
{
|
||||
auto & partplate_list = m_plater->get_partplate_list();
|
||||
|
||||
const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config();
|
||||
|
|
@ -543,10 +538,10 @@ void ArrangeJob::process()
|
|||
|
||||
BOOST_LOG_TRIVIAL(debug) << "arrange bedpts:" << bedpts[0].transpose() << ", " << bedpts[1].transpose() << ", " << bedpts[2].transpose() << ", " << bedpts[3].transpose();
|
||||
|
||||
params.stopcondition = [this]() { return was_canceled(); };
|
||||
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
|
||||
|
||||
params.progressind = [this](unsigned num_finished, std::string str = "") {
|
||||
update_status(num_finished, _L("Arranging") + " "+ wxString::FromUTF8(str));
|
||||
params.progressind = [this, &ctl](unsigned num_finished, std::string str = "") {
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging") + str);
|
||||
};
|
||||
|
||||
{
|
||||
|
|
@ -590,11 +585,13 @@ void ArrangeJob::process()
|
|||
}
|
||||
|
||||
// finalize just here.
|
||||
update_status(status_range(),
|
||||
was_canceled() ? _(L("Arranging canceled.")) :
|
||||
we_have_unpackable_items ? _(L("Arranging is done but there are unpacked items. Reduce spacing and try again.")) : _(L("Arranging done.")));
|
||||
ctl.update_status(100,
|
||||
ctl.was_canceled() ? _u8L("Arranging canceled.") :
|
||||
we_have_unpackable_items ? _u8L("Arranging is done but there are unpacked items. Reduce spacing and try again.") : _u8L("Arranging done."));
|
||||
}
|
||||
|
||||
ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} { }
|
||||
|
||||
static std::string concat_strings(const std::set<std::string> &strings,
|
||||
const std::string &delim = "\n")
|
||||
{
|
||||
|
|
@ -605,9 +602,20 @@ static std::string concat_strings(const std::set<std::string> &strings,
|
|||
});
|
||||
}
|
||||
|
||||
void ArrangeJob::finalize() {
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (libnest2d::GeometryException &) {
|
||||
show_error(m_plater, _(L("Arrange failed. "
|
||||
"Found some exceptions when processing object geometries.")));
|
||||
eptr = nullptr;
|
||||
} catch (...) {
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
// Unprintable items go to the last virtual bed
|
||||
int beds = 0;
|
||||
|
|
@ -716,7 +724,6 @@ void ArrangeJob::finalize() {
|
|||
|
||||
m_plater->update();
|
||||
|
||||
Job::finalize();
|
||||
m_plater->m_arrange_running.store(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef ARRANGEJOB_HPP
|
||||
#define ARRANGEJOB_HPP
|
||||
|
||||
#include "PlaterJob.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "Job.hpp"
|
||||
#include "libslic3r/Arrange.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
|
@ -12,7 +17,9 @@ class ModelInstance;
|
|||
|
||||
namespace GUI {
|
||||
|
||||
class ArrangeJob : public PlaterJob
|
||||
class Plater;
|
||||
|
||||
class ArrangeJob : public Job
|
||||
{
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
|
@ -24,6 +31,10 @@ class ArrangeJob : public PlaterJob
|
|||
arrangement::ArrangeParams params;
|
||||
int current_plate_index = 0;
|
||||
Polygon bed_poly;
|
||||
Plater *m_plater;
|
||||
|
||||
// BBS: add flag for whether on current part plate
|
||||
bool only_on_partplate{false};
|
||||
|
||||
// clear m_selected and m_unselected, reserve space for next usage
|
||||
void clear_input();
|
||||
|
|
@ -42,26 +53,23 @@ class ArrangeJob : public PlaterJob
|
|||
|
||||
protected:
|
||||
|
||||
void prepare() override;
|
||||
|
||||
void check_unprintable();
|
||||
|
||||
void on_exception(const std::exception_ptr &) override;
|
||||
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
|
||||
int status_range() const override
|
||||
void prepare();
|
||||
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
ArrangeJob();
|
||||
|
||||
int status_range() const
|
||||
{
|
||||
// ensure finalize() is called after all operations in process() is finished.
|
||||
return int(m_selected.size() + m_unprintable.size() + 1);
|
||||
}
|
||||
|
||||
void finalize() override;
|
||||
void finalize(bool canceled, std::exception_ptr &e) override;
|
||||
};
|
||||
|
||||
std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ wxDEFINE_EVENT(EVT_BIND_MACHINE_SUCCESS, wxCommandEvent);
|
|||
wxDEFINE_EVENT(EVT_BIND_MACHINE_FAIL, wxCommandEvent);
|
||||
|
||||
|
||||
static wxString waiting_auth_str = _L("Logging in");
|
||||
static wxString login_failed_str = _L("Login failed");
|
||||
static auto waiting_auth_str = _u8L("Logging in");
|
||||
static auto login_failed_str = _u8L("Login failed");
|
||||
|
||||
|
||||
BindJob::BindJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id, std::string dev_ip, std::string sec_link)
|
||||
: PlaterJob{std::move(pri), plater},
|
||||
BindJob::BindJob(std::string dev_id, std::string dev_ip, std::string sec_link)
|
||||
:
|
||||
m_dev_id(dev_id),
|
||||
m_dev_ip(dev_ip),
|
||||
m_sec_link(sec_link)
|
||||
|
|
@ -25,37 +25,27 @@ BindJob::BindJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::st
|
|||
;
|
||||
}
|
||||
|
||||
void BindJob::on_exception(const std::exception_ptr &eptr)
|
||||
{
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (std::exception &e) {
|
||||
PlaterJob::on_exception(eptr);
|
||||
}
|
||||
}
|
||||
|
||||
void BindJob::on_success(std::function<void()> success)
|
||||
{
|
||||
m_success_fun = success;
|
||||
}
|
||||
|
||||
void BindJob::update_status(int st, const wxString &msg)
|
||||
void BindJob::update_status(Ctl &ctl, int st, const std::string &msg)
|
||||
{
|
||||
GUI::Job::update_status(st, msg);
|
||||
ctl.update_status(st, msg);
|
||||
wxCommandEvent event(EVT_BIND_UPDATE_MESSAGE);
|
||||
event.SetString(msg);
|
||||
event.SetEventObject(m_event_handle);
|
||||
wxPostEvent(m_event_handle, event);
|
||||
}
|
||||
|
||||
void BindJob::process()
|
||||
void BindJob::process(Ctl &ctl)
|
||||
{
|
||||
int result_code = 0;
|
||||
std::string result_info;
|
||||
|
||||
/* display info */
|
||||
wxString msg = waiting_auth_str;
|
||||
auto msg = waiting_auth_str;
|
||||
int curr_percent = 0;
|
||||
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
|
|
@ -67,40 +57,40 @@ void BindJob::process()
|
|||
std::string timezone = get_timezone_utc_hm(offset);
|
||||
|
||||
int result = m_agent->bind(m_dev_ip, m_dev_id, m_sec_link, timezone, m_improved,
|
||||
[this, &curr_percent, &msg, &result_code, &result_info](int stage, int code, std::string info) {
|
||||
[this, &ctl, &curr_percent, &msg, &result_code, &result_info](int stage, int code, std::string info) {
|
||||
|
||||
result_code = code;
|
||||
result_info = info;
|
||||
|
||||
if (stage == BBL::BindJobStage::LoginStageConnect) {
|
||||
curr_percent = 15;
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageLogin) {
|
||||
curr_percent = 30;
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageWaitForLogin) {
|
||||
curr_percent = 45;
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageGetIdentify) {
|
||||
curr_percent = 60;
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageWaitAuth) {
|
||||
curr_percent = 80;
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageFinished) {
|
||||
curr_percent = 100;
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
} else {
|
||||
msg = _L("Logging in");
|
||||
msg = _u8L("Logging in");
|
||||
}
|
||||
|
||||
if (code != 0) {
|
||||
msg = _L("Login failed");
|
||||
msg = _u8L("Login failed");
|
||||
if (code == BAMBU_NETWORK_ERR_TIMEOUT) {
|
||||
msg += _L("Please check the printer network connection.");
|
||||
msg += _u8L("Please check the printer network connection.");
|
||||
}
|
||||
}
|
||||
update_status(curr_percent, msg);
|
||||
update_status(ctl, curr_percent, msg);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -138,11 +128,18 @@ void BindJob::process()
|
|||
return;
|
||||
}
|
||||
|
||||
void BindJob::finalize()
|
||||
void BindJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
if (was_canceled()) return;
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
eptr = nullptr;
|
||||
} catch (...) {
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
|
||||
Job::finalize();
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
}
|
||||
|
||||
void BindJob::set_event_handle(wxWindow *hanle)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include "PlaterJob.hpp"
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class BindJob : public PlaterJob
|
||||
class BindJob : public Job
|
||||
{
|
||||
wxWindow * m_event_handle{nullptr};
|
||||
std::function<void()> m_success_fun{nullptr};
|
||||
|
|
@ -21,12 +21,10 @@ class BindJob : public PlaterJob
|
|||
int m_print_job_completed_id = 0;
|
||||
bool m_improved{false};
|
||||
|
||||
protected:
|
||||
void on_exception(const std::exception_ptr &) override;
|
||||
public:
|
||||
BindJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater, std::string dev_id, std::string dev_ip, std::string sec_link);
|
||||
BindJob(std::string dev_id, std::string dev_ip, std::string sec_link);
|
||||
|
||||
int status_range() const override
|
||||
int status_range() const
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
|
@ -34,9 +32,9 @@ public:
|
|||
bool is_finished() { return m_job_finished; }
|
||||
|
||||
void on_success(std::function<void()> success);
|
||||
void update_status(int st, const wxString &msg);
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
void update_status(Ctl &ctl, int st, const std::string &msg);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
void set_event_handle(wxWindow* hanle);
|
||||
void post_fail_event(int code, std::string info);
|
||||
void set_improved(bool improved){m_improved = improved;};
|
||||
|
|
|
|||
186
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
186
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 Tomáš Mészáros @tamasmeszaros
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include <exception>
|
||||
|
||||
#include "BoostThreadWorker.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner)
|
||||
{
|
||||
switch(MsgType(get_type())) {
|
||||
case Empty: break;
|
||||
case Status: {
|
||||
auto info = boost::get<StatusInfo>(m_data);
|
||||
if (runner.get_pri()) {
|
||||
runner.get_pri()->set_progress(info.status);
|
||||
runner.get_pri()->set_status_text(info.msg.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Finalize: {
|
||||
auto& entry = boost::get<JobEntry>(m_data);
|
||||
entry.job->finalize(entry.canceled, entry.eptr);
|
||||
|
||||
// Unhandled exceptions are rethrown without mercy.
|
||||
if (entry.eptr)
|
||||
std::rethrow_exception(entry.eptr);
|
||||
|
||||
break;
|
||||
}
|
||||
case MainThreadCall: {
|
||||
auto &calldata = boost::get<MainThreadCallData >(m_data);
|
||||
calldata.fn();
|
||||
calldata.promise.set_value();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoostThreadWorker::run()
|
||||
{
|
||||
bool stop = false;
|
||||
while (!stop) {
|
||||
m_input_queue
|
||||
.consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) {
|
||||
if (!e.job)
|
||||
stop = true;
|
||||
else {
|
||||
m_canceled.store(false);
|
||||
|
||||
try {
|
||||
e.job->process(*this);
|
||||
} catch (...) {
|
||||
e.eptr = std::current_exception();
|
||||
}
|
||||
|
||||
e.canceled = m_canceled.load();
|
||||
m_output_queue.push(std::move(e)); // finalization message
|
||||
}
|
||||
m_running.store(false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void BoostThreadWorker::update_status(int st, const std::string &msg)
|
||||
{
|
||||
m_output_queue.push(st, msg);
|
||||
}
|
||||
|
||||
std::future<void> BoostThreadWorker::call_on_main_thread(std::function<void ()> fn)
|
||||
{
|
||||
MainThreadCallData cbdata{std::move(fn), {}};
|
||||
std::future<void> future = cbdata.promise.get_future();
|
||||
|
||||
m_output_queue.push(std::move(cbdata));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
BoostThreadWorker::BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes &attribs,
|
||||
const char * name)
|
||||
: m_progress(std::move(pri)), m_name{name}
|
||||
{
|
||||
if (m_progress)
|
||||
m_progress->set_cancel_callback([this](){ cancel(); });
|
||||
|
||||
m_thread = create_thread(attribs, [this] { this->run(); });
|
||||
|
||||
std::string nm{name};
|
||||
if (!nm.empty()) set_thread_name(m_thread, name);
|
||||
}
|
||||
|
||||
constexpr int ABORT_WAIT_MAX_MS = 10000;
|
||||
|
||||
BoostThreadWorker::~BoostThreadWorker()
|
||||
{
|
||||
bool joined = false;
|
||||
try {
|
||||
cancel_all();
|
||||
wait_for_idle(ABORT_WAIT_MAX_MS);
|
||||
m_input_queue.push(JobEntry{nullptr});
|
||||
joined = join(ABORT_WAIT_MAX_MS);
|
||||
} catch(...) {}
|
||||
|
||||
if (!joined)
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "Could not join worker thread '" << m_name << "'";
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::join(int timeout_ms)
|
||||
{
|
||||
if (!m_thread.joinable())
|
||||
return true;
|
||||
|
||||
if (timeout_ms <= 0) {
|
||||
m_thread.join();
|
||||
}
|
||||
else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) {
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BoostThreadWorker::process_events()
|
||||
{
|
||||
while (m_output_queue.consume_one([this](WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
}));
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
if (!is_idle()) {
|
||||
bool was_finish = false;
|
||||
bool timeout_reached = false;
|
||||
while (!timeout_reached && !was_finish) {
|
||||
timeout_reached =
|
||||
!m_output_queue.consume_one(BlockingWait{timeout_ms},
|
||||
[this, &was_finish](
|
||||
WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
if (msg.get_type() ==
|
||||
WorkerMessage::Finalize)
|
||||
was_finish = true;
|
||||
});
|
||||
}
|
||||
|
||||
ret = !timeout_reached;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms)
|
||||
{
|
||||
bool timeout_reached = false;
|
||||
while (!timeout_reached && !is_idle()) {
|
||||
timeout_reached = !m_output_queue
|
||||
.consume_one(BlockingWait{timeout_ms},
|
||||
[this](WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
});
|
||||
}
|
||||
|
||||
return !timeout_reached;
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::push(std::unique_ptr<Job> job)
|
||||
{
|
||||
if (!job)
|
||||
return false;
|
||||
|
||||
m_input_queue.push(JobEntry{std::move(job)});
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
159
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
159
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 Tomáš Mészáros @tamasmeszaros
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef BOOSTTHREADWORKER_HPP
|
||||
#define BOOSTTHREADWORKER_HPP
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include "Worker.hpp"
|
||||
|
||||
#include <libslic3r/Thread.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "ThreadSafeQueue.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// An implementation of the Worker interface which uses the boost::thread
|
||||
// API and two thread safe message queues to communicate with the main thread
|
||||
// back and forth. The queue from the main thread to the worker thread holds the
|
||||
// job entries that will be performed on the worker. The other queue holds messages
|
||||
// from the worker to the main thread. These messages include status updates,
|
||||
// finishing operation and arbitrary functiors that need to be performed
|
||||
// on the main thread during the jobs execution, like displaying intermediate
|
||||
// results.
|
||||
class BoostThreadWorker : public Worker, private Job::Ctl
|
||||
{
|
||||
struct JobEntry // Goes into worker and also out of worker as a finalize msg
|
||||
{
|
||||
std::unique_ptr<Job> job;
|
||||
bool canceled = false;
|
||||
std::exception_ptr eptr = nullptr;
|
||||
};
|
||||
|
||||
// A message data for status updates. Only goes from worker to main thread.
|
||||
struct StatusInfo { int status; std::string msg; };
|
||||
|
||||
// An arbitrary callback to be called on the main thread. Only from worker
|
||||
// to main thread.
|
||||
struct MainThreadCallData
|
||||
{
|
||||
std::function<void()> fn;
|
||||
std::promise<void> promise;
|
||||
};
|
||||
|
||||
struct EmptyMessage {};
|
||||
|
||||
class WorkerMessage
|
||||
{
|
||||
public:
|
||||
enum MsgType { Empty, Status, Finalize, MainThreadCall };
|
||||
|
||||
private:
|
||||
boost::variant<EmptyMessage, StatusInfo, JobEntry, MainThreadCallData> m_data;
|
||||
|
||||
public:
|
||||
WorkerMessage() = default;
|
||||
WorkerMessage(int s, std::string txt)
|
||||
: m_data{StatusInfo{s, std::move(txt)}}
|
||||
{}
|
||||
WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {}
|
||||
WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {}
|
||||
|
||||
int get_type () const { return m_data.which(); }
|
||||
|
||||
void deliver(BoostThreadWorker &runner);
|
||||
};
|
||||
|
||||
using JobQueue = ThreadSafeQueueSPSC<JobEntry>;
|
||||
using MessageQueue = ThreadSafeQueueSPSC<WorkerMessage>;
|
||||
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
JobQueue m_input_queue; // from main thread to worker
|
||||
MessageQueue m_output_queue; // form worker to main thread
|
||||
std::string m_name;
|
||||
|
||||
void run();
|
||||
|
||||
bool join(int timeout_ms = 0);
|
||||
|
||||
protected:
|
||||
// Implement Job::Ctl interface:
|
||||
|
||||
void update_status(int st, const std::string &msg = "") override;
|
||||
|
||||
bool was_canceled() const override { return m_canceled.load(); }
|
||||
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override;
|
||||
|
||||
public:
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes & attr,
|
||||
const char * name = "");
|
||||
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes && attr,
|
||||
const char * name = "")
|
||||
: BoostThreadWorker{std::move(pri), attr, name}
|
||||
{}
|
||||
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
const char * name = "")
|
||||
: BoostThreadWorker{std::move(pri), {}, name}
|
||||
{}
|
||||
|
||||
~BoostThreadWorker();
|
||||
|
||||
BoostThreadWorker(const BoostThreadWorker &) = delete;
|
||||
BoostThreadWorker(BoostThreadWorker &&) = delete;
|
||||
BoostThreadWorker &operator=(const BoostThreadWorker &) = delete;
|
||||
BoostThreadWorker &operator=(BoostThreadWorker &&) = delete;
|
||||
|
||||
bool push(std::unique_ptr<Job> job) override;
|
||||
|
||||
bool is_idle() const override
|
||||
{
|
||||
// The assumption is that jobs can only be queued from a single main
|
||||
// thread from which this method is also called. And the output
|
||||
// messages are also processed only in this calling thread. In that
|
||||
// case, if the input queue is empty, it will remain so during this
|
||||
// function call. If the worker thread is also not running and the
|
||||
// output queue is already processed, we can safely say that the
|
||||
// worker is dormant.
|
||||
return m_input_queue.empty() && !m_running.load() && m_output_queue.empty();
|
||||
}
|
||||
|
||||
void cancel() override { m_canceled.store(true); }
|
||||
void cancel_all() override { m_input_queue.clear(); cancel(); }
|
||||
|
||||
ProgressIndicator * get_pri() { return m_progress.get(); }
|
||||
const ProgressIndicator * get_pri() const { return m_progress.get(); }
|
||||
|
||||
void clear_percent() override
|
||||
{
|
||||
if (m_progress) {
|
||||
m_progress->clear_percent();
|
||||
}
|
||||
}
|
||||
|
||||
void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override
|
||||
{
|
||||
if (m_progress) {
|
||||
m_progress->show_error_info(from_u8(msg), code, from_u8(description), from_u8(extra));
|
||||
}
|
||||
}
|
||||
|
||||
void process_events() override;
|
||||
bool wait_for_current_job(unsigned timeout_ms = 0) override;
|
||||
bool wait_for_idle(unsigned timeout_ms = 0) override;
|
||||
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // BOOSTTHREADWORKER_HPP
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue