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:
SoftFever 2023-04-19 08:48:07 +08:00
commit 9f598046d1
658 changed files with 70312 additions and 4877 deletions

File diff suppressed because it is too large Load diff

View file

@ -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"); }

View file

@ -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);
}

View file

@ -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)

View file

@ -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);

View file

@ -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

View file

@ -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: {

View file

@ -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)
{

View file

@ -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");

View file

@ -85,7 +85,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
template<class PConf>
void fill_config(PConf& pcfg, const ArrangeParams &params) {
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 &params) {
// 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

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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);

View file

@ -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)
{

View file

@ -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.

View file

@ -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);

View file

@ -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);

View file

@ -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)

View file

@ -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;

View file

@ -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);

View file

@ -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)

View file

@ -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).");

View file

@ -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 &params,
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

View file

@ -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

View file

@ -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)) {

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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.

View file

@ -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;

View file

@ -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
}

View 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(&regionPtr->perimeters, paths);
if (regionPtr->perimeters.empty() == false) { getExtrusionPathsFromEntity(&regionPtr->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

View 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

View file

@ -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

View file

@ -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;

View 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

View 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_

View file

@ -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();

View file

@ -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

View file

@ -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()) {

View file

@ -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)

View file

@ -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; }

View file

@ -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)

View file

@ -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

View file

@ -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();
}

View file

@ -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.

View file

@ -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

View file

@ -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_

View file

@ -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 };

View file

@ -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]);
}

View file

@ -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)

View file

@ -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 &center) { 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_

View file

@ -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

View file

@ -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;

View file

@ -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]);

View file

@ -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();

View file

@ -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);

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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

View file

@ -2,7 +2,6 @@
#include "Polyline.hpp"
#include "Exception.hpp"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
#include "Line.hpp"
#include "Polygon.hpp"
#include <iostream>

View file

@ -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;

View file

@ -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"
};

View file

@ -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)

View file

@ -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;
}

View file

@ -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;

View file

@ -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))

View file

@ -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 */,

View file

@ -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;

View file

@ -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;
//}
}
/* BBSMusangKing - 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).");

View 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))

View file

@ -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;
}

View file

@ -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()),

View file

@ -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

View file

@ -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());

View file

@ -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

View file

@ -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())};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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
});

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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.

View file

@ -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; }}

View file

@ -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
}

View file

@ -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

View file

@ -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));

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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