From e4aaf2adc18a23596f791cd4bdd54ae1189eb6ef Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 18:58:09 +0800 Subject: [PATCH 01/35] libarea: added OffsetWithClipper function Also added a few extra setting variables. OffsetWithClipper perform offset operation using ClipperLib::ClipperOffset. --- src/Mod/Path/libarea/Area.cpp | 4 ++ src/Mod/Path/libarea/Area.h | 11 +++++ src/Mod/Path/libarea/AreaClipper.cpp | 61 +++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index f33e338c04..4755f9effd 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -10,7 +10,11 @@ double CArea::m_accuracy = 0.01; double CArea::m_units = 1.0; +bool CArea::m_clipper_simple = false; +double CArea::m_clipper_clean_distance = 0.0; bool CArea::m_fit_arcs = true; +int CArea::m_min_arc_points = 4; +int CArea::m_max_arc_points = 100; double CArea::m_single_area_processing_length = 0.0; double CArea::m_processing_done = 0.0; bool CArea::m_please_abort = false; diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index 23937196c1..56c221e043 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -7,6 +7,7 @@ #define AREA_HEADER #include "Curve.h" +#include "clipper.hpp" enum PocketMode { @@ -42,7 +43,11 @@ public: std::list m_curves; static double m_accuracy; static double m_units; // 1.0 for mm, 25.4 for inches. All points are multiplied by this before going to the engine + static bool m_clipper_simple; + static double m_clipper_clean_distance; static bool m_fit_arcs; + static int m_min_arc_points; + static int m_max_arc_points; static double m_processing_done; // 0.0 to 100.0, set inside MakeOnePocketCurve static double m_single_area_processing_length; static double m_after_MakeOffsets_length; @@ -50,6 +55,7 @@ public: static double m_split_processing_length; static bool m_set_processing_length_in_split; static bool m_please_abort; // the user sets this from another thread, to tell MakeOnePocketCurve to finish with no result. + static double m_clipper_scale; void append(const CCurve& curve); void Subtract(const CArea& a2); @@ -58,6 +64,11 @@ public: static CArea UniteCurves(std::list &curves); void Xor(const CArea& a2); void Offset(double inwards_value); + void OffsetWithClipper(double offset, + ClipperLib::JoinType joinType=ClipperLib::jtRound, + ClipperLib::EndType endType=ClipperLib::etOpenRound, + double miterLimit = 5.0, + double roundPrecision = 0.0); void Thicken(double value); void FitArcs(); unsigned int num_curves(){return static_cast(m_curves.size());} diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index ec5e63730d..c8c7c7c50f 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -12,7 +12,7 @@ using namespace ClipperLib; bool CArea::HolesLinked(){ return false; } //static const double PI = 3.1415926535897932; -static double Clipper4Factor = 10000.0; +double CArea::m_clipper_scale = 10000.0; class DoubleAreaPoint { @@ -20,8 +20,8 @@ public: double X, Y; DoubleAreaPoint(double x, double y){X = x; Y = y;} - DoubleAreaPoint(const IntPoint& p){X = (double)(p.X) / Clipper4Factor; Y = (double)(p.Y) / Clipper4Factor;} - IntPoint int_point(){return IntPoint((long64)(X * Clipper4Factor), (long64)(Y * Clipper4Factor));} + DoubleAreaPoint(const IntPoint& p){X = (double)(p.X) / CArea::m_clipper_scale; Y = (double)(p.Y) / CArea::m_clipper_scale;} + IntPoint int_point(){return IntPoint((long64)(X * CArea::m_clipper_scale), (long64)(Y * CArea::m_clipper_scale));} }; static std::list pts_for_AddVertex; @@ -81,10 +81,10 @@ static void AddVertex(const CVertex& vertex, const CVertex* prev_vertex) else Segments=(int)ceil(-phit/dphi); - if (Segments < 1) - Segments=1; - if (Segments > 100) - Segments=100; + if (Segments < CArea::m_min_arc_points) + Segments = CArea::m_min_arc_points; + if (Segments > CArea::m_max_arc_points) + Segments=CArea::m_max_arc_points; dphi=phit/(Segments); @@ -139,6 +139,7 @@ static void MakeLoop(const DoubleAreaPoint &pt0, const DoubleAreaPoint &pt1, con static void OffsetWithLoops(const TPolyPolygon &pp, TPolyPolygon &pp_new, double inwards_value) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); bool inwards = (inwards_value > 0); bool reverse = false; @@ -251,6 +252,7 @@ static void MakeObround(const Point &pt0, const CVertex &vt1, double radius) static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, double radius) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) @@ -355,8 +357,11 @@ static void MakePoly(const CCurve& curve, TPolygon &p) } } -static void SetFromResult( CCurve& curve, const TPolygon& p, bool reverse = true ) +static void SetFromResult( CCurve& curve, TPolygon& p, bool reverse = true ) { + if(CArea::m_clipper_clean_distance >= Point::tolerance) + CleanPolygon(p,CArea::m_clipper_clean_distance); + for(unsigned int j = 0; j < p.size(); j++) { const IntPoint &pt = p[j]; @@ -372,14 +377,14 @@ static void SetFromResult( CCurve& curve, const TPolygon& p, bool reverse = true if(CArea::m_fit_arcs)curve.FitArcs(); } -static void SetFromResult( CArea& area, const TPolyPolygon& pp, bool reverse = true ) +static void SetFromResult( CArea& area, TPolyPolygon& pp, bool reverse = true ) { // delete existing geometry area.m_curves.clear(); for(unsigned int i = 0; i < pp.size(); i++) { - const TPolygon& p = pp[i]; + TPolygon& p = pp[i]; area.m_curves.push_back(CCurve()); CCurve &curve = area.m_curves.back(); @@ -390,6 +395,7 @@ static void SetFromResult( CArea& area, const TPolyPolygon& pp, bool reverse = t void CArea::Subtract(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -403,6 +409,7 @@ void CArea::Subtract(const CArea& a2) void CArea::Intersect(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -416,6 +423,7 @@ void CArea::Intersect(const CArea& a2) void CArea::Union(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -430,6 +438,7 @@ void CArea::Union(const CArea& a2) CArea CArea::UniteCurves(std::list &curves) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp; @@ -452,6 +461,7 @@ CArea CArea::UniteCurves(std::list &curves) void CArea::Xor(const CArea& a2) { Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); TPolyPolygon pp1, pp2; MakePolyPoly(*this, pp1); MakePolyPoly(a2, pp2); @@ -471,6 +481,37 @@ void CArea::Offset(double inwards_value) this->Reorder(); } +void CArea::OffsetWithClipper(double offset, + JoinType joinType/* =jtRound */, + EndType endType/* =etOpenRound */, + double miterLimit/* = 5.0 */, + double roundPrecision/* = 0.0 */) +{ + offset *= m_units*m_clipper_scale; + if(roundPrecision == 0.0) { + // Clipper roundPrecision definition: https://goo.gl/4odfQh + double dphi=acos(1.0-m_accuracy*m_clipper_scale/fabs(offset)); + int Segments=(int)ceil(PI/dphi); + if (Segments < 2*CArea::m_min_arc_points) + Segments = 2*CArea::m_min_arc_points; + if (Segments > CArea::m_max_arc_points) + Segments=CArea::m_max_arc_points; + dphi = PI/Segments; + roundPrecision = (1.0-cos(dphi))*fabs(offset); + }else + roundPrecision *= m_clipper_scale; + + ClipperOffset clipper(miterLimit,roundPrecision); + TPolyPolygon pp, pp2; + MakePolyPoly(*this, pp, false); + int i=0; + for(const CCurve &c : m_curves) + clipper.AddPath(pp[i++],joinType,c.IsClosed()?etClosedPolygon:endType); + clipper.Execute(pp2,(long64)(offset)); + SetFromResult(*this, pp2, false); + this->Reorder(); +} + void CArea::Thicken(double value) { TPolyPolygon pp; From 9ec1b353ae112df7309e9e069fd00242cc76c087 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 19:01:48 +0800 Subject: [PATCH 02/35] libarea: split into area-native and area python --- src/Mod/Path/libarea/Area.cpp | 18 +++++++++++ src/Mod/Path/libarea/Area.h | 15 ++++++++++ src/Mod/Path/libarea/CMakeLists.txt | 45 ++++++++++++++++++++++------ src/Mod/Path/libarea/Curve.cpp | 6 ++++ src/Mod/Path/libarea/Point.h | 2 +- src/Mod/Path/libarea/PythonStuff.cpp | 8 ++--- 6 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index 4755f9effd..cb5573a0a7 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -24,6 +24,24 @@ bool CArea::m_set_processing_length_in_split = false; double CArea::m_after_MakeOffsets_length = 0.0; //static const double PI = 3.1415926535897932; +#define _CAREA_PARAM_DEFINE(_class,_type,_name) \ + _type CArea::get_##_name() {return _class::_name;}\ + void CArea::set_##_name(_type _name) {_class::_name = _name;} + +#define CAREA_PARAM_DEFINE(_type,_name) \ + _type CArea::get_##_name() {return m_##_name;}\ + void CArea::set_##_name(_type _name) {m_##_name = _name;} + +_CAREA_PARAM_DEFINE(Point,double,tolerance); +CAREA_PARAM_DEFINE(bool,fit_arcs) +CAREA_PARAM_DEFINE(bool,clipper_simple); +CAREA_PARAM_DEFINE(double,clipper_clean_distance); +CAREA_PARAM_DEFINE(double,accuracy); +CAREA_PARAM_DEFINE(double,units); +CAREA_PARAM_DEFINE(short,min_arc_points); +CAREA_PARAM_DEFINE(short,max_arc_points); +CAREA_PARAM_DEFINE(double,clipper_scale); + void CArea::append(const CCurve& curve) { m_curves.push_back(curve); diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index 56c221e043..224f48327f 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -84,6 +84,21 @@ public: void SpanIntersections(const Span& span, std::list &pts)const; void CurveIntersections(const CCurve& curve, std::list &pts)const; void InsideCurves(const CCurve& curve, std::list &curves_inside)const; + + //Avoid outside direct accessing static member variable because of Windows DLL issue +#define CAREA_PARAM_DECLARE(_type,_name) \ + static _type get_##_name();\ + static void set_##_name(_type _name); + + CAREA_PARAM_DECLARE(double,tolerance); + CAREA_PARAM_DECLARE(bool,fit_arcs) + CAREA_PARAM_DECLARE(bool,clipper_simple); + CAREA_PARAM_DECLARE(double,clipper_clean_distance); + CAREA_PARAM_DECLARE(double,accuracy); + CAREA_PARAM_DECLARE(double,units); + CAREA_PARAM_DECLARE(short,min_arc_points); + CAREA_PARAM_DECLARE(short,max_arc_points); + CAREA_PARAM_DECLARE(double,clipper_scale); }; enum eOverlapType diff --git a/src/Mod/Path/libarea/CMakeLists.txt b/src/Mod/Path/libarea/CMakeLists.txt index 8359267ab2..6e489b6382 100644 --- a/src/Mod/Path/libarea/CMakeLists.txt +++ b/src/Mod/Path/libarea/CMakeLists.txt @@ -65,37 +65,52 @@ file(GLOB headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") # this makes the Python module add_library( - area - MODULE + area-native + SHARED ${AREA_SRC_COMMON} ${AREA_SRC_CLIPPER} - ${PYAREA_SRC} +) + +add_library( + area + MODULE + ${PYAREA_SRC} ) if(MSVC) - set(area_LIBS - ${Boost_LIBRARIES} - ${PYTHON_LIBRARIES} + set(area_native_LIBS debug MSVCRTD.LIB debug MSVCPRTD.LIB optimized MSVCRT.LIB optimized MSVCPRT.LIB ) -elseif(MINGW) set(area_LIBS ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} + ${area_native_LIBS} + ) +elseif(MINGW) + set(area_native_LIBS Rpcrt4.lib ) + set(area_LIBS + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} + ${area_native_LIBS} + ) else(MSVC) + set(area_native_LIBS + ) set(area_LIBS ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ) endif(MSVC) -target_link_libraries(area ${area_LIBS}) +target_link_libraries(area-native ${area_native_LIBS}) +SET_BIN_DIR(area-native area-native) +target_link_libraries(area area-native ${area_LIBS} ${area_native_LIBS}) SET_BIN_DIR(area area) SET_PYTHON_PREFIX_SUFFIX(area) @@ -108,8 +123,20 @@ execute_process( message(STATUS "area module (for Path Workbench) will be installed to: " ${CMAKE_INSTALL_LIBDIR}) +if(WIN32) + set_target_properties(area-native PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + INSTALL(TARGETS area-native + RUNTIME DESTINATION bin + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +else(WIN32) + INSTALL(TARGETS area-native + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +endif(WIN32) + # this installs the python library install( TARGETS area - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/Mod/Path/libarea/Curve.cpp b/src/Mod/Path/libarea/Curve.cpp index 456c546e31..d627631961 100644 --- a/src/Mod/Path/libarea/Curve.cpp +++ b/src/Mod/Path/libarea/Curve.cpp @@ -13,6 +13,12 @@ double Point::tolerance = 0.001; //static const double PI = 3.1415926535897932; duplicated in kurve/geometry.h +//This function is moved from header here to solve windows DLL not export +//static variable problem +bool Point::operator==(const Point& p)const{ + return fabs(x-p.x)::const_iterator It = c.m_vertices.begin(); It != c.m_vertices.end(); It++, i++) { const CVertex& vertex = *It; - printf("vertex %d type = %d, x = %g, y = %g", i+1, vertex.m_type, vertex.m_p.x / CArea::m_units, vertex.m_p.y / CArea::m_units); - if(vertex.m_type)printf(", xc = %g, yc = %g", vertex.m_c.x / CArea::m_units, vertex.m_c.y / CArea::m_units); + printf("vertex %d type = %d, x = %g, y = %g", i+1, vertex.m_type, vertex.m_p.x / CArea::get_units(), vertex.m_p.y / CArea::get_units()); + if(vertex.m_type)printf(", xc = %g, yc = %g", vertex.m_c.x / CArea::get_units(), vertex.m_c.y / CArea::get_units()); printf("\n"); } } @@ -112,12 +112,12 @@ static CVertex LastVertex(const CCurve& curve) static void set_units(double units) { - CArea::m_units = units; + CArea::set_units(units); } static double get_units() { - return CArea::m_units; + return CArea::get_units(); } static bool holes_linked() From f6c8d3e433f7016b8208367777df4fe1c099e61a Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 19:03:11 +0800 Subject: [PATCH 03/35] libarea: make CArea::Record skip open wires --- src/Mod/Path/libarea/Area.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index cb5573a0a7..36799a7864 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -92,9 +92,13 @@ void CArea::Reorder() // returns 1, if the curves are overlapping CAreaOrderer ao; - for(std::list::iterator It = m_curves.begin(); It != m_curves.end(); It++) + std::list::iterator ItLast = m_curves.end(); + for(std::list::iterator It = m_curves.begin(); It != m_curves.end(); ++It) { CCurve& curve = *It; + if(!It->IsClosed()) + continue; + ItLast = It; ao.Insert(&curve); if(m_set_processing_length_in_split) { @@ -102,7 +106,19 @@ void CArea::Reorder() } } - *this = ao.ResultArea(); + if(ItLast == m_curves.end()) + return; + + if(ao.m_top_level) + ao.m_top_level->GetArea(*this); + + ++ItLast; + for(std::list::iterator It=m_curves.begin(), ItNext=It; It!=ItLast; It=ItNext) + { + ++ItNext; + if(It->IsClosed()) + m_curves.erase(It); + } } class ZigZag From c8835d3c75d503fb9764fbd64dcf6a53020a83b2 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 19:03:59 +0800 Subject: [PATCH 04/35] libarea: added CArea::Clip to handle open wires CArea::Clip enables ClipperLib open wire boolean operation as well as other settings (PolyFillType) for closed wires. --- src/Mod/Path/libarea/Area.h | 7 +++ src/Mod/Path/libarea/AreaClipper.cpp | 92 ++++++++++++++++------------ src/Mod/Path/libarea/clipper.hpp | 2 +- 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index 224f48327f..7a6634ac51 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -99,6 +99,13 @@ public: CAREA_PARAM_DECLARE(short,min_arc_points); CAREA_PARAM_DECLARE(short,max_arc_points); CAREA_PARAM_DECLARE(double,clipper_scale); + + // Following functions is add to operate on possible open curves + void PopulateClipper(ClipperLib::Clipper &c, ClipperLib::PolyType type) const; + void Clip(ClipperLib::ClipType op, + const CArea *a, + ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, + ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd); }; enum eOverlapType diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index c8c7c7c50f..bcb7ccd427 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -298,48 +298,14 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou } } -static void MakePolyPoly( const CArea& area, TPolyPolygon &pp, bool reverse = true ){ - pp.clear(); - - for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) - { - pts_for_AddVertex.clear(); - const CCurve& curve = *It; - const CVertex* prev_vertex = NULL; - for(std::list::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++) - { - const CVertex& vertex = *It2; - if(prev_vertex)AddVertex(vertex, prev_vertex); - prev_vertex = &vertex; - } - - TPolygon p; - p.resize(pts_for_AddVertex.size()); - if(reverse) - { - std::size_t i = pts_for_AddVertex.size() - 1;// clipper wants them the opposite way to CArea - for(std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i--) - { - p[i] = It->int_point(); - } - } - else - { - unsigned int i = 0; - for(std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i++) - { - p[i] = It->int_point(); - } - } - - pp.push_back(p); - } -} - -static void MakePoly(const CCurve& curve, TPolygon &p) +static void MakePoly(const CCurve& curve, TPolygon &p, bool reverse = false) { pts_for_AddVertex.clear(); const CVertex* prev_vertex = NULL; + + if(!curve.m_vertices.size()) return; + if(!curve.IsClosed()) AddVertex(curve.m_vertices.front(),NULL); + for (std::list::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++) { const CVertex& vertex = *It2; @@ -348,6 +314,15 @@ static void MakePoly(const CCurve& curve, TPolygon &p) } p.resize(pts_for_AddVertex.size()); + if(reverse) + { + std::size_t i = pts_for_AddVertex.size() - 1;// clipper wants them the opposite way to CArea + for(std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i--) + { + p[i] = It->int_point(); + } + } + else { unsigned int i = 0; for (std::list::iterator It = pts_for_AddVertex.begin(); It != pts_for_AddVertex.end(); It++, i++) @@ -357,6 +332,16 @@ static void MakePoly(const CCurve& curve, TPolygon &p) } } +static void MakePolyPoly( const CArea& area, TPolyPolygon &pp, bool reverse = true ){ + pp.clear(); + + for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) + { + pp.push_back(TPolygon()); + MakePoly(*It,pp.back(),reverse); + } +} + static void SetFromResult( CCurve& curve, TPolygon& p, bool reverse = true ) { if(CArea::m_clipper_clean_distance >= Point::tolerance) @@ -481,6 +466,35 @@ void CArea::Offset(double inwards_value) this->Reorder(); } +void CArea::PopulateClipper(Clipper &c, PolyType type) const +{ + for (std::list::const_iterator It = m_curves.begin(); It != m_curves.end(); It++) + { + const CCurve &curve = *It; + bool closed = curve.IsClosed(); + if(type == ptClip && !closed) + continue; + TPolygon p; + MakePoly(curve, p, false); + c.AddPath(p, type, closed); + } +} + +void CArea::Clip(ClipType op, const CArea *a, + PolyFillType subjFillType, + PolyFillType clipFillType) +{ + Clipper c; + c.StrictlySimple(CArea::m_clipper_simple); + PopulateClipper(c,ptSubject); + if(a) a->PopulateClipper(c,ptClip); + PolyTree tree; + c.Execute(op, tree, subjFillType,clipFillType); + TPolyPolygon solution; + PolyTreeToPaths(tree,solution); + SetFromResult(*this, solution); +} + void CArea::OffsetWithClipper(double offset, JoinType joinType/* =jtRound */, EndType endType/* =etOpenRound */, diff --git a/src/Mod/Path/libarea/clipper.hpp b/src/Mod/Path/libarea/clipper.hpp index 15af31e464..b88bffd790 100644 --- a/src/Mod/Path/libarea/clipper.hpp +++ b/src/Mod/Path/libarea/clipper.hpp @@ -44,7 +44,7 @@ //#define use_xyz //use_lines: Enables line clipping. Adds a very minor cost to performance. -//#define use_lines +#define use_lines //use_deprecated: Enables temporary support for the obsolete functions //#define use_deprecated From 3bf3bd5cee8f38dd9df4e00965e4b9391d807a43 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 23:05:30 +0800 Subject: [PATCH 05/35] Part: export Part::shape2pyshape --- src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp | 2 ++ src/Mod/Part/App/TopoShapePyImp.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp b/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp index 2a79113737..67a57a4b98 100644 --- a/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp +++ b/src/Mod/Part/App/Geom2d/Curve2dPyImp.cpp @@ -105,7 +105,9 @@ PyObject* Curve2dPy::reverse(PyObject * args) return 0; } +namespace Part { extern Py::Object shape2pyshape(const TopoDS_Shape &shape); +} PyObject* Curve2dPy::toShape(PyObject *args) { diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index dcc1cd385b..c67c84011e 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -157,8 +157,9 @@ int TopoShapePy::PyInit(PyObject* args, PyObject*) return 0; } +namespace Part { //common code.. maybe put somewhere else? -Py::Object shape2pyshape(const TopoDS_Shape &shape) +Py::Object PartExport shape2pyshape(const TopoDS_Shape &shape) { PyObject* ret = 0; if (!shape.IsNull()) { @@ -204,6 +205,7 @@ Py::Object shape2pyshape(const TopoDS_Shape &shape) return Py::asObject(ret); } +} //namespace Part PyObject* TopoShapePy::copy(PyObject *args) { From 4a63a702f401c684d1e2fea2d15f7c1b0e240169 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 19 Jan 2017 23:07:06 +0800 Subject: [PATCH 06/35] Path: added Path.Area and Path.FeatureArea --- src/Mod/Path/App/AppPath.cpp | 16 +- src/Mod/Path/App/Area.cpp | 592 +++++++++++ src/Mod/Path/App/Area.h | 238 +++++ src/Mod/Path/App/AreaParams.h | 129 +++ src/Mod/Path/App/AreaPy.xml | 62 ++ src/Mod/Path/App/AreaPyImp.cpp | 292 +++++ src/Mod/Path/App/CMakeLists.txt | 10 + src/Mod/Path/App/FeatureArea.cpp | 157 +++ src/Mod/Path/App/FeatureArea.h | 65 ++ src/Mod/Path/App/ParamsHelper.h | 998 ++++++++++++++++++ src/Mod/Path/Gui/AppPathGui.cpp | 3 + src/Mod/Path/Gui/CMakeLists.txt | 2 + src/Mod/Path/Gui/Command.cpp | 82 +- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Path/Gui/Resources/icons/Path-Area.svg | 648 ++++++++++++ src/Mod/Path/Gui/ViewProviderArea.cpp | 125 +++ src/Mod/Path/Gui/ViewProviderArea.h | 60 ++ src/Mod/Path/InitGui.py | 2 +- 18 files changed, 3479 insertions(+), 3 deletions(-) create mode 100644 src/Mod/Path/App/Area.cpp create mode 100644 src/Mod/Path/App/Area.h create mode 100644 src/Mod/Path/App/AreaParams.h create mode 100644 src/Mod/Path/App/AreaPy.xml create mode 100644 src/Mod/Path/App/AreaPyImp.cpp create mode 100644 src/Mod/Path/App/FeatureArea.cpp create mode 100644 src/Mod/Path/App/FeatureArea.h create mode 100644 src/Mod/Path/App/ParamsHelper.h create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Area.svg create mode 100644 src/Mod/Path/Gui/ViewProviderArea.cpp create mode 100644 src/Mod/Path/Gui/ViewProviderArea.h diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index ab6ffe02ec..4723ff7b1f 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -41,6 +41,8 @@ #include "PropertyTooltable.h" #include "FeaturePathCompound.h" #include "FeaturePathShape.h" +#include "AreaPy.h" +#include "FeatureArea.h" namespace Path { extern PyObject* initModule(); @@ -49,15 +51,24 @@ extern PyObject* initModule(); /* Python entry */ PyMODINIT_FUNC initPath() { + // load dependent module + try { + Base::Interpreter().runString("import Part"); + } + catch(const Base::Exception& e) { + PyErr_SetString(PyExc_ImportError, e.what()); + return; + } + PyObject* pathModule = Path::initModule(); Base::Console().Log("Loading Path module... done\n"); - // Add Types to module Base::Interpreter().addType(&Path::CommandPy ::Type, pathModule, "Command"); Base::Interpreter().addType(&Path::PathPy ::Type, pathModule, "Path"); Base::Interpreter().addType(&Path::ToolPy ::Type, pathModule, "Tool"); Base::Interpreter().addType(&Path::TooltablePy ::Type, pathModule, "Tooltable"); + Base::Interpreter().addType(&Path::AreaPy ::Type, pathModule, "Area"); // NOTE: To finish the initialization of our own type objects we must // call PyType_Ready, otherwise we run into a segmentation fault, later on. @@ -74,4 +85,7 @@ PyMODINIT_FUNC initPath() Path::FeatureCompoundPython ::init(); Path::FeatureShape ::init(); Path::FeatureShapePython ::init(); + Path::Area ::init(); + Path::FeatureArea ::init(); + Path::FeatureAreaPython ::init(); } diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp new file mode 100644 index 0000000000..45564e9608 --- /dev/null +++ b/src/Mod/Path/App/Area.cpp @@ -0,0 +1,592 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "Area.h" +#include "../libarea/Area.h" + +using namespace Path; + +CAreaParams::CAreaParams() + :PARAM_INIT(NAME,AREA_PARAMS_CAREA) +{} + +AreaParams::AreaParams() + :PARAM_INIT(NAME,AREA_PARAMS_BASE) +{} + +CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs) + :params(p) +{ + // Arc fitting is lossy. we shall reduce the number of unecessary fit + if(noFitArcs) + params.FitArcs=false; + +#define AREA_CONF_SAVE_AND_APPLY(_param) \ + PARAM_FNAME(_param) = BOOST_PP_CAT(CArea::get_,PARAM_FARG(_param))();\ + BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(params.PARAM_FNAME(_param)); + + PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY,AREA_PARAMS_CAREA) +} + +CAreaConfig::~CAreaConfig() { + +#define AREA_CONF_RESTORE(_param) \ + BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(PARAM_FNAME(_param)); + + PARAM_FOREACH(AREA_CONF_RESTORE,AREA_PARAMS_CAREA) +} + +////////////////////////////////////////////////////////////////////////////// + +TYPESYSTEM_SOURCE(Path::Area, Base::BaseClass); + +Area::Area(const AreaParams *params) +:myArea(NULL) +,myAreaOpen(NULL) +,myHaveFace(false) +{ + if(params) + setParams(*params); +} + +Area::~Area() { + clean(); +} + +void Area::setPlane(const TopoDS_Shape &shape) { + myWorkPlane = shape; +} + +void Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, + double deflection, CArea *areaOpen, bool to_edges, bool reorder) +{ + bool haveShape = false; + + for (TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + const TopoDS_Face &face = TopoDS::Face(it.Current()); + for (TopExp_Explorer it(face, TopAbs_WIRE); it.More(); it.Next()) + add(area,TopoDS::Wire(it.Current()),trsf,deflection); + } + + if(haveShape) return; + + CArea _area; + CArea _areaOpen; + + for (TopExp_Explorer it(shape, TopAbs_WIRE); it.More(); it.Next()) { + haveShape = true; + const TopoDS_Wire &wire = TopoDS::Wire(it.Current()); + if(BRep_Tool::IsClosed(wire)) + add(_area,wire,trsf,deflection); + else if(to_edges) { + for (TopExp_Explorer it(wire, TopAbs_EDGE); it.More(); it.Next()) + add(_areaOpen,BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(),trsf,deflection); + }else + add(_areaOpen,wire,trsf,deflection); + } + + if(!haveShape) { + for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + add(_areaOpen,BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(),trsf,deflection); + } + } + + if(reorder) + _area.Reorder(); + area.m_curves.splice(area.m_curves.end(),_area.m_curves); + if(areaOpen) + areaOpen->m_curves.splice(areaOpen->m_curves.end(),_areaOpen.m_curves); + else + area.m_curves.splice(area.m_curves.end(),_areaOpen.m_curves); +} + +void Area::add(CArea &area, const TopoDS_Wire& wire, + const gp_Trsf *trsf, double deflection) +{ + CCurve ccurve; + BRepTools_WireExplorer xp(trsf?TopoDS::Wire( + wire.Moved(TopLoc_Location(*trsf))):wire); + + gp_Pnt p = BRep_Tool::Pnt(xp.CurrentVertex()); + ccurve.append(CVertex(Point(p.X(),p.Y()))); + + for (;xp.More();xp.Next()) { + BRepAdaptor_Curve curve(xp.Current()); + bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); + + p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); + + switch (curve.GetType()) { + case GeomAbs_Line: { + ccurve.append(CVertex(Point(p.X(),p.Y()))); + break; + } case GeomAbs_Circle:{ + double first = curve.FirstParameter(); + double last = curve.LastParameter(); + gp_Circ circle = curve.Circle(); + gp_Ax1 axis = circle.Axis(); + int dir = axis.Direction().Z()<0?-1:1; + if(reversed) dir = -dir; + gp_Pnt loc = axis.Location(); + if(fabs(first-last)>M_PI) { + // Split arc(circle) larger than half circle. This is + // translated from PathUtil code. Not sure why it is + // needed. + gp_Pnt mid = curve.Value((last-first)*0.5+first); + ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), + Point(loc.X(),loc.Y()))); + } + ccurve.append(CVertex(dir,Point(p.X(),p.Y()), + Point(loc.X(),loc.Y()))); + break; + } default: { + // Discretize all other type of curves + GCPnts_UniformDeflection discretizer(curve, deflection, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + int nbPoints = discretizer.NbPoints (); + for (int i=1; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + } + }else + Standard_Failure::Raise("Curve discretization failed"); + }} + } + if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { + Base::Console().Warning("ccurve not closed\n"); + ccurve.append(ccurve.m_vertices.front()); + } + area.append(ccurve); +} + + +void Area::clean(bool deleteShapes) { + myShape.Nullify(); + delete myArea; + myArea = NULL; + delete myAreaOpen; + myAreaOpen = NULL; + if(deleteShapes) + myShapes.clear(); +} + +void Area::add(const TopoDS_Shape &shape,short op) { +#define AREA_SRC_OP(_v) op + PARAM_ENUM_CONVERT(AREA_SRC_OP,,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); + TopExp_Explorer it(shape, TopAbs_SHELL); + if(it.More()) + throw Base::ValueError("not a 2D shape"); + clean(); + if(myShapes.empty()) + Operation = ClipperLib::ctUnion; + myShapes.push_back(Shape((short)Operation,shape)); +} + + +void Area::setParams(const AreaParams ¶ms) { +#define AREA_SRC2(_v) params._v + // Validate all enum type of parameters + PARAM_ENUM_CHECK(AREA_SRC2,PARAM_ENUM_EXCEPT,AREA_PARAMS_CONF); + if(params!=myParams) + clean(); + myParams = params; +} + +void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { + if(!myHaveFace) { + TopExp_Explorer it(shape, TopAbs_FACE); + myHaveFace = it.More(); + } + CArea areaOpen; + add(area,shape,&myTrsf,myParams.Deflection,&areaOpen, + myParams.OpenMode==OpenModeEdges,myParams.Reorder); + if(areaOpen.m_curves.size()) { + if(&area == myArea || myParams.OpenMode == OpenModeNone) + myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); + else + Base::Console().Warning("open wires discarded in clipping shapes\n"); + } +} + +void Area::build() { + if(myArea) return; + + if(myShapes.empty()) + throw Base::ValueError("Null shape"); + +#define AREA_SRC(_v) myParams._v + PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); + + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + if(!myWorkPlane.IsNull()) + builder.Add(comp,myWorkPlane); + else { + for(const Shape &s : myShapes) + builder.Add(comp, s.shape); + } + BRepLib_FindSurface planeFinder(comp,-1,Standard_True); + if (!planeFinder.Found()) + throw Base::ValueError("shapes are not coplanar"); + + myTrsf.SetTransformation(GeomAdaptor_Surface( + planeFinder.Surface()).Plane().Position()); + + myArea = new CArea(); + myAreaOpen = new CArea(); + + CAreaConfig conf(myParams); + CArea areaClip; + + short op = ClipperLib::ctUnion; + bool pending = false; + for(const Shape &s : myShapes) { + if(op!=s.op) { + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + pending = false; + myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + areaClip.m_curves.clear(); + op=s.op; + } + addToBuild(op==ClipperLib::ctUnion?*myArea:areaClip,s.shape); + pending = true; + } + if(pending){ + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + } + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); +} + +TopoDS_Shape Area::toShape(CArea &area, short fill) { + gp_Trsf trsf(myTrsf.Inverted()); + bool bFill; + switch(fill){ + case Area::FillAuto: + bFill = myHaveFace; + break; + case Area::FillFace: + bFill = true; + break; + default: + bFill = false; + } + if(myParams.FitArcs) { + if(&area == myArea) { + CArea copy(area); + copy.FitArcs(); + return toShape(copy,bFill,&trsf); + } + area.FitArcs(); + } + return toShape(area,bFill,&trsf); +} + +const TopoDS_Shape &Area::getShape() { + if(myShape.IsNull()) { + build(); + CAreaConfig conf(myParams); + myShape = toShape(*myArea,myParams.Fill); + } + return myShape; +} + +TopoDS_Shape Area::makeOffset(PARAM_ARGS(ARG,AREA_PARAMS_OFFSET)) { + std::list shapes; + makeOffset(shapes,PARAM_FIELDS(ARG,AREA_PARAMS_OFFSET)); + if(shapes.empty()) + return TopoDS_Shape(); + if(shapes.size()==1) + return shapes.front(); + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(const TopoDS_Shape &s : shapes) + builder.Add(compound,s); + return compound; +} + +void Area::makeOffset(std::list &shapes, + PARAM_ARGS(ARG,AREA_PARAMS_OFFSET)) +{ + if(fabs(offset) 0) { + count += extra_pass; + }else{ + if(stepover>0 || offset>0) + throw Base::ValueError("invalid extra count"); + // In this case, we loop until no outputs from clipper + count=-1; + } + } + + PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_OFFSET_CONF); +#ifdef AREA_OFFSET_ALGO + PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); +#endif + + for(int i=0;count<0||im_curves) { + if(c.IsClosed()) + area.append(c); + else + areaOpen.append(c); + } + }else +#endif + area = *myArea; + +#ifdef AREA_OFFSET_ALGO + switch(myParams.Algo){ + case Area::Algolibarea: + // libarea somehow fails offset without Reorder, but ClipperOffset + // works okay. Don't know why + area.Reorder(); + area.Offset(-offset); + if(areaOpen.m_curves.size()) { + areaOpen.Thicken(offset); + area.Clip(ClipperLib::ctUnion,&areaOpen,SubjectFill,ClipFill); + } + break; + case Area::AlgoClipperOffset: +#endif + area.OffsetWithClipper(offset,JoinType,EndType, + myParams.MiterLimit,myParams.RoundPreceision); +#ifdef AREA_OFFSET_ALGO + break; + } +#endif + + if(area.m_curves.empty()) + return; + + if(count == 1) { + shapes.push_back(toShape(area,myParams.Fill)); + return; + } + shapes.push_back(toShape(area,Area::FillNone)); + } +} + +TopoDS_Shape Area::makePocket(PARAM_ARGS(ARG,AREA_PARAMS_POCKET)) { + if(tool_radius < Precision::Confusion()) + throw Base::ValueError("tool radius too small"); + + if(stepover == 0.0) + stepover = tool_radius; + + if(stepover < Precision::Confusion()) + throw Base::ValueError("stepover too small"); + + if(mode == Area::PocketModeNone) + return TopoDS_Shape(); + + PocketMode pm; + switch(mode) { + case Area::PocketModeZigZag: + pm = ZigZagPocketMode; + break; + case Area::PocketModeSpiral: + pm = SpiralPocketMode; + break; + case Area::PocketModeOffset: { + PARAM_DECLARE_INIT(NAME,AREA_PARAMS_OFFSET); + Offset = -tool_radius-extra_offset; + ExtraPass = -1; + Stepover = -stepover; + return makeOffset(PARAM_FIELDS(NAME,AREA_PARAMS_OFFSET)); + }case Area::PocketModeZigZagOffset: + pm = ZigZagThenSingleOffsetPocketMode; + break; + default: + throw Base::ValueError("unknown poket mode"); + } + + build(); + CAreaConfig conf(myParams); + CAreaPocketParams params( + tool_radius,extra_offset,stepover,from_center,pm,zig_angle); + CArea in(*myArea),out; + // MakePcoketToolPath internally uses libarea Offset which somehow demands + // reorder before input, otherwise nothing is shown. + in.Reorder(); + in.MakePocketToolpath(out.m_curves,params); + return toShape(out,FillNone); +} + +static inline bool IsLeft(const gp_Pnt &a, const gp_Pnt &b, const gp_Pnt &c) { + return ((b.X() - a.X())*(c.Y() - a.Y()) - (b.Y() - a.Y())*(c.X() - a.X())) > 0; +} + +TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) { + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + + for(const CCurve &c : area.m_curves) { + BRepBuilderAPI_MakeWire mkWire; + gp_Pnt pstart,pt; + bool first = true; + for(const CVertex &v : c.m_vertices){ + if(first){ + first = false; + pstart = pt = gp_Pnt(v.m_p.x,v.m_p.y,0); + continue; + } + gp_Pnt pnext(v.m_p.x,v.m_p.y,0); + if(v.m_type == 0) { + mkWire.Add(BRepBuilderAPI_MakeEdge(pt,pnext).Edge()); + } else { + gp_Pnt center(v.m_c.x,v.m_c.y,0); + double r = center.Distance(pt); + double r2 = center.Distance(pnext); + if(fabs(r-r2) > Precision::Confusion()) { + double d = pt.Distance(pnext); + double q = sqrt(r*r - d*d*0.25); + double x = (pt.X()+pnext.X())*0.5; + double y = (pt.Y()+pnext.Y())*0.5; + double dx = q*(pt.Y()-pnext.Y())/d; + double dy = q*(pnext.X()-pt.X())/d; + gp_Pnt newCenter(x + dx, y + dy,0); + if(IsLeft(pt,pnext,center) != IsLeft(pt,pnext,newCenter)) { + newCenter.SetX(x - dx); + newCenter.SetY(y - dy); + } + Base::Console().Warning( + "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", + r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); + center = newCenter; + } + gp_Ax2 axis(center, gp_Dir(0,0,v.m_type)); + mkWire.Add(BRepBuilderAPI_MakeEdge(gp_Circ(axis,r),pt,pnext).Edge()); + } + pt = pnext; + } + if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){ + // This should never happen after changing libarea's + // Point::tolerance to be the same as Precision::Confusion(). + // Just leave it here in case. + BRepAdaptor_Curve curve(mkWire.Edge()); + gp_Pnt p1(curve.Value(curve.FirstParameter())); + gp_Pnt p2(curve.Value(curve.LastParameter())); + std::stringstream str; + str<< "warning: patch open wire" << + c.m_vertices.back().m_type << endl << + '(' << p1.X() << ',' << p1.Y() << ')' << endl << + '(' << p2.X() << ',' << p2.Y() << ')' << endl << + '(' << pt.X() << ',' << pt.Y() << ')' << endl << + '(' << pstart.X() << ',' <* + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef PATH_AREA_H +#define PATH_AREA_H + +#include +#include +#include +#include + +#include "AreaParams.h" + +class CArea; + +namespace Path +{ + +/** Store libarea algorithm configuration */ +struct PathExport CAreaParams { + PARAM_DECLARE(NAME,AREA_PARAMS_CAREA) + CAreaParams(); +}; + +/** Store all Area configurations */ +struct PathExport AreaParams: CAreaParams { + + PARAM_DECLARE(NAME,AREA_PARAMS_BASE) + PARAM_DECLARE(NAME,AREA_PARAMS_OFFSET_CONF) + + bool operator==(const AreaParams &other) const { +#define AREA_COMPARE(_param) \ + if(PARAM_FIELD(NAME,_param)!=other.PARAM_FIELD(NAME,_param)) return false; + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CONF) + return true; + } + bool operator!=(const AreaParams &other) const { + return !(*this == other); + } + + AreaParams(); +}; + +/** libarea configurator + * + * It is kind of troublesome with the fact that libarea uses static variables to + * config its algorithm. CAreaConfig makes it easy to safely customize libarea. + */ +struct PathExport CAreaConfig { + + /** Stores current libarea settings */ + PARAM_DECLARE(NAME,AREA_PARAMS_CAREA) + + /** Stores user defined setting */ + CAreaParams params; + + /** The constructor automatically saves current setting and apply user defined ones + * + * \arg \c p user defined configurations + * \arg \c noFitArgs if true, will override and disable arc fitting. Because + * arc unfiting and fitting is lossy. And repeatedly perform these operation + * may cause shape deformation. So it is best to delay arc fitting until the + * final step*/ + CAreaConfig(const CAreaParams &p, bool noFitArcs=true); + + /** The destructor restores the setting, and thus exception safe. */ + ~CAreaConfig(); +}; + + +/** Base class for FreeCAD wrapping of libarea */ +class PathExport Area: public Base::BaseClass { + + TYPESYSTEM_HEADER(); + +protected: + + struct Shape { + short op; + TopoDS_Shape shape; + + Shape(short opCode, const TopoDS_Shape &s) + :op(opCode) + ,shape(s) + {} + }; + + std::list myShapes; + CArea *myArea; + CArea *myAreaOpen; + gp_Trsf myTrsf; + AreaParams myParams; + TopoDS_Shape myWorkPlane; + TopoDS_Shape myShape; + bool myHaveFace; + + /** Called internally to combine children shapes for further processing */ + void build(); + + /** Called by build() to add children shape + * + * Mainly for checking if there is any faces for auto fill*/ + void addToBuild(CArea &area, const TopoDS_Shape &shape); + + /** Called internally to obtain the combained children shapes */ + TopoDS_Shape toShape(CArea &area, short fill); + +public: + /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ + PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) + +public: + Area(const AreaParams *params = NULL); + virtual ~Area(); + + /** Set a working plane + * + * If no working plane are set, Area will try to find a working plane from + * all the added children shapes. The purpose of this function is in case + * the child shapes are all colinear edges + * + * \arg \c shape: a shape defining a working plane + */ + void setPlane(const TopoDS_Shape &shape); + + /** Add a child shape with given operation code + * + * No validation is done at this point. Exception will be thrown when asking + * for output shape, if any of the children shapes is not valid or not + * coplanar + * + * \arg \c shape: the child shape + * \arg \c op: operation code, see #AREA_PARAMS_OPCODE + */ + void add(const TopoDS_Shape &shape,PARAM_ARGS_DEF(ARG,AREA_PARAMS_OPCODE)); + + /** Generate an offset of the combined shape + * + * See #AREA_PARAMS_OFFSET for description of the arguments. + * If more than one offset is requested, a compound shape is return + * containing all offset shapes as wires regardless of \c Fill setting. + */ + TopoDS_Shape makeOffset(PARAM_ARGS_DEF(ARG,AREA_PARAMS_OFFSET)); + + /** Obtain a list of offset shapes of the combined shape, + * + * See #AREA_PARAMS_OFFSET for description of the arguments. + */ + void makeOffset(std::list &shapes, + PARAM_ARGS_DEF(ARG,AREA_PARAMS_OFFSET)); + + /** Make a pocket of the combined shape + * + * See #AREA_PARAMS_POCKET for description of the arguments. + */ + TopoDS_Shape makePocket(PARAM_ARGS_DEF(ARG,AREA_PARAMS_POCKET)); + + + /** Config this Area object */ + void setParams(const AreaParams ¶ms); + + + /** Get the current configuration */ + const AreaParams &getParams() const { + return myParams; + } + + /** Clean internal caches + * + * The combained shapes is cached internally to make other operation more + * efficient, such as makeOffset() and makePocket() + * + * \arg \c deleteShapes: if true, delete all children shapes. + */ + void clean(bool deleteShapes=false); + + /** Get the combined shape */ + const TopoDS_Shape &getShape(); + + /** Add a OCC wire shape to CArea + * + * \arg \c area: output converted curved object to here + * \arg \c wire: input wire object + * \arg \c trsf: optional transform matrix to transform the wire shape into + * XY0 plane. + * \arg \c deflection: for defecting non circular curves + * */ + static void add(CArea &area, const TopoDS_Wire &wire, + const gp_Trsf *trsf=NULL,double deflection=0.01); + + /** Add a OCC generic shape to CArea + * + * \arg \c area: output converted curved object to here + * \arg \c shape: input shape object + * \arg \c trsf: optional transform matrix to transform the wire shape into + * XY0 plane. + * \arg \c deflection: for defecting non circular curves + * \arg \c areaOpen: for collecting open curves. If not supplied, open + * curves are added to \c area + * \arg \c to_edges: separate open wires to individual edges + * \arg \c reorder: reorder closed wires for wire only shape + * */ + static void add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL, + double deflection=0.01,CArea *areaOpen=NULL, bool to_edges=false, bool reorder=true); + + /** Convert curves in CArea into an OCC shape + * + * \arg \c area: input area object + * \arg \c fill: if true, create a face object from the wires + * \arg \c trsf: optional transform matrix to transform the shape back into + * its original position. + * */ + static TopoDS_Shape toShape(const CArea &area, bool fill, + const gp_Trsf *trsf=NULL); +}; + +} //namespace Path + +#endif //PATH_AREA_H diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h new file mode 100644 index 0000000000..01b153676b --- /dev/null +++ b/src/Mod/Path/App/AreaParams.h @@ -0,0 +1,129 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef PATH_AreaParams_H +#define PATH_AreaParams_H + +// deifne this to enable offset algo selection +// #define AREA_OFFSET_ALGO + +/** \file + * Parameters definition for Path::Area and its companion + * See \ref ParamPage "here" for details of parameter definition. + */ + +#include "ParamsHelper.h" + +/** clipper fill type */ +#define AREA_CLIPPER_FILL_TYPE \ + (NonZero)(EvenOdd)(Positive)(Negative),(ClipperLib::PolyFillType,ClipperLib::pft) + +/** Paramerters of clipper fill types */ +#define AREA_PARAMS_CLIPPER_FILL \ + ((enum2,subject_fill,SubjectFill,0,\ + "ClipperLib subject fill type. \nSee https://goo.gl/5pYQQP",AREA_CLIPPER_FILL_TYPE))\ + ((enum2,clip_fill,ClipFill,0,\ + "ClipperLib clip fill type. \nSee https://goo.gl/5pYQQP",AREA_CLIPPER_FILL_TYPE)) + +/** Base parameters */ +#define AREA_PARAMS_BASE \ + ((enum,fill,Fill,2,"Fill the output wires to make a face. \n"\ + "Auto means make a face if any of the children has a face.",(None)(Face)(Auto)))\ + ((bool,reorder,Reorder,false,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ + ((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\ + "'None' means combin without openeration.\n"\ + "'Edges' means separate to edges before Union.\n"\ + "ClipperLib seems to have an urge to close open wires.",(None)(Union)(Edges)))\ + AREA_PARAMS_CLIPPER_FILL \ + ((double,deflection,Deflection,0.01,"Deflection for non circular curve discretization")) + +/** libarea algorithm option parameters */ +#define AREA_PARAMS_CAREA \ + ((double,tolerance,Tolerance,Precision::Confusion(),"Point coincidence tolerance"))\ + ((bool,fit_arcs,FitArcs,true,"Enable arc fitting"))\ + ((bool,clipper_simple,Simplify,false,\ + "Simplify polygons after operation. See https://goo.gl/Mh9XK1"))\ + ((double,clipper_clean_distance,CleanDistance,0.0,\ + "Clean polygon smaller than this distance. See https://goo.gl/jox3JY"))\ + ((double,accuracy,Accuracy,0.01,"Arc fitting accuracy"))\ + ((double,units,Unit,1.0,"Scaling factor for convertion to inch"))\ + ((short,min_arc_points,MinArcPoints,4,"Minimum segments for arc discretization"))\ + ((short,max_arc_points,MaxArcPoints,100,"Maximum segments for arc discretization"))\ + ((double,clipper_scale,ClipperScale,10000.0,\ + "ClipperLib operate on intergers. This is the scale factor to \nconvert floating points.")) + +/** Pocket parameters + * + * These parameters cooresponds to CAreaPocketParams in libarea + * */ +#define AREA_PARAMS_POCKET \ + ((enum,mode,PocketMode,1,"Selects the pocket toolpath pattern",(None)(ZigZag)(Offset)(Spiral)(ZigZagOffset)))\ + ((double,tool_radius,ToolRadius,1.0,"Tool radius for pocketing"))\ + ((double,extra_offset,PocketExtraOffset,0.0,"Extra offset for pocketing"))\ + ((double,stepover,PocketStepover,0.0,"Cutter diameter to step over on each pass. If =0, use ToolRadius."))\ + ((bool,from_center,FromCenter,true,"Start pocketing from center"))\ + ((double,zig_angle,ZigAngle,45,"Zig angle in degree")) + +/** Operation code */ +#define AREA_PARAMS_OPCODE \ + ((enum2,op,Operation,0,"Boolean operation",\ + (Union)(Difference)(Intersection)(Xor),(ClipperLib::ClipType,ClipperLib::ct))) + +/** Offset parameters */ +#define AREA_PARAMS_OFFSET \ + ((double,offset,Offset,0.0,"Offset value, positive for expansion, negative for shrinking"))\ + ((long,extra_pass,ExtraPass,0,"Number of extra offset pass to generate."))\ + ((double,stepover,Stepover,0.0,"Cutter diameter to step over on each pass. If =0, use Offset")) + +#ifdef AREA_OFFSET_ALGO +# define AREA_PARAMS_OFFSET_ALGO \ + ((enum,algo,Algo,0,"Offset algorithm type",(Clipper)(libarea))) +#else +# define AREA_PARAMS_OFFSET_ALGO +#endif + +/** Offset configuration parameters */ +#define AREA_PARAMS_OFFSET_CONF \ + AREA_PARAMS_OFFSET_ALGO \ + ((bool,thicken,Thicken,false,"Thicken the resulting wires with Offset"))\ + ((enum2,join_type,JoinType,0,"ClipperOffset join type. \nSee https://goo.gl/4odfQh",\ + (Round)(Square)(Miter),(ClipperLib::JoinType,ClipperLib::jt)))\ + ((enum2,end_type,EndType,0,"\nClipperOffset end type. See https://goo.gl/tj7gkX",\ + (OpenRound)(ClosedPolygon)(ClosedLine)(OpenSquare)(OpenButt),(ClipperLib::EndType,ClipperLib::et)))\ + ((double,miter_limit,MiterLimit,2.0,"Miter limit for joint type Miter. See https://goo.gl/K8xX9h"))\ + ((double,round_precision,RoundPreceision,0.0,\ + "Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh")) + +/** Group of all Area configuration parameters */ +#define AREA_PARAMS_CONF \ + AREA_PARAMS_CAREA \ + AREA_PARAMS_BASE \ + AREA_PARAMS_OFFSET_CONF + +/** Group of all Area parameters */ +#define AREA_PARAMS_ALL \ + AREA_PARAMS_CONF \ + AREA_PARAMS_OPCODE \ + AREA_PARAMS_OFFSET \ + AREA_PARAMS_POCKET + +#endif //PATH_AreaParam_H diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml new file mode 100644 index 0000000000..e4952e13bb --- /dev/null +++ b/src/Mod/Path/App/AreaPy.xml @@ -0,0 +1,62 @@ + + + + + + FreeCAD python wrapper of libarea + + + + + + + + + setPlane(shape): Set the working plane. The shape will not be used for +any operation + + + + + toShape(rebuild=False): Return the resulting shape + + + + + + + + + + + + + + + + + + + + getParamsDesc(as_string=True): Returns a list of supported parameters and their descriptions.\n +* as_string: if False, then return a dictionary of documents of all supported parameters. + + + + + + Get current algorithm parameters as a dictionary. + + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp new file mode 100644 index 0000000000..2277405198 --- /dev/null +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#include +#include + +#include "Mod/Path/App/Area.h" + +// inclusion of the generated files (generated out of AreaPy.xml) +#include "AreaPy.h" +#include "AreaPy.cpp" + + +struct AreaDoc { + const char *name; + const char *doc; +}; + +/** Generate doc string from parameter definitions + * It will generate doc string and replace the one generated from xml + * */ +static const AreaDoc myDocs[] = { + { + "setParams", + "setParam(key=value...): Set algorithm parameters. You can call getParamsDesc() to \n" + "get a list of supported parameters and their descriptions.\n" + + PARAM_PY_DOC(NAME,AREA_PARAMS_CONF) + }, + { + "add", + + "add((shape...)," PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OPCODE) "):\n" + "Add TopoShape(s) with given operation code\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_OPCODE) + "\nThe first shape's wires will be fused together regardless of the op code given.\n" + "Subsequent shape's wire will be combined using the op code. All shape wires\n" + "shall be coplanar, and are used to determine a working plane for face making and\n" + "offseting. You can call setPlane() to supply a reference shape to determin the\n" + "working plane in case the added shapes are all colinear lines.\n", + }, + + { + "makeOffset", + + "makeOffset(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" + "Make an 2D offset of the shape.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_OFFSET), + }, + { + "makePocket", + + "makePocket(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" + "Generate pocket toolpath of the shape.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), + }, +}; + +struct AreaPyDoc { + AreaPyDoc() { + for(PyMethodDef &method : Path::AreaPy::Methods) { + if(!method.ml_name) continue; + for(const AreaDoc &doc : myDocs) { + if(std::strcmp(method.ml_name,doc.name)==0) { + method.ml_doc = doc.doc; + break; + } + } + } + } +}; + +static AreaPyDoc doc; + +namespace Part { + extern Py::Object shape2pyshape(const TopoDS_Shape &shape); +} + +using namespace Path; + +// returns a string which represents the object e.g. when printed in python +std::string AreaPy::representation(void) const +{ + std::stringstream str; + str << ""; + return str.str(); +} + +PyObject *AreaPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + return new AreaPy(new Area); +} + +// constructor method +int AreaPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/) +{ + return 0; +} + +PyObject* AreaPy::setPlane(PyObject *args) { + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj)) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + +#define GET_TOPOSHAPE(_p) static_cast(_p)->getTopoShapePtr()->getShape() + getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj)); + return Py_None; +} + +PyObject* AreaPy::toShape(PyObject *args, PyObject *keywds) +{ + PyObject *pcObj = Py_True; + static char *kwlist[] = {"rebuild", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds,"|O",kwlist,&pcObj)) + Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + + try { + if(PyObject_IsTrue(pcObj)) + getAreaPtr()->clean(true); + return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape())); + } + PY_CATCH_OCC; +} + +PyObject* AreaPy::add(PyObject *args, PyObject *keywds) +{ + PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_OPCODE) + PyObject *pcObj; + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), + kwlist,&pcObj,PARAM_REF(ARG,AREA_PARAMS_OPCODE))) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + + if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { + getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op); + return Py_None; + } + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it){ + PyObject* item = (*it).ptr(); + getAreaPtr()->add(GET_TOPOSHAPE(item), + PARAM_PY_FIELDS(ARG,AREA_PARAMS_OPCODE)); + } + return Py_None; +} + +PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) +{ + //Generate a keyword string defined in the ARG field of OFFSET parameter list + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OFFSET), NULL}; + + //Declare variables defined in the ARG field of the OFFSET parameter list with + //initialization to defaults + PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_OFFSET) + + //Parse arguments to overwrite the defaults + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, + PARAM_REF(ARG,AREA_PARAMS_OFFSET))) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + + try { + //Expand the variable as function call arguments + TopoDS_Shape resultShape = getAreaPtr()->makeOffset( + PARAM_PY_FIELDS(ARG,AREA_PARAMS_OFFSET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); + } + PY_CATCH_OCC; +} + +PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), NULL}; + + PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_POCKET) + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, + PARAM_REF(ARG,AREA_PARAMS_POCKET))) + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + + try { + TopoDS_Shape resultShape = getAreaPtr()->makePocket( + PARAM_PY_FIELDS(ARG,AREA_PARAMS_POCKET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); + } + PY_CATCH_OCC; +} + + +PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; + + //Declare variables defined in the NAME field of the CONF parameter list + PARAM_PY_DECLARE(NAME,AREA_PARAMS_CONF); + + AreaParams params = getAreaPtr()->getParams(); + +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); + //populate the CONF variables with params + PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(NAME,AREA_PARAMS_CONF))) + Py_Error(Base::BaseExceptionFreeCADError, + "Wrong parameters, call getParamsDesc() to get supported params"); + +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + //populate 'params' with the CONF variables + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + + getAreaPtr()->setParams(params); + return Py_None; +} + +PyObject* AreaPy::getParams(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + + const AreaParams ¶ms =getAreaPtr()->getParams(); + + PyObject *dict = PyDict_New(); +#define AREA_SRC(_v) params._v + PARAM_PY_DICT_SET_VALUE(dict,AREA_SRC,AREA_PARAMS_CONF) + return dict; +} + +PyObject* AreaPy::getParamsDesc(PyObject *args, PyObject *keywds) +{ + PyObject *pcObj = Py_True; + static char *kwlist[] = {"as_string", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds,"|O",kwlist,&pcObj)) + Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + + if(PyObject_IsTrue(pcObj)) + return PyString_FromString(PARAM_PY_DOC(NAME,AREA_PARAMS_CONF)); + + PyObject *dict = PyDict_New(); + PARAM_PY_DICT_SET_DOC(dict,AREA_PARAMS_CONF) + return dict; +} + +// custom attributes get/set + +PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int AreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + + diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 266209f8b9..14531ff4d8 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -22,6 +22,7 @@ link_directories(${OCC_LIBRARY_DIR}) set(Path_LIBS # Robot Part + area-native FreeCADApp ) @@ -30,6 +31,7 @@ generate_from_xml(PathPy) generate_from_xml(ToolPy) generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) +generate_from_xml(AreaPy) SET(Python_SRCS CommandPy.xml @@ -41,6 +43,8 @@ SET(Python_SRCS TooltablePyImp.cpp FeaturePathCompoundPy.xml FeaturePathCompoundPyImp.cpp + AreaPy.xml + AreaPyImp.cpp ) SET(Mod_SRCS @@ -67,6 +71,12 @@ SET(Path_SRCS FeaturePathCompound.h FeaturePathShape.cpp FeaturePathShape.h + Area.cpp + Area.h + AreaParams.h + ParamsHelper.h + FeatureArea.cpp + FeatureArea.h ${Mod_SRCS} ${Python_SRCS} ) diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp new file mode 100644 index 0000000000..cdc26f1d7a --- /dev/null +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include + +#include "FeatureArea.h" +#include +#include +#include + +using namespace Path; + +PROPERTY_SOURCE(Path::FeatureArea, Part::Feature) + +PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_ALL) + +FeatureArea::FeatureArea() +{ + ADD_PROPERTY(Sources,(0)); + ADD_PROPERTY(WorkPlane,(TopoDS_Shape())); + + PARAM_PROP_ADD("Area",AREA_PARAMS_OPCODE); + PARAM_PROP_ADD("Area",AREA_PARAMS_BASE); + PARAM_PROP_ADD("Offset",AREA_PARAMS_OFFSET); + PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET); + PARAM_PROP_ADD("Offset Settings", AREA_PARAMS_OFFSET_CONF); + PARAM_PROP_ADD("libarea Settings",AREA_PARAMS_CAREA); + + PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_ALL); + PocketMode.setValue((long)0); +} + +FeatureArea::~FeatureArea() +{ +} + +App::DocumentObjectExecReturn *FeatureArea::execute(void) +{ + std::vector links = Sources.getValues(); + if (links.empty()) + return new App::DocumentObjectExecReturn("No shapes linked"); + + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + if (!(*it && (*it)->isDerivedFrom(Part::Feature::getClassTypeId()))) + return new App::DocumentObjectExecReturn("Linked object is not a Part object (has no Shape)."); + TopoDS_Shape shape = static_cast(*it)->Shape.getShape().getShape(); + if (shape.IsNull()) + return new App::DocumentObjectExecReturn("Linked shape object is empty"); + } + + AreaParams params; + +#define AREA_PROP_GET(_param) \ + params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); + + PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_CONF) + + Area area(¶ms); + + TopoDS_Shape workPlane = WorkPlane.getShape().getShape(); + if(!workPlane.IsNull()) + area.setPlane(workPlane); + + area.clean(true); + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + area.add(static_cast(*it)->Shape.getShape().getShape(), + PARAM_PROP_ARGS(AREA_PARAMS_OPCODE)); + } + + std::list shapes; + if(fabs(Offset.getValue())>Precision::Confusion()) + area.makeOffset(shapes,PARAM_PROP_ARGS(AREA_PARAMS_OFFSET)); + + if(PocketMode.getValue()) { + Area areaPocket(¶ms); + if(shapes.empty()) + areaPocket.add(area.getShape()); + else{ + bool front = true; + if(shapes.size()>1) { + double step = Stepover.getValue(); + if(fabs(step)0; + } + areaPocket.add(front?shapes.front():shapes.back()); + } + shapes.push_back(areaPocket.makePocket(PARAM_PROP_ARGS(AREA_PARAMS_POCKET))); + } + + if(shapes.empty()) + this->Shape.setValue(area.getShape()); + else if(shapes.size()==1) + this->Shape.setValue(shapes.front()); + else { + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(const TopoDS_Shape &s : shapes) + builder.Add(compound,s); + this->Shape.setValue(compound); + } + return Part::Feature::execute(); +} + +short FeatureArea::mustExecute(void) const +{ + if (Sources.isTouched()) + return 1; + if (WorkPlane.isTouched()) + return 1; + + PARAM_PROP_TOUCHED(AREA_PARAMS_ALL) + + return Part::Feature::mustExecute(); +} + +// Python Area feature --------------------------------------------------------- + +namespace App { +/// @cond DOXERR +PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaPython, Path::FeatureArea) + +template<> const char* Path::FeatureAreaPython::getViewProviderName(void) const { + return "PathGui::ViewProviderArea"; +} +/// @endcond + +// explicit template instantiation +template class PathExport FeaturePythonT; +} + diff --git a/src/Mod/Path/App/FeatureArea.h b/src/Mod/Path/App/FeatureArea.h new file mode 100644 index 0000000000..d5e10c2def --- /dev/null +++ b/src/Mod/Path/App/FeatureArea.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef PATH_FeatureArea_H +#define PATH_FeatureArea_H + +#include +#include +#include +#include +#include +#include "Mod/Part/App/PartFeature.h" + +#include "Area.h" + +namespace Path +{ + +class PathExport FeatureArea : public Part::Feature +{ + PROPERTY_HEADER(Path::FeatureArea); + +public: + /// Constructor + FeatureArea(void); + virtual ~FeatureArea(); + + /// returns the type name of the ViewProvider + virtual const char* getViewProviderName(void) const { + return "PathGui::ViewProviderArea"; + } + virtual App::DocumentObjectExecReturn *execute(void); + virtual short mustExecute(void) const; + + App::PropertyLinkList Sources; + Part::PropertyPartShape WorkPlane; + + PARAM_PROP_DECLARE(AREA_PARAMS_ALL) +}; + +typedef App::FeaturePythonT FeatureAreaPython; + +} //namespace Path + + +#endif // PATH_FeaturePath_H diff --git a/src/Mod/Path/App/ParamsHelper.h b/src/Mod/Path/App/ParamsHelper.h new file mode 100644 index 0000000000..f0d65dd966 --- /dev/null +++ b/src/Mod/Path/App/ParamsHelper.h @@ -0,0 +1,998 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef PARAMS_HELPER_H +#define PARAMS_HELPER_H + +/** \page ParamPage Parameter helper macros + * Collections of macros for managing groups of parameters. + * + * \section Motivation + * + * For an application like FreeCAD, there are often cases where the same set of + * parameters are refered in dozons of different places. The macros here is + * designed to help managing those parameters, so that you can define groups of + * parameters once, and refer them everywhere in groups with simple macro calls for + * all kinds of purposes. Any changing, adding and removing of parameters in the + * group become much easier. And by everywhere, I mean \ref ParamCommon + * "class definition, impelentation", \ref ParamProperty "document object properties", + * \ref ParamPy "python c++ classes", and even \ref ParamDoc "doc string", + * pretty much everything except the python code, which although not implemented + * yet, is in fact also possible to be done using C preprocessor (No one says C + * preprocessor must produce C code :). It is also possible (not implemented + * yet) to use macros to generate python wrapper class instead of using + * FreeCAD's current xml python export. + * + * \section Debugging + * + * Extensive use of macros has one noticable disadvantage, though. If some thing + * goes wrong, the compiler error message is kind of cryptic. If so, first + * double check your macro definition of the parameter is correctly, not missing + * or having extra parathesis or comma. Then, you can use the CMake + * intermeidate file target to get the preprocessor output for checking. For + * example, for a file located at \c src/Mod/Path/App/Area.cpp, + * \code{.sh} + * cd /src/Mod/Path/App + * make Area.cpp.i + * \endcode + * + * The preprocessed intermediate output will be at, + * \code{.sh} + * /src/Mod/Path/App.CMakeFiles/Path.dir/Area.cpp.i + * \endcode + * + * \section Intrudction of Boost.Preprocessor + * + * The macros here make heavy use of the awsome + * [Boost.Preprocessor](http://www.boost.org/libs/preprocessor/) (short for + * Boost.PP). Here are some brief introduction on Boost.PP conecept in order to + * explain why this marco library is designed the way it is. + * + * In Boost.PP, a sequence is defined as, + * \code{.sh} + * (a)(b)(c)... + * \endcode + * + * A sequence cannot be empty. Thus, \c () is not a sequence. And also those + * a, b, c here cannot directly contain ,. These restriction + * is due to the fact that ( ) , are among those very few special + * characters recognized by the preprocssor. \c a can itself be a sequence or + * other Boost.PP types, so by right, our parameter can be defined as something + * like + * \code{.sh} + * ((type)(name)(default)...) + * \endcode + * + * A bit awkward to write. So another Boost.PP type is chosen, tuple, to define + * each individual parameter. A tuple is defined as + * \code{.sh} + * (a,b,c ...) + * \endcode + * + * This is why the parameter definition requires a double parathesis, as shown + * in the following section. + * + * \section Library Overview + * + * In this macro library, a parameter is defined using a tuple inside a sequence, + * \code{.sh} + * ((, , , , , , )) + * \endcode + * + * - \c type is the type of the parameter. Currently only five types of + * parameters are defined, short, long, double, bool, enum, enum2. + * \enum2 type is the same as \enum with additional information to be able to + * map to a user defined C enum type. To add more types, search this file for + * keyword \a _short, and supply all relavant macros. It's quite trivial + * actually. + * + * - \c arg is the agument name. It is intended to be used as function argument. + * By convention, the name shall be all small cases, but that's not required. + * This \c arg can be repurposed, if the parameter is not going to be used as + * function agument. The #AREA_PARAMS_CAREA parameters repurposed this field + * to CArea internal setting variables to implement save, apply and restore + * function using CAreaConfig class. + * + * - \c name is normally a %CamelCase name which are used as member variable and + * property name. Because of this, make sure the names are unique to avoid + * conflicts. + * + * - \c default is the default value of this parameter. Right now, you must + * supply a default value. Boost.PP has trouble dealing with empty values. + * Remember that a sequence cannot be empty. Neight can tuple. Only array, + * something like (0,()) for an empty array. It is awkward to write, + * and didn't add much functionality I want, hence the restriction of + * non-empty defaults here. + * + * - \c doc is a string to describe the variable. + * + * - \c seq \anchor ParamSeq. Right now this field is used by \c enum and + * \c enum2 type parameter to define its enumerations. As the name suggests, + * it must be a sequence. It is not a tuple because looping through tuple is + * not as easy as sequence. Other type of parameter do not need to have this + * field + * + * - \c info is used to provide the supplimentery information for \c enum2 type + * of parameter, which can be converted to a user defined enum type by + * #PARAM_ENUM_CONVERT. \c info must be a tuple, with the user defined enum + * type as the first element, and a prefix as the second element. For \c enum2 + * type of parameter, this field is mandatory. + * + * The common usage is that you define a macro of a group of parameters. And use + * the macro helper here to do operation on each parameter in the group. See + * AreaParams.h file for an example of parameter definitions. + * + * Area.h, Area.cpp, FeatureArea.h, FeatureArea.cpp for usage of variouse macros. + * + * See struct AreaDoc for an example of doc string generation. + * + * Each field of the parameter can be refered to with various + * \ref ParamAccessor "various accessor macros", and can be easily + * \ref ParamStringizer "stringified". + * + * \anchor ParamField You can also use #PARAM_FIELD(_field,_param) to refer to + * each field, where \a _field is one of TYPE, ARG, NAME, DEF, DOC, or SEQ. + * And #PARAM_FIELD_STR to stringify. + * + * Here \a _param is the parameter definition described above in the form of a + * Boost.PP tuple, and is usally supplied by various \ref ParamLooper "looper macros" + * + * You can of course directly use various Boost.PP sequence looper to pass + * aditional arguments to the operation macro. See #PARAM_PY_DICT_SET_VALUE for + * an example of using tuple, and the more complex example #PARAM_ENUM_CONVERT + * + * Note that when generating comma separated list, the first and last comma are + * conveniently ommited, so that the macros can be mixed with others intuitively + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** \defgroup ParamHelper Parameters helper macros + * Collections of macros for managing groups of parameters */ + +/** + * \defgroup ParamAccessor Field accessors + * To abstract parameter field details + * \ingroup ParamHelper + * @{ + */ +#define PARAM_ITYPE 0 +#define PARAM_IARG 1 +#define PARAM_INAME 2 +#define PARAM_IDEF 3 +#define PARAM_IDOC 4 +#define PARAM_ISEQ 5 +#define PARAM_IINFO 6 + +#define PARAM_FIELD(_idx,_param) BOOST_PP_TUPLE_ELEM(PARAM_I##_idx,_param) + +#define PARAM_FTYPE(_param) PARAM_FIELD(TYPE,_param) +#define PARAM_FARG(_param) PARAM_FIELD(ARG,_param) +#define PARAM_FNAME(_param) PARAM_FIELD(NAME,_param) +#define PARAM_FDEF(_param) PARAM_FIELD(DEF,_param) +#define PARAM_FDOC(_param) PARAM_FIELD(DOC,_param) +#define PARAM_FSEQ(_param) PARAM_FIELD(SEQ,_param) +#define PARAM_FINFO(_param) PARAM_FIELD(INFO,_param) +#define PARAM_FENUM_TYPE(_param) BOOST_PP_TUPLE_ELEM(0,PARAM_FINFO(_param)) +#define PARAM_FENUM_PREFIX(_param) BOOST_PP_TUPLE_ELEM(1,PARAM_FINFO(_param)) +/** @} */ + + +/** + * \defgroup ParamStringizer Field stringizers + * \ingroup ParamHelper + * @{ */ +#define PARAM_FIELD_STR(_idx,_param) \ + BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(PARAM_I##_idx,_param)) + +#define PARAM_FTYPE_STR(_param) PARAM_FIELD_STR(TYPE,_param) +#define PARAM_FARG_STR(_param) PARAM_FIELD_STR(ARG,_param) +#define PARAM_FNAME_STR(_param) PARAM_FIELD_STR(NAME,_param) +#define PARAM_FDEF_STR(_param) PARAM_FIELD_STR(DEF,_param) +/** @} */ + +/** Helper for #PARAM_FSEQ_STR */ +#define PARAM_FSEQ_STR_(_i,_elem) \ + BOOST_PP_COMMA_IF(_i) BOOST_PP_STRINGIZE(_elem) + +/** \c SEQ stringizer will stringify each element separately + * + * Expands to: + * #seq[0], #seq[1] ... + * \ingroup ParamHelper + */ +#define PARAM_FSEQ_STR(_param) \ + PARAM_FOREACH_I(PARAM_FSEQ_STR_,PARAM_FSEQ(_param)) + + +/** \defgroup ParamLooper Looper macros + * Macros for looping through sequence to parameters + * \ingroup ParamHelper + */ + +/** Helper for #PARAM_FOREACH */ +#define PARAM_FOREACH_(_,_op,_param) _op(_param) + +/** Apply macro \a _op to each parameter in sequence \a _seq + * + * Operation macro \a _op shoud be defined as, + * \code + * _op(_param) + * \endcode + * \ingroup ParamLooper + */ +#define PARAM_FOREACH(_op,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_FOREACH_,_op,_seq) + +/** Helper for #PARAM_FOREACH_I */ +#define PARAM_FOREACH_I_(_,_op,_i,_param) _op(_i,_param) + +/** Apply macro \a _op to each parameter in sequence \a _seq with additional index + * + * Operation macro \a _op shoud be defined as, + * \code + * _op(_i,_param) + * \endcode + * \ingroup ParamLooper + * */ +#define PARAM_FOREACH_I(_op,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FOREACH_I_,_op,_seq) + + +/** Helper for #PARAM_TYPED_FOREACH */ +#define PARAM_TYPED_FOREACH_(_1,_op,_param) \ + PARAM_TYPED(_op,_param)(_param) + +/** Type depended macro construction + * + * Convert macro \a _op to \a _op##\. Note that it only converts the + * macro name, not contsucts a macro call. To expand to a macro call, simply + * \code + * PARAM_TYPED(_op,_param)(_param) + * \endcode + * \ingroup ParamLooper + */ +#define PARAM_TYPED(_op,_param) \ + BOOST_PP_CAT(_op,PARAM_FTYPE(_param)) + +/** Apply type dependent macro call to a sequence of parameters + * + * \a _op will be converted to \a _op##\ for each parameter + * \ingroup ParamLooper + */ +#define PARAM_TYPED_FOREACH(_op,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_TYPED_FOREACH_,_op,_seq) + + +/** \defgroup ParamCommon Common helpers + * \ingroup ParamHelper + */ + +#define PARAM_TYPE_short short +#define PARAM_TYPE_long long +#define PARAM_TYPE_double double +#define PARAM_TYPE_bool bool +#define PARAM_TYPE_enum short +#define PARAM_TYPE_enum2 short + +/** Obtain parameter type + * + * The main purpose is to alias enum type to short + * \ingroup ParamCommon + */ +#define PARAM_TYPE(_param) \ + PARAM_TYPED(PARAM_TYPE_,_param) + + +/** Helper for #PARAM_DECLARE */ +#define PARAM_DECLARE_(_1,_field,_param) \ + PARAM_TYPE(_param) PARAM_FIELD(_field,_param); + +/** + * Delcares parameters using the given field as name + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expands to: + * \code{.unparsed} + * type1 _field1;type2 _field2; ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_DECLARE(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_,_field,_seq) + + +/** Helper for #PARAM_DECLARE_INIT */ +#define PARAM_DECLARE_INIT_(_1,_field,_param) \ + PARAM_TYPE(_param) PARAM_FIELD(_field,_param) = PARAM_FDEF(_param); + +/** + * Delcares parameters with initialization to default using the given field as + * name + * + * \arg \c _field: \ref ParamField "field" to use as name + * + * Expands to: + * \code{.unparsed} + * type1 _field1=_def1;type2 _field2=_def2; ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_DECLARE_INIT(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_INIT_,_field,_seq) + + +#define PARAM_ENUM_DECLARE_enum_(_1,_name,_i,_elem) \ + BOOST_PP_COMMA_IF(_i) BOOST_PP_CAT(_name,_elem) + +#define PARAM_ENUM_DECLARE_enum(_param) \ + enum {BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_DECLARE_enum_,PARAM_FNAME(_param),PARAM_FSEQ(_param))}; + +#define PARAM_ENUM_DECLARE_short(_param) +#define PARAM_ENUM_DECLARE_long(_param) +#define PARAM_ENUM_DECLARE_double(_param) +#define PARAM_ENUM_DECLARE_bool(_param) +#define PARAM_ENUM_DECLARE_enum2 PARAM_ENUM_DECLARE_enum + +/** \defgroup ParamEnumHelper Enum convert helpers + * \ingroup ParamCommon + */ + +/** Make anonymous \c enum type + * + * Make anonymous \c enum type for \c enum type parameters in \a _seq. All other + * types are ignored. The enum member is prefixed with \a _name. Expand to: + * \code{.unparsed} + * enum {_name1##_seq1[0], _name1##_seq1[1] ...}; + * enum {_name2##_seq2[0], _name2##_seq2[1] ...}; + * ... + * \endcode + * \ingroup ParamEnumHelper*/ +#define PARAM_ENUM_DECLARE(_seq) \ + PARAM_TYPED_FOREACH(PARAM_ENUM_DECLARE_,_seq) + + +/** \addgroup ParamEnumHelper Enum convert helpers + * @{ */ +#define PARAM_ENUM_CONVERT_short(...) +#define PARAM_ENUM_CONVERT_long(...) +#define PARAM_ENUM_CONVERT_double(...) +#define PARAM_ENUM_CONVERT_bool(...) +#define PARAM_ENUM_CONVERT_enum(...) +#define PARAM_ENUM_CONVERT_enum2 PARAM_ENUM_CONVERT_SINGLE + +#define PARAM_ENUM_CONVERT_enum_(_dst,_name,_prefix,_elem) \ + case BOOST_PP_CAT(_name,_elem):\ + _dst(_name) = \ + BOOST_PP_CAT(_prefix,_elem);\ + break; + +#define PARAM_ENUM_CONVERT__(_1,_args,_i,_elem) \ + PARAM_ENUM_CONVERT_enum_(BOOST_PP_TUPLE_ELEM(0,_args),\ + BOOST_PP_TUPLE_ELEM(1,_args),\ + BOOST_PP_TUPLE_ELEM(2,_args),\ + _elem); + +#define PARAM_ENUM_CONVERT_(_1,_args,_param) \ + PARAM_TYPED(PARAM_ENUM_CONVERT_,_param)(BOOST_PP_TUPLE_ELEM(0,_args),\ + BOOST_PP_TUPLE_ELEM(1,_args),\ + BOOST_PP_TUPLE_ELEM(2,_args),\ + _param) + +/** Convert single enum parameter value into user defined enum type + * + * This macro is used by #PARAM_ENUM_CONVERT to convert each parameter, but + * you can use it directly for a single parameter. Check #PARAM_NUM_CONVERT + * for more detail. Make sure the outer parathesis of \c _param is stripped, + * i.e. not double but single parathesis + */ +#define PARAM_ENUM_CONVERT_SINGLE(_src,_dst,_default,_param) \ + PARAM_FENUM_TYPE(_param) _dst(PARAM_FNAME(_param));\ + switch(_src(PARAM_FNAME(_param))) {\ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CONVERT__,\ + (_dst,PARAM_FNAME(_param),PARAM_FENUM_PREFIX(_param)),PARAM_FSEQ(_param))\ + default: \ + _default(_param);\ + } + +/** Default handling in #PARAM_ENUM_CONVERT and #PARAM_ENUM_CHECK*/ +#define PARAM_ENUM_EXCEPT(_param) \ + throw Base::ValueError("invalid value for enum " PARAM_FNAME_STR(_param)) + +/** @} */ + +/* Convert ParamHelper defined enum type to user defined ones + * + * This assumes the user defined enum type is given in \ref ParamSeq "seq_type" + * of the parameter definition, and it has the same postfix as the ones + * speficied in \ref ParamSeq "seq" member of the parameter definition. See + * \ref ParamEnumHelper "here" for implementations + * + * \ingroup ParamEnumHelper + * + * \arg \c _src: Optional macro to generate source variable. The signature must + * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. + * In case you just want \c _name as the source variable name, you can simply + * omit this argument, because newer C++ preprocessor allows empty argument. + * \arg \c _dst: Optional macro to generate destination variable. Same as above. + * \arg \c _default: A macro to call for invalid value. Signature should be + * _default(_param)<\tt>, where \c _param is the parameter definition. You + * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD + * \arg \c _seq: Parameter sequence + * + * For example, with the following parameter definition + * \code{.unparsed} + * #define MY_PARAM_TEST \ + * ((enum,test1,Test1,0,"it's a test",(Foo)(Bar),(MyEnum1,myEnum1)) \ + * ((enum,test2,Test2,0,"it's a test",(Foo)(Bar),(MyEnum2,myEnum2))) + * + * #define MY_DST(_v) BOOST_PP_CAT(my,_v) + * \code{.unparsed} + * + * calling (note that the \c _src macro is omitted) + * \code{.unparsed} + * PARAM_ENUM_CONVERT(,MY_DST,My,PARAM_ENUM_EXCEP,MY_PARAM_TEST) + * \code{.unparsed} + * + * expands to + * \code{.unparsed} + * MyEnum1 myTest1; + * switch(Test1) { + * case Test1Foo: + * myTest1 = myEnum1Foo; + * break; + * case Test1Bar: + * myTest1 = myEnum1Bar; + * break; + * default: + * throw Base::ValueError("invalid value for enum Test1"); + * } + * MyEnum2 myTest2; + * switch(Test2) { + * case Test1Foo: + * myTest2 = myEnum2Foo; + * break; + * case Test2Bar: + * myTest2 = myEnum2Bar; + * break; + * default: + * throw Base::ValueError("invalid value for enum Test2"); + * } + * \endcode + * + * The above code assumes you've already defined \a Test1 and \a Test2 some + * where as the source variable. + */ +#define PARAM_ENUM_CONVERT(_src,_dst,_default,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_CONVERT_,(_src,_dst,_default),_seq) + + +#define PARAM_ENUM_CHECK_short(...) +#define PARAM_ENUM_CHECK_long(...) +#define PARAM_ENUM_CHECK_double(...) +#define PARAM_ENUM_CHECK_bool(...) +#define PARAM_ENUM_CHECK_enum PARAM_ENUM_CHECK_SINGLE +#define PARAM_ENUM_CHECK_enum2 PARAM_ENUM_CHECK_SINGLE + +#define PARAM_ENUM_CHECK_enum_(_1,_name,_i,_elem) \ + case BOOST_PP_CAT(_name,_elem): break; + +#define PARAM_ENUM_CHECK_(_1,_args,_param) \ + PARAM_TYPED(PARAM_ENUM_CHECK_,_param)(BOOST_PP_TUPLE_ELEM(0,_args),\ + BOOST_PP_TUPLE_ELEM(1,_args),\ + _param) + +#define PARAM_ENUM_CHECK_SINGLE(_src,_default,_param) \ + switch(_src(PARAM_FNAME(_param))) {\ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CHECK_enum_,\ + PARAM_FNAME(_param),PARAM_FSEQ(_param))\ + default: \ + _default(_param);\ + } + +/* Validate enum type parameters + * + * This macro validates the value a variable of enum type parameters. See + * similar macro #PARAM_ENUM_CONVERT for detail usage. + * + * \ingroup ParamEnumHelper + * + * \arg \c _src: Optional macro to generate source variable. The signature must + * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. + * In case you just want \c _name as the source variable name, you can simply + * omit this argument, because newer C++ preprocessor allows empty argument. + * + * \arg \c _default: A macro to call for invalid value. Signature should be + * _default(_param)<\tt>, where \c _param is the parameter definition. You + * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD + * + * \arg \c _seq: Parameter sequence + */ +#define PARAM_ENUM_CHECK(_src,_default,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_CHECK_,(_src,_default),_seq) + + +#define PARAM_ENUM_STRING_DECLARE_short(...) +#define PARAM_ENUM_STRING_DECLARE_long(...) +#define PARAM_ENUM_STRING_DECLARE_double(...) +#define PARAM_ENUM_STRING_DECLARE_bool(...) +#define PARAM_ENUM_STRING_DECLARE_enum2 PARAM_ENUM_STRING_DECLARE_enum + +/** Helper for #PARAM_ENUM_STRING_DECLARE */ +#define PARAM_ENUM_STRING_DECLARE_enum(_prefix,_param) \ + BOOST_PP_CAT(_prefix,PARAM_FNAME(_param))[] = {PARAM_FSEQ_STR(_param),NULL}; + +/** Helper for #PARAM_ENUM_STRING_DECLARE */ +#define PARAM_ENUM_STRING_DECLARE_(_1,_prefix,_param) \ + PARAM_TYPED(PARAM_ENUM_STRING_DECLARE_,_param)(_prefix,_param) + +/** Make \c enum string list + * + * Roughly translated: + * \code{.unparsed} + * _prefix##_name1[] = {#seq1[0], #seq1[1], ...,NULL}; + * _prefix##_name2[] = {#seq2[0], #seq2[1], ...,NULL}; + * ... + * \endcode + * Example usage: + * PARAM_ENUM_STRING_DECLARE(static const char *Enum, MyParamsSeq) + * \ingroup ParamEnumHelper + */ +#define PARAM_ENUM_STRING_DECLARE(_prefix,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_ENUM_STRING_DECLARE_,_prefix,_seq) + + +/** Helper for #PARAM_INIT */ +#define PARAM_INIT_(_,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FIELD(_field,_param)(PARAM_FDEF(_param)) + +/** Constructor initialization + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to, + * \code{.unparsed} + * field1(def1), field2(def2)... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_INIT(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_INIT_,_field,_seq) + + +/** Helper for #PARAM_ARGS_DEF */ +#define PARAM_ARGS_DEF_(_,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) PARAM_FIELD(_field,_param)=PARAM_FDEF(_param) + +/** Delcare the parameters as function argument list with defaults. + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to: + * \code{.unparsed} + * type1 field1=def1, type2 field2=def2 ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_ARGS_DEF(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_DEF_,_field,_seq) + + +/** Helper for #PARAM_ARGS */ +#define PARAM_ARGS_(_,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) PARAM_FIELD(_field,_param) + +/** Delcare the parameters as function argument list without defaults. + * + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to: + * \code{.unparsed} + * type1 field1, type2 field2 ... + * \endcode + * \ingroup ParamCommon + */ +#define PARAM_ARGS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_,_field,_seq) + + +/** \defgroup ParamPy Python helper + * Helper macros for Python bindings + * \ingroup ParamHelper + */ + +/** \defgroup ParamDoc Python doc helper + * Generate argument doc string for Python + * \ingroup ParamPy + */ + +/** Helper for #PARAM_PY_DOC_enum */ +#define PARAM_PY_DOC_enum_(_i,_elem) \ + BOOST_PP_IF(_i,","," ") #_i "=" #_elem + +/** Generate doc for an enum parameter */ +#define PARAM_PY_DOC_enum(_field,_param) \ + "\n* " PARAM_FIELD_STR(_field,_param) "(" PARAM_FDEF_STR(_param)"):" \ + PARAM_FOREACH_I(PARAM_PY_DOC_enum_, PARAM_FSEQ(_param)) ". " \ + PARAM_FDOC(_param) "\n" + +/* Generate doc for other type of parameter */ +#define PARAM_PY_DOC_short(_field,_param) \ + "\n* " PARAM_FIELD_STR(_field,_param) "(" PARAM_FDEF_STR(_param)"): " \ + PARAM_FDOC(_param) "\n" +#define PARAM_PY_DOC_long PARAM_PY_DOC_short +#define PARAM_PY_DOC_double PARAM_PY_DOC_short +#define PARAM_PY_DOC_bool PARAM_PY_DOC_short +#define PARAM_PY_DOC_enum2 PARAM_PY_DOC_enum + +#define PARAM_PY_DOC_(_,_field,_param) \ + PARAM_TYPED(PARAM_PY_DOC_,_param)(_field,_param) + +/* Generate document of a sequence of parameters + * \ingroup ParamDoc + */ +#define PARAM_PY_DOC(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DOC_,_field,_seq) + + +/** Helper for #PARAM_PY_ARGS_DOC */ +#define PARAM_PY_ARGS_DOC_(_,_field,_i,_param) \ + BOOST_PP_IF(_i,", "," ") PARAM_FIELD_STR(_field,_param) "=" PARAM_FDEF_STR(_param) + +/** Generate argument list string + * \arg \c _field: specifies the \ref ParamField "field" to use as name + * + * Expand to a single string: + * \code{.unparsed} + * "_field1=_def1,_field2=_def2 ..." + * \endcode + * + * \ingroup ParamDoc + */ +#define PARAM_PY_ARGS_DOC(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_ARGS_DOC_,_field,_seq) + + +/** Helper for #PARAM_FIELDS */ +#define PARAM_FIELDS_(_1,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FIELD(_field,_param) + +/** Expand to a list of the given field in the parameter sequence + * + * For example, PARAM_FIELDS(ARG, _seq) expands to: + * \code{.unparsed} + * arg1,arg2 ... + * \endcode + * \ingroup ParamCommon ParamPy + */ +#define PARAM_FIELDS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELDS_,_field,_seq) + + +#define PARAM_PY_CAST_short(_v) (_v) +#define PARAM_PY_CAST_long(_v) (_v) +#define PARAM_PY_CAST_double(_v) (_v) +#define PARAM_PY_CAST_bool(_v) ((_v)?Py_True:Py_False) +#define PARAM_PY_CAST_enum(_v) (_v) +#define PARAM_PY_CAST_enum2(_v) (_v) + +#define PARAM_CAST_PY_short(_v) (_v) +#define PARAM_CAST_PY_long(_v) (_v) +#define PARAM_CAST_PY_double(_v) (_v) +#define PARAM_CAST_PY_bool(_v) (PyObject_IsTrue(_v)?true:false) +#define PARAM_CAST_PY_enum(_v) (_v) +#define PARAM_CAST_PY_enum2(_v) (_v) + + +/** Helper for #PARAM_PY_FIELDS */ +#define PARAM_PY_FIELDS_(_1,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FIELD(_field,_param)) + +/** Expand to a list of the given field in the sequence + * + * The field will be casted from python C to C type + * \ingroup ParamCommon ParamPy + */ +#define PARAM_PY_FIELDS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_FIELDS_,_field,_seq) + + +/** Helper for #PARAM_FIELD_STRINGS */ +#define PARAM_FIELD_STRINGS_(_1,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FIELD_STR(_field,_param) + +/** Expand to a list of stringified fields + * \ingroup ParamCommon ParamPy + */ +#define PARAM_FIELD_STRINGS(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELD_STRINGS_,_field,_seq) + + +#define PARAM_PYARG_short "h" +#define PARAM_PYARG_long "l" +#define PARAM_PYARG_double "d" +#define PARAM_PYARG_bool "O" +#define PARAM_PYARG_enum "h" +#define PARAM_PYARG_enum2 "h" + +/** Helper for #PARAM_PY_KWDS */ +#define PARAM_PY_KWDS_(_param) \ + PARAM_TYPED(PARAM_PYARG_,_param) + +/** Generate a format string for kewords based argument + * \ingroup ParamPy + */ +#define PARAM_PY_KWDS(_seq) \ + PARAM_FOREACH(PARAM_PY_KWDS_,_seq) + +#define PARAM_PY_TYPE_short short +#define PARAM_PY_TYPE_long long +#define PARAM_PY_TYPE_double double +#define PARAM_PY_TYPE_bool PyObject* +#define PARAM_PY_TYPE_enum short +#define PARAM_PY_TYPE_enum2 short + +/** Helper for #PARAM_PY_DECLARE */ +#define PARAM_PY_DECLARE_(_1,_field,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) PARAM_FIELD(_field,_param); + +/** Declare field variables for Python C type without initialization + * \ingroup ParamPy + */ +#define PARAM_PY_DECLARE(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_,_field,_seq) + +#define PARAM_PY_INIT_short(_v) _v +#define PARAM_PY_INIT_long(_v) _v +#define PARAM_PY_INIT_double(_v) _v +#define PARAM_PY_INIT_bool(_v) ((_v)?Py_True:Py_False) +#define PARAM_PY_INIT_enum(_v) _v +#define PARAM_PY_INIT_enum2(_v) _v + +/** Helper for #PARAM_PY_DECLARE_INIT */ +#define PARAM_PY_DECLARE_INIT_(_1,_field,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) PARAM_FIELD(_field,_param) = \ + PARAM_TYPED(PARAM_PY_INIT_,_param)(PARAM_FDEF(_param)); + +/** Declare field variables of Python c type with initialization to default + * \ingroup ParamPy + */ +#define PARAM_PY_DECLARE_INIT(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_INIT_,_field,_seq) + + +/** Helper for #PARAM_REF */ +#define PARAM_REF_(_1,_field,_i,_param) \ + BOOST_PP_COMMA_IF(_i) &PARAM_FIELD(_field,_param) + +/** Generate a list of field references + * + * Expand to: + * \code{.unparsed} + * &_field1, &_field2 ... + * \endcode + * \ingroup ParamPy + */ +#define PARAM_REF(_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_REF_,_field,_seq) + + +#define PARAM_CAST_PYOBJ_short(_v) PyInt_FromLong(_v) +#define PARAM_CAST_PYOBJ_long(_v) PyInt_FromLong(_v) +#define PARAM_CAST_PYOBJ_double(_v) PyFloat_FromDouble(_v) +#define PARAM_CAST_PYOBJ_bool(_v) ((_v)?Py_True:Py_False) +#define PARAM_CAST_PYOBJ_enum PARAM_CAST_PYOBJ_short +#define PARAM_CAST_PYOBJ_enum2 PARAM_CAST_PYOBJ_short + + +/** Stringize field to a Python string */ +#define PARAM_PY_STRINGIZE(_field,_param) \ + PyString_FromString(PARAM_FIELD_STR(_field,_param)) + +/** Helper for #PARAM_PY_DICT_SET_VALUE */ +#define PARAM_PY_DICT_SET_VALUE_(_1,_args,_param) \ + PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \ + PARAM_PY_STRINGIZE(NAME,_param),\ + PARAM_TYPED(PARAM_CAST_PYOBJ_,_param)(\ + BOOST_PP_TUPLE_ELEM(1,_args)(PARAM_FIELD(NAME,_param)))); + +/** Populate a Python dict with a structure variable + * + * \arg \c _dict: the Python dictionary object + * \arg \c _src: Optional macro to generate source variable. The signature must + * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. + * In case you just want \c _name as the source variable name, you can simply + * omit this argument, because newer C++ preprocessor allows empty argument. + * + * Roughly translated to: + * \code{.unparsed} + * PyDict_SetItem(_dict,#name1,_src(name1)); + * PyDict_SetItem(_dict,#name2,_src(name2)); + * ... + * \endcode + * \ingroup ParamPy + */ +#define PARAM_PY_DICT_SET_VALUE(_dict,_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_VALUE_,(_dict,_src),_seq) + + +#define PARAM_PY_DICT_DOC_enum_(_i,_elem) \ + BOOST_PP_IF(_i,","," ") #_i "=" #_elem + +/** Generate doc for an enum parameter */ +#define PARAM_PY_DICT_DOC_enum(_param) \ + "(" PARAM_FDEF_STR(_param) ") - " \ + PARAM_FOREACH_I(PARAM_PY_DOC_enum_, PARAM_FSEQ(_param)) ".\n" \ + PARAM_FDOC(_param) "\n" + +/* Generate doc for other type of parameter */ +#define PARAM_PY_DICT_DOC_(_param) \ + "(" PARAM_FDEF_STR(_param) ") - " PARAM_FDOC(_param) "\n" + + +#define PARAM_PY_DICT_DOC_short PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_long PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_double PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_bool PARAM_PY_DICT_DOC_ +#define PARAM_PY_DICT_DOC_enum2 PARAM_PY_DICT_DOC_enum + +/** Helper for #PARAM_PY_DICT_SET_DOC */ +#define PARAM_PY_DICT_SET_DOC_(_1,_dict,_param) \ + PyDict_SetItem(_dict, PARAM_PY_STRINGIZE(NAME,_param),\ + PyString_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param))); + +/** Populate a Python dict with the doc field of the parameter sequence + * + * Roughly translated to: + * \code{.unparsed} + * PyDict_SetItem(_dict,#name1,doc1); + * PyDict_SetItem(_dict,#name2,doc2); + * ... + * \endcode + * \ingroup ParamDoc + */ +#define PARAM_PY_DICT_SET_DOC(_dict,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_DOC_,_dict,_seq) + + +/** \defgroup ParamProperty Property Macros + * Helper macros for FreeCAD properties + * \ingroup ParamHelper + * @{*/ +#define PARAM_PROP_bool(_v) App::PropertyBool _v +#define PARAM_PROP_double(_v) App::PropertyFloat _v +#define PARAM_PROP_short(_v) App::PropertyInteger _v +#define PARAM_PROP_long(_v) App::PropertyInteger _v +#define PARAM_PROP_enum(_v) App::PropertyEnumeration _v +#define PARAM_PROP_enum2(_v) App::PropertyEnumeration _v +/** @} */ + +/** Helper for #PARAM_PROP_DECLARE */ +#define PARAM_PROP_DECLARE_(_param) \ + PARAM_TYPED(PARAM_PROP_,_param)(PARAM_FNAME(_param)); + +/** Declare FreeCAD properties + * \ingroup ParamProperty + */ +#define PARAM_PROP_DECLARE(_seq) \ + PARAM_FOREACH(PARAM_PROP_DECLARE_,_seq) + +/** Replace FreeCAD #ADD_PROPERTY_TYPE to fix singifying macro */ +#define PARAM_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \ + do { \ + this->_prop_.setValue _defaultval_;\ + this->_prop_.setContainer(this); \ + propertyData.addProperty(static_cast(this), BOOST_PP_STRINGIZE(_prop_), &this->_prop_, (_group_),(_type_),(_Docu_)); \ + } while (0) + +/** Generic property adding */ +#define PARAM_PROP_ADD_(_group,_param) \ + PARAM_ADD_PROPERTY_TYPE(PARAM_FNAME(_param), (PARAM_FDEF(_param)),\ + _group,App::Prop_None,PARAM_FDOC(_param)); + +#define PARAM_PROP_ADD_short PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_long PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_double PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_bool PARAM_PROP_ADD_ +#define PARAM_PROP_ADD_enum2 PARAM_PROP_ADD_enum + +/** Add \c enum type parameter as property */ +#define PARAM_PROP_ADD_enum(_group,_param) \ + PARAM_ADD_PROPERTY_TYPE(PARAM_FNAME(_param), ((long)PARAM_FDEF(_param)),\ + _group,App::Prop_None,PARAM_FDOC(_param)); + +/** Helper for #PARAM_PROP_ADD */ +#define PARAM_PROP_ADD_TYPED(_1,_group,_i,_param) \ + PARAM_TYPED(PARAM_PROP_ADD_,_param)(_group,_param) + +/** Add FreeCAD properties + * \ingroup ParamProperty + */ +#define PARAM_PROP_ADD(_group,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PROP_ADD_TYPED,_group,_seq) + +#define PARAM_PROP_SET_ENUM_short(...) +#define PARAM_PROP_SET_ENUM_long(...) +#define PARAM_PROP_SET_ENUM_bool(...) +#define PARAM_PROP_SET_ENUM_double(...) +#define PARAM_PROP_SET_ENUM_enum2 PARAM_PROP_SET_ENUM_enum + +/** Setup \c enum type parameter */ +#define PARAM_PROP_SET_ENUM_enum(_prefix,_param) \ + PARAM_FNAME(_param).setEnums(BOOST_PP_CAT(_prefix,PARAM_FNAME(_param))); + +/** Helper for #PARAM_PROP_SET_ENUM */ +#define PARAM_PROP_SET_ENUM_TYPED(_1,_prefix,_param) \ + PARAM_TYPED(PARAM_PROP_SET_ENUM_,_param)(_prefix,_param) + +/* Setup the \c enum string list for \c enum type properties + * \ingroup ParamProperty + */ +#define PARAM_PROP_SET_ENUM(_prefix,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PROP_SET_ENUM_TYPED,_prefix,_seq) + + +/** Helper for #PARAM_PROP_ARGS */ +#define PARAM_PROP_ARGS_(_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_FNAME(_param).getValue() + +/** Expand the property list as function arguments + * + * Expand to: + * \code{.unparsed} + * name1.getValue(), name2.getValue() ... + * \endcode + * \ingroup ParamProperty + */ +#define PARAM_PROP_ARGS(_seq) \ + PARAM_FOREACH_I(PARAM_PROP_ARGS_,_seq) + + +/** Helper for #PARAM_PROP_TOUCHED */ +#define PARAM_PROP_TOUCHED_(_param) \ + if(PARAM_FNAME(_param).isTouched()) return 1; + +/** Returns 1 if any properties is touched + * + * Expand to: + * \code{.unparsed} + * if(name1.isTouched()) return 1; + * if(name2.isTouched()) return 1; + * ... + * \ingroup ParamProperty + */ +#define PARAM_PROP_TOUCHED(_seq) \ + PARAM_FOREACH(PARAM_PROP_TOUCHED_,_seq) + +#endif // PARAMS_HELPER_H diff --git a/src/Mod/Path/Gui/AppPathGui.cpp b/src/Mod/Path/Gui/AppPathGui.cpp index 3d05456343..1e4d93faf4 100644 --- a/src/Mod/Path/Gui/AppPathGui.cpp +++ b/src/Mod/Path/Gui/AppPathGui.cpp @@ -35,6 +35,7 @@ #include "DlgSettingsPathColor.h" #include "ViewProviderPathCompound.h" #include "ViewProviderPathShape.h" +#include "ViewProviderArea.h" // use a different name to CreateCommand() void CreatePathCommands(void); @@ -77,6 +78,8 @@ PyMODINIT_FUNC initPathGui() PathGui::ViewProviderPathCompoundPython ::init(); PathGui::ViewProviderPathShape ::init(); PathGui::ViewProviderPathPython ::init(); + PathGui::ViewProviderArea ::init(); + PathGui::ViewProviderAreaPython ::init(); // add resources and reloads the translators loadPathResource(); diff --git a/src/Mod/Path/Gui/CMakeLists.txt b/src/Mod/Path/Gui/CMakeLists.txt index 8f90c3e413..2be21706d1 100644 --- a/src/Mod/Path/Gui/CMakeLists.txt +++ b/src/Mod/Path/Gui/CMakeLists.txt @@ -81,6 +81,8 @@ SET(PathGui_SRCS_ViewProvider ViewProviderPathCompound.h ViewProviderPathShape.cpp ViewProviderPathShape.h + ViewProviderArea.cpp + ViewProviderArea.h ) SOURCE_GROUP("ViewProvider" FILES ${PathGui_SRCS_ViewProvider}) diff --git a/src/Mod/Path/Gui/Command.cpp b/src/Mod/Path/Gui/Command.cpp index 9dc8085f9b..ab0a8296dc 100644 --- a/src/Mod/Path/Gui/Command.cpp +++ b/src/Mod/Path/Gui/Command.cpp @@ -25,6 +25,8 @@ #ifndef _PreComp_ #endif +#include + #include #include #include @@ -39,8 +41,86 @@ #include #include #include +#include +// Path Area ##################################################################################################### + + +DEF_STD_CMD_A(CmdPathArea) + +CmdPathArea::CmdPathArea() + :Command("Path_Area") +{ + sAppModule = "Path"; + sGroup = QT_TR_NOOP("Path"); + sMenuText = QT_TR_NOOP("Area"); + sToolTipText = QT_TR_NOOP("Creates a feature area from selected objects"); + sWhatsThis = "Path_Area"; + sStatusTip = sToolTipText; + sPixmap = "Path-Area"; + sAccel = "P,A"; + +} + +void CmdPathArea::activated(int iMsg) +{ + Q_UNUSED(iMsg); + std::vector Sel = + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId()); + std::list cmds; + std::ostringstream sources; + if (Sel.size() > 0) { + for(const Gui::SelectionObject &selObj : Sel) { + const Part::Feature *pcObj = static_cast(selObj.getObject()); + if(selObj.getSubNames().empty()) { + const TopoDS_Shape &shape = pcObj->Shape.getShape().getShape(); + TopExp_Explorer it(shape, TopAbs_SHELL); + if(it.More()) { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; + continue; + } + + for(const std::string &name : selObj.getSubNames()) { + if(!name.compare(0,4,"Face") && + !name.compare(0,4,"Edge")) + { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + + int index = atoi(name.substr(4).c_str()); + + std::ostringstream subname; + subname << pcObj->getNameInDocument() << '_' << name; + std::string sub_fname = getUniqueObjectName(subname.str().c_str()); + + std::ostringstream cmd; + cmd << "FreeCAD.activeDocument().addObject('Path::Feature','" << sub_fname << "').Shape = " << + pcObj->getNameInDocument() << '.' << name.substr(0,4) << '[' << index-1 << ']'; + cmds.push_back(cmd.str()); + sources << "FreeCAD.activeDocument()." << sub_fname << ","; + } + } + } + std::string FeatName = getUniqueObjectName("FeatureArea"); + openCommand("Create Path Area"); + for(const std::string &cmd : cmds) + doCommand(Doc,cmd.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureArea','%s')",FeatName.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().%s.Sources = [ %s ]",FeatName.c_str(),sources.str().c_str()); + commitCommand(); + updateActive(); +} + +bool CmdPathArea::isActive(void) +{ + return hasActiveDocument(); +} + // Path compound ##################################################################################################### @@ -95,7 +175,6 @@ bool CmdPathCompound::isActive(void) return hasActiveDocument(); } - // Path Shape ##################################################################################################### @@ -149,4 +228,5 @@ void CreatePathCommands(void) Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdPathCompound()); rcCmdMgr.addCommand(new CmdPathShape()); + rcCmdMgr.addCommand(new CmdPathArea()); } diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index b6800333c6..23035a803b 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -46,6 +46,7 @@ icons/Path-ToolChange.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg + icons/Path-Area.svg icons/preferences-path.svg panels/ContourEdit.ui panels/DlgJobChooser.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Area.svg b/src/Mod/Path/Gui/Resources/icons/Path-Area.svg new file mode 100644 index 0000000000..a620c23b26 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Area.svg @@ -0,0 +1,648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-FaceProfile + 2016-01-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path- + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/ViewProviderArea.cpp b/src/Mod/Path/Gui/ViewProviderArea.cpp new file mode 100644 index 0000000000..2a3057f203 --- /dev/null +++ b/src/Mod/Path/Gui/ViewProviderArea.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#endif + +#include +#include +#include "ViewProviderArea.h" + +using namespace PathGui; + +PROPERTY_SOURCE(PathGui::ViewProviderArea, PartGui::ViewProviderPlaneParametric) + +ViewProviderArea::ViewProviderArea() +{ + sPixmap = "Path-Area.svg"; +} + +ViewProviderArea::~ViewProviderArea() +{ +} + +std::vector ViewProviderArea::claimChildren(void) const +{ + return std::vector( + static_cast(getObject())->Sources.getValues()); +} + +bool ViewProviderArea::canDragObjects() const +{ + return true; +} + +bool ViewProviderArea::canDragObject(App::DocumentObject* obj) const +{ + return obj && obj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()); +} + +void ViewProviderArea::dragObject(App::DocumentObject* obj) +{ + Path::FeatureArea* area = static_cast(getObject()); + std::vector sources = area->Sources.getValues(); + for (std::vector::iterator it = sources.begin(); it != sources.end(); ++it) { + if (*it == obj) { + sources.erase(it); + area->Sources.setValues(sources); + break; + } + } +} + +bool ViewProviderArea::canDropObjects() const +{ + return true; +} + +bool ViewProviderArea::canDropObject(App::DocumentObject* obj) const +{ + return canDragObject(obj); +} + +void ViewProviderArea::dropObject(App::DocumentObject* obj) +{ + Path::FeatureArea* area = static_cast(getObject()); + std::vector sources = area->Sources.getValues(); + sources.push_back(obj); + area->Sources.setValues(sources); +} + +void ViewProviderArea::updateData(const App::Property* prop) +{ + PartGui::ViewProviderPart::updateData(prop); + if (prop->getTypeId() == App::PropertyLinkList::getClassTypeId()) { + std::vector pShapes = static_cast(prop)->getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->hideViewProvider(*it); + } + } +} + +bool ViewProviderArea::onDelete(const std::vector &) +{ + // get the input shapes + Path::FeatureArea* area = static_cast(getObject()); + std::vector pShapes =area->Sources.getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->showViewProvider(*it); + } + return true; +} + +// Python object ----------------------------------------------------------------------- + +namespace Gui { +/// @cond DOXERR +PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaPython, PathGui::ViewProviderArea) +/// @endcond + +// explicit template instantiation +template class PathGuiExport ViewProviderPythonFeatureT; +} diff --git a/src/Mod/Path/Gui/ViewProviderArea.h b/src/Mod/Path/Gui/ViewProviderArea.h new file mode 100644 index 0000000000..7d3b43313c --- /dev/null +++ b/src/Mod/Path/Gui/ViewProviderArea.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + + +#ifndef PATH_ViewProviderArea_H +#define PATH_ViewProviderArea_H + +#include +#include + +namespace PathGui +{ + +class PathGuiExport ViewProviderArea : public PartGui::ViewProviderPlaneParametric +{ + PROPERTY_HEADER(PathGui::ViewProviderArea); + +public: + ViewProviderArea(); + virtual ~ViewProviderArea(); + + /// grouping handling + virtual std::vector claimChildren(void) const; + virtual void updateData(const App::Property*); + virtual bool onDelete(const std::vector &); + + /// drag and drop + virtual bool canDragObjects() const; + virtual bool canDragObject(App::DocumentObject*) const; + virtual void dragObject(App::DocumentObject*); + virtual bool canDropObjects() const; + virtual bool canDropObject(App::DocumentObject*) const; + virtual void dropObject(App::DocumentObject*); +}; + +typedef Gui::ViewProviderPythonFeatureT ViewProviderAreaPython; + +} //namespace PathGui + + +#endif // PATH_ViewProviderArea_H diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index dd0c1d6f3c..61c10a7ae6 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -86,7 +86,7 @@ class PathWorkbench (Workbench): threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"] - extracmdlist = ["Path_SelectLoop"] + extracmdlist = ["Path_SelectLoop", "Path_Area"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] From c11ce6ec79612b206904f67adf1660b7077b78fa Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 20 Jan 2017 02:36:59 +0800 Subject: [PATCH 07/35] Path.Area added coplanar check parameter --- src/Mod/Path/App/Area.cpp | 149 ++++++++++++++++++++++++---------- src/Mod/Path/App/Area.h | 17 +++- src/Mod/Path/App/AreaParams.h | 3 + 3 files changed, 125 insertions(+), 44 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 45564e9608..36a31371f7 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -111,22 +111,47 @@ Area::~Area() { } void Area::setPlane(const TopoDS_Shape &shape) { + if(shape.IsNull()) { + myWorkPlane.Nullify(); + return; + } + BRepLib_FindSurface planeFinder(shape,-1,Standard_True); + if (!planeFinder.Found()) + throw Base::ValueError("shape is not coplanar"); myWorkPlane = shape; + myTrsf.SetTransformation(GeomAdaptor_Surface( + planeFinder.Surface()).Plane().Position()); + clean(); } -void Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, - double deflection, CArea *areaOpen, bool to_edges, bool reorder) +bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + builder.Add(comp,s1); + builder.Add(comp,s2); + BRepLib_FindSurface planeFinder(comp,-1,Standard_True); + return planeFinder.Found(); +} + +int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, + double deflection, const TopoDS_Shape *plane, bool force_coplanar, + CArea *areaOpen, bool to_edges, bool reorder) { bool haveShape = false; - + int skipped = 0; for (TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { haveShape = true; const TopoDS_Face &face = TopoDS::Face(it.Current()); + if(plane && !isCoplanar(face,*plane)) { + ++skipped; + if(force_coplanar) continue; + } for (TopExp_Explorer it(face, TopAbs_WIRE); it.More(); it.Next()) add(area,TopoDS::Wire(it.Current()),trsf,deflection); } - if(haveShape) return; + if(haveShape) return skipped; CArea _area; CArea _areaOpen; @@ -134,6 +159,10 @@ void Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, for (TopExp_Explorer it(shape, TopAbs_WIRE); it.More(); it.Next()) { haveShape = true; const TopoDS_Wire &wire = TopoDS::Wire(it.Current()); + if(plane && !isCoplanar(wire,*plane)) { + ++skipped; + if(force_coplanar) continue; + } if(BRep_Tool::IsClosed(wire)) add(_area,wire,trsf,deflection); else if(to_edges) { @@ -146,6 +175,10 @@ void Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, if(!haveShape) { for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + if(plane && !isCoplanar(it.Current(),*plane)) { + ++skipped; + if(force_coplanar) continue; + } add(_areaOpen,BRepBuilderAPI_MakeWire( TopoDS::Edge(it.Current())).Wire(),trsf,deflection); } @@ -158,6 +191,7 @@ void Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, areaOpen->m_curves.splice(areaOpen->m_curves.end(),_areaOpen.m_curves); else area.m_curves.splice(area.m_curves.end(),_areaOpen.m_curves); + return skipped; } void Area::add(CArea &area, const TopoDS_Wire& wire, @@ -227,16 +261,16 @@ void Area::clean(bool deleteShapes) { myArea = NULL; delete myAreaOpen; myAreaOpen = NULL; - if(deleteShapes) + if(deleteShapes){ + myShapePlane.Nullify(); myShapes.clear(); + myHaveFace = false; + } } void Area::add(const TopoDS_Shape &shape,short op) { #define AREA_SRC_OP(_v) op PARAM_ENUM_CONVERT(AREA_SRC_OP,,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - TopExp_Explorer it(shape, TopAbs_SHELL); - if(it.More()) - throw Base::ValueError("not a 2D shape"); clean(); if(myShapes.empty()) Operation = ClipperLib::ctUnion; @@ -258,8 +292,14 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { TopExp_Explorer it(shape, TopAbs_FACE); myHaveFace = it.More(); } + const TopoDS_Shape *plane; + if(myParams.Coplanar == CoplanarNone) + plane = NULL; + else + plane = myWorkPlane.IsNull()?&myShapePlane:&myWorkPlane; CArea areaOpen; - add(area,shape,&myTrsf,myParams.Deflection,&areaOpen, + mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane, + myParams.Coplanar==CoplanarForce,&areaOpen, myParams.OpenMode==OpenModeEdges,myParams.Reorder); if(areaOpen.m_curves.size()) { if(&area == myArea || myParams.OpenMode == OpenModeNone) @@ -278,48 +318,75 @@ void Area::build() { #define AREA_SRC(_v) myParams._v PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); - TopoDS_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); - if(!myWorkPlane.IsNull()) - builder.Add(comp,myWorkPlane); - else { - for(const Shape &s : myShapes) - builder.Add(comp, s.shape); + if(myWorkPlane.IsNull()) { + myShapePlane.Nullify(); + for(const Shape &s : myShapes) { + bool haveFace = false; + for(TopExp_Explorer it(s.shape, TopAbs_FACE); it.More(); it.Next()) { + haveFace = true; + BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True); + if (!planeFinder.Found()) + continue; + myShapePlane = it.Current(); + myTrsf.SetTransformation(GeomAdaptor_Surface( + planeFinder.Surface()).Plane().Position()); + break; + } + if(!myShapePlane.IsNull()) break; + if(haveFace) continue; + for(TopExp_Explorer it(s.shape, TopAbs_WIRE); it.More(); it.Next()) { + BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True); + if (!planeFinder.Found()) + continue; + myShapePlane = it.Current(); + myTrsf.SetTransformation(GeomAdaptor_Surface( + planeFinder.Surface()).Plane().Position()); + break; + } + if(!myShapePlane.IsNull()) break; + } + + if(myShapePlane.IsNull()) + throw Base::ValueError("shapes are not planar"); } - BRepLib_FindSurface planeFinder(comp,-1,Standard_True); - if (!planeFinder.Found()) - throw Base::ValueError("shapes are not coplanar"); - myTrsf.SetTransformation(GeomAdaptor_Surface( - planeFinder.Surface()).Plane().Position()); + try { + myArea = new CArea(); + myAreaOpen = new CArea(); - myArea = new CArea(); - myAreaOpen = new CArea(); + CAreaConfig conf(myParams); + CArea areaClip; - CAreaConfig conf(myParams); - CArea areaClip; + mySkippedShapes = 0; + short op = ClipperLib::ctUnion; + bool pending = false; + for(const Shape &s : myShapes) { + if(op!=s.op) { + if(myParams.OpenMode!=OpenModeNone) + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + pending = false; + myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + areaClip.m_curves.clear(); + op=s.op; + } + addToBuild(op==ClipperLib::ctUnion?*myArea:areaClip,s.shape); + pending = true; + } + if(mySkippedShapes) + Base::Console().Warning("%s %d non coplanar shapes\n", + myParams.Coplanar==CoplanarForce?"Skipped":"Found",mySkippedShapes); - short op = ClipperLib::ctUnion; - bool pending = false; - for(const Shape &s : myShapes) { - if(op!=s.op) { + if(pending){ if(myParams.OpenMode!=OpenModeNone) myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); - pending = false; myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); - areaClip.m_curves.clear(); - op=s.op; } - addToBuild(op==ClipperLib::ctUnion?*myArea:areaClip,s.shape); - pending = true; + myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + + }catch(...) { + clean(); + throw; } - if(pending){ - if(myParams.OpenMode!=OpenModeNone) - myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); - myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); - } - myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); } TopoDS_Shape Area::toShape(CArea &area, short fill) { diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 40f4fffb8d..78b89aa016 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -109,9 +109,11 @@ protected: CArea *myAreaOpen; gp_Trsf myTrsf; AreaParams myParams; + TopoDS_Shape myShapePlane; TopoDS_Shape myWorkPlane; TopoDS_Shape myShape; bool myHaveFace; + int mySkippedShapes; /** Called internally to combine children shapes for further processing */ void build(); @@ -205,7 +207,7 @@ public: * \arg \c deflection: for defecting non circular curves * */ static void add(CArea &area, const TopoDS_Wire &wire, - const gp_Trsf *trsf=NULL,double deflection=0.01); + const gp_Trsf *trsf=NULL, double deflection=0.01); /** Add a OCC generic shape to CArea * @@ -214,13 +216,20 @@ public: * \arg \c trsf: optional transform matrix to transform the wire shape into * XY0 plane. * \arg \c deflection: for defecting non circular curves + * \arg \c plane: a shape for testing coplanar + * \arg \c force_coplaner: if true, discard non-coplanar shapes. * \arg \c areaOpen: for collecting open curves. If not supplied, open * curves are added to \c area * \arg \c to_edges: separate open wires to individual edges * \arg \c reorder: reorder closed wires for wire only shape + * + * \return Returns the number of non coplaner. Planar testing only happens + * if \c plane is supplied * */ - static void add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL, - double deflection=0.01,CArea *areaOpen=NULL, bool to_edges=false, bool reorder=true); + static int add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL, + double deflection=0.01,const TopoDS_Shape *plane = NULL, + bool force_coplanar=true, CArea *areaOpen=NULL, bool to_edges=false, + bool reorder=true); /** Convert curves in CArea into an OCC shape * @@ -231,6 +240,8 @@ public: * */ static TopoDS_Shape toShape(const CArea &area, bool fill, const gp_Trsf *trsf=NULL); + + static bool isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2); }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 01b153676b..8d16623dea 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -48,6 +48,9 @@ #define AREA_PARAMS_BASE \ ((enum,fill,Fill,2,"Fill the output wires to make a face. \n"\ "Auto means make a face if any of the children has a face.",(None)(Face)(Auto)))\ + ((enum,coplanar,Coplanar,2,"Specifies the way to check coplanar.\n"\ + "'Force' will discard non coplaner shapes, but 'Check' only gives warning.",\ + (None)(Check)(Force)))\ ((bool,reorder,Reorder,false,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ ((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\ "'None' means combin without openeration.\n"\ From a1a8645ea95fe106f47e50dd1fe4f75d2f1e101d Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 20 Jan 2017 17:46:47 +0800 Subject: [PATCH 08/35] Part: exported Part::sort_Edges Fixed export of Part::shape2pyshape --- src/Mod/Part/App/AppPartPy.cpp | 2 +- src/Mod/Part/App/TopoShapePyImp.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 1a5b24493d..3fab8fd9bb 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -147,7 +147,7 @@ struct EdgePoints { TopoDS_Edge edge; }; -static std::list sort_Edges(double tol3d, std::list& edges) +std::list sort_Edges(double tol3d, std::list& edges) { tol3d = tol3d * tol3d; std::list edge_points; diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index c67c84011e..8463311e0c 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -159,7 +159,7 @@ int TopoShapePy::PyInit(PyObject* args, PyObject*) namespace Part { //common code.. maybe put somewhere else? -Py::Object PartExport shape2pyshape(const TopoDS_Shape &shape) +PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape) { PyObject* ret = 0; if (!shape.IsNull()) { From d95e6e262bc420ae94adaa948982ad7bbb9302e0 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 20 Jan 2017 17:47:28 +0800 Subject: [PATCH 09/35] Path.Area: added support for solid * Path.Area/FeatureArea can now section solid shapes. * Added command to select workplane for FeatureArea * Generalized ParamsHelper --- src/Mod/Path/App/Area.cpp | 363 ++++++++-- src/Mod/Path/App/Area.h | 44 +- src/Mod/Path/App/AreaParams.h | 26 +- src/Mod/Path/App/AreaPy.xml | 6 +- src/Mod/Path/App/AreaPyImp.cpp | 55 +- src/Mod/Path/App/FeatureArea.cpp | 38 +- src/Mod/Path/App/ParamsHelper.h | 236 +++--- src/Mod/Path/Gui/Command.cpp | 153 +++- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Resources/icons/Path-Area-Workplane.svg | 676 ++++++++++++++++++ src/Mod/Path/InitGui.py | 2 +- 11 files changed, 1316 insertions(+), 284 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Area-Workplane.svg diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 36a31371f7..01cd1e7e96 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -51,6 +51,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -64,11 +68,11 @@ using namespace Path; CAreaParams::CAreaParams() - :PARAM_INIT(NAME,AREA_PARAMS_CAREA) + :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA) {} AreaParams::AreaParams() - :PARAM_INIT(NAME,AREA_PARAMS_BASE) + :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_AREA) {} CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs) @@ -98,9 +102,9 @@ CAreaConfig::~CAreaConfig() { TYPESYSTEM_SOURCE(Path::Area, Base::BaseClass); Area::Area(const AreaParams *params) -:myArea(NULL) -,myAreaOpen(NULL) -,myHaveFace(false) +:myHaveFace(false) +,myHaveSolid(false) +,myShapeDone(false) { if(params) setParams(*params); @@ -256,35 +260,51 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, void Area::clean(bool deleteShapes) { + myShapeDone = false; + mySections.clear(); myShape.Nullify(); - delete myArea; - myArea = NULL; - delete myAreaOpen; - myAreaOpen = NULL; + myArea.reset(); + myAreaOpen.reset(); if(deleteShapes){ myShapePlane.Nullify(); myShapes.clear(); myHaveFace = false; + myHaveSolid = false; } } void Area::add(const TopoDS_Shape &shape,short op) { -#define AREA_SRC_OP(_v) op - PARAM_ENUM_CONVERT(AREA_SRC_OP,,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); +#define AREA_SRC_OP(_param) op + PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); + Q_UNUSED(Operation); + + bool haveSolid = false; + for(TopExp_Explorer it(shape, TopAbs_SOLID);it.More();) { + haveSolid = true; + break; + } + //TODO: shall we support Shells? + if((!haveSolid && myHaveSolid) || + (haveSolid && !myHaveSolid && !myShapes.empty())) + throw Base::ValueError("mixing solid and planar shapes is not allowed"); + + myHaveSolid = haveSolid; + clean(); if(myShapes.empty()) - Operation = ClipperLib::ctUnion; - myShapes.push_back(Shape((short)Operation,shape)); + op = OperationUnion; + myShapes.push_back(Shape(op,shape)); } void Area::setParams(const AreaParams ¶ms) { -#define AREA_SRC2(_v) params._v +#define AREA_SRC2(_param) params.PARAM_FNAME(_param) // Validate all enum type of parameters PARAM_ENUM_CHECK(AREA_SRC2,PARAM_ENUM_EXCEPT,AREA_PARAMS_CONF); - if(params!=myParams) + if(params!=myParams) { clean(); - myParams = params; + myParams = params; + } } void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { @@ -299,24 +319,28 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { plane = myWorkPlane.IsNull()?&myShapePlane:&myWorkPlane; CArea areaOpen; mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane, - myParams.Coplanar==CoplanarForce,&areaOpen, + myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, myParams.OpenMode==OpenModeEdges,myParams.Reorder); if(areaOpen.m_curves.size()) { - if(&area == myArea || myParams.OpenMode == OpenModeNone) + if(&area == myArea.get() || myParams.OpenMode == OpenModeNone) myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); else Base::Console().Warning("open wires discarded in clipping shapes\n"); } } +namespace Part { +extern PartExport std::list sort_Edges(double tol3d, std::list& edges); +} + void Area::build() { - if(myArea) return; + if(myArea || mySections.size()) return; if(myShapes.empty()) throw Base::ValueError("Null shape"); -#define AREA_SRC(_v) myParams._v - PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); +#define AREA_SRC(_param) myParams.PARAM_FNAME(_param) + PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); if(myWorkPlane.IsNull()) { myShapePlane.Nullify(); @@ -350,36 +374,139 @@ void Area::build() { throw Base::ValueError("shapes are not planar"); } + if(myHaveSolid && myParams.SectionCount) { + + if(myParams.SectionOffset < 0) + throw Base::ValueError("invalid section offset"); + if(myParams.SectionCount>1 && myParams.Stepdown zMax-zMin) + count = ceil((zMax-zMin)/myParams.Stepdown); + for(int i=0;i area(new Area(&myParams)); + area->setPlane(face); + for(const Shape &s : myShapes) { + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(TopExp_Explorer it(s.shape, TopAbs_SOLID); it.More(); it.Next()) { + BRepAlgoAPI_Section section(it.Current().Moved(loc),face); + if(!section.IsDone()) { + ++error; + continue; + } + const TopoDS_Shape &shape = section.Shape(); + if(shape.IsNull()) continue; + + Part::FaceMakerBullseye mkFace; + mkFace.setPlane(pln); + + std::list edges; + for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) + edges.push_back(TopoDS::Edge(it.Current())); + bool open = false; + std::list wires; + while(edges.size()) { + const std::list sorted = + Part::sort_Edges(Precision::Confusion(),edges); + BRepBuilderAPI_MakeWire mkWire; + for(const TopoDS_Edge &e : sorted) + mkWire.Add(e); + const TopoDS_Wire &wire = mkWire.Wire(); + if(!BRep_Tool::IsClosed(wire)) + open = true; + wires.push_back(wire); + } + if(!open) { + for(const TopoDS_Wire &wire : wires) + mkFace.addWire(wire); + try { + mkFace.Build(); + if (mkFace.Shape().IsNull()) + continue; + builder.Add(comp,mkFace.Shape()); + continue; + }catch (Base::Exception &e){ + Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); + } + } + //Shouldn't have any open wire, so count as error + ++error; + for(const TopoDS_Wire &wire : wires) + builder.Add(comp,wire); + } + if(comp.IsNull()) continue; + area->add(comp,s.op); + } + mySections.push_back(area); + } + if(error) + Base::Console().Warning("Some errors occured during operation\n"); + return; + } + try { - myArea = new CArea(); - myAreaOpen = new CArea(); + myArea.reset(new CArea()); + myAreaOpen.reset(new CArea()); CAreaConfig conf(myParams); CArea areaClip; mySkippedShapes = 0; - short op = ClipperLib::ctUnion; + short op = OperationUnion; bool pending = false; for(const Shape &s : myShapes) { if(op!=s.op) { if(myParams.OpenMode!=OpenModeNone) myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); pending = false; - myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); + myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); areaClip.m_curves.clear(); op=s.op; } - addToBuild(op==ClipperLib::ctUnion?*myArea:areaClip,s.shape); + addToBuild(op==OperationUnion?*myArea:areaClip,s.shape); pending = true; } - if(mySkippedShapes) + if(mySkippedShapes && !myHaveSolid) Base::Console().Warning("%s %d non coplanar shapes\n", myParams.Coplanar==CoplanarForce?"Skipped":"Found",mySkippedShapes); if(pending){ if(myParams.OpenMode!=OpenModeNone) myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); - myArea->Clip((ClipperLib::ClipType)op,&areaClip,SubjectFill,ClipFill); + PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); + myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); } myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); @@ -403,7 +530,7 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { bFill = false; } if(myParams.FitArcs) { - if(&area == myArea) { + if(&area == myArea.get()) { CArea copy(area); copy.FitArcs(); return toShape(copy,bFill,&trsf); @@ -413,47 +540,132 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { return toShape(area,bFill,&trsf); } -const TopoDS_Shape &Area::getShape() { - if(myShape.IsNull()) { - build(); - CAreaConfig conf(myParams); - myShape = toShape(*myArea,myParams.Fill); - } - return myShape; -} +#define AREA_SECTION(_op,_index,...) do {\ + if(mySections.size()) {\ + if(_index>=(int)mySections.size())\ + throw Base::ValueError("index out of bound");\ + TopLoc_Location loc(myTrsf.Inverted());\ + if(_index<0) {\ + BRep_Builder builder;\ + TopoDS_Compound compound;\ + builder.MakeCompound(compound);\ + for(shared_ptr area : mySections)\ + builder.Add(compound,area->_op(-1, ## __VA_ARGS__).Moved(loc));\ + return compound;\ + }\ + return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\ + }\ +}while(0) + +TopoDS_Shape Area::getShape(int index) { + build(); + AREA_SECTION(getShape,index); + + if(myShapeDone) return myShape; + + CAreaConfig conf(myParams); + +#define AREA_MY(_param) myParams.PARAM_FNAME(_param) + + // if no offset or thicken, try pocket + if(fabs(myParams.Offset) < Precision::Confusion() && !myParams.Thicken) { + if(myParams.PocketMode == PocketModeNone) { + myShape = toShape(*myArea,myParams.Fill); + myShapeDone = true; + return myShape; + } + myShape = makePocket(-1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET)); + myShapeDone = true; + return myShape; + } + + // if no pocket, do offset or thicken + if(myParams.PocketMode == PocketModeNone){ + myShape = makeOffset(-1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_OFFSET)); + myShapeDone = true; + return myShape; + } + + // do offset first, then pocket the inner most offseted shape + std::list > areas; + makeOffset(areas,PARAM_FIELDS(AREA_MY,AREA_PARAMS_OFFSET)); + + if(areas.empty()) + areas.push_back(make_shared(*myArea)); + + Area areaPocket(&myParams); + bool front = true; + if(areas.size()>1) { + double step = myParams.Stepover; + if(fabs(step)0; + } + + // for pocketing, we discard the outer most offset wire in order to achieve + // the effect of offseting shape first than pocket, where the actual offset + // path is not wanted. For extra outline profiling, add extra_offset + if(front) { + areaPocket.add(toShape(*areas.front(),myParams.Fill)); + areas.pop_back(); + }else{ + areaPocket.add(toShape(*areas.back(),myParams.Fill)); + areas.pop_front(); + } -TopoDS_Shape Area::makeOffset(PARAM_ARGS(ARG,AREA_PARAMS_OFFSET)) { - std::list shapes; - makeOffset(shapes,PARAM_FIELDS(ARG,AREA_PARAMS_OFFSET)); - if(shapes.empty()) - return TopoDS_Shape(); - if(shapes.size()==1) - return shapes.front(); BRep_Builder builder; TopoDS_Compound compound; builder.MakeCompound(compound); - for(const TopoDS_Shape &s : shapes) - builder.Add(compound,s); + + short fill = myParams.Thicken?FillFace:FillNone; + for(shared_ptr area : areas) { + if(myParams.Thicken) + area->Thicken(myParams.ToolRadius); + builder.Add(compound,toShape(*area,fill)); + } + builder.Add(compound,areaPocket.makePocket( + -1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET))); + myShape = compound; + myShapeDone = true; + return myShape; +} + +TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET)) { + build(); + AREA_SECTION(makeOffset,index,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); + + std::list > areas; + makeOffset(areas,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); + if(areas.empty()) { + if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) { + CArea area(*myArea); + area.Thicken(myParams.ToolRadius); + return toShape(area,FillFace); + } + return TopoDS_Shape(); + } + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(shared_ptr area : areas) { + short fill; + if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) { + area->Thicken(myParams.ToolRadius); + fill = FillFace; + }else if(areas.size()==1) + fill = myParams.Fill; + else + fill = FillNone; + builder.Add(compound,toShape(*area,fill)); + } return compound; } -void Area::makeOffset(std::list &shapes, - PARAM_ARGS(ARG,AREA_PARAMS_OFFSET)) +void Area::makeOffset(list > &areas, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET)) { - if(fabs(offset) &shapes, } } - PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_OFFSET_CONF); + PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OFFSET_CONF); #ifdef AREA_OFFSET_ALGO - PARAM_ENUM_CONVERT(AREA_SRC,,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); + PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); #endif for(int i=0;count<0||i()); + CArea &area = *areas.back(); CArea areaOpen; #ifdef AREA_OFFSET_ALGO if(myParams.Algo == Area::Algolibarea) { @@ -512,16 +725,10 @@ void Area::makeOffset(std::list &shapes, if(area.m_curves.empty()) return; - - if(count == 1) { - shapes.push_back(toShape(area,myParams.Fill)); - return; - } - shapes.push_back(toShape(area,Area::FillNone)); } } -TopoDS_Shape Area::makePocket(PARAM_ARGS(ARG,AREA_PARAMS_POCKET)) { +TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKET)) { if(tool_radius < Precision::Confusion()) throw Base::ValueError("tool radius too small"); @@ -534,6 +741,9 @@ TopoDS_Shape Area::makePocket(PARAM_ARGS(ARG,AREA_PARAMS_POCKET)) { if(mode == Area::PocketModeNone) return TopoDS_Shape(); + build(); + AREA_SECTION(makePocket,index,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); + PocketMode pm; switch(mode) { case Area::PocketModeZigZag: @@ -543,11 +753,11 @@ TopoDS_Shape Area::makePocket(PARAM_ARGS(ARG,AREA_PARAMS_POCKET)) { pm = SpiralPocketMode; break; case Area::PocketModeOffset: { - PARAM_DECLARE_INIT(NAME,AREA_PARAMS_OFFSET); + PARAM_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_OFFSET); Offset = -tool_radius-extra_offset; ExtraPass = -1; Stepover = -stepover; - return makeOffset(PARAM_FIELDS(NAME,AREA_PARAMS_OFFSET)); + return makeOffset(index,PARAM_FIELDS(PARAM_FNAME,AREA_PARAMS_OFFSET)); }case Area::PocketModeZigZagOffset: pm = ZigZagThenSingleOffsetPocketMode; break; @@ -564,7 +774,12 @@ TopoDS_Shape Area::makePocket(PARAM_ARGS(ARG,AREA_PARAMS_POCKET)) { // reorder before input, otherwise nothing is shown. in.Reorder(); in.MakePocketToolpath(out.m_curves,params); - return toShape(out,FillNone); + + if(myParams.Thicken){ + out.Thicken(tool_radius); + return toShape(out,FillFace); + }else + return toShape(out,FillNone); } static inline bool IsLeft(const gp_Pnt &a, const gp_Pnt &b, const gp_Pnt &c) { diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 78b89aa016..02b4664bd1 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -37,15 +37,14 @@ namespace Path /** Store libarea algorithm configuration */ struct PathExport CAreaParams { - PARAM_DECLARE(NAME,AREA_PARAMS_CAREA) + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA) CAreaParams(); }; /** Store all Area configurations */ struct PathExport AreaParams: CAreaParams { - PARAM_DECLARE(NAME,AREA_PARAMS_BASE) - PARAM_DECLARE(NAME,AREA_PARAMS_OFFSET_CONF) + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_AREA) bool operator==(const AreaParams &other) const { #define AREA_COMPARE(_param) \ @@ -68,7 +67,7 @@ struct PathExport AreaParams: CAreaParams { struct PathExport CAreaConfig { /** Stores current libarea settings */ - PARAM_DECLARE(NAME,AREA_PARAMS_CAREA) + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA) /** Stores user defined setting */ CAreaParams params; @@ -105,14 +104,17 @@ protected: }; std::list myShapes; - CArea *myArea; - CArea *myAreaOpen; + std::unique_ptr myArea; + std::unique_ptr myAreaOpen; gp_Trsf myTrsf; AreaParams myParams; TopoDS_Shape myShapePlane; TopoDS_Shape myWorkPlane; TopoDS_Shape myShape; + std::vector > mySections; bool myHaveFace; + bool myHaveSolid; + bool myShapeDone; int mySkippedShapes; /** Called internally to combine children shapes for further processing */ @@ -126,7 +128,19 @@ protected: /** Called internally to obtain the combained children shapes */ TopoDS_Shape toShape(CArea &area, short fill); -public: + /** Obtain a list of offseted areas + * + * See #AREA_PARAMS_OFFSET for description of the arguments. + */ + void makeOffset(std::list > &areas, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); + + /** Make a pocket of the combined shape + * + * User #AREA_PARAMS_POCKET setting in myParams. + */ + TopoDS_Shape makePocket(); + /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) @@ -153,7 +167,8 @@ public: * \arg \c shape: the child shape * \arg \c op: operation code, see #AREA_PARAMS_OPCODE */ - void add(const TopoDS_Shape &shape,PARAM_ARGS_DEF(ARG,AREA_PARAMS_OPCODE)); + void add(const TopoDS_Shape &shape,PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OPCODE)); + /** Generate an offset of the combined shape * @@ -161,20 +176,13 @@ public: * If more than one offset is requested, a compound shape is return * containing all offset shapes as wires regardless of \c Fill setting. */ - TopoDS_Shape makeOffset(PARAM_ARGS_DEF(ARG,AREA_PARAMS_OFFSET)); - - /** Obtain a list of offset shapes of the combined shape, - * - * See #AREA_PARAMS_OFFSET for description of the arguments. - */ - void makeOffset(std::list &shapes, - PARAM_ARGS_DEF(ARG,AREA_PARAMS_OFFSET)); + TopoDS_Shape makeOffset(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); /** Make a pocket of the combined shape * * See #AREA_PARAMS_POCKET for description of the arguments. */ - TopoDS_Shape makePocket(PARAM_ARGS_DEF(ARG,AREA_PARAMS_POCKET)); + TopoDS_Shape makePocket(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); /** Config this Area object */ @@ -196,7 +204,7 @@ public: void clean(bool deleteShapes=false); /** Get the combined shape */ - const TopoDS_Shape &getShape(); + TopoDS_Shape getShape(int index); /** Add a OCC wire shape to CArea * diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 8d16623dea..80f8e325fb 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -86,6 +86,9 @@ ((bool,from_center,FromCenter,true,"Start pocketing from center"))\ ((double,zig_angle,ZigAngle,45,"Zig angle in degree")) +#define AREA_PARAMS_POCKET_CONF \ + ((bool,thicken,Thicken,false,"Thicken the resulting wires with ToolRadius")) + /** Operation code */ #define AREA_PARAMS_OPCODE \ ((enum2,op,Operation,0,"Boolean operation",\ @@ -97,6 +100,12 @@ ((long,extra_pass,ExtraPass,0,"Number of extra offset pass to generate."))\ ((double,stepover,Stepover,0.0,"Cutter diameter to step over on each pass. If =0, use Offset")) +/** Section parameters */ +#define AREA_PARAMS_SECTION \ + ((long,count,SectionCount,0,"Number of sections to generate. -1 means full sections."))\ + ((double,stepdown,Stepdown,1.0,"Step down distance for each section"))\ + ((double,offset,SectionOffset,0.0,"Offset for the first section")) + #ifdef AREA_OFFSET_ALGO # define AREA_PARAMS_OFFSET_ALGO \ ((enum,algo,Algo,0,"Offset algorithm type",(Clipper)(libarea))) @@ -107,7 +116,6 @@ /** Offset configuration parameters */ #define AREA_PARAMS_OFFSET_CONF \ AREA_PARAMS_OFFSET_ALGO \ - ((bool,thicken,Thicken,false,"Thicken the resulting wires with Offset"))\ ((enum2,join_type,JoinType,0,"ClipperOffset join type. \nSee https://goo.gl/4odfQh",\ (Round)(Square)(Miter),(ClipperLib::JoinType,ClipperLib::jt)))\ ((enum2,end_type,EndType,0,"\nClipperOffset end type. See https://goo.gl/tj7gkX",\ @@ -116,17 +124,23 @@ ((double,round_precision,RoundPreceision,0.0,\ "Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh")) +/** Group of all Area configuration parameters except CArea's*/ +#define AREA_PARAMS_AREA \ + AREA_PARAMS_BASE \ + AREA_PARAMS_OFFSET \ + AREA_PARAMS_OFFSET_CONF \ + AREA_PARAMS_POCKET \ + AREA_PARAMS_POCKET_CONF \ + AREA_PARAMS_SECTION + /** Group of all Area configuration parameters */ #define AREA_PARAMS_CONF \ AREA_PARAMS_CAREA \ - AREA_PARAMS_BASE \ - AREA_PARAMS_OFFSET_CONF + AREA_PARAMS_AREA /** Group of all Area parameters */ #define AREA_PARAMS_ALL \ AREA_PARAMS_CONF \ - AREA_PARAMS_OPCODE \ - AREA_PARAMS_OFFSET \ - AREA_PARAMS_POCKET + AREA_PARAMS_OPCODE #endif //PATH_AreaParam_H diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index e4952e13bb..6bc8cd2ecc 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -26,9 +26,11 @@ any operation - + - toShape(rebuild=False): Return the resulting shape + getShape(index=-1,rebuild=False): Return the resulting shape\n +\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n +\n* rebuild: clean the internal cache and rebuild diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 2277405198..7c8b058513 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -65,6 +65,7 @@ static const AreaDoc myDocs[] = { "makeOffset", "makeOffset(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" + "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" "Make an 2D offset of the shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_OFFSET), }, @@ -73,6 +74,7 @@ static const AreaDoc myDocs[] = { "makePocket(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" "Generate pocket toolpath of the shape.\n" + "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), }, }; @@ -94,7 +96,7 @@ struct AreaPyDoc { static AreaPyDoc doc; namespace Part { - extern Py::Object shape2pyshape(const TopoDS_Shape &shape); +extern PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape); } using namespace Path; @@ -128,29 +130,30 @@ PyObject* AreaPy::setPlane(PyObject *args) { return Py_None; } -PyObject* AreaPy::toShape(PyObject *args, PyObject *keywds) +PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) { PyObject *pcObj = Py_True; - static char *kwlist[] = {"rebuild", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds,"|O",kwlist,&pcObj)) + short index=-1; + static char *kwlist[] = {"index","rebuild", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj)) Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); try { if(PyObject_IsTrue(pcObj)) getAreaPtr()->clean(true); - return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape())); + return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); } PY_CATCH_OCC; } PyObject* AreaPy::add(PyObject *args, PyObject *keywds) { - PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_OPCODE) + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OPCODE) PyObject *pcObj; static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), - kwlist,&pcObj,PARAM_REF(ARG,AREA_PARAMS_OPCODE))) + kwlist,&pcObj,PARAM_REF(PARAM_FARG,AREA_PARAMS_OPCODE))) Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { @@ -168,7 +171,7 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds) for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it){ PyObject* item = (*it).ptr(); getAreaPtr()->add(GET_TOPOSHAPE(item), - PARAM_PY_FIELDS(ARG,AREA_PARAMS_OPCODE)); + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE)); } return Py_None; } @@ -176,22 +179,23 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds) PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) { //Generate a keyword string defined in the ARG field of OFFSET parameter list - static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OFFSET), NULL}; + static char *kwlist[] = {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OFFSET), NULL}; + short index = -1; //Declare variables defined in the ARG field of the OFFSET parameter list with //initialization to defaults - PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_OFFSET) + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OFFSET) //Parse arguments to overwrite the defaults if (!PyArg_ParseTupleAndKeywords(args, keywds, - "|" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, - PARAM_REF(ARG,AREA_PARAMS_OFFSET))) + "|h" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, + &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET))) Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); try { //Expand the variable as function call arguments - TopoDS_Shape resultShape = getAreaPtr()->makeOffset( - PARAM_PY_FIELDS(ARG,AREA_PARAMS_OFFSET)); + TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); return Py::new_reference_to(Part::shape2pyshape(resultShape)); } PY_CATCH_OCC; @@ -199,18 +203,19 @@ PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) { - static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), NULL}; + static char *kwlist[] = {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), NULL}; + short index = -1; - PARAM_PY_DECLARE_INIT(ARG,AREA_PARAMS_POCKET) + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_POCKET) if (!PyArg_ParseTupleAndKeywords(args, keywds, - "|" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, - PARAM_REF(ARG,AREA_PARAMS_POCKET))) + "|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, + &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET))) Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); try { - TopoDS_Shape resultShape = getAreaPtr()->makePocket( - PARAM_PY_FIELDS(ARG,AREA_PARAMS_POCKET)); + TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); return Py::new_reference_to(Part::shape2pyshape(resultShape)); } PY_CATCH_OCC; @@ -222,7 +227,7 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; //Declare variables defined in the NAME field of the CONF parameter list - PARAM_PY_DECLARE(NAME,AREA_PARAMS_CONF); + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); AreaParams params = getAreaPtr()->getParams(); @@ -235,7 +240,7 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) //Parse arguments to overwrite CONF variables if (!PyArg_ParseTupleAndKeywords(args, keywds, "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, - PARAM_REF(NAME,AREA_PARAMS_CONF))) + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters, call getParamsDesc() to get supported params"); @@ -257,8 +262,8 @@ PyObject* AreaPy::getParams(PyObject *args) const AreaParams ¶ms =getAreaPtr()->getParams(); PyObject *dict = PyDict_New(); -#define AREA_SRC(_v) params._v - PARAM_PY_DICT_SET_VALUE(dict,AREA_SRC,AREA_PARAMS_CONF) +#define AREA_SRC(_param) params.PARAM_FNAME(_param) + PARAM_PY_DICT_SET_VALUE(dict,NAME,AREA_SRC,AREA_PARAMS_CONF) return dict; } @@ -273,7 +278,7 @@ PyObject* AreaPy::getParamsDesc(PyObject *args, PyObject *keywds) return PyString_FromString(PARAM_PY_DOC(NAME,AREA_PARAMS_CONF)); PyObject *dict = PyDict_New(); - PARAM_PY_DICT_SET_DOC(dict,AREA_PARAMS_CONF) + PARAM_PY_DICT_SET_DOC(dict,NAME,AREA_PARAMS_CONF) return dict; } diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index cdc26f1d7a..b02d23441e 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -48,6 +48,8 @@ FeatureArea::FeatureArea() PARAM_PROP_ADD("Area",AREA_PARAMS_BASE); PARAM_PROP_ADD("Offset",AREA_PARAMS_OFFSET); PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET); + PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET_CONF); + PARAM_PROP_ADD("Section",AREA_PARAMS_SECTION); PARAM_PROP_ADD("Offset Settings", AREA_PARAMS_OFFSET_CONF); PARAM_PROP_ADD("libarea Settings",AREA_PARAMS_CAREA); @@ -77,7 +79,6 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) #define AREA_PROP_GET(_param) \ params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); - PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_CONF) Area area(¶ms); @@ -86,45 +87,12 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) if(!workPlane.IsNull()) area.setPlane(workPlane); - area.clean(true); for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { area.add(static_cast(*it)->Shape.getShape().getShape(), PARAM_PROP_ARGS(AREA_PARAMS_OPCODE)); } - std::list shapes; - if(fabs(Offset.getValue())>Precision::Confusion()) - area.makeOffset(shapes,PARAM_PROP_ARGS(AREA_PARAMS_OFFSET)); - - if(PocketMode.getValue()) { - Area areaPocket(¶ms); - if(shapes.empty()) - areaPocket.add(area.getShape()); - else{ - bool front = true; - if(shapes.size()>1) { - double step = Stepover.getValue(); - if(fabs(step)0; - } - areaPocket.add(front?shapes.front():shapes.back()); - } - shapes.push_back(areaPocket.makePocket(PARAM_PROP_ARGS(AREA_PARAMS_POCKET))); - } - - if(shapes.empty()) - this->Shape.setValue(area.getShape()); - else if(shapes.size()==1) - this->Shape.setValue(shapes.front()); - else { - BRep_Builder builder; - TopoDS_Compound compound; - builder.MakeCompound(compound); - for(const TopoDS_Shape &s : shapes) - builder.Add(compound,s); - this->Shape.setValue(compound); - } + this->Shape.setValue(area.getShape(-1)); return Part::Feature::execute(); } diff --git a/src/Mod/Path/App/ParamsHelper.h b/src/Mod/Path/App/ParamsHelper.h index f0d65dd966..0d59116ba9 100644 --- a/src/Mod/Path/App/ParamsHelper.h +++ b/src/Mod/Path/App/ParamsHelper.h @@ -315,42 +315,52 @@ /** Helper for #PARAM_DECLARE */ -#define PARAM_DECLARE_(_1,_field,_param) \ - PARAM_TYPE(_param) PARAM_FIELD(_field,_param); +#define PARAM_DECLARE_(_1,_src,_param) \ + PARAM_TYPE(_param) _src(_param); /** * Delcares parameters using the given field as name * - * \arg \c _field: specifies the \ref ParamField "field" to use as name + * \arg \c _src: \anchor ParamSrc Macro to generate source variable. The + * signature must be _src(_param)<\tt>, where \c _param is the tuple + * defining the parameter. You pass any of the \ref ParamAccessor "parameter + * accessors" to directly access the field. Or, supply your own macro to append + * any prefix as you like. For example: + * \code{.unparsed} + * #define MY_SRC(_param) BOOST_PP_CAT(my,PARAM_FNAME(_param)) + * -> + * my## + * \endcode * * Expands to: * \code{.unparsed} - * type1 _field1;type2 _field2; ... + * type1 _src(_param1);type2 _src(_param2); ... * \endcode * \ingroup ParamCommon */ -#define PARAM_DECLARE(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_,_field,_seq) +#define PARAM_DECLARE(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_,_src,_seq) /** Helper for #PARAM_DECLARE_INIT */ -#define PARAM_DECLARE_INIT_(_1,_field,_param) \ - PARAM_TYPE(_param) PARAM_FIELD(_field,_param) = PARAM_FDEF(_param); +#define PARAM_DECLARE_INIT_(_1,_src,_param) \ + PARAM_TYPE(_param) _src(_param) = PARAM_FDEF(_param); /** * Delcares parameters with initialization to default using the given field as * name * - * \arg \c _field: \ref ParamField "field" to use as name + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details * * Expands to: * \code{.unparsed} - * type1 _field1=_def1;type2 _field2=_def2; ... + * type1 _src(_param1)=_def1;type2 _src(_param2)=_def2; ... * \endcode * \ingroup ParamCommon */ -#define PARAM_DECLARE_INIT(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_INIT_,_field,_seq) +#define PARAM_DECLARE_INIT(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_DECLARE_INIT_,_src,_seq) #define PARAM_ENUM_DECLARE_enum_(_1,_name,_i,_elem) \ @@ -394,8 +404,7 @@ #define PARAM_ENUM_CONVERT_enum_(_dst,_name,_prefix,_elem) \ case BOOST_PP_CAT(_name,_elem):\ - _dst(_name) = \ - BOOST_PP_CAT(_prefix,_elem);\ + _dst = BOOST_PP_CAT(_prefix,_elem);\ break; #define PARAM_ENUM_CONVERT__(_1,_args,_i,_elem) \ @@ -418,10 +427,10 @@ * i.e. not double but single parathesis */ #define PARAM_ENUM_CONVERT_SINGLE(_src,_dst,_default,_param) \ - PARAM_FENUM_TYPE(_param) _dst(PARAM_FNAME(_param));\ - switch(_src(PARAM_FNAME(_param))) {\ + PARAM_FENUM_TYPE(_param) _dst(_param);\ + switch(_src(_param)) {\ BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CONVERT__,\ - (_dst,PARAM_FNAME(_param),PARAM_FENUM_PREFIX(_param)),PARAM_FSEQ(_param))\ + (_dst(_param),PARAM_FNAME(_param),PARAM_FENUM_PREFIX(_param)),PARAM_FSEQ(_param))\ default: \ _default(_param);\ } @@ -441,11 +450,12 @@ * * \ingroup ParamEnumHelper * - * \arg \c _src: Optional macro to generate source variable. The signature must - * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. - * In case you just want \c _name as the source variable name, you can simply - * omit this argument, because newer C++ preprocessor allows empty argument. - * \arg \c _dst: Optional macro to generate destination variable. Same as above. + * \arg \c _src: Macro to generate source variable. The signature must be + * _src(_param)<\tt>, where \c _param is the tuple defining the parameter. + * You pass any of the \ref ParamAccessor "parameter accessors" to directly + * access the field. Or, supply your own macro to append any prefix as you + * like. + * \arg \c _dst: Same as above. * \arg \c _default: A macro to call for invalid value. Signature should be * _default(_param)<\tt>, where \c _param is the parameter definition. You * can use #PARAM_ENUM_EXCEPT to throw Base::ValueError exception in FreeCAD @@ -457,12 +467,12 @@ * ((enum,test1,Test1,0,"it's a test",(Foo)(Bar),(MyEnum1,myEnum1)) \ * ((enum,test2,Test2,0,"it's a test",(Foo)(Bar),(MyEnum2,myEnum2))) * - * #define MY_DST(_v) BOOST_PP_CAT(my,_v) + * #define MY_DST(_param) BOOST_PP_CAT(my,PARAM_FNAME(_param)) * \code{.unparsed} * - * calling (note that the \c _src macro is omitted) + * calling * \code{.unparsed} - * PARAM_ENUM_CONVERT(,MY_DST,My,PARAM_ENUM_EXCEP,MY_PARAM_TEST) + * PARAM_ENUM_CONVERT(PARAM_FNAME,MY_DST,My,PARAM_ENUM_EXCEP,MY_PARAM_TEST) * \code{.unparsed} * * expands to @@ -514,7 +524,7 @@ _param) #define PARAM_ENUM_CHECK_SINGLE(_src,_default,_param) \ - switch(_src(PARAM_FNAME(_param))) {\ + switch(_src(_param)) {\ BOOST_PP_SEQ_FOR_EACH_I(PARAM_ENUM_CHECK_enum_,\ PARAM_FNAME(_param),PARAM_FSEQ(_param))\ default: \ @@ -528,10 +538,11 @@ * * \ingroup ParamEnumHelper * - * \arg \c _src: Optional macro to generate source variable. The signature must - * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. - * In case you just want \c _name as the source variable name, you can simply - * omit this argument, because newer C++ preprocessor allows empty argument. + * \arg \c _src: Macro to generate source variable. The signature must be + * _src(_param)<\tt>, where \c _param is the tuple defining the parameter. + * You pass any of the \ref ParamAccessor "parameter accessors" to directly + * access the field. Or, supply your own macro to append any prefix as you + * like. * * \arg \c _default: A macro to call for invalid value. Signature should be * _default(_param)<\tt>, where \c _param is the parameter definition. You @@ -574,57 +585,86 @@ /** Helper for #PARAM_INIT */ -#define PARAM_INIT_(_,_field,_i,_param) \ - BOOST_PP_COMMA_IF(_i) PARAM_FIELD(_field,_param)(PARAM_FDEF(_param)) +#define PARAM_INIT_(_,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) _src(_param)(PARAM_FDEF(_param)) /** Constructor initialization * - * \arg \c _field: specifies the \ref ParamField "field" to use as name + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details * * Expand to, * \code{.unparsed} - * field1(def1), field2(def2)... + * _src(_param1)(def1), _src(_param1)(def2)... * \endcode * \ingroup ParamCommon */ -#define PARAM_INIT(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH_I(PARAM_INIT_,_field,_seq) +#define PARAM_INIT(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_INIT_,_src,_seq) + + +/** Helper for #PARAM_OP */ +#define PARAM_OP_(_,_args,_param) \ + BOOST_PP_TUPLE_ELEM(0,_args)(_param) BOOST_PP_TUPLE_ELEM(1,_args) \ + BOOST_PP_TUPLE_ELEM(2,_args)(_param); + +/** Perform operation on two instance of each parameter in a sequence + * + * \arg \c _src: Macro to generate source variable. The signature must be + * _src(_param)<\tt>, where \c _param is the tuple defining the parameter. + * You pass any of the \ref ParamAccessor "parameter accessors" to directly + * access the field. Or, supply your own macro to append any prefix as you + * like. + * \arg \c _op: a boolean operator + * \arg \c _dst: Same as \c _src above. + * + * Expands to: + * \code{.unparsed} + * _src(_param1) _op _src(_param2); + * \endcode + * + * \ingroup ParamCommon + */ +#define PARAM_OP(_src,_op,_dst,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_COPY_,(_src,_op,_dst),_seq) /** Helper for #PARAM_ARGS_DEF */ -#define PARAM_ARGS_DEF_(_,_field,_i,_param) \ - BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) PARAM_FIELD(_field,_param)=PARAM_FDEF(_param) +#define PARAM_ARGS_DEF_(_,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) _src(_param)=PARAM_FDEF(_param) /** Delcare the parameters as function argument list with defaults. * - * \arg \c _field: specifies the \ref ParamField "field" to use as name + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details * * Expand to: * \code{.unparsed} - * type1 field1=def1, type2 field2=def2 ... + * type1 _src(_param1)=def1, type2 _src(_param1)=def2 ... * \endcode * \ingroup ParamCommon */ -#define PARAM_ARGS_DEF(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_DEF_,_field,_seq) +#define PARAM_ARGS_DEF(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_DEF_,_src,_seq) /** Helper for #PARAM_ARGS */ -#define PARAM_ARGS_(_,_field,_i,_param) \ - BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) PARAM_FIELD(_field,_param) +#define PARAM_ARGS_(_,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPE(_param) _src(_param) /** Delcare the parameters as function argument list without defaults. * - * \arg \c _field: specifies the \ref ParamField "field" to use as name + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details * * Expand to: * \code{.unparsed} - * type1 field1, type2 field2 ... + * type1 _src(_param1), type2 _src(_param2) ... * \endcode * \ingroup ParamCommon */ -#define PARAM_ARGS(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_,_field,_seq) +#define PARAM_ARGS(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_ARGS_,_src,_seq) /** \defgroup ParamPy Python helper @@ -685,19 +725,22 @@ /** Helper for #PARAM_FIELDS */ -#define PARAM_FIELDS_(_1,_field,_i,_param) \ - BOOST_PP_COMMA_IF(_i) PARAM_FIELD(_field,_param) +#define PARAM_FIELDS_(_1,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) _src(_param) /** Expand to a list of the given field in the parameter sequence * - * For example, PARAM_FIELDS(ARG, _seq) expands to: + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details + * + * For example, PARAM_FIELDS(PARAM_FARG, _seq) expands to: * \code{.unparsed} * arg1,arg2 ... * \endcode * \ingroup ParamCommon ParamPy */ -#define PARAM_FIELDS(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELDS_,_field,_seq) +#define PARAM_FIELDS(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELDS_,_src,_seq) #define PARAM_PY_CAST_short(_v) (_v) @@ -716,16 +759,19 @@ /** Helper for #PARAM_PY_FIELDS */ -#define PARAM_PY_FIELDS_(_1,_field,_i,_param) \ - BOOST_PP_COMMA_IF(_i) PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FIELD(_field,_param)) +#define PARAM_PY_FIELDS_(_1,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) PARAM_TYPED(PARAM_CAST_PY_,_param)(_src(_param)) -/** Expand to a list of the given field in the sequence +/** Expand to a comma separated list of the given field in the sequence + * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details * * The field will be casted from python C to C type * \ingroup ParamCommon ParamPy */ -#define PARAM_PY_FIELDS(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_FIELDS_,_field,_seq) +#define PARAM_PY_FIELDS(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_PY_FIELDS_,_src,_seq) /** Helper for #PARAM_FIELD_STRINGS */ @@ -733,7 +779,7 @@ BOOST_PP_COMMA_IF(_i) PARAM_FIELD_STR(_field,_param) /** Expand to a list of stringified fields - * \ingroup ParamCommon ParamPy + * \ingroup ParamStringizer ParamPy */ #define PARAM_FIELD_STRINGS(_field,_seq) \ BOOST_PP_SEQ_FOR_EACH_I(PARAM_FIELD_STRINGS_,_field,_seq) @@ -764,14 +810,14 @@ #define PARAM_PY_TYPE_enum2 short /** Helper for #PARAM_PY_DECLARE */ -#define PARAM_PY_DECLARE_(_1,_field,_param) \ - PARAM_TYPED(PARAM_PY_TYPE_,_param) PARAM_FIELD(_field,_param); +#define PARAM_PY_DECLARE_(_1,_src,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) _src(_param); /** Declare field variables for Python C type without initialization * \ingroup ParamPy */ -#define PARAM_PY_DECLARE(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_,_field,_seq) +#define PARAM_PY_DECLARE(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_,_src,_seq) #define PARAM_PY_INIT_short(_v) _v #define PARAM_PY_INIT_long(_v) _v @@ -781,31 +827,34 @@ #define PARAM_PY_INIT_enum2(_v) _v /** Helper for #PARAM_PY_DECLARE_INIT */ -#define PARAM_PY_DECLARE_INIT_(_1,_field,_param) \ - PARAM_TYPED(PARAM_PY_TYPE_,_param) PARAM_FIELD(_field,_param) = \ +#define PARAM_PY_DECLARE_INIT_(_1,_src,_param) \ + PARAM_TYPED(PARAM_PY_TYPE_,_param) _src(_param) = \ PARAM_TYPED(PARAM_PY_INIT_,_param)(PARAM_FDEF(_param)); /** Declare field variables of Python c type with initialization to default * \ingroup ParamPy */ -#define PARAM_PY_DECLARE_INIT(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_INIT_,_field,_seq) +#define PARAM_PY_DECLARE_INIT(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DECLARE_INIT_,_src,_seq) /** Helper for #PARAM_REF */ -#define PARAM_REF_(_1,_field,_i,_param) \ - BOOST_PP_COMMA_IF(_i) &PARAM_FIELD(_field,_param) +#define PARAM_REF_(_1,_src,_i,_param) \ + BOOST_PP_COMMA_IF(_i) &_src(_param) /** Generate a list of field references * + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * + * more details * Expand to: * \code{.unparsed} - * &_field1, &_field2 ... + * &_src(_param1), &_src(_param1) ... * \endcode * \ingroup ParamPy */ -#define PARAM_REF(_field,_seq) \ - BOOST_PP_SEQ_FOR_EACH_I(PARAM_REF_,_field,_seq) +#define PARAM_REF(_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH_I(PARAM_REF_,_src,_seq) #define PARAM_CAST_PYOBJ_short(_v) PyInt_FromLong(_v) @@ -816,35 +865,36 @@ #define PARAM_CAST_PYOBJ_enum2 PARAM_CAST_PYOBJ_short -/** Stringize field to a Python string */ -#define PARAM_PY_STRINGIZE(_field,_param) \ +/** Stringize field to a Python string + * \ingroup ParamPy ParamStringizer + */ +#define PARAM_PY_STR(_field,_param) \ PyString_FromString(PARAM_FIELD_STR(_field,_param)) /** Helper for #PARAM_PY_DICT_SET_VALUE */ #define PARAM_PY_DICT_SET_VALUE_(_1,_args,_param) \ PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \ - PARAM_PY_STRINGIZE(NAME,_param),\ + PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\ PARAM_TYPED(PARAM_CAST_PYOBJ_,_param)(\ - BOOST_PP_TUPLE_ELEM(1,_args)(PARAM_FIELD(NAME,_param)))); + BOOST_PP_TUPLE_ELEM(2,_args)(_param))); /** Populate a Python dict with a structure variable * * \arg \c _dict: the Python dictionary object - * \arg \c _src: Optional macro to generate source variable. The signature must - * be _src(_name)<\tt>, where \c _name will be the parameters \a name field. - * In case you just want \c _name as the source variable name, you can simply - * omit this argument, because newer C++ preprocessor allows empty argument. + * \arg \c _field: specifies the \ref ParamField "field" to use as key + * \arg \c _src: macro to generate source field. See \ref ParamSrc "here" for + * more details * * Roughly translated to: * \code{.unparsed} - * PyDict_SetItem(_dict,#name1,_src(name1)); - * PyDict_SetItem(_dict,#name2,_src(name2)); + * PyDict_SetItem(_dict,#_field1,_src(_param)); + * PyDict_SetItem(_dict,#_field2,_src(_param)); * ... * \endcode * \ingroup ParamPy */ -#define PARAM_PY_DICT_SET_VALUE(_dict,_src,_seq) \ - BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_VALUE_,(_dict,_src),_seq) +#define PARAM_PY_DICT_SET_VALUE(_dict,_field,_src,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_VALUE_,(_dict,_field,_src),_seq) #define PARAM_PY_DICT_DOC_enum_(_i,_elem) \ @@ -868,22 +918,26 @@ #define PARAM_PY_DICT_DOC_enum2 PARAM_PY_DICT_DOC_enum /** Helper for #PARAM_PY_DICT_SET_DOC */ -#define PARAM_PY_DICT_SET_DOC_(_1,_dict,_param) \ - PyDict_SetItem(_dict, PARAM_PY_STRINGIZE(NAME,_param),\ - PyString_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param))); +#define PARAM_PY_DICT_SET_DOC_(_1,_args,_param) \ + PyDict_SetItem(BOOST_PP_TUPLE_ELEM(0,_args), \ + PARAM_PY_STR(BOOST_PP_TUPLE_ELEM(1,_args),_param),\ + PyString_FromString(PARAM_TYPED(PARAM_PY_DICT_DOC_,_param)(_param))); /** Populate a Python dict with the doc field of the parameter sequence + * + * \arg \c _dict: the Python dictionary object + * \arg \c _field: specifies the \ref ParamField "field" to use as key * * Roughly translated to: * \code{.unparsed} - * PyDict_SetItem(_dict,#name1,doc1); - * PyDict_SetItem(_dict,#name2,doc2); + * PyDict_SetItem(_dict,#_field1,doc1); + * PyDict_SetItem(_dict,#_field1,doc2); * ... * \endcode * \ingroup ParamDoc */ -#define PARAM_PY_DICT_SET_DOC(_dict,_seq) \ - BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_DOC_,_dict,_seq) +#define PARAM_PY_DICT_SET_DOC(_dict,_field,_seq) \ + BOOST_PP_SEQ_FOR_EACH(PARAM_PY_DICT_SET_DOC_,(_dict,_field),_seq) /** \defgroup ParamProperty Property Macros diff --git a/src/Mod/Path/Gui/Command.cpp b/src/Mod/Path/Gui/Command.cpp index ab0a8296dc..07528e0c91 100644 --- a/src/Mod/Path/Gui/Command.cpp +++ b/src/Mod/Path/Gui/Command.cpp @@ -66,44 +66,37 @@ CmdPathArea::CmdPathArea() void CmdPathArea::activated(int iMsg) { Q_UNUSED(iMsg); - std::vector Sel = - getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId()); std::list cmds; std::ostringstream sources; - if (Sel.size() > 0) { - for(const Gui::SelectionObject &selObj : Sel) { - const Part::Feature *pcObj = static_cast(selObj.getObject()); - if(selObj.getSubNames().empty()) { - const TopoDS_Shape &shape = pcObj->Shape.getShape().getShape(); - TopExp_Explorer it(shape, TopAbs_SHELL); - if(it.More()) { - Base::Console().Error("Selected shape is not 2D\n"); - return; - } - sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; - continue; + for(const Gui::SelectionObject &selObj : + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) + { + const Part::Feature *pcObj = static_cast(selObj.getObject()); + const std::vector &subnames = selObj.getSubNames(); + if(subnames.empty()) { + sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; + continue; + } + for(const std::string &name : subnames) { + if(!name.compare(0,4,"Face") && + !name.compare(0,4,"Edge")) + { + Base::Console().Error("Selected shape is not 2D\n"); + return; } - for(const std::string &name : selObj.getSubNames()) { - if(!name.compare(0,4,"Face") && - !name.compare(0,4,"Edge")) - { - Base::Console().Error("Selected shape is not 2D\n"); - return; - } + int index = atoi(name.substr(4).c_str()); - int index = atoi(name.substr(4).c_str()); + std::ostringstream subname; + subname << pcObj->getNameInDocument() << '_' << name; + std::string sub_fname = getUniqueObjectName(subname.str().c_str()); - std::ostringstream subname; - subname << pcObj->getNameInDocument() << '_' << name; - std::string sub_fname = getUniqueObjectName(subname.str().c_str()); - - std::ostringstream cmd; - cmd << "FreeCAD.activeDocument().addObject('Path::Feature','" << sub_fname << "').Shape = " << - pcObj->getNameInDocument() << '.' << name.substr(0,4) << '[' << index-1 << ']'; - cmds.push_back(cmd.str()); - sources << "FreeCAD.activeDocument()." << sub_fname << ","; - } + std::ostringstream cmd; + cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname << + "').Shape = FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ".Shape." << + name.substr(0,4) << "s[" << index-1 << ']'; + cmds.push_back(cmd.str()); + sources << "FreeCAD.activeDocument()." << sub_fname << ","; } } std::string FeatName = getUniqueObjectName("FeatureArea"); @@ -121,6 +114,101 @@ bool CmdPathArea::isActive(void) return hasActiveDocument(); } + +DEF_STD_CMD_A(CmdPathAreaWorkplane) + +CmdPathAreaWorkplane::CmdPathAreaWorkplane() + :Command("Path_Area_Workplane") +{ + sAppModule = "Path"; + sGroup = QT_TR_NOOP("Path"); + sMenuText = QT_TR_NOOP("Area workplane"); + sToolTipText = QT_TR_NOOP("Select a workplane for a FeatureArea"); + sWhatsThis = "Path_Area_Workplane"; + sStatusTip = sToolTipText; + sPixmap = "Path-Area-Workplane"; + sAccel = "P,W"; + +} + +void CmdPathAreaWorkplane::activated(int iMsg) +{ + Q_UNUSED(iMsg); + + std::string areaName; + std::string planeSubname; + std::string planeName; + + for(Gui::SelectionObject &selObj : + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) + { + const std::vector &subnames = selObj.getSubNames(); + if(subnames.size()>1) { + Base::Console().Error("Please select one sub shape object for plane only\n"); + return; + } + const Part::Feature *pcObj = static_cast(selObj.getObject()); + if(subnames.empty()) { + if(pcObj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId())) { + if(areaName.size()){ + Base::Console().Error("Please select one FeatureArea only\n"); + return; + } + areaName = pcObj->getNameInDocument(); + continue; + } + for (TopExp_Explorer it(pcObj->Shape.getShape().getShape(), TopAbs_SHELL); it.More(); it.Next()) { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + } + if(planeName.size()){ + Base::Console().Error("Please select one shape object for plane only\n"); + return; + }else{ + planeSubname = planeName = pcObj->getNameInDocument(); + planeSubname += ".Shape"; + } + + for(const std::string &name : subnames) { + if(!name.compare(0,4,"Face") && + !name.compare(0,4,"Edge")) + { + Base::Console().Error("Selected shape is not 2D\n"); + return; + } + + int index = atoi(name.substr(4).c_str()); + + std::ostringstream subname; + subname << planeSubname << '.' << name.substr(0,4) << "s[" << index-1 << ']'; + planeSubname = subname.str(); + } + } + if(areaName.empty()) { + Base::Console().Error("Please select one FeatureArea\n"); + return; + } + if(planeName.empty()) { + Base::Console().Error("Please select one shape object\n"); + return; + } + + openCommand("Select Workplane for Path Area"); + doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = FreeCAD.activeDocument().%s", + areaName.c_str(),planeSubname.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = True",areaName.c_str()); + // doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = False",planeName.c_str()); + commitCommand(); + updateActive(); +} + +bool CmdPathAreaWorkplane::isActive(void) +{ + return !getSelection().getSelectionEx(NULL, Path::FeatureArea::getClassTypeId()).empty(); +} + + // Path compound ##################################################################################################### @@ -229,4 +317,5 @@ void CreatePathCommands(void) rcCmdMgr.addCommand(new CmdPathCompound()); rcCmdMgr.addCommand(new CmdPathShape()); rcCmdMgr.addCommand(new CmdPathArea()); + rcCmdMgr.addCommand(new CmdPathAreaWorkplane()); } diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 23035a803b..4ad6350181 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -47,6 +47,7 @@ icons/Path-Toolpath.svg icons/Path-ToolTable.svg icons/Path-Area.svg + icons/Path-Area-Workplane.svg icons/preferences-path.svg panels/ContourEdit.ui panels/DlgJobChooser.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Area-Workplane.svg b/src/Mod/Path/Gui/Resources/icons/Path-Area-Workplane.svg new file mode 100644 index 0000000000..1498ef33fe --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Area-Workplane.svg @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-FaceProfile + 2016-01-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path- + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 61c10a7ae6..be7d46a6a0 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -86,7 +86,7 @@ class PathWorkbench (Workbench): threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"] - extracmdlist = ["Path_SelectLoop", "Path_Area"] + extracmdlist = ["Path_SelectLoop", "Path_Area", "Path_Area_Workplane"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] From c9a9691fa910ea3195a3234c0e5ed20b02220822 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 20 Jan 2017 18:30:56 +0800 Subject: [PATCH 10/35] Part: fixed missing export declare of sort_Edges --- src/Mod/Part/App/AppPartPy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 3fab8fd9bb..6367229b2c 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -147,7 +147,7 @@ struct EdgePoints { TopoDS_Edge edge; }; -std::list sort_Edges(double tol3d, std::list& edges) +PartExport std::list sort_Edges(double tol3d, std::list& edges) { tol3d = tol3d * tol3d; std::list edge_points; From 3990b5308bc2bd6fe9633a291de4966c9fd9c6a6 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 20 Jan 2017 18:32:30 +0800 Subject: [PATCH 11/35] libarea: fixed memory leak in CAreaOrderer --- src/Mod/Path/libarea/Area.cpp | 19 ++------- src/Mod/Path/libarea/AreaOrderer.cpp | 58 +++++++++++----------------- src/Mod/Path/libarea/AreaOrderer.h | 26 +++++++------ 3 files changed, 41 insertions(+), 62 deletions(-) diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index 36799a7864..2ed88cfaad 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -92,33 +92,22 @@ void CArea::Reorder() // returns 1, if the curves are overlapping CAreaOrderer ao; - std::list::iterator ItLast = m_curves.end(); - for(std::list::iterator It = m_curves.begin(); It != m_curves.end(); ++It) + for(std::list::iterator It = m_curves.begin(), ItNext=It; It != m_curves.end(); It=ItNext) { + ++ItNext; CCurve& curve = *It; if(!It->IsClosed()) continue; - ItLast = It; - ao.Insert(&curve); + ao.Insert(make_shared(curve)); if(m_set_processing_length_in_split) { CArea::m_processing_done += (m_split_processing_length / m_curves.size()); } + m_curves.erase(It); } - if(ItLast == m_curves.end()) - return; - if(ao.m_top_level) ao.m_top_level->GetArea(*this); - - ++ItLast; - for(std::list::iterator It=m_curves.begin(), ItNext=It; It!=ItLast; It=ItNext) - { - ++ItNext; - if(It->IsClosed()) - m_curves.erase(It); - } } class ZigZag diff --git a/src/Mod/Path/libarea/AreaOrderer.cpp b/src/Mod/Path/libarea/AreaOrderer.cpp index c53c38d97c..d0732d0843 100644 --- a/src/Mod/Path/libarea/AreaOrderer.cpp +++ b/src/Mod/Path/libarea/AreaOrderer.cpp @@ -30,29 +30,27 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AreaOrderer.h" #include "Area.h" +using namespace std; + CAreaOrderer* CInnerCurves::area_orderer = NULL; -CInnerCurves::CInnerCurves(CInnerCurves* pOuter, const CCurve* curve) +CInnerCurves::CInnerCurves(shared_ptr pOuter, shared_ptr curve) +:m_pOuter(pOuter) +,m_curve(curve) { - m_pOuter = pOuter; - m_curve = curve; - m_unite_area = NULL; } CInnerCurves::~CInnerCurves() { - delete m_unite_area; } -void CInnerCurves::Insert(const CCurve* pcurve) +void CInnerCurves::Insert(shared_ptr pcurve) { - std::list outside_of_these; - std::list crossing_these; + std::list > outside_of_these; + std::list > crossing_these; // check all inner curves - for(std::set::iterator It = m_inner_curves.begin(); It != m_inner_curves.end(); It++) - { - CInnerCurves* c = *It; + for(shared_ptr c : m_inner_curves) { switch(GetOverlapType(*pcurve, *(c->m_curve))) { @@ -75,28 +73,24 @@ void CInnerCurves::Insert(const CCurve* pcurve) } // add as a new inner - CInnerCurves* new_item = new CInnerCurves(this, pcurve); + shared_ptr new_item(new CInnerCurves(shared_from_this(), pcurve)); this->m_inner_curves.insert(new_item); - for(std::list::iterator It = outside_of_these.begin(); It != outside_of_these.end(); It++) - { + for(shared_ptr c : outside_of_these) { // move items - CInnerCurves* c = *It; c->m_pOuter = new_item; new_item->m_inner_curves.insert(c); this->m_inner_curves.erase(c); } - for(std::list::iterator It = crossing_these.begin(); It != crossing_these.end(); It++) - { + for(shared_ptr c : crossing_these) { // unite these - CInnerCurves* c = *It; new_item->Unite(c); this->m_inner_curves.erase(c); } } -void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve)const +void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve) { if(use_curve && m_curve) { @@ -104,11 +98,9 @@ void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve)const outside = !outside; } - std::list do_after; + std::list > do_after; - for(std::set::const_iterator It = m_inner_curves.begin(); It != m_inner_curves.end(); It++) - { - const CInnerCurves* c = *It; + for(shared_ptr c: m_inner_curves) { area.m_curves.push_back(*c->m_curve); if(!outside)area.m_curves.back().Reverse(); @@ -116,20 +108,16 @@ void CInnerCurves::GetArea(CArea &area, bool outside, bool use_curve)const else do_after.push_back(c); } - for(std::list::iterator It = do_after.begin(); It != do_after.end(); It++) - { - const CInnerCurves* c = *It; + for(shared_ptr c : do_after) c->GetArea(area, !outside, false); - } } -void CInnerCurves::Unite(const CInnerCurves* c) +void CInnerCurves::Unite(shared_ptr c) { // unite all the curves in c, with this one - CArea* new_area = new CArea(); + shared_ptr new_area(new CArea()); new_area->m_curves.push_back(*m_curve); - delete m_unite_area; - m_unite_area = new_area; + m_unite_area = new_area; CArea a2; c->GetArea(a2); @@ -140,21 +128,21 @@ void CInnerCurves::Unite(const CInnerCurves* c) { CCurve &curve = *It; if(It == m_unite_area->m_curves.begin()) - m_curve = &curve; + m_curve = make_shared(curve); else { if(curve.IsClockwise())curve.Reverse(); - Insert(&curve); + Insert(shared_ptr(new CCurve(curve))); } } } CAreaOrderer::CAreaOrderer() + :m_top_level(make_shared()) { - m_top_level = new CInnerCurves(NULL, NULL); } -void CAreaOrderer::Insert(CCurve* pcurve) +void CAreaOrderer::Insert(shared_ptr pcurve) { CInnerCurves::area_orderer = this; diff --git a/src/Mod/Path/libarea/AreaOrderer.h b/src/Mod/Path/libarea/AreaOrderer.h index 13d78db7d2..3a4d21c854 100644 --- a/src/Mod/Path/libarea/AreaOrderer.h +++ b/src/Mod/Path/libarea/AreaOrderer.h @@ -28,6 +28,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once +#include #include #include @@ -36,30 +37,31 @@ class CCurve; class CAreaOrderer; -class CInnerCurves +class CInnerCurves: public std::enable_shared_from_this { - CInnerCurves* m_pOuter; - const CCurve* m_curve; // always empty if top level - std::set m_inner_curves; - CArea *m_unite_area; // new curves made by uniting are stored here + std::shared_ptr m_pOuter; + std::shared_ptr m_curve; // always empty if top level + std::set > m_inner_curves; + std::shared_ptr m_unite_area; // new curves made by uniting are stored here public: static CAreaOrderer* area_orderer; - CInnerCurves(CInnerCurves* pOuter, const CCurve* curve); + CInnerCurves(std::shared_ptr pOuter, std::shared_ptr curve); + CInnerCurves(){} ~CInnerCurves(); - void Insert(const CCurve* pcurve); - void GetArea(CArea &area, bool outside = true, bool use_curve = true)const; - void Unite(const CInnerCurves* c); + void Insert(std::shared_ptr pcurve); + void GetArea(CArea &area, bool outside = true, bool use_curve = true); + void Unite(std::shared_ptr c); }; class CAreaOrderer { public: - CInnerCurves* m_top_level; + std::shared_ptr m_top_level; CAreaOrderer(); - void Insert(CCurve* pcurve); + void Insert(std::shared_ptr pcurve); CArea ResultArea()const; -}; \ No newline at end of file +}; From 61ccb195263a3237d09ed1dfca98893eff861698 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 20 Jan 2017 18:32:57 +0800 Subject: [PATCH 12/35] Path.Area: make Area.Reorder default to true --- src/Mod/Path/App/AreaParams.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 80f8e325fb..c3493fa9a4 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -51,7 +51,7 @@ ((enum,coplanar,Coplanar,2,"Specifies the way to check coplanar.\n"\ "'Force' will discard non coplaner shapes, but 'Check' only gives warning.",\ (None)(Check)(Force)))\ - ((bool,reorder,Reorder,false,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ + ((bool,reorder,Reorder,true,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ ((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\ "'None' means combin without openeration.\n"\ "'Edges' means separate to edges before Union.\n"\ From 0c545c19759679f374119e993a61098cefe09677 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 21 Jan 2017 18:21:46 +0800 Subject: [PATCH 13/35] Path.Area: various fixes for Path.Area python object --- src/Mod/Path/App/Area.cpp | 2 +- src/Mod/Path/App/Area.h | 2 +- src/Mod/Path/App/AreaParams.h | 2 +- src/Mod/Path/App/AreaPy.xml | 5 ++++- src/Mod/Path/App/AreaPyImp.cpp | 24 ++++++++++++++++-------- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 01cd1e7e96..e90c5620da 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -121,7 +121,7 @@ void Area::setPlane(const TopoDS_Shape &shape) { } BRepLib_FindSurface planeFinder(shape,-1,Standard_True); if (!planeFinder.Found()) - throw Base::ValueError("shape is not coplanar"); + throw Base::ValueError("shape is not planar"); myWorkPlane = shape; myTrsf.SetTransformation(GeomAdaptor_Surface( planeFinder.Surface()).Plane().Position()); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 02b4664bd1..94470a1e2b 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -141,10 +141,10 @@ protected: */ TopoDS_Shape makePocket(); +public: /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) -public: Area(const AreaParams *params = NULL); virtual ~Area(); diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index c3493fa9a4..0da87d16e5 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -79,7 +79,7 @@ * These parameters cooresponds to CAreaPocketParams in libarea * */ #define AREA_PARAMS_POCKET \ - ((enum,mode,PocketMode,1,"Selects the pocket toolpath pattern",(None)(ZigZag)(Offset)(Spiral)(ZigZagOffset)))\ + ((enum,mode,PocketMode,0,"Selects the pocket toolpath pattern",(None)(ZigZag)(Offset)(Spiral)(ZigZagOffset)))\ ((double,tool_radius,ToolRadius,1.0,"Tool radius for pocketing"))\ ((double,extra_offset,PocketExtraOffset,0.0,"Extra offset for pocketing"))\ ((double,stepover,PocketStepover,0.0,"Cutter diameter to step over on each pass. If =0, use ToolRadius."))\ diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 6bc8cd2ecc..b4121fdb9d 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -13,7 +13,10 @@ Delete="true"> - FreeCAD python wrapper of libarea + FreeCAD python wrapper of libarea\n +Path.Area(key=value ...)\n +The constuctor accepts the same parameters as setParams(...) to configure the object +All arguments are optional. diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 7c8b058513..db4f0f3977 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -64,15 +64,15 @@ static const AreaDoc myDocs[] = { { "makeOffset", - "makeOffset(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" - "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" + "makeOffset(index=-1, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" "Make an 2D offset of the shape.\n" + "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_OFFSET), }, { "makePocket", - "makePocket(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" + "makePocket(index=-1, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" "Generate pocket toolpath of the shape.\n" "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), @@ -115,8 +115,9 @@ PyObject *AreaPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Pytho } // constructor method -int AreaPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/) +int AreaPy::PyInit(PyObject* args, PyObject* kwd) { + setParams(args,kwd); return 0; } @@ -132,15 +133,15 @@ PyObject* AreaPy::setPlane(PyObject *args) { PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) { - PyObject *pcObj = Py_True; + PyObject *pcObj = Py_False; short index=-1; static char *kwlist[] = {"index","rebuild", NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj)) - Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); try { if(PyObject_IsTrue(pcObj)) - getAreaPtr()->clean(true); + getAreaPtr()->clean(); return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); } PY_CATCH_OCC; @@ -150,7 +151,12 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds) { PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_OPCODE) PyObject *pcObj; - static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL}; + + //Strangely, PyArg_ParseTupleAndKeywords requires all arguments to be keyword based, + //even non-optional ones? That doesn't make sense in python. Seems only in python 3 + //they added '$' to address that issue. + static char *kwlist[] = {"shape",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_OPCODE), NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), kwlist,&pcObj,PARAM_REF(PARAM_FARG,AREA_PARAMS_OPCODE))) @@ -207,6 +213,8 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) short index = -1; PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_POCKET) + //Override pocket mode default + mode = Area::PocketModeZigZagOffset; if (!PyArg_ParseTupleAndKeywords(args, keywds, "|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, From efee314f646510e9090bc1e6b6da24e2ebf08f81 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 23 Jan 2017 17:26:45 +0800 Subject: [PATCH 14/35] Path.Area: fixed handling of closed edge --- src/Mod/Path/App/Area.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index e90c5620da..a44940f760 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -183,8 +183,9 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, ++skipped; if(force_coplanar) continue; } - add(_areaOpen,BRepBuilderAPI_MakeWire( - TopoDS::Edge(it.Current())).Wire(),trsf,deflection); + TopoDS_Wire wire = BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(); + add(BRep_Tool::IsClosed(wire)?_area:_areaOpen,wire,trsf,deflection); } } From 7e187477a8764fe2eb71266549ab9d458f31df33 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 23 Jan 2017 17:27:58 +0800 Subject: [PATCH 15/35] Path.Area: clean up python binding error handling --- src/Mod/Path/App/AreaPyImp.cpp | 45 +++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index db4f0f3977..df2cfb5e72 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -124,7 +124,7 @@ int AreaPy::PyInit(PyObject* args, PyObject* kwd) PyObject* AreaPy::setPlane(PyObject *args) { PyObject *pcObj; if (!PyArg_ParseTuple(args, "O!", &(Part::TopoShapePy::Type), &pcObj)) - Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + return 0; #define GET_TOPOSHAPE(_p) static_cast(_p)->getTopoShapePtr()->getShape() getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj)); @@ -137,7 +137,7 @@ PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) short index=-1; static char *kwlist[] = {"index","rebuild", NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj)) - Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + return 0; try { if(PyObject_IsTrue(pcObj)) @@ -160,26 +160,31 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|" PARAM_PY_KWDS(AREA_PARAMS_OPCODE), kwlist,&pcObj,PARAM_REF(PARAM_FARG,AREA_PARAMS_OPCODE))) - Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + return 0; if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op); return Py_None; - } - Py::Sequence shapeSeq(pcObj); - for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { - PyObject* item = (*it).ptr(); - if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { - PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); - return 0; + } else if (PyObject_TypeCheck(pcObj, &(PyList_Type)) || + PyObject_TypeCheck(pcObj, &(PyTuple_Type))) { + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } } + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it){ + PyObject* item = (*it).ptr(); + getAreaPtr()->add(GET_TOPOSHAPE(item), + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE)); + } + return Py_None; } - for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it){ - PyObject* item = (*it).ptr(); - getAreaPtr()->add(GET_TOPOSHAPE(item), - PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE)); - } - return Py_None; + + PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'"); + return 0; } PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) @@ -196,7 +201,7 @@ PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds, "|h" PARAM_PY_KWDS(AREA_PARAMS_OFFSET), kwlist, &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET))) - Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + return 0; try { //Expand the variable as function call arguments @@ -219,7 +224,7 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds, "|h" PARAM_PY_KWDS(AREA_PARAMS_POCKET), kwlist, &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET))) - Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters"); + return 0; try { TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, @@ -265,7 +270,7 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) PyObject* AreaPy::getParams(PyObject *args) { if (!PyArg_ParseTuple(args, "")) - Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + return 0; const AreaParams ¶ms =getAreaPtr()->getParams(); @@ -280,7 +285,7 @@ PyObject* AreaPy::getParamsDesc(PyObject *args, PyObject *keywds) PyObject *pcObj = Py_True; static char *kwlist[] = {"as_string", NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds,"|O",kwlist,&pcObj)) - Py_Error(Base::BaseExceptionFreeCADError, "This method accepts no argument"); + return 0; if(PyObject_IsTrue(pcObj)) return PyString_FromString(PARAM_PY_DOC(NAME,AREA_PARAMS_CONF)); From cd4d4f3cf4c5f7c60f3106617e3057c690866215 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 24 Jan 2017 14:37:12 +0800 Subject: [PATCH 16/35] Path.Area: fixed plane finding for edge only shapes --- src/Mod/Path/App/Area.cpp | 40 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index a44940f760..d7343a44ba 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -346,29 +346,23 @@ void Area::build() { if(myWorkPlane.IsNull()) { myShapePlane.Nullify(); for(const Shape &s : myShapes) { - bool haveFace = false; - for(TopExp_Explorer it(s.shape, TopAbs_FACE); it.More(); it.Next()) { - haveFace = true; - BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True); - if (!planeFinder.Found()) - continue; - myShapePlane = it.Current(); - myTrsf.SetTransformation(GeomAdaptor_Surface( - planeFinder.Surface()).Plane().Position()); - break; - } - if(!myShapePlane.IsNull()) break; - if(haveFace) continue; - for(TopExp_Explorer it(s.shape, TopAbs_WIRE); it.More(); it.Next()) { - BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True); - if (!planeFinder.Found()) - continue; - myShapePlane = it.Current(); - myTrsf.SetTransformation(GeomAdaptor_Surface( - planeFinder.Surface()).Plane().Position()); - break; - } - if(!myShapePlane.IsNull()) break; + bool haveShape = false; +#define AREA_CHECK_PLANE(_type) \ + for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\ + haveShape = true;\ + BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\ + if (!planeFinder.Found())\ + continue;\ + myShapePlane = it.Current();\ + myTrsf.SetTransformation(GeomAdaptor_Surface(\ + planeFinder.Surface()).Plane().Position());\ + break;\ + }\ + if(!myShapePlane.IsNull()) break;\ + if(haveShape) continue; + AREA_CHECK_PLANE(FACE) + AREA_CHECK_PLANE(WIRE) + AREA_CHECK_PLANE(EDGE) } if(myShapePlane.IsNull()) From 7d04c2188cdb5002ef58e52094e767d294c7993f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 24 Jan 2017 14:37:52 +0800 Subject: [PATCH 17/35] Path.Area: added Sections python attribute --- src/Mod/Path/App/Area.h | 6 ++++++ src/Mod/Path/App/AreaPy.xml | 6 ++++++ src/Mod/Path/App/AreaPyImp.cpp | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 94470a1e2b..b261ddb471 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -206,6 +206,12 @@ public: /** Get the combined shape */ TopoDS_Shape getShape(int index); + /** Return the number of sections */ + std::size_t getSectionCount() { + build(); + return mySections.size(); + } + /** Add a OCC wire shape to CArea * * \arg \c area: output converted curved object to here diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index b4121fdb9d..3f00663819 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -63,5 +63,11 @@ any operation Get current algorithm parameters as a dictionary. + + + List of sections in this area. + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index df2cfb5e72..4a6cb3fbd8 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -295,6 +295,14 @@ PyObject* AreaPy::getParamsDesc(PyObject *args, PyObject *keywds) return dict; } +Py::List AreaPy::getSections(void) const { + Py::List ret; + Area *area = getAreaPtr(); + for(size_t i=0,count=area->getSectionCount(); igetShape(i))); + return ret; +} + // custom attributes get/set PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const From 16cec19733c55f86582aee926feff32e6416455f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 25 Jan 2017 00:21:45 +0800 Subject: [PATCH 18/35] Path.Area: fixed error handling in Python constructor --- src/Mod/Path/App/AreaPyImp.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 4a6cb3fbd8..8cbabda4d6 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -109,15 +109,17 @@ std::string AreaPy::representation(void) const return str.str(); } -PyObject *AreaPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +PyObject *AreaPy::PyMake(struct _typeobject *, PyObject *args, PyObject *kwd) // Python wrapper { - return new AreaPy(new Area); + std::unique_ptr ret(new AreaPy(new Area)); + if(!ret->setParams(args,kwd)) + return 0; + return ret.release(); } // constructor method -int AreaPy::PyInit(PyObject* args, PyObject* kwd) +int AreaPy::PyInit(PyObject* , PyObject* ) { - setParams(args,kwd); return 0; } @@ -254,8 +256,7 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds, "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) - Py_Error(Base::BaseExceptionFreeCADError, - "Wrong parameters, call getParamsDesc() to get supported params"); + return 0; #define AREA_GET(_param) \ params.PARAM_FNAME(_param) = \ From 01f68f88e177082e964b4c8d2100a5fe6cdca50b Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 25 Jan 2017 00:23:21 +0800 Subject: [PATCH 19/35] Path: added support to get Path.Area from Path::FeatureArea --- src/Mod/Path/App/Area.cpp | 21 ++++++ src/Mod/Path/App/Area.h | 1 + src/Mod/Path/App/CMakeLists.txt | 3 + src/Mod/Path/App/FeatureArea.cpp | 21 ++++-- src/Mod/Path/App/FeatureArea.h | 3 + src/Mod/Path/App/FeatureAreaPy.xml | 30 +++++++++ src/Mod/Path/App/FeatureAreaPyImp.cpp | 93 +++++++++++++++++++++++++++ 7 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 src/Mod/Path/App/FeatureAreaPy.xml create mode 100644 src/Mod/Path/App/FeatureAreaPyImp.cpp diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index d7343a44ba..58516bab98 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -110,6 +110,27 @@ Area::Area(const AreaParams *params) setParams(*params); } +Area::Area(const Area &other, bool deep_copy) +:Base::BaseClass(other) +,myShapes(other.myShapes) +,myTrsf(other.myTrsf) +,myParams(other.myParams) +,myWorkPlane(other.myWorkPlane) +,myHaveFace(other.myHaveFace) +,myHaveSolid(other.myHaveSolid) +,myShapeDone(false) +{ + if(!deep_copy) return; + if(other.myArea) + myArea.reset(new CArea(*other.myArea)); + myShapePlane = other.myShapePlane; + myShape = other.myShape; + myShapeDone = other.myShapeDone; + mySections.reserve(other.mySections.size()); + for(shared_ptr area:mySections) + mySections.push_back(make_shared(*area,true)); +} + Area::~Area() { clean(); } diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index b261ddb471..ca2866149d 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -146,6 +146,7 @@ public: PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) Area(const AreaParams *params = NULL); + Area(const Area &other, bool deep_copy=true); virtual ~Area(); /** Set a working plane diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 14531ff4d8..0816c7d077 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -32,6 +32,7 @@ generate_from_xml(ToolPy) generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) generate_from_xml(AreaPy) +generate_from_xml(FeatureAreaPy) SET(Python_SRCS CommandPy.xml @@ -45,6 +46,8 @@ SET(Python_SRCS FeaturePathCompoundPyImp.cpp AreaPy.xml AreaPyImp.cpp + FeatureAreaPy.xml + FeatureAreaPyImp.cpp ) SET(Mod_SRCS diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index b02d23441e..a8193f5ef4 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -29,6 +29,7 @@ #include #include "FeatureArea.h" +#include "FeatureAreaPy.h" #include #include #include @@ -81,18 +82,18 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_CONF) - Area area(¶ms); + myArea.clean(true); + myArea.setParams(params); TopoDS_Shape workPlane = WorkPlane.getShape().getShape(); - if(!workPlane.IsNull()) - area.setPlane(workPlane); + myArea.setPlane(workPlane); for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { - area.add(static_cast(*it)->Shape.getShape().getShape(), + myArea.add(static_cast(*it)->Shape.getShape().getShape(), PARAM_PROP_ARGS(AREA_PARAMS_OPCODE)); } - this->Shape.setValue(area.getShape(-1)); + this->Shape.setValue(myArea.getShape(-1)); return Part::Feature::execute(); } @@ -108,6 +109,16 @@ short FeatureArea::mustExecute(void) const return Part::Feature::mustExecute(); } +PyObject *FeatureArea::getPyObject() +{ + if (PythonObject.is(Py::_None())){ + // ref counter is set to 1 + PythonObject = Py::Object(new FeatureAreaPy(this),true); + } + return Py::new_reference_to(PythonObject); +} + + // Python Area feature --------------------------------------------------------- namespace App { diff --git a/src/Mod/Path/App/FeatureArea.h b/src/Mod/Path/App/FeatureArea.h index d5e10c2def..5a084f3b18 100644 --- a/src/Mod/Path/App/FeatureArea.h +++ b/src/Mod/Path/App/FeatureArea.h @@ -40,6 +40,8 @@ class PathExport FeatureArea : public Part::Feature PROPERTY_HEADER(Path::FeatureArea); public: + Area myArea; + /// Constructor FeatureArea(void); virtual ~FeatureArea(); @@ -50,6 +52,7 @@ public: } virtual App::DocumentObjectExecReturn *execute(void); virtual short mustExecute(void) const; + virtual PyObject *getPyObject(void); App::PropertyLinkList Sources; Part::PropertyPartShape WorkPlane; diff --git a/src/Mod/Path/App/FeatureAreaPy.xml b/src/Mod/Path/App/FeatureAreaPy.xml new file mode 100644 index 0000000000..e62d94ed16 --- /dev/null +++ b/src/Mod/Path/App/FeatureAreaPy.xml @@ -0,0 +1,30 @@ + + + + + + This class handles Path Area features + + + + Return a copy of the encapsulated Python Area object. + + + + + setParams(key=value...): Convenient function to configure this feature.\n +Same usage as Path.Area.setParams(). This function stores the parameters in the properties. + + + + + + diff --git a/src/Mod/Path/App/FeatureAreaPyImp.cpp b/src/Mod/Path/App/FeatureAreaPyImp.cpp new file mode 100644 index 0000000000..1e76840e2b --- /dev/null +++ b/src/Mod/Path/App/FeatureAreaPyImp.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (c) 2017 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#include +#include "FeatureArea.h" + +// inclusion of the generated files (generated out of FeatureAreaPy.xml) +#include "FeatureAreaPy.h" +#include "FeatureAreaPy.cpp" + +#include "AreaPy.h" + +using namespace Path; + + +// returns a string which represent the object e.g. when printed in python +std::string FeatureAreaPy::representation(void) const +{ + return std::string(""); +} + + +PyObject* FeatureAreaPy::getArea(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + + return new AreaPy(new Area(getFeatureAreaPtr()->myArea)); +} + +PyObject* FeatureAreaPy::setParams(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; + + //Declare variables defined in the NAME field of the CONF parameter list + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); + + FeatureArea *feature = getFeatureAreaPtr(); + +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(feature->PARAM_FNAME(_param).getValue()); + //populate the CONF variables with values in properties + PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + Py_Error(Base::BaseExceptionFreeCADError, + "Wrong parameters, call getParamsDesc() to get supported params"); + +#define AREA_GET(_param) \ + feature->PARAM_FNAME(_param).setValue(\ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param))); + //populate properties with the CONF variables + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + + return Py_None; +} + +PyObject *FeatureAreaPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + + +int FeatureAreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + From be1d0f6151cb06c8c74234cfafe9749282c02af2 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 27 Jan 2017 17:13:16 +0800 Subject: [PATCH 20/35] Path: added Path.fromShapes and Path.sortWires * Path.fromShapes can now convert any number of shapes to Path with optimzied travel distances. It internally uses Path.sortWires to minimize travel distances, and also sort wires by its Z height in case of sectioned wires. * The above python function is impelmented in Path::Area class. * Path::FeatureShape is rewrote to take advantage of these two functions. * Add Path::FeatureAreaView to partially display a Path::FeatureArea's sections. --- src/Mod/Path/App/AppPath.cpp | 2 + src/Mod/Path/App/AppPathPy.cpp | 169 ++++- src/Mod/Path/App/Area.cpp | 582 ++++++++++++---- src/Mod/Path/App/Area.h | 95 ++- src/Mod/Path/App/AreaParams.h | 71 +- src/Mod/Path/App/AreaPy.xml | 5 + src/Mod/Path/App/AreaPyImp.cpp | 75 +- src/Mod/Path/App/FeatureArea.cpp | 95 ++- src/Mod/Path/App/FeatureArea.h | 31 +- src/Mod/Path/App/FeatureAreaPyImp.cpp | 2 +- src/Mod/Path/App/FeaturePathShape.cpp | 105 +-- src/Mod/Path/App/FeaturePathShape.h | 12 +- src/Mod/Path/App/Path.h | 188 ++--- src/Mod/Path/Gui/AppPathGui.cpp | 2 + src/Mod/Path/Gui/Command.cpp | 101 ++- src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/icons/Path-Area-View.svg | 657 ++++++++++++++++++ src/Mod/Path/Gui/ViewProviderArea.cpp | 72 ++ src/Mod/Path/Gui/ViewProviderArea.h | 23 + src/Mod/Path/Gui/ViewProviderPathShape.cpp | 76 +- src/Mod/Path/Gui/ViewProviderPathShape.h | 13 + src/Mod/Path/InitGui.py | 4 +- src/Mod/Path/PathCommands.py | 25 + src/Mod/Path/libarea/Area.cpp | 52 ++ src/Mod/Path/libarea/Area.h | 2 + src/Mod/Path/libarea/AreaClipper.cpp | 11 +- 26 files changed, 2093 insertions(+), 378 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index 4723ff7b1f..3a9e1cd0ea 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -88,4 +88,6 @@ PyMODINIT_FUNC initPath() Path::Area ::init(); Path::FeatureArea ::init(); Path::FeatureAreaPython ::init(); + Path::FeatureAreaView ::init(); + Path::FeatureAreaViewPython ::init(); } diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 79365f0834..36371b4511 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -57,7 +58,45 @@ #include "Path.h" #include "FeaturePath.h" #include "FeaturePathCompound.h" +#include "Area.h" +#define PATH_CATCH catch (Standard_Failure &e) \ + { \ + std::string str; \ + Standard_CString msg = e.GetMessageString(); \ + str += typeid(e).name(); \ + str += " "; \ + if (msg) {str += msg;} \ + else {str += "No OCCT Exception Message";} \ + Base::Console().Error(str.c_str()); \ + PyErr_SetString(Part::PartExceptionOCCError,str.c_str()); \ + } \ + catch(Base::Exception &e) \ + { \ + std::string str; \ + str += "FreeCAD exception thrown ("; \ + str += e.what(); \ + str += ")"; \ + e.ReportException(); \ + PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str());\ + } \ + catch(std::exception &e) \ + { \ + std::string str; \ + str += "STL exception thrown ("; \ + str += e.what(); \ + str += ")"; \ + Base::Console().Error(str.c_str()); \ + PyErr_SetString(Base::BaseExceptionFreeCADError,str.c_str());\ + } \ + catch(const char *e) \ + { \ + PyErr_SetString(Base::BaseExceptionFreeCADError,e); \ + } throw Py::Exception(); + +namespace Part { +extern PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape); +} namespace Path { class Module : public Py::ExtensionModule @@ -79,6 +118,22 @@ public: add_varargs_method("fromShape",&Module::fromShape, "fromShape(Shape): Returns a Path object from a Part Shape" ); + add_keyword_method("fromShapes",&Module::fromShapes, + "fromShapes(shapes, sort=True, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ")\n" + "\nReturns a Path object from a list of shapes\n" + "\n* shapes: input list of shapes.\n" + "\n* start (Vector()): optional start position.\n" + PARAM_PY_DOC(ARG, AREA_PARAMS_PATH) + ); + add_keyword_method("sortWires",&Module::sortWires, + "sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT_WIRES) ")\n" + "\nReturns (wires,end), where 'wires' is sorted accross Z value and with optimized travel distance,\n" + "and 'end' is the ending position of the whole wires\n" + "\n* shapes: input shape list\n" + "\n* start (Vector()): optional start position.\n" + "\n* params (None): optional dictionary for configuring Path.Area internally used to sort the wires.\n" + PARAM_PY_DOC(ARG, AREA_PARAMS_SORT_WIRES) + ); initialize("This module is the Path module."); // register with Python } @@ -261,7 +316,119 @@ private: throw Py::RuntimeError(e.what()); } } - + + Py::Object fromShapes(const Py::Tuple& args, const Py::Dict &kwds) + { + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_PATH) + PyObject *pShapes=NULL; + PyObject *start=NULL; + static char* kwd_list[] = {"shapes", "start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH), kwd_list, + &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH))) + throw Py::Exception(); + + std::list shapes; + if (PyObject_TypeCheck(pShapes, &(Part::TopoShapePy::Type))) + shapes.push_back(static_cast(pShapes)->getTopoShapePtr()->getShape()); + else if (PyObject_TypeCheck(pShapes, &(PyList_Type)) || + PyObject_TypeCheck(pShapes, &(PyTuple_Type))) + { + Py::Sequence shapeSeq(pShapes); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + throw Py::Exception(); + } + shapes.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + } + + gp_Pnt pstart; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); + } + + try { + std::unique_ptr path(new Toolpath); + Area::toPath(*path,shapes,&pstart,PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH)); + return Py::asObject(new PathPy(path.release())); + } PATH_CATCH + } + + Py::Object sortWires(const Py::Tuple& args, const Py::Dict &kwds) + { + AreaParams params; + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT_WIRES) + PyObject *pShapes=NULL; + PyObject *start=NULL; + PyObject *pParams=NULL; + static char* kwd_list[] = {"shapes", "start", "params", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT_WIRES), NULL}; + if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), + "O|O!O!" PARAM_PY_KWDS(AREA_PARAMS_SORT_WIRES), kwd_list, + &pShapes, &(Base::VectorPy::Type), &start, &PyDict_Type, &pParams, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT_WIRES))) + throw Py::Exception(); + + std::list shapes; + if (PyObject_TypeCheck(pShapes, &(Part::TopoShapePy::Type))) + shapes.push_back(static_cast(pShapes)->getTopoShapePtr()->getShape()); + else if (PyObject_TypeCheck(pShapes, &(PyList_Type)) || + PyObject_TypeCheck(pShapes, &(PyTuple_Type))) { + Py::Sequence shapeSeq(pShapes); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + throw Py::Exception(); + } + shapes.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + } + + if(pParams) { + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); + PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) + if (!PyArg_ParseTupleAndKeywords(NULL, pParams, + "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + throw Py::Exception(); + +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + } + + gp_Pnt pstart,pend; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); + } + + try { + std::list wires = Area::sortWires(shapes,¶ms,&pstart, + &pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT_WIRES)); + PyObject *list = PyList_New(0); + for(auto &wire : wires) + PyList_Append(list,Py::new_reference_to( + Part::shape2pyshape(TopoDS::Wire(wire)))); + PyObject *ret = PyTuple_New(2); + PyTuple_SetItem(ret,0,list); + PyTuple_SetItem(ret,1,new Base::VectorPy( + Base::Vector3d(pend.X(),pend.Y(),pend.Z()))); + return Py::asObject(ret); + } PATH_CATCH + } }; PyObject* initModule() diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 58516bab98..51d6908902 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -24,6 +24,8 @@ #ifndef _PreComp_ #endif +#include + #include #include #include @@ -150,6 +152,7 @@ void Area::setPlane(const TopoDS_Shape &shape) { } bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { + if(s1.IsEqual(s2)) return true; TopoDS_Builder builder; TopoDS_Compound comp; builder.MakeCompound(comp); @@ -161,7 +164,7 @@ bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, double deflection, const TopoDS_Shape *plane, bool force_coplanar, - CArea *areaOpen, bool to_edges, bool reorder) + CArea *areaOpen, bool to_edges, bool reorient) { bool haveShape = false; int skipped = 0; @@ -193,7 +196,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, else if(to_edges) { for (TopExp_Explorer it(wire, TopAbs_EDGE); it.More(); it.Next()) add(_areaOpen,BRepBuilderAPI_MakeWire( - TopoDS::Edge(it.Current())).Wire(),trsf,deflection); + TopoDS::Edge(it.Current())).Wire(),trsf,deflection,true); }else add(_areaOpen,wire,trsf,deflection); } @@ -210,7 +213,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, } } - if(reorder) + if(reorient) _area.Reorder(); area.m_curves.splice(area.m_curves.end(),_area.m_curves); if(areaOpen) @@ -221,7 +224,7 @@ int Area::add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf, } void Area::add(CArea &area, const TopoDS_Wire& wire, - const gp_Trsf *trsf, double deflection) + const gp_Trsf *trsf, double deflection, bool to_edges) { CCurve ccurve; BRepTools_WireExplorer xp(trsf?TopoDS::Wire( @@ -239,26 +242,32 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, switch (curve.GetType()) { case GeomAbs_Line: { ccurve.append(CVertex(Point(p.X(),p.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } break; } case GeomAbs_Circle:{ - double first = curve.FirstParameter(); - double last = curve.LastParameter(); - gp_Circ circle = curve.Circle(); - gp_Ax1 axis = circle.Axis(); - int dir = axis.Direction().Z()<0?-1:1; - if(reversed) dir = -dir; - gp_Pnt loc = axis.Location(); - if(fabs(first-last)>M_PI) { - // Split arc(circle) larger than half circle. This is - // translated from PathUtil code. Not sure why it is - // needed. - gp_Pnt mid = curve.Value((last-first)*0.5+first); - ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), + if(!to_edges) { + double first = curve.FirstParameter(); + double last = curve.LastParameter(); + gp_Circ circle = curve.Circle(); + gp_Ax1 axis = circle.Axis(); + int dir = axis.Direction().Z()<0?-1:1; + if(reversed) dir = -dir; + gp_Pnt loc = axis.Location(); + if(fabs(first-last)>M_PI) { + // Split arc(circle) larger than half circle. Because gcode + // can't handle full circle? + gp_Pnt mid = curve.Value((last-first)*0.5+first); + ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), + Point(loc.X(),loc.Y()))); + } + ccurve.append(CVertex(dir,Point(p.X(),p.Y()), Point(loc.X(),loc.Y()))); + break; } - ccurve.append(CVertex(dir,Point(p.X(),p.Y()), - Point(loc.X(),loc.Y()))); - break; + //fall through } default: { // Discretize all other type of curves GCPnts_UniformDeflection discretizer(curve, deflection, @@ -268,16 +277,22 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, for (int i=1; i<=nbPoints; i++) { gp_Pnt pt = discretizer.Value (i); ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } } }else Standard_Failure::Raise("Curve discretization failed"); }} } - if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { - Base::Console().Warning("ccurve not closed\n"); - ccurve.append(ccurve.m_vertices.front()); + if(!to_edges) { + if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { + Base::Console().Warning("ccurve not closed\n"); + ccurve.append(ccurve.m_vertices.front()); + } + area.append(ccurve); } - area.append(ccurve); } @@ -296,10 +311,32 @@ void Area::clean(bool deleteShapes) { } void Area::add(const TopoDS_Shape &shape,short op) { -#define AREA_SRC_OP(_param) op - PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - Q_UNUSED(Operation); +#define AREA_CONVERT_OP \ + ClipperLib::ClipType Operation;\ + switch(op){\ + case OperationUnion:\ + Operation = ClipperLib::ctUnion;\ + break;\ + case OperationDifference:\ + Operation = ClipperLib::ctDifference;\ + break;\ + case OperationIntersection:\ + Operation = ClipperLib::ctIntersection;\ + break;\ + case OperationXor:\ + Operation = ClipperLib::ctXor;\ + break;\ + default:\ + throw Base::ValueError("invalid Operation");\ + } + if(shape.IsNull()) + throw Base::ValueError("null shape"); + + if(op!=OperationCompound) { + AREA_CONVERT_OP; + Q_UNUSED(Operation); + } bool haveSolid = false; for(TopExp_Explorer it(shape, TopAbs_SOLID);it.More();) { haveSolid = true; @@ -313,7 +350,7 @@ void Area::add(const TopoDS_Shape &shape,short op) { myHaveSolid = haveSolid; clean(); - if(myShapes.empty()) + if(op!=OperationCompound && myShapes.empty()) op = OperationUnion; myShapes.push_back(Shape(op,shape)); } @@ -330,7 +367,7 @@ void Area::setParams(const AreaParams ¶ms) { } void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { - if(!myHaveFace) { + if(myParams.Fill==FillAuto && !myHaveFace) { TopExp_Explorer it(shape, TopAbs_FACE); myHaveFace = it.More(); } @@ -342,7 +379,7 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { CArea areaOpen; mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane, myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, - myParams.OpenMode==OpenModeEdges,myParams.Reorder); + myParams.OpenMode==OpenModeEdges,myParams.Reorient); if(areaOpen.m_curves.size()) { if(&area == myArea.get() || myParams.OpenMode == OpenModeNone) myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); @@ -359,7 +396,7 @@ void Area::build() { if(myArea || mySections.size()) return; if(myShapes.empty()) - throw Base::ValueError("Null shape"); + throw Base::ValueError("no shape added"); #define AREA_SRC(_param) myParams.PARAM_FNAME(_param) PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); @@ -368,24 +405,41 @@ void Area::build() { myShapePlane.Nullify(); for(const Shape &s : myShapes) { bool haveShape = false; + bool done = false; + TopoDS_Shape shapePlane; + gp_Trsf trsf; + gp_Ax3 pos; #define AREA_CHECK_PLANE(_type) \ + shapePlane.Nullify();\ for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\ haveShape = true;\ BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\ if (!planeFinder.Found())\ continue;\ - myShapePlane = it.Current();\ - myTrsf.SetTransformation(GeomAdaptor_Surface(\ - planeFinder.Surface()).Plane().Position());\ - break;\ + shapePlane = it.Current();\ + pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();\ + trsf.SetTransformation(pos);\ + gp_Dir dir(pos.Direction());\ + if(fabs(dir.X()) zMax-zMin) + if(count<0 || count*myParams.Stepdown > zMax-zMin) { count = ceil((zMax-zMin)/myParams.Stepdown); + if((count-1)*myParams.Stepdown < zMax-zMin) + ++count; + } for(int i=0;im_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); pending = false; - PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); - areaClip.m_curves.clear(); + if(areaClip.m_curves.size()) { + if(op == OperationCompound) + myArea->m_curves.splice(myArea->m_curves.end(),areaClip.m_curves); + else{ + AREA_CONVERT_OP; + myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); + areaClip.m_curves.clear(); + } + } op=s.op; } addToBuild(op==OperationUnion?*myArea:areaClip,s.shape); @@ -521,17 +582,106 @@ void Area::build() { if(pending){ if(myParams.OpenMode!=OpenModeNone) myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); - PARAM_ENUM_CONVERT(AREA_SRC_OP,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_OPCODE); - myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); + if(op == OperationCompound) + myArea->m_curves.splice(myArea->m_curves.end(),areaClip.m_curves); + else{ + AREA_CONVERT_OP; + myArea->Clip(Operation,&areaClip,SubjectFill,ClipFill); + } } myArea->m_curves.splice(myArea->m_curves.end(),myAreaOpen->m_curves); + //Reassemble wires after explode + if(myParams.Explode) { + std::list edges; + gp_Trsf trsf(myTrsf.Inverted()); + for(const auto &c : myArea->m_curves) { + TopoDS_Wire wire = toShape(c,&trsf); + if(wire.IsNull()) continue; + TopExp_Explorer it(wire, TopAbs_EDGE); + edges.push_back(TopoDS::Edge(it.Current())); + } + Area area(&myParams); + area.myParams.Explode = false; + area.myParams.Coplanar = CoplanarNone; + area.myWorkPlane = myWorkPlane.IsNull()?myShapePlane:myWorkPlane; + area.myTrsf = myTrsf; + while(edges.size()) { + BRepBuilderAPI_MakeWire mkWire; + for(const auto &e : Part::sort_Edges(myParams.Tolerance,edges)) + mkWire.Add(TopoDS::Edge(e)); + area.add(mkWire.Wire(),OperationCompound); + } + area.build(); + myArea = std::move(area.myArea); + } + }catch(...) { clean(); throw; } } +list Area::sortWires(int index, int count, const gp_Pnt *pstart, + gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_MIN_DIST)) +{ + std::list wires; + + build(); + + gp_Pnt pend,pt; + if(pstart) pt = *pstart; + + pt.Transform(TopLoc_Location(myTrsf)); + + if(mySections.size()) { + if(index>=(int)mySections.size()) + throw Base::ValueError("index out of bound"); + TopLoc_Location loc(myTrsf.Inverted()); + if(index<0) { + index = 0; + count = mySections.size(); + } + if(count<=0 || count>(int)mySections.size()) + count = mySections.size(); + for(int i=index;i ws = + mySections[i]->sortWires(-1,0,&pt,&pend, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + for(auto &wire : ws) + wires.push_back(wire.Moved(loc)); + pt = pend; + } + if(_pend) + *_pend = pend.Transformed(loc); + return wires; + } + + if(!myArea || myArea->m_curves.empty()) return wires; + + CArea area(*myArea); + Point p(pt.X(),pt.Y()); + area.ChangeStartToNearest(&p, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + gp_Trsf trsf(myTrsf.Inverted()); + for(const CCurve &c : area.m_curves) { + const TopoDS_Wire &wire = toShape(c,&trsf); + if(wire.IsNull()) continue; + wires.push_back(toShape(c,&trsf)); + } + if(_pend) { + gp_Pnt pend = pt; + if(area.m_curves.size() && + area.m_curves.back().m_vertices.size()) + { + const Point &pt = area.m_curves.back().m_vertices.back().m_p; + pend.SetCoord(pt.x,pt.y,0.0); + } + *_pend = pend.Transformed(TopLoc_Location(trsf)); + } + return wires; +} + TopoDS_Shape Area::toShape(CArea &area, short fill) { gp_Trsf trsf(myTrsf.Inverted()); bool bFill; @@ -559,14 +709,17 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { #define AREA_SECTION(_op,_index,...) do {\ if(mySections.size()) {\ if(_index>=(int)mySections.size())\ - throw Base::ValueError("index out of bound");\ + return TopoDS_Shape();\ TopLoc_Location loc(myTrsf.Inverted());\ if(_index<0) {\ BRep_Builder builder;\ TopoDS_Compound compound;\ builder.MakeCompound(compound);\ - for(shared_ptr area : mySections)\ - builder.Add(compound,area->_op(-1, ## __VA_ARGS__).Moved(loc));\ + for(shared_ptr area : mySections){\ + const TopoDS_Shape &s = area->_op(-1, ## __VA_ARGS__);\ + if(s.IsNull()) continue;\ + builder.Add(compound,s.Moved(loc));\ + }\ return compound;\ }\ return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\ @@ -637,6 +790,7 @@ TopoDS_Shape Area::getShape(int index) { for(shared_ptr area : areas) { if(myParams.Thicken) area->Thicken(myParams.ToolRadius); + const TopoDS_Shape &shape = toShape(*area,fill); builder.Add(compound,toShape(*area,fill)); } builder.Add(compound,areaPocket.makePocket( @@ -781,7 +935,6 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE throw Base::ValueError("unknown poket mode"); } - build(); CAreaConfig conf(myParams); CAreaPocketParams params( tool_radius,extra_offset,stepover,from_center,pm,zig_angle); @@ -802,75 +955,85 @@ static inline bool IsLeft(const gp_Pnt &a, const gp_Pnt &b, const gp_Pnt &c) { return ((b.X() - a.X())*(c.Y() - a.Y()) - (b.Y() - a.Y())*(c.X() - a.X())) > 0; } +TopoDS_Wire Area::toShape(const CCurve &c, const gp_Trsf *trsf) { + BRepBuilderAPI_MakeWire mkWire; + gp_Pnt pstart,pt; + bool first = true; + for(const CVertex &v : c.m_vertices){ + if(first){ + first = false; + pstart = pt = gp_Pnt(v.m_p.x,v.m_p.y,0); + continue; + } + gp_Pnt pnext(v.m_p.x,v.m_p.y,0); + if(pnext.Distance(pt) Precision::Confusion()) { + double d = pt.Distance(pnext); + double q = sqrt(r*r - d*d*0.25); + double x = (pt.X()+pnext.X())*0.5; + double y = (pt.Y()+pnext.Y())*0.5; + double dx = q*(pt.Y()-pnext.Y())/d; + double dy = q*(pnext.X()-pt.X())/d; + gp_Pnt newCenter(x + dx, y + dy,0); + if(IsLeft(pt,pnext,center) != IsLeft(pt,pnext,newCenter)) { + newCenter.SetX(x - dx); + newCenter.SetY(y - dy); + } + Base::Console().Warning( + "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", + r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); + center = newCenter; + } + gp_Ax2 axis(center, gp_Dir(0,0,v.m_type)); + mkWire.Add(BRepBuilderAPI_MakeEdge(gp_Circ(axis,r),pt,pnext).Edge()); + } + pt = pnext; + } + if(!mkWire.IsDone()) + return TopoDS_Wire(); + + if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){ + // This should never happen after changing libarea's + // Point::tolerance to be the same as Precision::Confusion(). + // Just leave it here in case. + BRepAdaptor_Curve curve(mkWire.Edge()); + gp_Pnt p1(curve.Value(curve.FirstParameter())); + gp_Pnt p2(curve.Value(curve.LastParameter())); + std::stringstream str; + str<< "warning: patch open wire type " << + c.m_vertices.back().m_type << endl << + '(' << p1.X() << ',' << p1.Y() << ')' << endl << + '(' << p2.X() << ',' << p2.Y() << ')' << endl << + '(' << pt.X() << ',' << pt.Y() << ')' << endl << + '(' << pstart.X() << ',' < Precision::Confusion()) { - double d = pt.Distance(pnext); - double q = sqrt(r*r - d*d*0.25); - double x = (pt.X()+pnext.X())*0.5; - double y = (pt.Y()+pnext.Y())*0.5; - double dx = q*(pt.Y()-pnext.Y())/d; - double dy = q*(pnext.X()-pt.X())/d; - gp_Pnt newCenter(x + dx, y + dy,0); - if(IsLeft(pt,pnext,center) != IsLeft(pt,pnext,newCenter)) { - newCenter.SetX(x - dx); - newCenter.SetY(y - dy); - } - Base::Console().Warning( - "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", - r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); - center = newCenter; - } - gp_Ax2 axis(center, gp_Dir(0,0,v.m_type)); - mkWire.Add(BRepBuilderAPI_MakeEdge(gp_Circ(axis,r),pt,pnext).Edge()); - } - pt = pnext; - } - if(c.IsClosed() && !BRep_Tool::IsClosed(mkWire.Wire())){ - // This should never happen after changing libarea's - // Point::tolerance to be the same as Precision::Confusion(). - // Just leave it here in case. - BRepAdaptor_Curve curve(mkWire.Edge()); - gp_Pnt p1(curve.Value(curve.FirstParameter())); - gp_Pnt p2(curve.Value(curve.LastParameter())); - std::stringstream str; - str<< "warning: patch open wire" << - c.m_vertices.back().m_type << endl << - '(' << p1.X() << ',' << p1.Y() << ')' << endl << - '(' << p2.X() << ',' << p2.Y() << ')' << endl << - '(' << pt.X() << ',' << pt.Y() << ')' << endl << - '(' << pstart.X() << ',' < Area::sortWires(const std::list &shapes, + const AreaParams *params, const gp_Pnt *_pstart, gp_Pnt *_pend, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT_WIRES)) +{ + std::list wires; + + //Heristic sorting by shape's vertex Z value. For performance's sake, we don't + //perform any planar checking here + std::multimap shape_map; + + for (auto &shape : shapes) { + std::list subshapes; + if(!explode) + subshapes.push_back(shape); + else{ + bool haveShape=false; + for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) { + haveShape=true; + subshapes.push_back(it.Current()); + } + if(!haveShape) { + for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) + subshapes.push_back(it.Current()); + } + } + //Order the shapes by its vertex Z value. + for(auto &s : subshapes) { + bool first=true; + double z=0.0; + for(TopExp_Explorer it(s,TopAbs_VERTEX);it.More();) { + gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())); + if(first || z < p.Z()) { + first = false; + z = p.Z(); + } + if(!top_z) break; + } + shape_map.insert(std::pair(z,s)); + } + } + if(!shape_map.size()) + return wires; + + Area area(params); + //We'll do planar checking here, so disable Area planar check + area.myParams.Coplanar = Area::CoplanarNone; + + gp_Pnt pstart,pend; + if(_pstart) pstart = *_pstart; + TopoDS_Shape plane = shape_map.rbegin()->second; + area.setPlane(plane); + for(auto &item : boost::adaptors::reverse(shape_map)) { + //do planar checking, and sort wires grouped by plane + if(!Area::isCoplanar(plane,item.second)) { + wires.splice(wires.end(),area.sortWires( + -1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST))); + pstart = pend; + area.clean(true); + plane = item.second; + area.setPlane(plane); + } + area.add(item.second,Area::OperationCompound); + } + wires.splice(wires.end(),area.sortWires( + -1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST))); + if(_pend) *_pend = pend; + return wires; +} + +static void addCommand(Toolpath &path, const gp_Pnt &p, + bool g0=false, double g0height=0.0, double clearance=0.0) +{ + Command cmd; + cmd.Name = g0?"G0":"G1"; + if(g0 && fabs(g0height)>Precision::Confusion()) { + cmd.Parameters["Z"] = g0height; + path.addCommand(cmd); + cmd.Parameters["X"] = p.X(); + cmd.Parameters["Y"] = p.Y(); + path.addCommand(cmd); + if(fabs(clearance)>Precision::Confusion()) { + cmd.Parameters["Z"] = p.Z()+clearance; + path.addCommand(cmd); + cmd.Name = "G1"; + } + }else + cmd.Parameters["X"] = p.X(); + cmd.Parameters["Y"] = p.Y(); + cmd.Parameters["Z"] = p.Z(); + path.addCommand(cmd); +} + +static void addCommand(Toolpath &path, + const gp_Pnt &pstart, const gp_Pnt &pend, + const gp_Pnt ¢er, bool clockwise) +{ + Command cmd; + cmd.Name = clockwise?"G2":"G3"; + cmd.Parameters["I"] = center.X()-pstart.X(); + cmd.Parameters["J"] = center.Y()-pstart.Y(); + cmd.Parameters["K"] = center.Z()-pstart.Z(); + cmd.Parameters["X"] = pend.X(); + cmd.Parameters["Y"] = pend.Y(); + cmd.Parameters["Z"] = pend.Z(); + path.addCommand(cmd); +} + +void Area::toPath(Toolpath &path, const std::list &shapes, + const gp_Pnt *pstart, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) +{ + std::list wires; + if(sort) + wires = sortWires(shapes,NULL,pstart); + else{ + for(auto &shape : shapes) { + if (shape.IsNull()) + continue; + bool haveShape=false; + for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) { + haveShape=true; + wires.push_back(it.Current()); + } + if(haveShape) continue; + for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) + wires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire()); + } + } + + if(threshold < Precision::Confusion()) + threshold = Precision::Confusion(); + gp_Pnt plast,p; + if(pstart) plast = *pstart; + bool first = true; + for(const TopoDS_Shape &wire : wires) { + BRepTools_WireExplorer xp(TopoDS::Wire(wire)); + p = BRep_Tool::Pnt(xp.CurrentVertex()); + if(first||(p.Z()>=plast.Z()&&p.Distance(plast)>threshold)) + addCommand(path,p,true,height,clearance); + else + addCommand(path,p); + plast = p; + first = false; + for(;xp.More();xp.Next(),plast=p) { + BRepAdaptor_Curve curve(xp.Current()); + bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); + p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); + + switch (curve.GetType()) { + case GeomAbs_Line: { + addCommand(path,p); + break; + } case GeomAbs_Circle:{ + double first = curve.FirstParameter(); + double last = curve.LastParameter(); + gp_Circ circle = curve.Circle(); + gp_Ax1 axis = circle.Axis(); + bool clockwise = axis.Direction().Z()<0; + if(reversed) clockwise = !clockwise; + gp_Pnt center = axis.Location(); + if(fabs(first-last)>M_PI) { + // Split arc(circle) larger than half circle. + gp_Pnt mid = curve.Value((last-first)*0.5+first); + addCommand(path,plast,mid,center,clockwise); + plast = mid; + } + addCommand(path,plast,p,center,clockwise); + break; + } default: { + // Discretize all other type of curves + GCPnts_UniformDeflection discretizer(curve, deflection, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + int nbPoints = discretizer.NbPoints (); + for (int i=1; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } + }else + Standard_Failure::Raise("Curve discretization failed"); + }} + } + } +} diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index ca2866149d..ef7175532d 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -23,14 +23,19 @@ #ifndef PATH_AREA_H #define PATH_AREA_H +#include +#include +#include #include #include #include #include +#include "Path.h" #include "AreaParams.h" class CArea; +class CCurve; namespace Path { @@ -152,8 +157,12 @@ public: /** Set a working plane * * If no working plane are set, Area will try to find a working plane from - * all the added children shapes. The purpose of this function is in case - * the child shapes are all colinear edges + * individual children faces, wires or edges. By right, we should create a + * compound of all shapes and then findplane on it. However, because we + * supports solid, and also because OCC may hang for a long time if + * something goes a bit off, we opt to find plane on each individual shape. + * If you intend to pass individual edges, you must supply a workplane shape + * manually * * \arg \c shape: a shape defining a working plane */ @@ -177,13 +186,13 @@ public: * If more than one offset is requested, a compound shape is return * containing all offset shapes as wires regardless of \c Fill setting. */ - TopoDS_Shape makeOffset(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); + TopoDS_Shape makeOffset(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_OFFSET)); /** Make a pocket of the combined shape * * See #AREA_PARAMS_POCKET for description of the arguments. */ - TopoDS_Shape makePocket(int index, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); + TopoDS_Shape makePocket(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); /** Config this Area object */ @@ -204,8 +213,11 @@ public: */ void clean(bool deleteShapes=false); - /** Get the combined shape */ - TopoDS_Shape getShape(int index); + /** Get the combined shape + * \arg \c index: index of the section, -1 for all sections. No effect on + * non-sectioned area. + */ + TopoDS_Shape getShape(int index=-1); /** Return the number of sections */ std::size_t getSectionCount() { @@ -219,10 +231,30 @@ public: * \arg \c wire: input wire object * \arg \c trsf: optional transform matrix to transform the wire shape into * XY0 plane. - * \arg \c deflection: for defecting non circular curves + * \arg \c deflection: for discretizing non circular curves + * \arg \c to_edges: if true, discretize all curves, and insert as open + * line segments * */ - static void add(CArea &area, const TopoDS_Wire &wire, - const gp_Trsf *trsf=NULL, double deflection=0.01); + static void add(CArea &area, const TopoDS_Wire &wire, const gp_Trsf *trsf=NULL, + double deflection=0.01, bool to_edges=false); + + /** Output a list or sorted wire with minimize traval distance + * + * \arg \c index: index of the section, -1 for all sections. No effect on + * non-sectioned area. + * \arg \c count: number of the sections to return, <=0 for all sections + * after \c index. No effect on non-sectioned area. + * \arg \c pstart: optional start point + * \arg \c pend: optional output containing the ending point of the returned + * wires + * + * See #AREA_PARAMS_MIN_DIST for other arguments + * + * \return sorted wires + * */ + std::list sortWires(int index=-1, int count=0, + const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_MIN_DIST)); /** Add a OCC generic shape to CArea * @@ -236,7 +268,7 @@ public: * \arg \c areaOpen: for collecting open curves. If not supplied, open * curves are added to \c area * \arg \c to_edges: separate open wires to individual edges - * \arg \c reorder: reorder closed wires for wire only shape + * \arg \c reorient: reorient closed wires for wire only shape * * \return Returns the number of non coplaner. Planar testing only happens * if \c plane is supplied @@ -244,7 +276,7 @@ public: static int add(CArea &area, const TopoDS_Shape &shape, const gp_Trsf *trsf=NULL, double deflection=0.01,const TopoDS_Shape *plane = NULL, bool force_coplanar=true, CArea *areaOpen=NULL, bool to_edges=false, - bool reorder=true); + bool reorient=true); /** Convert curves in CArea into an OCC shape * @@ -256,7 +288,48 @@ public: static TopoDS_Shape toShape(const CArea &area, bool fill, const gp_Trsf *trsf=NULL); + /** Convert a single curve into an OCC wire + * + * \arg \c curve: input curve object + * \arg \c trsf: optional transform matrix to transform the shape back into + * its original position. + * */ + static TopoDS_Wire toShape(const CCurve &curve, const gp_Trsf *trsf=NULL); + + /** Check if two OCC shape is coplanar */ static bool isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2); + + /** Group shapes by their plane, and return a list of sorted wires + * + * The output wires is ordered by its occupied plane, and sorted to + * minimize traval distance + * + * \arg \c shapes: input list of shapes. + * \arg \c params: optional Area parameters for the Area object internally + * used for sorting + * \arg \c pstart: optional start point + * \arg \c pend: optional output containing the ending point of the returned + * maybe broken if the algorithm see fits. + * + * See #AREA_PARAMS_SORT_WIRES for other arguments + * + * \return sorted wires + */ + static std::list sortWires(const std::list &shapes, + const AreaParams *params = NULL, const gp_Pnt *pstart=NULL, + gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT_WIRES)); + + /** Convert a list of wires to gcode + * + * \arg \c path: output toolpath + * \arg \c shapes: input list of shapes + * \arg \c pstart: output start point, + * + * See #AREA_PARAMS_PATH for other arguments + */ + static void toPath(Toolpath &path, const std::list &shapes, + const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 0da87d16e5..0f38db3a6a 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -44,20 +44,32 @@ ((enum2,clip_fill,ClipFill,0,\ "ClipperLib clip fill type. \nSee https://goo.gl/5pYQQP",AREA_CLIPPER_FILL_TYPE)) +/** Deflection parameter */ +#define AREA_PARAMS_DEFLECTION \ + ((double,deflection,Deflection,0.01,\ + "Deflection for non circular curve discretization. It also also used for\n"\ + "discretizing circular wires when you 'Explode' the shape for wire operations")) + /** Base parameters */ #define AREA_PARAMS_BASE \ ((enum,fill,Fill,2,"Fill the output wires to make a face. \n"\ "Auto means make a face if any of the children has a face.",(None)(Face)(Auto)))\ - ((enum,coplanar,Coplanar,2,"Specifies the way to check coplanar.\n"\ - "'Force' will discard non coplaner shapes, but 'Check' only gives warning.",\ - (None)(Check)(Force)))\ - ((bool,reorder,Reorder,true,"Re-orient closed wires in wire only shapes so that inner wires become holes."))\ - ((enum,open_mode,OpenMode,0,"Specify how to handle open wires.\n"\ - "'None' means combin without openeration.\n"\ - "'Edges' means separate to edges before Union.\n"\ - "ClipperLib seems to have an urge to close open wires.",(None)(Union)(Edges)))\ - AREA_PARAMS_CLIPPER_FILL \ - ((double,deflection,Deflection,0.01,"Deflection for non circular curve discretization")) + ((enum,coplanar,Coplanar,2,\ + "Specifies the way to check coplanar. 'Force' will discard non coplaner shapes,\n"\ + "but 'Check' only gives warning.",(None)(Check)(Force)))\ + ((bool,reorient,Reorient,true,\ + "Re-orient closed wires in wire only shapes so that inner wires become holes."))\ + ((bool,explode,Explode,false,\ + "If true, Area will explode the first shape into disconnected open edges, \n"\ + "with all curves discretized, so that later operations like 'Difference' \n"\ + "behave like wire cutting. Without exploding, 'Difference' in ClipperLib\n"\ + "behave like face cutting."))\ + ((enum,open_mode,OpenMode,0,\ + "Specify how to handle open wires. 'None' means combin without openeration.\n"\ + "'Edges' means separate to edges before Union. ClipperLib seems to have an.\n"\ + "urge to close open wires.",(None)(Union)(Edges)))\ + AREA_PARAMS_DEFLECTION \ + AREA_PARAMS_CLIPPER_FILL /** libarea algorithm option parameters */ #define AREA_PARAMS_CAREA \ @@ -72,7 +84,8 @@ ((short,min_arc_points,MinArcPoints,4,"Minimum segments for arc discretization"))\ ((short,max_arc_points,MaxArcPoints,100,"Maximum segments for arc discretization"))\ ((double,clipper_scale,ClipperScale,10000.0,\ - "ClipperLib operate on intergers. This is the scale factor to \nconvert floating points.")) + "ClipperLib operate on intergers. This is the scale factor to convert\n"\ + "floating points.")) /** Pocket parameters * @@ -91,8 +104,10 @@ /** Operation code */ #define AREA_PARAMS_OPCODE \ - ((enum2,op,Operation,0,"Boolean operation",\ - (Union)(Difference)(Intersection)(Xor),(ClipperLib::ClipType,ClipperLib::ct))) + ((enum,op,Operation,0,\ + "Boolean operation. For the first four operation, see https://goo.gl/Gj8RUu.\n"\ + "'Compound' means no operation, normal used to do Area.sortWires().",\ + (Union)(Difference)(Intersection)(Xor)(Compound))) /** Offset parameters */ #define AREA_PARAMS_OFFSET \ @@ -124,6 +139,36 @@ ((double,round_precision,RoundPreceision,0.0,\ "Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh")) +/** Area path generation parameters */ +#define AREA_PARAMS_PATH \ + ((bool, sort, SortShape, true, \ + "Whether to sort the shapes to optimize travel distance. You can customize wire\n"\ + "sorting by calling sortWires() manually."))\ + ((double, threshold, RetractThreshold, 0.0,\ + "If two wire's end points are separated within this threshold, they are consider\n"\ + "as connected. You may want to set this to the tool diameter to keep the tool down."))\ + ((double, height, RetractHeight, 0.0,"Tool retraction absolute height"))\ + ((double, clearance, Clearance, 0.0,\ + "When return from last retraction, this gives the pause height relative to the Z\n"\ + "value of the next move"))\ + AREA_PARAMS_DEFLECTION + +#define AREA_PARAMS_MIN_DIST \ + ((double, min_dist, MinDistance, 1.0, \ + "minimum distance for the generate new wires. Wires maybe broken if the\n"\ + "algorithm see fits."))\ + +/** Area wire sorting parameters */ +#define AREA_PARAMS_SORT_WIRES \ + ((bool, explode, Explode, true,\ + "If ture, the input shape will be exploded into wires before doing planar checking.\n"\ + "Otherwise, and whole shape is considered to be in one plane without checking."))\ + ((bool, top_z, TopZ, false, \ + "If ture, the planes is ordered by the first the shapes first vertex Z value.\n"\ + "Otherwise, by the highest Z of all of its vertexes."))\ + AREA_PARAMS_MIN_DIST + + /** Group of all Area configuration parameters except CArea's*/ #define AREA_PARAMS_AREA \ AREA_PARAMS_BASE \ diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 3f00663819..062b82935b 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -58,6 +58,11 @@ any operation + + + + + Get current algorithm parameters as a dictionary. diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 8cbabda4d6..75e370d2d0 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -22,10 +22,10 @@ #include "PreCompiled.h" -#include #include +#include -#include "Mod/Path/App/Area.h" +#include "Area.h" // inclusion of the generated files (generated out of AreaPy.xml) #include "AreaPy.h" @@ -77,6 +77,16 @@ static const AreaDoc myDocs[] = { "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), }, + { + "sortWires", + + "sortWires(index=-1, count=0, start=Vector()" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_MIN_DIST) "):\n" + "Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint of the wires.\n" + "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" + "\n* count (0): the number of sections to return. <=0 means all sections starting from index.\n" + "\n* start (Vector()): a vector specifies the start point.\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_MIN_DIST), + }, }; struct AreaPyDoc { @@ -141,12 +151,42 @@ PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds,"|hO",kwlist,&pcObj)) return 0; - try { - if(PyObject_IsTrue(pcObj)) - getAreaPtr()->clean(); - return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); + if(PyObject_IsTrue(pcObj)) + getAreaPtr()->clean(); + return Py::new_reference_to(Part::shape2pyshape(getAreaPtr()->getShape(index))); +} + +PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){ + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_MIN_DIST) + short index = -1; + short count = 0; + PyObject *start = NULL; + + static char *kwlist[] = {"index","count","start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_MIN_DIST), NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|hhO!" PARAM_PY_KWDS(AREA_PARAMS_MIN_DIST), + kwlist,&index,&count,&(Base::VectorPy::Type),&start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_MIN_DIST))) + return 0; + + gp_Pnt pstart,pend; + if(start) { + Base::Vector3d vec = static_cast(start)->value(); + pstart.SetCoord(vec.x, vec.y, vec.z); } - PY_CATCH_OCC; + std::list wires = getAreaPtr()->sortWires( + index,count,&pstart,&pend, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + PyObject *list = PyList_New(0); + for(auto &wire : wires) + PyList_Append(list,Py::new_reference_to( + Part::shape2pyshape(TopoDS::Wire(wire)))); + PyObject *ret = PyTuple_New(2); + PyTuple_SetItem(ret,0,list); + PyTuple_SetItem(ret,1,new Base::VectorPy( + Base::Vector3d(pend.X(),pend.Y(),pend.Z()))); + return ret; } PyObject* AreaPy::add(PyObject *args, PyObject *keywds) @@ -205,13 +245,10 @@ PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_OFFSET))) return 0; - try { - //Expand the variable as function call arguments - TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index, - PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); - return Py::new_reference_to(Part::shape2pyshape(resultShape)); - } - PY_CATCH_OCC; + //Expand the variable as function call arguments + TopoDS_Shape resultShape = getAreaPtr()->makeOffset(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OFFSET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); } PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) @@ -228,15 +265,11 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) &index,PARAM_REF(PARAM_FARG,AREA_PARAMS_POCKET))) return 0; - try { - TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, - PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); - return Py::new_reference_to(Part::shape2pyshape(resultShape)); - } - PY_CATCH_OCC; + TopoDS_Shape resultShape = getAreaPtr()->makePocket(index, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); + return Py::new_reference_to(Part::shape2pyshape(resultShape)); } - PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) { static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index a8193f5ef4..5c65c8f791 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -41,6 +41,7 @@ PROPERTY_SOURCE(Path::FeatureArea, Part::Feature) PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_ALL) FeatureArea::FeatureArea() + :myBuild(false) { ADD_PROPERTY(Sources,(0)); ADD_PROPERTY(WorkPlane,(TopoDS_Shape())); @@ -62,6 +63,14 @@ FeatureArea::~FeatureArea() { } +Area &FeatureArea::getArea() { + if(!myBuild) { + myBuild = true; + execute(); + } + return myArea; +} + App::DocumentObjectExecReturn *FeatureArea::execute(void) { std::vector links = Sources.getValues(); @@ -97,6 +106,17 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) return Part::Feature::execute(); } +std::list FeatureArea::getShapes() { + std::list shapes; + Area &area = getArea(); + if(area.getSectionCount()) { + for(int i=0;i<(int)area.getSectionCount();++i) + shapes.push_back(area.getShape(i)); + }else + shapes.push_back(area.getShape()); + return shapes; +} + short FeatureArea::mustExecute(void) const { if (Sources.isTouched()) @@ -119,14 +139,85 @@ PyObject *FeatureArea::getPyObject() } -// Python Area feature --------------------------------------------------------- +// FeatureAreaView ------------------------------------------------------------- +// +PROPERTY_SOURCE(Path::FeatureAreaView, Part::Feature) + +FeatureAreaView::FeatureAreaView() +{ + ADD_PROPERTY(Source,(0)); + ADD_PROPERTY_TYPE(SectionIndex,(0),"Section",App::Prop_None,"The start index of the section to show"); + ADD_PROPERTY_TYPE(SectionCount,(1),"Section",App::Prop_None,"Number of sections to show"); +} + +std::list FeatureAreaView::getShapes() { + std::list shapes; + App::DocumentObject* pObj = Source.getValue(); + if (!pObj) return shapes; + if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId())) + return shapes; + Area &area = static_cast(pObj)->getArea(); + + int index=SectionIndex.getValue(),count=SectionCount.getValue(); + if(index<0) { + index = 0; + count = area.getSectionCount(); + } + if(index >= (int)area.getSectionCount()) + return shapes; + + if(count<=0) + count = (int)area.getSectionCount(); + if(count==1) + shapes.push_back(area.getShape(index)); + else{ + count += index; + if(count>(int)area.getSectionCount()) + count = area.getSectionCount(); + for(int i=index;iisDerivedFrom(FeatureArea::getClassTypeId())) + return new App::DocumentObjectExecReturn("Linked object is not a FeatureArea"); + + std::list shapes = getShapes(); + if(shapes.empty()) + Shape.setValue(TopoDS_Shape()); + else if(shapes.size()==1) { + Shape.setValue(shapes.front()); + }else{ + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(auto &shape : shapes) + builder.Add(compound,shape); + Shape.setValue(compound); + } + + return Part::Feature::execute(); +} + +// Python feature --------------------------------------------------------- namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaPython, Path::FeatureArea) +PROPERTY_SOURCE_TEMPLATE(Path::FeatureAreaViewPython, Path::FeatureAreaView) template<> const char* Path::FeatureAreaPython::getViewProviderName(void) const { - return "PathGui::ViewProviderArea"; + return "PathGui::ViewProviderAreaPython"; +} +template<> const char* Path::FeatureAreaViewPython::getViewProviderName(void) const { + return "PathGui::ViewProviderAreaViewPython"; } /// @endcond diff --git a/src/Mod/Path/App/FeatureArea.h b/src/Mod/Path/App/FeatureArea.h index 5a084f3b18..7af914653b 100644 --- a/src/Mod/Path/App/FeatureArea.h +++ b/src/Mod/Path/App/FeatureArea.h @@ -40,12 +40,13 @@ class PathExport FeatureArea : public Part::Feature PROPERTY_HEADER(Path::FeatureArea); public: - Area myArea; - /// Constructor FeatureArea(void); virtual ~FeatureArea(); + Area &getArea(); + std::list getShapes(); + /// returns the type name of the ViewProvider virtual const char* getViewProviderName(void) const { return "PathGui::ViewProviderArea"; @@ -58,10 +59,36 @@ public: Part::PropertyPartShape WorkPlane; PARAM_PROP_DECLARE(AREA_PARAMS_ALL) + +private: + bool myBuild; + Area myArea; }; typedef App::FeaturePythonT FeatureAreaPython; +class PathExport FeatureAreaView : public Part::Feature +{ + PROPERTY_HEADER(Path::FeatureAreaView); + +public: + /// Constructor + FeatureAreaView(void); + + std::list getShapes(); + + virtual const char* getViewProviderName(void) const { + return "PathGui::ViewProviderAreaView"; + } + virtual App::DocumentObjectExecReturn *execute(void); + + App::PropertyLink Source; + App::PropertyInteger SectionIndex; + App::PropertyInteger SectionCount; +}; + +typedef App::FeaturePythonT FeatureAreaViewPython; + } //namespace Path diff --git a/src/Mod/Path/App/FeatureAreaPyImp.cpp b/src/Mod/Path/App/FeatureAreaPyImp.cpp index 1e76840e2b..755db26899 100644 --- a/src/Mod/Path/App/FeatureAreaPyImp.cpp +++ b/src/Mod/Path/App/FeatureAreaPyImp.cpp @@ -46,7 +46,7 @@ PyObject* FeatureAreaPy::getArea(PyObject *args) if (!PyArg_ParseTuple(args, "")) return NULL; - return new AreaPy(new Area(getFeatureAreaPtr()->myArea)); + return new AreaPy(new Area(getFeatureAreaPtr()->getArea())); } PyObject* FeatureAreaPy::setParams(PyObject *args, PyObject *keywds) diff --git a/src/Mod/Path/App/FeaturePathShape.cpp b/src/Mod/Path/App/FeaturePathShape.cpp index dcdd7e4fe3..5f148b487a 100644 --- a/src/Mod/Path/App/FeaturePathShape.cpp +++ b/src/Mod/Path/App/FeaturePathShape.cpp @@ -19,7 +19,9 @@ * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ - +/* + * Copyright (c) 2017 Zheng, Lei + */ #include "PreCompiled.h" @@ -32,20 +34,17 @@ #include #include #include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include "FeatureArea.h" using namespace Path; @@ -54,79 +53,39 @@ PROPERTY_SOURCE(Path::FeatureShape, Path::Feature) FeatureShape::FeatureShape() { - ADD_PROPERTY_TYPE(Shape,(TopoDS_Shape()),"Path",App::Prop_None,"The shape data of this feature"); + ADD_PROPERTY(Sources,(0)); + ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position"); + PARAM_PROP_ADD("Path",AREA_PARAMS_PATH); } FeatureShape::~FeatureShape() { } -short FeatureShape::mustExecute(void) const -{ - return Path::Feature::mustExecute(); -} - App::DocumentObjectExecReturn *FeatureShape::execute(void) { - TopoDS_Shape shape = Shape.getValue(); - if (!shape.IsNull()) { - if (shape.ShapeType() == TopAbs_WIRE) { - Path::Toolpath result; - bool first = true; - Base::Placement last; - - TopExp_Explorer ExpEdges (shape,TopAbs_EDGE); - while (ExpEdges.More()) { - const TopoDS_Edge& edge = TopoDS::Edge(ExpEdges.Current()); - TopExp_Explorer ExpVerts(edge,TopAbs_VERTEX); - bool vfirst = true; - while (ExpVerts.More()) { - const TopoDS_Vertex& vert = TopoDS::Vertex(ExpVerts.Current()); - gp_Pnt pnt = BRep_Tool::Pnt(vert); - Base::Placement tpl; - tpl.setPosition(Base::Vector3d(pnt.X(),pnt.Y(),pnt.Z())); - if (first) { - // add first point as a G0 move - Path::Command cmd; - std::ostringstream ctxt; - ctxt << "G0 X" << tpl.getPosition().x << " Y" << tpl.getPosition().y << " Z" << tpl.getPosition().z; - cmd.setFromGCode(ctxt.str()); - result.addCommand(cmd); - first = false; - vfirst = false; - } else { - if (vfirst) - vfirst = false; - else { - Path::Command cmd; - cmd.setFromPlacement(tpl); - - // write arc data if needed - BRepAdaptor_Curve adapt(edge); - if (adapt.GetType() == GeomAbs_Circle) { - gp_Circ circ = adapt.Circle(); - gp_Pnt c = circ.Location(); - bool clockwise = false; - gp_Dir n = circ.Axis().Direction(); - if (n.Z() < 0) - clockwise = true; - Base::Vector3d center = Base::Vector3d(c.X(),c.Y(),c.Z()); - // center coords must be relative to last point - center -= last.getPosition(); - cmd.setCenter(center,clockwise); - } - result.addCommand(cmd); - } - } - ExpVerts.Next(); - last = tpl; - } - ExpEdges.Next(); - } - - Path.setValue(result); - } + Toolpath path; + std::vector links = Sources.getValues(); + if (links.empty()) { + Path.setValue(path); + return new App::DocumentObjectExecReturn("No shapes linked"); } + + const Base::Vector3d &v = StartPoint.getValue(); + gp_Pnt pstart(v.x,v.y,v.z); + + std::list shapes; + for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { + if (!(*it && (*it)->isDerivedFrom(Part::Feature::getClassTypeId()))) + continue; + const TopoDS_Shape &shape = static_cast(*it)->Shape.getShape().getShape(); + if (shape.IsNull()) + continue; + shapes.push_back(shape); + } + Area::toPath(path,shapes,&pstart,PARAM_PROP_ARGS(AREA_PARAMS_PATH)); + + Path.setValue(path); return App::DocumentObject::StdReturn; } diff --git a/src/Mod/Path/App/FeaturePathShape.h b/src/Mod/Path/App/FeaturePathShape.h index 23648abcb3..7478a25899 100644 --- a/src/Mod/Path/App/FeaturePathShape.h +++ b/src/Mod/Path/App/FeaturePathShape.h @@ -19,6 +19,9 @@ * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ +/* + * Copyright (c) 2017 Zheng, Lei + */ #ifndef PATH_FeaturePathShape_H @@ -26,14 +29,13 @@ #include #include -#include #include #include #include "Mod/Part/App/PropertyTopoShape.h" -#include "Path.h" #include "PropertyPath.h" #include "FeaturePath.h" +#include "Area.h" namespace Path { @@ -47,12 +49,14 @@ public: FeatureShape(void); virtual ~FeatureShape(); - Part::PropertyPartShape Shape; + // Part::PropertyPartShape Shape; + App::PropertyLinkList Sources; + App::PropertyVector StartPoint; + PARAM_PROP_DECLARE(AREA_PARAMS_PATH) //@{ /// recalculate the feature virtual App::DocumentObjectExecReturn *execute(void); - virtual short mustExecute(void) const; //@} /// returns the type name of the ViewProvider diff --git a/src/Mod/Path/App/Path.h b/src/Mod/Path/App/Path.h index db7643f16e..75f460b51c 100644 --- a/src/Mod/Path/App/Path.h +++ b/src/Mod/Path/App/Path.h @@ -1,95 +1,95 @@ -/*************************************************************************** - * Copyright (c) Yorik van Havre (yorik@uncreated.net) 2014 * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ +/*************************************************************************** + * Copyright (c) Yorik van Havre (yorik@uncreated.net) 2014 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef PATH_Path_H +#define PATH_Path_H - -#ifndef PATH_Path_H -#define PATH_Path_H - -#include "Command.h" -//#include "Mod/Robot/App/kdl_cp/path_composite.hpp" -//#include "Mod/Robot/App/kdl_cp/frames_io.hpp" -#include -#include - -namespace Path -{ - - /** The representation of a CNC Toolpath */ - - class PathExport Toolpath : public Base::Persistence - { - TYPESYSTEM_HEADER(); - - public: - Toolpath(); - Toolpath(const Toolpath&); - ~Toolpath(); - - Toolpath &operator=(const Toolpath&); - - // from base class - virtual unsigned int getMemSize (void) const; - virtual void Save (Base::Writer &/*writer*/) const; - virtual void Restore(Base::XMLReader &/*reader*/); - void SaveDocFile (Base::Writer &writer) const; - void RestoreDocFile(Base::Reader &reader); - - // interface - void clear(void); // clears the internal data - void addCommand(const Command &Cmd); // adds a command at the end - void insertCommand(const Command &Cmd, int); // inserts a command - void deleteCommand(int); // deletes a command - double getLength(void); // return the Length (mm) of the Path - void recalculate(void); // recalculates the points - void setFromGCode(const std::string); // sets the path from the contents of the given GCode string - std::string toGCode(void) const; // gets a gcode string representation from the Path - - // shortcut functions - unsigned int getSize(void) const{return vpcCommands.size();} - const std::vector &getCommands(void)const{return vpcCommands;} - const Command &getCommand(unsigned int pos)const {return *vpcCommands[pos];} - - protected: - std::vector vpcCommands; - //KDL::Path_Composite *pcPath; - - /* - inline KDL::Frame toFrame(const Base::Placement &To){ - return KDL::Frame(KDL::Rotation::Quaternion(To.getRotation()[0], - To.getRotation()[1], - To.getRotation()[2], - To.getRotation()[3]), - KDL::Vector(To.getPosition()[0], - To.getPosition()[1], - To.getPosition()[2])); - } - inline Base::Placement toPlacement(const KDL::Frame &To){ - double x,y,z,w; - To.M.GetQuaternion(x,y,z,w); - return Base::Placement(Base::Vector3d(To.p[0],To.p[1],To.p[2]),Base::Rotation(x,y,z,w)); - } */ - }; - -} //namespace Path - - -#endif // PATH_Path_H +#include "Command.h" +//#include "Mod/Robot/App/kdl_cp/path_composite.hpp" +//#include "Mod/Robot/App/kdl_cp/frames_io.hpp" +#include +#include + +namespace Path +{ + + /** The representation of a CNC Toolpath */ + + class PathExport Toolpath : public Base::Persistence + { + TYPESYSTEM_HEADER(); + + public: + Toolpath(); + Toolpath(const Toolpath&); + ~Toolpath(); + + Toolpath &operator=(const Toolpath&); + + // from base class + virtual unsigned int getMemSize (void) const; + virtual void Save (Base::Writer &/*writer*/) const; + virtual void Restore(Base::XMLReader &/*reader*/); + void SaveDocFile (Base::Writer &writer) const; + void RestoreDocFile(Base::Reader &reader); + + // interface + void clear(void); // clears the internal data + void addCommand(const Command &Cmd); // adds a command at the end + void insertCommand(const Command &Cmd, int); // inserts a command + void deleteCommand(int); // deletes a command + double getLength(void); // return the Length (mm) of the Path + void recalculate(void); // recalculates the points + void setFromGCode(const std::string); // sets the path from the contents of the given GCode string + std::string toGCode(void) const; // gets a gcode string representation from the Path + + // shortcut functions + unsigned int getSize(void) const{return vpcCommands.size();} + const std::vector &getCommands(void)const{return vpcCommands;} + const Command &getCommand(unsigned int pos)const {return *vpcCommands[pos];} + + protected: + std::vector vpcCommands; + //KDL::Path_Composite *pcPath; + + /* + inline KDL::Frame toFrame(const Base::Placement &To){ + return KDL::Frame(KDL::Rotation::Quaternion(To.getRotation()[0], + To.getRotation()[1], + To.getRotation()[2], + To.getRotation()[3]), + KDL::Vector(To.getPosition()[0], + To.getPosition()[1], + To.getPosition()[2])); + } + inline Base::Placement toPlacement(const KDL::Frame &To){ + double x,y,z,w; + To.M.GetQuaternion(x,y,z,w); + return Base::Placement(Base::Vector3d(To.p[0],To.p[1],To.p[2]),Base::Rotation(x,y,z,w)); + } */ + }; + +} //namespace Path + + +#endif // PATH_Path_H diff --git a/src/Mod/Path/Gui/AppPathGui.cpp b/src/Mod/Path/Gui/AppPathGui.cpp index 1e4d93faf4..70a9186e12 100644 --- a/src/Mod/Path/Gui/AppPathGui.cpp +++ b/src/Mod/Path/Gui/AppPathGui.cpp @@ -80,6 +80,8 @@ PyMODINIT_FUNC initPathGui() PathGui::ViewProviderPathPython ::init(); PathGui::ViewProviderArea ::init(); PathGui::ViewProviderAreaPython ::init(); + PathGui::ViewProviderAreaView ::init(); + PathGui::ViewProviderAreaViewPython ::init(); // add resources and reloads the translators loadPathResource(); diff --git a/src/Mod/Path/Gui/Command.cpp b/src/Mod/Path/Gui/Command.cpp index 07528e0c91..2f7a125b9b 100644 --- a/src/Mod/Path/Gui/Command.cpp +++ b/src/Mod/Path/Gui/Command.cpp @@ -68,39 +68,55 @@ void CmdPathArea::activated(int iMsg) Q_UNUSED(iMsg); std::list cmds; std::ostringstream sources; + std::string areaName; + bool addView = true; for(const Gui::SelectionObject &selObj : getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) { const Part::Feature *pcObj = static_cast(selObj.getObject()); const std::vector &subnames = selObj.getSubNames(); + if(addView && areaName.size()) addView = false; + if(subnames.empty()) { + if(addView && pcObj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId())) + areaName = pcObj->getNameInDocument(); sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; continue; } for(const std::string &name : subnames) { - if(!name.compare(0,4,"Face") && - !name.compare(0,4,"Edge")) - { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { Base::Console().Error("Selected shape is not 2D\n"); return; } - int index = atoi(name.substr(4).c_str()); - std::ostringstream subname; subname << pcObj->getNameInDocument() << '_' << name; std::string sub_fname = getUniqueObjectName(subname.str().c_str()); std::ostringstream cmd; cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname << - "').Shape = FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ".Shape." << - name.substr(0,4) << "s[" << index-1 << ']'; + "').Shape = PathCommands.findShape(FreeCAD.activeDocument()." << + pcObj->getNameInDocument() << ".Shape,'" << name << "'"; + if(!name.compare(0,4,"Edge")) + cmd << ",'Wires'"; + cmd << ')'; cmds.push_back(cmd.str()); sources << "FreeCAD.activeDocument()." << sub_fname << ","; } } + if(addView && areaName.size()) { + std::string FeatName = getUniqueObjectName("FeatureAreaView"); + openCommand("Create Path Area View"); + doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureAreaView','%s')",FeatName.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().%s.Source = FreeCAD.activeDocument().%s", + FeatName.c_str(),areaName.c_str()); + commitCommand(); + updateActive(); + return; + } std::string FeatName = getUniqueObjectName("FeatureArea"); openCommand("Create Path Area"); + doCommand(Doc,"import PathCommands"); for(const std::string &cmd : cmds) doCommand(Doc,cmd.c_str()); doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureArea','%s')",FeatName.c_str()); @@ -171,17 +187,12 @@ void CmdPathAreaWorkplane::activated(int iMsg) } for(const std::string &name : subnames) { - if(!name.compare(0,4,"Face") && - !name.compare(0,4,"Edge")) - { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { Base::Console().Error("Selected shape is not 2D\n"); return; } - - int index = atoi(name.substr(4).c_str()); - std::ostringstream subname; - subname << planeSubname << '.' << name.substr(0,4) << "s[" << index-1 << ']'; + subname << planeSubname << ",'" << name << "','Wires'"; planeSubname = subname.str(); } } @@ -195,10 +206,10 @@ void CmdPathAreaWorkplane::activated(int iMsg) } openCommand("Select Workplane for Path Area"); - doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = FreeCAD.activeDocument().%s", - areaName.c_str(),planeSubname.c_str()); + doCommand(Doc,"import PathCommands"); + doCommand(Doc,"FreeCAD.activeDocument().%s.WorkPlane = PathCommands.findShape(" + "FreeCAD.activeDocument().%s)", areaName.c_str(),planeSubname.c_str()); doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = True",areaName.c_str()); - // doCommand(Doc,"FreeCAD.activeDocument().%s.ViewObject.Visibility = False",planeName.c_str()); commitCommand(); updateActive(); } @@ -284,24 +295,48 @@ CmdPathShape::CmdPathShape() void CmdPathShape::activated(int iMsg) { Q_UNUSED(iMsg); - std::vector Sel = getSelection().getSelection(); - if (Sel.size() == 1) { - if (Sel[0].pObject->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { - Part::Feature *pcPartObject = static_cast(Sel[0].pObject); - std::string FeatName = getUniqueObjectName("PathShape"); - openCommand("Create Path Compound"); - doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureShape','%s')",FeatName.c_str()); - doCommand(Doc,"FreeCAD.activeDocument().%s.Shape = FreeCAD.activeDocument().%s.Shape.copy()",FeatName.c_str(),pcPartObject->getNameInDocument()); - commitCommand(); - updateActive(); - } else { - Base::Console().Error("Exactly one shape object must be selected\n"); - return; + std::list cmds; + std::ostringstream sources; + for(const Gui::SelectionObject &selObj : + getSelection().getSelectionEx(NULL, Part::Feature::getClassTypeId())) + { + const Part::Feature *pcObj = static_cast(selObj.getObject()); + const std::vector &subnames = selObj.getSubNames(); + if(subnames.empty()) { + sources << "FreeCAD.activeDocument()." << pcObj->getNameInDocument() << ","; + continue; + } + for(const std::string &name : subnames) { + if(name.compare(0,4,"Face") && name.compare(0,4,"Edge")) { + Base::Console().Warning("Ignored shape %s %s\n", + pcObj->getNameInDocument(), name.c_str()); + continue; + } + + std::ostringstream subname; + subname << pcObj->getNameInDocument() << '_' << name; + std::string sub_fname = getUniqueObjectName(subname.str().c_str()); + + std::ostringstream cmd; + cmd << "FreeCAD.activeDocument().addObject('Part::Feature','" << sub_fname << + "').Shape = PathCommands.findShape(FreeCAD.activeDocument()." << + pcObj->getNameInDocument() << ".Shape,'" << name << "'"; + if(!name.compare(0,4,"Edge")) + cmd << ",'Wires'"; + cmd << ')'; + cmds.push_back(cmd.str()); + sources << "FreeCAD.activeDocument()." << sub_fname << ","; } - } else { - Base::Console().Error("Exactly one shape object must be selected\n"); - return; } + std::string FeatName = getUniqueObjectName("PathShape"); + openCommand("Create Path Shape"); + doCommand(Doc,"import PathCommands"); + for(const std::string &cmd : cmds) + doCommand(Doc,cmd.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().addObject('Path::FeatureShape','%s')",FeatName.c_str()); + doCommand(Doc,"FreeCAD.activeDocument().%s.Sources = [ %s ]",FeatName.c_str(),sources.str().c_str()); + commitCommand(); + updateActive(); } bool CmdPathShape::isActive(void) diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 4ad6350181..8600ec94e9 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -47,6 +47,7 @@ icons/Path-Toolpath.svg icons/Path-ToolTable.svg icons/Path-Area.svg + icons/Path-Area-View.svg icons/Path-Area-Workplane.svg icons/preferences-path.svg panels/ContourEdit.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg b/src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg new file mode 100644 index 0000000000..504106dcb5 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Area-View.svg @@ -0,0 +1,657 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-FaceProfile + 2016-01-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path- + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/ViewProviderArea.cpp b/src/Mod/Path/Gui/ViewProviderArea.cpp index 2a3057f203..040bacbea9 100644 --- a/src/Mod/Path/Gui/ViewProviderArea.cpp +++ b/src/Mod/Path/Gui/ViewProviderArea.cpp @@ -115,11 +115,83 @@ bool ViewProviderArea::onDelete(const std::vector &) // Python object ----------------------------------------------------------------------- +PROPERTY_SOURCE(PathGui::ViewProviderAreaView, PartGui::ViewProviderPlaneParametric) + +ViewProviderAreaView::ViewProviderAreaView() +{ + sPixmap = "Path-Area-View.svg"; +} + +ViewProviderAreaView::~ViewProviderAreaView() +{ +} + +std::vector ViewProviderAreaView::claimChildren(void) const +{ + std::vector ret; + Path::FeatureAreaView* feature = static_cast(getObject()); + if(feature->Source.getValue()) + ret.push_back(feature->Source.getValue()); + return ret; +} + +bool ViewProviderAreaView::canDragObjects() const +{ + return true; +} + +bool ViewProviderAreaView::canDragObject(App::DocumentObject* obj) const +{ + return obj && obj->getTypeId().isDerivedFrom(Path::FeatureArea::getClassTypeId()); +} + +void ViewProviderAreaView::dragObject(App::DocumentObject* ) +{ + Path::FeatureAreaView* feature = static_cast(getObject()); + feature->Source.setValue(NULL); +} + +bool ViewProviderAreaView::canDropObjects() const +{ + return true; +} + +bool ViewProviderAreaView::canDropObject(App::DocumentObject* obj) const +{ + return canDragObject(obj); +} + +void ViewProviderAreaView::dropObject(App::DocumentObject* obj) +{ + Path::FeatureAreaView* feature = static_cast(getObject()); + feature->Source.setValue(obj); +} + +void ViewProviderAreaView::updateData(const App::Property* prop) +{ + PartGui::ViewProviderPlaneParametric::updateData(prop); + if (prop->getTypeId() == App::PropertyLink::getClassTypeId()) + Gui::Application::Instance->hideViewProvider( + static_cast(prop)->getValue()); +} + +bool ViewProviderAreaView::onDelete(const std::vector &) +{ + Path::FeatureAreaView* feature = static_cast(getObject()); + Gui::Application::Instance->showViewProvider(feature->Source.getValue()); + return true; +} + +// Python object ----------------------------------------------------------------------- + namespace Gui { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaPython, PathGui::ViewProviderArea) +PROPERTY_SOURCE_TEMPLATE(PathGui::ViewProviderAreaViewPython, PathGui::ViewProviderAreaView) /// @endcond // explicit template instantiation template class PathGuiExport ViewProviderPythonFeatureT; +template class PathGuiExport ViewProviderPythonFeatureT; } + diff --git a/src/Mod/Path/Gui/ViewProviderArea.h b/src/Mod/Path/Gui/ViewProviderArea.h index 7d3b43313c..f149d856b0 100644 --- a/src/Mod/Path/Gui/ViewProviderArea.h +++ b/src/Mod/Path/Gui/ViewProviderArea.h @@ -54,6 +54,29 @@ public: typedef Gui::ViewProviderPythonFeatureT ViewProviderAreaPython; + +class PathGuiExport ViewProviderAreaView : public PartGui::ViewProviderPlaneParametric +{ + PROPERTY_HEADER(PathGui::ViewProviderAreaView); + +public: + ViewProviderAreaView(); + virtual ~ViewProviderAreaView(); + virtual std::vector claimChildren(void) const; + virtual void updateData(const App::Property*); + virtual bool onDelete(const std::vector &); + + /// drag and drop + virtual bool canDragObjects() const; + virtual bool canDragObject(App::DocumentObject*) const; + virtual void dragObject(App::DocumentObject*); + virtual bool canDropObjects() const; + virtual bool canDropObject(App::DocumentObject*) const; + virtual void dropObject(App::DocumentObject*); +}; + +typedef Gui::ViewProviderPythonFeatureT ViewProviderAreaViewPython; + } //namespace PathGui diff --git a/src/Mod/Path/Gui/ViewProviderPathShape.cpp b/src/Mod/Path/Gui/ViewProviderPathShape.cpp index 52f4c71a08..81b21372eb 100644 --- a/src/Mod/Path/Gui/ViewProviderPathShape.cpp +++ b/src/Mod/Path/Gui/ViewProviderPathShape.cpp @@ -26,8 +26,11 @@ #ifndef _PreComp_ #endif -#include "ViewProviderPathShape.h" #include +#include +#include +#include +#include "ViewProviderPathShape.h" using namespace Gui; using namespace PathGui; @@ -38,3 +41,74 @@ QIcon ViewProviderPathShape::getIcon() const { return Gui::BitmapFactory().pixmap("Path-Shape"); } + +std::vector ViewProviderPathShape::claimChildren(void) const +{ + return std::vector( + static_cast(getObject())->Sources.getValues()); +} + +bool ViewProviderPathShape::canDragObjects() const +{ + return true; +} + +bool ViewProviderPathShape::canDragObject(App::DocumentObject* obj) const +{ + return obj && obj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()); +} + +void ViewProviderPathShape::dragObject(App::DocumentObject* obj) +{ + Path::FeatureShape *feature = static_cast(getObject()); + std::vector sources = feature->Sources.getValues(); + for (std::vector::iterator it = sources.begin(); it != sources.end(); ++it) { + if (*it == obj) { + sources.erase(it); + feature->Sources.setValues(sources); + break; + } + } +} + +bool ViewProviderPathShape::canDropObjects() const +{ + return true; +} + +bool ViewProviderPathShape::canDropObject(App::DocumentObject* obj) const +{ + return canDragObject(obj); +} + +void ViewProviderPathShape::dropObject(App::DocumentObject* obj) +{ + Path::FeatureShape *feature = static_cast(getObject()); + std::vector sources = feature->Sources.getValues(); + sources.push_back(obj); + feature->Sources.setValues(sources); +} + +void ViewProviderPathShape::updateData(const App::Property* prop) +{ + PathGui::ViewProviderPath::updateData(prop); + if (prop->getTypeId() == App::PropertyLinkList::getClassTypeId()) { + std::vector pShapes = static_cast(prop)->getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->hideViewProvider(*it); + } + } +} + +bool ViewProviderPathShape::onDelete(const std::vector &) +{ + // get the input shapes + Path::FeatureShape *feature = static_cast(getObject()); + std::vector pShapes =feature->Sources.getValues(); + for (std::vector::iterator it = pShapes.begin(); it != pShapes.end(); ++it) { + if (*it) + Gui::Application::Instance->showViewProvider(*it); + } + return true; +} diff --git a/src/Mod/Path/Gui/ViewProviderPathShape.h b/src/Mod/Path/Gui/ViewProviderPathShape.h index 8e525dcf46..7aeb950835 100644 --- a/src/Mod/Path/Gui/ViewProviderPathShape.h +++ b/src/Mod/Path/Gui/ViewProviderPathShape.h @@ -34,6 +34,19 @@ class PathGuiExport ViewProviderPathShape: public ViewProviderPath PROPERTY_HEADER(PathGui::ViewProviderPathShape); public: + + /// grouping handling + virtual std::vector claimChildren(void) const; + virtual void updateData(const App::Property*); + virtual bool onDelete(const std::vector &); + + /// drag and drop + virtual bool canDragObjects() const; + virtual bool canDragObject(App::DocumentObject*) const; + virtual void dragObject(App::DocumentObject*); + virtual bool canDropObjects() const; + virtual bool canDropObject(App::DocumentObject*) const; + virtual void dropObject(App::DocumentObject*); QIcon getIcon(void) const; }; diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index be7d46a6a0..11a1120d19 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -81,12 +81,12 @@ class PathWorkbench (Workbench): # build commands list projcmdlist = ["Path_Job", "Path_Post", "Path_Inspect", "Path_Sanity"] toolcmdlist = ["Path_ToolLibraryEdit", "Path_LoadTool"] - prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"] + prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_Shape"] twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"] threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"] - extracmdlist = ["Path_SelectLoop", "Path_Area", "Path_Area_Workplane"] + extracmdlist = ["Path_SelectLoop", "Path_Shape", "Path_Area", "Path_Area_Workplane"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index 128f9b19a9..d837c98be2 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -78,3 +78,28 @@ class _CommandSelectLoop: if FreeCAD.GuiUp: FreeCADGui.addCommand('Path_SelectLoop',_CommandSelectLoop()) +def findShape(shape,subname=None,subtype=None): + '''To find a higher oder shape containing the subshape with subname. + E.g. to find the wire containing 'Edge1' in shape, + findShape(shape,'Edge1','Wires') + ''' + if not subname: + return shape + ret = shape.getElement(subname) + if not subtype or not ret or ret.isNull(): + return ret; + if subname.startswith('Face'): + tp = 'Faces' + elif subname.startswith('Edge'): + tp = 'Edges' + elif subname.startswith('Vertex'): + tp = 'Vertex' + else: + return ret + for obj in getattr(shape,subtype): + for sobj in getattr(obj,tp): + if sobj.isEqual(ret): + return obj + return ret + + diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index 2ed88cfaad..7323ce3f5f 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -73,6 +73,58 @@ Point CArea::NearestPoint(const Point& p)const return best_point; } +void CArea::ChangeStartToNearest(const Point *point, double min_dist) { + + for(std::list::const_iterator It=m_curves.begin(),ItNext=It; + It != m_curves.end(); It=ItNext) + { + ++ItNext; + if(It->m_vertices.size()<=1) + m_curves.erase(It); + } + + if(m_curves.empty()) return; + + std::list curves; + Point p; + if(point) p =*point; + if(min_dist < Point::tolerance) + min_dist = Point::tolerance; + + while(m_curves.size()) { + std::list::iterator It=m_curves.begin(); + std::list::iterator ItBest=It++; + Point best_point = ItBest->NearestPoint(p); + double best_dist = p.dist(best_point); + for(; It != m_curves.end(); ++It) + { + const CCurve& curve = *It; + Point near_point = curve.NearestPoint(p); + double dist = near_point.dist(p); + if(dist < best_dist) + { + best_dist = dist; + best_point = near_point; + ItBest = It; + } + } + if(ItBest->IsClosed()) { + ItBest->ChangeStart(best_point); + }else if(ItBest->m_vertices.back().m_p.dist(best_point)<=min_dist) { + ItBest->Reverse(); + }else if(ItBest->m_vertices.front().m_p.dist(best_point)>min_dist) { + ItBest->Break(best_point); + m_curves.push_back(*ItBest); + m_curves.back().ChangeEnd(best_point); + ItBest->ChangeStart(best_point); + } + curves.splice(curves.end(),m_curves,ItBest); + p = curves.back().m_vertices.back().m_p; + } + m_curves.splice(m_curves.end(),curves); +} + + void CArea::GetBox(CBox2D &box) { for(std::list::iterator It = m_curves.begin(); It != m_curves.end(); It++) diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index 7a6634ac51..ca9a641527 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -85,6 +85,8 @@ public: void CurveIntersections(const CCurve& curve, std::list &pts)const; void InsideCurves(const CCurve& curve, std::list &curves_inside)const; + void ChangeStartToNearest(const Point *point=NULL, double min_dist=1.0); + //Avoid outside direct accessing static member variable because of Windows DLL issue #define CAREA_PARAM_DECLARE(_type,_name) \ static _type get_##_name();\ diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index bcb7ccd427..a4f4f0e2e9 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -468,16 +468,23 @@ void CArea::Offset(double inwards_value) void CArea::PopulateClipper(Clipper &c, PolyType type) const { + int skipped = 0; for (std::list::const_iterator It = m_curves.begin(); It != m_curves.end(); It++) { const CCurve &curve = *It; bool closed = curve.IsClosed(); - if(type == ptClip && !closed) - continue; + if(!closed) { + if(type == ptClip){ + ++skipped; + continue; + } + } TPolygon p; MakePoly(curve, p, false); c.AddPath(p, type, closed); } + if(skipped) + std::cout << "libarea: warning skipped " << skipped << " open wires" << std::endl; } void CArea::Clip(ClipType op, const CArea *a, From 8dadcbe196375d4276a25e355849aec5027f88d4 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 28 Jan 2017 01:19:09 +0800 Subject: [PATCH 21/35] Path: fixed FeatureAreaView negative SectionIndex behavior --- src/Mod/Path/App/FeatureArea.cpp | 38 ++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index 5c65c8f791..97379ed806 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -146,8 +146,8 @@ PROPERTY_SOURCE(Path::FeatureAreaView, Part::Feature) FeatureAreaView::FeatureAreaView() { ADD_PROPERTY(Source,(0)); - ADD_PROPERTY_TYPE(SectionIndex,(0),"Section",App::Prop_None,"The start index of the section to show"); - ADD_PROPERTY_TYPE(SectionCount,(1),"Section",App::Prop_None,"Number of sections to show"); + ADD_PROPERTY_TYPE(SectionIndex,(0),"Section",App::Prop_None,"The start index of the section to show, negative value for reverse index from bottom"); + ADD_PROPERTY_TYPE(SectionCount,(1),"Section",App::Prop_None,"Number of sections to show, 0 to show all section starting from SectionIndex"); } std::list FeatureAreaView::getShapes() { @@ -158,25 +158,29 @@ std::list FeatureAreaView::getShapes() { return shapes; Area &area = static_cast(pObj)->getArea(); + if(!area.getSectionCount()) { + shapes.push_back(area.getShape()); + return shapes; + } + int index=SectionIndex.getValue(),count=SectionCount.getValue(); if(index<0) { - index = 0; - count = area.getSectionCount(); - } - if(index >= (int)area.getSectionCount()) + index += ((int)area.getSectionCount()); + if(index<0) return shapes; + if(count<=0 || index+1-count<0) { + count = index+1; + index = 0; + }else + index -= count-1; + }else if(index >= (int)area.getSectionCount()) return shapes; - if(count<=0) - count = (int)area.getSectionCount(); - if(count==1) - shapes.push_back(area.getShape(index)); - else{ - count += index; - if(count>(int)area.getSectionCount()) - count = area.getSectionCount(); - for(int i=index;i(int)area.getSectionCount()) + count = area.getSectionCount(); + for(int i=index;i Date: Sat, 28 Jan 2017 01:22:09 +0800 Subject: [PATCH 22/35] Path: fixed Path.h line ending issue --- src/Mod/Path/App/Path.h | 184 ++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/src/Mod/Path/App/Path.h b/src/Mod/Path/App/Path.h index 75f460b51c..7ce9e602a1 100644 --- a/src/Mod/Path/App/Path.h +++ b/src/Mod/Path/App/Path.h @@ -1,95 +1,95 @@ -/*************************************************************************** - * Copyright (c) Yorik van Havre (yorik@uncreated.net) 2014 * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#ifndef PATH_Path_H -#define PATH_Path_H +/*************************************************************************** + * Copyright (c) Yorik van Havre (yorik@uncreated.net) 2014 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef PATH_Path_H +#define PATH_Path_H #include "Command.h" -//#include "Mod/Robot/App/kdl_cp/path_composite.hpp" +//#include "Mod/Robot/App/kdl_cp/path_composite.hpp" //#include "Mod/Robot/App/kdl_cp/frames_io.hpp" -#include -#include - -namespace Path -{ - - /** The representation of a CNC Toolpath */ - - class PathExport Toolpath : public Base::Persistence - { - TYPESYSTEM_HEADER(); - - public: - Toolpath(); - Toolpath(const Toolpath&); - ~Toolpath(); - - Toolpath &operator=(const Toolpath&); - - // from base class - virtual unsigned int getMemSize (void) const; - virtual void Save (Base::Writer &/*writer*/) const; - virtual void Restore(Base::XMLReader &/*reader*/); - void SaveDocFile (Base::Writer &writer) const; - void RestoreDocFile(Base::Reader &reader); - - // interface - void clear(void); // clears the internal data - void addCommand(const Command &Cmd); // adds a command at the end - void insertCommand(const Command &Cmd, int); // inserts a command - void deleteCommand(int); // deletes a command - double getLength(void); // return the Length (mm) of the Path - void recalculate(void); // recalculates the points - void setFromGCode(const std::string); // sets the path from the contents of the given GCode string - std::string toGCode(void) const; // gets a gcode string representation from the Path - - // shortcut functions - unsigned int getSize(void) const{return vpcCommands.size();} - const std::vector &getCommands(void)const{return vpcCommands;} - const Command &getCommand(unsigned int pos)const {return *vpcCommands[pos];} - - protected: - std::vector vpcCommands; - //KDL::Path_Composite *pcPath; - - /* - inline KDL::Frame toFrame(const Base::Placement &To){ - return KDL::Frame(KDL::Rotation::Quaternion(To.getRotation()[0], - To.getRotation()[1], - To.getRotation()[2], - To.getRotation()[3]), - KDL::Vector(To.getPosition()[0], - To.getPosition()[1], - To.getPosition()[2])); - } - inline Base::Placement toPlacement(const KDL::Frame &To){ - double x,y,z,w; - To.M.GetQuaternion(x,y,z,w); - return Base::Placement(Base::Vector3d(To.p[0],To.p[1],To.p[2]),Base::Rotation(x,y,z,w)); - } */ - }; - -} //namespace Path - - -#endif // PATH_Path_H +#include +#include + +namespace Path +{ + + /** The representation of a CNC Toolpath */ + + class PathExport Toolpath : public Base::Persistence + { + TYPESYSTEM_HEADER(); + + public: + Toolpath(); + Toolpath(const Toolpath&); + ~Toolpath(); + + Toolpath &operator=(const Toolpath&); + + // from base class + virtual unsigned int getMemSize (void) const; + virtual void Save (Base::Writer &/*writer*/) const; + virtual void Restore(Base::XMLReader &/*reader*/); + void SaveDocFile (Base::Writer &writer) const; + void RestoreDocFile(Base::Reader &reader); + + // interface + void clear(void); // clears the internal data + void addCommand(const Command &Cmd); // adds a command at the end + void insertCommand(const Command &Cmd, int); // inserts a command + void deleteCommand(int); // deletes a command + double getLength(void); // return the Length (mm) of the Path + void recalculate(void); // recalculates the points + void setFromGCode(const std::string); // sets the path from the contents of the given GCode string + std::string toGCode(void) const; // gets a gcode string representation from the Path + + // shortcut functions + unsigned int getSize(void) const{return vpcCommands.size();} + const std::vector &getCommands(void)const{return vpcCommands;} + const Command &getCommand(unsigned int pos)const {return *vpcCommands[pos];} + + protected: + std::vector vpcCommands; + //KDL::Path_Composite *pcPath; + + /* + inline KDL::Frame toFrame(const Base::Placement &To){ + return KDL::Frame(KDL::Rotation::Quaternion(To.getRotation()[0], + To.getRotation()[1], + To.getRotation()[2], + To.getRotation()[3]), + KDL::Vector(To.getPosition()[0], + To.getPosition()[1], + To.getPosition()[2])); + } + inline Base::Placement toPlacement(const KDL::Frame &To){ + double x,y,z,w; + To.M.GetQuaternion(x,y,z,w); + return Base::Placement(Base::Vector3d(To.p[0],To.p[1],To.p[2]),Base::Rotation(x,y,z,w)); + } */ + }; + +} //namespace Path + + +#endif // PATH_Path_H From 6ad617938d841dcad2c49221301223ebe40afbd6 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 28 Jan 2017 01:30:13 +0800 Subject: [PATCH 23/35] libarea: fixed linux build --- src/Mod/Path/libarea/Area.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index 7323ce3f5f..c39bde2e49 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -75,7 +75,7 @@ Point CArea::NearestPoint(const Point& p)const void CArea::ChangeStartToNearest(const Point *point, double min_dist) { - for(std::list::const_iterator It=m_curves.begin(),ItNext=It; + for(std::list::iterator It=m_curves.begin(),ItNext=It; It != m_curves.end(); It=ItNext) { ++ItNext; From b19fe515f578130fec09838687c5ee980d8c3c96 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 29 Jan 2017 18:24:01 +0800 Subject: [PATCH 24/35] Path.Area: fixed non-circular curve handling, etc. * Fixed non-circular curve orientation handling * Section changed to use Part::CrossSection, because it seems BRepAlgoAPI_Section has trouble with non-circular curves (LastParameter becomes huge which causes discretization to produce many many points) * Exposed Area.makeSections() to section with variable heights * Modified Area.setPlane() to accept non-planar shape * Exposed Area.getPlane() to obtain current workplane * Exposed Area.Shapes attribute to return the current holding children shape. --- src/Mod/Path/App/Area.cpp | 577 ++++++++++++++++++++----------- src/Mod/Path/App/Area.h | 86 ++++- src/Mod/Path/App/AreaParams.h | 19 +- src/Mod/Path/App/AreaPy.xml | 25 +- src/Mod/Path/App/AreaPyImp.cpp | 82 ++++- src/Mod/Path/App/FeatureArea.cpp | 4 +- 6 files changed, 564 insertions(+), 229 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 51d6908902..602789709e 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ #endif +#include "boost/date_time/posix_time/posix_time.hpp" #include #include @@ -52,22 +53,27 @@ #include #include #include -#include +#include #include #include #include -#include +#include #include #include #include +#include +#include #include +#include #include +#include #include "Area.h" #include "../libarea/Area.h" using namespace Path; +using namespace boost::posix_time; CAreaParams::CAreaParams() :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA) @@ -78,17 +84,17 @@ AreaParams::AreaParams() {} CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs) - :params(p) { - // Arc fitting is lossy. we shall reduce the number of unecessary fit - if(noFitArcs) - params.FitArcs=false; - #define AREA_CONF_SAVE_AND_APPLY(_param) \ PARAM_FNAME(_param) = BOOST_PP_CAT(CArea::get_,PARAM_FARG(_param))();\ - BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(params.PARAM_FNAME(_param)); + BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(p.PARAM_FNAME(_param)); PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY,AREA_PARAMS_CAREA) + + // Arc fitting is lossy. we shall reduce the number of unecessary fit + if(noFitArcs) + CArea::set_fit_arcs(false); + } CAreaConfig::~CAreaConfig() { @@ -117,15 +123,16 @@ Area::Area(const Area &other, bool deep_copy) ,myShapes(other.myShapes) ,myTrsf(other.myTrsf) ,myParams(other.myParams) +,myShapePlane(other.myShapePlane) ,myWorkPlane(other.myWorkPlane) ,myHaveFace(other.myHaveFace) ,myHaveSolid(other.myHaveSolid) ,myShapeDone(false) { - if(!deep_copy) return; + if(!deep_copy || !other.isBuilt()) + return; if(other.myArea) myArea.reset(new CArea(*other.myArea)); - myShapePlane = other.myShapePlane; myShape = other.myShape; myShapeDone = other.myShapeDone; mySections.reserve(other.mySections.size()); @@ -142,16 +149,18 @@ void Area::setPlane(const TopoDS_Shape &shape) { myWorkPlane.Nullify(); return; } - BRepLib_FindSurface planeFinder(shape,-1,Standard_True); - if (!planeFinder.Found()) + TopoDS_Shape plane; + gp_Trsf trsf; + findPlane(shape,plane,trsf); + if (plane.IsNull()) throw Base::ValueError("shape is not planar"); - myWorkPlane = shape; - myTrsf.SetTransformation(GeomAdaptor_Surface( - planeFinder.Surface()).Plane().Position()); + myWorkPlane = plane; + myTrsf = trsf; clean(); } bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { + if(s1.IsNull() || s2.IsNull()) return false; if(s1.IsEqual(s2)) return true; TopoDS_Builder builder; TopoDS_Compound comp; @@ -234,7 +243,8 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, ccurve.append(CVertex(Point(p.X(),p.Y()))); for (;xp.More();xp.Next()) { - BRepAdaptor_Curve curve(xp.Current()); + const TopoDS_Edge &edge = TopoDS::Edge(xp.Current()); + BRepAdaptor_Curve curve(edge); bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); @@ -270,18 +280,31 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, //fall through } default: { // Discretize all other type of curves - GCPnts_UniformDeflection discretizer(curve, deflection, + GCPnts_QuasiUniformDeflection discretizer(curve, deflection, curve.FirstParameter(), curve.LastParameter()); - if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + if (discretizer.IsDone () && discretizer.NbPoints () > 1) { int nbPoints = discretizer.NbPoints (); - for (int i=1; i<=nbPoints; i++) { - gp_Pnt pt = discretizer.Value (i); - ccurve.append(CVertex(Point(pt.X(),pt.Y()))); - if(to_edges) { - area.append(ccurve); - ccurve.m_vertices.pop_front(); + //strangly OCC discretizer points are one-based, not zero-based, why? + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = discretizer.Value (i); + ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } } } + }else Standard_Failure::Raise("Curve discretization failed"); }} @@ -371,13 +394,10 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { TopExp_Explorer it(shape, TopAbs_FACE); myHaveFace = it.More(); } - const TopoDS_Shape *plane; - if(myParams.Coplanar == CoplanarNone) - plane = NULL; - else - plane = myWorkPlane.IsNull()?&myShapePlane:&myWorkPlane; + TopoDS_Shape plane = getPlane(); CArea areaOpen; - mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane, + mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection, + myParams.Coplanar==CoplanarNone?NULL:&plane, myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, myParams.OpenMode==OpenModeEdges,myParams.Reorient); if(areaOpen.m_curves.size()) { @@ -392,8 +412,272 @@ namespace Part { extern PartExport std::list sort_Edges(double tol3d, std::list& edges); } +void Area::explode(const TopoDS_Shape &shape) { + const TopoDS_Shape &plane = getPlane(); + bool haveShape = false; + for(TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){ + ++mySkippedShapes; + if(myParams.Coplanar == CoplanarForce) + continue; + } + for(TopExp_Explorer itw(it.Current(), TopAbs_WIRE); itw.More(); itw.Next()) { + for(BRepTools_WireExplorer xp(TopoDS::Wire(itw.Current()));xp.More();xp.Next()) + add(*myArea,BRepBuilderAPI_MakeWire( + TopoDS::Edge(xp.Current())).Wire(),&myTrsf,myParams.Deflection,true); + } + } + if(haveShape) return; + for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){ + ++mySkippedShapes; + if(myParams.Coplanar == CoplanarForce) + continue; + } + add(*myArea,BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true); + } +} + +#if 0 +static void show(const TopoDS_Shape &shape, const char *name) { + App::Document *pcDoc = App::GetApplication().getActiveDocument(); + if (!pcDoc) + pcDoc = App::GetApplication().newDocument(); + Part::Feature *pcFeature = (Part::Feature *)pcDoc->addObject("Part::Feature", name); + // copy the data + //TopoShape* shape = new MeshObject(*pShape->getTopoShapeObjectPtr()); + pcFeature->Shape.setValue(shape); + //pcDoc->recompute(); +} +#endif + +bool Area::findPlane(const TopoDS_Shape &shape, + TopoDS_Shape &plane, gp_Trsf &trsf) +{ + return findPlane(shape,TopAbs_FACE,plane,trsf) || + findPlane(shape,TopAbs_WIRE,plane,trsf) || + findPlane(shape,TopAbs_EDGE,plane,trsf); +} + +bool Area::findPlane(const TopoDS_Shape &shape, int type, + TopoDS_Shape &dst, gp_Trsf &dst_trsf) +{ + bool haveShape = false; + double top_z; + bool top_found = false; + gp_Trsf trsf; + for(TopExp_Explorer it(shape,(TopAbs_ShapeEnum)type); it.More(); it.Next()) { + haveShape = true; + const TopoDS_Shape &plane = it.Current(); + BRepLib_FindSurface planeFinder(plane,-1,Standard_True); + if (!planeFinder.Found()) + continue; + gp_Ax3 pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position(); + gp_Dir dir(pos.Direction()); + trsf.SetTransformation(pos); + + //pos.Location().Z() is always 0, why? As a walk around, use the first vertex Z + gp_Pnt origin = pos.Location(); + + for(TopExp_Explorer it(plane.Moved(trsf),TopAbs_VERTEX);it.More();) { + origin.SetZ(BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z()); + break; + } + + if(fabs(dir.X()) origin.Z()) + continue; + top_found = true; + top_z = origin.Z(); + }else if(!dst.IsNull()) + continue; + dst = plane; + + //Some how the plane returned by BRepLib_FindSurface has Z always set to 0. + //We need to manually translate Z to its actual value + gp_Trsf trsf2; + trsf2.SetTranslationPart(gp_XYZ(0,0,-origin.Z())); + dst_trsf = trsf.Multiplied(trsf2); + } + return haveShape; +} + +std::vector > Area::makeSections( + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + const std::vector &_heights, + const TopoDS_Shape &_plane) +{ + TopoDS_Shape plane; + gp_Trsf trsf; + + if(!_plane.IsNull()) + findPlane(_plane,plane,trsf); + else + plane = getPlane(&trsf); + + if(plane.IsNull()) + throw Base::ValueError("failed to obtain section plane"); + + TopLoc_Location loc(trsf); + + Bnd_Box bounds; + for(const Shape &s : myShapes) { + const TopoDS_Shape &shape = s.shape.Moved(loc); + BRepBndLib::Add(shape, bounds, Standard_False); + } + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + + bool hit_bottom = false; + std::vector heights; + if(_heights.empty()) { + if(mode != SectionModeAbsolute && myParams.SectionOffset<0) + throw Base::ValueError("only positive section offset is allowed in non-absolute mode"); + if(myParams.SectionCount>1 && myParams.Stepdown zMax-zMin) { + count = ceil((zMax-zMin)/myParams.Stepdown); + if((count-1)*myParams.Stepdown < zMax-zMin) + ++count; + } + heights.reserve(count); + for(int i=0;iPrecision::Confusion()) { + hit_bottom = true; + continue; + }else if ((z-zMax)>Precision::Confusion()) + continue; + heights.push_back(z); + } + } + + if(hit_bottom) + heights.push_back(zMin); + else if(heights.empty()) + heights.push_back(zMax); + + std::vector > sections; + sections.reserve(heights.size()); + for(double z : heights) { + gp_Pln pln(gp_Pnt(0,0,z),gp_Dir(0,0,1)); + Standard_Real a,b,c,d; + pln.Coefficients(a,b,c,d); + BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax); + const TopoDS_Shape &face = mkFace.Face(); + + shared_ptr area(new Area(&myParams)); + area->setPlane(face); + for(const Shape &s : myShapes) { + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(TopExp_Explorer it(s.shape.Moved(loc), TopAbs_SOLID); it.More(); it.Next()) { + Part::CrossSection section(a,b,c,it.Current()); + std::list wires = section.slice(-d); + if(wires.empty()) { + Base::Console().Warning("Section return no wires\n"); + continue; + } + + Part::FaceMakerBullseye mkFace; + mkFace.setPlane(pln); + for(const TopoDS_Wire &wire : wires) + mkFace.addWire(wire); + try { + mkFace.Build(); + if (mkFace.Shape().IsNull()) + Base::Console().Warning("FaceMakerBullseye return null shape on section\n"); + else { + builder.Add(comp,mkFace.Shape()); + continue; + } + }catch (Base::Exception &e){ + Base::Console().Warning("FaceMakerBullseye failed on section: %s\n", e.what()); + } + for(const TopoDS_Wire &wire : wires) + builder.Add(comp,wire); + } + + // Make sure the compound has at least one edge + for(TopExp_Explorer it(comp,TopAbs_EDGE);it.More();) { + area->add(comp,s.op); + break; + } + } + if(area->myShapes.size()) + sections.push_back(area); + else + Base::Console().Warning("Discard empty section\n"); + } + return std::move(sections); +} + +TopoDS_Shape Area::getPlane(gp_Trsf *trsf) { + if(!myWorkPlane.IsNull()) { + if(trsf) *trsf = myTrsf; + return myWorkPlane; + } + if(!isBuilt()) { + myShapePlane.Nullify(); + for(const Shape &s : myShapes) + findPlane(s.shape,myShapePlane,myTrsf); + if(myShapePlane.IsNull()) + throw Base::ValueError("shapes are not planar"); + } + if(trsf) *trsf = myTrsf; + return myShapePlane; +} + +bool Area::isBuilt() const { + return (myArea || mySections.size()); +} + + void Area::build() { - if(myArea || mySections.size()) return; + if(isBuilt()) return; if(myShapes.empty()) throw Base::ValueError("no shape added"); @@ -401,144 +685,13 @@ void Area::build() { #define AREA_SRC(_param) myParams.PARAM_FNAME(_param) PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); - if(myWorkPlane.IsNull()) { - myShapePlane.Nullify(); - for(const Shape &s : myShapes) { - bool haveShape = false; - bool done = false; - TopoDS_Shape shapePlane; - gp_Trsf trsf; - gp_Ax3 pos; -#define AREA_CHECK_PLANE(_type) \ - shapePlane.Nullify();\ - for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\ - haveShape = true;\ - BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\ - if (!planeFinder.Found())\ - continue;\ - shapePlane = it.Current();\ - pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();\ - trsf.SetTransformation(pos);\ - gp_Dir dir(pos.Direction());\ - if(fabs(dir.X())1 && myParams.Stepdown zMax-zMin) { - count = ceil((zMax-zMin)/myParams.Stepdown); - if((count-1)*myParams.Stepdown < zMax-zMin) - ++count; - } - for(int i=0;i area(new Area(&myParams)); - area->setPlane(face); - for(const Shape &s : myShapes) { - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); - for(TopExp_Explorer it(s.shape, TopAbs_SOLID); it.More(); it.Next()) { - BRepAlgoAPI_Section section(it.Current().Moved(loc),face); - if(!section.IsDone()) { - ++error; - continue; - } - const TopoDS_Shape &shape = section.Shape(); - if(shape.IsNull()) continue; - - Part::FaceMakerBullseye mkFace; - mkFace.setPlane(pln); - - std::list edges; - for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) - edges.push_back(TopoDS::Edge(it.Current())); - bool open = false; - std::list wires; - while(edges.size()) { - const std::list sorted = - Part::sort_Edges(Precision::Confusion(),edges); - BRepBuilderAPI_MakeWire mkWire; - for(const TopoDS_Edge &e : sorted) - mkWire.Add(e); - const TopoDS_Wire &wire = mkWire.Wire(); - if(!BRep_Tool::IsClosed(wire)) - open = true; - wires.push_back(wire); - } - if(!open) { - for(const TopoDS_Wire &wire : wires) - mkFace.addWire(wire); - try { - mkFace.Build(); - if (mkFace.Shape().IsNull()) - continue; - builder.Add(comp,mkFace.Shape()); - continue; - }catch (Base::Exception &e){ - Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); - } - } - //Shouldn't have any open wire, so count as error - ++error; - for(const TopoDS_Wire &wire : wires) - builder.Add(comp,wire); - } - if(comp.IsNull()) continue; - area->add(comp,s.op); - } - mySections.push_back(area); - } - if(error) - Base::Console().Warning("Some errors occured during operation\n"); + mySections = makeSections(myParams.SectionMode); return; } + getPlane(); + try { myArea.reset(new CArea()); myAreaOpen.reset(new CArea()); @@ -549,13 +702,11 @@ void Area::build() { mySkippedShapes = 0; short op = OperationUnion; bool pending = false; - bool explode = myParams.Explode; + bool exploding = myParams.Explode; for(const Shape &s : myShapes) { - if(explode) { - explode = false; - for (TopExp_Explorer it(s.shape, TopAbs_EDGE); it.More(); it.Next()) - add(*myArea,BRepBuilderAPI_MakeWire( - TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true); + if(exploding) { + exploding = false; + explode(s.shape); continue; }else if(op!=s.op) { if(myParams.OpenMode!=OpenModeNone) @@ -604,8 +755,7 @@ void Area::build() { Area area(&myParams); area.myParams.Explode = false; area.myParams.Coplanar = CoplanarNone; - area.myWorkPlane = myWorkPlane.IsNull()?myShapePlane:myWorkPlane; - area.myTrsf = myTrsf; + area.myWorkPlane = getPlane(&area.myTrsf); while(edges.size()) { BRepBuilderAPI_MakeWire mkWire; for(const auto &e : Part::sort_Edges(myParams.Tolerance,edges)) @@ -706,6 +856,7 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { return toShape(area,bFill,&trsf); } + #define AREA_SECTION(_op,_index,...) do {\ if(mySections.size()) {\ if(_index>=(int)mySections.size())\ @@ -720,9 +871,14 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { if(s.IsNull()) continue;\ builder.Add(compound,s.Moved(loc));\ }\ - return compound;\ + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();)\ + return compound;\ + return TopoDS_Shape();\ }\ - return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\ + const TopoDS_Shape &shape = mySections[_index]->_op(-1, ## __VA_ARGS__);\ + if(!shape.IsNull())\ + return shape.Moved(loc);\ + return shape;\ }\ }while(0) @@ -732,6 +888,8 @@ TopoDS_Shape Area::getShape(int index) { if(myShapeDone) return myShape; + if(!myArea) return TopoDS_Shape(); + CAreaConfig conf(myParams); #define AREA_MY(_param) myParams.PARAM_FNAME(_param) @@ -793,9 +951,13 @@ TopoDS_Shape Area::getShape(int index) { const TopoDS_Shape &shape = toShape(*area,fill); builder.Add(compound,toShape(*area,fill)); } - builder.Add(compound,areaPocket.makePocket( - -1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET))); - myShape = compound; + // make sure the compound has at least one edge + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { + builder.Add(compound,areaPocket.makePocket( + -1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET))); + myShape = compound; + break; + } myShapeDone = true; return myShape; } @@ -826,9 +988,13 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET fill = myParams.Fill; else fill = FillNone; - builder.Add(compound,toShape(*area,fill)); + const TopoDS_Shape &shape = toShape(*area,fill); + if(shape.IsNull()) continue; + builder.Add(compound,shape); } - return compound; + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) + return compound; + return TopoDS_Shape(); } void Area::makeOffset(list > &areas, @@ -855,7 +1021,7 @@ void Area::makeOffset(list > &areas, #ifdef AREA_OFFSET_ALGO PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); #endif - + for(int i=0;count<0||i()); CArea &area = *areas.back(); @@ -1032,23 +1198,25 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) { if(!wire.IsNull()) builder.Add(compound,wire); } - - if(!compound.IsNull() && fill) { - try{ - Part::FaceMakerBullseye mkFace; - if(trsf) - mkFace.setPlane(gp_Pln().Transformed(*trsf)); - for(TopExp_Explorer it(compound, TopAbs_WIRE); it.More(); it.Next()) - mkFace.addWire(TopoDS::Wire(it.Current())); - mkFace.Build(); - if (mkFace.Shape().IsNull()) - Base::Console().Warning("FaceMakerBullseye returns null shape\n"); - return mkFace.Shape(); - }catch (Base::Exception &e){ - Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { + if(fill) { + try{ + Part::FaceMakerBullseye mkFace; + if(trsf) + mkFace.setPlane(gp_Pln().Transformed(*trsf)); + for(TopExp_Explorer it(compound, TopAbs_WIRE); it.More(); it.Next()) + mkFace.addWire(TopoDS::Wire(it.Current())); + mkFace.Build(); + if (mkFace.Shape().IsNull()) + Base::Console().Warning("FaceMakerBullseye returns null shape\n"); + return mkFace.Shape(); + }catch (Base::Exception &e){ + Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); + } } + return compound; } - return compound; + return TopoDS_Shape(); } std::list Area::sortWires(const std::list &shapes, @@ -1220,13 +1388,20 @@ void Area::toPath(Toolpath &path, const std::list &shapes, break; } default: { // Discretize all other type of curves - GCPnts_UniformDeflection discretizer(curve, deflection, + GCPnts_QuasiUniformDeflection discretizer(curve, deflection, curve.FirstParameter(), curve.LastParameter()); - if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + if (discretizer.IsDone () && discretizer.NbPoints () > 1) { int nbPoints = discretizer.NbPoints (); - for (int i=1; i<=nbPoints; i++) { - gp_Pnt pt = discretizer.Value (i); - addCommand(path,pt); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } } }else Standard_Failure::Raise("Curve discretization failed"); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index ef7175532d..3bad235312 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -54,7 +54,8 @@ struct PathExport AreaParams: CAreaParams { bool operator==(const AreaParams &other) const { #define AREA_COMPARE(_param) \ if(PARAM_FIELD(NAME,_param)!=other.PARAM_FIELD(NAME,_param)) return false; - PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CONF) + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CAREA) + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_AREA) return true; } bool operator!=(const AreaParams &other) const { @@ -71,12 +72,9 @@ struct PathExport AreaParams: CAreaParams { */ struct PathExport CAreaConfig { - /** Stores current libarea settings */ + /** For saving current libarea settings */ PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA) - /** Stores user defined setting */ - CAreaParams params; - /** The constructor automatically saves current setting and apply user defined ones * * \arg \c p user defined configurations @@ -96,8 +94,7 @@ class PathExport Area: public Base::BaseClass { TYPESYSTEM_HEADER(); -protected: - +public: struct Shape { short op; TopoDS_Shape shape; @@ -108,6 +105,7 @@ protected: {} }; +protected: std::list myShapes; std::unique_ptr myArea; std::unique_ptr myAreaOpen; @@ -146,6 +144,10 @@ protected: */ TopoDS_Shape makePocket(); + void explode(const TopoDS_Shape &shape); + + bool isBuilt() const; + public: /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) @@ -156,18 +158,27 @@ public: /** Set a working plane * - * If no working plane are set, Area will try to find a working plane from - * individual children faces, wires or edges. By right, we should create a - * compound of all shapes and then findplane on it. However, because we - * supports solid, and also because OCC may hang for a long time if - * something goes a bit off, we opt to find plane on each individual shape. - * If you intend to pass individual edges, you must supply a workplane shape - * manually + * \arg \c shape: a shape defining a working plane. * - * \arg \c shape: a shape defining a working plane + * The supplied shape does not need to be planar. Area will try to find planar + * sub-shape (face, wire or edge). If more than one planar sub-shape is found, + * it will prefer the top plane parallel to XY0 plane. + * + * If no working plane are set, Area will try to find a working plane from + * the added children shape using the same algorithm */ void setPlane(const TopoDS_Shape &shape); + /** Return the current active workplane + * + * \arg \c trsf: optional return of a transformation matrix that will bring the + * found plane to XY0 plane. + * + * If no workplane is set using setPlane(), the active workplane is derived from + * the added children shapes using the same algorithm empolyed by setPlane(). + */ + TopoDS_Shape getPlane(gp_Trsf *trsf = NULL); + /** Add a child shape with given operation code * * No validation is done at this point. Exception will be thrown when asking @@ -195,10 +206,19 @@ public: TopoDS_Shape makePocket(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); + std::vector > makeSections( + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + const std::vector &_heights = std::vector(), + const TopoDS_Shape &plane = TopoDS_Shape()); + /** Config this Area object */ void setParams(const AreaParams ¶ms); + const std::list getChildren() const { + return myShapes; + } + /** Get the current configuration */ const AreaParams &getParams() const { return myParams; @@ -330,6 +350,42 @@ public: static void toPath(Toolpath &path, const std::list &shapes, const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + + /** Explore the shape to find a planar element, and return its transformation + * + * \arg \c shape: shape to explore + * \arg \c type: OCC shape type (TopAbs_ShapeEnum) to explore + * \arg \c plane: returns the sub planar shape found + * \arg \c trsf: the transformation of the plane which will transform the + * plane into XY0 plane. + * + * If there are multiple subshapes on different planes. It will prefer the + * top XY plane. If there is no XY parallel plane, the first planar shape + * encountered will be returned + * + * \return Returns true is there is any subshape of the give type found. + * You should use plane.IsNull() to see if there is any planar shape found. + */ + static bool findPlane(const TopoDS_Shape &shape, int type, + TopoDS_Shape &plane, gp_Trsf &trsf); + + /** Explore the shape with subtype FACE, WIRE and EDGE to find a planar + * subshape + * + * \arg \c shape: shape to explore + * \arg \c plane: returns the sub planar shape found + * \arg \c trsf: the transformation of the plane which will transform the + * plane into XY0 plane. + * + * If there are multiple subshapes on different planes. It will prefer the + * top XY plane. If there is no XY parallel plane, the first planar shape + * encountered will be returned + * + * \return Returns true is there is any subshape is found. You should use + * plane.IsNull() to see if there is any planar shape found. + */ + static bool findPlane(const TopoDS_Shape &shape, + TopoDS_Shape &plane, gp_Trsf &trsf); }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 0f38db3a6a..0f10d923be 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -104,9 +104,9 @@ /** Operation code */ #define AREA_PARAMS_OPCODE \ - ((enum,op,Operation,0,\ - "Boolean operation. For the first four operation, see https://goo.gl/Gj8RUu.\n"\ - "'Compound' means no operation, normal used to do Area.sortWires().",\ + ((enum,op,Operation,0,"Boolean operation.\n"\ + "For the first four operations, see https://goo.gl/Gj8RUu.\n"\ + "'Compound' means no operation, normally used to do Area.sortWires().",\ (Union)(Difference)(Intersection)(Xor)(Compound))) /** Offset parameters */ @@ -115,11 +115,22 @@ ((long,extra_pass,ExtraPass,0,"Number of extra offset pass to generate."))\ ((double,stepover,Stepover,0.0,"Cutter diameter to step over on each pass. If =0, use Offset")) +#define AREA_PARAMS_SECTION_EXTRA \ + ((enum,mode,SectionMode,2,"Section offset coordinate mode.\n"\ + "'Absolute' means the absolute Z height to start section.\n"\ + "'BoundBox' means relative Z height to the bounding box of all the children shape. Only\n"\ + "positive value is allowed, which specifies the offset below the top Z of the bounding box.\n"\ + "Note that OCC has trouble getting the minimumi bounding box of some solids, particually\n"\ + "those with non-planar surface.\n"\ + "'Workplane' means relative to workplane.",\ + (Absolute)(BoundBox)(Workplane))) + /** Section parameters */ #define AREA_PARAMS_SECTION \ ((long,count,SectionCount,0,"Number of sections to generate. -1 means full sections."))\ ((double,stepdown,Stepdown,1.0,"Step down distance for each section"))\ - ((double,offset,SectionOffset,0.0,"Offset for the first section")) + ((double,offset,SectionOffset,0.0,"Offset for the first section"))\ + AREA_PARAMS_SECTION_EXTRA #ifdef AREA_OFFSET_ALGO # define AREA_PARAMS_OFFSET_ALGO \ diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 062b82935b..170288ed3c 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -25,8 +25,12 @@ All arguments are optional. - setPlane(shape): Set the working plane. The shape will not be used for -any operation + setPlane(shape): Set the working plane.\n +The supplied shape does not need to be planar. Area will try to find planar +sub-shape (face, wire or edge). If more than one planar sub-shape is found, it +will prefer the top plane parallel to XY0 plane. If no working plane are set, +Area will try to find a working plane from the added children shape using the +same algorithm @@ -46,6 +50,11 @@ any operation + + + + + @@ -74,5 +83,17 @@ any operation + + + The current workplane. If no plane is set, it is derived from the added shapes. + + + + + + A list of tuple: [(shape,op), ...] containing the added shapes together with their operation code + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 75e370d2d0..3f44a6c98d 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -54,11 +54,11 @@ static const AreaDoc myDocs[] = { "add((shape...)," PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OPCODE) "):\n" "Add TopoShape(s) with given operation code\n" PARAM_PY_DOC(ARG,AREA_PARAMS_OPCODE) - "\nThe first shape's wires will be fused together regardless of the op code given.\n" - "Subsequent shape's wire will be combined using the op code. All shape wires\n" - "shall be coplanar, and are used to determine a working plane for face making and\n" - "offseting. You can call setPlane() to supply a reference shape to determin the\n" - "working plane in case the added shapes are all colinear lines.\n", + "\nThe first shape's wires will be unioned together regardless of the op code given\n" + "(except for 'Compound'). Subsequent shape's wire will be combined using the op code.\n" + "All shape wires shall be coplanar, and are used to determine a working plane for face\n" + "making and offseting. You can call setPlane() to supply a reference shape to determin\n" + "the workplane in case the added shapes are all colinear lines.\n", }, { @@ -77,6 +77,17 @@ static const AreaDoc myDocs[] = { "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), }, + { + "makeSections", + + "makeSections(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) ", heights=[], plane=None):\n" + "Make a list of area holding the sectioned children shapes on given heights\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) + "\n* heights ([]): a list of section heights, the meaning of the value is determined by 'mode'.\n" + "If not specified, the current SectionCount, and SectionOffset of this Area is used.\n" + "\n* plane (None): optional shape to specify a section plane. If not give, the current workplane\n" + "of this Area is used.", + }, { "sortWires", @@ -270,6 +281,53 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) return Py::new_reference_to(Part::shape2pyshape(resultShape)); } +PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SECTION_EXTRA), + "heights", "plane", NULL}; + PyObject *heights = NULL; + PyObject *plane = NULL; + + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA) + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_SECTION_EXTRA) "OO!", kwlist, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + &heights, &(Part::TopoShapePy::Type), &plane)) + return 0; + + std::vector h; + if(heights) { + if (PyObject_TypeCheck(heights, &(PyFloat_Type))) + h.push_back(PyFloat_AsDouble(heights)); + else if (PyObject_TypeCheck(heights, &(PyList_Type)) || + PyObject_TypeCheck(heights, &(PyTuple_Type))) { + Py::Sequence shapeSeq(heights); + h.reserve(shapeSeq.size()); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(PyFloat_Type))) { + PyErr_SetString(PyExc_TypeError, "heights must only contain float type"); + return 0; + } + h.push_back(PyFloat_AsDouble(item)); + } + }else{ + PyErr_SetString(PyExc_TypeError, "heights must be of type float or list/tuple of float"); + return 0; + } + } + + std::vector > sections = getAreaPtr()->makeSections( + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + h,plane?GET_TOPOSHAPE(plane):TopoDS_Shape()); + + Py::List ret; + for(auto &area : sections) + ret.append(Py::asObject(new AreaPy(new Area(*area,false)))); + return Py::new_reference_to(ret); +} + PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) { static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; @@ -337,6 +395,20 @@ Py::List AreaPy::getSections(void) const { return ret; } +Py::List AreaPy::getShapes(void) const { + Py::List ret; + Area *area = getAreaPtr(); + const std::list &shapes = area->getChildren(); + for(auto &s : shapes) + ret.append(Py::TupleN(Part::shape2pyshape(s.shape),Py::Int(s.op))); + return ret; +} + +Py::Object AreaPy::getWorkplane(void) const { + return Part::shape2pyshape(getAreaPtr()->getPlane()); +} + + // custom attributes get/set PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index 97379ed806..f016a4611f 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -49,11 +49,11 @@ FeatureArea::FeatureArea() PARAM_PROP_ADD("Area",AREA_PARAMS_OPCODE); PARAM_PROP_ADD("Area",AREA_PARAMS_BASE); PARAM_PROP_ADD("Offset",AREA_PARAMS_OFFSET); + PARAM_PROP_ADD("Offset", AREA_PARAMS_OFFSET_CONF); PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET); PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET_CONF); PARAM_PROP_ADD("Section",AREA_PARAMS_SECTION); - PARAM_PROP_ADD("Offset Settings", AREA_PARAMS_OFFSET_CONF); - PARAM_PROP_ADD("libarea Settings",AREA_PARAMS_CAREA); + PARAM_PROP_ADD("libarea",AREA_PARAMS_CAREA); PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_ALL); PocketMode.setValue((long)0); From 9150ee24d1ffbcc21230cd6672d297e9b862b0c1 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 29 Jan 2017 20:17:18 +0800 Subject: [PATCH 25/35] Path.Area: fixed auto workplane finding logic --- src/Mod/Path/App/Area.cpp | 70 ++++++++++++++++++++++----------------- src/Mod/Path/App/Area.h | 40 +++------------------- 2 files changed, 44 insertions(+), 66 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 602789709e..ad339672a9 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -123,7 +123,6 @@ Area::Area(const Area &other, bool deep_copy) ,myShapes(other.myShapes) ,myTrsf(other.myTrsf) ,myParams(other.myParams) -,myShapePlane(other.myShapePlane) ,myWorkPlane(other.myWorkPlane) ,myHaveFace(other.myHaveFace) ,myHaveSolid(other.myHaveSolid) @@ -133,6 +132,7 @@ Area::Area(const Area &other, bool deep_copy) return; if(other.myArea) myArea.reset(new CArea(*other.myArea)); + myShapePlane = other.myShapePlane; myShape = other.myShape; myShapeDone = other.myShapeDone; mySections.reserve(other.mySections.size()); @@ -149,9 +149,8 @@ void Area::setPlane(const TopoDS_Shape &shape) { myWorkPlane.Nullify(); return; } - TopoDS_Shape plane; gp_Trsf trsf; - findPlane(shape,plane,trsf); + TopoDS_Shape plane = findPlane(shape,trsf); if (plane.IsNull()) throw Base::ValueError("shape is not planar"); myWorkPlane = plane; @@ -325,8 +324,8 @@ void Area::clean(bool deleteShapes) { myShape.Nullify(); myArea.reset(); myAreaOpen.reset(); + myShapePlane.Nullify(); if(deleteShapes){ - myShapePlane.Nullify(); myShapes.clear(); myHaveFace = false; myHaveSolid = false; @@ -453,20 +452,21 @@ static void show(const TopoDS_Shape &shape, const char *name) { } #endif -bool Area::findPlane(const TopoDS_Shape &shape, - TopoDS_Shape &plane, gp_Trsf &trsf) +TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf) { - return findPlane(shape,TopAbs_FACE,plane,trsf) || - findPlane(shape,TopAbs_WIRE,plane,trsf) || - findPlane(shape,TopAbs_EDGE,plane,trsf); + TopoDS_Shape plane; + double top_z; + if(!findPlane(shape,TopAbs_FACE,plane,trsf,top_z) && + !findPlane(shape,TopAbs_WIRE,plane,trsf,top_z)) + findPlane(shape,TopAbs_EDGE,plane,trsf,top_z); + return plane; } bool Area::findPlane(const TopoDS_Shape &shape, int type, - TopoDS_Shape &dst, gp_Trsf &dst_trsf) + TopoDS_Shape &dst, gp_Trsf &dst_trsf, double &top_z) { bool haveShape = false; - double top_z; - bool top_found = false; + bool top_found = !dst.IsNull(); gp_Trsf trsf; for(TopExp_Explorer it(shape,(TopAbs_ShapeEnum)type); it.More(); it.Next()) { haveShape = true; @@ -478,17 +478,26 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, gp_Dir dir(pos.Direction()); trsf.SetTransformation(pos); - //pos.Location().Z() is always 0, why? As a walk around, use the first vertex Z gp_Pnt origin = pos.Location(); - for(TopExp_Explorer it(plane.Moved(trsf),TopAbs_VERTEX);it.More();) { - origin.SetZ(BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z()); - break; - } - if(fabs(dir.X()) origin.Z()) continue; top_found = true; @@ -496,12 +505,8 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, }else if(!dst.IsNull()) continue; dst = plane; + dst_trsf = trsf; - //Some how the plane returned by BRepLib_FindSurface has Z always set to 0. - //We need to manually translate Z to its actual value - gp_Trsf trsf2; - trsf2.SetTranslationPart(gp_XYZ(0,0,-origin.Z())); - dst_trsf = trsf.Multiplied(trsf2); } return haveShape; } @@ -509,13 +514,13 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, std::vector > Area::makeSections( PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), const std::vector &_heights, - const TopoDS_Shape &_plane) + const TopoDS_Shape §ion_plane) { TopoDS_Shape plane; gp_Trsf trsf; - if(!_plane.IsNull()) - findPlane(_plane,plane,trsf); + if(!section_plane.IsNull()) + plane = findPlane(section_plane,trsf); else plane = getPlane(&trsf); @@ -660,10 +665,15 @@ TopoDS_Shape Area::getPlane(gp_Trsf *trsf) { if(trsf) *trsf = myTrsf; return myWorkPlane; } - if(!isBuilt()) { - myShapePlane.Nullify(); - for(const Shape &s : myShapes) - findPlane(s.shape,myShapePlane,myTrsf); + if(myShapePlane.IsNull()) { + if(myShapes.empty()) + throw Base::ValueError("no shape added"); + double top_z; + for(auto &s : myShapes) { + if(!findPlane(s.shape,TopAbs_FACE,myShapePlane,myTrsf,top_z) && + !findPlane(s.shape,TopAbs_WIRE,myShapePlane,myTrsf,top_z)) + findPlane(s.shape,TopAbs_EDGE,myShapePlane,myTrsf,top_z); + } if(myShapePlane.IsNull()) throw Base::ValueError("shapes are not planar"); } diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 3bad235312..de9cca972a 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -148,6 +148,10 @@ protected: bool isBuilt() const; + static bool findPlane(const TopoDS_Shape &shape, int type, + TopoDS_Shape &plane, gp_Trsf &trsf, double &top_z); + TopoDS_Shape findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf); + public: /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) @@ -350,42 +354,6 @@ public: static void toPath(Toolpath &path, const std::list &shapes, const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); - - /** Explore the shape to find a planar element, and return its transformation - * - * \arg \c shape: shape to explore - * \arg \c type: OCC shape type (TopAbs_ShapeEnum) to explore - * \arg \c plane: returns the sub planar shape found - * \arg \c trsf: the transformation of the plane which will transform the - * plane into XY0 plane. - * - * If there are multiple subshapes on different planes. It will prefer the - * top XY plane. If there is no XY parallel plane, the first planar shape - * encountered will be returned - * - * \return Returns true is there is any subshape of the give type found. - * You should use plane.IsNull() to see if there is any planar shape found. - */ - static bool findPlane(const TopoDS_Shape &shape, int type, - TopoDS_Shape &plane, gp_Trsf &trsf); - - /** Explore the shape with subtype FACE, WIRE and EDGE to find a planar - * subshape - * - * \arg \c shape: shape to explore - * \arg \c plane: returns the sub planar shape found - * \arg \c trsf: the transformation of the plane which will transform the - * plane into XY0 plane. - * - * If there are multiple subshapes on different planes. It will prefer the - * top XY plane. If there is no XY parallel plane, the first planar shape - * encountered will be returned - * - * \return Returns true is there is any subshape is found. You should use - * plane.IsNull() to see if there is any planar shape found. - */ - static bool findPlane(const TopoDS_Shape &shape, - TopoDS_Shape &plane, gp_Trsf &trsf); }; } //namespace Path From 43432ea7721c325cd7c67feb8b4e0b4a67f262b1 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 29 Jan 2017 20:25:13 +0800 Subject: [PATCH 26/35] Path.Area: fixed auto workplane correction --- src/Mod/Path/App/Area.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index ad339672a9..c29cf990bf 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -491,14 +491,13 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, z = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z(); break; } - if(origin.Z() != z) { + if(fabs(origin.Z()-z)>Precision::Confusion()) { Base::Console().Warning("XY plane has wrong Z height %lf, %lf\n",origin.Z(),z); - origin.SetZ(z); gp_Trsf trsf2; - trsf2.SetTranslationPart(gp_XYZ(0,0,-origin.Z())); + trsf2.SetTranslationPart(gp_XYZ(0,0,origin.Z()-z)); trsf.Multiply(trsf2); } - if(top_found && top_z > origin.Z()) + if(top_found && top_z > z) continue; top_found = true; top_z = origin.Z(); From ecc426aa2a92fc503aecaa7e945e96796ad30f55 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 29 Jan 2017 18:59:52 +0000 Subject: [PATCH 27/35] Path.Area: fixed compiler warning --- src/Mod/Path/App/Area.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index c29cf990bf..161dfa19e3 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -486,11 +486,8 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, // Probably another OCC bug, sometimes pos.Location().Z() for XY // plane is stuck at zero, even though the plane is at above. So we // double check the first vertex Z value - double z; - for(TopExp_Explorer it(plane,TopAbs_VERTEX);it.More();) { - z = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z(); - break; - } + TopExp_Explorer it(plane,TopAbs_VERTEX); + double z = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z(); if(fabs(origin.Z()-z)>Precision::Confusion()) { Base::Console().Warning("XY plane has wrong Z height %lf, %lf\n",origin.Z(),z); gp_Trsf trsf2; @@ -958,7 +955,8 @@ TopoDS_Shape Area::getShape(int index) { if(myParams.Thicken) area->Thicken(myParams.ToolRadius); const TopoDS_Shape &shape = toShape(*area,fill); - builder.Add(compound,toShape(*area,fill)); + if(shape.IsNull()) continue; + builder.Add(compound,shape); } // make sure the compound has at least one edge for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { From 56b5ba26b744582c4a8a0d1c2d7b3bf933e3e51b Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 29 Jan 2017 19:01:10 +0000 Subject: [PATCH 28/35] Force BOOST_PP_VARIADIC for clang Current clang has full support for all variadic macro feature required by Boost.Preprocessor. However, older Boost turned off variadic feature for clang by default, probably because clang was broken at that time. It should be safe to turn it on now. --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index eeac7d5bbb..7515b8ac54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,13 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) endif() endif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) +if(CMAKE_COMPILER_IS_CLANGXX) + # older boost.preprocessor turn off variadics for clang + add_definitions(-DBOOST_PP_VARIADICS=1) + message(STATUS "Force BOOST_PP_VARIADICS=1 for clang") +endif() + + # ================================================================================ # Output directories for install target From 9c6113914625995aa4d91eceebf06686dc9e6a5a Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 30 Jan 2017 16:29:36 +0800 Subject: [PATCH 29/35] Path.Area: fixed workplane orientation issue --- src/Mod/Path/App/Area.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 161dfa19e3..b2f8bd92c0 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -475,6 +475,10 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, if (!planeFinder.Found()) continue; gp_Ax3 pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position(); + + //force plane to be right handed + if(!pos.Direct()) + pos = gp_Ax3(pos.Ax2()); gp_Dir dir(pos.Direction()); trsf.SetTransformation(pos); @@ -494,10 +498,17 @@ bool Area::findPlane(const TopoDS_Shape &shape, int type, trsf2.SetTranslationPart(gp_XYZ(0,0,origin.Z()-z)); trsf.Multiply(trsf2); } + gp_Pnt pt = origin.Transformed(TopLoc_Location(trsf)); + if(fabs(pt.X()) > Precision::Confusion() || + fabs(pt.Y()) > Precision::Confusion() || + fabs(pt.Z()) > Precision::Confusion()) { + Base::Console().Warning("wrong transformation %lf, %lf, %lf\n",pt.X(),pt.Y(),pt.Z()); + } + if(top_found && top_z > z) continue; top_found = true; - top_z = origin.Z(); + top_z = z; }else if(!dst.IsNull()) continue; dst = plane; @@ -619,7 +630,7 @@ std::vector > Area::makeSections( Part::CrossSection section(a,b,c,it.Current()); std::list wires = section.slice(-d); if(wires.empty()) { - Base::Console().Warning("Section return no wires\n"); + Base::Console().Log("Section returns no wires\n"); continue; } From a2f58500879bc4bb6f2756d6e26e493ac2ab8c52 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 31 Jan 2017 19:08:39 +0800 Subject: [PATCH 30/35] libarea: make ChangeStartToNearest path breaking optinoal --- src/Mod/Path/libarea/Area.cpp | 39 +++++++++++++++++++++++++---------- src/Mod/Path/libarea/Area.h | 2 +- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/libarea/Area.cpp b/src/Mod/Path/libarea/Area.cpp index c39bde2e49..a4e5bf29d9 100644 --- a/src/Mod/Path/libarea/Area.cpp +++ b/src/Mod/Path/libarea/Area.cpp @@ -73,8 +73,8 @@ Point CArea::NearestPoint(const Point& p)const return best_point; } -void CArea::ChangeStartToNearest(const Point *point, double min_dist) { - +void CArea::ChangeStartToNearest(const Point *point, double min_dist) +{ for(std::list::iterator It=m_curves.begin(),ItNext=It; It != m_curves.end(); It=ItNext) { @@ -99,8 +99,22 @@ void CArea::ChangeStartToNearest(const Point *point, double min_dist) { for(; It != m_curves.end(); ++It) { const CCurve& curve = *It; - Point near_point = curve.NearestPoint(p); - double dist = near_point.dist(p); + Point near_point; + double dist; + if(min_dist>Point::tolerance && !curve.IsClosed()) { + double d1 = curve.m_vertices.front().m_p.dist(p); + double d2 = curve.m_vertices.back().m_p.dist(p); + if(d1IsClosed()) { ItBest->ChangeStart(best_point); - }else if(ItBest->m_vertices.back().m_p.dist(best_point)<=min_dist) { - ItBest->Reverse(); - }else if(ItBest->m_vertices.front().m_p.dist(best_point)>min_dist) { - ItBest->Break(best_point); - m_curves.push_back(*ItBest); - m_curves.back().ChangeEnd(best_point); - ItBest->ChangeStart(best_point); + }else{ + double dfront = ItBest->m_vertices.front().m_p.dist(best_point); + double dback = ItBest->m_vertices.back().m_p.dist(best_point); + if(min_dist>Point::tolerance && dfront>min_dist && dback>min_dist) { + ItBest->Break(best_point); + m_curves.push_back(*ItBest); + m_curves.back().ChangeEnd(best_point); + ItBest->ChangeStart(best_point); + }else if(dfront>dback) + ItBest->Reverse(); } curves.splice(curves.end(),m_curves,ItBest); p = curves.back().m_vertices.back().m_p; diff --git a/src/Mod/Path/libarea/Area.h b/src/Mod/Path/libarea/Area.h index ca9a641527..88e8fca5cf 100644 --- a/src/Mod/Path/libarea/Area.h +++ b/src/Mod/Path/libarea/Area.h @@ -85,7 +85,7 @@ public: void CurveIntersections(const CCurve& curve, std::list &pts)const; void InsideCurves(const CCurve& curve, std::list &curves_inside)const; - void ChangeStartToNearest(const Point *point=NULL, double min_dist=1.0); + void ChangeStartToNearest(const Point *pstart=NULL, double min_dist=1.0); //Avoid outside direct accessing static member variable because of Windows DLL issue #define CAREA_PARAM_DECLARE(_type,_name) \ From 00cb8095a7c6c43c2bc84d6f1af621e8eb041826 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 31 Jan 2017 19:09:33 +0800 Subject: [PATCH 31/35] Path.Area: added segmentation and 3D wire support --- src/Mod/Path/App/AppPathPy.cpp | 58 ++- src/Mod/Path/App/Area.cpp | 653 ++++++++++++++++++++------ src/Mod/Path/App/Area.h | 92 +++- src/Mod/Path/App/AreaParams.h | 48 +- src/Mod/Path/App/AreaPyImp.cpp | 26 +- src/Mod/Path/App/FeatureArea.cpp | 64 ++- src/Mod/Path/App/FeatureArea.h | 3 +- src/Mod/Path/App/FeaturePathShape.cpp | 12 +- src/Mod/Path/App/FeaturePathShape.h | 2 +- 9 files changed, 712 insertions(+), 246 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 36371b4511..f7b53cde1b 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -126,13 +126,13 @@ public: PARAM_PY_DOC(ARG, AREA_PARAMS_PATH) ); add_keyword_method("sortWires",&Module::sortWires, - "sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT_WIRES) ")\n" + "sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ")\n" "\nReturns (wires,end), where 'wires' is sorted accross Z value and with optimized travel distance,\n" "and 'end' is the ending position of the whole wires\n" "\n* shapes: input shape list\n" "\n* start (Vector()): optional start position.\n" "\n* params (None): optional dictionary for configuring Path.Area internally used to sort the wires.\n" - PARAM_PY_DOC(ARG, AREA_PARAMS_SORT_WIRES) + PARAM_PY_DOC(ARG, AREA_PARAMS_SORT) ); initialize("This module is the Path module."); // register with Python } @@ -320,14 +320,17 @@ private: Py::Object fromShapes(const Py::Tuple& args, const Py::Dict &kwds) { PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_PATH) + PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF) PyObject *pShapes=NULL; PyObject *start=NULL; static char* kwd_list[] = {"shapes", "start", - PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), NULL}; + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), + PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH), kwd_list, &pShapes, &(Base::VectorPy::Type), &start, - PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH))) + PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH), + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) throw Py::Exception(); std::list shapes; @@ -347,6 +350,12 @@ private: } } +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + AreaParams params; + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) + gp_Pnt pstart; if(start) { Base::Vector3d vec = static_cast(start)->value(); @@ -355,24 +364,26 @@ private: try { std::unique_ptr path(new Toolpath); - Area::toPath(*path,shapes,&pstart,PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH)); + Area::toPath(*path,shapes,¶ms, &pstart, NULL, + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH)); return Py::asObject(new PathPy(path.release())); } PATH_CATCH } Py::Object sortWires(const Py::Tuple& args, const Py::Dict &kwds) { - AreaParams params; - PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT_WIRES) + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT) + PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF) PyObject *pShapes=NULL; PyObject *start=NULL; - PyObject *pParams=NULL; - static char* kwd_list[] = {"shapes", "start", "params", - PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT_WIRES), NULL}; + static char* kwd_list[] = {"shapes", "start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), + PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), - "O|O!O!" PARAM_PY_KWDS(AREA_PARAMS_SORT_WIRES), kwd_list, - &pShapes, &(Base::VectorPy::Type), &start, &PyDict_Type, &pParams, - PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT_WIRES))) + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_SORT), kwd_list, + &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT), + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) throw Py::Exception(); std::list shapes; @@ -391,23 +402,8 @@ private: } } - if(pParams) { - static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; - PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); -#define AREA_SET(_param) \ - PARAM_FNAME(_param) = \ - PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); - PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) - if (!PyArg_ParseTupleAndKeywords(NULL, pParams, - "|" PARAM_PY_KWDS(AREA_PARAMS_CONF), kwlist, - PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) - throw Py::Exception(); - -#define AREA_GET(_param) \ - params.PARAM_FNAME(_param) = \ - PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); - PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) - } + AreaParams params; + PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) gp_Pnt pstart,pend; if(start) { @@ -417,7 +413,7 @@ private: try { std::list wires = Area::sortWires(shapes,¶ms,&pstart, - &pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT_WIRES)); + &pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); PyObject *list = PyList_New(0); for(auto &wire : wires) PyList_Append(list,Py::new_reference_to( diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index b2f8bd92c0..07bcda2a77 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -23,8 +23,6 @@ #ifndef _PreComp_ #endif - -#include "boost/date_time/posix_time/posix_time.hpp" #include #include @@ -54,14 +52,16 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include -#include #include #include @@ -73,7 +73,6 @@ #include "../libarea/Area.h" using namespace Path; -using namespace boost::posix_time; CAreaParams::CAreaParams() :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA) @@ -310,7 +309,7 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, } if(!to_edges) { if(BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { - Base::Console().Warning("ccurve not closed\n"); + AREA_WARN("ccurve not closed"); ccurve.append(ccurve.m_vertices.front()); } area.append(ccurve); @@ -403,7 +402,7 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { if(&area == myArea.get() || myParams.OpenMode == OpenModeNone) myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); else - Base::Console().Warning("open wires discarded in clipping shapes\n"); + AREA_WARN("open wires discarded in clipping shapes"); } } @@ -452,70 +451,92 @@ static void show(const TopoDS_Shape &shape, const char *name) { } #endif -TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf) -{ - TopoDS_Shape plane; - double top_z; - if(!findPlane(shape,TopAbs_FACE,plane,trsf,top_z) && - !findPlane(shape,TopAbs_WIRE,plane,trsf,top_z)) - findPlane(shape,TopAbs_EDGE,plane,trsf,top_z); - return plane; +template +static int foreachSubshape(const TopoDS_Shape &shape, Func func, int type=TopAbs_FACE) { + bool haveShape = false; + switch(type) { + case TopAbs_FACE: + for(TopExp_Explorer it(shape,TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_FACE); + } + if(haveShape) return TopAbs_FACE; + //fall through + case TopAbs_WIRE: + for(TopExp_Explorer it(shape,TopAbs_WIRE); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_WIRE); + } + if(haveShape) return TopAbs_WIRE; + //fall through + default: + for(TopExp_Explorer it(shape,TopAbs_EDGE); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_EDGE); + } + } + return haveShape?TopAbs_EDGE:-1; } -bool Area::findPlane(const TopoDS_Shape &shape, int type, - TopoDS_Shape &dst, gp_Trsf &dst_trsf, double &top_z) -{ - bool haveShape = false; - bool top_found = !dst.IsNull(); - gp_Trsf trsf; - for(TopExp_Explorer it(shape,(TopAbs_ShapeEnum)type); it.More(); it.Next()) { - haveShape = true; - const TopoDS_Shape &plane = it.Current(); - BRepLib_FindSurface planeFinder(plane,-1,Standard_True); - if (!planeFinder.Found()) - continue; - gp_Ax3 pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position(); +struct FindPlane { + TopoDS_Shape &myShape; + gp_Trsf &myTrsf; + double &myZ; + FindPlane(TopoDS_Shape &s, gp_Trsf &t, double &z) + :myShape(s),myTrsf(t),myZ(z) + {} + void operator()(const TopoDS_Shape &shape, int) { + gp_Trsf trsf; + BRepLib_FindSurface finder(shape,-1,Standard_True); + if (!finder.Found()) + return; + + gp_Ax3 pos = GeomAdaptor_Surface(finder.Surface()).Plane().Position(); //force plane to be right handed if(!pos.Direct()) pos = gp_Ax3(pos.Ax2()); gp_Dir dir(pos.Direction()); + trsf.SetTransformation(pos); - - gp_Pnt origin = pos.Location(); - if(fabs(dir.X())Precision::Confusion()) { - Base::Console().Warning("XY plane has wrong Z height %lf, %lf\n",origin.Z(),z); + AREA_WARN("XY plane has wrong Z height "< Precision::Confusion() || - fabs(pt.Y()) > Precision::Confusion() || - fabs(pt.Z()) > Precision::Confusion()) { - Base::Console().Warning("wrong transformation %lf, %lf, %lf\n",pt.X(),pt.Y(),pt.Z()); + fabs(pt.Y()) > Precision::Confusion() || + fabs(pt.Z()) > Precision::Confusion()) { + AREA_WARN("wrong transformation "< z) - continue; - top_found = true; - top_z = z; - }else if(!dst.IsNull()) - continue; - dst = plane; - dst_trsf = trsf; - + if(!myShape.IsNull() && myZ > z) + return; + myZ = z; + }else if(!myShape.IsNull()) + return; + myShape = shape; + myTrsf = trsf; } - return haveShape; +}; + +TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf) +{ + TopoDS_Shape plane; + double top_z; + foreachSubshape(shape,FindPlane(plane,trsf,top_z)); + return plane; } std::vector > Area::makeSections( @@ -534,6 +555,8 @@ std::vector > Area::makeSections( if(plane.IsNull()) throw Base::ValueError("failed to obtain section plane"); + TIME_INIT2(t,t1); + TopLoc_Location loc(trsf); Bnd_Box bounds; @@ -630,7 +653,7 @@ std::vector > Area::makeSections( Part::CrossSection section(a,b,c,it.Current()); std::list wires = section.slice(-d); if(wires.empty()) { - Base::Console().Log("Section returns no wires\n"); + AREA_LOG("Section returns no wires"); continue; } @@ -641,13 +664,13 @@ std::vector > Area::makeSections( try { mkFace.Build(); if (mkFace.Shape().IsNull()) - Base::Console().Warning("FaceMakerBullseye return null shape on section\n"); + AREA_WARN("FaceMakerBullseye return null shape on section"); else { builder.Add(comp,mkFace.Shape()); continue; } }catch (Base::Exception &e){ - Base::Console().Warning("FaceMakerBullseye failed on section: %s\n", e.what()); + AREA_WARN("FaceMakerBullseye failed on section: " << e.what()); } for(const TopoDS_Wire &wire : wires) builder.Add(comp,wire); @@ -662,8 +685,10 @@ std::vector > Area::makeSections( if(area->myShapes.size()) sections.push_back(area); else - Base::Console().Warning("Discard empty section\n"); + AREA_WARN("Discard empty section"); + TIME_PRINT(t1,"makeSection " << z); } + TIME_PRINT(t,"makeSection count: " << sections.size()<<", total"); return std::move(sections); } @@ -676,11 +701,8 @@ TopoDS_Shape Area::getPlane(gp_Trsf *trsf) { if(myShapes.empty()) throw Base::ValueError("no shape added"); double top_z; - for(auto &s : myShapes) { - if(!findPlane(s.shape,TopAbs_FACE,myShapePlane,myTrsf,top_z) && - !findPlane(s.shape,TopAbs_WIRE,myShapePlane,myTrsf,top_z)) - findPlane(s.shape,TopAbs_EDGE,myShapePlane,myTrsf,top_z); - } + for(auto &s : myShapes) + foreachSubshape(s.shape,FindPlane(myShapePlane,myTrsf,top_z)); if(myShapePlane.IsNull()) throw Base::ValueError("shapes are not planar"); } @@ -707,6 +729,7 @@ void Area::build() { return; } + TIME_INIT(t); getPlane(); try { @@ -744,8 +767,8 @@ void Area::build() { pending = true; } if(mySkippedShapes && !myHaveSolid) - Base::Console().Warning("%s %d non coplanar shapes\n", - myParams.Coplanar==CoplanarForce?"Skipped":"Found",mySkippedShapes); + AREA_WARN((myParams.Coplanar==CoplanarForce?"Skipped ":"Found ")<< + mySkippedShapes<<" non coplanar shapes"); if(pending){ if(myParams.OpenMode!=OpenModeNone) @@ -783,6 +806,8 @@ void Area::build() { myArea = std::move(area.myArea); } + TIME_PRINT(t,"prepare"); + }catch(...) { clean(); throw; @@ -790,7 +815,7 @@ void Area::build() { } list Area::sortWires(int index, int count, const gp_Pnt *pstart, - gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_MIN_DIST)) + gp_Pnt *_pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) { std::list wires; @@ -813,23 +838,22 @@ list Area::sortWires(int index, int count, const gp_Pnt *pstart, count = mySections.size(); for(int i=index;i ws = - mySections[i]->sortWires(-1,0,&pt,&pend, - PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + mySections[i]->sortWires(0,0,&pt,&pend, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); for(auto &wire : ws) wires.push_back(wire.Moved(loc)); pt = pend; } if(_pend) *_pend = pend.Transformed(loc); - return wires; + return std::move(wires); } if(!myArea || myArea->m_curves.empty()) return wires; CArea area(*myArea); Point p(pt.X(),pt.Y()); - area.ChangeStartToNearest(&p, - PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + area.ChangeStartToNearest(&p, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); gp_Trsf trsf(myTrsf.Inverted()); for(const CCurve &c : area.m_curves) { const TopoDS_Wire &wire = toShape(c,&trsf); @@ -846,7 +870,7 @@ list Area::sortWires(int index, int count, const gp_Pnt *pstart, } *_pend = pend.Transformed(TopLoc_Location(trsf)); } - return wires; + return std::move(wires); } TopoDS_Shape Area::toShape(CArea &area, short fill) { @@ -930,6 +954,8 @@ TopoDS_Shape Area::getShape(int index) { return myShape; } + TIME_INIT(t); + // do offset first, then pocket the inner most offseted shape std::list > areas; makeOffset(areas,PARAM_FIELDS(AREA_MY,AREA_PARAMS_OFFSET)); @@ -962,13 +988,20 @@ TopoDS_Shape Area::getShape(int index) { builder.MakeCompound(compound); short fill = myParams.Thicken?FillFace:FillNone; + TIME_INIT(t2); + DURATION_INIT(d); for(shared_ptr area : areas) { - if(myParams.Thicken) + if(myParams.Thicken){ area->Thicken(myParams.ToolRadius); + DURATION_PLUS(d,t2); + } const TopoDS_Shape &shape = toShape(*area,fill); if(shape.IsNull()) continue; builder.Add(compound,shape); } + if(myParams.Thicken) + DURATION_PRINT(d,"Thicken"); + // make sure the compound has at least one edge for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { builder.Add(compound,areaPocket.makePocket( @@ -977,6 +1010,7 @@ TopoDS_Shape Area::getShape(int index) { break; } myShapeDone = true; + TIME_PRINT(t,"total"); return myShape; } @@ -989,7 +1023,9 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET if(areas.empty()) { if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) { CArea area(*myArea); + TIME_INIT(t); area.Thicken(myParams.ToolRadius); + TIME_PRINT(t,"Thicken"); return toShape(area,FillFace); } return TopoDS_Shape(); @@ -997,10 +1033,13 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET BRep_Builder builder; TopoDS_Compound compound; builder.MakeCompound(compound); + TIME_INIT(t); + DURATION_INIT(d); for(shared_ptr area : areas) { short fill; if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) { area->Thicken(myParams.ToolRadius); + DURATION_PLUS(d,t); fill = FillFace; }else if(areas.size()==1) fill = myParams.Fill; @@ -1010,6 +1049,8 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET if(shape.IsNull()) continue; builder.Add(compound,shape); } + if(myParams.Thicken && myParams.ToolRadius>Precision::Confusion()) + DURATION_PRINT(d,"Thicken"); for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) return compound; return TopoDS_Shape(); @@ -1021,6 +1062,8 @@ void Area::makeOffset(list > &areas, if(fabs(offset) > &areas, break; } #endif - + if(count>1) + TIME_PRINT(t1,"makeOffset " << i << '/' << count); if(area.m_curves.empty()) return; } + TIME_PRINT(t,"makeOffset count: " << count); } TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKET)) { @@ -1098,6 +1143,8 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE build(); AREA_SECTION(makePocket,index,PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_POCKET)); + TIME_INIT(t); + PocketMode pm; switch(mode) { case Area::PocketModeZigZag: @@ -1128,6 +1175,8 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE in.Reorder(); in.MakePocketToolpath(out.m_curves,params); + TIME_PRINT(t,"makePocket"); + if(myParams.Thicken){ out.Thicken(tool_radius); return toShape(out,FillFace); @@ -1170,9 +1219,8 @@ TopoDS_Wire Area::toShape(const CCurve &c, const gp_Trsf *trsf) { newCenter.SetX(x - dx); newCenter.SetY(y - dy); } - Base::Console().Warning( - "Arc correction: %lf,%lf, center(%lf,%lf)->(%lf,%lf)\n", - r,r2,center.X(),center.Y(),newCenter.X(),newCenter.Y()); + AREA_WARN("Arc correction: "<"< &wires; + GetWires(std::list &ws) + :wires(ws) + {} + void operator()(const TopoDS_Shape &shape, int type) { + WireInfo info; + if(type == TopAbs_WIRE) + info.wire = shape; + else + info.wire = BRepBuilderAPI_MakeWire(TopoDS::Edge(shape)).Wire(); + + BRepTools_WireExplorer xp(TopoDS::Wire(shape)); + info.pstart = BRep_Tool::Pnt(xp.CurrentVertex()); + for(;xp.More();xp.Next()); + info.pend = BRep_Tool::Pnt(xp.CurrentVertex()); + info.wire = shape; + wires.push_back(info); + } +}; + +struct ShapeInfo{ + gp_Pln myPln; + std::list myWires; + TopoDS_Shape myShape; + gp_Pnt myBestPt; + std::list::iterator myBestWire; + TopoDS_Shape mySupport; + bool mySupportEdge; + bool myPlanar; + bool myRebase; + bool myStart; + + ShapeInfo(BRepLib_FindSurface &finder, const TopoDS_Shape &_shape) + :myPln(GeomAdaptor_Surface(finder.Surface()).Plane()) + ,myShape(_shape) + ,myPlanar(true) + {} + ShapeInfo(const TopoDS_Shape &_shape) + :myShape(_shape) + ,myPlanar(false) + {} + double nearest(const gp_Pnt &pt) { + if(myWires.empty()) + foreachSubshape(myShape,GetWires(myWires),TopAbs_WIRE); + TopoDS_Shape v = BRepBuilderAPI_MakeVertex(pt); + bool first = true; + double best_d=1e20; + myBestWire = myWires.begin(); + for(auto it=myWires.begin();it!=myWires.end();++it) { + const TopoDS_Shape &wire = it->wire; + TopoDS_Shape support; + bool support_edge; + double d; + gp_Pnt p; + bool done = false; + bool is_start = false; + if(BRep_Tool::IsClosed(wire)) { + BRepExtrema_DistShapeShape extss(v,wire); + if(extss.IsDone() && extss.NbSolution()) { + d = extss.Value(); + p = extss.PointOnShape2(0); + support = extss.SupportOnShape2(0); + support_edge = extss.SupportTypeShape2(0)==BRepExtrema_IsOnEdge; + done = true; + }else + AREA_WARN("BRepExtrema_DistShapeShape failed"); + } + if(!done){ + double d1 = p.Distance(it->pstart); + double d2 = p.Distance(it->pend); + AREA_TRACE("start "<pstart)<<", " << d1 << + AREA_PT(it->pend)<<", " <pstart; + is_start = true; + }else{ + d = d2; + p = it->pend; + is_start = false; + } + } + if(!first && d>=best_d) continue; + first = false; + myBestPt = p; + myBestWire = it; + best_d = d; + myRebase = done; + myStart = is_start; + if(done) { + mySupport = support; + mySupportEdge = support_edge; + } + } + return best_d; + } + + //Assumes nearest() has been called. Rebased the best wire + //to begin with the best point. Currently only works with closed wire + TopoDS_Shape rebaseWire(gp_Pnt &pend) { + BRepBuilderAPI_MakeWire mkWire; + TopoDS_Shape estart; + TopoDS_Edge eend; + for(int state=0;state<3;++state) { + BRepTools_WireExplorer xp(TopoDS::Wire(myBestWire->wire)); + pend = BRep_Tool::Pnt(xp.CurrentVertex()); + + //checking the case of bestpoint == wire start + if(state==0 && !mySupportEdge && pend.Distance(myBestPt)pend; + return myBestWire->wire; + } + + gp_Pnt pt; + for(;xp.More();xp.Next(),pend=pt) { + //state==2 means we are in second pass. estart marks the new start of the wire + if(state==2 && estart.IsSame(xp.Current())) + break; + + BRepAdaptor_Curve curve(xp.Current()); + bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); + pt = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); + //state!=0 means we've found the new start of wire, now just keep adding new edges + if(state) { + mkWire.Add(xp.Current()); + pend = pt; + continue; + } + //state==0 means we are looking for the new start + if(mySupportEdge) { + //if best point is on some edge, break the edge in half + if(xp.Current().IsEqual(mySupport)) { + estart = mySupport; + state = 1; + eend = BRepBuilderAPI_MakeEdge(curve.Curve().Curve(), pend, myBestPt); + mkWire.Add(BRepBuilderAPI_MakeEdge(curve.Curve().Curve(), myBestPt, pt)); + } + }else if(myBestPt.Distance(pend)pend; + return myBestWire->wire; + } + + std::list sortWires(gp_Pnt &pend) { + std::list wires; + while(true) { + AREA_TRACE("3D sort pt " << AREA_PT(myBestPt)); + if(myRebase) { + AREA_TRACE("3D sort rebase"); + wires.push_back(rebaseWire(pend)); + }else if(!myStart){ + AREA_TRACE("3D sort reverse"); + wires.push_back(myBestWire->wire.Reversed()); + pend = myBestWire->pstart; + }else{ + wires.push_back(myBestWire->wire); + pend = myBestWire->pend; + } + AREA_TRACE("3D sort end " << AREA_PT(pend)); + myWires.erase(myBestWire); + if(myWires.empty()) break; + nearest(pend); + } + return std::move(wires); + } +}; + +struct ShapeInfoBuilder { + std::list &myList; + + ShapeInfoBuilder(std::list &list) + :myList(list) + {} + + void operator()(const TopoDS_Shape &shape, int) { + BRepLib_FindSurface finder(shape,-1,Standard_True); + if(finder.Found()) + myList.push_back(ShapeInfo(finder,shape)); + else + myList.push_back(ShapeInfo(shape)); + } +}; + + std::list Area::sortWires(const std::list &shapes, const AreaParams *params, const gp_Pnt *_pstart, gp_Pnt *_pend, - PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT_WIRES)) + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) { std::list wires; - //Heristic sorting by shape's vertex Z value. For performance's sake, we don't - //perform any planar checking here - std::multimap shape_map; + if(shapes.empty()) return wires; - for (auto &shape : shapes) { - std::list subshapes; - if(!explode) - subshapes.push_back(shape); - else{ + if(sort_mode == SortModeNone) { + for(auto &shape : shapes) { + if (shape.IsNull()) + continue; bool haveShape=false; for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) { haveShape=true; - subshapes.push_back(it.Current()); - } - if(!haveShape) { - for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) - subshapes.push_back(it.Current()); + wires.push_back(it.Current()); } + if(haveShape) continue; + for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) + wires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire()); } - //Order the shapes by its vertex Z value. - for(auto &s : subshapes) { - bool first=true; - double z=0.0; - for(TopExp_Explorer it(s,TopAbs_VERTEX);it.More();) { - gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())); - if(first || z < p.Z()) { - first = false; - z = p.Z(); - } - if(!top_z) break; - } - shape_map.insert(std::pair(z,s)); - } + return std::move(wires); + } + + std::list shape_list; + + TIME_INIT2(t,t1); +#define SORT_WIRE_TIME(_msg) \ + TIME_PRINT(t1,"sortWires "<< _msg) + + if(sort_mode == SortMode3D) { + for(auto &shape : shapes) + shape_list.push_back(ShapeInfo(shape)); + }else{ + //first pass, find plane of each shape + for(auto &shape : shapes) { + //explode the shape + foreachSubshape(shape,ShapeInfoBuilder(shape_list)); + } + + if(shape_list.empty()) + return wires; + + SORT_WIRE_TIME("plan finding"); + } + + Bnd_Box bounds; + gp_Pnt pstart,pend; + if(_pstart) + pstart = *_pstart; + bool use_bound = fabs(pstart.X())myShape, bounds, Standard_False); + if(!it->myPlanar) continue; + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + bool empty = true; + for(auto itNext3=itNext,itNext2=itNext;itNext2!=shape_list.end();itNext2=itNext3) { + ++itNext3; + if(!itNext2->myPlanar || + !it->myPln.Position().IsCoplanar(itNext2->myPln.Position(), + Precision::Confusion(),Precision::Confusion())) + continue; + if(itNext == itNext2) ++itNext; + builder.Add(comp,itNext2->myShape); + shape_list.erase(itNext2); + empty = false; + } + if(!empty) { + builder.Add(comp,it->myShape); + it->myShape = comp; + } + } + SORT_WIRE_TIME("plane merging"); } - if(!shape_map.size()) - return wires; Area area(params); - //We'll do planar checking here, so disable Area planar check + //We have done planar checking here, so disable Area planar check area.myParams.Coplanar = Area::CoplanarNone; - gp_Pnt pstart,pend; - if(_pstart) pstart = *_pstart; - TopoDS_Shape plane = shape_map.rbegin()->second; - area.setPlane(plane); - for(auto &item : boost::adaptors::reverse(shape_map)) { - //do planar checking, and sort wires grouped by plane - if(!Area::isCoplanar(plane,item.second)) { - wires.splice(wires.end(),area.sortWires( - -1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST))); - pstart = pend; - area.clean(true); - plane = item.second; - area.setPlane(plane); - } - area.add(item.second,Area::OperationCompound); + DURATION_INIT2(td1,td2); + + if(use_bound) { + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + pstart.SetCoord(xMax,yMax,zMax); + } + bool has_2d5=false,has_3d=false; + while(shape_list.size()) { + AREA_TRACE("start " << shape_list.size() << ' ' << AREA_PT(pstart)); + double best_d; + auto best_it = shape_list.begin(); + bool first = true; + for(auto it=best_it;it!=shape_list.end();++it) { + double d; + gp_Pnt pt; + if(it->myPlanar){ + d = it->myPln.Distance(pstart); +#define AREA_TIME_2D5 \ + DURATION_PLUS(td1,t1);\ + has_2d5=true + + AREA_TIME_2D5; + }else{ + d = it->nearest(pstart); +#define AREA_TIME_3D \ + DURATION_PLUS(td2,t1);\ + has_3d=true + + AREA_TIME_3D; + } + if(first || dmyPlanar) { + area.clean(true); + area.myWorkPlane = best_it->myShape; + area.myTrsf.SetTransformation(best_it->myPln.Position()); + area.add(best_it->myShape,Area::OperationCompound); + wires.splice(wires.end(),area.sortWires( + 0,-1,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT))); + AREA_TIME_2D5; + }else{ + wires.splice(wires.end(),best_it->sortWires(pend)); + AREA_TIME_3D; + } + pstart = pend; + shape_list.erase(best_it); } - wires.splice(wires.end(),area.sortWires( - -1,0,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST))); if(_pend) *_pend = pend; - return wires; + + if(has_2d5) DURATION_PRINT(td1,"sortWires 2D5"); + if(has_3d) DURATION_PRINT(td2,"sortWires 3D"); + TIME_PRINT(t,"sortWires total"); + return std::move(wires); } static void addCommand(Toolpath &path, const gp_Pnt &p, @@ -1345,25 +1672,15 @@ static void addCommand(Toolpath &path, } void Area::toPath(Toolpath &path, const std::list &shapes, - const gp_Pnt *pstart, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) + const AreaParams *_params, const gp_Pnt *pstart, gp_Pnt *pend, + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) { std::list wires; - if(sort) - wires = sortWires(shapes,NULL,pstart); - else{ - for(auto &shape : shapes) { - if (shape.IsNull()) - continue; - bool haveShape=false; - for(TopExp_Explorer it(shape,TopAbs_WIRE);it.More();it.Next()) { - haveShape=true; - wires.push_back(it.Current()); - } - if(haveShape) continue; - for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) - wires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire()); - } - } + + AreaParams params; + if(_params) params =*_params; + wires = sortWires(shapes,¶ms,pstart,pend, + PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); if(threshold < Precision::Confusion()) threshold = Precision::Confusion(); @@ -1373,7 +1690,7 @@ void Area::toPath(Toolpath &path, const std::list &shapes, for(const TopoDS_Shape &wire : wires) { BRepTools_WireExplorer xp(TopoDS::Wire(wire)); p = BRep_Tool::Pnt(xp.CurrentVertex()); - if(first||(p.Z()>=plast.Z()&&p.Distance(plast)>threshold)) + if(first||p.Distance(plast)>threshold) addCommand(path,p,true,height,clearance); else addCommand(path,p); @@ -1386,6 +1703,25 @@ void Area::toPath(Toolpath &path, const std::list &shapes, switch (curve.GetType()) { case GeomAbs_Line: { + if(segmentation > Precision::Confusion()) { + GCPnts_UniformAbscissa discretizer(curve, segmentation, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 2) { + int nbPoints = discretizer.NbPoints (); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,pt); + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,pt); + } + } + break; + } + } addCommand(path,p); break; } case GeomAbs_Circle:{ @@ -1396,6 +1732,27 @@ void Area::toPath(Toolpath &path, const std::list &shapes, bool clockwise = axis.Direction().Z()<0; if(reversed) clockwise = !clockwise; gp_Pnt center = axis.Location(); + if(segmentation > Precision::Confusion()) { + GCPnts_UniformAbscissa discretizer(curve, segmentation, + curve.FirstParameter(), curve.LastParameter()); + if (discretizer.IsDone () && discretizer.NbPoints () > 2) { + int nbPoints = discretizer.NbPoints (); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,plast,pt,center,clockwise); + plast = pt; + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = curve.Value(discretizer.Parameter(i)); + addCommand(path,plast,pt,center,clockwise); + plast = pt; + } + } + break; + } + } if(fabs(first-last)>M_PI) { // Split arc(circle) larger than half circle. gp_Pnt mid = curve.Value((last-first)*0.5+first); @@ -1406,7 +1763,7 @@ void Area::toPath(Toolpath &path, const std::list &shapes, break; } default: { // Discretize all other type of curves - GCPnts_QuasiUniformDeflection discretizer(curve, deflection, + GCPnts_QuasiUniformDeflection discretizer(curve, params.Deflection, curve.FirstParameter(), curve.LastParameter()); if (discretizer.IsDone () && discretizer.NbPoints () > 1) { int nbPoints = discretizer.NbPoints (); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index de9cca972a..9db1cc2bc9 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -23,6 +23,8 @@ #ifndef PATH_AREA_H #define PATH_AREA_H +#include +#include #include #include #include @@ -31,9 +33,78 @@ #include #include +#include #include "Path.h" #include "AreaParams.h" +// #define AREA_TRACE_ENABLE + +#define _AREA_LOG(_l,_msg) do {\ + std::stringstream str;\ + str << "Path.Area: " << _msg;\ + Base::Console()._l("%s\n",str.str().c_str());\ + qApp->sendPostedEvents();\ +}while(0) + +#define AREA_LOG(_msg) _AREA_LOG(Log,_msg) +#define AREA_WARN(_msg) _AREA_LOG(Warning,_msg) +#define AREA_PT(_pt) '('<<(_pt).X()<<", " << (_pt).Y()<<", " << (_pt).Z()<<')' +#define AREA_PT2(_pt) '('<<(_pt).x<<", " << (_pt).y<<')' +#ifdef AREA_TRACE_ENABLE +# define AREA_TRACE AREA_LOG +#else +# define AREA_TRACE(...) do{}while(0) +#endif + +#define AREA_TIME_ENABLE + +#ifdef AREA_TIME_ENABLE +#define TIME_UNIT duration +#define TIME_CLOCK high_resolution_clock +#define TIME_POINT std::chrono::TIME_CLOCK::time_point + +#define TIME_INIT(_t) \ + auto _t=std::chrono::TIME_CLOCK::now() + +#define TIME_INIT2(_t1,_t2) TIME_INIT(_t1),_t2=_t1 +#define TIME_INIT3(_t1,_t2,_t3) TIME_INIT(_t1),_t2=_t1,_t3=_t1 + +#define DURATION_PRINT(_d,_msg) \ + AREA_LOG(_msg<< " time: " << _d.count()<<'s'); + +#define TIME_PRINT(_t,_msg) \ + DURATION_PRINT(Path::getDuration(_t),_msg); + +#define DURATION_INIT(_d) \ + std::chrono::TIME_UNIT _d(0) + +#define DURATION_INIT2(_d1,_d2) DURATION_INIT(_d1),_d2(0) + +namespace Path { +inline std::chrono::TIME_UNIT getDuration(TIME_POINT &t) +{ + auto tnow = std::chrono::TIME_CLOCK::now(); + auto d = std::chrono::duration_cast(tnow-t); + t = tnow; + return d; +} +} + +#define DURATION_PLUS(_d,_t) _d += Path::getDuration(_t) + +#else + +#define TIME_INIT(...) do{}while(0) +#define TIME_INIT2(...) do{}while(0) +#define TIME_INIT3(...) do{}while(0) +#define TIME_PRINT(...) do{}while(0) +#define DURATION_PRINT(...) do{}while(0) +#define DURATION_INIT(...) do{}while(0) +#define DURATION_INIT2(...) do{}while(0) +#define DURATION_PLUS(...) do{}while(0) + +#endif + class CArea; class CCurve; @@ -148,8 +219,6 @@ protected: bool isBuilt() const; - static bool findPlane(const TopoDS_Shape &shape, int type, - TopoDS_Shape &plane, gp_Trsf &trsf, double &top_z); TopoDS_Shape findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf); public: @@ -271,14 +340,15 @@ public: * \arg \c pstart: optional start point * \arg \c pend: optional output containing the ending point of the returned * wires + * \arg \c allow_Break: whether allow to break open wires * - * See #AREA_PARAMS_MIN_DIST for other arguments + * See #AREA_PARAMS_SORT for other arguments * * \return sorted wires * */ std::list sortWires(int index=-1, int count=0, - const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, - PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT)); /** Add a OCC generic shape to CArea * @@ -333,27 +403,31 @@ public: * used for sorting * \arg \c pstart: optional start point * \arg \c pend: optional output containing the ending point of the returned - * maybe broken if the algorithm see fits. * - * See #AREA_PARAMS_SORT_WIRES for other arguments + * See #AREA_PARAMS_SORT for other arguments * * \return sorted wires */ static std::list sortWires(const std::list &shapes, const AreaParams *params = NULL, const gp_Pnt *pstart=NULL, - gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT_WIRES)); + gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT)); /** Convert a list of wires to gcode * * \arg \c path: output toolpath * \arg \c shapes: input list of shapes + * \arg \c params: optional Area parameters for the Area object internally + * used for sorting * \arg \c pstart: output start point, + * \arg \c pend: optional output containing the ending point of the returned * * See #AREA_PARAMS_PATH for other arguments */ static void toPath(Toolpath &path, const std::list &shapes, - const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + const AreaParams *params=NULL, const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + PARAM_ENUM_DECLARE(AREA_PARAMS_PATH) }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 0f10d923be..9a6583ff15 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -71,10 +71,13 @@ AREA_PARAMS_DEFLECTION \ AREA_PARAMS_CLIPPER_FILL +#define AREA_PARAMS_FIT_ARCS \ + ((bool,fit_arcs,FitArcs,true,"Enable arc fitting")) + /** libarea algorithm option parameters */ #define AREA_PARAMS_CAREA \ ((double,tolerance,Tolerance,Precision::Confusion(),"Point coincidence tolerance"))\ - ((bool,fit_arcs,FitArcs,true,"Enable arc fitting"))\ + AREA_PARAMS_FIT_ARCS \ ((bool,clipper_simple,Simplify,false,\ "Simplify polygons after operation. See https://goo.gl/Mh9XK1"))\ ((double,clipper_clean_distance,CleanDistance,0.0,\ @@ -150,11 +153,24 @@ ((double,round_precision,RoundPreceision,0.0,\ "Round joint precision. If =0, it defaults to Accuracy. \nSee https://goo.gl/4odfQh")) +#define AREA_PARAMS_MIN_DIST \ + ((double, min_dist, MinDistance, 0.0, \ + "minimum distance for the generated new wires. Wires maybe broken if the algorithm see fits.\n"\ + "Set to zero to disable wire breaking.")) + +/** Area wire sorting parameters */ +#define AREA_PARAMS_SORT \ + ((enum, sort_mode, SortMode, 1, "Wire sorting mode to optimize travel distance.\n"\ + "'2D5' explode shapes into wires, and groups the shapes by its plane. The 'start' position\n"\ + "chooses the first plane to start. The algorithm will then sort within the plane and then\n"\ + "move on to the next nearest plane.\n"\ + "'3D' makes no assumption of planarity. The sorting is done across 3D space\n",\ + (None)(2D5)(3D)))\ + AREA_PARAMS_MIN_DIST + /** Area path generation parameters */ #define AREA_PARAMS_PATH \ - ((bool, sort, SortShape, true, \ - "Whether to sort the shapes to optimize travel distance. You can customize wire\n"\ - "sorting by calling sortWires() manually."))\ + AREA_PARAMS_SORT \ ((double, threshold, RetractThreshold, 0.0,\ "If two wire's end points are separated within this threshold, they are consider\n"\ "as connected. You may want to set this to the tool diameter to keep the tool down."))\ @@ -162,23 +178,17 @@ ((double, clearance, Clearance, 0.0,\ "When return from last retraction, this gives the pause height relative to the Z\n"\ "value of the next move"))\ - AREA_PARAMS_DEFLECTION + ((double,segmentation,Segmentation,0.0,\ + "Break long curves into segments of this length. One use case is for PCB autolevel,\n"\ + "so that more correction points can be inserted")) -#define AREA_PARAMS_MIN_DIST \ - ((double, min_dist, MinDistance, 1.0, \ - "minimum distance for the generate new wires. Wires maybe broken if the\n"\ - "algorithm see fits."))\ - -/** Area wire sorting parameters */ -#define AREA_PARAMS_SORT_WIRES \ - ((bool, explode, Explode, true,\ - "If ture, the input shape will be exploded into wires before doing planar checking.\n"\ - "Otherwise, and whole shape is considered to be in one plane without checking."))\ - ((bool, top_z, TopZ, false, \ - "If ture, the planes is ordered by the first the shapes first vertex Z value.\n"\ - "Otherwise, by the highest Z of all of its vertexes."))\ - AREA_PARAMS_MIN_DIST +#define AREA_PARAMS_PATH_EXTRA \ + AREA_PARAMS_DEFLECTION \ + AREA_PARAMS_FIT_ARCS +#define AREA_PARAMS_PATH_CONF \ + AREA_PARAMS_PATH \ + AREA_PARAMS_PATH_EXTRA /** Group of all Area configuration parameters except CArea's*/ #define AREA_PARAMS_AREA \ diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 3f44a6c98d..bec2817f0c 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -91,12 +91,12 @@ static const AreaDoc myDocs[] = { { "sortWires", - "sortWires(index=-1, count=0, start=Vector()" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_MIN_DIST) "):\n" + "sortWires(index=-1, count=0, start=Vector(), allow_break=False, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) "):\n" "Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint of the wires.\n" "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" "\n* count (0): the number of sections to return. <=0 means all sections starting from index.\n" "\n* start (Vector()): a vector specifies the start point.\n" - PARAM_PY_DOC(ARG,AREA_PARAMS_MIN_DIST), + PARAM_PY_DOC(ARG,AREA_PARAMS_SORT), }, }; @@ -151,7 +151,8 @@ PyObject* AreaPy::setPlane(PyObject *args) { #define GET_TOPOSHAPE(_p) static_cast(_p)->getTopoShapePtr()->getShape() getAreaPtr()->setPlane(GET_TOPOSHAPE(pcObj)); - return Py_None; + Py_INCREF(this); + return this; } PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) @@ -168,17 +169,17 @@ PyObject* AreaPy::getShape(PyObject *args, PyObject *keywds) } PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){ - PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_MIN_DIST) + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT) short index = -1; short count = 0; PyObject *start = NULL; static char *kwlist[] = {"index","count","start", - PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_MIN_DIST), NULL}; + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds, - "|hhO!" PARAM_PY_KWDS(AREA_PARAMS_MIN_DIST), + "|hhO!" PARAM_PY_KWDS(AREA_PARAMS_SORT), kwlist,&index,&count,&(Base::VectorPy::Type),&start, - PARAM_REF(PARAM_FARG,AREA_PARAMS_MIN_DIST))) + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT))) return 0; gp_Pnt pstart,pend; @@ -188,7 +189,7 @@ PyObject* AreaPy::sortWires(PyObject *args, PyObject *keywds){ } std::list wires = getAreaPtr()->sortWires( index,count,&pstart,&pend, - PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_MIN_DIST)); + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); PyObject *list = PyList_New(0); for(auto &wire : wires) PyList_Append(list,Py::new_reference_to( @@ -217,7 +218,8 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds) if (PyObject_TypeCheck(pcObj, &(Part::TopoShapePy::Type))) { getAreaPtr()->add(GET_TOPOSHAPE(pcObj),op); - return Py_None; + Py_INCREF(this); + return this; } else if (PyObject_TypeCheck(pcObj, &(PyList_Type)) || PyObject_TypeCheck(pcObj, &(PyTuple_Type))) { Py::Sequence shapeSeq(pcObj); @@ -233,7 +235,8 @@ PyObject* AreaPy::add(PyObject *args, PyObject *keywds) getAreaPtr()->add(GET_TOPOSHAPE(item), PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_OPCODE)); } - return Py_None; + Py_INCREF(this); + return this; } PyErr_SetString(PyExc_TypeError, "shape must be 'TopoShape' or list of 'TopoShape'"); @@ -356,7 +359,8 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) getAreaPtr()->setParams(params); - return Py_None; + Py_INCREF(this); + return this; } PyObject* AreaPy::getParams(PyObject *args) diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index f016a4611f..5d399a599c 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -64,10 +64,8 @@ FeatureArea::~FeatureArea() } Area &FeatureArea::getArea() { - if(!myBuild) { - myBuild = true; + if(!myBuild) execute(); - } return myArea; } @@ -85,6 +83,9 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) return new App::DocumentObjectExecReturn("Linked shape object is empty"); } + TIME_INIT(t); + + myBuild = true; AreaParams params; #define AREA_PROP_GET(_param) \ @@ -102,19 +103,35 @@ App::DocumentObjectExecReturn *FeatureArea::execute(void) PARAM_PROP_ARGS(AREA_PARAMS_OPCODE)); } - this->Shape.setValue(myArea.getShape(-1)); + myShapes.clear(); + if(myArea.getSectionCount()==0) + myShapes.push_back(myArea.getShape(-1)); + else { + myShapes.reserve(myArea.getSectionCount()); + for(int i=0;i<(int)myArea.getSectionCount();++i) + myShapes.push_back(myArea.getShape(i)); + } + + if(myShapes.empty()) + Shape.setValue(TopoDS_Shape()); + else if(myShapes.size()==1) + Shape.setValue(myShapes.front()); + else{ + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + for(auto &shape : myShapes) + builder.Add(compound,shape); + Shape.setValue(compound); + } + + TIME_PRINT(t,"feature execute"); return Part::Feature::execute(); } -std::list FeatureArea::getShapes() { - std::list shapes; - Area &area = getArea(); - if(area.getSectionCount()) { - for(int i=0;i<(int)area.getSectionCount();++i) - shapes.push_back(area.getShape(i)); - }else - shapes.push_back(area.getShape()); - return shapes; +const std::vector &FeatureArea::getShapes() { + getArea(); + return myShapes; } short FeatureArea::mustExecute(void) const @@ -156,32 +173,31 @@ std::list FeatureAreaView::getShapes() { if (!pObj) return shapes; if(!pObj->isDerivedFrom(FeatureArea::getClassTypeId())) return shapes; - Area &area = static_cast(pObj)->getArea(); - if(!area.getSectionCount()) { - shapes.push_back(area.getShape()); + auto all_shapes = static_cast(pObj)->getShapes(); + + if(all_shapes.empty()) return shapes; - } int index=SectionIndex.getValue(),count=SectionCount.getValue(); if(index<0) { - index += ((int)area.getSectionCount()); + index += ((int)all_shapes.size()); if(index<0) return shapes; if(count<=0 || index+1-count<0) { count = index+1; index = 0; }else index -= count-1; - }else if(index >= (int)area.getSectionCount()) + }else if(index >= (int)all_shapes.size()) return shapes; - if(count<=0) count = area.getSectionCount(); + if(count<=0) count = all_shapes.size(); count += index; - if(count>(int)area.getSectionCount()) - count = area.getSectionCount(); + if(count>(int)all_shapes.size()) + count = all_shapes.size(); for(int i=index;i getShapes(); + const std::vector &getShapes(); /// returns the type name of the ViewProvider virtual const char* getViewProviderName(void) const { @@ -63,6 +63,7 @@ public: private: bool myBuild; Area myArea; + std::vector myShapes; }; typedef App::FeaturePythonT FeatureAreaPython; diff --git a/src/Mod/Path/App/FeaturePathShape.cpp b/src/Mod/Path/App/FeaturePathShape.cpp index 5f148b487a..300b8739d1 100644 --- a/src/Mod/Path/App/FeaturePathShape.cpp +++ b/src/Mod/Path/App/FeaturePathShape.cpp @@ -50,12 +50,14 @@ using namespace Path; PROPERTY_SOURCE(Path::FeatureShape, Path::Feature) +PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_PATH) FeatureShape::FeatureShape() { ADD_PROPERTY(Sources,(0)); ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position"); - PARAM_PROP_ADD("Path",AREA_PARAMS_PATH); + PARAM_PROP_ADD("Path",AREA_PARAMS_PATH_CONF); + PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_PATH_CONF); } FeatureShape::~FeatureShape() @@ -83,7 +85,13 @@ App::DocumentObjectExecReturn *FeatureShape::execute(void) continue; shapes.push_back(shape); } - Area::toPath(path,shapes,&pstart,PARAM_PROP_ARGS(AREA_PARAMS_PATH)); + + AreaParams params; +#define AREA_PROP_GET(_param) \ + params.PARAM_FNAME(_param) = PARAM_FNAME(_param).getValue(); + PARAM_FOREACH(AREA_PROP_GET,AREA_PARAMS_PATH_EXTRA) + + Area::toPath(path,shapes,¶ms,&pstart,NULL,PARAM_PROP_ARGS(AREA_PARAMS_PATH)); Path.setValue(path); return App::DocumentObject::StdReturn; diff --git a/src/Mod/Path/App/FeaturePathShape.h b/src/Mod/Path/App/FeaturePathShape.h index 7478a25899..4a539761ca 100644 --- a/src/Mod/Path/App/FeaturePathShape.h +++ b/src/Mod/Path/App/FeaturePathShape.h @@ -52,7 +52,7 @@ public: // Part::PropertyPartShape Shape; App::PropertyLinkList Sources; App::PropertyVector StartPoint; - PARAM_PROP_DECLARE(AREA_PARAMS_PATH) + PARAM_PROP_DECLARE(AREA_PARAMS_PATH_CONF) //@{ /// recalculate the feature From 4a2c7eb13319e18d4ea1ac66e61fb3b2e6b99410 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 1 Feb 2017 02:01:33 +0800 Subject: [PATCH 32/35] Path: fixed fromShapes and sortWires parameter parsing --- src/Mod/Path/App/AppPathPy.cpp | 23 ++++++++++++----------- src/Mod/Path/App/AreaParams.h | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index f7b53cde1b..d2c10f97ff 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -119,20 +119,21 @@ public: "fromShape(Shape): Returns a Path object from a Part Shape" ); add_keyword_method("fromShapes",&Module::fromShapes, - "fromShapes(shapes, sort=True, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ")\n" + "fromShapes(shapes, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ", key=value...)\n" "\nReturns a Path object from a list of shapes\n" "\n* shapes: input list of shapes.\n" "\n* start (Vector()): optional start position.\n" PARAM_PY_DOC(ARG, AREA_PARAMS_PATH) + "\n* : any key supported by Path.Area, see Path.Area.getParamDesc() for description" ); add_keyword_method("sortWires",&Module::sortWires, - "sortWires(shapes, start=Vector(), params=None, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ")\n" + "sortWires(shapes, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ", key=value...)\n" "\nReturns (wires,end), where 'wires' is sorted accross Z value and with optimized travel distance,\n" "and 'end' is the ending position of the whole wires\n" "\n* shapes: input shape list\n" "\n* start (Vector()): optional start position.\n" - "\n* params (None): optional dictionary for configuring Path.Area internally used to sort the wires.\n" PARAM_PY_DOC(ARG, AREA_PARAMS_SORT) + "\n* : any key supported by Path.Area, see Path.Area.getParamDesc() for description" ); initialize("This module is the Path module."); // register with Python } @@ -327,10 +328,10 @@ private: PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_PATH), PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), - "O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH), kwd_list, - &pShapes, &(Base::VectorPy::Type), &start, - PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH), - PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_PATH) PARAM_PY_KWDS(AREA_PARAMS_CONF), + kwd_list, &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_PATH), + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) throw Py::Exception(); std::list shapes; @@ -380,10 +381,10 @@ private: PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), - "O|O!" PARAM_PY_KWDS(AREA_PARAMS_SORT), kwd_list, - &pShapes, &(Base::VectorPy::Type), &start, - PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT), - PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) + "O|O!" PARAM_PY_KWDS(AREA_PARAMS_SORT) PARAM_PY_KWDS(AREA_PARAMS_CONF), + kwd_list, &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT), + PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) throw Py::Exception(); std::list shapes; diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 9a6583ff15..c59fa34a10 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -155,8 +155,8 @@ #define AREA_PARAMS_MIN_DIST \ ((double, min_dist, MinDistance, 0.0, \ - "minimum distance for the generated new wires. Wires maybe broken if the algorithm see fits.\n"\ - "Set to zero to disable wire breaking.")) + "minimum distance for the generated new wires. Wires maybe broken if the\n"\ + "algorithm see fits. Set to zero to disable wire breaking.")) /** Area wire sorting parameters */ #define AREA_PARAMS_SORT \ From 84a51626982563025355f1d0748c105988225f83 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 1 Feb 2017 02:02:07 +0800 Subject: [PATCH 33/35] Part.Area: fixed sortWires3D --- src/Mod/Path/App/Area.cpp | 90 +++++++++++++++++++++++++++++---------- src/Mod/Path/App/Area.h | 11 +++-- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 07bcda2a77..1797f84736 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -729,7 +729,10 @@ void Area::build() { return; } +#ifdef AREA_TRACE_ENABLE TIME_INIT(t); +#endif + getPlane(); try { @@ -806,7 +809,7 @@ void Area::build() { myArea = std::move(area.myArea); } - TIME_PRINT(t,"prepare"); + TIME_TRACE(t,"prepare"); }catch(...) { clean(); @@ -1283,7 +1286,7 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) { } struct WireInfo { - TopoDS_Shape wire; + TopoDS_Wire wire; gp_Pnt pend; gp_Pnt pstart; }; @@ -1296,15 +1299,14 @@ struct GetWires { void operator()(const TopoDS_Shape &shape, int type) { WireInfo info; if(type == TopAbs_WIRE) - info.wire = shape; + info.wire = TopoDS::Wire(shape); else info.wire = BRepBuilderAPI_MakeWire(TopoDS::Edge(shape)).Wire(); - BRepTools_WireExplorer xp(TopoDS::Wire(shape)); + BRepTools_WireExplorer xp(info.wire); info.pstart = BRep_Tool::Pnt(xp.CurrentVertex()); for(;xp.More();xp.Next()); info.pend = BRep_Tool::Pnt(xp.CurrentVertex()); - info.wire = shape; wires.push_back(info); } }; @@ -1349,18 +1351,16 @@ struct ShapeInfo{ BRepExtrema_DistShapeShape extss(v,wire); if(extss.IsDone() && extss.NbSolution()) { d = extss.Value(); - p = extss.PointOnShape2(0); - support = extss.SupportOnShape2(0); - support_edge = extss.SupportTypeShape2(0)==BRepExtrema_IsOnEdge; + p = extss.PointOnShape2(1); + support = extss.SupportOnShape2(1); + support_edge = extss.SupportTypeShape2(1)==BRepExtrema_IsOnEdge; done = true; }else AREA_WARN("BRepExtrema_DistShapeShape failed"); } if(!done){ - double d1 = p.Distance(it->pstart); - double d2 = p.Distance(it->pend); - AREA_TRACE("start "<pstart)<<", " << d1 << - AREA_PT(it->pend)<<", " <pstart); + double d2 = pt.Distance(it->pend); if(d1pstart; @@ -1388,24 +1388,31 @@ struct ShapeInfo{ //Assumes nearest() has been called. Rebased the best wire //to begin with the best point. Currently only works with closed wire - TopoDS_Shape rebaseWire(gp_Pnt &pend) { + TopoDS_Shape rebaseWire(gp_Pnt &pend, double min_dist) { + AREA_TRACE("rebase wire"); BRepBuilderAPI_MakeWire mkWire; TopoDS_Shape estart; TopoDS_Edge eend; + + if(min_dist < Precision::Confusion()) + min_dist = Precision::Confusion(); + for(int state=0;state<3;++state) { BRepTools_WireExplorer xp(TopoDS::Wire(myBestWire->wire)); pend = BRep_Tool::Pnt(xp.CurrentVertex()); //checking the case of bestpoint == wire start if(state==0 && !mySupportEdge && pend.Distance(myBestPt)pend; return myBestWire->wire; } gp_Pnt pt; for(;xp.More();xp.Next(),pend=pt) { - //state==2 means we are in second pass. estart marks the new start of the wire - if(state==2 && estart.IsSame(xp.Current())) + //state==2 means we are in second pass. estart marks the new start of the wire. + //so seeing estart means we're done + if(state==2 && estart.IsEqual(xp.Current())) break; BRepAdaptor_Curve curve(xp.Current()); @@ -1421,12 +1428,50 @@ struct ShapeInfo{ if(mySupportEdge) { //if best point is on some edge, break the edge in half if(xp.Current().IsEqual(mySupport)) { - estart = mySupport; - state = 1; - eend = BRepBuilderAPI_MakeEdge(curve.Curve().Curve(), pend, myBestPt); - mkWire.Add(BRepBuilderAPI_MakeEdge(curve.Curve().Curve(), myBestPt, pt)); + double d1 = pend.Distance(myBestPt); + double d2 = pt.Distance(myBestPt); + + if(d1>min_dist && d2>min_dist) { + BRepBuilderAPI_MakeEdge mkEdge1,mkEdge2; + if(reversed) { + mkEdge1.Init(curve.Curve().Curve(), myBestPt, myBestPt); + mkEdge2.Init(curve.Curve().Curve(), pt, myBestPt); + }else{ + mkEdge1.Init(curve.Curve().Curve(), pend, myBestPt); + mkEdge2.Init(curve.Curve().Curve(), myBestPt, pt); + } + if(mkEdge1.IsDone() && mkEdge2.IsDone()) { + if(reversed) { + eend = TopoDS::Edge(mkEdge1.Edge().Reversed()); + mkWire.Add(TopoDS::Edge(mkEdge2.Edge().Reversed())); + }else{ + eend = mkEdge1.Edge(); + mkWire.Add(mkEdge2.Edge()); + } + estart = mySupport; + state = 1; + AREA_TRACE("edge broken "<start"); + estart = xp.Current(); + state = 1; + mkWire.Add(xp.Current()); + }else{ + AREA_TRACE("break edge->end"); + mySupportEdge = false; + myBestPt = pt; + continue; + } } }else if(myBestPt.Distance(pend)wire; } - std::list sortWires(gp_Pnt &pend) { + std::list sortWires3D(gp_Pnt &pend,double min_dist) { std::list wires; while(true) { AREA_TRACE("3D sort pt " << AREA_PT(myBestPt)); if(myRebase) { AREA_TRACE("3D sort rebase"); - wires.push_back(rebaseWire(pend)); + pend = myBestPt; + wires.push_back(rebaseWire(pend,min_dist)); }else if(!myStart){ AREA_TRACE("3D sort reverse"); wires.push_back(myBestWire->wire.Reversed()); @@ -1619,7 +1665,7 @@ std::list Area::sortWires(const std::list &shapes, 0,-1,&pstart,&pend, PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT))); AREA_TIME_2D5; }else{ - wires.splice(wires.end(),best_it->sortWires(pend)); + wires.splice(wires.end(),best_it->sortWires3D(pend,min_dist)); AREA_TIME_3D; } pstart = pend; diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 9db1cc2bc9..2955d7cb25 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -51,7 +51,7 @@ #define AREA_PT(_pt) '('<<(_pt).X()<<", " << (_pt).Y()<<", " << (_pt).Z()<<')' #define AREA_PT2(_pt) '('<<(_pt).x<<", " << (_pt).y<<')' #ifdef AREA_TRACE_ENABLE -# define AREA_TRACE AREA_LOG +# define AREA_TRACE(_msg) AREA_LOG('('<<__LINE__<<"): " <<_msg) #else # define AREA_TRACE(...) do{}while(0) #endif @@ -69,12 +69,17 @@ #define TIME_INIT2(_t1,_t2) TIME_INIT(_t1),_t2=_t1 #define TIME_INIT3(_t1,_t2,_t3) TIME_INIT(_t1),_t2=_t1,_t3=_t1 -#define DURATION_PRINT(_d,_msg) \ - AREA_LOG(_msg<< " time: " << _d.count()<<'s'); +#define _DURATION_PRINT(_l,_d,_msg) \ + AREA_##_l(_msg<< " time: " << _d.count()<<'s'); + +#define DURATION_PRINT(_d,_msg) _DURATION_PRINT(LOG,_d,_msg) #define TIME_PRINT(_t,_msg) \ DURATION_PRINT(Path::getDuration(_t),_msg); +#define TIME_TRACE(_t,_msg) \ + _DURATION_PRINT(TRACE,Path::getDuration(_t),_msg); + #define DURATION_INIT(_d) \ std::chrono::TIME_UNIT _d(0) From ce271046580e57ebaec505ea4a949d0007510e18 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 1 Feb 2017 02:45:13 +0800 Subject: [PATCH 34/35] Path.Area: added python abort() to abort lengthy operation --- src/Mod/Path/App/Area.cpp | 2 ++ src/Mod/Path/App/Area.h | 13 +++++++++++++ src/Mod/Path/App/AreaPy.xml | 7 +++++++ src/Mod/Path/App/AreaPyImp.cpp | 25 ++++++++++++++++++++----- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 1797f84736..794f51059f 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -108,6 +108,8 @@ CAreaConfig::~CAreaConfig() { TYPESYSTEM_SOURCE(Path::Area, Base::BaseClass); +bool Area::s_aborting; + Area::Area(const AreaParams *params) :myHaveFace(false) ,myHaveSolid(false) diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 2955d7cb25..06a71a9727 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -44,6 +44,10 @@ str << "Path.Area: " << _msg;\ Base::Console()._l("%s\n",str.str().c_str());\ qApp->sendPostedEvents();\ + if(Area::aborted()) {\ + Area::abort(false);\ + throw Base::AbortException("operation aborted");\ + }\ }while(0) #define AREA_LOG(_msg) _AREA_LOG(Log,_msg) @@ -195,6 +199,7 @@ protected: bool myHaveSolid; bool myShapeDone; int mySkippedShapes; + static bool s_aborting; /** Called internally to combine children shapes for further processing */ void build(); @@ -432,6 +437,14 @@ public: const AreaParams *params=NULL, const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + static void abort(bool aborting) { + s_aborting = aborting; + } + + static bool aborted() { + return s_aborting; + } + PARAM_ENUM_DECLARE(AREA_PARAMS_PATH) }; diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 170288ed3c..622dbb04a2 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -77,6 +77,13 @@ same algorithm Get current algorithm parameters as a dictionary. + + + abort(aborting=True): Static method to abort any ongoing operation\n +To ensure no stray abortion is left in the previous operaion, it is advised to manually +clear the aborting flag by calling abort(False) before starting a new operation. + + List of sections in this area. diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index bec2817f0c..29db5f4859 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -100,10 +100,23 @@ static const AreaDoc myDocs[] = { }, }; -struct AreaPyDoc { - AreaPyDoc() { +static PyObject * abortArea(PyObject *, PyObject *args, PyObject *kwd) { + static char *kwlist[] = {"aborting", NULL}; + PyObject *pObj = Py_True; + if (!PyArg_ParseTupleAndKeywords(args,kwd,"|O",kwlist,&pObj)) + return 0; + Area::abort(PyObject_IsTrue(pObj)); + return Py_None; +} + +struct AreaPyModifier { + AreaPyModifier() { for(PyMethodDef &method : Path::AreaPy::Methods) { if(!method.ml_name) continue; + if(std::strcmp(method.ml_name,"abort")==0) { + method.ml_meth = (PyCFunction)abortArea; + method.ml_flags |= METH_STATIC; + } for(const AreaDoc &doc : myDocs) { if(std::strcmp(method.ml_name,doc.name)==0) { method.ml_doc = doc.doc; @@ -114,7 +127,7 @@ struct AreaPyDoc { } }; -static AreaPyDoc doc; +static AreaPyModifier mod; namespace Part { extern PartExport Py::Object shape2pyshape(const TopoDS_Shape &shape); @@ -376,6 +389,10 @@ PyObject* AreaPy::getParams(PyObject *args) return dict; } +PyObject* AreaPy::abort(PyObject *, PyObject *) { + return 0; +} + PyObject* AreaPy::getParamsDesc(PyObject *args, PyObject *keywds) { PyObject *pcObj = Py_True; @@ -424,5 +441,3 @@ int AreaPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) { return 0; } - - From b35e41771a152ff39d8e224d76bdf34622795b60 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 1 Feb 2017 12:02:01 +0800 Subject: [PATCH 35/35] Path.Area: added python static method setDefaultParams() set/getDefaultParams controls the default parameters used when creating Path.Area object. It also has extra parameter to control Path.Area log level. --- src/Mod/Path/App/Area.cpp | 34 +++++++- src/Mod/Path/App/Area.h | 50 +++++++----- src/Mod/Path/App/AreaParams.h | 11 +++ src/Mod/Path/App/AreaPy.xml | 14 +++- src/Mod/Path/App/AreaPyImp.cpp | 142 ++++++++++++++++++++++----------- 5 files changed, 180 insertions(+), 71 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 794f51059f..6e7a6364e6 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -111,7 +111,8 @@ TYPESYSTEM_SOURCE(Path::Area, Base::BaseClass); bool Area::s_aborting; Area::Area(const AreaParams *params) -:myHaveFace(false) +:myParams(s_params) +,myHaveFace(false) ,myHaveSolid(false) ,myShapeDone(false) { @@ -731,10 +732,7 @@ void Area::build() { return; } -#ifdef AREA_TRACE_ENABLE TIME_INIT(t); -#endif - getPlane(); try { @@ -1832,3 +1830,31 @@ void Area::toPath(Toolpath &path, const std::list &shapes, } } } + +void Area::abort(bool aborting) { + s_aborting = aborting; +} + +bool Area::aborting() { + return s_aborting; +} + +AreaStaticParams::AreaStaticParams() + :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_EXTRA_CONF) +{} + +AreaStaticParams Area::s_params; + +void Area::setDefaultParams(const AreaStaticParams ¶ms){ + s_params = params; +} + +const AreaStaticParams &Area::getDefaultParams() { + return s_params; +} + +#define AREA_LOG_CHECK_DEFINE(_1,_2,_elem) \ +bool Area::BOOST_PP_CAT(_elem,Enabled)() {\ + return s_params.LogLevel >= BOOST_PP_CAT(LogLevel,_elem);\ +} +BOOST_PP_SEQ_FOR_EACH(AREA_LOG_CHECK_DEFINE,_,AREA_PARAM_LOG_LEVEL) diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 06a71a9727..336562c591 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -37,14 +37,14 @@ #include "Path.h" #include "AreaParams.h" -// #define AREA_TRACE_ENABLE - #define _AREA_LOG(_l,_msg) do {\ - std::stringstream str;\ - str << "Path.Area: " << _msg;\ - Base::Console()._l("%s\n",str.str().c_str());\ + if(Area::_l##Enabled()){\ + std::stringstream str;\ + str << "Path.Area: " << _msg;\ + Base::Console()._l("%s\n",str.str().c_str());\ + }\ qApp->sendPostedEvents();\ - if(Area::aborted()) {\ + if(Area::aborting()) {\ Area::abort(false);\ throw Base::AbortException("operation aborted");\ }\ @@ -52,13 +52,13 @@ #define AREA_LOG(_msg) _AREA_LOG(Log,_msg) #define AREA_WARN(_msg) _AREA_LOG(Warning,_msg) +#define AREA_ERR(_msg) _AREA_LOG(Error,_msg) #define AREA_PT(_pt) '('<<(_pt).X()<<", " << (_pt).Y()<<", " << (_pt).Z()<<')' #define AREA_PT2(_pt) '('<<(_pt).x<<", " << (_pt).y<<')' -#ifdef AREA_TRACE_ENABLE -# define AREA_TRACE(_msg) AREA_LOG('('<<__LINE__<<"): " <<_msg) -#else -# define AREA_TRACE(...) do{}while(0) -#endif + +#define AREA_TRACE(_msg) do{\ + if(Area::TraceEnabled()) AREA_LOG('('<<__LINE__<<"): " <<_msg);\ +}while(0) #define AREA_TIME_ENABLE @@ -145,6 +145,12 @@ struct PathExport AreaParams: CAreaParams { AreaParams(); }; +struct PathExport AreaStaticParams: AreaParams { + PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_EXTRA_CONF); + + AreaStaticParams(); +}; + /** libarea configurator * * It is kind of troublesome with the fact that libarea uses static variables to @@ -199,7 +205,9 @@ protected: bool myHaveSolid; bool myShapeDone; int mySkippedShapes; + static bool s_aborting; + static AreaStaticParams s_params; /** Called internally to combine children shapes for further processing */ void build(); @@ -437,15 +445,19 @@ public: const AreaParams *params=NULL, const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); - static void abort(bool aborting) { - s_aborting = aborting; - } - - static bool aborted() { - return s_aborting; - } - PARAM_ENUM_DECLARE(AREA_PARAMS_PATH) + + static void abort(bool aborting); + static bool aborting(); + + static void setDefaultParams(const AreaStaticParams ¶ms); + static const AreaStaticParams &getDefaultParams(); + +#define AREA_LOG_CHECK_DECLARE(_1,_2,_elem) \ + static bool BOOST_PP_CAT(_elem,Enabled)(); + BOOST_PP_SEQ_FOR_EACH(AREA_LOG_CHECK_DECLARE,_,AREA_PARAM_LOG_LEVEL) + + PARAM_ENUM_DECLARE(AREA_PARAMS_LOG_LEVEL) }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index c59fa34a10..8dc4be2d2b 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -209,4 +209,15 @@ AREA_PARAMS_CONF \ AREA_PARAMS_OPCODE +#define AREA_PARAM_LOG_LEVEL (Error)(Warning)(Log)(Trace) +#define AREA_PARAMS_LOG_LEVEL \ + ((enum, log_level, LogLevel, 1, "Area log level", AREA_PARAM_LOG_LEVEL)) + +#define AREA_PARAMS_EXTRA_CONF \ + AREA_PARAMS_LOG_LEVEL + +#define AREA_PARAMS_STATIC_CONF \ + AREA_PARAMS_CONF \ + AREA_PARAMS_EXTRA_CONF + #endif //PATH_AreaParam_H diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 622dbb04a2..ad4a0a306f 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -60,6 +60,16 @@ same algorithm + + + + + + + + + + getParamsDesc(as_string=True): Returns a list of supported parameters and their descriptions.\n @@ -79,9 +89,7 @@ same algorithm - abort(aborting=True): Static method to abort any ongoing operation\n -To ensure no stray abortion is left in the previous operaion, it is advised to manually -clear the aborting flag by calling abort(False) before starting a new operation. + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 29db5f4859..3fb7f10893 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -32,25 +32,68 @@ #include "AreaPy.cpp" -struct AreaDoc { - const char *name; - const char *doc; -}; +static PyObject * areaAbort(PyObject *, PyObject *args, PyObject *kwd) { + static char *kwlist[] = {"aborting", NULL}; + PyObject *pObj = Py_True; + if (!PyArg_ParseTupleAndKeywords(args,kwd,"|O",kwlist,&pObj)) + return 0; + Area::abort(PyObject_IsTrue(pObj)); + return Py_None; +} -/** Generate doc string from parameter definitions - * It will generate doc string and replace the one generated from xml - * */ -static const AreaDoc myDocs[] = { +static PyObject * areaSetParams(PyObject *, PyObject *args, PyObject *kwd) { + + static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_STATIC_CONF),NULL}; + + //Declare variables defined in the NAME field of the CONF parameter list + PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_STATIC_CONF); + + AreaStaticParams params = Area::getDefaultParams(); + +#define AREA_SET(_param) \ + PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); + //populate the CONF variables with params + PARAM_FOREACH(AREA_SET,AREA_PARAMS_STATIC_CONF) + + //Parse arguments to overwrite CONF variables + if (!PyArg_ParseTupleAndKeywords(args, kwd, + "|" PARAM_PY_KWDS(AREA_PARAMS_STATIC_CONF), kwlist, + PARAM_REF(PARAM_FNAME,AREA_PARAMS_STATIC_CONF))) + return 0; + +#define AREA_GET(_param) \ + params.PARAM_FNAME(_param) = \ + PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); + //populate 'params' with the CONF variables + PARAM_FOREACH(AREA_GET,AREA_PARAMS_STATIC_CONF) + + Area::setDefaultParams(params); + return Py_None; +} + +static PyObject* areaGetParams(PyObject *, PyObject *args) { + if (!PyArg_ParseTuple(args, "")) + return 0; + + const AreaStaticParams ¶ms = Area::getDefaultParams(); + + PyObject *dict = PyDict_New(); +#define AREA_SRC(_param) params.PARAM_FNAME(_param) + PARAM_PY_DICT_SET_VALUE(dict,NAME,AREA_SRC,AREA_PARAMS_STATIC_CONF) + return dict; +} + + +static const PyMethodDef areaOverrides[] = { { - "setParams", + "setParams",NULL,0, "setParam(key=value...): Set algorithm parameters. You can call getParamsDesc() to \n" "get a list of supported parameters and their descriptions.\n" - PARAM_PY_DOC(NAME,AREA_PARAMS_CONF) }, { - "add", - + "add",NULL,0, "add((shape...)," PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OPCODE) "):\n" "Add TopoShape(s) with given operation code\n" PARAM_PY_DOC(ARG,AREA_PARAMS_OPCODE) @@ -62,24 +105,21 @@ static const AreaDoc myDocs[] = { }, { - "makeOffset", - + "makeOffset",NULL,0, "makeOffset(index=-1, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OFFSET) "):\n" "Make an 2D offset of the shape.\n" "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_OFFSET), }, { - "makePocket", - + "makePocket",NULL,0, "makePocket(index=-1, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_POCKET) "):\n" "Generate pocket toolpath of the shape.\n" "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), }, { - "makeSections", - + "makeSections",NULL,0, "makeSections(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) ", heights=[], plane=None):\n" "Make a list of area holding the sectioned children shapes on given heights\n" PARAM_PY_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) @@ -89,37 +129,46 @@ static const AreaDoc myDocs[] = { "of this Area is used.", }, { - "sortWires", - + "sortWires",NULL,0, "sortWires(index=-1, count=0, start=Vector(), allow_break=False, " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) "):\n" - "Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint of the wires.\n" + "Returns a tuple (wires,end): sorted wires with minimized travel distance, and the endpoint\n" + "of the wires.\n" "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" "\n* count (0): the number of sections to return. <=0 means all sections starting from index.\n" "\n* start (Vector()): a vector specifies the start point.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_SORT), }, + { + "setDefaultParams",(PyCFunction)areaSetParams, METH_VARARGS|METH_KEYWORDS|METH_STATIC, + "setDefaultParams(" PARAM_PY_ARGS_DOC(NAME,AREA_PARAMS_EXTRA_CONF) ", key=value...):\n" + "Static method to set the default parameters of all following Path.Area, plus the following\n" + "additional parameters.\n" + PARAM_PY_DOC(NAME,AREA_PARAMS_EXTRA_CONF) + }, + { + "getDefaultParams",(PyCFunction)areaGetParams, METH_VARARGS|METH_STATIC, + "getDefaultParams(): Static method to return the current default parameters." + }, + { + "abort",(PyCFunction)areaAbort, METH_VARARGS|METH_KEYWORDS|METH_STATIC, + "abort(aborting=True): Static method to abort any ongoing operation\n" + "\nTo ensure no stray abortion is left in the previous operaion, it is advised to manually clear\n" + "the aborting flag by calling abort(False) before starting a new operation.", + }, }; -static PyObject * abortArea(PyObject *, PyObject *args, PyObject *kwd) { - static char *kwlist[] = {"aborting", NULL}; - PyObject *pObj = Py_True; - if (!PyArg_ParseTupleAndKeywords(args,kwd,"|O",kwlist,&pObj)) - return 0; - Area::abort(PyObject_IsTrue(pObj)); - return Py_None; -} - struct AreaPyModifier { AreaPyModifier() { - for(PyMethodDef &method : Path::AreaPy::Methods) { + for(auto &method : Path::AreaPy::Methods) { if(!method.ml_name) continue; - if(std::strcmp(method.ml_name,"abort")==0) { - method.ml_meth = (PyCFunction)abortArea; - method.ml_flags |= METH_STATIC; - } - for(const AreaDoc &doc : myDocs) { - if(std::strcmp(method.ml_name,doc.name)==0) { - method.ml_doc = doc.doc; + for(auto &entry : areaOverrides) { + if(std::strcmp(method.ml_name,entry.ml_name)==0) { + if(entry.ml_doc) + method.ml_doc = entry.ml_doc; + if(entry.ml_meth) + method.ml_meth = entry.ml_meth; + if(entry.ml_flags) + method.ml_flags = entry.ml_flags; break; } } @@ -344,6 +393,11 @@ PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) return Py::new_reference_to(ret); } +PyObject* AreaPy::setDefaultParams(PyObject *, PyObject *) +{ + return 0; +} + PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) { static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; @@ -353,9 +407,6 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) AreaParams params = getAreaPtr()->getParams(); -#define AREA_SET(_param) \ - PARAM_FNAME(_param) = \ - PARAM_TYPED(PARAM_PY_CAST_,_param)(params.PARAM_FNAME(_param)); //populate the CONF variables with params PARAM_FOREACH(AREA_SET,AREA_PARAMS_CONF) @@ -365,9 +416,6 @@ PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) return 0; -#define AREA_GET(_param) \ - params.PARAM_FNAME(_param) = \ - PARAM_TYPED(PARAM_CAST_PY_,_param)(PARAM_FNAME(_param)); //populate 'params' with the CONF variables PARAM_FOREACH(AREA_GET,AREA_PARAMS_CONF) @@ -384,11 +432,15 @@ PyObject* AreaPy::getParams(PyObject *args) const AreaParams ¶ms =getAreaPtr()->getParams(); PyObject *dict = PyDict_New(); -#define AREA_SRC(_param) params.PARAM_FNAME(_param) PARAM_PY_DICT_SET_VALUE(dict,NAME,AREA_SRC,AREA_PARAMS_CONF) return dict; } +PyObject* AreaPy::getDefaultParams(PyObject *) +{ + return 0; +} + PyObject* AreaPy::abort(PyObject *, PyObject *) { return 0; }