Merge pull request #11939 from davidgilkaufman/restMachiningFromGcode
[Path] Rest machining from gcode
This commit is contained in:
@@ -75,6 +75,7 @@
|
||||
#include <Mod/Part/App/CrossSection.h>
|
||||
#include <Mod/Part/App/FaceMakerBullseye.h>
|
||||
#include <Mod/Part/App/PartFeature.h>
|
||||
#include <Mod/Path/App/PathSegmentWalker.h>
|
||||
#include <Mod/Path/libarea/Area.h>
|
||||
|
||||
#include "Area.h"
|
||||
@@ -496,47 +497,169 @@ void Area::add(const TopoDS_Shape& shape, short op) {
|
||||
myShapes.emplace_back(op, shape);
|
||||
}
|
||||
|
||||
std::shared_ptr<Area> Area::getClearedArea(double tipDiameter, double diameter) {
|
||||
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 ¢er) override
|
||||
{
|
||||
(void)id;
|
||||
(void)center;
|
||||
|
||||
// Compute cw vs ccw
|
||||
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;
|
||||
|
||||
// Add an arc
|
||||
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
|
||||
{
|
||||
// (peck) drilling
|
||||
(void)id;
|
||||
(void)qlist; // pecks are always within the bounds of plist
|
||||
|
||||
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
|
||||
{
|
||||
// probe operation; clears nothing
|
||||
(void)id;
|
||||
(void)last;
|
||||
(void)next;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<Area> Area::getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox) {
|
||||
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);
|
||||
(void)SubjectFill;
|
||||
(void)ClipFill;
|
||||
|
||||
// Precision losses in arc/segment conversions (multiples of Accuracy):
|
||||
// 2.3 in generation of gcode (see documentation in the implementation of CCurve::CheckForArc (libarea/Curve.cpp)
|
||||
// 1 in gcode arc to segment
|
||||
// 1 in Thicken() cleared area
|
||||
// 2 in getRestArea target area offset in and back out
|
||||
// Oversize cleared areas by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this
|
||||
AreaParams params = myParams;
|
||||
params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total
|
||||
params.SubjectFill = ClipperLib::pftNonZero;
|
||||
params.ClipFill = ClipperLib::pftNonZero;
|
||||
const double buffer = myParams.Accuracy * 3;
|
||||
|
||||
// Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off
|
||||
// those arcs will be converted back to segments again for clipper differencing in getRestArea anyway
|
||||
CAreaConfig conf(myParams, /*no_fit_arcs*/ true);
|
||||
CAreaConfig conf(params, /*no_fit_arcs*/ true);
|
||||
|
||||
const double roundPrecision = myParams.Accuracy;
|
||||
const double buffer = 2 * roundPrecision;
|
||||
ClearedAreaSegmentVisitor visitor(zmax, diameter/2 + buffer, bbox);
|
||||
PathSegmentWalker walker(*path);
|
||||
walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1));
|
||||
|
||||
// A = myArea
|
||||
// prevCenters = offset(A, -rTip)
|
||||
const double rTip = tipDiameter / 2.;
|
||||
CArea prevCenter(*myArea);
|
||||
prevCenter.OffsetWithClipper(-rTip, JoinType, EndType, myParams.MiterLimit, roundPrecision);
|
||||
|
||||
// prevCleared = offset(prevCenter, r).
|
||||
CArea prevCleared(prevCenter);
|
||||
prevCleared.OffsetWithClipper(diameter / 2. + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision);
|
||||
|
||||
std::shared_ptr<Area> clearedArea = make_shared<Area>(*this);
|
||||
clearedArea->myArea.reset(new CArea(prevCleared));
|
||||
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);
|
||||
|
||||
const double roundPrecision = myParams.Accuracy;
|
||||
const double buffer = 2 * roundPrecision;
|
||||
// Precision losses in arc/segment conversions (multiples of Accuracy):
|
||||
// 2.3 in generation of gcode (see documentation in the implementation of CCurve::CheckForArc (libarea/Curve.cpp)
|
||||
// 1 in gcode arc to segment
|
||||
// 1 in Thicken() cleared area
|
||||
// 2 in getRestArea target area offset in and back out
|
||||
// Cleared area representations are oversized by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this
|
||||
AreaParams params = myParams;
|
||||
params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total
|
||||
const double buffer = myParams.Accuracy * 3;
|
||||
const double roundPrecision = params.Accuracy;
|
||||
|
||||
// transform all clearedAreas into our workplane
|
||||
Area clearedAreasInPlane(&myParams);
|
||||
Area clearedAreasInPlane(¶ms);
|
||||
clearedAreasInPlane.myArea.reset(new CArea());
|
||||
for (std::shared_ptr<Area> clearedArea : clearedAreas) {
|
||||
gp_Trsf trsf = clearedArea->myTrsf;
|
||||
@@ -547,18 +670,28 @@ std::shared_ptr<Area> Area::getRestArea(std::vector<std::shared_ptr<Area>> clear
|
||||
&myWorkPlane);
|
||||
}
|
||||
|
||||
// remaining = A - prevCleared
|
||||
CArea remaining(*myArea);
|
||||
// clearable = offset(offset(A, -dTool/2), dTool/2)
|
||||
CArea clearable(*myArea);
|
||||
clearable.OffsetWithClipper(-diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision);
|
||||
clearable.OffsetWithClipper(diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision);
|
||||
|
||||
// remaining = clearable - prevCleared
|
||||
CArea remaining(clearable);
|
||||
remaining.Clip(toClipperOp(Area::OperationDifference), &*(clearedAreasInPlane.myArea), SubjectFill, ClipFill);
|
||||
|
||||
// rest = intersect(A, offset(remaining, dTool))
|
||||
// rest = intersect(clearable, offset(remaining, dTool))
|
||||
// add buffer to dTool to compensate for oversizing in getClearedArea
|
||||
CArea restCArea(remaining);
|
||||
restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision);
|
||||
restCArea.Clip(toClipperOp(Area::OperationIntersection), &*myArea, SubjectFill, ClipFill);
|
||||
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);
|
||||
std::shared_ptr<Area> restArea = make_shared<Area>(&myParams);
|
||||
restArea->add(restShape, OperationCompound);
|
||||
|
||||
return restArea;
|
||||
|
||||
@@ -242,7 +242,7 @@ public:
|
||||
const std::vector<double>& heights = std::vector<double>(),
|
||||
const TopoDS_Shape& plane = TopoDS_Shape());
|
||||
|
||||
std::shared_ptr<Area> getClearedArea(double tipDiameter, double diameter);
|
||||
std::shared_ptr<Area> getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox);
|
||||
std::shared_ptr<Area> getRestArea(std::vector<std::shared_ptr<Area>> clearedAreas, double diameter);
|
||||
TopoDS_Shape toTopoShape();
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <Base/PyWrapParseTupleAndKeywords.h>
|
||||
#include <Mod/Part/App/OCCError.h>
|
||||
#include <Mod/Part/App/TopoShapePy.h>
|
||||
|
||||
// inclusion of the generated files (generated out of AreaPy.xml)
|
||||
#include "PathPy.h"
|
||||
#include "AreaPy.h"
|
||||
#include "AreaPy.cpp"
|
||||
|
||||
@@ -149,8 +151,8 @@ static const PyMethodDef areaOverrides[] = {
|
||||
},
|
||||
{
|
||||
"getClearedArea",nullptr,0,
|
||||
"getClearedArea(tipDiameter, diameter):\n"
|
||||
"Gets the area cleared when a tool maximally clears this area. This method assumes a tool tip diameter 'tipDiameter' traces the full area, and that (perhaps at a different height on the tool) this clears a different region with tool diameter 'diameter'.\n",
|
||||
"getClearedArea(path, diameter, zmax, bbox):\n"
|
||||
"Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax and path segments that don't affect space within the x/y space of bbox.\n",
|
||||
},
|
||||
{
|
||||
"getRestArea",nullptr,0,
|
||||
@@ -406,10 +408,21 @@ PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds)
|
||||
PyObject* AreaPy::getClearedArea(PyObject *args)
|
||||
{
|
||||
PY_TRY {
|
||||
double tipDiameter, diameter;
|
||||
if (!PyArg_ParseTuple(args, "dd", &tipDiameter, &diameter))
|
||||
PyObject *pyPath, *pyBbox;
|
||||
double diameter, zmax;
|
||||
if (!PyArg_ParseTuple(args, "OddO", &pyPath, &diameter, &zmax, &pyBbox))
|
||||
return nullptr;
|
||||
std::shared_ptr<Area> clearedArea = getAreaPtr()->getClearedArea(tipDiameter, diameter);
|
||||
if (!PyObject_TypeCheck(pyPath, &(PathPy::Type))) {
|
||||
PyErr_SetString(PyExc_TypeError, "path must be of type PathPy");
|
||||
return nullptr;
|
||||
}
|
||||
if (!PyObject_TypeCheck(pyBbox, &(Base::BoundBoxPy::Type))) {
|
||||
PyErr_SetString(PyExc_TypeError, "bbox must be of type BoundBoxPy");
|
||||
return nullptr;
|
||||
}
|
||||
const PathPy *path = static_cast<PathPy*>(pyPath);
|
||||
const Py::BoundingBox bbox(pyBbox, false);
|
||||
std::shared_ptr<Area> clearedArea = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax, bbox.getValue());
|
||||
auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true)));
|
||||
return Py::new_reference_to(pyClearedArea);
|
||||
} PY_CATCH_OCC
|
||||
@@ -440,6 +453,9 @@ PyObject* AreaPy::getRestArea(PyObject *args)
|
||||
}
|
||||
|
||||
std::shared_ptr<Area> restArea = getAreaPtr()->getRestArea(clearedAreas, diameter);
|
||||
if (!restArea) {
|
||||
return Py_None;
|
||||
}
|
||||
auto pyRestArea = Py::asObject(new AreaPy(new Area(*restArea, true)));
|
||||
return Py::new_reference_to(pyRestArea);
|
||||
} PY_CATCH_OCC
|
||||
|
||||
@@ -240,41 +240,22 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
# Rest machining
|
||||
self.sectionShapes = self.sectionShapes + [section.toTopoShape() for section in sections]
|
||||
if hasattr(obj, "UseRestMachining") and obj.UseRestMachining:
|
||||
# Loop through prior operations
|
||||
clearedAreas = []
|
||||
foundSelf = False
|
||||
for op in self.job.Operations.Group:
|
||||
if foundSelf:
|
||||
break
|
||||
oplist = [op] + op.OutListRecursive
|
||||
oplist = list(filter(lambda op: hasattr(op, "Active"), oplist))
|
||||
for op in oplist:
|
||||
if op.Proxy == self:
|
||||
# Ignore self, and all later operations
|
||||
foundSelf = True
|
||||
break
|
||||
if hasattr(op, "RestMachiningRegions") and op.Active:
|
||||
if hasattr(op, "RestMachiningRegionsNeedRecompute") and op.RestMachiningRegionsNeedRecompute:
|
||||
Path.Log.warning(
|
||||
translate("PathAreaOp", "Previous operation %s is required for rest machining, but it has no stored rest machining metadata. Recomputing to generate this metadata...") % op.Label
|
||||
)
|
||||
op.recompute()
|
||||
|
||||
tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController)
|
||||
diameter = tool.Diameter.getValueAs("mm")
|
||||
def shapeToArea(shape):
|
||||
area = Path.Area()
|
||||
area.setPlane(PathUtils.makeWorkplane(shape))
|
||||
area.add(shape)
|
||||
return area
|
||||
opClearedAreas = [shapeToArea(pa).getClearedArea(diameter, diameter) for pa in op.RestMachiningRegions.SubShapes]
|
||||
clearedAreas.extend(opClearedAreas)
|
||||
restSections = []
|
||||
for section in sections:
|
||||
z = section.getShape().BoundBox.ZMin
|
||||
sectionClearedAreas = [a for a in clearedAreas if a.getShape().BoundBox.ZMax <= z]
|
||||
bbox = section.getShape().BoundBox
|
||||
z = bbox.ZMin
|
||||
sectionClearedAreas = []
|
||||
for op in self.job.Operations.Group:
|
||||
if self in [x.Proxy for x in [op] + op.OutListRecursive if hasattr(x, "Proxy")]:
|
||||
break
|
||||
if hasattr(op, "Active") and op.Active and op.Path:
|
||||
tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController)
|
||||
diameter = tool.Diameter.getValueAs("mm")
|
||||
dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz translates to the full width part of the tool
|
||||
sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+self.job.GeometryTolerance.getValueAs("mm"), bbox))
|
||||
restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm"))
|
||||
restSections.append(restSection)
|
||||
if (restSection is not None):
|
||||
restSections.append(restSection)
|
||||
sections = restSections
|
||||
|
||||
shapelist = [sec.getShape() for sec in sections]
|
||||
@@ -481,10 +462,6 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
)
|
||||
)
|
||||
|
||||
if hasattr(obj, "RestMachiningRegions"):
|
||||
obj.RestMachiningRegions = Part.makeCompound(self.sectionShapes)
|
||||
if hasattr(obj, "RestMachiningRegionsNeedRecompute"):
|
||||
obj.RestMachiningRegionsNeedRecompute = False
|
||||
Path.Log.debug("obj.Name: " + str(obj.Name) + "\n\n")
|
||||
return sims
|
||||
|
||||
|
||||
@@ -194,16 +194,6 @@ class ObjectPocket(PathAreaOp.ObjectOp):
|
||||
"Skips machining regions that have already been cleared by previous operations.",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"Part::PropertyPartShape",
|
||||
"RestMachiningRegions",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The areas cleared by this operation, one area per height, stored as a compound part. Used internally for rest machining.",
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("RestMachiningRegions", 2) # hide
|
||||
|
||||
for n in self.pocketPropertyEnumerations():
|
||||
setattr(obj, n[0], n[1])
|
||||
@@ -277,29 +267,10 @@ class ObjectPocket(PathAreaOp.ObjectOp):
|
||||
),
|
||||
)
|
||||
|
||||
if not hasattr(obj, "RestMachiningRegions"):
|
||||
obj.addProperty(
|
||||
"Part::PropertyPartShape",
|
||||
"RestMachiningRegions",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The areas cleared by this operation, one area per height, stored as a compound part. Used internally for rest machining.",
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("RestMachiningRegions", 2) # hide
|
||||
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"RestMachiningRegionsNeedRecompute",
|
||||
"Pocket",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Flag to indicate that the rest machining regions have never been computed, and must be recomputed before being used.",
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("RestMachiningRegionsNeedRecompute", 2) # hide
|
||||
obj.RestMachiningRegionsNeedRecompute = True
|
||||
if hasattr(obj, "RestMachiningRegions"):
|
||||
obj.removeProperty("RestMachiningRegions")
|
||||
if hasattr(obj, "RestMachiningRegionsNeedRecompute"):
|
||||
obj.removeProperty("RestMachiningRegionsNeedRecompute")
|
||||
|
||||
Path.Log.track()
|
||||
|
||||
|
||||
@@ -252,12 +252,16 @@ static void MakeObround(const Point &pt0, const CVertex &vt1, double radius)
|
||||
static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, double radius)
|
||||
{
|
||||
Clipper c;
|
||||
c.StrictlySimple(CArea::m_clipper_simple);
|
||||
|
||||
c.StrictlySimple(CArea::m_clipper_simple);
|
||||
pp_new.clear();
|
||||
|
||||
for(std::list<CCurve>::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++)
|
||||
{
|
||||
c.Clear();
|
||||
c.AddPaths(pp_new, ptSubject, true);
|
||||
pp_new.clear();
|
||||
pts_for_AddVertex.clear();
|
||||
|
||||
const CCurve& curve = *It;
|
||||
const CVertex* prev_vertex = NULL;
|
||||
for(std::list<CVertex>::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++)
|
||||
@@ -278,10 +282,9 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou
|
||||
}
|
||||
prev_vertex = &vertex;
|
||||
}
|
||||
c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero);
|
||||
}
|
||||
|
||||
pp_new.clear();
|
||||
c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero);
|
||||
|
||||
// reverse all the resulting polygons
|
||||
TPolyPolygon copy = pp_new;
|
||||
|
||||
Reference in New Issue
Block a user