mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 05:27:50 +00:00
Merge branch 'main' into libvgcode
This commit is contained in:
@@ -6,8 +6,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Include dev-utils for encoding check and other utilities
|
||||
add_subdirectory(dev-utils)
|
||||
# Clipper2 math utils
|
||||
add_subdirectory(clipper2)
|
||||
# add_subdirectory(avrdude)
|
||||
# Note: semver and hints are now included from deps_src/CMakeLists.txt
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS OFF)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CLIPPER2_INC
|
||||
Clipper2Lib/include/clipper2/clipper.h
|
||||
Clipper2Lib/include/clipper2/clipper.core.h
|
||||
Clipper2Lib/include/clipper2/clipper.engine.h
|
||||
Clipper2Lib/include/clipper2/clipper.export.h
|
||||
Clipper2Lib/include/clipper2/clipper.minkowski.h
|
||||
Clipper2Lib/include/clipper2/clipper.offset.h
|
||||
Clipper2Lib/include/clipper2/clipper.rectclip.h
|
||||
Clipper2Lib/include/clipper2/clipper2_z.hpp
|
||||
)
|
||||
|
||||
set(CLIPPER2_SRC
|
||||
Clipper2Lib/src/clipper.engine.cpp
|
||||
Clipper2Lib/src/clipper.offset.cpp
|
||||
Clipper2Lib/src/clipper.rectclip.cpp
|
||||
Clipper2Lib/src/clipper2_z.cpp
|
||||
)
|
||||
|
||||
# 2d version of Clipper2
|
||||
add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC})
|
||||
|
||||
target_include_directories(Clipper2
|
||||
PUBLIC Clipper2Lib/include
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_compile_options(Clipper2 PRIVATE /W4 /WX)
|
||||
else()
|
||||
target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
||||
target_link_libraries(Clipper2 PUBLIC -lm)
|
||||
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14.1)
|
||||
target_compile_options(Clipper2 PRIVATE -Wno-error=template-id-cdtor)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set_target_properties(Clipper2 PROPERTIES FOLDER Libraries
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
PUBLIC_HEADER "${CLIPPER2_INC}"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,646 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 17 September 2024 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : This is the main polygon clipping module *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_ENGINE_H
|
||||
#define CLIPPER_ENGINE_H
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
struct Scanline;
|
||||
struct IntersectNode;
|
||||
struct Active;
|
||||
struct Vertex;
|
||||
struct LocalMinima;
|
||||
struct OutRec;
|
||||
struct HorzSegment;
|
||||
|
||||
//Note: all clipping operations except for Difference are commutative.
|
||||
enum class ClipType { NoClip, Intersection, Union, Difference, Xor };
|
||||
|
||||
enum class PathType { Subject, Clip };
|
||||
enum class JoinWith { NoJoin, Left, Right };
|
||||
|
||||
enum class VertexFlags : uint32_t {
|
||||
Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
|
||||
};
|
||||
|
||||
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
|
||||
{
|
||||
return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
|
||||
}
|
||||
|
||||
constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b)
|
||||
{
|
||||
return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
Point64 pt;
|
||||
Vertex* next = nullptr;
|
||||
Vertex* prev = nullptr;
|
||||
VertexFlags flags = VertexFlags::Empty;
|
||||
};
|
||||
|
||||
struct OutPt {
|
||||
Point64 pt;
|
||||
OutPt* next = nullptr;
|
||||
OutPt* prev = nullptr;
|
||||
OutRec* outrec;
|
||||
HorzSegment* horz = nullptr;
|
||||
|
||||
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
|
||||
next = this;
|
||||
prev = this;
|
||||
}
|
||||
};
|
||||
|
||||
class PolyPath;
|
||||
class PolyPath64;
|
||||
class PolyPathD;
|
||||
using PolyTree64 = PolyPath64;
|
||||
using PolyTreeD = PolyPathD;
|
||||
|
||||
struct OutRec;
|
||||
typedef std::vector<OutRec*> OutRecList;
|
||||
|
||||
//OutRec: contains a path in the clipping solution. Edges in the AEL will
|
||||
//have OutRec pointers assigned when they form part of the clipping solution.
|
||||
struct OutRec {
|
||||
size_t idx = 0;
|
||||
OutRec* owner = nullptr;
|
||||
Active* front_edge = nullptr;
|
||||
Active* back_edge = nullptr;
|
||||
OutPt* pts = nullptr;
|
||||
PolyPath* polypath = nullptr;
|
||||
OutRecList* splits = nullptr;
|
||||
OutRec* recursive_split = nullptr;
|
||||
Rect64 bounds = {};
|
||||
Path64 path;
|
||||
bool is_open = false;
|
||||
|
||||
~OutRec() {
|
||||
if (splits) delete splits;
|
||||
// nb: don't delete the split pointers
|
||||
// as these are owned by ClipperBase's outrec_list_
|
||||
};
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
//Important: UP and DOWN here are premised on Y-axis positive down
|
||||
//displays, which is the orientation used in Clipper's development.
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Active {
|
||||
Point64 bot;
|
||||
Point64 top;
|
||||
int64_t curr_x = 0; //current (updated at every new scanline)
|
||||
double dx = 0.0;
|
||||
int wind_dx = 1; //1 or -1 depending on winding direction
|
||||
int wind_cnt = 0;
|
||||
int wind_cnt2 = 0; //winding count of the opposite polytype
|
||||
OutRec* outrec = nullptr;
|
||||
//AEL: 'active edge list' (Vatti's AET - active edge table)
|
||||
// a linked list of all edges (from left to right) that are present
|
||||
// (or 'active') within the current scanbeam (a horizontal 'beam' that
|
||||
// sweeps from bottom to top over the paths in the clipping operation).
|
||||
Active* prev_in_ael = nullptr;
|
||||
Active* next_in_ael = nullptr;
|
||||
//SEL: 'sorted edge list' (Vatti's ST - sorted table)
|
||||
// linked list used when sorting edges into their new positions at the
|
||||
// top of scanbeams, but also (re)used to process horizontals.
|
||||
Active* prev_in_sel = nullptr;
|
||||
Active* next_in_sel = nullptr;
|
||||
Active* jump = nullptr;
|
||||
Vertex* vertex_top = nullptr;
|
||||
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
|
||||
bool is_left_bound = false;
|
||||
JoinWith join_with = JoinWith::NoJoin;
|
||||
};
|
||||
|
||||
struct LocalMinima {
|
||||
Vertex* vertex;
|
||||
PathType polytype;
|
||||
bool is_open;
|
||||
LocalMinima(Vertex* v, PathType pt, bool open) :
|
||||
vertex(v), polytype(pt), is_open(open){}
|
||||
};
|
||||
|
||||
struct IntersectNode {
|
||||
Point64 pt;
|
||||
Active* edge1;
|
||||
Active* edge2;
|
||||
IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
|
||||
IntersectNode(Active* e1, Active* e2, Point64& pt_) :
|
||||
pt(pt_), edge1(e1), edge2(e2) {}
|
||||
};
|
||||
|
||||
struct HorzSegment {
|
||||
OutPt* left_op;
|
||||
OutPt* right_op = nullptr;
|
||||
bool left_to_right = true;
|
||||
HorzSegment() : left_op(nullptr) { }
|
||||
explicit HorzSegment(OutPt* op) : left_op(op) { }
|
||||
};
|
||||
|
||||
struct HorzJoin {
|
||||
OutPt* op1 = nullptr;
|
||||
OutPt* op2 = nullptr;
|
||||
HorzJoin() {};
|
||||
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
|
||||
};
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
|
||||
|
||||
typedef std::function<void(const PointD& e1bot, const PointD& e1top,
|
||||
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
|
||||
#endif
|
||||
|
||||
typedef std::vector<HorzSegment> HorzSegmentList;
|
||||
typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
|
||||
typedef std::vector<LocalMinima_ptr> LocalMinimaList;
|
||||
typedef std::vector<IntersectNode> IntersectNodeList;
|
||||
|
||||
// ReuseableDataContainer64 ------------------------------------------------
|
||||
|
||||
class ReuseableDataContainer64 {
|
||||
private:
|
||||
friend class ClipperBase;
|
||||
LocalMinimaList minima_list_;
|
||||
std::vector<Vertex*> vertex_lists_;
|
||||
void AddLocMin(Vertex& vert, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ReuseableDataContainer64();
|
||||
void Clear();
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
};
|
||||
|
||||
// ClipperBase -------------------------------------------------------------
|
||||
|
||||
class ClipperBase {
|
||||
private:
|
||||
ClipType cliptype_ = ClipType::NoClip;
|
||||
FillRule fillrule_ = FillRule::EvenOdd;
|
||||
FillRule fillpos = FillRule::Positive;
|
||||
int64_t bot_y_ = 0;
|
||||
bool minima_list_sorted_ = false;
|
||||
bool using_polytree_ = false;
|
||||
Active* actives_ = nullptr;
|
||||
Active *sel_ = nullptr;
|
||||
LocalMinimaList minima_list_; //pointers in case of memory reallocs
|
||||
LocalMinimaList::iterator current_locmin_iter_;
|
||||
std::vector<Vertex*> vertex_lists_;
|
||||
std::priority_queue<int64_t> scanline_list_;
|
||||
IntersectNodeList intersect_nodes_;
|
||||
HorzSegmentList horz_seg_list_;
|
||||
std::vector<HorzJoin> horz_join_list_;
|
||||
void Reset();
|
||||
inline void InsertScanline(int64_t y);
|
||||
inline bool PopScanline(int64_t &y);
|
||||
inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
|
||||
void DisposeAllOutRecs();
|
||||
void DisposeVerticesAndLocalMinima();
|
||||
void DeleteEdges(Active*& e);
|
||||
inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
|
||||
bool IsContributingClosed(const Active &e) const;
|
||||
inline bool IsContributingOpen(const Active &e) const;
|
||||
void SetWindCountForClosedPathEdge(Active &edge);
|
||||
void SetWindCountForOpenPathEdge(Active &e);
|
||||
void InsertLocalMinimaIntoAEL(int64_t bot_y);
|
||||
void InsertLeftEdge(Active &e);
|
||||
inline void PushHorz(Active &e);
|
||||
inline bool PopHorz(Active *&e);
|
||||
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
|
||||
inline void UpdateEdgeIntoAEL(Active *e);
|
||||
void IntersectEdges(Active &e1, Active &e2, const Point64& pt);
|
||||
inline void DeleteFromAEL(Active &e);
|
||||
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
|
||||
void DoIntersections(const int64_t top_y);
|
||||
void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y);
|
||||
bool BuildIntersectList(const int64_t top_y);
|
||||
void ProcessIntersectList();
|
||||
void SwapPositionsInAEL(Active& edge1, Active& edge2);
|
||||
OutRec* NewOutRec();
|
||||
OutPt* AddOutPt(const Active &e, const Point64& pt);
|
||||
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
|
||||
const Point64& pt, bool is_new = false);
|
||||
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
|
||||
void DoHorizontal(Active &horz);
|
||||
bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
|
||||
int64_t &horz_left, int64_t &horz_right);
|
||||
void DoTopOfScanbeam(const int64_t top_y);
|
||||
Active *DoMaxima(Active &e);
|
||||
void JoinOutrecPaths(Active &e1, Active &e2);
|
||||
void FixSelfIntersects(OutRec* outrec);
|
||||
void DoSplitOp(OutRec* outRec, OutPt* splitOp);
|
||||
|
||||
inline void AddTrialHorzJoin(OutPt* op);
|
||||
void ConvertHorzSegsToJoins();
|
||||
void ProcessHorzJoins();
|
||||
|
||||
void Split(Active& e, const Point64& pt);
|
||||
inline void CheckJoinLeft(Active& e,
|
||||
const Point64& pt, bool check_curr_x = false);
|
||||
inline void CheckJoinRight(Active& e,
|
||||
const Point64& pt, bool check_curr_x = false);
|
||||
protected:
|
||||
bool preserve_collinear_ = true;
|
||||
bool reverse_solution_ = false;
|
||||
int error_code_ = 0;
|
||||
bool has_open_paths_ = false;
|
||||
bool succeeded_ = true;
|
||||
OutRecList outrec_list_; //pointers in case list memory reallocated
|
||||
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
|
||||
void CleanCollinear(OutRec* outrec);
|
||||
bool CheckBounds(OutRec* outrec);
|
||||
bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
|
||||
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
|
||||
#ifdef USINGZ
|
||||
ZCallback64 zCallback_ = nullptr;
|
||||
void SetZ(const Active& e1, const Active& e2, Point64& pt);
|
||||
#endif
|
||||
void CleanUp(); // unlike Clear, CleanUp preserves added paths
|
||||
void AddPath(const Path64& path, PathType polytype, bool is_open);
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ClipperBase();
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; };
|
||||
bool PreserveCollinear() const { return preserve_collinear_;};
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; };
|
||||
bool ReverseSolution() const { return reverse_solution_; };
|
||||
void Clear();
|
||||
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
|
||||
#ifdef USINGZ
|
||||
int64_t DefaultZ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
// PolyPath / PolyTree --------------------------------------------------------
|
||||
|
||||
//PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned
|
||||
//by clipping operations. While this structure is more complex than the
|
||||
//alternative Paths structure, it does preserve path 'ownership' - ie those
|
||||
//paths that contain (or own) other paths. This will be useful to some users.
|
||||
|
||||
class PolyPath {
|
||||
protected:
|
||||
PolyPath* parent_;
|
||||
public:
|
||||
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
|
||||
virtual ~PolyPath() {};
|
||||
//https://en.cppreference.com/w/cpp/language/rule_of_three
|
||||
PolyPath(const PolyPath&) = delete;
|
||||
PolyPath& operator=(const PolyPath&) = delete;
|
||||
|
||||
unsigned Level() const
|
||||
{
|
||||
unsigned result = 0;
|
||||
const PolyPath* p = parent_;
|
||||
while (p) { ++result; p = p->parent_; }
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual PolyPath* AddChild(const Path64& path) = 0;
|
||||
|
||||
virtual void Clear() = 0;
|
||||
virtual size_t Count() const { return 0; }
|
||||
|
||||
const PolyPath* Parent() const { return parent_; }
|
||||
|
||||
bool IsHole() const
|
||||
{
|
||||
unsigned lvl = Level();
|
||||
//Even levels except level 0
|
||||
return lvl && !(lvl & 1);
|
||||
}
|
||||
template<typename T>
|
||||
static double Clipper2LibArea(const Path<T> &poly)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
return Clipper2Lib_Z::Area<T>(poly);
|
||||
#else
|
||||
return Clipper2Lib::Area<T>(poly);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
|
||||
typedef typename std::vector<std::unique_ptr<PolyPathD>> PolyPathDList;
|
||||
|
||||
class PolyPath64 : public PolyPath {
|
||||
private:
|
||||
PolyPath64List childs_;
|
||||
Path64 polygon_;
|
||||
public:
|
||||
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
|
||||
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
|
||||
|
||||
~PolyPath64() {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
PolyPath64* operator [] (size_t index) const
|
||||
{
|
||||
return childs_[index].get(); //std::unique_ptr
|
||||
}
|
||||
|
||||
PolyPath64* Child(size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
|
||||
PolyPath64List::const_iterator end() const { return childs_.cend(); }
|
||||
|
||||
PolyPath64* AddChild(const Path64& path) override
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPath64>(this, path)).get();
|
||||
}
|
||||
|
||||
void Clear() override
|
||||
{
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
size_t Count() const override
|
||||
{
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const Path64& Polygon() const { return polygon_; };
|
||||
|
||||
double Area() const
|
||||
{
|
||||
return std::accumulate(childs_.cbegin(), childs_.cend(), Clipper2LibArea<int64_t>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PolyPathD : public PolyPath {
|
||||
private:
|
||||
PolyPathDList childs_;
|
||||
double scale_;
|
||||
PathD polygon_;
|
||||
public:
|
||||
explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
}
|
||||
|
||||
explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
int error_code = 0;
|
||||
polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
|
||||
}
|
||||
|
||||
explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
polygon_ = path;
|
||||
}
|
||||
|
||||
~PolyPathD() {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
PolyPathD* operator [] (size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPathD* Child(size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
|
||||
PolyPathDList::const_iterator end() const { return childs_.cend(); }
|
||||
|
||||
void SetScale(double value) { scale_ = value; }
|
||||
double Scale() const { return scale_; }
|
||||
|
||||
PolyPathD* AddChild(const Path64& path) override
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
|
||||
}
|
||||
|
||||
PolyPathD* AddChild(const PathD& path)
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
|
||||
}
|
||||
|
||||
void Clear() override
|
||||
{
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
size_t Count() const override
|
||||
{
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const PathD& Polygon() const { return polygon_; };
|
||||
|
||||
double Area() const
|
||||
{
|
||||
return std::accumulate(childs_.begin(), childs_.end(), Clipper2LibArea<double>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
};
|
||||
|
||||
class Clipper64 : public ClipperBase
|
||||
{
|
||||
private:
|
||||
void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen);
|
||||
void BuildTree64(PolyPath64& polytree, Paths64& open_paths);
|
||||
public:
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallback64 cb) { zCallback_ = cb; }
|
||||
#endif
|
||||
|
||||
void AddSubject(const Paths64& subjects)
|
||||
{
|
||||
AddPaths(subjects, PathType::Subject, false);
|
||||
}
|
||||
void AddOpenSubject(const Paths64& open_subjects)
|
||||
{
|
||||
AddPaths(open_subjects, PathType::Subject, true);
|
||||
}
|
||||
void AddClip(const Paths64& clips)
|
||||
{
|
||||
AddPaths(clips, PathType::Clip, false);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, Paths64& closed_paths)
|
||||
{
|
||||
Paths64 dummy;
|
||||
return Execute(clip_type, fill_rule, closed_paths, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule,
|
||||
Paths64& closed_paths, Paths64& open_paths)
|
||||
{
|
||||
closed_paths.clear();
|
||||
open_paths.clear();
|
||||
if (ExecuteInternal(clip_type, fill_rule, false))
|
||||
BuildPaths64(closed_paths, &open_paths);
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree)
|
||||
{
|
||||
Paths64 dummy;
|
||||
return Execute(clip_type, fill_rule, polytree, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths)
|
||||
{
|
||||
if (ExecuteInternal(clip_type, fill_rule, true))
|
||||
{
|
||||
open_paths.clear();
|
||||
polytree.Clear();
|
||||
BuildTree64(polytree, open_paths);
|
||||
}
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
};
|
||||
|
||||
class ClipperD : public ClipperBase {
|
||||
private:
|
||||
double scale_ = 1.0, invScale_ = 1.0;
|
||||
#ifdef USINGZ
|
||||
ZCallbackD zCallbackD_ = nullptr;
|
||||
#endif
|
||||
void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
|
||||
void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
|
||||
public:
|
||||
explicit ClipperD(int precision = 2) : ClipperBase()
|
||||
{
|
||||
CheckPrecisionRange(precision, error_code_);
|
||||
// to optimize scaling / descaling precision
|
||||
// set the scale to a power of double's radix (2) (#25)
|
||||
scale_ = std::pow(std::numeric_limits<double>::radix,
|
||||
std::ilogb(std::pow(10, precision)) + 1);
|
||||
invScale_ = 1 / scale_;
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
|
||||
|
||||
void ZCB(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)
|
||||
{
|
||||
// de-scale (x & y)
|
||||
// temporarily convert integers to their initial float values
|
||||
// this will slow clipping marginally but will make it much easier
|
||||
// to understand the coordinates passed to the callback function
|
||||
PointD tmp = PointD(pt) * invScale_;
|
||||
PointD e1b = PointD(e1bot) * invScale_;
|
||||
PointD e1t = PointD(e1top) * invScale_;
|
||||
PointD e2b = PointD(e2bot) * invScale_;
|
||||
PointD e2t = PointD(e2top) * invScale_;
|
||||
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
|
||||
pt.z = tmp.z; // only update 'z'
|
||||
};
|
||||
|
||||
void CheckCallback()
|
||||
{
|
||||
if(zCallbackD_)
|
||||
// if the user defined float point callback has been assigned
|
||||
// then assign the proxy callback function
|
||||
ClipperBase::zCallback_ =
|
||||
std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3,
|
||||
std::placeholders::_4, std::placeholders::_5);
|
||||
else
|
||||
ClipperBase::zCallback_ = nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void AddSubject(const PathsD& subjects)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
|
||||
}
|
||||
|
||||
void AddOpenSubject(const PathsD& open_subjects)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
|
||||
}
|
||||
|
||||
void AddClip(const PathsD& clips)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
|
||||
{
|
||||
PathsD dummy;
|
||||
return Execute(clip_type, fill_rule, closed_paths, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
CheckCallback();
|
||||
#endif
|
||||
if (ExecuteInternal(clip_type, fill_rule, false))
|
||||
{
|
||||
BuildPathsD(closed_paths, &open_paths);
|
||||
}
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree)
|
||||
{
|
||||
PathsD dummy;
|
||||
return Execute(clip_type, fill_rule, polytree, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
CheckCallback();
|
||||
#endif
|
||||
if (ExecuteInternal(clip_type, fill_rule, true))
|
||||
{
|
||||
polytree.Clear();
|
||||
polytree.SetScale(invScale_);
|
||||
open_paths.clear();
|
||||
BuildTreeD(polytree, open_paths);
|
||||
}
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // CLIPPER_ENGINE_H
|
||||
@@ -1,836 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 24 January 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : This module exports the Clipper2 Library (ie DLL/so) *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
/*
|
||||
Boolean clipping:
|
||||
cliptype: NoClip=0, Intersection=1, Union=2, Difference=3, Xor=4
|
||||
fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
|
||||
|
||||
Polygon offsetting (inflate/deflate):
|
||||
jointype: Square=0, Bevel=1, Round=2, Miter=3
|
||||
endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
|
||||
|
||||
The path structures used extensively in other parts of this library are all
|
||||
based on std::vector classes. Since C++ classes can't be accessed by other
|
||||
languages, these paths are exported here as very simple array structures
|
||||
(either of int64_t or double) that can be parsed by just about any
|
||||
programming language.
|
||||
|
||||
These 2D paths are defined by series of x and y coordinates together with an
|
||||
optional user-defined 'z' value (see Z-values below). Hence, a vertex refers
|
||||
to a single x and y coordinate (+/- a user-defined value). Data structures
|
||||
have names with suffixes that indicate the array type (either int64_t or
|
||||
double). For example, the data structure CPath64 contains an array of int64_t
|
||||
values, whereas the data structure CPathD contains an array of double.
|
||||
Where documentation omits the type suffix (eg CPath), it is referring to an
|
||||
array whose data type could be either int64_t or double.
|
||||
|
||||
For conciseness, the following letters are used in the diagrams below:
|
||||
N: Number of vertices in a given path
|
||||
C: Count (ie number) of paths (or PolyPaths) in the structure
|
||||
A: Number of elements in an array
|
||||
|
||||
|
||||
CPath64 and CPathD:
|
||||
These are arrays of either int64_t or double values. Apart from
|
||||
the first two elements, these arrays are a series of vertices
|
||||
that together define a path. The very first element contains the
|
||||
number of vertices (N) in the path, while second element should
|
||||
contain a 0 value.
|
||||
_______________________________________________________________
|
||||
| counters | vertex1 | vertex2 | ... | vertexN |
|
||||
| N, 0 | x1, y1, (z1) | x2, y2, (z2) | ... | xN, yN, (zN) |
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
CPaths64 and CPathsD:
|
||||
These are also arrays of either int64_t or double values that
|
||||
contain any number of consecutive CPath structures. However,
|
||||
preceding the first path is a pair of values. The first value
|
||||
contains the length of the entire array structure (A), and the
|
||||
second contains the number (ie count) of contained paths (C).
|
||||
Memory allocation for CPaths64 = A * sizeof(int64_t)
|
||||
Memory allocation for CPathsD = A * sizeof(double)
|
||||
__________________________________________
|
||||
| counters | path1 | path2 | ... | pathC |
|
||||
| A, C | | | ... | |
|
||||
------------------------------------------
|
||||
|
||||
|
||||
CPolytree64 and CPolytreeD:
|
||||
The entire polytree structure is an array of int64_t or double. The
|
||||
first element in the array indicates the array's total length (A).
|
||||
The second element indicates the number (C) of CPolyPath structures
|
||||
that are the TOP LEVEL CPolyPath in the polytree, and these top
|
||||
level CPolyPath immediately follow these first two array elements.
|
||||
These top level CPolyPath structures may, in turn, contain nested
|
||||
CPolyPath children, and these collectively make a tree structure.
|
||||
_________________________________________________________
|
||||
| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC |
|
||||
| A, C | | | ... | |
|
||||
---------------------------------------------------------
|
||||
|
||||
|
||||
CPolyPath64 and CPolyPathD:
|
||||
These array structures consist of a pair of counter values followed by a
|
||||
series of polygon vertices and a series of nested CPolyPath children.
|
||||
The first counter values indicates the number of vertices in the
|
||||
polygon (N), and the second counter indicates the CPolyPath child count (C).
|
||||
_____________________________________________________________________________
|
||||
|cntrs |vertex1 |vertex2 |...|vertexN |child1|child2|...|childC|
|
||||
|N, C |x1, y1, (z1)| x2, y2, (z2)|...|xN, yN, (zN)| | |...| |
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
DisposeArray64 & DisposeArrayD:
|
||||
All array structures are allocated in heap memory which will eventually
|
||||
need to be released. However, since applications linking to these DLL
|
||||
functions may use different memory managers, the only safe way to release
|
||||
this memory is to use the exported DisposeArray functions.
|
||||
|
||||
|
||||
(Optional) Z-Values:
|
||||
Structures will only contain user-defined z-values when the USINGZ
|
||||
pre-processor identifier is used. The library does not assign z-values
|
||||
because this field is intended for users to assign custom values to vertices.
|
||||
Z-values in input paths (subject and clip) will be copied to solution paths.
|
||||
New vertices at path intersections will generate a callback event that allows
|
||||
users to assign z-values at these new vertices. The user's callback function
|
||||
must conform with the DLLZCallback definition and be registered with the
|
||||
DLL via SetZCallback. To assist the user in assigning z-values, the library
|
||||
passes in the callback function the new intersection point together with
|
||||
the four vertices that define the two segments that are intersecting.
|
||||
|
||||
*/
|
||||
#ifndef CLIPPER2_EXPORT_H
|
||||
#define CLIPPER2_EXPORT_H
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
typedef int64_t* CPath64;
|
||||
typedef int64_t* CPaths64;
|
||||
typedef double* CPathD;
|
||||
typedef double* CPathsD;
|
||||
|
||||
typedef int64_t* CPolyPath64;
|
||||
typedef int64_t* CPolyTree64;
|
||||
typedef double* CPolyPathD;
|
||||
typedef double* CPolyTreeD;
|
||||
|
||||
template <typename T>
|
||||
struct CRect {
|
||||
T left;
|
||||
T top;
|
||||
T right;
|
||||
T bottom;
|
||||
};
|
||||
|
||||
typedef CRect<int64_t> CRect64;
|
||||
typedef CRect<double> CRectD;
|
||||
|
||||
template <typename T>
|
||||
inline bool CRectIsEmpty(const CRect<T>& rect)
|
||||
{
|
||||
return (rect.right <= rect.left) || (rect.bottom <= rect.top);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Rect<T> CRectToRect(const CRect<T>& rect)
|
||||
{
|
||||
Rect<T> result;
|
||||
result.left = rect.left;
|
||||
result.top = rect.top;
|
||||
result.right = rect.right;
|
||||
result.bottom = rect.bottom;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
inline T1 Reinterpret(T2 value) {
|
||||
return *reinterpret_cast<T1*>(&value);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define EXTERN_DLL_EXPORT extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// EXPORTED FUNCTION DECLARATIONS
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
EXTERN_DLL_EXPORT const char* Version();
|
||||
|
||||
EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p)
|
||||
{
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT void DisposeArrayD(double*& p)
|
||||
{
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPaths64& solution, CPaths64& solution_open,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPolyTree64& sol_tree, CPaths64& solution_open,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPathsD& solution, CPathsD& solution_open, int precision = 2,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPolyTreeD& solution, CPathsD& solution_open, int precision = 2,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
double miter_limit = 2.0, double arc_tolerance = 0.0,
|
||||
bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision = 2, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
double miter_limit = 2.0, double arc_tolerance = 0.0,
|
||||
bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision = 2, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0, bool reverse_solution = false);
|
||||
|
||||
// RectClip & RectClipLines:
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
|
||||
const CPaths64 paths);
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
|
||||
const CPathsD paths, int precision = 2);
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
|
||||
const CPaths64 paths);
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
|
||||
const CPathsD paths, int precision = 2);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// INTERNAL FUNCTIONS
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
#ifdef USINGZ
|
||||
ZCallback64 dllCallback64 = nullptr;
|
||||
ZCallbackD dllCallbackD = nullptr;
|
||||
|
||||
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3;
|
||||
#else
|
||||
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2;
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
|
||||
size_t& cnt, size_t& array_len)
|
||||
{
|
||||
array_len = 2;
|
||||
cnt = 0;
|
||||
for (const Path<T>& path : paths)
|
||||
if (path.size())
|
||||
{
|
||||
array_len += path.size() * EXPORT_VERTEX_DIMENSIONALITY + 2;
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t GetPolyPathArrayLen64(const PolyPath64& pp)
|
||||
{
|
||||
size_t result = 2; // poly_length + child_count
|
||||
result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY;
|
||||
//plus nested children :)
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
result += GetPolyPathArrayLen64(*pp[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t GetPolyPathArrayLenD(const PolyPathD& pp)
|
||||
{
|
||||
size_t result = 2; // poly_length + child_count
|
||||
result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY;
|
||||
//plus nested children :)
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
result += GetPolyPathArrayLenD(*pp[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void GetPolytreeCountAndCStorageSize64(const PolyTree64& tree,
|
||||
size_t& cnt, size_t& array_len)
|
||||
{
|
||||
cnt = tree.Count(); // nb: top level count only
|
||||
array_len = GetPolyPathArrayLen64(tree);
|
||||
}
|
||||
|
||||
static void GetPolytreeCountAndCStorageSizeD(const PolyTreeD& tree,
|
||||
size_t& cnt, size_t& array_len)
|
||||
{
|
||||
cnt = tree.Count(); // nb: top level count only
|
||||
array_len = GetPolyPathArrayLenD(tree);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T* CreateCPathsFromPathsT(const Paths<T>& paths)
|
||||
{
|
||||
size_t cnt = 0, array_len = 0;
|
||||
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
|
||||
T* result = new T[array_len], * v = result;
|
||||
*v++ = array_len;
|
||||
*v++ = cnt;
|
||||
for (const Path<T>& path : paths)
|
||||
{
|
||||
if (!path.size()) continue;
|
||||
*v++ = path.size();
|
||||
*v++ = 0;
|
||||
for (const Point<T>& pt : path)
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
*v++ = Reinterpret<T>(pt.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CPathsD CreateCPathsDFromPathsD(const PathsD& paths)
|
||||
{
|
||||
if (!paths.size()) return nullptr;
|
||||
size_t cnt, array_len;
|
||||
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
|
||||
CPathsD result = new double[array_len], v = result;
|
||||
*v++ = (double)array_len;
|
||||
*v++ = (double)cnt;
|
||||
for (const PathD& path : paths)
|
||||
{
|
||||
if (!path.size()) continue;
|
||||
*v = (double)path.size();
|
||||
++v; *v++ = 0;
|
||||
for (const PointD& pt : path)
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<double>(pt.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
|
||||
{
|
||||
if (!paths.size()) return nullptr;
|
||||
size_t cnt, array_len;
|
||||
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
|
||||
CPathsD result = new double[array_len], v = result;
|
||||
*v++ = (double)array_len;
|
||||
*v++ = (double)cnt;
|
||||
for (const Path64& path : paths)
|
||||
{
|
||||
if (!path.size()) continue;
|
||||
*v = (double)path.size();
|
||||
++v; *v++ = 0;
|
||||
for (const Point64& pt : path)
|
||||
{
|
||||
*v++ = pt.x * scale;
|
||||
*v++ = pt.y * scale;
|
||||
#ifdef USINGZ
|
||||
*v++ = Reinterpret<double>(pt.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Path<T> ConvertCPathToPathT(T* path)
|
||||
{
|
||||
Path<T> result;
|
||||
if (!path) return result;
|
||||
T* v = path;
|
||||
size_t cnt = static_cast<size_t>(*v);
|
||||
v += 2; // skip 0 value
|
||||
result.reserve(cnt);
|
||||
for (size_t j = 0; j < cnt; ++j)
|
||||
{
|
||||
T x = *v++, y = *v++;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
result.emplace_back(x, y, z);
|
||||
#else
|
||||
result.emplace_back(x, y);
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Paths<T> ConvertCPathsToPathsT(T* paths)
|
||||
{
|
||||
Paths<T> result;
|
||||
if (!paths) return result;
|
||||
T* v = paths; ++v;
|
||||
size_t cnt = static_cast<size_t>(*v++);
|
||||
result.reserve(cnt);
|
||||
for (size_t i = 0; i < cnt; ++i)
|
||||
{
|
||||
size_t cnt2 = static_cast<size_t>(*v);
|
||||
v += 2;
|
||||
Path<T> path;
|
||||
path.reserve(cnt2);
|
||||
for (size_t j = 0; j < cnt2; ++j)
|
||||
{
|
||||
T x = *v++, y = *v++;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
path.emplace_back(x, y, z);
|
||||
#else
|
||||
path.emplace_back(x, y);
|
||||
#endif
|
||||
}
|
||||
result.emplace_back(std::move(path));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale)
|
||||
{
|
||||
Path64 result;
|
||||
if (!path) return result;
|
||||
double* v = path;
|
||||
size_t cnt = static_cast<size_t>(*v);
|
||||
v += 2; // skip 0 value
|
||||
result.reserve(cnt);
|
||||
for (size_t j = 0; j < cnt; ++j)
|
||||
{
|
||||
double x = *v++ * scale;
|
||||
double y = *v++ * scale;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
result.emplace_back(x, y, z);
|
||||
#else
|
||||
result.emplace_back(x, y);
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale)
|
||||
{
|
||||
Paths64 result;
|
||||
if (!paths) return result;
|
||||
double* v = paths;
|
||||
++v; // skip the first value (0)
|
||||
size_t cnt = static_cast<size_t>(*v++);
|
||||
result.reserve(cnt);
|
||||
for (size_t i = 0; i < cnt; ++i)
|
||||
{
|
||||
size_t cnt2 = static_cast<size_t>(*v);
|
||||
v += 2;
|
||||
Path64 path;
|
||||
path.reserve(cnt2);
|
||||
for (size_t j = 0; j < cnt2; ++j)
|
||||
{
|
||||
double x = *v++ * scale;
|
||||
double y = *v++ * scale;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
path.emplace_back(x, y, z);
|
||||
#else
|
||||
path.emplace_back(x, y);
|
||||
#endif
|
||||
}
|
||||
result.emplace_back(std::move(path));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v)
|
||||
{
|
||||
*v++ = static_cast<int64_t>(pp->Polygon().size());
|
||||
*v++ = static_cast<int64_t>(pp->Count());
|
||||
for (const Point64& pt : pp->Polygon())
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<int64_t>(pt.z); // raw memory copy
|
||||
#endif
|
||||
}
|
||||
for (size_t i = 0; i < pp->Count(); ++i)
|
||||
CreateCPolyPath64(pp->Child(i), v);
|
||||
}
|
||||
|
||||
static void CreateCPolyPathD(const PolyPathD* pp, double*& v)
|
||||
{
|
||||
*v++ = static_cast<double>(pp->Polygon().size());
|
||||
*v++ = static_cast<double>(pp->Count());
|
||||
for (const PointD& pt : pp->Polygon())
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<double>(pt.z); // raw memory copy
|
||||
#endif
|
||||
}
|
||||
for (size_t i = 0; i < pp->Count(); ++i)
|
||||
CreateCPolyPathD(pp->Child(i), v);
|
||||
}
|
||||
|
||||
static int64_t* CreateCPolyTree64(const PolyTree64& tree)
|
||||
{
|
||||
size_t cnt, array_len;
|
||||
GetPolytreeCountAndCStorageSize64(tree, cnt, array_len);
|
||||
if (!cnt) return nullptr;
|
||||
// allocate storage
|
||||
int64_t* result = new int64_t[array_len];
|
||||
int64_t* v = result;
|
||||
*v++ = static_cast<int64_t>(array_len);
|
||||
*v++ = static_cast<int64_t>(tree.Count());
|
||||
for (size_t i = 0; i < tree.Count(); ++i)
|
||||
CreateCPolyPath64(tree.Child(i), v);
|
||||
return result;
|
||||
}
|
||||
|
||||
static double* CreateCPolyTreeD(const PolyTreeD& tree)
|
||||
{
|
||||
double scale = std::log10(tree.Scale());
|
||||
size_t cnt, array_len;
|
||||
GetPolytreeCountAndCStorageSizeD(tree, cnt, array_len);
|
||||
if (!cnt) return nullptr;
|
||||
// allocate storage
|
||||
double* result = new double[array_len];
|
||||
double* v = result;
|
||||
*v++ = static_cast<double>(array_len);
|
||||
*v++ = static_cast<double>(tree.Count());
|
||||
for (size_t i = 0; i < tree.Count(); ++i)
|
||||
CreateCPolyPathD(tree.Child(i), v);
|
||||
return result;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// EXPORTED FUNCTION DEFINITIONS
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
EXTERN_DLL_EXPORT const char* Version()
|
||||
{
|
||||
return CLIPPER2_VERSION;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPaths64& solution, CPaths64& solution_open,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
|
||||
Paths64 sub, sub_open, clp, sol, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
Clipper64 clipper;
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallback64)
|
||||
clipper.SetZCallback(dllCallback64);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open))
|
||||
return -1; // clipping bug - should never happen :)
|
||||
solution = CreateCPathsFromPathsT(sol);
|
||||
solution_open = CreateCPathsFromPathsT(sol_open);
|
||||
return 0; //success !!
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPolyTree64& sol_tree, CPaths64& solution_open,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
Paths64 sub, sub_open, clp, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
PolyTree64 tree;
|
||||
Clipper64 clipper;
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallback64)
|
||||
clipper.SetZCallback(dllCallback64);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
|
||||
return -1; // clipping bug - should never happen :)
|
||||
|
||||
sol_tree = CreateCPolyTree64(tree);
|
||||
solution_open = CreateCPathsFromPathsT(sol_open);
|
||||
return 0; //success !!
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPathsD& solution, CPathsD& solution_open, int precision,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8) return -5;
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
//const double scale = std::pow(10, precision);
|
||||
|
||||
PathsD sub, sub_open, clp, sol, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
ClipperD clipper(precision);
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallbackD)
|
||||
clipper.SetZCallback(dllCallbackD);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype),
|
||||
FillRule(fillrule), sol, sol_open)) return -1;
|
||||
solution = CreateCPathsDFromPathsD(sol);
|
||||
solution_open = CreateCPathsDFromPathsD(sol_open);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPolyTreeD& solution, CPathsD& solution_open, int precision,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8) return -5;
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
//double scale = std::pow(10, precision);
|
||||
|
||||
int err = 0;
|
||||
PathsD sub, sub_open, clp, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
PolyTreeD tree;
|
||||
ClipperD clipper(precision);
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallbackD)
|
||||
clipper.SetZCallback(dllCallbackD);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
|
||||
return -1; // clipping bug - should never happen :)
|
||||
|
||||
solution = CreateCPolyTreeD(tree);
|
||||
solution_open = CreateCPathsDFromPathsD(sol_open);
|
||||
return 0; //success !!
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
Paths64 pp;
|
||||
pp = ConvertCPathsToPathsT(paths);
|
||||
ClipperOffset clip_offset( miter_limit,
|
||||
arc_tolerance, reverse_solution);
|
||||
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta, result);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8 || !paths) return nullptr;
|
||||
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta * scale, result);
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path,
|
||||
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
Path64 pp;
|
||||
pp = ConvertCPathToPathT(path);
|
||||
ClipperOffset clip_offset(miter_limit,
|
||||
arc_tolerance, reverse_solution);
|
||||
clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta, result);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8 || !path) return nullptr;
|
||||
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
|
||||
Path64 pp = ConvertCPathDToPath64WithScale(path, scale);
|
||||
clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta * scale, result);
|
||||
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
Rect64 r64 = CRectToRect(rect);
|
||||
class RectClip64 rc(r64);
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 result = rc.Execute(pp);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
if (precision < -8 || precision > 8) return nullptr;
|
||||
const double scale = std::pow(10, precision);
|
||||
|
||||
RectD r = CRectToRect(rect);
|
||||
Rect64 rec = ScaleRect<int64_t, double>(r, scale);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
class RectClip64 rc(rec);
|
||||
Paths64 result = rc.Execute(pp);
|
||||
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
|
||||
const CPaths64 paths)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
Rect64 r = CRectToRect(rect);
|
||||
class RectClipLines64 rcl (r);
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 result = rcl.Execute(pp);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
|
||||
const CPathsD paths, int precision)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
if (precision < -8 || precision > 8) return nullptr;
|
||||
|
||||
const double scale = std::pow(10, precision);
|
||||
Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
|
||||
class RectClipLines64 rcl(r);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
Paths64 result = rcl.Execute(pp);
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
|
||||
{
|
||||
Path64 path = ConvertCPathToPathT(cpath);
|
||||
Path64 pattern = ConvertCPathToPathT(cpattern);
|
||||
Paths64 solution = MinkowskiSum(pattern, path, is_closed);
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
|
||||
{
|
||||
Path64 path = ConvertCPathToPathT(cpath);
|
||||
Path64 pattern = ConvertCPathToPathT(cpattern);
|
||||
Paths64 solution = MinkowskiDiff(pattern, path, is_closed);
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
|
||||
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
|
||||
|
||||
EXTERN_DLL_EXPORT void SetZCallback64(DLLZCallback64 callback)
|
||||
{
|
||||
dllCallback64 = callback;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT void SetZCallbackD(DLLZCallbackD callback)
|
||||
{
|
||||
dllCallbackD = callback;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif // CLIPPER2_EXPORT_H
|
||||
@@ -1,770 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 27 April 2024 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : This module provides a simple interface to the Clipper Library *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_H
|
||||
#define CLIPPER_H
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const Paths64& subjects, const Paths64& clips)
|
||||
{
|
||||
Paths64 result;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
|
||||
{
|
||||
Paths64 sol_open;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, solution, sol_open);
|
||||
}
|
||||
|
||||
inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const PathsD& subjects, const PathsD& clips, int precision = 2)
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
PathsD result;
|
||||
if (error_code) return result;
|
||||
ClipperD clipper(precision);
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const PathsD& subjects, const PathsD& clips,
|
||||
PolyTreeD& polytree, int precision = 2)
|
||||
{
|
||||
polytree.Clear();
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return;
|
||||
ClipperD clipper(precision);
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, polytree);
|
||||
}
|
||||
|
||||
inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Union, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
|
||||
{
|
||||
Paths64 result;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.Execute(ClipType::Union, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2)
|
||||
{
|
||||
PathsD result;
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return result;
|
||||
ClipperD clipper(precision);
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.Execute(ClipType::Union, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 InflatePaths(const Paths64& paths, double delta,
|
||||
JoinType jt, EndType et, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0)
|
||||
{
|
||||
if (!delta) return paths;
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
clip_offset.AddPaths(paths, jt, et);
|
||||
Paths64 solution;
|
||||
clip_offset.Execute(delta, solution);
|
||||
return solution;
|
||||
}
|
||||
|
||||
inline PathsD InflatePaths(const PathsD& paths, double delta,
|
||||
JoinType jt, EndType et, double miter_limit = 2.0,
|
||||
int precision = 2, double arc_tolerance = 0.0)
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (!delta) return paths;
|
||||
if (error_code) return PathsD();
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
|
||||
if (error_code) return PathsD();
|
||||
Paths64 solution;
|
||||
clip_offset.Execute(delta * scale, solution);
|
||||
return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
|
||||
{
|
||||
Path<T> result;
|
||||
result.reserve(path.size());
|
||||
std::transform(path.begin(), path.end(), back_inserter(result),
|
||||
[dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
|
||||
{
|
||||
return TranslatePath<int64_t>(path, dx, dy);
|
||||
}
|
||||
|
||||
inline PathD TranslatePath(const PathD& path, double dx, double dy)
|
||||
{
|
||||
return TranslatePath<double>(path, dx, dy);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
|
||||
{
|
||||
Paths<T> result;
|
||||
result.reserve(paths.size());
|
||||
std::transform(paths.begin(), paths.end(), back_inserter(result),
|
||||
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
|
||||
{
|
||||
return TranslatePaths<int64_t>(paths, dx, dy);
|
||||
}
|
||||
|
||||
inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
|
||||
{
|
||||
return TranslatePaths<double>(paths, dx, dy);
|
||||
}
|
||||
|
||||
inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
|
||||
{
|
||||
if (rect.IsEmpty() || paths.empty()) return Paths64();
|
||||
RectClip64 rc(rect);
|
||||
return rc.Execute(paths);
|
||||
}
|
||||
|
||||
inline Paths64 RectClip(const Rect64& rect, const Path64& path)
|
||||
{
|
||||
if (rect.IsEmpty() || path.empty()) return Paths64();
|
||||
RectClip64 rc(rect);
|
||||
return rc.Execute(Paths64{ path });
|
||||
}
|
||||
|
||||
inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
|
||||
{
|
||||
if (rect.IsEmpty() || paths.empty()) return PathsD();
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return PathsD();
|
||||
const double scale = std::pow(10, precision);
|
||||
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
|
||||
RectClip64 rc(r);
|
||||
Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
|
||||
if (error_code) return PathsD(); // ie: error_code result is lost
|
||||
return ScalePaths<double, int64_t>(
|
||||
rc.Execute(pp), 1 / scale, error_code);
|
||||
}
|
||||
|
||||
inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2)
|
||||
{
|
||||
return RectClip(rect, PathsD{ path }, precision);
|
||||
}
|
||||
|
||||
inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines)
|
||||
{
|
||||
if (rect.IsEmpty() || lines.empty()) return Paths64();
|
||||
RectClipLines64 rcl(rect);
|
||||
return rcl.Execute(lines);
|
||||
}
|
||||
|
||||
inline Paths64 RectClipLines(const Rect64& rect, const Path64& line)
|
||||
{
|
||||
return RectClipLines(rect, Paths64{ line });
|
||||
}
|
||||
|
||||
inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
|
||||
{
|
||||
if (rect.IsEmpty() || lines.empty()) return PathsD();
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return PathsD();
|
||||
const double scale = std::pow(10, precision);
|
||||
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
|
||||
RectClipLines64 rcl(r);
|
||||
Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
|
||||
if (error_code) return PathsD();
|
||||
p = rcl.Execute(p);
|
||||
return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2)
|
||||
{
|
||||
return RectClipLines(rect, PathsD{ line }, precision);
|
||||
}
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths)
|
||||
{
|
||||
paths.emplace_back(polypath.Polygon());
|
||||
for (const auto& child : polypath)
|
||||
PolyPathToPaths64(*child, paths);
|
||||
}
|
||||
|
||||
inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths)
|
||||
{
|
||||
paths.emplace_back(polypath.Polygon());
|
||||
for (const auto& child : polypath)
|
||||
PolyPathToPathsD(*child, paths);
|
||||
}
|
||||
|
||||
inline bool PolyPath64ContainsChildren(const PolyPath64& pp)
|
||||
{
|
||||
for (const auto& child : pp)
|
||||
{
|
||||
// return false if this child isn't fully contained by its parent
|
||||
|
||||
// checking for a single vertex outside is a bit too crude since
|
||||
// it doesn't account for rounding errors. It's better to check
|
||||
// for consecutive vertices found outside the parent's polygon.
|
||||
|
||||
int outsideCnt = 0;
|
||||
for (const Point64& pt : child->Polygon())
|
||||
{
|
||||
PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
|
||||
if (result == PointInPolygonResult::IsInside) --outsideCnt;
|
||||
else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
|
||||
if (outsideCnt > 1) return false;
|
||||
else if (outsideCnt < -1) break;
|
||||
}
|
||||
|
||||
// now check any nested children too
|
||||
if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void OutlinePolyPath(std::ostream& os,
|
||||
size_t idx, bool isHole, size_t count, const std::string& preamble)
|
||||
{
|
||||
std::string plural = (count == 1) ? "." : "s.";
|
||||
if (isHole)
|
||||
os << preamble << "+- Hole (" << idx << ") contains " << count <<
|
||||
" nested polygon" << plural << std::endl;
|
||||
else
|
||||
os << preamble << "+- Polygon (" << idx << ") contains " << count <<
|
||||
" hole" << plural << std::endl;
|
||||
}
|
||||
|
||||
static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
|
||||
size_t idx, std::string preamble)
|
||||
{
|
||||
OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " ");
|
||||
}
|
||||
|
||||
static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
|
||||
size_t idx, std::string preamble)
|
||||
{
|
||||
OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " ");
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
inline constexpr void MakePathGeneric(const T an_array,
|
||||
size_t array_size, std::vector<U>& result)
|
||||
{
|
||||
result.reserve(array_size / 2);
|
||||
for (size_t i = 0; i < array_size; i +=2)
|
||||
#ifdef USINGZ
|
||||
result.emplace_back( an_array[i], an_array[i + 1], 0 );
|
||||
#else
|
||||
result.emplace_back( an_array[i], an_array[i + 1] );
|
||||
#endif
|
||||
}
|
||||
|
||||
} // end details namespace
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
|
||||
{
|
||||
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
|
||||
os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPath64(os, *pp.Child(i), i, " ");
|
||||
os << std::endl << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
|
||||
{
|
||||
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
|
||||
os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPathD(os, *pp.Child(i), i, " ");
|
||||
os << std::endl << std::endl;
|
||||
if (!pp.Level()) os << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
|
||||
{
|
||||
Paths64 result;
|
||||
for (const auto& child : polytree)
|
||||
details::PolyPathToPaths64(*child, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree)
|
||||
{
|
||||
PathsD result;
|
||||
for (const auto& child : polytree)
|
||||
details::PolyPathToPathsD(*child, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
|
||||
{
|
||||
for (const auto& child : polytree)
|
||||
if (child->Count() > 0 &&
|
||||
!details::PolyPath64ContainsChildren(*child))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline Path64 MakePath(const std::vector<T>& list)
|
||||
{
|
||||
const auto size = list.size() - list.size() % 2;
|
||||
if (list.size() != size)
|
||||
DoError(non_pair_error_i); // non-fatal without exception handling
|
||||
Path64 result;
|
||||
details::MakePathGeneric(list, size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N,
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline Path64 MakePath(const T(&list)[N])
|
||||
{
|
||||
// Make the compiler error on unpaired value (i.e. no runtime effects).
|
||||
static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
|
||||
Path64 result;
|
||||
details::MakePathGeneric(list, N, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename std::enable_if<
|
||||
std::is_arithmetic<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline PathD MakePathD(const std::vector<T>& list)
|
||||
{
|
||||
const auto size = list.size() - list.size() % 2;
|
||||
if (list.size() != size)
|
||||
DoError(non_pair_error_i); // non-fatal without exception handling
|
||||
PathD result;
|
||||
details::MakePathGeneric(list, size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N,
|
||||
typename std::enable_if<
|
||||
std::is_arithmetic<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline PathD MakePathD(const T(&list)[N])
|
||||
{
|
||||
// Make the compiler error on unpaired value (i.e. no runtime effects).
|
||||
static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
|
||||
PathD result;
|
||||
details::MakePathGeneric(list, N, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
template<typename T2, std::size_t N>
|
||||
inline Path64 MakePathZ(const T2(&list)[N])
|
||||
{
|
||||
static_assert(N % 3 == 0 && std::numeric_limits<T2>::is_integer,
|
||||
"MakePathZ requires integer values in multiples of 3");
|
||||
std::size_t size = N / 3;
|
||||
Path64 result(size);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = Point64(list[i * 3],
|
||||
list[i * 3 + 1], list[i * 3 + 2]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T2, std::size_t N>
|
||||
inline PathD MakePathZD(const T2(&list)[N])
|
||||
{
|
||||
static_assert(N % 3 == 0,
|
||||
"MakePathZD requires values in multiples of 3");
|
||||
std::size_t size = N / 3;
|
||||
PathD result(size);
|
||||
if constexpr (std::numeric_limits<T2>::is_integer)
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = PointD(list[i * 3],
|
||||
list[i * 3 + 1], list[i * 3 + 2]);
|
||||
else
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = PointD(list[i * 3], list[i * 3 + 1],
|
||||
static_cast<int64_t>(list[i * 3 + 2]));
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
|
||||
{
|
||||
size_t len = p.size();
|
||||
if (len < 3)
|
||||
{
|
||||
if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
|
||||
else return p;
|
||||
}
|
||||
|
||||
Path64 dst;
|
||||
dst.reserve(len);
|
||||
Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
|
||||
|
||||
if (!is_open_path)
|
||||
{
|
||||
while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1)))
|
||||
++srcIt;
|
||||
while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt))
|
||||
--stop;
|
||||
if (srcIt == stop) return Path64();
|
||||
}
|
||||
|
||||
prevIt = srcIt++;
|
||||
dst.emplace_back(*prevIt);
|
||||
for (; srcIt != stop; ++srcIt)
|
||||
{
|
||||
if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1)))
|
||||
{
|
||||
prevIt = srcIt;
|
||||
dst.emplace_back(*prevIt);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_open_path)
|
||||
dst.emplace_back(*srcIt);
|
||||
else if (!IsCollinear(*prevIt, *stop, dst[0]))
|
||||
dst.emplace_back(*stop);
|
||||
else
|
||||
{
|
||||
while (dst.size() > 2 &&
|
||||
IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
|
||||
dst.pop_back();
|
||||
if (dst.size() < 3) return Path64();
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return PathD();
|
||||
const double scale = std::pow(10, precision);
|
||||
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
|
||||
if (error_code) return PathD();
|
||||
p = TrimCollinear(p, is_open_path);
|
||||
return ScalePath<double, int64_t>(p, 1/scale, error_code);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline double Distance(const Point<T> pt1, const Point<T> pt2)
|
||||
{
|
||||
return std::sqrt(DistanceSqr(pt1, pt2));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline double Length(const Path<T>& path, bool is_closed_path = false)
|
||||
{
|
||||
double result = 0.0;
|
||||
if (path.size() < 2) return result;
|
||||
auto it = path.cbegin(), stop = path.end() - 1;
|
||||
for (; it != stop; ++it)
|
||||
result += Distance(*it, *(it + 1));
|
||||
if (is_closed_path)
|
||||
result += Distance(*stop, *path.cbegin());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
|
||||
{
|
||||
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
|
||||
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> Ellipse(const Rect<T>& rect, size_t steps = 0)
|
||||
{
|
||||
return Ellipse(rect.MidPoint(),
|
||||
static_cast<double>(rect.Width()) *0.5,
|
||||
static_cast<double>(rect.Height()) * 0.5, steps);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> Ellipse(const Point<T>& center,
|
||||
double radiusX, double radiusY = 0, size_t steps = 0)
|
||||
{
|
||||
if (radiusX <= 0) return Path<T>();
|
||||
if (radiusY <= 0) radiusY = radiusX;
|
||||
if (steps <= 2)
|
||||
steps = static_cast<size_t>(PI * sqrt((radiusX + radiusY) / 2));
|
||||
|
||||
double si = std::sin(2 * PI / steps);
|
||||
double co = std::cos(2 * PI / steps);
|
||||
double dx = co, dy = si;
|
||||
Path<T> result;
|
||||
result.reserve(steps);
|
||||
result.emplace_back(center.x + radiusX, static_cast<double>(center.y));
|
||||
for (size_t i = 1; i < steps; ++i)
|
||||
{
|
||||
result.emplace_back(center.x + radiusX * dx, center.y + radiusY * dy);
|
||||
double x = dx * co - dy * si;
|
||||
dy = dy * co + dx * si;
|
||||
dx = x;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> SimplifyPath(const Path<T> &path,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
{
|
||||
const size_t len = path.size(), high = len -1;
|
||||
const double epsSqr = Sqr(epsilon);
|
||||
if (len < 4) return Path<T>(path);
|
||||
|
||||
std::vector<bool> flags(len);
|
||||
std::vector<double> distSqr(len);
|
||||
size_t prior = high, curr = 0, start, next, prior2;
|
||||
if (isClosedPath)
|
||||
{
|
||||
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
|
||||
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
distSqr[0] = MAX_DBL;
|
||||
distSqr[high] = MAX_DBL;
|
||||
}
|
||||
for (size_t i = 1; i < high; ++i)
|
||||
distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (distSqr[curr] > epsSqr)
|
||||
{
|
||||
start = curr;
|
||||
do
|
||||
{
|
||||
curr = GetNext(curr, high, flags);
|
||||
} while (curr != start && distSqr[curr] > epsSqr);
|
||||
if (curr == start) break;
|
||||
}
|
||||
|
||||
prior = GetPrior(curr, high, flags);
|
||||
next = GetNext(curr, high, flags);
|
||||
if (next == prior) break;
|
||||
|
||||
// flag for removal the smaller of adjacent 'distances'
|
||||
if (distSqr[next] < distSqr[curr])
|
||||
{
|
||||
prior2 = prior;
|
||||
prior = curr;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
}
|
||||
else
|
||||
prior2 = GetPrior(prior, high, flags);
|
||||
|
||||
flags[curr] = true;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
|
||||
if (isClosedPath || ((curr != high) && (curr != 0)))
|
||||
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
|
||||
if (isClosedPath || ((prior != 0) && (prior != high)))
|
||||
distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
|
||||
}
|
||||
Path<T> result;
|
||||
result.reserve(len);
|
||||
for (typename Path<T>::size_type i = 0; i < len; ++i)
|
||||
if (!flags[i]) result.emplace_back(path[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Paths<T> SimplifyPaths(const Paths<T> &paths,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
{
|
||||
Paths<T> result;
|
||||
result.reserve(paths.size());
|
||||
for (const auto& path : paths)
|
||||
result.emplace_back(std::move(SimplifyPath(path, epsilon, isClosedPath)));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void RDP(const Path<T> path, std::size_t begin,
|
||||
std::size_t end, double epsSqrd, std::vector<bool>& flags)
|
||||
{
|
||||
typename Path<T>::size_type idx = 0;
|
||||
double max_d = 0;
|
||||
while (end > begin && path[begin] == path[end]) flags[end--] = false;
|
||||
for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
|
||||
{
|
||||
// PerpendicDistFromLineSqrd - avoids expensive Sqrt()
|
||||
double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
|
||||
if (d <= max_d) continue;
|
||||
max_d = d;
|
||||
idx = i;
|
||||
}
|
||||
if (max_d <= epsSqrd) return;
|
||||
flags[idx] = true;
|
||||
if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
|
||||
if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
|
||||
{
|
||||
const typename Path<T>::size_type len = path.size();
|
||||
if (len < 5) return Path<T>(path);
|
||||
std::vector<bool> flags(len);
|
||||
flags[0] = true;
|
||||
flags[len - 1] = true;
|
||||
RDP(path, 0, len - 1, Sqr(epsilon), flags);
|
||||
Path<T> result;
|
||||
result.reserve(len);
|
||||
for (typename Path<T>::size_type i = 0; i < len; ++i)
|
||||
if (flags[i])
|
||||
result.emplace_back(path[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Paths<T> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
|
||||
{
|
||||
Paths<T> result;
|
||||
result.reserve(paths.size());
|
||||
std::transform(paths.begin(), paths.end(), back_inserter(result),
|
||||
[epsilon](const auto& path)
|
||||
{ return RamerDouglasPeucker<T>(path, epsilon); });
|
||||
return result;
|
||||
}
|
||||
|
||||
} // end Clipper2Lib namespace
|
||||
|
||||
#endif // CLIPPER_H
|
||||
@@ -1,120 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 1 November 2023 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2023 *
|
||||
* Purpose : Minkowski Sum and Difference *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_MINKOWSKI_H
|
||||
#define CLIPPER_MINKOWSKI_H
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed)
|
||||
{
|
||||
size_t delta = isClosed ? 0 : 1;
|
||||
size_t patLen = pattern.size(), pathLen = path.size();
|
||||
if (patLen == 0 || pathLen == 0) return Paths64();
|
||||
Paths64 tmp;
|
||||
tmp.reserve(pathLen);
|
||||
|
||||
if (isSum)
|
||||
{
|
||||
for (const Point64& p : path)
|
||||
{
|
||||
Path64 path2(pattern.size());
|
||||
std::transform(pattern.cbegin(), pattern.cend(),
|
||||
path2.begin(), [p](const Point64& pt2) {return p + pt2; });
|
||||
tmp.emplace_back(std::move(path2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const Point64& p : path)
|
||||
{
|
||||
Path64 path2(pattern.size());
|
||||
std::transform(pattern.cbegin(), pattern.cend(),
|
||||
path2.begin(), [p](const Point64& pt2) {return p - pt2; });
|
||||
tmp.emplace_back(std::move(path2));
|
||||
}
|
||||
}
|
||||
|
||||
Paths64 result;
|
||||
result.reserve((pathLen - delta) * patLen);
|
||||
size_t g = isClosed ? pathLen - 1 : 0;
|
||||
for (size_t h = patLen - 1, i = delta; i < pathLen; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < patLen; j++)
|
||||
{
|
||||
Path64 quad;
|
||||
quad.reserve(4);
|
||||
{
|
||||
quad.emplace_back(tmp[g][h]);
|
||||
quad.emplace_back(tmp[i][h]);
|
||||
quad.emplace_back(tmp[i][j]);
|
||||
quad.emplace_back(tmp[g][j]);
|
||||
};
|
||||
if (!IsPositive(quad))
|
||||
std::reverse(quad.begin(), quad.end());
|
||||
result.emplace_back(std::move(quad));
|
||||
h = j;
|
||||
}
|
||||
g = i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
|
||||
{
|
||||
Paths64 result;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.Execute(ClipType::Union, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed)
|
||||
{
|
||||
return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero);
|
||||
}
|
||||
|
||||
inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
|
||||
{
|
||||
int error_code = 0;
|
||||
double scale = pow(10, decimalPlaces);
|
||||
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
|
||||
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
|
||||
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
|
||||
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
|
||||
{
|
||||
return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero);
|
||||
}
|
||||
|
||||
inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
|
||||
{
|
||||
int error_code = 0;
|
||||
double scale = pow(10, decimalPlaces);
|
||||
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
|
||||
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
|
||||
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
|
||||
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
|
||||
#endif // CLIPPER_MINKOWSKI_H
|
||||
@@ -1,129 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 22 January 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_OFFSET_H_
|
||||
#define CLIPPER_OFFSET_H_
|
||||
|
||||
#include "clipper.core.h"
|
||||
#include "clipper.engine.h"
|
||||
#include <optional>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
enum class JoinType { Square, Bevel, Round, Miter };
|
||||
//Square : Joins are 'squared' at exactly the offset distance (more complex code)
|
||||
//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster)
|
||||
|
||||
enum class EndType {Polygon, Joined, Butt, Square, Round};
|
||||
//Butt : offsets both sides of a path, with square blunt ends
|
||||
//Square : offsets both sides of a path, with square extended ends
|
||||
//Round : offsets both sides of a path, with round extended ends
|
||||
//Joined : offsets both sides of a path, with joined ends
|
||||
//Polygon: offsets only one side of a closed path
|
||||
|
||||
typedef std::function<double(const Path64& path, const PathD& path_normals, size_t curr_idx, size_t prev_idx)> DeltaCallback64;
|
||||
|
||||
class ClipperOffset {
|
||||
private:
|
||||
|
||||
class Group {
|
||||
public:
|
||||
Paths64 paths_in;
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
bool is_reversed = false;
|
||||
JoinType join_type;
|
||||
EndType end_type;
|
||||
Group(const Paths64& _paths, JoinType _join_type, EndType _end_type);
|
||||
};
|
||||
|
||||
int error_code_ = 0;
|
||||
double delta_ = 0.0;
|
||||
double group_delta_ = 0.0;
|
||||
double temp_lim_ = 0.0;
|
||||
double steps_per_rad_ = 0.0;
|
||||
double step_sin_ = 0.0;
|
||||
double step_cos_ = 0.0;
|
||||
PathD norms;
|
||||
Path64 path_out;
|
||||
Paths64* solution = nullptr;
|
||||
PolyTree64* solution_tree = nullptr;
|
||||
std::vector<Group> groups_;
|
||||
JoinType join_type_ = JoinType::Bevel;
|
||||
EndType end_type_ = EndType::Polygon;
|
||||
|
||||
double miter_limit_ = 0.0;
|
||||
double arc_tolerance_ = 0.0;
|
||||
bool preserve_collinear_ = false;
|
||||
bool reverse_solution_ = false;
|
||||
|
||||
#ifdef USINGZ
|
||||
ZCallback64 zCallback64_ = nullptr;
|
||||
void ZCB(const Point64& bot1, const Point64& top1,
|
||||
const Point64& bot2, const Point64& top2, Point64& ip);
|
||||
#endif
|
||||
DeltaCallback64 deltaCallback64_ = nullptr;
|
||||
size_t CalcSolutionCapacity();
|
||||
bool CheckReverseOrientation();
|
||||
void DoBevel(const Path64& path, size_t j, size_t k);
|
||||
void DoSquare(const Path64& path, size_t j, size_t k);
|
||||
void DoMiter(const Path64& path, size_t j, size_t k, double cos_a);
|
||||
void DoRound(const Path64& path, size_t j, size_t k, double angle);
|
||||
void BuildNormals(const Path64& path);
|
||||
void OffsetPolygon(Group& group, const Path64& path);
|
||||
void OffsetOpenJoined(Group& group, const Path64& path);
|
||||
void OffsetOpenPath(Group& group, const Path64& path);
|
||||
void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k);
|
||||
void DoGroupOffset(Group &group);
|
||||
void ExecuteInternal(double delta);
|
||||
public:
|
||||
explicit ClipperOffset(double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0,
|
||||
bool preserve_collinear = false,
|
||||
bool reverse_solution = false) :
|
||||
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
|
||||
preserve_collinear_(preserve_collinear),
|
||||
reverse_solution_(reverse_solution) { };
|
||||
|
||||
~ClipperOffset() { Clear(); };
|
||||
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void AddPath(const Path64& path, JoinType jt_, EndType et_);
|
||||
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
|
||||
void Clear() { groups_.clear(); norms.clear(); };
|
||||
|
||||
void Execute(double delta, Paths64& sols_64);
|
||||
void Execute(double delta, PolyTree64& polytree);
|
||||
void Execute(DeltaCallback64 delta_cb, Paths64& paths);
|
||||
|
||||
double MiterLimit() const { return miter_limit_; }
|
||||
void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
|
||||
|
||||
//ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg)
|
||||
double ArcTolerance() const { return arc_tolerance_; }
|
||||
void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
|
||||
|
||||
bool PreserveCollinear() const { return preserve_collinear_; }
|
||||
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
|
||||
|
||||
bool ReverseSolution() const { return reverse_solution_; }
|
||||
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
|
||||
#endif
|
||||
void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif /* CLIPPER_OFFSET_H_ */
|
||||
@@ -1,83 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 5 July 2024 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : FAST rectangular clipping *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_RECTCLIP_H
|
||||
#define CLIPPER_RECTCLIP_H
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include <queue>
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
// Location: the order is important here, see StartLocsIsClockwise()
|
||||
enum class Location { Left, Top, Right, Bottom, Inside };
|
||||
|
||||
class OutPt2;
|
||||
typedef std::vector<OutPt2*> OutPt2List;
|
||||
|
||||
class OutPt2 {
|
||||
public:
|
||||
Point64 pt;
|
||||
size_t owner_idx = 0;
|
||||
OutPt2List* edge = nullptr;
|
||||
OutPt2* next = nullptr;
|
||||
OutPt2* prev = nullptr;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// RectClip64
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class RectClip64 {
|
||||
private:
|
||||
void ExecuteInternal(const Path64& path);
|
||||
Path64 GetPath(OutPt2*& op);
|
||||
protected:
|
||||
const Rect64 rect_;
|
||||
const Path64 rect_as_path_;
|
||||
const Point64 rect_mp_;
|
||||
Rect64 path_bounds_;
|
||||
std::deque<OutPt2> op_container_;
|
||||
OutPt2List results_; // each path can be broken into multiples
|
||||
OutPt2List edges_[8]; // clockwise and counter-clockwise
|
||||
std::vector<Location> start_locs_;
|
||||
void CheckEdges();
|
||||
void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw);
|
||||
void GetNextLocation(const Path64& path,
|
||||
Location& loc, size_t& i, size_t highI);
|
||||
OutPt2* Add(Point64 pt, bool start_new = false);
|
||||
void AddCorner(Location prev, Location curr);
|
||||
void AddCorner(Location& loc, bool isClockwise);
|
||||
public:
|
||||
explicit RectClip64(const Rect64& rect) :
|
||||
rect_(rect),
|
||||
rect_as_path_(rect.AsPath()),
|
||||
rect_mp_(rect.MidPoint()) {}
|
||||
Paths64 Execute(const Paths64& paths);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// RectClipLines64
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class RectClipLines64 : public RectClip64 {
|
||||
private:
|
||||
void ExecuteInternal(const Path64& path);
|
||||
Path64 GetPath(OutPt2*& op);
|
||||
public:
|
||||
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
|
||||
Paths64 Execute(const Paths64& paths);
|
||||
};
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
#endif // CLIPPER_RECTCLIP_H
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.5.2";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
@@ -1,17 +0,0 @@
|
||||
// Hackish wrapper around the ClipperLib library to compile the Clipper2 library with the Z support.
|
||||
|
||||
#ifndef clipper2_z_hpp
|
||||
#ifdef CLIPPER_H
|
||||
#error "You should include clipper2_z.hpp before clipper.h"
|
||||
#endif
|
||||
|
||||
#define clipper2_z_hpp
|
||||
|
||||
// Enable the Z coordinate support.
|
||||
#define USINGZ
|
||||
|
||||
#include "clipper.h"
|
||||
|
||||
#undef CLIPPER_H
|
||||
#undef USINGZ
|
||||
#endif // clipper2_z_hpp
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,658 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 22 January 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#include "clipper2/clipper.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
const double floating_point_tolerance = 1e-12;
|
||||
|
||||
// Clipper2 approximates arcs by using series of relatively short straight
|
||||
//line segments. And logically, shorter line segments will produce better arc
|
||||
// approximations. But very short segments can degrade performance, usually
|
||||
// with little or no discernable improvement in curve quality. Very short
|
||||
// segments can even detract from curve quality, due to the effects of integer
|
||||
// rounding. Since there isn't an optimal number of line segments for any given
|
||||
// arc radius (that perfectly balances curve approximation with performance),
|
||||
// arc tolerance is user defined. Nevertheless, when the user doesn't define
|
||||
// an arc tolerance (ie leaves alone the 0 default value), the calculated
|
||||
// default arc tolerance (offset_radius / 500) generally produces good (smooth)
|
||||
// arc approximations without producing excessively small segment lengths.
|
||||
// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm
|
||||
const double arc_const = 0.002; // <-- 1/500
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Miscellaneous methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
|
||||
{
|
||||
std::optional<size_t> result;
|
||||
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
for (const Point64& pt : paths[i])
|
||||
{
|
||||
if ((pt.y < botPt.y) ||
|
||||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
|
||||
result = i;
|
||||
botPt.x = pt.x;
|
||||
botPt.y = pt.y;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline double Hypot(double x, double y)
|
||||
{
|
||||
// given that this is an internal function, and given the x and y parameters
|
||||
// will always be coordinate values (or the difference between coordinate values),
|
||||
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
|
||||
// there should be no risk that the following computation will overflow
|
||||
// see https://stackoverflow.com/a/32436148/359538
|
||||
return std::sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
|
||||
{
|
||||
if (pt1 == pt2) return PointD(0.0, 0.0);
|
||||
double dx = static_cast<double>(pt2.x - pt1.x);
|
||||
double dy = static_cast<double>(pt2.y - pt1.y);
|
||||
double inverse_hypot = 1.0 / Hypot(dx, dy);
|
||||
dx *= inverse_hypot;
|
||||
dy *= inverse_hypot;
|
||||
return PointD(dy, -dx);
|
||||
}
|
||||
|
||||
inline bool AlmostZero(double value, double epsilon = 0.001)
|
||||
{
|
||||
return std::fabs(value) < epsilon;
|
||||
}
|
||||
|
||||
inline PointD NormalizeVector(const PointD& vec)
|
||||
{
|
||||
double h = Hypot(vec.x, vec.y);
|
||||
if (AlmostZero(h)) return PointD(0,0);
|
||||
double inverseHypot = 1 / h;
|
||||
return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
|
||||
}
|
||||
|
||||
inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
|
||||
{
|
||||
return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
|
||||
}
|
||||
|
||||
inline bool IsClosedPath(EndType et)
|
||||
{
|
||||
return et == EndType::Polygon || et == EndType::Joined;
|
||||
}
|
||||
|
||||
static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
|
||||
#else
|
||||
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
|
||||
#else
|
||||
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void NegatePath(PathD& path)
|
||||
{
|
||||
for (PointD& pt : path)
|
||||
{
|
||||
pt.x = -pt.x;
|
||||
pt.y = -pt.y;
|
||||
#ifdef USINGZ
|
||||
pt.z = pt.z;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// ClipperOffset::Group methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
|
||||
paths_in(_paths), join_type(_join_type), end_type(_end_type)
|
||||
{
|
||||
bool is_joined =
|
||||
(end_type == EndType::Polygon) ||
|
||||
(end_type == EndType::Joined);
|
||||
for (Path64& p: paths_in)
|
||||
StripDuplicates(p, is_joined);
|
||||
|
||||
if (end_type == EndType::Polygon)
|
||||
{
|
||||
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
|
||||
// the lowermost path must be an outer path, so if its orientation is negative,
|
||||
// then flag the whole group is 'reversed' (will negate delta etc.)
|
||||
// as this is much more efficient than reversing every path.
|
||||
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_path_idx = std::nullopt;
|
||||
is_reversed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// ClipperOffset methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
|
||||
{
|
||||
groups_.emplace_back(Paths64(1, path), jt_, et_);
|
||||
}
|
||||
|
||||
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
|
||||
{
|
||||
if (paths.size() == 0) return;
|
||||
groups_.emplace_back(paths, jt_, et_);
|
||||
}
|
||||
|
||||
void ClipperOffset::BuildNormals(const Path64& path)
|
||||
{
|
||||
norms.clear();
|
||||
norms.reserve(path.size());
|
||||
if (path.size() == 0) return;
|
||||
Path64::const_iterator path_iter, path_stop_iter = --path.cend();
|
||||
for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
|
||||
norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1)));
|
||||
norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
|
||||
}
|
||||
|
||||
void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt1, pt2;
|
||||
if (j == k)
|
||||
{
|
||||
double abs_delta = std::abs(group_delta_);
|
||||
#ifdef USINGZ
|
||||
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
|
||||
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
|
||||
#else
|
||||
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
|
||||
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef USINGZ
|
||||
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
|
||||
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
|
||||
#else
|
||||
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
|
||||
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
|
||||
#endif
|
||||
}
|
||||
path_out.emplace_back(pt1);
|
||||
path_out.emplace_back(pt2);
|
||||
}
|
||||
|
||||
void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD vec;
|
||||
if (j == k)
|
||||
vec = PointD(norms[j].y, -norms[j].x);
|
||||
else
|
||||
vec = GetAvgUnitVector(
|
||||
PointD(-norms[k].y, norms[k].x),
|
||||
PointD(norms[j].y, -norms[j].x));
|
||||
|
||||
double abs_delta = std::abs(group_delta_);
|
||||
|
||||
// now offset the original vertex delta units along unit vector
|
||||
PointD ptQ = PointD(path[j]);
|
||||
ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
|
||||
// get perpendicular vertices
|
||||
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
|
||||
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
|
||||
// get 2 vertices along one edge offset
|
||||
PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
|
||||
if (j == k)
|
||||
{
|
||||
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
path_out.emplace_back(pt);
|
||||
}
|
||||
else
|
||||
{
|
||||
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
path_out.emplace_back(pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
}
|
||||
}
|
||||
|
||||
void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
|
||||
{
|
||||
double q = group_delta_ / (cos_a + 1);
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(
|
||||
path[j].x + (norms[k].x + norms[j].x) * q,
|
||||
path[j].y + (norms[k].y + norms[j].y) * q,
|
||||
path[j].z);
|
||||
#else
|
||||
path_out.emplace_back(
|
||||
path[j].x + (norms[k].x + norms[j].x) * q,
|
||||
path[j].y + (norms[k].y + norms[j].y) * q);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
|
||||
{
|
||||
if (deltaCallback64_) {
|
||||
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
|
||||
// so we'll need to do the following calculations for *every* vertex.
|
||||
double abs_delta = std::fabs(group_delta_);
|
||||
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
|
||||
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const);
|
||||
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
|
||||
step_sin_ = std::sin(2 * PI / steps_per_360);
|
||||
step_cos_ = std::cos(2 * PI / steps_per_360);
|
||||
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
|
||||
steps_per_rad_ = steps_per_360 / (2 * PI);
|
||||
}
|
||||
|
||||
Point64 pt = path[j];
|
||||
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
|
||||
|
||||
if (j == k) offsetVec.Negate();
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
|
||||
#else
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
|
||||
#endif
|
||||
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
|
||||
for (int i = 1; i < steps; ++i) // ie 1 less than steps
|
||||
{
|
||||
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
|
||||
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
|
||||
#else
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
|
||||
#endif
|
||||
}
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
// Let A = change in angle where edges join
|
||||
// A == 0: ie no change in angle (flat join)
|
||||
// A == PI: edges 'spike'
|
||||
// sin(A) < 0: right turning
|
||||
// cos(A) < 0: change in angle is more than 90 degree
|
||||
|
||||
if (path[j] == path[k]) return;
|
||||
|
||||
double sin_a = CrossProduct(norms[j], norms[k]);
|
||||
double cos_a = DotProduct(norms[j], norms[k]);
|
||||
if (sin_a > 1.0) sin_a = 1.0;
|
||||
else if (sin_a < -1.0) sin_a = -1.0;
|
||||
|
||||
if (deltaCallback64_) {
|
||||
group_delta_ = deltaCallback64_(path, norms, j, k);
|
||||
if (group.is_reversed) group_delta_ = -group_delta_;
|
||||
}
|
||||
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||
{
|
||||
path_out.emplace_back(path[j]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
|
||||
{
|
||||
// is concave
|
||||
// by far the simplest way to construct concave joins, especially those joining very
|
||||
// short segments, is to insert 3 points that produce negative regions. These regions
|
||||
// will be removed later by the finishing union operation. This is also the best way
|
||||
// to ensure that path reversals (ie over-shrunk paths) are removed.
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
|
||||
path_out.emplace_back(path[j]); // (#405, #873, #916)
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z);
|
||||
#else
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_));
|
||||
path_out.emplace_back(path[j]); // (#405, #873, #916)
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
|
||||
#endif
|
||||
}
|
||||
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
|
||||
{
|
||||
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
|
||||
DoMiter(path, j, k, cos_a);
|
||||
}
|
||||
else if (join_type_ == JoinType::Miter)
|
||||
{
|
||||
// miter unless the angle is sufficiently acute to exceed ML
|
||||
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
|
||||
else DoSquare(path, j, k);
|
||||
}
|
||||
else if (join_type_ == JoinType::Round)
|
||||
DoRound(path, j, k, std::atan2(sin_a, cos_a));
|
||||
else if ( join_type_ == JoinType::Bevel)
|
||||
DoBevel(path, j, k);
|
||||
else
|
||||
DoSquare(path, j, k);
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
|
||||
{
|
||||
path_out.clear();
|
||||
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
solution->emplace_back(path_out);
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
|
||||
{
|
||||
OffsetPolygon(group, path);
|
||||
Path64 reverse_path(path);
|
||||
std::reverse(reverse_path.begin(), reverse_path.end());
|
||||
|
||||
//rebuild normals
|
||||
std::reverse(norms.begin(), norms.end());
|
||||
norms.emplace_back(norms[0]);
|
||||
norms.erase(norms.begin());
|
||||
NegatePath(norms);
|
||||
|
||||
OffsetPolygon(group, reverse_path);
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
|
||||
{
|
||||
// do the line start cap
|
||||
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
|
||||
|
||||
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||
path_out.emplace_back(path[0]);
|
||||
else
|
||||
{
|
||||
switch (end_type_)
|
||||
{
|
||||
case EndType::Butt:
|
||||
DoBevel(path, 0, 0);
|
||||
break;
|
||||
case EndType::Round:
|
||||
DoRound(path, 0, 0, PI);
|
||||
break;
|
||||
default:
|
||||
DoSquare(path, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t highI = path.size() - 1;
|
||||
// offset the left side going forward
|
||||
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
|
||||
// reverse normals
|
||||
for (size_t i = highI; i > 0; --i)
|
||||
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
|
||||
norms[0] = norms[highI];
|
||||
|
||||
// do the line end cap
|
||||
if (deltaCallback64_)
|
||||
group_delta_ = deltaCallback64_(path, norms, highI, highI);
|
||||
|
||||
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||
path_out.emplace_back(path[highI]);
|
||||
else
|
||||
{
|
||||
switch (end_type_)
|
||||
{
|
||||
case EndType::Butt:
|
||||
DoBevel(path, highI, highI);
|
||||
break;
|
||||
case EndType::Round:
|
||||
DoRound(path, highI, highI, PI);
|
||||
break;
|
||||
default:
|
||||
DoSquare(path, highI, highI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
solution->emplace_back(path_out);
|
||||
}
|
||||
|
||||
void ClipperOffset::DoGroupOffset(Group& group)
|
||||
{
|
||||
if (group.end_type == EndType::Polygon)
|
||||
{
|
||||
// a straight path (2 points) can now also be 'polygon' offset
|
||||
// where the ends will be treated as (180 deg.) joins
|
||||
if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
|
||||
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
|
||||
}
|
||||
else
|
||||
group_delta_ = std::abs(delta_);// *0.5;
|
||||
|
||||
double abs_delta = std::fabs(group_delta_);
|
||||
join_type_ = group.join_type;
|
||||
end_type_ = group.end_type;
|
||||
|
||||
if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
|
||||
{
|
||||
// calculate the number of steps required to approximate a circle
|
||||
// (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm)
|
||||
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
|
||||
// will be relative to the size of the offset (delta). Obviously very
|
||||
//large offsets will almost always require much less precision.
|
||||
double arcTol = (arc_tolerance_ > floating_point_tolerance) ?
|
||||
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const;
|
||||
|
||||
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
|
||||
step_sin_ = std::sin(2 * PI / steps_per_360);
|
||||
step_cos_ = std::cos(2 * PI / steps_per_360);
|
||||
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
|
||||
steps_per_rad_ = steps_per_360 / (2 * PI);
|
||||
}
|
||||
|
||||
//double min_area = PI * Sqr(group_delta_);
|
||||
Paths64::const_iterator path_in_it = group.paths_in.cbegin();
|
||||
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
|
||||
{
|
||||
Path64::size_type pathLen = path_in_it->size();
|
||||
path_out.clear();
|
||||
|
||||
if (pathLen == 1) // single point
|
||||
{
|
||||
if (deltaCallback64_)
|
||||
{
|
||||
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
|
||||
if (group.is_reversed) group_delta_ = -group_delta_;
|
||||
abs_delta = std::fabs(group_delta_);
|
||||
}
|
||||
|
||||
if (group_delta_ < 1) continue;
|
||||
const Point64& pt = (*path_in_it)[0];
|
||||
//single vertex so build a circle or square ...
|
||||
if (group.join_type == JoinType::Round)
|
||||
{
|
||||
double radius = abs_delta;
|
||||
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
|
||||
path_out = Ellipse(pt, radius, radius, steps);
|
||||
#ifdef USINGZ
|
||||
for (auto& p : path_out) p.z = pt.z;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
int d = (int)std::ceil(abs_delta);
|
||||
Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
|
||||
path_out = r.AsPath();
|
||||
#ifdef USINGZ
|
||||
for (auto& p : path_out) p.z = pt.z;
|
||||
#endif
|
||||
}
|
||||
|
||||
solution->emplace_back(path_out);
|
||||
continue;
|
||||
} // end of offsetting a single point
|
||||
|
||||
if ((pathLen == 2) && (group.end_type == EndType::Joined))
|
||||
end_type_ = (group.join_type == JoinType::Round) ?
|
||||
EndType::Round :
|
||||
EndType::Square;
|
||||
|
||||
BuildNormals(*path_in_it);
|
||||
if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it);
|
||||
else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
|
||||
else OffsetOpenPath(group, *path_in_it);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
|
||||
const Point64& bot2, const Point64& top2, Point64& ip)
|
||||
{
|
||||
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
|
||||
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
|
||||
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
|
||||
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t ClipperOffset::CalcSolutionCapacity()
|
||||
{
|
||||
size_t result = 0;
|
||||
for (const Group& g : groups_)
|
||||
result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ClipperOffset::CheckReverseOrientation()
|
||||
{
|
||||
// nb: this assumes there's consistency in orientation between groups
|
||||
bool is_reversed_orientation = false;
|
||||
for (const Group& g : groups_)
|
||||
if (g.end_type == EndType::Polygon)
|
||||
{
|
||||
is_reversed_orientation = g.is_reversed;
|
||||
break;
|
||||
}
|
||||
return is_reversed_orientation;
|
||||
}
|
||||
|
||||
void ClipperOffset::ExecuteInternal(double delta)
|
||||
{
|
||||
error_code_ = 0;
|
||||
if (groups_.size() == 0) return;
|
||||
solution->reserve(CalcSolutionCapacity());
|
||||
|
||||
if (std::abs(delta) < 0.5) // ie: offset is insignificant
|
||||
{
|
||||
Paths64::size_type sol_size = 0;
|
||||
for (const Group& group : groups_) sol_size += group.paths_in.size();
|
||||
solution->reserve(sol_size);
|
||||
for (const Group& group : groups_)
|
||||
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
temp_lim_ = (miter_limit_ <= 1) ?
|
||||
2.0 :
|
||||
2.0 / (miter_limit_ * miter_limit_);
|
||||
|
||||
delta_ = delta;
|
||||
std::vector<Group>::iterator git;
|
||||
for (git = groups_.begin(); git != groups_.end(); ++git)
|
||||
{
|
||||
DoGroupOffset(*git);
|
||||
if (!error_code_) continue; // all OK
|
||||
solution->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (!solution->size()) return;
|
||||
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
//clean up self-intersections ...
|
||||
Clipper64 c;
|
||||
c.PreserveCollinear(false);
|
||||
//the solution should retain the orientation of the input
|
||||
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
||||
#ifdef USINGZ
|
||||
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3,
|
||||
std::placeholders::_4, std::placeholders::_5);
|
||||
c.SetZCallback(fp);
|
||||
#endif
|
||||
c.AddSubject(*solution);
|
||||
if (solution_tree)
|
||||
{
|
||||
if (paths_reversed)
|
||||
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
|
||||
else
|
||||
c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (paths_reversed)
|
||||
c.Execute(ClipType::Union, FillRule::Negative, *solution);
|
||||
else
|
||||
c.Execute(ClipType::Union, FillRule::Positive, *solution);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipperOffset::Execute(double delta, Paths64& paths64)
|
||||
{
|
||||
paths64.clear();
|
||||
solution = &paths64;
|
||||
solution_tree = nullptr;
|
||||
ExecuteInternal(delta);
|
||||
}
|
||||
|
||||
|
||||
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
|
||||
{
|
||||
polytree.Clear();
|
||||
solution_tree = &polytree;
|
||||
solution = new Paths64();
|
||||
ExecuteInternal(delta);
|
||||
delete solution;
|
||||
solution = nullptr;
|
||||
}
|
||||
|
||||
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
|
||||
{
|
||||
deltaCallback64_ = delta_cb;
|
||||
Execute(1.0, paths);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
|
||||
// Enable the Z coordinate support.
|
||||
#define USINGZ
|
||||
|
||||
// and let it compile
|
||||
#include "clipper.engine.cpp"
|
||||
#include "clipper.offset.cpp"
|
||||
#include "clipper.rectclip.cpp"
|
||||
+104
-3
@@ -12,6 +12,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
@@ -134,6 +135,25 @@ void AppConfig::set_defaults()
|
||||
if (!get("use_legacy_opengl").empty())
|
||||
erase("app", "use_legacy_opengl");
|
||||
|
||||
// Migrate legacy_networking boolean to network_plugin_version string
|
||||
std::string legacy_networking = get("legacy_networking");
|
||||
std::string network_version = get("network_plugin_version");
|
||||
|
||||
if (!legacy_networking.empty()) {
|
||||
// Old legacy_networking setting exists - migrate it
|
||||
bool was_legacy = (legacy_networking == "true" || legacy_networking == "1");
|
||||
|
||||
if (was_legacy && network_version.empty()) {
|
||||
// User had legacy mode enabled - set to legacy version number
|
||||
BOOST_LOG_TRIVIAL(info) << "Migrating legacy_networking=true to network_plugin_version=01.10.01.01";
|
||||
set_network_plugin_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||
}
|
||||
// Note: If was_legacy=false, we leave the version empty and let the GUI layer set it to the latest version
|
||||
|
||||
// Remove the old setting
|
||||
erase("app", "legacy_networking");
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (get("use_retina_opengl").empty())
|
||||
set_bool("use_retina_opengl", true);
|
||||
@@ -280,9 +300,6 @@ void AppConfig::set_defaults()
|
||||
if (get("allow_abnormal_storage").empty()) {
|
||||
set_bool("allow_abnormal_storage", false);
|
||||
}
|
||||
if (get("legacy_networking").empty()) {
|
||||
set_bool("legacy_networking", false);
|
||||
}
|
||||
|
||||
if(get("check_stable_update_only").empty()) {
|
||||
set_bool("check_stable_update_only", false);
|
||||
@@ -317,10 +334,18 @@ void AppConfig::set_defaults()
|
||||
set("auto_calculate_flush","all");
|
||||
}
|
||||
|
||||
if (get("show_canvas_zoom_button").empty()) {
|
||||
set_bool("show_canvas_zoom_button", true);
|
||||
}
|
||||
|
||||
if (get("remember_printer_config").empty()) {
|
||||
set_bool("remember_printer_config", true);
|
||||
}
|
||||
|
||||
if (get("group_filament_presets").empty()) {
|
||||
set("group_filament_presets", "1"); // All "0" / None "1" / By Type "2" / By Vendor "3"
|
||||
}
|
||||
|
||||
if (get("enable_high_low_temp_mixed_printing").empty()){
|
||||
set_bool("enable_high_low_temp_mixed_printing", false);
|
||||
}
|
||||
@@ -1435,6 +1460,82 @@ std::string AppConfig::get_nozzle_volume_types_from_config(const std::string& pr
|
||||
return nozzle_volume_types;
|
||||
}
|
||||
|
||||
std::string AppConfig::get_network_plugin_version() const
|
||||
{
|
||||
return get(SETTING_NETWORK_PLUGIN_VERSION);
|
||||
}
|
||||
|
||||
void AppConfig::set_network_plugin_version(const std::string& version)
|
||||
{
|
||||
set(SETTING_NETWORK_PLUGIN_VERSION, version);
|
||||
}
|
||||
|
||||
std::vector<std::string> AppConfig::get_skipped_network_versions() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string skipped = get(SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS);
|
||||
if (skipped.empty())
|
||||
return result;
|
||||
|
||||
std::stringstream ss(skipped);
|
||||
std::string version;
|
||||
while (std::getline(ss, version, ';')) {
|
||||
if (!version.empty())
|
||||
result.push_back(version);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppConfig::add_skipped_network_version(const std::string& version)
|
||||
{
|
||||
auto skipped = get_skipped_network_versions();
|
||||
if (std::find(skipped.begin(), skipped.end(), version) == skipped.end()) {
|
||||
skipped.push_back(version);
|
||||
std::string joined;
|
||||
for (size_t i = 0; i < skipped.size(); ++i) {
|
||||
if (i > 0) joined += ";";
|
||||
joined += skipped[i];
|
||||
}
|
||||
set(SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS, joined);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppConfig::is_network_version_skipped(const std::string& version) const
|
||||
{
|
||||
auto skipped = get_skipped_network_versions();
|
||||
return std::find(skipped.begin(), skipped.end(), version) != skipped.end();
|
||||
}
|
||||
|
||||
void AppConfig::clear_skipped_network_versions()
|
||||
{
|
||||
set(SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS, "");
|
||||
}
|
||||
|
||||
bool AppConfig::is_network_update_prompt_disabled() const
|
||||
{
|
||||
return get_bool(SETTING_NETWORK_PLUGIN_UPDATE_DISABLED);
|
||||
}
|
||||
|
||||
void AppConfig::set_network_update_prompt_disabled(bool disabled)
|
||||
{
|
||||
set_bool(SETTING_NETWORK_PLUGIN_UPDATE_DISABLED, disabled);
|
||||
}
|
||||
|
||||
bool AppConfig::should_remind_network_update_later() const
|
||||
{
|
||||
return get_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER);
|
||||
}
|
||||
|
||||
void AppConfig::set_remind_network_update_later(bool remind)
|
||||
{
|
||||
set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, remind);
|
||||
}
|
||||
|
||||
void AppConfig::clear_remind_network_update_later()
|
||||
{
|
||||
set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, false);
|
||||
}
|
||||
|
||||
void AppConfig::reset_selections()
|
||||
{
|
||||
auto it = m_storage.find("presets");
|
||||
|
||||
@@ -24,6 +24,12 @@ using namespace nlohmann;
|
||||
#define OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK "always_ask"
|
||||
#define OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY "load_geometry_only"
|
||||
|
||||
#define SETTING_NETWORK_PLUGIN_VERSION "network_plugin_version"
|
||||
#define SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS "network_plugin_skipped_versions"
|
||||
#define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled"
|
||||
#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later"
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01"
|
||||
|
||||
#define SUPPORT_DARK_MODE
|
||||
//#define _MSW_DARK_MODE
|
||||
|
||||
@@ -347,6 +353,21 @@ public:
|
||||
static const std::string SECTION_MATERIALS;
|
||||
static const std::string SECTION_EMBOSS_STYLE;
|
||||
|
||||
std::string get_network_plugin_version() const;
|
||||
void set_network_plugin_version(const std::string& version);
|
||||
|
||||
std::vector<std::string> get_skipped_network_versions() const;
|
||||
void add_skipped_network_version(const std::string& version);
|
||||
bool is_network_version_skipped(const std::string& version) const;
|
||||
void clear_skipped_network_versions();
|
||||
|
||||
bool is_network_update_prompt_disabled() const;
|
||||
void set_network_update_prompt_disabled(bool disabled);
|
||||
|
||||
bool should_remind_network_update_later() const;
|
||||
void set_remind_network_update_later(bool remind);
|
||||
void clear_remind_network_update_later();
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const
|
||||
|
||||
+165
-1004
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,11 @@ Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const Container& in)
|
||||
return out;
|
||||
}
|
||||
|
||||
Clipper2Lib::Paths64 Slic3rPolylines_to_Paths64(const Polylines& in)
|
||||
{
|
||||
return Slic3rPoints_to_Paths64(in);
|
||||
}
|
||||
|
||||
Points Path64ToPoints(const Clipper2Lib::Path64& path64)
|
||||
{
|
||||
Points points;
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "clipper2/clipper.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Clipper2Lib::Paths64 Slic3rPolylines_to_Paths64(const Slic3r::Polylines& in);
|
||||
Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in);
|
||||
Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip);
|
||||
Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip);
|
||||
ExPolygons union_ex_2(const Polygons &expolygons);
|
||||
|
||||
@@ -162,8 +162,8 @@ void fuzzy_extrusion_line(Arachne::ExtrusionJunctions& ext_lines, coordf_t slice
|
||||
}
|
||||
|
||||
if (ext_lines.back().p == ext_lines.front().p) { // Connect endpoints.
|
||||
out.front().p = out.back().p;
|
||||
out.front().w = out.back().w;
|
||||
out.back().p = out.front().p;
|
||||
out.back().w = out.front().w;
|
||||
}
|
||||
|
||||
if (out.size() >= 3)
|
||||
|
||||
@@ -200,6 +200,10 @@ void Fill3DHoneycomb::_fill_surface_single(
|
||||
if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle);
|
||||
BoundingBox bb = expolygon.contour.bounding_box();
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
coord_t expand = 5 * (scale_(this->spacing));
|
||||
bb.offset(expand);
|
||||
|
||||
// Note: with equally-scaled X/Y/Z, the pattern will create a vertically-stretched
|
||||
// truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2)
|
||||
coordf_t zScale = sqrt(2);
|
||||
|
||||
@@ -1371,42 +1371,47 @@ void Filler::_fill_surface_single(
|
||||
all_polylines.reserve(lines.size());
|
||||
std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; });
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(all_polylines, params, spacing);
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(all_polylines, params, spacing);
|
||||
|
||||
// Crop all polylines
|
||||
all_polylines = intersection_pl(std::move(all_polylines), expolygon);
|
||||
#endif
|
||||
}
|
||||
|
||||
// After intersection_pl some polylines with only one line are split into more lines
|
||||
for (Polyline &polyline : all_polylines) {
|
||||
//FIXME assert that all the points are collinear and in between the start and end point.
|
||||
if (polyline.points.size() > 2)
|
||||
polyline.points.erase(polyline.points.begin() + 1, polyline.points.end() - 1);
|
||||
}
|
||||
// assert(has_no_collinear_lines(all_polylines));
|
||||
if (params.multiline == 1) {
|
||||
// After intersection_pl some polylines with only one line are split into more lines
|
||||
for (Polyline& polyline : all_polylines) {
|
||||
// FIXME assert that all the points are collinear and in between the start and end point.
|
||||
if (polyline.points.size() > 2)
|
||||
polyline.points.erase(polyline.points.begin() + 1, polyline.points.end() - 1);
|
||||
}
|
||||
// assert(has_no_collinear_lines(all_polylines));
|
||||
|
||||
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++));
|
||||
}
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++));
|
||||
}
|
||||
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
|
||||
|
||||
const auto hook_length = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length)));
|
||||
const auto hook_length_max = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length_max)));
|
||||
const auto hook_length = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length)));
|
||||
const auto hook_length_max = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length_max)));
|
||||
|
||||
Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length, hook_length_max) : std::move(all_polylines);
|
||||
|
||||
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_infill_lines_to_svg(expolygon, all_polylines_with_hooks, debug_out_path("FillAdaptive-hooks-%d.svg", iRun++));
|
||||
}
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_infill_lines_to_svg(expolygon, all_polylines_with_hooks, debug_out_path("FillAdaptive-hooks-%d.svg", iRun++));
|
||||
}
|
||||
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
|
||||
|
||||
chain_or_connect_infill(std::move(all_polylines_with_hooks), expolygon, polylines_out, this->spacing, params);
|
||||
chain_or_connect_infill(std::move(all_polylines_with_hooks), expolygon, polylines_out, this->spacing, params);
|
||||
} else {
|
||||
// if multiline is > 1 infill is ready to connect
|
||||
chain_or_connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params);
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
|
||||
{
|
||||
@@ -1443,6 +1448,17 @@ static std::vector<CubeProperties> make_cubes_properties(double max_cube_edge_le
|
||||
if (edge_length > max_cube_edge_length)
|
||||
break;
|
||||
}
|
||||
// Orca: Ensure at least 2 levels so build_octree() will insert triangles.
|
||||
// Fixes scenario where adaptive fill is disconnected from walls on low densities
|
||||
if (cubes_properties.size() == 1) {
|
||||
CubeProperties p = cubes_properties.back();
|
||||
p.edge_length *= 2.0;
|
||||
p.height = p.edge_length * sqrt(3);
|
||||
p.diagonal_length = p.edge_length * sqrt(2);
|
||||
p.line_z_distance = p.edge_length / sqrt(3);
|
||||
p.line_xy_distance = p.edge_length / sqrt(6);
|
||||
cubes_properties.push_back(p);
|
||||
}
|
||||
return cubes_properties;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Clipper2Utils.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Geometry/Circle.hpp"
|
||||
@@ -1706,6 +1707,12 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
||||
size_t polyline_idx1 = get_and_update_merged_with(((cp1 - graph.map_infill_end_point_to_boundary.data()) / 2));
|
||||
size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2));
|
||||
const Points &contour = graph.boundary[cp1->contour_idx];
|
||||
|
||||
// Orca: If multiline infill is requested, skip connections that are too short.
|
||||
if (params.multiline > 1 && arc.arc_length < scale_(spacing) * params.multiline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<double> &contour_params = graph.boundary_params[cp1->contour_idx];
|
||||
if (polyline_idx1 != polyline_idx2) {
|
||||
Polyline &polyline1 = infill_ordered[polyline_idx1];
|
||||
@@ -2699,60 +2706,77 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boun
|
||||
connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params);
|
||||
}
|
||||
|
||||
//Fill Multiline
|
||||
// Fill Multiline -Clipper2 version
|
||||
void multiline_fill(Polylines& polylines, const FillParams& params, float spacing)
|
||||
{
|
||||
if (params.multiline > 1) {
|
||||
const int n_lines = params.multiline;
|
||||
const int n_polylines = static_cast<int>(polylines.size());
|
||||
Polylines all_polylines;
|
||||
all_polylines.reserve(n_lines * n_polylines);
|
||||
if (params.multiline <= 1)
|
||||
return;
|
||||
|
||||
const float center = (n_lines - 1) / 2.0f;
|
||||
const int n_lines = params.multiline;
|
||||
const int n_polylines = static_cast<int>(polylines.size());
|
||||
Polylines all_polylines;
|
||||
all_polylines.reserve(n_lines * n_polylines);
|
||||
|
||||
for (int line = 0; line < n_lines; ++line) {
|
||||
float offset = scale_((static_cast<float>(line) - center) * spacing);
|
||||
// Remove invalid polylines
|
||||
polylines.erase(std::remove_if(polylines.begin(), polylines.end(),
|
||||
[](const Polyline& p) { return p.size() < 2; }),
|
||||
polylines.end());
|
||||
|
||||
for (const Polyline& pl : polylines) {
|
||||
const size_t n = pl.points.size();
|
||||
if (n < 2) {
|
||||
all_polylines.emplace_back(pl);
|
||||
continue;
|
||||
}
|
||||
if (polylines.empty())
|
||||
return;
|
||||
// Convert source polylines to Clipper2 paths
|
||||
Clipper2Lib::Paths64 subject_paths = Slic3rPolylines_to_Paths64(polylines);
|
||||
|
||||
Points new_points;
|
||||
new_points.reserve(n);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
Vec2f tangent;
|
||||
// For the first and last point, if the polyline is a
|
||||
// closed loop, get the tangent from the points on either
|
||||
// side of the join, otherwise just use the first or last
|
||||
// line.
|
||||
if (i == 0) {
|
||||
if (pl.points[0] == pl.points[n-1]) {
|
||||
tangent = (pl.points[1] - pl.points[n-2]).template cast<float>().normalized();
|
||||
} else {
|
||||
tangent = (pl.points[1] - pl.points[0]).template cast<float>().normalized();
|
||||
}
|
||||
} else if (i == n - 1) {
|
||||
if (pl.points[0] == pl.points[n-1]) {
|
||||
tangent = (pl.points[1] - pl.points[n-2]).template cast<float>().normalized();
|
||||
} else {
|
||||
tangent = (pl.points[n-1] - pl.points[n-2]).template cast<float>().normalized();
|
||||
}
|
||||
} else
|
||||
tangent = (pl.points[i+1] - pl.points[i-1]).template cast<float>().normalized();
|
||||
Vec2f normal(-tangent.y(), tangent.x());
|
||||
const double miter_limit = 2.0;
|
||||
const int rings = n_lines / 2;
|
||||
|
||||
Point p = pl.points[i] + (normal * offset).template cast<coord_t>();
|
||||
new_points.push_back(p);
|
||||
}
|
||||
// Compute offsets (in units of spacing)
|
||||
std::vector<double> offsets;
|
||||
offsets.reserve(n_lines);
|
||||
|
||||
all_polylines.emplace_back(std::move(new_points));
|
||||
}
|
||||
}
|
||||
polylines = std::move(all_polylines);
|
||||
if (n_lines % 2 != 0) {
|
||||
// Odd: center line at offset = 0
|
||||
offsets.push_back(0.0);
|
||||
|
||||
for (int i = 1; i <= rings; ++i)
|
||||
offsets.push_back(i * spacing);
|
||||
} else {
|
||||
// Even: no center, start at 0.5 * spacing
|
||||
double start = 0.5 * spacing;
|
||||
for (int i = 0; i < rings; ++i)
|
||||
offsets.push_back(start + i * spacing);
|
||||
}
|
||||
|
||||
// Process each offset
|
||||
Clipper2Lib::ClipperOffset offsetter(miter_limit);
|
||||
offsetter.AddPaths(subject_paths, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Round);
|
||||
|
||||
for (double t : offsets) {
|
||||
if (t == 0.0) {
|
||||
// Center line (only applies when n_lines is odd)
|
||||
all_polylines.insert(all_polylines.end(), polylines.begin(), polylines.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
// ClipperOffset with current offset distance (union is not needed here)
|
||||
Clipper2Lib::Paths64 offset_paths;
|
||||
offsetter.Execute(scale_(t), offset_paths);
|
||||
if (offset_paths.empty())
|
||||
continue;
|
||||
|
||||
// Convert back to polylines
|
||||
Polylines new_polylines = Paths64_to_polylines(offset_paths);
|
||||
|
||||
for (Polyline& pl : new_polylines) {
|
||||
if (pl.points.size() < 3)
|
||||
continue;
|
||||
if (pl.points.front() != pl.points.back())
|
||||
pl.points.push_back(pl.points.front());
|
||||
all_polylines.emplace_back(std::move(pl));
|
||||
}
|
||||
}
|
||||
|
||||
polylines = std::move(all_polylines);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -19,7 +19,7 @@ void FillConcentric::_fill_surface_single(
|
||||
// no rotation is supported for this infill pattern
|
||||
BoundingBox bounding_box = expolygon.contour.bounding_box();
|
||||
|
||||
coord_t min_spacing = scale_(this->spacing);
|
||||
coord_t min_spacing = scale_(this->spacing) * params.multiline;
|
||||
coord_t distance = coord_t(min_spacing / params.density);
|
||||
|
||||
if (params.density > 0.9999f && !params.dont_adjust) {
|
||||
@@ -27,8 +27,12 @@ void FillConcentric::_fill_surface_single(
|
||||
this->spacing = unscale<double>(distance);
|
||||
}
|
||||
|
||||
Polygons loops = to_polygons(expolygon);
|
||||
ExPolygons last { std::move(expolygon) };
|
||||
// Contract surface polygon by half line width to avoid excesive overlap with perimeter
|
||||
ExPolygons contracted = offset_ex(expolygon, -float(scale_(0.5 * (params.multiline - 1) * this->spacing )));
|
||||
|
||||
Polygons loops = to_polygons(contracted);
|
||||
|
||||
ExPolygons last { std::move(contracted) };
|
||||
while (! last.empty()) {
|
||||
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
|
||||
append(loops, to_polygons(last));
|
||||
@@ -46,6 +50,9 @@ void FillConcentric::_fill_surface_single(
|
||||
last_pos = polylines_out.back().last_point();
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(polylines_out, params, spacing);
|
||||
|
||||
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
// Keep valid paths only.
|
||||
size_t j = iPathFirst;
|
||||
|
||||
@@ -74,7 +74,7 @@ void FillHoneycomb::_fill_surface_single(
|
||||
}
|
||||
}
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(all_polylines, params, 1.1 * spacing);
|
||||
multiline_fill(all_polylines, params, spacing);
|
||||
|
||||
all_polylines = intersection_pl(std::move(all_polylines), expolygon);
|
||||
chain_or_connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params);
|
||||
|
||||
@@ -81,6 +81,9 @@ void FillPlanePath::_fill_surface_single(
|
||||
|
||||
BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON);
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
snug_bounding_box.offset(scale_(this->spacing)*params.multiline);
|
||||
|
||||
// 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.
|
||||
@@ -97,7 +100,7 @@ void FillPlanePath::_fill_surface_single(
|
||||
|
||||
Polyline polyline;
|
||||
{
|
||||
auto distance_between_lines = scaled<double>(this->spacing) / params.density;
|
||||
auto distance_between_lines = scaled<double>(this->spacing) * params.multiline / 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));
|
||||
@@ -117,8 +120,13 @@ void FillPlanePath::_fill_surface_single(
|
||||
}
|
||||
}
|
||||
|
||||
Polylines polylines = {polyline};
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(polylines, params, spacing);
|
||||
|
||||
if (polyline.size() >= 2) {
|
||||
Polylines polylines = intersection_pl(polyline, expolygon);
|
||||
polylines = intersection_pl(std::move(polylines), expolygon);
|
||||
if (!polylines.empty()) {
|
||||
Polylines chained;
|
||||
if (params.dont_connect() || params.density > 0.5) {
|
||||
|
||||
@@ -3000,7 +3000,7 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar
|
||||
params.density /= double(sweep_params.size());
|
||||
assert(params.density > 0.0001f && params.density <= 1.f);
|
||||
|
||||
ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap - 0.5 * this->spacing)));
|
||||
ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap + 0.5 * params.multiline * this->spacing)));//increase offset to crop infill lines when using multiline infill
|
||||
if (poly_with_offset_base.n_contours == 0)
|
||||
// Not a single infill line fits.
|
||||
return true;
|
||||
@@ -3012,19 +3012,24 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar
|
||||
for (const SweepParams &sweep : sweep_params) {
|
||||
// Rotate polygons so that we can work with vertical lines here
|
||||
float angle = rotate_vector.first + sweep.angle_base;
|
||||
//Fill Multiline
|
||||
for (int i = 0; i < params.multiline; ++i) {
|
||||
coord_t group_offset = i * line_spacing;
|
||||
coord_t internal_offset = (i - (params.multiline - 1) / 2.0f) * line_width;
|
||||
coord_t total_offset = group_offset + internal_offset;
|
||||
coord_t pattern_shift = scale_(sweep.pattern_shift + unscale_(total_offset));
|
||||
|
||||
make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, -angle), rotate_vector.second.rotated(-angle), angle,
|
||||
line_width + coord_t(SCALED_EPSILON), line_spacing, pattern_shift, fill_lines);
|
||||
}
|
||||
make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, -angle), rotate_vector.second.rotated(-angle), angle,
|
||||
line_width + coord_t(SCALED_EPSILON), line_spacing, coord_t(scale_(sweep.pattern_shift)), fill_lines);
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(fill_lines, params, spacing);
|
||||
|
||||
// Contract surface polygon by half line width to avoid excesive overlap with perimeter
|
||||
ExPolygons contracted = offset_ex(surface->expolygon, -float(scale_(0.5 * this->spacing)));
|
||||
|
||||
// if contraction results in empty polygon, use original surface
|
||||
const ExPolygon &intersection_surface = contracted.empty() ? surface->expolygon : contracted.front();
|
||||
|
||||
if ((params.pattern == ipLateralLattice || params.pattern == ipLateralHoneycomb ) && params.multiline >1 )
|
||||
// Intersect polylines with perimeter
|
||||
fill_lines = intersection_pl(std::move(fill_lines), intersection_surface);
|
||||
|
||||
if ((params.pattern == ipLateralLattice || params.pattern == ipLateralHoneycomb ) && params.multiline >1 )
|
||||
remove_overlapped(fill_lines, line_width);
|
||||
|
||||
if (!fill_lines.empty()) {
|
||||
@@ -3033,7 +3038,260 @@ if ((params.pattern == ipLateralLattice || params.pattern == ipLateralHoneycomb
|
||||
fill_lines = chain_polylines(std::move(fill_lines));
|
||||
append(polylines_out, std::move(fill_lines));
|
||||
} else
|
||||
connect_infill(std::move(fill_lines), poly_with_offset_base.polygons_outer, get_extents(surface->expolygon.contour), polylines_out, this->spacing, params);
|
||||
connect_infill(std::move(fill_lines), intersection_surface, polylines_out, this->spacing, params);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FillRectilinear::fill_surface_trapezoidal(
|
||||
const Surface* surface,
|
||||
FillParams params,
|
||||
const std::initializer_list<SweepParams>& sweep_params,
|
||||
Polylines& polylines_out,
|
||||
int Pattern_type) // 0=grid, 1=triangular
|
||||
{
|
||||
assert(params.multiline > 1);
|
||||
|
||||
Polylines polylines;
|
||||
|
||||
// Common parameters
|
||||
const coord_t d1 = coord_t(scale_(this->spacing)) * params.multiline; // Infill total wall thickness
|
||||
|
||||
// Pattern-specific parameters
|
||||
coord_t period;
|
||||
double base_angle;
|
||||
std::pair<double, Point> rotate_vector = this->_infill_direction(surface);
|
||||
|
||||
if (Pattern_type == 0) {
|
||||
// Grid pattern parameters
|
||||
period = coord_t((2.0 * d1 / params.density) * std::sqrt(2.0));
|
||||
base_angle = rotate_vector.first + M_PI_4; // 45
|
||||
} else {
|
||||
// Triangular pattern parameters
|
||||
period = coord_t(( 2.0 * d1 / params.density) * std::sqrt(3.0));
|
||||
base_angle = rotate_vector.first + M_PI_2; //90
|
||||
}
|
||||
|
||||
// Obtain the expolygon and rotate to align with pattern base angle
|
||||
ExPolygon expolygon = surface->expolygon;
|
||||
if (std::abs(base_angle) >= EPSILON) {
|
||||
expolygon.rotate(-base_angle, rotate_vector.second);
|
||||
}
|
||||
|
||||
// Use extended object bounding box for consistent pattern across layers
|
||||
BoundingBox bb = this->extended_object_bounding_box();
|
||||
|
||||
switch (Pattern_type) {
|
||||
case 0: // Grid / Trapezoidal
|
||||
{
|
||||
// Generate a non-crossing trapezoidal pattern to avoid overextrusion at intersections when `multiline > 1`.
|
||||
// P1--P2
|
||||
// / \
|
||||
// P0/ \P3__P4
|
||||
//
|
||||
// P1x-P2x=P3x-P4x=d1
|
||||
// P0y-P1y=P2y-P3y=d2
|
||||
|
||||
const coord_t d2 = coord_t(0.5 * period - d1);
|
||||
|
||||
// Align bounding box to the grid
|
||||
bb.merge(align_to_grid(bb.min, Point(period, period)));
|
||||
const coord_t xmin = bb.min.x();
|
||||
const coord_t xmax = bb.max.x();
|
||||
const coord_t ymin = bb.min.y();
|
||||
const coord_t ymax = bb.max.y();
|
||||
|
||||
// Create the two base row patterns once
|
||||
Polyline base_row_normal;
|
||||
base_row_normal.points.reserve(((xmax - xmin) / period + 1) * 5); // 5 points per trapezoid
|
||||
Polyline base_row_flipped;
|
||||
base_row_flipped.points.reserve(((xmax - xmin) / period + 1) * 5); // 5 points per trapezoid
|
||||
|
||||
// Build complete rows from xmin to xmax
|
||||
for (coord_t x = xmin; x < xmax; x += period) {
|
||||
// Normal row
|
||||
base_row_normal.points.emplace_back(Point(x, d1 / 2)); // P0
|
||||
base_row_normal.points.emplace_back(Point(x + d1, d1 / 2)); // P1
|
||||
base_row_normal.points.emplace_back(Point(x + d1 + d2, d1 / 2 + d2)); // P2
|
||||
base_row_normal.points.emplace_back(Point(x + 2 * d1 + d2, d1 / 2 + d2)); // P3
|
||||
base_row_normal.points.emplace_back(Point(x + 2 * d1 + 2 * d2, d1 / 2)); // P4
|
||||
}
|
||||
|
||||
// Flipped row (mirrored vertically)
|
||||
base_row_flipped.points = base_row_normal.points;
|
||||
for (auto& p : base_row_flipped.points) {
|
||||
p.y() = period / 2 - p.y();
|
||||
}
|
||||
|
||||
// Pre-allocate polylines
|
||||
const size_t estimated_rows = ((ymax - ymin) / (period / 2) + 1);
|
||||
polylines.reserve(estimated_rows);
|
||||
|
||||
bool flip_vertical = false;
|
||||
|
||||
// Now just copy and translate vertically
|
||||
for (coord_t y = ymin; y < ymax; y += period / 2) {
|
||||
Polyline pl_row = flip_vertical ? base_row_flipped : base_row_normal;
|
||||
|
||||
// Translate all points vertically
|
||||
for (Point& p : pl_row.points) {
|
||||
p.y() += y;
|
||||
}
|
||||
|
||||
polylines.emplace_back(std::move(pl_row));
|
||||
flip_vertical = !flip_vertical;
|
||||
}
|
||||
|
||||
// transpose points for odd layers
|
||||
if (layer_id % 2 == 1) {
|
||||
for (Polyline& pl : polylines) {
|
||||
for (Point& p : pl.points) {
|
||||
std::swap(p.x(), p.y());
|
||||
p.x() += d1 / 2;
|
||||
p.y() -= d1 / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: // Triangular
|
||||
{
|
||||
// Generate a non-crossing trapezoidal pattern with a base line below.
|
||||
// P1-P2
|
||||
// / \
|
||||
// P0/ \P3_P4
|
||||
// ----------------
|
||||
// P1x-P2x=P3x-P4x=d2
|
||||
// P0y-P1y=P2y-P3y=h-2d1
|
||||
//
|
||||
|
||||
// Triangular pattern density adjustment:
|
||||
const coord_t d2_tri = coord_t(2.0 / std::sqrt(3.0) * d1);
|
||||
const coord_t h = coord_t(0.5 * std::sqrt(3.0) * period); // height of triangle
|
||||
|
||||
// Align bounding box to the grid
|
||||
bb.merge(align_to_grid(bb.center(), Point(period,h)));
|
||||
const int layer_mod = layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
|
||||
const Point rotation_center = bb.center();
|
||||
const coord_t half_w = bb.size().x() / 2;
|
||||
const coord_t half_h = bb.size().y() / 2;
|
||||
|
||||
// Compute how many full periods fit in each direction
|
||||
const coord_t num_periods_x = coord_t(std::ceil(half_w / double(period)));
|
||||
coord_t num_periods_y =coord_t(std::ceil(half_h / double(h)));
|
||||
// Ensure an even number of rows so the pattern stays centered
|
||||
if ((num_periods_y % 2) != 0)
|
||||
++num_periods_y;
|
||||
|
||||
// Compute aligned limits (symmetric around the origin)
|
||||
const coord_t x_min_aligned = -num_periods_x * period;
|
||||
const coord_t x_max_aligned = num_periods_x * period;
|
||||
const coord_t y_min_aligned = -num_periods_y * h;
|
||||
const coord_t y_max_aligned = num_periods_y * h;
|
||||
|
||||
// Pre-allocate estimated number of polylines
|
||||
const size_t estimated_rows = (y_max_aligned - y_min_aligned) / h + 2;
|
||||
const size_t estimated_polylines = (estimated_rows + 1) * 2; // base line + trapezoid line per row
|
||||
polylines.reserve(estimated_polylines);
|
||||
|
||||
// Create the two base row templates once
|
||||
Polyline base_line_template;
|
||||
base_line_template.points.reserve(2); // 2 points for base line
|
||||
Polyline trapezoid_row_normal;
|
||||
trapezoid_row_normal.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 5); // 5 points per trapezoid
|
||||
Polyline trapezoid_row_shifted;
|
||||
trapezoid_row_shifted.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 5); // 5 points per trapezoid
|
||||
// Build base line template (from x_min_aligned to x_max_aligned)
|
||||
base_line_template.points.emplace_back(Point(x_min_aligned, 0));
|
||||
base_line_template.points.emplace_back(Point(x_max_aligned, 0));
|
||||
|
||||
// Build complete trapezoid rows once
|
||||
// Normal row (no shift)
|
||||
for (coord_t x = x_min_aligned; x < x_max_aligned; x += period) {
|
||||
trapezoid_row_normal.points.emplace_back(Point(x + d2_tri / 2, d1)); // P0
|
||||
trapezoid_row_normal.points.emplace_back(Point(x + period / 2 - d2_tri / 2, h - d1)); // P1
|
||||
trapezoid_row_normal.points.emplace_back(Point(x + period / 2 + d2_tri / 2, h - d1)); // P2
|
||||
trapezoid_row_normal.points.emplace_back(Point(x + period - d2_tri / 2, d1)); // P3
|
||||
trapezoid_row_normal.points.emplace_back(Point(x + period, d1)); // P4
|
||||
}
|
||||
|
||||
// Shifted row (mirrored vertically)
|
||||
trapezoid_row_shifted.points = trapezoid_row_normal.points;
|
||||
for (auto& p : trapezoid_row_shifted.points)
|
||||
p.y() = h - p.y();
|
||||
|
||||
bool shift_row = false;
|
||||
|
||||
// Generate pattern by copying and translating templates vertically
|
||||
for (coord_t y = y_min_aligned; y < y_max_aligned; y += h) {
|
||||
// Base line - copy and translate
|
||||
Polyline base_line = base_line_template;
|
||||
for (Point& p : base_line.points) {
|
||||
p.y() += y;
|
||||
}
|
||||
polylines.emplace_back(std::move(base_line));
|
||||
|
||||
// Trapezoid line - copy and translate the appropriate template
|
||||
Polyline trapezoid_line = shift_row ? trapezoid_row_shifted : trapezoid_row_normal;
|
||||
for (Point& p : trapezoid_line.points) {
|
||||
p.y() += y;
|
||||
}
|
||||
|
||||
if (!trapezoid_line.points.empty()) {
|
||||
polylines.emplace_back(std::move(trapezoid_line));
|
||||
}
|
||||
|
||||
shift_row = !shift_row;
|
||||
}
|
||||
|
||||
// Rotate around origin (0,0)
|
||||
if (layer_mod)
|
||||
for (auto& pl : polylines)
|
||||
pl.rotate(angle, Point(0,0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle unknown pattern type
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply multiline fill
|
||||
multiline_fill(polylines, params, spacing);
|
||||
|
||||
// Contract surface polygon by half line width to avoid excesive overlap with perimeter
|
||||
ExPolygons contracted = offset_ex(expolygon, -float(scale_(0.5 * this->spacing)));
|
||||
|
||||
// if contraction results in empty polygon, use original surface
|
||||
const ExPolygon &intersection_surface = contracted.empty() ? expolygon : contracted.front();
|
||||
|
||||
// Intersect polylines with offset expolygon
|
||||
polylines = intersection_pl(std::move(polylines), intersection_surface);
|
||||
|
||||
// Remove very short segments that may cause connection issues
|
||||
const double minlength = scale_(0.8 * this->spacing);
|
||||
if (minlength > 0 && !polylines.empty()) {
|
||||
polylines.erase(std::remove_if(polylines.begin(), polylines.end(),
|
||||
[minlength](const Polyline& pl) { return pl.length() < minlength; }),
|
||||
polylines.end());
|
||||
}
|
||||
|
||||
// Connect infill lines using offset expolygon
|
||||
int infill_start_idx = polylines_out.size();
|
||||
if (!polylines.empty()) {
|
||||
Slic3r::Fill::chain_or_connect_infill(std::move(polylines), intersection_surface, polylines_out, this->spacing, params);
|
||||
|
||||
// Rotate back the infill lines to original orientation
|
||||
if (std::abs(base_angle) >= EPSILON) {
|
||||
for (auto it = polylines_out.begin() + infill_start_idx; it != polylines_out.end(); ++it) {
|
||||
it->rotate(base_angle, rotate_vector.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -3077,15 +3335,27 @@ Polylines FillMonotonicLine::fill_surface(const Surface* surface, const FillPara
|
||||
Polylines FillGrid::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
Polylines polylines_out;
|
||||
if (! this->fill_surface_by_multilines(
|
||||
surface, params,
|
||||
{ { 0.f, 0.f }, { float(M_PI / 2.), 0.f } },
|
||||
polylines_out))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface() failed to fill a region.";
|
||||
|
||||
if (this->layer_id % 2 == 1)
|
||||
for (int i = 0; i < polylines_out.size(); i++)
|
||||
std::reverse(polylines_out[i].begin(), polylines_out[i].end());
|
||||
if (params.multiline > 1) {
|
||||
// Experimental trapezoidal grid
|
||||
if (!this->fill_surface_trapezoidal(
|
||||
surface, params,
|
||||
{ { 0.f, 0.f }, { float(M_PI / 2.), 0.f } },
|
||||
polylines_out,0))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface_trapezoidal() failed.";
|
||||
|
||||
} else {
|
||||
if (!this->fill_surface_by_multilines(
|
||||
surface, params,
|
||||
{ { 0.f, 0.f }, { float(M_PI / 2.), 0.f } },
|
||||
polylines_out))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface() failed to fill a region.";
|
||||
|
||||
|
||||
if (this->layer_id % 2 == 1)
|
||||
for (int i = 0; i < polylines_out.size(); i++)
|
||||
std::reverse(polylines_out[i].begin(), polylines_out[i].end());
|
||||
}
|
||||
return polylines_out;
|
||||
}
|
||||
|
||||
@@ -3108,12 +3378,23 @@ Polylines FillLateralLattice::fill_surface(const Surface *surface, const FillPar
|
||||
|
||||
Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams ¶ms){
|
||||
Polylines polylines_out;
|
||||
if (params.multiline > 1) {
|
||||
// Experimental trapezoidal grid
|
||||
if (!this->fill_surface_trapezoidal(
|
||||
surface, params,
|
||||
{ { 0.f, 0.f }, { float(M_PI / 2.), 0.f } },
|
||||
polylines_out,1))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface_trapezoidal() failed.";
|
||||
|
||||
} else {
|
||||
if (! this->fill_surface_by_multilines(
|
||||
surface, params,
|
||||
{ { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), 0. } },
|
||||
polylines_out))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillTriangles::fill_surface() failed to fill a region.";
|
||||
}
|
||||
return polylines_out;
|
||||
|
||||
}
|
||||
|
||||
Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
@@ -3144,8 +3425,8 @@ Polylines FillQuarterCubic::fill_surface(const Surface* surface, const FillParam
|
||||
using namespace boost::math::float_constants;
|
||||
Polylines polylines_out;
|
||||
|
||||
coord_t line_width = coord_t(scale_(this->spacing));
|
||||
coord_t period = coord_t(scale_(this->spacing) / params.density) * 4;
|
||||
coord_t line_width = coord_t(scale_(this->spacing)) * params.multiline;
|
||||
coord_t period = coord_t(scale_(this->spacing) *params.multiline / params.density) * 4;
|
||||
|
||||
// First half tetrahedral fill
|
||||
double pattern_z_shift = 0.0;
|
||||
|
||||
@@ -29,6 +29,7 @@ protected:
|
||||
float pattern_shift;
|
||||
};
|
||||
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
|
||||
bool fill_surface_trapezoidal(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out,int Pattern_type);
|
||||
|
||||
// The extended bounding box of the whole object that covers any rotation of every layer.
|
||||
BoundingBox extended_object_bounding_box() const;
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Slic3r {
|
||||
const int g_min_flush_volume_from_support = 700;
|
||||
const int g_flush_volume_to_support = 230;
|
||||
|
||||
const int g_max_flush_volume = 900;
|
||||
const int g_max_flush_volume = 20000; //Orca: increase limit to 20k vs 900 in upstream BBS code.
|
||||
// Retain a clamp to guard against extreme values entered in the UI
|
||||
|
||||
static float to_radians(float degree)
|
||||
{
|
||||
|
||||
+51
-43
@@ -2725,8 +2725,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
|
||||
auto probe_dist_x = std::max(1., m_config.bed_mesh_probe_distance.value.x());
|
||||
auto probe_dist_y = std::max(1., m_config.bed_mesh_probe_distance.value.y());
|
||||
int probe_count_x = std::max(3, (int) std::ceil(mesh_bbox.size().x() / probe_dist_x));
|
||||
int probe_count_y = std::max(3, (int) std::ceil(mesh_bbox.size().y() / probe_dist_y));
|
||||
int probe_count_x = std::max(3, (int) std::ceil(mesh_bbox.size().x() / probe_dist_x) + 1);
|
||||
int probe_count_y = std::max(3, (int) std::ceil(mesh_bbox.size().y() / probe_dist_y) + 1);
|
||||
auto bed_mesh_algo = "bicubic";
|
||||
if (probe_count_x * probe_count_y <= 6) { // lagrange needs up to a total of 6 mesh points
|
||||
bed_mesh_algo = "lagrange";
|
||||
@@ -3082,10 +3082,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
m_sorted_layer_filaments.emplace_back(lt.extruders);
|
||||
}
|
||||
|
||||
// Orca: finish tracking power lost recovery
|
||||
// Orca: disable power loss recovery if it was enabled earlier
|
||||
{
|
||||
if (m_second_layer_things_done && print.config().enable_power_loss_recovery.value == true) {
|
||||
file.write(m_writer.enable_power_loss_recovery(false));
|
||||
const auto plr_mode = print.config().enable_power_loss_recovery.value;
|
||||
if (m_second_layer_things_done && plr_mode == PowerLossRecoveryMode::Enable) {
|
||||
file.write(m_writer.enable_power_loss_recovery(PowerLossRecoveryMode::Disable));
|
||||
}
|
||||
}
|
||||
++ finished_objects;
|
||||
@@ -3163,9 +3164,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
m_sorted_layer_filaments.emplace_back(lt.extruders);
|
||||
}
|
||||
|
||||
// Orca: finish tracking power lost recovery
|
||||
if (m_second_layer_things_done && print.config().enable_power_loss_recovery.value == true) {
|
||||
file.write(m_writer.enable_power_loss_recovery(false));
|
||||
// Orca: disable power loss recovery
|
||||
if (m_second_layer_things_done && print.config().enable_power_loss_recovery.value == PowerLossRecoveryMode::Enable) {
|
||||
file.write(m_writer.enable_power_loss_recovery(PowerLossRecoveryMode::Disable));
|
||||
}
|
||||
if (m_wipe_tower)
|
||||
// Purge the extruder, pull out the active filament.
|
||||
@@ -4249,8 +4250,8 @@ LayerResult GCode::process_layer(
|
||||
bool is_multi_extruder = m_config.nozzle_diameter.size() > 1;
|
||||
|
||||
bool need_insert_timelapse_gcode_for_traditional = false;
|
||||
if (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) {
|
||||
need_insert_timelapse_gcode_for_traditional = ((is_i3_printer && !m_spiral_vase)|| is_multi_extruder);
|
||||
if ((!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) && (is_BBL_Printer() || !m_config.time_lapse_gcode.value.empty())) {
|
||||
need_insert_timelapse_gcode_for_traditional = ((is_i3_printer && !m_spiral_vase) || is_multi_extruder);
|
||||
}
|
||||
|
||||
bool has_insert_timelapse_gcode = false;
|
||||
@@ -4266,19 +4267,18 @@ LayerResult GCode::process_layer(
|
||||
gcode += this->change_layer(print_z); // this will increase m_layer_index
|
||||
m_layer = &layer;
|
||||
m_object_layer_over_raft = false;
|
||||
if(is_BBL_Printer()){
|
||||
} else {
|
||||
if (!m_config.time_lapse_gcode.value.empty()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
|
||||
gcode += this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(),
|
||||
&config) +
|
||||
"\n";
|
||||
}
|
||||
|
||||
if (!m_config.time_lapse_gcode.value.empty() && !is_BBL_Printer()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
|
||||
gcode += this->placeholder_parser_process("timelapse_gcode",
|
||||
print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config)
|
||||
+ "\n";
|
||||
}
|
||||
if (! m_config.layer_change_gcode.value.empty()) {
|
||||
|
||||
if (!m_config.layer_change_gcode.value.empty()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder)));
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
@@ -4380,10 +4380,9 @@ LayerResult GCode::process_layer(
|
||||
}
|
||||
|
||||
if (!first_layer && !m_second_layer_things_done) {
|
||||
// Orca: start tracking power lost recovery
|
||||
if (print.config().enable_power_loss_recovery.value == true) {
|
||||
gcode += m_writer.enable_power_loss_recovery(true);
|
||||
}
|
||||
// Orca: set power loss recovery
|
||||
const auto plr_mode = print.config().enable_power_loss_recovery.value;
|
||||
gcode += m_writer.enable_power_loss_recovery(plr_mode);
|
||||
|
||||
if (print.is_BBL_printer()) {
|
||||
// BBS: open first layer inspection at second layer
|
||||
@@ -4727,7 +4726,7 @@ LayerResult GCode::process_layer(
|
||||
|
||||
auto timelapse_pos=m_timelapse_pos_picker.pick_pos(ctx);
|
||||
|
||||
std::string timepals_gcode;
|
||||
std::string timelapse_gcode;
|
||||
if (!print.config().time_lapse_gcode.value.empty()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
@@ -4738,17 +4737,18 @@ LayerResult GCode::process_layer(
|
||||
config.set_key_value("timelapse_pos_x", new ConfigOptionInt((int)timelapse_pos.x()));
|
||||
config.set_key_value("timelapse_pos_y", new ConfigOptionInt((int)timelapse_pos.y()));
|
||||
config.set_key_value("has_timelapse_safe_pos", new ConfigOptionBool(timelapse_pos != DefaultTimelapsePos));
|
||||
timepals_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n";
|
||||
timelapse_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n";
|
||||
}
|
||||
if (!timepals_gcode.empty()) {
|
||||
m_writer.set_current_position_clear(false);
|
||||
|
||||
double temp_z_after_tool_change;
|
||||
if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_tool_change)) {
|
||||
Vec3d pos = m_writer.get_position();
|
||||
pos(2) = temp_z_after_tool_change;
|
||||
m_writer.set_position(pos);
|
||||
}
|
||||
if (!timelapse_gcode.empty()) {
|
||||
m_writer.set_current_position_clear(false);
|
||||
|
||||
double temp_z_after_tool_change;
|
||||
if (GCodeProcessor::get_last_z_from_gcode(timelapse_gcode, temp_z_after_tool_change)) {
|
||||
Vec3d pos = m_writer.get_position();
|
||||
pos(2) = temp_z_after_tool_change;
|
||||
m_writer.set_position(pos);
|
||||
}
|
||||
}
|
||||
|
||||
// (layer_object_label_ids.size() < 64) this restriction comes from _encode_label_ids_to_base64()
|
||||
@@ -4769,13 +4769,13 @@ LayerResult GCode::process_layer(
|
||||
std::string end_str = std::string("; object ids of this layer") + std::to_string(m_layer_index + 1) + (" end: ") + oss.str() + "\n";
|
||||
end_str += "M625\n";
|
||||
|
||||
timepals_gcode = start_str + timepals_gcode + end_str;
|
||||
timelapse_gcode = start_str + timelapse_gcode + end_str;
|
||||
}
|
||||
|
||||
return timepals_gcode;
|
||||
return timelapse_gcode;
|
||||
};
|
||||
|
||||
if (!need_insert_timelapse_gcode_for_traditional) { // Equivalent to the timelapse gcode placed in layer_change_gcode
|
||||
if (!need_insert_timelapse_gcode_for_traditional && is_BBL_Printer()) { // Equivalent to the timelapse gcode placed in layer_change_gcode
|
||||
if (FILAMENT_CONFIG(retract_when_changing_layer)) {
|
||||
gcode += this->retract(false, false, auto_lift_type, true);
|
||||
}
|
||||
@@ -4871,10 +4871,12 @@ LayerResult GCode::process_layer(
|
||||
|
||||
gcode_toolchange = this->set_extruder(extruder_id, print_z);
|
||||
}
|
||||
|
||||
if (!gcode_toolchange.empty()) {
|
||||
// Disable vase mode for layers that has toolchange
|
||||
result.spiral_vase_enable = false;
|
||||
}
|
||||
|
||||
gcode += std::move(gcode_toolchange);
|
||||
|
||||
// let analyzer tag generator aware of a role type change
|
||||
@@ -6182,10 +6184,16 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
);
|
||||
}
|
||||
|
||||
// if still in avoidance mode and under “max”, clamp to “min”
|
||||
if (m_resonance_avoidance
|
||||
&& speed <= m_config.max_resonance_avoidance_speed.value) {
|
||||
speed = std::min(speed, m_config.min_resonance_avoidance_speed.value);
|
||||
// if still in avoidance mode and under "max", adjust speed:
|
||||
// - speeds in lower half of range: clamp down to "min"
|
||||
// - speeds in upper half of range: boost up to "max"
|
||||
if (m_resonance_avoidance && speed < m_config.max_resonance_avoidance_speed.value) {
|
||||
if (speed < m_config.min_resonance_avoidance_speed.value +
|
||||
((m_config.max_resonance_avoidance_speed.value - m_config.min_resonance_avoidance_speed.value) / 2)) {
|
||||
speed = std::min(speed, m_config.min_resonance_avoidance_speed.value);
|
||||
} else {
|
||||
speed = m_config.max_resonance_avoidance_speed.value;
|
||||
}
|
||||
}
|
||||
|
||||
// reset flag for next segment
|
||||
|
||||
@@ -97,7 +97,8 @@ public:
|
||||
m_enable_timelapse_print(print_config.timelapse_type.value == TimelapseType::tlSmooth),
|
||||
m_enable_wrapping_detection(print_config.enable_wrapping_detection && (print_config.wrapping_exclude_area.values.size() > 2) && (slice_used_filaments.size() <= 1)),
|
||||
m_is_first_print(true),
|
||||
m_print_config(&print_config)
|
||||
m_print_config(&print_config),
|
||||
m_last_wipe_tower_print_z(print_config.z_offset.value)
|
||||
{
|
||||
// initialize with the extruder offset of master extruder id
|
||||
m_extruder_offsets.resize(print_config.filament_map.size(), print_config.extruder_offset.get_at(print_config.master_extruder_id.value - 1));
|
||||
@@ -143,7 +144,7 @@ private:
|
||||
// Current layer index.
|
||||
int m_layer_idx;
|
||||
int m_tool_change_idx;
|
||||
double m_last_wipe_tower_print_z = 0.f;
|
||||
double m_last_wipe_tower_print_z;
|
||||
|
||||
// BBS
|
||||
Vec3d m_plate_origin;
|
||||
|
||||
@@ -1893,7 +1893,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
// sanity check
|
||||
if(m_preheat_steps < 1)
|
||||
m_preheat_steps = 1;
|
||||
m_result.backtrace_enabled = m_preheat_time > 0 && (m_is_XL_printer || (!m_single_extruder_multi_material && filament_count > 1));
|
||||
m_result.backtrace_enabled = config.ooze_prevention && m_preheat_time > 0 && (m_is_XL_printer || (!m_single_extruder_multi_material && filament_count > 1));
|
||||
|
||||
assert(config.nozzle_volume.size() == config.nozzle_diameter.size());
|
||||
m_nozzle_volume.resize(config.nozzle_volume.size());
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include "SmallAreaInfillFlowCompensator.hpp"
|
||||
#include "spline/spline.h"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -47,37 +46,43 @@ SmallAreaInfillFlowCompensator::SmallAreaInfillFlowCompensator(const Slic3r::GCo
|
||||
}
|
||||
} catch (...) {
|
||||
std::stringstream ss;
|
||||
ss << "Error parsing data point in small area infill compensation model:" << line << std::endl;
|
||||
ss << "Small Area Flow Compensation: Error parsing data point in small area infill compensation model:" << line << std::endl;
|
||||
|
||||
throw Slic3r::InvalidArgument(ss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < eLengths.size(); i++) {
|
||||
for (size_t i = 0; i < eLengths.size(); i++) {
|
||||
if (i == 0) {
|
||||
if (!nearly_equal(eLengths[i], 0.0)) {
|
||||
throw Slic3r::InvalidArgument("First extrusion length for small area infill compensation model must be 0");
|
||||
throw Slic3r::InvalidArgument("Small Area Flow Compensation: First extrusion length for small area infill compensation model must be 0");
|
||||
}
|
||||
} else {
|
||||
if (nearly_equal(eLengths[i], 0.0)) {
|
||||
throw Slic3r::InvalidArgument("Only the first extrusion length for small area infill compensation model can be 0");
|
||||
throw Slic3r::InvalidArgument("Small Area Flow Compensation: Only the first extrusion length for small area infill compensation model can be 0");
|
||||
}
|
||||
if (eLengths[i] <= eLengths[i - 1]) {
|
||||
throw Slic3r::InvalidArgument("Extrusion lengths for subsequent points must be increasing");
|
||||
throw Slic3r::InvalidArgument("Small Area Flow Compensation: Extrusion lengths for subsequent points must be increasing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!flowComps.empty() && !nearly_equal(flowComps.back(), 1.0)) {
|
||||
throw Slic3r::InvalidArgument("Final compensation factor for small area infill flow compensation model must be 1.0");
|
||||
for (size_t i = 1; i < flowComps.size(); ++i) {
|
||||
if (flowComps[i] <= flowComps[i - 1]) {
|
||||
throw Slic3r::InvalidArgument("Small Area Flow Compensation: Flow compensation factors must strictly increase with extrusion length");
|
||||
}
|
||||
}
|
||||
|
||||
flowModel = std::make_unique<tk::spline>();
|
||||
flowModel->set_points(eLengths, flowComps);
|
||||
if (!flowComps.empty() && !nearly_equal(flowComps.back(), 1.0)) {
|
||||
throw Slic3r::InvalidArgument("Small Area Flow Compensation: Final compensation factor for small area infill flow compensation model must be 1.0");
|
||||
}
|
||||
|
||||
flowModel = std::make_unique<PchipInterpolatorHelper>(eLengths, flowComps);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error parsing small area infill compensation model: " << e.what();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +97,7 @@ double SmallAreaInfillFlowCompensator::flow_comp_model(const double line_length)
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (*flowModel)(line_length);
|
||||
return flowModel->interpolate(line_length);
|
||||
}
|
||||
|
||||
double SmallAreaInfillFlowCompensator::modify_flow(const double line_length, const double dE, const ExtrusionRole role)
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "PchipInterpolatorHelper.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace tk {
|
||||
class spline;
|
||||
} // namespace tk
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SmallAreaInfillFlowCompensator
|
||||
@@ -26,8 +23,7 @@ private:
|
||||
std::vector<double> eLengths;
|
||||
std::vector<double> flowComps;
|
||||
|
||||
// TODO: Cubic Spline
|
||||
std::unique_ptr<tk::spline> flowModel;
|
||||
std::unique_ptr<PchipInterpolatorHelper> flowModel;
|
||||
|
||||
double flow_comp_model(const double line_length);
|
||||
|
||||
|
||||
@@ -1215,6 +1215,10 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
|
||||
result.extrusions = std::move(writer.extrusions());
|
||||
result.wipe_path = std::move(writer.wipe_path());
|
||||
result.is_finish_first = is_finish;
|
||||
// ORCA: Always initialize the tool_change_start_pos with a valid position
|
||||
// to avoid undefined variable travel on X in Gcode.cpp function std::string WipeTowerIntegration::post_process_wipe_tower_moves
|
||||
result.tool_change_start_pos = result.start_pos; // always valid fallback
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2032,7 +2036,11 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
// brim (first layer only)
|
||||
if (first_layer) {
|
||||
writer.append("; WIPE_TOWER_BRIM_START\n");
|
||||
size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing;
|
||||
float brim_width = m_wipe_tower_brim_width;
|
||||
if (brim_width < 0.f)
|
||||
brim_width = WipeTower::get_auto_brim_by_height(m_wipe_tower_height);
|
||||
|
||||
size_t loops_num = (brim_width + spacing / 2.f) / spacing;
|
||||
|
||||
for (size_t i = 0; i < loops_num; ++ i) {
|
||||
poly = offset(poly, scale_(spacing)).front();
|
||||
|
||||
@@ -58,10 +58,18 @@ public:
|
||||
std::vector<std::pair<float, float>> get_z_and_depth_pairs() const;
|
||||
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
|
||||
float get_wipe_tower_height() const { return m_wipe_tower_height; }
|
||||
|
||||
|
||||
|
||||
|
||||
// ORCA: Match WipeTower API used by Print skirt/brim planning.
|
||||
// Returned bounding box is in WIPE-TOWER-LOCAL coordinates (before placement on the bed).
|
||||
// Include brim and y-shift to match what WT gcode actually prints.
|
||||
BoundingBoxf get_bbx() const{
|
||||
const float brim = m_wipe_tower_brim_width_real;
|
||||
const Vec2d min(-brim, -brim + double(m_y_shift));
|
||||
const Vec2d max(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim + double(m_y_shift));
|
||||
return BoundingBoxf(min, max);
|
||||
}
|
||||
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
|
||||
// so expose a zero offset for consistency purposes (to maintain API parity).
|
||||
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
|
||||
@@ -444,23 +444,28 @@ std::string GCodeWriter::reset_e(bool force)
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCodeWriter::enable_power_loss_recovery(bool enable)
|
||||
std::string GCodeWriter::enable_power_loss_recovery(PowerLossRecoveryMode mode)
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
|
||||
|
||||
if (mode == PowerLossRecoveryMode::PrinterConfiguration)
|
||||
return std::string();
|
||||
|
||||
const bool enable = mode == PowerLossRecoveryMode::Enable;
|
||||
|
||||
if (m_is_bbl_printers) {
|
||||
gcode << "; start tracking Power Loss Recovery https://wiki.bambulab.com/en/knowledge-sharing/power-loss-recovery\n";
|
||||
gcode << "M1003 S" << (enable ? "1" : "0") << "\n";
|
||||
gcode << "M1003 S" << (enable ? "1" : "0");
|
||||
}
|
||||
else if (FLAVOR_IS(gcfMarlinFirmware)) {
|
||||
gcode << "; start tracking Power-loss Recovery https://marlinfw.org/docs/gcode/M413.html\n";
|
||||
gcode << "M413 S" << (enable ? "1" : "0") << "\n";
|
||||
gcode << "M413 S" << (enable ? "1" : "0");
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
if (GCodeWriter::full_gcode_comment) gcode << " ; set Power-loss Recovery";
|
||||
gcode << "\n";
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
|
||||
std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
|
||||
{
|
||||
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
std::string set_input_shaping(char axis, float damp, float freq, std::string type) const;
|
||||
std::string reset_e(bool force = false);
|
||||
std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const;
|
||||
std::string enable_power_loss_recovery(bool enable);
|
||||
std::string enable_power_loss_recovery(PowerLossRecoveryMode mode);
|
||||
// return false if this extruder was already selected
|
||||
bool need_toolchange(unsigned int filament_id) const;
|
||||
std::string set_extruder(unsigned int filament_id);
|
||||
|
||||
@@ -1045,8 +1045,8 @@ namespace client
|
||||
case coFloatOrPercent:
|
||||
{
|
||||
std::string opt_key(opt.it_range.begin(), opt.it_range.end());
|
||||
if (boost::ends_with(opt_key, "extrusion_width")) {
|
||||
// Extrusion width supports defaults and a complex graph of dependencies.
|
||||
if (boost::ends_with(opt_key, "line_width")) {
|
||||
// Line width supports defaults and a complex graph of dependencies.
|
||||
output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast<unsigned int>(ctx->current_extruder_id)));
|
||||
} else if (! static_cast<const ConfigOptionFloatOrPercent*>(opt.opt)->percent) {
|
||||
// Not a percent, just return the value.
|
||||
@@ -1060,8 +1060,8 @@ namespace client
|
||||
const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over);
|
||||
if (opt_parent == nullptr)
|
||||
ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range);
|
||||
if (boost::ends_with(opt_def->ratio_over, "extrusion_width")) {
|
||||
// Extrusion width supports defaults and a complex graph of dependencies.
|
||||
if (boost::ends_with(opt_def->ratio_over, "line_width")) {
|
||||
// Line width supports defaults and a complex graph of dependencies.
|
||||
assert(opt_parent->type() == coFloatOrPercent);
|
||||
v *= Flow::extrusion_width(opt_def->ratio_over, static_cast<const ConfigOptionFloatOrPercent*>(opt_parent), *ctx, static_cast<unsigned int>(ctx->current_extruder_id));
|
||||
break;
|
||||
@@ -2197,9 +2197,8 @@ namespace client
|
||||
initializer_list(_r1)[px::bind(&MyContext::vector_variable_new_from_initializer_list, _r1, _a, _b, _1)]
|
||||
// Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index.
|
||||
// Only process such variable references, which return a naked vector variable.
|
||||
// Orca todo: following code cause strange build errors with MSVC C++17
|
||||
// | eps(px::bind(&MyContext::could_be_vector_variable_reference, _b)) >>
|
||||
// variable_reference(_r1)[px::val(qi::_pass) = px::bind(&MyContext::vector_variable_new_from_copy, _r1, _a, _b, _1)]
|
||||
| eps(px::bind(&MyContext::could_be_vector_variable_reference, _b)) >>
|
||||
variable_reference(_r1)[qi::_pass = px::bind(&MyContext::vector_variable_new_from_copy, _r1, _a, _b, _1)]
|
||||
// Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above.
|
||||
| conditional_expression(_r1)
|
||||
[px::bind(&MyContext::scalar_variable_new_from_scalar_expression, _r1, _a, _b, _1)]
|
||||
|
||||
@@ -904,7 +904,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
||||
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
||||
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
||||
"brim_width", "brim_object_gap", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"brim_width", "brim_object_gap", "brim_use_efc_outline", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"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
|
||||
|
||||
@@ -2751,6 +2751,8 @@ Preset *PresetBundle::get_similar_printer_preset(std::string printer_model, std:
|
||||
{
|
||||
if (printer_model.empty())
|
||||
printer_model = printers.get_selected_preset().config.opt_string("printer_model");
|
||||
if (printer_model.empty()) // ORCA ensure a compatible model exist. fixes switches to blank preset if preset has no inherited value
|
||||
return nullptr;
|
||||
auto printer_variant_old = printers.get_selected_preset().config.opt_string("printer_variant");
|
||||
std::map<std::string, Preset*> printer_presets;
|
||||
for (auto &preset : printers.m_presets) {
|
||||
@@ -2761,7 +2763,8 @@ Preset *PresetBundle::get_similar_printer_preset(std::string printer_model, std:
|
||||
}
|
||||
if (printer_presets.empty())
|
||||
return nullptr;
|
||||
auto prefer_printer = printers.get_selected_preset().name;
|
||||
auto prefer_printer = printers.get_selected_preset().alias; //.name ORCA use alias instead "name" for calling system presets. otherwise nozzle combo will not change printer presets if they custom named
|
||||
|
||||
if (!printer_variant.empty())
|
||||
boost::replace_all(prefer_printer, printer_variant_old, printer_variant);
|
||||
else if (auto n = prefer_printer.find(printer_variant_old); n != std::string::npos)
|
||||
|
||||
@@ -3333,6 +3333,8 @@ void Print::_make_wipe_tower()
|
||||
m_wipe_tower_data.z_and_depth_pairs = wipe_tower.get_z_and_depth_pairs();
|
||||
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
|
||||
m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height();
|
||||
m_wipe_tower_data.bbx = wipe_tower.get_bbx();
|
||||
m_wipe_tower_data.rib_offset = wipe_tower.get_rib_offset();
|
||||
|
||||
// Unload the current filament over the purge tower.
|
||||
coordf_t layer_height = m_objects.front()->config().layer_height.value;
|
||||
|
||||
@@ -161,6 +161,14 @@ static t_config_enum_values s_keys_map_BedTempFormula {
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedTempFormula)
|
||||
|
||||
// Orca
|
||||
static t_config_enum_values s_keys_map_PowerLossRecoveryMode {
|
||||
{ "printer_configuration", int(PowerLossRecoveryMode::PrinterConfiguration) },
|
||||
{ "enable", int(PowerLossRecoveryMode::Enable) },
|
||||
{ "disable", int(PowerLossRecoveryMode::Disable) }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PowerLossRecoveryMode)
|
||||
|
||||
static t_config_enum_values s_keys_map_FuzzySkinType {
|
||||
{ "none", int(FuzzySkinType::None) },
|
||||
{ "external", int(FuzzySkinType::External) },
|
||||
@@ -1562,6 +1570,16 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
|
||||
def = this->add("brim_use_efc_outline", coBool);
|
||||
def->label = L("Brim follows compensated outline");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("When enabled, the brim is aligned with the first-layer perimeter geometry after Elephant Foot Compensation is applied.\n"
|
||||
"This option is intended for cases where Elephant Foot Compensation significantly alters the first-layer footprint.\n"
|
||||
"\n"
|
||||
"If your current setup already works well, enabling it may be unnecessary and can cause the brim to fuse with upper layers." );
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("brim_ears", coBool);
|
||||
def->label = L("Brim ears");
|
||||
def->category = L("Support");
|
||||
@@ -2733,7 +2751,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Fill Multiline");
|
||||
def->tooltip = L("Using multiple lines for the infill pattern, if supported by infill pattern.");
|
||||
def->min = 1;
|
||||
def->max = 5; // Maximum number of lines for infill pattern
|
||||
def->max = 10; // Maximum number of lines for infill pattern
|
||||
def->set_default_value(new ConfigOptionInt(1));
|
||||
|
||||
def = this->add("sparse_infill_pattern", coEnum);
|
||||
@@ -3366,11 +3384,18 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
// Orca
|
||||
def = this->add("enable_power_loss_recovery", coBool);
|
||||
def->label = L("Turn on Power Loss Recovery");
|
||||
def->tooltip = L("Enable this to insert power loss recovery commands in generated G-code.(Only for Bambu Lab printers and Marlin firmware based printers)");
|
||||
def = this->add("enable_power_loss_recovery", coEnum);
|
||||
def->label = L("Power Loss Recovery");
|
||||
def->tooltip = L("Choose how to control power loss recovery. When set to Printer configuration, the slicer will not emit power loss recovery G-code and will leave the printer's configuration unchanged. Applicable to Bambu Lab or Marlin 2 firmware based printers.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
def->enum_keys_map = &ConfigOptionEnum<PowerLossRecoveryMode>::get_enum_values();
|
||||
def->enum_values.push_back("printer_configuration");
|
||||
def->enum_values.push_back("enable");
|
||||
def->enum_values.push_back("disable");
|
||||
def->enum_labels.push_back(L("Printer configuration"));
|
||||
def->enum_labels.push_back(L("Enable"));
|
||||
def->enum_labels.push_back(L("Disable"));
|
||||
def->set_default_value(new ConfigOptionEnum<PowerLossRecoveryMode>(PowerLossRecoveryMode::PrinterConfiguration));
|
||||
|
||||
//BBS
|
||||
// def = this->add("spaghetti_detector", coBool);
|
||||
@@ -7432,6 +7457,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
||||
else if (opt_key == "extruder_type") {
|
||||
ReplaceString(value, "DirectDrive", "Direct Drive");
|
||||
}
|
||||
else if (opt_key == "enable_power_loss_recovery") {
|
||||
if (value == "1" || boost::iequals(value, "true")) {
|
||||
value = "enable";
|
||||
} else if (value == "0" || boost::iequals(value, "false")) {
|
||||
value = "disable";
|
||||
}
|
||||
}
|
||||
else if(opt_key == "ensure_vertical_shell_thickness") {
|
||||
if(value == "1") {
|
||||
value = "ensure_all";
|
||||
@@ -7646,6 +7678,9 @@ std::set<std::string> filament_options_with_variant = {
|
||||
"filament_retraction_length",
|
||||
"filament_z_hop",
|
||||
"filament_z_hop_types",
|
||||
"filament_retract_lift_above",
|
||||
"filament_retract_lift_below",
|
||||
"filament_retract_lift_enforce",
|
||||
"filament_retract_restart_extra",
|
||||
"filament_retraction_speed",
|
||||
"filament_deretraction_speed",
|
||||
@@ -7664,7 +7699,11 @@ std::set<std::string> filament_options_with_variant = {
|
||||
"filament_flush_volumetric_speed",
|
||||
"filament_flush_temp",
|
||||
"volumetric_speed_coefficients",
|
||||
"filament_adaptive_volumetric_speed"
|
||||
"filament_adaptive_volumetric_speed",
|
||||
"filament_ironing_flow",
|
||||
"filament_ironing_spacing",
|
||||
"filament_ironing_inset",
|
||||
"filament_ironing_speed"
|
||||
};
|
||||
|
||||
// Parameters that are the same as the number of extruders
|
||||
|
||||
@@ -102,6 +102,13 @@ enum class BedTempFormula {
|
||||
count,
|
||||
};
|
||||
|
||||
// Orca
|
||||
enum class PowerLossRecoveryMode {
|
||||
PrinterConfiguration,
|
||||
Enable,
|
||||
Disable,
|
||||
};
|
||||
|
||||
// BBS
|
||||
enum class WallSequence {
|
||||
InnerOuter,
|
||||
@@ -507,6 +514,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerWallType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
|
||||
@@ -874,6 +882,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
PrintObjectConfig,
|
||||
|
||||
((ConfigOptionFloat, brim_object_gap))
|
||||
((ConfigOptionBool, brim_use_efc_outline))
|
||||
((ConfigOptionEnum<BrimType>, brim_type))
|
||||
((ConfigOptionFloat, brim_width))
|
||||
((ConfigOptionFloat, brim_ears_detection_length))
|
||||
@@ -1271,7 +1280,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionIntsNullable, filament_flush_temp))
|
||||
// BBS
|
||||
((ConfigOptionBool, scan_first_layer))
|
||||
((ConfigOptionBool, enable_power_loss_recovery))
|
||||
((ConfigOptionEnum<PowerLossRecoveryMode>, enable_power_loss_recovery))
|
||||
((ConfigOptionBool, enable_wrapping_detection))
|
||||
((ConfigOptionInt, wrapping_detection_layers))
|
||||
((ConfigOptionPoints, wrapping_exclude_area))
|
||||
|
||||
@@ -1006,6 +1006,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
for (const t_config_option_key &opt_key : opt_keys) {
|
||||
if ( opt_key == "brim_width"
|
||||
|| opt_key == "brim_object_gap"
|
||||
|| opt_key == "brim_use_efc_outline"
|
||||
|| opt_key == "brim_type"
|
||||
|| opt_key == "brim_ears_max_angle"
|
||||
|| opt_key == "brim_ears_detection_length"
|
||||
|
||||
@@ -458,6 +458,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/UnsavedChangesDialog.hpp
|
||||
GUI/UpdateDialogs.cpp
|
||||
GUI/UpdateDialogs.hpp
|
||||
GUI/NetworkPluginDialog.cpp
|
||||
GUI/NetworkPluginDialog.hpp
|
||||
GUI/UpgradePanel.cpp
|
||||
GUI/UpgradePanel.hpp
|
||||
GUI/UserManager.cpp
|
||||
@@ -494,6 +496,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Widgets/ErrorMsgStaticText.hpp
|
||||
GUI/Widgets/FanControl.cpp
|
||||
GUI/Widgets/FanControl.hpp
|
||||
GUI/Widgets/HyperLink.cpp
|
||||
GUI/Widgets/HyperLink.hpp
|
||||
GUI/Widgets/ImageSwitchButton.cpp
|
||||
GUI/Widgets/ImageSwitchButton.hpp
|
||||
GUI/Widgets/Label.cpp
|
||||
@@ -705,8 +709,16 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
|
||||
|
||||
encoding_check(libslic3r_gui)
|
||||
|
||||
|
||||
if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
|
||||
set(_opengl_link_lib "")
|
||||
else()
|
||||
set(_opengl_link_lib OpenGL::GL)
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise)
|
||||
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(libslic3r_gui Setupapi.lib)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
@@ -723,7 +735,11 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
${CURL_LIBRARIES}
|
||||
)
|
||||
elseif (APPLE)
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security")
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security" "-framework OpenGL")
|
||||
else()
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (SLIC3R_STATIC)
|
||||
|
||||
@@ -298,11 +298,7 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
|
||||
if (language.find("zh") == 0)
|
||||
region = "zh";
|
||||
wxString link_url = wxString::Format("https://wiki.bambulab.com/%s/software/bambu-studio/calibration_pa", region);
|
||||
m_wiki_ctrl = new wxHyperlinkCtrl(parent, wxID_ANY, "Wiki", link_url);
|
||||
m_wiki_ctrl->SetNormalColour(*wxBLUE);
|
||||
m_wiki_ctrl->SetHoverColour(wxColour(0, 0, 200));
|
||||
m_wiki_ctrl->SetVisitedColour(*wxBLUE);
|
||||
m_wiki_ctrl->SetFont(Label::Head_14);
|
||||
m_wiki_ctrl = new HyperLink(parent, "Wiki Guide", link_url);
|
||||
cali_title_sizer->Add(m_ratio_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
cali_title_sizer->Add(m_wiki_ctrl, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
#include "slic3r/Utils/CalibUtils.hpp"
|
||||
#include <wx/hyperlink.h>
|
||||
|
||||
#define AMS_MATERIALS_SETTING_DEF_COLOUR wxColour(255, 255, 255)
|
||||
#define AMS_MATERIALS_SETTING_GREY900 wxColour(38, 46, 48)
|
||||
@@ -174,7 +174,7 @@ protected:
|
||||
|
||||
wxPanel * m_panel_kn;
|
||||
wxStaticText* m_ratio_text;
|
||||
wxHyperlinkCtrl * m_wiki_ctrl;
|
||||
HyperLink * m_wiki_ctrl;
|
||||
wxStaticText* m_k_param;
|
||||
TextInput* m_input_k_val;
|
||||
wxStaticText* m_n_param;
|
||||
|
||||
@@ -98,18 +98,8 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
|
||||
m_status_text->Wrap(FromDIP(440));
|
||||
m_status_text->SetForegroundColour(wxColour(38, 46, 48));
|
||||
|
||||
m_link_show_ping_code_wiki = new wxStaticText(request_bind_panel, wxID_ANY, _L("Can't find Pin Code?"));
|
||||
m_link_show_ping_code_wiki->SetFont(Label::Body_14);
|
||||
m_link_show_ping_code_wiki->SetBackgroundColour(*wxWHITE);
|
||||
m_link_show_ping_code_wiki->SetForegroundColour(wxColour(31, 142, 234));
|
||||
|
||||
m_link_show_ping_code_wiki->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); });
|
||||
m_link_show_ping_code_wiki->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); });
|
||||
|
||||
m_link_show_ping_code_wiki->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
||||
m_ping_code_wiki = "https://wiki.bambulab.com/en/bambu-studio/manual/pin-code";
|
||||
wxLaunchDefaultBrowser(m_ping_code_wiki);
|
||||
});
|
||||
// ORCA standardized HyperLink
|
||||
m_link_show_ping_code_wiki = new HyperLink(request_bind_panel, _L("Can't find Pin Code?"), "https://wiki.bambulab.com/en/bambu-studio/manual/pin-code");
|
||||
|
||||
m_text_input_title = new wxStaticText(request_bind_panel, wxID_ANY, _L("Pin Code"));
|
||||
m_text_input_title->SetFont(Label::Body_14);
|
||||
@@ -453,11 +443,11 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
m_st_privacy_title->SetFont(Label::Body_13);
|
||||
m_st_privacy_title->SetForegroundColour(wxColour(38, 46, 48));
|
||||
|
||||
auto m_link_Terms_title = new Label(m_panel_agreement, _L("Terms and Conditions"));
|
||||
// ORCA standardized HyperLink
|
||||
auto m_link_Terms_title = new HyperLink(m_panel_agreement, _L("Terms and Conditions"));
|
||||
m_link_Terms_title->SetFont(Label::Head_13);
|
||||
m_link_Terms_title->SetMaxSize(wxSize(FromDIP(450), -1));
|
||||
m_link_Terms_title->Wrap(FromDIP(450));
|
||||
m_link_Terms_title->SetForegroundColour(wxColour("#009688"));
|
||||
m_link_Terms_title->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
||||
wxString txt = _L("Thank you for purchasing a Bambu Lab device. Before using your Bambu Lab device, please read the terms and conditions. "
|
||||
"By clicking to agree to use your Bambu Lab device, you agree to abide by the Privacy Policy and Terms of Use (collectively, the \"Terms\"). "
|
||||
@@ -467,18 +457,16 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
confirm_dlg.CenterOnParent();
|
||||
confirm_dlg.on_show();
|
||||
});
|
||||
m_link_Terms_title->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); });
|
||||
m_link_Terms_title->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); });
|
||||
|
||||
auto m_st_and_title = new Label(m_panel_agreement, _L("and"));
|
||||
m_st_and_title->SetFont(Label::Body_13);
|
||||
m_st_and_title->SetForegroundColour(wxColour(38, 46, 48));
|
||||
|
||||
auto m_link_privacy_title = new Label(m_panel_agreement, _L("Privacy Policy"));
|
||||
// ORCA standardized HyperLink
|
||||
auto m_link_privacy_title = new HyperLink(m_panel_agreement, _L("Privacy Policy"));
|
||||
m_link_privacy_title->SetFont(Label::Head_13);
|
||||
m_link_privacy_title->SetMaxSize(wxSize(FromDIP(450), -1));
|
||||
m_link_privacy_title->Wrap(FromDIP(450));
|
||||
m_link_privacy_title->SetForegroundColour(wxColour("#009688"));
|
||||
m_link_privacy_title->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
||||
std::string url;
|
||||
std::string country_code = Slic3r::GUI::wxGetApp().app_config->get_country_code();
|
||||
@@ -491,8 +479,6 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
}
|
||||
wxLaunchDefaultBrowser(url);
|
||||
});
|
||||
m_link_privacy_title->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND);});
|
||||
m_link_privacy_title->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW);});
|
||||
|
||||
sizere_notice_agreement->Add(0, 0, 0, wxTOP, FromDIP(4));
|
||||
sizer_privacy_agreement->Add(m_st_privacy_title, 0, wxALIGN_CENTER, 0);
|
||||
@@ -514,13 +500,11 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
m_st_notice_title->SetFont(Label::Body_13);
|
||||
m_st_notice_title->SetForegroundColour(wxColour(38, 46, 48));
|
||||
|
||||
auto m_link_notice_title = new Label(m_panel_agreement, notice_link_title);
|
||||
// ORCA standardized HyperLink
|
||||
auto m_link_notice_title = new HyperLink(m_panel_agreement, notice_link_title);
|
||||
m_link_notice_title->SetFont(Label::Head_13);
|
||||
m_link_notice_title->SetMaxSize(wxSize(FromDIP(450), -1));
|
||||
m_link_notice_title->Wrap(FromDIP(450));
|
||||
m_link_notice_title->SetForegroundColour(wxColour("#009688"));
|
||||
m_link_notice_title->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); });
|
||||
m_link_notice_title->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); });
|
||||
m_link_notice_title->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
||||
wxString txt = _L("In the 3D Printing community, we learn from each other's successes and failures to adjust "
|
||||
"our own slicing parameters and settings. %s follows the same principle and uses machine "
|
||||
@@ -580,13 +564,8 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
wxBoxSizer* m_sizer_bind_failed_info = new wxBoxSizer(wxVERTICAL);
|
||||
m_sw_bind_failed_info->SetSizer( m_sizer_bind_failed_info );
|
||||
|
||||
m_link_network_state = new wxHyperlinkCtrl(m_sw_bind_failed_info, wxID_ANY,_L("Check the status of current system services"),"");
|
||||
m_link_network_state->SetFont(::Label::Body_12);
|
||||
m_link_network_state->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_network_check(); });
|
||||
m_link_network_state->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {m_link_network_state->SetCursor(wxCURSOR_HAND); });
|
||||
m_link_network_state->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {m_link_network_state->SetCursor(wxCURSOR_ARROW); });
|
||||
|
||||
|
||||
// ORCA standardized HyperLink
|
||||
m_link_network_state = new HyperLink(m_sw_bind_failed_info, _L("Check the status of current system services"), wxGetApp().link_to_network_check());
|
||||
|
||||
wxBoxSizer* sizer_error_code = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer* sizer_error_desc = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
#include <wx/dialog.h>
|
||||
#include <curl/curl.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <wx/hyperlink.h>
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Widgets/StepCtrl.hpp"
|
||||
#include "Widgets/ProgressDialog.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/ProgressBar.hpp"
|
||||
#include "Widgets/RoundedRectangle.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
#include "Jobs/BindJob.hpp"
|
||||
#include "BBLStatusBar.hpp"
|
||||
#include "BBLStatusBarBind.hpp"
|
||||
@@ -56,7 +56,7 @@ private:
|
||||
|
||||
Label* m_status_text;
|
||||
wxStaticText* m_text_input_title;
|
||||
wxStaticText* m_link_show_ping_code_wiki;
|
||||
HyperLink* m_link_show_ping_code_wiki; // ORCA
|
||||
TextInput* m_text_input_single_code[PING_CODE_LENGTH];
|
||||
Button* m_button_bind;
|
||||
Button* m_button_cancel;
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
Label* m_st_txt_error_code{ nullptr };
|
||||
Label* m_st_txt_error_desc{ nullptr };
|
||||
Label* m_st_txt_extra_info{ nullptr };
|
||||
wxHyperlinkCtrl* m_link_network_state{ nullptr };
|
||||
HyperLink* m_link_network_state{ nullptr };
|
||||
wxString m_result_info;
|
||||
wxString m_result_extra;
|
||||
wxString m_ping_code_wiki;
|
||||
@@ -114,7 +114,7 @@ private:
|
||||
Label* m_st_txt_error_code{ nullptr };
|
||||
Label* m_st_txt_error_desc{ nullptr };
|
||||
Label* m_st_txt_extra_info{ nullptr };
|
||||
wxHyperlinkCtrl* m_link_network_state{ nullptr };
|
||||
HyperLink* m_link_network_state{ nullptr };
|
||||
wxString m_result_info;
|
||||
wxString m_result_extra;
|
||||
bool m_show_error_info_state = true;
|
||||
|
||||
@@ -269,7 +269,7 @@ void SelectMObjectPopup::Popup(wxWindow* WXUNUSED(focus))
|
||||
}
|
||||
}
|
||||
|
||||
wxPostEvent(this, wxTimerEvent());
|
||||
wxPostEvent(this, wxTimerEvent(*m_refresh_timer));
|
||||
PopupWindow::Popup();
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ void CalibrationPanel::init_timer()
|
||||
m_refresh_timer = new wxTimer();
|
||||
m_refresh_timer->SetOwner(this);
|
||||
m_refresh_timer->Start(REFRESH_INTERVAL);
|
||||
wxPostEvent(this, wxTimerEvent());
|
||||
wxPostEvent(this, wxCommandEvent(wxEVT_TIMER));
|
||||
}
|
||||
|
||||
void CalibrationPanel::on_timer(wxTimerEvent& event) {
|
||||
@@ -634,7 +634,7 @@ bool CalibrationPanel::Show(bool show) {
|
||||
m_refresh_timer->Stop();
|
||||
m_refresh_timer->SetOwner(this);
|
||||
m_refresh_timer->Start(REFRESH_INTERVAL);
|
||||
wxPostEvent(this, wxTimerEvent());
|
||||
wxPostEvent(this, wxCommandEvent(wxEVT_TIMER));
|
||||
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (dev) {
|
||||
|
||||
@@ -466,21 +466,12 @@ void CaliPageCaption::init_bitmaps() {
|
||||
|
||||
void CaliPageCaption::create_wiki(wxWindow* parent)
|
||||
{
|
||||
m_wiki_text = new Label(parent, _L("Wiki"));
|
||||
m_wiki_text->SetFont(Label::Head_14);
|
||||
m_wiki_text->SetForegroundColour({ 0, 88, 220 });
|
||||
m_wiki_text->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& e) {
|
||||
e.Skip();
|
||||
SetCursor(wxCURSOR_HAND);
|
||||
});
|
||||
m_wiki_text->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) {
|
||||
e.Skip();
|
||||
SetCursor(wxCURSOR_ARROW);
|
||||
});
|
||||
// ORCA standardized HyperLink
|
||||
m_wiki_text = new HyperLink(parent, _L("Wiki Guide"));
|
||||
m_wiki_text->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
|
||||
if (!m_wiki_url.empty())
|
||||
wxLaunchDefaultBrowser(m_wiki_url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void CaliPageCaption::show_prev_btn(bool show)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/AMSControl.hpp"
|
||||
#include "Widgets/ProgressBar.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "PresetComboBoxes.hpp"
|
||||
|
||||
@@ -140,7 +141,7 @@ private:
|
||||
void init_bitmaps();
|
||||
void create_wiki(wxWindow* parent);
|
||||
|
||||
Label* m_wiki_text;
|
||||
HyperLink* m_wiki_text; // ORCA
|
||||
wxString m_wiki_url;
|
||||
ScalableBitmap m_prev_bmp_normal;
|
||||
ScalableBitmap m_prev_bmp_hover;
|
||||
|
||||
@@ -604,8 +604,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
|
||||
|
||||
// Infill patterns that support multiline infill.
|
||||
InfillPattern pattern = config->opt_enum<InfillPattern>("sparse_infill_pattern");
|
||||
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb ||
|
||||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic;
|
||||
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric ||
|
||||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral;
|
||||
|
||||
// If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill.
|
||||
if (have_infill) {
|
||||
@@ -705,6 +705,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
|
||||
|
||||
bool have_brim = (config->opt_enum<BrimType>("brim_type") != btNoBrim);
|
||||
toggle_field("brim_object_gap", have_brim);
|
||||
toggle_field("brim_use_efc_outline", have_brim);
|
||||
bool have_brim_width = (config->opt_enum<BrimType>("brim_type") != btNoBrim) && config->opt_enum<BrimType>("brim_type") != btAutoBrim &&
|
||||
config->opt_enum<BrimType>("brim_type") != btPainted;
|
||||
toggle_field("brim_width", have_brim_width);
|
||||
@@ -774,10 +775,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
|
||||
// Orca: Force solid support interface when using support ironing
|
||||
toggle_field("support_interface_spacing", have_support_material && have_support_interface && !has_support_ironing);
|
||||
|
||||
bool have_skirt_height = have_skirt &&
|
||||
(config->opt_int("skirt_height") > 1 || config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
|
||||
toggle_line("support_speed", have_support_material || have_skirt_height);
|
||||
toggle_line("support_interface_speed", have_support_material && have_support_interface);
|
||||
// see issue #10915
|
||||
// bool have_skirt_height = have_skirt &&
|
||||
// (config->opt_int("skirt_height") > 1 || config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
|
||||
// toggle_line("support_speed", have_support_material || have_skirt_height);
|
||||
// toggle_line("support_interface_speed", have_support_material && have_support_interface);
|
||||
|
||||
// BBS
|
||||
//toggle_field("support_material_synchronize_layers", have_support_soluble);
|
||||
@@ -934,12 +936,15 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
|
||||
bool lattice_options = config->opt_enum<InfillPattern>("sparse_infill_pattern") == InfillPattern::ipLateralLattice;
|
||||
for (auto el : { "lateral_lattice_angle_1", "lateral_lattice_angle_2"})
|
||||
toggle_line(el, lattice_options);
|
||||
|
||||
// Adaptative Cubic and support cubic infill patterns do not support infill rotation.
|
||||
bool FillAdaptive = (pattern == InfillPattern::ipAdaptiveCubic || pattern == InfillPattern::ipSupportCubic);
|
||||
|
||||
//Orca: disable infill_direction/solid_infill_direction if sparse_infill_rotate_template/solid_infill_rotate_template is not empty value
|
||||
toggle_field("infill_direction", config->opt_string("sparse_infill_rotate_template") == "");
|
||||
//Orca: disable infill_direction/solid_infill_direction if sparse_infill_rotate_template/solid_infill_rotate_template is not empty value and adaptive cubic/support cubic infill pattern is not selected
|
||||
toggle_field("sparse_infill_rotate_template", !FillAdaptive);
|
||||
toggle_field("infill_direction", config->opt_string("sparse_infill_rotate_template") == "" && !FillAdaptive);
|
||||
toggle_field("solid_infill_direction", config->opt_string("solid_infill_rotate_template") == "");
|
||||
|
||||
|
||||
|
||||
toggle_line("infill_overhang_angle", config->opt_enum<InfillPattern>("sparse_infill_pattern") == InfillPattern::ipLateralHoneycomb);
|
||||
|
||||
std::string printer_type = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "Tab.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
#include "Widgets/HyperLink.hpp" // ORCA
|
||||
|
||||
#define NAME_OPTION_COMBOBOX_SIZE wxSize(FromDIP(200), FromDIP(24))
|
||||
#define FILAMENT_PRESET_COMBOBOX_SIZE wxSize(FromDIP(300), FromDIP(24))
|
||||
@@ -5027,8 +5028,8 @@ wxPanel *PresetTree::get_child_item(wxPanel *parent, std::shared_ptr<Preset> pre
|
||||
bool base_id_error = false;
|
||||
if (preset->inherits() == "" && preset->base_id != "") base_id_error = true;
|
||||
if (base_id_error) {
|
||||
std::string wiki_url = "https://wiki.bambulab.com/en/software/bambu-studio/custom-filament-issue";
|
||||
wxHyperlinkCtrl *m_download_hyperlink = new wxHyperlinkCtrl(panel, wxID_ANY, _L("[Delete Required]"), wiki_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
|
||||
// ORCA standardized HyperLink
|
||||
HyperLink *m_download_hyperlink = new HyperLink(panel, _L("[Delete Required]"), "https://wiki.bambulab.com/en/software/bambu-studio/custom-filament-issue");
|
||||
m_download_hyperlink->SetFont(Label::Body_10);
|
||||
sizer->Add(m_download_hyperlink, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,25 @@ namespace Slic3r
|
||||
userMachineList.clear();
|
||||
}
|
||||
|
||||
void DeviceManager::set_agent(NetworkAgent* agent)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updating agent for "
|
||||
<< localMachineList.size() << " local and "
|
||||
<< userMachineList.size() << " user machines";
|
||||
m_agent = agent;
|
||||
|
||||
std::lock_guard<std::mutex> lock(listMutex);
|
||||
for (auto& it : localMachineList) {
|
||||
if (it.second) {
|
||||
it.second->set_agent(agent);
|
||||
}
|
||||
}
|
||||
for (auto& it : userMachineList) {
|
||||
if (it.second) {
|
||||
it.second->set_agent(agent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::EnableMultiMachine(bool enable)
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
|
||||
public:
|
||||
NetworkAgent* get_agent() const { return m_agent; }
|
||||
void set_agent(NetworkAgent* agent) { m_agent = agent; }
|
||||
void set_agent(NetworkAgent* agent);
|
||||
|
||||
void start_refresher();
|
||||
void stop_refresher();
|
||||
|
||||
@@ -135,6 +135,8 @@ public:
|
||||
MachineObject(DeviceManager* manager, NetworkAgent* agent, std::string name, std::string id, std::string ip);
|
||||
~MachineObject();
|
||||
|
||||
void set_agent(NetworkAgent* agent) { m_agent = agent; }
|
||||
|
||||
public:
|
||||
enum ActiveState {
|
||||
NotActive,
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
//#include "ConfigWizard.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Jobs/BoostThreadWorker.hpp"
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
|
||||
#include "Widgets/HyperLink.hpp" // ORCA
|
||||
|
||||
#define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1)
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -72,7 +75,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
||||
|
||||
sizer_download_failed->Add(m_statictext_download_failed, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
auto m_download_hyperlink = new wxHyperlinkCtrl(m_panel_download_failed, wxID_ANY, _L("click here to see more info"), download_failed_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
|
||||
// ORCA standardized HyperLink
|
||||
auto m_download_hyperlink = new HyperLink(m_panel_download_failed, _L("click here to see more info"), download_failed_url);
|
||||
sizer_download_failed->Add(m_download_hyperlink, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
|
||||
@@ -93,7 +97,8 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
||||
|
||||
sizer_install_failed->Add(m_statictext_install_failed, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
auto m_install_hyperlink = new wxHyperlinkCtrl(m_panel_install_failed, wxID_ANY, _L("click here to see more info"), install_failed_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
|
||||
// ORCA standardized HyperLink
|
||||
auto m_install_hyperlink = new HyperLink(m_panel_install_failed, _L("click here to see more info"), install_failed_url);
|
||||
sizer_install_failed->Add(m_install_hyperlink, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
|
||||
@@ -203,6 +208,16 @@ void DownloadProgressDialog::update_release_note(std::string release_note, std::
|
||||
|
||||
std::unique_ptr<UpgradeNetworkJob> DownloadProgressDialog::make_job() { return std::make_unique<UpgradeNetworkJob>(); }
|
||||
|
||||
void DownloadProgressDialog::on_finish() { wxGetApp().restart_networking(); }
|
||||
void DownloadProgressDialog::on_finish()
|
||||
{
|
||||
if (wxGetApp().hot_reload_network_plugin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageDialog dlg(nullptr,
|
||||
_L("The network plugin was installed but could not be loaded. Please restart the application."),
|
||||
_L("Restart Required"), wxOK | wxICON_INFORMATION);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -147,15 +147,10 @@ FilamentGroupPopup::FilamentGroupPopup(wxWindow *parent) : PopupWindow(parent, w
|
||||
{
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
const std::string wiki_path = Slic3r::resources_dir() + "/wiki/filament_group_wiki_zh.html";
|
||||
const std::string wiki_path = Slic3r::resources_dir() + "/wiki/filament_group_wiki_zh.html"; // NEEDFIX this link is broken
|
||||
|
||||
auto* wiki_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wiki_link = new wxStaticText(this, wxID_ANY, _L("Learn more"));
|
||||
wiki_link->SetBackgroundColour(BackGroundColor);
|
||||
wiki_link->SetForegroundColour(GreenColor);
|
||||
wiki_link->SetFont(Label::Body_12.Underlined());
|
||||
wiki_link->SetCursor(wxCursor(wxCURSOR_HAND));
|
||||
wiki_link->Bind(wxEVT_LEFT_DOWN, [wiki_path](wxMouseEvent &) { wxLaunchDefaultBrowser(wxString(wiki_path.c_str())); });
|
||||
wiki_link = new HyperLink(this, _L("Wiki Guide"), wxString(wiki_path.c_str())); // ORCA
|
||||
wiki_sizer->Add(wiki_link, 0, wxALIGN_CENTER | wxALL, FromDIP(3));
|
||||
|
||||
button_sizer->Add(wiki_sizer, 0, wxLEFT, horizontal_margin);
|
||||
|
||||
@@ -804,17 +804,13 @@ void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle)
|
||||
msg_dlg.ShowModal();
|
||||
}
|
||||
|
||||
if (preset_bundle)
|
||||
m_nozzle_nums = preset_bundle->get_printer_extruder_count();
|
||||
|
||||
// set to color print by default if use multi extruders
|
||||
if (m_nozzle_nums > 1) {
|
||||
m_view_type_sel = std::distance(view_type_items.begin(),std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::Summary));
|
||||
set_view_type(libvgcode::EViewType::Summary);
|
||||
} else {
|
||||
m_view_type_sel = std::distance(view_type_items.begin(),std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::ColorPrint));
|
||||
set_view_type(libvgcode::EViewType::ColorPrint);
|
||||
}
|
||||
// Orca:
|
||||
// Default view type at first slice.
|
||||
// May be overridden in load() once we know how many tools are actually used in the G-code.
|
||||
m_nozzle_nums = preset_bundle ? preset_bundle->get_printer_extruder_count() : 1;
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), EViewType::FeatureType);
|
||||
m_view_type_sel = (it != view_type_items.end()) ? std::distance(view_type_items.begin(), it) : 0;
|
||||
set_view_type(EViewType::FeatureType);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished");
|
||||
}
|
||||
@@ -1087,6 +1083,22 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
m_max_print_height = gcode_result.printable_height;
|
||||
m_z_offset = gcode_result.z_offset;
|
||||
|
||||
load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
|
||||
|
||||
// ORCA: Only show filament/color print preview if more than one tool/extruder is actually used in the toolpaths.
|
||||
// Only reset back to Toolpaths (FeatureType) if we are currently in ColorPrint and this load is single-tool.
|
||||
if (m_extruder_ids.size() > 1) {
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), EViewType::ColorPrint);
|
||||
if (it != view_type_items.end())
|
||||
m_view_type_sel = std::distance(view_type_items.begin(), it);
|
||||
set_view_type(EViewType::ColorPrint);
|
||||
} else if (m_view_type == EViewType::ColorPrint) {
|
||||
auto it = std::find(view_type_items.begin(), view_type_items.end(), EViewType::FeatureType);
|
||||
if (it != view_type_items.end())
|
||||
m_view_type_sel = std::distance(view_type_items.begin(), it);
|
||||
set_view_type(EViewType::FeatureType);
|
||||
}
|
||||
|
||||
// BBS: data for rendering color arrangement recommendation
|
||||
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
// Orca hack: Hide filament group for non-bbl printers
|
||||
|
||||
+156
-32
@@ -5879,6 +5879,7 @@ static const float cameraProjection[16] = {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.
|
||||
void GLCanvas3D::_render_3d_navigator()
|
||||
{
|
||||
if (!wxGetApp().show_3d_navigator()) {
|
||||
m_canvas_toolbar_pos[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5925,7 +5926,7 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
}
|
||||
|
||||
const float size = 128 * sc;
|
||||
m_axis_button_pos[0] = size - 10;
|
||||
m_canvas_toolbar_pos[0] = size;
|
||||
const auto result = ImGuizmo::ViewManipulate(cameraView, cameraProjection, ImGuizmo::OPERATION::ROTATE, ImGuizmo::MODE::WORLD, nullptr,
|
||||
camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), ImVec2(size, size),
|
||||
0x00101010);
|
||||
@@ -5961,7 +5962,6 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
|
||||
request_extra_frame();
|
||||
}
|
||||
_render_camera_toolbar();
|
||||
}
|
||||
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
||||
@@ -7815,6 +7815,8 @@ void GLCanvas3D::_render_overlays()
|
||||
m_labels.render(sorted_instances);
|
||||
|
||||
_render_3d_navigator();
|
||||
|
||||
_render_canvas_toolbar();
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_style_editor()
|
||||
@@ -8453,47 +8455,169 @@ void GLCanvas3D::_render_return_toolbar() const
|
||||
imgui.end();
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_camera_toolbar()
|
||||
void GLCanvas3D::_render_canvas_toolbar()
|
||||
{
|
||||
float font_size = ImGui::GetFontSize();
|
||||
float sc = get_scale();
|
||||
ImVec2 button_icon_size = ImVec2(font_size * 2.5, font_size * 2.5);
|
||||
ImGuiWrapper &imgui = *wxGetApp().imgui();
|
||||
float sc = get_scale();
|
||||
|
||||
ImGuiWrapper &imgui = *wxGetApp().imgui();
|
||||
float window_width = button_icon_size.x + imgui.scaled(2.0f);
|
||||
float window_height = button_icon_size.y + imgui.scaled(2.0f);
|
||||
#ifdef WIN32
|
||||
const int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
|
||||
sc *= (float) dpi / (float) DPI_DEFAULT;
|
||||
#endif // WIN32
|
||||
|
||||
Size cnv_size = get_canvas_size();
|
||||
m_axis_button_pos[1] = cnv_size.get_height() - button_icon_size[1] - 20 * sc;
|
||||
imgui.set_next_window_pos(m_axis_button_pos[0], m_axis_button_pos[1], ImGuiCond_Always, 0, 0);
|
||||
#ifdef __WINDOWS__
|
||||
imgui.set_next_window_size(window_width, window_height, ImGuiCond_Always);
|
||||
#endif
|
||||
ImVec2 btn_size = ImVec2(36.f, 36.f) * sc;
|
||||
ImVec2 margin = ImVec2(m_canvas_toolbar_pos[0] > 0 ? 0.f : (10.f * sc), 10.f * sc);
|
||||
ImVec2 spacing = ImVec2(6.f, 6.f) * sc;
|
||||
ImVec2 padding = ImVec2(2.f, 2.f) * sc;
|
||||
Vec2i32 pos = {
|
||||
m_canvas_toolbar_pos[0] + margin.x,
|
||||
get_canvas_size().get_height() - margin.y
|
||||
};
|
||||
bool zoom_btn = wxGetApp().show_canvas_zoom_button();
|
||||
|
||||
imgui.begin(_L("Toggle Axis"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove |
|
||||
imgui.set_next_window_pos(pos[0], pos[1], ImGuiCond_Always, 0, 1); // pivot bottom-left
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0 );
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding , {0,0});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing , {0,0});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding , padding); // without padding images clipping
|
||||
|
||||
imgui.begin(_L("Canvas Toolbar"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);//
|
||||
|
||||
ImTextureID normal_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_AXIS_TOGGLE_DARK : GLGizmosManager::MENU_ICON_NAME::IC_AXIS_TOGGLE);
|
||||
ImTextureID hover_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_AXIS_TOGGLE_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_AXIS_TOGGLE_HOVER);
|
||||
ImTextureID m_normal_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU_DARK : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU);
|
||||
ImTextureID m_hover_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_MENU_HOVER);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0});
|
||||
|
||||
if (ImGui::ImageButton3(normal_id, hover_id, button_icon_size, ImVec2(0, 0), ImVec2(1, 1), -1,
|
||||
ImVec4(0, 0, 0, 0), ImVec4(1, 1, 1, 1), ImVec2(10, 0))) {
|
||||
//select_view("plate");
|
||||
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D || m_canvas_type == ECanvasType::CanvasPreview) {
|
||||
toggle_world_axes_visibility(false);
|
||||
if (ImGui::ImageButton3(m_normal_id, m_hover_id, btn_size)) {
|
||||
if(!ImGui::IsPopupOpen("CanvasToolbarMenu")){
|
||||
ImGui::SetNextWindowPos(ImVec2(pos[0] + padding.x, pos[1] - padding.y - (zoom_btn ? (btn_size.y + spacing.y) : 0.f)), ImGuiCond_Always, ImVec2(0, 1)); // pivot bottom-left
|
||||
ImGui::OpenPopup("CanvasToolbarMenu");
|
||||
}
|
||||
}
|
||||
|
||||
if(zoom_btn){
|
||||
ImGui::Dummy({ 0, spacing.y});
|
||||
|
||||
ImTextureID z_normal_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_DARK : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM);
|
||||
ImTextureID z_hover_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_HOVER);
|
||||
|
||||
if (ImGui::ImageButton3(z_normal_id, z_hover_id, btn_size)) {
|
||||
select_view("plate");
|
||||
if (m_selection.is_empty()) {
|
||||
if (m_canvas_type == ECanvasType::CanvasAssembleView)
|
||||
zoom_to_volumes();
|
||||
else
|
||||
zoom_to_bed();
|
||||
} else {
|
||||
zoom_to_selection();
|
||||
}
|
||||
} else if (ImGui::IsItemHovered()) {
|
||||
auto tooltip = _L("Fit camera to scene or selected object.");
|
||||
auto width = ImGui::CalcTextSize(tooltip.c_str()).x + imgui.scaled(2.0f);
|
||||
imgui.tooltip(tooltip, width);
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
auto temp_tooltip = _L("Toggle Axis");
|
||||
auto width = ImGui::CalcTextSize(temp_tooltip.c_str()).x + imgui.scaled(2.0f);
|
||||
imgui.tooltip(temp_tooltip, width);
|
||||
|
||||
ImGui::PopStyleVar(4); // Window
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg , m_is_dark ? ImGuiWrapper::COL_TOOLBAR_BG_DARK : ImGuiWrapper::COL_TOOLBAR_BG);
|
||||
ImGui::PushStyleColor(ImGuiCol_Separator , m_is_dark ? ImVec4(1, 1, 1, .20f) : ImVec4(0, 0, 0, .2f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text , m_is_dark ? ImVec4(1, 1, 1, .88f) : ImVec4(50 / 255.f, 58 / 255.f, 61 / 255.f, 1.f));
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled , m_is_dark ? ImVec4(1, 1, 1, .44f) : ImVec4(50 / 255.f, 58 / 255.f, 61 / 255.f, .5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered , ImVec4(0, 0, 0, 0.f)); // bg color for menu item
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderActive , ImGuiWrapper::COL_ORCA);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.f );
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding , 8.f * sc);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.f * sc);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding , 2.f * sc);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding , ImVec2(4.f, 10.f) * sc);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing , ImVec2(0.f, 8.f ) * sc);
|
||||
|
||||
if (ImGui::BeginPopup("CanvasToolbarMenu")) {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true);
|
||||
|
||||
Plater* p = wxGetApp().plater();
|
||||
AppConfig* cfg = wxGetApp().app_config;
|
||||
|
||||
auto create_menu_item = [this, sc](
|
||||
const std::string& name,
|
||||
bool enable,
|
||||
bool condition,
|
||||
const std::function<void()>& action
|
||||
) {
|
||||
ImGui::Dummy({2.f * sc,0});
|
||||
ImGui::SameLine();
|
||||
if (ImGui::BBLMenuItem((" " + _u8L(name)).c_str(), nullptr, false, enable, ImGui::CalcTextSize(_u8L(name).c_str()).y))
|
||||
action();
|
||||
ImGui::SameLine(12.f * sc);
|
||||
ImGui::TextColored(enable ? ImVec4(1,1,1,1) : ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled), "%s", into_u8(condition ? ImGui::VisibleIcon : ImGui::HiddenIcon).c_str());
|
||||
};
|
||||
|
||||
create_menu_item( "3D Navigator",
|
||||
m_canvas_type != ECanvasType::CanvasAssembleView, // not work on assembly
|
||||
wxGetApp().show_3d_navigator(),
|
||||
[this]{
|
||||
wxGetApp().toggle_show_3d_navigator();
|
||||
ImGui::CloseCurrentPopup(); // Close popup to show changes on UI
|
||||
}
|
||||
);
|
||||
|
||||
create_menu_item( "Zoom button",
|
||||
true, // work on all
|
||||
wxGetApp().show_canvas_zoom_button(),
|
||||
[this]{
|
||||
wxGetApp().toggle_canvas_zoom_button();
|
||||
ImGui::CloseCurrentPopup(); // Close popup to show changes on UI
|
||||
}
|
||||
);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
create_menu_item( "Overhangs",
|
||||
m_canvas_type == ECanvasType::CanvasView3D, // work only on prepare
|
||||
p->is_view3D_overhang_shown(),
|
||||
[this, p]{p->show_view3D_overhang(!p->is_view3D_overhang_shown());}
|
||||
);
|
||||
|
||||
create_menu_item( "Outline",
|
||||
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
|
||||
wxGetApp().show_outline(),
|
||||
[this]{wxGetApp().toggle_show_outline();}
|
||||
);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
create_menu_item( "Perspective",
|
||||
true, // work on all
|
||||
cfg->get_bool("use_perspective_camera"),
|
||||
[this, &cfg]{
|
||||
cfg->set_bool("use_perspective_camera", !(cfg->get_bool("use_perspective_camera")));
|
||||
wxGetApp().update_ui_from_settings();
|
||||
}
|
||||
);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
create_menu_item( "Axes",
|
||||
m_canvas_type != ECanvasType::CanvasAssembleView, // not work on assembly
|
||||
m_show_world_axes,
|
||||
[this]{toggle_world_axes_visibility(false);}
|
||||
);
|
||||
|
||||
// will add an option for gridlines in here
|
||||
|
||||
create_menu_item( "Labels",
|
||||
m_canvas_type == ECanvasType::CanvasView3D, // work only on prepare
|
||||
p->are_view3D_labels_shown(),
|
||||
[this, p]{p->show_view3D_labels(!p->are_view3D_labels_shown());}
|
||||
);
|
||||
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImGui::PopStyleColor(6);
|
||||
ImGui::PopStyleVar(6);
|
||||
|
||||
imgui.end();
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ private:
|
||||
mutable IMToolbar m_sel_plate_toolbar;
|
||||
mutable GLToolbar m_assemble_view_toolbar;
|
||||
mutable IMReturnToolbar m_return_toolbar;
|
||||
mutable Vec2i32 m_axis_button_pos = {128, 5};
|
||||
mutable Vec2i32 m_canvas_toolbar_pos = {140, 5};
|
||||
mutable float m_sc{1};
|
||||
mutable float m_paint_toolbar_width;
|
||||
|
||||
@@ -1255,7 +1255,7 @@ private:
|
||||
void _render_imgui_select_plate_toolbar();
|
||||
void _render_assemble_view_toolbar() const;
|
||||
void _render_return_toolbar() const;
|
||||
void _render_camera_toolbar();
|
||||
void _render_canvas_toolbar();
|
||||
void _render_separator_toolbar_right() const;
|
||||
void _render_separator_toolbar_left() const;
|
||||
void _render_collapse_toolbar() const;
|
||||
|
||||
@@ -108,7 +108,12 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
try{
|
||||
|
||||
if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) {
|
||||
auto vec_new = std::make_unique<ConfigOptionBoolsNullable>(1, boost::any_cast<unsigned char>(value) );
|
||||
const auto v = boost::any_cast<unsigned char>(value);
|
||||
auto vec_new = std::make_unique<ConfigOptionBoolsNullable>(1, v);
|
||||
if (v == ConfigOptionBoolsNullable::nil_value()) {
|
||||
vec_new->set_at_to_nil(0);
|
||||
}
|
||||
|
||||
config.option<ConfigOptionBoolsNullable>(opt_key)->set_at(vec_new.get(), opt_index, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
+522
-43
@@ -42,6 +42,7 @@
|
||||
#include <wx/menuitem.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/busyinfo.h>
|
||||
#include <wx/dir.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/filefn.h>
|
||||
@@ -98,6 +99,7 @@
|
||||
#include "UnsavedChangesDialog.hpp"
|
||||
#include "SavePresetDialog.hpp"
|
||||
#include "PrintHostDialogs.hpp"
|
||||
#include "NetworkPluginDialog.hpp"
|
||||
#include "DesktopIntegrationDialog.hpp"
|
||||
#include "SendSystemInfoDialog.hpp"
|
||||
#include "ParamsDialog.hpp"
|
||||
@@ -959,15 +961,7 @@ void GUI_App::post_init()
|
||||
|
||||
m_show_gcode_window = app_config->get_bool("show_gcode_window");
|
||||
if (m_networking_need_update) {
|
||||
//updating networking
|
||||
int ret = updating_bambu_networking();
|
||||
if (!ret) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<":networking plugin updated successfully";
|
||||
//restart_networking();
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<<":networking plugin updated failed";
|
||||
}
|
||||
show_network_plugin_download_dialog(false);
|
||||
}
|
||||
|
||||
// Start preset sync after project opened, otherwise we could have preset change during project opening which could cause crash
|
||||
@@ -1177,12 +1171,20 @@ std::string GUI_App::get_plugin_url(std::string name, std::string country_code)
|
||||
{
|
||||
std::string url = get_http_url(country_code);
|
||||
|
||||
std::string curr_version = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BAMBU_NETWORK_AGENT_VERSION;
|
||||
std::string curr_version;
|
||||
if (NetworkAgent::use_legacy_network) {
|
||||
curr_version = BAMBU_NETWORK_AGENT_VERSION_LEGACY;
|
||||
} else if (name == "plugins" && app_config) {
|
||||
std::string user_version = app_config->get_network_plugin_version();
|
||||
curr_version = user_version.empty() ? BBL::get_latest_network_version() : user_version;
|
||||
} else {
|
||||
curr_version = BBL::get_latest_network_version();
|
||||
}
|
||||
|
||||
std::string using_version = curr_version.substr(0, 9) + "00";
|
||||
if (name == "cameratools")
|
||||
using_version = curr_version.substr(0, 6) + "00.00";
|
||||
url += (boost::format("?slicer/%1%/cloud=%2%") % name % using_version).str();
|
||||
//url += (boost::format("?slicer/plugins/cloud=%1%") % "01.01.00.00").str();
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -1413,6 +1415,32 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
return InstallStatusUnzipFailed;
|
||||
}
|
||||
|
||||
boost::filesystem::path legacy_lib_path, legacy_lib_backup;
|
||||
bool had_existing_legacy = false;
|
||||
if (name == "plugins") {
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
legacy_lib_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
|
||||
#elif defined(__WXMAC__)
|
||||
legacy_lib_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
|
||||
#else
|
||||
legacy_lib_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
|
||||
#endif
|
||||
legacy_lib_backup = legacy_lib_path;
|
||||
legacy_lib_backup += ".backup";
|
||||
|
||||
if (boost::filesystem::exists(legacy_lib_path)) {
|
||||
had_existing_legacy = true;
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::rename(legacy_lib_path, legacy_lib_backup, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[install_plugin] failed to backup existing legacy library: " << ec.message();
|
||||
had_existing_legacy = false;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "[install_plugin] backed up existing legacy library";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
mz_zip_archive_file_stat stat;
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("[install_plugin]: %1%, got %2% files")%__LINE__ %num_entries;
|
||||
@@ -1487,6 +1515,47 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
}
|
||||
|
||||
close_zip_reader(&archive);
|
||||
|
||||
if (name == "plugins") {
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
if (config_version.empty()) {
|
||||
config_version = BBL::get_latest_network_version();
|
||||
BOOST_LOG_TRIVIAL(info) << "[install_plugin] config_version was empty, using latest: " << config_version;
|
||||
app_config->set_network_plugin_version(config_version);
|
||||
GUI::wxGetApp().CallAfter([this] {
|
||||
if (app_config)
|
||||
app_config->save();
|
||||
});
|
||||
}
|
||||
if (!config_version.empty() && boost::filesystem::exists(legacy_lib_path)) {
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
auto versioned_lib = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".dll");
|
||||
#elif defined(__WXMAC__)
|
||||
auto versioned_lib = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".dylib");
|
||||
#else
|
||||
auto versioned_lib = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".so");
|
||||
#endif
|
||||
BOOST_LOG_TRIVIAL(info) << "[install_plugin] renaming newly extracted " << legacy_lib_path.string() << " to " << versioned_lib.string();
|
||||
boost::system::error_code ec;
|
||||
if (boost::filesystem::exists(versioned_lib)) {
|
||||
boost::filesystem::remove(versioned_lib, ec);
|
||||
}
|
||||
boost::filesystem::rename(legacy_lib_path, versioned_lib, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << "[install_plugin] failed to rename to versioned: " << ec.message();
|
||||
}
|
||||
}
|
||||
|
||||
if (had_existing_legacy && boost::filesystem::exists(legacy_lib_backup)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "[install_plugin] restoring backed up legacy library";
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::rename(legacy_lib_backup, legacy_lib_path, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[install_plugin] failed to restore legacy library backup: " << ec.message();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
fs::path dir_path(plugin_folder);
|
||||
if (fs::exists(dir_path) && fs::is_directory(dir_path)) {
|
||||
@@ -1519,6 +1588,8 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (pro_fn)
|
||||
pro_fn(InstallStatusInstallCompleted, 100, cancel);
|
||||
if (name == "plugins")
|
||||
@@ -1572,6 +1643,247 @@ void GUI_App::restart_networking()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" exit, m_agent=%1%")%m_agent;
|
||||
}
|
||||
|
||||
// Network plugin hot reload timeout constants (in milliseconds)
|
||||
namespace {
|
||||
constexpr int CALLBACK_DRAIN_TIMEOUT_MS = 200; // Time to drain pending CallAfter callbacks
|
||||
constexpr int NETWORK_IDLE_TIMEOUT_MS = 500; // Max wait for network operations to complete
|
||||
constexpr int FINAL_DRAIN_TIMEOUT_MS = 100; // Final event processing before destruction
|
||||
constexpr int POLL_INTERVAL_MS = 50; // Polling interval for state checks
|
||||
constexpr int MAX_YIELD_ITERATIONS = 20; // Maximum wxYield calls per drain cycle
|
||||
}
|
||||
|
||||
// Process pending wx events with bounded iteration count
|
||||
void GUI_App::drain_pending_events(int timeout_ms)
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(timeout_ms);
|
||||
int yield_count = 0;
|
||||
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
// Process pending events
|
||||
if (wxTheApp) {
|
||||
wxTheApp->ProcessPendingEvents();
|
||||
}
|
||||
|
||||
// Bounded wxYield to prevent infinite loops
|
||||
if (yield_count < MAX_YIELD_ITERATIONS) {
|
||||
wxYield();
|
||||
++yield_count;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for network operations to complete with state verification
|
||||
bool GUI_App::wait_for_network_idle(int timeout_ms)
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(timeout_ms);
|
||||
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
if (!m_agent) {
|
||||
return true; // Agent already gone
|
||||
}
|
||||
|
||||
// Verify all operations completed
|
||||
bool server_disconnected = !m_agent->is_server_connected();
|
||||
|
||||
if (server_disconnected) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": network is idle";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process events while waiting
|
||||
if (wxTheApp) {
|
||||
wxTheApp->ProcessPendingEvents();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": timeout after " << timeout_ms
|
||||
<< "ms, server_connected=" << (m_agent ? m_agent->is_server_connected() : false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GUI_App::hot_reload_network_plugin()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": starting hot reload";
|
||||
|
||||
wxBusyCursor busy;
|
||||
wxBusyInfo info(_L("Reloading network plugin..."), mainframe);
|
||||
wxYield();
|
||||
wxWindowDisabler disabler;
|
||||
|
||||
if (mainframe) {
|
||||
int current_tab = mainframe->m_tabpanel->GetSelection();
|
||||
if (current_tab == MainFrame::TabPosition::tpMonitor) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload";
|
||||
mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tp3DEditor);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": stopping sync thread before unload";
|
||||
if (m_user_sync_token) {
|
||||
m_user_sync_token.reset();
|
||||
}
|
||||
if (m_sync_update_thread.joinable()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": waiting for sync thread to finish";
|
||||
m_sync_update_thread.join();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": sync thread finished";
|
||||
}
|
||||
|
||||
if (m_agent) {
|
||||
// Phase 1: Clear all callbacks (stops new invocations)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 1 - clearing callbacks";
|
||||
m_agent->set_on_ssdp_msg_fn(nullptr);
|
||||
m_agent->set_on_user_login_fn(nullptr);
|
||||
m_agent->set_on_printer_connected_fn(nullptr);
|
||||
m_agent->set_on_server_connected_fn(nullptr);
|
||||
m_agent->set_on_http_error_fn(nullptr);
|
||||
m_agent->set_on_subscribe_failure_fn(nullptr);
|
||||
m_agent->set_on_message_fn(nullptr);
|
||||
m_agent->set_on_user_message_fn(nullptr);
|
||||
m_agent->set_on_local_connect_fn(nullptr);
|
||||
m_agent->set_on_local_message_fn(nullptr);
|
||||
m_agent->set_queue_on_main_fn(nullptr);
|
||||
|
||||
// Phase 2: Drain pending CallAfter callbacks (bounded)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 2 - draining callbacks";
|
||||
drain_pending_events(CALLBACK_DRAIN_TIMEOUT_MS);
|
||||
|
||||
// Phase 3: Stop operations and verify return values
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 3 - stopping operations";
|
||||
bool discovery_stopped = m_agent->start_discovery(false, false);
|
||||
int disconnect_result = m_agent->disconnect_printer();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": discovery_stopped=" << discovery_stopped
|
||||
<< ", disconnect_result=" << disconnect_result;
|
||||
|
||||
// Phase 4: Wait for idle with state verification
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 4 - waiting for idle";
|
||||
bool became_idle = wait_for_network_idle(NETWORK_IDLE_TIMEOUT_MS);
|
||||
if (!became_idle) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": proceeding despite timeout";
|
||||
}
|
||||
|
||||
// Phase 5: Final bounded drain before destruction
|
||||
drain_pending_events(FINAL_DRAIN_TIMEOUT_MS);
|
||||
|
||||
// Phase 6: Destroy agent
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 6 - destroying agent";
|
||||
delete m_agent;
|
||||
m_agent = nullptr;
|
||||
}
|
||||
|
||||
// Phase 7: Unload module
|
||||
if (Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 7 - unloading module";
|
||||
drain_pending_events(FINAL_DRAIN_TIMEOUT_MS);
|
||||
int unload_result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unload_result=" << unload_result;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": calling restart_networking";
|
||||
restart_networking();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": restart_networking returned";
|
||||
|
||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||
bool success = m_agent != nullptr && !loaded_version.empty() && loaded_version != "00.00.00.00";
|
||||
bool user_logged_in = m_agent && m_agent->is_user_login();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": after restart_networking, is_user_login = " << user_logged_in
|
||||
<< ", m_agent = " << (m_agent ? "valid" : "null")
|
||||
<< ", version = " << loaded_version;
|
||||
|
||||
if (success && m_agent && m_device_manager) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": connecting to cloud server";
|
||||
m_agent->connect_server();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": re-subscribing to cloud printers";
|
||||
m_device_manager->add_user_subscribe();
|
||||
}
|
||||
|
||||
if (mainframe && mainframe->m_monitor) {
|
||||
mainframe->m_monitor->update_network_version_footer();
|
||||
mainframe->m_monitor->set_default();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reset monitor panel";
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": hot reload " << (success ? "successful" : "failed");
|
||||
return success;
|
||||
}
|
||||
|
||||
std::string GUI_App::get_latest_network_version() const
|
||||
{
|
||||
return BBL::get_latest_network_version();
|
||||
}
|
||||
|
||||
bool GUI_App::has_network_update_available() const
|
||||
{
|
||||
std::string current = Slic3r::NetworkAgent::get_version();
|
||||
std::string latest = get_latest_network_version();
|
||||
|
||||
if (current.empty() || current == "00.00.00.00")
|
||||
return false;
|
||||
|
||||
return current.substr(0, 8) != latest.substr(0, 8);
|
||||
}
|
||||
|
||||
void GUI_App::show_network_plugin_download_dialog(bool is_update)
|
||||
{
|
||||
auto load_error = Slic3r::NetworkAgent::get_load_error();
|
||||
|
||||
NetworkPluginDownloadDialog::Mode mode;
|
||||
if (load_error.has_error) {
|
||||
mode = NetworkPluginDownloadDialog::Mode::CorruptedPlugin;
|
||||
} else if (is_update) {
|
||||
mode = NetworkPluginDownloadDialog::Mode::UpdateAvailable;
|
||||
} else {
|
||||
mode = NetworkPluginDownloadDialog::Mode::MissingPlugin;
|
||||
}
|
||||
|
||||
std::string current_version = Slic3r::NetworkAgent::get_version();
|
||||
|
||||
NetworkPluginDownloadDialog dlg(mainframe, mode, current_version,
|
||||
load_error.message, load_error.technical_details);
|
||||
|
||||
int result = dlg.ShowModal();
|
||||
|
||||
switch (result) {
|
||||
case NetworkPluginDownloadDialog::RESULT_DOWNLOAD:
|
||||
{
|
||||
std::string selected = dlg.get_selected_version();
|
||||
app_config->set_network_plugin_version(selected);
|
||||
app_config->save();
|
||||
|
||||
DownloadProgressDialog download_dlg(_L("Downloading Network Plugin"));
|
||||
download_dlg.ShowModal();
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkPluginDownloadDialog::RESULT_REMIND_LATER:
|
||||
app_config->set_remind_network_update_later(true);
|
||||
app_config->save();
|
||||
break;
|
||||
|
||||
case NetworkPluginDownloadDialog::RESULT_SKIP_VERSION:
|
||||
{
|
||||
std::string latest = get_latest_network_version();
|
||||
app_config->add_skipped_network_version(latest);
|
||||
app_config->save();
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkPluginDownloadDialog::RESULT_DONT_ASK:
|
||||
app_config->set_network_update_prompt_disabled(true);
|
||||
app_config->save();
|
||||
break;
|
||||
|
||||
case NetworkPluginDownloadDialog::RESULT_SKIP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::remove_old_networking_plugins()
|
||||
{
|
||||
std::string data_dir_str = data_dir();
|
||||
@@ -1601,8 +1913,20 @@ bool GUI_App::check_networking_version()
|
||||
if (!network_ver.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "get_network_agent_version=" << network_ver;
|
||||
}
|
||||
std::string studio_ver = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BAMBU_NETWORK_AGENT_VERSION;
|
||||
if (network_ver.length() >= 8) {
|
||||
|
||||
std::string studio_ver;
|
||||
if (NetworkAgent::use_legacy_network) {
|
||||
studio_ver = BAMBU_NETWORK_AGENT_VERSION_LEGACY;
|
||||
} else if (app_config) {
|
||||
std::string user_version = app_config->get_network_plugin_version();
|
||||
studio_ver = user_version.empty() ? BBL::get_latest_network_version() : user_version;
|
||||
} else {
|
||||
studio_ver = BBL::get_latest_network_version();
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "check_networking_version: network_ver=" << network_ver << ", expected=" << studio_ver;
|
||||
|
||||
if (network_ver.length() >= 8 && studio_ver.length() >= 8) {
|
||||
if (network_ver.substr(0,8) == studio_ver.substr(0,8)) {
|
||||
m_networking_compatible = true;
|
||||
return true;
|
||||
@@ -2072,7 +2396,13 @@ void GUI_App::init_app_config()
|
||||
}
|
||||
|
||||
// Change current dirtory of application
|
||||
[[maybe_unused]] auto unused_result = chdir(encode_path((Slic3r::data_dir() + "/log").c_str()).c_str());
|
||||
|
||||
#ifdef _WIN32
|
||||
[[maybe_unused]] auto unused_result = _chdir(encode_path((Slic3r::data_dir() + "/log").c_str()).c_str());
|
||||
#else
|
||||
[[maybe_unused]] auto unused_result = chdir(encode_path((Slic3r::data_dir() + "/log").c_str()).c_str());
|
||||
#endif
|
||||
|
||||
} else {
|
||||
m_datadir_redefined = true;
|
||||
}
|
||||
@@ -2669,8 +2999,11 @@ bool GUI_App::on_init_inner()
|
||||
std::map<std::string, std::string> extra_headers = get_extra_header();
|
||||
Slic3r::Http::set_extra_headers(extra_headers);
|
||||
|
||||
// Orca: select network plugin version
|
||||
NetworkAgent::use_legacy_network = app_config->get_bool("legacy_networking");
|
||||
// Orca: select network plugin version based on configured version string
|
||||
std::string configured_version = app_config->get_network_plugin_version();
|
||||
NetworkAgent::use_legacy_network = (configured_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||
BOOST_LOG_TRIVIAL(info) << "Network plugin mode: "
|
||||
<< (NetworkAgent::use_legacy_network ? ("legacy (version: " + std::string(BAMBU_NETWORK_AGENT_VERSION_LEGACY) + ")") : ("modern (version: " + configured_version + ")"));
|
||||
// Force legacy network plugin if debugger attached
|
||||
// See https://github.com/bambulab/BambuStudio/issues/6726
|
||||
/* if (!NetworkAgent::use_legacy_network) {
|
||||
@@ -2858,78 +3191,103 @@ void GUI_App::copy_network_if_available()
|
||||
{
|
||||
if (app_config->get("update_network_plugin") != "true")
|
||||
return;
|
||||
std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst;
|
||||
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto plugin_folder = data_dir_path / "plugins";
|
||||
auto cache_folder = data_dir_path / "ota";
|
||||
std::string changelog_file = cache_folder.string() + "/network_plugins.json";
|
||||
|
||||
std::string cached_version;
|
||||
if (boost::filesystem::exists(changelog_file)) {
|
||||
try {
|
||||
boost::nowide::ifstream ifs(changelog_file);
|
||||
json j;
|
||||
ifs >> j;
|
||||
if (j.contains("version"))
|
||||
cached_version = j["version"];
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": cached_version = " << cached_version;
|
||||
} catch (nlohmann::detail::parse_error& err) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << changelog_file << " failed: " << err.what();
|
||||
}
|
||||
}
|
||||
|
||||
if (cached_version.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no version found in changelog, aborting copy";
|
||||
app_config->set("update_network_plugin", "false");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst;
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
network_library = cache_folder.string() + "/bambu_networking.dll";
|
||||
player_library = cache_folder.string() + "/BambuSource.dll";
|
||||
live555_library = cache_folder.string() + "/live555.dll";
|
||||
network_library_dst = plugin_folder.string() + "/bambu_networking.dll";
|
||||
player_library_dst = plugin_folder.string() + "/BambuSource.dll";
|
||||
player_library = cache_folder.string() + "/BambuSource.dll";
|
||||
live555_library = cache_folder.string() + "/live555.dll";
|
||||
network_library_dst = plugin_folder.string() + "/" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + cached_version + ".dll";
|
||||
player_library_dst = plugin_folder.string() + "/BambuSource.dll";
|
||||
live555_library_dst = plugin_folder.string() + "/live555.dll";
|
||||
#elif defined(__WXMAC__)
|
||||
network_library = cache_folder.string() + "/libbambu_networking.dylib";
|
||||
player_library = cache_folder.string() + "/libBambuSource.dylib";
|
||||
live555_library = cache_folder.string() + "/liblive555.dylib";
|
||||
network_library_dst = plugin_folder.string() + "/libbambu_networking.dylib";
|
||||
network_library_dst = plugin_folder.string() + "/lib" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + cached_version + ".dylib";
|
||||
player_library_dst = plugin_folder.string() + "/libBambuSource.dylib";
|
||||
live555_library_dst = plugin_folder.string() + "/liblive555.dylib";
|
||||
#else
|
||||
network_library = cache_folder.string() + "/libbambu_networking.so";
|
||||
player_library = cache_folder.string() + "/libBambuSource.so";
|
||||
live555_library = cache_folder.string() + "/liblive555.so";
|
||||
network_library_dst = plugin_folder.string() + "/libbambu_networking.so";
|
||||
player_library_dst = plugin_folder.string() + "/libBambuSource.so";
|
||||
player_library = cache_folder.string() + "/libBambuSource.so";
|
||||
live555_library = cache_folder.string() + "/liblive555.so";
|
||||
network_library_dst = plugin_folder.string() + "/lib" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + cached_version + ".so";
|
||||
player_library_dst = plugin_folder.string() + "/libBambuSource.so";
|
||||
live555_library_dst = plugin_folder.string() + "/liblive555.so";
|
||||
#endif
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": checking network_library " << network_library << ", player_library " << player_library;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": checking network_library " << network_library << ", player_library " << player_library;
|
||||
if (!boost::filesystem::exists(plugin_folder)) {
|
||||
BOOST_LOG_TRIVIAL(info)<< __FUNCTION__ << ": create directory "<<plugin_folder.string();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create directory " << plugin_folder.string();
|
||||
boost::filesystem::create_directory(plugin_folder);
|
||||
}
|
||||
std::string error_message;
|
||||
if (boost::filesystem::exists(network_library)) {
|
||||
CopyFileResult cfr = copy_file(network_library, network_library_dst, error_message, false);
|
||||
if (cfr != CopyFileResult::SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message;
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
||||
fs::permissions(network_library_dst, perms);
|
||||
fs::remove(network_library);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying network library from" << network_library << " to " << network_library_dst<<" successfully.";
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying network library from " << network_library << " to " << network_library_dst << " successfully.";
|
||||
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, cached_version);
|
||||
app_config->save();
|
||||
}
|
||||
|
||||
if (boost::filesystem::exists(player_library)) {
|
||||
CopyFileResult cfr = copy_file(player_library, player_library_dst, error_message, false);
|
||||
if (cfr != CopyFileResult::SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message;
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
||||
fs::permissions(player_library_dst, perms);
|
||||
fs::remove(player_library);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying player library from" << player_library << " to " << player_library_dst<<" successfully.";
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying player library from " << player_library << " to " << player_library_dst << " successfully.";
|
||||
}
|
||||
|
||||
if (boost::filesystem::exists(live555_library)) {
|
||||
CopyFileResult cfr = copy_file(live555_library, live555_library_dst, error_message, false);
|
||||
if (cfr != CopyFileResult::SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message;
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
||||
fs::permissions(live555_library_dst, perms);
|
||||
fs::remove(live555_library);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying live555 library from" << live555_library << " to " << live555_library_dst<<" successfully.";
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying live555 library from " << live555_library << " to " << live555_library_dst << " successfully.";
|
||||
}
|
||||
if (boost::filesystem::exists(changelog_file))
|
||||
fs::remove(changelog_file);
|
||||
@@ -2940,13 +3298,39 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
{
|
||||
bool create_network_agent = false;
|
||||
auto should_load_networking_plugin = app_config->get_bool("installed_networking");
|
||||
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
|
||||
if(!should_load_networking_plugin) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Don't load plugin as installed_networking is false";
|
||||
} else {
|
||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module();
|
||||
if (config_version.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
|
||||
m_networking_need_update = true;
|
||||
|
||||
if (!m_device_manager)
|
||||
m_device_manager = new Slic3r::DeviceManager();
|
||||
if (!m_user_manager)
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
|
||||
return false;
|
||||
}
|
||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
__retry:
|
||||
if (!load_agent_dll) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
|
||||
|
||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
std::string config_base = BBL::extract_base_version(config_version);
|
||||
if (config_base != loaded_version) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version << " to loaded " << loaded_version;
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, loaded_version);
|
||||
app_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (check_networking_version()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
||||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
@@ -2963,7 +3347,7 @@ __retry:
|
||||
if (try_backup) {
|
||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true);
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version);
|
||||
try_backup = false;
|
||||
goto __retry;
|
||||
}
|
||||
@@ -3042,6 +3426,24 @@ __retry:
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
}
|
||||
|
||||
if (create_network_agent && m_networking_compatible && !NetworkAgent::use_legacy_network) {
|
||||
app_config->clear_remind_network_update_later();
|
||||
|
||||
if (has_network_update_available()) {
|
||||
std::string latest = get_latest_network_version();
|
||||
|
||||
bool should_prompt = !app_config->is_network_update_prompt_disabled()
|
||||
&& !app_config->is_network_version_skipped(latest)
|
||||
&& !app_config->should_remind_network_update_later();
|
||||
|
||||
if (should_prompt) {
|
||||
CallAfter([this]() {
|
||||
show_network_plugin_download_dialog(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3415,7 +3817,7 @@ void GUI_App::set_side_menu_popup_status(bool status)
|
||||
m_side_popup_status = status;
|
||||
}
|
||||
|
||||
void GUI_App::link_to_network_check()
|
||||
std::string GUI_App::link_to_network_check()
|
||||
{
|
||||
std::string url;
|
||||
std::string country_code = app_config->get_country_code();
|
||||
@@ -3430,10 +3832,11 @@ void GUI_App::link_to_network_check()
|
||||
else {
|
||||
url = "https://status.bambulab.com";
|
||||
}
|
||||
wxLaunchDefaultBrowser(url);
|
||||
//wxLaunchDefaultBrowser(url);
|
||||
return url; // ORCA
|
||||
}
|
||||
|
||||
void GUI_App::link_to_lan_only_wiki()
|
||||
std::string GUI_App::link_to_lan_only_wiki()
|
||||
{
|
||||
std::string url;
|
||||
std::string country_code = app_config->get_country_code();
|
||||
@@ -3447,7 +3850,8 @@ void GUI_App::link_to_lan_only_wiki()
|
||||
else {
|
||||
url = "https://wiki.bambulab.com/en/knowledge-sharing/enable-lan-mode";
|
||||
}
|
||||
wxLaunchDefaultBrowser(url);
|
||||
//wxLaunchDefaultBrowser(url);
|
||||
return url; // ORCA
|
||||
}
|
||||
|
||||
bool GUI_App::tabs_as_menu() const
|
||||
@@ -4880,7 +5284,84 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user)
|
||||
bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
|
||||
{
|
||||
if (dev_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << msg;
|
||||
if (msg == "wait_info") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, wait_info";
|
||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev)
|
||||
return true;
|
||||
MachineObject* obj = dev->get_selected_machine();
|
||||
if (obj && m_agent)
|
||||
m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
|
||||
if (!m_show_error_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Retrieving printer information, please try again later."), "", wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (msg == "update_studio") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, update_studio";
|
||||
if (!m_show_error_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Please try updating OrcaSlicer and then try again."), "", wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (msg == "update_fixed_studio") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, update_fixed_studio";
|
||||
if (!m_show_error_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Please try updating OrcaSlicer and then try again."), "", wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (msg == "cert_expired") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, cert_expired";
|
||||
if (!m_show_error_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("The certificate has expired. Please check the time settings or update OrcaSlicer and try again."), "", wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (msg == "cert_revoked") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, cert_revoked";
|
||||
if (!m_show_error_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("The certificate is no longer valid and the printing functions are unavailable."), "", wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (msg == "update_firmware_studio") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, firmware internal error";
|
||||
if (!m_show_error_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Internal error. Please try upgrading the firmware and OrcaSlicer version. If the issue persists, contact support."), "", wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (msg == "unsigned_studio") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio";
|
||||
MessageDialog msg_dlg(nullptr,
|
||||
_L("Bambu Lab has implemented a signature verification check in their network plugin that restricts "
|
||||
"third-party software from communicating with your printer.\n\n"
|
||||
"As a result, some printing functions are unavailable in OrcaSlicer."),
|
||||
_L("Network Plugin Restriction"), wxAPPLY | wxOK);
|
||||
m_show_error_msgdlg = true;
|
||||
msg_dlg.ShowModal();
|
||||
m_show_error_msgdlg = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (msg == "device_cert_installed") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, device_cert_installed";
|
||||
@@ -4889,7 +5370,6 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
|
||||
obj->update_device_cert_state(true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (msg == "device_cert_uninstalled") {
|
||||
@@ -4899,7 +5379,6 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
|
||||
obj->update_device_cert_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,6 +312,7 @@ private:
|
||||
bool m_adding_script_handler { false };
|
||||
bool m_side_popup_status{false};
|
||||
bool m_show_http_errpr_msgdlg{false};
|
||||
bool m_show_error_msgdlg{false};
|
||||
wxString m_info_dialog_content;
|
||||
HttpServer m_http_server;
|
||||
bool m_show_gcode_window{true};
|
||||
@@ -355,6 +356,9 @@ public:
|
||||
bool show_3d_navigator() const { return app_config->get_bool("show_3d_navigator"); }
|
||||
void toggle_show_3d_navigator() const { app_config->set_bool("show_3d_navigator", !show_3d_navigator()); }
|
||||
|
||||
bool show_canvas_zoom_button() const { return app_config->get_bool("show_canvas_zoom_button"); }
|
||||
void toggle_canvas_zoom_button() const { app_config->set_bool("show_canvas_zoom_button", !show_canvas_zoom_button()); }
|
||||
|
||||
bool show_outline() const { return app_config->get_bool("show_outline"); }
|
||||
void toggle_show_outline() const { app_config->set_bool("show_outline", !show_outline()); }
|
||||
|
||||
@@ -403,8 +407,8 @@ public:
|
||||
//update side popup status
|
||||
bool get_side_menu_popup_status();
|
||||
void set_side_menu_popup_status(bool status);
|
||||
void link_to_network_check();
|
||||
void link_to_lan_only_wiki();
|
||||
std::string link_to_network_check(); // ORCA
|
||||
std::string link_to_lan_only_wiki(); // ORCA
|
||||
|
||||
const wxColour& get_label_clr_modified() { return m_color_label_modified; }
|
||||
const wxColour& get_label_clr_sys() { return m_color_label_sys; }
|
||||
@@ -686,6 +690,11 @@ public:
|
||||
void restart_networking();
|
||||
void check_config_updates_from_updater() { check_updates(false); }
|
||||
|
||||
void show_network_plugin_download_dialog(bool is_update = false);
|
||||
bool hot_reload_network_plugin();
|
||||
std::string get_latest_network_version() const;
|
||||
bool has_network_update_available() const;
|
||||
|
||||
private:
|
||||
int updating_bambu_networking();
|
||||
bool on_init_inner();
|
||||
@@ -694,6 +703,8 @@ private:
|
||||
void init_networking_callbacks();
|
||||
void init_app_config();
|
||||
void remove_old_networking_plugins();
|
||||
void drain_pending_events(int timeout_ms);
|
||||
bool wait_for_network_idle(int timeout_ms);
|
||||
//BBS set extra header for http request
|
||||
std::map<std::string, std::string> get_extra_header();
|
||||
void init_http_extra_header();
|
||||
@@ -712,6 +723,7 @@ private:
|
||||
bool m_init_app_config_from_older { false };
|
||||
bool m_datadir_redefined { false };
|
||||
std::string m_older_data_dir_path;
|
||||
bool m_unsigned_plugin_warning_shown { false };
|
||||
boost::optional<Semver> m_last_config_version;
|
||||
bool m_config_corrupted { false };
|
||||
std::string m_open_method;
|
||||
|
||||
@@ -87,14 +87,14 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::OBJECT_C
|
||||
{"precise_z_height", "",10}
|
||||
|
||||
}},
|
||||
{ L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},
|
||||
{"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6}, {"support_threshold_overlap", "",6}, {"support_on_build_plate_only", "",7},
|
||||
{"support_filament", "",8},{"support_interface_filament", "",9},{"support_expansion", "",24},{"support_style", "",25},
|
||||
{"tree_support_brim_width", "",26}, {"tree_support_branch_angle", "",10},{"tree_support_branch_angle_organic","",10}, {"tree_support_wall_count", "",11},{"tree_support_branch_diameter_angle", "",11},//tree support
|
||||
{"support_top_z_distance", "",13},{"support_bottom_z_distance", "",12},{"support_base_pattern", "",14},{"support_base_pattern_spacing", "",15},
|
||||
{"support_interface_top_layers", "",16},{"support_interface_bottom_layers", "",17},{"support_interface_spacing", "",18},{"support_bottom_interface_spacing", "",19},
|
||||
{"support_object_xy_distance", "",20}, {"bridge_no_support", "",21},{"max_bridge_length", "",22},{"support_critical_regions_only", "",23},{"support_remove_small_overhang","",27},
|
||||
{"support_object_first_layer_gap","",28}
|
||||
{ L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},{"brim_use_efc_outline", "",4},
|
||||
{"enable_support", "",5},{"support_type", "",6},{"support_threshold_angle", "",7}, {"support_threshold_overlap", "",8}, {"support_on_build_plate_only", "",9},
|
||||
{"support_filament", "",10},{"support_interface_filament", "",11},{"support_expansion", "",12},{"support_style", "",13},
|
||||
{"tree_support_brim_width", "",14}, {"tree_support_branch_angle", "",15},{"tree_support_branch_angle_organic","",16}, {"tree_support_wall_count", "",17},{"tree_support_branch_diameter_angle", "",18},//tree support
|
||||
{"support_bottom_z_distance", "",19},{"support_top_z_distance", "",20},{"support_base_pattern", "",21},{"support_base_pattern_spacing", "",22},
|
||||
{"support_interface_top_layers", "",23},{"support_interface_bottom_layers", "",24},{"support_interface_spacing", "",25},{"support_bottom_interface_spacing", "",26},
|
||||
{"support_object_xy_distance", "",27}, {"bridge_no_support", "",28},{"max_bridge_length", "",29},{"support_critical_regions_only", "",30},{"support_remove_small_overhang","",31},
|
||||
{"support_object_first_layer_gap","",32}
|
||||
}},
|
||||
{ L("Speed"), {{"support_speed", "",12}, {"support_interface_speed", "",13}
|
||||
}}
|
||||
|
||||
@@ -281,23 +281,43 @@ bool GLGizmosManager::init_icon_textures()
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/axis_toggle.svg", 64, 64, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_AXIS_TOGGLE, texture_id));
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_menu.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_MENU, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/axis_toggle_hover.svg", 64, 64, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_AXIS_TOGGLE_HOVER, texture_id));
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_menu_hover.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_MENU_HOVER, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/axis_toggle_dark.svg", 64, 64, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_AXIS_TOGGLE_DARK, texture_id));
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_menu_dark.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_MENU_DARK, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/axis_toggle_hover_dark.svg", 64, 64, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_AXIS_TOGGLE_DARK_HOVER, texture_id));
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_menu_dark_hover.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_MENU_DARK_HOVER, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_zoom.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_zoom_hover.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM_HOVER, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_zoom_dark.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM_DARK, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/canvas_zoom_dark_hover.svg", 72, 72, texture_id))
|
||||
icon_list.insert(std::make_pair((int) IC_CANVAS_ZOOM_DARK_HOVER, texture_id));
|
||||
else
|
||||
return false;
|
||||
|
||||
|
||||
@@ -170,10 +170,14 @@ public:
|
||||
IC_TOOLBAR_TOOLTIP,
|
||||
IC_TOOLBAR_TOOLTIP_HOVER,
|
||||
IC_NAME_COUNT,
|
||||
IC_AXIS_TOGGLE,
|
||||
IC_AXIS_TOGGLE_HOVER,
|
||||
IC_AXIS_TOGGLE_DARK,
|
||||
IC_AXIS_TOGGLE_DARK_HOVER,
|
||||
IC_CANVAS_MENU,
|
||||
IC_CANVAS_MENU_HOVER,
|
||||
IC_CANVAS_MENU_DARK,
|
||||
IC_CANVAS_MENU_DARK_HOVER,
|
||||
IC_CANVAS_ZOOM,
|
||||
IC_CANVAS_ZOOM_HOVER,
|
||||
IC_CANVAS_ZOOM_DARK,
|
||||
IC_CANVAS_ZOOM_DARK_HOVER,
|
||||
};
|
||||
|
||||
explicit GLGizmosManager(GLCanvas3D& parent);
|
||||
|
||||
@@ -29,6 +29,14 @@ ImageDPIFrame::ImageDPIFrame()
|
||||
#ifdef __APPLE__
|
||||
SetWindowStyleFlag(GetWindowStyleFlag() | wxSTAY_ON_TOP);
|
||||
#endif
|
||||
|
||||
// ORCA add border
|
||||
Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {
|
||||
wxPaintDC dc(this);
|
||||
dc.SetPen(StateColor::darkModeColorFor(wxColour("#DBDBDB")));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 0);
|
||||
});
|
||||
|
||||
m_sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -427,16 +427,18 @@ void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const Da
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
// write data from base into volume
|
||||
base.write(*volume);
|
||||
|
||||
GUI_App &app = wxGetApp(); // may be move to input
|
||||
|
||||
if (volume->name != base.volume_name) {
|
||||
volume->name = base.volume_name;
|
||||
// write data from base into volume
|
||||
base.write(*volume);
|
||||
|
||||
const ObjectList *obj_list = app.obj_list();
|
||||
if (obj_list != nullptr)
|
||||
update_name_in_list(*obj_list, *volume);
|
||||
} else {
|
||||
// write data from base into volume
|
||||
base.write(*volume);
|
||||
}
|
||||
|
||||
ModelObject *object = volume->get_object();
|
||||
|
||||
@@ -342,7 +342,7 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
//model_object->ensure_on_bed();
|
||||
//BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": model_object->ensure_on_bed()";
|
||||
|
||||
if (m_instances && wxGetApp().app_config->get("auto_arrange") == "true") {
|
||||
if (m_instances) {// && wxGetApp().app_config->get("auto_arrange") == "true") {
|
||||
m_plater->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
m_plater->arrange();
|
||||
}
|
||||
|
||||
@@ -3113,7 +3113,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
// help
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Tutorial"), _L("Calibration help"),
|
||||
[this](wxCommandEvent&) {
|
||||
std::string url = "https://github.com/OrcaSlicer/OrcaSlicer/wiki/Calibration";
|
||||
std::string url = "https://www.orcaslicer.com/wiki/Calibration";
|
||||
if (const std::string country_code = wxGetApp().app_config->get_country_code(); country_code == "CN") {
|
||||
// Use gitee mirror for China users
|
||||
url = "https://gitee.com/n0isyfox/orca-slicer-docs/wikis/%E6%A0%A1%E5%87%86/%E6%89%93%E5%8D%B0%E5%8F%82%E6%95%B0%E6%A0%A1%E5%87%86";
|
||||
@@ -3229,7 +3229,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
// help
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Tutorial"), _L("Calibration help"),
|
||||
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/OrcaSlicer/OrcaSlicer/wiki/Calibration", wxBROWSER_NEW_WINDOW); }, "", nullptr,
|
||||
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://www.orcaslicer.com/wiki/Calibration", wxBROWSER_NEW_WINDOW); }, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
m_menubar->Append(calib_menu,wxString::Format("&%s", _L("Calibration")));
|
||||
|
||||
@@ -769,7 +769,7 @@ bool MediaPlayCtrl::start_stream_service(bool *need_install)
|
||||
auto file_dll = tools_dir + dll;
|
||||
auto file_dll2 = plugins_dir + dll;
|
||||
if (!boost::filesystem::exists(file_dll) || boost::filesystem::last_write_time(file_dll) != boost::filesystem::last_write_time(file_dll2))
|
||||
boost::filesystem::copy_file(file_dll2, file_dll, boost::filesystem::copy_option::overwrite_if_exists);
|
||||
boost::filesystem::copy_file(file_dll2, file_dll, boost::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir),
|
||||
boost::process::windows::create_no_window,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "Tab.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
@@ -195,6 +197,11 @@ void MonitorPanel::init_tabpanel()
|
||||
m_hms_panel = new HMSPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), "", false);
|
||||
|
||||
std::string network_ver = Slic3r::NetworkAgent::get_version();
|
||||
if (!network_ver.empty()) {
|
||||
m_tabpanel->SetFooterText(wxString::Format("Network plugin v%s", network_ver));
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
|
||||
}
|
||||
@@ -407,6 +414,7 @@ bool MonitorPanel::Show(bool show)
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (show) {
|
||||
start_update();
|
||||
update_network_version_footer();
|
||||
|
||||
m_refresh_timer->Stop();
|
||||
m_refresh_timer->SetOwner(this);
|
||||
@@ -517,5 +525,25 @@ void MonitorPanel::jump_to_LiveView()
|
||||
m_status_info_panel->get_media_play_ctrl()->jump_to_play();
|
||||
}
|
||||
|
||||
void MonitorPanel::update_network_version_footer()
|
||||
{
|
||||
std::string binary_version = Slic3r::NetworkAgent::get_version();
|
||||
if (binary_version.empty())
|
||||
return;
|
||||
|
||||
std::string configured_version = wxGetApp().app_config->get_network_plugin_version();
|
||||
std::string suffix = BBL::extract_suffix(configured_version);
|
||||
std::string configured_base = BBL::extract_base_version(configured_version);
|
||||
|
||||
wxString footer_text;
|
||||
if (!suffix.empty() && configured_base == binary_version) {
|
||||
footer_text = wxString::Format("Network plugin v%s (%s)", binary_version, suffix);
|
||||
} else {
|
||||
footer_text = wxString::Format("Network plugin v%s", binary_version);
|
||||
}
|
||||
|
||||
m_tabpanel->SetFooterText(footer_text);
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
@@ -157,6 +157,7 @@ public:
|
||||
|
||||
void jump_to_HMS();
|
||||
void jump_to_LiveView();
|
||||
void update_network_version_footer();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -586,7 +586,8 @@ wxBoxSizer *Newer3mfVersionDialog::get_msg_sizer()
|
||||
if (file_version_newer) {
|
||||
text1 = new wxStaticText(this, wxID_ANY, _L("The 3MF file version is in Beta and it is newer than the current OrcaSlicer version."));
|
||||
wxStaticText * text2 = new wxStaticText(this, wxID_ANY, _L("If you would like to try Orca Slicer Beta, you may click to"));
|
||||
wxHyperlinkCtrl *github_link = new wxHyperlinkCtrl(this, wxID_ANY, _L("Download Beta Version"), "https://github.com/bambulab/BambuStudio/releases");
|
||||
// ORCA standardized HyperLink
|
||||
HyperLink * github_link = new HyperLink(this, _L("Download Beta Version"), "https://github.com/SoftFever/OrcaSlicer/releases");
|
||||
horizontal_sizer->Add(text2, 0, wxEXPAND, 0);
|
||||
horizontal_sizer->Add(github_link, 0, wxEXPAND | wxLEFT, 5);
|
||||
|
||||
@@ -672,11 +673,9 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
|
||||
|
||||
wxBoxSizer* sizer_link = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_link_server_state = new wxHyperlinkCtrl(this, wxID_ANY, _L("Check the status of current system services"), "");
|
||||
// ORCA standardized HyperLink
|
||||
m_link_server_state = new HyperLink(this, _L("Check the status of current system services"), wxGetApp().link_to_network_check());
|
||||
m_link_server_state->SetFont(::Label::Body_13);
|
||||
m_link_server_state->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_network_check(); });
|
||||
m_link_server_state->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); });
|
||||
m_link_server_state->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); });
|
||||
|
||||
sizer_link->Add(m_link_server_state, 0, wxALL, 0);
|
||||
|
||||
@@ -690,11 +689,9 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
|
||||
m_text_proposal->SetFont(::Label::Body_14);
|
||||
m_text_proposal->SetForegroundColour(0x323A3C);
|
||||
|
||||
m_text_wiki = new wxHyperlinkCtrl(this, wxID_ANY, _L("How to use LAN only mode"), "");
|
||||
// ORCA standardized HyperLink
|
||||
m_text_wiki = new HyperLink(this, _L("How to use LAN only mode"), wxGetApp().link_to_lan_only_wiki());
|
||||
m_text_wiki->SetFont(::Label::Body_13);
|
||||
m_text_wiki->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_lan_only_wiki(); });
|
||||
m_text_wiki->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); });
|
||||
m_text_wiki->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); });
|
||||
|
||||
sizer_help->Add(m_text_proposal, 0, wxEXPAND, 0);
|
||||
sizer_help->Add(m_text_wiki, 0, wxALL, 0);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
#include "BBLStatusBar.hpp"
|
||||
#include "BBLStatusBarSend.hpp"
|
||||
#include "libslic3r/Semver.hpp"
|
||||
@@ -424,9 +425,9 @@ public:
|
||||
|
||||
private:
|
||||
Label* m_text_basic;
|
||||
wxHyperlinkCtrl* m_link_server_state;
|
||||
HyperLink* m_link_server_state; // ORCA
|
||||
Label* m_text_proposal;
|
||||
wxHyperlinkCtrl* m_text_wiki;
|
||||
HyperLink* m_text_wiki; // ORCA
|
||||
Button * m_button_confirm;
|
||||
|
||||
public:
|
||||
|
||||
@@ -653,7 +653,7 @@ void MultiMachineManagerPage::start_timer()
|
||||
|
||||
m_flipping_timer->SetOwner(this);
|
||||
m_flipping_timer->Start(1000);
|
||||
wxPostEvent(this, wxTimerEvent());
|
||||
wxPostEvent(this, wxTimerEvent(*m_flipping_timer));
|
||||
}
|
||||
|
||||
void MultiMachineManagerPage::update_page_number()
|
||||
|
||||
@@ -61,7 +61,7 @@ bool MultiMachinePage::Show(bool show)
|
||||
m_refresh_timer->Stop();
|
||||
m_refresh_timer->SetOwner(this);
|
||||
m_refresh_timer->Start(2000);
|
||||
wxPostEvent(this, wxTimerEvent());
|
||||
wxPostEvent(this, wxTimerEvent(*m_refresh_timer));
|
||||
}
|
||||
else {
|
||||
m_refresh_timer->Stop();
|
||||
@@ -96,7 +96,7 @@ void MultiMachinePage::init_timer()
|
||||
m_refresh_timer = new wxTimer();
|
||||
//m_refresh_timer->SetOwner(this);
|
||||
//m_refresh_timer->Start(8000);
|
||||
//wxPostEvent(this, wxTimerEvent());
|
||||
//wxPostEvent(this, wxTimerEvent(*m_refresh_timer));
|
||||
}
|
||||
|
||||
void MultiMachinePage::on_timer(wxTimerEvent& event)
|
||||
@@ -482,7 +482,7 @@ bool MultiMachinePickPage::Show(bool show)
|
||||
//m_refresh_timer->Stop();
|
||||
//m_refresh_timer->SetOwner(this);
|
||||
//m_refresh_timer->Start(4000);
|
||||
//wxPostEvent(this, wxTimerEvent());
|
||||
//wxPostEvent(this, wxTimerEvent(*m_refresh_timer));
|
||||
}
|
||||
else {
|
||||
//m_refresh_timer->Stop();
|
||||
|
||||
@@ -1402,7 +1402,7 @@ void CloudTaskManagerPage::start_timer()
|
||||
|
||||
m_flipping_timer->SetOwner(this);
|
||||
m_flipping_timer->Start(1000);
|
||||
wxPostEvent(this, wxTimerEvent());
|
||||
wxPostEvent(this, wxTimerEvent(*m_flipping_timer));
|
||||
}
|
||||
|
||||
void CloudTaskManagerPage::on_timer(wxTimerEvent& event)
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
#include "NetworkPluginDialog.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/collpane.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode mode,
|
||||
const std::string& current_version,
|
||||
const std::string& error_message,
|
||||
const std::string& error_details)
|
||||
: DPIDialog(parent, wxID_ANY, mode == Mode::UpdateAvailable ?
|
||||
_L("Network Plugin Update Available") : _L("Bambu Network Plugin Required"),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
, m_mode(mode)
|
||||
, m_error_message(error_message)
|
||||
, m_error_details(error_details)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
|
||||
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
main_sizer->Add(m_line_top, 0, wxEXPAND, 0);
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
|
||||
if (mode == Mode::UpdateAvailable) {
|
||||
create_update_available_ui(current_version);
|
||||
} else {
|
||||
create_missing_plugin_ui();
|
||||
}
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::create_missing_plugin_ui()
|
||||
{
|
||||
wxBoxSizer* main_sizer = static_cast<wxBoxSizer*>(GetSizer());
|
||||
|
||||
auto* desc = new wxStaticText(this, wxID_ANY,
|
||||
m_mode == Mode::CorruptedPlugin ?
|
||||
_L("The Bambu Network Plugin is corrupted or incompatible. Please reinstall it.") :
|
||||
_L("The Bambu Network Plugin is required for cloud features, printer discovery, and remote printing."));
|
||||
desc->SetFont(::Label::Body_13);
|
||||
desc->Wrap(FromDIP(400));
|
||||
main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
|
||||
|
||||
if (!m_error_message.empty()) {
|
||||
auto* error_label = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format(_L("Error: %s"), wxString::FromUTF8(m_error_message)));
|
||||
error_label->SetFont(::Label::Body_13);
|
||||
error_label->SetForegroundColour(wxColour(208, 93, 93));
|
||||
error_label->Wrap(FromDIP(400));
|
||||
main_sizer->Add(error_label, 0, wxLEFT | wxRIGHT, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
|
||||
if (!m_error_details.empty()) {
|
||||
m_details_pane = new wxCollapsiblePane(this, wxID_ANY, _L("Show details"));
|
||||
auto* pane = m_details_pane->GetPane();
|
||||
auto* pane_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* details_text = new wxStaticText(pane, wxID_ANY, wxString::FromUTF8(m_error_details));
|
||||
details_text->SetFont(wxGetApp().code_font());
|
||||
details_text->Wrap(FromDIP(380));
|
||||
pane_sizer->Add(details_text, 0, wxALL, FromDIP(10));
|
||||
|
||||
pane->SetSizer(pane_sizer);
|
||||
main_sizer->Add(m_details_pane, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
}
|
||||
}
|
||||
|
||||
auto* version_label = new wxStaticText(this, wxID_ANY, _L("Version to install:"));
|
||||
version_label->SetFont(::Label::Body_13);
|
||||
main_sizer->Add(version_label, 0, wxLEFT | wxRIGHT, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
|
||||
|
||||
setup_version_selector();
|
||||
main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
btn_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
StateColor btn_bg_green(
|
||||
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
|
||||
|
||||
StateColor btn_bg_white(
|
||||
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
|
||||
auto* btn_download = new Button(this, _L("Download and Install"));
|
||||
btn_download->SetBackgroundColor(btn_bg_green);
|
||||
btn_download->SetBorderColor(*wxWHITE);
|
||||
btn_download->SetTextColor(*wxWHITE);
|
||||
btn_download->SetFont(::Label::Body_12);
|
||||
btn_download->SetMinSize(wxSize(FromDIP(120), FromDIP(24)));
|
||||
btn_download->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this);
|
||||
btn_sizer->Add(btn_download, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
auto* btn_skip = new Button(this, _L("Skip for Now"));
|
||||
btn_skip->SetBackgroundColor(btn_bg_white);
|
||||
btn_skip->SetBorderColor(wxColour(38, 46, 48));
|
||||
btn_skip->SetFont(::Label::Body_12);
|
||||
btn_skip->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
|
||||
btn_skip->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip, this);
|
||||
btn_sizer->Add(btn_skip, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::create_update_available_ui(const std::string& current_version)
|
||||
{
|
||||
wxBoxSizer* main_sizer = static_cast<wxBoxSizer*>(GetSizer());
|
||||
|
||||
auto* desc = new wxStaticText(this, wxID_ANY,
|
||||
_L("A new version of the Bambu Network Plugin is available."));
|
||||
desc->SetFont(::Label::Body_13);
|
||||
desc->Wrap(FromDIP(400));
|
||||
main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
|
||||
|
||||
auto* version_text = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format(_L("Current version: %s"), wxString::FromUTF8(current_version)));
|
||||
version_text->SetFont(::Label::Body_13);
|
||||
main_sizer->Add(version_text, 0, wxLEFT | wxRIGHT, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
|
||||
auto* update_label = new wxStaticText(this, wxID_ANY, _L("Update to version:"));
|
||||
update_label->SetFont(::Label::Body_13);
|
||||
main_sizer->Add(update_label, 0, wxLEFT | wxRIGHT, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
|
||||
|
||||
setup_version_selector();
|
||||
main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
btn_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
StateColor btn_bg_green(
|
||||
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
|
||||
|
||||
StateColor btn_bg_white(
|
||||
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
|
||||
auto* btn_download = new Button(this, _L("Update Now"));
|
||||
btn_download->SetBackgroundColor(btn_bg_green);
|
||||
btn_download->SetBorderColor(*wxWHITE);
|
||||
btn_download->SetTextColor(*wxWHITE);
|
||||
btn_download->SetFont(::Label::Body_12);
|
||||
btn_download->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
|
||||
btn_download->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this);
|
||||
btn_sizer->Add(btn_download, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
auto* btn_remind = new Button(this, _L("Remind Later"));
|
||||
btn_remind->SetBackgroundColor(btn_bg_white);
|
||||
btn_remind->SetBorderColor(wxColour(38, 46, 48));
|
||||
btn_remind->SetFont(::Label::Body_12);
|
||||
btn_remind->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
|
||||
btn_remind->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_remind_later, this);
|
||||
btn_sizer->Add(btn_remind, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
auto* btn_skip = new Button(this, _L("Skip Version"));
|
||||
btn_skip->SetBackgroundColor(btn_bg_white);
|
||||
btn_skip->SetBorderColor(wxColour(38, 46, 48));
|
||||
btn_skip->SetFont(::Label::Body_12);
|
||||
btn_skip->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
|
||||
btn_skip->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip_version, this);
|
||||
btn_sizer->Add(btn_skip, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
auto* btn_dont_ask = new Button(this, _L("Don't Ask Again"));
|
||||
btn_dont_ask->SetBackgroundColor(btn_bg_white);
|
||||
btn_dont_ask->SetBorderColor(wxColour(38, 46, 48));
|
||||
btn_dont_ask->SetFont(::Label::Body_12);
|
||||
btn_dont_ask->SetMinSize(wxSize(FromDIP(110), FromDIP(24)));
|
||||
btn_dont_ask->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_dont_ask, this);
|
||||
btn_sizer->Add(btn_dont_ask, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::setup_version_selector()
|
||||
{
|
||||
m_version_combo = new ComboBox(this, wxID_ANY, wxEmptyString,
|
||||
wxDefaultPosition, wxSize(FromDIP(380), FromDIP(28)), 0, nullptr, wxCB_READONLY);
|
||||
m_version_combo->SetFont(::Label::Body_13);
|
||||
|
||||
m_available_versions = BBL::get_all_available_versions();
|
||||
for (size_t i = 0; i < m_available_versions.size(); ++i) {
|
||||
const auto& ver = m_available_versions[i];
|
||||
wxString label;
|
||||
if (!ver.suffix.empty()) {
|
||||
label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name);
|
||||
} else {
|
||||
label = wxString::FromUTF8(ver.display_name);
|
||||
if (ver.is_latest) {
|
||||
label += wxString(" ") + _L("(Latest)");
|
||||
}
|
||||
}
|
||||
m_version_combo->Append(label);
|
||||
}
|
||||
|
||||
m_version_combo->SetSelection(0);
|
||||
}
|
||||
|
||||
std::string NetworkPluginDownloadDialog::get_selected_version() const
|
||||
{
|
||||
if (!m_version_combo) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int selection = m_version_combo->GetSelection();
|
||||
if (selection < 0 || selection >= static_cast<int>(m_available_versions.size())) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return m_available_versions[selection].version;
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::on_download(wxCommandEvent& evt)
|
||||
{
|
||||
int selection = m_version_combo ? m_version_combo->GetSelection() : 0;
|
||||
if (selection >= 0 && selection < static_cast<int>(m_available_versions.size())) {
|
||||
const std::string& warning = m_available_versions[selection].warning;
|
||||
if (!warning.empty()) {
|
||||
MessageDialog warn_dlg(this, wxString::FromUTF8(warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING);
|
||||
if (warn_dlg.ShowModal() != wxID_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
EndModal(RESULT_DOWNLOAD);
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::on_skip(wxCommandEvent& evt)
|
||||
{
|
||||
EndModal(RESULT_SKIP);
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::on_remind_later(wxCommandEvent& evt)
|
||||
{
|
||||
EndModal(RESULT_REMIND_LATER);
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::on_skip_version(wxCommandEvent& evt)
|
||||
{
|
||||
EndModal(RESULT_SKIP_VERSION);
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::on_dont_ask(wxCommandEvent& evt)
|
||||
{
|
||||
EndModal(RESULT_DONT_ASK);
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
NetworkPluginRestartDialog::NetworkPluginRestartDialog(wxWindow* parent)
|
||||
: DPIDialog(parent, wxID_ANY, _L("Restart Required"),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
|
||||
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
main_sizer->Add(m_line_top, 0, wxEXPAND, 0);
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
auto* icon_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto* icon_bitmap = new wxStaticBitmap(this, wxID_ANY,
|
||||
create_scaled_bitmap("info", nullptr, 64));
|
||||
icon_sizer->Add(icon_bitmap, 0, wxALL, FromDIP(10));
|
||||
|
||||
auto* text_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* desc = new wxStaticText(this, wxID_ANY,
|
||||
_L("The Bambu Network Plugin has been installed successfully."));
|
||||
desc->SetFont(::Label::Body_14);
|
||||
desc->Wrap(FromDIP(350));
|
||||
text_sizer->Add(desc, 0, wxTOP, FromDIP(10));
|
||||
text_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
|
||||
auto* restart_msg = new wxStaticText(this, wxID_ANY,
|
||||
_L("A restart is required to load the new plugin. Would you like to restart now?"));
|
||||
restart_msg->SetFont(::Label::Body_13);
|
||||
restart_msg->Wrap(FromDIP(350));
|
||||
text_sizer->Add(restart_msg, 0, wxBOTTOM, FromDIP(10));
|
||||
|
||||
icon_sizer->Add(text_sizer, 1, wxEXPAND | wxRIGHT, FromDIP(20));
|
||||
main_sizer->Add(icon_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(15));
|
||||
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
btn_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
StateColor btn_bg_green(
|
||||
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
|
||||
|
||||
StateColor btn_bg_white(
|
||||
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
|
||||
auto* btn_restart = new Button(this, _L("Restart Now"));
|
||||
btn_restart->SetBackgroundColor(btn_bg_green);
|
||||
btn_restart->SetBorderColor(*wxWHITE);
|
||||
btn_restart->SetTextColor(*wxWHITE);
|
||||
btn_restart->SetFont(::Label::Body_12);
|
||||
btn_restart->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
|
||||
btn_restart->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
m_restart_now = true;
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
btn_sizer->Add(btn_restart, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
auto* btn_later = new Button(this, _L("Restart Later"));
|
||||
btn_later->SetBackgroundColor(btn_bg_white);
|
||||
btn_later->SetBorderColor(wxColour(38, 46, 48));
|
||||
btn_later->SetFont(::Label::Body_12);
|
||||
btn_later->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
|
||||
btn_later->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
m_restart_now = false;
|
||||
EndModal(wxID_CANCEL);
|
||||
});
|
||||
btn_sizer->Add(btn_later, 0, wxRIGHT, FromDIP(10));
|
||||
|
||||
main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
void NetworkPluginRestartDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef slic3r_GUI_NetworkPluginDialog_hpp_
|
||||
#define slic3r_GUI_NetworkPluginDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include <wx/collpane.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class NetworkPluginDownloadDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
enum class Mode {
|
||||
MissingPlugin,
|
||||
UpdateAvailable,
|
||||
CorruptedPlugin
|
||||
};
|
||||
|
||||
NetworkPluginDownloadDialog(wxWindow* parent, Mode mode,
|
||||
const std::string& current_version = "",
|
||||
const std::string& error_message = "",
|
||||
const std::string& error_details = "");
|
||||
|
||||
std::string get_selected_version() const;
|
||||
|
||||
enum ResultCode {
|
||||
RESULT_DOWNLOAD = wxID_OK,
|
||||
RESULT_SKIP = wxID_CANCEL,
|
||||
RESULT_REMIND_LATER = wxID_APPLY,
|
||||
RESULT_SKIP_VERSION = wxID_IGNORE,
|
||||
RESULT_DONT_ASK = wxID_ABORT
|
||||
};
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
private:
|
||||
void create_missing_plugin_ui();
|
||||
void create_update_available_ui(const std::string& current_version);
|
||||
void setup_version_selector();
|
||||
void on_download(wxCommandEvent& evt);
|
||||
void on_skip(wxCommandEvent& evt);
|
||||
void on_remind_later(wxCommandEvent& evt);
|
||||
void on_skip_version(wxCommandEvent& evt);
|
||||
void on_dont_ask(wxCommandEvent& evt);
|
||||
|
||||
Mode m_mode;
|
||||
ComboBox* m_version_combo{nullptr};
|
||||
wxCollapsiblePane* m_details_pane{nullptr};
|
||||
std::string m_error_message;
|
||||
std::string m_error_details;
|
||||
std::vector<BBL::NetworkLibraryVersionInfo> m_available_versions;
|
||||
};
|
||||
|
||||
class NetworkPluginRestartDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
NetworkPluginRestartDialog(wxWindow* parent);
|
||||
|
||||
bool should_restart_now() const { return m_restart_now; }
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
private:
|
||||
bool m_restart_now{false};
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GUI_NetworkPluginDialog_hpp_
|
||||
@@ -980,7 +980,7 @@ private:
|
||||
|
||||
NotificationData{NotificationType::BBLUserPresetExceedLimit, NotificationLevel::WarningNotificationLevel, BBL_NOTICE_MAX_INTERVAL,
|
||||
_u8L("The number of user presets cached in the cloud has exceeded the upper limit, newly created user presets can only be used locally."),
|
||||
_u8L("Wiki"),
|
||||
_u8L("Wiki Guide"),
|
||||
[](wxEvtHandler* evnthndlr) {
|
||||
wxLaunchDefaultBrowser("https://wiki.bambulab.com/en/software/bambu-studio/3rd-party-printer-profile#cloud-user-presets-limit");
|
||||
return false;
|
||||
|
||||
@@ -53,7 +53,7 @@ OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent,
|
||||
// BBS: new font
|
||||
m_font = Label::Body_14;
|
||||
SetFont(m_font);
|
||||
m_em_unit = em_unit(m_parent);
|
||||
m_em_unit = em_unit(m_parent);
|
||||
m_v_gap = lround(1.2 * m_em_unit);
|
||||
m_v_gap2 = lround(0.8 * m_em_unit);
|
||||
m_h_gap = lround(0.2 * m_em_unit);
|
||||
@@ -597,7 +597,7 @@ void OG_CustomCtrl::msw_rescale()
|
||||
SetFont(m_font);
|
||||
m_em_unit = em_unit(m_parent);
|
||||
m_v_gap = lround(1.2 * m_em_unit);
|
||||
m_v_gap2 = lround(0.8 * m_em_unit);
|
||||
m_v_gap2 = lround(0.8 * m_em_unit);
|
||||
m_h_gap = lround(0.2 * m_em_unit);
|
||||
|
||||
//m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize();
|
||||
@@ -992,7 +992,7 @@ wxPoint OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBi
|
||||
pos.y += lround((height - get_bitmap_size(bmp_undo).GetHeight()) / 2);
|
||||
}
|
||||
#endif
|
||||
wxCoord h_pos = pos.x;
|
||||
wxCoord h_pos = pos.x - ctrl->m_h_gap; // Orca: adjust position to the left
|
||||
wxCoord v_pos = pos.y;
|
||||
|
||||
#ifndef DISABLE_UNDO_SYS
|
||||
|
||||
@@ -47,14 +47,7 @@ wxBoxSizer* ObjColorDialog::create_btn_sizer(long flags,bool exist_error)
|
||||
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
if (!exist_error) {
|
||||
btn_sizer->AddSpacer(FromDIP(25));
|
||||
wxStaticText *tips = new wxStaticText(this, wxID_ANY, _L("Open Wiki for more information >"));
|
||||
/* wxFont font(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false);
|
||||
font.SetUnderlined(true);
|
||||
tips->SetFont(font);*/
|
||||
auto font = tips->GetFont();
|
||||
font.SetUnderlined(true);
|
||||
tips->SetFont(font);
|
||||
tips->SetForegroundColour(wxColour("#009687"));
|
||||
auto *tips = new HyperLink(this, _L("Wiki Guide")); // ORCA
|
||||
tips->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) {
|
||||
bool is_zh = wxGetApp().app_config->get("language") == "zh_CN";
|
||||
if (is_zh) {
|
||||
|
||||
@@ -1312,7 +1312,7 @@ wxString OptionsGroup::get_url(const std::string& path_end)
|
||||
str = str.Left(pos) + anchor;
|
||||
}
|
||||
// Orca: point to sf wiki for seam parameters
|
||||
return wxString::Format(L"https://github.com/OrcaSlicer/OrcaSlicer/wiki/%s", from_u8(path_end));
|
||||
return wxString::Format(L"https://www.orcaslicer.com/wiki/%s", from_u8(path_end));
|
||||
|
||||
}
|
||||
|
||||
|
||||
+35
-18
@@ -220,9 +220,14 @@ wxDEFINE_EVENT(EVT_NOTICE_FULL_SCREEN_CHANGED, IntEvent);
|
||||
|
||||
static string get_diameter_string(float diameter)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << std::fixed << std::setprecision(1) << diameter;
|
||||
return stream.str();
|
||||
std::ostringstream stream; // ORCA ensure 0.25 returned as 0.25. previous code returned as 0.2 because of std::setprecision(1)
|
||||
stream << std::fixed << std::setprecision(2) << diameter; // Use 2 decimals to capture 0.25 / 0.15 reliably
|
||||
std::string s = stream.str();
|
||||
if (s.find('.') != std::string::npos) { // Remove trailing zeros, but keep at least one decimal if needed
|
||||
s.erase(s.find_last_not_of('0') + 1);
|
||||
if (s.back() == '.') s += '0'; // Ensure "1." → "1.0"
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool Plater::has_illegal_filename_characters(const wxString& wxs_name)
|
||||
@@ -574,7 +579,9 @@ void Sidebar::priv::layout_printer(bool isBBL, bool isDual)
|
||||
// ORCA show plate type combo box only when its supported
|
||||
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
panel_printer_bed->Show(isBBL || cfg.opt_bool("support_multi_bed_types"));
|
||||
// Orca: we use preset_bundle.is_bbl_vendor() instead of isBBL to determine if the plate type combo box should be shown
|
||||
// ref: https://github.com/OrcaSlicer/OrcaSlicer/pull/11610#discussion_r2607411847
|
||||
panel_printer_bed->Show(preset_bundle.is_bbl_vendor() || cfg.opt_bool("support_multi_bed_types"));
|
||||
|
||||
extruder_dual_sizer->Show(isDual);
|
||||
|
||||
@@ -1230,7 +1237,8 @@ bool Sidebar::priv::switch_diameter(bool single)
|
||||
}
|
||||
auto preset = wxGetApp().preset_bundle->get_similar_printer_preset({}, diameter.ToStdString());
|
||||
if (preset == nullptr) {
|
||||
MessageDialog dlg(this->plater, "", "");
|
||||
// ORCA add a text. this appears when user tries to change nozzle value but config doesnt have a inherited or compatible preset
|
||||
MessageDialog dlg(this->plater, _L("Configuration incompatible"), _L("Warning"), wxICON_WARNING | wxOK);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
}
|
||||
@@ -2536,9 +2544,18 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
p->layout_printer(preset_bundle.use_bbl_network(), isBBL && is_dual_extruder);
|
||||
auto diameters = wxGetApp().preset_bundle->printers.diameters_of_selected_printer();
|
||||
auto diameter = printer_preset.config.opt_string("printer_variant");
|
||||
auto update_extruder_diameter = [&diameters, &diameter](ExtruderGroup & extruder) {
|
||||
auto update_extruder_diameter = [&diameters, &diameter, &nozzle_diameter](int extruder_index,ExtruderGroup & extruder) {
|
||||
extruder.combo_diameter->Clear();
|
||||
int select = -1;
|
||||
// ORCA if user defined a custom nozzle in printer config select it instead inherited one. this will show correct nozzle diameter in combobox if its exist in nozzle diameters list
|
||||
auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]);
|
||||
if(nozzle_dia != diameter && std::find(diameters.begin(), diameters.end(), nozzle_dia) != diameters.end())
|
||||
diameter = nozzle_dia;
|
||||
// ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias
|
||||
if(diameters[0].empty() && !nozzle_dia.empty()){
|
||||
diameters[0] = nozzle_dia;
|
||||
diameter = nozzle_dia;
|
||||
}
|
||||
for (size_t i = 0; i < diameters.size(); ++i) {
|
||||
if (diameters[i] == diameter)
|
||||
select = extruder.combo_diameter->GetCount();
|
||||
@@ -2552,14 +2569,14 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
AMSCountPopupWindow::UpdateAMSCount(0, p->left_extruder);
|
||||
AMSCountPopupWindow::UpdateAMSCount(1, p->right_extruder);
|
||||
//if (!p->is_switching_diameter) {
|
||||
update_extruder_diameter(*p->left_extruder);
|
||||
update_extruder_diameter(*p->right_extruder);
|
||||
update_extruder_diameter(0, *p->left_extruder);
|
||||
update_extruder_diameter(1, *p->right_extruder);
|
||||
//}
|
||||
p->image_printer_bed->SetBitmap(create_scaled_bitmap(image_path, this, PRINTER_THUMBNAIL_SIZE.GetHeight()));
|
||||
} else {
|
||||
AMSCountPopupWindow::UpdateAMSCount(0, p->single_extruder);
|
||||
//if (!p->is_switching_diameter)
|
||||
update_extruder_diameter(*p->single_extruder);
|
||||
update_extruder_diameter(0, *p->single_extruder);
|
||||
|
||||
// ORCA sync unified nozzle combo box
|
||||
p->combo_nozzle_dia->Clear();
|
||||
@@ -4651,7 +4668,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
"extruder_clearance_radius",
|
||||
"extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
|
||||
"nozzle_height", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle",
|
||||
"brim_width", "brim_object_gap", "brim_type", "nozzle_diameter", "single_extruder_multi_material", "preferred_orientation",
|
||||
"brim_width", "brim_object_gap", "brim_use_efc_outline", "brim_type", "nozzle_diameter", "single_extruder_multi_material", "preferred_orientation",
|
||||
"enable_prime_tower", "wipe_tower_x", "wipe_tower_y", "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_tower_enable_framework",
|
||||
"prime_tower_infill_gap", "prime_volume",
|
||||
"extruder_colour", "filament_colour", "filament_type", "material_colour", "printable_height", "extruder_printable_height", "printer_model", "printer_technology",
|
||||
@@ -14095,10 +14112,10 @@ void Plater::increase_instances(size_t num)
|
||||
|
||||
p->selection_changed();
|
||||
this->p->schedule_background_process();
|
||||
if (wxGetApp().app_config->get("auto_arrange") == "true") {
|
||||
this->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
this->arrange();
|
||||
}
|
||||
//if (wxGetApp().app_config->get("auto_arrange") == "true") {
|
||||
// this->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
// this->arrange();
|
||||
//}
|
||||
}
|
||||
|
||||
void Plater::decrease_instances(size_t num)
|
||||
@@ -14126,10 +14143,10 @@ void Plater::decrease_instances(size_t num)
|
||||
|
||||
p->selection_changed();
|
||||
this->p->schedule_background_process();
|
||||
if (wxGetApp().app_config->get("auto_arrange") == "true") {
|
||||
this->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
this->arrange();
|
||||
}
|
||||
//if (wxGetApp().app_config->get("auto_arrange") == "true") {
|
||||
// this->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
// this->arrange();
|
||||
//}
|
||||
}
|
||||
|
||||
static long GetNumberFromUser( const wxString& msg,
|
||||
|
||||
+131
-13
@@ -14,6 +14,8 @@
|
||||
#include "NetworkTestDialog.hpp"
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
#include "Widgets/RadioGroup.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include "DownloadProgressDialog.hpp"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _MSW_DARK_MODE
|
||||
@@ -78,7 +80,7 @@ std::tuple<wxBoxSizer*, ComboBox*> PreferencesDialog::create_item_combobox_base(
|
||||
return {m_sizer_combox, combobox};
|
||||
}
|
||||
|
||||
wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist)
|
||||
wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange)
|
||||
{
|
||||
unsigned int current_index = 0;
|
||||
|
||||
@@ -90,8 +92,10 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
|
||||
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
|
||||
|
||||
//// save config
|
||||
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param](wxCommandEvent& e) {
|
||||
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, onchange](wxCommandEvent& e) {
|
||||
app_config->set(param, std::to_string(e.GetSelection()));
|
||||
if (onchange)
|
||||
onchange(std::to_string(e.GetSelection()));
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
@@ -882,7 +886,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
if (pbool) {
|
||||
GUI::wxGetApp().CallAfter([] { GUI::wxGetApp().ShowDownNetPluginDlg(); });
|
||||
}
|
||||
if (m_legacy_networking_ckeckbox != nullptr) { m_legacy_networking_ckeckbox->Enable(pbool); }
|
||||
}
|
||||
|
||||
#endif // __WXMSW__
|
||||
@@ -910,7 +913,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
if (param == "enable_high_low_temp_mixed_printing") {
|
||||
if (checkbox->GetValue()) {
|
||||
const wxString warning_title = _L("Bed Temperature Difference Warning");
|
||||
const wxString warning_message =
|
||||
const wxString warning_message =
|
||||
_L("Using filaments with significantly different temperatures may cause:\n"
|
||||
"• Extruder clogging\n"
|
||||
"• Nozzle damage\n"
|
||||
@@ -950,11 +953,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
//// for debug mode
|
||||
if (param == "developer_mode") { m_developer_mode_ckeckbox = checkbox; }
|
||||
if (param == "internal_developer_mode") { m_internal_developer_mode_ckeckbox = checkbox; }
|
||||
if (param == "legacy_networking") {
|
||||
m_legacy_networking_ckeckbox = checkbox;
|
||||
bool pbool = app_config->get_bool("installed_networking");
|
||||
checkbox->Enable(pbool);
|
||||
}
|
||||
|
||||
return m_sizer_checkbox;
|
||||
}
|
||||
@@ -1289,6 +1287,10 @@ void PreferencesDialog::create_items()
|
||||
auto item_remember_printer = create_item_checkbox(_L("Remember printer configuration"), _L("If enabled, Orca will remember and switch filament/process configuration for each printer automatically."), "remember_printer_config");
|
||||
g_sizer->Add(item_remember_printer);
|
||||
|
||||
auto item_filament_preset_grouping = create_item_combobox(_L("Group user filament presets"), _L("Group user filament presets based on selection"),
|
||||
"group_filament_presets", {_L("All"), _L("None"), _L("By type"), _L("By vendor")}, [](wxString value) {wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);});
|
||||
g_sizer->Add(item_filament_preset_grouping);
|
||||
|
||||
//// GENERAL > Features
|
||||
g_sizer->Add(create_item_title(_L("Features")), 1, wxEXPAND);
|
||||
|
||||
@@ -1414,9 +1416,113 @@ void PreferencesDialog::create_items()
|
||||
|
||||
auto item_enable_plugin = create_item_checkbox(_L("Enable network plugin"), "", "installed_networking");
|
||||
g_sizer->Add(item_enable_plugin);
|
||||
|
||||
auto item_legacy_network = create_item_checkbox(_L("Use legacy network plugin"), _L("Disable to use latest network plugin that supports new BambuLab firmwares."), "legacy_networking", _L("(Requires restart)"));
|
||||
g_sizer->Add(item_legacy_network);
|
||||
|
||||
m_network_version_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_network_version_sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
|
||||
|
||||
auto version_title = new wxStaticText(m_parent, wxID_ANY, _L("Network plugin version"), wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
|
||||
version_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
version_title->SetFont(::Label::Body_14);
|
||||
version_title->SetToolTip(_L("Select the network plugin version to use"));
|
||||
version_title->Wrap(DESIGN_TITLE_SIZE.x);
|
||||
m_network_version_sizer->Add(version_title, 0, wxALIGN_CENTER);
|
||||
|
||||
m_network_version_combo = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(180), -1), 0, nullptr, wxCB_READONLY);
|
||||
m_network_version_combo->SetFont(::Label::Body_14);
|
||||
m_network_version_combo->GetDropDown().SetFont(::Label::Body_14);
|
||||
|
||||
std::string current_version = app_config->get_network_plugin_version();
|
||||
if (current_version.empty()) {
|
||||
current_version = BBL::get_latest_network_version();
|
||||
}
|
||||
int current_selection = 0;
|
||||
|
||||
m_available_versions = BBL::get_all_available_versions();
|
||||
|
||||
for (size_t i = 0; i < m_available_versions.size(); i++) {
|
||||
const auto& ver = m_available_versions[i];
|
||||
wxString label;
|
||||
|
||||
if (!ver.suffix.empty()) {
|
||||
label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name);
|
||||
} else {
|
||||
label = wxString::FromUTF8(ver.display_name);
|
||||
}
|
||||
|
||||
if (ver.is_latest) {
|
||||
label += " " + _L("(Latest)");
|
||||
}
|
||||
m_network_version_combo->Append(label);
|
||||
if (current_version == ver.version) {
|
||||
current_selection = i;
|
||||
}
|
||||
}
|
||||
|
||||
m_network_version_combo->SetSelection(current_selection);
|
||||
m_network_version_sizer->Add(m_network_version_combo, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
|
||||
|
||||
m_network_version_combo->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) {
|
||||
int selection = e.GetSelection();
|
||||
if (selection >= 0 && selection < (int)m_available_versions.size()) {
|
||||
const auto& selected_ver = m_available_versions[selection];
|
||||
std::string new_version = selected_ver.version;
|
||||
std::string old_version = app_config->get_network_plugin_version();
|
||||
if (old_version.empty()) {
|
||||
old_version = BBL::get_latest_network_version();
|
||||
}
|
||||
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, new_version);
|
||||
app_config->save();
|
||||
|
||||
if (new_version != old_version) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version;
|
||||
|
||||
// Update the use_legacy_network flag immediately
|
||||
bool is_legacy = (new_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||
bool was_legacy = (old_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||
if (is_legacy != was_legacy) {
|
||||
Slic3r::NetworkAgent::use_legacy_network = is_legacy;
|
||||
BOOST_LOG_TRIVIAL(info) << "Updated use_legacy_network flag to " << is_legacy;
|
||||
}
|
||||
|
||||
if (!selected_ver.warning.empty()) {
|
||||
MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING);
|
||||
if (warn_dlg.ShowModal() != wxID_OK) {
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, old_version);
|
||||
app_config->save();
|
||||
Slic3r::NetworkAgent::use_legacy_network = was_legacy;
|
||||
e.Skip();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the selected version already exists on disk
|
||||
if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload";
|
||||
if (wxGetApp().hot_reload_network_plugin()) {
|
||||
MessageDialog dlg(this, _L("Network plugin switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION);
|
||||
dlg.ShowModal();
|
||||
} else {
|
||||
MessageDialog dlg(this, _L("Failed to load network plugin. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
} else {
|
||||
wxString msg = wxString::Format(
|
||||
_L("You've selected network plugin version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."),
|
||||
wxString::FromUTF8(new_version));
|
||||
|
||||
MessageDialog dlg(this, msg, _L("Download Network Plugin"), wxYES_NO | wxICON_QUESTION);
|
||||
if (dlg.ShowModal() == wxID_YES) {
|
||||
DownloadProgressDialog progress_dlg(_L("Downloading Network Plugin"));
|
||||
progress_dlg.ShowModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
g_sizer->Add(m_network_version_sizer);
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
@@ -1487,6 +1593,18 @@ void PreferencesDialog::create_items()
|
||||
auto loglevel_combox = create_item_loglevel_combobox(_L("Log Level"), _L("Log Level"), log_level_list);
|
||||
g_sizer->Add(loglevel_combox);
|
||||
|
||||
g_sizer->Add(create_item_title(_L("Network Plugin")), 1, wxEXPAND);
|
||||
auto item_reload_plugin = create_item_button(_L("Network plugin"), _L("Reload"), _L("Reload the network plugin without restarting the application"), "", [this]() {
|
||||
if (wxGetApp().hot_reload_network_plugin()) {
|
||||
MessageDialog dlg(this, _L("Network plugin reloaded successfully."), _L("Reload"), wxOK | wxICON_INFORMATION);
|
||||
dlg.ShowModal();
|
||||
} else {
|
||||
MessageDialog dlg(this, _L("Failed to reload network plugin. Please restart the application."), _L("Reload Failed"), wxOK | wxICON_ERROR);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
});
|
||||
g_sizer->Add(item_reload_plugin);
|
||||
|
||||
//// DEVELOPER > Debug
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
g_sizer->Add(create_item_title(_L("Debug")), 1, wxEXPAND);
|
||||
@@ -1687,7 +1805,7 @@ wxBoxSizer* PreferencesDialog::create_debug_page()
|
||||
bSizer->Add(enable_ssl_for_mqtt, 0, wxTOP, FromDIP(3));
|
||||
bSizer->Add(enable_ssl_for_ftp, 0, wxTOP, FromDIP(3));
|
||||
bSizer->Add(item_internal_developer, 0, wxTOP, FromDIP(3));
|
||||
bSizer->Add(title_host, 0, wxEXPAND);
|
||||
bSizer->Add(title_host, 0, wxEXPAND | wxTOP, FromDIP(10));
|
||||
bSizer->Add(radio_group, 0, wxEXPAND | wxLEFT, FromDIP(DESIGN_LEFT_MARGIN));
|
||||
bSizer->Add(debug_button, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(15));
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/TabCtrl.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
@@ -67,7 +68,9 @@ public:
|
||||
::CheckBox * m_internal_developer_mode_ckeckbox = {nullptr};
|
||||
::CheckBox * m_dark_mode_ckeckbox = {nullptr};
|
||||
::TextInput *m_backup_interval_textinput = {nullptr};
|
||||
::CheckBox * m_legacy_networking_ckeckbox = {nullptr};
|
||||
::ComboBox * m_network_version_combo = {nullptr};
|
||||
wxBoxSizer * m_network_version_sizer = {nullptr};
|
||||
std::vector<BBL::NetworkLibraryVersionInfo> m_available_versions;
|
||||
|
||||
wxString m_developer_mode_def;
|
||||
wxString m_internal_developer_mode_def;
|
||||
@@ -77,7 +80,7 @@ public:
|
||||
std::vector<wxFlexGridSizer*> f_sizers;
|
||||
|
||||
wxBoxSizer *create_item_title(wxString title);
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist);
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {});
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index);
|
||||
wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip);
|
||||
wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip);
|
||||
|
||||
@@ -1132,6 +1132,7 @@ void PlaterPresetComboBox::update()
|
||||
std::map<wxString, wxString> preset_descriptions;
|
||||
std::map<wxString, std::string> preset_filament_vendors;
|
||||
std::map<wxString, std::string> preset_filament_types;
|
||||
std::map<wxString, std::string> preset_filament_names; // ORCA
|
||||
//BBS: move system to the end
|
||||
wxString selected_system_preset;
|
||||
wxString selected_user_preset;
|
||||
@@ -1174,7 +1175,8 @@ void PlaterPresetComboBox::update()
|
||||
|
||||
bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb;
|
||||
#endif
|
||||
if (preset.is_system) {
|
||||
// ORCA allow caching vendor and type values for all presets instead just system ones
|
||||
// if (preset.is_system) {
|
||||
if (!preset.is_compatible && preset_filament_vendors.count(name) > 0)
|
||||
continue;
|
||||
else if (preset.is_compatible && preset_filament_vendors.count(name) > 0)
|
||||
@@ -1183,7 +1185,8 @@ void PlaterPresetComboBox::update()
|
||||
if (preset_filament_vendors[name] == "Bambu Lab")
|
||||
preset_filament_vendors[name] = "Bambu";
|
||||
preset_filament_types[name] = preset.config.option<ConfigOptionStrings>("filament_type")->values.at(0);
|
||||
}
|
||||
preset_filament_names[name] = name.ToStdString(); // ORCA
|
||||
//}
|
||||
}
|
||||
wxBitmap* bmp = get_bmp(preset);
|
||||
assert(bmp);
|
||||
@@ -1251,7 +1254,7 @@ void PlaterPresetComboBox::update()
|
||||
"Bambu PLA Galaxy", "Bambu PLA Metal", "Bambu PLA Marble", "Bambu PETG-CF", "Bambu PETG Translucent", "Bambu ABS-GF"};
|
||||
std::vector<std::string> first_vendors = {"", "Bambu", "Generic"}; // Empty vendor for non-system presets
|
||||
std::vector<std::string> first_types = {"PLA", "PETG", "ABS", "TPU"};
|
||||
auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &first_types, &selected_in_ams]
|
||||
auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &preset_filament_names, &first_types, &selected_in_ams]
|
||||
(std::map<wxString, wxBitmap *> const &presets, wxString const &selected, std::string const &group, wxString const &groupName) {
|
||||
if (!presets.empty()) {
|
||||
set_label_marker(Append(_L(group), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
@@ -1285,9 +1288,31 @@ void PlaterPresetComboBox::update()
|
||||
}
|
||||
return l->first < r->first;
|
||||
});
|
||||
// ORCA add sorting support for vendor / type for user presets. also non grouped items
|
||||
if (groupName == "by_vendor" || groupName == "by_type" || groupName == ""){
|
||||
auto by = groupName == "by_vendor" ? preset_filament_vendors
|
||||
: groupName == "by_type" ? preset_filament_types
|
||||
: preset_filament_names;
|
||||
std::sort(list.begin(), list.end(), [&by](auto *l, auto *r) {
|
||||
auto get_key = [&](auto* item) -> std::pair<bool, std::string> {
|
||||
std::string str = by.count(item->first) ? by.at(item->first) : "";
|
||||
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c);});
|
||||
return {!str.empty(), str}; // is_valid, lower_case
|
||||
};
|
||||
auto [l_valid, l_lower] = get_key(l);
|
||||
auto [r_valid, r_lower] = get_key(r);
|
||||
return (l_valid != r_valid) ? l_valid > r_valid
|
||||
: (l_lower != r_lower) ? l_lower < r_lower
|
||||
: l->first < r->first;
|
||||
});
|
||||
}
|
||||
bool unsupported = group == "Unsupported presets";
|
||||
for (auto it : list) {
|
||||
auto groupName2 = groupByGroup ? groupName : preset_filament_vendors[it->first];
|
||||
// ORCA add sorting support for vendor / type for user presets
|
||||
auto groupName2 = groupName == "by_type" ? (preset_filament_types[it->first].empty() ? _L("Unspecified") : preset_filament_types[it->first])
|
||||
: groupName == "by_vendor" ? (preset_filament_vendors[it->first].empty() ? _L("Unspecified") : preset_filament_vendors[it->first])
|
||||
: groupByGroup ? groupName
|
||||
: preset_filament_vendors[it->first];
|
||||
int index = Append(it->first, *it->second, groupName2, nullptr, unsupported ? DD_ITEM_STYLE_DISABLED : 0);
|
||||
if (unsupported)
|
||||
set_label_marker(index, LABEL_ITEM_DISABLED);
|
||||
@@ -1311,7 +1336,13 @@ void PlaterPresetComboBox::update()
|
||||
|
||||
//BBS: add project embedded preset logic
|
||||
add_presets(project_embedded_presets, selected_user_preset, L("Project-inside presets"), _L("Project") + " ");
|
||||
add_presets(nonsys_presets, selected_user_preset, L("User presets"), _L("Custom") + " ");
|
||||
// ORCA add sorting support for vendor / type for user presets
|
||||
auto group_filament_presets = wxGetApp().app_config->get("group_filament_presets");
|
||||
auto group_filament_presets_by = group_filament_presets == "0" ? (_L("Custom") + " ") // Append all to "Custom" sub menu
|
||||
: group_filament_presets == "2" ? "by_type" // Create sub menus with filament type
|
||||
: group_filament_presets == "3" ? "by_vendor" // Create sub menus with filament vendor
|
||||
: ""; // Use without sub menu
|
||||
add_presets(nonsys_presets, selected_user_preset, L("User presets"), group_filament_presets_by);
|
||||
// BBS: move system to the end
|
||||
add_presets(system_presets, selected_system_preset, L("System presets"), _L("System"));
|
||||
add_presets(uncompatible_presets, {}, L("Unsupported presets"), _L("Unsupported") + " ");
|
||||
|
||||
@@ -1153,11 +1153,8 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent)
|
||||
change_nozzle_tips->SetFont(Label::Body_13);
|
||||
change_nozzle_tips->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
|
||||
|
||||
m_wiki_link = new Label(single_panel, _L("View wiki"));
|
||||
m_wiki_link = new HyperLink(single_panel, _L("Wiki Guide")); // ORCA
|
||||
m_wiki_link->SetFont(Label::Body_13);
|
||||
m_wiki_link->SetForegroundColour(wxColour("#009688"));
|
||||
m_wiki_link->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_HAND); });
|
||||
m_wiki_link->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_ARROW); });
|
||||
m_wiki_link->Bind(wxEVT_LEFT_DOWN, &PrinterPartsDialog::OnWikiClicked, this);
|
||||
|
||||
h_tips_sizer->Add(change_nozzle_tips, 0, wxLEFT);
|
||||
@@ -1267,11 +1264,8 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent)
|
||||
multiple_change_nozzle_tips->SetFont(Label::Body_13);
|
||||
multiple_change_nozzle_tips->SetForegroundColour(STATIC_TEXT_CAPTION_COL);
|
||||
|
||||
multiple_wiki_link = new Label(multiple_panel, _L("View wiki"));
|
||||
multiple_wiki_link = new HyperLink(multiple_panel, _L("Wiki Guide")); // ORCA
|
||||
multiple_wiki_link->SetFont(Label::Body_13);
|
||||
multiple_wiki_link->SetForegroundColour(wxColour("#009688"));
|
||||
multiple_wiki_link->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_HAND); });
|
||||
multiple_wiki_link->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_ARROW); });
|
||||
multiple_wiki_link->Bind(wxEVT_LEFT_DOWN, &PrinterPartsDialog::OnWikiClicked, this);
|
||||
|
||||
wxSizer* multiple_change_tips_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
|
||||
// Previous definitions
|
||||
class SwitchBoard;
|
||||
@@ -33,7 +34,7 @@ protected:
|
||||
Label* nozzle_flow_type_label;
|
||||
ComboBox* nozzle_flow_type_checkbox;
|
||||
Label *change_nozzle_tips;
|
||||
Label* m_wiki_link;
|
||||
HyperLink* m_wiki_link;
|
||||
Button* m_single_update_nozzle_button;
|
||||
Button* m_multiple_update_nozzle_button;
|
||||
|
||||
@@ -46,7 +47,7 @@ protected:
|
||||
ComboBox *multiple_right_nozzle_flow_checkbox;
|
||||
|
||||
Label *multiple_change_nozzle_tips;
|
||||
Label* multiple_wiki_link;
|
||||
HyperLink* multiple_wiki_link;
|
||||
|
||||
wxPanel *single_panel;
|
||||
wxPanel *multiple_panel;
|
||||
|
||||
@@ -1510,7 +1510,8 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow *parent)
|
||||
m_tip4->SetMinSize(wxSize(FromDIP(355), -1));
|
||||
m_tip4->SetMaxSize(wxSize(FromDIP(355), -1));
|
||||
|
||||
m_trouble_shoot = new wxHyperlinkCtrl(this, wxID_ANY, "How to trouble shooting", "");
|
||||
// ORCA standardized HyperLink
|
||||
m_trouble_shoot = new HyperLink(this, "How to trouble shooting");
|
||||
|
||||
m_img_help = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("input_access_code_x1_en", this, 198), wxDefaultPosition, wxSize(FromDIP(355), -1), 0);
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ public:
|
||||
wxStaticBitmap* m_img_step1{ nullptr };
|
||||
wxStaticBitmap* m_img_step2{ nullptr };
|
||||
wxStaticBitmap* m_img_step3{ nullptr };
|
||||
wxHyperlinkCtrl* m_trouble_shoot{ nullptr };
|
||||
HyperLink* m_trouble_shoot{ nullptr }; // ORCA
|
||||
wxTimer* closeTimer{ nullptr };
|
||||
int closeCount{3};
|
||||
bool m_show_access_code{ false };
|
||||
|
||||
@@ -688,12 +688,9 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
|
||||
sizer_extra_info->Add(st_title_extra_info_doc, 0, wxALL, 0);
|
||||
sizer_extra_info->Add(m_st_txt_extra_info, 0, wxALL, 0);
|
||||
|
||||
|
||||
m_link_network_state = new wxHyperlinkCtrl(m_sw_print_failed_info, wxID_ANY,_L("Check the status of current system services"),"");
|
||||
// ORCA standardized HyperLink
|
||||
m_link_network_state = new HyperLink(m_sw_print_failed_info, _L("Check the status of current system services"), wxGetApp().link_to_network_check());
|
||||
m_link_network_state->SetFont(::Label::Body_12);
|
||||
m_link_network_state->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_network_check();});
|
||||
m_link_network_state->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {m_link_network_state->SetCursor(wxCURSOR_HAND);});
|
||||
m_link_network_state->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {m_link_network_state->SetCursor(wxCURSOR_ARROW);});
|
||||
|
||||
sizer_print_failed_info->Add(m_link_network_state, 0, wxLEFT, 5);
|
||||
sizer_print_failed_info->Add(sizer_error_code, 0, wxLEFT, 5);
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/ScrolledWindow.hpp"
|
||||
#include "Widgets/PopupWindow.hpp"
|
||||
#include "Widgets/HyperLink.hpp" // ORCA
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/hashmap.h>
|
||||
|
||||
@@ -371,7 +372,7 @@ protected:
|
||||
Label* m_st_txt_error_desc{nullptr};
|
||||
Label* m_st_txt_extra_info{nullptr};
|
||||
Label* m_ams_backup_tip{nullptr};
|
||||
wxHyperlinkCtrl* m_link_network_state{ nullptr };
|
||||
HyperLink* m_link_network_state{ nullptr }; // ORCA
|
||||
wxSimplebook* m_rename_switch_panel{nullptr};
|
||||
wxSimplebook* m_simplebook{nullptr};
|
||||
wxStaticText* m_rename_text{nullptr};
|
||||
|
||||
@@ -596,8 +596,9 @@ void SelectMachinePopup::update_other_devices()
|
||||
m_placeholder_panel = new wxWindow(m_scrolledWindow, wxID_ANY, wxDefaultPosition, wxSize(-1,FromDIP(26)));
|
||||
wxBoxSizer* placeholder_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_hyperlink = new wxHyperlinkCtrl(m_placeholder_panel, wxID_ANY, _L("Can't find my devices?"), wxT("https://wiki.bambulab.com/en/software/bambu-studio/failed-to-connect-printer"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
|
||||
m_hyperlink->SetNormalColour(StateColor::darkModeColorFor("#009789"));
|
||||
// ORCA standardized HyperLink
|
||||
m_hyperlink = new HyperLink(m_placeholder_panel, _L("Can't find my devices?"), wxT("https://wiki.bambulab.com/en/software/bambu-studio/failed-to-connect-printer"));
|
||||
m_hyperlink->SetFont(::Label::Body_12);
|
||||
placeholder_sizer->Add(m_hyperlink, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ private:
|
||||
PinCodePanel* m_panel_ping_code{nullptr};
|
||||
PinCodePanel* m_panel_direct_connection{nullptr};
|
||||
wxWindow* m_placeholder_panel{nullptr};
|
||||
wxHyperlinkCtrl* m_hyperlink{nullptr};
|
||||
HyperLink* m_hyperlink{nullptr}; // ORCA
|
||||
Label* m_ping_code_text{nullptr};
|
||||
wxStaticBitmap* m_img_ping_code{nullptr};
|
||||
wxBoxSizer * m_sizer_body{nullptr};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user