Add offset() module to inset/outset polygons using Clipper (fixes #483).

offset
Torsten Paul 2014-02-23 21:51:11 +01:00
parent e1623ab3e0
commit b5c561a9f5
9 changed files with 184 additions and 0 deletions

View File

@ -223,6 +223,7 @@ HEADERS += src/typedefs.h \
src/feature.h \
src/node.h \
src/csgnode.h \
src/offsetnode.h \
src/linearextrudenode.h \
src/rotateextrudenode.h \
src/projectionnode.h \
@ -306,6 +307,7 @@ SOURCES += src/version_check.cc \
src/render.cc \
src/dxfdata.cc \
src/dxfdim.cc \
src/offset.cc \
src/linearextrude.cc \
src/rotateextrude.cc \
src/printutils.cc \

View File

@ -6,6 +6,7 @@
#include "Polygon2d.h"
#include "module.h"
#include "state.h"
#include "offsetnode.h"
#include "transformnode.h"
#include "linearextrudenode.h"
#include "rotateextrudenode.h"
@ -400,6 +401,33 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node)
return ContinueTraversal;
}
Response GeometryEvaluator::visit(State &state, const OffsetNode &node)
{
if (state.isPrefix() && isSmartCached(node)) return PruneTraversal;
if (state.isPostfix()) {
shared_ptr<const Geometry> geom;
if (!isSmartCached(node)) {
const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION);
if (geometry) {
const Polygon2d *polygon = dynamic_cast<const Polygon2d*>(geometry);
// ClipperLib documentation: The formula for the number of steps in a full
// circular arc is ... Pi / acos(1 - arc_tolerance / abs(delta))
double n = Calc::get_fragments_from_r(10, node.fn, node.fs, node.fa);
double arc_tolerance = abs(node.delta) * (1 - cos(M_PI / n));
const Polygon2d *result = ClipperUtils::applyOffset(*polygon, node.delta, node.join_type, node.miter_limit, arc_tolerance);
assert(result);
geom.reset(result);
delete geometry;
}
}
else {
geom = smartCacheGet(node);
}
addToParent(state, node, geom);
}
return ContinueTraversal;
}
/*!
RenderNodes just pass on convexity
*/

View File

@ -30,6 +30,7 @@ public:
virtual Response visit(State &state, const CgaladvNode &node);
virtual Response visit(State &state, const ProjectionNode &node);
virtual Response visit(State &state, const RenderNode &node);
virtual Response visit(State &state, const OffsetNode &node);
const Tree &getTree() const { return this->tree; }

View File

@ -35,6 +35,7 @@ extern void register_builtin_render();
extern void register_builtin_import();
extern void register_builtin_projection();
extern void register_builtin_cgaladv();
extern void register_builtin_offset();
extern void register_builtin_dxf_linear_extrude();
extern void register_builtin_dxf_rotate_extrude();
extern void initialize_builtin_dxf_dim();
@ -60,6 +61,7 @@ void Builtins::initialize()
register_builtin_import();
register_builtin_projection();
register_builtin_cgaladv();
register_builtin_offset();
register_builtin_dxf_linear_extrude();
register_builtin_dxf_rotate_extrude();

View File

@ -234,4 +234,11 @@ namespace ClipperUtils {
return toPolygon2d(polytree);
}
Polygon2d *applyOffset(const Polygon2d& poly, double offset, ClipperLib::JoinType joinType, double miter_limit, double arc_tolerance) {
ClipperLib::ClipperOffset co(miter_limit, arc_tolerance * CLIPPER_SCALE);
co.AddPaths(fromPolygon2d(poly), joinType, ClipperLib::etClosedPolygon);
ClipperLib::PolyTree result;
co.Execute(result, offset * CLIPPER_SCALE);
return toPolygon2d(result);
}
};

View File

