Don't just ignore geometric nodes having zero volume/area - when doing difference/intersection, they tend to turn negative objects into positive ones. Fixes #221

felipesanches-svg
Marius Kintel 2013-01-08 13:26:25 -05:00
parent 8cea6834f6
commit 810f1a8618
11 changed files with 113 additions and 106 deletions

View File

@ -61,14 +61,16 @@ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedr
if (target.dim != 2 && target.dim != 3) {
assert(false && "Dimension of Nef polyhedron must be 2 or 3");
}
if (src.empty()) return; // Empty polyhedron. This can happen for e.g. square([0,0])
if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0])
if (target.isEmpty() && op != CGE_UNION) return; // empty op <something> => empty
if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
try {
switch (op) {
case CGE_UNION:
target += src;
if (target.isEmpty()) target = src.copy();
else target += src;
break;
case CGE_INTERSECTION:
target *= src;
@ -110,7 +112,8 @@ CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, CGA
if (!isCached(*chnode)) {
CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN);
}
if (N.empty()) N = chN.copy();
// Initialize N on first iteration with first expected geometric object
if (N.isNull() && !N.isEmpty()) N = chN.copy();
else process(N, chN, op);
chnode->progress_report();
@ -245,7 +248,6 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node)
if (!isCached(node)) {
// First union all children
N = applyToChildren(node, CGE_UNION);
if ( matrix_contains_infinity( node.matrix ) || matrix_contains_nan( node.matrix ) ) {
// due to the way parse/eval works we can't currently distinguish between NaN and Inf
PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object.");
@ -253,51 +255,53 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node)
}
// Then apply transform
// If there is no geometry under the transform, N will be empty and of dim 0,
// just just silently ignore such nodes
if (N.dim == 2) {
// Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2
// objects. So we convert in to our internal 2d data format, transform it,
// tesselate it and create a new CGAL_Nef_polyhedron2 from it.. What a hack!
Eigen::Matrix2f testmat;
testmat << node.matrix(0,0), node.matrix(0,1), node.matrix(1,0), node.matrix(1,1);
if (testmat.determinant() == 0) {
PRINT("Warning: Scaling a 2D object with 0 - removing object");
N.reset();
}
else {
CGAL_Aff_transformation2 t(
node.matrix(0,0), node.matrix(0,1), node.matrix(0,3),
node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), node.matrix(3,3));
// If there is no geometry under the transform, N will be empty
// just silently ignore such nodes
if (!N.isNull()) {
if (N.dim == 2) {
// Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2
// objects. So we convert in to our internal 2d data format, transform it,
// tesselate it and create a new CGAL_Nef_polyhedron2 from it.. What a hack!
DxfData *dd = N.convertToDxfData();
for (size_t i=0; i < dd->points.size(); i++) {
CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]);
p = t.transform(p);
dd->points[i][0] = to_double(p.x());
dd->points[i][1] = to_double(p.y());
Eigen::Matrix2f testmat;
testmat << node.matrix(0,0), node.matrix(0,1), node.matrix(1,0), node.matrix(1,1);
if (testmat.determinant() == 0) {
PRINT("Warning: Scaling a 2D object with 0 - removing object");
N.reset();
}
else {
CGAL_Aff_transformation2 t(
node.matrix(0,0), node.matrix(0,1), node.matrix(0,3),
node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), node.matrix(3,3));
DxfData *dd = N.convertToDxfData();
for (size_t i=0; i < dd->points.size(); i++) {
CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]);
p = t.transform(p);
dd->points[i][0] = to_double(p.x());
dd->points[i][1] = to_double(p.y());
}
PolySet ps;
ps.is2d = true;
dxf_tesselate(&ps, *dd, 0, true, false, 0);
N = evaluateCGALMesh(ps);
delete dd;
}
PolySet ps;
ps.is2d = true;
dxf_tesselate(&ps, *dd, 0, true, false, 0);
N = evaluateCGALMesh(ps);
delete dd;
}
}
else if (N.dim == 3) {
if (node.matrix.matrix().determinant() == 0) {
PRINT("Warning: Scaling a 3D object with 0 - removing object");
N.reset();
}
else {
CGAL_Aff_transformation t(
node.matrix(0,0), node.matrix(0,1), node.matrix(0,2), node.matrix(0,3),
node.matrix(1,0), node.matrix(1,1), node.matrix(1,2), node.matrix(1,3),
node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3));
N.p3->transform(t);
else if (N.dim == 3) {
if (node.matrix.matrix().determinant() == 0) {
PRINT("Warning: Scaling a 3D object with 0 - removing object");
N.reset();
}
else {
CGAL_Aff_transformation t(
node.matrix(0,0), node.matrix(0,1), node.matrix(0,2), node.matrix(0,3),
node.matrix(1,0), node.matrix(1,1), node.matrix(1,2), node.matrix(1,3),
node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3));
N.p3->transform(t);
}
}
}
}
@ -388,7 +392,7 @@ void CGALEvaluator::addToParent(const State &state, const AbstractNode &node, co
CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const PolySet &ps)
{
if (ps.empty()) return CGAL_Nef_polyhedron();
if (ps.empty()) return CGAL_Nef_polyhedron(ps.is2d ? 2 : 3);
if (ps.is2d)
{

View File

@ -43,7 +43,11 @@
CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root)
{
if (root.dim == 2) {
if (this->root.isNull()) {
this->polyhedron = NULL;
this->polyset = NULL;
}
else if (root.dim == 2) {
DxfData *dd = root.convertToDxfData();
this->polyhedron = NULL;
this->polyset = new PolySet();
@ -67,10 +71,6 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root)
CGAL::OGL::Nef3_Converter<CGAL_Nef_polyhedron3>::convert_to_OGLPolyhedron(*this->root.p3, this->polyhedron);
this->polyhedron->init();
}
else {
this->polyhedron = NULL;
this->polyset = NULL;
}
}
CGALRenderer::~CGALRenderer()
@ -81,6 +81,7 @@ CGALRenderer::~CGALRenderer()
void CGALRenderer::draw(bool showfaces, bool showedges) const
{
if (this->root.isNull()) return;
if (this->root.dim == 2) {
// Draw 2D polygons
glDisable(GL_LIGHTING);

View File

@ -61,7 +61,7 @@ CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &o
int CGAL_Nef_polyhedron::weight() const
{
if (this->empty()) return 0;
if (this->isNull()) return 0;
size_t memsize = sizeof(CGAL_Nef_polyhedron);
if (this->dim == 2) {
@ -84,8 +84,7 @@ int CGAL_Nef_polyhedron::weight() const
*/
PolySet *CGAL_Nef_polyhedron::convertToPolyset()
{
if (this->empty())
return new PolySet();
if (this->isNull()) return new PolySet();
PolySet *ps = NULL;
if (this->dim == 2) {
ps = new PolySet();

View File

@ -8,12 +8,15 @@
class CGAL_Nef_polyhedron
{
public:
CGAL_Nef_polyhedron() : dim(0) {}
CGAL_Nef_polyhedron(int dim = 0) : dim(dim) {}
CGAL_Nef_polyhedron(CGAL_Nef_polyhedron2 *p);
CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p);
~CGAL_Nef_polyhedron() {}
bool empty() const { return (dim == 0 || (!p2 && !p3)); }
// Empty means it is a geometric node which has zero area/volume
bool isEmpty() const { return (dim > 0 && !p2 && !p3); }
// Null means the node doesn't contain any geometry (for whatever reason)
bool isNull() const { return !p2 && !p3; }
void reset() { dim=0; p2.reset(); p3.reset(); }
CGAL_Nef_polyhedron &operator+=(const CGAL_Nef_polyhedron &other);
CGAL_Nef_polyhedron &operator*=(const CGAL_Nef_polyhedron &other);

View File

@ -134,11 +134,11 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
if (v->modinst->isBackground()) continue;
CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v);
if (N.dim == 3) {
if (sum.empty()) sum = N.copy();
if (sum.isNull()) sum = N.copy();
else sum += N;
}
}
if (sum.empty()) return NULL;
if (sum.isNull()) return NULL;
if (!sum.p3->is_simple()) {
if (!node.cut_mode) {
PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design..");
@ -149,7 +149,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
//std::cout << sum.dump();
//std::cout.flush();
CGAL_Nef_polyhedron nef_poly;
CGAL_Nef_polyhedron nef_poly(2);
if (node.cut_mode) {
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
@ -180,7 +180,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
}
}
if ( sum.p3->is_empty() ) {
if (sum.p3->is_empty()) {
CGAL::set_error_behaviour(old_behaviour);
PRINT("WARNING: projection() failed.");
return NULL;
@ -204,7 +204,6 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
log << "<!-- volume end. -->\n";
}
nef_poly.p2 = zremover.output_nefpoly2d;
nef_poly.dim = 2;
} catch (const CGAL::Failure_exception &e) {
PRINTB("CGAL error in projection node while flattening: %s", e.what());
}
@ -284,8 +283,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
plist.push_back(p);
}
// FIXME: Should the CGAL_Nef_polyhedron2 be cached?
if (nef_poly.empty()) {
nef_poly.dim = 2;
if (nef_poly.isEmpty()) {
nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED));
}
else {
@ -385,18 +383,18 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const LinearExtrudeNode &node)
BOOST_FOREACH (AbstractNode * v, node.getChildren()) {
if (v->modinst->isBackground()) continue;
CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v);
if (!N.empty()) {
if (!N.isNull()) {
if (N.dim != 2) {
PRINT("ERROR: linear_extrude() is not defined for 3D child objects!");
}
else {
if (sum.empty()) sum = N.copy();
if (sum.isNull()) sum = N.copy();
else sum += N;
}
}
}
if (sum.empty()) return NULL;
if (sum.isNull()) return NULL;
dxf = sum.convertToDxfData();;
} else {
dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale);
@ -485,18 +483,18 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RotateExtrudeNode &node)
BOOST_FOREACH (AbstractNode * v, node.getChildren()) {
if (v->modinst->isBackground()) continue;
CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v);
if (!N.empty()) {
if (!N.isNull()) {
if (N.dim != 2) {
PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!");
}
else {
if (sum.empty()) sum = N.copy();
if (sum.isNull()) sum = N.copy();
else sum += N;
}
}
}
if (sum.empty()) return NULL;
if (sum.isNull()) return NULL;
dxf = sum.convertToDxfData();
} else {
dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale);
@ -511,7 +509,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const CgaladvNode &node)
{
CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node);
PolySet *ps = NULL;
if (!N.empty()) {
if (!N.isNull()) {
ps = N.convertToPolyset();
if (ps) ps->convexity = node.convexity;
}
@ -523,7 +521,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RenderNode &node)
{
CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node);
PolySet *ps = NULL;
if (!N.empty()) {
if (!N.isNull()) {
if (N.dim == 3 && !N.p3->is_simple()) {
PRINT("WARNING: Body of render() isn't valid 2-manifold!");
}

View File

@ -1236,35 +1236,37 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N)
PolySetCache::instance()->print();
CGALCache::instance()->print();
if (root_N->dim == 2) {
PRINT(" Top level object is a 2D object:");
PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no"));
PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no"));
PRINTB(" Vertices: %6d", root_N->p2->explorer().number_of_vertices());
PRINTB(" Halfedges: %6d", root_N->p2->explorer().number_of_halfedges());
PRINTB(" Edges: %6d", root_N->p2->explorer().number_of_edges());
PRINTB(" Faces: %6d", root_N->p2->explorer().number_of_faces());
PRINTB(" FaceCycles: %6d", root_N->p2->explorer().number_of_face_cycles());
PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components());
}
if (root_N->dim == 3) {
PRINT(" Top level object is a 3D object:");
PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no"));
PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no"));
PRINTB(" Vertices: %6d", root_N->p3->number_of_vertices());
PRINTB(" Halfedges: %6d", root_N->p3->number_of_halfedges());
PRINTB(" Edges: %6d", root_N->p3->number_of_edges());
PRINTB(" Halffacets: %6d", root_N->p3->number_of_halffacets());
PRINTB(" Facets: %6d", root_N->p3->number_of_facets());
PRINTB(" Volumes: %6d", root_N->p3->number_of_volumes());
if (!root_N->isNull()) {
if (root_N->dim == 2) {
PRINT(" Top level object is a 2D object:");
PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no"));
PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no"));
PRINTB(" Vertices: %6d", root_N->p2->explorer().number_of_vertices());
PRINTB(" Halfedges: %6d", root_N->p2->explorer().number_of_halfedges());
PRINTB(" Edges: %6d", root_N->p2->explorer().number_of_edges());
PRINTB(" Faces: %6d", root_N->p2->explorer().number_of_faces());
PRINTB(" FaceCycles: %6d", root_N->p2->explorer().number_of_face_cycles());
PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components());
}
if (root_N->dim == 3) {
PRINT(" Top level object is a 3D object:");
PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no"));
PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no"));
PRINTB(" Vertices: %6d", root_N->p3->number_of_vertices());
PRINTB(" Halfedges: %6d", root_N->p3->number_of_halfedges());
PRINTB(" Edges: %6d", root_N->p3->number_of_edges());
PRINTB(" Halffacets: %6d", root_N->p3->number_of_halffacets());
PRINTB(" Facets: %6d", root_N->p3->number_of_facets());
PRINTB(" Volumes: %6d", root_N->p3->number_of_volumes());
}
}
int s = this->progresswidget->elapsedTime() / 1000;
PRINTB("Total rendering time: %d hours, %d minutes, %d seconds", (s / (60*60)) % ((s / 60) % 60) % (s % 60));
this->root_N = root_N;
if (!this->root_N->empty()) {
if (!this->root_N->isNull()) {
this->cgalRenderer = new CGALRenderer(*this->root_N);
// Go to CGAL view mode
if (viewActionCGALGrid->isChecked()) {

View File

@ -29,6 +29,7 @@ translate([24,0,0]) difference() {
translate([0,0,6.99]) cylinder(r=4, h=4, center=true);
}
// Subtracting something from nothing
translate([24,12,0]) difference() {
cube([0,10,10], center=true);
# cylinder(r=4, h=20, center=true);

View File

@ -773,7 +773,6 @@ set_test_config(Heavy opencsgtest_minkowski3-tests
cgalpngtest_minkowski3-tests
cgalpngtest_for-tests
cgalpngtest_for-nested-tests
cgalpngtest_difference-tests
cgalpngtest_intersection-tests)
foreach(FILE ${EXAMPLE_FILES})

View File

@ -153,30 +153,30 @@ int main(int argc, char **argv)
exit(1);
}
CGALRenderer cgalRenderer(N);
CGALRenderer cgalRenderer(N);
BoundingBox bbox;
if (cgalRenderer.polyhedron) {
CGAL::Bbox_3 cgalbbox = cgalRenderer.polyhedron->bbox();
bbox = BoundingBox(Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()),
Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax()));
Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax()));
}
else if (cgalRenderer.polyset) {
bbox = cgalRenderer.polyset->getBoundingBox();
}
Vector3d center = getBoundingCenter(bbox);
double radius = getBoundingRadius(bbox);
Vector3d cameradir(1, 1, -0.5);
Vector3d camerapos = center - radius*2*cameradir;
csgInfo.glview->setCamera(camerapos, center);
csgInfo.glview->setRenderer(&cgalRenderer);
csgInfo.glview->paintGL();
csgInfo.glview->save(outfile);
delete root_node;
delete root_module;

View File

@ -129,7 +129,7 @@ int main(int argc, char **argv)
CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node);
current_path(original_path);
if (!N.empty()) {
if (!N.isNull()) {
std::ofstream outfile;
outfile.open(outfilename);

View File

@ -122,7 +122,7 @@ int main(int argc, char **argv)
CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node);
current_path(original_path);
if (!N.empty()) {
if (!N.isNull()) {
export_stl(&N, std::cout);
}