Merge branch 'main' into libvgcode

This commit is contained in:
SoftFever
2026-01-06 11:09:09 +08:00
committed by GitHub
510 changed files with 11271 additions and 4462 deletions
-2
View File
@@ -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
-54
View File
@@ -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
View File
@@ -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");
+21
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -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;
+3
View File
@@ -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)
+4
View File
@@ -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);
+36 -20
View File
@@ -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;
}
+69 -45
View File
@@ -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
+10 -3
View File
@@ -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;
+1 -1
View File
@@ -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);
+10 -2
View File
@@ -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) {
+303 -22
View File
@@ -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 &params)
{
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 &params){
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 &params)
@@ -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;
+1
View File
@@ -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;
+2 -1
View File
@@ -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
View File
@@ -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
+3 -2
View File
@@ -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;
+1 -1
View File
@@ -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);
+9 -1
View File
@@ -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();
+12 -4
View File
@@ -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(
+13 -8
View File
@@ -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))
+1 -1
View File
@@ -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);
+6 -7
View File
@@ -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)]
+1 -1
View File
@@ -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
+4 -1
View File
@@ -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)
+2
View File
@@ -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;
+45 -6
View File
@@ -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
+10 -1
View File
@@ -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))
+1
View File
@@ -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"
+17 -1
View File
@@ -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)
+1 -5
View File
@@ -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);
+2 -2
View File
@@ -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;
+10 -31
View File
@@ -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);
+4 -4
View File
@@ -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;
+3 -3
View File
@@ -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) {
+3 -12
View File
@@ -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)
+2 -1
View File
@@ -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;
+15 -10
View File
@@ -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);
+3 -2
View File
@@ -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);
}
+19
View File
@@ -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)
{
+1 -1
View File
@@ -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();
+2
View File
@@ -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,
+18 -3
View File
@@ -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
+2 -7
View File
@@ -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);
+23 -11
View File
@@ -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
View File
@@ -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();
}
+2 -2
View File
@@ -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;
+6 -1
View File
@@ -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
View File
@@ -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;
}
+14 -2
View File
@@ -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;
+8 -8
View File
@@ -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}
}}
+28 -8
View File
@@ -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;
+8 -4
View File
@@ -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);
+8
View File
@@ -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);
+6 -4
View File
@@ -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();
+1 -1
View File
@@ -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();
}
+2 -2
View File
@@ -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")));
+1 -1
View File
@@ -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,
+28
View File
@@ -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
+1
View File
@@ -157,6 +157,7 @@ public:
void jump_to_HMS();
void jump_to_LiveView();
void update_network_version_footer();
};
+6 -9
View File
@@ -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);
+3 -2
View File
@@ -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:
+1 -1
View File
@@ -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()
+3 -3
View File
@@ -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();
+1 -1
View File
@@ -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)
+377
View File
@@ -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();
}
}
}
+76
View File
@@ -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_
+1 -1
View File
@@ -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;
+3 -3
View File
@@ -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
+1 -8
View File
@@ -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) {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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));
+5 -2
View File
@@ -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);
+36 -5
View File
@@ -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") + " ");
+2 -8
View File
@@ -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);
+3 -2
View File
@@ -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;
+2 -1
View File
@@ -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);
+1 -1
View File
@@ -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 };
+2 -5
View File
@@ -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);
+2 -1
View File
@@ -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};
+3 -2
View File
@@ -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);
+1 -1
View File
@@ -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