mirror of https://github.com/vitalif/openscad
Add offset() module to inset/outset polygons using Clipper (fixes #483).
parent
e1623ab3e0
commit
b5c561a9f5
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue