mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-12-05 16:51:07 -07:00
Merge remote-tracking branch 'remote/master' into feature/merge_upstream
# Conflicts: # bbl/i18n/OrcaSlicer.pot # bbl/i18n/de/OrcaSlicer_de.po # bbl/i18n/en/OrcaSlicer_en.po # bbl/i18n/es/OrcaSlicer_es.po # bbl/i18n/fr/OrcaSlicer_fr.po # bbl/i18n/hu/OrcaSlicer_hu.po # bbl/i18n/it/OrcaSlicer_it.po # bbl/i18n/ja/OrcaSlicer_ja.po # bbl/i18n/nl/OrcaSlicer_nl.po # bbl/i18n/sv/OrcaSlicer_sv.po # bbl/i18n/zh_cn/OrcaSlicer_zh_CN.po # resources/config.json # resources/i18n/de/BambuStudio.mo # resources/i18n/en/BambuStudio.mo # resources/i18n/es/BambuStudio.mo # resources/i18n/fr/BambuStudio.mo # resources/i18n/hu/BambuStudio.mo # resources/i18n/it/BambuStudio.mo # resources/i18n/ja/OrcaSlicer.mo # resources/i18n/nl/BambuStudio.mo # resources/i18n/sv/BambuStudio.mo # resources/i18n/zh_cn/BambuStudio.mo # resources/images/ams_humidity_2.svg # resources/images/ams_humidity_3.svg # resources/images/ams_humidity_4.svg # resources/images/ams_humidity_tips.svg # resources/images/monitor_state_on.svg # resources/images/sdcard_state_normal.svg # resources/profiles/BBL.json # resources/profiles/BBL/filament/Bambu PETG-CF @base.json # resources/profiles/BBL/filament/Generic PETG-CF @base.json # resources/profiles/BBL/machine/Bambu Lab P1P 0.4 nozzle.json # resources/web/data/text.js # resources/web/guide/3/index.html # resources/web/guide/31/index.html # src/BambuStudio.cpp # src/libslic3r/AABBTreeLines.hpp # src/libslic3r/Brim.cpp # src/libslic3r/CMakeLists.txt # src/libslic3r/ExPolygon.hpp # src/libslic3r/Fill/FillBase.hpp # src/libslic3r/Format/bbs_3mf.cpp # src/libslic3r/GCodeWriter.cpp # src/libslic3r/Line.hpp # src/libslic3r/PerimeterGenerator.cpp # src/libslic3r/Preset.cpp # src/libslic3r/Print.cpp # src/libslic3r/Print.hpp # src/libslic3r/PrintConfig.cpp # src/libslic3r/PrintConfig.hpp # src/libslic3r/TreeSupport.cpp # src/slic3r/GUI/AmsMappingPopup.cpp # src/slic3r/GUI/BackgroundSlicingProcess.cpp # src/slic3r/GUI/ConfigManipulation.cpp # src/slic3r/GUI/GCodeViewer.cpp # src/slic3r/GUI/GCodeViewer.hpp # src/slic3r/GUI/GLCanvas3D.cpp # src/slic3r/GUI/GUI_App.cpp # src/slic3r/GUI/MainFrame.cpp # src/slic3r/GUI/PartPlate.cpp # src/slic3r/GUI/Plater.cpp # src/slic3r/GUI/Preferences.cpp # src/slic3r/GUI/SelectMachine.cpp # src/slic3r/GUI/Widgets/AMSControl.cpp # src/slic3r/GUI/wxMediaCtrl2.cpp # src/slic3r/Utils/Process.cpp # version.inc
This commit is contained in:
commit
9f598046d1
658 changed files with 70312 additions and 4877 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -39,7 +39,8 @@ private:
|
|||
bool export_models(IO::ExportFormat format);
|
||||
//BBS: add export_project function
|
||||
bool export_project(Model *model, std::string& path, PlateDataPtrs &partplate_data, std::vector<Preset*>& project_presets,
|
||||
std::vector<ThumbnailData*>& thumbnails, std::vector<ThumbnailData*>& calibration_thumbnails,
|
||||
std::vector<ThumbnailData*>& thumbnails, std::vector<ThumbnailData*>& top_thumbnails, std::vector<ThumbnailData*>& pick_thumbnails,
|
||||
std::vector<ThumbnailData*>& calibration_thumbnails,
|
||||
std::vector<PlateBBoxData*>& plate_bboxes, const DynamicPrintConfig* config);
|
||||
|
||||
bool has_print_action() const { return m_config.opt_bool("export_gcode") || m_config.opt_bool("export_sla"); }
|
||||
|
|
|
|||
|
|
@ -189,6 +189,10 @@ namespace ImGui
|
|||
const wchar_t TextSearchIcon = 0x0828;
|
||||
const wchar_t TextSearchCloseIcon = 0x0829;
|
||||
|
||||
const wchar_t ExpandBtn = 0x0830;
|
||||
const wchar_t CollapseBtn = 0x0831;
|
||||
const wchar_t RevertBtn = 0x0832;
|
||||
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3815,7 +3815,7 @@ void ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir d
|
|||
draw_list->AddTriangleFilled(center + a, center + b, center + c, col);
|
||||
}
|
||||
|
||||
void ImGui::BBLRenderArrow(ImDrawList *draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale)
|
||||
void ImGui::BBLRenderArrow(ImDrawList *draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float thickness, float scale)
|
||||
{
|
||||
const float h = draw_list->_Data->FontSize * 1.00f;
|
||||
float r = h * 0.40f * scale;
|
||||
|
|
@ -3841,8 +3841,8 @@ void ImGui::BBLRenderArrow(ImDrawList *draw_list, ImVec2 pos, ImU32 col, ImGuiDi
|
|||
case ImGuiDir_COUNT: IM_ASSERT(0); break;
|
||||
}
|
||||
//draw_list->AddTriangleFilled(center + a, center + b, center + c, col);
|
||||
draw_list->AddLine(center + a, center + c,col);
|
||||
draw_list->AddLine(center + a, center + b,col);
|
||||
draw_list->AddLine(center + a, center + c, col, thickness);
|
||||
draw_list->AddLine(center + a, center + b, col, thickness);
|
||||
}
|
||||
|
||||
void ImGui::RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col)
|
||||
|
|
|
|||
|
|
@ -2528,7 +2528,7 @@ namespace ImGui
|
|||
|
||||
// Render helpers (those functions don't access any ImGui state!)
|
||||
IMGUI_API void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale = 1.0f);
|
||||
IMGUI_API void BBLRenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale = 1.0f);
|
||||
IMGUI_API void BBLRenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float thickness = 1.0f, float scale = 1.0f);
|
||||
IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col);
|
||||
IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz);
|
||||
IMGUI_API void RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include <type_traits>
|
||||
#include <limits>
|
||||
|
||||
#define MAX_NUM_PLATES 50
|
||||
#define MAX_NUM_PLATES 36
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
|
||||
#define BP2D_NOEXCEPT
|
||||
|
|
|
|||
|
|
@ -602,6 +602,22 @@ private:
|
|||
return nfp::subtract({innerNfp}, nfps);
|
||||
}
|
||||
|
||||
Shapes calcnfp(const RawShape &sliding, const Shapes &stationarys, const Box &bed, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
|
||||
{
|
||||
using namespace nfp;
|
||||
|
||||
Shapes nfps(stationarys.size());
|
||||
Item slidingItem(sliding);
|
||||
slidingItem.transformedShape();
|
||||
__parallel::enumerate(stationarys.begin(), stationarys.end(), [&nfps, sliding, &slidingItem](const RawShape &stationary, size_t n) {
|
||||
auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(stationary, sliding);
|
||||
correctNfpPosition(subnfp_r, stationary, slidingItem);
|
||||
nfps[n] = subnfp_r.first;
|
||||
});
|
||||
|
||||
RawShape innerNfp = nfpInnerRectBed(bed, sliding).first;
|
||||
return nfp::subtract({innerNfp}, nfps);
|
||||
}
|
||||
|
||||
template<class Level>
|
||||
Shapes calcnfp(const Item &/*trsh*/, Level)
|
||||
|
|
@ -702,18 +718,31 @@ private:
|
|||
};
|
||||
}
|
||||
|
||||
if(items_.empty()) {
|
||||
bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; });
|
||||
|
||||
// item won't overlap with virtual objects if it's inside or touches NFP
|
||||
auto overlapWithVirtObject = [&]() -> double {
|
||||
if (items_.empty()) return 0;
|
||||
nfps = calcnfp(item, binbb, Lvl<MaxNfpLevel::value>());
|
||||
auto v = item.referenceVertex();
|
||||
for (const RawShape &nfp : nfps) {
|
||||
if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return 0; }
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
if (first_object) {
|
||||
setInitialPosition(item);
|
||||
auto best_tr = item.translation();
|
||||
auto best_rot = item.rotation();
|
||||
best_overfit = overfit(item.transformedShape(), bin_);
|
||||
best_overfit = overfit(item.transformedShape(), bin_) + overlapWithVirtObject();
|
||||
|
||||
for(auto rot : config_.rotations) {
|
||||
item.translation(initial_tr);
|
||||
item.rotation(initial_rot + rot);
|
||||
setInitialPosition(item);
|
||||
double of = 0.;
|
||||
if ((of = overfit(item.transformedShape(), bin_)) < best_overfit) {
|
||||
if ((of = overfit(item.transformedShape(), bin_)) + overlapWithVirtObject() < best_overfit) {
|
||||
best_overfit = of;
|
||||
best_tr = item.translation();
|
||||
best_rot = item.rotation();
|
||||
|
|
@ -725,7 +754,8 @@ private:
|
|||
global_score = 0.2;
|
||||
item.rotation(best_rot);
|
||||
item.translation(best_tr);
|
||||
} else {
|
||||
}
|
||||
if (can_pack == false) {
|
||||
|
||||
Pile merged_pile = merged_pile_;
|
||||
|
||||
|
|
@ -1035,27 +1065,9 @@ private:
|
|||
if (!item.is_virt_object)
|
||||
bb = sl::boundingBox(item.boundingBox(), bb);
|
||||
|
||||
// if move to center is infeasible, move to topright corner instead
|
||||
auto alignment = config_.alignment;
|
||||
if (!config_.m_excluded_regions.empty() && alignment== Config::Alignment::CENTER) {
|
||||
Box bb2 = bb;
|
||||
auto d = bbin.center() - bb2.center();
|
||||
d.x() = std::max(d.x(), 0);
|
||||
d.y() = std::max(d.y(), 0);
|
||||
bb2.minCorner() += d;
|
||||
bb2.maxCorner() += d;
|
||||
for (auto& region : config_.m_excluded_regions) {
|
||||
auto region_bb = region.boundingBox();
|
||||
if (bb2.intersection(region_bb).area()>0) {
|
||||
alignment = Config::Alignment::TOP_RIGHT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vertex ci, cb;
|
||||
|
||||
switch(alignment) {
|
||||
switch(config_.alignment) {
|
||||
case Config::Alignment::CENTER: {
|
||||
ci = bb.center();
|
||||
cb = bbin.center();
|
||||
|
|
@ -1086,13 +1098,44 @@ private:
|
|||
|
||||
auto d = cb - ci;
|
||||
|
||||
// BBS TODO we assume the exclude region contains bottom left corner. If not, change the code below
|
||||
if (!config_.m_excluded_regions.empty()) { // do not move to left to much to avoid clash with excluded regions
|
||||
if (d.x() < 0) {
|
||||
d.x() = 0;// std::max(long(d.x()), long(bbin.maxCorner().x() - bb.maxCorner().x()));
|
||||
// BBS make sure the item won't clash with excluded regions
|
||||
// do we have wipe tower after arranging?
|
||||
std::set<int> extruders;
|
||||
for (const Item& item : items_) {
|
||||
if (!item.is_virt_object) { extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end()); }
|
||||
}
|
||||
bool need_wipe_tower = extruders.size() > 1;
|
||||
|
||||
std::vector<RawShape> objs,excludes;
|
||||
for (const Item &item : items_) {
|
||||
if (item.isFixed()) continue;
|
||||
objs.push_back(item.transformedShape());
|
||||
}
|
||||
if (objs.empty())
|
||||
return;
|
||||
{ // find a best position inside NFP of fixed items (excluded regions), so the center of pile is cloest to bed center
|
||||
RawShape objs_convex_hull = sl::convexHull(objs);
|
||||
for (const Item &item : config_.m_excluded_regions) { excludes.push_back(item.transformedShape()); }
|
||||
for (const Item &item : items_) {
|
||||
if (item.isFixed()) {
|
||||
if (!(item.is_wipe_tower && !need_wipe_tower))
|
||||
excludes.push_back(item.transformedShape());
|
||||
}
|
||||
}
|
||||
if (d.y() < 0) {
|
||||
d.y() = 0;// std::max(long(d.y()), long(bbin.maxCorner().y() - bb.maxCorner().y()));
|
||||
|
||||
auto nfps = calcnfp(objs_convex_hull, excludes, bbin, Lvl<MaxNfpLevel::value>());
|
||||
if (nfps.empty()) {
|
||||
return;
|
||||
}
|
||||
Item objs_convex_hull_item(objs_convex_hull);
|
||||
Vertex objs_convex_hull_ref = objs_convex_hull_item.referenceVertex();
|
||||
Vertex diff = objs_convex_hull_ref - sl::boundingBox(objs_convex_hull).center();
|
||||
Vertex ref_aligned = cb + diff; // reference point when pile center aligned with bed center
|
||||
bool ref_aligned_is_ok = std::any_of(nfps.begin(), nfps.end(), [&ref_aligned](auto& nfp) {return sl::isInside(ref_aligned, nfp); });
|
||||
if (!ref_aligned_is_ok) {
|
||||
// ref_aligned is not good, then find a nearest point on nfp boundary
|
||||
Vertex ref_projected = projection_onto(nfps, ref_aligned);
|
||||
d += (ref_projected - ref_aligned);
|
||||
}
|
||||
}
|
||||
for(Item& item : items_)
|
||||
|
|
@ -1104,7 +1147,10 @@ private:
|
|||
Box bb = item.boundingBox();
|
||||
|
||||
Vertex ci, cb;
|
||||
auto bbin = sl::boundingBox(bin_);
|
||||
Box bbin = sl::boundingBox(bin_);
|
||||
Vertex shrink(10, 10);
|
||||
bbin.maxCorner() -= shrink;
|
||||
bbin.minCorner() += shrink;
|
||||
|
||||
switch(config_.starting_point) {
|
||||
case Config::Alignment::CENTER: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||
|
||||
// Definition of the ray intersection hit structure.
|
||||
|
|
@ -83,6 +84,13 @@ public:
|
|||
// to split around.
|
||||
template<typename SourceNode>
|
||||
void build(std::vector<SourceNode> &&input)
|
||||
{
|
||||
this->build_modify_input(input);
|
||||
input.clear();
|
||||
}
|
||||
|
||||
template<typename SourceNode>
|
||||
void build_modify_input(std::vector<SourceNode> &input)
|
||||
{
|
||||
if (input.empty())
|
||||
clear();
|
||||
|
|
@ -91,7 +99,6 @@ public:
|
|||
m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node());
|
||||
build_recursive(input, 0, 0, input.size() - 1);
|
||||
}
|
||||
input.clear();
|
||||
}
|
||||
|
||||
const std::vector<Node>& nodes() const { return m_nodes; }
|
||||
|
|
@ -211,6 +218,23 @@ using Tree3f = Tree<3, float>;
|
|||
using Tree2d = Tree<2, double>;
|
||||
using Tree3d = Tree<3, double>;
|
||||
|
||||
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
|
||||
// to build an AABBTree over coord_t 2D bounding boxes.
|
||||
class BoundingBoxWrapper {
|
||||
public:
|
||||
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
|
||||
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
|
|
@ -513,7 +537,7 @@ namespace detail {
|
|||
const VectorType origin;
|
||||
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index,
|
||||
ScalarType& squared_distance){
|
||||
ScalarType& squared_distance) const {
|
||||
const auto &triangle = this->faces[primitive_index];
|
||||
VectorType closest_point = closest_point_to_triangle<VectorType>(origin,
|
||||
this->vertices[triangle(0)].template cast<ScalarType>(),
|
||||
|
|
@ -895,48 +919,54 @@ struct Intersecting<Eigen::AlignedBox<CoordType, NumD>> {
|
|||
|
||||
template<class G> auto intersecting(const G &g) { return Intersecting<G>{g}; }
|
||||
|
||||
template<class G> struct Containing {};
|
||||
template<class G> struct Within {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Containing<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
struct Within<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Containing(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
Within(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return box.contains(node.bbox);
|
||||
return node.is_leaf() ? box.contains(node.bbox) : box.intersects(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto containing(const G &g) { return Containing<G>{g}; }
|
||||
template<class G> auto within(const G &g) { return Within<G>{g}; }
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Returns true in case traversal should continue,
|
||||
// returns false if traversal should stop (for example if the first hit was found).
|
||||
template<int Dims, typename T, typename Pred, typename Fn>
|
||||
void traverse_recurse(const Tree<Dims, T> &tree,
|
||||
bool traverse_recurse(const Tree<Dims, T> &tree,
|
||||
size_t idx,
|
||||
Pred && pred,
|
||||
Fn && callback)
|
||||
{
|
||||
assert(tree.node(idx).is_valid());
|
||||
|
||||
if (!pred(tree.node(idx))) return;
|
||||
if (!pred(tree.node(idx)))
|
||||
// Continue traversal.
|
||||
return true;
|
||||
|
||||
if (tree.node(idx).is_leaf()) {
|
||||
callback(tree.node(idx).idx);
|
||||
// Callback returns true to continue traversal, false to stop traversal.
|
||||
return callback(tree.node(idx));
|
||||
} else {
|
||||
|
||||
// call this with left and right node idx:
|
||||
auto trv = [&](size_t idx) {
|
||||
traverse_recurse(tree, idx, std::forward<Pred>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
auto trv = [&](size_t idx) -> bool {
|
||||
return traverse_recurse(tree, idx, std::forward<Pred>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
};
|
||||
|
||||
// Left / right child node index.
|
||||
trv(Tree<Dims, T>::left_child_idx(idx));
|
||||
trv(Tree<Dims, T>::right_child_idx(idx));
|
||||
// Returns true if both children allow the traversal to continue.
|
||||
return trv(Tree<Dims, T>::left_child_idx(idx)) &&
|
||||
trv(Tree<Dims, T>::right_child_idx(idx));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -946,6 +976,7 @@ void traverse_recurse(const Tree<Dims, T> &tree,
|
|||
// traverse(tree, intersecting(QueryBox), [](size_t face_idx) {
|
||||
// /* ... */
|
||||
// });
|
||||
// Callback shall return true to continue traversal, false if it wants to stop traversal, for example if it found the answer.
|
||||
template<int Dims, typename T, typename Predicate, typename Fn>
|
||||
void traverse(const Tree<Dims, T> &tree, Predicate &&pred, Fn &&callback)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ void AppConfig::set_defaults()
|
|||
set_bool("reverse_mouse_wheel_zoom", false);
|
||||
#endif
|
||||
|
||||
if (get("zoom_to_mouse").empty())
|
||||
set_bool("zoom_to_mouse", false);
|
||||
|
||||
//#ifdef SUPPORT_SHOW_HINTS
|
||||
if (get("show_hints").empty())
|
||||
set_bool("show_hints", true);
|
||||
|
|
@ -287,9 +290,17 @@ void AppConfig::set_defaults()
|
|||
if (get("mouse_wheel").empty()) {
|
||||
set("mouse_wheel", "0");
|
||||
}
|
||||
|
||||
if (get("max_recent_count").empty()) {
|
||||
set("max_recent_count", "18");
|
||||
}
|
||||
|
||||
if (get("backup_switch").empty()) {
|
||||
set_bool("backup_switch", false);
|
||||
if (get("sync_system_preset").empty()) {
|
||||
set_bool("sync_system_preset", true);
|
||||
}
|
||||
|
||||
if (get("backup_switch").empty() || get("version") < "01.06.00.00") {
|
||||
set_bool("backup_switch", true);
|
||||
}
|
||||
|
||||
if (get("backup_interval").empty()) {
|
||||
|
|
@ -314,6 +325,16 @@ void AppConfig::set_defaults()
|
|||
set_str("presets", "filament_colors", "#F2754E");
|
||||
}
|
||||
|
||||
if (get("print", "bed_leveling").empty()) {
|
||||
set_str("print", "bed_leveling", "1");
|
||||
}
|
||||
if (get("print", "flow_cali").empty()) {
|
||||
set_str("print", "flow_cali", "1");
|
||||
}
|
||||
if (get("print", "timelapse").empty()) {
|
||||
set_str("print", "timelapse", "1");
|
||||
}
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("app", "main_frame_maximized");
|
||||
erase("app", "main_frame_pos");
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
|
|||
template<class PConf>
|
||||
void fill_config(PConf& pcfg, const ArrangeParams ¶ms) {
|
||||
|
||||
if (params.is_seq_print || params.excluded_regions.empty()==false) {
|
||||
if (params.is_seq_print) {
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
// Start placing the items from the center of the print bed
|
||||
|
|
@ -95,7 +95,7 @@ void fill_config(PConf& pcfg, const ArrangeParams ¶ms) {
|
|||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
// Start placing the items from the center of the print bed
|
||||
pcfg.starting_point = PConf::Alignment::CENTER;
|
||||
pcfg.starting_point = PConf::Alignment::TOP_RIGHT;
|
||||
}
|
||||
|
||||
// Try 4 angles (45 degree step) and find the one with min cost
|
||||
|
|
@ -453,19 +453,23 @@ protected:
|
|||
|
||||
std::set<int> extruder_ids;
|
||||
int non_virt_cnt = 0;
|
||||
std::set<int> first_object_extruder_ids;
|
||||
for (int i = 0; i < m_items.size(); i++) {
|
||||
Item& p = m_items[i];
|
||||
if (p.is_virt_object) continue;
|
||||
extruder_ids.insert(p.extrude_ids.begin(),p.extrude_ids.end());
|
||||
non_virt_cnt++;
|
||||
if (non_virt_cnt == 1) { first_object_extruder_ids.insert(p.extrude_ids.begin(), p.extrude_ids.end()); }
|
||||
}
|
||||
extruder_ids.insert(item.extrude_ids.begin(),item.extrude_ids.end());
|
||||
|
||||
// add a large cost if not multi materials on same plate is not allowed
|
||||
if (!params.allow_multi_materials_on_same_plate) {
|
||||
bool first_object = non_virt_cnt == 0;
|
||||
bool same_color_with_first_object = std::all_of(item.extrude_ids.begin(), item.extrude_ids.end(),
|
||||
[&](int color) { return first_object_extruder_ids.find(color) != first_object_extruder_ids.end(); });
|
||||
// non_virt_cnt==0 means it's the first object, which can be multi-color
|
||||
if (extruder_ids.size() > 1 && non_virt_cnt > 0)
|
||||
score += LARGE_COST_TO_REJECT * 1.1;
|
||||
if (!(first_object || same_color_with_first_object)) score += LARGE_COST_TO_REJECT * 1.3;
|
||||
}
|
||||
// for layered printing, we want extruder change as few as possible
|
||||
// this has very weak effect, CAN NOT use a large weight
|
||||
|
|
@ -541,11 +545,11 @@ public:
|
|||
|
||||
auto binbb = sl::boundingBox(m_bin);
|
||||
// BBS: excluded region (virtual object but not wipe tower) should not affect final alignment
|
||||
bool all_is_excluded_region = std::all_of(items.begin(), items.end(), [](Item &itm) { return itm.is_virt_object && !itm.is_wipe_tower; });
|
||||
if (!all_is_excluded_region)
|
||||
cfg.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||
else
|
||||
cfg.alignment = PConfig::Alignment::CENTER;
|
||||
//bool all_is_excluded_region = std::all_of(items.begin(), items.end(), [](Item &itm) { return itm.is_virt_object && !itm.is_wipe_tower; });
|
||||
//if (!all_is_excluded_region)
|
||||
// cfg.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||
//else
|
||||
// cfg.alignment = PConfig::Alignment::CENTER;
|
||||
|
||||
auto starting_point = cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? binbb.minCorner() : binbb.center();
|
||||
// if we have wipe tower, items should be arranged around wipe tower
|
||||
|
|
|
|||
|
|
@ -160,7 +160,8 @@ bool BridgeDetector::detect_angle(double bridge_direction_override)
|
|||
// if any other direction is within extrusion width of coverage, prefer it if shorter
|
||||
// TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred?
|
||||
size_t i_best = 0;
|
||||
for (size_t i = 1; i < candidates.size() && abs(candidates[i_best].archored_percent - candidates[i].archored_percent) < EPSILON; ++ i)
|
||||
// for (size_t i = 1; i < candidates.size() && abs(candidates[i_best].archored_percent - candidates[i].archored_percent) < EPSILON; ++ i)
|
||||
for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ i)
|
||||
if (candidates[i].max_length < candidates[i_best].max_length)
|
||||
i_best = i;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
#ifndef slic3r_BridgeDetector_hpp_
|
||||
#define slic3r_BridgeDetector_hpp_
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "PrincipalComponents2D.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <string>
|
||||
|
|
@ -48,7 +54,7 @@ private:
|
|||
// the best direction is the one causing most lines to be bridged (thus most coverage)
|
||||
bool operator<(const BridgeDirection &other) const {
|
||||
// Initial sort by coverage only - comparator must obey strict weak ordering
|
||||
return this->archored_percent > other.archored_percent;
|
||||
return this->coverage > other.coverage;//this->archored_percent > other.archored_percent;
|
||||
};
|
||||
double angle;
|
||||
double coverage;
|
||||
|
|
@ -65,6 +71,59 @@ private:
|
|||
ExPolygons _anchor_regions;
|
||||
};
|
||||
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
|
||||
|
||||
if (floating_polylines.empty()) {
|
||||
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
|
||||
auto [pc1, pc2] = compute_principal_components(overhang_area);
|
||||
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
return {Vec2d{1.0,0.0}, 0.0};
|
||||
} else {
|
||||
return {pc2.normalized().cast<double>(), 0.0};
|
||||
}
|
||||
}
|
||||
|
||||
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
|
||||
Lines floating_edges = to_lines(floating_polylines);
|
||||
std::unordered_map<double, Vec2d> directions{};
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d normal = l.normal().cast<double>().normalized();
|
||||
double quantized_angle = std::ceil(std::atan2(normal.y(),normal.x()) * 1000.0);
|
||||
directions.emplace(quantized_angle, normal);
|
||||
}
|
||||
std::vector<std::pair<Vec2d, double>> direction_costs{};
|
||||
// it is acutally cost of a perpendicular bridge direction - we find the minimal cost and then return the perpendicular dir
|
||||
for (const auto& d : directions) {
|
||||
direction_costs.emplace_back(d.second, 0.0);
|
||||
}
|
||||
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d line = (l.b - l.a).cast<double>();
|
||||
for (auto &dir_cost : direction_costs) {
|
||||
// the dot product already contains the length of the line. dir_cost.first is normalized.
|
||||
dir_cost.second += std::abs(line.dot(dir_cost.first));
|
||||
}
|
||||
}
|
||||
|
||||
Vec2d result_dir = Vec2d::Ones();
|
||||
double min_cost = std::numeric_limits<double>::max();
|
||||
for (const auto &cost : direction_costs) {
|
||||
if (cost.second < min_cost) {
|
||||
// now flip the orientation back and return the direction of the bridge extrusions
|
||||
result_dir = Vec2d{cost.first.y(), -cost.first.x()};
|
||||
min_cost = cost.second;
|
||||
}
|
||||
}
|
||||
|
||||
return {result_dir, min_cost};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -892,10 +892,15 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
|||
// BBS: inner and outer boundary are offset from the same polygon incase of round off error.
|
||||
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
|
||||
append(brim_area_object, diff_ex(offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION), innerExpoly));
|
||||
|
||||
}
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) {
|
||||
append(brim_area_object, diff_ex(offset_ex(ex_poly_holes_reversed, -brim_offset), offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset)));
|
||||
}
|
||||
if (brim_type != BrimType::btInnerOnly && brim_type != BrimType::btOuterAndInner) {
|
||||
// BBS: brim should be apart from holes
|
||||
append(no_brim_area_object, diff_ex(ex_poly_holes_reversed, offset_ex(ex_poly_holes_reversed, -scale_(5.))));
|
||||
}
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly_holes_reversed));
|
||||
if (brim_type == BrimType::btNoBrim)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ set(lisbslic3r_sources
|
|||
ArcFitter.hpp
|
||||
pchheader.cpp
|
||||
pchheader.hpp
|
||||
AABBTreeIndirect.hpp
|
||||
AABBTreeLines.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
BridgeDetector.cpp
|
||||
|
|
@ -51,8 +53,6 @@ set(lisbslic3r_sources
|
|||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonCollection.cpp
|
||||
ExPolygonCollection.hpp
|
||||
Extruder.cpp
|
||||
Extruder.hpp
|
||||
ExtrusionEntity.cpp
|
||||
|
|
@ -125,6 +125,8 @@ set(lisbslic3r_sources
|
|||
# GCode/PressureEqualizer.hpp
|
||||
GCode/PrintExtents.cpp
|
||||
GCode/PrintExtents.hpp
|
||||
GCode/RetractWhenCrossingPerimeters.cpp
|
||||
GCode/RetractWhenCrossingPerimeters.hpp
|
||||
GCode/SpiralVase.cpp
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
|
|
@ -138,6 +140,8 @@ set(lisbslic3r_sources
|
|||
GCode/AvoidCrossingPerimeters.cpp
|
||||
GCode/AvoidCrossingPerimeters.hpp
|
||||
GCode/ExtrusionProcessor.hpp
|
||||
GCode/ConflictChecker.cpp
|
||||
GCode/ConflictChecker.hpp
|
||||
GCode.cpp
|
||||
GCode.hpp
|
||||
GCodeReader.cpp
|
||||
|
|
@ -217,6 +221,8 @@ set(lisbslic3r_sources
|
|||
PresetBundle.hpp
|
||||
ProjectTask.cpp
|
||||
ProjectTask.hpp
|
||||
PrincipalComponents2D.hpp
|
||||
PrincipalComponents2D.cpp
|
||||
AppConfig.cpp
|
||||
AppConfig.hpp
|
||||
Print.cpp
|
||||
|
|
|
|||
|
|
@ -57,8 +57,113 @@ err:
|
|||
#endif /* CLIPPER_UTILS_DEBUG */
|
||||
|
||||
namespace ClipperUtils {
|
||||
Points EmptyPathsProvider::s_empty_points;
|
||||
Points SinglePathProvider::s_end;
|
||||
Points EmptyPathsProvider::s_empty_points;
|
||||
Points SinglePathProvider::s_end;
|
||||
|
||||
// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
|
||||
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one
|
||||
// with a set of polygons covering the whole layer below.
|
||||
template<typename PointType> inline void clip_clipper_polygon_with_subject_bbox_templ(const std::vector<PointType> &src, const BoundingBox &bbox, std::vector<PointType> &out, const bool get_entire_polygons=false)
|
||||
{
|
||||
out.clear();
|
||||
const size_t cnt = src.size();
|
||||
if (cnt < 3) return;
|
||||
|
||||
enum class Side { Left = 1, Right = 2, Top = 4, Bottom = 8 };
|
||||
|
||||
auto sides = [bbox](const PointType &p) {
|
||||
return int(p.x() < bbox.min.x()) * int(Side::Left) + int(p.x() > bbox.max.x()) * int(Side::Right) + int(p.y() < bbox.min.y()) * int(Side::Bottom) +
|
||||
int(p.y() > bbox.max.y()) * int(Side::Top);
|
||||
};
|
||||
|
||||
int sides_prev = sides(src.back());
|
||||
int sides_this = sides(src.front());
|
||||
const size_t last = cnt - 1;
|
||||
for (size_t i = 0; i < last; ++i) {
|
||||
int sides_next = sides(src[i + 1]);
|
||||
if ( // This point is inside. Take it.
|
||||
sides_this == 0 ||
|
||||
// Either this point is outside and previous or next is inside, or
|
||||
// the edge possibly cuts corner of the bounding box.
|
||||
(sides_prev & sides_this & sides_next) == 0) {
|
||||
out.emplace_back(src[i]);
|
||||
sides_prev = sides_this;
|
||||
} else {
|
||||
// All the three points (this, prev, next) are outside at the same side.
|
||||
// Ignore this point.
|
||||
}
|
||||
sides_this = sides_next;
|
||||
}
|
||||
|
||||
// Never produce just a single point output polygon.
|
||||
if (!out.empty())
|
||||
if(get_entire_polygons){
|
||||
out=src;
|
||||
}else{
|
||||
if (int sides_next = sides(out.front());
|
||||
// The last point is inside. Take it.
|
||||
sides_this == 0 ||
|
||||
// Either this point is outside and previous or next is inside, or
|
||||
// the edge possibly cuts corner of the bounding box.
|
||||
(sides_prev & sides_this & sides_next) == 0)
|
||||
out.emplace_back(src.back());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out, const bool get_entire_polygons) { clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out, get_entire_polygons); }
|
||||
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out) { clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); }
|
||||
|
||||
template<typename PointType> [[nodiscard]] std::vector<PointType> clip_clipper_polygon_with_subject_bbox_templ(const std::vector<PointType> &src, const BoundingBox &bbox)
|
||||
{
|
||||
std::vector<PointType> out;
|
||||
clip_clipper_polygon_with_subject_bbox(src, bbox, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox) { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); }
|
||||
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox) { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); }
|
||||
|
||||
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out) {
|
||||
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points);
|
||||
}
|
||||
|
||||
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, const bool get_entire_polygons)
|
||||
{
|
||||
Polygon out;
|
||||
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points, get_entire_polygons);
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(src.size());
|
||||
for (const Polygon &p : src) out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox));
|
||||
out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) { return polygon.empty(); }), out.end());
|
||||
return out;
|
||||
}
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(src.num_contours());
|
||||
out.emplace_back(clip_clipper_polygon_with_subject_bbox(src.contour, bbox, get_entire_polygons));
|
||||
for (const Polygon &p : src.holes) out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox, get_entire_polygons));
|
||||
out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) { return polygon.empty(); }), out.end());
|
||||
return out;
|
||||
}
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(number_polygons(src));
|
||||
for (const ExPolygon &p : src) {
|
||||
Polygons temp = clip_clipper_polygons_with_subject_bbox(p, bbox, get_entire_polygons);
|
||||
out.insert(out.end(), temp.begin(), temp.end());
|
||||
}
|
||||
|
||||
out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) {return polygon.empty(); }), out.end());
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree)
|
||||
|
|
@ -560,6 +665,8 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c
|
|||
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
|
||||
|
|
@ -646,10 +753,16 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r
|
|||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset);}
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygons& clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset);}
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
|
|
@ -745,6 +858,8 @@ Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subj
|
|||
return retval;
|
||||
}
|
||||
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline& subject, const Slic3r::Polygons& clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::PolygonsProvider(clip)); }
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); }
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip)
|
||||
|
|
@ -757,6 +872,10 @@ Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygon
|
|||
{ return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip)
|
||||
{ return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::PolygonsProvider(clip)); }
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip)
|
||||
|
|
|
|||
|
|
@ -296,7 +296,26 @@ namespace ClipperUtils {
|
|||
const SurfacesPtr &m_surfaces;
|
||||
size_t m_size;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// For ClipperLib with Z coordinates.
|
||||
using ZPoint = Vec3i32;
|
||||
using ZPoints = std::vector<Vec3i32>;
|
||||
|
||||
// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
|
||||
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one
|
||||
// with a set of polygons covering the whole layer below.
|
||||
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out, const bool get_entire_polygons = false);
|
||||
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out);
|
||||
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox);
|
||||
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox);
|
||||
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out);
|
||||
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
|
||||
}
|
||||
|
||||
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
|
||||
|
|
@ -412,6 +431,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly
|
|||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline& subject, const Slic3r::Polygons& clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||
|
|
@ -452,6 +472,7 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon
|
|||
|
||||
// Safety offset is applied to the clipping polygons only.
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
|
|
@ -462,28 +483,24 @@ Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::E
|
|||
Slic3r::Polygons intersection(const Slic3r::Polygons& subject, const Slic3r::Polygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygons& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip);
|
||||
|
||||
// BBS
|
||||
inline Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygons& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No)
|
||||
{
|
||||
Slic3r::ExPolygons subject_temp;
|
||||
subject_temp.push_back(subject);
|
||||
|
||||
return intersection_ex(subject_temp, clip, do_safety_offset);
|
||||
}
|
||||
|
||||
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
return _clipper_ln(ClipperLib::ctIntersection, subject, clip);
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution)
|
|||
create_from_m_contours(resolution);
|
||||
}
|
||||
|
||||
void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resolution)
|
||||
{
|
||||
create(expolygons.expolygons, resolution);
|
||||
}
|
||||
|
||||
// m_contours has been initialized. Now fill in the edge grid.
|
||||
void EdgeGrid::Grid::create_from_m_contours(coord_t resolution)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#include "Point.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace EdgeGrid {
|
||||
|
|
@ -112,7 +111,6 @@ public:
|
|||
void create(const std::vector<Points> &polygons, coord_t resolution) { this->create(polygons, resolution, false); }
|
||||
void create(const ExPolygon &expoly, coord_t resolution);
|
||||
void create(const ExPolygons &expolygons, coord_t resolution);
|
||||
void create(const ExPolygonCollection &expolygons, coord_t resolution);
|
||||
|
||||
const std::vector<Contour>& contours() const { return m_contours; }
|
||||
|
||||
|
|
@ -123,7 +121,6 @@ public:
|
|||
bool intersect(const Polygons &polygons) { for (size_t i = 0; i < polygons.size(); ++ i) if (intersect(polygons[i])) return true; return false; }
|
||||
bool intersect(const ExPolygon &expoly) { if (intersect(expoly.contour)) return true; for (size_t i = 0; i < expoly.holes.size(); ++ i) if (intersect(expoly.holes[i])) return true; return false; }
|
||||
bool intersect(const ExPolygons &expolygons) { for (size_t i = 0; i < expolygons.size(); ++ i) if (intersect(expolygons[i])) return true; return false; }
|
||||
bool intersect(const ExPolygonCollection &expolygons) { return intersect(expolygons.expolygons); }
|
||||
|
||||
// Test, whether a point is inside a contour.
|
||||
bool inside(const Point &pt);
|
||||
|
|
@ -391,7 +388,7 @@ protected:
|
|||
|
||||
// Referencing the source contours.
|
||||
// This format allows one to work with any Slic3r fixed point contour format
|
||||
// (Polygon, ExPolygon, ExPolygonCollection etc).
|
||||
// (Polygon, ExPolygon, ExPolygons etc).
|
||||
std::vector<Contour> m_contours;
|
||||
|
||||
// Referencing a contour and a line segment of m_contours.
|
||||
|
|
|
|||
|
|
@ -12,27 +12,6 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
ExPolygon::operator Points() const
|
||||
{
|
||||
Points points;
|
||||
Polygons pp = *this;
|
||||
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
|
||||
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
|
||||
points.push_back(*point);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
ExPolygon::operator Polygons() const
|
||||
{
|
||||
return to_polygons(*this);
|
||||
}
|
||||
|
||||
ExPolygon::operator Polylines() const
|
||||
{
|
||||
return to_polylines(*this);
|
||||
}
|
||||
|
||||
void ExPolygon::scale(double factor)
|
||||
{
|
||||
contour.scale(factor);
|
||||
|
|
@ -40,6 +19,13 @@ void ExPolygon::scale(double factor)
|
|||
hole.scale(factor);
|
||||
}
|
||||
|
||||
void ExPolygon::scale(double factor_x, double factor_y)
|
||||
{
|
||||
contour.scale(factor_x, factor_y);
|
||||
for (Polygon &hole : holes)
|
||||
hole.scale(factor_x, factor_y);
|
||||
}
|
||||
|
||||
void ExPolygon::translate(const Point &p)
|
||||
{
|
||||
contour.translate(p);
|
||||
|
|
@ -118,34 +104,53 @@ bool ExPolygon::contains(const Polylines &polylines) const
|
|||
return pl_out.empty();
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Point &point) const
|
||||
bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const
|
||||
{
|
||||
if (! this->contour.contains(point))
|
||||
if (! Slic3r::contains(contour, point, border_result))
|
||||
// Outside the outer contour, not on the contour boundary.
|
||||
return false;
|
||||
for (const Polygon &hole : this->holes)
|
||||
if (hole.contains(point))
|
||||
if (Slic3r::contains(hole, point, ! border_result))
|
||||
// Inside a hole, not on the hole boundary.
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// inclusive version of contains() that also checks whether point is on boundaries
|
||||
bool ExPolygon::contains_b(const Point &point) const
|
||||
bool ExPolygon::on_boundary(const Point &point, double eps) const
|
||||
{
|
||||
return this->contains(point) || this->has_boundary_point(point);
|
||||
if (this->contour.on_boundary(point, eps))
|
||||
return true;
|
||||
for (const Polygon &hole : this->holes)
|
||||
if (hole.on_boundary(point, eps))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ExPolygon::has_boundary_point(const Point &point) const
|
||||
// Projection of a point onto the polygon.
|
||||
Point ExPolygon::point_projection(const Point &point) const
|
||||
{
|
||||
if (this->contour.has_boundary_point(point)) return true;
|
||||
for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
|
||||
if (h->has_boundary_point(point)) return true;
|
||||
if (this->holes.empty()) {
|
||||
return this->contour.point_projection(point);
|
||||
} else {
|
||||
double dist_min2 = std::numeric_limits<double>::max();
|
||||
Point closest_pt_min;
|
||||
for (size_t i = 0; i < this->num_contours(); ++ i) {
|
||||
Point closest_pt = this->contour_or_hole(i).point_projection(point);
|
||||
double d2 = (closest_pt - point).cast<double>().squaredNorm();
|
||||
if (d2 < dist_min2) {
|
||||
dist_min2 = d2;
|
||||
closest_pt_min = closest_pt;
|
||||
}
|
||||
}
|
||||
return closest_pt_min;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExPolygon::overlaps(const ExPolygon &other) const
|
||||
{
|
||||
if (this->empty() || other.empty())
|
||||
return false;
|
||||
|
||||
#if 0
|
||||
BoundingBox bbox = get_extents(other);
|
||||
bbox.merge(get_extents(*this));
|
||||
|
|
@ -155,61 +160,92 @@ bool ExPolygon::overlaps(const ExPolygon &other) const
|
|||
svg.draw_outline(*this);
|
||||
svg.draw_outline(other, "blue");
|
||||
#endif
|
||||
Polylines pl_out = intersection_pl((Polylines)other, *this);
|
||||
|
||||
Polylines pl_out = intersection_pl(to_polylines(other), *this);
|
||||
|
||||
#if 0
|
||||
svg.draw(pl_out, "red");
|
||||
#endif
|
||||
if (! pl_out.empty())
|
||||
return true;
|
||||
//FIXME ExPolygon::overlaps() shall be commutative, it is not!
|
||||
return ! other.contour.points.empty() && this->contains_b(other.contour.points.front());
|
||||
|
||||
// See unit test SCENARIO("Clipper diff with polyline", "[Clipper]")
|
||||
// for in which case the intersection_pl produces any intersection.
|
||||
return ! pl_out.empty() ||
|
||||
// If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation.
|
||||
other.contains(this->contour.points.front());
|
||||
}
|
||||
|
||||
void ExPolygon::simplify_p(double tolerance, Polygons* polygons, SimplifyMethod method) const
|
||||
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2)
|
||||
{
|
||||
Polygons pp = this->simplify_p(tolerance, method);
|
||||
for (const ExPolygon& expoly1 : expolys1) {
|
||||
for (const ExPolygon& expoly2 : expolys2) {
|
||||
if (expoly1.overlaps(expoly2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Point projection_onto(const ExPolygons& polygons, const Point& from)
|
||||
{
|
||||
Point projected_pt;
|
||||
double min_dist = std::numeric_limits<double>::max();
|
||||
|
||||
for (const auto& poly : polygons) {
|
||||
for (int i = 0; i < poly.num_contours(); i++) {
|
||||
Point p = from.projection_onto(poly.contour_or_hole(i));
|
||||
double dist = (from - p).cast<double>().squaredNorm();
|
||||
if (dist < min_dist) {
|
||||
projected_pt = p;
|
||||
min_dist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return projected_pt;
|
||||
}
|
||||
|
||||
void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const
|
||||
{
|
||||
Polygons pp = this->simplify_p(tolerance);
|
||||
polygons->insert(polygons->end(), pp.begin(), pp.end());
|
||||
}
|
||||
|
||||
Polygons ExPolygon::simplify_p(double tolerance, SimplifyMethod method) const
|
||||
Polygons ExPolygon::simplify_p(double tolerance) const
|
||||
{
|
||||
Polygons pp;
|
||||
pp.reserve(this->holes.size() + 1);
|
||||
std::map<int, std::function<Points(const Points&, const double)>> method_list = { {SimplifyMethodDP, MultiPoint::_douglas_peucker}, {SimplifyMethodVisvalingam, MultiPoint::visivalingam},{SimplifyMethodConcave, MultiPoint::concave_hull_2d} };
|
||||
// contour
|
||||
{
|
||||
Polygon p = this->contour;
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = method_list[method](p.points, tolerance);
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
// holes
|
||||
for (Polygon p : this->holes) {
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = method_list[method](p.points, tolerance);
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
return simplify_polygons(pp);
|
||||
}
|
||||
|
||||
ExPolygons ExPolygon::simplify(double tolerance, SimplifyMethod method) const
|
||||
ExPolygons ExPolygon::simplify(double tolerance) const
|
||||
{
|
||||
return union_ex(this->simplify_p(tolerance, method));
|
||||
return union_ex(this->simplify_p(tolerance));
|
||||
}
|
||||
|
||||
void ExPolygon::simplify(double tolerance, ExPolygons* expolygons, SimplifyMethod method) const
|
||||
void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
|
||||
{
|
||||
append(*expolygons, this->simplify(tolerance, method));
|
||||
append(*expolygons, this->simplify(tolerance));
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
|
||||
void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const
|
||||
{
|
||||
// init helper object
|
||||
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
|
||||
ma.lines = this->lines();
|
||||
Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this);
|
||||
|
||||
// compute the Voronoi diagram and extract medial axis polylines
|
||||
ThickPolylines pp;
|
||||
|
|
@ -240,7 +276,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
|
|||
call, so we keep the inner point until we perform the second intersection() as well */
|
||||
Point new_front = polyline.points.front();
|
||||
Point new_back = polyline.points.back();
|
||||
if (polyline.endpoints.first && !this->has_boundary_point(new_front)) {
|
||||
if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) {
|
||||
Vec2d p1 = polyline.points.front().cast<double>();
|
||||
Vec2d p2 = polyline.points[1].cast<double>();
|
||||
// prevent the line from touching on the other side, otherwise intersection() might return that solution
|
||||
|
|
@ -250,7 +286,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
|
|||
p1 -= (p2 - p1).normalized() * max_width;
|
||||
this->contour.intersection(Line(p1.cast<coord_t>(), p2.cast<coord_t>()), &new_front);
|
||||
}
|
||||
if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
|
||||
if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) {
|
||||
Vec2d p1 = (polyline.points.end() - 2)->cast<double>();
|
||||
Vec2d p2 = polyline.points.back().cast<double>();
|
||||
// prevent the line from touching on the other side, otherwise intersection() might return that solution
|
||||
|
|
@ -312,16 +348,17 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
polylines->insert(polylines->end(), pp.begin(), pp.end());
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
|
||||
void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->medial_axis(max_width, min_width, &tp);
|
||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||
this->medial_axis(min_width, max_width, &tp);
|
||||
polylines->reserve(polylines->size() + tp.size());
|
||||
for (auto &pl : tp)
|
||||
polylines->emplace_back(pl.points);
|
||||
}
|
||||
|
||||
Lines ExPolygon::lines() const
|
||||
|
|
@ -334,6 +371,18 @@ Lines ExPolygon::lines() const
|
|||
return lines;
|
||||
}
|
||||
|
||||
// Do expolygons match? If they match, they must have the same topology,
|
||||
// however their contours may be rotated.
|
||||
bool expolygons_match(const ExPolygon &l, const ExPolygon &r)
|
||||
{
|
||||
if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour))
|
||||
return false;
|
||||
for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx)
|
||||
if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExPolygon &expolygon)
|
||||
{
|
||||
return get_extents(expolygon.contour);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef slic3r_ExPolygon_hpp_
|
||||
#define slic3r_ExPolygon_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
|
@ -9,13 +10,7 @@
|
|||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
typedef std::vector<ExPolygon> ExPolygons;
|
||||
|
||||
typedef enum SimplifyMethod_ {
|
||||
SimplifyMethodDP=0,
|
||||
SimplifyMethodVisvalingam,
|
||||
SimplifyMethodConcave
|
||||
}SimplifyMethod;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
class ExPolygon
|
||||
{
|
||||
|
|
@ -37,14 +32,12 @@ public:
|
|||
ExPolygon& operator=(const ExPolygon &other) = default;
|
||||
ExPolygon& operator=(ExPolygon &&other) = default;
|
||||
|
||||
Polygon contour;
|
||||
Polygons holes;
|
||||
Polygon contour; //CCW
|
||||
Polygons holes; //CW
|
||||
|
||||
operator Points() const;
|
||||
operator Polygons() const;
|
||||
operator Polylines() const;
|
||||
void clear() { contour.points.clear(); holes.clear(); }
|
||||
void scale(double factor);
|
||||
void scale(double factor_x, double factor_y);
|
||||
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
|
||||
void translate(const Point &vector);
|
||||
void rotate(double angle);
|
||||
|
|
@ -58,21 +51,29 @@ public:
|
|||
bool contains(const Line &line) const;
|
||||
bool contains(const Polyline &polyline) const;
|
||||
bool contains(const Polylines &polylines) const;
|
||||
bool contains(const Point &point) const;
|
||||
bool contains_b(const Point &point) const;
|
||||
bool has_boundary_point(const Point &point) const;
|
||||
bool contains(const Point &point, bool border_result = true) const;
|
||||
// Approximate on boundary test.
|
||||
bool on_boundary(const Point &point, double eps) const;
|
||||
// Projection of a point onto the polygon.
|
||||
Point point_projection(const Point &point) const;
|
||||
|
||||
// Does this expolygon overlap another expolygon?
|
||||
// Either the ExPolygons intersect, or one is fully inside the other,
|
||||
// and it is not inside a hole of the other expolygon.
|
||||
// The test may not be commutative if the two expolygons touch by a boundary only,
|
||||
// see unit test SCENARIO("Clipper diff with polyline", "[Clipper]").
|
||||
// Namely expolygons touching at a vertical boundary are considered overlapping, while expolygons touching
|
||||
// at a horizontal boundary are NOT considered overlapping.
|
||||
bool overlaps(const ExPolygon &other) const;
|
||||
|
||||
void simplify_p(double tolerance, Polygons* polygons, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
Polygons simplify_p(double tolerance, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
ExPolygons simplify(double tolerance, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
void simplify(double tolerance, ExPolygons* expolygons, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
|
||||
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
|
||||
void simplify_p(double tolerance, Polygons* polygons) const;
|
||||
Polygons simplify_p(double tolerance) const;
|
||||
ExPolygons simplify(double tolerance) const;
|
||||
void simplify(double tolerance, ExPolygons* expolygons) const;
|
||||
void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const;
|
||||
void medial_axis(double min_width, double max_width, Polylines* polylines) const;
|
||||
Polylines medial_axis(double min_width, double max_width) const
|
||||
{ Polylines out; this->medial_axis(min_width, max_width, &out); return out; }
|
||||
Lines lines() const;
|
||||
|
||||
// Number of contours (outer contour with holes).
|
||||
|
|
@ -84,22 +85,22 @@ public:
|
|||
inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; }
|
||||
inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour != rhs.contour || lhs.holes != rhs.holes; }
|
||||
|
||||
inline size_t count_points(const ExPolygons& expolys)
|
||||
inline size_t count_points(const ExPolygons &expolys)
|
||||
{
|
||||
size_t n_points = 0;
|
||||
for (const auto& expoly : expolys) {
|
||||
for (const auto &expoly : expolys) {
|
||||
n_points += expoly.contour.points.size();
|
||||
for (const auto& hole : expoly.holes)
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
}
|
||||
return n_points;
|
||||
}
|
||||
|
||||
inline size_t count_points(const ExPolygon& expoly)
|
||||
inline size_t count_points(const ExPolygon &expoly)
|
||||
{
|
||||
size_t n_points = expoly.contour.points.size();
|
||||
for (const auto& hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
return n_points;
|
||||
}
|
||||
|
||||
|
|
@ -115,11 +116,8 @@ inline size_t number_polygons(const ExPolygons &expolys)
|
|||
|
||||
inline Lines to_lines(const ExPolygon &src)
|
||||
{
|
||||
size_t n_lines = src.contour.points.size();
|
||||
for (size_t i = 0; i < src.holes.size(); ++ i)
|
||||
n_lines += src.holes[i].points.size();
|
||||
Lines lines;
|
||||
lines.reserve(n_lines);
|
||||
lines.reserve(count_points(src));
|
||||
for (size_t i = 0; i <= src.holes.size(); ++ i) {
|
||||
const Polygon &poly = (i == 0) ? src.contour : src.holes[i - 1];
|
||||
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
|
||||
|
|
@ -131,14 +129,8 @@ inline Lines to_lines(const ExPolygon &src)
|
|||
|
||||
inline Lines to_lines(const ExPolygons &src)
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
|
||||
n_lines += it_expoly->contour.points.size();
|
||||
for (size_t i = 0; i < it_expoly->holes.size(); ++ i)
|
||||
n_lines += it_expoly->holes[i].points.size();
|
||||
}
|
||||
Lines lines;
|
||||
lines.reserve(n_lines);
|
||||
lines.reserve(count_points(src));
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
|
||||
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
|
||||
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
|
||||
|
|
@ -149,59 +141,6 @@ inline Lines to_lines(const ExPolygons &src)
|
|||
}
|
||||
return lines;
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
assert(count_lines == 0 || count_lines == count_points(src));
|
||||
if (count_lines == 0)
|
||||
count_lines = count_points(src);
|
||||
Linesf lines;
|
||||
lines.reserve(count_lines);
|
||||
Vec2d prev_pd;
|
||||
auto to_lines = [&lines, &prev_pd](const Points& pts) {
|
||||
assert(pts.size() >= 3);
|
||||
if (pts.size() < 2)
|
||||
return;
|
||||
bool is_first = true;
|
||||
for (const Point& p : pts) {
|
||||
Vec2d pd = p.cast<double>();
|
||||
if (is_first)
|
||||
is_first = false;
|
||||
else
|
||||
lines.emplace_back(prev_pd, pd);
|
||||
prev_pd = pd;
|
||||
}
|
||||
lines.emplace_back(prev_pd, pts.front().cast<double>());
|
||||
};
|
||||
for (const ExPolygon& expoly : src) {
|
||||
to_lines(expoly.contour.points);
|
||||
for (const Polygon& hole : expoly.holes)
|
||||
to_lines(hole.points);
|
||||
}
|
||||
assert(lines.size() == count_lines);
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Linesf to_unscaled_linesf(const ExPolygons& src)
|
||||
{
|
||||
Linesf lines;
|
||||
lines.reserve(count_points(src));
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++it_expoly) {
|
||||
for (size_t i = 0; i <= it_expoly->holes.size(); ++i) {
|
||||
const Points& points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
|
||||
Vec2d unscaled_a = unscaled(points.front());
|
||||
Vec2d unscaled_b = unscaled_a;
|
||||
for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) {
|
||||
unscaled_b = unscaled(*(it));
|
||||
lines.push_back(Linef(unscaled_a, unscaled_b));
|
||||
unscaled_a = unscaled_b;
|
||||
}
|
||||
lines.push_back(Linef(unscaled_a, unscaled(points.front())));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Points to_points(const ExPolygons& src)
|
||||
{
|
||||
|
|
@ -216,6 +155,56 @@ inline Points to_points(const ExPolygons& src)
|
|||
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)
|
||||
{
|
||||
assert(count_lines == 0 || count_lines == count_points(src));
|
||||
if (count_lines == 0) count_lines = count_points(src);
|
||||
Linesf lines;
|
||||
lines.reserve(count_lines);
|
||||
Vec2d prev_pd;
|
||||
auto to_lines = [&lines, &prev_pd](const Points &pts) {
|
||||
assert(pts.size() >= 3);
|
||||
if (pts.size() < 2) return;
|
||||
bool is_first = true;
|
||||
for (const Point &p : pts) {
|
||||
Vec2d pd = p.cast<double>();
|
||||
if (is_first) is_first = false;
|
||||
else lines.emplace_back(prev_pd, pd);
|
||||
prev_pd = pd;
|
||||
}
|
||||
lines.emplace_back(prev_pd, pts.front().cast<double>());
|
||||
};
|
||||
for (const ExPolygon& expoly: src) {
|
||||
to_lines(expoly.contour.points);
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
to_lines(hole.points);
|
||||
}
|
||||
assert(lines.size() == count_lines);
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Linesf to_unscaled_linesf(const ExPolygons &src)
|
||||
{
|
||||
Linesf lines;
|
||||
lines.reserve(count_points(src));
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
|
||||
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
|
||||
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
|
||||
Vec2d unscaled_a = unscaled(points.front());
|
||||
Vec2d unscaled_b = unscaled_a;
|
||||
for (Points::const_iterator it = points.begin()+1; it != points.end(); ++it){
|
||||
unscaled_b = unscaled(*(it));
|
||||
lines.push_back(Linef(unscaled_a, unscaled_b));
|
||||
unscaled_a = unscaled_b;
|
||||
}
|
||||
lines.push_back(Linef(unscaled_a, unscaled(points.front())));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(const ExPolygon &src)
|
||||
{
|
||||
Polylines polylines;
|
||||
|
|
@ -260,10 +249,10 @@ inline Polylines to_polylines(ExPolygon &&src)
|
|||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(src.contour.points);
|
||||
pl.points.push_back(pl.points.front());
|
||||
for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
|
||||
for (auto ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(ith->points);
|
||||
pl.points.push_back(ith->points.front());
|
||||
pl.points.push_back(pl.points.front());
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
|
|
@ -274,14 +263,14 @@ inline Polylines to_polylines(ExPolygons &&src)
|
|||
Polylines polylines;
|
||||
polylines.assign(number_polygons(src), Polyline());
|
||||
size_t idx = 0;
|
||||
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
|
||||
for (auto it = src.begin(); it != src.end(); ++it) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(it->contour.points);
|
||||
pl.points.push_back(pl.points.front());
|
||||
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
for (auto ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(ith->points);
|
||||
pl.points.push_back(ith->points.front());
|
||||
pl.points.push_back(pl.points.front());
|
||||
}
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
|
|
@ -335,8 +324,9 @@ inline Polygons to_polygons(ExPolygon &&src)
|
|||
Polygons polygons;
|
||||
polygons.reserve(src.holes.size() + 1);
|
||||
polygons.push_back(std::move(src.contour));
|
||||
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(polygons));
|
||||
src.holes.clear();
|
||||
polygons.insert(polygons.end(),
|
||||
std::make_move_iterator(src.holes.begin()),
|
||||
std::make_move_iterator(src.holes.end()));
|
||||
return polygons;
|
||||
}
|
||||
|
||||
|
|
@ -344,10 +334,11 @@ inline Polygons to_polygons(ExPolygons &&src)
|
|||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(number_polygons(src));
|
||||
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) {
|
||||
polygons.push_back(std::move(it->contour));
|
||||
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons));
|
||||
it->holes.clear();
|
||||
for (ExPolygon& expoly: src) {
|
||||
polygons.push_back(std::move(expoly.contour));
|
||||
polygons.insert(polygons.end(),
|
||||
std::make_move_iterator(expoly.holes.begin()),
|
||||
std::make_move_iterator(expoly.holes.end()));
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
|
@ -370,6 +361,16 @@ inline ExPolygons to_expolygons(Polygons &&polys)
|
|||
return ex_polys;
|
||||
}
|
||||
|
||||
inline Points to_points(const ExPolygon &expoly)
|
||||
{
|
||||
Points out;
|
||||
out.reserve(count_points(expoly));
|
||||
append(out, expoly.contour.points);
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
append(out, hole.points);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, const ExPolygon &src)
|
||||
{
|
||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||
|
|
@ -389,18 +390,20 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src)
|
|||
inline void polygons_append(Polygons &dst, ExPolygon &&src)
|
||||
{
|
||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||
dst.push_back(std::move(src.contour));
|
||||
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst));
|
||||
src.holes.clear();
|
||||
dst.push_back(std::move(src.contour));
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(src.holes.begin()),
|
||||
std::make_move_iterator(src.holes.end()));
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, ExPolygons &&src)
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) {
|
||||
dst.push_back(std::move(it->contour));
|
||||
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst));
|
||||
it->holes.clear();
|
||||
for (ExPolygon& expoly: src) {
|
||||
dst.push_back(std::move(expoly.contour));
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(expoly.holes.begin()),
|
||||
std::make_move_iterator(expoly.holes.end()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -414,21 +417,22 @@ inline void expolygons_append(ExPolygons &dst, ExPolygons &&src)
|
|||
if (dst.empty()) {
|
||||
dst = std::move(src);
|
||||
} else {
|
||||
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
|
||||
src.clear();
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(src.begin()),
|
||||
std::make_move_iterator(src.end()));
|
||||
}
|
||||
}
|
||||
|
||||
inline void expolygons_rotate(ExPolygons &expolys, double angle)
|
||||
{
|
||||
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
|
||||
p->rotate(angle);
|
||||
for (ExPolygon &expoly : expolys)
|
||||
expoly.rotate(angle);
|
||||
}
|
||||
|
||||
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt)
|
||||
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true)
|
||||
{
|
||||
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
|
||||
if (p->contains(pt))
|
||||
for (const ExPolygon &expoly : expolys)
|
||||
if (expoly.contains(pt, border_result))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -442,6 +446,14 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc
|
|||
return out;
|
||||
}
|
||||
|
||||
// Do expolygons match? If they match, they must have the same topology,
|
||||
// however their contours may be rotated.
|
||||
bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
|
||||
|
||||
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2);
|
||||
|
||||
Point projection_onto(const ExPolygons& expolys, const Point& pt);
|
||||
|
||||
BoundingBox get_extents(const ExPolygon &expolygon);
|
||||
BoundingBox get_extents(const ExPolygons &expolygons);
|
||||
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include "ExtrusionEntity.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include "Flow.hpp"
|
||||
|
|
@ -12,14 +12,14 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
||||
void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection.expolygons), retval);
|
||||
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval);
|
||||
}
|
||||
|
||||
void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
||||
void ExtrusionPath::subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection.expolygons), retval);
|
||||
this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection), retval);
|
||||
}
|
||||
|
||||
void ExtrusionPath::clip_end(double distance)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygonCollection;
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
class ExtrusionEntityCollection;
|
||||
class Extruder;
|
||||
|
||||
|
|
@ -187,12 +188,12 @@ public:
|
|||
size_t size() const { return this->polyline.size(); }
|
||||
bool empty() const { return this->polyline.empty(); }
|
||||
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
|
||||
// Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
|
||||
// Produce a list of extrusion paths into retval by clipping this path by ExPolygons.
|
||||
// Currently not used.
|
||||
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
// Produce a list of extrusion paths into retval by removing parts of this path by ExPolygonCollection.
|
||||
void intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const;
|
||||
// Produce a list of extrusion paths into retval by removing parts of this path by ExPolygons.
|
||||
// Currently not used.
|
||||
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
void subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const;
|
||||
void clip_end(double distance);
|
||||
void simplify(double tolerance);
|
||||
double length() const override;
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||
|
||||
params.config = &layerm->region().config();
|
||||
for (ExPolygon& expoly : surface_fill.expolygons) {
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly});
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
|
|
|
|||
|
|
@ -1636,11 +1636,13 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
double arc_length;
|
||||
};
|
||||
std::vector<Arc> arches;
|
||||
arches.reserve(graph.map_infill_end_point_to_boundary.size());
|
||||
for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary)
|
||||
if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next())
|
||||
arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) });
|
||||
std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length < r.arc_length; });
|
||||
if (!params.dont_sort) {
|
||||
arches.reserve(graph.map_infill_end_point_to_boundary.size());
|
||||
for (ContourIntersectionPoint& cp : graph.map_infill_end_point_to_boundary)
|
||||
if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next())
|
||||
arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) });
|
||||
std::sort(arches.begin(), arches.end(), [](const auto& l, const auto& r) { return l.arc_length < r.arc_length; });
|
||||
}
|
||||
|
||||
//FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization.
|
||||
for (Arc &arc : arches)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ struct FillParams
|
|||
//BBS: only used for new top surface pattern
|
||||
float no_extrusion_overlap{ 0.0 };
|
||||
const PrintRegionConfig* config{ nullptr };
|
||||
|
||||
bool dont_sort{ false }; // do not sort the lines, just simply connect them
|
||||
};
|
||||
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,66 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
class InfillPolylineClipper : public InfillPolylineOutput {
|
||||
public:
|
||||
InfillPolylineClipper(const BoundingBox bbox, const double scale_out) : InfillPolylineOutput(scale_out), m_bbox(bbox) {}
|
||||
|
||||
void add_point(const Vec2d &pt);
|
||||
Points&& result() { return std::move(m_out); }
|
||||
bool clips() const override { return true; }
|
||||
|
||||
private:
|
||||
enum class Side {
|
||||
Left = 1,
|
||||
Right = 2,
|
||||
Top = 4,
|
||||
Bottom = 8
|
||||
};
|
||||
|
||||
int sides(const Point &p) const {
|
||||
return int(p.x() < m_bbox.min.x()) * int(Side::Left) +
|
||||
int(p.x() > m_bbox.max.x()) * int(Side::Right) +
|
||||
int(p.y() < m_bbox.min.y()) * int(Side::Bottom) +
|
||||
int(p.y() > m_bbox.max.y()) * int(Side::Top);
|
||||
};
|
||||
|
||||
// Bounding box to clip the polyline with.
|
||||
BoundingBox m_bbox;
|
||||
|
||||
// Classification of the two last points processed.
|
||||
int m_sides_prev;
|
||||
int m_sides_this;
|
||||
};
|
||||
|
||||
void InfillPolylineClipper::add_point(const Vec2d &fpt)
|
||||
{
|
||||
const Point pt{ this->scaled(fpt) };
|
||||
|
||||
if (m_out.size() < 2) {
|
||||
// Collect the two first points and their status.
|
||||
(m_out.empty() ? m_sides_prev : m_sides_this) = sides(pt);
|
||||
m_out.emplace_back(pt);
|
||||
} else {
|
||||
// Classify the last inserted point, possibly remove it.
|
||||
int sides_next = sides(pt);
|
||||
if (// This point is inside. Take it.
|
||||
m_sides_this == 0 ||
|
||||
// Either this point is outside and previous or next is inside, or
|
||||
// the edge possibly cuts corner of the bounding box.
|
||||
(m_sides_prev & m_sides_this & sides_next) == 0) {
|
||||
// Keep the last point.
|
||||
m_sides_prev = m_sides_this;
|
||||
} else {
|
||||
// All the three points (this, prev, next) are outside at the same side.
|
||||
// Ignore the last point.
|
||||
m_out.pop_back();
|
||||
}
|
||||
// And save the current point.
|
||||
m_out.emplace_back(pt);
|
||||
m_sides_this = sides_next;
|
||||
}
|
||||
}
|
||||
|
||||
void FillPlanePath::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
|
|
@ -13,37 +73,52 @@ void FillPlanePath::_fill_surface_single(
|
|||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
expolygon.rotate(- direction.first);
|
||||
expolygon.rotate(-direction.first);
|
||||
|
||||
coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density);
|
||||
|
||||
// align infill across layers using the object's bounding box
|
||||
// Rotated bounding box of the whole object.
|
||||
BoundingBox bounding_box = this->bounding_box.rotated(- direction.first);
|
||||
|
||||
Point shift = this->_centered() ?
|
||||
//FIXME Vojtech: We are not sure whether the user expects the fill patterns on visible surfaces to be aligned across all the islands of a single layer.
|
||||
// One may align for this->centered() to align the patterns for Archimedean Chords and Octagram Spiral patterns.
|
||||
const bool align = params.density < 0.995;
|
||||
|
||||
BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON);
|
||||
|
||||
// Rotated bounding box of the area to fill in with the pattern.
|
||||
BoundingBox bounding_box = align ?
|
||||
// Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box.
|
||||
this->bounding_box.rotated(-direction.first) :
|
||||
// Solid infill does not need to be aligned across layers, generate the infill pattern
|
||||
// around the clipping expolygon only.
|
||||
snug_bounding_box;
|
||||
|
||||
Point shift = this->centered() ?
|
||||
bounding_box.center() :
|
||||
bounding_box.min;
|
||||
expolygon.translate(-shift.x(), -shift.y());
|
||||
bounding_box.translate(-shift.x(), -shift.y());
|
||||
|
||||
Pointfs pts = _generate(
|
||||
coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)),
|
||||
params.resolution);
|
||||
Polyline polyline;
|
||||
{
|
||||
auto distance_between_lines = scaled<double>(this->spacing) / params.density;
|
||||
auto min_x = coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines));
|
||||
auto min_y = coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines));
|
||||
auto max_x = coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines));
|
||||
auto max_y = coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines));
|
||||
auto resolution = scaled<double>(params.resolution) / distance_between_lines;
|
||||
if (align) {
|
||||
// Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box.
|
||||
snug_bounding_box.translate(-shift.x(), -shift.y());
|
||||
InfillPolylineClipper output(snug_bounding_box, distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
polyline.points = std::move(output.result());
|
||||
} else {
|
||||
// Filling in a snug bounding box, no need to clip.
|
||||
InfillPolylineOutput output(distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
polyline.points = std::move(output.result());
|
||||
}
|
||||
}
|
||||
|
||||
if (pts.size() >= 2) {
|
||||
// Convert points to a polyline, upscale.
|
||||
Polylines polylines(1, Polyline());
|
||||
Polyline &polyline = polylines.front();
|
||||
polyline.points.reserve(pts.size());
|
||||
for (const Vec2d &pt : pts)
|
||||
polyline.points.emplace_back(
|
||||
coord_t(floor(pt.x() * distance_between_lines + 0.5)),
|
||||
coord_t(floor(pt.y() * distance_between_lines + 0.5)));
|
||||
polylines = intersection_pl(polylines, expolygon);
|
||||
if (polyline.size() >= 2) {
|
||||
Polylines polylines = intersection_pl(polyline, expolygon);
|
||||
Polylines chained;
|
||||
if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1)
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
|
|
@ -59,7 +134,8 @@ void FillPlanePath::_fill_surface_single(
|
|||
}
|
||||
|
||||
// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta
|
||||
Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution)
|
||||
template<typename Output>
|
||||
static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, Output &output)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
|
|
@ -70,15 +146,22 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m
|
|||
coordf_t r = 1;
|
||||
Pointfs out;
|
||||
//FIXME Vojtech: If used as a solid infill, there is a gap left at the center.
|
||||
out.emplace_back(0, 0);
|
||||
out.emplace_back(1, 0);
|
||||
output.add_point({ 0, 0 });
|
||||
output.add_point({ 1, 0 });
|
||||
while (r < rmax) {
|
||||
// Discretization angle to achieve a discretization error lower than resolution.
|
||||
theta += 2. * acos(1. - resolution / r);
|
||||
r = a + b * theta;
|
||||
out.emplace_back(r * cos(theta), r * sin(theta));
|
||||
output.add_point({ r * cos(theta), r * sin(theta) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void FillArchimedeanChords::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, output);
|
||||
}
|
||||
|
||||
// Adapted from
|
||||
|
|
@ -126,7 +209,8 @@ static inline Point hilbert_n_to_xy(const size_t n)
|
|||
return Point(x, y);
|
||||
}
|
||||
|
||||
Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
template<typename Output>
|
||||
static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
// Minimum power of two square to fit the domain.
|
||||
size_t sz = 2;
|
||||
|
|
@ -140,46 +224,59 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x,
|
|||
}
|
||||
|
||||
size_t sz2 = sz * sz;
|
||||
Pointfs line;
|
||||
line.reserve(sz2);
|
||||
output.reserve(sz2);
|
||||
for (size_t i = 0; i < sz2; ++ i) {
|
||||
Point p = hilbert_n_to_xy(i);
|
||||
line.emplace_back(p.x() + min_x, p.y() + min_y);
|
||||
output.add_point({ p.x() + min_x, p.y() + min_y });
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
// Now unwind the spiral.
|
||||
coordf_t r = 0;
|
||||
coordf_t r_inc = sqrt(2.);
|
||||
Pointfs out;
|
||||
out.emplace_back(0., 0.);
|
||||
output.add_point({ 0., 0. });
|
||||
while (r < rmax) {
|
||||
r += r_inc;
|
||||
coordf_t rx = r / sqrt(2.);
|
||||
coordf_t r2 = r + rx;
|
||||
out.emplace_back( r, 0.);
|
||||
out.emplace_back( r2, rx);
|
||||
out.emplace_back( rx, rx);
|
||||
out.emplace_back( rx, r2);
|
||||
out.emplace_back( 0., r);
|
||||
out.emplace_back(-rx, r2);
|
||||
out.emplace_back(-rx, rx);
|
||||
out.emplace_back(-r2, rx);
|
||||
out.emplace_back(- r, 0.);
|
||||
out.emplace_back(-r2, -rx);
|
||||
out.emplace_back(-rx, -rx);
|
||||
out.emplace_back(-rx, -r2);
|
||||
out.emplace_back( 0., -r);
|
||||
out.emplace_back( rx, -r2);
|
||||
out.emplace_back( rx, -rx);
|
||||
out.emplace_back( r2+r_inc, -rx);
|
||||
output.add_point({ r, 0. });
|
||||
output.add_point({ r2, rx });
|
||||
output.add_point({ rx, rx });
|
||||
output.add_point({ rx, r2 });
|
||||
output.add_point({ 0., r });
|
||||
output.add_point({-rx, r2 });
|
||||
output.add_point({-rx, rx });
|
||||
output.add_point({-r2, rx });
|
||||
output.add_point({- r, 0. });
|
||||
output.add_point({-r2, -rx });
|
||||
output.add_point({-rx, -rx });
|
||||
output.add_point({-rx, -r2 });
|
||||
output.add_point({ 0., -r });
|
||||
output.add_point({ rx, -r2 });
|
||||
output.add_point({ rx, -rx });
|
||||
output.add_point({ r2+r_inc, -rx });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
generate_octagram_spiral(min_x, min_y, max_x, max_y, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_octagram_spiral(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -13,6 +13,26 @@ namespace Slic3r {
|
|||
// http://user42.tuxfamily.org/math-planepath/
|
||||
// http://user42.tuxfamily.org/math-planepath/gallery.html
|
||||
|
||||
class InfillPolylineOutput {
|
||||
public:
|
||||
InfillPolylineOutput(const double scale_out) : m_scale_out(scale_out) {}
|
||||
|
||||
void reserve(size_t n) { m_out.reserve(n); }
|
||||
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
|
||||
Points&& result() { return std::move(m_out); }
|
||||
virtual bool clips() const { return false; }
|
||||
|
||||
protected:
|
||||
const Point scaled(const Vec2d& fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; }
|
||||
|
||||
// Output polyline.
|
||||
Points m_out;
|
||||
|
||||
private:
|
||||
// Scaling coefficient of the generated points before tested against m_bbox and clipped by bbox.
|
||||
double m_scale_out;
|
||||
};
|
||||
|
||||
class FillPlanePath : public Fill
|
||||
{
|
||||
public:
|
||||
|
|
@ -27,8 +47,11 @@ protected:
|
|||
Polylines &polylines_out) override;
|
||||
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
virtual bool _centered() const = 0;
|
||||
virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0;
|
||||
virtual bool centered() const = 0;
|
||||
|
||||
friend class InfillPolylineClipper;
|
||||
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0;
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
|
|
@ -38,8 +61,8 @@ public:
|
|||
~FillArchimedeanChords() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
bool centered() const override { return true; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillHilbertCurve : public FillPlanePath
|
||||
|
|
@ -49,8 +72,8 @@ public:
|
|||
~FillHilbertCurve() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return false; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
bool centered() const override { return false; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
|
|
@ -60,8 +83,8 @@ public:
|
|||
~FillOctagramSpiral() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
bool centered() const override { return true; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -415,7 +415,7 @@ public:
|
|||
// bool sticks_removed =
|
||||
remove_sticks(polygons_src);
|
||||
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
|
||||
polygons_outer = aoffset1 == 0 ? polygons_src : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
|
||||
polygons_outer = aoffset1 == 0 ? to_polygons(polygons_src) : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
|
||||
if (aoffset2 < 0)
|
||||
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
|
||||
// Filter out contours with zero area or small area, contours with 2 points only.
|
||||
|
|
@ -3165,7 +3165,7 @@ void FillMonotonicLineWGapFill::fill_surface_extrusion(const Surface* surface, c
|
|||
for (ExPolygon& ex : gaps_ex_sorted) {
|
||||
//BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
|
||||
ex.douglas_peucker(SCALED_RESOLUTION * 0.1);
|
||||
ex.medial_axis(max, min, &polylines);
|
||||
ex.medial_axis(min, max, &polylines);
|
||||
}
|
||||
|
||||
if (!polylines.empty() && !is_bridge(params.extrusion_role)) {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ const std::string BBS_SEAM_PAINTING_VERSION = "BambuStudio:SeamPaintingV
|
|||
const std::string BBS_MM_PAINTING_VERSION = "BambuStudio:MmPaintingVersion";
|
||||
const std::string BBL_MODEL_ID_TAG = "model_id";
|
||||
const std::string BBL_MODEL_NAME_TAG = "Title";
|
||||
const std::string BBL_ORIGIN_TAG = "Origin";
|
||||
const std::string BBL_DESIGNER_TAG = "Designer";
|
||||
const std::string BBL_DESIGNER_USER_ID_TAG = "DesignerUserId";
|
||||
const std::string BBL_DESIGNER_COVER_FILE_TAG = "DesignerCover";
|
||||
|
|
@ -121,6 +122,12 @@ const std::string BBL_MODIFICATION_TAG = "ModificationDate";
|
|||
const std::string BBL_CREATION_DATE_TAG = "CreationDate";
|
||||
const std::string BBL_APPLICATION_TAG = "Application";
|
||||
|
||||
const std::string BBL_PROFILE_TITLE_TAG = "ProfileTitle";
|
||||
const std::string BBL_PROFILE_COVER_TAG = "ProfileCover";
|
||||
const std::string BBL_PROFILE_DESCRIPTION_TAG = "ProfileDescription";
|
||||
const std::string BBL_PROFILE_USER_ID_TAG = "ProfileUserId";
|
||||
const std::string BBL_PROFILE_USER_NAME_TAG = "ProfileUserName";
|
||||
|
||||
const std::string MODEL_FOLDER = "3D/";
|
||||
const std::string MODEL_EXTENSION = ".model";
|
||||
const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
|
||||
|
|
@ -156,7 +163,7 @@ const std::string PROJECT_EMBEDDED_PRINT_PRESETS_FILE = "Metadata/print_setting_
|
|||
const std::string PROJECT_EMBEDDED_SLICE_PRESETS_FILE = "Metadata/process_settings_";
|
||||
const std::string PROJECT_EMBEDDED_FILAMENT_PRESETS_FILE = "Metadata/filament_settings_";
|
||||
const std::string PROJECT_EMBEDDED_PRINTER_PRESETS_FILE = "Metadata/machine_settings_";
|
||||
|
||||
const std::string CUT_INFORMATION_FILE = "Metadata/cut_information.xml";
|
||||
|
||||
const unsigned int AUXILIARY_STR_LEN = 12;
|
||||
const unsigned int METADATA_STR_LEN = 9;
|
||||
|
|
@ -199,6 +206,24 @@ 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
|
||||
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";
|
||||
static constexpr const char* FONT_INDEX_ATTR = "font_index";
|
||||
static constexpr const char* FONT_SIZE_ATTR = "font_size";
|
||||
static constexpr const char* THICKNESS_ATTR = "thickness";
|
||||
static constexpr const char* EMBEDED_DEPTH_ATTR = "embeded_depth";
|
||||
static constexpr const char* ROTATE_ANGLE_ATTR = "rotate_angle";
|
||||
static constexpr const char* TEXT_GAP_ATTR = "text_gap";
|
||||
static constexpr const char* BOLD_ATTR = "bold";
|
||||
static constexpr const char* ITALIC_ATTR = "italic";
|
||||
static constexpr const char* SURFACE_TEXT_ATTR = "surface_text";
|
||||
static constexpr const char* KEEP_HORIZONTAL_ATTR = "keep_horizontal";
|
||||
static constexpr const char* HIT_MESH_ATTR = "hit_mesh";
|
||||
static constexpr const char* HIT_POSITION_ATTR = "hit_position";
|
||||
static constexpr const char* HIT_NORMAL_ATTR = "hit_normal";
|
||||
|
||||
// BBS: encrypt
|
||||
static constexpr const char* RELATIONSHIP_TAG = "Relationship";
|
||||
static constexpr const char* PID_ATTR = "pid";
|
||||
|
|
@ -243,11 +268,13 @@ static constexpr const char* BED_TYPE_ATTR = "bed_type";
|
|||
static constexpr const char* PRINT_SEQUENCE_ATTR = "print_sequence";
|
||||
static constexpr const char* GCODE_FILE_ATTR = "gcode_file";
|
||||
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file";
|
||||
static constexpr const char* TOP_FILE_ATTR = "top_file";
|
||||
static constexpr const char* PICK_FILE_ATTR = "pick_file";
|
||||
static constexpr const char* PATTERN_FILE_ATTR = "pattern_file";
|
||||
static constexpr const char* PATTERN_BBOX_FILE_ATTR = "pattern_bbox_file";
|
||||
static constexpr const char* OBJECT_ID_ATTR = "object_id";
|
||||
static constexpr const char* INSTANCEID_ATTR = "instance_id";
|
||||
static constexpr const char* ARRANGE_ORDER_ATTR = "arrange_order";
|
||||
static constexpr const char* IDENTIFYID_ATTR = "identify_id";
|
||||
static constexpr const char* PLATERID_ATTR = "plater_id";
|
||||
static constexpr const char* PLATER_NAME_ATTR = "plater_name";
|
||||
static constexpr const char* PLATE_IDX_ATTR = "index";
|
||||
|
|
@ -363,6 +390,33 @@ bool bbs_get_attribute_value_bool(const char** attributes, unsigned int attribut
|
|||
return (text != nullptr) ? (bool)::atoi(text) : true;
|
||||
}
|
||||
|
||||
void add_vec3(std::stringstream &stream, const Slic3r::Vec3f &tr)
|
||||
{
|
||||
for (unsigned r = 0; r < 3; ++r) {
|
||||
stream << tr(r);
|
||||
if (r != 2)
|
||||
stream << " ";
|
||||
}
|
||||
}
|
||||
|
||||
Slic3r::Vec3f get_vec3_from_string(const std::string &pos_str)
|
||||
{
|
||||
Slic3r::Vec3f pos(0, 0, 0);
|
||||
if (pos_str.empty())
|
||||
return pos;
|
||||
|
||||
std::vector<std::string> values;
|
||||
boost::split(values, pos_str, boost::is_any_of(" "), boost::token_compress_on);
|
||||
|
||||
if (values.size() != 3)
|
||||
return pos;
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
pos(i) = ::atof(values[i].c_str());
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
Slic3r::Transform3d bbs_get_transform_from_3mf_specs_string(const std::string& mat_str)
|
||||
{
|
||||
// check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md
|
||||
|
|
@ -608,7 +662,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
{
|
||||
int object_id;
|
||||
int instance_id;
|
||||
int arrange_order;
|
||||
int identify_id;
|
||||
};
|
||||
|
||||
struct Instance
|
||||
|
|
@ -648,6 +702,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
MetadataList metadata;
|
||||
RepairedMeshErrors mesh_stats;
|
||||
ModelVolumeType part_type;
|
||||
TextInfo text_info;
|
||||
|
||||
VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
: first_triangle_id(first_triangle_id)
|
||||
|
|
@ -672,6 +727,19 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
VolumeMetadataList volumes;
|
||||
};
|
||||
|
||||
struct CutObjectInfo
|
||||
{
|
||||
struct Connector
|
||||
{
|
||||
int volume_id;
|
||||
int type;
|
||||
float r_tolerance;
|
||||
float h_tolerance;
|
||||
};
|
||||
CutObjectBase id;
|
||||
std::vector<Connector> connectors;
|
||||
};
|
||||
|
||||
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
|
||||
//typedef std::pair<std::string, int> Id; // BBS: encrypt
|
||||
typedef std::map<Id, CurrentObject> IdToCurrentObjectMap;
|
||||
|
|
@ -680,6 +748,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
//typedef std::map<Id, ComponentsList> IdToAliasesMap;
|
||||
typedef std::vector<Instance> InstancesList;
|
||||
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
|
||||
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
|
||||
//typedef std::map<Id, Geometry> IdToGeometryMap;
|
||||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||
/*typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||
|
|
@ -848,6 +917,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
std::string m_designer_cover;
|
||||
ModelInfo model_info;
|
||||
BBLProject project_info;
|
||||
std::string m_profile_title;
|
||||
std::string m_profile_cover;
|
||||
std::string m_Profile_description;
|
||||
std::string m_profile_user_id;
|
||||
std::string m_profile_user_name;
|
||||
|
||||
XML_Parser m_xml_parser;
|
||||
// Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state
|
||||
|
|
@ -866,6 +940,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
//IdToGeometryMap m_orig_geometries; // backup & restore
|
||||
CurrentConfig m_curr_config;
|
||||
IdToMetadataMap m_objects_metadata;
|
||||
IdToCutObjectInfoMap m_cut_object_infos;
|
||||
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
||||
/*IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||
IdToSlaSupportPointsMap m_sla_support_points;
|
||||
|
|
@ -924,6 +999,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
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);
|
||||
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions);
|
||||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
|
@ -1031,6 +1107,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool _handle_start_assemble_item(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_assemble_item();
|
||||
|
||||
bool _handle_start_text_info_item(const char **attributes, unsigned int num_attributes);
|
||||
bool _handle_end_text_info_item();
|
||||
|
||||
// BBS: callbacks to parse the .rels file
|
||||
static void XMLCALL _handle_start_relationships_element(void* userData, const char* name, const char** attributes);
|
||||
static void XMLCALL _handle_end_relationships_element(void* userData, const char* name);
|
||||
|
|
@ -1128,7 +1207,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
m_plater_data.clear();
|
||||
m_curr_instance.object_id = -1;
|
||||
m_curr_instance.instance_id = -1;
|
||||
m_curr_instance.arrange_order = 0;
|
||||
m_curr_instance.identify_id = 0;
|
||||
clear_errors();
|
||||
|
||||
// restore
|
||||
|
|
@ -1380,6 +1459,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
m_model->design_info->Designer = m_designer;
|
||||
}
|
||||
|
||||
m_model->profile_info = std::make_shared<ModelProfileInfo>();
|
||||
m_model->profile_info->ProfileTile = m_profile_title;
|
||||
m_model->profile_info->ProfileCover = m_profile_cover;
|
||||
m_model->profile_info->ProfileDescription = m_Profile_description;
|
||||
m_model->profile_info->ProfileUserId = m_profile_user_id;
|
||||
m_model->profile_info->ProfileUserName = m_profile_user_name;
|
||||
|
||||
|
||||
m_model->model_info = std::make_shared<ModelInfo>();
|
||||
m_model->model_info->load(model_info);
|
||||
|
||||
|
|
@ -1445,6 +1532,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
// extract slic3r print config file
|
||||
_extract_project_config_from_archive(archive, stat, config, config_substitutions, model);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, CUT_INFORMATION_FILE)) {
|
||||
// extract object cut info
|
||||
_extract_cut_information_from_archive(archive, stat, config_substitutions);
|
||||
}
|
||||
//BBS: project embedded presets
|
||||
else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_PRINT_PRESETS_FILE)) {
|
||||
// extract slic3r layer config ranges file
|
||||
|
|
@ -1668,6 +1759,19 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
|
||||
if (!_generate_volumes_new(*model_object, object_id_list, *volumes_ptr, config_substitutions))
|
||||
return false;
|
||||
|
||||
// Apply cut information for object if any was loaded
|
||||
// m_cut_object_ids are indexed by a 1 based model object index.
|
||||
IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1);
|
||||
if (cut_object_info != m_cut_object_infos.end()) {
|
||||
model_object->cut_id = cut_object_info->second.id;
|
||||
|
||||
for (auto connector : cut_object_info->second.connectors) {
|
||||
assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size()));
|
||||
model_object->volumes[connector.volume_id]->cut_info =
|
||||
ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If instances contain a single volume, the volume offset should be 0,0,0
|
||||
|
|
@ -1776,12 +1880,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate_data_list[it->first-1]->warnings = it->second->warnings;
|
||||
plate_data_list[it->first-1]->thumbnail_file = (m_load_restore || it->second->thumbnail_file.empty()) ? it->second->thumbnail_file : m_backup_path + "/" + it->second->thumbnail_file;
|
||||
plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
|
||||
//plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
|
||||
plate_data_list[it->first-1]->top_file = (m_load_restore || it->second->top_file.empty()) ? it->second->top_file : m_backup_path + "/" + it->second->top_file;
|
||||
plate_data_list[it->first-1]->pick_file = (m_load_restore || it->second->pick_file.empty()) ? it->second->pick_file : m_backup_path + "/" + it->second->pick_file;
|
||||
plate_data_list[it->first-1]->pattern_bbox_file = (m_load_restore || it->second->pattern_bbox_file.empty()) ? it->second->pattern_bbox_file : m_backup_path + "/" + it->second->pattern_bbox_file;
|
||||
plate_data_list[it->first-1]->config = it->second->config;
|
||||
|
||||
current_plate_data = plate_data_list[it->first - 1];
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%")%it->first %plate_data_list[it->first-1]->thumbnail_file;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%")%plate_data_list[it->first-1]->top_file %plate_data_list[it->first-1]->pick_file;
|
||||
it++;
|
||||
|
||||
//update the arrange order
|
||||
|
|
@ -1817,7 +1924,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
continue;
|
||||
}
|
||||
ModelInstance* inst = obj->instances[inst_index];
|
||||
inst->arrange_order = map_it->second.second;
|
||||
inst->loaded_id = map_it->second.second;
|
||||
map_it++;
|
||||
}
|
||||
}
|
||||
|
|
@ -2022,6 +2129,61 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
return true;
|
||||
}
|
||||
|
||||
void _BBS_3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
std::string buffer((size_t) stat.m_uncomp_size, 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void *) buffer.data(), (size_t) stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
add_error("Error while reading cut information data to buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
std::istringstream iss(buffer); // wrap returned xml to istringstream
|
||||
pt::ptree objects_tree;
|
||||
pt::read_xml(iss, objects_tree);
|
||||
|
||||
for (const auto &object : objects_tree.get_child("objects")) {
|
||||
pt::ptree object_tree = object.second;
|
||||
int obj_idx = object_tree.get<int>("<xmlattr>.id", -1);
|
||||
if (obj_idx <= 0) {
|
||||
add_error("Found invalid object id");
|
||||
continue;
|
||||
}
|
||||
|
||||
IdToCutObjectInfoMap::iterator object_item = m_cut_object_infos.find(obj_idx);
|
||||
if (object_item != m_cut_object_infos.end()) {
|
||||
add_error("Found duplicated cut_object_id");
|
||||
continue;
|
||||
}
|
||||
|
||||
CutObjectBase cut_id;
|
||||
std::vector<CutObjectInfo::Connector> connectors;
|
||||
|
||||
for (const auto &obj_cut_info : object_tree) {
|
||||
if (obj_cut_info.first == "cut_id") {
|
||||
pt::ptree cut_id_tree = obj_cut_info.second;
|
||||
cut_id = CutObjectBase(ObjectID(cut_id_tree.get<size_t>("<xmlattr>.id")), cut_id_tree.get<size_t>("<xmlattr>.check_sum"),
|
||||
cut_id_tree.get<size_t>("<xmlattr>.connectors_cnt"));
|
||||
}
|
||||
if (obj_cut_info.first == "connectors") {
|
||||
pt::ptree cut_connectors_tree = obj_cut_info.second;
|
||||
for (const auto &cut_connector : cut_connectors_tree) {
|
||||
if (cut_connector.first != "connector") continue;
|
||||
pt::ptree connector_tree = cut_connector.second;
|
||||
CutObjectInfo::Connector connector = {connector_tree.get<int>("<xmlattr>.volume_id"), connector_tree.get<int>("<xmlattr>.type"),
|
||||
connector_tree.get<float>("<xmlattr>.r_tolerance"), connector_tree.get<float>("<xmlattr>.h_tolerance")};
|
||||
connectors.emplace_back(connector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CutObjectInfo cut_info{cut_id, connectors};
|
||||
m_cut_object_infos.insert({obj_idx, cut_info});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _BBS_3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, const std::string& archive_filename)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
|
|
@ -2709,6 +2871,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
res = _handle_start_assemble(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
||||
res = _handle_start_assemble_item(attributes, num_attributes);
|
||||
else if (::strcmp(TEXT_INFO_TAG, name) == 0)
|
||||
res = _handle_start_text_info_item(attributes, num_attributes);
|
||||
|
||||
if (!res)
|
||||
_stop_xml_parser();
|
||||
|
|
@ -3075,7 +3239,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
|
||||
bool _BBS_3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
||||
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||
int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
||||
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||
|
||||
/*Id id = std::make_pair(m_sub_model_path, object_id);
|
||||
|
|
@ -3089,7 +3254,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
}*/
|
||||
|
||||
if (m_curr_object) {
|
||||
Id id = std::make_pair(m_sub_model_path, object_id);
|
||||
Id id = std::make_pair(m_sub_model_path.empty() ? path : m_sub_model_path, object_id);
|
||||
m_curr_object->components.emplace_back(id, transform);
|
||||
}
|
||||
|
||||
|
|
@ -3158,7 +3323,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool _BBS_3MF_Importer::_handle_end_metadata()
|
||||
{
|
||||
if ((m_curr_metadata_name == BBS_3MF_VERSION)||(m_curr_metadata_name == BBS_3MF_VERSION1)) {
|
||||
m_is_bbl_3mf = true;
|
||||
//m_is_bbl_3mf = true;
|
||||
m_version = (unsigned int)atoi(m_curr_characters.c_str());
|
||||
/*if (m_check_version && (m_version > VERSION_BBS_3MF_COMPATIBLE)) {
|
||||
// std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
|
||||
|
|
@ -3195,6 +3360,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
} else if (m_curr_metadata_name == BBL_MODEL_NAME_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found model name = " << m_curr_characters;
|
||||
model_info.model_name = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_ORIGIN_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found model name = " << m_curr_characters;
|
||||
model_info.origin = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_DESIGNER_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer = " << m_curr_characters;
|
||||
m_designer = xml_unescape(m_curr_characters);
|
||||
|
|
@ -3216,6 +3384,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
} else if (m_curr_metadata_name == BBL_REGION_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found region = " << m_curr_characters;
|
||||
m_contry_code = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_PROFILE_TITLE_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_title = " << m_curr_characters;
|
||||
m_profile_title = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_PROFILE_COVER_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_cover = " << m_curr_characters;
|
||||
m_profile_cover = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_PROFILE_DESCRIPTION_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_description = " << m_curr_characters;
|
||||
m_Profile_description = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_PROFILE_USER_ID_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_user_id = " << m_curr_characters;
|
||||
m_profile_user_id = xml_unescape(m_curr_characters);
|
||||
}else if (m_curr_metadata_name == BBL_PROFILE_USER_NAME_TAG) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_user_name = " << m_curr_characters;
|
||||
m_profile_user_name = xml_unescape(m_curr_characters);
|
||||
} else if (m_curr_metadata_name == BBL_CREATION_DATE_TAG) {
|
||||
;
|
||||
} else if (m_curr_metadata_name == BBL_MODIFICATION_TAG) {
|
||||
|
|
@ -3497,10 +3680,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
{
|
||||
m_curr_plater->thumbnail_file = value;
|
||||
}
|
||||
else if (key == PATTERN_FILE_ATTR)
|
||||
else if (key == TOP_FILE_ATTR)
|
||||
{
|
||||
m_curr_plater->pattern_file = value;
|
||||
m_curr_plater->top_file = value;
|
||||
}
|
||||
else if (key == PICK_FILE_ATTR)
|
||||
{
|
||||
m_curr_plater->pick_file = value;
|
||||
}
|
||||
//else if (key == PATTERN_FILE_ATTR)
|
||||
//{
|
||||
// m_curr_plater->pattern_file = value;
|
||||
//}
|
||||
else if (key == PATTERN_BBOX_FILE_ATTR)
|
||||
{
|
||||
m_curr_plater->pattern_bbox_file = value;
|
||||
|
|
@ -3509,9 +3700,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
{
|
||||
m_curr_instance.instance_id = atoi(value.c_str());
|
||||
}
|
||||
else if (key == ARRANGE_ORDER_ATTR)
|
||||
else if (key == IDENTIFYID_ATTR)
|
||||
{
|
||||
m_curr_instance.arrange_order = atoi(value.c_str());
|
||||
m_curr_instance.identify_id = atoi(value.c_str());
|
||||
}
|
||||
else if (key == OBJECT_ID_ATTR)
|
||||
{
|
||||
|
|
@ -3666,13 +3857,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
//add_error("invalid object id/instance id");
|
||||
//skip this instance
|
||||
m_curr_instance.object_id = m_curr_instance.instance_id = -1;
|
||||
m_curr_instance.arrange_order = 0;
|
||||
m_curr_instance.identify_id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_curr_plater->obj_inst_map.emplace(m_curr_instance.object_id, std::make_pair(m_curr_instance.instance_id, m_curr_instance.arrange_order));
|
||||
m_curr_plater->obj_inst_map.emplace(m_curr_instance.object_id, std::make_pair(m_curr_instance.instance_id, m_curr_instance.identify_id));
|
||||
m_curr_instance.object_id = m_curr_instance.instance_id = -1;
|
||||
m_curr_instance.arrange_order = 0;
|
||||
m_curr_instance.identify_id = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -3721,6 +3912,56 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_text_info_item(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 find object for text_info, id " + std::to_string(m_curr_config.object_id));
|
||||
return false;
|
||||
}
|
||||
if ((m_curr_config.volume_id == -1) || ((object->second.volumes.size() - 1) < m_curr_config.volume_id)) {
|
||||
add_error("can not find part for text_info");
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectMetadata::VolumeMetadata &volume = object->second.volumes[m_curr_config.volume_id];
|
||||
|
||||
TextInfo text_info;
|
||||
text_info.m_text = 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);
|
||||
|
||||
text_info.m_curr_font_idx = bbs_get_attribute_value_int(attributes, num_attributes, FONT_INDEX_ATTR);
|
||||
|
||||
text_info.m_font_size = bbs_get_attribute_value_float(attributes, num_attributes, FONT_SIZE_ATTR);
|
||||
text_info.m_thickness = bbs_get_attribute_value_float(attributes, num_attributes, THICKNESS_ATTR);
|
||||
text_info.m_embeded_depth = bbs_get_attribute_value_float(attributes, num_attributes, EMBEDED_DEPTH_ATTR);
|
||||
text_info.m_rotate_angle = bbs_get_attribute_value_float(attributes, num_attributes, ROTATE_ANGLE_ATTR);
|
||||
text_info.m_text_gap = bbs_get_attribute_value_float(attributes, num_attributes, TEXT_GAP_ATTR);
|
||||
|
||||
text_info.m_bold = bbs_get_attribute_value_int(attributes, num_attributes, BOLD_ATTR);
|
||||
text_info.m_italic = bbs_get_attribute_value_int(attributes, num_attributes, ITALIC_ATTR);
|
||||
text_info.m_is_surface_text = bbs_get_attribute_value_int(attributes, num_attributes, SURFACE_TEXT_ATTR);
|
||||
text_info.m_keep_horizontal = bbs_get_attribute_value_int(attributes, num_attributes, KEEP_HORIZONTAL_ATTR);
|
||||
|
||||
text_info.m_rr.mesh_id = bbs_get_attribute_value_int(attributes, num_attributes, HIT_MESH_ATTR);
|
||||
|
||||
std::string hit_pos = bbs_get_attribute_value_string(attributes, num_attributes, HIT_POSITION_ATTR);
|
||||
if (!hit_pos.empty())
|
||||
text_info.m_rr.hit = get_vec3_from_string(hit_pos);
|
||||
|
||||
std::string hit_normal = bbs_get_attribute_value_string(attributes, num_attributes, HIT_NORMAL_ATTR);
|
||||
if (!hit_normal.empty())
|
||||
text_info.m_rr.normal = get_vec3_from_string(hit_normal);
|
||||
|
||||
volume.text_info = text_info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_text_info_item()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void XMLCALL _BBS_3MF_Importer::_handle_start_relationships_element(void* userData, const char* name, const char** attributes)
|
||||
{
|
||||
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
||||
|
|
@ -3906,16 +4147,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
|
||||
TriangleMesh triangle_mesh(std::move(its), volume_data->mesh_stats);
|
||||
|
||||
if (!m_is_bbl_3mf) {
|
||||
// if the 3mf was not produced by OrcaSlicer and there is only one instance,
|
||||
// bake the transformation into the geometry to allow the reload from disk command
|
||||
// to work properly
|
||||
if (object.instances.size() == 1) {
|
||||
triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false);
|
||||
object.instances.front()->set_transformation(Slic3r::Geometry::Transformation());
|
||||
//FIXME do the mesh fixing?
|
||||
}
|
||||
}
|
||||
// BBS: no need to multiply the instance matrix into the volume
|
||||
//if (!m_is_bbl_3mf) {
|
||||
// // if the 3mf was not produced by BambuStudio and there is only one instance,
|
||||
// // bake the transformation into the geometry to allow the reload from disk command
|
||||
// // to work properly
|
||||
// if (object.instances.size() == 1) {
|
||||
// triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false);
|
||||
// object.instances.front()->set_transformation(Slic3r::Geometry::Transformation());
|
||||
// //FIXME do the mesh fixing?
|
||||
// }
|
||||
//}
|
||||
if (triangle_mesh.volume() < 0)
|
||||
triangle_mesh.flip_triangles();
|
||||
|
||||
|
|
@ -3972,6 +4214,9 @@ 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);
|
||||
|
||||
// apply the remaining volume's metadata
|
||||
for (const Metadata& metadata : volume_data->metadata) {
|
||||
if (metadata.key == NAME_KEY)
|
||||
|
|
@ -4716,6 +4961,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool m_save_gcode { false }; // whether to save gcode for normal save
|
||||
bool m_skip_model { false }; // skip model when exporting .gcode.3mf
|
||||
bool m_skip_auxiliary { false }; // skip normal axuiliary files
|
||||
bool m_use_loaded_id { false }; // whether to use loaded id for identify_id
|
||||
|
||||
public:
|
||||
//BBS: add plate data related logic
|
||||
|
|
@ -4734,6 +4980,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
std::vector<Preset*>& project_presets,
|
||||
const DynamicPrintConfig* config,
|
||||
const std::vector<ThumbnailData*>& thumbnail_data,
|
||||
const std::vector<ThumbnailData*>& top_thumbnail_data,
|
||||
const std::vector<ThumbnailData*>& pick_thumbnail_data,
|
||||
Export3mfProgressFn proFn,
|
||||
const std::vector<ThumbnailData*>& calibration_data,
|
||||
const std::vector<PlateBBoxData*>& id_bboxes,
|
||||
|
|
@ -4744,7 +4992,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
|
||||
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
|
||||
|
||||
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index);
|
||||
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, const char* local_path, int index);
|
||||
bool _add_calibration_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index);
|
||||
bool _add_bbox_file_to_archive(mz_zip_archive& archive, const PlateBBoxData& id_bboxes, int index);
|
||||
bool _add_relationships_file_to_archive(mz_zip_archive & archive,
|
||||
|
|
@ -4767,7 +5015,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool _add_project_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, Model& model);
|
||||
//BBS: add project embedded preset files
|
||||
bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector<Preset*> project_presets);
|
||||
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true);
|
||||
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false);
|
||||
bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model);
|
||||
bool _add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list);
|
||||
bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr);
|
||||
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
|
||||
|
|
@ -4801,12 +5050,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
m_skip_model = store_params.strategy & SaveStrategy::SkipModel;
|
||||
m_skip_auxiliary = store_params.strategy & SaveStrategy::SkipAuxiliary;
|
||||
|
||||
m_use_loaded_id = store_params.strategy & SaveStrategy::UseLoadedId;
|
||||
|
||||
boost::system::error_code ec;
|
||||
std::string filename = std::string(store_params.path);
|
||||
boost::filesystem::remove(filename + ".tmp", ec);
|
||||
|
||||
bool result = _save_model_to_file(filename + ".tmp", *store_params.model, store_params.plate_data_list, store_params.project_presets, store_params.config,
|
||||
store_params.thumbnail_data, store_params.proFn, store_params.calibration_thumbnail_data, store_params.id_bboxes, store_params.project, store_params.export_plate_idx);
|
||||
store_params.thumbnail_data, store_params.top_thumbnail_data, store_params.pick_thumbnail_data, store_params.proFn,
|
||||
store_params.calibration_thumbnail_data, store_params.id_bboxes, store_params.project, store_params.export_plate_idx);
|
||||
if (result) {
|
||||
boost::filesystem::rename(filename + ".tmp", filename, ec);
|
||||
if (ec) {
|
||||
|
|
@ -4874,6 +5126,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
std::vector<Preset*>& project_presets,
|
||||
const DynamicPrintConfig* config,
|
||||
const std::vector<ThumbnailData*>& thumbnail_data,
|
||||
const std::vector<ThumbnailData*>& top_thumbnail_data,
|
||||
const std::vector<ThumbnailData*>& pick_thumbnail_data,
|
||||
Export3mfProgressFn proFn,
|
||||
const std::vector<ThumbnailData*>& calibration_data,
|
||||
const std::vector<PlateBBoxData*>& id_bboxes,
|
||||
|
|
@ -4888,7 +5142,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool cb_cancel = false;
|
||||
|
||||
//BBS progress point
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before open zip writer, m_skip_static %1%, m_save_gcode %2%\n")%m_skip_static %m_save_gcode;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ <<
|
||||
boost::format(",before open zip writer, m_skip_static %1%, m_save_gcode %2%, m_use_loaded_id %3%")%m_skip_static %m_save_gcode %m_use_loaded_id;
|
||||
if (proFn) {
|
||||
proFn(EXPORT_STAGE_OPEN_3MF, 0, 1, cb_cancel);
|
||||
if (cb_cancel)
|
||||
|
|
@ -4927,43 +5182,115 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
}
|
||||
|
||||
//BBS progress point
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(",before add thumbnails, count %1%\n") % thumbnail_data.size();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(",before add thumbnails, count %1%") % thumbnail_data.size();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",top&&pick thumbnails, count %1%")%top_thumbnail_data.size();
|
||||
|
||||
//BBS: add thumbnail for each plate
|
||||
if (!m_skip_static && thumbnail_data.size() > 0) {
|
||||
// Adds the file Metadata/thumbnail.png.
|
||||
if (!m_skip_static) {
|
||||
std::vector<bool> thumbnail_status(plate_data_list.size(), false);
|
||||
std::vector<bool> top_thumbnail_status(plate_data_list.size(), false);
|
||||
std::vector<bool> pick_thumbnail_status(plate_data_list.size(), false);
|
||||
|
||||
if ((thumbnail_data.size() > 0)&&(thumbnail_data.size() > plate_data_list.size())) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", thumbnail_data size %1% > plate count %2%")
|
||||
% thumbnail_data.size() %plate_data_list.size();
|
||||
return false;
|
||||
}
|
||||
if ((top_thumbnail_data.size() > 0)&&(top_thumbnail_data.size() > plate_data_list.size())) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_data size %1% > plate count %2%")
|
||||
% top_thumbnail_data.size() %plate_data_list.size();
|
||||
return false;
|
||||
}
|
||||
if ((pick_thumbnail_data.size() > 0)&&(pick_thumbnail_data.size() > plate_data_list.size())) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", pick_thumbnail_data size %1% > plate count %2%")
|
||||
% pick_thumbnail_data.size() %plate_data_list.size();
|
||||
return false;
|
||||
}
|
||||
if (top_thumbnail_data.size() != pick_thumbnail_data.size()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_data size %1% != pick_thumbnail_data size %2%")
|
||||
% top_thumbnail_data.size() %pick_thumbnail_data.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (proFn) {
|
||||
proFn(EXPORT_STAGE_ADD_THUMBNAILS, 0, plate_data_list.size(), cb_cancel);
|
||||
if (cb_cancel)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int index = 0; index < thumbnail_data.size(); index++)
|
||||
{
|
||||
if (proFn) {
|
||||
proFn(EXPORT_STAGE_ADD_THUMBNAILS, index, thumbnail_data.size(), cb_cancel);
|
||||
if (cb_cancel)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (thumbnail_data[index]->is_valid())
|
||||
{
|
||||
if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data[index], index)) {
|
||||
if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data[index], "Metadata/plate", index)) {
|
||||
return false;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",add thumbnail %1%'s data into 3mf")%(index+1);
|
||||
thumbnail_status[index] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!m_skip_static && plate_data_list.size() > 0) {
|
||||
|
||||
// Adds the file Metadata/top_i.png and Metadata/pick_i.png
|
||||
for (unsigned int index = 0; index < top_thumbnail_data.size(); index++)
|
||||
{
|
||||
if (top_thumbnail_data[index]->is_valid())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",add top thumbnail %1%'s data into 3mf")%(index+1);
|
||||
if (!_add_thumbnail_file_to_archive(archive, *top_thumbnail_data[index], "Metadata/top", index)) {
|
||||
return false;
|
||||
}
|
||||
top_thumbnail_status[index] = true;
|
||||
}
|
||||
|
||||
if (pick_thumbnail_data[index]->is_valid())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",add pick thumbnail %1%'s data into 3mf")%(index+1);
|
||||
if (!_add_thumbnail_file_to_archive(archive, *pick_thumbnail_data[index], "Metadata/pick", index)) {
|
||||
return false;
|
||||
}
|
||||
pick_thumbnail_status[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < plate_data_list.size(); i++) {
|
||||
PlateData *plate_data = plate_data_list[i];
|
||||
if (proFn) {
|
||||
proFn(EXPORT_STAGE_ADD_THUMBNAILS, i, plate_data_list.size(), cb_cancel);
|
||||
if (cb_cancel)
|
||||
return false;
|
||||
}
|
||||
if (!plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file))){
|
||||
|
||||
if (!thumbnail_status[i] && !plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file))){
|
||||
std::string dst_in_3mf = (boost::format("Metadata/plate_%1%.png") % (i + 1)).str();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add thumbnail %1% from file %2%") % (i+1) %plate_data->thumbnail_file;
|
||||
|
||||
if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->thumbnail_file)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add thumbnail %1% from file %2% failed\n") % (i+1) %plate_data->thumbnail_file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!top_thumbnail_status[i] && !plate_data->top_file.empty() && (boost::filesystem::exists(plate_data->top_file))){
|
||||
std::string dst_in_3mf = (boost::format("Metadata/top_%1%.png") % (i + 1)).str();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add top thumbnail %1% from file %2%") % (i+1) %plate_data->top_file;
|
||||
if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->top_file)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add top thumbnail %1% failed") % (i+1);
|
||||
return false;
|
||||
}
|
||||
top_thumbnail_status[i] = true;
|
||||
}
|
||||
|
||||
if (!pick_thumbnail_status[i] && !plate_data->pick_file.empty() && (boost::filesystem::exists(plate_data->pick_file))){
|
||||
std::string dst_in_3mf = (boost::format("Metadata/pick_%1%.png") % (i + 1)).str();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add pick thumbnail %1% from file %2%") % (i+1) %plate_data->pick_file;
|
||||
if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->pick_file)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add pick thumbnail %1% failed") % (i+1);
|
||||
return false;
|
||||
}
|
||||
pick_thumbnail_status[i] = true;
|
||||
}
|
||||
}
|
||||
if (proFn) {
|
||||
proFn(EXPORT_STAGE_ADD_THUMBNAILS, plate_data_list.size(), plate_data_list.size(), cb_cancel);
|
||||
if (cb_cancel)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5155,11 +5482,16 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
// This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides).
|
||||
// As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data
|
||||
// is stored here as well.
|
||||
if (!_add_model_config_file_to_archive(archive, model, plate_data_list, objects_data, export_plate_idx, m_save_gcode)) {
|
||||
if (!_add_model_config_file_to_archive(archive, model, plate_data_list, objects_data, export_plate_idx, m_save_gcode, m_use_loaded_id)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_model_config_file_to_archive failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_add_cut_information_file_to_archive(archive, model)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_cut_information_file_to_archive failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//BBS progress point
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add sliced info to 3mf\n");
|
||||
if (proFn) {
|
||||
|
|
@ -5267,14 +5599,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index)
|
||||
bool _BBS_3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, const char* local_path, int index)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
size_t png_size = 0;
|
||||
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_COMPRESSION, 1);
|
||||
if (png_data != nullptr) {
|
||||
std::string thumbnail_name = (boost::format("Metadata/plate_%1%.png") % (index + 1)).str();
|
||||
std::string thumbnail_name = (boost::format("%1%_%2%.png")%local_path % (index + 1)).str();
|
||||
res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)png_data, png_size, MZ_NO_COMPRESSION);
|
||||
mz_free(png_data);
|
||||
}
|
||||
|
|
@ -5291,7 +5623,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
{
|
||||
bool res = false;
|
||||
|
||||
size_t png_size = 0;
|
||||
/*size_t png_size = 0;
|
||||
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_COMPRESSION, 1);
|
||||
if (png_data != nullptr) {
|
||||
std::string thumbnail_name = (boost::format(PATTERN_FILE_FORMAT) % (index + 1)).str();
|
||||
|
|
@ -5302,7 +5634,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
if (!res) {
|
||||
add_error("Unable to add thumbnail file to archive");
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add thumbnail file to archive\n");
|
||||
}
|
||||
}*/
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
@ -5437,6 +5769,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
stream << " xmlns:p=\"http://schemas.microsoft.com/3dmanufacturing/production/2015/06\" requiredextensions=\"p\"";
|
||||
stream << ">\n";
|
||||
|
||||
std::string origin;
|
||||
std::string name;
|
||||
std::string user_name;
|
||||
std::string user_id;
|
||||
|
|
@ -5465,6 +5798,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
description = model.model_info->description;
|
||||
copyright = model.model_info->copyright;
|
||||
name = model.model_info->model_name;
|
||||
origin = model.model_info->origin;
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_cover = " << design_cover;
|
||||
}
|
||||
// remember to use metadata_item_map to store metadata info
|
||||
|
|
@ -5476,6 +5810,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
}
|
||||
|
||||
metadata_item_map[BBL_MODEL_NAME_TAG] = xml_escape(name);
|
||||
metadata_item_map[BBL_ORIGIN_TAG] = xml_escape(origin);
|
||||
metadata_item_map[BBL_DESIGNER_TAG] = xml_escape(user_name);
|
||||
metadata_item_map[BBL_DESIGNER_USER_ID_TAG] = user_id;
|
||||
metadata_item_map[BBL_DESIGNER_COVER_FILE_TAG] = xml_escape(design_cover);
|
||||
|
|
@ -5650,6 +5985,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
mz_zip_writer_init_heap(&archive, 0, 1024 * 1024);
|
||||
CNumericLocalesSetter locales_setter;
|
||||
_add_model_file_to_archive(object_paths[i], archive, model, objects_data2, nullptr, project);
|
||||
iter->second = objects_data2.begin()->second;
|
||||
void *ppBuf; size_t pSize;
|
||||
|
|
@ -6302,7 +6638,39 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx, bool save_gcode)
|
||||
void _add_text_info_to_archive(std::stringstream& stream, const TextInfo& text_info) {
|
||||
stream << " <" << TEXT_INFO_TAG << " ";
|
||||
|
||||
stream << TEXT_ATTR << "=\"" << 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 IdToObjectDataMap &objects_data, int export_plate_idx, bool save_gcode, bool use_loaded_id)
|
||||
{
|
||||
std::stringstream stream;
|
||||
std::map<const TriangleMesh*, int> shared_meshes;
|
||||
|
|
@ -6398,6 +6766,10 @@ 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);
|
||||
|
||||
//add the shared mesh logic
|
||||
const TriangleMesh* current_mesh = volume->mesh_ptr();
|
||||
std::map<const TriangleMesh*,int>::iterator mesh_iter;
|
||||
|
|
@ -6464,10 +6836,20 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << THUMBNAIL_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << thumbnail_file_in_3mf << "\"/>\n";
|
||||
}
|
||||
|
||||
if (!plate_data->pattern_file.empty()) {
|
||||
if (!plate_data->top_file.empty()) {
|
||||
std::string top_file_in_3mf = (boost::format(TOP_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TOP_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << top_file_in_3mf << "\"/>\n";
|
||||
}
|
||||
|
||||
if (!plate_data->pick_file.empty()) {
|
||||
std::string pick_file_in_3mf = (boost::format(PICK_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PICK_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pick_file_in_3mf << "\"/>\n";
|
||||
}
|
||||
|
||||
/*if (!plate_data->pattern_file.empty()) {
|
||||
std::string pattern_file_in_3mf = (boost::format(PATTERN_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PATTERN_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pattern_file_in_3mf << "\"/>\n";
|
||||
}
|
||||
}*/
|
||||
if (!plate_data->pattern_bbox_file.empty()) {
|
||||
std::string pattern_bbox_file_in_3mf = (boost::format(PATTERN_CONFIG_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PATTERN_BBOX_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pattern_bbox_file_in_3mf << "\"/>\n";
|
||||
|
|
@ -6480,7 +6862,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
stream << " <" << INSTANCE_TAG << ">\n";
|
||||
int obj_id = plate_data->objects_and_instances[j].first;
|
||||
int inst_id = plate_data->objects_and_instances[j].second;
|
||||
int arrange_o = 0;
|
||||
int identify_id = 0;
|
||||
ModelObject* obj = NULL;
|
||||
ModelInstance* inst = NULL;
|
||||
if (obj_id >= model.objects.size()) {
|
||||
|
|
@ -6494,7 +6876,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
}
|
||||
else if (obj){
|
||||
inst = obj->instances[inst_id];
|
||||
arrange_o = inst->arrange_order;
|
||||
if (use_loaded_id && (inst->loaded_id > 0))
|
||||
identify_id = inst->loaded_id;
|
||||
else
|
||||
identify_id = inst->id().id;
|
||||
}
|
||||
if (m_skip_static && obj) {
|
||||
obj_id = obj->get_backup_id();
|
||||
|
|
@ -6505,7 +6890,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OBJECT_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << obj_id << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << INSTANCEID_ATTR << "\" " << VALUE_ATTR << "=\"" << inst_id << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ARRANGE_ORDER_ATTR << "\" " << VALUE_ATTR << "=\"" << arrange_o << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << IDENTIFYID_ATTR << "\" " << VALUE_ATTR << "=\"" << identify_id << "\"/>\n";
|
||||
stream << " </" << INSTANCE_TAG << ">\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -6562,6 +6947,67 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model)
|
||||
{
|
||||
std::string out = "";
|
||||
pt::ptree tree;
|
||||
|
||||
unsigned int object_cnt = 0;
|
||||
for (const ModelObject *object : model.objects) {
|
||||
object_cnt++;
|
||||
pt::ptree &obj_tree = tree.add("objects.object", "");
|
||||
|
||||
obj_tree.put("<xmlattr>.id", object_cnt);
|
||||
|
||||
// Store info for cut_id
|
||||
pt::ptree &cut_id_tree = obj_tree.add("cut_id", "");
|
||||
|
||||
// store cut_id atributes
|
||||
cut_id_tree.put("<xmlattr>.id", object->cut_id.id().id);
|
||||
cut_id_tree.put("<xmlattr>.check_sum", object->cut_id.check_sum());
|
||||
cut_id_tree.put("<xmlattr>.connectors_cnt", object->cut_id.connectors_cnt());
|
||||
|
||||
int volume_idx = -1;
|
||||
for (const ModelVolume *volume : object->volumes) {
|
||||
++volume_idx;
|
||||
if (volume->is_cut_connector()) {
|
||||
pt::ptree &connectors_tree = obj_tree.add("connectors.connector", "");
|
||||
connectors_tree.put("<xmlattr>.volume_id", volume_idx);
|
||||
connectors_tree.put("<xmlattr>.type", int(volume->cut_info.connector_type));
|
||||
connectors_tree.put("<xmlattr>.r_tolerance", volume->cut_info.radius_tolerance);
|
||||
connectors_tree.put("<xmlattr>.h_tolerance", volume->cut_info.height_tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tree.empty()) {
|
||||
std::ostringstream oss;
|
||||
pt::write_xml(oss, tree);
|
||||
out = oss.str();
|
||||
|
||||
// Post processing("beautification") of the output string for a better preview
|
||||
boost::replace_all(out, "><object", ">\n <object");
|
||||
boost::replace_all(out, "><cut_id", ">\n <cut_id");
|
||||
boost::replace_all(out, "></cut_id>", ">\n </cut_id>");
|
||||
boost::replace_all(out, "><connectors", ">\n <connectors");
|
||||
boost::replace_all(out, "></connectors>", ">\n </connectors>");
|
||||
boost::replace_all(out, "><connector", ">\n <connector");
|
||||
boost::replace_all(out, "></connector>", ">\n </connector>");
|
||||
boost::replace_all(out, "></object>", ">\n </object>");
|
||||
// OR just
|
||||
boost::replace_all(out, "><", ">\n<");
|
||||
}
|
||||
|
||||
if (!out.empty()) {
|
||||
if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void *) out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
||||
add_error("Unable to add cut information file to archive");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list)
|
||||
{
|
||||
std::stringstream stream;
|
||||
|
|
@ -7105,7 +7551,10 @@ public:
|
|||
while (true)
|
||||
{
|
||||
while (m_tasks.empty()) {
|
||||
m_cond.timed_wait(lock, m_next_backup);
|
||||
if (m_interval > 0)
|
||||
m_cond.timed_wait(lock, m_next_backup);
|
||||
else
|
||||
m_cond.wait(lock);
|
||||
if (m_interval > 0 && boost::get_system_time() > m_next_backup) {
|
||||
m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq });
|
||||
m_next_backup += boost::posix_time::seconds(m_interval);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ struct ThumbnailData;
|
|||
|
||||
#define GCODE_FILE_FORMAT "Metadata/plate_%1%.gcode"
|
||||
#define THUMBNAIL_FILE_FORMAT "Metadata/plate_%1%.png"
|
||||
#define PATTERN_FILE_FORMAT "Metadata/plate_%1%_pattern_layer_0.png"
|
||||
#define TOP_FILE_FORMAT "Metadata/top_%1%.png"
|
||||
#define PICK_FILE_FORMAT "Metadata/pick_%1%.png"
|
||||
//#define PATTERN_FILE_FORMAT "Metadata/plate_%1%_pattern_layer_0.png"
|
||||
#define PATTERN_CONFIG_FILE_FORMAT "Metadata/plate_%1%.json"
|
||||
#define EMBEDDED_PRINT_FILE_FORMAT "Metadata/process_settings_%1%.config"
|
||||
#define EMBEDDED_FILAMENT_FILE_FORMAT "Metadata/filament_settings_%1%.config"
|
||||
|
|
@ -65,8 +67,10 @@ struct PlateData
|
|||
std::string gcode_file_md5;
|
||||
std::string thumbnail_file;
|
||||
ThumbnailData plate_thumbnail;
|
||||
ThumbnailData pattern_thumbnail;
|
||||
std::string pattern_file;
|
||||
std::string top_file;
|
||||
std::string pick_file;
|
||||
//ThumbnailData pattern_thumbnail;
|
||||
//std::string pattern_file;
|
||||
std::string pattern_bbox_file;
|
||||
std::string gcode_prediction;
|
||||
std::string gcode_weight;
|
||||
|
|
@ -103,6 +107,7 @@ enum class SaveStrategy
|
|||
SkipModel = 1 << 7,
|
||||
WithSliceInfo = 1 << 8,
|
||||
SkipAuxiliary = 1 << 9,
|
||||
UseLoadedId = 1 << 10,
|
||||
|
||||
SplitModel = 0x1000 | ProductionExt,
|
||||
Encrypted = SecureContentExt | SplitModel,
|
||||
|
|
@ -195,6 +200,8 @@ struct StoreParams
|
|||
std::vector<Preset*> project_presets;
|
||||
DynamicPrintConfig* config;
|
||||
std::vector<ThumbnailData*> thumbnail_data;
|
||||
std::vector<ThumbnailData*> top_thumbnail_data;
|
||||
std::vector<ThumbnailData*> pick_thumbnail_data;
|
||||
std::vector<ThumbnailData*> calibration_thumbnail_data;
|
||||
SaveStrategy strategy = SaveStrategy::Zip64;
|
||||
Export3mfProgressFn proFn = nullptr;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
#include "TopExp_Explorer.hxx"
|
||||
#include "TopoDS.hxx"
|
||||
#include "BRepExtrema_SelfIntersection.hxx"
|
||||
#include "clipper/clipper.hpp"
|
||||
|
||||
using namespace ClipperLib;
|
||||
|
||||
namespace Slic3r {
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.005;
|
||||
|
|
@ -188,7 +191,8 @@ bool get_svg_profile(const char *path, std::vector<Element_Info> &element_infos,
|
|||
}
|
||||
}
|
||||
// keep the start and end points of profile connected
|
||||
profile_line_points.back().second = profile_line_points[0].first;
|
||||
if (shape->fill.gradient != nullptr)
|
||||
profile_line_points.back().second = profile_line_points[0].first;
|
||||
|
||||
if (is_profile_self_interaction(profile_line_points))
|
||||
BOOST_LOG_TRIVIAL(warning) << "the profile is self interaction.";
|
||||
|
|
@ -196,6 +200,56 @@ bool get_svg_profile(const char *path, std::vector<Element_Info> &element_infos,
|
|||
path_line_points.push_back(profile_line_points);
|
||||
}
|
||||
|
||||
if (shape->fill.gradient == nullptr) {
|
||||
double scale_size = 1e6;
|
||||
std::vector<std::vector<std::pair<gp_Pnt, gp_Pnt>>> new_path_line_points;
|
||||
float stroke_width = shape->strokeWidth * scale_size;
|
||||
Polygons polygons;
|
||||
bool close_polygon = false;
|
||||
for (int i = 0; i < path_line_points.size(); ++i) {
|
||||
ClipperLib::Path pt_path;
|
||||
for (auto line_point : path_line_points[i]) {
|
||||
pt_path.push_back(IntPoint(line_point.first.X() * scale_size, line_point.first.Y() * scale_size));
|
||||
}
|
||||
pt_path.push_back(IntPoint(path_line_points[i].back().second.X() * scale_size, path_line_points[i].back().second.Y() * scale_size));
|
||||
|
||||
ClipperLib::Paths out_paths;
|
||||
ClipperLib::ClipperOffset co;
|
||||
if (pt_path.front() == pt_path.back()) {
|
||||
co.AddPath(pt_path, ClipperLib::jtMiter, ClipperLib::etClosedLine);
|
||||
close_polygon = true;
|
||||
} else {
|
||||
co.AddPath(pt_path, ClipperLib::jtMiter, ClipperLib::etOpenSquare);
|
||||
close_polygon = false;
|
||||
}
|
||||
co.Execute(out_paths, stroke_width / 2);
|
||||
|
||||
for (auto out_path : out_paths) {
|
||||
polygons.emplace_back(Polygon(out_path));
|
||||
}
|
||||
}
|
||||
|
||||
if (!close_polygon)
|
||||
polygons = union_(polygons);
|
||||
|
||||
std::vector<std::pair<gp_Pnt, gp_Pnt>> profile_line_points;
|
||||
for (auto polygon : polygons) {
|
||||
profile_line_points.clear();
|
||||
for (int i = 0; i < polygon.size() - 1; ++i) {
|
||||
gp_Pnt pt1(double(polygon[i][0] / scale_size), double(polygon[i][1] / scale_size), 0);
|
||||
gp_Pnt pt2(double(polygon[i + 1][0] / scale_size), double(polygon[i + 1][1] / scale_size), 0);
|
||||
profile_line_points.push_back({pt1, pt2});
|
||||
}
|
||||
gp_Pnt pt1(double(polygon.back()[0] / scale_size), double(polygon.back()[1] / scale_size), 0);
|
||||
gp_Pnt pt2(double(polygon.front()[0] / scale_size), double(polygon.front()[1] / scale_size), 0);
|
||||
profile_line_points.push_back({pt1, pt2});
|
||||
|
||||
new_path_line_points.push_back(profile_line_points);
|
||||
}
|
||||
|
||||
path_line_points = new_path_line_points;
|
||||
}
|
||||
|
||||
// generate all profile curves
|
||||
std::vector<TopoDS_Wire> wires;
|
||||
int index = 0;
|
||||
|
|
|
|||
|
|
@ -529,9 +529,20 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||
|
||||
// retract before toolchange
|
||||
toolchange_gcode_str = toolchange_retract_str + toolchange_gcode_str;
|
||||
//BBS: current position and fan_speed is unclear after interting change_filament_gcode
|
||||
toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n";
|
||||
gcodegen.writer().set_current_position_clear(false);
|
||||
//BBS
|
||||
{
|
||||
//BBS: current position and fan_speed is unclear after interting change_filament_gcode
|
||||
check_add_eol(toolchange_gcode_str);
|
||||
toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n";
|
||||
gcodegen.writer().set_current_position_clear(false);
|
||||
//BBS: check whether custom gcode changes the z position. Update if changed
|
||||
double temp_z_after_tool_change;
|
||||
if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) {
|
||||
Vec3d pos = gcodegen.writer().get_position();
|
||||
pos(2) = temp_z_after_tool_change;
|
||||
gcodegen.writer().set_position(pos);
|
||||
}
|
||||
}
|
||||
|
||||
// move to start_pos for wiping after toolchange
|
||||
std::string start_pos_str;
|
||||
|
|
@ -1791,6 +1802,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||
|
||||
//BBS: gcode writer doesn't know where the real position of extruder is after inserting custom gcode
|
||||
m_writer.set_current_position_clear(false);
|
||||
m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode);
|
||||
|
||||
// Process filament-specific gcode.
|
||||
/* if (has_wipe_tower) {
|
||||
|
|
@ -4371,11 +4383,13 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp
|
|||
if (role == erSupportMaterial || role == erSupportTransition) {
|
||||
const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
|
||||
//FIXME support_layer->support_islands.contains should use some search structure!
|
||||
if (support_layer != NULL && support_layer->support_islands.contains(travel))
|
||||
if (support_layer != NULL)
|
||||
// skip retraction if this is a travel move inside a support material island
|
||||
//FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
|
||||
// at the end of the extrusion path!
|
||||
return false;
|
||||
for (const ExPolygon& support_island : support_layer->support_islands)
|
||||
if (support_island.contains(travel))
|
||||
return false;
|
||||
//reduce the retractions in lightning infills for tree support
|
||||
if (support_layer != NULL && support_layer->support_type==stInnerTree)
|
||||
for (auto &area : support_layer->base_areas)
|
||||
|
|
@ -4384,7 +4398,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp
|
|||
}
|
||||
//BBS: need retract when long moving to print perimeter to avoid dropping of material
|
||||
if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr &&
|
||||
m_config.sparse_infill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel))
|
||||
m_config.sparse_infill_density.value > 0 && m_retract_when_crossing_perimeters.travel_inside_internal_regions(*m_layer, travel))
|
||||
// Skip retraction if travel is contained in an internal slice *and*
|
||||
// internal infill is enabled (so that stringing is entirely not visible).
|
||||
//FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
|
||||
|
|
@ -4485,18 +4499,26 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
float new_retract_length = m_config.retraction_length.get_at(extruder_id);
|
||||
float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(extruder_id);
|
||||
int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id): m_config.nozzle_temperature.get_at(extruder_id);
|
||||
// BBS: if print_z == 0 use first layer temperature
|
||||
if (abs(print_z) < EPSILON)
|
||||
new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(extruder_id);
|
||||
|
||||
Vec3d nozzle_pos = m_writer.get_position();
|
||||
float old_retract_length, old_retract_length_toolchange, wipe_volume;
|
||||
int old_filament_temp, old_filament_e_feedrate;
|
||||
|
||||
float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(extruder_id), 2));
|
||||
|
||||
if (m_writer.extruder() != nullptr) {
|
||||
//BBS: add handling for filament change in start gcode
|
||||
int previous_extruder_id = -1;
|
||||
if (m_writer.extruder() != nullptr || m_start_gcode_filament != -1) {
|
||||
std::vector<float> flush_matrix(cast<float>(m_config.flush_volumes_matrix.values));
|
||||
const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON);
|
||||
assert(m_writer.extruder()->id() < number_of_extruders);
|
||||
if (m_writer.extruder() != nullptr)
|
||||
assert(m_writer.extruder()->id() < number_of_extruders);
|
||||
else
|
||||
assert(m_start_gcode_filament < number_of_extruders);
|
||||
|
||||
int previous_extruder_id = m_writer.extruder()->id();
|
||||
previous_extruder_id = m_writer.extruder() != nullptr ? m_writer.extruder()->id() : m_start_gcode_filament;
|
||||
old_retract_length = m_config.retraction_length.get_at(previous_extruder_id);
|
||||
old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(previous_extruder_id);
|
||||
old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : m_config.nozzle_temperature.get_at(previous_extruder_id);
|
||||
|
|
@ -4504,8 +4526,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
wipe_volume *= m_config.flush_multiplier;
|
||||
old_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area);
|
||||
old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate;
|
||||
}
|
||||
else {
|
||||
//BBS: must clean m_start_gcode_filament
|
||||
m_start_gcode_filament = -1;
|
||||
} else {
|
||||
old_retract_length = 0.f;
|
||||
old_retract_length_toolchange = 0.f;
|
||||
old_filament_temp = 0;
|
||||
|
|
@ -4517,7 +4540,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate;
|
||||
|
||||
DynamicConfig dyn_config;
|
||||
dyn_config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.extruder() != nullptr ? m_writer.extruder()->id() : -1)));
|
||||
dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
|
||||
dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id));
|
||||
dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
|
|
@ -4566,12 +4589,23 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
std::string toolchange_gcode_parsed;
|
||||
if (!change_filament_gcode.empty()) {
|
||||
toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, extruder_id, &dyn_config);
|
||||
check_add_eol(toolchange_gcode_parsed);
|
||||
gcode += toolchange_gcode_parsed;
|
||||
check_add_eol(gcode);
|
||||
//BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode
|
||||
//Set this flag so that normal lift will be used the first time after tool change.
|
||||
gcode += ";_FORCE_RESUME_FAN_SPEED\n";
|
||||
m_writer.set_current_position_clear(false);
|
||||
|
||||
//BBS
|
||||
{
|
||||
//BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode
|
||||
//Set this flag so that normal lift will be used the first time after tool change.
|
||||
gcode += ";_FORCE_RESUME_FAN_SPEED\n";
|
||||
m_writer.set_current_position_clear(false);
|
||||
//BBS: check whether custom gcode changes the z position. Update if changed
|
||||
double temp_z_after_tool_change;
|
||||
if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_parsed, temp_z_after_tool_change)) {
|
||||
Vec3d pos = m_writer.get_position();
|
||||
pos(2) = temp_z_after_tool_change;
|
||||
m_writer.set_position(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BBS. Reset old extruder E-value.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "PrintConfig.hpp"
|
||||
#include "GCode/AvoidCrossingPerimeters.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
#include "GCode/RetractWhenCrossingPerimeters.hpp"
|
||||
#include "GCode/SpiralVase.hpp"
|
||||
#include "GCode/ToolOrdering.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
|
|
@ -433,6 +434,7 @@ private:
|
|||
OozePrevention m_ooze_prevention;
|
||||
Wipe m_wipe;
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
|
||||
bool m_enable_loop_clipping;
|
||||
// If enabled, the G-code generator will put following comments at the ends
|
||||
// of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _OVERHANG_FAN_START, _OVERHANG_FAN_END
|
||||
|
|
@ -494,6 +496,7 @@ private:
|
|||
unsigned int m_toolchange_count;
|
||||
coordf_t m_nominal_z;
|
||||
bool m_need_change_layer_lift_z = false;
|
||||
int m_start_gcode_filament = -1;
|
||||
|
||||
// BBS
|
||||
int get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const;
|
||||
|
|
|
|||
|
|
@ -1010,7 +1010,7 @@ static ExPolygons get_boundary(const Layer &layer)
|
|||
ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing));
|
||||
if(support_layer) {
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
append(boundary, inner_offset(support_layer->support_islands.expolygons, 1.5 * perimeter_spacing));
|
||||
append(boundary, inner_offset(support_layer->support_islands, 1.5 * perimeter_spacing));
|
||||
#endif
|
||||
auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON);
|
||||
if (layer_below)
|
||||
|
|
@ -1063,7 +1063,7 @@ static Polygons get_boundary_external(const Layer &layer)
|
|||
for (const ExPolygon &island : layer_below->lslices)
|
||||
append(holes_per_obj, island.holes);
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
append(supports_per_obj, support_layer->support_islands.expolygons);
|
||||
append(supports_per_obj, support_layer->support_islands);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
278
src/libslic3r/GCode/ConflictChecker.cpp
Normal file
278
src/libslic3r/GCode/ConflictChecker.cpp
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
#include "ConflictChecker.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace RasterizationImpl {
|
||||
using IndexPair = std::pair<int64_t, int64_t>;
|
||||
using Grids = std::vector<IndexPair>;
|
||||
|
||||
inline constexpr int64_t RasteXDistance = scale_(1);
|
||||
inline constexpr int64_t RasteYDistance = scale_(1);
|
||||
|
||||
inline IndexPair point_map_grid_index(const Point &pt, int64_t xdist, int64_t ydist)
|
||||
{
|
||||
auto x = pt.x() / xdist;
|
||||
auto y = pt.y() / ydist;
|
||||
return std::make_pair(x, y);
|
||||
}
|
||||
|
||||
inline bool nearly_equal(const Point &p1, const Point &p2) { return std::abs(p1.x() - p2.x()) < SCALED_EPSILON && std::abs(p1.y() - p2.y()) < SCALED_EPSILON; }
|
||||
|
||||
inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance, int64_t ydist = RasteYDistance)
|
||||
{
|
||||
Grids res;
|
||||
Point rayStart = line.a;
|
||||
Point rayEnd = line.b;
|
||||
IndexPair currentVoxel = point_map_grid_index(rayStart, xdist, ydist);
|
||||
IndexPair firstVoxel = currentVoxel;
|
||||
IndexPair lastVoxel = point_map_grid_index(rayEnd, xdist, ydist);
|
||||
|
||||
Point ray = rayEnd - rayStart;
|
||||
|
||||
double stepX = ray.x() >= 0 ? 1 : -1;
|
||||
double stepY = ray.y() >= 0 ? 1 : -1;
|
||||
|
||||
double nextVoxelBoundaryX = (currentVoxel.first + stepX) * xdist;
|
||||
double nextVoxelBoundaryY = (currentVoxel.second + stepY) * ydist;
|
||||
|
||||
if (stepX < 0) { nextVoxelBoundaryX += xdist; }
|
||||
if (stepY < 0) { nextVoxelBoundaryY += ydist; }
|
||||
|
||||
double tMaxX = ray.x() != 0 ? (nextVoxelBoundaryX - rayStart.x()) / ray.x() : DBL_MAX;
|
||||
double tMaxY = ray.y() != 0 ? (nextVoxelBoundaryY - rayStart.y()) / ray.y() : DBL_MAX;
|
||||
|
||||
double tDeltaX = ray.x() != 0 ? static_cast<double>(xdist) / ray.x() * stepX : DBL_MAX;
|
||||
double tDeltaY = ray.y() != 0 ? static_cast<double>(ydist) / ray.y() * stepY : DBL_MAX;
|
||||
|
||||
res.push_back(currentVoxel);
|
||||
|
||||
double tx = tMaxX;
|
||||
double ty = tMaxY;
|
||||
|
||||
while (lastVoxel != currentVoxel) {
|
||||
if (lastVoxel.first == currentVoxel.first) {
|
||||
for (int64_t i = currentVoxel.second; i != lastVoxel.second; i += (int64_t) stepY) {
|
||||
currentVoxel.second += (int64_t) stepY;
|
||||
res.push_back(currentVoxel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lastVoxel.second == currentVoxel.second) {
|
||||
for (int64_t i = currentVoxel.first; i != lastVoxel.first; i += (int64_t) stepX) {
|
||||
currentVoxel.first += (int64_t) stepX;
|
||||
res.push_back(currentVoxel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (tx < ty) {
|
||||
currentVoxel.first += (int64_t) stepX;
|
||||
tx += tDeltaX;
|
||||
} else {
|
||||
currentVoxel.second += (int64_t) stepY;
|
||||
ty += tDeltaY;
|
||||
}
|
||||
res.push_back(currentVoxel);
|
||||
if (res.size() >= 100000) { // bug
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace RasterizationImpl
|
||||
|
||||
void LinesBucketQueue::emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Point offset)
|
||||
{
|
||||
auto oldSize = _buckets.capacity();
|
||||
if (_objsPtrToId.find(objPtr) == _objsPtrToId.end()) {
|
||||
_objsPtrToId.insert({objPtr, _objsPtrToId.size()});
|
||||
_idToObjsPtr.insert({_objsPtrToId.size() - 1, objPtr});
|
||||
}
|
||||
_buckets.emplace_back(std::move(paths), _objsPtrToId[objPtr], offset);
|
||||
_pq.push(&_buckets.back());
|
||||
auto newSize = _buckets.capacity();
|
||||
if (oldSize != newSize) { // pointers change
|
||||
decltype(_pq) newQueue;
|
||||
for (LinesBucket &bucket : _buckets) { newQueue.push(&bucket); }
|
||||
std::swap(_pq, newQueue);
|
||||
}
|
||||
}
|
||||
|
||||
double LinesBucketQueue::removeLowests()
|
||||
{
|
||||
auto lowest = _pq.top();
|
||||
_pq.pop();
|
||||
double curHeight = lowest->curHeight();
|
||||
std::vector<LinesBucket *> lowests;
|
||||
lowests.push_back(lowest);
|
||||
|
||||
while (_pq.empty() == false && std::abs(_pq.top()->curHeight() - lowest->curHeight()) < EPSILON) {
|
||||
lowests.push_back(_pq.top());
|
||||
_pq.pop();
|
||||
}
|
||||
|
||||
for (LinesBucket *bp : lowests) {
|
||||
bp->raise();
|
||||
if (bp->valid()) { _pq.push(bp); }
|
||||
}
|
||||
return curHeight;
|
||||
}
|
||||
|
||||
LineWithIDs LinesBucketQueue::getCurLines() const
|
||||
{
|
||||
LineWithIDs lines;
|
||||
for (const LinesBucket &bucket : _buckets) {
|
||||
if (bucket.valid()) {
|
||||
LineWithIDs tmpLines = bucket.curLines();
|
||||
lines.insert(lines.end(), tmpLines.begin(), tmpLines.end());
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths)
|
||||
{
|
||||
std::function<void(const ExtrusionEntityCollection *, ExtrusionPaths &)> getExtrusionPathImpl = [&](const ExtrusionEntityCollection *entity, ExtrusionPaths &paths) {
|
||||
for (auto entityPtr : entity->entities) {
|
||||
if (const ExtrusionEntityCollection *collection = dynamic_cast<ExtrusionEntityCollection *>(entityPtr)) {
|
||||
getExtrusionPathImpl(collection, paths);
|
||||
} else if (const ExtrusionPath *path = dynamic_cast<ExtrusionPath *>(entityPtr)) {
|
||||
paths.push_back(*path);
|
||||
} else if (const ExtrusionMultiPath *multipath = dynamic_cast<ExtrusionMultiPath *>(entityPtr)) {
|
||||
for (const ExtrusionPath &path : multipath->paths) { paths.push_back(path); }
|
||||
} else if (const ExtrusionLoop *loop = dynamic_cast<ExtrusionLoop *>(entityPtr)) {
|
||||
for (const ExtrusionPath &path : loop->paths) { paths.push_back(path); }
|
||||
}
|
||||
}
|
||||
};
|
||||
getExtrusionPathImpl(entity, paths);
|
||||
}
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
for (auto regionPtr : layerRegionPtrs) {
|
||||
getExtrusionPathsFromEntity(®ionPtr->perimeters, paths);
|
||||
if (regionPtr->perimeters.empty() == false) { getExtrusionPathsFromEntity(®ionPtr->fills, paths); }
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
getExtrusionPathsFromEntity(&supportLayer->support_fills, paths);
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj)
|
||||
{
|
||||
std::vector<ExtrusionPaths> objPaths, supportPaths;
|
||||
|
||||
for (auto layerPtr : obj->layers()) { objPaths.push_back(getExtrusionPathsFromLayer(layerPtr->regions())); }
|
||||
|
||||
for (auto supportLayerPtr : obj->support_layers()) { supportPaths.push_back(getExtrusionPathsFromSupportLayer(supportLayerPtr)); }
|
||||
|
||||
return {std::move(objPaths), std::move(supportPaths)};
|
||||
}
|
||||
|
||||
ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines)
|
||||
{
|
||||
using namespace RasterizationImpl;
|
||||
std::map<IndexPair, std::vector<int>> indexToLine;
|
||||
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
const LineWithID &l1 = lines[i];
|
||||
auto indexes = line_rasterization(l1._line);
|
||||
for (auto index : indexes) {
|
||||
const auto &possibleIntersectIdxs = indexToLine[index];
|
||||
for (auto possibleIntersectIdx : possibleIntersectIdxs) {
|
||||
const LineWithID &l2 = lines[possibleIntersectIdx];
|
||||
if (auto interRes = line_intersect(l1, l2); interRes.has_value()) { return interRes; }
|
||||
}
|
||||
indexToLine[index].push_back(i);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs,
|
||||
std::optional<const FakeWipeTower *> wtdptr) // find the first intersection point of lines in different objects
|
||||
{
|
||||
if (objs.size() <= 1) { return {}; }
|
||||
LinesBucketQueue conflictQueue;
|
||||
if (wtdptr.has_value()) { // wipe tower at 0 by default
|
||||
auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
|
||||
conflictQueue.emplace_back_bucket(std::move(wtpaths), wtdptr.value(), {wtdptr.value()->plate_origin.x(),wtdptr.value()->plate_origin.y()});
|
||||
}
|
||||
for (PrintObject *obj : objs) {
|
||||
auto layers = getAllLayersExtrusionPathsFromObject(obj);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.first), obj, obj->instances().front().shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.second), obj, obj->instances().front().shift);
|
||||
}
|
||||
|
||||
std::vector<LineWithIDs> layersLines;
|
||||
std::vector<double> heights;
|
||||
while (conflictQueue.valid()) {
|
||||
LineWithIDs lines = conflictQueue.getCurLines();
|
||||
double curHeight = conflictQueue.removeLowests();
|
||||
heights.push_back(curHeight);
|
||||
layersLines.push_back(std::move(lines));
|
||||
}
|
||||
|
||||
bool find = false;
|
||||
tbb::concurrent_vector<std::pair<ConflictComputeResult,double>> conflict;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, layersLines.size()), [&](tbb::blocked_range<size_t> range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
auto interRes = find_inter_of_lines(layersLines[i]);
|
||||
if (interRes.has_value()) {
|
||||
find = true;
|
||||
conflict.emplace_back(interRes.value(),heights[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (find) {
|
||||
const void *ptr1 = conflictQueue.idToObjsPtr(conflict[0].first._obj1);
|
||||
const void *ptr2 = conflictQueue.idToObjsPtr(conflict[0].first._obj2);
|
||||
double conflictHeight = conflict[0].second;
|
||||
if (wtdptr.has_value()) {
|
||||
const FakeWipeTower *wtdp = wtdptr.value();
|
||||
if (ptr1 == wtdp || ptr2 == wtdp) {
|
||||
if (ptr2 == wtdp) { std::swap(ptr1, ptr2); }
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2);
|
||||
}
|
||||
}
|
||||
const PrintObject *obj1 = reinterpret_cast<const PrintObject *>(ptr1);
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictHeight, ptr1, ptr2);
|
||||
} else
|
||||
return {};
|
||||
}
|
||||
|
||||
ConflictComputeOpt ConflictChecker::line_intersect(const LineWithID &l1, const LineWithID &l2)
|
||||
{
|
||||
if (l1._id == l2._id) { return {}; } // return true if lines are from same object
|
||||
Point inter;
|
||||
bool intersect = l1._line.intersection(l2._line, &inter);
|
||||
if (intersect) {
|
||||
auto dist1 = std::min(unscale(Point(l1._line.a - inter)).norm(), unscale(Point(l1._line.b - inter)).norm());
|
||||
auto dist2 = std::min(unscale(Point(l2._line.a - inter)).norm(), unscale(Point(l2._line.b - inter)).norm());
|
||||
auto dist = std::min(dist1, dist2);
|
||||
if (dist > 0.01) { return std::make_optional<ConflictComputeResult>(l1._id, l2._id); } // the two lines intersects if dist>0.01mm
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
125
src/libslic3r/GCode/ConflictChecker.hpp
Normal file
125
src/libslic3r/GCode/ConflictChecker.hpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#ifndef slic3r_ConflictChecker_hpp_
|
||||
#define slic3r_ConflictChecker_hpp_
|
||||
|
||||
#include "../Utils.hpp"
|
||||
#include "../Model.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../Layer.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct LineWithID
|
||||
{
|
||||
Line _line;
|
||||
int _id;
|
||||
int _role;
|
||||
|
||||
LineWithID(const Line &line, int id, int role) : _line(line), _id(id), _role(role) {}
|
||||
};
|
||||
|
||||
using LineWithIDs = std::vector<LineWithID>;
|
||||
|
||||
class LinesBucket
|
||||
{
|
||||
private:
|
||||
double _curHeight = 0.0;
|
||||
unsigned _curPileIdx = 0;
|
||||
|
||||
std::vector<ExtrusionPaths> _piles;
|
||||
int _id;
|
||||
Point _offset;
|
||||
|
||||
public:
|
||||
LinesBucket(std::vector<ExtrusionPaths> &&paths, int id, Point offset) : _piles(paths), _id(id), _offset(offset) {}
|
||||
LinesBucket(LinesBucket &&) = default;
|
||||
|
||||
bool valid() const { return _curPileIdx < _piles.size(); }
|
||||
void raise()
|
||||
{
|
||||
if (valid()) {
|
||||
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; }
|
||||
_curPileIdx++;
|
||||
}
|
||||
}
|
||||
double curHeight() const { return _curHeight; }
|
||||
LineWithIDs curLines() const
|
||||
{
|
||||
LineWithIDs lines;
|
||||
for (const ExtrusionPath &path : _piles[_curPileIdx]) {
|
||||
if (path.is_force_no_extrusion() == false) {
|
||||
Polyline check_polyline = path.polyline;
|
||||
check_polyline.translate(_offset);
|
||||
Lines tmpLines = check_polyline.lines();
|
||||
for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); }
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
friend bool operator>(const LinesBucket &left, const LinesBucket &right) { return left._curHeight > right._curHeight; }
|
||||
friend bool operator<(const LinesBucket &left, const LinesBucket &right) { return left._curHeight < right._curHeight; }
|
||||
friend bool operator==(const LinesBucket &left, const LinesBucket &right) { return left._curHeight == right._curHeight; }
|
||||
};
|
||||
|
||||
struct LinesBucketPtrComp
|
||||
{
|
||||
bool operator()(const LinesBucket *left, const LinesBucket *right) { return *left > *right; }
|
||||
};
|
||||
|
||||
class LinesBucketQueue
|
||||
{
|
||||
private:
|
||||
std::vector<LinesBucket> _buckets;
|
||||
std::priority_queue<LinesBucket *, std::vector<LinesBucket *>, LinesBucketPtrComp> _pq;
|
||||
std::map<int, const void *> _idToObjsPtr;
|
||||
std::map<const void *, int> _objsPtrToId;
|
||||
|
||||
public:
|
||||
void emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Point offset);
|
||||
bool valid() const { return _pq.empty() == false; }
|
||||
const void *idToObjsPtr(int id)
|
||||
{
|
||||
if (_idToObjsPtr.find(id) != _idToObjsPtr.end())
|
||||
return _idToObjsPtr[id];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
double removeLowests();
|
||||
LineWithIDs getCurLines() const;
|
||||
};
|
||||
|
||||
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths);
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs);
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer);
|
||||
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj);
|
||||
|
||||
struct ConflictComputeResult
|
||||
{
|
||||
int _obj1;
|
||||
int _obj2;
|
||||
|
||||
ConflictComputeResult(int o1, int o2) : _obj1(o1), _obj2(o2) {}
|
||||
ConflictComputeResult() = default;
|
||||
};
|
||||
|
||||
using ConflictComputeOpt = std::optional<ConflictComputeResult>;
|
||||
|
||||
using ConflictObjName = std::optional<std::pair<std::string, std::string>>;
|
||||
|
||||
struct ConflictChecker
|
||||
{
|
||||
static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional<const FakeWipeTower *> wtdptr);
|
||||
static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines);
|
||||
static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <fast_float/fast_float.h>
|
||||
|
||||
#include <float.h>
|
||||
#include <assert.h>
|
||||
#include <regex>
|
||||
|
|
@ -1929,6 +1931,95 @@ template<typename T>
|
|||
}
|
||||
}
|
||||
|
||||
int GCodeProcessor::get_gcode_last_filament(const std::string& gcode_str)
|
||||
{
|
||||
int str_size = gcode_str.size();
|
||||
int start_index = 0;
|
||||
int end_index = 0;
|
||||
int out_filament = -1;
|
||||
while (end_index < str_size) {
|
||||
if (gcode_str[end_index] != '\n') {
|
||||
end_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (end_index > start_index) {
|
||||
std::string line_str = gcode_str.substr(start_index, end_index - start_index);
|
||||
line_str.erase(0, line_str.find_first_not_of(" "));
|
||||
line_str.erase(line_str.find_last_not_of(" ") + 1);
|
||||
if (line_str.empty() || line_str[0] != 'T') {
|
||||
start_index = end_index + 1;
|
||||
end_index = start_index;
|
||||
continue;
|
||||
}
|
||||
|
||||
int out = -1;
|
||||
if (parse_number(line_str.substr(1), out) && out >= 0 && out < 255)
|
||||
out_filament = out;
|
||||
}
|
||||
|
||||
start_index = end_index + 1;
|
||||
end_index = start_index;
|
||||
}
|
||||
|
||||
return out_filament;
|
||||
}
|
||||
|
||||
//BBS: get last z position from gcode
|
||||
bool GCodeProcessor::get_last_z_from_gcode(const std::string& gcode_str, double& z)
|
||||
{
|
||||
int str_size = gcode_str.size();
|
||||
int start_index = 0;
|
||||
int end_index = 0;
|
||||
bool is_z_changed = false;
|
||||
while (end_index < str_size) {
|
||||
//find a full line
|
||||
if (gcode_str[end_index] != '\n') {
|
||||
end_index++;
|
||||
continue;
|
||||
}
|
||||
//parse the line
|
||||
if (end_index > start_index) {
|
||||
std::string line_str = gcode_str.substr(start_index, end_index - start_index);
|
||||
line_str.erase(0, line_str.find_first_not_of(" "));
|
||||
line_str.erase(line_str.find_last_not_of(";") + 1);
|
||||
line_str.erase(line_str.find_last_not_of(" ") + 1);
|
||||
|
||||
//command which may have z movement
|
||||
if (line_str.size() > 5 && (line_str.find("G0 ") == 0
|
||||
|| line_str.find("G1 ") == 0
|
||||
|| line_str.find("G2 ") == 0
|
||||
|| line_str.find("G3 ") == 0))
|
||||
{
|
||||
auto z_pos = line_str.find(" Z");
|
||||
double temp_z = 0;
|
||||
if (z_pos != line_str.npos
|
||||
&& z_pos + 2 < line_str.size()) {
|
||||
// Try to parse the numeric value.
|
||||
std::string z_sub = line_str.substr(z_pos + 2);
|
||||
char* c = &z_sub[0];
|
||||
char* end = c + sizeof(z_sub.c_str());
|
||||
|
||||
auto is_end_of_word = [](char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0 || c == ';';
|
||||
};
|
||||
|
||||
auto [pend, ec] = fast_float::from_chars(c, end, temp_z);
|
||||
if (pend != c && is_end_of_word(*pend)) {
|
||||
// The axis value has been parsed correctly.
|
||||
z = temp_z;
|
||||
is_z_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//loop to handle next line
|
||||
start_index = end_index + 1;
|
||||
end_index = start_index;
|
||||
}
|
||||
return is_z_changed;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled)
|
||||
{
|
||||
// producers tags
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ namespace Slic3r {
|
|||
time = 0.0f;
|
||||
prepare_time = 0.0f;
|
||||
custom_gcode_times.clear();
|
||||
custom_gcode_times.shrink_to_fit();
|
||||
moves_times.clear();
|
||||
moves_times.shrink_to_fit();
|
||||
roles_times.clear();
|
||||
roles_times.shrink_to_fit();
|
||||
layers_times.clear();
|
||||
layers_times.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -81,6 +85,7 @@ namespace Slic3r {
|
|||
m.reset();
|
||||
}
|
||||
volumes_per_color_change.clear();
|
||||
volumes_per_color_change.shrink_to_fit();
|
||||
volumes_per_extruder.clear();
|
||||
flush_per_filament.clear();
|
||||
used_filaments_per_role.clear();
|
||||
|
|
@ -88,8 +93,25 @@ namespace Slic3r {
|
|||
}
|
||||
};
|
||||
|
||||
struct ConflictResult
|
||||
{
|
||||
std::string _objName1;
|
||||
std::string _objName2;
|
||||
double _height;
|
||||
const void *_obj1; // nullptr means wipe tower
|
||||
const void *_obj2;
|
||||
int layer = -1;
|
||||
ConflictResult(const std::string &objName1, const std::string &objName2, double height, const void *obj1, const void *obj2)
|
||||
: _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2)
|
||||
{}
|
||||
ConflictResult() = default;
|
||||
};
|
||||
|
||||
using ConflictResultOpt = std::optional<ConflictResult>;
|
||||
|
||||
struct GCodeProcessorResult
|
||||
{
|
||||
ConflictResultOpt conflict_result;
|
||||
|
||||
struct SettingsIds
|
||||
{
|
||||
|
|
@ -238,6 +260,9 @@ namespace Slic3r {
|
|||
// (the first max_count found tags are returned into found_tag)
|
||||
static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector<std::string>& found_tag);
|
||||
|
||||
static int get_gcode_last_filament(const std::string &gcode_str);
|
||||
static bool get_last_z_from_gcode(const std::string& gcode_str, double& z);
|
||||
|
||||
static const float Wipe_Width;
|
||||
static const float Wipe_Height;
|
||||
|
||||
|
|
|
|||
54
src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp
Normal file
54
src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
#include "RetractWhenCrossingPerimeters.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &layer, const Polyline &travel)
|
||||
{
|
||||
if (m_layer != &layer) {
|
||||
// Update cache.
|
||||
m_layer = &layer;
|
||||
m_internal_islands.clear();
|
||||
m_aabbtree_internal_islands.clear();
|
||||
// Collect expolygons of internal slices.
|
||||
for (const LayerRegion *layerm : layer.regions())
|
||||
for (const Surface &surface : layerm->get_slices().surfaces)
|
||||
if (surface.is_internal())
|
||||
m_internal_islands.emplace_back(&surface.expolygon);
|
||||
// Calculate bounding boxes of internal slices.
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(m_internal_islands.size());
|
||||
for (size_t i = 0; i < m_internal_islands.size(); ++ i)
|
||||
bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));
|
||||
// Build AABB tree over bounding boxes of internal slices.
|
||||
m_aabbtree_internal_islands.build_modify_input(bboxes);
|
||||
}
|
||||
|
||||
BoundingBox bbox_travel = get_extents(travel);
|
||||
AABBTree::BoundingBox bbox_travel_eigen{ bbox_travel.min, bbox_travel.max };
|
||||
int result = -1;
|
||||
bbox_travel.offset(SCALED_EPSILON);
|
||||
AABBTreeIndirect::traverse(m_aabbtree_internal_islands,
|
||||
[&bbox_travel_eigen](const AABBTree::Node &node) {
|
||||
return bbox_travel_eigen.intersects(node.bbox);
|
||||
},
|
||||
[&travel, &bbox_travel, &result, &islands = m_internal_islands](const AABBTree::Node &node) {
|
||||
assert(node.is_leaf());
|
||||
assert(node.is_valid());
|
||||
Polygons clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*islands[node.idx], bbox_travel);
|
||||
if (diff_pl(travel, clipped).empty()) {
|
||||
// Travel path is completely inside an "internal" island. Don't retract.
|
||||
result = int(node.idx);
|
||||
// Stop traversal.
|
||||
return false;
|
||||
}
|
||||
// Continue traversal.
|
||||
return true;
|
||||
});
|
||||
return result != -1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
32
src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp
Normal file
32
src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef slic3r_RetractWhenCrossingPerimeters_hpp_
|
||||
#define slic3r_RetractWhenCrossingPerimeters_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../AABBTreeIndirect.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class ExPolygon;
|
||||
class Layer;
|
||||
class Polyline;
|
||||
|
||||
class RetractWhenCrossingPerimeters
|
||||
{
|
||||
public:
|
||||
bool travel_inside_internal_regions(const Layer &layer, const Polyline &travel);
|
||||
|
||||
private:
|
||||
// Last object layer visited, for which a cache of internal islands was created.
|
||||
const Layer *m_layer;
|
||||
// Internal islands only, referencing data owned by m_layer->regions()->surfaces().
|
||||
std::vector<const ExPolygon*> m_internal_islands;
|
||||
// Search structure over internal islands.
|
||||
using AABBTree = AABBTreeIndirect::Tree<2, coord_t>;
|
||||
AABBTree m_aabbtree_internal_islands;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_RetractWhenCrossingPerimeters_hpp_
|
||||
|
|
@ -704,11 +704,12 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume()
|
|||
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders));
|
||||
|
||||
unsigned int current_extruder_id = -1;
|
||||
for (LayerTools& lt : m_layer_tools) {
|
||||
for (int i = 0; i < m_layer_tools.size(); ++i) {
|
||||
LayerTools& lt = m_layer_tools[i];
|
||||
if (lt.extruders.empty())
|
||||
continue;
|
||||
// todo: The algorithm complexity is too high(o(n2)), currently only 8 colors are supported
|
||||
if (lt.extruders.size() <= 8) {
|
||||
if (i != 0 && lt.extruders.size() <= 8) {
|
||||
lt.extruders = get_extruders_order(wipe_volumes, lt.extruders, current_extruder_id);
|
||||
}
|
||||
current_extruder_id = lt.extruders.back();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,35 @@ inline float align_floor(float value, float base)
|
|||
return std::floor((value) / base) * base;
|
||||
}
|
||||
|
||||
static bool is_valid_gcode(const std::string &gcode)
|
||||
{
|
||||
int str_size = gcode.size();
|
||||
int start_index = 0;
|
||||
int end_index = 0;
|
||||
bool is_valid = false;
|
||||
while (end_index < str_size) {
|
||||
if (gcode[end_index] != '\n') {
|
||||
end_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (end_index > start_index) {
|
||||
std::string line_str = gcode.substr(start_index, end_index - start_index);
|
||||
line_str.erase(0, line_str.find_first_not_of(" "));
|
||||
line_str.erase(line_str.find_last_not_of(" ") + 1);
|
||||
if (!line_str.empty() && line_str[0] != ';') {
|
||||
is_valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start_index = end_index + 1;
|
||||
end_index = start_index;
|
||||
}
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
class WipeTowerWriter
|
||||
{
|
||||
public:
|
||||
|
|
@ -1089,7 +1118,8 @@ void WipeTower::toolchange_Wipe(
|
|||
|
||||
x_to_wipe -= (xr - xl);
|
||||
if (x_to_wipe < WT_EPSILON) {
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200);
|
||||
// BBS: Delete some unnecessary travel
|
||||
//writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200);
|
||||
break;
|
||||
}
|
||||
// stepping to the next line:
|
||||
|
|
@ -1101,9 +1131,12 @@ void WipeTower::toolchange_Wipe(
|
|||
|
||||
// We may be going back to the model - wipe the nozzle. If this is followed
|
||||
// by finish_layer, this wipe path will be overwritten.
|
||||
//writer.add_wipe_point(writer.x(), writer.y())
|
||||
// .add_wipe_point(writer.x(), writer.y() - dy)
|
||||
// .add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy);
|
||||
// BBS: modify the wipe_path after toolchange
|
||||
writer.add_wipe_point(writer.x(), writer.y())
|
||||
.add_wipe_point(writer.x(), writer.y() - dy)
|
||||
.add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy);
|
||||
.add_wipe_point(! m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
|
||||
|
||||
if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool)
|
||||
m_left_to_right = !m_left_to_right;
|
||||
|
|
@ -1171,13 +1204,15 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
|
|||
writer.rectangle_fill_box(this, fill_box.ld, fill_box.rd.x() - fill_box.ld.x(), fill_box.ru.y() - fill_box.rd.y(), feedrate);
|
||||
|
||||
// we are in one of the corners, travel to ld along the perimeter:
|
||||
if (writer.x() > fill_box.ld.x() + EPSILON) writer.travel(fill_box.ld.x(), writer.y());
|
||||
if (writer.y() > fill_box.ld.y() + EPSILON) writer.travel(writer.x(), fill_box.ld.y());
|
||||
// BBS: Delete some unnecessary travel
|
||||
//if (writer.x() > fill_box.ld.x() + EPSILON) writer.travel(fill_box.ld.x(), writer.y());
|
||||
//if (writer.y() > fill_box.ld.y() + EPSILON) writer.travel(writer.x(), fill_box.ld.y());
|
||||
|
||||
// Extrude infill to support the material to be printed above.
|
||||
const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width);
|
||||
float left = fill_box.lu.x() + 2*m_perimeter_width;
|
||||
float right = fill_box.ru.x() - 2 * m_perimeter_width;
|
||||
std::vector<Vec2f> finish_rect_wipe_path;
|
||||
if (extruder_fill && dy > m_perimeter_width)
|
||||
{
|
||||
writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f))
|
||||
|
|
@ -1225,6 +1260,9 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
|
|||
writer.travel(x,writer.y());
|
||||
writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y());
|
||||
}
|
||||
// BBS: add wipe_path for this case: only with finish rectangle
|
||||
finish_rect_wipe_path.emplace_back(writer.pos());
|
||||
finish_rect_wipe_path.emplace_back(Vec2f(left + dx * n, n % 2 ? fill_box.ru.y() : fill_box.rd.y()));
|
||||
}
|
||||
|
||||
writer.append("; CP EMPTY GRID END\n"
|
||||
|
|
@ -1278,6 +1316,11 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
|
|||
(writer.pos() == wt_box.rd ? wt_box.ru :
|
||||
(writer.pos() == wt_box.ru ? wt_box.lu :
|
||||
wt_box.ld)));
|
||||
|
||||
// BBS: add wipe_path for this case: only with finish rectangle
|
||||
if (finish_rect_wipe_path.size() == 2 && finish_rect_wipe_path[0] == writer.pos())
|
||||
target = finish_rect_wipe_path[1];
|
||||
|
||||
writer.add_wipe_point(writer.pos())
|
||||
.add_wipe_point(target);
|
||||
|
||||
|
|
@ -1606,7 +1649,7 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
|||
else {
|
||||
if (idx == -1)
|
||||
layer_result[0] = merge_tcr(finish_layer_tcr, layer_result[0]);
|
||||
else
|
||||
else if (is_valid_gcode(finish_layer_tcr.gcode))
|
||||
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
|
||||
}
|
||||
|
||||
|
|
@ -1641,8 +1684,9 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall()
|
|||
bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON;
|
||||
|
||||
// we are in one of the corners, travel to ld along the perimeter:
|
||||
if (writer.x() > fill_box.ld.x() + EPSILON) writer.travel(fill_box.ld.x(), writer.y());
|
||||
if (writer.y() > fill_box.ld.y() + EPSILON) writer.travel(writer.x(), fill_box.ld.y());
|
||||
// BBS: Delete some unnecessary travel
|
||||
//if (writer.x() > fill_box.ld.x() + EPSILON) writer.travel(fill_box.ld.x(), writer.y());
|
||||
//if (writer.y() > fill_box.ld.y() + EPSILON) writer.travel(writer.x(), fill_box.ld.y());
|
||||
|
||||
// outer perimeter (always):
|
||||
// BBS
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ public:
|
|||
|
||||
float get_depth() const { return m_wipe_tower_depth; }
|
||||
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
|
||||
float get_height() const { return m_wipe_tower_height; }
|
||||
float get_layer_height() const { return m_layer_height; }
|
||||
|
||||
void set_last_layer_extruder_fill(bool extruder_fill) {
|
||||
if (!m_plan.empty()) {
|
||||
|
|
|
|||
|
|
@ -441,20 +441,30 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
|||
/* In all the other cases, we perform an actual XYZ move and cancel
|
||||
the lift. */
|
||||
m_lifted = 0;
|
||||
m_pos = point;
|
||||
}
|
||||
|
||||
//BBS: take plate offset into consider
|
||||
this->set_current_position_clear(true);
|
||||
Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
|
||||
m_pos = dest_point;
|
||||
|
||||
std::string out_string;
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point_on_plate);
|
||||
w.emit_f(travel_speed * 60.0);
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
return w.string();
|
||||
if (!this->is_current_position_clear())
|
||||
{
|
||||
//force to move xy first then z after filament change
|
||||
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
out_string = w.string() + _travel_to_z(point_on_plate.z(), comment);
|
||||
} else {
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point_on_plate);
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
out_string = w.string();
|
||||
}
|
||||
|
||||
m_pos = dest_point;
|
||||
this->set_current_position_clear(true);
|
||||
return out_string;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ public:
|
|||
std::string lift(LiftType lift_type = LiftType::NormalLift);
|
||||
std::string unlift();
|
||||
Vec3d get_position() const { return m_pos; }
|
||||
void set_position(Vec3d& in) { m_pos = in; }
|
||||
|
||||
//BBS: set offset for gcode writer
|
||||
void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; }
|
||||
|
|
|
|||
|
|
@ -409,6 +409,20 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl
|
|||
}
|
||||
}
|
||||
|
||||
Transform3d translation_transform(const Vec3d &translation)
|
||||
{
|
||||
Transform3d transform = Transform3d::Identity();
|
||||
transform.translate(translation);
|
||||
return transform;
|
||||
}
|
||||
|
||||
Transform3d rotation_transform(const Vec3d& rotation)
|
||||
{
|
||||
Transform3d transform = Transform3d::Identity();
|
||||
transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX()));
|
||||
return transform;
|
||||
}
|
||||
|
||||
Transformation::Flags::Flags()
|
||||
: dont_translate(true)
|
||||
, dont_rotate(true)
|
||||
|
|
|
|||
|
|
@ -348,6 +348,15 @@ Vec3d extract_euler_angles(const Transform3d& transform);
|
|||
// Euler angles can be obtained by extract_euler_angles()
|
||||
void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix = nullptr);
|
||||
|
||||
// Returns the transform obtained by assembling the given translation
|
||||
Transform3d translation_transform(const Vec3d &translation);
|
||||
|
||||
// Returns the transform obtained by assembling the given rotations in the following order:
|
||||
// 1) rotate X
|
||||
// 2) rotate Y
|
||||
// 3) rotate Z
|
||||
Transform3d rotation_transform(const Vec3d &rotation);
|
||||
|
||||
class Transformation
|
||||
{
|
||||
struct Flags
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "libslic3r.h"
|
||||
#include "ConvexHull.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
|
||||
#include <boost/multiprecision/integer.hpp>
|
||||
|
||||
|
|
@ -19,13 +20,13 @@ Polygon convex_hull(Points pts)
|
|||
hull.points.resize(2 * n);
|
||||
// Build lower hull
|
||||
for (int i = 0; i < n; ++ i) {
|
||||
while (k >= 2 && pts[i].ccw(hull[k-2], hull[k-1]) <= 0)
|
||||
while (k >= 2 && Geometry::orient(pts[i], hull[k-2], hull[k-1]) != Geometry::ORIENTATION_CCW)
|
||||
-- k;
|
||||
hull[k ++] = pts[i];
|
||||
}
|
||||
// Build upper hull
|
||||
for (int i = n-2, t = k+1; i >= 0; i--) {
|
||||
while (k >= t && pts[i].ccw(hull[k-2], hull[k-1]) <= 0)
|
||||
while (k >= t && Geometry::orient(pts[i], hull[k-2], hull[k-1]) != Geometry::ORIENTATION_CCW)
|
||||
-- k;
|
||||
hull[k ++] = pts[i];
|
||||
}
|
||||
|
|
@ -58,7 +59,7 @@ Pointf3s convex_hull(Pointf3s points)
|
|||
Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1));
|
||||
Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1));
|
||||
|
||||
if (p.ccw(k2, k1) <= 0)
|
||||
if (Geometry::orient(p, k2, k1) != Geometry::ORIENTATION_CCW)
|
||||
--k;
|
||||
else
|
||||
break;
|
||||
|
|
@ -76,7 +77,7 @@ Pointf3s convex_hull(Pointf3s points)
|
|||
Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1));
|
||||
Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1));
|
||||
|
||||
if (p.ccw(k2, k1) <= 0)
|
||||
if (Geometry::orient(p, k2, k1) != Geometry::ORIENTATION_CCW)
|
||||
--k;
|
||||
else
|
||||
break;
|
||||
|
|
@ -103,6 +104,29 @@ Polygon convex_hull(const Polygons &polygons)
|
|||
return convex_hull(std::move(pp));
|
||||
}
|
||||
|
||||
Polygon convex_hull(const ExPolygons &expolygons)
|
||||
{
|
||||
Points pp;
|
||||
size_t sz = 0;
|
||||
for (const auto &expoly : expolygons)
|
||||
sz += expoly.contour.size();
|
||||
pp.reserve(sz);
|
||||
for (const auto &expoly : expolygons)
|
||||
pp.insert(pp.end(), expoly.contour.points.begin(), expoly.contour.points.end());
|
||||
return convex_hull(pp);
|
||||
}
|
||||
|
||||
Polygon convex_hulll(const Polylines &polylines)
|
||||
{
|
||||
Points pp;
|
||||
size_t sz = 0;
|
||||
for (const auto &polyline : polylines)
|
||||
sz += polyline.points.size();
|
||||
pp.reserve(sz);
|
||||
for (const auto &polyline : polylines)
|
||||
pp.insert(pp.end(), polyline.points.begin(), polyline.points.end());
|
||||
return convex_hull(pp);
|
||||
}
|
||||
|
||||
namespace rotcalip {
|
||||
|
||||
|
|
@ -374,7 +398,7 @@ bool inside_convex_polygon(const std::pair<std::vector<Vec2d>, std::vector<Vec2d
|
|||
// At min x.
|
||||
assert(pt.x() == it_bottom->x());
|
||||
assert(pt.x() == it_top->x());
|
||||
assert(it_bottom->y() <= pt.y() <= it_top->y());
|
||||
assert(it_bottom->y() <= pt.y() && pt.y() <= it_top->y());
|
||||
return pt.y() >= it_bottom->y() && pt.y() <= it_top->y();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
#ifndef slic3r_Geometry_ConvexHull_hpp_
|
||||
#define slic3r_Geometry_ConvexHull_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
namespace Geometry {
|
||||
|
||||
Pointf3s convex_hull(Pointf3s points);
|
||||
Polygon convex_hull(Points points);
|
||||
Polygon convex_hull(const Polygons &polygons);
|
||||
Polygon convex_hull(const ExPolygons &expolygons);
|
||||
Polygon convex_hulll(const Polylines &polylines);
|
||||
|
||||
// Returns true if the intersection of the two convex polygons A and B
|
||||
// is not an empty set.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "MedialAxis.hpp"
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "VoronoiOffset.hpp"
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
namespace boost { namespace polygon {
|
||||
|
|
@ -392,8 +393,7 @@ inline const typename VD::point_type retrieve_cell_point(const typename VD::cell
|
|||
}
|
||||
|
||||
template<typename VD, typename SEGMENTS>
|
||||
inline std::pair<typename VD::coord_type, typename VD::coord_type>
|
||||
measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
|
||||
inline std::pair<typename VD::coord_type, typename VD::coord_type> measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
|
||||
{
|
||||
typedef typename VD::coord_type T;
|
||||
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
|
||||
|
|
@ -442,15 +442,21 @@ private:
|
|||
const Lines &lines;
|
||||
};
|
||||
|
||||
void
|
||||
MedialAxis::build(ThickPolylines* polylines)
|
||||
MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) :
|
||||
m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width)
|
||||
{}
|
||||
|
||||
void MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
|
||||
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
|
||||
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
|
||||
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
||||
|
||||
/*
|
||||
// DEBUG: dump all Voronoi edges
|
||||
{
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); ++edge) {
|
||||
if (edge->is_infinite()) continue;
|
||||
|
||||
ThickPolyline polyline;
|
||||
|
|
@ -462,74 +468,57 @@ MedialAxis::build(ThickPolylines* polylines)
|
|||
}
|
||||
*/
|
||||
|
||||
//typedef const VD::vertex_type vert_t;
|
||||
typedef const VD::edge_type edge_t;
|
||||
|
||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||
// note: this keeps twins, so it inserts twice the number of the valid edges
|
||||
this->valid_edges.clear();
|
||||
{
|
||||
std::set<const VD::edge_type*> seen_edges;
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
// if we only process segments representing closed loops, none if the
|
||||
// infinite edges (if any) would be part of our MAT anyway
|
||||
if (edge->is_secondary() || edge->is_infinite()) continue;
|
||||
|
||||
// don't re-validate twins
|
||||
if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
|
||||
seen_edges.insert(&*edge);
|
||||
seen_edges.insert(edge->twin());
|
||||
|
||||
if (!this->validate_edge(&*edge)) continue;
|
||||
this->valid_edges.insert(&*edge);
|
||||
this->valid_edges.insert(edge->twin());
|
||||
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});
|
||||
for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); edge += 2)
|
||||
if (edge->is_primary() && edge->is_finite() &&
|
||||
(Voronoi::vertex_category(edge->vertex0()) == Voronoi::VertexCategory::Inside ||
|
||||
Voronoi::vertex_category(edge->vertex1()) == Voronoi::VertexCategory::Inside) &&
|
||||
this->validate_edge(&*edge)) {
|
||||
// Valid skeleton edge.
|
||||
this->edge_data(*edge).first.active = true;
|
||||
}
|
||||
}
|
||||
this->edges = this->valid_edges;
|
||||
|
||||
// iterate through the valid edges to build polylines
|
||||
while (!this->edges.empty()) {
|
||||
const edge_t* edge = *this->edges.begin();
|
||||
ThickPolyline reverse_polyline;
|
||||
for (VD::const_edge_iterator seed_edge = m_vd.edges().begin(); seed_edge != m_vd.edges().end(); seed_edge += 2)
|
||||
if (EdgeData &seed_edge_data = this->edge_data(*seed_edge).first; seed_edge_data.active) {
|
||||
// Mark this edge as visited.
|
||||
seed_edge_data.active = false;
|
||||
|
||||
// Start a polyline.
|
||||
ThickPolyline polyline;
|
||||
polyline.points.emplace_back(seed_edge->vertex0()->x(), seed_edge->vertex0()->y());
|
||||
polyline.points.emplace_back(seed_edge->vertex1()->x(), seed_edge->vertex1()->y());
|
||||
polyline.width.emplace_back(seed_edge_data.width_start);
|
||||
polyline.width.emplace_back(seed_edge_data.width_end);
|
||||
// Grow the polyline in a forward direction.
|
||||
this->process_edge_neighbors(&*seed_edge, &polyline);
|
||||
assert(polyline.width.size() == polyline.points.size() * 2 - 2);
|
||||
|
||||
// start a polyline
|
||||
ThickPolyline polyline;
|
||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||
polyline.width.push_back(this->thickness[edge].first);
|
||||
polyline.width.push_back(this->thickness[edge].second);
|
||||
// Grow the polyline in a backward direction.
|
||||
reverse_polyline.clear();
|
||||
this->process_edge_neighbors(seed_edge->twin(), &reverse_polyline);
|
||||
polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend());
|
||||
polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend());
|
||||
polyline.endpoints.first = reverse_polyline.endpoints.second;
|
||||
assert(polyline.width.size() == polyline.points.size() * 2 - 2);
|
||||
|
||||
// remove this edge and its twin from the available edges
|
||||
(void)this->edges.erase(edge);
|
||||
(void)this->edges.erase(edge->twin());
|
||||
|
||||
// get next points
|
||||
this->process_edge_neighbors(edge, &polyline);
|
||||
|
||||
// get previous points
|
||||
{
|
||||
ThickPolyline rpolyline;
|
||||
this->process_edge_neighbors(edge->twin(), &rpolyline);
|
||||
polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
|
||||
polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
|
||||
polyline.endpoints.first = rpolyline.endpoints.second;
|
||||
// Prevent loop endpoints from being extended.
|
||||
if (polyline.first_point() == polyline.last_point()) {
|
||||
polyline.endpoints.first = false;
|
||||
polyline.endpoints.second = false;
|
||||
}
|
||||
|
||||
// Append polyline to result.
|
||||
polylines->emplace_back(std::move(polyline));
|
||||
}
|
||||
|
||||
assert(polyline.width.size() == polyline.points.size()*2 - 2);
|
||||
|
||||
// prevent loop endpoints from being extended
|
||||
if (polyline.first_point() == polyline.last_point()) {
|
||||
polyline.endpoints.first = false;
|
||||
polyline.endpoints.second = false;
|
||||
}
|
||||
|
||||
// append polyline to result
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
|
||||
dump_voronoi_to_svg(m_lines, m_vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
|
||||
printf("Thick lines: ");
|
||||
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
|
||||
ThickLines lines = it->thicklines();
|
||||
|
|
@ -542,56 +531,68 @@ MedialAxis::build(ThickPolylines* polylines)
|
|||
#endif /* SLIC3R_DEBUG */
|
||||
}
|
||||
|
||||
void
|
||||
MedialAxis::build(Polylines* polylines)
|
||||
void MedialAxis::build(Polylines* polylines)
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->build(&tp);
|
||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||
polylines->reserve(polylines->size() + tp.size());
|
||||
for (auto &pl : tp)
|
||||
polylines->emplace_back(pl.points);
|
||||
}
|
||||
|
||||
void
|
||||
MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
|
||||
void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline)
|
||||
{
|
||||
while (true) {
|
||||
for (;;) {
|
||||
// Since rot_next() works on the edge starting point but we want
|
||||
// to find neighbors on the ending point, we just swap edge with
|
||||
// its twin.
|
||||
const VD::edge_type* twin = edge->twin();
|
||||
const VD::edge_type *twin = edge->twin();
|
||||
|
||||
// count neighbors for this edge
|
||||
std::vector<const VD::edge_type*> neighbors;
|
||||
for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
|
||||
neighbor = neighbor->rot_next()) {
|
||||
if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
|
||||
}
|
||||
size_t num_neighbors = 0;
|
||||
const VD::edge_type *first_neighbor = nullptr;
|
||||
for (const VD::edge_type *neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next())
|
||||
if (this->edge_data(*neighbor).first.active) {
|
||||
if (num_neighbors == 0)
|
||||
first_neighbor = neighbor;
|
||||
++ num_neighbors;
|
||||
}
|
||||
|
||||
// if we have a single neighbor then we can continue recursively
|
||||
if (neighbors.size() == 1) {
|
||||
const VD::edge_type* neighbor = neighbors.front();
|
||||
|
||||
// break if this is a closed loop
|
||||
if (this->edges.count(neighbor) == 0) return;
|
||||
|
||||
Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
|
||||
polyline->points.push_back(new_point);
|
||||
polyline->width.push_back(this->thickness[neighbor].first);
|
||||
polyline->width.push_back(this->thickness[neighbor].second);
|
||||
(void)this->edges.erase(neighbor);
|
||||
(void)this->edges.erase(neighbor->twin());
|
||||
edge = neighbor;
|
||||
} else if (neighbors.size() == 0) {
|
||||
if (num_neighbors == 1) {
|
||||
if (std::pair<EdgeData&, bool> neighbor_data = this->edge_data(*first_neighbor);
|
||||
neighbor_data.first.active) {
|
||||
neighbor_data.first.active = false;
|
||||
polyline->points.emplace_back(first_neighbor->vertex1()->x(), first_neighbor->vertex1()->y());
|
||||
if (neighbor_data.second) {
|
||||
polyline->width.push_back(neighbor_data.first.width_end);
|
||||
polyline->width.push_back(neighbor_data.first.width_start);
|
||||
} else {
|
||||
polyline->width.push_back(neighbor_data.first.width_start);
|
||||
polyline->width.push_back(neighbor_data.first.width_end);
|
||||
}
|
||||
edge = first_neighbor;
|
||||
// Continue chaining.
|
||||
continue;
|
||||
}
|
||||
} else if (num_neighbors == 0) {
|
||||
polyline->endpoints.second = true;
|
||||
return;
|
||||
} else {
|
||||
// T-shaped or star-shaped joint
|
||||
return;
|
||||
// T-shaped or star-shaped joint
|
||||
}
|
||||
// Stop chaining.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
||||
{
|
||||
auto retrieve_segment = [this](const VD::cell_type* cell) -> const Line& { return m_lines[cell->source_index()]; };
|
||||
auto retrieve_endpoint = [retrieve_segment](const VD::cell_type* cell) -> const Point& {
|
||||
const Line &line = retrieve_segment(cell);
|
||||
return cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT ? line.a : line.b;
|
||||
};
|
||||
|
||||
// prevent overflows and detect almost-infinite edges
|
||||
#ifndef CLIPPERLIB_INT32
|
||||
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
|
|
@ -602,32 +603,18 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
|||
#endif // CLIPPERLIB_INT32
|
||||
|
||||
// construct the line representing this edge of the Voronoi diagram
|
||||
const Line line(
|
||||
Point( edge->vertex0()->x(), edge->vertex0()->y() ),
|
||||
Point( edge->vertex1()->x(), edge->vertex1()->y() )
|
||||
);
|
||||
|
||||
// discard edge if it lies outside the supplied shape
|
||||
// this could maybe be optimized (checking inclusion of the endpoints
|
||||
// might give false positives as they might belong to the contour itself)
|
||||
if (this->expolygon != NULL) {
|
||||
if (line.a == line.b) {
|
||||
// in this case, contains(line) returns a false positive
|
||||
if (!this->expolygon->contains(line.a)) return false;
|
||||
} else {
|
||||
if (!this->expolygon->contains(line)) return false;
|
||||
}
|
||||
}
|
||||
const Line line({ edge->vertex0()->x(), edge->vertex0()->y() },
|
||||
{ edge->vertex1()->x(), edge->vertex1()->y() });
|
||||
|
||||
// retrieve the original line segments which generated the edge we're checking
|
||||
const VD::cell_type* cell_l = edge->cell();
|
||||
const VD::cell_type* cell_r = edge->twin()->cell();
|
||||
const Line &segment_l = this->retrieve_segment(cell_l);
|
||||
const Line &segment_r = this->retrieve_segment(cell_r);
|
||||
const Line &segment_l = retrieve_segment(cell_l);
|
||||
const Line &segment_r = retrieve_segment(cell_r);
|
||||
|
||||
/*
|
||||
SVG svg("edge.svg");
|
||||
svg.draw(*this->expolygon);
|
||||
svg.draw(m_expolygon);
|
||||
svg.draw(line);
|
||||
svg.draw(segment_l, "red");
|
||||
svg.draw(segment_r, "blue");
|
||||
|
|
@ -651,62 +638,48 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
|||
|
||||
coordf_t w0 = cell_r->contains_segment()
|
||||
? segment_r.distance_to(line.a)*2
|
||||
: (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
|
||||
: (retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
|
||||
|
||||
coordf_t w1 = cell_l->contains_segment()
|
||||
? segment_l.distance_to(line.b)*2
|
||||
: (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
|
||||
: (retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
|
||||
|
||||
if (cell_l->contains_segment() && cell_r->contains_segment()) {
|
||||
// calculate the relative angle between the two boundary segments
|
||||
double angle = fabs(segment_r.orientation() - segment_l.orientation());
|
||||
if (angle > PI) angle = 2*PI - angle;
|
||||
if (angle > PI)
|
||||
angle = 2. * PI - angle;
|
||||
assert(angle >= 0 && angle <= PI);
|
||||
|
||||
|
||||
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
|
||||
// we're interested only in segments close to the second case (facing segments)
|
||||
// so we allow some tolerance.
|
||||
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
|
||||
// we don't run it on edges not generated by two segments (thus generated by one segment
|
||||
// and the endpoint of another segment), since their orientation would not be meaningful
|
||||
if (PI - angle > PI/8) {
|
||||
if (PI - angle > PI / 8.) {
|
||||
// angle is not narrow enough
|
||||
|
||||
// only apply this filter to segments that are not too short otherwise their
|
||||
// angle could possibly be not meaningful
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= m_min_width)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON)
|
||||
return false;
|
||||
}
|
||||
//BBS
|
||||
if (w0 < this->min_width || w1 < this->min_width)
|
||||
return false;
|
||||
//BBS
|
||||
if (w0 > this->max_width || w1 > this->max_width)
|
||||
return false;
|
||||
|
||||
this->thickness[edge] = std::make_pair(w0, w1);
|
||||
this->thickness[edge->twin()] = std::make_pair(w1, w0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const
|
||||
{
|
||||
return this->lines[cell->source_index()];
|
||||
}
|
||||
|
||||
const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
|
||||
{
|
||||
const Line& line = this->retrieve_segment(cell);
|
||||
if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
return line.a;
|
||||
} else {
|
||||
return line.b;
|
||||
if ((w0 >= m_min_width || w1 >= m_min_width) &&
|
||||
(w0 <= m_max_width || w1 <= m_max_width)) {
|
||||
std::pair<EdgeData&, bool> ed = this->edge_data(*edge);
|
||||
if (ed.second)
|
||||
std::swap(w0, w1);
|
||||
ed.first.width_start = w0;
|
||||
ed.first.width_end = w1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} } // namespace Slicer::Geometry
|
||||
|
|
|
|||
|
|
@ -4,30 +4,43 @@
|
|||
#include "Voronoi.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
class MedialAxis {
|
||||
public:
|
||||
Lines lines;
|
||||
const ExPolygon* expolygon;
|
||||
double max_width;
|
||||
double min_width;
|
||||
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
|
||||
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
|
||||
MedialAxis(double min_width, double max_width, const ExPolygon &expolygon);
|
||||
void build(ThickPolylines* polylines);
|
||||
void build(Polylines* polylines);
|
||||
|
||||
private:
|
||||
// Input
|
||||
const ExPolygon &m_expolygon;
|
||||
Lines m_lines;
|
||||
// for filtering of the skeleton edges
|
||||
double m_min_width;
|
||||
double m_max_width;
|
||||
|
||||
// Voronoi Diagram.
|
||||
using VD = VoronoiDiagram;
|
||||
VD vd;
|
||||
std::set<const VD::edge_type*> edges, valid_edges;
|
||||
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
|
||||
VD m_vd;
|
||||
|
||||
// Annotations of the VD skeleton edges.
|
||||
struct EdgeData {
|
||||
bool active { false };
|
||||
double width_start { 0 };
|
||||
double width_end { 0 };
|
||||
};
|
||||
// Returns a reference to EdgeData and a "reversed" boolean.
|
||||
std::pair<EdgeData&, bool> edge_data(const VD::edge_type &edge) {
|
||||
size_t edge_id = &edge - &m_vd.edges().front();
|
||||
return { m_edge_data[edge_id / 2], (edge_id & 1) != 0 };
|
||||
}
|
||||
std::vector<EdgeData> m_edge_data;
|
||||
|
||||
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
|
||||
bool validate_edge(const VD::edge_type* edge);
|
||||
const Line& retrieve_segment(const VD::cell_type* cell) const;
|
||||
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
|
||||
};
|
||||
|
||||
} } // namespace Slicer::Geometry
|
||||
} // namespace Slicer::Geometry
|
||||
|
||||
#endif // slic3r_Geometry_MedialAxis_hpp_
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
#include "Flow.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
class Layer;
|
||||
using LayerPtrs = std::vector<Layer*>;
|
||||
class LayerRegion;
|
||||
|
|
@ -30,6 +32,8 @@ public:
|
|||
const Layer* layer() const { return m_layer; }
|
||||
const PrintRegion& region() const { return *m_region; }
|
||||
|
||||
const SurfaceCollection& get_slices() const { return slices; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
SurfaceCollection slices;
|
||||
|
|
@ -193,7 +197,7 @@ public:
|
|||
protected:
|
||||
friend class PrintObject;
|
||||
friend std::vector<Layer*> new_layers(PrintObject*, const std::vector<coordf_t>&);
|
||||
friend std::string fix_slicing_errors(PrintObject* object, LayerPtrs&, const std::function<void()>&);
|
||||
friend std::string fix_slicing_errors(PrintObject* object, LayerPtrs&, const std::function<void()>&, int &);
|
||||
|
||||
Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
|
||||
upper_layer(nullptr), lower_layer(nullptr), slicing_errors(false),
|
||||
|
|
@ -224,7 +228,7 @@ class SupportLayer : public Layer
|
|||
public:
|
||||
// Polygons covered by the supports: base, interface and contact areas.
|
||||
// Used to suppress retraction if moving for a support extrusion over these support_islands.
|
||||
ExPolygonCollection support_islands;
|
||||
ExPolygons support_islands;
|
||||
// Extrusion paths for the support base and for the support interface and contacts.
|
||||
ExtrusionEntityCollection support_fills;
|
||||
SupportInnerType support_type = stInnerNormal;
|
||||
|
|
@ -265,6 +269,7 @@ protected:
|
|||
ExPolygon *area;
|
||||
int type;
|
||||
coordf_t dist_to_top; // mm dist to top
|
||||
bool need_infill = false;
|
||||
AreaGroup(ExPolygon *a, int t, coordf_t d) : area(a), type(t), dist_to_top(d) {}
|
||||
};
|
||||
enum OverhangType { Detected = 0, Enforced };
|
||||
|
|
|
|||
|
|
@ -311,6 +311,15 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||
// would get merged into a single one while they need different directions
|
||||
// also, supply the original expolygon instead of the grown one, because in case
|
||||
// of very thin (but still working) anchors, the grown expolygon would go beyond them
|
||||
double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value);
|
||||
if (custom_angle > 0.0) {
|
||||
bridges[idx_last].bridge_angle = custom_angle;
|
||||
} else {
|
||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices));
|
||||
bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||
}
|
||||
|
||||
/*
|
||||
BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill, object_config.thick_bridges).scaled_width());
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf("Processing bridge at layer %zu:\n", this->layer()->id());
|
||||
|
|
@ -330,6 +339,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||
// using a bridging flow, therefore it makes sense to respect the custom bridging direction.
|
||||
bridges[idx_last].bridge_angle = custom_angle;
|
||||
}
|
||||
*/
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
surfaces_append(bottom, union_safety_offset_ex(grown), bridges[idx_last]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,14 @@ bool Line::intersection_infinite(const Line &other, Point* point) const
|
|||
if (std::fabs(denom) < EPSILON)
|
||||
return false;
|
||||
double t1 = cross2(v12, v2) / denom;
|
||||
*point = (a1 + t1 * v1).cast<coord_t>();
|
||||
Vec2d result = (a1 + t1 * v1);
|
||||
if (result.x() > std::numeric_limits<coord_t>::max() || result.x() < std::numeric_limits<coord_t>::lowest() ||
|
||||
result.y() > std::numeric_limits<coord_t>::max() || result.y() < std::numeric_limits<coord_t>::lowest()) {
|
||||
// Intersection has at least one of the coordinates much bigger (or smaller) than coord_t maximum value (or minimum).
|
||||
// So it can not be stored into the Point without integer overflows. That could mean that input lines are parallel or near parallel.
|
||||
return false;
|
||||
}
|
||||
*point = (result).cast<coord_t>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -84,28 +91,7 @@ bool Line::perpendicular_to(const Line& line) const
|
|||
|
||||
bool Line::intersection(const Line &l2, Point *intersection) const
|
||||
{
|
||||
const Line &l1 = *this;
|
||||
const Vec2d v1 = (l1.b - l1.a).cast<double>();
|
||||
const Vec2d v2 = (l2.b - l2.a).cast<double>();
|
||||
double denom = cross2(v1, v2);
|
||||
if (fabs(denom) < EPSILON)
|
||||
#if 0
|
||||
// Lines are collinear. Return true if they are coincident (overlappign).
|
||||
return ! (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
const Vec2d v12 = (l1.a - l2.a).cast<double>();
|
||||
double nume_a = cross2(v2, v12);
|
||||
double nume_b = cross2(v1, v12);
|
||||
double t1 = nume_a / denom;
|
||||
double t2 = nume_b / denom;
|
||||
if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) {
|
||||
// Get the intersection point.
|
||||
(*intersection) = (l1.a.cast<double>() + t1 * v1).cast<coord_t>();
|
||||
return true;
|
||||
}
|
||||
return false; // not intersecting
|
||||
return line_alg::intersection(*this, l2, intersection);
|
||||
}
|
||||
|
||||
bool Line::clip_with_bbox(const BoundingBox &bbox)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#ifndef slic3r_Line_hpp_
|
||||
#define slic3r_Line_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
|
|
@ -22,201 +22,175 @@ Linef3 transform(const Linef3& line, const Transform3d& t);
|
|||
|
||||
namespace line_alg {
|
||||
|
||||
template <class L, class En = void>
|
||||
struct Traits {
|
||||
static constexpr int Dim = L::Dim;
|
||||
using Scalar = typename L::Scalar;
|
||||
template<class L, class En = void> struct Traits {
|
||||
static constexpr int Dim = L::Dim;
|
||||
using Scalar = typename L::Scalar;
|
||||
|
||||
static Vec<Dim, Scalar>& get_a(L& l) { return l.a; }
|
||||
static Vec<Dim, Scalar>& get_b(L& l) { return l.b; }
|
||||
static const Vec<Dim, Scalar>& get_a(const L& l) { return l.a; }
|
||||
static const Vec<Dim, Scalar>& get_b(const L& l) { return l.b; }
|
||||
};
|
||||
static Vec<Dim, Scalar>& get_a(L &l) { return l.a; }
|
||||
static Vec<Dim, Scalar>& get_b(L &l) { return l.b; }
|
||||
static const Vec<Dim, Scalar>& get_a(const L &l) { return l.a; }
|
||||
static const Vec<Dim, Scalar>& get_b(const L &l) { return l.b; }
|
||||
};
|
||||
|
||||
template <class L>
|
||||
const constexpr int Dim = Traits<remove_cvref_t<L>>::Dim;
|
||||
template <class L>
|
||||
using Scalar = typename Traits<remove_cvref_t<L>>::Scalar;
|
||||
template<class L> const constexpr int Dim = Traits<remove_cvref_t<L>>::Dim;
|
||||
template<class L> using Scalar = typename Traits<remove_cvref_t<L>>::Scalar;
|
||||
|
||||
template <class L>
|
||||
auto get_a(L&& l) { return Traits<remove_cvref_t<L>>::get_a(l); }
|
||||
template <class L>
|
||||
auto get_b(L&& l) { return Traits<remove_cvref_t<L>>::get_b(l); }
|
||||
template<class L> auto get_a(L &&l) { return Traits<remove_cvref_t<L>>::get_a(l); }
|
||||
template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l); }
|
||||
|
||||
// Distance to the closest point of line.
|
||||
template <class L>
|
||||
double distance_to_squared(const L& line, const Vec<Dim<L>, Scalar<L>>& point, Vec<Dim<L>, Scalar<L>>* nearest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
if (l2 == 0.0) {
|
||||
// a == b case
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
if (t <= 0.0) {
|
||||
// beyond the 'a' end of the segment
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
} else if (t >= 1.0) {
|
||||
// beyond the 'b' end of the segment
|
||||
*nearest_point = get_b(line);
|
||||
return (point - get_b(line)).template cast<double>().squaredNorm();
|
||||
}
|
||||
|
||||
*nearest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
// Distance to the closest point of line.
|
||||
template<class L>
|
||||
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
if (l2 == 0.0) {
|
||||
// a == b case
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
if (t <= 0.0) {
|
||||
// beyond the 'a' end of the segment
|
||||
*nearest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
} else if (t >= 1.0) {
|
||||
// beyond the 'b' end of the segment
|
||||
*nearest_point = get_b(line);
|
||||
return (point - get_b(line)).template cast<double>().squaredNorm();
|
||||
}
|
||||
|
||||
// Distance to the closest point of line.
|
||||
template <class L>
|
||||
double distance_to_squared(const L& line, const Vec<Dim<L>, Scalar<L>>& point)
|
||||
{
|
||||
Vec<Dim<L>, Scalar<L>> nearest_point;
|
||||
return distance_to_squared<L>(line, point, &nearest_point);
|
||||
}
|
||||
*nearest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
}
|
||||
|
||||
template <class L>
|
||||
double distance_to(const L& line, const Vec<Dim<L>, Scalar<L>>& point)
|
||||
{
|
||||
return std::sqrt(distance_to_squared(line, point));
|
||||
}
|
||||
// Distance to the closest point of line.
|
||||
template<class L>
|
||||
double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
Vec<Dim<L>, Scalar<L>> nearest_point;
|
||||
return distance_to_squared<L>(line, point, &nearest_point);
|
||||
}
|
||||
|
||||
// Returns a squared distance to the closest point on the infinite.
|
||||
// Returned nearest_point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template <class L>
|
||||
double distance_to_infinite_squared(const L& line, const Vec<Dim<L>, Scalar<L>>& point, Vec<Dim<L>, Scalar<L>>* closest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
if (l2 == 0.) {
|
||||
// a == b case
|
||||
*closest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
*closest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
}
|
||||
template<class L>
|
||||
double distance_to(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
return std::sqrt(distance_to_squared(line, point));
|
||||
}
|
||||
|
||||
// Returns a squared distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template <class L>
|
||||
double distance_to_infinite_squared(const L& line, const Vec<Dim<L>, Scalar<L>>& point)
|
||||
{
|
||||
Vec<Dim<L>, Scalar<L>> nearest_point;
|
||||
return distance_to_infinite_squared<L>(line, point, &nearest_point);
|
||||
// Returns a squared distance to the closest point on the infinite.
|
||||
// Returned nearest_point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template<class L>
|
||||
double distance_to_infinite_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *closest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
if (l2 == 0.) {
|
||||
// a == b case
|
||||
*closest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
*closest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
}
|
||||
|
||||
// Returns a distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template <class L>
|
||||
double distance_to_infinite(const L& line, const Vec<Dim<L>, Scalar<L>>& point)
|
||||
{
|
||||
return std::sqrt(distance_to_infinite_squared(line, point));
|
||||
}
|
||||
// Returns a squared distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template<class L>
|
||||
double distance_to_infinite_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
Vec<Dim<L>, Scalar<L>> nearest_point;
|
||||
return distance_to_infinite_squared<L>(line, point, &nearest_point);
|
||||
}
|
||||
|
||||
template <class L>
|
||||
bool intersection(const L& l1, const L& l2, Vec<Dim<L>, Scalar<L>>* intersection_pt)
|
||||
{
|
||||
using Floating = typename std::conditional<std::is_floating_point<Scalar<L>>::value, Scalar<L>, double>::type;
|
||||
using VecType = const Vec<Dim<L>, Floating>;
|
||||
const VecType v1 = (l1.b - l1.a).template cast<Floating>();
|
||||
const VecType v2 = (l2.b - l2.a).template cast<Floating>();
|
||||
Floating denom = cross2(v1, v2);
|
||||
if (fabs(denom) < EPSILON)
|
||||
// Returns a distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template<class L>
|
||||
double distance_to_infinite(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
return std::sqrt(distance_to_infinite_squared(line, point));
|
||||
}
|
||||
|
||||
template<class L> bool intersection(const L &l1, const L &l2, Vec<Dim<L>, Scalar<L>> *intersection_pt)
|
||||
{
|
||||
using Floating = typename std::conditional<std::is_floating_point<Scalar<L>>::value, Scalar<L>, double>::type;
|
||||
using VecType = const Vec<Dim<L>, Floating>;
|
||||
const VecType v1 = (l1.b - l1.a).template cast<Floating>();
|
||||
const VecType v2 = (l2.b - l2.a).template cast<Floating>();
|
||||
Floating denom = cross2(v1, v2);
|
||||
if (fabs(denom) < EPSILON)
|
||||
#if 0
|
||||
// Lines are collinear. Return true if they are coincident (overlappign).
|
||||
return ! (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON);
|
||||
#else
|
||||
return false;
|
||||
return false;
|
||||
#endif
|
||||
const VecType v12 = (l1.a - l2.a).template cast<Floating>();
|
||||
Floating nume_a = cross2(v2, v12);
|
||||
Floating nume_b = cross2(v1, v12);
|
||||
Floating t1 = nume_a / denom;
|
||||
Floating t2 = nume_b / denom;
|
||||
if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) {
|
||||
// Get the intersection point.
|
||||
(*intersection_pt) = (l1.a.template cast<Floating>() + t1 * v1).template cast<Scalar<L>>();
|
||||
return true;
|
||||
}
|
||||
return false; // not intersecting
|
||||
const VecType v12 = (l1.a - l2.a).template cast<Floating>();
|
||||
Floating nume_a = cross2(v2, v12);
|
||||
Floating nume_b = cross2(v1, v12);
|
||||
Floating t1 = nume_a / denom;
|
||||
Floating t2 = nume_b / denom;
|
||||
if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) {
|
||||
// Get the intersection point.
|
||||
(*intersection_pt) = (l1.a.template cast<Floating>() + t1 * v1).template cast<Scalar<L>>();
|
||||
return true;
|
||||
}
|
||||
return false; // not intersecting
|
||||
}
|
||||
|
||||
} // namespace line_alg
|
||||
|
||||
class Line {
|
||||
class Line
|
||||
{
|
||||
public:
|
||||
Line() { }
|
||||
Line(const Point& _a, const Point& _b)
|
||||
: a(_a)
|
||||
, b(_b)
|
||||
{
|
||||
}
|
||||
explicit operator Lines() const
|
||||
{
|
||||
Lines lines;
|
||||
lines.emplace_back(*this);
|
||||
return lines;
|
||||
}
|
||||
void scale(double factor)
|
||||
{
|
||||
this->a *= factor;
|
||||
this->b *= factor;
|
||||
}
|
||||
void translate(const Point& v)
|
||||
{
|
||||
this->a += v;
|
||||
this->b += v;
|
||||
}
|
||||
void translate(double x, double y) { this->translate(Point(x, y)); }
|
||||
void rotate(double angle, const Point& center)
|
||||
{
|
||||
this->a.rotate(angle, center);
|
||||
this->b.rotate(angle, center);
|
||||
}
|
||||
void reverse() { std::swap(this->a, this->b); }
|
||||
Line() {}
|
||||
Line(const Point& _a, const Point& _b) : a(_a), b(_b) {}
|
||||
explicit operator Lines() const { Lines lines; lines.emplace_back(*this); return lines; }
|
||||
void scale(double factor) { this->a *= factor; this->b *= factor; }
|
||||
void translate(const Point &v) { this->a += v; this->b += v; }
|
||||
void translate(double x, double y) { this->translate(Point(x, y)); }
|
||||
void rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); }
|
||||
void reverse() { std::swap(this->a, this->b); }
|
||||
double length() const { return (b - a).cast<double>().norm(); }
|
||||
Point midpoint() const { return (this->a + this->b) / 2; }
|
||||
bool intersection_infinite(const Line& other, Point* point) const;
|
||||
bool operator==(const Line& rhs) const { return this->a == rhs.a && this->b == rhs.b; }
|
||||
double distance_to_squared(const Point& point) const { return distance_to_squared(point, this->a, this->b); }
|
||||
double distance_to_squared(const Point& point, Point* closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); }
|
||||
double distance_to(const Point& point) const { return distance_to(point, this->a, this->b); }
|
||||
double distance_to_infinite_squared(const Point& point, Point* closest_point) const { return line_alg::distance_to_infinite_squared(*this, point, closest_point); }
|
||||
double perp_distance_to(const Point& point) const;
|
||||
bool parallel_to(double angle) const;
|
||||
bool parallel_to(const Line& line) const;
|
||||
bool perpendicular_to(double angle) const;
|
||||
bool perpendicular_to(const Line& line) const;
|
||||
Point midpoint() const { return (this->a + this->b) / 2; }
|
||||
bool intersection_infinite(const Line &other, Point* point) const;
|
||||
bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; }
|
||||
double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); }
|
||||
double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); }
|
||||
double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); }
|
||||
double distance_to_infinite_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_infinite_squared(*this, point, closest_point); }
|
||||
double perp_distance_to(const Point &point) const;
|
||||
bool parallel_to(double angle) const;
|
||||
bool parallel_to(const Line& line) const;
|
||||
bool perpendicular_to(double angle) const;
|
||||
bool perpendicular_to(const Line& line) const;
|
||||
double atan2_() const { return atan2(this->b(1) - this->a(1), this->b(0) - this->a(0)); }
|
||||
double orientation() const;
|
||||
double direction() const;
|
||||
Vector vector() const { return this->b - this->a; }
|
||||
Vector normal() const { return Vector((this->b(1) - this->a(1)), -(this->b(0) - this->a(0))); }
|
||||
bool intersection(const Line& line, Point* intersection) const;
|
||||
bool intersection(const Line& line, Point* intersection) const;
|
||||
// Clip a line with a bounding box. Returns false if the line is completely outside of the bounding box.
|
||||
bool clip_with_bbox(const BoundingBox& bbox);
|
||||
bool clip_with_bbox(const BoundingBox &bbox);
|
||||
// Extend the line from both sides by an offset.
|
||||
void extend(double offset);
|
||||
void extend(double offset);
|
||||
|
||||
static inline double distance_to_squared(const Point& point, const Point& a, const Point& b) { return line_alg::distance_to_squared(Line { a, b }, Vec<2, coord_t> { point }); }
|
||||
static double distance_to(const Point& point, const Point& a, const Point& b) { return sqrt(distance_to_squared(point, a, b)); }
|
||||
static inline double distance_to_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_squared(Line{a, b}, Vec<2, coord_t>{point}); }
|
||||
static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); }
|
||||
|
||||
// Returns a distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
static inline double distance_to_infinite_squared(const Point& point, const Point& a, const Point& b) { return line_alg::distance_to_infinite_squared(Line { a, b }, Vec<2, coord_t> { point }); }
|
||||
static double distance_to_infinite(const Point& point, const Point& a, const Point& b) { return sqrt(distance_to_infinite_squared(point, a, b)); }
|
||||
static inline double distance_to_infinite_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_infinite_squared(Line{a, b}, Vec<2, coord_t>{point}); }
|
||||
static double distance_to_infinite(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_infinite_squared(point, a, b)); }
|
||||
|
||||
Point a;
|
||||
Point b;
|
||||
|
|
@ -225,43 +199,23 @@ public:
|
|||
using Scalar = Point::Scalar;
|
||||
};
|
||||
|
||||
class ThickLine : public Line {
|
||||
class ThickLine : public Line
|
||||
{
|
||||
public:
|
||||
ThickLine()
|
||||
: a_width(0)
|
||||
, b_width(0)
|
||||
{
|
||||
}
|
||||
ThickLine(const Point& a, const Point& b)
|
||||
: Line(a, b)
|
||||
, a_width(0)
|
||||
, b_width(0)
|
||||
{
|
||||
}
|
||||
ThickLine(const Point& a, const Point& b, double wa, double wb)
|
||||
: Line(a, b)
|
||||
, a_width(wa)
|
||||
, b_width(wb)
|
||||
{
|
||||
}
|
||||
ThickLine() : a_width(0), b_width(0) {}
|
||||
ThickLine(const Point& a, const Point& b) : Line(a, b), a_width(0), b_width(0) {}
|
||||
ThickLine(const Point& a, const Point& b, double wa, double wb) : Line(a, b), a_width(wa), b_width(wb) {}
|
||||
|
||||
double a_width, b_width;
|
||||
};
|
||||
|
||||
class Line3 {
|
||||
class Line3
|
||||
{
|
||||
public:
|
||||
Line3()
|
||||
: a(Vec3crd::Zero())
|
||||
, b(Vec3crd::Zero())
|
||||
{
|
||||
}
|
||||
Line3(const Vec3crd& _a, const Vec3crd& _b)
|
||||
: a(_a)
|
||||
, b(_b)
|
||||
{
|
||||
}
|
||||
Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {}
|
||||
Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {}
|
||||
|
||||
double length() const { return (this->a - this->b).cast<double>().norm(); }
|
||||
double length() const { return (this->a - this->b).cast<double>().norm(); }
|
||||
Vec3crd vector() const { return this->b - this->a; }
|
||||
|
||||
Vec3crd a;
|
||||
|
|
@ -271,18 +225,11 @@ public:
|
|||
using Scalar = Vec3crd::Scalar;
|
||||
};
|
||||
|
||||
class Linef {
|
||||
class Linef
|
||||
{
|
||||
public:
|
||||
Linef()
|
||||
: a(Vec2d::Zero())
|
||||
, b(Vec2d::Zero())
|
||||
{
|
||||
}
|
||||
Linef(const Vec2d& _a, const Vec2d& _b)
|
||||
: a(_a)
|
||||
, b(_b)
|
||||
{
|
||||
}
|
||||
Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
|
||||
Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {}
|
||||
|
||||
Vec2d a;
|
||||
Vec2d b;
|
||||
|
|
@ -292,28 +239,17 @@ public:
|
|||
};
|
||||
using Linesf = std::vector<Linef>;
|
||||
|
||||
class Linef3 {
|
||||
class Linef3
|
||||
{
|
||||
public:
|
||||
Linef3()
|
||||
: a(Vec3d::Zero())
|
||||
, b(Vec3d::Zero())
|
||||
{
|
||||
}
|
||||
Linef3(const Vec3d& _a, const Vec3d& _b)
|
||||
: a(_a)
|
||||
, b(_b)
|
||||
{
|
||||
}
|
||||
Linef3() : a(Vec3d::Zero()), b(Vec3d::Zero()) {}
|
||||
Linef3(const Vec3d& _a, const Vec3d& _b) : a(_a), b(_b) {}
|
||||
|
||||
Vec3d intersect_plane(double z) const;
|
||||
void scale(double factor)
|
||||
{
|
||||
this->a *= factor;
|
||||
this->b *= factor;
|
||||
}
|
||||
Vec3d vector() const { return this->b - this->a; }
|
||||
Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); }
|
||||
double length() const { return vector().norm(); }
|
||||
Vec3d intersect_plane(double z) const;
|
||||
void scale(double factor) { this->a *= factor; this->b *= factor; }
|
||||
Vec3d vector() const { return this->b - this->a; }
|
||||
Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); }
|
||||
double length() const { return vector().norm(); }
|
||||
|
||||
Vec3d a;
|
||||
Vec3d b;
|
||||
|
|
@ -322,31 +258,26 @@ public:
|
|||
using Scalar = Vec3d::Scalar;
|
||||
};
|
||||
|
||||
BoundingBox get_extents(const Lines& lines);
|
||||
BoundingBox get_extents(const Lines &lines);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// start Boost
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
namespace boost {
|
||||
namespace polygon {
|
||||
namespace boost { namespace polygon {
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::Line> {
|
||||
typedef segment_concept type;
|
||||
};
|
||||
struct geometry_concept<Slic3r::Line> { typedef segment_concept type; };
|
||||
|
||||
template <>
|
||||
struct segment_traits<Slic3r::Line> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
|
||||
static inline point_type get(const Slic3r::Line& line, direction_1d dir)
|
||||
{
|
||||
|
||||
static inline point_type get(const Slic3r::Line& line, direction_1d dir) {
|
||||
return dir.to_int() ? line.b : line.a;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} }
|
||||
// end Boost
|
||||
|
||||
#endif // slic3r_Line_hpp_
|
||||
#endif // slic3r_Line_hpp_
|
||||
|
|
@ -70,6 +70,7 @@ Model& Model::assign_copy(const Model &rhs)
|
|||
// BBS: for design info
|
||||
this->design_info = rhs.design_info;
|
||||
this->model_info = rhs.model_info;
|
||||
this->profile_info = rhs.profile_info;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -104,6 +105,8 @@ Model& Model::assign_copy(Model &&rhs)
|
|||
rhs.design_info.reset();
|
||||
this->model_info = rhs.model_info;
|
||||
rhs.model_info.reset();
|
||||
this->profile_info = rhs.profile_info;
|
||||
rhs.profile_info.reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -667,8 +670,21 @@ bool Model::looks_like_imperial_units() const
|
|||
return false;
|
||||
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (obj->get_object_stl_stats().volume < volume_threshold_inches)
|
||||
return true;
|
||||
if (obj->get_object_stl_stats().volume < volume_threshold_inches) {
|
||||
if (!obj->is_cut())
|
||||
return true;
|
||||
bool all_cut_parts_look_like_imperial_units = true;
|
||||
for (ModelObject* obj_other : this->objects) {
|
||||
if (obj_other == obj)
|
||||
continue;
|
||||
if (obj_other->cut_id.is_equal(obj->cut_id) && obj_other->get_object_stl_stats().volume >= volume_threshold_inches) {
|
||||
all_cut_parts_look_like_imperial_units = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_cut_parts_look_like_imperial_units)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -790,9 +806,11 @@ std::string Model::get_backup_path()
|
|||
buf << this->id().id;
|
||||
|
||||
backup_path = parent_path.string() + buf.str();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("model %1%, id %2%, backup_path empty, set to %3%")%this%this->id().id%backup_path;
|
||||
boost::filesystem::path temp_path(backup_path);
|
||||
if (boost::filesystem::exists(temp_path))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("model %1%, id %2%, remove previous %3%")%this%this->id().id%backup_path;
|
||||
boost::filesystem::remove_all(temp_path);
|
||||
}
|
||||
}
|
||||
|
|
@ -815,6 +833,19 @@ std::string Model::get_backup_path()
|
|||
return backup_path;
|
||||
}
|
||||
|
||||
void Model::remove_backup_path_if_exist()
|
||||
{
|
||||
if (!backup_path.empty()) {
|
||||
boost::filesystem::path temp_path(backup_path);
|
||||
if (boost::filesystem::exists(temp_path))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("model %1%, id %2% remove backup_path %3%")%this%this->id().id%backup_path;
|
||||
boost::filesystem::remove_all(temp_path);
|
||||
}
|
||||
backup_path.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Model::get_backup_path(const std::string &sub_path)
|
||||
{
|
||||
auto path = get_backup_path() + "/" + sub_path;
|
||||
|
|
@ -837,9 +868,12 @@ void Model::set_backup_path(std::string const& path)
|
|||
backup_path.clear();
|
||||
return;
|
||||
}
|
||||
if (!backup_path.empty())
|
||||
if (!backup_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(", model %1%, id %2%, remove previous backup %3%")%this%this->id().id%backup_path;
|
||||
Slic3r::remove_backup(*this, true);
|
||||
}
|
||||
backup_path = path;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(", model %1%, id %2%, set backup to %3%")%this%this->id().id%backup_path;
|
||||
}
|
||||
|
||||
void Model::load_from(Model& model)
|
||||
|
|
@ -850,8 +884,10 @@ void Model::load_from(Model& model)
|
|||
next_object_backup_id = model.next_object_backup_id;
|
||||
design_info = model.design_info;
|
||||
model_info = model.model_info;
|
||||
profile_info = model.profile_info;
|
||||
model.design_info.reset();
|
||||
model.model_info.reset();
|
||||
model.profile_info.reset();
|
||||
}
|
||||
|
||||
// BBS: backup
|
||||
|
|
@ -880,6 +916,25 @@ bool Model::is_mm_painted() const
|
|||
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); });
|
||||
}
|
||||
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
ModelObject::~ModelObject()
|
||||
{
|
||||
this->clear_volumes();
|
||||
|
|
@ -907,6 +962,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
|||
this->layer_height_profile = rhs.layer_height_profile;
|
||||
this->printable = rhs.printable;
|
||||
this->origin_translation = rhs.origin_translation;
|
||||
this->cut_id.copy(rhs.cut_id);
|
||||
m_bounding_box = rhs.m_bounding_box;
|
||||
m_bounding_box_valid = rhs.m_bounding_box_valid;
|
||||
m_raw_bounding_box = rhs.m_raw_bounding_box;
|
||||
|
|
@ -1019,6 +1075,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, ModelVolumeType t
|
|||
ModelVolume* v = new ModelVolume(this, other);
|
||||
if (type != ModelVolumeType::INVALID && v->type() != type)
|
||||
v->set_type(type);
|
||||
|
||||
v->cut_info = other.cut_info;
|
||||
|
||||
this->volumes.push_back(v);
|
||||
// The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull.
|
||||
// v->center_geometry_after_creation();
|
||||
|
|
@ -1571,6 +1630,375 @@ size_t ModelObject::parts_count() const
|
|||
return num;
|
||||
}
|
||||
|
||||
bool ModelObject::has_connectors() const
|
||||
{
|
||||
assert(is_cut());
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->cut_info.is_connector) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
|
||||
{
|
||||
indexed_triangle_set connector_mesh;
|
||||
|
||||
int sectorCount {1};
|
||||
switch (CutConnectorShape(connector_attributes.shape)) {
|
||||
case CutConnectorShape::Triangle:
|
||||
sectorCount = 3;
|
||||
break;
|
||||
case CutConnectorShape::Square:
|
||||
sectorCount = 4;
|
||||
break;
|
||||
case CutConnectorShape::Circle:
|
||||
sectorCount = 360;
|
||||
break;
|
||||
case CutConnectorShape::Hexagon:
|
||||
sectorCount = 6;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (connector_attributes.style == CutConnectorStyle::Prizm)
|
||||
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else if (connector_attributes.type == CutConnectorType::Plug)
|
||||
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else
|
||||
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
|
||||
|
||||
return connector_mesh;
|
||||
}
|
||||
|
||||
void ModelObject::apply_cut_connectors(const std::string &name)
|
||||
{
|
||||
if (cut_connectors.empty())
|
||||
return;
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
size_t connector_id = cut_id.connectors_cnt();
|
||||
for (const CutConnector &connector : cut_connectors) {
|
||||
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
|
||||
// Mesh will be centered when loading.
|
||||
ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
|
||||
|
||||
Transform3d translate_transform = Transform3d::Identity();
|
||||
translate_transform.translate(connector.pos);
|
||||
Transform3d scale_transform = Transform3d::Identity();
|
||||
scale_transform.scale(Vec3f(connector.radius, connector.radius, connector.height).cast<double>());
|
||||
|
||||
// Transform the new modifier to be aligned inside the instance
|
||||
new_volume->set_transformation(translate_transform * connector.rotation_m * scale_transform);
|
||||
|
||||
new_volume->cut_info = {connector.attribs.type, connector.radius_tolerance, connector.height_tolerance};
|
||||
new_volume->name = name + "-" + std::to_string(++connector_id);
|
||||
}
|
||||
cut_id.increase_connectors_cnt(cut_connectors.size());
|
||||
|
||||
// delete all connectors
|
||||
cut_connectors.clear();
|
||||
}
|
||||
|
||||
void ModelObject::invalidate_cut()
|
||||
{
|
||||
this->cut_id.invalidate();
|
||||
for (ModelVolume *volume : this->volumes)
|
||||
volume->invalidate_cut_info();
|
||||
}
|
||||
|
||||
void ModelObject::delete_connectors()
|
||||
{
|
||||
for (int id = int(this->volumes.size()) - 1; id >= 0; id--) {
|
||||
if (volumes[id]->is_cut_connector())
|
||||
this->delete_volume(size_t(id));
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::synchronize_model_after_cut()
|
||||
{
|
||||
for (ModelObject *obj : m_model->objects) {
|
||||
if (obj == this || obj->cut_id.is_equal(this->cut_id)) continue;
|
||||
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
|
||||
obj->cut_id.copy(this->cut_id);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
|
||||
{
|
||||
// we don't save cut information, if result will not contains all parts of initial object
|
||||
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) ||
|
||||
!attributes.has(ModelObjectCutAttribute::KeepLower) ||
|
||||
attributes.has(ModelObjectCutAttribute::InvalidateCutInfo))
|
||||
return;
|
||||
|
||||
if (cut_id.id().invalid())
|
||||
cut_id.init();
|
||||
|
||||
{
|
||||
int cut_obj_cnt = -1;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
cut_obj_cnt++;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
cut_obj_cnt++;
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels))
|
||||
cut_obj_cnt++;
|
||||
if (cut_obj_cnt > 0)
|
||||
cut_id.increase_check_sum(size_t(cut_obj_cnt));
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::clone_for_cut(ModelObject **obj)
|
||||
{
|
||||
(*obj) = ModelObject::new_clone(*this);
|
||||
(*obj)->set_model(nullptr);
|
||||
(*obj)->sla_support_points.clear();
|
||||
(*obj)->sla_drain_holes.clear();
|
||||
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
(*obj)->clear_volumes();
|
||||
(*obj)->input_file.clear();
|
||||
}
|
||||
|
||||
Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array<Vec3d, 4>& plane_points)
|
||||
{
|
||||
Vec3d mid_point = {0.0, 0.0, 0.0};
|
||||
for (auto pt : plane_points)
|
||||
mid_point += pt;
|
||||
mid_point /= (double) plane_points.size();
|
||||
|
||||
Vec3d movement = -mid_point;
|
||||
|
||||
Vec3d v01 = plane_points[1] - plane_points[0];
|
||||
Vec3d v12 = plane_points[2] - plane_points[1];
|
||||
|
||||
Vec3d plane_normal = v01.cross(v12);
|
||||
plane_normal.normalize();
|
||||
|
||||
Vec3d axis = {0.0, 0.0, 0.0};
|
||||
double phi = 0.0;
|
||||
Matrix3d matrix;
|
||||
matrix.setIdentity();
|
||||
Geometry::rotation_from_two_vectors(plane_normal, {0.0, 0.0, 1.0}, axis, phi, &matrix);
|
||||
Vec3d angles = Geometry::extract_euler_angles(matrix);
|
||||
|
||||
movement = matrix * movement;
|
||||
Transform3d transfo;
|
||||
transfo.setIdentity();
|
||||
transfo.translate(movement);
|
||||
transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX()));
|
||||
return transfo;
|
||||
}
|
||||
|
||||
void ModelObject::process_connector_cut(
|
||||
ModelVolume *volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject *upper, ModelObject *lower,
|
||||
std::vector<ModelObject *> &dowels,
|
||||
Vec3d &local_dowels_displace)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
volume->cut_info.set_processed();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume *vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
vol->apply_tolerance();
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume *vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject *dowel{nullptr};
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume *vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the dowels
|
||||
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
volume->apply_tolerance();
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::process_modifier_cut(
|
||||
ModelVolume *volume,
|
||||
const Transform3d &instance_matrix,
|
||||
const Transform3d &inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject *upper,
|
||||
ModelObject *lower)
|
||||
{
|
||||
const auto volume_matrix = instance_matrix * volume->get_matrix();
|
||||
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Geometry::Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CutToParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
void ModelObject::process_volume_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
TriangleMesh & upper_mesh,
|
||||
TriangleMesh & lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
const Geometry::Transformation cut_transformation = Geometry::Transformation(cut_matrix);
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_matrix(true, false, true, true).inverse()
|
||||
* translation_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
void ModelObject::process_solid_part_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & cut_matrix,
|
||||
const std::array<Vec3d, 4> &plane_points,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject * upper,
|
||||
ModelObject * lower,
|
||||
Vec3d & local_displace)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
if (attributes.has(ModelObjectCutAttribute::CutToParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
|
||||
// The upper part displacement is set to half of the lower part bounding box
|
||||
// this is done in hope at least a part of the upper part will always be visible and draggable
|
||||
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
|
||||
{
|
||||
if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) {
|
||||
object->center_around_origin();
|
||||
object->translate_instances(-object->origin_translation);
|
||||
object->origin_translation = Vec3d::Zero();
|
||||
}
|
||||
else {
|
||||
object->invalidate_bounding_box();
|
||||
object->center_around_origin();
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: replace z with plane_points
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
|
|
@ -1579,30 +2007,16 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_poi
|
|||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
bool keep_upper = attributes.has(ModelObjectCutAttribute::KeepUpper);
|
||||
bool keep_lower = attributes.has(ModelObjectCutAttribute::KeepLower);
|
||||
bool cut_to_parts = attributes.has(ModelObjectCutAttribute::CutToParts);
|
||||
ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr;
|
||||
ModelObject* lower = (cut_to_parts&&upper!=nullptr) ? upper : (keep_lower ? ModelObject::new_clone(*this) : nullptr);
|
||||
// apply cut attributes for object
|
||||
apply_cut_attributes(attributes);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
upper->set_model(nullptr);
|
||||
upper->sla_support_points.clear();
|
||||
upper->sla_drain_holes.clear();
|
||||
upper->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
upper->clear_volumes();
|
||||
upper->input_file.clear();
|
||||
}
|
||||
ModelObject* upper{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
clone_for_cut(&upper);
|
||||
|
||||
if (keep_lower && lower != upper) {
|
||||
lower->set_model(nullptr);
|
||||
lower->sla_support_points.clear();
|
||||
lower->sla_drain_holes.clear();
|
||||
lower->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
lower->clear_volumes();
|
||||
lower->input_file.clear();
|
||||
}
|
||||
ModelObject* lower{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::CutToParts))
|
||||
clone_for_cut(&lower);
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
|
|
@ -1622,10 +2036,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_poi
|
|||
for (Vec3d& point : plane_points) {
|
||||
point -= instances[instance]->get_offset();
|
||||
}
|
||||
Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points);
|
||||
Transform3d cut_matrix = inverse_cut_matrix.inverse();
|
||||
|
||||
std::vector<ModelObject *> dowels;
|
||||
// Displacement (in instance coordinates) to be applied to place the upper parts
|
||||
Vec3d local_displace = Vec3d::Zero();
|
||||
|
||||
Vec3d local_dowels_displace = Vec3d::Zero();
|
||||
|
||||
for (ModelVolume *volume : volumes) {
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
|
|
@ -1634,121 +2052,62 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_poi
|
|||
volume->mmu_segmentation_facets.reset();
|
||||
|
||||
if (! volume->is_model_part()) {
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
|
||||
volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower->add_volume(*volume);
|
||||
if (volume->cut_info.is_processed) {
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
//Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points);
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||
}
|
||||
else {
|
||||
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace);
|
||||
}
|
||||
}
|
||||
else if (! volume->mesh().empty()) {
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(instance_matrix * volume_matrix, true);
|
||||
volume->reset_mesh();
|
||||
// Reset volume transformation except for offset
|
||||
const Vec3d offset = volume->get_offset();
|
||||
volume->set_transformation(Geometry::Transformation());
|
||||
volume->set_offset(offset);
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
{
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, plane_points, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) {
|
||||
ModelVolume* vol = upper->add_volume(upper_mesh);
|
||||
vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_upper"; // BBS
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != volume->config.id());
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) {
|
||||
ModelVolume* vol = lower->add_volume(lower_mesh);
|
||||
vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_lower"; // BBS
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != volume->config.id());
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
|
||||
// The upper part displacement is set to half of the lower part bounding box
|
||||
// this is done in hope at least a part of the upper part will always be visible and draggable
|
||||
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
|
||||
}
|
||||
process_solid_part_cut(volume, instance_matrix, cut_matrix, plane_points, attributes, upper, lower, local_displace);
|
||||
}
|
||||
}
|
||||
|
||||
ModelObjectPtrs res;
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
|
||||
if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
|
||||
// BBS: do not move the parts if cut_to_parts
|
||||
if (!cut_to_parts) {
|
||||
upper->center_around_origin();
|
||||
upper->translate_instances(-upper->origin_translation);
|
||||
upper->origin_translation = Vec3d::Zero();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
auto &instance = upper->instances[i];
|
||||
const Vec3d offset = instance->get_offset();
|
||||
// BBS
|
||||
//const double rot_z = instance->get_rotation().z();
|
||||
// BBS: do not move the parts if cut_to_parts
|
||||
Vec3d displace(0, 0, 0);
|
||||
if (!cut_to_parts)
|
||||
displace = Geometry::assemble_transform(Vec3d::Zero(), instance->get_rotation()) * local_displace;
|
||||
|
||||
instance->set_transformation(Geometry::Transformation());
|
||||
instance->set_offset(offset + displace);
|
||||
// BBS
|
||||
//instance->set_rotation(Vec3d(0.0, 0.0, rot_z));
|
||||
}
|
||||
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CutToParts) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, instance, cut_matrix);
|
||||
res.push_back(upper);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
|
||||
if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
|
||||
if (!cut_to_parts) {
|
||||
lower->center_around_origin();
|
||||
lower->translate_instances(-lower->origin_translation);
|
||||
lower->origin_translation = Vec3d::Zero();
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
|
||||
invalidate_translations(upper, instances[instance]);
|
||||
|
||||
reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
attributes.has(ModelObjectCutAttribute::FlipUpper), local_displace);
|
||||
|
||||
res.push_back(upper);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
|
||||
invalidate_translations(lower, instances[instance]);
|
||||
|
||||
reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
|
||||
res.push_back(lower);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
invalidate_translations(dowel, instances[instance]);
|
||||
|
||||
reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace);
|
||||
|
||||
local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0));
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
res.push_back(dowel);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
for (auto *instance : lower->instances) {
|
||||
const Vec3d offset = instance->get_offset();
|
||||
// BBS
|
||||
//const double rot_z = instance->get_rotation().z();
|
||||
instance->set_transformation(Geometry::Transformation());
|
||||
instance->set_offset(offset);
|
||||
// BBS
|
||||
//instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
|
||||
}
|
||||
|
||||
if(res.empty() || lower != res.back())
|
||||
res.push_back(lower);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||||
|
||||
synchronize_model_after_cut();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
@ -2188,7 +2547,7 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
|
|||
// %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z();
|
||||
for (ModelInstance* model_instance : this->instances) {
|
||||
unsigned int inside_outside = 0;
|
||||
for (const ModelVolume* vol : this->volumes)
|
||||
for (const ModelVolume *vol : this->volumes) {
|
||||
if (vol->is_model_part()) {
|
||||
//BBS: add bounding box empty check logic, for some volume is empty before split(it will be removed after split to object)
|
||||
BoundingBoxf3 bb = vol->get_convex_hull().bounding_box();
|
||||
|
|
@ -2214,6 +2573,7 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
|
|||
// Volume colliding with the build volume.
|
||||
inside_outside |= INSIDE | OUTSIDE;
|
||||
}
|
||||
}
|
||||
model_instance->print_volume_state =
|
||||
inside_outside == (INSIDE | OUTSIDE) ? ModelInstancePVS_Partly_Outside :
|
||||
inside_outside == INSIDE ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside;
|
||||
|
|
@ -2366,6 +2726,23 @@ bool ModelVolume::is_splittable() const
|
|||
return m_is_splittable == 1;
|
||||
}
|
||||
|
||||
void ModelVolume::apply_tolerance()
|
||||
{
|
||||
assert(cut_info.is_connector);
|
||||
if (!cut_info.is_processed)
|
||||
return;
|
||||
|
||||
Vec3d sf = get_scaling_factor();
|
||||
// make a "hole" wider
|
||||
sf[X] *= 1. + double(cut_info.radius_tolerance);
|
||||
sf[Y] *= 1. + double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] *= 1. + double(cut_info.height_tolerance);
|
||||
|
||||
set_scaling_factor(sf);
|
||||
}
|
||||
|
||||
// BBS
|
||||
std::vector<int> ModelVolume::get_extruders() const
|
||||
{
|
||||
|
|
@ -2989,18 +3366,20 @@ void ModelInstance::get_arrange_polygon(void *ap, const Slic3r::DynamicPrintConf
|
|||
return;
|
||||
}
|
||||
ret.extrude_ids = volume->get_extruders();
|
||||
if (ret.extrude_ids.empty()) //the default extruder
|
||||
ret.extrude_ids.push_back(1);
|
||||
|
||||
// get per-object support extruders
|
||||
auto op = object->get_config_value<ConfigOptionBool>(config_global, "enable_support");
|
||||
bool is_support_enabled = op && op->getBool();
|
||||
if (is_support_enabled) {
|
||||
auto op1 = object->get_config_value<ConfigOptionInt>(config_global, "support_filament");
|
||||
auto op2 = object->get_config_value<ConfigOptionInt>(config_global, "support_interface_filament");
|
||||
if (op1) ret.extrude_ids.push_back(op1->getInt());
|
||||
if (op2) ret.extrude_ids.push_back(op2->getInt());
|
||||
int extruder_id;
|
||||
// id==0 means follow previous material, so need not be recorded
|
||||
if (op1 && (extruder_id = op1->getInt()) > 0) ret.extrude_ids.push_back(extruder_id);
|
||||
if (op2 && (extruder_id = op2->getInt()) > 0) ret.extrude_ids.push_back(extruder_id);
|
||||
}
|
||||
|
||||
if (ret.extrude_ids.empty()) //the default extruder
|
||||
ret.extrude_ids.push_back(1);
|
||||
}
|
||||
|
||||
indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const
|
||||
|
|
|
|||
|
|
@ -232,6 +232,80 @@ private:
|
|||
friend class ModelObject;
|
||||
};
|
||||
|
||||
enum class CutConnectorType : int {
|
||||
Plug,
|
||||
Dowel,
|
||||
Undef
|
||||
};
|
||||
|
||||
enum class CutConnectorStyle : int {
|
||||
Prizm,
|
||||
Frustum,
|
||||
Undef
|
||||
//,Claw
|
||||
};
|
||||
|
||||
enum class CutConnectorShape : int {
|
||||
Triangle,
|
||||
Square,
|
||||
Hexagon,
|
||||
Circle,
|
||||
Undef
|
||||
//,D-shape
|
||||
};
|
||||
|
||||
struct CutConnectorAttributes
|
||||
{
|
||||
CutConnectorType type{CutConnectorType::Plug};
|
||||
CutConnectorStyle style{CutConnectorStyle::Prizm};
|
||||
CutConnectorShape shape{CutConnectorShape::Circle};
|
||||
|
||||
CutConnectorAttributes() {}
|
||||
|
||||
CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh) : type(t), style(st), shape(sh) {}
|
||||
|
||||
CutConnectorAttributes(const CutConnectorAttributes &rhs) : CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {}
|
||||
|
||||
bool operator==(const CutConnectorAttributes &other) const;
|
||||
|
||||
bool operator!=(const CutConnectorAttributes &other) const { return !(other == (*this)); }
|
||||
|
||||
bool operator<(const CutConnectorAttributes &other) const
|
||||
{
|
||||
return this->type < other.type || (this->type == other.type && this->style < other.style) ||
|
||||
(this->type == other.type && this->style == other.style && this->shape < other.shape);
|
||||
}
|
||||
|
||||
template<class Archive> inline void serialize(Archive &ar) { ar(type, style, shape); }
|
||||
};
|
||||
|
||||
struct CutConnector
|
||||
{
|
||||
Vec3d pos;
|
||||
Transform3d rotation_m;
|
||||
float radius;
|
||||
float height;
|
||||
float radius_tolerance; // [0.f : 1.f]
|
||||
float height_tolerance; // [0.f : 1.f]
|
||||
CutConnectorAttributes attribs;
|
||||
|
||||
CutConnector() : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {}
|
||||
|
||||
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
|
||||
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes)
|
||||
{}
|
||||
|
||||
CutConnector(const CutConnector &rhs) : CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {}
|
||||
|
||||
bool operator==(const CutConnector &other) const;
|
||||
|
||||
bool operator!=(const CutConnector &other) const { return !(other == (*this)); }
|
||||
|
||||
template<class Archive> inline void serialize(Archive &ar) { ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs); }
|
||||
};
|
||||
|
||||
using CutConnectors = std::vector<CutConnector>;
|
||||
|
||||
// Declared outside of ModelVolume, so it could be forward declared.
|
||||
enum class ModelVolumeType : int {
|
||||
INVALID = -1,
|
||||
|
|
@ -242,7 +316,7 @@ enum class ModelVolumeType : int {
|
|||
SUPPORT_ENFORCER
|
||||
};
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower, CutToParts };
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, CutToParts, InvalidateCutInfo };
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
|
|
@ -293,6 +367,10 @@ public:
|
|||
// BBS: save for compare with new load volumes
|
||||
std::vector<ObjectID> volume_ids;
|
||||
|
||||
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
|
||||
CutConnectors cut_connectors;
|
||||
CutObjectBase cut_id;
|
||||
|
||||
Model* get_model() { return m_model; }
|
||||
const Model* get_model() const { return m_model; }
|
||||
// BBS: production extension
|
||||
|
|
@ -385,6 +463,47 @@ public:
|
|||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
size_t parts_count() const;
|
||||
|
||||
bool is_cut() const { return cut_id.id().valid(); }
|
||||
bool has_connectors() const;
|
||||
static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
|
||||
void apply_cut_connectors(const std::string &name);
|
||||
// invalidate cut state for this object and its connectors/volumes
|
||||
void invalidate_cut();
|
||||
// delete volumes which are marked as connector for this object
|
||||
void delete_connectors();
|
||||
void synchronize_model_after_cut();
|
||||
void apply_cut_attributes(ModelObjectCutAttributes attributes);
|
||||
void clone_for_cut(ModelObject **obj);
|
||||
Transform3d calculate_cut_plane_inverse_matrix(const std::array<Vec3d, 4> &plane_points);
|
||||
void process_connector_cut(ModelVolume *volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject *upper, ModelObject *lower,
|
||||
std::vector<ModelObject *> &dowels,
|
||||
Vec3d &local_dowels_displace);
|
||||
void process_modifier_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject * upper,
|
||||
ModelObject * lower);
|
||||
void process_volume_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
TriangleMesh & upper_mesh,
|
||||
TriangleMesh & lower_mesh);
|
||||
void process_solid_part_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & cut_matrix,
|
||||
const std::array<Vec3d, 4> &plane_points,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject * upper,
|
||||
ModelObject * lower,
|
||||
Vec3d & local_displace);
|
||||
|
||||
// BBS: replace z with plane_points
|
||||
ModelObjectPtrs cut(size_t instance, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes);
|
||||
// BBS
|
||||
|
|
@ -533,7 +652,8 @@ private:
|
|||
Internal::StaticSerializationWrapper<LayerHeightProfile const> layer_heigth_profile_wrapper(layer_height_profile);
|
||||
ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
|
||||
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
|
||||
cut_connectors, cut_id);
|
||||
}
|
||||
template<class Archive> void load(Archive& ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
|
|
@ -543,7 +663,8 @@ private:
|
|||
SaveObjectGaurd gaurd(*this);
|
||||
ar(name, module_name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
|
||||
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
|
||||
cut_connectors, cut_id);
|
||||
std::vector<ObjectID> volume_ids2;
|
||||
std::transform(volumes.begin(), volumes.end(), std::back_inserter(volume_ids2), std::mem_fn(&ObjectBase::id));
|
||||
if (volume_ids != volume_ids2)
|
||||
|
|
@ -710,6 +831,31 @@ public:
|
|||
};
|
||||
Source source;
|
||||
|
||||
// struct used by cut command
|
||||
// It contains information about connetors
|
||||
struct CutInfo
|
||||
{
|
||||
bool is_connector{false};
|
||||
bool is_processed{true};
|
||||
CutConnectorType connector_type{CutConnectorType::Plug};
|
||||
float radius_tolerance{0.f}; // [0.f : 1.f]
|
||||
float height_tolerance{0.f}; // [0.f : 1.f]
|
||||
|
||||
CutInfo() = default;
|
||||
CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false)
|
||||
: is_connector(true), is_processed(processed), connector_type(type), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance)
|
||||
{}
|
||||
|
||||
void set_processed() { is_processed = true; }
|
||||
void invalidate() { is_connector = false; }
|
||||
|
||||
template<class Archive> inline void serialize(Archive &ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); }
|
||||
};
|
||||
CutInfo cut_info;
|
||||
|
||||
bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; }
|
||||
void invalidate_cut_info() { cut_info.invalidate(); }
|
||||
|
||||
// The triangular model.
|
||||
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
||||
const TriangleMesh* mesh_ptr() const { return m_mesh.get(); }
|
||||
|
|
@ -760,6 +906,8 @@ public:
|
|||
|
||||
bool is_splittable() const;
|
||||
|
||||
void apply_tolerance();
|
||||
|
||||
// BBS
|
||||
std::vector<int> get_extruders() const;
|
||||
void update_extruder_count(size_t extruder_count);
|
||||
|
|
@ -1001,7 +1149,7 @@ 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);
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, 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();
|
||||
|
|
@ -1027,7 +1175,7 @@ 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);
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info);
|
||||
cereal::save_by_value(ar, supported_facets);
|
||||
cereal::save_by_value(ar, seam_facets);
|
||||
cereal::save_by_value(ar, mmu_segmentation_facets);
|
||||
|
|
@ -1072,6 +1220,7 @@ public:
|
|||
// Whether or not this instance is printable
|
||||
bool printable;
|
||||
int arrange_order = 0; // BBS
|
||||
size_t loaded_id = 0; // BBS
|
||||
|
||||
ModelObject* get_object() const { return this->object; }
|
||||
|
||||
|
|
@ -1260,6 +1409,17 @@ struct GlobalSpeedMap
|
|||
Polygon bed_poly;
|
||||
};
|
||||
|
||||
/* Profile data */
|
||||
class ModelProfileInfo
|
||||
{
|
||||
public:
|
||||
std::string ProfileTile;
|
||||
std::string ProfileCover;
|
||||
std::string ProfileDescription;
|
||||
std::string ProfileUserId;
|
||||
std::string ProfileUserName;
|
||||
};
|
||||
|
||||
/* info in ModelDesignInfo can not changed after initialization */
|
||||
class ModelDesignInfo
|
||||
{
|
||||
|
|
@ -1278,6 +1438,7 @@ public:
|
|||
std::string description; // utf8 format
|
||||
std::string copyright; // utf8 format
|
||||
std::string model_name; // utf8 format
|
||||
std::string origin; // utf8 format
|
||||
|
||||
std::map<std::string, std::string> metadata_items; // other meta data items
|
||||
|
||||
|
|
@ -1287,6 +1448,7 @@ public:
|
|||
this->description = info.description;
|
||||
this->copyright = info.copyright;
|
||||
this->model_name = info.model_name;
|
||||
this->origin = info.origin;
|
||||
this->metadata_items = info.metadata_items;
|
||||
}
|
||||
};
|
||||
|
|
@ -1313,6 +1475,7 @@ public:
|
|||
// DesignInfo of Model
|
||||
std::shared_ptr<ModelDesignInfo> design_info = nullptr;
|
||||
std::shared_ptr<ModelInfo> model_info = nullptr;
|
||||
std::shared_ptr<ModelProfileInfo> profile_info = nullptr;
|
||||
|
||||
void SetDesigner(std::string designer, std::string designer_user_id) {
|
||||
if (design_info == nullptr) {
|
||||
|
|
@ -1436,6 +1599,7 @@ public:
|
|||
void load_from(Model & model);
|
||||
bool is_need_backup() { return need_backup; }
|
||||
void set_need_backup();
|
||||
void remove_backup_path_if_exist();
|
||||
|
||||
// Checks if any of objects is painted using the fdm support painting gizmo.
|
||||
bool is_fdm_support_painted() const;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,13 @@ struct segment_traits<Slic3r::ColoredLine> {
|
|||
//#define MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
||||
|
||||
namespace Slic3r {
|
||||
bool is_equal(float left, float right, float eps = 1e-3) {
|
||||
return abs(left - right) <= eps;
|
||||
}
|
||||
|
||||
bool is_less(float left, float right, float eps = 1e-3) {
|
||||
return left + eps < right;
|
||||
}
|
||||
|
||||
// Assumes that is at most same projected_l length or below than projection_l
|
||||
static bool project_line_on_line(const Line &projection_l, const Line &projected_l, Line *new_projected)
|
||||
|
|
@ -1518,6 +1525,12 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
||||
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
||||
|
||||
// BBS: use shell_triangles_by_color_bottom & shell_triangles_by_color_top to save the top and bottom embedded layers's color information
|
||||
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_bottom(num_extruders);
|
||||
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_top(num_extruders);
|
||||
shell_triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
||||
shell_triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
||||
|
||||
struct LayerColorStat {
|
||||
// Number of regions for a queried color.
|
||||
int num_regions { 0 };
|
||||
|
|
@ -1560,7 +1573,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
};
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
||||
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
|
||||
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||
size_t group_idx = range.begin() / granularity;
|
||||
size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
|
|
@ -1575,7 +1589,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
|
||||
float offset = 0.f;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
|
||||
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
||||
//offset -= stat.extrusion_width ;
|
||||
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
||||
|
|
@ -1583,7 +1597,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1603,7 +1617,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1613,9 +1627,11 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
|
||||
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback,
|
||||
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
ExPolygons painted_exploys;
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
||||
|
|
@ -1623,6 +1639,27 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
|||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
|
||||
self = union_ex(self);
|
||||
|
||||
append(painted_exploys, self);
|
||||
}
|
||||
|
||||
painted_exploys = union_ex(painted_exploys);
|
||||
|
||||
//BBS: merge the top and bottom shell layers
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
|
||||
auto top_area = diff_ex(union_ex(shell_triangles_by_color_top[color_idx][layer_idx],
|
||||
shell_triangles_by_color_top[color_idx][layer_idx + num_layers]),
|
||||
painted_exploys);
|
||||
|
||||
auto bottom_area = diff_ex(union_ex(shell_triangles_by_color_bottom[color_idx][layer_idx],
|
||||
shell_triangles_by_color_bottom[color_idx][layer_idx + num_layers]),
|
||||
painted_exploys);
|
||||
|
||||
append(self, top_area);
|
||||
append(self, bottom_area);
|
||||
self = union_ex(self);
|
||||
}
|
||||
// Trim one region by the other if some of the regions overlap.
|
||||
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++ color_idx)
|
||||
|
|
@ -1883,7 +1920,7 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
|||
for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) {
|
||||
const Layer *layer = *layer_it;
|
||||
size_t layer_idx = layer_it - layers.begin();
|
||||
if (input_expolygons[layer_idx].empty() || facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z())
|
||||
if (input_expolygons[layer_idx].empty() || is_less(layer->slice_z, facet[0].z()) || is_less(facet[2].z(), layer->slice_z))
|
||||
continue;
|
||||
|
||||
// https://kandepet.com/3d-printing-slicing-3d-objects/
|
||||
|
|
@ -1891,7 +1928,12 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
|||
Vec3f line_start_f = facet[0] + t * (facet[2] - facet[0]);
|
||||
Vec3f line_end_f;
|
||||
|
||||
if (facet[1].z() > layer->slice_z) {
|
||||
// BBS: When one side of a triangle coincides with the slice_z.
|
||||
if ((is_equal(facet[0].z(), facet[1].z()) && is_equal(facet[1].z(), layer->slice_z))
|
||||
|| (is_equal(facet[1].z(), facet[2].z()) && is_equal(facet[1].z(), layer->slice_z))) {
|
||||
line_end_f = facet[1];
|
||||
}
|
||||
else if (facet[1].z() > layer->slice_z) {
|
||||
// [P0, P2] and [P0, P1]
|
||||
float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z());
|
||||
line_end_f = facet[0] + t1 * (facet[1] - facet[0]);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ protected:
|
|||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
ObjectBase(int) : m_id(ObjectID(0)) {}
|
||||
|
||||
ObjectBase(const ObjectID id) : m_id(id) {}
|
||||
// The class tree will have virtual tables and type information.
|
||||
virtual ~ObjectBase() = default;
|
||||
|
||||
|
|
@ -89,7 +91,6 @@ private:
|
|||
friend class cereal::access;
|
||||
friend class Slic3r::UndoRedo::StackImpl;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(m_id); }
|
||||
ObjectBase(const ObjectID id) : m_id(id) {}
|
||||
template<class Archive> static void load_and_construct(Archive & ar, cereal::construct<ObjectBase> &construct) { ObjectID id; ar(id); construct(id); }
|
||||
};
|
||||
|
||||
|
|
@ -128,6 +129,67 @@ private:
|
|||
template<class Archive> void serialize(Archive &ar) { ar(m_timestamp); }
|
||||
};
|
||||
|
||||
class CutObjectBase : public ObjectBase
|
||||
{
|
||||
// check sum of CutParts in initial Object
|
||||
size_t m_check_sum{1};
|
||||
// connectors count
|
||||
size_t m_connectors_cnt{0};
|
||||
|
||||
public:
|
||||
// Default Constructor to assign an invalid ID
|
||||
CutObjectBase() : ObjectBase(-1) {}
|
||||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
CutObjectBase(int) : ObjectBase(-1) {}
|
||||
// Constructor to initialize full information from 3mf
|
||||
CutObjectBase(ObjectID id, size_t check_sum, size_t connectors_cnt) : ObjectBase(id), m_check_sum(check_sum), m_connectors_cnt(connectors_cnt) {}
|
||||
// The class tree will have virtual tables and type information.
|
||||
virtual ~CutObjectBase() = default;
|
||||
|
||||
bool operator<(const CutObjectBase &other) const { return other.id() > this->id(); }
|
||||
bool operator==(const CutObjectBase &other) const { return other.id() == this->id(); }
|
||||
|
||||
void copy(const CutObjectBase &rhs)
|
||||
{
|
||||
this->copy_id(rhs);
|
||||
this->m_check_sum = rhs.check_sum();
|
||||
this->m_connectors_cnt = rhs.connectors_cnt();
|
||||
}
|
||||
CutObjectBase &operator=(const CutObjectBase &other)
|
||||
{
|
||||
this->copy(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void invalidate()
|
||||
{
|
||||
set_invalid_id();
|
||||
m_check_sum = 1;
|
||||
m_connectors_cnt = 0;
|
||||
}
|
||||
|
||||
void init() { this->set_new_unique_id(); }
|
||||
bool has_same_id(const CutObjectBase &rhs) { return this->id() == rhs.id(); }
|
||||
bool is_equal(const CutObjectBase &rhs) { return this->id() == rhs.id() && this->check_sum() == rhs.check_sum() && this->connectors_cnt() == rhs.connectors_cnt(); }
|
||||
|
||||
size_t check_sum() const { return m_check_sum; }
|
||||
void set_check_sum(size_t cs) { m_check_sum = cs; }
|
||||
void increase_check_sum(size_t cnt) { m_check_sum += cnt; }
|
||||
|
||||
size_t connectors_cnt() const { return m_connectors_cnt; }
|
||||
void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; }
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
ar(m_check_sum, m_connectors_cnt);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Unique object / instance ID for the wipe tower.
|
||||
extern ObjectID wipe_tower_object_id();
|
||||
extern ObjectID wipe_tower_instance_id();
|
||||
|
|
|
|||
|
|
@ -268,6 +268,10 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
|||
}
|
||||
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
|
||||
// get non 100% overhang paths by intersecting this loop with the grown lower slices
|
||||
// prepare grown lower layer slices for overhang detection
|
||||
BoundingBox bbox(polygon.points);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
|
||||
Polylines remain_polines;
|
||||
|
||||
//BBS: don't calculate overhang degree when enable fuzzy skin. It's unmeaning
|
||||
|
|
@ -275,10 +279,10 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
|||
for (auto it = lower_polygons_series->begin();
|
||||
it != lower_polygons_series->end(); it++)
|
||||
{
|
||||
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(it->second, bbox);
|
||||
|
||||
Polylines inside_polines = (it == lower_polygons_series->begin()) ?
|
||||
intersection_pl({ polygon }, it->second) :
|
||||
intersection_pl(remain_polines, it->second);
|
||||
Polylines inside_polines = (it == lower_polygons_series->begin()) ? intersection_pl({polygon}, lower_polygons_series_clipped) :
|
||||
intersection_pl(remain_polines, lower_polygons_series_clipped);
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
std::move(inside_polines),
|
||||
|
|
@ -289,9 +293,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
|||
extrusion_width,
|
||||
(float)perimeter_generator.layer_height);
|
||||
|
||||
remain_polines = (it == lower_polygons_series->begin()) ?
|
||||
diff_pl({ polygon }, it->second) :
|
||||
diff_pl(remain_polines, it->second);
|
||||
remain_polines = (it == lower_polygons_series->begin()) ? diff_pl({polygon}, lower_polygons_series_clipped) :
|
||||
diff_pl(remain_polines, lower_polygons_series_clipped);
|
||||
|
||||
if (remain_polines.size() == 0)
|
||||
break;
|
||||
|
|
@ -299,7 +302,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
|||
} else {
|
||||
auto it = lower_polygons_series->end();
|
||||
it--;
|
||||
Polylines inside_polines = intersection_pl({ polygon }, it->second);
|
||||
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(it->second, bbox);
|
||||
|
||||
Polylines inside_polines = intersection_pl({polygon}, lower_polygons_series_clipped);
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
std::move(inside_polines),
|
||||
|
|
@ -310,7 +315,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
|||
extrusion_width,
|
||||
(float)perimeter_generator.layer_height);
|
||||
|
||||
remain_polines = diff_pl({ polygon }, it->second);
|
||||
remain_polines = diff_pl({polygon}, lower_polygons_series_clipped);
|
||||
}
|
||||
|
||||
// get 100% overhang paths by checking what parts of this loop fall
|
||||
|
|
@ -490,6 +495,90 @@ struct PerimeterGeneratorArachneExtrusion
|
|||
bool fuzzify = false;
|
||||
};
|
||||
|
||||
|
||||
static void smooth_overhang_level(ExtrusionPaths &paths)
|
||||
{
|
||||
const double threshold_length = scale_(0.8);
|
||||
const double filter_range = scale_(6.5);
|
||||
|
||||
// 0.save old overhang series first which is input of filter
|
||||
const int path_num = paths.size();
|
||||
if (path_num < 2)
|
||||
// don't need to do filting if only has one path in vector
|
||||
return;
|
||||
std::vector<int> old_overhang_series;
|
||||
old_overhang_series.reserve(path_num);
|
||||
for (int i = 0; i < path_num; i++) old_overhang_series.push_back(paths[i].get_overhang_degree());
|
||||
|
||||
for (int i = 0; i < path_num;) {
|
||||
if ((paths[i].role() != erPerimeter && paths[i].role() != erExternalPerimeter)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
double current_length = paths[i].length();
|
||||
int current_overhang_degree = old_overhang_series[i];
|
||||
double total_lens = current_length;
|
||||
int pt = i + 1;
|
||||
|
||||
for (; pt < path_num; pt++) {
|
||||
if (paths[pt].get_overhang_degree() != current_overhang_degree || (paths[pt].role() != erPerimeter && paths[pt].role() != erExternalPerimeter)) {
|
||||
break;
|
||||
}
|
||||
total_lens += paths[pt].length();
|
||||
}
|
||||
|
||||
if (total_lens < threshold_length) {
|
||||
double left_total_length = (filter_range - total_lens) / 2;
|
||||
double right_total_length = left_total_length;
|
||||
|
||||
double temp_length;
|
||||
int j = i - 1;
|
||||
int index;
|
||||
std::vector<std::pair<double, int>> neighbor_path;
|
||||
while (left_total_length > 0) {
|
||||
index = (j < 0) ? path_num - 1 : j;
|
||||
if (paths[index].role() == erOverhangPerimeter) break;
|
||||
temp_length = paths[index].length();
|
||||
if (temp_length > left_total_length)
|
||||
neighbor_path.emplace_back(std::pair<double, int>(left_total_length, old_overhang_series[index]));
|
||||
else
|
||||
neighbor_path.emplace_back(std::pair<double, int>(temp_length, old_overhang_series[index]));
|
||||
left_total_length -= temp_length;
|
||||
j = index;
|
||||
j--;
|
||||
}
|
||||
|
||||
j = pt;
|
||||
while (right_total_length > 0) {
|
||||
index = j % path_num;
|
||||
if (paths[index].role() == erOverhangPerimeter) break;
|
||||
temp_length = paths[index].length();
|
||||
if (temp_length > right_total_length)
|
||||
neighbor_path.emplace_back(std::pair<double, int>(right_total_length, old_overhang_series[index]));
|
||||
else
|
||||
neighbor_path.emplace_back(std::pair<double, int>(temp_length, old_overhang_series[index]));
|
||||
right_total_length -= temp_length;
|
||||
j++;
|
||||
}
|
||||
|
||||
double sum = 0;
|
||||
double length_sum = 0;
|
||||
for (auto it = neighbor_path.begin(); it != neighbor_path.end(); it++) {
|
||||
sum += (it->first * it->second);
|
||||
length_sum += it->first;
|
||||
}
|
||||
|
||||
double average_overhang = (double) (total_lens * current_overhang_degree + sum) / (length_sum + total_lens);
|
||||
|
||||
for (int idx=i; idx<pt;idx++)
|
||||
paths[idx].set_overhang_degree((int) average_overhang);
|
||||
}
|
||||
|
||||
i = pt;
|
||||
}
|
||||
}
|
||||
|
||||
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector<PerimeterGeneratorArachneExtrusion>& pg_extrusions)
|
||||
{
|
||||
ExtrusionEntityCollection extrusion_coll;
|
||||
|
|
@ -509,26 +598,87 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
|||
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers
|
||||
&& !((perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) &&
|
||||
perimeter_generator.object_config->support_top_z_distance.value == 0)) {
|
||||
|
||||
ClipperLib_Z::Path extrusion_path;
|
||||
extrusion_path.reserve(extrusion->size());
|
||||
for (const Arachne::ExtrusionJunction& ej : extrusion->junctions)
|
||||
BoundingBox extrusion_path_bbox;
|
||||
for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) {
|
||||
extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w);
|
||||
|
||||
ClipperLib_Z::Paths lower_slices_paths;
|
||||
lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size());
|
||||
for (const Polygon& poly : perimeter_generator.lower_slices_polygons()) {
|
||||
lower_slices_paths.emplace_back();
|
||||
ClipperLib_Z::Path& out = lower_slices_paths.back();
|
||||
out.reserve(poly.points.size());
|
||||
for (const Point& pt : poly.points)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
extrusion_path_bbox.merge(Point(ej.p.x(), ej.p.y()));
|
||||
}
|
||||
|
||||
// get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role,
|
||||
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
|
||||
ClipperLib_Z::Paths lower_slices_paths;
|
||||
{
|
||||
lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size());
|
||||
Points clipped;
|
||||
extrusion_path_bbox.offset(SCALED_EPSILON);
|
||||
for (const Polygon &poly : perimeter_generator.lower_slices_polygons()) {
|
||||
clipped.clear();
|
||||
ClipperUtils::clip_clipper_polygon_with_subject_bbox(poly.points, extrusion_path_bbox, clipped);
|
||||
if (!clipped.empty()) {
|
||||
lower_slices_paths.emplace_back();
|
||||
ClipperLib_Z::Path &out = lower_slices_paths.back();
|
||||
out.reserve(clipped.size());
|
||||
for (const Point &pt : clipped)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExtrusionPaths temp_paths;
|
||||
// get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
extrusion_paths_append(temp_paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role,
|
||||
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
|
||||
|
||||
if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
|
||||
|
||||
Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow;
|
||||
std::map<double, std::vector<Polygons>> clipper_serise;
|
||||
|
||||
std::map<double,ExtrusionPaths> recognization_paths;
|
||||
for (const ExtrusionPath &path : temp_paths) {
|
||||
if (recognization_paths.count(path.width))
|
||||
recognization_paths[path.width].emplace_back(std::move(path));
|
||||
else
|
||||
recognization_paths.insert(std::pair<double, ExtrusionPaths>(path.width, {std::move(path)}));
|
||||
}
|
||||
|
||||
for (const auto &it : recognization_paths) {
|
||||
Polylines be_clipped;
|
||||
|
||||
for (const ExtrusionPath &p : it.second) {
|
||||
be_clipped.emplace_back(std::move(p.polyline));
|
||||
}
|
||||
|
||||
BoundingBox extrusion_bboxs = get_extents(be_clipped);
|
||||
//ExPolygons lower_slcier_chopped = *perimeter_generator.lower_slices;
|
||||
Polygons lower_slcier_chopped=ClipperUtils::clip_clipper_polygons_with_subject_bbox(*perimeter_generator.lower_slices, extrusion_bboxs, true);
|
||||
|
||||
double start_pos = -it.first * 0.5;
|
||||
double end_pos = 0.5 * it.first;
|
||||
|
||||
Polylines remain_polylines;
|
||||
std::vector<Polygons> degree_polygons;
|
||||
for (int j = 0; j < overhang_sampling_number; j++) {
|
||||
Polygons limiton_polygons = offset(lower_slcier_chopped, float(scale_(start_pos + (j + 0.5) * (end_pos - start_pos) / (overhang_sampling_number - 1))));
|
||||
|
||||
Polylines inside_polines = j == 0 ? intersection_pl(be_clipped, limiton_polygons) : intersection_pl(remain_polylines, limiton_polygons);
|
||||
|
||||
remain_polylines = j == 0 ? diff_pl(be_clipped, limiton_polygons) : diff_pl(remain_polylines, limiton_polygons);
|
||||
|
||||
extrusion_paths_append(paths, std::move(inside_polines), j, int(0), role, it.second.front().mm3_per_mm, it.second.front().width, it.second.front().height);
|
||||
|
||||
if (remain_polylines.size() == 0) break;
|
||||
}
|
||||
|
||||
if (remain_polylines.size() != 0) {
|
||||
extrusion_paths_append(paths, std::move(remain_polylines), overhang_sampling_number - 1, int(0), erOverhangPerimeter, it.second.front().mm3_per_mm, it.second.front().width, it.second.front().height);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
paths = std::move(temp_paths);
|
||||
|
||||
}
|
||||
// get overhang paths by checking what parts of this loop fall
|
||||
// outside the grown lower slices (thus where the distance between
|
||||
// the loop centerline and original lower slices is >= half nozzle diameter
|
||||
|
|
@ -571,6 +721,12 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
|||
}
|
||||
|
||||
chain_and_reorder_extrusion_paths(paths, &start_point);
|
||||
|
||||
if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
|
||||
// BBS: filter the speed
|
||||
smooth_overhang_level(paths);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -683,7 +839,7 @@ void PerimeterGenerator::process_classic()
|
|||
// extra perimeters for each one
|
||||
|
||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||
double surface_simplify_resolution = (print_config->enable_arc_fitting) ? 0.1 * m_scaled_resolution : m_scaled_resolution;
|
||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||
for (const Surface &surface : this->slices->surfaces) {
|
||||
// detect how many perimeters must be generated for this island
|
||||
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
|
||||
|
|
@ -724,7 +880,7 @@ void PerimeterGenerator::process_classic()
|
|||
float(min_width / 2.));
|
||||
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||
for (ExPolygon &ex : expp)
|
||||
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
|
||||
ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls);
|
||||
} else {
|
||||
coord_t ext_perimeter_smaller_width = this->smaller_ext_perimeter_flow.scaled_width();
|
||||
for (const ExPolygon& expolygon : last) {
|
||||
|
|
@ -803,8 +959,8 @@ void PerimeterGenerator::process_classic()
|
|||
break;
|
||||
}
|
||||
{
|
||||
const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && i == 0 && this->layer_id > 0;
|
||||
const bool fuzzify_holes = fuzzify_contours && this->config->fuzzy_skin == FuzzySkinType::All;
|
||||
const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && ((i == 0 && this->layer_id > 0) || this->config->fuzzy_skin == FuzzySkinType::AllWalls);
|
||||
const bool fuzzify_holes = fuzzify_contours && (this->config->fuzzy_skin == FuzzySkinType::All || this->config->fuzzy_skin == FuzzySkinType::AllWalls);
|
||||
for (const ExPolygon& expolygon : offsets) {
|
||||
// Outer contour may overlap with an inner contour,
|
||||
// inner contour may overlap with another inner contour,
|
||||
|
|
@ -848,7 +1004,16 @@ void PerimeterGenerator::process_classic()
|
|||
offset_top_surface = 0;
|
||||
//don't takes into account too thin areas
|
||||
double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2 + 10), 1.0 * (double(perimeter_width)));
|
||||
ExPolygons grown_upper_slices = offset_ex(*this->upper_slices, min_width_top_surface);
|
||||
|
||||
Polygons grown_upper_slices = offset(*this->upper_slices, min_width_top_surface);
|
||||
|
||||
//BBS: get boungding box of last
|
||||
BoundingBox last_box = get_extents(last);
|
||||
last_box.offset(SCALED_EPSILON);
|
||||
|
||||
// BBS: get the Polygons upper the polygon this layer
|
||||
Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(grown_upper_slices, last_box);
|
||||
|
||||
//set the clip to a virtual "second perimeter"
|
||||
fill_clip = offset_ex(last, -double(ext_perimeter_spacing));
|
||||
// get the real top surface
|
||||
|
|
@ -856,13 +1021,15 @@ void PerimeterGenerator::process_classic()
|
|||
ExPolygons bridge_checker;
|
||||
// BBS: check whether surface be bridge or not
|
||||
if (this->lower_slices != NULL) {
|
||||
grown_lower_slices =*this->lower_slices;
|
||||
// BBS: get the Polygons below the polygon this layer
|
||||
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, last_box);
|
||||
|
||||
double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width)));
|
||||
bridge_checker = offset_ex(diff_ex(last, grown_lower_slices, ApplySafetyOffset::Yes), 1.5 * bridge_offset);
|
||||
bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset);
|
||||
}
|
||||
ExPolygons delete_bridge = diff_ex(last, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, grown_upper_slices, ApplySafetyOffset::Yes);
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
//get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before)
|
||||
ExPolygons temp_gap = diff_ex(top_polygons, fill_clip);
|
||||
ExPolygons inner_polygons = diff_ex(last,
|
||||
|
|
@ -997,7 +1164,7 @@ void PerimeterGenerator::process_classic()
|
|||
for (ExPolygon& ex : gaps_ex) {
|
||||
//BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
|
||||
ex.douglas_peucker(surface_simplify_resolution);
|
||||
ex.medial_axis(max, min, &polylines);
|
||||
ex.medial_axis(min, max, &polylines);
|
||||
}
|
||||
|
||||
#ifdef GAPS_OF_PERIMETER_DEBUG_TO_SVG
|
||||
|
|
@ -1124,6 +1291,9 @@ void PerimeterGenerator::process_arachne()
|
|||
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2)));
|
||||
}
|
||||
|
||||
|
||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||
// we need to process each island separately because we might have different
|
||||
// extra perimeters for each one
|
||||
for (const Surface& surface : this->slices->surfaces) {
|
||||
|
|
@ -1137,7 +1307,7 @@ void PerimeterGenerator::process_arachne()
|
|||
// BBS: set the topmost layer to be one wall
|
||||
if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr)
|
||||
loop_number = 0;
|
||||
ExPolygons last = offset_ex(surface.expolygon.simplify_p(m_scaled_resolution),
|
||||
ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution),
|
||||
config->precise_outer_wall ? -float(ext_perimeter_width / 2. - bead_width_0 / 2.)
|
||||
: -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
|
||||
Polygons last_p = to_polygons(last);
|
||||
|
|
|
|||
|
|
@ -578,6 +578,9 @@ namespace cereal {
|
|||
|
||||
template<class Archive> void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); }
|
||||
template<class Archive> void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); }
|
||||
|
||||
template<class Archive> void load(Archive &archive, Slic3r::Transform3d &m) { archive.loadBinary((char *) m.data(), sizeof(double) * 16); }
|
||||
template<class Archive> void save(Archive &archive, const Slic3r::Transform3d &m) { archive.saveBinary((char *) m.data(), sizeof(double) * 16); }
|
||||
}
|
||||
|
||||
// To be able to use Vec<> and Mat<> in range based for loops:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,17 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
double Polygon::length() const
|
||||
{
|
||||
double l = 0;
|
||||
if (this->points.size() > 1) {
|
||||
l = (this->points.back() - this->points.front()).cast<double>().norm();
|
||||
for (size_t i = 1; i < this->points.size(); ++ i)
|
||||
l += (this->points[i] - this->points[i - 1]).cast<double>().norm();
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
Lines Polygon::lines() const
|
||||
{
|
||||
return to_lines(*this);
|
||||
|
|
@ -88,36 +99,11 @@ void Polygon::douglas_peucker(double tolerance)
|
|||
this->points = std::move(p);
|
||||
}
|
||||
|
||||
// Does an unoriented polygon contain a point?
|
||||
// Tested by counting intersections along a horizontal line.
|
||||
bool Polygon::contains(const Point &point) const
|
||||
{
|
||||
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
||||
bool result = false;
|
||||
Points::const_iterator i = this->points.begin();
|
||||
Points::const_iterator j = this->points.end() - 1;
|
||||
for (; i != this->points.end(); j = i++) {
|
||||
//FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point(1) well.
|
||||
// Does the ray with y == point(1) intersect this line segment?
|
||||
#if 1
|
||||
if ( (((*i)(1) > point(1)) != ((*j)(1) > point(1)))
|
||||
&& ((double)point(0) < (double)((*j)(0) - (*i)(0)) * (double)(point(1) - (*i)(1)) / (double)((*j)(1) - (*i)(1)) + (double)(*i)(0)) )
|
||||
result = !result;
|
||||
#else
|
||||
if (((*i)(1) > point(1)) != ((*j)(1) > point(1))) {
|
||||
// Orientation predicated relative to i-th point.
|
||||
double orient = (double)(point(0) - (*i)(0)) * (double)((*j)(1) - (*i)(1)) - (double)(point(1) - (*i)(1)) * (double)((*j)(0) - (*i)(0));
|
||||
if (((*i)(1) > (*j)(1)) ? (orient > 0.) : (orient < 0.))
|
||||
result = !result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons()
|
||||
Polygons Polygon::simplify(double tolerance) const
|
||||
{
|
||||
// Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()!
|
||||
assert(this->is_counter_clockwise());
|
||||
|
||||
// repeat first point at the end in order to apply Douglas-Peucker
|
||||
// on the whole polygon
|
||||
Points points = this->points;
|
||||
|
|
@ -130,13 +116,6 @@ Polygons Polygon::simplify(double tolerance) const
|
|||
return simplify_polygons(pp);
|
||||
}
|
||||
|
||||
void Polygon::simplify(double tolerance, Polygons &polygons) const
|
||||
{
|
||||
Polygons pp = this->simplify(tolerance);
|
||||
polygons.reserve(polygons.size() + pp.size());
|
||||
polygons.insert(polygons.end(), pp.begin(), pp.end());
|
||||
}
|
||||
|
||||
// Only call this on convex polygons or it will return invalid results
|
||||
void Polygon::triangulate_convex(Polygons* polygons) const
|
||||
{
|
||||
|
|
@ -171,50 +150,125 @@ Point Polygon::centroid() const
|
|||
return Point(Vec2d(c / (3. * area_sum)));
|
||||
}
|
||||
|
||||
// find all concave vertices (i.e. having an internal angle greater than the supplied angle)
|
||||
// (external = right side, thus we consider ccw orientation)
|
||||
Points Polygon::concave_points(double angle) const
|
||||
bool Polygon::intersection(const Line &line, Point *intersection) const
|
||||
{
|
||||
Points points;
|
||||
angle = 2. * PI - angle + EPSILON;
|
||||
|
||||
// check whether first point forms a concave angle
|
||||
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle)
|
||||
points.push_back(this->points.front());
|
||||
|
||||
// check whether points 1..(n-1) form concave angles
|
||||
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++ p)
|
||||
if (p->ccw_angle(*(p-1), *(p+1)) <= angle)
|
||||
points.push_back(*p);
|
||||
|
||||
// check whether last point forms a concave angle
|
||||
if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle)
|
||||
points.push_back(this->points.back());
|
||||
|
||||
return points;
|
||||
if (this->points.size() < 2)
|
||||
return false;
|
||||
if (Line(this->points.front(), this->points.back()).intersection(line, intersection))
|
||||
return true;
|
||||
for (size_t i = 1; i < this->points.size(); ++ i)
|
||||
if (Line(this->points[i - 1], this->points[i]).intersection(line, intersection))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle)
|
||||
// (external = right side, thus we consider ccw orientation)
|
||||
Points Polygon::convex_points(double angle) const
|
||||
bool Polygon::first_intersection(const Line& line, Point* intersection) const
|
||||
{
|
||||
Points points;
|
||||
angle = 2*PI - angle - EPSILON;
|
||||
|
||||
// check whether first point forms a convex angle
|
||||
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle)
|
||||
points.push_back(this->points.front());
|
||||
|
||||
// check whether points 1..(n-1) form convex angles
|
||||
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
|
||||
if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points.push_back(*p);
|
||||
if (this->points.size() < 2)
|
||||
return false;
|
||||
|
||||
bool found = false;
|
||||
double dmin = 0.;
|
||||
Line l(this->points.back(), this->points.front());
|
||||
for (size_t i = 0; i < this->points.size(); ++ i) {
|
||||
l.b = this->points[i];
|
||||
Point ip;
|
||||
if (l.intersection(line, &ip)) {
|
||||
if (! found) {
|
||||
found = true;
|
||||
dmin = (line.a - ip).cast<double>().squaredNorm();
|
||||
*intersection = ip;
|
||||
} else {
|
||||
double d = (line.a - ip).cast<double>().squaredNorm();
|
||||
if (d < dmin) {
|
||||
dmin = d;
|
||||
*intersection = ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
l.a = l.b;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool Polygon::intersections(const Line &line, Points *intersections) const
|
||||
{
|
||||
if (this->points.size() < 2)
|
||||
return false;
|
||||
|
||||
size_t intersections_size = intersections->size();
|
||||
Line l(this->points.back(), this->points.front());
|
||||
for (size_t i = 0; i < this->points.size(); ++ i) {
|
||||
l.b = this->points[i];
|
||||
Point intersection;
|
||||
if (l.intersection(line, &intersection))
|
||||
intersections->emplace_back(std::move(intersection));
|
||||
l.a = l.b;
|
||||
}
|
||||
return intersections->size() > intersections_size;
|
||||
}
|
||||
bool Polygon::overlaps(const Polygons& other) const
|
||||
{
|
||||
if (this->empty() || other.empty())
|
||||
return false;
|
||||
Polylines pl_out = intersection_pl(to_polylines(other), *this);
|
||||
|
||||
// See unit test SCENARIO("Clipper diff with polyline", "[Clipper]")
|
||||
// for in which case the intersection_pl produces any intersection.
|
||||
return !pl_out.empty() ||
|
||||
// If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation.
|
||||
std::any_of(other.begin(), other.end(), [this](auto& poly) {return poly.contains(this->points.front()); });
|
||||
}
|
||||
// Filter points from poly to the output with the help of FilterFn.
|
||||
// filter function receives two vectors:
|
||||
// v1: this_point - previous_point
|
||||
// v2: next_point - this_point
|
||||
// and returns true if the point is to be copied to the output.
|
||||
template<typename FilterFn>
|
||||
Points filter_points_by_vectors(const Points &poly, FilterFn filter)
|
||||
{
|
||||
// Last point is the first point visited.
|
||||
Point p1 = poly.back();
|
||||
// Previous vector to p1.
|
||||
Vec2d v1 = (p1 - *(poly.end() - 2)).cast<double>();
|
||||
|
||||
Points out;
|
||||
for (Point p2 : poly) {
|
||||
// p2 is next point to the currently visited point p1.
|
||||
Vec2d v2 = (p2 - p1).cast<double>();
|
||||
if (filter(v1, v2))
|
||||
out.emplace_back(p2);
|
||||
v1 = v2;
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
// check whether last point forms a convex angle
|
||||
if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle)
|
||||
points.push_back(this->points.back());
|
||||
|
||||
return points;
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename ConvexConcaveFilterFn>
|
||||
Points filter_convex_concave_points_by_angle_threshold(const Points &poly, double angle_threshold, ConvexConcaveFilterFn convex_concave_filter)
|
||||
{
|
||||
assert(angle_threshold >= 0.);
|
||||
if (angle_threshold < EPSILON) {
|
||||
double cos_angle = cos(angle_threshold);
|
||||
return filter_points_by_vectors(poly, [convex_concave_filter, cos_angle](const Vec2d &v1, const Vec2d &v2){
|
||||
return convex_concave_filter(v1, v2) && v1.normalized().dot(v2.normalized()) < cos_angle;
|
||||
});
|
||||
} else {
|
||||
return filter_points_by_vectors(poly, [convex_concave_filter](const Vec2d &v1, const Vec2d &v2){
|
||||
return convex_concave_filter(v1, v2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Points Polygon::convex_points(double angle_threshold) const
|
||||
{
|
||||
return filter_convex_concave_points_by_angle_threshold(this->points, angle_threshold, [](const Vec2d &v1, const Vec2d &v2){ return cross2(v1, v2) > 0.; });
|
||||
}
|
||||
|
||||
Points Polygon::concave_points(double angle_threshold) const
|
||||
{
|
||||
return filter_convex_concave_points_by_angle_threshold(this->points, angle_threshold, [](const Vec2d &v1, const Vec2d &v2){ return cross2(v1, v2) < 0.; });
|
||||
}
|
||||
|
||||
// Projection of a point onto the polygon.
|
||||
|
|
@ -540,4 +594,74 @@ void remove_collinear(Polygons &polys)
|
|||
remove_collinear(poly);
|
||||
}
|
||||
|
||||
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(source_polygons.size());
|
||||
for (const Polygon &source_polygon : source_polygons) {
|
||||
// Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline),
|
||||
Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance);
|
||||
// then remove the last (repeated) point.
|
||||
simplified.pop_back();
|
||||
// Simplify the decimated contour by ClipperLib.
|
||||
bool ccw = ClipperLib::Area(simplified) > 0.;
|
||||
for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) {
|
||||
if (! ccw)
|
||||
// ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW.
|
||||
std::reverse(path.begin(), path.end());
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Do polygons match? If they match, they must have the same topology,
|
||||
// however their contours may be rotated.
|
||||
bool polygons_match(const Polygon &l, const Polygon &r)
|
||||
{
|
||||
if (l.size() != r.size())
|
||||
return false;
|
||||
auto it_l = std::find(l.points.begin(), l.points.end(), r.points.front());
|
||||
if (it_l == l.points.end())
|
||||
return false;
|
||||
auto it_r = r.points.begin();
|
||||
for (; it_l != l.points.end(); ++ it_l, ++ it_r)
|
||||
if (*it_l != *it_r)
|
||||
return false;
|
||||
it_l = l.points.begin();
|
||||
for (; it_r != r.points.end(); ++ it_l, ++ it_r)
|
||||
if (*it_l != *it_r)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool overlaps(const Polygons& polys1, const Polygons& polys2)
|
||||
{
|
||||
for (const Polygon& poly1 : polys1) {
|
||||
if (poly1.overlaps(polys2))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool contains(const Polygon &polygon, const Point &p, bool border_result)
|
||||
{
|
||||
if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points);
|
||||
poly_count_inside == -1)
|
||||
return border_result;
|
||||
else
|
||||
return (poly_count_inside % 2) == 1;
|
||||
}
|
||||
|
||||
bool contains(const Polygons &polygons, const Point &p, bool border_result)
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const Polygon &poly : polygons) {
|
||||
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
|
||||
if (is_inside_this_poly == -1)
|
||||
return border_result;
|
||||
poly_count_inside += is_inside_this_poly;
|
||||
}
|
||||
return (poly_count_inside % 2) == 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,11 +15,14 @@ using Polygons = std::vector<Polygon>;
|
|||
using PolygonPtrs = std::vector<Polygon*>;
|
||||
using ConstPolygonPtrs = std::vector<const Polygon*>;
|
||||
|
||||
// Returns true if inside. Returns border_result if on boundary.
|
||||
bool contains(const Polygon& polygon, const Point& p, bool border_result = true);
|
||||
bool contains(const Polygons& polygons, const Point& p, bool border_result = true);
|
||||
|
||||
class Polygon : public MultiPoint
|
||||
{
|
||||
public:
|
||||
Polygon() = default;
|
||||
virtual ~Polygon() = default;
|
||||
explicit Polygon(const Points &points) : MultiPoint(points) {}
|
||||
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
||||
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
||||
|
|
@ -38,9 +41,10 @@ public:
|
|||
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
|
||||
|
||||
// last point == first point for polygons
|
||||
const Point& last_point() const override { return this->points.front(); }
|
||||
const Point& last_point() const { return this->points.front(); }
|
||||
|
||||
Lines lines() const override;
|
||||
double length() const;
|
||||
Lines lines() const;
|
||||
Polyline split_at_vertex(const Point &point) const;
|
||||
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
|
||||
Polyline split_at_index(int index) const;
|
||||
|
|
@ -58,15 +62,27 @@ public:
|
|||
void douglas_peucker(double tolerance);
|
||||
|
||||
// Does an unoriented polygon contain a point?
|
||||
// Tested by counting intersections along a horizontal line.
|
||||
bool contains(const Point &point) const;
|
||||
bool contains(const Point &point) const { return Slic3r::contains(*this, point, true); }
|
||||
// Approximate on boundary test.
|
||||
bool on_boundary(const Point &point, double eps) const
|
||||
{ return (this->point_projection(point) - point).cast<double>().squaredNorm() < eps * eps; }
|
||||
|
||||
// Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()!
|
||||
Polygons simplify(double tolerance) const;
|
||||
void simplify(double tolerance, Polygons &polygons) const;
|
||||
void densify(float min_length, std::vector<float>* lengths = nullptr);
|
||||
void triangulate_convex(Polygons* polygons) const;
|
||||
Point centroid() const;
|
||||
Points concave_points(double angle = PI) const;
|
||||
Points convex_points(double angle = PI) const;
|
||||
|
||||
bool intersection(const Line& line, Point* intersection) const;
|
||||
bool first_intersection(const Line& line, Point* intersection) const;
|
||||
bool intersections(const Line& line, Points* intersections) const;
|
||||
bool overlaps(const Polygons& other) const;
|
||||
|
||||
// Considering CCW orientation of this polygon, find all convex resp. concave points
|
||||
// with the angle at the vertex larger than a threshold.
|
||||
// Zero angle_threshold means to accept all convex resp. concave points.
|
||||
Points convex_points(double angle_threshold = 0.) const;
|
||||
Points concave_points(double angle_threshold = 0.) const;
|
||||
// Projection of a point onto the polygon.
|
||||
Point point_projection(const Point &point) const;
|
||||
std::vector<float> parameter_by_length() const;
|
||||
|
|
@ -136,14 +152,7 @@ inline void polygons_append(Polygons &dst, Polygons &&src)
|
|||
}
|
||||
}
|
||||
|
||||
inline Polygons polygons_simplify(const Polygons &polys, double tolerance)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(polys.size());
|
||||
for (const Polygon &p : polys)
|
||||
polygons_append(out, p.simplify(tolerance));
|
||||
return out;
|
||||
}
|
||||
Polygons polygons_simplify(const Polygons &polys, double tolerance);
|
||||
|
||||
inline void polygons_rotate(Polygons &polys, double angle)
|
||||
{
|
||||
|
|
@ -164,13 +173,16 @@ inline Points to_points(const Polygon &poly)
|
|||
return poly.points;
|
||||
}
|
||||
|
||||
inline size_t count_points(const Polygons &polys) {
|
||||
size_t n_points = 0;
|
||||
for (const auto &poly: polys) n_points += poly.points.size();
|
||||
return n_points;
|
||||
}
|
||||
|
||||
inline Points to_points(const Polygons &polys)
|
||||
{
|
||||
size_t n_points = 0;
|
||||
for (size_t i = 0; i < polys.size(); ++ i)
|
||||
n_points += polys[i].points.size();
|
||||
Points points;
|
||||
points.reserve(n_points);
|
||||
points.reserve(count_points(polys));
|
||||
for (const Polygon &poly : polys)
|
||||
append(points, poly.points);
|
||||
return points;
|
||||
|
|
@ -190,11 +202,8 @@ inline Lines to_lines(const Polygon &poly)
|
|||
|
||||
inline Lines to_lines(const Polygons &polys)
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (size_t i = 0; i < polys.size(); ++ i)
|
||||
n_lines += polys[i].points.size();
|
||||
Lines lines;
|
||||
lines.reserve(n_lines);
|
||||
lines.reserve(count_points(polys));
|
||||
for (size_t i = 0; i < polys.size(); ++ i) {
|
||||
const Polygon &poly = polys[i];
|
||||
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
|
||||
|
|
@ -204,18 +213,22 @@ inline Lines to_lines(const Polygons &polys)
|
|||
return lines;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(const Polygons &polys)
|
||||
inline Polyline to_polyline(const Polygon &polygon)
|
||||
{
|
||||
Polylines polylines;
|
||||
polylines.assign(polys.size(), Polyline());
|
||||
size_t idx = 0;
|
||||
for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = it->points;
|
||||
pl.points.push_back(it->points.front());
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
Polyline out;
|
||||
out.points.reserve(polygon.size() + 1);
|
||||
out.points.assign(polygon.points.begin(), polygon.points.end());
|
||||
out.points.push_back(polygon.points.front());
|
||||
return out;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(const Polygons &polygons)
|
||||
{
|
||||
Polylines out;
|
||||
out.reserve(polygons.size());
|
||||
for (const Polygon &polygon : polygons)
|
||||
out.emplace_back(to_polyline(polygon));
|
||||
return out;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(Polygons &&polys)
|
||||
|
|
@ -223,10 +236,10 @@ inline Polylines to_polylines(Polygons &&polys)
|
|||
Polylines polylines;
|
||||
polylines.assign(polys.size(), Polyline());
|
||||
size_t idx = 0;
|
||||
for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) {
|
||||
for (auto it = polys.begin(); it != polys.end(); ++ it) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(it->points);
|
||||
pl.points.push_back(it->points.front());
|
||||
pl.points.push_back(pl.points.front());
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
|
|
@ -245,11 +258,16 @@ inline Polygons to_polygons(std::vector<Points> &&paths)
|
|||
{
|
||||
Polygons out;
|
||||
out.reserve(paths.size());
|
||||
for (const Points &path : paths)
|
||||
for (Points &path : paths)
|
||||
out.emplace_back(std::move(path));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Do polygons match? If they match, they must have the same topology,
|
||||
// however their contours may be rotated.
|
||||
bool polygons_match(const Polygon &l, const Polygon &r);
|
||||
|
||||
bool overlaps(const Polygons& polys1, const Polygons& polys2);
|
||||
} // Slic3r
|
||||
|
||||
// start Boost
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
#include "Polyline.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <iostream>
|
||||
|
|
|
|||
|
|
@ -234,6 +234,10 @@ public:
|
|||
std::reverse(this->width.begin(), this->width.end());
|
||||
std::swap(this->endpoints.first, this->endpoints.second);
|
||||
}
|
||||
void clear() {
|
||||
Polyline::clear();
|
||||
width.clear();
|
||||
}
|
||||
|
||||
std::vector<coordf_t> width;
|
||||
std::pair<bool,bool> endpoints;
|
||||
|
|
|
|||
|
|
@ -725,7 +725,7 @@ static std::vector<std::string> s_Preset_print_options {
|
|||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
||||
// BBS
|
||||
//"independent_support_layer_height",
|
||||
"independent_support_layer_height",
|
||||
"support_angle", "support_interface_top_layers", "support_interface_bottom_layers",
|
||||
"support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern",
|
||||
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "max_bridge_length", "print_sequence",
|
||||
|
|
@ -747,16 +747,16 @@ static std::vector<std::string> s_Preset_print_options {
|
|||
"initial_layer_infill_speed", "only_one_wall_top",
|
||||
"timelapse_type", "internal_bridge_support_thickness",
|
||||
"wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
|
||||
"wall_distribution_count", "min_feature_size", "min_bead_width",
|
||||
"wall_distribution_count", "min_feature_size", "min_bead_width", "post_process",
|
||||
// SoftFever
|
||||
"small_perimeter_speed", "small_perimeter_threshold","bridge_angle", "filter_out_gap_fill", "post_process", "travel_acceleration","inner_wall_acceleration",
|
||||
"small_perimeter_speed", "small_perimeter_threshold","bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration",
|
||||
"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk",
|
||||
"top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap",
|
||||
"role_based_wipe_speed", "wipe_speed", "accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops",
|
||||
"bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration",
|
||||
"sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim",
|
||||
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
|
||||
"initial_layer_travel_speed", "exclude_object"
|
||||
"role_based_wipe_speed", "wipe_speed", "accel_to_decel_enable", "accel_to_decel_factor", "wipe_on_loops",
|
||||
"bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration",
|
||||
"sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim",
|
||||
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
|
||||
"initial_layer_travel_speed", "exclude_object"
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -241,12 +241,12 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||
//BBS: change system config to json
|
||||
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
|
||||
|
||||
// Load default user presets always
|
||||
load_user_presets(DEFAULT_USER_FOLDER_NAME, substitution_rule);
|
||||
// BBS load preset from user's folder, load system default if
|
||||
// BBS: change directories by design
|
||||
std::string dir_user_presets = config.get("preset_folder");
|
||||
if (!dir_user_presets.empty()) {
|
||||
if (dir_user_presets.empty()) {
|
||||
load_user_presets(DEFAULT_USER_FOLDER_NAME, substitution_rule);
|
||||
} else {
|
||||
load_user_presets(dir_user_presets, substitution_rule);
|
||||
}
|
||||
|
||||
|
|
@ -650,24 +650,26 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
|||
BOOST_LOG_TRIVIAL(warning) << "Preset type is unknown, not loading: " << name;
|
||||
continue;
|
||||
}
|
||||
if (overwrite == 0) overwrite = 1;
|
||||
if (auto p = collection->find_preset(name, false)) {
|
||||
if (p->is_default || p->is_system) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present and is system preset, not loading: " << name;
|
||||
continue;
|
||||
}
|
||||
overwrite = override_confirm(name);
|
||||
if (overwrite == 0 || overwrite == 2) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (overwrite == 0 || overwrite == 2) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
|
||||
continue;
|
||||
}
|
||||
|
||||
DynamicPrintConfig new_config;
|
||||
Preset * inherit_preset = nullptr;
|
||||
ConfigOption *inherits_config = config.option(BBL_JSON_KEY_INHERITS);
|
||||
std::string inherits_value;
|
||||
if (inherits_config) {
|
||||
ConfigOptionString *option_str = dynamic_cast<ConfigOptionString *>(inherits_config);
|
||||
std::string inherits_value = option_str->value;
|
||||
inherits_value = option_str->value;
|
||||
inherit_preset = collection->find_preset(inherits_value, false, true);
|
||||
}
|
||||
if (inherit_preset) {
|
||||
|
|
@ -684,6 +686,7 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
|||
Preset &preset = collection->load_preset(collection->path_from_name(name), name, std::move(new_config), false);
|
||||
preset.is_external = true;
|
||||
preset.version = *version;
|
||||
inherit_preset = collection->find_preset(inherits_value, false, true); // pointer maybe wrong after insert, redo find
|
||||
if (inherit_preset)
|
||||
preset.base_id = inherit_preset->setting_id;
|
||||
Preset::normalize(preset.config);
|
||||
|
|
@ -863,7 +866,7 @@ void PresetBundle::remove_users_preset(AppConfig &config, std::map<std::string,
|
|||
std::string printer_selected_preset_name = printers.get_selected_preset().name;
|
||||
bool need_reset_printer_preset = false;
|
||||
for (auto it = printers.begin(); it != printers.end();) {
|
||||
if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0 && check_removed(*it)) {
|
||||
if (it->is_user() && it->user_id.compare(preset_folder_user_id) == 0 && check_removed(*it)) {
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":printers erase %1%, type %2%, user_id %3%") % it->name % Preset::get_type_string(it->type) % it->user_id;
|
||||
if (it->name == printer_selected_preset_name)
|
||||
need_reset_printer_preset = true;
|
||||
|
|
@ -1306,7 +1309,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
|||
if (config.has("presets", "filament_colors")) {
|
||||
boost::algorithm::split(filament_colors, config.get("presets", "filament_colors"), boost::algorithm::is_any_of(","));
|
||||
}
|
||||
filament_colors.resize(filament_presets.size());
|
||||
filament_colors.resize(filament_presets.size(), "#00AE42");
|
||||
project_config.option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
|
||||
std::vector<std::string> matrix;
|
||||
if (config.has("presets", "flush_volumes_matrix")) {
|
||||
|
|
@ -1319,6 +1322,11 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
|||
auto flush_volumes_vector = matrix | boost::adaptors::transformed(boost::lexical_cast<double, std::string>);
|
||||
project_config.option<ConfigOptionFloats>("flush_volumes_vector")->values = std::vector<double>(flush_volumes_vector.begin(), flush_volumes_vector.end());
|
||||
}
|
||||
if (config.has("app", "flush_multiplier")) {
|
||||
std::string str_flush_multiplier = config.get("app", "flush_multiplier");
|
||||
if (!str_flush_multiplier.empty())
|
||||
project_config.option<ConfigOptionFloat>("flush_multiplier")->set(new ConfigOptionFloat(std::stof(str_flush_multiplier)));
|
||||
}
|
||||
|
||||
// Update visibility of presets based on their compatibility with the active printer.
|
||||
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||
|
|
@ -1393,6 +1401,9 @@ void PresetBundle::export_selections(AppConfig &config)
|
|||
config.set("presets", "flush_volumes_vector", flush_volumes_vector);
|
||||
|
||||
config.set("presets", PRESET_PRINTER_NAME, printers.get_selected_preset_name());
|
||||
|
||||
auto flush_multi_opt = project_config.option<ConfigOptionFloat>("flush_multiplier");
|
||||
config.set("flush_multiplier", std::to_string(flush_multi_opt ? flush_multi_opt->getFloat() : 1.0f));
|
||||
// BBS
|
||||
//config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
||||
//config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
|
||||
|
|
@ -3510,6 +3521,7 @@ std::vector<std::string> PresetBundle::export_current_configs(const std::string
|
|||
if ((preset->is_system && !export_system_settings) || preset->is_default)
|
||||
continue;
|
||||
std::string file = path + "/" + preset->name + ".json";
|
||||
if (overwrite == 0) overwrite = 1;
|
||||
if (boost::filesystem::exists(file) && overwrite < 2) {
|
||||
overwrite = override_confirm(preset->name);
|
||||
if (overwrite == 0 || overwrite == 2)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
//BBS: add json support
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "GCode/ConflictChecker.hpp"
|
||||
|
||||
#include <codecvt>
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
// Mark string for localization and translate.
|
||||
|
|
@ -227,7 +231,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||
steps.emplace_back(psWipeTower);
|
||||
steps.emplace_back(psSkirtBrim);
|
||||
} else if (opt_key == "filament_soluble"
|
||||
|| opt_key == "filament_is_support") {
|
||||
|| opt_key == "filament_is_support"
|
||||
|| opt_key == "independent_support_layer_height") {
|
||||
steps.emplace_back(psWipeTower);
|
||||
// Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers.
|
||||
// Thus switching between soluble / non-soluble interface layer material may require recalculation of supports.
|
||||
|
|
@ -366,10 +371,11 @@ std::vector<unsigned int> Print::extruders(bool conside_custom_gcode) const
|
|||
|
||||
if (conside_custom_gcode) {
|
||||
//BBS
|
||||
int num_extruders = m_config.filament_colour.size();
|
||||
for (auto plate_data : m_model.plates_custom_gcodes) {
|
||||
for (auto item : plate_data.second.gcodes) {
|
||||
if (item.type == CustomGCode::Type::ToolChange)
|
||||
extruders.push_back((unsigned int)item.extruder);
|
||||
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= num_extruders)
|
||||
extruders.push_back((unsigned int)(item.extruder - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -488,14 +494,17 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
|||
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
|
||||
for (const PrintInstance &instance : print_object->instances()) {
|
||||
Polygon convex_hull_no_offset = convex_hull0, convex_hull;
|
||||
convex_hull = offset(convex_hull_no_offset,
|
||||
auto tmp = offset(convex_hull_no_offset,
|
||||
// Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects
|
||||
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
|
||||
float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)),
|
||||
jtRound, scale_(0.1)).front();
|
||||
// instance.shift is a position of a centered object, while model object may not be centered.
|
||||
// Convert the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset.
|
||||
convex_hull.translate(instance.shift - print_object->center_offset());
|
||||
jtRound, scale_(0.1));
|
||||
if (!tmp.empty()) { // tmp may be empty due to clipper's bug, see STUDIO-2452
|
||||
convex_hull = tmp.front();
|
||||
// instance.shift is a position of a centered object, while model object may not be centered.
|
||||
// Convert the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset.
|
||||
convex_hull.translate(instance.shift - print_object->center_offset());
|
||||
}
|
||||
convex_hull_no_offset.translate(instance.shift - print_object->center_offset());
|
||||
//juedge the exclude area
|
||||
if (!intersection(exclude_polys, convex_hull_no_offset).empty()) {
|
||||
|
|
@ -1136,6 +1145,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
|||
if (layer_height > min_nozzle_diameter)
|
||||
return {L("Layer height cannot exceed nozzle diameter"), object, "layer_height"};
|
||||
|
||||
for (auto range : object->m_model_object->layer_config_ranges) {
|
||||
double range_layer_height = range.second.opt_float("layer_height");
|
||||
if (range_layer_height > object->m_slicing_params.max_layer_height ||
|
||||
range_layer_height < object->m_slicing_params.min_layer_height)
|
||||
return { L("Layer height cannot exceed nozzle diameter"), nullptr, "layer_height" };
|
||||
}
|
||||
|
||||
// Validate extrusion widths.
|
||||
std::string err_msg;
|
||||
if (!validate_extrusion_width(object->config(), "line_width", layer_height, err_msg))
|
||||
|
|
@ -1475,14 +1491,20 @@ void Print::process(bool use_cache)
|
|||
for (int index = 0; index < object_count; index++)
|
||||
{
|
||||
PrintObject *obj = m_objects[index];
|
||||
bool found_shared = false;
|
||||
if (need_slicing_objects.find(obj) == need_slicing_objects.end()) {
|
||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||
{
|
||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||
obj->set_shared_object(slicing_obj);
|
||||
found_shared = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_shared) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Also can not find the shared object, identify_id %1%")%obj->model_object()->instances[0]->loaded_id;
|
||||
throw Slic3r::SlicingError("Can not find the cached data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1685,6 +1707,27 @@ void Print::process(bool use_cache)
|
|||
}
|
||||
}
|
||||
|
||||
// BBS
|
||||
if(!m_no_check)
|
||||
{
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
auto startTime = Clock::now();
|
||||
std::optional<const FakeWipeTower *> wipe_tower_opt = {};
|
||||
if (this->has_wipe_tower()) {
|
||||
m_fake_wipe_tower.set_pos({m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index)});
|
||||
wipe_tower_opt = std::make_optional<const FakeWipeTower *>(&m_fake_wipe_tower);
|
||||
}
|
||||
auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects, wipe_tower_opt);
|
||||
auto endTime = Clock::now();
|
||||
volatile double seconds = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() / (double) 1000;
|
||||
BOOST_LOG_TRIVIAL(info) << "gcode path conflicts check takes " << seconds << " secs.";
|
||||
|
||||
m_conflict_result = conflictRes;
|
||||
if (conflictRes.has_value()) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%")%conflictRes.value()._objName1 %conflictRes.value()._objName2;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
|
||||
}
|
||||
|
||||
|
|
@ -1713,6 +1756,8 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
|||
const Vec3d origin = this->get_plate_origin();
|
||||
gcode.set_gcode_offset(origin(0), origin(1));
|
||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
//BBS
|
||||
result->conflict_result = m_conflict_result;
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
|
|
@ -2147,8 +2192,11 @@ void Print::_make_wipe_tower()
|
|||
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
|
||||
wipe_tower.tool_change((unsigned int)(-1)));
|
||||
|
||||
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
|
||||
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
|
||||
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
|
||||
const Vec3d origin = this->get_plate_origin();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), m_wipe_tower_data.depth,
|
||||
m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())});
|
||||
}
|
||||
|
||||
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
|
||||
|
|
@ -2251,14 +2299,18 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
|
|||
#define JSON_EXPOLYGON "expolygon"
|
||||
#define JSON_ARC_FITTING "arc_fitting"
|
||||
#define JSON_OBJECT_NAME "name"
|
||||
#define JSON_ARRANGE_ORDER "arrange_order"
|
||||
#define JSON_IDENTIFY_ID "identify_id"
|
||||
|
||||
|
||||
#define JSON_LAYERS "layers"
|
||||
#define JSON_SUPPORT_LAYERS "support_layers"
|
||||
#define JSON_TREE_SUPPORT_LAYERS "tree_support_layers"
|
||||
#define JSON_LAYER_REGIONS "layer_regions"
|
||||
#define JSON_FIRSTLAYER_GROUPS "first_layer_groups"
|
||||
|
||||
#define JSON_FIRSTLAYER_GROUP_ID "group_id"
|
||||
#define JSON_FIRSTLAYER_GROUP_VOLUME_IDS "volume_ids"
|
||||
#define JSON_FIRSTLAYER_GROUP_SLICES "slices"
|
||||
|
||||
#define JSON_LAYER_PRINT_Z "print_z"
|
||||
#define JSON_LAYER_SLICE_Z "slice_z"
|
||||
|
|
@ -2584,6 +2636,24 @@ static void to_json(json& j, const LayerRegion& layer_region) {
|
|||
return;
|
||||
}
|
||||
|
||||
static void to_json(json& j, const groupedVolumeSlices& first_layer_group) {
|
||||
json volumes_json = json::array(), slices_json = json::array();
|
||||
j[JSON_FIRSTLAYER_GROUP_ID] = first_layer_group.groupId;
|
||||
|
||||
for (const ObjectID& obj_id : first_layer_group.volume_ids)
|
||||
{
|
||||
volumes_json.push_back(obj_id.id);
|
||||
}
|
||||
j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS] = std::move(volumes_json);
|
||||
|
||||
for (const ExPolygon& slice_expolygon : first_layer_group.slices) {
|
||||
json slice_expolygon_json = slice_expolygon;
|
||||
|
||||
slices_json.push_back(std::move(slice_expolygon_json));
|
||||
}
|
||||
j[JSON_FIRSTLAYER_GROUP_SLICES] = std::move(slices_json);
|
||||
}
|
||||
|
||||
//load apis from json
|
||||
static void from_json(const json& j, Points& p_s) {
|
||||
int array_size = j.size();
|
||||
|
|
@ -2909,7 +2979,7 @@ void extract_support_layer(const json& support_layer_json, SupportLayer& support
|
|||
ExPolygon polygon;
|
||||
|
||||
polygon = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS][islands_index];
|
||||
support_layer.support_islands.expolygons.push_back(std::move(polygon));
|
||||
support_layer.support_islands.push_back(std::move(polygon));
|
||||
}
|
||||
|
||||
//support_fills
|
||||
|
|
@ -2930,6 +3000,29 @@ void extract_support_layer(const json& support_layer_json, SupportLayer& support
|
|||
return;
|
||||
}
|
||||
|
||||
static void from_json(const json& j, groupedVolumeSlices& firstlayer_group)
|
||||
{
|
||||
firstlayer_group.groupId = j[JSON_FIRSTLAYER_GROUP_ID];
|
||||
|
||||
int volume_count = j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS].size();
|
||||
for (int volume_index = 0; volume_index < volume_count; volume_index++)
|
||||
{
|
||||
ObjectID obj_id;
|
||||
|
||||
obj_id.id = j[JSON_FIRSTLAYER_GROUP_VOLUME_IDS][volume_index];
|
||||
firstlayer_group.volume_ids.push_back(std::move(obj_id));
|
||||
}
|
||||
|
||||
int slices_count = j[JSON_FIRSTLAYER_GROUP_SLICES].size();
|
||||
for (int slice_index = 0; slice_index < slices_count; slice_index++)
|
||||
{
|
||||
ExPolygon polygon;
|
||||
|
||||
polygon = j[JSON_FIRSTLAYER_GROUP_SLICES][slice_index];
|
||||
firstlayer_group.slices.push_back(std::move(polygon));
|
||||
}
|
||||
}
|
||||
|
||||
int Print::export_cached_data(const std::string& directory, bool with_space)
|
||||
{
|
||||
int ret = 0;
|
||||
|
|
@ -2987,18 +3080,19 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
|
|||
BOOST_LOG_TRIVIAL(info) << boost::format("shared object %1%, skip directly")%model_obj->name;
|
||||
continue;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("begin to dump object %1%")%model_obj->name;
|
||||
|
||||
const PrintInstance &print_instance = obj->instances()[0];
|
||||
const ModelInstance *model_instance = print_instance.model_instance;
|
||||
int arrange_order = model_instance->arrange_order;
|
||||
std::string file_name = directory +"/obj_"+std::to_string(arrange_order)+".json";
|
||||
size_t identify_id = (model_instance->loaded_id > 0)?model_instance->loaded_id: model_instance->id().id;
|
||||
std::string file_name = directory +"/obj_"+std::to_string(identify_id)+".json";
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("begin to dump object %1%, identify_id %2% to %3%")%model_obj->name %identify_id %file_name;
|
||||
|
||||
try {
|
||||
json root_json, layers_json = json::array(), support_layers_json = json::array();
|
||||
json root_json, layers_json = json::array(), support_layers_json = json::array(), first_layer_groups = json::array();
|
||||
|
||||
root_json[JSON_OBJECT_NAME] = model_obj->name;
|
||||
root_json[JSON_ARRANGE_ORDER] = arrange_order;
|
||||
root_json[JSON_IDENTIFY_ID] = identify_id;
|
||||
|
||||
//export the layers
|
||||
std::vector<json> layers_json_vector(obj->layer_count());
|
||||
|
|
@ -3043,7 +3137,7 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
|
|||
support_layer_json[JSON_SUPPORT_LAYER_TYPE] = support_layer->support_type;
|
||||
|
||||
//support_islands
|
||||
for (const ExPolygon& support_island : support_layer->support_islands.expolygons) {
|
||||
for (const ExPolygon& support_island : support_layer->support_islands) {
|
||||
json support_island_json = support_island;
|
||||
support_islands_json.push_back(std::move(support_island_json));
|
||||
}
|
||||
|
|
@ -3104,6 +3198,35 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
|
|||
} // for each layer*/
|
||||
root_json[JSON_SUPPORT_LAYERS] = std::move(support_layers_json);
|
||||
|
||||
const std::vector<groupedVolumeSlices> &first_layer_obj_groups = obj->firstLayerObjGroups();
|
||||
for (size_t s_group_index = 0; s_group_index < first_layer_obj_groups.size(); ++ s_group_index) {
|
||||
groupedVolumeSlices group = first_layer_obj_groups[s_group_index];
|
||||
|
||||
//convert the id
|
||||
for (ObjectID& obj_id : group.volume_ids)
|
||||
{
|
||||
const ModelVolume* currentModelVolumePtr = nullptr;
|
||||
//BBS: support shared object logic
|
||||
const PrintObject* shared_object = obj->get_shared_object();
|
||||
if (!shared_object)
|
||||
shared_object = obj;
|
||||
const ModelVolumePtrs& volumes_ptr = shared_object->model_object()->volumes;
|
||||
size_t volume_count = volumes_ptr.size();
|
||||
for (size_t index = 0; index < volume_count; index ++) {
|
||||
currentModelVolumePtr = volumes_ptr[index];
|
||||
if (currentModelVolumePtr->id() == obj_id) {
|
||||
obj_id.id = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json first_layer_group_json;
|
||||
|
||||
first_layer_group_json = group;
|
||||
first_layer_groups.push_back(std::move(first_layer_group_json));
|
||||
}
|
||||
root_json[JSON_FIRSTLAYER_GROUPS] = std::move(first_layer_groups);
|
||||
|
||||
filename_vector.push_back(file_name);
|
||||
json_vector.push_back(std::move(root_json));
|
||||
|
|
@ -3183,12 +3306,14 @@ int Print::load_cached_data(const std::string& directory)
|
|||
obj->clear_layers();
|
||||
obj->clear_support_layers();
|
||||
|
||||
int arrange_order = model_instance->arrange_order;
|
||||
if (arrange_order <= 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": object %1% has invalid arrange_order %2%, can not load cached_data")%model_obj->name %arrange_order;
|
||||
continue;
|
||||
int identify_id = model_instance->loaded_id;
|
||||
if (identify_id <= 0) {
|
||||
//for old 3mf
|
||||
identify_id = model_instance->id().id;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": object %1%'s loaded_id is 0, need to use the instance_id %2%")%model_obj->name %identify_id;
|
||||
//continue;
|
||||
}
|
||||
std::string file_name = directory +"/obj_"+std::to_string(arrange_order)+".json";
|
||||
std::string file_name = directory +"/obj_"+std::to_string(identify_id)+".json";
|
||||
|
||||
if (!fs::exists(file_name)) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(": file %1% not exist, maybe a shared object, skip it")%file_name;
|
||||
|
|
@ -3232,13 +3357,15 @@ int Print::load_cached_data(const std::string& directory)
|
|||
//ifs >> root_json;
|
||||
|
||||
std::string name = root_json.at(JSON_OBJECT_NAME);
|
||||
int order = root_json.at(JSON_ARRANGE_ORDER);
|
||||
int layer_count = 0, support_layer_count = 0;
|
||||
int identify_id = root_json.at(JSON_IDENTIFY_ID);
|
||||
int layer_count = 0, support_layer_count = 0, firstlayer_group_count = 0;
|
||||
|
||||
layer_count = root_json[JSON_LAYERS].size();
|
||||
support_layer_count = root_json[JSON_SUPPORT_LAYERS].size();
|
||||
firstlayer_group_count = root_json[JSON_FIRSTLAYER_GROUPS].size();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(":will load %1%, arrange_order %2%, layer_count %3%, support_layer_count %4%")%name %order %layer_count %support_layer_count;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(":will load %1%, identify_id %2%, layer_count %3%, support_layer_count %4%, firstlayer_group_count %5%")
|
||||
%name %identify_id %layer_count %support_layer_count %firstlayer_group_count;
|
||||
|
||||
Layer* previous_layer = NULL;
|
||||
//create layer and layer regions
|
||||
|
|
@ -3319,6 +3446,31 @@ int Print::load_cached_data(const std::string& directory)
|
|||
}
|
||||
);
|
||||
|
||||
//load first group volumes
|
||||
std::vector<groupedVolumeSlices>& firstlayer_objgroups = obj->firstLayerObjGroupsMod();
|
||||
for (int index = 0; index < firstlayer_group_count; index++)
|
||||
{
|
||||
json& firstlayer_group_json = root_json[JSON_FIRSTLAYER_GROUPS][index];
|
||||
groupedVolumeSlices firstlayer_group = firstlayer_group_json;
|
||||
//convert the id
|
||||
for (ObjectID& obj_id : firstlayer_group.volume_ids)
|
||||
{
|
||||
ModelVolume* currentModelVolumePtr = nullptr;
|
||||
ModelVolumePtrs& volumes_ptr = obj->model_object()->volumes;
|
||||
size_t volume_count = volumes_ptr.size();
|
||||
if (obj_id.id < volume_count) {
|
||||
currentModelVolumePtr = volumes_ptr[obj_id.id];
|
||||
obj_id = currentModelVolumePtr->id();
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": can not find volume_id %1% from object file %2% in firstlayer groups, volume_count %3%!")
|
||||
%obj_id.id %object_filenames[obj_index].first %volume_count;
|
||||
return CLI_IMPORT_CACHE_LOAD_FAILED;
|
||||
}
|
||||
}
|
||||
firstlayer_objgroups.push_back(std::move(firstlayer_group));
|
||||
}
|
||||
|
||||
count ++;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": load object %1% from %2% successfully.")%count%object_filenames[obj_index].first;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ struct groupedVolumeSlices
|
|||
enum SupportNecessaryType {
|
||||
NoNeedSupp=0,
|
||||
SharpTail,
|
||||
Cantilever,
|
||||
LargeOverhang,
|
||||
};
|
||||
|
||||
|
|
@ -80,7 +81,8 @@ enum PrintStep {
|
|||
// should be refreshed.
|
||||
psSlicingFinished = psSkirtBrim,
|
||||
psGCodeExport,
|
||||
psCount,
|
||||
psConflictCheck,
|
||||
psCount
|
||||
};
|
||||
|
||||
enum PrintObjectStep {
|
||||
|
|
@ -528,6 +530,58 @@ private:
|
|||
static bool infill_only_where_needed;
|
||||
};
|
||||
|
||||
struct FakeWipeTower
|
||||
{
|
||||
// generate fake extrusion
|
||||
Vec2f pos;
|
||||
float width;
|
||||
float height;
|
||||
float layer_height;
|
||||
float depth;
|
||||
float brim_width;
|
||||
Vec2d plate_origin;
|
||||
|
||||
void set_fake_extrusion_data(Vec2f p, float w, float h, float lh, float d, float bd, Vec2d o)
|
||||
{
|
||||
pos = p;
|
||||
width = w;
|
||||
height = h;
|
||||
layer_height = lh;
|
||||
depth = d;
|
||||
brim_width = bd;
|
||||
plate_origin = o;
|
||||
}
|
||||
|
||||
void set_pos(Vec2f p) { pos = p; }
|
||||
|
||||
std::vector<ExtrusionPaths> getFakeExtrusionPathsFromWipeTower() const
|
||||
{
|
||||
float h = height;
|
||||
float lh = layer_height;
|
||||
int d = scale_(depth);
|
||||
int w = scale_(width);
|
||||
int bd = scale_(brim_width);
|
||||
Point minCorner = {scale_(pos.x()), scale_(pos.y())};
|
||||
Point maxCorner = {minCorner.x() + w, minCorner.y() + d};
|
||||
|
||||
std::vector<ExtrusionPaths> paths;
|
||||
for (float hh = 0.f; hh < h; hh += lh) {
|
||||
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, lh);
|
||||
path.polyline = {minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner};
|
||||
paths.push_back({path});
|
||||
|
||||
if (hh == 0.f) { // add brim
|
||||
ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, lh);
|
||||
Point wtbminCorner = {minCorner - Point{bd, bd}};
|
||||
Point wtbmaxCorner = {maxCorner + Point{bd, bd}};
|
||||
fakeBrim.polyline = {wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner};
|
||||
paths.back().push_back(fakeBrim);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
};
|
||||
|
||||
struct WipeTowerData
|
||||
{
|
||||
// Following section will be consumed by the GCodeGenerator.
|
||||
|
|
@ -736,9 +790,19 @@ public:
|
|||
int get_modified_count() const {return m_modified_count;}
|
||||
//BBS: add status for whether support used
|
||||
bool is_support_used() const {return m_support_used;}
|
||||
std::string get_conflict_string() const
|
||||
{
|
||||
std::string result;
|
||||
if (m_conflict_result) {
|
||||
result = "Found gcode path conflicts between object " + m_conflict_result.value()._objName1 + " and " + m_conflict_result.value()._objName2;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//BBS
|
||||
static StringObjectException sequential_print_clearance_valid(const Print &print, Polygons *polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
|
||||
ConflictResultOpt get_conflict_result() const { return m_conflict_result; }
|
||||
|
||||
// Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim.
|
||||
std::vector<Point> first_layer_wipe_tower_corners(bool check_wipe_tower_existance=true) const;
|
||||
|
|
@ -804,6 +868,9 @@ private:
|
|||
Vec3d m_origin;
|
||||
//BBS: modified_count
|
||||
int m_modified_count {0};
|
||||
//BBS
|
||||
ConflictResultOpt m_conflict_result;
|
||||
FakeWipeTower m_fake_wipe_tower;
|
||||
|
||||
//SoftFever: calibration
|
||||
Calib_Params m_calib_params;
|
||||
|
|
|
|||
|
|
@ -168,6 +168,8 @@ static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_
|
|||
auto it2 = lr2.begin();
|
||||
for (const auto &kvp1 : lr1) {
|
||||
const auto &kvp2 = *it2 ++;
|
||||
if (!kvp2.second.has("layer_height") || !kvp1.second.has("layer_height"))
|
||||
return false;
|
||||
if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON ||
|
||||
std::abs(kvp1.first.second - kvp2.first.second) > EPSILON ||
|
||||
(check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON))
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ void PrintBase::set_status(int percent, const std::string &message, unsigned in
|
|||
if (m_status_callback)
|
||||
m_status_callback(SlicingStatus(percent, message, flags, warning_step));
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(info) <<boost::format("Percent %1%: %2%\n")%percent %message.c_str();
|
||||
BOOST_LOG_TRIVIAL(debug) <<boost::format("Percent %1%: %2%\n")%percent %message.c_str();
|
||||
}
|
||||
|
||||
void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel /* warning_level */,
|
||||
|
|
|
|||
|
|
@ -510,6 +510,8 @@ public:
|
|||
//BBS: get/set plate id
|
||||
int get_plate_index() const { return m_plate_index; }
|
||||
void set_plate_index(int index) { m_plate_index = index; }
|
||||
bool get_no_check_flag() const { return m_no_check; }
|
||||
void set_no_check_flag(bool no_check) { m_no_check = no_check; }
|
||||
|
||||
//SoftFever plate name
|
||||
std::string get_plate_name() const { return m_plate_name; }
|
||||
|
|
@ -547,6 +549,7 @@ protected:
|
|||
|
||||
//BBS: add plate id into print base
|
||||
int m_plate_index{ 0 };
|
||||
bool m_no_check = false;
|
||||
|
||||
// SoftFever: current plate name
|
||||
std::string m_plate_name;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor)
|
|||
static t_config_enum_values s_keys_map_FuzzySkinType {
|
||||
{ "none", int(FuzzySkinType::None) },
|
||||
{ "external", int(FuzzySkinType::External) },
|
||||
{ "all", int(FuzzySkinType::All) }
|
||||
{ "all", int(FuzzySkinType::All) },
|
||||
{ "allwalls", int(FuzzySkinType::AllWalls)}
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType)
|
||||
|
||||
|
|
@ -193,7 +194,9 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle)
|
|||
static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern {
|
||||
{ "auto", smipAuto },
|
||||
{ "rectilinear", smipRectilinear },
|
||||
{ "concentric", smipConcentric }
|
||||
{ "concentric", smipConcentric },
|
||||
{ "rectilinear_interlaced", smipRectilinearInterlaced},
|
||||
{ "grid", smipGrid }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern)
|
||||
|
||||
|
|
@ -560,7 +563,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"Value 0 means the filament does not support to print on the Cool Plate");
|
||||
def->sidetext = L("°C");
|
||||
def->min = 0;
|
||||
def->max = 300;
|
||||
def->max = 120;
|
||||
def->set_default_value(new ConfigOptionInts{ 35 });
|
||||
|
||||
def = this->add("eng_plate_temp_initial_layer", coInts);
|
||||
|
|
@ -822,7 +825,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def = this->add("brim_type", coEnum);
|
||||
def->label = L("Brim type");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("This controls the generation of the brim at outer side of models. "
|
||||
def->tooltip = L("This controls the generation of the brim at outer and/or inner side of models. "
|
||||
"Auto means the brim width is analysed and calculated automatically.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<BrimType>::get_enum_values();
|
||||
def->enum_values.emplace_back("auto_brim");
|
||||
|
|
@ -1668,8 +1671,10 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("none");
|
||||
def->enum_values.push_back("external");
|
||||
def->enum_values.push_back("all");
|
||||
def->enum_values.push_back("allwalls");
|
||||
def->enum_labels.push_back(L("None"));
|
||||
def->enum_labels.push_back(L("Outer wall"));
|
||||
def->enum_labels.push_back(L("Contour"));
|
||||
def->enum_labels.push_back(L("Contour and hole"));
|
||||
def->enum_labels.push_back(L("All walls"));
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionEnum<FuzzySkinType>(FuzzySkinType::None));
|
||||
|
|
@ -1680,6 +1685,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("The width within which to jitter. It's adversed to be below outer wall line width");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(0.3));
|
||||
|
||||
|
|
@ -1688,6 +1694,8 @@ void PrintConfigDef::init_fff_params()
|
|||
def->category = L("Others");
|
||||
def->tooltip = L("The average diatance between the random points introducded on each line segment");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 5;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(0.8));
|
||||
|
||||
|
|
@ -2839,7 +2847,7 @@ void PrintConfigDef::init_fff_params()
|
|||
|
||||
def = this->add("support_filament", coInt);
|
||||
def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
|
||||
def->label = L("Support base");
|
||||
def->label = L("Support/raft base");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("Filament to print support base and raft. \"Default\" means no specific filament for support and current filament is used");
|
||||
def->min = 0;
|
||||
|
|
@ -2864,7 +2872,7 @@ void PrintConfigDef::init_fff_params()
|
|||
|
||||
def = this->add("support_interface_filament", coInt);
|
||||
def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
|
||||
def->label = L("Support interface");
|
||||
def->label = L("Support/raft interface");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("Filament to print support interface. \"Default\" means no specific filament for support interface and current filament is used");
|
||||
def->min = 0;
|
||||
|
|
@ -2964,9 +2972,13 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("auto");
|
||||
def->enum_values.push_back("rectilinear");
|
||||
def->enum_values.push_back("concentric");
|
||||
def->enum_values.push_back("rectilinear_interlaced");
|
||||
def->enum_values.push_back("grid");
|
||||
def->enum_labels.push_back(L("Default"));
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Concentric"));
|
||||
def->enum_labels.push_back(L("Rectilinear Interlaced"));
|
||||
def->enum_labels.push_back(L("Grid"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<SupportMaterialInterfacePattern>(smipRectilinear));
|
||||
|
||||
|
|
@ -3024,7 +3036,8 @@ void PrintConfigDef::init_fff_params()
|
|||
def = this->add("independent_support_layer_height", coBool);
|
||||
def->label = L("Independent support layer height");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("Support layer uses layer height independent with object layer. This is to support customizing z-gap and save print time.");
|
||||
def->tooltip = L("Support layer uses layer height independent with object layer. This is to support customizing z-gap and save print time."
|
||||
"This option will be invalid when the prime tower is enabled.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
|
|
@ -3238,7 +3251,8 @@ void PrintConfigDef::init_fff_params()
|
|||
|
||||
def = this->add("enable_prime_tower", coBool);
|
||||
def->label = L("Enable");
|
||||
def->tooltip = L("Print a tower to prime material in nozzle after switching to a new material.");
|
||||
def->tooltip = L("The wiping tower can be used to clean up the residue on the nozzle and stabilize the chamber pressure inside the nozzle, "
|
||||
"in order to avoid appearance defects when printing objects.");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
|
|
@ -4412,10 +4426,12 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments)
|
|||
//if (alh_opt)
|
||||
// alh_opt->value = false;
|
||||
}
|
||||
/* BBS: MusangKing - not sure if this is still valid, just comment it out cause "Independent support layer height" is re-opened.
|
||||
else {
|
||||
if (islh_opt)
|
||||
islh_opt->value = true;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4501,6 +4517,7 @@ t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int num_objects, int us
|
|||
// //alh_opt->value = false;
|
||||
//}
|
||||
}
|
||||
/* BBS:MusangKing - use "global->support->Independent support layer height" widget to replace previous assignment
|
||||
else {
|
||||
if (islh_opt) {
|
||||
if (!islh_opt->value) {
|
||||
|
|
@ -4510,6 +4527,7 @@ t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int num_objects, int us
|
|||
//islh_opt->value = true;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return changed_keys;
|
||||
|
|
@ -4614,24 +4632,34 @@ std::string DynamicPrintConfig::get_filament_type(std::string &displayed_filamen
|
|||
if (is_support) {
|
||||
if (filament_id) {
|
||||
if (filament_id->get_at(id) == "GFS00") {
|
||||
displayed_filament_type = "Support W";
|
||||
displayed_filament_type = "Sup.PLA";
|
||||
return "PLA-S";
|
||||
}
|
||||
else if (filament_id->get_at(id) == "GFS01") {
|
||||
displayed_filament_type = "Support G";
|
||||
displayed_filament_type = "Sup.PA";
|
||||
return "PA-S";
|
||||
}
|
||||
else {
|
||||
displayed_filament_type = filament_type->get_at(id);
|
||||
return filament_type->get_at(id);
|
||||
if (filament_type->get_at(id) == "PLA") {
|
||||
displayed_filament_type = "Sup.PLA";
|
||||
return "PLA-S";
|
||||
}
|
||||
else if (filament_type->get_at(id) == "PA") {
|
||||
displayed_filament_type = "Sup.PA";
|
||||
return "PA-S";
|
||||
}
|
||||
else {
|
||||
displayed_filament_type = filament_type->get_at(id);
|
||||
return filament_type->get_at(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (filament_type->get_at(id) == "PLA") {
|
||||
displayed_filament_type = "Support W";
|
||||
displayed_filament_type = "Sup.PLA";
|
||||
return "PLA-S";
|
||||
} else if (filament_type->get_at(id) == "PA") {
|
||||
displayed_filament_type = "Support G";
|
||||
displayed_filament_type = "Sup.PA";
|
||||
return "PA-S";
|
||||
} else {
|
||||
displayed_filament_type = filament_type->get_at(id);
|
||||
|
|
@ -4975,6 +5003,33 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
|||
def->cli = "uptodate";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("mtcpp", coInt);
|
||||
def->label = L("mtcpp");
|
||||
def->tooltip = L("max triangle count per plate for slicing.");
|
||||
def->cli = "mtcpp";
|
||||
def->cli_params = "count";
|
||||
def->set_default_value(new ConfigOptionInt(1000000));
|
||||
|
||||
def = this->add("mstpp", coInt);
|
||||
def->label = L("mstpp");
|
||||
def->tooltip = L("max slicing time per plate in seconds.");
|
||||
def->cli = "mstpp";
|
||||
def->cli_params = "time";
|
||||
def->set_default_value(new ConfigOptionInt(300));
|
||||
|
||||
// must define new params here, otherwise comamnd param check will fail
|
||||
def = this->add("no_check", coBool);
|
||||
def->label = L("No check");
|
||||
def->tooltip = L("Do not run any validity checks, such as gcode path conflicts check.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("normative_check", coBool);
|
||||
def->label = L("Normative check");
|
||||
def->tooltip = L("Check the normative items.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
/*def = this->add("help_fff", coBool);
|
||||
def->label = L("Help (FFF options)");
|
||||
def->tooltip = L("Show the full list of print/G-code configuration options.");
|
||||
|
|
@ -5000,7 +5055,7 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
|||
def->label = L("Send progress to pipe");
|
||||
def->tooltip = L("Send progress to pipe.");
|
||||
def->cli_params = "pipename";
|
||||
def->set_default_value(new ConfigOptionString("cli_pipe"));
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
}
|
||||
|
||||
//BBS: remove unused command currently
|
||||
|
|
@ -5153,6 +5208,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
|||
def->cli_params = "\"filament1.json;filament2.json;...\"";
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
def = this->add("skip_objects", coStrings);
|
||||
def->label = L("Skip Objects");
|
||||
def->tooltip = L("Skip some objects in this print");
|
||||
def->cli_params = "\"3;5;10;77\"";
|
||||
def->set_default_value(new ConfigOptionInts());
|
||||
|
||||
/*def = this->add("output", coString);
|
||||
def->label = L("Output File");
|
||||
def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file).");
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ enum class FuzzySkinType {
|
|||
None,
|
||||
External,
|
||||
All,
|
||||
AllWalls,
|
||||
};
|
||||
|
||||
enum PrintHostType {
|
||||
|
|
@ -104,7 +105,7 @@ enum SupportMaterialStyle {
|
|||
};
|
||||
|
||||
enum SupportMaterialInterfacePattern {
|
||||
smipAuto, smipRectilinear, smipConcentric,
|
||||
smipAuto, smipRectilinear, smipConcentric, smipRectilinearInterlaced, smipGrid
|
||||
};
|
||||
|
||||
// BBS
|
||||
|
|
@ -117,7 +118,7 @@ inline bool is_tree(SupportType stype)
|
|||
};
|
||||
inline bool is_tree_slim(SupportType type, SupportMaterialStyle style)
|
||||
{
|
||||
return is_tree(type) && (style==smsDefault || style==smsTreeSlim);
|
||||
return is_tree(type) && style==smsTreeSlim;
|
||||
};
|
||||
inline bool is_auto(SupportType stype)
|
||||
{
|
||||
|
|
@ -715,52 +716,46 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionFloat, bridge_angle))
|
||||
((ConfigOptionFloat, bridge_flow))
|
||||
((ConfigOptionFloat, bridge_speed))
|
||||
((ConfigOptionPercent, bridge_density))
|
||||
((ConfigOptionBool, ensure_vertical_shell_thickness))
|
||||
((ConfigOptionEnum<InfillPattern>, top_surface_pattern))
|
||||
((ConfigOptionFloat, top_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloat, bottom_solid_infill_flow_ratio))
|
||||
((ConfigOptionEnum<InfillPattern>, bottom_surface_pattern))
|
||||
((ConfigOptionFloat, outer_wall_line_width))
|
||||
((ConfigOptionFloat, outer_wall_speed))
|
||||
((ConfigOptionFloatOrPercent, small_perimeter_speed))
|
||||
((ConfigOptionFloat, small_perimeter_threshold))
|
||||
((ConfigOptionFloat, infill_direction))
|
||||
((ConfigOptionPercent, sparse_infill_density))
|
||||
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))
|
||||
((ConfigOptionEnum<FuzzySkinType>, fuzzy_skin))
|
||||
((ConfigOptionFloat, fuzzy_skin_thickness))
|
||||
((ConfigOptionFloat, fuzzy_skin_point_distance))
|
||||
((ConfigOptionFloat, filter_out_gap_fill))
|
||||
((ConfigOptionFloat, gap_infill_speed))
|
||||
((ConfigOptionInt, sparse_infill_filament))
|
||||
((ConfigOptionFloat, sparse_infill_line_width))
|
||||
((ConfigOptionPercent, infill_wall_overlap))
|
||||
((ConfigOptionFloat, sparse_infill_speed))
|
||||
//BBS
|
||||
((ConfigOptionBool, infill_combination))
|
||||
((ConfigOptionBool, infill_combination))
|
||||
// Ironing options
|
||||
((ConfigOptionEnum<IroningType>, ironing_type))
|
||||
((ConfigOptionPercent, ironing_flow))
|
||||
((ConfigOptionFloat, ironing_spacing))
|
||||
((ConfigOptionFloat, ironing_speed))
|
||||
((ConfigOptionEnum<IroningType>, ironing_type))
|
||||
((ConfigOptionPercent, ironing_flow))
|
||||
((ConfigOptionFloat, ironing_spacing))
|
||||
((ConfigOptionFloat, ironing_speed))
|
||||
// Detect bridging perimeters
|
||||
((ConfigOptionBool, detect_overhang_wall))
|
||||
((ConfigOptionInt, wall_filament))
|
||||
((ConfigOptionFloat, inner_wall_line_width))
|
||||
((ConfigOptionFloat, inner_wall_speed))
|
||||
((ConfigOptionBool, detect_overhang_wall))
|
||||
((ConfigOptionInt, wall_filament))
|
||||
((ConfigOptionFloat, inner_wall_line_width))
|
||||
((ConfigOptionFloat, inner_wall_speed))
|
||||
// Total number of perimeters.
|
||||
((ConfigOptionInt, wall_loops))
|
||||
((ConfigOptionFloat, minimum_sparse_infill_area))
|
||||
((ConfigOptionInt, solid_infill_filament))
|
||||
((ConfigOptionFloat, internal_solid_infill_line_width))
|
||||
((ConfigOptionFloat, internal_solid_infill_speed))
|
||||
((ConfigOptionInt, wall_loops))
|
||||
((ConfigOptionFloat, minimum_sparse_infill_area))
|
||||
((ConfigOptionInt, solid_infill_filament))
|
||||
((ConfigOptionFloat, internal_solid_infill_line_width))
|
||||
((ConfigOptionFloat, internal_solid_infill_speed))
|
||||
// Detect thin walls.
|
||||
((ConfigOptionBool, detect_thin_wall))
|
||||
((ConfigOptionFloat, top_surface_line_width))
|
||||
((ConfigOptionInt, top_shell_layers))
|
||||
((ConfigOptionFloat, top_shell_thickness))
|
||||
((ConfigOptionFloat, top_surface_speed))
|
||||
((ConfigOptionBool, detect_thin_wall))
|
||||
((ConfigOptionFloat, top_surface_line_width))
|
||||
((ConfigOptionInt, top_shell_layers))
|
||||
((ConfigOptionFloat, top_shell_thickness))
|
||||
((ConfigOptionFloat, top_surface_speed))
|
||||
//BBS
|
||||
((ConfigOptionBool, enable_overhang_speed))
|
||||
((ConfigOptionFloat, overhang_1_4_speed))
|
||||
|
|
@ -768,8 +763,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionFloat, overhang_3_4_speed))
|
||||
((ConfigOptionFloat, overhang_4_4_speed))
|
||||
((ConfigOptionBool, only_one_wall_top))
|
||||
((ConfigOptionBool, only_one_wall_first_layer))
|
||||
|
||||
//SoftFever
|
||||
((ConfigOptionBool, only_one_wall_first_layer))
|
||||
((ConfigOptionFloat, print_flow_ratio))
|
||||
((ConfigOptionFloatOrPercent, seam_gap))
|
||||
((ConfigOptionBool, role_based_wipe_speed))
|
||||
|
|
@ -778,6 +774,12 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionEnum<WallInfillOrder>, wall_infill_order))
|
||||
((ConfigOptionBool, precise_outer_wall))
|
||||
((ConfigOptionBool, overhang_speed_classic))
|
||||
((ConfigOptionPercent, bridge_density))
|
||||
((ConfigOptionFloat, filter_out_gap_fill))
|
||||
((ConfigOptionFloatOrPercent, small_perimeter_speed))
|
||||
((ConfigOptionFloat, small_perimeter_threshold))
|
||||
((ConfigOptionFloat, top_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloat, bottom_solid_infill_flow_ratio))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -458,19 +458,26 @@ void PrintObject::generate_support_material()
|
|||
|
||||
this->_generate_support_material();
|
||||
m_print->throw_if_canceled();
|
||||
} else {
|
||||
} else if(!m_print->get_no_check_flag()) {
|
||||
// BBS: pop a warning if objects have significant amount of overhangs but support material is not enabled
|
||||
m_print->set_status(50, L("Checking support necessity"));
|
||||
typedef std::chrono::high_resolution_clock clock_;
|
||||
typedef std::chrono::duration<double, std::ratio<1> > second_;
|
||||
std::chrono::time_point<clock_> t0{ clock_::now() };
|
||||
|
||||
SupportNecessaryType sntype = this->is_support_necessary();
|
||||
|
||||
double duration{ std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
|
||||
BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(0) << "is_support_necessary takes " << duration << " secs.";
|
||||
|
||||
if (sntype != NoNeedSupp) {
|
||||
m_print->set_status(50, L("Checking support necessity"));
|
||||
if (sntype == SharpTail) {
|
||||
std::string warning_message = format(L("It seems object %s has completely floating regions. Please re-orient the object or enable support generation."),
|
||||
this->model_object()->name);
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning_message, PrintStateBase::SlicingNeedSupportOn);
|
||||
} else {
|
||||
std::string warning_message = format(L("It seems object %s has large overhangs. Please enable support generation."), this->model_object()->name);
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning_message, PrintStateBase::SlicingNeedSupportOn);
|
||||
}
|
||||
std::map<SupportNecessaryType, std::string> reasons = {
|
||||
{SharpTail,L("floating regions")},
|
||||
{Cantilever,L("floating cantilever")},
|
||||
{LargeOverhang,L("large overhangs")} };
|
||||
std::string warning_message = format(L("It seems object %s has %s. Please re-orient the object or enable support generation."),
|
||||
this->model_object()->name, reasons[sntype]);
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning_message, PrintStateBase::SlicingNeedSupportOn);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
|
@ -2511,19 +2518,21 @@ void PrintObject::remove_bridges_from_contacts(
|
|||
if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) {
|
||||
auto bbox = get_extents(surface.expolygon);
|
||||
auto bbox_size = bbox.size();
|
||||
if (bbox_size[0] < max_bridge_length || bbox_size[1] < max_bridge_length)
|
||||
if (bbox_size[0] < max_bridge_length && bbox_size[1] < max_bridge_length)
|
||||
polygons_append(bridges, surface.expolygon);
|
||||
else {
|
||||
if (break_bridge) {
|
||||
Polygons holes;
|
||||
int x0 = bbox.min.x();
|
||||
int x1 = bbox.max.x();
|
||||
int y0 = bbox.min.y();
|
||||
int y1 = bbox.max.y();
|
||||
coord_t x0 = bbox.min.x();
|
||||
coord_t x1 = bbox.max.x();
|
||||
coord_t y0 = bbox.min.y();
|
||||
coord_t y1 = bbox.max.y();
|
||||
const int grid_lw = int(w/2); // grid line width
|
||||
|
||||
#if 1
|
||||
if (fabs(surface.bridge_angle-0)<fabs(surface.bridge_angle-M_PI_2)) {
|
||||
Vec2f bridge_direction{ cos(surface.bridge_angle),sin(surface.bridge_angle) };
|
||||
if (fabs(bridge_direction(0)) > fabs(bridge_direction(1)))
|
||||
{ // cut bridge along x-axis if bridge direction is aligned to x-axis more than to y-axis
|
||||
// Note: surface.bridge_angle may be pi, so we can't compare it to 0 & pi/2.
|
||||
int step = bbox_size(0) / ceil(bbox_size(0) / max_bridge_length);
|
||||
for (int x = x0 + step; x < x1; x += step) {
|
||||
Polygon poly;
|
||||
|
|
@ -2538,17 +2547,6 @@ void PrintObject::remove_bridges_from_contacts(
|
|||
holes.emplace_back(poly);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int stepx = bbox_size(0) / ceil(bbox_size(0) / max_bridge_length);
|
||||
int stepy = bbox_size(1) / ceil(bbox_size(1) / max_bridge_length);
|
||||
for (int x = x0 + stepx; x < x1; x += stepx)
|
||||
for (int y = y0 + stepy; y < y1; y += stepy) {
|
||||
Polygon poly;
|
||||
poly.points = {Point(x-grid_lw, y - grid_lw), Point(x+grid_lw, y - grid_lw), Point(x+grid_lw, y + grid_lw), Point(x-grid_lw, y + grid_lw)};
|
||||
holes.emplace_back(poly);
|
||||
}
|
||||
|
||||
#endif
|
||||
auto expoly = diff_ex(surface.expolygon, holes);
|
||||
polygons_append(bridges, expoly);
|
||||
}
|
||||
|
|
@ -2586,9 +2584,9 @@ template void PrintObject::remove_bridges_from_contacts<Polygons>(
|
|||
|
||||
SupportNecessaryType PrintObject::is_support_necessary()
|
||||
{
|
||||
#if 0
|
||||
static const double super_overhang_area_threshold = SQ(scale_(5.0));
|
||||
|
||||
const double cantilevel_dist_thresh = scale_(6);
|
||||
#if 0
|
||||
double threshold_rad = (m_config.support_threshold_angle.value < EPSILON ? 30 : m_config.support_threshold_angle.value + 1) * M_PI / 180.;
|
||||
int enforce_support_layers = m_config.enforce_support_layers;
|
||||
const coordf_t extrusion_width = m_config.line_width.value;
|
||||
|
|
@ -2672,12 +2670,12 @@ SupportNecessaryType PrintObject::is_support_necessary()
|
|||
#else
|
||||
TreeSupport tree_support(*this, m_slicing_params);
|
||||
tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection
|
||||
tree_support.detect_overhangs();
|
||||
tree_support.detect_overhangs(true);
|
||||
this->clear_support_layers();
|
||||
if (tree_support.has_sharp_tails)
|
||||
return SharpTail;
|
||||
else if (tree_support.has_cantilever)
|
||||
return LargeOverhang;
|
||||
else if (tree_support.has_cantilever && tree_support.max_cantilever_dist > cantilevel_dist_thresh)
|
||||
return Cantilever;
|
||||
#endif
|
||||
return NoNeedSupp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,11 +345,11 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|||
if (!bbox_a.overlap(bbox_b))
|
||||
continue;
|
||||
|
||||
if (intersection_ex(expoly_a, expoly_b).empty())
|
||||
ExPolygons temp = intersection_ex(expoly_b, expoly_a, ApplySafetyOffset::Yes);
|
||||
if (temp.empty())
|
||||
continue;
|
||||
|
||||
ExPolygons temp = intersection_ex(expoly_b, expoly_a);
|
||||
if (expoly_a.area() > expoly_b.area())
|
||||
if (expoly_a.contour.length() > expoly_b.contour.length())
|
||||
trimming_a.insert(trimming_a.end(), temp.begin(), temp.end());
|
||||
else
|
||||
trimming_b.insert(trimming_b.end(), temp.begin(), temp.end());
|
||||
|
|
@ -397,6 +397,12 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|||
// Clip every non-zero region preceding it.
|
||||
for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2)
|
||||
if (! temp_slices[idx_region2].expolygons.empty()) {
|
||||
// Skip trim_overlap for now, because it slow down the performace so much for some special cases
|
||||
#if 1
|
||||
if (const PrintObjectRegions::VolumeRegion& region2 = layer_range.volume_regions[idx_region2];
|
||||
!region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox))
|
||||
temp_slices[idx_region2].expolygons = diff_ex(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons);
|
||||
#else
|
||||
const PrintObjectRegions::VolumeRegion& region2 = layer_range.volume_regions[idx_region2];
|
||||
if (!region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox))
|
||||
//BBS: handle negative_volume seperately, always minus the negative volume and don't need to trim overlap
|
||||
|
|
@ -404,6 +410,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|||
trim_overlap(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons);
|
||||
else
|
||||
temp_slices[idx_region2].expolygons = diff_ex(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -462,20 +469,33 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|||
bool doesVolumeIntersect(VolumeSlices& vs1, VolumeSlices& vs2)
|
||||
{
|
||||
if (vs1.volume_id == vs2.volume_id) return true;
|
||||
// two volumes in the same object should have same number of layers, otherwise the slicing is incorrect.
|
||||
if (vs1.slices.size() != vs2.slices.size()) return false;
|
||||
|
||||
for (int i = 0; i != vs1.slices.size(); ++i) {
|
||||
auto& vs1s = vs1.slices;
|
||||
auto& vs2s = vs2.slices;
|
||||
bool is_intersect = false;
|
||||
|
||||
if (vs1.slices[i].empty()) continue;
|
||||
if (!vs2.slices[i].empty() && !intersection_ex(vs1.slices[i], vs2.slices[i]).empty()) return true;
|
||||
if (i + 1 != vs2.slices.size() && !vs2.slices[i + 1].empty()) {
|
||||
if (!intersection_ex(vs1.slices[i], vs2.slices[i + 1]).empty()) return true;
|
||||
}
|
||||
if (i - 1 >= 0 && !vs2.slices[i - 1].empty()) {
|
||||
if (!intersection_ex(vs1.slices[i], vs2.slices[i - 1]).empty()) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, vs1s.size()),
|
||||
[&vs1s, &vs2s, &is_intersect](const tbb::blocked_range<int>& range) {
|
||||
for (auto i = range.begin(); i != range.end(); ++i) {
|
||||
if (vs1s[i].empty()) continue;
|
||||
|
||||
if (overlaps(vs1s[i], vs2s[i])) {
|
||||
is_intersect = true;
|
||||
break;
|
||||
}
|
||||
if (i + 1 != vs2s.size() && overlaps(vs1s[i], vs2s[i + 1])) {
|
||||
is_intersect = true;
|
||||
break;
|
||||
}
|
||||
if (i - 1 >= 0 && overlaps(vs1s[i], vs2s[i - 1])) {
|
||||
is_intersect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return is_intersect;
|
||||
}
|
||||
|
||||
//BBS: grouping the volumes of an object according to their connection relationship
|
||||
|
|
@ -581,15 +601,18 @@ void reGroupingLayerPolygons(std::vector<groupedVolumeSlices>& gvss, ExPolygons
|
|||
std::vector<int> epsIndex;
|
||||
epsIndex.resize(eps.size(), -1);
|
||||
for (int ie = 0; ie != eps.size(); ie++) {
|
||||
if (eps[ie].area() <= 0)
|
||||
continue;
|
||||
double minArea = eps[ie].area();
|
||||
for (int iv = 0; iv != gvss.size(); iv++) {
|
||||
auto clipedExPolys = diff_ex(eps[ie], gvss[iv].slices);
|
||||
double area = 0;
|
||||
for (const auto& ce : clipedExPolys) {
|
||||
area += ce.area();
|
||||
}
|
||||
if (eps[ie].area() > 0 && area / eps[ie].area() < 0.3) {
|
||||
if (area < minArea) {
|
||||
minArea = area;
|
||||
epsIndex[ie] = iv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -603,7 +626,7 @@ void reGroupingLayerPolygons(std::vector<groupedVolumeSlices>& gvss, ExPolygons
|
|||
}
|
||||
}
|
||||
|
||||
std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std::function<void()> &throw_if_canceled)
|
||||
std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std::function<void()> &throw_if_canceled, int &firstLayerReplacedBy)
|
||||
{
|
||||
std::string error_msg;//BBS
|
||||
|
||||
|
|
@ -715,10 +738,14 @@ std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std
|
|||
|
||||
// BBS: first layer slices are sorted by volume group, if the first layer is empty and replaced by the 2nd layer
|
||||
// the later will be stored in "object->firstLayerObjGroupsMod()"
|
||||
int firstLayerReplacedBy = 0;
|
||||
if (!buggy_layers.empty() && buggy_layers.front() == 0)
|
||||
if (!buggy_layers.empty() && buggy_layers.front() == 0 && layers.size() > 1)
|
||||
firstLayerReplacedBy = 1;
|
||||
|
||||
return error_msg;
|
||||
}
|
||||
|
||||
void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLayerReplacedBy)
|
||||
{
|
||||
const auto scaled_resolution = scaled<double>(object->print()->config().resolution.value);
|
||||
auto partsObjSliceByVolume = findPartVolumes(object->firstLayerObjSliceMod(), object->model_object()->volumes);
|
||||
groupingVolumes(partsObjSliceByVolume, object->firstLayerObjGroupsMod(), scaled_resolution, firstLayerReplacedBy);
|
||||
|
|
@ -726,8 +753,6 @@ std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std
|
|||
|
||||
// BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
|
||||
reGroupingLayerPolygons(object->firstLayerObjGroupsMod(), layers.front()->lslices);
|
||||
|
||||
return error_msg;
|
||||
}
|
||||
|
||||
// Called by make_perimeters()
|
||||
|
|
@ -752,15 +777,23 @@ void PrintObject::slice()
|
|||
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile));
|
||||
this->slice_volumes();
|
||||
m_print->throw_if_canceled();
|
||||
int firstLayerReplacedBy = 0;
|
||||
|
||||
#if 1
|
||||
// Fix the model.
|
||||
//FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend.
|
||||
std::string warning = fix_slicing_errors(this, m_layers, [this](){ m_print->throw_if_canceled(); });
|
||||
std::string warning = fix_slicing_errors(this, m_layers, [this](){ m_print->throw_if_canceled(); }, firstLayerReplacedBy);
|
||||
m_print->throw_if_canceled();
|
||||
//BBS: send warning message to slicing callback
|
||||
if (!warning.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << warning;
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers);
|
||||
}
|
||||
#endif
|
||||
|
||||
// BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
|
||||
groupingVolumesForBrim(this, m_layers, firstLayerReplacedBy);
|
||||
|
||||
// Update bounding boxes, back up raw slices of complex models.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ struct FilamentInfo
|
|||
std::string type;
|
||||
std::string color;
|
||||
std::string filament_id;
|
||||
std::string brand;
|
||||
float used_m;
|
||||
float used_g;
|
||||
int tray_id; // start with 0
|
||||
|
|
|
|||
|
|
@ -184,10 +184,11 @@ IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
|||
|
||||
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
// BBS: STUDIO-2591 A mesh with overlapping faces cannot be painted
|
||||
//hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
// [](const igl::Hit& a, const igl::Hit& b)
|
||||
// { return a.t == b.t; }),
|
||||
// hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ public:
|
|||
void add(const ExPolygon &ep)
|
||||
{
|
||||
m_polys.emplace_back(ep);
|
||||
m_index.insert(BoundingBox{ep}, unsigned(m_index.size()));
|
||||
m_index.insert(get_extents(ep), unsigned(m_index.size()));
|
||||
}
|
||||
|
||||
// Check an arbitrary polygon for intersection with the indexed polygons
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "SupportPointGenerator.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "Concurrency.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
|
|
@ -11,7 +12,6 @@
|
|||
#include "Point.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "MinAreaBoundingBox.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
|||
// auto bb = get_extents(islands);
|
||||
|
||||
if (flags & icfIsNew) {
|
||||
auto chull = ExPolygonCollection{islands}.convex_hull();
|
||||
auto chull = Geometry::convex_hull(islands);
|
||||
auto rotbox = MinAreaBoundigBox{chull, MinAreaBoundigBox::pcConvex};
|
||||
Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())};
|
||||
|
||||
|
|
|
|||
|
|
@ -335,6 +335,7 @@ remove_unconnected_vertices(const indexed_triangle_set &its)
|
|||
// Drill holes into the hollowed/original mesh.
|
||||
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
||||
{
|
||||
/*
|
||||
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
|
||||
bool is_hollowed =
|
||||
(po.m_hollowing_data && po.m_hollowing_data->interior &&
|
||||
|
|
@ -465,6 +466,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
|||
if (hole_fail)
|
||||
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
L("Failed to drill some holes into the model"));
|
||||
*/
|
||||
}
|
||||
|
||||
// The slicing will be performed on an imaginary 1D grid which starts from
|
||||
|
|
|
|||
|
|
@ -88,10 +88,8 @@ void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_op
|
|||
this->fill = fill;
|
||||
|
||||
std::string d;
|
||||
Polygons pp = expolygon;
|
||||
for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) {
|
||||
d += this->get_path_d(*p, true) + " ";
|
||||
}
|
||||
for (const Polygon &p : to_polygons(expolygon))
|
||||
d += this->get_path_d(p, true) + " ";
|
||||
this->path(d, true, 0, fill_opacity);
|
||||
}
|
||||
|
||||
|
|
@ -279,13 +277,14 @@ std::string SVG::get_path_d(const ClipperLib::Path &path, double scale, bool clo
|
|||
return d.str();
|
||||
}
|
||||
|
||||
// font_size: font-size={font_size*10}px
|
||||
void SVG::draw_text(const Point &pt, const char *text, const char *color, int font_size)
|
||||
{
|
||||
fprintf(this->f,
|
||||
"<text x=\"%f\" y=\"%f\" font-family=\"sans-serif\" font-size=\"20px\" fill=\"%s\">%s</text>",
|
||||
"<text x=\"%f\" y=\"%f\" font-family=\"sans-serif\" font-size=\"%dpx\" fill=\"%s\">%s</text>",
|
||||
to_svg_x(pt(0)-origin(0)),
|
||||
to_svg_y(pt(1)-origin(1)),
|
||||
color, text);
|
||||
font_size*10, color, text);
|
||||
}
|
||||
|
||||
void SVG::draw_legend(const Point &pt, const char *text, const char *color)
|
||||
|
|
@ -391,7 +390,7 @@ void SVG::export_expolygons(const char *path, const std::vector<std::pair<Slic3r
|
|||
for (const auto &exp_with_attr : expolygons_with_attributes)
|
||||
if (exp_with_attr.second.radius_points > 0)
|
||||
for (const ExPolygon &expoly : exp_with_attr.first)
|
||||
svg.draw((Points)expoly, exp_with_attr.second.color_points, exp_with_attr.second.radius_points);
|
||||
svg.draw(to_points(expoly), exp_with_attr.second.color_points, exp_with_attr.second.radius_points);
|
||||
|
||||
// Export legend.
|
||||
// 1st row
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ std::vector<std::string> init_occt_fonts()
|
|||
return stdFontNames;
|
||||
}
|
||||
|
||||
static bool TextToBRep(const char* text, const char* font, const float theTextHeight, Font_FontAspect& theFontAspect, TopoDS_Shape& theShape)
|
||||
static bool TextToBRep(const char* text, const char* font, const float theTextHeight, Font_FontAspect& theFontAspect, TopoDS_Shape& theShape, double& text_width)
|
||||
{
|
||||
Standard_Integer anArgIt = 1;
|
||||
Standard_CString aName = "text_shape";
|
||||
|
|
@ -122,8 +122,24 @@ static bool TextToBRep(const char* text, const char* font, const float theTextHe
|
|||
|
||||
aPenAx3 = gp_Ax3(aPenLoc, aNormal, aDirection);
|
||||
|
||||
Handle(Font_TextFormatter) aFormatter = new Font_TextFormatter();
|
||||
aFormatter->Reset();
|
||||
aFormatter->SetupAlignment(aHJustification, aVJustification);
|
||||
aFormatter->Append(aText, *aFont.FTFont());
|
||||
aFormatter->Format();
|
||||
|
||||
// get the text width
|
||||
text_width = 0;
|
||||
NCollection_String coll_str = aText;
|
||||
for (NCollection_Utf8Iter anIter = coll_str.Iterator(); *anIter != 0;) {
|
||||
const Standard_Utf32Char aCharThis = *anIter;
|
||||
const Standard_Utf32Char aCharNext = *++anIter;
|
||||
double width = aFont.AdvanceX(aCharThis, aCharNext);
|
||||
text_width += width;
|
||||
}
|
||||
|
||||
Font_BRepTextBuilder aBuilder;
|
||||
theShape = aBuilder.Perform(aFont, aText, aPenAx3, aHJustification, aVJustification);
|
||||
theShape = aBuilder.Perform(aFont, aFormatter, aPenAx3);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +237,7 @@ static void MakeMesh(TopoDS_Shape& theSolid, TriangleMesh& theMesh)
|
|||
theMesh.from_stl(stl);
|
||||
}
|
||||
|
||||
void load_text_shape(const char*text, const char* font, const float text_height, const float thickness, bool is_bold, bool is_italic, TriangleMesh& text_mesh)
|
||||
void load_text_shape(const char*text, const char* font, const float text_height, const float thickness, bool is_bold, bool is_italic, TextResult &text_result)
|
||||
{
|
||||
Handle(Font_FontMgr) aFontMgr = Font_FontMgr::GetInstance();
|
||||
if (aFontMgr->GetAvailableFonts().IsEmpty())
|
||||
|
|
@ -238,14 +254,14 @@ void load_text_shape(const char*text, const char* font, const float text_height,
|
|||
else
|
||||
aFontAspect = Font_FontAspect_Regular;
|
||||
|
||||
if (!TextToBRep(text, font, text_height, aFontAspect, aTextBase))
|
||||
if (!TextToBRep(text, font, text_height, aFontAspect, aTextBase, text_result.text_width))
|
||||
return;
|
||||
|
||||
TopoDS_Shape aTextShape;
|
||||
if (!Prism(aTextBase, thickness, aTextShape))
|
||||
return;
|
||||
|
||||
MakeMesh(aTextShape, text_mesh);
|
||||
MakeMesh(aTextShape, text_result.text_mesh);
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -4,8 +4,14 @@
|
|||
namespace Slic3r {
|
||||
class TriangleMesh;
|
||||
|
||||
struct TextResult
|
||||
{
|
||||
TriangleMesh text_mesh;
|
||||
double text_width;
|
||||
};
|
||||
|
||||
extern std::vector<std::string> init_occt_fonts();
|
||||
extern void load_text_shape(const char* text, const char* font, const float text_height, const float thickness, bool is_bold, bool is_italic, TriangleMesh& text_mesh);
|
||||
extern void load_text_shape(const char *text, const char *font, const float text_height, const float thickness, bool is_bold, bool is_italic, TextResult &text_result);
|
||||
|
||||
std::map<std::string, std::string> get_occt_fonts_maps();
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@
|
|||
#endif // SUPPORT_USE_AGG_RASTERIZER
|
||||
|
||||
// #define SLIC3R_DEBUG
|
||||
|
||||
// #define SUPPORT_TREE_DEBUG_TO_SVG
|
||||
// Make assert active if SLIC3R_DEBUG
|
||||
#ifdef SLIC3R_DEBUG
|
||||
#if defined(SLIC3R_DEBUG) || defined(SUPPORT_TREE_DEBUG_TO_SVG)
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
|
|
@ -412,7 +412,12 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
|
|||
support_pattern == smpHoneycomb ? ipHoneycomb :
|
||||
m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase;
|
||||
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
m_support_params.contact_fill_pattern =
|
||||
if (m_object_config->support_interface_pattern == smipGrid)
|
||||
m_support_params.contact_fill_pattern = ipGrid;
|
||||
else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced)
|
||||
m_support_params.contact_fill_pattern = ipRectilinear;
|
||||
else
|
||||
m_support_params.contact_fill_pattern =
|
||||
(m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
|
||||
m_object_config->support_interface_pattern == smipConcentric ?
|
||||
ipConcentric :
|
||||
|
|
@ -609,6 +614,29 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
|||
layers_append(layers_sorted, base_interface_layers);
|
||||
// Sort the layers lexicographically by a raising print_z and a decreasing height.
|
||||
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
|
||||
|
||||
// BBS: MusangKing - erase mini layer heights (< 0.08mm) arised by top/bottom_z_distance & top_contacts under variable layer height
|
||||
if (this->synchronize_layers() && !object.slicing_parameters().soluble_interface) {
|
||||
auto thres = m_support_params.support_layer_height_min - EPSILON;
|
||||
for (size_t i = 1; i < layers_sorted.size() - 1; ++i) {
|
||||
auto& lowr = layers_sorted[i - 1];
|
||||
auto& curr = layers_sorted[i];
|
||||
auto& higr = layers_sorted[i + 1];
|
||||
// "Rounding" suspicious top/bottom contacts
|
||||
if (curr->layer_type == sltTopContact || curr->layer_type == sltBottomContact) {
|
||||
// Check adjacent-layer print_z diffs
|
||||
coordf_t height_low = curr->print_z - lowr->print_z;
|
||||
coordf_t height_high = higr->print_z - curr->print_z;
|
||||
if (height_low < thres || height_high < thres) {
|
||||
// Mark to-be-deleted layer as Unknown type
|
||||
curr->layer_type = sltUnknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Retains the order
|
||||
layers_sorted.erase(std::remove_if(layers_sorted.begin(), layers_sorted.end(), [](MyLayer* l) {return l->layer_type == sltUnknown; }), layers_sorted.end());
|
||||
}
|
||||
|
||||
int layer_id = 0;
|
||||
int layer_id_interface = 0;
|
||||
assert(object.support_layers().empty());
|
||||
|
|
@ -692,6 +720,18 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
|||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
#if 0 // #ifdef SLIC3R_DEBUG
|
||||
// check bounds
|
||||
std::ofstream out;
|
||||
out.open("./SVG/ns_support_layers.txt");
|
||||
if (out.is_open()) {
|
||||
out << "### Support Layers ###" << std::endl;
|
||||
for (auto& i : object.support_layers()) {
|
||||
out << i->print_z << std::endl;
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
// Generate the actual toolpaths and save them into each layer.
|
||||
this->generate_toolpaths(object.support_layers(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers);
|
||||
|
||||
|
|
@ -956,16 +996,18 @@ public:
|
|||
if (!support_polygons_simplified.empty())
|
||||
bbox.merge(get_extents(support_polygons_simplified));
|
||||
SVG svg(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z).c_str(), bbox);
|
||||
svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f);
|
||||
svg.draw(islands, "red", 0.5f);
|
||||
svg.draw(union_ex(out), "green", 0.5f);
|
||||
svg.draw(union_ex(*m_support_polygons), "blue", 0.5f);
|
||||
svg.draw_outline(islands, "red", "red", scale_(0.05));
|
||||
svg.draw_outline(union_ex(out), "green", "green", scale_(0.05));
|
||||
svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05));
|
||||
for (const Point &pt : samples)
|
||||
svg.draw(pt, "black", coord_t(scale_(0.15)));
|
||||
svg.Close();
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f);
|
||||
svg.draw(islands, "red", 0.5f);
|
||||
svg.draw(union_ex(out), "green", 0.5f);
|
||||
svg.draw(union_ex(*m_support_polygons), "blue", 0.5f);
|
||||
svg.draw_outline(islands, "red", "red", scale_(0.05));
|
||||
svg.draw_outline(union_ex(out), "green", "green", scale_(0.05));
|
||||
svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05));
|
||||
for (const Point& pt : samples)
|
||||
svg.draw(pt, "black", coord_t(scale_(0.15)));
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
if (m_support_angle != 0.)
|
||||
|
|
@ -1486,14 +1528,13 @@ static const double sharp_tail_max_support_height = 16.f;
|
|||
|
||||
// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset
|
||||
// no_interface_offset: minimum of external perimeter widths
|
||||
static inline Polygons detect_overhangs(
|
||||
static inline ExPolygons detect_overhangs(
|
||||
const Layer &layer,
|
||||
const size_t layer_id,
|
||||
Polygons &lower_layer_polygons,
|
||||
const PrintConfig &print_config,
|
||||
const PrintObjectConfig &object_config,
|
||||
SupportAnnotations &annotations,
|
||||
SlicesMarginCache &slices_margin,
|
||||
const double gap_xy
|
||||
#ifdef SLIC3R_DEBUG
|
||||
, size_t iRun
|
||||
|
|
@ -1508,9 +1549,10 @@ static inline Polygons detect_overhangs(
|
|||
const bool buildplate_only = ! annotations.buildplate_covered.empty();
|
||||
// If user specified a custom angle threshold, convert it to radians.
|
||||
// Zero means automatic overhang detection.
|
||||
const double threshold_rad = (object_config.support_threshold_angle.value > 0) ?
|
||||
M_PI * double(object_config.support_threshold_angle.value + 1) / 180. : // +1 makes the threshold inclusive
|
||||
0.;
|
||||
// +1 makes the threshold inclusive
|
||||
double thresh_angle = object_config.support_threshold_angle.value > 0 ? object_config.support_threshold_angle.value + 1 : 0;
|
||||
thresh_angle = std::min(thresh_angle, 89.); // BBS should be smaller than 90
|
||||
const double threshold_rad = Geometry::deg2rad(thresh_angle);
|
||||
const coordf_t max_bridge_length = scale_(object_config.max_bridge_length.value);
|
||||
const bool bridge_no_support = object_config.bridge_no_support.value;
|
||||
const coordf_t xy_expansion = scale_(object_config.support_expansion.value);
|
||||
|
|
@ -1535,9 +1577,16 @@ static inline Polygons detect_overhangs(
|
|||
// Generate overhang / contact_polygons for non-raft layers.
|
||||
const Layer &lower_layer = *layer.lower_layer;
|
||||
const bool has_enforcer = !annotations.enforcers_layers.empty() && !annotations.enforcers_layers[layer_id].empty();
|
||||
const ExPolygons& lower_layer_expolys = lower_layer.lslices;
|
||||
const ExPolygons& lower_layer_sharptails = lower_layer.sharp_tails;
|
||||
auto& lower_layer_sharptails_height = lower_layer.sharp_tails_height;
|
||||
// Can't directly use lower_layer.lslices, or we'll miss some very sharp tails.
|
||||
// Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose!
|
||||
// FIXME if there are multiple regions with different extrusion width, the following code may not be right.
|
||||
float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width());
|
||||
ExPolygons lower_layer_expolys;
|
||||
for (const ExPolygon& expoly : lower_layer.lslices) {
|
||||
if (!offset_ex(expoly, -fw / 2).empty()) {
|
||||
lower_layer_expolys.emplace_back(expoly);
|
||||
}
|
||||
}
|
||||
|
||||
float lower_layer_offset = 0;
|
||||
for (LayerRegion *layerm : layer.regions()) {
|
||||
|
|
@ -1588,73 +1637,13 @@ static inline Polygons detect_overhangs(
|
|||
for (ExPolygon& expoly : layerm->raw_slices) {
|
||||
bool is_sharp_tail = false;
|
||||
float accum_height = layer.height;
|
||||
do {
|
||||
if (!g_config_support_sharp_tails) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 1. nothing below
|
||||
// Check whether this is a sharp tail region.
|
||||
// Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body.
|
||||
if (intersection_ex({ expoly }, lower_layer_expolys).empty()) {
|
||||
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly,-0.5*fw).empty();
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. something below
|
||||
// check whether this is above a sharp tail region.
|
||||
|
||||
// 2.1 If no sharp tail below, this is considered as common region.
|
||||
ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails);
|
||||
if (supported_by_lower.empty()) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.2 If sharp tail below, check whether it support this region enough.
|
||||
float supported_area = 0.f;
|
||||
BoundingBox bbox;
|
||||
for (ExPolygon& temp : supported_by_lower) {
|
||||
supported_area += temp.area();
|
||||
bbox.merge(get_extents(temp));
|
||||
}
|
||||
#if 0
|
||||
if (supported_area > area_thresh_well_supported) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.3 check whether sharp tail exceed the max height
|
||||
for (auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
|
||||
if (!intersection_ex(*lower_sharp_tail_height.first, expoly).empty()) {
|
||||
accum_height += lower_sharp_tail_height.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (accum_height >= sharp_tail_max_support_height) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.4 if the area grows fast than threshold, it get connected to other part or
|
||||
// it has a sharp slop and will be auto supported.
|
||||
ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails);
|
||||
Point size_diff = get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size();
|
||||
if (size_diff.both_comp(Point(scale_(5),scale_(5)),">") || !offset_ex(new_overhang_expolys, -5.0 * fw).empty()) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.5 mark the expoly as sharptail
|
||||
is_sharp_tail = true;
|
||||
} while (0);
|
||||
// 1. nothing below
|
||||
// Check whether this is a sharp tail region.
|
||||
// Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body.
|
||||
if (g_config_support_sharp_tails && !overlaps(offset_ex(expoly, 0.5 * fw), lower_layer_expolys)) {
|
||||
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly,-0.1*fw).empty();
|
||||
}
|
||||
|
||||
if (is_sharp_tail) {
|
||||
ExPolygons overhang = diff_ex({ expoly }, lower_layer_polygons);
|
||||
|
|
@ -1663,7 +1652,7 @@ static inline Polygons detect_overhangs(
|
|||
overhang = offset_ex(overhang, 0.05 * fw);
|
||||
polygons_append(diff_polygons, to_polygons(overhang));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff_polygons.empty())
|
||||
|
|
@ -1692,8 +1681,30 @@ static inline Polygons detect_overhangs(
|
|||
} // for each layer.region
|
||||
}
|
||||
|
||||
// BBS: hotfix to make sure ccw polygon is before cw polygon
|
||||
return to_polygons(union_ex(overhang_polygons));
|
||||
ExPolygons overhang_areas = union_ex(overhang_polygons);
|
||||
// check cantilever
|
||||
if (layer.lower_layer) {
|
||||
for (ExPolygon& poly : overhang_areas) {
|
||||
float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width());
|
||||
auto cluster_boundary_ex = intersection_ex(poly, offset_ex(layer.lower_layer->lslices, scale_(0.5)));
|
||||
Polygons cluster_boundary = to_polygons(cluster_boundary_ex);
|
||||
if (cluster_boundary.empty()) continue;
|
||||
double dist_max = 0;
|
||||
for (auto& pt : poly.contour.points) {
|
||||
double dist_pt = std::numeric_limits<double>::max();
|
||||
for (auto& ply : cluster_boundary) {
|
||||
double d = ply.distance_to(pt);
|
||||
dist_pt = std::min(dist_pt, d);
|
||||
}
|
||||
dist_max = std::max(dist_max, dist_pt);
|
||||
}
|
||||
if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base
|
||||
layer.cantilevers.emplace_back(poly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return overhang_areas;
|
||||
}
|
||||
|
||||
// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset
|
||||
|
|
@ -1721,11 +1732,6 @@ static inline std::tuple<Polygons, Polygons, double> detect_contacts(
|
|||
// BBS.
|
||||
const bool auto_normal_support = object_config.support_type.value == stNormalAuto;
|
||||
const bool buildplate_only = !annotations.buildplate_covered.empty();
|
||||
// If user specified a custom angle threshold, convert it to radians.
|
||||
// Zero means automatic overhang detection.
|
||||
const double threshold_rad = (object_config.support_threshold_angle.value > 0) ?
|
||||
M_PI * double(object_config.support_threshold_angle.value + 1) / 180. : // +1 makes the threshold inclusive
|
||||
0.;
|
||||
float no_interface_offset = 0.f;
|
||||
|
||||
if (layer_id == 0)
|
||||
|
|
@ -1851,7 +1857,7 @@ static inline std::pair<PrintObjectSupportMaterial::MyLayer*, PrintObjectSupport
|
|||
bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z;
|
||||
} else {
|
||||
print_z = layer.bottom_z() - slicing_params.gap_support_object;
|
||||
height = print_config.independent_support_layer_height ? 0. : object_config.layer_height;
|
||||
height = print_config.independent_support_layer_height ? 0. : layer.lower_layer->height/*object_config.layer_height*/; // BBS: need to consider adaptive layer heights
|
||||
bottom_z = print_z - height;
|
||||
// Ignore this contact area if it's too low.
|
||||
// Don't want to print a layer below the first layer height as it may not stick well.
|
||||
|
|
@ -2117,27 +2123,30 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double
|
|||
|
||||
|
||||
struct OverhangCluster {
|
||||
std::map<int, std::vector<Polygon*>> layer_overhangs;
|
||||
Polygons merged_overhangs_dilated;
|
||||
std::map<int, std::vector<ExPolygon*>> layer_overhangs;
|
||||
ExPolygons merged_overhangs_dilated;
|
||||
int min_layer = 1e7;
|
||||
int max_layer = 0;
|
||||
coordf_t offset_scaled = 0;
|
||||
bool is_cantilever = false;
|
||||
bool is_sharp_tail = false;
|
||||
bool is_small_overhang = false;
|
||||
|
||||
OverhangCluster(Polygon* overhang, int layer_nr, coordf_t offset_scaled) {
|
||||
OverhangCluster(ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) {
|
||||
this->offset_scaled = offset_scaled;
|
||||
insert(overhang, layer_nr);
|
||||
}
|
||||
|
||||
void insert(Polygon* overhang_new, int layer_nr) {
|
||||
void insert(ExPolygon* overhang_new, int layer_nr) {
|
||||
if (layer_overhangs.find(layer_nr) != layer_overhangs.end()) {
|
||||
layer_overhangs[layer_nr].push_back(overhang_new);
|
||||
}
|
||||
else {
|
||||
layer_overhangs.emplace(layer_nr, std::vector<Polygon*>{ overhang_new });
|
||||
layer_overhangs.emplace(layer_nr, std::vector<ExPolygon*>{ overhang_new });
|
||||
}
|
||||
Polygons overhang_dilated = offset_scaled > EPSILON ? expand(*overhang_new, offset_scaled) : Polygons{ *overhang_new };
|
||||
ExPolygons overhang_dilated = offset_scaled > EPSILON ? offset_ex(*overhang_new, offset_scaled) : ExPolygons{ *overhang_new };
|
||||
if (!overhang_dilated.empty())
|
||||
merged_overhangs_dilated = union_(merged_overhangs_dilated, overhang_dilated);
|
||||
merged_overhangs_dilated = union_ex(merged_overhangs_dilated, overhang_dilated);
|
||||
min_layer = std::min(min_layer, layer_nr);
|
||||
max_layer = std::max(max_layer, layer_nr);
|
||||
}
|
||||
|
|
@ -2146,36 +2155,39 @@ struct OverhangCluster {
|
|||
return max_layer - min_layer + 1;
|
||||
}
|
||||
|
||||
bool intersects(const Polygon& overhang_new, int layer_nr) {
|
||||
bool intersects(const ExPolygon& overhang_new, int layer_nr) {
|
||||
if (layer_nr < 1)
|
||||
return false;
|
||||
|
||||
auto it = layer_overhangs.find(layer_nr - 1);
|
||||
if (it == layer_overhangs.end())
|
||||
//auto it = layer_overhangs.find(layer_nr - 1);
|
||||
//if (it == layer_overhangs.end())
|
||||
// return false;
|
||||
//ExPolygons overhangs_lower;
|
||||
//for (ExPolygon* poly : it->second) {
|
||||
// overhangs_lower.push_back(*poly);
|
||||
//}
|
||||
if (layer_nr<min_layer - 1 || layer_nr>max_layer + 1)
|
||||
return false;
|
||||
|
||||
Polygons overhangs_lower;
|
||||
for (Polygon* poly : it->second) {
|
||||
overhangs_lower.push_back(*poly);
|
||||
}
|
||||
const Polygons overhang_dilated = expand(overhang_new, offset_scaled);
|
||||
return !intersection(overhang_dilated, overhangs_lower).empty();
|
||||
const ExPolygons overhang_dilated = offset_ex(overhang_new, offset_scaled);
|
||||
return overlaps(overhang_dilated, merged_overhangs_dilated);
|
||||
}
|
||||
};
|
||||
|
||||
static void add_overhang(std::vector<OverhangCluster>& clusters, Polygon* overhang, int layer_nr, coordf_t offset_scaled) {
|
||||
static OverhangCluster* add_overhang(std::vector<OverhangCluster>& clusters, ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) {
|
||||
OverhangCluster* cluster = nullptr;
|
||||
bool found = false;
|
||||
for (int i = 0; i < clusters.size(); i++) {
|
||||
auto& cluster = clusters[i];
|
||||
if (cluster.intersects(*overhang, layer_nr)) {
|
||||
cluster.insert(overhang, layer_nr);
|
||||
found = true;
|
||||
auto cluster_i = &clusters[i];
|
||||
if (cluster_i->intersects(*overhang, layer_nr)) {
|
||||
cluster_i->insert(overhang, layer_nr);
|
||||
cluster = cluster_i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
clusters.emplace_back(overhang, layer_nr, offset_scaled);
|
||||
if (!cluster) {
|
||||
cluster = &clusters.emplace_back(overhang, layer_nr, offset_scaled);
|
||||
}
|
||||
return cluster;
|
||||
};
|
||||
|
||||
// Generate top contact layers supporting overhangs.
|
||||
|
|
@ -2214,113 +2226,186 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
contact_out.assign(num_layers * 2, nullptr);
|
||||
tbb::spin_mutex layer_storage_mutex;
|
||||
|
||||
std::vector<Polygons> overhangs_per_layers(num_layers);
|
||||
for (size_t layer_id = this->has_raft() ? 0 : 1; layer_id < num_layers; layer_id++) {
|
||||
const Layer& layer = *object.layers()[layer_id];
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices);
|
||||
SlicesMarginCache slices_margin;
|
||||
std::vector<ExPolygons> overhangs_per_layers(num_layers);
|
||||
size_t layer_id_start = this->has_raft() ? 0 : 1;
|
||||
// main part of overhang detection can be parallel
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(layer_id_start, num_layers),
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) {
|
||||
const Layer& layer = *object.layers()[layer_id];
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices);
|
||||
|
||||
Polygons overhang_polygons = detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, slices_margin, m_support_params.gap_xy
|
||||
overhangs_per_layers[layer_id] = detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, m_support_params.gap_xy
|
||||
#ifdef SLIC3R_DEBUG
|
||||
, iRun
|
||||
, iRun
|
||||
#endif // SLIC3R_DEBUG
|
||||
);
|
||||
);
|
||||
|
||||
overhangs_per_layers[layer_id] = std::move(overhang_polygons);
|
||||
if (object.print()->canceled())
|
||||
break;
|
||||
}
|
||||
}
|
||||
); // end tbb::parallel_for
|
||||
|
||||
if (object.print()->canceled())
|
||||
return MyLayersPtr();
|
||||
if (object.print()->canceled())
|
||||
return MyLayersPtr();
|
||||
|
||||
// check if the sharp tails should be extended higher
|
||||
bool detect_first_sharp_tail_only = false;
|
||||
const coordf_t extrusion_width = m_object_config->line_width.value;
|
||||
const coordf_t extrusion_width_scaled = scale_(extrusion_width);
|
||||
if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) {
|
||||
for (size_t layer_nr = 0; layer_nr < object.layer_count(); layer_nr++) {
|
||||
if (object.print()->canceled())
|
||||
break;
|
||||
|
||||
const Layer* layer = object.get_layer(layer_nr);
|
||||
const Layer* lower_layer = layer->lower_layer;
|
||||
// skip if:
|
||||
// 1) if the current layer is already detected as sharp tails
|
||||
// 2) lower layer has no sharp tails
|
||||
if (!lower_layer || layer->sharp_tails.empty() == false || lower_layer->sharp_tails.empty() == true)
|
||||
continue;
|
||||
|
||||
// BBS detect sharp tail
|
||||
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
|
||||
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
||||
for (const ExPolygon& expoly : layer->lslices) {
|
||||
bool is_sharp_tail = false;
|
||||
float accum_height = layer->height;
|
||||
do {
|
||||
// 2. something below
|
||||
// check whether this is above a sharp tail region.
|
||||
|
||||
// 2.1 If no sharp tail below, this is considered as common region.
|
||||
ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails);
|
||||
if (supported_by_lower.empty()) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.2 If sharp tail below, check whether it support this region enough.
|
||||
#if 0
|
||||
// judge by area isn't reliable, failure cases include 45 degree rotated cube
|
||||
float supported_area = area(supported_by_lower);
|
||||
if (supported_area > area_thresh_well_supported) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
BoundingBox bbox = get_extents(supported_by_lower);
|
||||
if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.3 check whether sharp tail exceed the max height
|
||||
for (auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
|
||||
if (lower_sharp_tail_height.first->overlaps(expoly)) {
|
||||
accum_height += lower_sharp_tail_height.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (accum_height >= sharp_tail_max_support_height) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.4 if the area grows fast than threshold, it get connected to other part or
|
||||
// it has a sharp slop and will be auto supported.
|
||||
ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails);
|
||||
Point size_diff = get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size();
|
||||
if (size_diff.both_comp(Point(scale_(5), scale_(5)), ">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.5 mark the expoly as sharptail
|
||||
is_sharp_tail = true;
|
||||
} while (0);
|
||||
|
||||
if (is_sharp_tail) {
|
||||
ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices);
|
||||
layer->sharp_tails.push_back(expoly);
|
||||
layer->sharp_tails_height.insert({ &expoly, accum_height });
|
||||
append(overhangs_per_layers[layer_nr], overhang);
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), object.bounding_box());
|
||||
if (svg.is_opened()) svg.draw(overhang, "yellow");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (object.print()->canceled())
|
||||
return MyLayersPtr();
|
||||
|
||||
// BBS
|
||||
// BBS group overhang clusters
|
||||
if (g_config_remove_small_overhangs) {
|
||||
std::vector<OverhangCluster> clusters;
|
||||
double fw_scaled = scale_(m_object_config->line_width);
|
||||
std::set<Polygon*> removed_overhang;
|
||||
std::set<ExPolygon*> removed_overhang;
|
||||
|
||||
for (size_t layer_id = this->has_raft() ? 0 : 1; layer_id < num_layers; layer_id++) {
|
||||
for (Polygon& overhang : overhangs_per_layers[layer_id]) {
|
||||
if (overhang.is_counter_clockwise())
|
||||
add_overhang(clusters, &overhang, layer_id, fw_scaled);
|
||||
for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) {
|
||||
const Layer* layer = object.get_layer(layer_id);
|
||||
for (auto& overhang : overhangs_per_layers[layer_id]) {
|
||||
OverhangCluster* cluster = add_overhang(clusters, &overhang, layer_id, fw_scaled);
|
||||
if (overlaps({ overhang }, layer->cantilevers))
|
||||
cluster->is_cantilever = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (OverhangCluster& cluster : clusters) {
|
||||
// 1. check overhang span size is smaller than 3mm
|
||||
//auto bbox_size = get_extents(cluster.merged_overhangs_dilated).size();
|
||||
//const double dimension_limit = scale_(3.0) + 2 * fw_scaled;
|
||||
//if (bbox_size.x() > dimension_limit || bbox_size.y() > dimension_limit)
|
||||
// continue;
|
||||
|
||||
double area = 0.f;
|
||||
// 2. check overhang cluster size is smaller than 3.0 * fw_scaled
|
||||
auto erode1 = offset(cluster.merged_overhangs_dilated, -2.5 * fw_scaled);
|
||||
for (Polygon& poly : erode1)
|
||||
area += poly.area() * (poly.is_counter_clockwise() ? 1.0 : -1.0);
|
||||
if (std::abs(area) > SQ(scale_(0.1)))
|
||||
continue;
|
||||
|
||||
// 3. check whether the small overhang is sharp tail
|
||||
bool is_sharp_tail = false;
|
||||
for (size_t layer_id = cluster.min_layer; layer_id < cluster.max_layer; layer_id++) {
|
||||
const Layer& layer = *object.layers()[layer_id];
|
||||
if (!intersection_ex(layer.sharp_tails, cluster.merged_overhangs_dilated).empty()) {
|
||||
is_sharp_tail = true;
|
||||
cluster.is_sharp_tail = false;
|
||||
for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) {
|
||||
const Layer* layer = object.get_layer(layer_id);
|
||||
if (overlaps(layer->sharp_tails, cluster.merged_overhangs_dilated)) {
|
||||
cluster.is_sharp_tail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_sharp_tail)
|
||||
continue;
|
||||
|
||||
// 4. check whether the overhang cluster is cantilever (far awary from main body)
|
||||
const Layer* layer = object.get_layer(cluster.min_layer);
|
||||
if (layer->lower_layer == NULL) continue;
|
||||
Layer* lower_layer = layer->lower_layer;
|
||||
auto cluster_boundary = intersection(cluster.merged_overhangs_dilated, offset(lower_layer->lslices, scale_(0.5)));
|
||||
double dist_max = 0;
|
||||
Points cluster_pts;
|
||||
for (auto& poly : cluster.merged_overhangs_dilated)
|
||||
append(cluster_pts, poly.points);
|
||||
for (auto& pt : cluster_pts) {
|
||||
double dist_pt = std::numeric_limits<double>::max();
|
||||
for (auto& poly : cluster_boundary) {
|
||||
double d = poly.distance_to(pt);
|
||||
dist_pt = std::min(dist_pt, d);
|
||||
if (!cluster.is_sharp_tail && !cluster.is_cantilever) {
|
||||
// 2. check overhang cluster size is small
|
||||
cluster.is_small_overhang = false;
|
||||
auto erode1 = offset_ex(cluster.merged_overhangs_dilated, -2.5 * fw_scaled);
|
||||
if (area(erode1) < SQ(scale_(0.1))) {
|
||||
cluster.is_small_overhang = true;
|
||||
}
|
||||
dist_max = std::max(dist_max, dist_pt);
|
||||
}
|
||||
if (dist_max > 5.0 * fw_scaled)
|
||||
continue;
|
||||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
const Layer* layer1 = object.get_layer(cluster.min_layer);
|
||||
BoundingBox bbox = get_extents(cluster.merged_overhangs_dilated);
|
||||
bbox.merge(get_extents(layer1->lslices));
|
||||
SVG svg(format("SVG/overhangCluster_%s_%s_tail=%s_cantilever=%s_small=%s.svg", cluster.min_layer, layer1->print_z, cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox);
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(layer1->lslices, "red");
|
||||
svg.draw(cluster.merged_overhangs_dilated, "blue");
|
||||
}
|
||||
#endif
|
||||
|
||||
// 5. remove small overhangs
|
||||
for (auto overhangs : cluster.layer_overhangs) {
|
||||
for (Polygon* poly : overhangs.second)
|
||||
removed_overhang.insert(poly);
|
||||
if (cluster.is_small_overhang) {
|
||||
for (auto overhangs : cluster.layer_overhangs) {
|
||||
for (auto* poly : overhangs.second)
|
||||
removed_overhang.insert(poly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t layer_id = this->has_raft() ? 0 : 1; layer_id < num_layers; layer_id++) {
|
||||
Polygons& layer_overhangs = overhangs_per_layers[layer_id];
|
||||
for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) {
|
||||
auto& layer_overhangs = overhangs_per_layers[layer_id];
|
||||
if (layer_overhangs.empty())
|
||||
continue;
|
||||
|
||||
bool remove_hole = false;
|
||||
for (int poly_idx = 0; poly_idx < layer_overhangs.size(); poly_idx++) {
|
||||
Polygon* overhang = &layer_overhangs[poly_idx];
|
||||
if (overhang->is_counter_clockwise()) {
|
||||
if (removed_overhang.find(overhang) != removed_overhang.end()) {
|
||||
remove_hole = true;
|
||||
overhang->clear();
|
||||
}
|
||||
else
|
||||
remove_hole = false;
|
||||
}
|
||||
else {
|
||||
if (remove_hole)
|
||||
overhang->clear();
|
||||
auto* overhang = &layer_overhangs[poly_idx];
|
||||
if (removed_overhang.find(overhang) != removed_overhang.end()) {
|
||||
overhang->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2329,9 +2414,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
if (object.print()->canceled())
|
||||
return MyLayersPtr();
|
||||
|
||||
for (size_t layer_id = this->has_raft() ? 0 : 1; layer_id < num_layers; layer_id++) {
|
||||
for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) {
|
||||
const Layer& layer = *object.layers()[layer_id];
|
||||
Polygons overhang_polygons = overhangs_per_layers[layer_id];
|
||||
Polygons overhang_polygons = to_polygons(overhangs_per_layers[layer_id]);
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices);
|
||||
SlicesMarginCache slices_margin;
|
||||
|
||||
|
|
@ -3024,6 +3109,36 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
|
|||
assert(top_contacts[i]->height > 0.);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
#if 0 // #ifdef SLIC3R_DEBUG
|
||||
// check bounds
|
||||
std::ofstream out;
|
||||
out.open("./SVG/ns_bounds.txt");
|
||||
if (out.is_open()) {
|
||||
if (!top_contacts.empty()) {
|
||||
out << "### Top Contacts ###" << std::endl;
|
||||
for (auto& t : top_contacts) {
|
||||
out << t->print_z << std::endl;
|
||||
}
|
||||
}
|
||||
if (!bottom_contacts.empty()) {
|
||||
out << "### Bottome Contacts ###" << std::endl;
|
||||
for (auto& b : bottom_contacts) {
|
||||
out << b->print_z << std::endl;
|
||||
}
|
||||
}
|
||||
if (!intermediate_layers.empty()) {
|
||||
out << "### Intermediate Layers ###" << std::endl;
|
||||
for (auto& i : intermediate_layers) {
|
||||
out << i->print_z << std::endl;
|
||||
}
|
||||
}
|
||||
out << "### Slice Layers ###" << std::endl;
|
||||
for (size_t j = 0; j < object.layers().size(); ++j) {
|
||||
out << object.layers()[j]->print_z << std::endl;
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
return intermediate_layers;
|
||||
}
|
||||
|
||||
|
|
@ -3606,7 +3721,6 @@ static inline void fill_expolygon_generate_paths(
|
|||
ExPolygon &&expolygon,
|
||||
Fill *filler,
|
||||
const FillParams &fill_params,
|
||||
float density,
|
||||
ExtrusionRole role,
|
||||
const Flow &flow)
|
||||
{
|
||||
|
|
@ -3628,12 +3742,11 @@ static inline void fill_expolygons_generate_paths(
|
|||
ExPolygons &&expolygons,
|
||||
Fill *filler,
|
||||
const FillParams &fill_params,
|
||||
float density,
|
||||
ExtrusionRole role,
|
||||
const Flow &flow)
|
||||
{
|
||||
for (ExPolygon &expoly : expolygons)
|
||||
fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, density, role, flow);
|
||||
fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow);
|
||||
}
|
||||
|
||||
static inline void fill_expolygons_generate_paths(
|
||||
|
|
@ -3647,7 +3760,7 @@ static inline void fill_expolygons_generate_paths(
|
|||
FillParams fill_params;
|
||||
fill_params.density = density;
|
||||
fill_params.dont_adjust = true;
|
||||
fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow);
|
||||
fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, role, flow);
|
||||
}
|
||||
|
||||
static inline void fill_expolygons_with_sheath_generate_paths(
|
||||
|
|
@ -3695,7 +3808,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
|
|||
}
|
||||
extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
|
||||
// Fill in the rest.
|
||||
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
|
||||
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, role, flow);
|
||||
if (no_sort && ! eec->empty())
|
||||
dst.emplace_back(eec.release());
|
||||
}
|
||||
|
|
@ -4545,13 +4658,23 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density;
|
||||
filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing();
|
||||
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
|
||||
// BBS support more interface patterns
|
||||
FillParams fill_params;
|
||||
fill_params.density = density;
|
||||
fill_params.dont_adjust = true;
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = Geometry::deg2rad(m_support_params.base_angle);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced)
|
||||
filler_interface->layer_id = support_layer.interface_id();
|
||||
fill_expolygons_generate_paths(
|
||||
// Destination
|
||||
layer_ex.extrusions,
|
||||
// Regions to fill
|
||||
union_safety_offset_ex(layer_ex.polygons_to_extrude()),
|
||||
// Filler and its parameters
|
||||
filler_interface.get(), float(density),
|
||||
filler_interface.get(), fill_params,
|
||||
// Extrusion parameters
|
||||
erSupportMaterialInterface, interface_flow);
|
||||
}
|
||||
|
|
@ -4660,7 +4783,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
|
||||
}
|
||||
if (! polys.empty())
|
||||
expolygons_append(support_layer.support_islands.expolygons, union_ex(polys));
|
||||
expolygons_append(support_layer.support_islands, union_ex(polys));
|
||||
} // for each support_layer_id
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -67,12 +67,9 @@ SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int nty
|
|||
|
||||
void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons)
|
||||
{
|
||||
for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
|
||||
if (surface->surface_type == type) {
|
||||
Polygons pp = surface->expolygon;
|
||||
polygons->insert(polygons->end(), pp.begin(), pp.end());
|
||||
}
|
||||
}
|
||||
for (const Surface &surface : this->surfaces)
|
||||
if (surface.surface_type == type)
|
||||
polygons_append(*polygons, to_polygons(surface.expolygon));
|
||||
}
|
||||
|
||||
void SurfaceCollection::keep_type(const SurfaceType type)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -83,7 +83,7 @@ public:
|
|||
* \param layer The layer of interest
|
||||
* \return Polygons object
|
||||
*/
|
||||
const ExPolygons& get_avoidance(coordf_t radius, size_t layer_idx) const;
|
||||
const ExPolygons& get_avoidance(coordf_t radius, size_t layer_idx, int recursions=0) const;
|
||||
|
||||
Polygons get_contours(size_t layer_nr) const;
|
||||
Polygons get_contours_with_holes(size_t layer_nr) const;
|
||||
|
|
@ -94,11 +94,20 @@ private:
|
|||
/*!
|
||||
* \brief Convenience typedef for the keys to the caches
|
||||
*/
|
||||
using RadiusLayerPair = std::pair<coordf_t, size_t>;
|
||||
|
||||
struct RadiusLayerPair {
|
||||
coordf_t radius;
|
||||
size_t layer_nr;
|
||||
int recursions;
|
||||
|
||||
};
|
||||
struct RadiusLayerPairEquality {
|
||||
constexpr bool operator()(const RadiusLayerPair& _Left, const RadiusLayerPair& _Right) const {
|
||||
return _Left.radius == _Right.radius && _Left.layer_nr == _Right.layer_nr;
|
||||
}
|
||||
};
|
||||
struct RadiusLayerPairHash {
|
||||
size_t operator()(const RadiusLayerPair& elem) const {
|
||||
return std::hash<coord_t>()(elem.first) ^ std::hash<coord_t>()(elem.second * 7919);
|
||||
return std::hash<coord_t>()(elem.radius) ^ std::hash<coord_t>()(elem.layer_nr * 7919);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -168,8 +177,8 @@ public:
|
|||
* coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for.
|
||||
* So we change to tbb::concurrent_unordered_map
|
||||
*/
|
||||
mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash> m_collision_cache;
|
||||
mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash> m_avoidance_cache;
|
||||
mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash, RadiusLayerPairEquality> m_collision_cache;
|
||||
mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash, RadiusLayerPairEquality> m_avoidance_cache;
|
||||
|
||||
friend TreeSupport;
|
||||
};
|
||||
|
|
@ -203,7 +212,7 @@ public:
|
|||
*/
|
||||
void generate();
|
||||
|
||||
void detect_overhangs();
|
||||
void detect_overhangs(bool detect_first_sharp_tail_only=false);
|
||||
|
||||
enum NodeType {
|
||||
eCircle,
|
||||
|
|
@ -230,6 +239,7 @@ public:
|
|||
, height(0.0)
|
||||
{}
|
||||
|
||||
// when dist_mm_to_top_==0, new node's dist_mm_to_top=parent->dist_mm_to_top + parent->height;
|
||||
Node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, Node* parent,
|
||||
coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_=0)
|
||||
: distance_to_top(distance_to_top)
|
||||
|
|
@ -249,6 +259,8 @@ public:
|
|||
if (dist_mm_to_top==0)
|
||||
dist_mm_to_top = parent->dist_mm_to_top + parent->height;
|
||||
parent->child = this;
|
||||
for (auto& neighbor : parent->merged_neighbours)
|
||||
neighbor->child = this;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,15 +282,15 @@ public:
|
|||
/*!
|
||||
* \brief The position of this node on the layer.
|
||||
*/
|
||||
Point position;
|
||||
|
||||
Point movement; // movement towards neighbor center or outline
|
||||
mutable double radius = 0.0;
|
||||
mutable double max_move_dist = 0.0;
|
||||
NodeType type = eCircle;
|
||||
bool is_merged = false; // this node is generated by merging upper nodes
|
||||
Point position;
|
||||
Point movement; // movement towards neighbor center or outline
|
||||
mutable double radius = 0.0;
|
||||
mutable double max_move_dist = 0.0;
|
||||
NodeType type = eCircle;
|
||||
bool is_merged = false; // this node is generated by merging upper nodes
|
||||
bool is_corner = false;
|
||||
const ExPolygon* overhang = nullptr; // when type==ePolygon, set this value to get original overhang area
|
||||
bool is_processed = false;
|
||||
const ExPolygon *overhang = nullptr; // when type==ePolygon, set this value to get original overhang area
|
||||
|
||||
/*!
|
||||
* \brief The direction of the skin lines above the tip of the branch.
|
||||
|
|
@ -371,7 +383,9 @@ public:
|
|||
bool has_overhangs = false;
|
||||
bool has_sharp_tails = false;
|
||||
bool has_cantilever = false;
|
||||
double max_cantilever_dist = 0;
|
||||
SupportType support_type;
|
||||
SupportMaterialStyle support_style;
|
||||
|
||||
std::unique_ptr<FillLightning::Generator> generator;
|
||||
std::unordered_map<double, size_t> printZ_to_lightninglayer;
|
||||
|
|
@ -395,6 +409,7 @@ private:
|
|||
coordf_t MAX_BRANCH_RADIUS = 10.0;
|
||||
coordf_t MIN_BRANCH_RADIUS = 0.5;
|
||||
float tree_support_branch_diameter_angle = 5.0;
|
||||
bool is_strong = false;
|
||||
bool is_slim = false;
|
||||
bool with_infill = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -984,6 +984,61 @@ indexed_triangle_set its_make_cone(double r, double h, double fa)
|
|||
return mesh;
|
||||
}
|
||||
|
||||
// Generates mesh for a frustum dowel centered about the origin, using the count of sectors
|
||||
// Note: This function uses code for sphere generation, but for stackCount = 2;
|
||||
indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount)
|
||||
{
|
||||
int stackCount = 2;
|
||||
float sectorStep = float(2. * M_PI / sectorCount);
|
||||
float stackStep = float(M_PI / stackCount);
|
||||
|
||||
indexed_triangle_set mesh;
|
||||
auto& vertices = mesh.vertices;
|
||||
vertices.reserve((stackCount - 1) * sectorCount + 2);
|
||||
for (int i = 0; i <= stackCount; ++i) {
|
||||
// from pi/2 to -pi/2
|
||||
double stackAngle = 0.5 * M_PI - stackStep * i;
|
||||
double xy = radius * cos(stackAngle);
|
||||
double z = radius * sin(stackAngle);
|
||||
if (i == 0 || i == stackCount)
|
||||
vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle))));
|
||||
else
|
||||
for (int j = 0; j < sectorCount; ++j) {
|
||||
// from 0 to 2pi
|
||||
double sectorAngle = sectorStep * j + 0.25 * M_PI;
|
||||
vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast<float>());
|
||||
}
|
||||
}
|
||||
|
||||
auto& facets = mesh.indices;
|
||||
facets.reserve(2 * (stackCount - 1) * sectorCount);
|
||||
for (int i = 0; i < stackCount; ++i) {
|
||||
// Beginning of current stack.
|
||||
int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
|
||||
int k1_first = k1;
|
||||
// Beginning of next stack.
|
||||
int k2 = (i == 0) ? 1 : (k1 + sectorCount);
|
||||
int k2_first = k2;
|
||||
for (int j = 0; j < sectorCount; ++j) {
|
||||
// 2 triangles per sector excluding first and last stacks
|
||||
int k1_next = k1;
|
||||
int k2_next = k2;
|
||||
if (i != 0) {
|
||||
k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
|
||||
facets.emplace_back(k1, k2, k1_next);
|
||||
}
|
||||
if (i + 1 != stackCount) {
|
||||
k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
|
||||
facets.emplace_back(k1_next, k2, k2_next);
|
||||
}
|
||||
k1 = k1_next;
|
||||
k2 = k2_next;
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
indexed_triangle_set its_make_pyramid(float base, float height)
|
||||
{
|
||||
float a = base / 2.f;
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ indexed_triangle_set its_make_cube(double x, double y, double z);
|
|||
indexed_triangle_set its_make_prism(float width, float length, float height);
|
||||
indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360));
|
||||
indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360));
|
||||
indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount);
|
||||
indexed_triangle_set its_make_pyramid(float base, float height);
|
||||
indexed_triangle_set its_make_sphere(double radius, double fa);
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@
|
|||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
const float epson = 1e-3;
|
||||
bool is_equal(float lh, float rh)
|
||||
{
|
||||
return abs(lh - rh) <= epson;
|
||||
}
|
||||
|
||||
bool is_less(float lh, float rh)
|
||||
{
|
||||
return lh + epson < rh;
|
||||
}
|
||||
|
||||
class IntersectionReference
|
||||
{
|
||||
|
|
@ -2007,11 +2017,12 @@ static void triangulate_slice(
|
|||
map_vertex_to_index.emplace_back(to_2d(its.vertices[i]), i);
|
||||
std::sort(map_vertex_to_index.begin(), map_vertex_to_index.end(),
|
||||
[](const std::pair<Vec2f, int> &l, const std::pair<Vec2f, int> &r) {
|
||||
return l.first.x() < r.first.x() ||
|
||||
(l.first.x() == r.first.x() && (l.first.y() < r.first.y() ||
|
||||
(l.first.y() == r.first.y() && l.second < r.second))); });
|
||||
return is_less(l.first.x(), r.first.x()) ||
|
||||
(is_equal(l.first.x(), r.first.x()) && (is_less(l.first.y(), r.first.y()) ||
|
||||
(is_equal(l.first.y(), r.first.y()) && l.second < r.second))); });
|
||||
|
||||
// 2) Discover duplicate points on the slice. Remap duplicate vertices to a vertex with a lowest index.
|
||||
// Remove denegerate triangles, if they happen to be created by merging duplicate vertices.
|
||||
{
|
||||
std::vector<int> map_duplicate_vertex(int(its.vertices.size()) - num_original_vertices, -1);
|
||||
int i = 0;
|
||||
|
|
@ -2024,7 +2035,7 @@ static void triangulate_slice(
|
|||
// map to itself
|
||||
map_duplicate_vertex[iidx - num_original_vertices] = iidx;
|
||||
int j = i;
|
||||
for (++ j; j < int(map_vertex_to_index.size()) && ipos.x() == map_vertex_to_index[j].first.x() && ipos.y() == map_vertex_to_index[j].first.y(); ++ j) {
|
||||
for (++ j; j < int(map_vertex_to_index.size()) && is_equal(ipos.x(), map_vertex_to_index[j].first.x()) && is_equal(ipos.y(), map_vertex_to_index[j].first.y()); ++ j) {
|
||||
const int jidx = map_vertex_to_index[j].second;
|
||||
assert(jidx >= num_original_vertices);
|
||||
if (jidx >= num_original_vertices)
|
||||
|
|
@ -2034,10 +2045,20 @@ static void triangulate_slice(
|
|||
i = j;
|
||||
}
|
||||
map_vertex_to_index.erase(map_vertex_to_index.begin() + k, map_vertex_to_index.end());
|
||||
for (stl_triangle_vertex_indices &f : its.indices)
|
||||
for (i = 0; i < 3; ++ i)
|
||||
if (f(i) >= num_original_vertices)
|
||||
f(i) = map_duplicate_vertex[f(i) - num_original_vertices];
|
||||
for (i = 0; i < int(its.indices.size());) {
|
||||
stl_triangle_vertex_indices &f = its.indices[i];
|
||||
// Remap the newly added face vertices.
|
||||
for (k = 0; k < 3; ++ k)
|
||||
if (f(k) >= num_original_vertices)
|
||||
f(k) = map_duplicate_vertex[f(k) - num_original_vertices];
|
||||
if (f(0) == f(1) || f(0) == f(2) || f(1) == f(2)) {
|
||||
// Remove degenerate face.
|
||||
f = its.indices.back();
|
||||
its.indices.pop_back();
|
||||
} else
|
||||
// Keep the face.
|
||||
++ i;
|
||||
}
|
||||
}
|
||||
|
||||
if (triangulate) {
|
||||
|
|
@ -2048,9 +2069,11 @@ static void triangulate_slice(
|
|||
for (size_t j = 0; j < 3; ++ j) {
|
||||
Vec3f v = triangles[i ++].cast<float>();
|
||||
auto it = lower_bound_by_predicate(map_vertex_to_index.begin(), map_vertex_to_index.end(),
|
||||
[&v](const std::pair<Vec2f, int> &l) { return l.first.x() < v.x() || (l.first.x() == v.x() && l.first.y() < v.y()); });
|
||||
[&v](const std::pair<Vec2f, int> &l) {
|
||||
return is_less(l.first.x(), v.x()) || (is_equal(l.first.x(), v.x()) && is_less(l.first.y(), v.y()));
|
||||
});
|
||||
int idx = -1;
|
||||
if (it != map_vertex_to_index.end() && it->first.x() == v.x() && it->first.y() == v.y())
|
||||
if (it != map_vertex_to_index.end() && is_equal(it->first.x(), v.x()) && is_equal(it->first.y(), v.y()))
|
||||
idx = it->second;
|
||||
else {
|
||||
// Try to find the vertex in the list of newly added vertices. Those vertices are not matched on the cut and they shall be rare.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#define CLI_3MF_NEW_MACHINE_NOT_SUPPORTED -16
|
||||
#define CLI_PROCESS_NOT_COMPATIBLE -17
|
||||
#define CLI_INVALID_VALUES_IN_3MF -18
|
||||
#define CLI_POSTPROCESS_NOT_SUPPORTED -19
|
||||
|
||||
|
||||
#define CLI_NO_SUITABLE_OBJECTS -50
|
||||
|
|
@ -46,9 +47,12 @@
|
|||
#define CLI_IMPORT_CACHE_NOT_FOUND -55
|
||||
#define CLI_IMPORT_CACHE_DATA_CAN_NOT_USE -56
|
||||
#define CLI_IMPORT_CACHE_LOAD_FAILED -57
|
||||
#define CLI_SLICING_TIME_EXCEEDS_LIMIT -58
|
||||
#define CLI_TRIANGLE_COUNT_EXCEEDS_LIMIT -59
|
||||
#define CLI_NO_SUITABLE_OBJECTS_AFTER_SKIP -60
|
||||
|
||||
|
||||
#define CLI_SLICING_ERROR -100
|
||||
#define CLI_SLICING_ERROR -100
|
||||
#define CLI_GCODE_PATH_CONFLICTS -101
|
||||
|
||||
|
||||
namespace boost { namespace filesystem { class directory_entry; }}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
|
@ -895,6 +896,8 @@ CopyFileResult copy_file(const std::string &from, const std::string &to, std::st
|
|||
goto __finished;
|
||||
}
|
||||
|
||||
FlushFileBuffers(handledst);
|
||||
|
||||
__finished:
|
||||
if (src_wstr)
|
||||
delete[] src_wstr;
|
||||
|
|
@ -1149,7 +1152,16 @@ std::string get_process_name(int pid)
|
|||
while (auto q = strchr(p + 1, '/')) p = q;
|
||||
return p;
|
||||
#else
|
||||
return {};
|
||||
char pathbuf[512] = {0};
|
||||
char proc_path[32] = "/proc/self/exe";
|
||||
if (pid != 0) { snprintf(proc_path, sizeof(proc_path), "/proc/%d/exe", pid); }
|
||||
if (readlink(proc_path, pathbuf, sizeof(pathbuf)) < 0) {
|
||||
perror(NULL);
|
||||
return {};
|
||||
}
|
||||
char *p = pathbuf;
|
||||
while (auto q = strchr(p + 1, '/')) p = q;
|
||||
return p;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/AuxiliaryDialog.hpp
|
||||
GUI/Auxiliary.cpp
|
||||
GUI/Auxiliary.hpp
|
||||
GUI/Project.cpp
|
||||
GUI/Project.hpp
|
||||
GUI/BackgroundSlicingProcess.cpp
|
||||
GUI/BackgroundSlicingProcess.hpp
|
||||
GUI/BitmapCache.cpp
|
||||
|
|
@ -195,6 +197,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/GUI_Factories.hpp
|
||||
GUI/GUI_ObjectList.cpp
|
||||
GUI/GUI_ObjectList.hpp
|
||||
GUI/GUI_ObjectLayers.cpp
|
||||
GUI/GUI_ObjectLayers.hpp
|
||||
GUI/GUI_AuxiliaryList.cpp
|
||||
GUI/GUI_AuxiliaryList.hpp
|
||||
GUI/GUI_ObjectSettings.cpp
|
||||
|
|
@ -267,6 +271,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/3DBed.hpp
|
||||
GUI/Camera.cpp
|
||||
GUI/Camera.hpp
|
||||
GUI/CameraUtils.cpp
|
||||
GUI/CameraUtils.hpp
|
||||
GUI/wxExtensions.cpp
|
||||
GUI/wxExtensions.hpp
|
||||
GUI/WipeTowerDialog.cpp
|
||||
|
|
|
|||
|
|
@ -569,26 +569,6 @@ Transform3d GLVolume::world_matrix() const
|
|||
return m;
|
||||
}
|
||||
|
||||
//BBS: scaled_matrix
|
||||
Transform3d GLVolume::world_matrix( float scale_factor) const
|
||||
{
|
||||
//const Vec3d& volume_translation = m_volume_transformation.get_offset();
|
||||
//Vec3d scaling_factor = { scale_factor, scale_factor, scale_factor };
|
||||
Vec3d ofs2ass = m_offset_to_assembly * (GLVolume::explosion_ratio - 1.0);
|
||||
Vec3d volofs2obj = m_volume_transformation.get_offset() * (GLVolume::explosion_ratio - 1.0);
|
||||
|
||||
Transform3d volume_matrix = Geometry::assemble_transform(
|
||||
m_volume_transformation.get_offset() + ofs2ass + volofs2obj,
|
||||
m_volume_transformation.get_rotation(),
|
||||
m_volume_transformation.get_scaling_factor() * scale_factor,
|
||||
m_volume_transformation.get_mirror()
|
||||
);
|
||||
Transform3d m = m_instance_transformation.get_matrix() * volume_matrix;
|
||||
|
||||
//m.translation()(2) += m_sla_shift_z;
|
||||
return m;
|
||||
}
|
||||
|
||||
bool GLVolume::is_left_handed() const
|
||||
{
|
||||
const Vec3d &m1 = m_instance_transformation.get_mirror();
|
||||
|
|
@ -834,8 +814,6 @@ void GLVolume::render(bool with_outline) const
|
|||
fclose(file);
|
||||
}
|
||||
#endif
|
||||
|
||||
Transform3d matrix = world_matrix();
|
||||
render_body();
|
||||
//BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, outline render body, shader name %2%")%__LINE__ %shader->get_name();
|
||||
|
||||
|
|
@ -887,7 +865,8 @@ void GLVolume::render(bool with_outline) const
|
|||
glsafe(::glPopMatrix());
|
||||
glsafe(::glPushMatrix());
|
||||
|
||||
matrix = world_matrix(scale);
|
||||
Transform3d matrix = world_matrix();
|
||||
matrix.scale(scale);
|
||||
glsafe(::glMultMatrixd(matrix.data()));
|
||||
this->indexed_vertex_array.render(this->tverts_range, this->qverts_range);
|
||||
//BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, outline render for body, shader name %2%")%__LINE__ %shader->get_name();
|
||||
|
|
@ -1065,7 +1044,8 @@ int GLVolumeCollection::load_object_volume(
|
|||
int instance_idx,
|
||||
const std::string &color_by,
|
||||
bool opengl_initialized,
|
||||
bool in_assemble_view)
|
||||
bool in_assemble_view,
|
||||
bool use_loaded_id)
|
||||
{
|
||||
const ModelVolume *model_volume = model_object->volumes[volume_idx];
|
||||
const int extruder_id = model_volume->extruder_id();
|
||||
|
|
@ -1100,6 +1080,11 @@ int GLVolumeCollection::load_object_volume(
|
|||
else
|
||||
v.set_instance_transformation(instance->get_transformation());
|
||||
v.set_volume_transformation(model_volume->get_transformation());
|
||||
//use object's instance id
|
||||
if (use_loaded_id && (instance->loaded_id > 0))
|
||||
v.model_object_ID = instance->loaded_id;
|
||||
else
|
||||
v.model_object_ID = instance->id().id;
|
||||
|
||||
return int(this->volumes.size() - 1);
|
||||
}
|
||||
|
|
@ -1287,14 +1272,13 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
||||
|
||||
// render sinking contours of non-hovered volumes
|
||||
//BBS: remove sinking logic
|
||||
/*if (m_show_sinking_contours)
|
||||
if (m_show_sinking_contours)
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
|
||||
shader->stop_using();
|
||||
volume.first->render_sinking_contours();
|
||||
shader->start_using();
|
||||
}*/
|
||||
}
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
|
@ -1350,8 +1334,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
||||
}
|
||||
|
||||
//BBS: remove sinking logic
|
||||
/*if (m_show_sinking_contours) {
|
||||
if (m_show_sinking_contours) {
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
// render sinking contours of hovered/displaced volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
|
|
@ -1363,7 +1346,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
shader->start_using();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
if (disable_cullface)
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
|
|
|
|||
|
|
@ -357,6 +357,8 @@ public:
|
|||
// An ID containing the extruder ID (used to select color).
|
||||
int extruder_id;
|
||||
|
||||
size_t model_object_ID{0};
|
||||
|
||||
// Various boolean flags.
|
||||
struct {
|
||||
// Is this object selected?
|
||||
|
|
@ -501,9 +503,6 @@ public:
|
|||
Transform3d world_matrix() const;
|
||||
bool is_left_handed() const;
|
||||
|
||||
//BBS: world_matrix with scale factor
|
||||
Transform3d world_matrix(float scale_factor) const;
|
||||
|
||||
const BoundingBoxf3& transformed_bounding_box() const;
|
||||
// non-caching variant
|
||||
BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const;
|
||||
|
|
@ -637,7 +636,8 @@ public:
|
|||
int instance_idx,
|
||||
const std::string &color_by,
|
||||
bool opengl_initialized,
|
||||
bool in_assemble_view = false);
|
||||
bool in_assemble_view = false,
|
||||
bool use_loaded_id = false);
|
||||
|
||||
// Load SLA auxiliary GLVolumes (for support trees or pad).
|
||||
void load_object_auxiliary(
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@
|
|||
#include "GUI_App.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
static bool show_flag;
|
||||
|
||||
wxDEFINE_EVENT(EVT_SELECTED_COLOR, wxCommandEvent);
|
||||
|
||||
AMSMaterialsSetting::AMSMaterialsSetting(wxWindow *parent, wxWindowID id)
|
||||
: DPIDialog(parent, id, _L("AMS Materials Setting"), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
|
||||
, m_color_picker_popup(ColorPickerPopup(this))
|
||||
{
|
||||
create();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
|
@ -39,10 +42,18 @@ void AMSMaterialsSetting::create()
|
|||
m_button_confirm->SetCornerRadius(FromDIP(12));
|
||||
m_button_confirm->Bind(wxEVT_BUTTON, &AMSMaterialsSetting::on_select_ok, this);
|
||||
|
||||
m_button_close = new Button(this, _L("Close"));
|
||||
m_btn_bg_gray = StateColor(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(*wxWHITE, StateColor::Focused),
|
||||
m_button_reset = new Button(this, _L("Reset"));
|
||||
m_btn_bg_gray = StateColor(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(*wxWHITE, StateColor::Focused),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
m_button_reset->SetBackgroundColor(m_btn_bg_gray);
|
||||
m_button_reset->SetBorderColor(AMS_MATERIALS_SETTING_GREY900);
|
||||
m_button_reset->SetTextColor(AMS_MATERIALS_SETTING_GREY900);
|
||||
m_button_reset->SetMinSize(AMS_MATERIALS_SETTING_BUTTON_SIZE);
|
||||
m_button_reset->SetCornerRadius(FromDIP(12));
|
||||
m_button_reset->Bind(wxEVT_BUTTON, &AMSMaterialsSetting::on_select_reset, this);
|
||||
|
||||
m_button_close = new Button(this, _L("Close"));
|
||||
m_button_close->SetBackgroundColor(m_btn_bg_gray);
|
||||
m_button_close->SetBorderColor(AMS_MATERIALS_SETTING_GREY900);
|
||||
m_button_close->SetTextColor(AMS_MATERIALS_SETTING_GREY900);
|
||||
|
|
@ -51,6 +62,7 @@ void AMSMaterialsSetting::create()
|
|||
m_button_close->Bind(wxEVT_BUTTON, &AMSMaterialsSetting::on_select_close, this);
|
||||
|
||||
m_sizer_button->Add(m_button_confirm, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(20));
|
||||
m_sizer_button->Add(m_button_reset, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(20));
|
||||
m_sizer_button->Add(m_button_close, 0, wxALIGN_CENTER, 0);
|
||||
|
||||
m_sizer_main->Add(m_panel_normal, 0, wxALL, FromDIP(2));
|
||||
|
|
@ -96,6 +108,7 @@ void AMSMaterialsSetting::create()
|
|||
});
|
||||
|
||||
Bind(wxEVT_PAINT, &AMSMaterialsSetting::paintEvent, this);
|
||||
Bind(EVT_SELECTED_COLOR, &AMSMaterialsSetting::on_picker_color, this);
|
||||
m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
|
||||
}
|
||||
|
||||
|
|
@ -141,17 +154,12 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent)
|
|||
|
||||
m_sizer_colour->Add(0, 0, 0, wxEXPAND, 0);
|
||||
|
||||
m_clrData = new wxColourData();
|
||||
m_clrData->SetChooseFull(true);
|
||||
m_clrData->SetChooseAlpha(false);
|
||||
m_clr_picker = new ColorPicker(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_clr_picker->set_show_full(true);
|
||||
m_clr_picker->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_clr_picker = new Button(parent, wxEmptyString, wxEmptyString, wxBU_AUTODRAW);
|
||||
m_clr_picker->SetCanFocus(false);
|
||||
m_clr_picker->SetSize(FromDIP(50), FromDIP(25));
|
||||
m_clr_picker->SetMinSize(wxSize(FromDIP(50), FromDIP(25)));
|
||||
m_clr_picker->SetCornerRadius(FromDIP(6));
|
||||
m_clr_picker->SetBorderColor(wxColour(172, 172, 172));
|
||||
m_clr_picker->Bind(wxEVT_BUTTON, &AMSMaterialsSetting::on_clr_picker, this);
|
||||
|
||||
m_clr_picker->Bind(wxEVT_LEFT_DOWN, &AMSMaterialsSetting::on_clr_picker, this);
|
||||
m_sizer_colour->Add(m_clr_picker, 0, 0, 0);
|
||||
|
||||
wxBoxSizer* m_sizer_temperature = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
|
@ -260,8 +268,9 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
|
|||
{
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
// title
|
||||
auto ratio_text = new wxStaticText(parent, wxID_ANY, _L("Factors of dynamic flow cali"));
|
||||
ratio_text->SetFont(Label::Head_14);
|
||||
m_ratio_text = new wxStaticText(parent, wxID_ANY, _L("Factors of dynamic flow cali"));
|
||||
m_ratio_text->SetForegroundColour(wxColour(50, 58, 61));
|
||||
m_ratio_text->SetFont(Label::Head_14);
|
||||
|
||||
auto kn_val_sizer = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
kn_val_sizer->SetFlexibleDirection(wxBOTH);
|
||||
|
|
@ -295,7 +304,7 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
|
|||
m_input_n_val->Hide();
|
||||
|
||||
sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
sizer->Add(ratio_text, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
sizer->Add(m_ratio_text, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
sizer->Add(kn_val_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
|
|
@ -379,6 +388,62 @@ void AMSMaterialsSetting::enable_confirm_button(bool en)
|
|||
}
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Are you sure you want to clear the filament information?"), wxEmptyString, wxICON_WARNING | wxOK | wxCANCEL);
|
||||
auto result = msg_dlg.ShowModal();
|
||||
if (result != wxID_OK)
|
||||
return;
|
||||
|
||||
m_input_nozzle_min->GetTextCtrl()->SetValue("");
|
||||
m_input_nozzle_max->GetTextCtrl()->SetValue("");
|
||||
ams_filament_id = "";
|
||||
ams_setting_id = "";
|
||||
wxString k_text = "0.000";
|
||||
wxString n_text = "0.000";
|
||||
m_filament_type = "";
|
||||
long nozzle_temp_min_int = 0;
|
||||
long nozzle_temp_max_int = 0;
|
||||
wxColour color = *wxWHITE;
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02XFF", (int)color.Red(), (int)color.Green(), (int)color.Blue());
|
||||
|
||||
if (obj) {
|
||||
// set filament
|
||||
if (obj->is_support_filament_edit_virtual_tray || !is_virtual_tray()) {
|
||||
if (is_virtual_tray()) {
|
||||
obj->command_ams_filament_settings(255, VIRTUAL_TRAY_ID, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
}
|
||||
else {
|
||||
obj->command_ams_filament_settings(ams_id, tray_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
}
|
||||
}
|
||||
|
||||
// set k / n value
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
// set extrusion cali ratio
|
||||
int cali_tray_id = ams_id * 4 + tray_id;
|
||||
|
||||
double k = 0.0;
|
||||
try {
|
||||
k_text.ToDouble(&k);
|
||||
}
|
||||
catch (...) {
|
||||
;
|
||||
}
|
||||
|
||||
double n = 0.0;
|
||||
try {
|
||||
n_text.ToDouble(&n);
|
||||
}
|
||||
catch (...) {
|
||||
;
|
||||
}
|
||||
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
{
|
||||
wxString k_text = m_input_k_val->GetTextCtrl()->GetValue();
|
||||
|
|
@ -408,10 +473,11 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
|||
}
|
||||
obj->command_extrusion_cali_set(VIRTUAL_TRAY_ID, "", "", k, n);
|
||||
Close();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!m_is_third) {
|
||||
// check and set k n
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
if (!ExtrusionCalibration::check_k_validation(k_text)) {
|
||||
wxString k_tips = _L("Please input a valid value (K in 0~0.5)");
|
||||
wxString kn_tips = _L("Please input a valid value (K in 0~0.5, N in 0.6~2.0)");
|
||||
|
|
@ -423,7 +489,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
|||
|
||||
|
||||
// set k / n value
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
// set extrusion cali ratio
|
||||
int cali_tray_id = ams_id * 4 + tray_id;
|
||||
|
||||
|
|
@ -448,34 +514,68 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
|||
return;
|
||||
}
|
||||
wxString nozzle_temp_min = m_input_nozzle_min->GetTextCtrl()->GetValue();
|
||||
auto filament = m_comboBox_filament->GetValue();
|
||||
auto filament = m_comboBox_filament->GetValue();
|
||||
|
||||
wxString nozzle_temp_max = m_input_nozzle_max->GetTextCtrl()->GetValue();
|
||||
|
||||
long nozzle_temp_min_int, nozzle_temp_max_int;
|
||||
nozzle_temp_min.ToLong(&nozzle_temp_min_int);
|
||||
nozzle_temp_max.ToLong(&nozzle_temp_max_int);
|
||||
wxColour color = m_clrData->GetColour();
|
||||
wxColour color = m_clr_picker->m_colour;
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02XFF", (int) color.Red(), (int) color.Green(), (int) color.Blue());
|
||||
sprintf(col_buf, "%02X%02X%02XFF", (int)color.Red(), (int)color.Green(), (int)color.Blue());
|
||||
ams_filament_id = "";
|
||||
ams_setting_id = "";
|
||||
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
|
||||
|
||||
if (it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
|
||||
|
||||
|
||||
//check is it in the filament blacklist
|
||||
if(!is_virtual_tray()){
|
||||
bool in_blacklist = false;
|
||||
std::string action;
|
||||
std::string info;
|
||||
std::string filamnt_type;
|
||||
it->get_filament_type(filamnt_type);
|
||||
|
||||
if (it->vendor) {
|
||||
DeviceManager::check_filaments_in_blacklist(it->vendor->name, filamnt_type, in_blacklist, action, info);
|
||||
}
|
||||
|
||||
if (in_blacklist) {
|
||||
if (action == "prohibition") {
|
||||
MessageDialog msg_wingow(nullptr, info, _L("Error"), wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
//m_comboBox_filament->SetSelection(m_filament_selection);
|
||||
return;
|
||||
}
|
||||
else if (action == "warning") {
|
||||
MessageDialog msg_wingow(nullptr, info, _L("Warning"), wxICON_INFORMATION | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ams_filament_id = it->filament_id;
|
||||
ams_setting_id = it->setting_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ams_filament_id.empty() || nozzle_temp_min.empty() || nozzle_temp_max.empty() || m_filament_type.empty()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Invalid Setting id";
|
||||
} else {
|
||||
MessageDialog msg_dlg(nullptr, _L("You need to select the material type and color first."), wxEmptyString, wxICON_WARNING | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (obj) {
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
if (!ExtrusionCalibration::check_k_validation(k_text)) {
|
||||
wxString k_tips = _L("Please input a valid value (K in 0~0.5)");
|
||||
wxString kn_tips = _L("Please input a valid value (K in 0~0.5, N in 0.6~2.0)");
|
||||
|
|
@ -489,13 +589,14 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
|||
if (obj->is_support_filament_edit_virtual_tray || !is_virtual_tray()) {
|
||||
if (is_virtual_tray()) {
|
||||
obj->command_ams_filament_settings(255, VIRTUAL_TRAY_ID, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
obj->command_ams_filament_settings(ams_id, tray_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
}
|
||||
}
|
||||
|
||||
// set k / n value
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
if (obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
// set extrusion cali ratio
|
||||
int cali_tray_id = ams_id * 4 + tray_id;
|
||||
|
||||
|
|
@ -529,15 +630,47 @@ void AMSMaterialsSetting::on_select_close(wxCommandEvent &event)
|
|||
|
||||
void AMSMaterialsSetting::set_color(wxColour color)
|
||||
{
|
||||
m_clrData->SetColour(color);
|
||||
//m_clrData->SetColour(color);
|
||||
m_clr_picker->set_color(color);
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_clr_picker(wxCommandEvent & event)
|
||||
void AMSMaterialsSetting::set_colors(std::vector<wxColour> colors)
|
||||
{
|
||||
//m_clrData->SetColour(color);
|
||||
m_clr_picker->set_colors(colors);
|
||||
}
|
||||
|
||||
|
||||
void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
|
||||
{
|
||||
unsigned int color_num = event.GetInt();
|
||||
set_color(wxColour(color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF));
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
{
|
||||
if(!m_is_third || obj->is_in_printing() || obj->can_resume())
|
||||
return;
|
||||
auto clr_dialog = new wxColourDialog(this, m_clrData);
|
||||
show_flag = true;
|
||||
|
||||
|
||||
std::vector<wxColour> ams_colors;
|
||||
for (auto ams_it = obj->amsList.begin(); ams_it != obj->amsList.end(); ++ams_it) {
|
||||
for (auto tray_id = ams_it->second->trayList.begin(); tray_id != ams_it->second->trayList.end(); ++tray_id) {
|
||||
std::vector<wxColour>::iterator iter = find(ams_colors.begin(), ams_colors.end(), AmsTray::decode_color(tray_id->second->color));
|
||||
if (iter == ams_colors.end()) {
|
||||
ams_colors.push_back(AmsTray::decode_color(tray_id->second->color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxPoint img_pos = m_clr_picker->ClientToScreen(wxPoint(0, 0));
|
||||
wxPoint popup_pos(img_pos.x + FromDIP(50), img_pos.y);
|
||||
m_color_picker_popup.Position(popup_pos, wxSize(0, 0));
|
||||
m_color_picker_popup.set_ams_colours(ams_colors);
|
||||
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour);
|
||||
m_color_picker_popup.Popup();
|
||||
|
||||
/*auto clr_dialog = new wxColourDialog(this, m_clrData);
|
||||
if (clr_dialog->ShowModal() == wxID_OK) {
|
||||
m_clrData = &(clr_dialog->GetColourData());
|
||||
m_clr_picker->SetBackgroundColor(wxColour(
|
||||
|
|
@ -546,7 +679,7 @@ void AMSMaterialsSetting::on_clr_picker(wxCommandEvent & event)
|
|||
m_clrData->GetColour().Blue(),
|
||||
254
|
||||
));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
bool AMSMaterialsSetting::is_virtual_tray()
|
||||
|
|
@ -565,7 +698,7 @@ void AMSMaterialsSetting::update_widgets()
|
|||
else
|
||||
m_panel_normal->Hide();
|
||||
m_panel_kn->Show();
|
||||
} else if (obj && obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
} else if (obj && obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
m_panel_normal->Show();
|
||||
m_panel_kn->Show();
|
||||
} else {
|
||||
|
|
@ -581,7 +714,21 @@ bool AMSMaterialsSetting::Show(bool show)
|
|||
m_button_confirm->SetMinSize(AMS_MATERIALS_SETTING_BUTTON_SIZE);
|
||||
m_input_nozzle_max->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20)));
|
||||
m_input_nozzle_min->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20)));
|
||||
m_clr_picker->SetBackgroundColour(m_clr_picker->GetParent()->GetBackgroundColour());
|
||||
//m_clr_picker->set_color(m_clr_picker->GetParent()->GetBackgroundColour());
|
||||
|
||||
if (obj && obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
m_ratio_text->Show();
|
||||
m_k_param->Show();
|
||||
m_input_k_val->Show();
|
||||
}
|
||||
else {
|
||||
m_ratio_text->Hide();
|
||||
m_k_param->Hide();
|
||||
m_input_k_val->Hide();
|
||||
}
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
}
|
||||
return DPIDialog::Show(show);
|
||||
}
|
||||
|
|
@ -599,6 +746,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||
m_input_n_val->GetTextCtrl()->SetValue(n);
|
||||
|
||||
if (is_virtual_tray() && obj && !obj->is_support_filament_edit_virtual_tray) {
|
||||
m_button_reset->Show();
|
||||
m_button_confirm->Show();
|
||||
update();
|
||||
Layout();
|
||||
|
|
@ -606,15 +754,16 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||
ShowModal();
|
||||
return;
|
||||
} else {
|
||||
m_clr_picker->SetBackgroundColor(wxColour(
|
||||
/* m_clr_picker->set_color(wxColour(
|
||||
m_clrData->GetColour().Red(),
|
||||
m_clrData->GetColour().Green(),
|
||||
m_clrData->GetColour().Blue(),
|
||||
254
|
||||
));
|
||||
));*/
|
||||
|
||||
if (!m_is_third) {
|
||||
if (obj && obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
|
||||
m_button_reset->Hide();
|
||||
if (obj && obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_TYAY)) {
|
||||
m_button_confirm->Show();
|
||||
} else {
|
||||
m_button_confirm->Hide();
|
||||
|
|
@ -636,6 +785,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||
return;
|
||||
}
|
||||
|
||||
m_button_reset->Show();
|
||||
m_button_confirm->Show();
|
||||
m_panel_SN->Hide();
|
||||
m_comboBox_filament->Show();
|
||||
|
|
@ -721,37 +871,18 @@ void AMSMaterialsSetting::post_select_event() {
|
|||
wxPostEvent(m_comboBox_filament, event);
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::msw_rescale()
|
||||
{
|
||||
m_clr_picker->msw_rescale();
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
||||
{
|
||||
m_filament_type = "";
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
|
||||
if (it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
|
||||
|
||||
//check is it in the filament blacklist
|
||||
bool in_blacklist = false;
|
||||
std::string action;
|
||||
std::string info;
|
||||
std::string filamnt_type;
|
||||
it->get_filament_type(filamnt_type);
|
||||
|
||||
if (it->vendor) {
|
||||
DeviceManager::check_filaments_in_blacklist(it->vendor->name, filamnt_type, in_blacklist, action, info);
|
||||
}
|
||||
|
||||
if (in_blacklist) {
|
||||
if (action == "prohibition") {
|
||||
MessageDialog msg_wingow(nullptr, info, _L("Error"), wxICON_WARNING | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
m_comboBox_filament->SetSelection(m_filament_selection);
|
||||
return;
|
||||
}
|
||||
else if (action == "warning") {
|
||||
MessageDialog msg_wingow(nullptr, info, _L("Warning"), wxICON_INFORMATION | wxOK);
|
||||
msg_wingow.ShowModal();
|
||||
}
|
||||
}
|
||||
if (!m_comboBox_filament->GetValue().IsEmpty() && it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
|
||||
|
||||
// ) if nozzle_temperature_range is found
|
||||
ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
|
||||
|
|
@ -789,10 +920,10 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
|||
}
|
||||
}
|
||||
if (m_input_nozzle_min->GetTextCtrl()->GetValue().IsEmpty()) {
|
||||
m_input_nozzle_min->GetTextCtrl()->SetValue("220");
|
||||
m_input_nozzle_min->GetTextCtrl()->SetValue("0");
|
||||
}
|
||||
if (m_input_nozzle_max->GetTextCtrl()->GetValue().IsEmpty()) {
|
||||
m_input_nozzle_max->GetTextCtrl()->SetValue("220");
|
||||
m_input_nozzle_max->GetTextCtrl()->SetValue("0");
|
||||
}
|
||||
|
||||
m_filament_selection = evt.GetSelection();
|
||||
|
|
@ -800,4 +931,297 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
|||
|
||||
void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect) { this->Refresh(); }
|
||||
|
||||
ColorPicker::ColorPicker(wxWindow* parent, wxWindowID id, const wxPoint& pos /*= wxDefaultPosition*/, const wxSize& size /*= wxDefaultSize*/)
|
||||
{
|
||||
wxWindow::Create(parent, id, pos, size);
|
||||
|
||||
SetSize(wxSize(FromDIP(25), FromDIP(25)));
|
||||
SetMinSize(wxSize(FromDIP(25), FromDIP(25)));
|
||||
SetMaxSize(wxSize(FromDIP(25), FromDIP(25)));
|
||||
|
||||
Bind(wxEVT_PAINT, &ColorPicker::paintEvent, this);
|
||||
m_bitmap_border = create_scaled_bitmap("color_picker_border", nullptr, 25);
|
||||
}
|
||||
|
||||
ColorPicker::~ColorPicker(){}
|
||||
|
||||
void ColorPicker::msw_rescale()
|
||||
{
|
||||
m_bitmap_border = create_scaled_bitmap("color_picker_border", nullptr, 25);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ColorPicker::set_color(wxColour col)
|
||||
{
|
||||
m_colour = col;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ColorPicker::set_colors(std::vector<wxColour> cols)
|
||||
{
|
||||
m_cols = cols;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ColorPicker::paintEvent(wxPaintEvent& evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
render(dc);
|
||||
}
|
||||
|
||||
void ColorPicker::render(wxDC& dc)
|
||||
{
|
||||
#ifdef __WXMSW__
|
||||
wxSize size = GetSize();
|
||||
wxMemoryDC memdc;
|
||||
wxBitmap bmp(size.x, size.y);
|
||||
memdc.SelectObject(bmp);
|
||||
memdc.Blit({ 0, 0 }, size, &dc, { 0, 0 });
|
||||
|
||||
{
|
||||
wxGCDC dc2(memdc);
|
||||
doRender(dc2);
|
||||
}
|
||||
|
||||
memdc.SelectObject(wxNullBitmap);
|
||||
dc.DrawBitmap(bmp, 0, 0);
|
||||
#else
|
||||
doRender(dc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorPicker::doRender(wxDC& dc)
|
||||
{
|
||||
wxSize size = GetSize();
|
||||
|
||||
auto radius = m_show_full?size.x / 2:size.x / 2 - FromDIP(1);
|
||||
if (m_selected) radius -= FromDIP(1);
|
||||
|
||||
dc.SetPen(wxPen(m_colour));
|
||||
dc.SetBrush(wxBrush(m_colour));
|
||||
dc.DrawCircle(size.x / 2, size.x / 2, radius);
|
||||
|
||||
if (m_selected) {
|
||||
dc.SetPen(wxPen(m_colour));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawCircle(size.x / 2, size.x / 2, size.x / 2);
|
||||
}
|
||||
|
||||
if (m_show_full) {
|
||||
dc.SetPen(wxPen(wxColour(0x6B6B6B)));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawCircle(size.x / 2, size.x / 2, radius);
|
||||
|
||||
if (m_cols.size() > 1) {
|
||||
int left = FromDIP(0);
|
||||
float total_width = size.x;
|
||||
int gwidth = std::round(total_width / (m_cols.size() - 1));
|
||||
|
||||
for (int i = 0; i < m_cols.size() - 1; i++) {
|
||||
|
||||
if ((left + gwidth) > (size.x)) {
|
||||
gwidth = size.x - left;
|
||||
}
|
||||
|
||||
auto rect = wxRect(left, 0, gwidth, size.y);
|
||||
dc.GradientFillLinear(rect, m_cols[i], m_cols[i + 1], wxEAST);
|
||||
left += gwidth;
|
||||
}
|
||||
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
:PopupWindow(parent, wxBORDER_NONE)
|
||||
{
|
||||
m_def_colors.clear();
|
||||
m_def_colors.push_back(wxColour(0xFFFFFF));
|
||||
m_def_colors.push_back(wxColour(0xfff144));
|
||||
m_def_colors.push_back(wxColour(0xDCF478));
|
||||
m_def_colors.push_back(wxColour(0x0ACC38));
|
||||
m_def_colors.push_back(wxColour(0x057748));
|
||||
m_def_colors.push_back(wxColour(0x0d6284));
|
||||
m_def_colors.push_back(wxColour(0x0EE2A0));
|
||||
m_def_colors.push_back(wxColour(0x76D9F4));
|
||||
m_def_colors.push_back(wxColour(0x46a8f9));
|
||||
m_def_colors.push_back(wxColour(0x2850E0));
|
||||
m_def_colors.push_back(wxColour(0x443089));
|
||||
m_def_colors.push_back(wxColour(0xA03CF7));
|
||||
m_def_colors.push_back(wxColour(0xF330F9));
|
||||
m_def_colors.push_back(wxColour(0xD4B1DD));
|
||||
m_def_colors.push_back(wxColour(0xf95d73));
|
||||
m_def_colors.push_back(wxColour(0xf72323));
|
||||
m_def_colors.push_back(wxColour(0x7c4b00));
|
||||
m_def_colors.push_back(wxColour(0xf98c36));
|
||||
m_def_colors.push_back(wxColour(0xfcecd6));
|
||||
m_def_colors.push_back(wxColour(0xD3C5A3));
|
||||
m_def_colors.push_back(wxColour(0xAF7933));
|
||||
m_def_colors.push_back(wxColour(0x898989));
|
||||
m_def_colors.push_back(wxColour(0xBCBCBC));
|
||||
m_def_colors.push_back(wxColour(0x161616));
|
||||
|
||||
|
||||
SetBackgroundColour(wxColour(*wxWHITE));
|
||||
|
||||
wxBoxSizer* m_sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer* m_sizer_box = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_def_color_box = new StaticBox(this);
|
||||
wxBoxSizer* m_sizer_ams = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto m_title_ams = new wxStaticText(m_def_color_box, wxID_ANY, _L("AMS"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_title_ams->SetFont(::Label::Body_14);
|
||||
m_title_ams->SetBackgroundColour(wxColour(238, 238, 238));
|
||||
m_sizer_ams->Add(m_title_ams, 0, wxALL, 5);
|
||||
auto ams_line = new wxPanel(m_def_color_box, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
ams_line->SetBackgroundColour(wxColour(0xCECECE));
|
||||
ams_line->SetMinSize(wxSize(-1, 1));
|
||||
ams_line->SetMaxSize(wxSize(-1, 1));
|
||||
m_sizer_ams->Add(ams_line, 1, wxALIGN_CENTER, 0);
|
||||
|
||||
|
||||
m_def_color_box->SetCornerRadius(FromDIP(10));
|
||||
m_def_color_box->SetBackgroundColor(StateColor(std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Normal)));
|
||||
m_def_color_box->SetBorderColor(StateColor(std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Normal)));
|
||||
|
||||
//ams
|
||||
m_ams_fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
m_ams_fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
m_ams_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
//other
|
||||
wxFlexGridSizer* fg_sizer;
|
||||
fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
|
||||
for (wxColour col : m_def_colors) {
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(col);
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
m_color_pickers.push_back(cp);
|
||||
fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour);
|
||||
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 16) + ((cp->m_colour.Green() & 0xff) << 8) + (cp->m_colour.Blue() & 0xff);
|
||||
evt.SetInt(g_col);
|
||||
wxPostEvent(GetParent(), evt);
|
||||
});
|
||||
}
|
||||
|
||||
wxBoxSizer* m_sizer_other = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto m_title_other = new wxStaticText(m_def_color_box, wxID_ANY, _L("Other color"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_title_other->SetFont(::Label::Body_14);
|
||||
m_title_other->SetBackgroundColour(wxColour(238, 238, 238));
|
||||
m_sizer_other->Add(m_title_other, 0, wxALL, 5);
|
||||
auto other_line = new wxPanel(m_def_color_box, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
other_line->SetMinSize(wxSize(-1, 1));
|
||||
other_line->SetMaxSize(wxSize(-1, 1));
|
||||
other_line->SetBackgroundColour(wxColour(0xCECECE));
|
||||
m_sizer_other->Add(other_line, 1, wxALIGN_CENTER, 0);
|
||||
|
||||
m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_ams, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_ams_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_other, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
|
||||
|
||||
m_def_color_box->SetSizer(m_sizer_box);
|
||||
m_def_color_box->Layout();
|
||||
m_def_color_box->Fit();
|
||||
|
||||
m_sizer_main->Add(m_def_color_box, 0, wxALL | wxEXPAND, 10);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
Bind(wxEVT_PAINT, &ColorPickerPopup::paintEvent, this);
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
|
||||
|
||||
void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
{
|
||||
if (m_ams_color_pickers.size() > 0) {
|
||||
for (ColorPicker* col_pick:m_ams_color_pickers) {
|
||||
|
||||
std::vector<ColorPicker*>::iterator iter = find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
|
||||
if (iter != m_color_pickers.end()) {
|
||||
col_pick->Destroy();
|
||||
m_color_pickers.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
m_ams_color_pickers.clear();
|
||||
}
|
||||
|
||||
|
||||
m_ams_colors = ams;
|
||||
for (wxColour col : m_ams_colors) {
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(col);
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
m_color_pickers.push_back(cp);
|
||||
m_ams_color_pickers.push_back(cp);
|
||||
m_ams_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour);
|
||||
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 16) + ((cp->m_colour.Green() & 0xff) << 8) + (cp->m_colour.Blue() & 0xff);
|
||||
evt.SetInt(g_col);
|
||||
wxPostEvent(GetParent(), evt);
|
||||
});
|
||||
}
|
||||
m_ams_fg_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_def_colour(wxColour col)
|
||||
{
|
||||
m_def_col = col;
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (cp->m_selected) {
|
||||
cp->set_selected(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (cp->m_colour == m_def_col) {
|
||||
cp->set_selected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Dismiss();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::paintEvent(wxPaintEvent& evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
dc.SetPen(wxColour(0xAC, 0xAC, 0xAC));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 0);
|
||||
}
|
||||
|
||||
void ColorPickerPopup::OnDismiss() {}
|
||||
|
||||
void ColorPickerPopup::Popup()
|
||||
{
|
||||
PopupWindow::Popup();
|
||||
}
|
||||
|
||||
bool ColorPickerPopup::ProcessLeftDown(wxMouseEvent& event) {
|
||||
return PopupWindow::ProcessLeftDown(event);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
|
|
|||
|
|
@ -28,6 +28,54 @@
|
|||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class ColorPicker : public wxWindow
|
||||
{
|
||||
public:
|
||||
wxBitmap m_bitmap_border;
|
||||
wxColour m_colour;
|
||||
std::vector<wxColour> m_cols;
|
||||
bool m_selected{false};
|
||||
bool m_show_full{false};
|
||||
|
||||
ColorPicker(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
|
||||
~ColorPicker();
|
||||
|
||||
void msw_rescale();
|
||||
void set_color(wxColour col);
|
||||
void set_colors(std::vector<wxColour> cols);
|
||||
void set_selected(bool sel) {m_selected = sel;Refresh();};
|
||||
void set_show_full(bool full) {m_show_full = full;Refresh();};
|
||||
|
||||
void paintEvent(wxPaintEvent& evt);
|
||||
void render(wxDC& dc);
|
||||
void doRender(wxDC& dc);
|
||||
};
|
||||
|
||||
class ColorPickerPopup : public PopupWindow
|
||||
{
|
||||
public:
|
||||
StaticBox* m_def_color_box;
|
||||
wxFlexGridSizer* m_ams_fg_sizer;
|
||||
wxColour m_def_col;
|
||||
std::vector<wxColour> m_def_colors;
|
||||
std::vector<wxColour> m_ams_colors;
|
||||
std::vector<ColorPicker*> m_color_pickers;
|
||||
std::vector<ColorPicker*> m_ams_color_pickers;
|
||||
|
||||
public:
|
||||
ColorPickerPopup(wxWindow* parent);
|
||||
~ColorPickerPopup() {};
|
||||
void set_ams_colours(std::vector<wxColour> ams);
|
||||
void set_def_colour(wxColour col);
|
||||
void paintEvent(wxPaintEvent& evt);
|
||||
void Popup();
|
||||
virtual void OnDismiss() wxOVERRIDE;
|
||||
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
|
||||
|
||||
public:
|
||||
};
|
||||
|
||||
|
||||
class AMSMaterialsSetting : public DPIDialog
|
||||
{
|
||||
public:
|
||||
|
|
@ -46,10 +94,12 @@ public:
|
|||
wxString k = wxEmptyString, wxString n = wxEmptyString);
|
||||
|
||||
void post_select_event();
|
||||
|
||||
void msw_rescale();
|
||||
void set_color(wxColour color);
|
||||
void set_colors(std::vector<wxColour> colors);
|
||||
|
||||
MachineObject *obj{nullptr};
|
||||
void on_picker_color(wxCommandEvent& color);
|
||||
MachineObject* obj{ nullptr };
|
||||
int ams_id { 0 }; /* 0 ~ 3 */
|
||||
int tray_id { 0 }; /* 0 ~ 3 */
|
||||
|
||||
|
|
@ -62,6 +112,7 @@ public:
|
|||
wxString m_brand_tmp;
|
||||
wxColour m_brand_colour;
|
||||
std::string m_filament_type;
|
||||
ColorPickerPopup m_color_picker_popup;
|
||||
|
||||
protected:
|
||||
void create_panel_normal(wxWindow* parent);
|
||||
|
|
@ -69,8 +120,9 @@ protected:
|
|||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
void on_select_filament(wxCommandEvent& evt);
|
||||
void on_select_ok(wxCommandEvent &event);
|
||||
void on_select_reset(wxCommandEvent &event);
|
||||
void on_select_close(wxCommandEvent &event);
|
||||
void on_clr_picker(wxCommandEvent &event);
|
||||
void on_clr_picker(wxMouseEvent &event);
|
||||
bool is_virtual_tray();
|
||||
void update_widgets();
|
||||
|
||||
|
|
@ -87,13 +139,15 @@ protected:
|
|||
wxStaticText * m_title_temperature;
|
||||
TextInput * m_input_nozzle_min;
|
||||
TextInput* m_input_nozzle_max;
|
||||
Button * m_button_reset;
|
||||
Button * m_button_confirm;
|
||||
wxStaticText* m_tip_readonly;
|
||||
Button * m_button_close;
|
||||
Button * m_clr_picker;
|
||||
ColorPicker * m_clr_picker;
|
||||
wxColourData * m_clrData;
|
||||
|
||||
wxPanel * m_panel_kn;
|
||||
wxStaticText* m_ratio_text;
|
||||
wxStaticText* m_k_param;
|
||||
TextInput* m_input_k_val;
|
||||
wxStaticText* m_n_param;
|
||||
|
|
@ -108,6 +162,8 @@ protected:
|
|||
TextInput* m_readonly_filament;
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_SELECTED_COLOR, wxCommandEvent);
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ void AMSSetting::create()
|
|||
m_checkbox_switch_filament->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_switch_filament, this);
|
||||
m_sizer_switch_filament->Add(m_checkbox_switch_filament, 0, wxTOP, 1);
|
||||
m_sizer_switch_filament->Add(0, 0, 0, wxLEFT, 12);
|
||||
m_title_switch_filament = new wxStaticText(m_panel_body, wxID_ANY, _L("AMS auto switch filament"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_title_switch_filament = new wxStaticText(m_panel_body, wxID_ANY, _L("AMS filament backup"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_title_switch_filament->SetFont(::Label::Head_13);
|
||||
m_title_switch_filament->SetForegroundColour(AMS_SETTING_GREY800);
|
||||
m_title_switch_filament->Wrap(AMS_SETTING_BODY_WIDTH);
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ AboutDialog::AboutDialog()
|
|||
|
||||
wxBoxSizer *text_sizer_horiz = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *text_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
text_sizer_horiz->Add( 0, 0, 0, wxLEFT, FromDIP(23));
|
||||
text_sizer_horiz->Add( 0, 0, 0, wxLEFT, FromDIP(20));
|
||||
|
||||
std::vector<wxString> text_list;
|
||||
text_list.push_back(_L("OrcaSlicer is based on BambuStudio, PrusaSlicer, and SuperSlicer."));
|
||||
|
|
@ -318,8 +318,7 @@ AboutDialog::AboutDialog()
|
|||
wxBoxSizer *copyright_ver_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer *copyright_hor_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
copyright_hor_sizer->Add(copyright_ver_sizer, 0, wxALL,5);
|
||||
copyright_hor_sizer->Add( 0, 0, 0, wxLEFT, FromDIP(120));
|
||||
copyright_hor_sizer->Add(copyright_ver_sizer, 0, wxLEFT, FromDIP(20));
|
||||
|
||||
wxStaticText *html_text = new wxStaticText(this, wxID_ANY, "Copyright(C) 2022-2023 Li Jiang All Rights Reserved", wxDefaultPosition, wxDefaultSize);
|
||||
html_text->SetForegroundColour(wxColour(107, 107, 107));
|
||||
|
|
@ -364,10 +363,10 @@ AboutDialog::AboutDialog()
|
|||
copyright_button_ver->Add( 0, 0, 0, wxTOP, FromDIP(10));
|
||||
copyright_button_ver->Add(button_portions, 0, wxALL,0);
|
||||
|
||||
copyright_hor_sizer->Add(copyright_button_ver, 0, wxALL,0);
|
||||
copyright_hor_sizer->Add( 0, 0, 0, wxRIGHT, FromDIP(13));
|
||||
copyright_hor_sizer->AddStretchSpacer();
|
||||
copyright_hor_sizer->Add(copyright_button_ver, 0, wxRIGHT, FromDIP(20));
|
||||
|
||||
ver_sizer->Add(copyright_hor_sizer, 0, wxALIGN_CENTER_HORIZONTAL|wxALL,0);
|
||||
ver_sizer->Add(copyright_hor_sizer, 0, wxEXPAND ,0);
|
||||
ver_sizer->Add( 0, 0, 0, wxTOP, FromDIP(30));
|
||||
button_portions->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);
|
||||
|
||||
|
|
|
|||
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