| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | #define BOOST_GEOMETRY_DISABLE_DEPRECATED_03_WARNING |
| |
|
| | #include <limits> |
| |
|
| | #include <boost/geometry.hpp> |
| | #include <boost/geometry/geometries/register/point.hpp> |
| | #include <boost/geometry/index/rtree.hpp> |
| | #include <boost/range/adaptor/transformed.hpp> |
| |
|
| | #include <Bnd_Box.hxx> |
| | #include <BRep_Builder.hxx> |
| | #include <BRep_Tool.hxx> |
| | #include <BRepAdaptor_Curve.hxx> |
| | #include <BRepAdaptor_Surface.hxx> |
| | #include <BRepBndLib.hxx> |
| | #include <BRepBuilderAPI_MakeEdge.hxx> |
| | #include <BRepBuilderAPI_MakeFace.hxx> |
| | #include <BRepBuilderAPI_MakeVertex.hxx> |
| | #include <BRepBuilderAPI_MakeWire.hxx> |
| | #include <BRepExtrema_DistShapeShape.hxx> |
| | #include <BRepLib.hxx> |
| | #include <BRepLib_MakeFace.hxx> |
| | #include <BRepLib_FindSurface.hxx> |
| | #include <BRepTools_WireExplorer.hxx> |
| | #include <GCPnts_QuasiUniformDeflection.hxx> |
| | #include <GCPnts_UniformAbscissa.hxx> |
| | #include <GCPnts_UniformDeflection.hxx> |
| | #include <GeomAPI_ProjectPointOnCurve.hxx> |
| | #include <gp_Circ.hxx> |
| | #include <HLRAlgo_Projector.hxx> |
| | #include <HLRBRep_Algo.hxx> |
| | #include <HLRBRep_HLRToShape.hxx> |
| | #include <Precision.hxx> |
| | #include <ShapeAnalysis_FreeBounds.hxx> |
| | #include <ShapeExtend_WireData.hxx> |
| | #include <ShapeFix_ShapeTolerance.hxx> |
| | #include <ShapeFix_Wire.hxx> |
| | #include <Standard_Failure.hxx> |
| | #include <Standard_Version.hxx> |
| | #include <TopExp.hxx> |
| | #include <TopExp_Explorer.hxx> |
| | #include <TopoDS_Compound.hxx> |
| | #include <TopTools_HSequenceOfShape.hxx> |
| |
|
| | #include <App/Application.h> |
| | #include <App/Document.h> |
| | #include <Base/Exception.h> |
| | #include <Base/Tools.h> |
| | #include <Mod/Part/App/CrossSection.h> |
| | #include <Mod/Part/App/FaceMakerBullseye.h> |
| | #include <Mod/Part/App/FuzzyHelper.h> |
| | #include <Mod/Part/App/PartFeature.h> |
| | #include <Mod/CAM/App/PathSegmentWalker.h> |
| | #include <Mod/CAM/libarea/Area.h> |
| |
|
| | #include "Area.h" |
| |
|
| |
|
| | |
| | #if defined(__clang__) |
| | # pragma clang diagnostic push |
| | # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" |
| | #endif |
| |
|
| |
|
| | namespace bg = boost::geometry; |
| | namespace bgi = boost::geometry::index; |
| |
|
| | using RParameters = bgi::linear<16>; |
| |
|
| | BOOST_GEOMETRY_REGISTER_POINT_3D_GET_SET(gp_Pnt, double, bg::cs::cartesian, X, Y, Z, SetX, SetY, SetZ) |
| |
|
| | #define AREA_LOG FC_LOG |
| | #define AREA_WARN FC_WARN |
| | #define AREA_ERR FC_ERR |
| | #define AREA_TRACE FC_TRACE |
| | #define AREA_XYZ FC_XYZ |
| | #define AREA_XY AREA_XY |
| |
|
| | #ifdef FC_DEBUG |
| | # define AREA_DBG FC_WARN |
| | #else |
| | # define AREA_DBG(...) \ |
| | do { \ |
| | } while (0) |
| | #endif |
| |
|
| | FC_LOG_LEVEL_INIT("Path.Area", true, true) |
| |
|
| | using namespace Path; |
| |
|
| | CAreaParams::CAreaParams() |
| | : PARAM_INIT(PARAM_FNAME, AREA_PARAMS_CAREA) |
| | {} |
| |
|
| | AreaParams::AreaParams() |
| | : PARAM_INIT(PARAM_FNAME, AREA_PARAMS_AREA) |
| | {} |
| |
|
| | void AreaParams::dump(const char* msg) const |
| | { |
| |
|
| | #define AREA_PARAM_PRINT(_param) \ |
| | ss << PARAM_FNAME_STR(_param) << " = " << PARAM_FNAME(_param) << '\n'; |
| |
|
| | if (FC_LOG_INSTANCE.level() > FC_LOGLEVEL_TRACE) { |
| | std::ostringstream ss; |
| | ss << msg << '\n'; |
| | PARAM_FOREACH(AREA_PARAM_PRINT, AREA_PARAMS_AREA); |
| | FC_MSG(ss.str()); |
| | } |
| | } |
| |
|
| | CAreaConfig::CAreaConfig(const CAreaParams& p, bool noFitArcs) |
| | { |
| | #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))(p.PARAM_FNAME(_param)); |
| |
|
| | PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY, AREA_PARAMS_CAREA); |
| |
|
| | |
| | if (noFitArcs) { |
| | CArea::set_fit_arcs(false); |
| | } |
| | } |
| |
|
| | 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) |
| |
|
| | bool Area::s_aborting; |
| |
|
| | Area::Area(const AreaParams* params) |
| | : myParams(s_params) |
| | , myHaveFace(false) |
| | , myHaveSolid(false) |
| | , myShapeDone(false) |
| | , myProjecting(false) |
| | , mySkippedShapes(0) |
| | { |
| | if (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) |
| | , myProjecting(false) |
| | , mySkippedShapes(0) |
| | { |
| | if (!deep_copy || !other.isBuilt()) { |
| | return; |
| | } |
| | if (other.myArea) { |
| | myArea = std::make_unique<CArea>(*other.myArea); |
| | } |
| | myShapePlane = other.myShapePlane; |
| | myShape = other.myShape; |
| | myShapeDone = other.myShapeDone; |
| | mySections.reserve(other.mySections.size()); |
| | for (const shared_ptr<Area>& area : other.mySections) { |
| | mySections.push_back(make_shared<Area>(*area, true)); |
| | } |
| | } |
| |
|
| | Area::~Area() |
| | { |
| | clean(); |
| | } |
| |
|
| | void Area::setPlane(const TopoDS_Shape& shape) |
| | { |
| | clean(); |
| | if (shape.IsNull()) { |
| | myWorkPlane.Nullify(); |
| | return; |
| | } |
| | gp_Trsf trsf; |
| | TopoDS_Shape plane = findPlane(shape, trsf); |
| | if (plane.IsNull()) { |
| | throw Base::ValueError("shape is not planar"); |
| | } |
| | myWorkPlane = plane; |
| | myTrsf = trsf; |
| | } |
| |
|
| | static bool getShapePlane(const TopoDS_Shape& shape, gp_Pln& pln) |
| | { |
| | if (shape.IsNull()) { |
| | return false; |
| | } |
| | if (shape.ShapeType() == TopAbs_FACE) { |
| | BRepAdaptor_Surface adapt(TopoDS::Face(shape)); |
| | if (adapt.GetType() != GeomAbs_Plane) { |
| | return false; |
| | } |
| | pln = adapt.Plane(); |
| | return true; |
| | } |
| | BRepLib_FindSurface finder(shape.Located(TopLoc_Location()), -1, Standard_True); |
| | if (!finder.Found()) { |
| | return false; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | pln = GeomAdaptor_Surface(finder.Surface()).Plane(); |
| | pln.Transform(shape.Location().Transformation()); |
| | return true; |
| | } |
| |
|
| | bool Area::isCoplanar(const TopoDS_Shape& s1, const TopoDS_Shape& s2) |
| | { |
| | if (s1.IsNull() || s2.IsNull()) { |
| | return false; |
| | } |
| | if (s1.IsSame(s2)) { |
| | return true; |
| | } |
| | gp_Pln pln1, pln2; |
| | if (!getShapePlane(s1, pln1) || !getShapePlane(s2, pln2)) { |
| | return false; |
| | } |
| | return pln1.Position().IsCoplanar(pln2.Position(), Precision::Confusion(), Precision::Confusion()); |
| | } |
| |
|
| | int Area::addShape( |
| | 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 reorient |
| | ) |
| | { |
| | 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()) { |
| | addWire(area, TopoDS::Wire(it.Current()), trsf, deflection); |
| | } |
| | } |
| |
|
| | if (haveShape) { |
| | return skipped; |
| | } |
| |
|
| | 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 (plane && !isCoplanar(wire, *plane)) { |
| | ++skipped; |
| | if (force_coplanar) { |
| | continue; |
| | } |
| | } |
| | if (BRep_Tool::IsClosed(wire)) { |
| | addWire(_area, wire, trsf, deflection); |
| | } |
| | else if (to_edges) { |
| | for (TopExp_Explorer it(wire, TopAbs_EDGE); it.More(); it.Next()) { |
| | addWire( |
| | _areaOpen, |
| | BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire(), |
| | trsf, |
| | deflection, |
| | true |
| | ); |
| | } |
| | } |
| | else { |
| | addWire(_areaOpen, wire, trsf, deflection); |
| | } |
| | } |
| |
|
| | if (!haveShape) { |
| | for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { |
| | if (plane && !isCoplanar(it.Current(), *plane)) { |
| | ++skipped; |
| | if (force_coplanar) { |
| | continue; |
| | } |
| | } |
| | TopoDS_Wire wire = BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire(); |
| | addWire(BRep_Tool::IsClosed(wire) ? _area : _areaOpen, wire, trsf, deflection); |
| | } |
| | } |
| |
|
| | if (reorient) { |
| | _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); |
| | } |
| | return skipped; |
| | } |
| |
|
| | static std::vector<gp_Pnt> discretize(const TopoDS_Edge& edge, double deflection) |
| | { |
| | std::vector<gp_Pnt> ret; |
| | BRepAdaptor_Curve curve(edge); |
| | Standard_Real efirst, elast; |
| | efirst = curve.FirstParameter(); |
| | elast = curve.LastParameter(); |
| | bool reversed = (edge.Orientation() == TopAbs_REVERSED); |
| |
|
| | |
| | ret.push_back(curve.Value(reversed ? elast : efirst)); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | GCPnts_UniformDeflection discretizer(curve, deflection, efirst, elast); |
| | if (!discretizer.IsDone()) { |
| | Standard_Failure::Raise("Curve discretization failed"); |
| | } |
| | if (discretizer.NbPoints() > 1) { |
| | int nbPoints = discretizer.NbPoints(); |
| | |
| | if (reversed) { |
| | for (int i = nbPoints - 1; i >= 1; --i) { |
| | ret.push_back(discretizer.Value(i)); |
| | } |
| | } |
| | else { |
| | for (int i = 2; i <= nbPoints; i++) { |
| | ret.push_back(discretizer.Value(i)); |
| | } |
| | } |
| | } |
| | |
| | ret.push_back(curve.Value(reversed ? efirst : elast)); |
| | return ret; |
| | } |
| |
|
| | void Area::addWire(CArea& area, const TopoDS_Wire& wire, const gp_Trsf* trsf, double deflection, bool to_edges) |
| | { |
| | CCurve ccurve; |
| | BRepTools_WireExplorer xp(trsf ? TopoDS::Wire(wire.Moved(TopLoc_Location(*trsf))) : wire); |
| |
|
| | if (!xp.More()) { |
| | AREA_TRACE("empty wire"); |
| | return; |
| | } |
| |
|
| | gp_Pnt p = BRep_Tool::Pnt(xp.CurrentVertex()); |
| | ccurve.append(CVertex(Point(p.X(), p.Y()))); |
| |
|
| | for (; xp.More(); xp.Next()) { |
| | 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()); |
| |
|
| | 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_Dir dir = circle.Axis().Direction(); |
| | gp_Pnt center = circle.Location(); |
| | int type = dir.Z() < 0 ? -1 : 1; |
| | if (reversed) { |
| | type = -type; |
| | } |
| | if (fabs(first - last) > std::numbers::pi) { |
| | |
| | |
| | gp_Pnt mid = curve.Value((last - first) * 0.5 + first); |
| | ccurve.append( |
| | CVertex(type, Point(mid.X(), mid.Y()), Point(center.X(), center.Y())) |
| | ); |
| | } |
| | ccurve.append(CVertex(type, Point(p.X(), p.Y()), Point(center.X(), center.Y()))); |
| | if (to_edges) { |
| | ccurve.UnFitArcs(); |
| | CCurve c; |
| | c.append(ccurve.m_vertices.front()); |
| | auto it = ccurve.m_vertices.begin(); |
| | for (++it; it != ccurve.m_vertices.end(); ++it) { |
| | c.append(*it); |
| | area.append(c); |
| | c.m_vertices.pop_front(); |
| | } |
| | ccurve.m_vertices.clear(); |
| | ccurve.append(c.m_vertices.front()); |
| | } |
| | break; |
| | } |
| | default: { |
| | |
| | const auto& pts = discretize(edge, deflection); |
| | for (size_t i = 1; i < pts.size(); ++i) { |
| | auto& pt = pts[i]; |
| | ccurve.append(CVertex(Point(pt.X(), pt.Y()))); |
| | if (to_edges) { |
| | area.append(ccurve); |
| | ccurve.m_vertices.pop_front(); |
| | } |
| | } |
| | } |
| | } |
| | } |
| | if (!to_edges) { |
| | if (BRep_Tool::IsClosed(wire) && !ccurve.IsClosed()) { |
| | AREA_WARN("ccurve not closed"); |
| | ccurve.append(ccurve.m_vertices.front()); |
| | } |
| | area.move(std::move(ccurve)); |
| | } |
| | } |
| |
|
| |
|
| | void Area::clean(bool deleteShapes) |
| | { |
| | myShapeDone = false; |
| | mySections.clear(); |
| | myShape.Nullify(); |
| | myArea.reset(); |
| | myAreaOpen.reset(); |
| | myShapePlane.Nullify(); |
| | if (deleteShapes) { |
| | myShapes.clear(); |
| | myHaveFace = false; |
| | myHaveSolid = false; |
| | } |
| | } |
| |
|
| | static inline ClipperLib::ClipType toClipperOp(short op) |
| | { |
| | switch (op) { |
| | case Area::OperationUnion: |
| | return ClipperLib::ctUnion; |
| | break; |
| | case Area::OperationDifference: |
| | return ClipperLib::ctDifference; |
| | break; |
| | case Area::OperationIntersection: |
| | return ClipperLib::ctIntersection; |
| | break; |
| | case Area::OperationXor: |
| | return ClipperLib::ctXor; |
| | break; |
| | default: |
| | throw Base::ValueError("invalid Operation"); |
| | } |
| | } |
| |
|
| | void Area::add(const TopoDS_Shape& shape, short op) |
| | { |
| |
|
| | if (shape.IsNull()) { |
| | throw Base::ValueError("null shape"); |
| | } |
| |
|
| | if (op != OperationCompound) { |
| | toClipperOp(op); |
| | } |
| |
|
| | bool haveSolid = TopExp_Explorer(shape, TopAbs_SOLID).More(); |
| |
|
| | |
| | if ((!haveSolid && myHaveSolid) || (haveSolid && !myHaveSolid && !myShapes.empty())) { |
| | throw Base::ValueError("mixing solid and planar shapes is not allowed"); |
| | } |
| |
|
| | myHaveSolid = haveSolid; |
| |
|
| | clean(); |
| | if (op != OperationCompound && myShapes.empty()) { |
| | op = OperationUnion; |
| | } |
| | myShapes.emplace_back(op, shape); |
| | } |
| |
|
| | class ClearedAreaSegmentVisitor: public PathSegmentVisitor |
| | { |
| | private: |
| | CArea pathSegments; |
| | CArea holes; |
| | double maxZ; |
| | double radius; |
| | Base::BoundBox3d bbox; |
| |
|
| | void point(const Base::Vector3d& p) |
| | { |
| | if (p.z <= maxZ) { |
| | if (bbox.MinX <= p.x && p.x <= bbox.MaxX && bbox.MinY <= p.y && p.y <= bbox.MaxY) { |
| | CCurve curve; |
| | curve.append(CVertex {{p.x + radius, p.y}}); |
| | curve.append(CVertex {1, {p.x - radius, p.y}, {p.x, p.y}}); |
| | curve.append(CVertex {1, {p.x + radius, p.y}, {p.x, p.y}}); |
| | holes.append(curve); |
| | } |
| | } |
| | } |
| |
|
| | void line(const Base::Vector3d& last, const Base::Vector3d& next) |
| | { |
| | if (last.z <= maxZ && next.z <= maxZ) { |
| | Base::BoundBox2d segBox = {}; |
| | segBox.Add({last.x, last.y}); |
| | segBox.Add({next.x, next.y}); |
| | if (bbox.Intersect(segBox)) { |
| | CCurve curve; |
| | curve.append(CVertex {{last.x, last.y}}); |
| | curve.append(CVertex {{next.x, next.y}}); |
| | pathSegments.append(curve); |
| | } |
| | } |
| | } |
| |
|
| | public: |
| | ClearedAreaSegmentVisitor(double maxZ, double radius, Base::BoundBox3d bbox) |
| | : maxZ(maxZ) |
| | , radius(radius) |
| | , bbox(bbox) |
| | { |
| | bbox.Enlarge(radius); |
| | } |
| |
|
| | CArea getClearedArea() |
| | { |
| | CArea result {pathSegments}; |
| | result.Thicken(radius); |
| | result.Union(holes); |
| | return result; |
| | } |
| |
|
| | void g0( |
| | int id, |
| | const Base::Vector3d& last, |
| | const Base::Vector3d& next, |
| | const std::deque<Base::Vector3d>& pts |
| | ) override |
| | { |
| | (void)id; |
| | (void)pts; |
| | line(last, next); |
| | } |
| |
|
| | void g1( |
| | int id, |
| | const Base::Vector3d& last, |
| | const Base::Vector3d& next, |
| | const std::deque<Base::Vector3d>& pts |
| | ) override |
| | { |
| | (void)id; |
| | (void)pts; |
| | line(last, next); |
| | } |
| |
|
| | void g23( |
| | int id, |
| | const Base::Vector3d& last, |
| | const Base::Vector3d& next, |
| | const std::deque<Base::Vector3d>& pts, |
| | const Base::Vector3d& center |
| | ) override |
| | { |
| | (void)id; |
| | (void)center; |
| |
|
| | |
| | const Base::Vector3d vdirect = next - last; |
| | const Base::Vector3d vstep = pts[0] - last; |
| | const bool ccw = vstep.x * vdirect.y - vstep.y * vdirect.x > 0; |
| |
|
| | |
| | CCurve curve; |
| | curve.append(CVertex {{last.x, last.y}}); |
| | curve.append(CVertex {ccw ? 1 : -1, {next.x, next.y}, {center.x, center.y}}); |
| | pathSegments.append(curve); |
| | } |
| |
|
| | void g8x( |
| | int id, |
| | const Base::Vector3d& last, |
| | const Base::Vector3d& next, |
| | const std::deque<Base::Vector3d>& pts, |
| | const std::deque<Base::Vector3d>& plist, |
| | const std::deque<Base::Vector3d>& qlist |
| | ) override |
| | { |
| | |
| | (void)id; |
| | (void)qlist; |
| |
|
| | point(last); |
| | for (const auto p : pts) { |
| | point(p); |
| | } |
| | for (const auto p : plist) { |
| | point(p); |
| | } |
| | point(next); |
| | } |
| | void g38(int id, const Base::Vector3d& last, const Base::Vector3d& next) override |
| | { |
| | |
| | (void)id; |
| | (void)last; |
| | (void)next; |
| | } |
| | }; |
| |
|
| | std::shared_ptr<Area> Area::getClearedArea( |
| | const Toolpath* path, |
| | double diameter, |
| | double zmax, |
| | Base::BoundBox3d bbox |
| | ) |
| | { |
| | build(); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | AreaParams params = myParams; |
| | params.Accuracy = myParams.Accuracy * .7 / 4; |
| | params.SubjectFill = ClipperLib::pftNonZero; |
| | params.ClipFill = ClipperLib::pftNonZero; |
| | const double buffer = myParams.Accuracy * 3; |
| |
|
| | |
| | |
| | |
| | CAreaConfig conf(params, true); |
| |
|
| | ClearedAreaSegmentVisitor visitor(zmax, diameter / 2 + buffer, bbox); |
| | PathSegmentWalker walker(*path); |
| | walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); |
| |
|
| | std::shared_ptr<Area> clearedArea = make_shared<Area>(¶ms); |
| | clearedArea->myTrsf = {}; |
| | const CArea ca = visitor.getClearedArea(); |
| | if (ca.m_curves.size() > 0) { |
| | TopoDS_Shape clearedAreaShape = Area::toShape(ca, false); |
| | clearedArea->add(clearedAreaShape, OperationCompound); |
| | clearedArea->build(); |
| | } |
| | else { |
| | clearedArea->myArea = std::make_unique<CArea>(); |
| | clearedArea->myAreaOpen = std::make_unique<CArea>(); |
| | } |
| |
|
| | return clearedArea; |
| | } |
| |
|
| | std::shared_ptr<Area> Area::getRestArea(std::vector<std::shared_ptr<Area>> clearedAreas, double diameter) |
| | { |
| | build(); |
| | #define AREA_MY(_param) myParams.PARAM_FNAME(_param) |
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); |
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | AreaParams params = myParams; |
| | params.Accuracy = myParams.Accuracy * .7 / 4; |
| | const double buffer = myParams.Accuracy * 3; |
| | const double roundPrecision = params.Accuracy; |
| |
|
| | |
| | Area clearedAreasInPlane(¶ms); |
| | clearedAreasInPlane.myArea.reset(new CArea()); |
| | for (std::shared_ptr<Area> clearedArea : clearedAreas) { |
| | gp_Trsf trsf = clearedArea->myTrsf; |
| | trsf.Invert(); |
| | trsf.SetTranslationPart( |
| | gp_Vec { |
| | trsf.TranslationPart().X(), |
| | trsf.TranslationPart().Y(), |
| | -myTrsf.TranslationPart().Z() |
| | } |
| | ); |
| | TopoDS_Shape clearedShape = Area::toShape(*clearedArea->myArea, false, &trsf); |
| | Area::addShape( |
| | *(clearedAreasInPlane.myArea), |
| | clearedShape, |
| | &myTrsf, |
| | .01 , |
| | &myWorkPlane |
| | ); |
| | } |
| |
|
| | CArea clearable(*myArea); |
| | clearable.OffsetWithClipper(-diameter / 2, JoinType, EndType, params.MiterLimit, roundPrecision); |
| | clearable.OffsetWithClipper(diameter / 2, JoinType, EndType, params.MiterLimit, roundPrecision); |
| |
|
| | |
| | CArea remaining(clearable); |
| | remaining.Clip( |
| | toClipperOp(Area::OperationDifference), |
| | &*(clearedAreasInPlane.myArea), |
| | SubjectFill, |
| | ClipFill |
| | ); |
| |
|
| | |
| | |
| | CArea restCArea(remaining); |
| | restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, params.MiterLimit, roundPrecision); |
| | restCArea.Clip(toClipperOp(Area::OperationIntersection), &clearable, SubjectFill, ClipFill); |
| |
|
| | if (restCArea.m_curves.size() == 0) { |
| | return {}; |
| | } |
| |
|
| | std::shared_ptr<Area> restArea = make_shared<Area>(¶ms); |
| | gp_Trsf trsf(myTrsf.Inverted()); |
| | TopoDS_Shape restShape = Area::toShape(restCArea, false, &trsf); |
| | restArea->add(restShape, OperationCompound); |
| |
|
| | return restArea; |
| | } |
| |
|
| | TopoDS_Shape Area::toTopoShape() |
| | { |
| | build(); |
| | gp_Trsf trsf = myTrsf.Inverted(); |
| | return toShape(*myArea, false, &trsf); |
| | } |
| |
|
| |
|
| | void Area::setParams(const AreaParams& params) |
| | { |
| | #define AREA_SRC(_param) params.PARAM_FNAME(_param) |
| | |
| | PARAM_ENUM_CHECK(AREA_SRC, PARAM_ENUM_EXCEPT, AREA_PARAMS_CONF); |
| | if (params != myParams) { |
| | clean(); |
| | myParams = params; |
| | } |
| | } |
| |
|
| | void Area::addToBuild(CArea& area, const TopoDS_Shape& shape) |
| | { |
| | if (myParams.Fill == FillAuto && !myHaveFace) { |
| | TopExp_Explorer it(shape, TopAbs_FACE); |
| | myHaveFace = it.More(); |
| | } |
| | TopoDS_Shape plane = getPlane(); |
| | CArea areaOpen; |
| | mySkippedShapes += addShape( |
| | area, |
| | shape, |
| | &myTrsf, |
| | myParams.Deflection, |
| | myParams.Coplanar == CoplanarNone ? nullptr : &plane, |
| | myHaveSolid || myParams.Coplanar == CoplanarForce, |
| | &areaOpen, |
| | myParams.OpenMode == OpenModeEdges, |
| | myParams.Reorient |
| | ); |
| |
|
| | if (myProjecting) { |
| | |
| | |
| | for (auto& c : area.m_curves) { |
| | if (c.IsClosed() && c.IsClockwise()) { |
| | c.Reverse(); |
| | } |
| | } |
| | } |
| |
|
| | if (!areaOpen.m_curves.empty()) { |
| | if (&area == myArea.get() || myParams.OpenMode == OpenModeNone) { |
| | myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(), areaOpen.m_curves); |
| | } |
| | else { |
| | AREA_WARN("open wires discarded in clipping shapes"); |
| | } |
| | } |
| | } |
| |
|
| | static inline void getEndPoints(const TopoDS_Edge& e, gp_Pnt& p1, gp_Pnt& p2) |
| | { |
| | p1 = BRep_Tool::Pnt(TopExp::FirstVertex(e)); |
| | p2 = BRep_Tool::Pnt(TopExp::LastVertex(e)); |
| | } |
| |
|
| | static inline void getEndPoints(const TopoDS_Wire& wire, gp_Pnt& p1, gp_Pnt& p2) |
| | { |
| | BRepTools_WireExplorer xp(wire); |
| | p1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.CurrentVertex())); |
| | for (; xp.More(); xp.Next()) |
| | ; |
| | p2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.CurrentVertex())); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | struct WireJoiner |
| | { |
| |
|
| | using Box = bg::model::box<gp_Pnt>; |
| |
|
| | static bool getBBox(const TopoDS_Edge& e, Box& box) |
| | { |
| | Bnd_Box bound; |
| | BRepBndLib::Add(e, bound); |
| | bound.SetGap(0.1); |
| | if (bound.IsVoid()) { |
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { |
| | AREA_WARN("failed to get bound of edge"); |
| | } |
| | return false; |
| | } |
| | Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; |
| | bound.Get(xMin, yMin, zMin, xMax, yMax, zMax); |
| | box = Box(gp_Pnt(xMin, yMin, zMin), gp_Pnt(xMax, yMax, zMax)); |
| | return true; |
| | } |
| |
|
| | struct EdgeInfo |
| | { |
| | TopoDS_Edge edge; |
| | gp_Pnt p1; |
| | gp_Pnt p2; |
| | Box box; |
| | int iteration; |
| | int iStart[2]; |
| | int iEnd[2]; |
| | bool used; |
| | bool hasBox; |
| | EdgeInfo(const TopoDS_Edge& e, bool bbox) |
| | : edge(e) |
| | , hasBox(false) |
| | { |
| | getEndPoints(e, p1, p2); |
| | if (bbox) { |
| | hasBox = getBBox(e, box); |
| | } |
| | reset(); |
| | } |
| | EdgeInfo(const TopoDS_Edge& e, const gp_Pnt& pt1, const gp_Pnt& pt2, bool bbox) |
| | : edge(e) |
| | , p1(pt1) |
| | , p2(pt2) |
| | , hasBox(false) |
| | { |
| | if (bbox) { |
| | hasBox = getBBox(e, box); |
| | } |
| | reset(); |
| | } |
| | void reset() |
| | { |
| | iteration = 0; |
| | used = false; |
| | iStart[0] = iStart[1] = iEnd[0] = iEnd[1] = -1; |
| | } |
| | }; |
| |
|
| | using Edges = std::list<EdgeInfo>; |
| | Edges edges; |
| |
|
| | struct VertexInfo |
| | { |
| | Edges::iterator it; |
| | bool start; |
| | VertexInfo(Edges::iterator it, bool start) |
| | : it(it) |
| | , start(start) |
| | {} |
| | bool operator==(const VertexInfo& other) const |
| | { |
| | return it == other.it && start == other.start; |
| | } |
| | const gp_Pnt& pt() const |
| | { |
| | return start ? it->p1 : it->p2; |
| | } |
| | const gp_Pnt& ptOther() const |
| | { |
| | return start ? it->p2 : it->p1; |
| | } |
| | }; |
| |
|
| | struct PntGetter |
| | { |
| | using result_type = const gp_Pnt&; |
| | result_type operator()(const VertexInfo& v) const |
| | { |
| | return v.pt(); |
| | } |
| | }; |
| |
|
| | bgi::rtree<VertexInfo, RParameters, PntGetter> vmap; |
| |
|
| | struct BoxGetter |
| | { |
| | using result_type = const Box&; |
| | result_type operator()(Edges::iterator it) const |
| | { |
| | return it->box; |
| | } |
| | }; |
| | bgi::rtree<Edges::iterator, RParameters, BoxGetter> boxMap; |
| |
|
| | BRep_Builder builder; |
| | TopoDS_Compound comp; |
| |
|
| | WireJoiner() |
| | { |
| | builder.MakeCompound(comp); |
| | } |
| |
|
| | void remove(Edges::iterator it) |
| | { |
| | if (it->hasBox) { |
| | boxMap.remove(it); |
| | } |
| | vmap.remove(VertexInfo(it, true)); |
| | vmap.remove(VertexInfo(it, false)); |
| | edges.erase(it); |
| | } |
| |
|
| | void add(Edges::iterator it) |
| | { |
| | vmap.insert(VertexInfo(it, true)); |
| | vmap.insert(VertexInfo(it, false)); |
| | if (it->hasBox) { |
| | boxMap.insert(it); |
| | } |
| | } |
| |
|
| | void add(const TopoDS_Edge& e, bool bbox = false) |
| | { |
| | gp_Pnt p1, p2; |
| | getEndPoints(e, p1, p2); |
| | edges.emplace_front(e, p1, p2, bbox); |
| | add(edges.begin()); |
| | } |
| |
|
| | void add(const TopoDS_Shape& shape, bool bbox = false) |
| | { |
| | for (TopExp_Explorer xp(shape, TopAbs_EDGE); xp.More(); xp.Next()) { |
| | add(TopoDS::Edge(xp.Current()), bbox); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | void join(double tol) |
| | { |
| | tol = tol * tol; |
| | while (!edges.empty()) { |
| | auto it = edges.begin(); |
| | BRepBuilderAPI_MakeWire mkWire; |
| | mkWire.Add(it->edge); |
| | gp_Pnt pstart(it->p1), pend(it->p2); |
| | remove(it); |
| |
|
| | bool done = false; |
| | for (int idx = 0; !done && idx < 2; ++idx) { |
| | while (!edges.empty()) { |
| | std::vector<VertexInfo> ret; |
| | ret.reserve(1); |
| | const gp_Pnt& pt = idx == 0 ? pstart : pend; |
| | vmap.query(bgi::nearest(pt, 1), std::back_inserter(ret)); |
| | assert(ret.size() == 1); |
| | double d = ret[0].pt().SquareDistance(pt); |
| | if (d > tol) { |
| | break; |
| | } |
| |
|
| | const auto& info = *ret[0].it; |
| | bool start = ret[0].start; |
| | if (d > Precision::SquareConfusion()) { |
| | |
| | const gp_Pnt& pt = ret[idx].pt(); |
| | if (idx) { |
| | mkWire.Add(BRepBuilderAPI_MakeEdge(pend, pt).Edge()); |
| | } |
| | else { |
| | mkWire.Add(BRepBuilderAPI_MakeEdge(pt, pstart).Edge()); |
| | } |
| | } |
| |
|
| | if (idx == 1 && start) { |
| | pend = info.p2; |
| | mkWire.Add(info.edge); |
| | } |
| | else if (idx == 0 && !start) { |
| | pstart = info.p1; |
| | mkWire.Add(info.edge); |
| | } |
| | else if (idx == 0 && start) { |
| | pstart = info.p2; |
| | mkWire.Add(TopoDS::Edge(info.edge.Reversed())); |
| | } |
| | else { |
| | pend = info.p1; |
| | mkWire.Add(TopoDS::Edge(info.edge.Reversed())); |
| | } |
| | remove(ret[0].it); |
| | if (pstart.SquareDistance(pend) <= Precision::SquareConfusion()) { |
| | done = true; |
| | break; |
| | } |
| | } |
| | } |
| | builder.Add(comp, mkWire.Wire()); |
| | } |
| | } |
| |
|
| | |
| | void splitEdges() |
| | { |
| | for (auto it = edges.begin(); it != edges.end();) { |
| | const auto& info = *it; |
| | if (!info.hasBox) { |
| | ++it; |
| | continue; |
| | } |
| |
|
| | gp_Pnt pstart(info.p1), pend(info.p2); |
| |
|
| | gp_Pnt pt; |
| | bool intersects = false; |
| |
|
| | for (auto vit = boxMap.qbegin(bgi::intersects(info.box)); |
| | !intersects && vit != boxMap.qend(); |
| | ++vit) { |
| | const auto& other = *(*vit); |
| | if (info.edge.IsSame(other.edge)) { |
| | continue; |
| | } |
| |
|
| | for (int i = 0; i < 2; ++i) { |
| | const gp_Pnt& p = i ? other.p1 : other.p2; |
| | if (pstart.SquareDistance(p) <= Precision::SquareConfusion() |
| | || pend.SquareDistance(p) <= Precision::SquareConfusion()) { |
| | continue; |
| | } |
| |
|
| | BRepExtrema_DistShapeShape extss(BRepBuilderAPI_MakeVertex(p), info.edge); |
| | if (extss.IsDone() && extss.NbSolution()) { |
| | const gp_Pnt& pp = extss.PointOnShape2(1); |
| | if (pp.SquareDistance(p) <= Precision::SquareConfusion()) { |
| | pt = pp; |
| | intersects = true; |
| | break; |
| | } |
| | } |
| | else if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { |
| | AREA_WARN("BRepExtrema_DistShapeShape failed"); |
| | } |
| | } |
| | } |
| |
|
| | if (!intersects) { |
| | ++it; |
| | continue; |
| | } |
| |
|
| | Standard_Real first, last; |
| | Handle(Geom_Curve) curve = BRep_Tool::Curve(it->edge, first, last); |
| | bool reversed = pstart.SquareDistance(curve->Value(last)) <= Precision::SquareConfusion(); |
| |
|
| | BRepBuilderAPI_MakeEdge mkEdge1, mkEdge2; |
| | if (reversed) { |
| | mkEdge1.Init(curve, pt, pstart); |
| | mkEdge2.Init(curve, pend, pt); |
| | } |
| | else { |
| | mkEdge1.Init(curve, pstart, pt); |
| | mkEdge2.Init(curve, pt, pend); |
| | } |
| | if (!mkEdge1.IsDone() || !mkEdge2.IsDone()) { |
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { |
| | AREA_WARN( |
| | (reversed ? "reversed " : "") |
| | << "edge split failed " << AREA_XYZ(pstart) << ", " << AREA_XYZ(pt) << ", " |
| | << AREA_XYZ(pend) << ", " << ", err: " << mkEdge1.Error() << ", " |
| | << mkEdge2.Error() |
| | ); |
| | } |
| | ++it; |
| | continue; |
| | } |
| |
|
| | Edges::iterator itNext = it; |
| | ++itNext; |
| | remove(it); |
| | add(it = edges.emplace(itNext, mkEdge1.Edge(), true)); |
| | add(edges.emplace(itNext, mkEdge2.Edge(), true)); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | int findClosedWires(double tol = Precision::Confusion()) |
| | { |
| | |
| | |
| | |
| |
|
| | std::vector<VertexInfo> adjacentList; |
| | std::set<EdgeInfo*> edgesToVisit; |
| | int count = 0; |
| | int skips = 0; |
| |
|
| | for (auto& info : edges) { |
| | info.reset(); |
| | } |
| |
|
| | FC_TIME_INIT(t); |
| | int rcount = 0; |
| |
|
| | for (auto& info : edges) { |
| | if (BRep_Tool::IsClosed(info.edge)) { |
| | auto wire = BRepBuilderAPI_MakeWire(info.edge).Wire(); |
| | Area::showShape(wire, "closed"); |
| | builder.Add(comp, wire); |
| | ++count; |
| | continue; |
| | } |
| | gp_Pnt pt[2]; |
| | pt[0] = info.p1; |
| | pt[1] = info.p2; |
| | for (int i = 0; i < 2; ++i) { |
| | if (info.iStart[i] >= 0) { |
| | continue; |
| | } |
| | info.iEnd[i] = info.iStart[i] = (int)adjacentList.size(); |
| |
|
| | |
| | constexpr int intMax = std::numeric_limits<int>::max(); |
| | for (auto vit = vmap.qbegin(bgi::nearest(pt[i], intMax)); vit != vmap.qend(); ++vit) { |
| | ++rcount; |
| | if (vit->pt().SquareDistance(pt[i]) > tol) { |
| | break; |
| | } |
| | auto& vinfo = *vit; |
| | |
| | |
| | adjacentList.push_back(vinfo); |
| | ++info.iEnd[i]; |
| | } |
| | |
| | for (int j = info.iStart[i]; j < info.iEnd[i]; ++j) { |
| | auto& other = adjacentList[j]; |
| | auto& otherInfo = *other.it; |
| | if (&otherInfo != &info) { |
| | int k = other.start ? 0 : 1; |
| | otherInfo.iStart[k] = info.iStart[i]; |
| | otherInfo.iEnd[k] = info.iEnd[i]; |
| | } |
| | } |
| | } |
| | if (info.iStart[0] != info.iEnd[0] && info.iStart[1] != info.iEnd[1]) { |
| | |
| | |
| | edgesToVisit.insert(&info); |
| | } |
| | else { |
| | ++skips; |
| | } |
| | } |
| |
|
| | FC_TIME_LOG(t, "rtree::nearest (" << rcount << ')'); |
| |
|
| | struct StackInfo |
| | { |
| | size_t iStart; |
| | size_t iEnd; |
| | size_t iCurrent; |
| | explicit StackInfo(size_t idx) |
| | : iStart(idx) |
| | , iEnd(idx) |
| | , iCurrent(idx) |
| | {} |
| | }; |
| | std::vector<StackInfo> stack; |
| | std::vector<VertexInfo> vertexStack; |
| |
|
| | for (int iteration = 1; !edgesToVisit.empty(); ++iteration) { |
| | EdgeInfo* currentInfo = *edgesToVisit.begin(); |
| | int currentIdx = 1; |
| | TopoDS_Edge& e = currentInfo->edge; |
| | edgesToVisit.erase(edgesToVisit.begin()); |
| | gp_Pnt pstart = currentInfo->p1; |
| | gp_Pnt pend = currentInfo->p2; |
| | currentInfo->used = true; |
| | currentInfo->iteration = iteration; |
| | stack.clear(); |
| | vertexStack.clear(); |
| |
|
| | |
| | while (true) { |
| | |
| | stack.emplace_back(vertexStack.size()); |
| | auto& r = stack.back(); |
| |
|
| | |
| | for (int i = currentInfo->iStart[currentIdx]; i < currentInfo->iEnd[currentIdx]; ++i) { |
| | auto& info = *adjacentList[i].it; |
| | if (info.iteration != iteration) { |
| | info.iteration = iteration; |
| | vertexStack.push_back(adjacentList[i]); |
| | ++r.iEnd; |
| | } |
| | } |
| | while (true) { |
| | auto& r = stack.back(); |
| | if (r.iCurrent < r.iEnd) { |
| | |
| | |
| | auto& vinfo = vertexStack[r.iCurrent]; |
| | pend = vinfo.ptOther(); |
| | |
| | currentInfo = &(*vinfo.it); |
| | currentIdx = vinfo.start ? 1 : 0; |
| | break; |
| | } |
| | |
| | vertexStack.erase(vertexStack.begin() + r.iStart, vertexStack.end()); |
| | stack.pop_back(); |
| | if (stack.empty()) { |
| | break; |
| | } |
| | ++stack.back().iCurrent; |
| | } |
| | if (stack.empty()) { |
| | |
| | |
| | ++skips; |
| | break; |
| | } |
| | if (pstart.SquareDistance(pend) > tol) { |
| | |
| | |
| | continue; |
| | } |
| |
|
| | Handle(ShapeExtend_WireData) wireData = new ShapeExtend_WireData(); |
| | wireData->Add(e); |
| | for (auto& r : stack) { |
| | const auto& v = vertexStack[r.iCurrent]; |
| | auto& info = *v.it; |
| | if (v.start) { |
| | wireData->Add(info.edge); |
| | } |
| | else { |
| | wireData->Add(TopoDS::Edge(info.edge.Reversed())); |
| | } |
| | } |
| | |
| | TopoDS_Wire wire = makeCleanWire(wireData, 0.01); |
| | if (!BRep_Tool::IsClosed(wire)) { |
| | FC_WARN("failed to close some projection wire"); |
| | Area::showShape(wire, "failed"); |
| | ++skips; |
| | } |
| | else { |
| | for (auto& r : stack) { |
| | const auto& v = vertexStack[r.iCurrent]; |
| | auto& info = *v.it; |
| | if (!info.used) { |
| | info.used = true; |
| | edgesToVisit.erase(&info); |
| | } |
| | } |
| | Area::showShape(wire, "joined"); |
| | builder.Add(comp, wire); |
| | ++count; |
| | } |
| | break; |
| | } |
| | } |
| | FC_TIME_LOG(t, "found " << count << " closed wires, skipped " << skips << "edges. "); |
| | return skips; |
| | } |
| |
|
| | |
| | |
| | static TopoDS_Wire makeCleanWire(Handle(ShapeExtend_WireData) wireData, double tol) |
| | { |
| | TopoDS_Wire result; |
| | BRepBuilderAPI_MakeWire mkWire; |
| | ShapeFix_ShapeTolerance sTol; |
| |
|
| | Handle(ShapeFix_Wire) fixer = new ShapeFix_Wire; |
| | fixer->Load(wireData); |
| | fixer->Perform(); |
| | fixer->FixReorder(); |
| | fixer->SetMaxTolerance(tol); |
| | fixer->ClosedWireMode() = Standard_True; |
| | fixer->FixConnected(Precision::Confusion()); |
| | fixer->FixClosed(Precision::Confusion()); |
| |
|
| | for (int i = 1; i <= wireData->NbEdges(); i++) { |
| | TopoDS_Edge edge = fixer->WireData()->Edge(i); |
| | sTol.SetTolerance(edge, tol, TopAbs_VERTEX); |
| | mkWire.Add(edge); |
| | } |
| |
|
| | result = mkWire.Wire(); |
| | return result; |
| | } |
| | }; |
| |
|
| | 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()) { |
| | addWire( |
| | *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; |
| | } |
| | } |
| | addWire( |
| | *myArea, |
| | BRepBuilderAPI_MakeWire(TopoDS::Edge(it.Current())).Wire(), |
| | &myTrsf, |
| | myParams.Deflection, |
| | true |
| | ); |
| | } |
| | } |
| |
|
| | void Area::showShape(const TopoDS_Shape& shape, const char* name, const char* fmt, ...) |
| | { |
| | if (FC_LOG_INSTANCE.level() > FC_LOGLEVEL_TRACE) { |
| | App::Document* pcDoc = App::GetApplication().getActiveDocument(); |
| | if (!pcDoc) { |
| | pcDoc = App::GetApplication().newDocument(); |
| | } |
| | char buf[256]; |
| | if (!name && fmt) { |
| | va_list args; |
| | va_start(args, fmt); |
| | vsnprintf(buf, sizeof(buf), fmt, args); |
| | va_end(args); |
| | name = buf; |
| | } |
| | Part::Feature* pcFeature = pcDoc->addObject<Part::Feature>(name); |
| | pcFeature->Shape.setValue(shape); |
| | } |
| | } |
| |
|
| | template<class T> |
| | static void showShapes(const T& shapes, const char* name, const char* fmt = nullptr, ...) |
| | { |
| | if (FC_LOG_INSTANCE.level() > FC_LOGLEVEL_TRACE) { |
| | BRep_Builder builder; |
| | TopoDS_Compound comp; |
| | builder.MakeCompound(comp); |
| | for (auto& s : shapes) { |
| | if (s.IsNull()) { |
| | continue; |
| | } |
| | builder.Add(comp, s); |
| | } |
| | char buf[256]; |
| | if (!name && fmt) { |
| | va_list args; |
| | va_start(args, fmt); |
| | vsnprintf(buf, sizeof(buf), fmt, args); |
| | va_end(args); |
| | name = buf; |
| | } |
| | Area::showShape(comp, name); |
| | } |
| | } |
| |
|
| | template<class Func> |
| | static int foreachSubshape( |
| | const TopoDS_Shape& shape, |
| | Func func, |
| | int type = TopAbs_FACE, |
| | bool groupOpenEdges = false |
| | ) |
| | { |
| | int res = -1; |
| | std::vector<TopoDS_Shape> openShapes; |
| | switch (type) { |
| | case TopAbs_SOLID: |
| | for (TopExp_Explorer it(shape, TopAbs_SOLID); it.More(); it.Next()) { |
| | res = TopAbs_SOLID; |
| | func(it.Current(), TopAbs_SOLID); |
| | } |
| | if (res >= 0) { |
| | break; |
| | } |
| | |
| | case TopAbs_FACE: |
| | for (TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { |
| | res = TopAbs_FACE; |
| | func(it.Current(), TopAbs_FACE); |
| | } |
| | if (res >= 0) { |
| | break; |
| | } |
| | |
| | case TopAbs_WIRE: |
| | for (TopExp_Explorer it(shape, TopAbs_WIRE); it.More(); it.Next()) { |
| | res = TopAbs_WIRE; |
| | if (groupOpenEdges && !BRep_Tool::IsClosed(TopoDS::Wire(it.Current()))) { |
| | openShapes.push_back(it.Current()); |
| | } |
| | else { |
| | func(it.Current(), TopAbs_WIRE); |
| | } |
| | } |
| | if (res >= 0) { |
| | break; |
| | } |
| | |
| | default: |
| | for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { |
| | res = TopAbs_EDGE; |
| | if (groupOpenEdges) { |
| | TopoDS_Edge e = TopoDS::Edge(it.Current()); |
| | gp_Pnt p1, p2; |
| | getEndPoints(e, p1, p2); |
| | if (p1.SquareDistance(p2) > Precision::SquareConfusion()) { |
| | openShapes.push_back(it.Current()); |
| | continue; |
| | } |
| | } |
| | func(it.Current(), TopAbs_EDGE); |
| | } |
| | } |
| | if (openShapes.empty()) { |
| | return res; |
| | } |
| |
|
| | BRep_Builder builder; |
| | TopoDS_Compound comp; |
| | builder.MakeCompound(comp); |
| | for (auto& s : openShapes) { |
| | builder.Add(comp, s); |
| | } |
| | func(comp, TopAbs_COMPOUND); |
| | return TopAbs_COMPOUND; |
| | } |
| |
|
| | struct FindPlane |
| | { |
| | TopoDS_Shape& myPlaneShape; |
| | gp_Trsf& myTrsf; |
| | double& myZ; |
| | FindPlane(TopoDS_Shape& s, gp_Trsf& t, double& z) |
| | : myPlaneShape(s) |
| | , myTrsf(t) |
| | , myZ(z) |
| | {} |
| | void operator()(const TopoDS_Shape& shape, int) |
| | { |
| | gp_Trsf trsf; |
| |
|
| | gp_Pln pln; |
| | if (!getShapePlane(shape, pln)) { |
| | return; |
| | } |
| | gp_Ax3 pos = pln.Position(); |
| | AREA_TRACE("plane pos " << AREA_XYZ(pos.Location()) << ", " << AREA_XYZ(pos.Direction())); |
| |
|
| | |
| | if (!pos.Direct()) { |
| | AREA_WARN("left hand coordinate"); |
| | pos = gp_Ax3(pos.Ax2()); |
| | } |
| |
|
| | gp_Dir dir(pos.Direction()); |
| |
|
| | |
| | |
| | bool x0 = fabs(dir.X()) < Precision::Confusion(); |
| | bool y0 = fabs(dir.Y()) < Precision::Confusion(); |
| | bool z0 = fabs(dir.Z()) < Precision::Confusion(); |
| | if (x0 && y0) { |
| | dir.SetZ(fabs(dir.Z())); |
| | } |
| | else if (x0 && z0) { |
| | dir.SetY(fabs(dir.Y())); |
| | } |
| | else if (y0 && z0) { |
| | dir.SetX(fabs(dir.X())); |
| | } |
| | pos.SetDirection(dir); |
| |
|
| | trsf.SetTransformation(pos); |
| |
|
| | if (x0 && y0) { |
| | TopExp_Explorer it(shape, TopAbs_VERTEX); |
| | const auto& pt = BRep_Tool::Pnt(TopoDS::Vertex(it.Current())); |
| | if (!myPlaneShape.IsNull() && myZ > pt.Z()) { |
| | return; |
| | } |
| | myZ = pt.Z(); |
| | } |
| | else if (!myPlaneShape.IsNull()) { |
| | return; |
| | } |
| | myPlaneShape = shape; |
| | myTrsf = trsf; |
| | AREA_TRACE("plane pos " << AREA_XYZ(pos.Location()) << ", " << AREA_XYZ(pos.Direction())); |
| | } |
| | }; |
| |
|
| | 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; |
| | } |
| |
|
| | int Area::project( |
| | TopoDS_Shape& shape_out, |
| | const TopoDS_Shape& shape_in, |
| | const AreaParams* params, |
| | const TopoDS_Shape* work_plane |
| | ) |
| | { |
| | FC_TIME_INIT2(t, t1); |
| | Handle(HLRBRep_Algo) brep_hlr; |
| | gp_Dir dir(0, 0, 1); |
| | try { |
| | brep_hlr = new HLRBRep_Algo(); |
| | brep_hlr->Add(shape_in, 0); |
| | HLRAlgo_Projector projector(gp_Ax2(gp_Pnt(), dir)); |
| | brep_hlr->Projector(projector); |
| | brep_hlr->Update(); |
| | brep_hlr->Hide(); |
| | } |
| | catch (...) { |
| | AREA_ERR("error occurred while projecting shape"); |
| | return -1; |
| | } |
| | FC_TIME_LOG(t1, "HLRBrep_Algo"); |
| | WireJoiner joiner; |
| | try { |
| | #define ADD_HLR_SHAPE(_name) \ |
| | shape = hlrToShape._name##Compound(); \ |
| | if (!shape.IsNull()) { \ |
| | BRepLib::BuildCurves3d(shape); \ |
| | joiner.add(shape, true); \ |
| | showShape(shape, "raw_" #_name); \ |
| | } |
| | TopoDS_Shape shape; |
| | HLRBRep_HLRToShape hlrToShape(brep_hlr); |
| | ADD_HLR_SHAPE(V); |
| | ADD_HLR_SHAPE(OutLineV); |
| | } |
| | catch (...) { |
| | AREA_ERR("error occurred while extracting edges"); |
| | return -1; |
| | } |
| | FC_TIME_LOG(t1, "WireJoiner init"); |
| | joiner.splitEdges(); |
| | FC_TIME_LOG(t1, "WireJoiner splitEdges"); |
| | for (const auto& v : joiner.edges) { |
| | showShape(v.edge, "split"); |
| | } |
| |
|
| | double tolerance = params ? params->Tolerance : Precision::Confusion(); |
| | int skips = joiner.findClosedWires(tolerance); |
| | FC_TIME_LOG(t1, "WireJoiner findClosedWires"); |
| |
|
| | showShape(joiner.comp, "pre_project"); |
| |
|
| | Area area(params); |
| | area.myParams.SectionCount = 0; |
| | area.myParams.Offset = 0.0; |
| | area.myParams.PocketMode = 0; |
| | area.myParams.Explode = false; |
| | area.myParams.FitArcs = false; |
| | area.myParams.Reorient = false; |
| | area.myParams.Outline = true; |
| | area.myParams.Fill = TopExp_Explorer(shape_in, TopAbs_FACE).More() ? FillFace : FillNone; |
| | area.myParams.Coplanar = CoplanarNone; |
| | area.myProjecting = true; |
| | if (work_plane) { |
| | area.myWorkPlane = *work_plane; |
| | } |
| | area.add(joiner.comp, OperationUnion); |
| | const TopoDS_Shape& shape = area.getShape(); |
| |
|
| | area.myParams.dump("project"); |
| |
|
| | showShape(shape, "projected"); |
| |
|
| | FC_TIME_LOG(t1, "Clipper wire union"); |
| | FC_TIME_LOG(t, "project total"); |
| |
|
| | if (shape.IsNull()) { |
| | AREA_ERR("project failed"); |
| | return -1; |
| | } |
| | shape_out = shape; |
| | return skips; |
| | } |
| |
|
| | std::vector<shared_ptr<Area>> Area::makeSections( |
| | PARAM_ARGS(PARAM_FARG, AREA_PARAMS_SECTION_EXTRA), |
| | const std::vector<double>& _heights, |
| | const TopoDS_Shape& section_plane |
| | ) |
| | { |
| | TopoDS_Shape plane; |
| | gp_Trsf trsf; |
| |
|
| | if (!section_plane.IsNull()) { |
| | plane = findPlane(section_plane, trsf); |
| | } |
| | else { |
| | plane = getPlane(&trsf); |
| | } |
| |
|
| | if (plane.IsNull()) { |
| | throw Base::ValueError("failed to obtain section plane"); |
| | } |
| |
|
| | FC_TIME_INIT2(t, t1); |
| |
|
| | 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); |
| | AREA_TRACE( |
| | "section bounds X(" << xMin << ',' << xMax << "), Y(" << yMin << ',' << yMax << "), Z(" |
| | << zMin << ',' << zMax << ')' |
| | ); |
| |
|
| | std::vector<double> heights; |
| | double tolerance = 0.0; |
| | if (_heights.empty()) { |
| | double z; |
| | double d = fabs(myParams.Stepdown); |
| | if (myParams.SectionCount > 1 && d < Precision::Confusion()) { |
| | throw Base::ValueError("invalid stepdown"); |
| | } |
| |
|
| | if (mode == SectionModeBoundBox) { |
| | if (myParams.Stepdown > 0.0) { |
| | z = zMax - myParams.SectionOffset; |
| | } |
| | else { |
| | z = zMin + myParams.SectionOffset; |
| | } |
| | } |
| | else if (mode == SectionModeWorkplane) { |
| | |
| | |
| | |
| | |
| | if (myParams.Stepdown > 0.0) { |
| | z = -myParams.SectionOffset; |
| | } |
| | else { |
| | z = myParams.SectionOffset; |
| | } |
| | } |
| | else { |
| | gp_Pnt pt(0, 0, myParams.SectionOffset); |
| | z = pt.Transformed(loc).Z(); |
| | } |
| | if (z > zMax) { |
| | z = zMax; |
| | } |
| | else if (z < zMin) { |
| | z = zMin; |
| | } |
| | double dz; |
| | if (myParams.Stepdown > 0.0) { |
| | dz = z - zMin; |
| | tolerance = myParams.SectionTolerance; |
| | } |
| | else { |
| | dz = zMax - z; |
| | tolerance = -myParams.SectionTolerance; |
| | } |
| | int count = myParams.SectionCount; |
| | if (count < 0 || count * d > dz) { |
| | count = floor(dz / d) + 1; |
| | } |
| | heights.reserve(count); |
| | for (int i = 0; i < count; ++i, z -= myParams.Stepdown) { |
| | double height = z - tolerance; |
| | if (z - zMin < myParams.SectionTolerance) { |
| | height = zMin + myParams.SectionTolerance; |
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { |
| | AREA_WARN("hit bottom " << z << ',' << zMin << ',' << height); |
| | } |
| | heights.push_back(height); |
| | if (myParams.Stepdown > 0.0) { |
| | break; |
| | } |
| | } |
| | else if (zMax - z < myParams.SectionTolerance) { |
| | height = zMax - myParams.SectionTolerance; |
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { |
| | AREA_WARN("hit top " << z << ',' << zMax << ',' << height); |
| | } |
| | heights.push_back(height); |
| | if (myParams.Stepdown < 0.0) { |
| | break; |
| | } |
| | } |
| | else { |
| | heights.push_back(height); |
| | } |
| | } |
| | } |
| | else { |
| | heights.reserve(_heights.size()); |
| |
|
| | bool hitMax = false, hitMin = false; |
| | for (double z : _heights) { |
| | switch (mode) { |
| | case SectionModeAbsolute: { |
| | gp_Pnt pt(0, 0, z); |
| | z = pt.Transformed(loc).Z(); |
| | break; |
| | } |
| | case SectionModeBoundBox: |
| | z = zMax - z; |
| | break; |
| | case SectionModeWorkplane: |
| | z = -z; |
| | break; |
| | default: |
| | throw Base::ValueError("invalid section mode"); |
| | } |
| | if (z - zMin < myParams.SectionTolerance) { |
| | if (hitMin) { |
| | continue; |
| | } |
| | hitMin = true; |
| | double zNew = zMin + myParams.SectionTolerance; |
| | |
| | if (_heights.empty() && FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { |
| | AREA_WARN("hit bottom " << z << ',' << zMin << ',' << zNew); |
| | } |
| | z = zNew; |
| | } |
| | else if (zMax - z < myParams.SectionTolerance) { |
| | if (hitMax) { |
| | continue; |
| | } |
| | double zNew = zMax - myParams.SectionTolerance; |
| | AREA_WARN("hit top " << z << ',' << zMax << ',' << zNew); |
| | z = zNew; |
| | } |
| | heights.push_back(z); |
| | } |
| | } |
| |
|
| | if (heights.empty()) { |
| | throw Base::ValueError("no sections"); |
| | } |
| |
|
| | std::vector<shared_ptr<Area>> sections; |
| | sections.reserve(heights.size()); |
| |
|
| | std::list<Shape> projectedShapes; |
| | if (project) { |
| | projectedShapes = getProjectedShapes(trsf, false); |
| | if (projectedShapes.empty()) { |
| | AREA_ERR("empty projection"); |
| | return sections; |
| | } |
| | } |
| |
|
| | tolerance *= 2.0; |
| | bool can_retry = fabs(tolerance) > Precision::Confusion(); |
| | TopLoc_Location locInverse(loc.Inverted()); |
| |
|
| | for (size_t i = 0; i < heights.size(); ++i) { |
| | double z = heights[i]; |
| | bool retried = !can_retry; |
| | while (true) { |
| | 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> area(std::make_shared<Area>(&myParams)); |
| | area->myParams.Outline = false; |
| | area->setPlane(face.Moved(locInverse)); |
| |
|
| | if (project) { |
| | for (const auto& s : projectedShapes) { |
| | gp_Trsf t; |
| | t.SetTranslation(gp_Vec(0, 0, -d)); |
| | TopLoc_Location wloc(t); |
| | area->add(s.shape.Moved(wloc).Moved(locInverse), s.op); |
| | } |
| | sections.push_back(area); |
| | break; |
| | } |
| |
|
| | for (auto it = myShapes.begin(); it != myShapes.end(); ++it) { |
| | const auto& s = *it; |
| | BRep_Builder builder; |
| | TopoDS_Compound comp; |
| | builder.MakeCompound(comp); |
| |
|
| | for (TopExp_Explorer xp(s.shape.Moved(loc), TopAbs_SOLID); xp.More(); xp.Next()) { |
| | showShape(xp.Current(), nullptr, "section_%zu_shape", i); |
| | std::list<TopoDS_Wire> wires; |
| | Part::CrossSection section(a, b, c, xp.Current()); |
| | Part::FuzzyHelper::withBooleanFuzzy(.0, [&]() { |
| | |
| | |
| | |
| | |
| | wires = section.slice(-d); |
| | }); |
| | showShapes(wires, nullptr, "section_%zu_wire", i); |
| | if (wires.empty()) { |
| | AREA_LOG("Section returns no wires"); |
| | continue; |
| | } |
| |
|
| | |
| | Part::FaceMakerBullseye mkFace; |
| | mkFace.setPlane(pln); |
| | for (const TopoDS_Wire& wire : wires) { |
| | if (BRep_Tool::IsClosed(wire)) { |
| | mkFace.addWire(wire); |
| | } |
| | } |
| | try { |
| | mkFace.Build(); |
| | const TopoDS_Shape& shape = mkFace.Shape(); |
| | if (shape.IsNull()) { |
| | AREA_WARN("FaceMakerBullseye return null shape on section"); |
| | } |
| | else { |
| | showShape(shape, nullptr, "section_%zu_face", i); |
| | for (auto it = wires.begin(), itNext = it; it != wires.end(); |
| | it = itNext) { |
| | ++itNext; |
| | if (BRep_Tool::IsClosed(*it)) { |
| | wires.erase(it); |
| | } |
| | } |
| | for (TopExp_Explorer xp( |
| | shape, |
| | myParams.Fill == FillNone ? TopAbs_WIRE : TopAbs_FACE |
| | ); |
| | xp.More(); |
| | xp.Next()) { |
| | builder.Add(comp, xp.Current()); |
| | } |
| | } |
| | } |
| | catch (Base::Exception& e) { |
| | AREA_WARN("FaceMakerBullseye failed on section: " << e.what()); |
| | } |
| | for (const TopoDS_Wire& wire : wires) { |
| | builder.Add(comp, wire); |
| | } |
| | } |
| |
|
| | |
| | if (TopExp_Explorer(comp, TopAbs_EDGE).More()) { |
| | const TopoDS_Shape& shape = comp.Moved(locInverse); |
| | showShape(shape, nullptr, "section_%zu_result", i); |
| | area->add(shape, s.op); |
| | } |
| | else if (area->myShapes.empty()) { |
| | auto itNext = it; |
| | if (++itNext != myShapes.end() |
| | && (itNext->op == OperationIntersection || itNext->op == OperationDifference)) { |
| | break; |
| | } |
| | } |
| | } |
| | if (!area->myShapes.empty()) { |
| | sections.push_back(area); |
| | FC_TIME_LOG(t1, "makeSection " << z); |
| | showShape(area->getShape(), nullptr, "section_%zu_final", i); |
| | break; |
| | } |
| | if (retried) { |
| | AREA_WARN("Discard empty section"); |
| | break; |
| | } |
| | else { |
| | AREA_TRACE("retry section " << z << "->" << z + tolerance); |
| | z += tolerance; |
| | retried = true; |
| | } |
| | } |
| | } |
| | FC_TIME_LOG(t, "makeSection count: " << sections.size() << ", total"); |
| | return sections; |
| | } |
| |
|
| | TopoDS_Shape Area::getPlane(gp_Trsf* trsf) |
| | { |
| | if (!myWorkPlane.IsNull()) { |
| | if (trsf) { |
| | *trsf = myTrsf; |
| | } |
| | return myWorkPlane; |
| | } |
| | if (myShapePlane.IsNull()) { |
| | if (myShapes.empty()) { |
| | throw Base::ValueError("no shape added"); |
| | } |
| | double top_z; |
| | for (auto& s : myShapes) { |
| | foreachSubshape(s.shape, FindPlane(myShapePlane, myTrsf, top_z)); |
| | } |
| | if (myShapePlane.IsNull()) { |
| | throw Base::ValueError("shapes are not planar"); |
| | } |
| | } |
| | if (trsf) { |
| | *trsf = myTrsf; |
| | } |
| | return myShapePlane; |
| | } |
| |
|
| | bool Area::isBuilt() const |
| | { |
| | return (myArea || !mySections.empty()); |
| | } |
| |
|
| | std::list<Area::Shape> Area::getProjectedShapes(const gp_Trsf& trsf, bool inverse) const |
| | { |
| | std::list<Shape> ret; |
| | TopLoc_Location loc(trsf); |
| | TopLoc_Location locInverse(loc.Inverted()); |
| |
|
| | mySkippedShapes = 0; |
| | for (auto& s : myShapes) { |
| | TopoDS_Shape out; |
| | int skipped = Area::project(out, s.shape.Moved(loc), &myParams, &myWorkPlane); |
| | if (skipped < 0) { |
| | ++mySkippedShapes; |
| | continue; |
| | } |
| | else { |
| | mySkippedShapes += skipped; |
| | } |
| | if (!out.IsNull()) { |
| | ret.emplace_back(s.op, inverse ? out.Moved(locInverse) : out); |
| | } |
| | } |
| | if (mySkippedShapes) { |
| | AREA_WARN("skipped " << mySkippedShapes << " sub shapes during projection"); |
| | } |
| | return ret; |
| | } |
| |
|
| | void Area::build() |
| | { |
| | if (isBuilt()) { |
| | return; |
| | } |
| |
|
| | if (myShapes.empty()) { |
| | throw Base::ValueError("no shape added"); |
| | } |
| |
|
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); |
| |
|
| | if (myHaveSolid && myParams.SectionCount) { |
| | mySections = makeSections(PARAM_FIELDS(AREA_MY, AREA_PARAMS_SECTION_EXTRA)); |
| | return; |
| | } |
| |
|
| | FC_TIME_INIT(t); |
| | gp_Trsf trsf; |
| | getPlane(&trsf); |
| |
|
| | try { |
| | myArea = std::make_unique<CArea>(); |
| | myAreaOpen = std::make_unique<CArea>(); |
| |
|
| | CAreaConfig conf(myParams); |
| | CArea areaClip; |
| |
|
| | mySkippedShapes = 0; |
| | short op = OperationUnion; |
| | bool pending = false; |
| | bool exploding = myParams.Explode; |
| | const auto& shapes = (myParams.Outline && !myProjecting) ? getProjectedShapes(trsf) |
| | : myShapes; |
| | for (const Shape& s : shapes) { |
| | if (exploding) { |
| | exploding = false; |
| | explode(s.shape); |
| | continue; |
| | } |
| | else if (op != s.op) { |
| | if (myParams.OpenMode != OpenModeNone) { |
| | myArea->m_curves.splice(myArea->m_curves.end(), myAreaOpen->m_curves); |
| | } |
| | pending = false; |
| | if (!areaClip.m_curves.empty()) { |
| | if (op == OperationCompound) { |
| | myArea->m_curves.splice(myArea->m_curves.end(), areaClip.m_curves); |
| | } |
| | else { |
| | myArea->Clip(toClipperOp(op), &areaClip, SubjectFill, ClipFill); |
| | areaClip.m_curves.clear(); |
| | } |
| | } |
| | op = s.op; |
| | } |
| | addToBuild(op == OperationUnion ? *myArea : areaClip, s.shape); |
| | pending = true; |
| | } |
| | if (mySkippedShapes && !myHaveSolid) { |
| | AREA_WARN( |
| | (myParams.Coplanar == CoplanarForce ? "Skipped " : "Found ") |
| | << mySkippedShapes << " non coplanar shapes" |
| | ); |
| | } |
| |
|
| | if (pending) { |
| | if (myParams.OpenMode != OpenModeNone) { |
| | myArea->m_curves.splice(myArea->m_curves.end(), myAreaOpen->m_curves); |
| | } |
| | if (op == OperationCompound) { |
| | myArea->m_curves.splice(myArea->m_curves.end(), areaClip.m_curves); |
| | } |
| | else { |
| | myArea->Clip(toClipperOp(op), &areaClip, SubjectFill, ClipFill); |
| | } |
| | } |
| | myArea->m_curves.splice(myArea->m_curves.end(), myAreaOpen->m_curves); |
| |
|
| | |
| | if (myParams.Explode) { |
| | WireJoiner joiner; |
| | gp_Trsf trsf(myTrsf.Inverted()); |
| | for (const auto& c : myArea->m_curves) { |
| | auto wire = toShape(c, &trsf); |
| | if (!wire.IsNull()) { |
| | joiner.add(wire); |
| | } |
| | } |
| | joiner.join(Precision::Confusion()); |
| |
|
| | Area area(&myParams); |
| | area.myParams.Explode = false; |
| | area.myParams.Coplanar = CoplanarNone; |
| | area.myWorkPlane = getPlane(&area.myTrsf); |
| | area.add(joiner.comp, OperationCompound); |
| | area.build(); |
| | myArea = std::move(area.myArea); |
| | } |
| |
|
| | if (myParams.Outline) { |
| | myArea->Reorder(); |
| | for (auto it = myArea->m_curves.begin(), itNext = it; it != myArea->m_curves.end(); |
| | it = itNext) { |
| | ++itNext; |
| | auto& curve = *it; |
| | if (curve.IsClosed() && curve.IsClockwise()) { |
| | myArea->m_curves.erase(it); |
| | } |
| | } |
| | } |
| |
|
| | FC_TIME_TRACE(t, "prepare"); |
| | } |
| | catch (...) { |
| | clean(); |
| | throw; |
| | } |
| | } |
| |
|
| | TopoDS_Shape Area::toShape(CArea& area, short fill, int reorient) |
| | { |
| | 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.get()) { |
| | CArea copy(area); |
| | copy.FitArcs(); |
| | return toShape(copy, bFill, &trsf, reorient); |
| | } |
| | area.FitArcs(); |
| | } |
| | return toShape(area, bFill, &trsf, reorient); |
| | } |
| |
|
| |
|
| | #define AREA_SECTION(_op, _index, ...) \ |
| | do { \ |
| | if (mySections.size()) { \ |
| | if (_index >= (int)mySections.size()) \ |
| | return TopoDS_Shape(); \ |
| | if (_index < 0) { \ |
| | BRep_Builder builder; \ |
| | TopoDS_Compound compound; \ |
| | builder.MakeCompound(compound); \ |
| | for (shared_ptr<Area> area : mySections) { \ |
| | const TopoDS_Shape& s = area->_op(_index, ##__VA_ARGS__); \ |
| | if (s.IsNull()) \ |
| | continue; \ |
| | builder.Add(compound, s); \ |
| | } \ |
| | if (TopExp_Explorer(compound, TopAbs_EDGE).More()) \ |
| | return TopoDS_Shape(std::move(compound)); \ |
| | return TopoDS_Shape(); \ |
| | } \ |
| | return mySections[_index]->_op(_index, ##__VA_ARGS__); \ |
| | } \ |
| | } while (0) |
| |
|
| | TopoDS_Shape Area::getShape(int index) |
| | { |
| | build(); |
| | AREA_SECTION(getShape, index); |
| |
|
| | if (myShapeDone) { |
| | return myShape; |
| | } |
| |
|
| | if (!myArea) { |
| | return TopoDS_Shape(); |
| | } |
| |
|
| | CAreaConfig conf(myParams); |
| |
|
| | |
| | if (fabs(myParams.Offset) < Precision::Confusion()) { |
| | if (myParams.PocketMode == PocketModeNone) { |
| | myShape = toShape(*myArea, myParams.Fill); |
| | myShapeDone = true; |
| | return myShape; |
| | } |
| | myShape = makePocket(index, PARAM_FIELDS(AREA_MY, AREA_PARAMS_POCKET)); |
| | myShapeDone = true; |
| | return myShape; |
| | } |
| |
|
| | |
| | if (myParams.PocketMode == PocketModeNone) { |
| | myShape = makeOffset(index, PARAM_FIELDS(AREA_MY, AREA_PARAMS_OFFSET)); |
| | myShapeDone = true; |
| | return myShape; |
| | } |
| |
|
| | FC_TIME_INIT(t); |
| |
|
| | |
| | std::list<shared_ptr<CArea>> areas; |
| | makeOffset(areas, PARAM_FIELDS(AREA_MY, AREA_PARAMS_OFFSET)); |
| |
|
| | if (areas.empty()) { |
| | areas.push_back(make_shared<CArea>(*myArea)); |
| | } |
| |
|
| | Area areaPocket(&myParams); |
| | bool front = true; |
| | if (areas.size() > 1) { |
| | double step = myParams.Stepover; |
| | if (fabs(step) < Precision::Confusion()) { |
| | step = myParams.Offset; |
| | } |
| | front = step > 0; |
| | } |
| |
|
| | |
| | |
| | |
| | if (front) { |
| | areaPocket.add(toShape(*areas.front(), myParams.Fill)); |
| | areas.pop_back(); |
| | } |
| | else { |
| | areaPocket.add(toShape(*areas.back(), myParams.Fill)); |
| | areas.pop_front(); |
| | } |
| |
|
| | BRep_Builder builder; |
| | TopoDS_Compound compound; |
| | builder.MakeCompound(compound); |
| |
|
| | short fill = myParams.Thicken ? FillFace : FillNone; |
| | FC_TIME_INIT(t2); |
| | FC_DURATION_DECL_INIT(d); |
| | for (shared_ptr<CArea> area : areas) { |
| | if (myParams.Thicken) { |
| | area->Thicken(myParams.ToolRadius); |
| | FC_DURATION_PLUS(d, t2); |
| | } |
| | const TopoDS_Shape& shape = toShape(*area, fill); |
| | if (shape.IsNull()) { |
| | continue; |
| | } |
| | builder.Add(compound, shape); |
| | } |
| | if (myParams.Thicken) { |
| | FC_DURATION_LOG(d, "Thicken"); |
| | } |
| |
|
| | |
| | if (TopExp_Explorer(compound, TopAbs_EDGE).More()) { |
| | builder.Add(compound, areaPocket.makePocket(-1, PARAM_FIELDS(AREA_MY, AREA_PARAMS_POCKET))); |
| | myShape = compound; |
| | } |
| | myShapeDone = true; |
| | FC_TIME_LOG(t, "total"); |
| | return myShape; |
| | } |
| |
|
| | TopoDS_Shape Area::makeOffset( |
| | int index, |
| | PARAM_ARGS(PARAM_FARG, AREA_PARAMS_OFFSET), |
| | int reorient, |
| | bool from_center |
| | ) |
| | { |
| | build(); |
| | AREA_SECTION(makeOffset, index, PARAM_FIELDS(PARAM_FARG, AREA_PARAMS_OFFSET), reorient, from_center); |
| |
|
| | std::list<shared_ptr<CArea>> areas; |
| | makeOffset(areas, PARAM_FIELDS(PARAM_FARG, AREA_PARAMS_OFFSET), from_center); |
| | if (areas.empty()) { |
| | if (myParams.Thicken && myParams.ToolRadius > Precision::Confusion()) { |
| | CArea area(*myArea); |
| | FC_TIME_INIT(t); |
| | area.Thicken(myParams.ToolRadius); |
| | FC_TIME_LOG(t, "Thicken"); |
| | return toShape(area, FillFace, reorient); |
| | } |
| | return TopoDS_Shape(); |
| | } |
| | BRep_Builder builder; |
| | TopoDS_Compound compound; |
| | builder.MakeCompound(compound); |
| | FC_TIME_INIT(t); |
| | FC_DURATION_DECL_INIT(d); |
| |
|
| | bool thicken = myParams.Thicken && myParams.ToolRadius > Precision::Confusion(); |
| |
|
| | for (shared_ptr<CArea> area : areas) { |
| | short fill; |
| | if (thicken) { |
| | area->Thicken(myParams.ToolRadius); |
| | FC_DURATION_PLUS(d, t); |
| | fill = FillFace; |
| | } |
| | else if (areas.size() == 1) { |
| | fill = myParams.Fill; |
| | } |
| | else { |
| | fill = FillNone; |
| | } |
| | const TopoDS_Shape& shape = toShape(*area, fill, reorient); |
| | if (shape.IsNull()) { |
| | continue; |
| | } |
| | builder.Add(compound, shape); |
| | } |
| | if (thicken) { |
| | FC_DURATION_LOG(d, "Thicken"); |
| | } |
| | if (TopExp_Explorer(compound, TopAbs_EDGE).More()) { |
| | return TopoDS_Shape(std::move(compound)); |
| | } |
| | return TopoDS_Shape(); |
| | } |
| |
|
| | void Area::makeOffset( |
| | list<shared_ptr<CArea>>& areas, |
| | PARAM_ARGS(PARAM_FARG, AREA_PARAMS_OFFSET), |
| | bool from_center |
| | ) |
| | { |
| | if (fabs(offset) < Precision::Confusion()) { |
| | return; |
| | } |
| |
|
| | FC_TIME_INIT2(t, t1); |
| |
|
| | long count = 1; |
| | if (extra_pass) { |
| | if (fabs(stepover) < Precision::Confusion()) { |
| | stepover = offset; |
| | } |
| | if (extra_pass > 0) { |
| | count += extra_pass; |
| | } |
| | else { |
| | if (stepover > 0 || offset > 0) { |
| | throw Base::ValueError("invalid extra count"); |
| | } |
| | |
| | count = -1; |
| | } |
| | } |
| |
|
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); |
| | #ifdef AREA_OFFSET_ALGO |
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); |
| | #endif |
| |
|
| | if (offset < 0) { |
| | if (count < 0) { |
| | if (!last_stepover) { |
| | last_stepover = offset * 0.5; |
| | } |
| | } |
| | else { |
| | last_stepover = 0; |
| | } |
| | } |
| | for (int i = 0; count < 0 || i < count; ++i, offset += stepover) { |
| | if (from_center) { |
| | areas.push_front(make_shared<CArea>()); |
| | } |
| | else { |
| | areas.push_back(make_shared<CArea>()); |
| | } |
| | CArea& area = from_center ? (*areas.front()) : (*areas.back()); |
| | CArea areaOpen; |
| | #ifdef AREA_OFFSET_ALGO |
| | if (myParams.Algo == Area::Algolibarea) { |
| | for (const CCurve& c : myArea->m_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: |
| | |
| | |
| | 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.RoundPrecision |
| | ); |
| | #ifdef AREA_OFFSET_ALGO |
| | break; |
| | } |
| | #endif |
| | if (count > 1) { |
| | FC_TIME_LOG(t1, "makeOffset " << i << '/' << count); |
| | } |
| | if (area.m_curves.empty()) { |
| | if (from_center) { |
| | areas.pop_front(); |
| | } |
| | else { |
| | areas.pop_back(); |
| | } |
| | if (areas.empty()) { |
| | break; |
| | } |
| | if (last_stepover && last_stepover > stepover) { |
| | offset -= stepover; |
| | stepover = last_stepover; |
| | --i; |
| | continue; |
| | } |
| | return; |
| | } |
| | } |
| | FC_TIME_LOG(t, "makeOffset count: " << count); |
| | } |
| |
|
| | 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"); |
| | } |
| |
|
| | if (stepover == 0.0) { |
| | stepover = tool_radius; |
| | } |
| |
|
| | if (stepover < Precision::Confusion()) { |
| | throw Base::ValueError("stepover too small"); |
| | } |
| |
|
| | if (mode == Area::PocketModeNone) { |
| | return TopoDS_Shape(); |
| | } |
| |
|
| | build(); |
| | AREA_SECTION(makePocket, index, PARAM_FIELDS(PARAM_FARG, AREA_PARAMS_POCKET)); |
| |
|
| | FC_TIME_INIT(t); |
| | bool done = false; |
| |
|
| | if (index >= 0) { |
| | if (fabs(angle_shift) >= Precision::Confusion()) { |
| | angle += index * angle_shift; |
| | } |
| |
|
| | if (fabs(shift) >= Precision::Confusion()) { |
| | shift *= index; |
| | } |
| | } |
| |
|
| | if (angle < -360.0) { |
| | angle += ceil(fabs(angle) / 360.0) * 360.0; |
| | } |
| | else if (angle > 360.0) { |
| | angle -= floor(angle / 360.0) * 360.0; |
| | } |
| | else if (angle < 0.0) { |
| | angle += 360.0; |
| | } |
| |
|
| | if (shift < -stepover) { |
| | shift += ceil(fabs(shift) / stepover) * stepover; |
| | } |
| | else if (shift > stepover) { |
| | shift -= floor(shift / stepover) * stepover; |
| | } |
| | else if (shift < 0.0) { |
| | shift += stepover; |
| | } |
| |
|
| | CAreaConfig conf(myParams); |
| |
|
| | CArea out; |
| | PocketMode pm; |
| |
|
| | switch (mode) { |
| | case Area::PocketModeZigZag: |
| | pm = ZigZagPocketMode; |
| | break; |
| | case Area::PocketModeSpiral: |
| | pm = SpiralPocketMode; |
| | break; |
| | case Area::PocketModeOffset: { |
| | PARAM_DECLARE_INIT(PARAM_FNAME, AREA_PARAMS_OFFSET); |
| | Offset = -tool_radius - extra_offset - shift; |
| | ExtraPass = -1; |
| | Stepover = -stepover; |
| | LastStepover = -last_stepover; |
| | |
| | return makeOffset(index, PARAM_FIELDS(PARAM_FNAME, AREA_PARAMS_OFFSET), -1, from_center); |
| | } |
| | case Area::PocketModeZigZagOffset: |
| | pm = ZigZagThenSingleOffsetPocketMode; |
| | break; |
| | case Area::PocketModeLine: |
| | case Area::PocketModeGrid: |
| | case Area::PocketModeTriangle: { |
| | CBox2D box; |
| | myArea->GetBox(box); |
| | if (!box.m_valid) { |
| | throw Base::ValueError("failed to get bound box"); |
| | } |
| | double angles[4]; |
| | int count = 1; |
| | angles[0] = 0.0; |
| | if (mode == Area::PocketModeGrid) { |
| | angles[1] = 90.0; |
| | count = 2; |
| | if (shift < Precision::Confusion()) { |
| | count = 4; |
| | angles[2] = 180.0; |
| | angles[3] = 270.0; |
| | } |
| | } |
| | else if (mode == Area::PocketModeTriangle) { |
| | count = 3; |
| | angles[1] = 120; |
| | angles[2] = 240; |
| | } |
| | else { |
| | shift = 0.0; |
| | } |
| | Point center(box.Centre()); |
| | double r = box.Radius() + stepover; |
| | if (extra_offset > 0) { |
| | r += extra_offset; |
| | } |
| | int steps = (int)ceil(r * 2.0 / stepover); |
| | for (int i = 0; i < count; ++i) { |
| | double a = angle + angles[i]; |
| | if (a > 360.0) { |
| | a -= 360.0; |
| | } |
| | double offset = -r + shift; |
| | for (int j = 0; j < steps; ++j, offset += stepover) { |
| | Point p1(-r, offset), p2(r, offset); |
| | if (a > Precision::Confusion()) { |
| | double r = Base::toRadians(a); |
| | p1.Rotate(r); |
| | p2.Rotate(r); |
| | } |
| | out.m_curves.emplace_back(); |
| | CCurve& curve = out.m_curves.back(); |
| | curve.m_vertices.emplace_back(p1 + center); |
| | curve.m_vertices.emplace_back(p2 + center); |
| | } |
| | } |
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); |
| | PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); |
| | auto area = *myArea; |
| | area.OffsetWithClipper( |
| | -tool_radius - extra_offset, |
| | JoinType, |
| | EndType, |
| | myParams.MiterLimit, |
| | myParams.RoundPrecision |
| | ); |
| | out.Clip(toClipperOp(OperationIntersection), &area, SubjectFill, ClipFill); |
| | done = true; |
| | break; |
| | } |
| | default: |
| | throw Base::ValueError("unknown pocket mode"); |
| | } |
| |
|
| | if (!done) { |
| | CAreaPocketParams params(tool_radius, extra_offset, stepover, from_center, pm, angle); |
| | CArea in(*myArea); |
| | |
| | |
| | in.Reorder(); |
| | in.MakePocketToolpath(out.m_curves, params); |
| | } |
| |
|
| | FC_TIME_LOG(t, "makePocket"); |
| |
|
| | if (myParams.Thicken) { |
| | FC_TIME_INIT(t); |
| | out.Thicken(tool_radius); |
| | FC_TIME_LOG(t, "thicken"); |
| | 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) |
| | { |
| | return ((b.X() - a.X()) * (c.Y() - a.Y()) - (b.Y() - a.Y()) * (c.X() - a.X())) > 0; |
| | } |
| |
|
| | TopoDS_Shape Area::toShape(const CCurve& _c, const gp_Trsf* trsf, int reorient) |
| | { |
| | Handle(TopTools_HSequenceOfShape) hEdges = new TopTools_HSequenceOfShape(); |
| | Handle(TopTools_HSequenceOfShape) hWires = new TopTools_HSequenceOfShape(); |
| |
|
| | CCurve cReversed; |
| | if (reorient) { |
| | if (_c.IsClosed() |
| | && ((reorient > 0 && _c.IsClockwise()) || (reorient < 0 && !_c.IsClockwise()))) { |
| | cReversed = _c; |
| | cReversed.Reverse(); |
| | } |
| | else { |
| | reorient = 0; |
| | } |
| | } |
| | const CCurve& c = reorient ? cReversed : _c; |
| |
|
| | TopoDS_Shape shape; |
| | 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.SquareDistance(pt) <= Precision::SquareConfusion()) { |
| | continue; |
| | } |
| | if (v.m_type == 0) { |
| | auto edge = BRepBuilderAPI_MakeEdge(pt, pnext).Edge(); |
| | hEdges->Append(edge); |
| | } |
| | else { |
| | gp_Pnt center(v.m_c.x, v.m_c.y, 0); |
| | double r = center.Distance(pt); |
| | double r2 = center.Distance(pnext); |
| | bool fix_arc = fabs(r - r2) > Precision::Confusion(); |
| | while (true) { |
| | if (fix_arc) { |
| | double d = pt.Distance(pnext); |
| | double rr = r * r; |
| | double dd = d * d * 0.25; |
| | double q = rr <= dd ? 0 : sqrt(rr - dd); |
| | 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); |
| | } |
| | AREA_WARN( |
| | "Arc correction: " << r << ", " << r2 << ", center" << AREA_XYZ(center) |
| | << "->" << AREA_XYZ(newCenter) |
| | ); |
| | center = newCenter; |
| | } |
| | gp_Ax2 axis(center, gp_Dir(0, 0, v.m_type)); |
| | try { |
| | auto edge = BRepBuilderAPI_MakeEdge(gp_Circ(axis, r), pt, pnext).Edge(); |
| | hEdges->Append(edge); |
| | break; |
| | } |
| | catch (Standard_Failure& e) { |
| | if (!fix_arc) { |
| | fix_arc = true; |
| | AREA_WARN("OCC exception on making arc: " << e.GetMessageString()); |
| | } |
| | else { |
| | AREA_ERR("OCC exception on making arc: " << e.GetMessageString()); |
| | throw; |
| | } |
| | } |
| | } |
| | } |
| | pt = pnext; |
| | } |
| |
|
| | ShapeAnalysis_FreeBounds::ConnectEdgesToWires(hEdges, Precision::Confusion(), Standard_False, hWires); |
| | if (!hWires->Length()) { |
| | return shape; |
| | } |
| | if (hWires->Length() == 1) { |
| | shape = hWires->Value(1); |
| | } |
| | else { |
| | BRep_Builder builder; |
| | TopoDS_Compound compound; |
| | builder.MakeCompound(compound); |
| | for (int i = 1; i <= hWires->Length(); ++i) { |
| | builder.Add(compound, hWires->Value(i)); |
| | } |
| | shape = compound; |
| | } |
| |
|
| | if (trsf) { |
| | shape.Move(TopLoc_Location(*trsf)); |
| | } |
| | return shape; |
| | } |
| |
|
| | TopoDS_Shape Area::toShape(const CArea& area, bool fill, const gp_Trsf* trsf, int reorient) |
| | { |
| | BRep_Builder builder; |
| | TopoDS_Compound compound; |
| | builder.MakeCompound(compound); |
| |
|
| | for (const CCurve& c : area.m_curves) { |
| | const auto& wire = toShape(c, trsf, reorient); |
| | if (!wire.IsNull()) { |
| | builder.Add(compound, wire); |
| | } |
| | } |
| | TopExp_Explorer xp(compound, TopAbs_EDGE); |
| | if (!xp.More()) { |
| | return TopoDS_Shape(); |
| | } |
| | if (fill) { |
| | try { |
| | FC_TIME_INIT(t); |
| | 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()) { |
| | AREA_WARN("FaceMakerBullseye returns null shape"); |
| | } |
| | FC_TIME_LOG(t, "makeFace"); |
| | return mkFace.Shape(); |
| | } |
| | catch (Base::Exception& e) { |
| | AREA_WARN("FaceMakerBullseye failed: " << e.what()); |
| | } |
| | } |
| | return TopoDS_Shape(std::move(compound)); |
| | } |
| |
|
| | struct WireInfo |
| | { |
| | TopoDS_Wire wire; |
| | std::deque<gp_Pnt> points; |
| | gp_Pnt pt_end; |
| | bool isClosed; |
| |
|
| | inline const gp_Pnt& pstart() const |
| | { |
| | return points.front(); |
| | } |
| | inline const gp_Pnt& pend() const |
| | { |
| | return isClosed ? pstart() : pt_end; |
| | } |
| | }; |
| |
|
| | using Wires = std::list<WireInfo>; |
| | using RValue = std::pair<Wires::iterator, size_t>; |
| |
|
| | struct RGetter |
| | { |
| | using result_type = const gp_Pnt&; |
| | result_type operator()(const RValue& v) const |
| | { |
| | return v.first->points[v.second]; |
| | } |
| | }; |
| | using RTree = bgi::rtree<RValue, RParameters, RGetter>; |
| |
|
| | struct ShapeParams |
| | { |
| | double abscissa; |
| | int k; |
| | short orientation; |
| | short direction; |
| | FC_DURATION_DECLARE(qd); |
| | FC_DURATION_DECLARE(bd); |
| | FC_DURATION_DECLARE(rd); |
| | FC_DURATION_DECLARE(xd); |
| |
|
| | ShapeParams(double _a, int _k, short o, short d) |
| | : abscissa(_a) |
| | , k(_k) |
| | , orientation(o) |
| | , direction(d) |
| | { |
| | FC_DURATION_INIT3(qd, bd, rd); |
| | FC_DURATION_INIT(xd); |
| | } |
| | }; |
| |
|
| | bool operator<(const Wires::iterator& a, const Wires::iterator& b) |
| | { |
| | return &(*a) < &(*b); |
| | } |
| | using RResults = std::map<Wires::iterator, size_t>; |
| |
|
| | struct GetWires |
| | { |
| | Wires& wires; |
| | RTree& rtree; |
| | ShapeParams& params; |
| | GetWires(std::list<WireInfo>& ws, RTree& rt, ShapeParams& rp) |
| | : wires(ws) |
| | , rtree(rt) |
| | , params(rp) |
| | {} |
| | void operator()(const TopoDS_Shape& shape, int type) |
| | { |
| | wires.emplace_back(); |
| | WireInfo& info = wires.back(); |
| | if (type == TopAbs_WIRE) { |
| | info.wire = TopoDS::Wire(shape); |
| | } |
| | else { |
| | info.wire = BRepBuilderAPI_MakeWire(TopoDS::Edge(shape)).Wire(); |
| | } |
| | info.isClosed = BRep_Tool::IsClosed(info.wire); |
| |
|
| | if (info.isClosed && params.orientation == Area::OrientationReversed) { |
| | info.wire.Reverse(); |
| | } |
| |
|
| | FC_TIME_INIT(t); |
| | if (params.abscissa < Precision::Confusion() || !info.isClosed) { |
| | gp_Pnt p1, p2; |
| | getEndPoints(info.wire, p1, p2); |
| | if (!info.isClosed && params.direction != Area::DirectionNone) { |
| | bool reverse = false; |
| | switch (params.direction) { |
| | case Area::DirectionXPositive: |
| | reverse = p1.X() > p2.X(); |
| | break; |
| | case Area::DirectionXNegative: |
| | reverse = p1.X() < p2.X(); |
| | break; |
| | case Area::DirectionYPositive: |
| | reverse = p1.Y() > p2.Y(); |
| | break; |
| | case Area::DirectionYNegative: |
| | reverse = p1.Y() < p2.Y(); |
| | break; |
| | case Area::DirectionZPositive: |
| | reverse = p1.Z() > p2.Z(); |
| | break; |
| | case Area::DirectionZNegative: |
| | reverse = p1.Z() < p2.Z(); |
| | break; |
| | } |
| | if (reverse) { |
| | info.wire.Reverse(); |
| | std::swap(p1, p2); |
| | } |
| | } |
| | |
| | |
| | info.points.push_back(p1); |
| | if (!info.isClosed && params.direction == Area::DirectionNone) { |
| | info.points.push_back(p2); |
| | } |
| | info.pt_end = p2; |
| | } |
| | else { |
| | |
| | |
| | |
| | for (BRepTools_WireExplorer xp(info.wire); xp.More(); xp.Next()) { |
| |
|
| | |
| | info.points.push_back(BRep_Tool::Pnt(xp.CurrentVertex())); |
| |
|
| | BRepAdaptor_Curve curve(xp.Current()); |
| | GCPnts_UniformAbscissa discretizer( |
| | curve, |
| | params.abscissa, |
| | curve.FirstParameter(), |
| | curve.LastParameter() |
| | ); |
| | if (discretizer.IsDone()) { |
| | int nbPoints = discretizer.NbPoints(); |
| | |
| | |
| | |
| | |
| | |
| | |
| | for (int i = 2; i < nbPoints; i++) { |
| | info.points.push_back(curve.Value(discretizer.Parameter(i))); |
| | } |
| | } |
| | else { |
| | AREA_WARN("discretizer failed"); |
| | } |
| | } |
| | |
| | |
| | } |
| | auto it = wires.end(); |
| | --it; |
| | for (size_t i = 0, count = it->points.size(); i < count; ++i) { |
| | rtree.insert(RValue(it, i)); |
| | } |
| | FC_DURATION_PLUS(params.bd, t); |
| | } |
| | }; |
| |
|
| | struct ShapeInfo |
| | { |
| | gp_Pln myPln; |
| | Wires myWires; |
| | RTree myRTree; |
| | TopoDS_Shape myShape; |
| | gp_Pnt myBestPt; |
| | gp_Pnt myStartPt; |
| | Wires::iterator myBestWire; |
| | TopoDS_Shape mySupport; |
| | ShapeParams& myParams; |
| | Standard_Real myBestParameter; |
| | bool mySupportEdge; |
| | bool myPlanar; |
| | bool myRebase; |
| | bool myStart; |
| |
|
| | ShapeInfo(const gp_Pln& pln, const TopoDS_Shape& shape, ShapeParams& params) |
| | : myPln(pln) |
| | , myShape(shape) |
| | , myStartPt(1e20, 1e20, 1e20) |
| | , myParams(params) |
| | , myBestParameter(0) |
| | , mySupportEdge(false) |
| | , myPlanar(true) |
| | , myRebase(false) |
| | , myStart(false) |
| | {} |
| |
|
| | ShapeInfo(const TopoDS_Shape& shape, ShapeParams& params) |
| | : myShape(shape) |
| | , myStartPt(1e20, 1e20, 1e20) |
| | , myParams(params) |
| | , myBestParameter(0) |
| | , mySupportEdge(false) |
| | , myPlanar(false) |
| | , myRebase(false) |
| | , myStart(false) |
| | {} |
| | double nearest(const gp_Pnt& pt) |
| | { |
| | myStartPt = pt; |
| |
|
| | if (myWires.empty()) { |
| | foreachSubshape(myShape, GetWires(myWires, myRTree, myParams), TopAbs_WIRE); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | RResults ret; |
| | { |
| | FC_TIME_INIT(t); |
| | myRTree.query(bgi::nearest(pt, myParams.k), bgi::inserter(ret)); |
| | FC_DURATION_PLUS(myParams.qd, t); |
| | } |
| |
|
| | TopoDS_Shape v = BRepBuilderAPI_MakeVertex(pt); |
| | bool first = true; |
| | double best_d = 1e20; |
| | myBestWire = myWires.begin(); |
| | for (auto r : ret) { |
| | Wires::iterator it = r.first; |
| | const TopoDS_Shape& wire = it->wire; |
| | TopoDS_Shape support; |
| | bool support_edge; |
| | double d = 0; |
| | gp_Pnt p; |
| | bool done = false; |
| | bool is_start = false; |
| | if (BRep_Tool::IsClosed(wire)) { |
| | FC_TIME_INIT(t); |
| | BRepExtrema_DistShapeShape extss(v, wire); |
| | if (extss.IsDone() && extss.NbSolution()) { |
| | d = extss.Value(); |
| | d *= d; |
| | p = extss.PointOnShape2(1); |
| | support = extss.SupportOnShape2(1); |
| | support_edge = extss.SupportTypeShape2(1) == BRepExtrema_IsOnEdge; |
| | if (support_edge) { |
| | extss.ParOnEdgeS2(1, myBestParameter); |
| | } |
| | done = true; |
| | } |
| | else { |
| | AREA_WARN("BRepExtrema_DistShapeShape failed"); |
| | } |
| | FC_DURATION_PLUS(myParams.xd, t); |
| | } |
| | if (!done) { |
| | double d1 = pt.SquareDistance(it->pstart()); |
| | if (myParams.direction != Area::DirectionNone) { |
| | d = d1; |
| | p = it->pstart(); |
| | is_start = true; |
| | } |
| | else { |
| | double d2 = pt.SquareDistance(it->pend()); |
| | if (d1 < d2) { |
| | d = d1; |
| | p = it->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; |
| | } |
| |
|
| | |
| | |
| | TopoDS_Shape rebaseWire(gp_Pnt& pend, double min_dist) |
| | { |
| | min_dist *= min_dist; |
| | BRepBuilderAPI_MakeWire mkWire; |
| | TopoDS_Shape estart; |
| | TopoDS_Edge eend; |
| |
|
| | for (int state = 0; state < 3; ++state) { |
| | BRepTools_WireExplorer xp(TopoDS::Wire(myBestWire->wire)); |
| | gp_Pnt pprev(BRep_Tool::Pnt(xp.CurrentVertex())); |
| |
|
| | |
| | if (state == 0 && !mySupportEdge |
| | && pprev.SquareDistance(myBestPt) <= Precision::SquareConfusion()) { |
| | pend = myBestWire->pend(); |
| | return myBestWire->wire; |
| | } |
| |
|
| | gp_Pnt pt; |
| | for (; xp.More(); xp.Next(), pprev = pt) { |
| | const auto& edge = xp.Current(); |
| |
|
| | |
| | |
| | if (state == 2 && estart.IsEqual(edge)) { |
| | break; |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | Standard_Real first, last; |
| | Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, first, last); |
| | pt = curve->Value(last); |
| | bool reversed; |
| | if (pprev.SquareDistance(pt) <= Precision::SquareConfusion()) { |
| | reversed = true; |
| | pt = curve->Value(first); |
| | } |
| | else { |
| | reversed = false; |
| | } |
| |
|
| | |
| | |
| | if (state) { |
| | mkWire.Add(edge); |
| | continue; |
| | } |
| | |
| | if (mySupportEdge) { |
| | |
| | if (edge.IsEqual(mySupport)) { |
| | |
| | GeomAPI_ProjectPointOnCurve gpp; |
| | gpp.Init(myBestPt, curve); |
| | gpp.Perform(myBestPt); |
| | myBestPt = gpp.NearestPoint(); |
| |
|
| | gpp.Perform(pprev); |
| | pprev = gpp.NearestPoint(); |
| |
|
| | gpp.Perform(pt); |
| | pt = gpp.NearestPoint(); |
| |
|
| | double d1 = pprev.SquareDistance(myBestPt); |
| | double d2 = pt.SquareDistance(myBestPt); |
| |
|
| | if (d1 > min_dist && d2 > min_dist) { |
| | BRepBuilderAPI_MakeEdge mkEdge1, mkEdge2; |
| | if (reversed) { |
| | mkEdge1.Init(curve, myBestPt, pprev); |
| | mkEdge2.Init(curve, pt, myBestPt); |
| | } |
| | else { |
| | mkEdge1.Init(curve, pprev, myBestPt); |
| | mkEdge2.Init(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()); |
| | } |
| | pend = myBestPt; |
| | estart = mySupport; |
| | state = 1; |
| | continue; |
| | } |
| | AREA_WARN( |
| | (reversed ? "reversed " : "") |
| | << "edge split failed " << AREA_XYZ(pprev) << ", " |
| | << AREA_XYZ(myBestPt) << ", " << AREA_XYZ(pt) << ", " << d1 << ", " |
| | << d2 << ", err: " << mkEdge1.Error() << ", " << mkEdge2.Error() |
| | ); |
| | } |
| |
|
| | if (d1 < d2) { |
| | pend = pprev; |
| | estart = edge; |
| | state = 1; |
| | mkWire.Add(edge); |
| | } |
| | else { |
| | mySupportEdge = false; |
| | myBestPt = pt; |
| | continue; |
| | } |
| | } |
| | } |
| | else if (myBestPt.SquareDistance(pprev) <= Precision::SquareConfusion()) { |
| | pend = pprev; |
| | estart = edge; |
| | state = 1; |
| | mkWire.Add(edge); |
| | } |
| | } |
| |
|
| | if (state == 0) { |
| | AREA_WARN("edge break point not found"); |
| | pend = myBestWire->pend(); |
| | return myBestWire->wire; |
| | } |
| | } |
| | if (!eend.IsNull()) { |
| | mkWire.Add(eend); |
| | } |
| | if (mkWire.IsDone()) { |
| | return mkWire.Wire(); |
| | } |
| | AREA_WARN("wire rebase failed"); |
| | pend = myBestWire->pend(); |
| | return myBestWire->wire; |
| | } |
| |
|
| | std::list<TopoDS_Shape> sortWires( |
| | const gp_Pnt& pstart, |
| | gp_Pnt& pend, |
| | double min_dist, |
| | double max_dist, |
| | gp_Pnt* pentry |
| | ) |
| | { |
| |
|
| | std::list<TopoDS_Shape> wires; |
| |
|
| | if (myWires.empty() || pstart.SquareDistance(myStartPt) > Precision::SquareConfusion()) { |
| | nearest(pstart); |
| | if (myWires.empty()) { |
| | return wires; |
| | } |
| | } |
| |
|
| | if (pentry) { |
| | *pentry = myBestPt; |
| | } |
| |
|
| | if (min_dist < 0.01) { |
| | min_dist = 0.01; |
| | } |
| | while (true) { |
| | if (myRebase) { |
| | pend = myBestPt; |
| | wires.push_back(rebaseWire(pend, min_dist)); |
| | } |
| | else if (!myStart) { |
| | wires.push_back(myBestWire->wire.Reversed()); |
| | pend = myBestWire->pstart(); |
| | } |
| | else { |
| | wires.push_back(myBestWire->wire); |
| | pend = myBestWire->pend(); |
| | } |
| | FC_TIME_INIT(t); |
| | for (size_t i = 0, count = myBestWire->points.size(); i < count; ++i) { |
| | myRTree.remove(RValue(myBestWire, i)); |
| | } |
| | FC_DURATION_PLUS(myParams.rd, t); |
| | myWires.erase(myBestWire); |
| | if (myWires.empty()) { |
| | break; |
| | } |
| | double d = nearest(pend); |
| | if (max_dist > 0 && d > max_dist) { |
| | break; |
| | } |
| | } |
| | return wires; |
| | } |
| | }; |
| |
|
| | struct ShapeInfoBuilder |
| | { |
| | std::list<ShapeInfo>& myList; |
| | gp_Trsf& myTrsf; |
| | short& myArcPlane; |
| | bool& myArcPlaneFound; |
| | ShapeParams& myParams; |
| |
|
| | ShapeInfoBuilder( |
| | bool& plane_found, |
| | short& arc_plane, |
| | gp_Trsf& trsf, |
| | std::list<ShapeInfo>& list, |
| | ShapeParams& params |
| | ) |
| | : myList(list) |
| | , myTrsf(trsf) |
| | , myArcPlane(arc_plane) |
| | , myArcPlaneFound(plane_found) |
| | , myParams(params) |
| | {} |
| |
|
| | void operator()(const TopoDS_Shape& shape, int type) |
| | { |
| | gp_Pln pln; |
| | if (!getShapePlane(shape, pln)) { |
| | myList.emplace_back(shape, myParams); |
| | return; |
| | } |
| | myList.emplace_back(pln, shape, myParams); |
| | if (myArcPlaneFound || myArcPlane == Area::ArcPlaneNone |
| | || myArcPlane == Area::ArcPlaneVariable) { |
| | return; |
| | } |
| |
|
| | if (type == TopAbs_EDGE) { |
| | BRepAdaptor_Curve curve(TopoDS::Edge(shape)); |
| | if (curve.GetType() != GeomAbs_Circle) { |
| | return; |
| | } |
| | } |
| | else { |
| | bool found = false; |
| | for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { |
| | BRepAdaptor_Curve curve(TopoDS::Edge(it.Current())); |
| | if (curve.GetType() == GeomAbs_Circle) { |
| | found = true; |
| | break; |
| | } |
| | } |
| | if (!found) { |
| | return; |
| | } |
| | } |
| | gp_Ax3 pos = myList.back().myPln.Position(); |
| | if (!pos.Direct()) { |
| | pos = gp_Ax3(pos.Ax2()); |
| | } |
| | const gp_Dir& dir = pos.Direction(); |
| | gp_Ax3 dstPos; |
| | bool x0 = fabs(dir.X()) <= Precision::Confusion(); |
| | bool y0 = fabs(dir.Y()) <= Precision::Confusion(); |
| | bool z0 = fabs(dir.Z()) <= Precision::Confusion(); |
| | switch (myArcPlane) { |
| | case Area::ArcPlaneAuto: { |
| | if (x0 && y0) { |
| | AREA_TRACE("found arc plane XY"); |
| | myArcPlane = Area::ArcPlaneXY; |
| | } |
| | else if (x0 && z0) { |
| | AREA_TRACE("found arc plane ZX"); |
| | myArcPlane = Area::ArcPlaneZX; |
| | } |
| | else if (z0 && y0) { |
| | AREA_TRACE("found arc plane YZ"); |
| | myArcPlane = Area::ArcPlaneYZ; |
| | } |
| | else { |
| | myArcPlane = Area::ArcPlaneXY; |
| | dstPos = gp_Ax3(pos.Location(), gp_Dir(0, 0, 1)); |
| | break; |
| | } |
| | myArcPlaneFound = true; |
| | return; |
| | } |
| | case Area::ArcPlaneXY: |
| | if (x0 && y0) { |
| | myArcPlaneFound = true; |
| | return; |
| | } |
| | dstPos = gp_Ax3(pos.Location(), gp_Dir(0, 0, 1)); |
| | break; |
| | case Area::ArcPlaneZX: |
| | if (x0 && z0) { |
| | myArcPlaneFound = true; |
| | return; |
| | } |
| | dstPos = gp_Ax3(pos.Location(), gp_Dir(0, 1, 0)); |
| | break; |
| | case Area::ArcPlaneYZ: |
| | if (z0 && y0) { |
| | myArcPlaneFound = true; |
| | return; |
| | } |
| | dstPos = gp_Ax3(pos.Location(), gp_Dir(1, 0, 0)); |
| | break; |
| | default: |
| | return; |
| | } |
| | AREA_WARN("force arc plane " << AREA_XYZ(dir) << " to " << AREA_XYZ(dstPos.Direction())); |
| | myTrsf.SetTransformation(pos); |
| | gp_Trsf trsf; |
| | trsf.SetTransformation(dstPos); |
| | myTrsf.PreMultiply(trsf.Inverted()); |
| | myArcPlaneFound = true; |
| | } |
| | }; |
| |
|
| | struct WireOrienter |
| | { |
| | std::list<TopoDS_Shape>& wires; |
| | const gp_Dir& dir; |
| | short orientation; |
| | short direction; |
| |
|
| | WireOrienter(std::list<TopoDS_Shape>& ws, const gp_Dir& dir, short o, short d) |
| | : wires(ws) |
| | , dir(dir) |
| | , orientation(o) |
| | , direction(d) |
| | {} |
| |
|
| | void operator()(const TopoDS_Shape& shape, int type) |
| | { |
| | if (type == TopAbs_WIRE) { |
| | wires.push_back(shape); |
| | } |
| | else { |
| | wires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(shape)).Wire()); |
| | } |
| |
|
| | TopoDS_Shape& wire = wires.back(); |
| |
|
| | if (BRep_Tool::IsClosed(wire)) { |
| | if (orientation == Area::OrientationReversed) { |
| | wire.Reverse(); |
| | } |
| | } |
| | else if (direction != Area::DirectionNone) { |
| | gp_Pnt p1, p2; |
| | getEndPoints(TopoDS::Wire(wire), p1, p2); |
| | bool reverse = false; |
| | switch (direction) { |
| | case Area::DirectionXPositive: |
| | reverse = p1.X() > p2.X(); |
| | break; |
| | case Area::DirectionXNegative: |
| | reverse = p1.X() < p2.X(); |
| | break; |
| | case Area::DirectionYPositive: |
| | reverse = p1.Y() > p2.Y(); |
| | break; |
| | case Area::DirectionYNegative: |
| | reverse = p1.Y() < p2.Y(); |
| | break; |
| | case Area::DirectionZPositive: |
| | reverse = p1.Z() > p2.Z(); |
| | break; |
| | case Area::DirectionZNegative: |
| | reverse = p1.Z() < p2.Z(); |
| | break; |
| | } |
| | if (reverse) { |
| | wire.Reverse(); |
| | } |
| | } |
| | } |
| | }; |
| |
|
| | typedef Standard_Real (gp_Pnt::*AxisGetter)() const; |
| | typedef void (gp_Pnt::*AxisSetter)(Standard_Real); |
| |
|
| | std::list<TopoDS_Shape> Area::sortWires( |
| | const std::list<TopoDS_Shape>& shapes, |
| | bool has_start, |
| | gp_Pnt* _pstart, |
| | gp_Pnt* _pend, |
| | double* stepdown_hint, |
| | short* _parc_plane, |
| | PARAM_ARGS(PARAM_FARG, AREA_PARAMS_SORT) |
| | ) |
| | { |
| | std::list<TopoDS_Shape> wires; |
| |
|
| | if (shapes.empty()) { |
| | return wires; |
| | } |
| |
|
| | AxisGetter getter; |
| | AxisSetter setter; |
| | switch (retract_axis) { |
| | case RetractAxisX: |
| | getter = &gp_Pnt::X; |
| | setter = &gp_Pnt::SetX; |
| | break; |
| | case RetractAxisY: |
| | getter = &gp_Pnt::Y; |
| | setter = &gp_Pnt::SetY; |
| | break; |
| | default: |
| | getter = &gp_Pnt::Z; |
| | setter = &gp_Pnt::SetZ; |
| | } |
| |
|
| | if (sort_mode == SortModeGreedy && threshold < Precision::Confusion()) { |
| | AREA_WARN("Sort mode 'Greedy' requires a threshold value (suggestion: use the tool diameter)"); |
| | sort_mode = SortMode2D5; |
| | } |
| |
|
| | short _arc_plane = ArcPlaneNone; |
| | short& arc_plane = _parc_plane ? *_parc_plane : _arc_plane; |
| |
|
| | if (sort_mode == SortModeNone) { |
| | gp_Dir dir; |
| | switch (arc_plane) { |
| | case ArcPlaneYZ: |
| | dir = gp_Dir(1, 0, 0); |
| | break; |
| | case ArcPlaneZX: |
| | dir = gp_Dir(0, 1, 0); |
| | break; |
| | default: |
| | if (arc_plane != ArcPlaneXY) { |
| | AREA_WARN("Sort mode 'None' without a given arc plane, using XY plane"); |
| | } |
| | arc_plane = ArcPlaneXY; |
| | dir = gp_Dir(0, 0, 1); |
| | break; |
| | } |
| | for (auto& shape : shapes) { |
| | if (!shape.IsNull()) { |
| | foreachSubshape(shape, WireOrienter(wires, dir, orientation, direction), TopAbs_WIRE); |
| | } |
| | } |
| | return wires; |
| | } |
| |
|
| | ShapeParams rparams(abscissa, nearest_k > 0 ? nearest_k : 1, orientation, direction); |
| | std::list<ShapeInfo> shape_list; |
| |
|
| | FC_TIME_INIT2(t, t1); |
| |
|
| | gp_Trsf trsf; |
| | bool arcPlaneFound = false; |
| |
|
| | if (sort_mode == SortMode3D) { |
| | TopoDS_Builder builder; |
| | TopoDS_Compound comp; |
| | builder.MakeCompound(comp); |
| | for (auto& shape : shapes) { |
| | if (!shape.IsNull()) { |
| | builder.Add(comp, shape); |
| | } |
| | } |
| | TopExp_Explorer xp(comp, TopAbs_EDGE); |
| | if (xp.More()) { |
| | shape_list.emplace_back(comp, rparams); |
| | } |
| | } |
| | else { |
| | |
| | for (auto& shape : shapes) { |
| | |
| | if (!shape.IsNull()) { |
| | foreachSubshape( |
| | shape, |
| | ShapeInfoBuilder(arcPlaneFound, arc_plane, trsf, shape_list, rparams), |
| | TopAbs_FACE, |
| | true |
| | ); |
| | } |
| | } |
| | FC_TIME_LOG(t1, "plane finding"); |
| | } |
| |
|
| | if (shape_list.empty()) { |
| | return wires; |
| | } |
| |
|
| | Bnd_Box bounds; |
| | gp_Pnt pstart, pend; |
| | if (_pstart) { |
| | pstart = *_pstart; |
| | } |
| | bool use_bound = !has_start || !_pstart; |
| |
|
| | |
| |
|
| | for (auto& info : shape_list) { |
| | if (arcPlaneFound) { |
| | info.myShape.Move(trsf); |
| | if (info.myPlanar) { |
| | info.myPln.Transform(trsf); |
| | } |
| | } |
| |
|
| | BRepBndLib::Add(info.myShape, bounds, Standard_False); |
| | } |
| |
|
| | if (use_bound || sort_mode == SortMode2D5 || sort_mode == SortModeGreedy) { |
| |
|
| | for (auto itNext = shape_list.begin(), it = itNext; it != shape_list.end(); it = itNext) { |
| | ++itNext; |
| | 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; |
| | } |
| | } |
| | FC_TIME_LOG(t, "plane merging"); |
| | } |
| |
|
| | bounds.SetGap(0.0); |
| | Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; |
| | bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); |
| | AREA_TRACE( |
| | "bound (" << xMin << ", " << xMax << "), (" << yMin << ", " << yMax << "), (" << zMin |
| | << ", " << zMax << ')' |
| | ); |
| |
|
| | if (use_bound) { |
| | pstart.SetCoord(xMax, yMax, zMax); |
| | if (_pstart) { |
| | *_pstart = pstart; |
| | } |
| | } |
| | else { |
| | switch (retract_axis) { |
| | case RetractAxisX: |
| | if (pstart.X() < xMax) { |
| | pstart.SetX(xMax); |
| | } |
| | break; |
| | case RetractAxisY: |
| | if (pstart.Y() < yMax) { |
| | pstart.SetY(yMax); |
| | } |
| | break; |
| | default: |
| | if (pstart.Z() < zMax) { |
| | pstart.SetZ(zMax); |
| | } |
| | } |
| | if (_pstart) { |
| | *_pstart = pstart; |
| | } |
| | } |
| |
|
| |
|
| | gp_Pln pln; |
| | double hint = 0.0; |
| | bool hint_first = true; |
| | auto current_it = shape_list.end(); |
| | double current_height = (pstart.*getter)(); |
| | double max_dist = sort_mode == SortModeGreedy ? threshold * threshold : 0; |
| | while (!shape_list.empty()) { |
| | AREA_TRACE("sorting " << shape_list.size() << ' ' << AREA_XYZ(pstart)); |
| | double best_d = std::numeric_limits<double>::max(); |
| | auto best_it = shape_list.begin(); |
| | for (auto it = best_it; it != shape_list.end(); ++it) { |
| | double d; |
| | gp_Pnt pt; |
| | if (it->myPlanar && current_it == shape_list.end()) { |
| | d = it->myPln.SquareDistance(pstart); |
| | } |
| | else { |
| | d = it->nearest(pstart); |
| | } |
| | if (d < best_d) { |
| | best_it = it; |
| | best_d = d; |
| | } |
| | } |
| | gp_Pnt pentry; |
| | if (sort_mode == SortModeGreedy) { |
| | |
| | |
| | |
| | if (current_it == shape_list.end()) { |
| | current_it = best_it; |
| | current_height = (pstart.*getter)(); |
| | } |
| | else if (best_d > max_dist) { |
| | |
| | |
| | (pstart.*setter)(current_height); |
| | current_it->nearest(pstart); |
| | best_it = current_it; |
| | } |
| | } |
| |
|
| | wires.splice(wires.end(), best_it->sortWires(pstart, pend, min_dist, max_dist, &pentry)); |
| |
|
| | if (use_bound && _pstart) { |
| | use_bound = false; |
| | *_pstart = pentry; |
| | } |
| | if ((sort_mode == SortMode2D5 || sort_mode == SortMode3D) && stepdown_hint) { |
| | if (!best_it->myPlanar) { |
| | hint_first = true; |
| | } |
| | else if (hint_first) { |
| | hint_first = false; |
| | } |
| | else { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const gp_Pnt& P = pln.Position().Location(); |
| | const gp_Pnt& loc = best_it->myPln.Position().Location(); |
| | const gp_Dir& dir = best_it->myPln.Position().Direction(); |
| | double d |
| | = (dir.X() * (P.X() - loc.X()) + dir.Y() * (P.Y() - loc.Y()) |
| | + dir.Z() * (P.Z() - loc.Z())); |
| | if (d < 0) { |
| | d = -d; |
| | } |
| | if (d > hint) { |
| | hint = d; |
| | } |
| | } |
| | pln = best_it->myPln; |
| | } |
| | pstart = pend; |
| |
|
| | if (best_it->myWires.empty()) { |
| | if (current_it == best_it) { |
| | current_it = shape_list.end(); |
| | } |
| | shape_list.erase(best_it); |
| | } |
| | } |
| | if (stepdown_hint && hint != 0.0) { |
| | *stepdown_hint = hint; |
| | } |
| | if (_pend) { |
| | *_pend = pend; |
| | } |
| | FC_DURATION_LOG(rparams.bd, "rtree build"); |
| | FC_DURATION_LOG(rparams.qd, "rtree query"); |
| | FC_DURATION_LOG(rparams.rd, "rtree clean"); |
| | FC_DURATION_LOG(rparams.xd, "BRepExtrema"); |
| | FC_TIME_LOG(t, "sortWires total"); |
| | return wires; |
| | } |
| |
|
| | static inline void addParameter( |
| | bool verbose, |
| | Command& cmd, |
| | const char* name, |
| | double last, |
| | double next, |
| | bool relative = false |
| | ) |
| | { |
| | double d = next - last; |
| | if (verbose || fabs(d) > Precision::Confusion()) { |
| | cmd.Parameters[name] = relative ? d : next; |
| | } |
| | } |
| |
|
| | static inline void addGCode( |
| | bool verbose, |
| | Toolpath& path, |
| | const gp_Pnt& last, |
| | const gp_Pnt& next, |
| | const char* name |
| | ) |
| | { |
| | Command cmd; |
| | cmd.Name = name; |
| | addParameter(verbose, cmd, "X", last.X(), next.X()); |
| | addParameter(verbose, cmd, "Y", last.Y(), next.Y()); |
| | addParameter(verbose, cmd, "Z", last.Z(), next.Z()); |
| | path.addCommand(cmd); |
| | return; |
| | } |
| |
|
| | static inline void addG1( |
| | bool verbose, |
| | Toolpath& path, |
| | const gp_Pnt& last, |
| | const gp_Pnt& next, |
| | double f, |
| | double& last_f |
| | ) |
| | { |
| | addGCode(verbose, path, last, next, "G1"); |
| | if (f > Precision::Confusion()) { |
| | Command* cmd = path.getCommands().back(); |
| | addParameter(verbose, *cmd, "F", last_f, f); |
| | last_f = f; |
| | } |
| | return; |
| | } |
| |
|
| | static void addG0( |
| | bool verbose, |
| | Toolpath& path, |
| | gp_Pnt last, |
| | const gp_Pnt& next, |
| | AxisSetter setter, |
| | double height |
| | ) |
| | { |
| | gp_Pnt pt(next); |
| | (pt.*setter)(height); |
| | if (!last.IsEqual(pt, Precision::Confusion())) { |
| | addGCode(verbose, path, last, pt, "G0"); |
| | } |
| | } |
| |
|
| | static void addGArc( |
| | bool verbose, |
| | bool abs_center, |
| | Toolpath& path, |
| | const gp_Pnt& pstart, |
| | const gp_Pnt& pend, |
| | const gp_Pnt& center, |
| | bool clockwise, |
| | double f, |
| | double& last_f |
| | ) |
| | { |
| | Command cmd; |
| | cmd.Name = clockwise ? "G2" : "G3"; |
| | if (abs_center) { |
| | addParameter(verbose, cmd, "I", 0.0, center.X()); |
| | addParameter(verbose, cmd, "J", 0.0, center.Y()); |
| | addParameter(verbose, cmd, "K", 0.0, center.Z()); |
| | } |
| | else { |
| | addParameter(verbose, cmd, "I", pstart.X(), center.X(), true); |
| | addParameter(verbose, cmd, "J", pstart.Y(), center.Y(), true); |
| | addParameter(verbose, cmd, "K", pstart.Z(), center.Z(), true); |
| | } |
| | addParameter(verbose, cmd, "X", pstart.X(), pend.X()); |
| | addParameter(verbose, cmd, "Y", pstart.Y(), pend.Y()); |
| | addParameter(verbose, cmd, "Z", pstart.Z(), pend.Z()); |
| | if (f > Precision::Confusion()) { |
| | addParameter(verbose, cmd, "F", last_f, f); |
| | last_f = f; |
| | } |
| | path.addCommand(cmd); |
| | } |
| |
|
| | static inline void addGCode(Toolpath& path, const char* name) |
| | { |
| | Command cmd; |
| | cmd.Name = name; |
| | path.addCommand(cmd); |
| | } |
| |
|
| | void Area::setWireOrientation(TopoDS_Wire& wire, const gp_Dir& dir, bool wire_ccw) |
| | { |
| | |
| | BRepBuilderAPI_MakeFace mkFace(wire, Standard_True); |
| | if (!mkFace.IsDone()) { |
| | AREA_WARN("setWireOrientation: failed to make test face"); |
| | return; |
| | } |
| | TopoDS_Face tmpFace = mkFace.Face(); |
| | |
| | BRepAdaptor_Surface surf(tmpFace); |
| | bool ccw = surf.Plane().Axis().Direction().Dot(dir) > 0; |
| |
|
| | |
| | |
| | TopoDS_Iterator it(tmpFace, Standard_False); |
| | ccw ^= it.Value().Orientation() != wire.Orientation(); |
| |
|
| | if (ccw != wire_ccw) { |
| | wire.Reverse(); |
| | } |
| | } |
| |
|
| | void Area::toPath( |
| | Toolpath& path, |
| | const std::list<TopoDS_Shape>& shapes, |
| | const gp_Pnt* _pstart, |
| | gp_Pnt* pend, |
| | PARAM_ARGS(PARAM_FARG, AREA_PARAMS_PATH) |
| | ) |
| | { |
| | std::list<TopoDS_Shape> wires; |
| |
|
| | gp_Pnt pstart; |
| | if (_pstart) { |
| | pstart = *_pstart; |
| | } |
| |
|
| | double stepdown_hint = 1.0; |
| | wires = sortWires( |
| | shapes, |
| | _pstart != nullptr, |
| | &pstart, |
| | pend, |
| | &stepdown_hint, |
| | PARAM_REF(PARAM_FARG, AREA_PARAMS_ARC_PLANE), |
| | PARAM_FIELDS(PARAM_FARG, AREA_PARAMS_SORT) |
| | ); |
| |
|
| | if (wires.empty()) { |
| | return; |
| | } |
| |
|
| | short currentArcPlane = arc_plane; |
| | if (preamble) { |
| | |
| | addGCode(path, "G90"); |
| | if (abs_center) { |
| | addGCode(path, "G90.1"); |
| | } |
| |
|
| | if (arc_plane == ArcPlaneZX) { |
| | addGCode(path, "G18"); |
| | } |
| | else if (arc_plane == ArcPlaneYZ) { |
| | addGCode(path, "G19"); |
| | } |
| | else { |
| | currentArcPlane = ArcPlaneXY; |
| | addGCode(path, "G17"); |
| | } |
| | } |
| |
|
| | AxisGetter getter; |
| | AxisSetter setter; |
| | switch (retract_axis) { |
| | case RetractAxisX: |
| | getter = &gp_Pnt::X; |
| | setter = &gp_Pnt::SetX; |
| | break; |
| | case RetractAxisY: |
| | getter = &gp_Pnt::Y; |
| | setter = &gp_Pnt::SetY; |
| | break; |
| | default: |
| | getter = &gp_Pnt::Z; |
| | setter = &gp_Pnt::SetZ; |
| | } |
| |
|
| | threshold = fabs(threshold); |
| | if (threshold < Precision::Confusion()) { |
| | threshold = Precision::Confusion(); |
| | } |
| | threshold *= threshold; |
| |
|
| | |
| | |
| | if (!_pstart || pstart.SquareDistance(*_pstart) > Precision::SquareConfusion()) { |
| | (pstart.*setter)(resume_height); |
| | } |
| |
|
| | gp_Pnt plast, p; |
| | |
| | (p.*setter)(std::max(retraction, (pstart.*getter)())); |
| | addGCode(false, path, plast, p, "G0"); |
| | plast = p; |
| | p = pstart; |
| |
|
| | |
| | gp_Pnt tmpPlast = plast; |
| | (tmpPlast.*setter)((p.*getter)()); |
| | if (_pstart && p.IsEqual(tmpPlast, Precision::Confusion())) { |
| | plast.SetCoord(10.0, 10.0, 10.0); |
| | (plast.*setter)(retraction); |
| | } |
| | (p.*setter)(retraction); |
| | addGCode(false, path, plast, p, "G0"); |
| |
|
| |
|
| | plast = p; |
| | bool first = true; |
| | bool arcWarned = false; |
| | double cur_f = 0.0; |
| | double nf = fabs(feedrate); |
| | double vf = fabs(feedrate_v); |
| | if (vf < Precision::Confusion()) { |
| | vf = nf; |
| | } |
| |
|
| | for (const TopoDS_Shape& wire : wires) { |
| |
|
| | BRepTools_WireExplorer xp(TopoDS::Wire(wire)); |
| | p = BRep_Tool::Pnt(xp.CurrentVertex()); |
| |
|
| | gp_Pnt pTmp(p), plastTmp(plast); |
| | |
| | |
| | |
| | |
| | (pTmp.*setter)(0.0); |
| | (plastTmp.*setter)(0.0); |
| |
|
| | if (first) { |
| | |
| | addG0(false, path, plast, p, setter, retraction); |
| | |
| | addG0(false, path, plast, p, setter, resume_height); |
| | } |
| | else if (pTmp.SquareDistance(plastTmp) > threshold) { |
| | |
| | addG0(false, path, plast, plast, setter, retraction); |
| | |
| | addG0(false, path, plast, p, setter, retraction); |
| | |
| | addG0(false, path, plast, p, setter, resume_height); |
| | } |
| | addG1(verbose, path, plast, p, vf, cur_f); |
| | plast = p; |
| | first = false; |
| | for (; xp.More(); xp.Next(), plast = p) { |
| | const auto& edge = xp.Current(); |
| | BRepAdaptor_Curve curve(edge); |
| | bool reversed = (edge.Orientation() == TopAbs_REVERSED); |
| | p = curve.Value(reversed ? curve.FirstParameter() : curve.LastParameter()); |
| |
|
| | 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)); |
| | addG1(verbose, path, plast, pt, nf, cur_f); |
| | plast = pt; |
| | } |
| | } |
| | else { |
| | for (int i = 2; i <= nbPoints; i++) { |
| | gp_Pnt pt = curve.Value(discretizer.Parameter(i)); |
| | addG1(verbose, path, plast, pt, nf, cur_f); |
| | plast = pt; |
| | } |
| | } |
| | break; |
| | } |
| | } |
| | addG1(verbose, path, plast, p, nf, cur_f); |
| | break; |
| | } |
| | case GeomAbs_Circle: { |
| | const gp_Circ& circle = curve.Circle(); |
| | const gp_Dir& dir = circle.Axis().Direction(); |
| | short arcPlane = ArcPlaneNone; |
| | bool clockwise; |
| | const char* cmd; |
| | if (fabs(dir.X()) < Precision::Confusion() |
| | && fabs(dir.Y()) < Precision::Confusion()) { |
| | clockwise = dir.Z() < 0; |
| | arcPlane = ArcPlaneXY; |
| | cmd = "G17"; |
| | } |
| | else if (fabs(dir.Z()) < Precision::Confusion() |
| | && fabs(dir.X()) < Precision::Confusion()) { |
| | clockwise = dir.Y() < 0; |
| | arcPlane = ArcPlaneZX; |
| | cmd = "G18"; |
| | } |
| | else if (fabs(dir.Y()) < Precision::Confusion() |
| | && fabs(dir.Z()) < Precision::Confusion()) { |
| | clockwise = dir.X() < 0; |
| | arcPlane = ArcPlaneYZ; |
| | cmd = "G19"; |
| | } |
| |
|
| | if (arcPlane != ArcPlaneNone |
| | && (arcPlane == currentArcPlane || arc_plane == ArcPlaneVariable)) { |
| | if (arcPlane != currentArcPlane) { |
| | addGCode(path, cmd); |
| | } |
| |
|
| | if (reversed) { |
| | clockwise = !clockwise; |
| | } |
| | gp_Pnt center = circle.Location(); |
| |
|
| | double first = curve.FirstParameter(); |
| | double last = curve.LastParameter(); |
| | if (segmentation > Precision::Confusion()) { |
| | GCPnts_UniformAbscissa discretizer(curve, segmentation, first, last); |
| | 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)); |
| | addGArc( |
| | verbose, |
| | abs_center, |
| | path, |
| | plast, |
| | pt, |
| | center, |
| | clockwise, |
| | nf, |
| | cur_f |
| | ); |
| | plast = pt; |
| | } |
| | } |
| | else { |
| | for (int i = 2; i <= nbPoints; i++) { |
| | gp_Pnt pt = curve.Value(discretizer.Parameter(i)); |
| | addGArc( |
| | verbose, |
| | abs_center, |
| | path, |
| | plast, |
| | pt, |
| | center, |
| | clockwise, |
| | nf, |
| | cur_f |
| | ); |
| | plast = pt; |
| | } |
| | } |
| | break; |
| | } |
| | } |
| |
|
| | if (fabs(first - last) > std::numbers::pi) { |
| | |
| | gp_Pnt mid = curve.Value((last - first) * 0.5 + first); |
| | addGArc(verbose, abs_center, path, plast, mid, center, clockwise, nf, cur_f); |
| | plast = mid; |
| | } |
| | addGArc(verbose, abs_center, path, plast, p, center, clockwise, nf, cur_f); |
| | break; |
| | } |
| |
|
| | if (!arcWarned) { |
| | arcWarned = true; |
| | AREA_WARN("arc plane not aligned, force discretization"); |
| | } |
| | AREA_TRACE("arc discretize " << AREA_XYZ(dir)); |
| | } |
| | |
| | default: { |
| | const auto& pts = discretize(edge, deflection); |
| | for (size_t i = 1; i < pts.size(); ++i) { |
| | auto& pt = pts[i]; |
| | addG1(verbose, path, plast, pt, nf, cur_f); |
| | plast = pt; |
| | } |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | void Area::abort(bool aborting) |
| | { |
| | s_aborting = aborting; |
| | } |
| |
|
| | bool Area::aborting() |
| | { |
| | return s_aborting; |
| | } |
| |
|
| | AreaStaticParams::AreaStaticParams() |
| | {} |
| |
|
| | AreaStaticParams Area::s_params; |
| |
|
| | void Area::setDefaultParams(const AreaStaticParams& params) |
| | { |
| | s_params = params; |
| | } |
| |
|
| | const AreaStaticParams& Area::getDefaultParams() |
| | { |
| | return s_params; |
| | } |
| |
|
| | #if defined(__clang__) |
| | # pragma clang diagnostic pop |
| | #endif |
| |
|