Path.Area: fixed non-circular curve handling, etc.

* Fixed non-circular curve orientation handling

* Section changed to use Part::CrossSection, because it seems
BRepAlgoAPI_Section has trouble with non-circular curves (LastParameter
becomes huge which causes discretization to produce many many points)

* Exposed Area.makeSections() to section with variable heights

* Modified Area.setPlane() to accept non-planar shape

* Exposed Area.getPlane() to obtain current workplane

* Exposed Area.Shapes attribute to return the current holding children
shape.
This commit is contained in:
Zheng, Lei 2017-01-29 18:24:01 +08:00
parent 41c7827287
commit 228a0dc905
6 changed files with 564 additions and 229 deletions

View File

@ -24,6 +24,7 @@
#ifndef _PreComp_
#endif
#include "boost/date_time/posix_time/posix_time.hpp"
#include <boost/range/adaptor/reversed.hpp>
#include <BRepLib.hxx>
@ -52,22 +53,27 @@
#include <gp_Circ.hxx>
#include <gp_GTrsf.hxx>
#include <Standard_Version.hxx>
#include <GCPnts_UniformDeflection.hxx>
#include <GCPnts_QuasiUniformDeflection.hxx>
#include <BRepBndLib.hxx>
#include <BRepLib_MakeFace.hxx>
#include <Bnd_Box.hxx>
#include <BRepAlgoAPI_Section.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <Base/Exception.h>
#include <Base/Tools.h>
#include <Base/Console.h>
#include <App/Application.h>
#include <App/Document.h>
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Part/App/PartFeature.h>
#include <Mod/Part/App/FaceMakerBullseye.h>
#include <Mod/Part/App/CrossSection.h>
#include "Area.h"
#include "../libarea/Area.h"
using namespace Path;
using namespace boost::posix_time;
CAreaParams::CAreaParams()
:PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA)
@ -78,17 +84,17 @@ AreaParams::AreaParams()
{}
CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs)
:params(p)
{
// Arc fitting is lossy. we shall reduce the number of unecessary fit
if(noFitArcs)
params.FitArcs=false;
#define AREA_CONF_SAVE_AND_APPLY(_param) \
PARAM_FNAME(_param) = BOOST_PP_CAT(CArea::get_,PARAM_FARG(_param))();\
BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(params.PARAM_FNAME(_param));
BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(p.PARAM_FNAME(_param));
PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY,AREA_PARAMS_CAREA)
// Arc fitting is lossy. we shall reduce the number of unecessary fit
if(noFitArcs)
CArea::set_fit_arcs(false);
}
CAreaConfig::~CAreaConfig() {
@ -117,15 +123,16 @@ Area::Area(const Area &other, bool deep_copy)
,myShapes(other.myShapes)
,myTrsf(other.myTrsf)
,myParams(other.myParams)
,myShapePlane(other.myShapePlane)
,myWorkPlane(other.myWorkPlane)
,myHaveFace(other.myHaveFace)
,myHaveSolid(other.myHaveSolid)
,myShapeDone(false)
{
if(!deep_copy) return;
if(!deep_copy || !other.isBuilt())
return;
if(other.myArea)
myArea.reset(new CArea(*other.myArea));
myShapePlane = other.myShapePlane;
myShape = other.myShape;
myShapeDone = other.myShapeDone;
mySections.reserve(other.mySections.size());
@ -142,16 +149,18 @@ void Area::setPlane(const TopoDS_Shape &shape) {
myWorkPlane.Nullify();
return;
}
BRepLib_FindSurface planeFinder(shape,-1,Standard_True);
if (!planeFinder.Found())
TopoDS_Shape plane;
gp_Trsf trsf;
findPlane(shape,plane,trsf);
if (plane.IsNull())
throw Base::ValueError("shape is not planar");
myWorkPlane = shape;
myTrsf.SetTransformation(GeomAdaptor_Surface(
planeFinder.Surface()).Plane().Position());
myWorkPlane = plane;
myTrsf = trsf;
clean();
}
bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) {
if(s1.IsNull() || s2.IsNull()) return false;
if(s1.IsEqual(s2)) return true;
TopoDS_Builder builder;
TopoDS_Compound comp;
@ -234,7 +243,8 @@ void Area::add(CArea &area, const TopoDS_Wire& wire,
ccurve.append(CVertex(Point(p.X(),p.Y())));
for (;xp.More();xp.Next()) {
BRepAdaptor_Curve curve(xp.Current());
const TopoDS_Edge &edge = TopoDS::Edge(xp.Current());
BRepAdaptor_Curve curve(edge);
bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED);
p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter());
@ -270,18 +280,31 @@ void Area::add(CArea &area, const TopoDS_Wire& wire,
//fall through
} default: {
// Discretize all other type of curves
GCPnts_UniformDeflection discretizer(curve, deflection,
GCPnts_QuasiUniformDeflection discretizer(curve, deflection,
curve.FirstParameter(), curve.LastParameter());
if (discretizer.IsDone () && discretizer.NbPoints () > 0) {
if (discretizer.IsDone () && discretizer.NbPoints () > 1) {
int nbPoints = discretizer.NbPoints ();
for (int i=1; i<=nbPoints; i++) {
gp_Pnt pt = discretizer.Value (i);
ccurve.append(CVertex(Point(pt.X(),pt.Y())));
if(to_edges) {
area.append(ccurve);
ccurve.m_vertices.pop_front();
//strangly OCC discretizer points are one-based, not zero-based, why?
if(reversed) {
for (int i=nbPoints-1; i>=1; --i) {
gp_Pnt pt = discretizer.Value (i);
ccurve.append(CVertex(Point(pt.X(),pt.Y())));
if(to_edges) {
area.append(ccurve);
ccurve.m_vertices.pop_front();
}
}
}else{
for (int i=2; i<=nbPoints; i++) {
gp_Pnt pt = discretizer.Value (i);
ccurve.append(CVertex(Point(pt.X(),pt.Y())));
if(to_edges) {
area.append(ccurve);
ccurve.m_vertices.pop_front();
}
}
}
}else
Standard_Failure::Raise("Curve discretization failed");
}}
@ -371,13 +394,10 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) {
TopExp_Explorer it(shape, TopAbs_FACE);
myHaveFace = it.More();
}
const TopoDS_Shape *plane;
if(myParams.Coplanar == CoplanarNone)
plane = NULL;
else
plane = myWorkPlane.IsNull()?&myShapePlane:&myWorkPlane;
TopoDS_Shape plane = getPlane();
CArea areaOpen;
mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane,
mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,
myParams.Coplanar==CoplanarNone?NULL:&plane,
myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen,
myParams.OpenMode==OpenModeEdges,myParams.Reorient);
if(areaOpen.m_curves.size()) {
@ -392,8 +412,272 @@ namespace Part {
extern PartExport std::list<TopoDS_Edge> sort_Edges(double tol3d, std::list<TopoDS_Edge>& edges);
}
void Area::explode(const TopoDS_Shape &shape) {
const TopoDS_Shape &plane = getPlane();
bool haveShape = false;
for(TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) {
haveShape = true;
if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){
++mySkippedShapes;
if(myParams.Coplanar == CoplanarForce)
continue;
}
for(TopExp_Explorer itw(it.Current(), TopAbs_WIRE); itw.More(); itw.Next()) {
for(BRepTools_WireExplorer xp(TopoDS::Wire(itw.Current()));xp.More();xp.Next())
add(*myArea,BRepBuilderAPI_MakeWire(
TopoDS::Edge(xp.Current())).Wire(),&myTrsf,myParams.Deflection,true);
}
}
if(haveShape) return;
for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) {
if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){
++mySkippedShapes;
if(myParams.Coplanar == CoplanarForce)
continue;
}
add(*myArea,BRepBuilderAPI_MakeWire(
TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true);
}
}
#if 0
static void show(const TopoDS_Shape &shape, const char *name) {
App::Document *pcDoc = App::GetApplication().getActiveDocument();
if (!pcDoc)
pcDoc = App::GetApplication().newDocument();
Part::Feature *pcFeature = (Part::Feature *)pcDoc->addObject("Part::Feature", name);
// copy the data
//TopoShape* shape = new MeshObject(*pShape->getTopoShapeObjectPtr());
pcFeature->Shape.setValue(shape);
//pcDoc->recompute();
}
#endif
bool Area::findPlane(const TopoDS_Shape &shape,
TopoDS_Shape &plane, gp_Trsf &trsf)
{
return findPlane(shape,TopAbs_FACE,plane,trsf) ||
findPlane(shape,TopAbs_WIRE,plane,trsf) ||
findPlane(shape,TopAbs_EDGE,plane,trsf);
}
bool Area::findPlane(const TopoDS_Shape &shape, int type,
TopoDS_Shape &dst, gp_Trsf &dst_trsf)
{
bool haveShape = false;
double top_z;
bool top_found = false;
gp_Trsf trsf;
for(TopExp_Explorer it(shape,(TopAbs_ShapeEnum)type); it.More(); it.Next()) {
haveShape = true;
const TopoDS_Shape &plane = it.Current();
BRepLib_FindSurface planeFinder(plane,-1,Standard_True);
if (!planeFinder.Found())
continue;
gp_Ax3 pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();
gp_Dir dir(pos.Direction());
trsf.SetTransformation(pos);
//pos.Location().Z() is always 0, why? As a walk around, use the first vertex Z
gp_Pnt origin = pos.Location();
for(TopExp_Explorer it(plane.Moved(trsf),TopAbs_VERTEX);it.More();) {
origin.SetZ(BRep_Tool::Pnt(TopoDS::Vertex(it.Current())).Z());
break;
}
if(fabs(dir.X())<Precision::Confusion() &&
fabs(dir.Y())<Precision::Confusion())
{
if(top_found && top_z > origin.Z())
continue;
top_found = true;
top_z = origin.Z();
}else if(!dst.IsNull())
continue;
dst = plane;
//Some how the plane returned by BRepLib_FindSurface has Z always set to 0.
//We need to manually translate Z to its actual value
gp_Trsf trsf2;
trsf2.SetTranslationPart(gp_XYZ(0,0,-origin.Z()));
dst_trsf = trsf.Multiplied(trsf2);
}
return haveShape;
}
std::vector<shared_ptr<Area> > Area::makeSections(
PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA),
const std::vector<double> &_heights,
const TopoDS_Shape &_plane)
{
TopoDS_Shape plane;
gp_Trsf trsf;
if(!_plane.IsNull())
findPlane(_plane,plane,trsf);
else
plane = getPlane(&trsf);
if(plane.IsNull())
throw Base::ValueError("failed to obtain section plane");
TopLoc_Location loc(trsf);
Bnd_Box bounds;
for(const Shape &s : myShapes) {
const TopoDS_Shape &shape = s.shape.Moved(loc);
BRepBndLib::Add(shape, bounds, Standard_False);
}
bounds.SetGap(0.0);
Standard_Real xMin, yMin, zMin, xMax, yMax, zMax;
bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax);
bool hit_bottom = false;
std::vector<double> heights;
if(_heights.empty()) {
if(mode != SectionModeAbsolute && myParams.SectionOffset<0)
throw Base::ValueError("only positive section offset is allowed in non-absolute mode");
if(myParams.SectionCount>1 && myParams.Stepdown<Precision::Confusion())
throw Base::ValueError("invalid stepdown");
if(mode == SectionModeBoundBox)
zMax -= myParams.SectionOffset;
else if(mode == SectionModeWorkplane)
zMax = -myParams.SectionOffset;
else {
gp_Pnt pt(0,0,myParams.SectionOffset);
double z = pt.Transformed(loc).Z();
if(z < zMax)
zMax = z;
}
if(zMax <= zMin)
throw Base::ValueError("section offset too big");
int count = myParams.SectionCount;
if(count<0 || count*myParams.Stepdown > zMax-zMin) {
count = ceil((zMax-zMin)/myParams.Stepdown);
if((count-1)*myParams.Stepdown < zMax-zMin)
++count;
}
heights.reserve(count);
for(int i=0;i<count;++i,zMax-=myParams.Stepdown) {
if(zMax < zMin) {
hit_bottom = true;
break;
}
heights.push_back(zMax);
}
}else{
heights.reserve(_heights.size());
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((zMin-z)>Precision::Confusion()) {
hit_bottom = true;
continue;
}else if ((z-zMax)>Precision::Confusion())
continue;
heights.push_back(z);
}
}
if(hit_bottom)
heights.push_back(zMin);
else if(heights.empty())
heights.push_back(zMax);
std::vector<shared_ptr<Area> > sections;
sections.reserve(heights.size());
for(double z : heights) {
gp_Pln pln(gp_Pnt(0,0,z),gp_Dir(0,0,1));
Standard_Real a,b,c,d;
pln.Coefficients(a,b,c,d);
BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax);
const TopoDS_Shape &face = mkFace.Face();
shared_ptr<Area> area(new Area(&myParams));
area->setPlane(face);
for(const Shape &s : myShapes) {
BRep_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
for(TopExp_Explorer it(s.shape.Moved(loc), TopAbs_SOLID); it.More(); it.Next()) {
Part::CrossSection section(a,b,c,it.Current());
std::list<TopoDS_Wire> wires = section.slice(-d);
if(wires.empty()) {
Base::Console().Warning("Section return no wires\n");
continue;
}
Part::FaceMakerBullseye mkFace;
mkFace.setPlane(pln);
for(const TopoDS_Wire &wire : wires)
mkFace.addWire(wire);
try {
mkFace.Build();
if (mkFace.Shape().IsNull())
Base::Console().Warning("FaceMakerBullseye return null shape on section\n");
else {
builder.Add(comp,mkFace.Shape());
continue;
}
}catch (Base::Exception &e){
Base::Console().Warning("FaceMakerBullseye failed on section: %s\n", e.what());
}
for(const TopoDS_Wire &wire : wires)
builder.Add(comp,wire);
}
// Make sure the compound has at least one edge
for(TopExp_Explorer it(comp,TopAbs_EDGE);it.More();) {
area->add(comp,s.op);
break;
}
}
if(area->myShapes.size())
sections.push_back(area);
else
Base::Console().Warning("Discard empty section\n");
}
return std::move(sections);
}
TopoDS_Shape Area::getPlane(gp_Trsf *trsf) {
if(!myWorkPlane.IsNull()) {
if(trsf) *trsf = myTrsf;
return myWorkPlane;
}
if(!isBuilt()) {
myShapePlane.Nullify();
for(const Shape &s : myShapes)
findPlane(s.shape,myShapePlane,myTrsf);
if(myShapePlane.IsNull())
throw Base::ValueError("shapes are not planar");
}
if(trsf) *trsf = myTrsf;
return myShapePlane;
}
bool Area::isBuilt() const {
return (myArea || mySections.size());
}
void Area::build() {
if(myArea || mySections.size()) return;
if(isBuilt()) return;
if(myShapes.empty())
throw Base::ValueError("no shape added");
@ -401,144 +685,13 @@ void Area::build() {
#define AREA_SRC(_param) myParams.PARAM_FNAME(_param)
PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL);
if(myWorkPlane.IsNull()) {
myShapePlane.Nullify();
for(const Shape &s : myShapes) {
bool haveShape = false;
bool done = false;
TopoDS_Shape shapePlane;
gp_Trsf trsf;
gp_Ax3 pos;
#define AREA_CHECK_PLANE(_type) \
shapePlane.Nullify();\
for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\
haveShape = true;\
BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\
if (!planeFinder.Found())\
continue;\
shapePlane = it.Current();\
pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();\
trsf.SetTransformation(pos);\
gp_Dir dir(pos.Direction());\
if(fabs(dir.X())<Precision::Confusion() &&\
fabs(dir.Y())<Precision::Confusion()) {\
myShapePlane = shapePlane;\
myTrsf = trsf;\
done = true;\
break;\
}\
if(myShapePlane.IsNull()) {\
myShapePlane = shapePlane;\
myTrsf = trsf;\
}\
}\
if(done) break;\
if(haveShape) continue;
//Try to find a plane by iterating through shapes, prefer plane paralell with XY0
AREA_CHECK_PLANE(FACE)
AREA_CHECK_PLANE(WIRE)
AREA_CHECK_PLANE(EDGE)
}
if(myShapePlane.IsNull())
throw Base::ValueError("shapes are not planar");
}
if(myHaveSolid && myParams.SectionCount) {
if(myParams.SectionOffset < 0)
throw Base::ValueError("invalid section offset");
if(myParams.SectionCount>1 && myParams.Stepdown<Precision::Confusion())
throw Base::ValueError("invalid stepdown");
TopLoc_Location loc(myTrsf);
Bnd_Box bounds;
for(const Shape &s : myShapes)
BRepBndLib::Add(s.shape.Moved(loc), bounds);
bounds.SetGap(0.0);
Standard_Real xMin, yMin, zMin, xMax, yMax, zMax;
bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax);
zMax -= myParams.SectionOffset;
if(zMax <= zMin)
throw Base::ValueError("section offset too big");
int error = 0;
int count = myParams.SectionCount;
if(count<0 || count*myParams.Stepdown > zMax-zMin) {
count = ceil((zMax-zMin)/myParams.Stepdown);
if((count-1)*myParams.Stepdown < zMax-zMin)
++count;
}
for(int i=0;i<count;++i,zMax-=myParams.Stepdown) {
if(zMax < zMin) zMax = zMin;
gp_Pln pln(gp_Pnt(0,0,zMax),gp_Dir(0,0,1));
BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax);
const TopoDS_Shape &face = mkFace.Face();
shared_ptr<Area> area(new Area(&myParams));
area->setPlane(face);
for(const Shape &s : myShapes) {
BRep_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
for(TopExp_Explorer it(s.shape, TopAbs_SOLID); it.More(); it.Next()) {
BRepAlgoAPI_Section section(it.Current().Moved(loc),face);
if(!section.IsDone()) {
++error;
continue;
}
const TopoDS_Shape &shape = section.Shape();
if(shape.IsNull()) continue;
Part::FaceMakerBullseye mkFace;
mkFace.setPlane(pln);
std::list<TopoDS_Edge> edges;
for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next())
edges.push_back(TopoDS::Edge(it.Current()));
bool open = false;
std::list<TopoDS_Wire> wires;
while(edges.size()) {
const std::list<TopoDS_Edge> sorted =
Part::sort_Edges(Precision::Confusion(),edges);
BRepBuilderAPI_MakeWire mkWire;
for(const TopoDS_Edge &e : sorted)
mkWire.Add(e);
const TopoDS_Wire &wire = mkWire.Wire();
if(!BRep_Tool::IsClosed(wire))
open = true;
wires.push_back(wire);
}
if(!open) {
for(const TopoDS_Wire &wire : wires)
mkFace.addWire(wire);
try {
mkFace.Build();
if (mkFace.Shape().IsNull())
continue;
builder.Add(comp,mkFace.Shape());
continue;
}catch (Base::Exception &e){
Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what());
}
}
//Shouldn't have any open wire, so count as error
++error;
for(const TopoDS_Wire &wire : wires)
builder.Add(comp,wire);
}
if(comp.IsNull()) continue;
area->add(comp,s.op);
}
mySections.push_back(area);
}
if(error)
Base::Console().Warning("Some errors occured during operation\n");
mySections = makeSections(myParams.SectionMode);
return;
}
getPlane();
try {
myArea.reset(new CArea());
myAreaOpen.reset(new CArea());
@ -549,13 +702,11 @@ void Area::build() {
mySkippedShapes = 0;
short op = OperationUnion;
bool pending = false;
bool explode = myParams.Explode;
bool exploding = myParams.Explode;
for(const Shape &s : myShapes) {
if(explode) {
explode = false;
for (TopExp_Explorer it(s.shape, TopAbs_EDGE); it.More(); it.Next())
add(*myArea,BRepBuilderAPI_MakeWire(
TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true);
if(exploding) {
exploding = false;
explode(s.shape);
continue;
}else if(op!=s.op) {
if(myParams.OpenMode!=OpenModeNone)
@ -604,8 +755,7 @@ void Area::build() {
Area area(&myParams);
area.myParams.Explode = false;
area.myParams.Coplanar = CoplanarNone;
area.myWorkPlane = myWorkPlane.IsNull()?myShapePlane:myWorkPlane;
area.myTrsf = myTrsf;
area.myWorkPlane = getPlane(&area.myTrsf);
while(edges.size()) {
BRepBuilderAPI_MakeWire mkWire;
for(const auto &e : Part::sort_Edges(myParams.Tolerance,edges))
@ -706,6 +856,7 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) {
return toShape(area,bFill,&trsf);
}
#define AREA_SECTION(_op,_index,...) do {\
if(mySections.size()) {\
if(_index>=(int)mySections.size())\
@ -720,9 +871,14 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) {
if(s.IsNull()) continue;\
builder.Add(compound,s.Moved(loc));\
}\
return compound;\
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();)\
return compound;\
return TopoDS_Shape();\
}\
return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\
const TopoDS_Shape &shape = mySections[_index]->_op(-1, ## __VA_ARGS__);\
if(!shape.IsNull())\
return shape.Moved(loc);\
return shape;\
}\
}while(0)
@ -732,6 +888,8 @@ TopoDS_Shape Area::getShape(int index) {
if(myShapeDone) return myShape;
if(!myArea) return TopoDS_Shape();
CAreaConfig conf(myParams);
#define AREA_MY(_param) myParams.PARAM_FNAME(_param)
@ -793,9 +951,13 @@ TopoDS_Shape Area::getShape(int index) {
const TopoDS_Shape &shape = toShape(*area,fill);
builder.Add(compound,toShape(*area,fill));
}
builder.Add(compound,areaPocket.makePocket(
-1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET)));
myShape = compound;
// make sure the compound has at least one edge
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) {
builder.Add(compound,areaPocket.makePocket(
-1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET)));
myShape = compound;
break;
}
myShapeDone = true;
return myShape;
}
@ -826,9 +988,13 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET
fill = myParams.Fill;
else
fill = FillNone;
builder.Add(compound,toShape(*area,fill));
const TopoDS_Shape &shape = toShape(*area,fill);
if(shape.IsNull()) continue;
builder.Add(compound,shape);
}
return compound;
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();)
return compound;
return TopoDS_Shape();
}
void Area::makeOffset(list<shared_ptr<CArea> > &areas,
@ -855,7 +1021,7 @@ void Area::makeOffset(list<shared_ptr<CArea> > &areas,
#ifdef AREA_OFFSET_ALGO
PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL);
#endif
for(int i=0;count<0||i<count;++i,offset+=stepover) {
areas.push_back(make_shared<CArea>());
CArea &area = *areas.back();
@ -1032,23 +1198,25 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) {
if(!wire.IsNull())
builder.Add(compound,wire);
}
if(!compound.IsNull() && fill) {
try{
Part::FaceMakerBullseye mkFace;
if(trsf)
mkFace.setPlane(gp_Pln().Transformed(*trsf));
for(TopExp_Explorer it(compound, TopAbs_WIRE); it.More(); it.Next())
mkFace.addWire(TopoDS::Wire(it.Current()));
mkFace.Build();
if (mkFace.Shape().IsNull())
Base::Console().Warning("FaceMakerBullseye returns null shape\n");
return mkFace.Shape();
}catch (Base::Exception &e){
Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what());
for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) {
if(fill) {
try{
Part::FaceMakerBullseye mkFace;
if(trsf)
mkFace.setPlane(gp_Pln().Transformed(*trsf));
for(TopExp_Explorer it(compound, TopAbs_WIRE); it.More(); it.Next())
mkFace.addWire(TopoDS::Wire(it.Current()));
mkFace.Build();
if (mkFace.Shape().IsNull())
Base::Console().Warning("FaceMakerBullseye returns null shape\n");
return mkFace.Shape();
}catch (Base::Exception &e){
Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what());
}
}
return compound;
}
return compound;
return TopoDS_Shape();
}
std::list<TopoDS_Shape> Area::sortWires(const std::list<TopoDS_Shape> &shapes,
@ -1220,13 +1388,20 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
break;
} default: {
// Discretize all other type of curves
GCPnts_UniformDeflection discretizer(curve, deflection,
GCPnts_QuasiUniformDeflection discretizer(curve, deflection,
curve.FirstParameter(), curve.LastParameter());
if (discretizer.IsDone () && discretizer.NbPoints () > 0) {
if (discretizer.IsDone () && discretizer.NbPoints () > 1) {
int nbPoints = discretizer.NbPoints ();
for (int i=1; i<=nbPoints; i++) {
gp_Pnt pt = discretizer.Value (i);
addCommand(path,pt);
if(reversed) {
for (int i=nbPoints-1; i>=1; --i) {
gp_Pnt pt = discretizer.Value (i);
addCommand(path,pt);
}
}else{
for (int i=2; i<=nbPoints; i++) {
gp_Pnt pt = discretizer.Value (i);
addCommand(path,pt);
}
}
}else
Standard_Failure::Raise("Curve discretization failed");

View File

@ -54,7 +54,8 @@ struct PathExport AreaParams: CAreaParams {
bool operator==(const AreaParams &other) const {
#define AREA_COMPARE(_param) \
if(PARAM_FIELD(NAME,_param)!=other.PARAM_FIELD(NAME,_param)) return false;
PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CONF)
PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CAREA)
PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_AREA)
return true;
}
bool operator!=(const AreaParams &other) const {
@ -71,12 +72,9 @@ struct PathExport AreaParams: CAreaParams {
*/
struct PathExport CAreaConfig {
/** Stores current libarea settings */
/** For saving current libarea settings */
PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA)
/** Stores user defined setting */
CAreaParams params;
/** The constructor automatically saves current setting and apply user defined ones
*
* \arg \c p user defined configurations
@ -96,8 +94,7 @@ class PathExport Area: public Base::BaseClass {
TYPESYSTEM_HEADER();
protected:
public:
struct Shape {
short op;
TopoDS_Shape shape;
@ -108,6 +105,7 @@ protected:
{}
};
protected:
std::list<Shape> myShapes;
std::unique_ptr<CArea> myArea;
std::unique_ptr<CArea> myAreaOpen;
@ -146,6 +144,10 @@ protected:
*/
TopoDS_Shape makePocket();
void explode(const TopoDS_Shape &shape);
bool isBuilt() const;
public:
/** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */
PARAM_ENUM_DECLARE(AREA_PARAMS_ALL)
@ -156,18 +158,27 @@ public:
/** Set a working plane
*
* If no working plane are set, Area will try to find a working plane from
* individual children faces, wires or edges. By right, we should create a
* compound of all shapes and then findplane on it. However, because we
* supports solid, and also because OCC may hang for a long time if
* something goes a bit off, we opt to find plane on each individual shape.
* If you intend to pass individual edges, you must supply a workplane shape
* manually
* \arg \c shape: a shape defining a working plane.
*
* \arg \c shape: a shape defining a working plane
* The supplied shape does not need to be planar. Area will try to find planar
* sub-shape (face, wire or edge). If more than one planar sub-shape is found,
* it will prefer the top plane parallel to XY0 plane.
*
* If no working plane are set, Area will try to find a working plane from
* the added children shape using the same algorithm
*/
void setPlane(const TopoDS_Shape &shape);
/** Return the current active workplane
*
* \arg \c trsf: optional return of a transformation matrix that will bring the
* found plane to XY0 plane.
*
* If no workplane is set using setPlane(), the active workplane is derived from
* the added children shapes using the same algorithm empolyed by setPlane().
*/
TopoDS_Shape getPlane(gp_Trsf *trsf = NULL);
/** Add a child shape with given operation code
*
* No validation is done at this point. Exception will be thrown when asking
@ -195,10 +206,19 @@ public:
TopoDS_Shape makePocket(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET));
std::vector<std::shared_ptr<Area> > makeSections(
PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA),
const std::vector<double> &_heights = std::vector<double>(),
const TopoDS_Shape &plane = TopoDS_Shape());
/** Config this Area object */
void setParams(const AreaParams &params);
const std::list<Shape> getChildren() const {
return myShapes;
}
/** Get the current configuration */
const AreaParams &getParams() const {
return myParams;
@ -330,6 +350,42 @@ public:
static void toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH));
/** Explore the shape to find a planar element, and return its transformation
*
* \arg \c shape: shape to explore
* \arg \c type: OCC shape type (TopAbs_ShapeEnum) to explore
* \arg \c plane: returns the sub planar shape found
* \arg \c trsf: the transformation of the plane which will transform the
* plane into XY0 plane.
*
* If there are multiple subshapes on different planes. It will prefer the
* top XY plane. If there is no XY parallel plane, the first planar shape
* encountered will be returned
*
* \return Returns true is there is any subshape of the give type found.
* You should use plane.IsNull() to see if there is any planar shape found.
*/
static bool findPlane(const TopoDS_Shape &shape, int type,
TopoDS_Shape &plane, gp_Trsf &trsf);
/** Explore the shape with subtype FACE, WIRE and EDGE to find a planar
* subshape
*
* \arg \c shape: shape to explore
* \arg \c plane: returns the sub planar shape found
* \arg \c trsf: the transformation of the plane which will transform the
* plane into XY0 plane.
*
* If there are multiple subshapes on different planes. It will prefer the
* top XY plane. If there is no XY parallel plane, the first planar shape
* encountered will be returned
*
* \return Returns true is there is any subshape is found. You should use
* plane.IsNull() to see if there is any planar shape found.
*/
static bool findPlane(const TopoDS_Shape &shape,
TopoDS_Shape &plane, gp_Trsf &trsf);
};
} //namespace Path

View File

@ -104,9 +104,9 @@
/** Operation code */
#define AREA_PARAMS_OPCODE \
((enum,op,Operation,0,\
"Boolean operation. For the first four operation, see https://goo.gl/Gj8RUu.\n"\
"'Compound' means no operation, normal used to do Area.sortWires().",\
((enum,op,Operation,0,"Boolean operation.\n"\
"For the first four operations, see https://goo.gl/Gj8RUu.\n"\
"'Compound' means no operation, normally used to do Area.sortWires().",\
(Union)(Difference)(Intersection)(Xor)(Compound)))
/** Offset parameters */
@ -115,11 +115,22 @@
((long,extra_pass,ExtraPass,0,"Number of extra offset pass to generate."))\
((double,stepover,Stepover,0.0,"Cutter diameter to step over on each pass. If =0, use Offset"))
#define AREA_PARAMS_SECTION_EXTRA \
((enum,mode,SectionMode,2,"Section offset coordinate mode.\n"\
"'Absolute' means the absolute Z height to start section.\n"\
"'BoundBox' means relative Z height to the bounding box of all the children shape. Only\n"\
"positive value is allowed, which specifies the offset below the top Z of the bounding box.\n"\
"Note that OCC has trouble getting the minimumi bounding box of some solids, particually\n"\
"those with non-planar surface.\n"\
"'Workplane' means relative to workplane.",\
(Absolute)(BoundBox)(Workplane)))
/** Section parameters */
#define AREA_PARAMS_SECTION \
((long,count,SectionCount,0,"Number of sections to generate. -1 means full sections."))\
((double,stepdown,Stepdown,1.0,"Step down distance for each section"))\
((double,offset,SectionOffset,0.0,"Offset for the first section"))
((double,offset,SectionOffset,0.0,"Offset for the first section"))\
AREA_PARAMS_SECTION_EXTRA
#ifdef AREA_OFFSET_ALGO
# define AREA_PARAMS_OFFSET_ALGO \

View File

@ -25,8 +25,12 @@ All arguments are optional.</UserDocu>
</Methode>
<Methode Name="setPlane">
<Documentation>
<UserDocu>setPlane(shape): Set the working plane. The shape will not be used for
any operation</UserDocu>
<UserDocu>setPlane(shape): Set the working plane.\n
The supplied shape does not need to be planar. Area will try to find planar
sub-shape (face, wire or edge). If more than one planar sub-shape is found, it
will prefer the top plane parallel to XY0 plane. If no working plane are set,
Area will try to find a working plane from the added children shape using the
same algorithm</UserDocu>
</Documentation>
</Methode>
<Methode Name="getShape" Keyword='true'>
@ -46,6 +50,11 @@ any operation</UserDocu>
<UserDocu></UserDocu>
</Documentation>
</Methode>
<Methode Name="makeSections" Keyword="true">
<Documentation>
<UserDocu></UserDocu>
</Documentation>
</Methode>
<Methode Name="setParams" Keyword="true">
<Documentation>
<UserDocu></UserDocu>
@ -74,5 +83,17 @@ any operation</UserDocu>
</Documentation>
<Parameter Name="Sections" Type="List"/>
</Attribute>
<Attribute Name="Workplane" ReadOnly="true">
<Documentation>
<UserDocu>The current workplane. If no plane is set, it is derived from the added shapes.</UserDocu>
</Documentation>
<Parameter Name="Workplane" Type="Object"/>
</Attribute>
<Attribute Name="Shapes" ReadOnly="true">
<Documentation>
<UserDocu>A list of tuple: [(shape,op), ...] containing the added shapes together with their operation code</UserDocu>
</Documentation>
<Parameter Name="Shapes" Type="List"/>
</Attribute>
</PythonExport>
</GenerateModel>

View File

@ -54,11 +54,11 @@ static const AreaDoc myDocs[] = {
"add((shape...)," PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OPCODE) "):\n"
"Add TopoShape(s) with given operation code\n"
PARAM_PY_DOC(ARG,AREA_PARAMS_OPCODE)
"\nThe first shape's wires will be fused together regardless of the op code given.\n"
"Subsequent shape's wire will be combined using the op code. All shape wires\n"
"shall be coplanar, and are used to determine a working plane for face making and\n"
"offseting. You can call setPlane() to supply a reference shape to determin the\n"
"working plane in case the added shapes are all colinear lines.\n",
"\nThe first shape's wires will be unioned together regardless of the op code given\n"
"(except for 'Compound'). Subsequent shape's wire will be combined using the op code.\n"
"All shape wires shall be coplanar, and are used to determine a working plane for face\n"
"making and offseting. You can call setPlane() to supply a reference shape to determin\n"
"the workplane in case the added shapes are all colinear lines.\n",
},
{
@ -77,6 +77,17 @@ static const AreaDoc myDocs[] = {
"\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n"
PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET),
},
{
"makeSections",
"makeSections(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) ", heights=[], plane=None):\n"
"Make a list of area holding the sectioned children shapes on given heights\n"
PARAM_PY_DOC(ARG,AREA_PARAMS_SECTION_EXTRA)
"\n* heights ([]): a list of section heights, the meaning of the value is determined by 'mode'.\n"
"If not specified, the current SectionCount, and SectionOffset of this Area is used.\n"
"\n* plane (None): optional shape to specify a section plane. If not give, the current workplane\n"
"of this Area is used.",
},
{
"sortWires",
@ -270,6 +281,53 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds)
return Py::new_reference_to(Part::shape2pyshape(resultShape));
}
PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds)
{
static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SECTION_EXTRA),
"heights", "plane", NULL};
PyObject *heights = NULL;
PyObject *plane = NULL;
PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA)
if (!PyArg_ParseTupleAndKeywords(args, keywds,
"|" PARAM_PY_KWDS(AREA_PARAMS_SECTION_EXTRA) "OO!", kwlist,
PARAM_REF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA),
&heights, &(Part::TopoShapePy::Type), &plane))
return 0;
std::vector<double> h;
if(heights) {
if (PyObject_TypeCheck(heights, &(PyFloat_Type)))
h.push_back(PyFloat_AsDouble(heights));
else if (PyObject_TypeCheck(heights, &(PyList_Type)) ||
PyObject_TypeCheck(heights, &(PyTuple_Type))) {
Py::Sequence shapeSeq(heights);
h.reserve(shapeSeq.size());
for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) {
PyObject* item = (*it).ptr();
if(!PyObject_TypeCheck(item, &(PyFloat_Type))) {
PyErr_SetString(PyExc_TypeError, "heights must only contain float type");
return 0;
}
h.push_back(PyFloat_AsDouble(item));
}
}else{
PyErr_SetString(PyExc_TypeError, "heights must be of type float or list/tuple of float");
return 0;
}
}
std::vector<std::shared_ptr<Area> > sections = getAreaPtr()->makeSections(
PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA),
h,plane?GET_TOPOSHAPE(plane):TopoDS_Shape());
Py::List ret;
for(auto &area : sections)
ret.append(Py::asObject(new AreaPy(new Area(*area,false))));
return Py::new_reference_to(ret);
}
PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds)
{
static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL};
@ -337,6 +395,20 @@ Py::List AreaPy::getSections(void) const {
return ret;
}
Py::List AreaPy::getShapes(void) const {
Py::List ret;
Area *area = getAreaPtr();
const std::list<Area::Shape> &shapes = area->getChildren();
for(auto &s : shapes)
ret.append(Py::TupleN(Part::shape2pyshape(s.shape),Py::Int(s.op)));
return ret;
}
Py::Object AreaPy::getWorkplane(void) const {
return Part::shape2pyshape(getAreaPtr()->getPlane());
}
// custom attributes get/set
PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const

View File

@ -49,11 +49,11 @@ FeatureArea::FeatureArea()
PARAM_PROP_ADD("Area",AREA_PARAMS_OPCODE);
PARAM_PROP_ADD("Area",AREA_PARAMS_BASE);
PARAM_PROP_ADD("Offset",AREA_PARAMS_OFFSET);
PARAM_PROP_ADD("Offset", AREA_PARAMS_OFFSET_CONF);
PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET);
PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET_CONF);
PARAM_PROP_ADD("Section",AREA_PARAMS_SECTION);
PARAM_PROP_ADD("Offset Settings", AREA_PARAMS_OFFSET_CONF);
PARAM_PROP_ADD("libarea Settings",AREA_PARAMS_CAREA);
PARAM_PROP_ADD("libarea",AREA_PARAMS_CAREA);
PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_ALL);
PocketMode.setValue((long)0);