@ -16,6 +16,7 @@ namespace ClipperUtils {
ClipperLib::Paths process(const ClipperLib::Paths &polygons,
ClipperLib::ClipType, ClipperLib::PolyFillType);
Polygon2d *applyOffset(const Polygon2d& poly, double offset, ClipperLib::JoinType joinType, double miter_limit, double arc_tolerance);
Polygon2d *applyMinkowski(const std::vector<const Polygon2d*> &polygons);
Polygon2d *apply(const std::vector<const Polygon2d*> &polygons, ClipperLib::ClipType);
Polygon2d *apply(const std::vector<ClipperLib::Paths> &pathsvector, ClipperLib::ClipType);

117
src/offset.cc Normal file
View File

@ -0,0 +1,117 @@
/*
* OpenSCAD (www.openscad.org)
* Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
* Marius Kintel <marius@kintel.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* As a special exception, you have permission to link this program
* with the CGAL library and distribute executables, as long as you
* follow the requirements of the GNU GPL in regard to all of the
* software in the executable aside from CGAL.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "offsetnode.h"
#include "module.h"
#include "evalcontext.h"
#include "printutils.h"
#include "fileutils.h"
#include "builtin.h"
#include "calc.h"
#include "polyset.h"
#include "mathc99.h"
#include <sstream>
#include <boost/assign/std/vector.hpp>
using namespace boost::assign; // bring 'operator+=()' into scope
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
class OffsetModule : public AbstractModule
{
public:
OffsetModule() { }
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
};
AbstractNode *OffsetModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
{
OffsetNode *node = new OffsetNode(inst);
AssignmentList args;
args += Assignment("delta", NULL);
Context c(ctx);
c.setVariables(args, evalctx);
node->fn = c.lookup_variable("$fn").toDouble();
node->fs = c.lookup_variable("$fs").toDouble();
node->fa = c.lookup_variable("$fa").toDouble();
Value delta = c.lookup_variable("delta");
node->delta = 1;
delta.getDouble(node->delta);
Value miter_limit = c.lookup_variable("miter_limit", true);
node->miter_limit = 2;
miter_limit.getDouble(node->miter_limit);
Value join_type = c.lookup_variable("join_type", true);
if (join_type.type() == Value::STRING) {
std::string jt = join_type.toString();
if (std::string("square") == jt) {
node->join_type = ClipperLib::jtSquare;
} else if (std::string("round") == jt) {
node->join_type = ClipperLib::jtRound;
} else if (std::string("miter") == jt) {
node->join_type = ClipperLib::jtMiter;
} else {
PRINTB("Unknown join_type for offset(): '%1'", jt);
}
}
std::vector<AbstractNode *> instantiatednodes = inst->instantiateChildren(evalctx);
node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end());
return node;
}
std::string OffsetNode::toString() const
{
std::stringstream stream;
stream << this->name()
<< "(delta = " << std::dec << this->delta
<< ", join_type = "
<< (this->join_type == ClipperLib::jtSquare
? "square"
: this->join_type == ClipperLib::jtRound
? "round"
: "miter")
<< ", miter_limit = " << this->miter_limit
<< ", $fn = " << this->fn
<< ", $fa = " << this->fa
<< ", $fs = " << this->fs << ")";
return stream.str();
}
void register_builtin_offset()
{
Builtins::init("offset", new OffsetModule());
}

23
src/offsetnode.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef OFFSETNODE_H_
#define OFFSETNODE_H_
#include "node.h"
#include "visitor.h"
#include "value.h"
#include "clipper-utils.h"
class OffsetNode : public AbstractPolyNode
{
public:
OffsetNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi), fn(0), fs(0), fa(0), delta(1), miter_limit(2.0), join_type(ClipperLib::jtMiter) { }
virtual Response accept(class State &state, Visitor &visitor) const {
return visitor.visit(state, *this);
}
virtual std::string toString() const;
virtual std::string name() const { return "offset"; }
double fn, fs, fa, delta, miter_limit;
ClipperLib::JoinType join_type;
};
#endif

View File

@ -52,6 +52,9 @@ public:
virtual Response visit(class State &state, const class ColorNode &node) {
return visit(state, (const class AbstractNode &)node);
}
virtual Response visit(class State &state, const class OffsetNode &node) {
return visit(state, (const class AbstractNode &)node);
}
// Add visit() methods for new visitable subtypes of AbstractNode here
};