diff options
-rw-r--r-- | dxfdata.cc | 213 | ||||
-rw-r--r-- | dxflinextrude.cc | 352 | ||||
-rw-r--r-- | lexer.l | 7 | ||||
-rw-r--r-- | mainwin.cc | 2 | ||||
-rw-r--r-- | module.cc | 1 | ||||
-rw-r--r-- | openscad.h | 28 | ||||
-rw-r--r-- | openscad.pro | 4 | ||||
-rw-r--r-- | polyset.cc | 20 |
8 files changed, 625 insertions, 2 deletions
diff --git a/dxfdata.cc b/dxfdata.cc new file mode 100644 index 0000000..06c2c3f --- /dev/null +++ b/dxfdata.cc @@ -0,0 +1,213 @@ +/* + * OpenSCAD (www.openscad.at) + * Copyright (C) 2009 Clifford Wolf <clifford@clifford.at> + * + * 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. + * + * 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 "openscad.h" + +#include <QFile> + +DxfData::DxfData(double /* fn */, double /* fs */, double /* fa */, QString filename, QString layername) +{ + QFile f(filename); + + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + PRINTF("WARNING: Can't open DXF file `%s'.", filename.toAscii().data()); + return; + } + + // WARNING: The algorithms used here are extreamly sub-optimal and perform + // as bad as O(n^3). So for reading large DXF paths one might consider optimizing + // the code in this function.. + QVector<Line> lines; + + QString mode, layer; + double x1 = 0, x2 = 0, y1 = 0, y2 = 0; + + while (!f.atEnd()) + { + QString id_str = QString(f.readLine()).remove("\n"); + QString data = QString(f.readLine()).remove("\n"); + + bool status; + int id = id_str.toInt(&status); + + if (!status) + break; + + switch (id) + { + case 0: + if (mode == "LINE" && (layername.isNull() || layername == layer)) { + lines.append(Line(p(x1, y1), p(x2, y2))); + } + mode = data; + break; + case 8: + layer = data; + break; + case 10: + x1 = data.toDouble(); + break; + case 11: + x2 = data.toDouble(); + break; + case 20: + y1 = data.toDouble(); + break; + case 21: + y2 = data.toDouble(); + break; + } + } + + // extract all open paths + while (lines.count() > 0) + { + int current_line, current_point; + + for (int i = 0; i < lines.count(); i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < lines.count(); k++) { + if (lines[i].p[j] == lines[k].p[0]) + goto next_open_path_j; + if (lines[i].p[j] == lines[k].p[1]) + goto next_open_path_j; + } + current_line = i; + current_point = j; + goto create_open_path; + next_open_path_j:; + } + } + + break; + + create_open_path: + paths.append(Path()); + Path *this_path = &paths.last(); + + this_path->points.append(lines[current_line].p[current_point]); + while (1) { + this_path->points.append(lines[current_line].p[!current_point]); + Point *ref_point = lines[current_line].p[!current_point]; + lines.remove(current_line); + for (int k = 0; k < lines.count(); k++) { + if (ref_point == lines[k].p[0]) { + current_line = k; + current_point = 0; + goto found_next_line_in_open_path; + } + if (ref_point == lines[k].p[1]) { + current_line = k; + current_point = 1; + goto found_next_line_in_open_path; + } + } + break; + found_next_line_in_open_path:; + } + } + + // extract all closed paths + while (lines.count() > 0) + { + int current_line = 0, current_point = 0; + + paths.append(Path()); + Path *this_path = &paths.last(); + this_path->is_closed = true; + + this_path->points.append(lines[current_line].p[current_point]); + while (1) { + this_path->points.append(lines[current_line].p[!current_point]); + Point *ref_point = lines[current_line].p[!current_point]; + lines.remove(current_line); + for (int k = 0; k < lines.count(); k++) { + if (ref_point == lines[k].p[0]) { + current_line = k; + current_point = 0; + goto found_next_line_in_closed_path; + } + if (ref_point == lines[k].p[1]) { + current_line = k; + current_point = 1; + goto found_next_line_in_closed_path; + } + } + break; + found_next_line_in_closed_path:; + } + } + + if (paths.count() > 0) { + double min_x1 = paths[0].points[0]->x; + int min_x_path = 0; + for (int i = 0; i < paths.count(); i++) { + if (!paths[i].is_closed) + break; + paths[i].is_inner = true; + double min_x2 = paths[i].points[0]->x; + int min_x_point = 0; + for (int j = 0; j < paths[i].points.count(); j++) { + if (paths[i].points[j]->x < min_x1) { + min_x1 = paths[i].points[j]->x; + min_x_path = i; + } + if (paths[i].points[j]->x < min_x2) { + min_x2 = paths[i].points[j]->x; + min_x_point = j; + } + } + // rotate points if the path is not in non-standard rotation + int b = min_x_point; + int a = b == 0 ? paths[i].points.count() - 1 : b - 1; + int c = b == paths[i].points.count() - 1 ? 0 : b + 1; + double ax = paths[i].points[a]->x - paths[i].points[b]->x; + double ay = paths[i].points[a]->y - paths[i].points[b]->y; + double cx = paths[i].points[c]->x - paths[i].points[c]->x; + double cy = paths[i].points[c]->y - paths[i].points[c]->y; + if (atan2(ax, ay) < atan2(cx, cy)) { + for (int j = 0; j < paths[i].points.count()/2; j++) + paths[i].points.swap(j, paths[i].points.count()-1-j); + } + } + paths[min_x_path].is_inner = false; + } + +#if 0 + printf("----- DXF Data -----\n"); + for (int i = 0; i < paths.count(); i++) { + printf("Path %d (%s, %s):\n", i, paths[i].is_closed ? "closed" : "open", paths[i].is_inner ? "inner" : "outer"); + for (int j = 0; j < paths[i].points.count(); j++) + printf(" %f %f\n", paths[i].points[j]->x, paths[i].points[j]->y); + } + printf("--------------------\n"); +#endif +} + +DxfData::Point *DxfData::p(double x, double y) +{ + for (int i = 0; i < points.count(); i++) { + if (abs(points[i].x - x) < 0.01 && abs(points[i].y - y) < 0.01) + return &points[i]; + } + points.append(Point(x, y)); + return &points[points.count()-1]; +} + diff --git a/dxflinextrude.cc b/dxflinextrude.cc new file mode 100644 index 0000000..c03da0a --- /dev/null +++ b/dxflinextrude.cc @@ -0,0 +1,352 @@ +/* + * OpenSCAD (www.openscad.at) + * Copyright (C) 2009 Clifford Wolf <clifford@clifford.at> + * + * 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. + * + * 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 + * + */ + +#define INCLUDE_ABSTRACT_NODE_DETAILS + +#include "openscad.h" + +class DxfLinearExtrudeModule : public AbstractModule +{ +public: + DxfLinearExtrudeModule() { } + virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstanciation *inst) const; +}; + +class DxfLinearExtrudeNode : public AbstractPolyNode +{ +public: + int convexity; + double fn, fs, fa, height; + QString filename, layername; + DxfLinearExtrudeNode(const ModuleInstanciation *mi) : AbstractPolyNode(mi) { } + virtual PolySet *render_polyset(render_mode_e mode) const; + virtual QString dump(QString indent) const; +}; + +AbstractNode *DxfLinearExtrudeModule::evaluate(const Context *ctx, const ModuleInstanciation *inst) const +{ + DxfLinearExtrudeNode *node = new DxfLinearExtrudeNode(inst); + + QVector<QString> argnames = QVector<QString>() << "file" << "layer" << "height"; + QVector<Expression*> argexpr; + + Context c(ctx); + c.args(argnames, argexpr, inst->argnames, inst->argvalues); + + node->fn = c.lookup_variable("$fn").num; + node->fs = c.lookup_variable("$fs").num; + node->fa = c.lookup_variable("$fa").num; + + Value file = c.lookup_variable("file"); + Value layer = c.lookup_variable("layer"); + Value height = c.lookup_variable("height"); + Value convexity = c.lookup_variable("convexity"); + + node->filename = file.text; + node->layername = layer.text; + node->height = height.num; + node->convexity = convexity.num; + + if (node->height <= 0) + node->height = 100; + + if (node->convexity <= 0) + node->convexity = 1; + + return node; +} + +void register_builtin_dxf_linear_extrude() +{ + builtin_modules["dxf_linear_extrude"] = new DxfLinearExtrudeModule(); +} + +static void add_slice(PolySet *ps, DxfData::Path *pt, double h1, double h2) +{ + for (int j = 1; j < pt->points.count(); j++) + { + int k = j - 1; + ps->append_poly(); + if (pt->is_inner) { + ps->append_vertex(pt->points[k]->x, pt->points[k]->y, h1); + ps->append_vertex(pt->points[j]->x, pt->points[j]->y, h1); + ps->append_vertex(pt->points[j]->x, pt->points[j]->y, h2); + } else { + ps->insert_vertex(pt->points[k]->x, pt->points[k]->y, h1); + ps->insert_vertex(pt->points[j]->x, pt->points[j]->y, h1); + ps->insert_vertex(pt->points[j]->x, pt->points[j]->y, h2); + } + + ps->append_poly(); + if (pt->is_inner) { + ps->append_vertex(pt->points[k]->x, pt->points[k]->y, h2); + ps->append_vertex(pt->points[k]->x, pt->points[k]->y, h1); + ps->append_vertex(pt->points[j]->x, pt->points[j]->y, h2); + } else { + ps->insert_vertex(pt->points[k]->x, pt->points[k]->y, h2); + ps->insert_vertex(pt->points[k]->x, pt->points[k]->y, h1); + ps->insert_vertex(pt->points[j]->x, pt->points[j]->y, h2); + } + } +} + +struct tess_vdata { + GLdouble v[3]; +}; + +struct tess_triangle { + GLdouble *p[3]; + tess_triangle() { p[0] = NULL; p[1] = NULL; p[2] = NULL; } + tess_triangle(double *p1, double *p2, double *p3) { p[0] = p1; p[1] = p2; p[2] = p3; } +}; + +static GLenum tess_type; +static int tess_count; +static QVector<tess_triangle> tess_tri; +static GLdouble *tess_p1, *tess_p2; + +static void tess_vertex(void *vertex_data) +{ + GLdouble *p = (double*)vertex_data; +#if 0 + printf(" %d: %f %f %f\n", tess_count, p[0], p[1], p[2]); +#endif + if (tess_type == GL_TRIANGLE_FAN) { + if (tess_count == 0) { + tess_p1 = p; + } + if (tess_count == 1) { + tess_p2 = p; + } + if (tess_count > 1) { + tess_tri.append(tess_triangle(tess_p1, tess_p2, p)); + tess_p2 = p; + } + } + if (tess_type == GL_TRIANGLE_STRIP) { + if (tess_count == 0) { + tess_p1 = p; + } + if (tess_count == 1) { + tess_p2 = p; + } + if (tess_count > 1) { + if (tess_count % 2 == 1) { + tess_tri.append(tess_triangle(tess_p2, tess_p1, p)); + } else { + tess_tri.append(tess_triangle(tess_p1, tess_p2, p)); + } + tess_p1 = tess_p2; + tess_p2 = p; + } + } + if (tess_type == GL_TRIANGLES) { + if (tess_count == 0) { + tess_p1 = p; + } + if (tess_count == 1) { + tess_p2 = p; + } + if (tess_count == 2) { + tess_tri.append(tess_triangle(tess_p1, tess_p2, p)); + tess_count = -1; + } + } + tess_count++; +} + +static void tess_begin(GLenum type) +{ +#if 0 + if (type == GL_TRIANGLE_FAN) { + printf("GL_TRIANGLE_FAN:\n"); + } + if (type == GL_TRIANGLE_STRIP) { + printf("GL_TRIANGLE_STRIP:\n"); + } + if (type == GL_TRIANGLES) { + printf("GL_TRIANGLES:\n"); + } +#endif + tess_count = 0; + tess_type = type; +} + +static void tess_end(void) +{ + /* nothing to be done here */ +} + +static void tess_error(GLenum errno) +{ + PRINTF("GLU tesselation error %d!", errno); +} + +static bool point_on_line(double *p1, double *p2, double *p3) +{ + if (fabs(p1[0] - p2[0]) < 0.0001 && fabs(p1[1] - p2[1]) < 0.0001) + return false; + + if (fabs(p3[0] - p2[0]) < 0.0001 && fabs(p3[1] - p2[1]) < 0.0001) + return false; + + double v1[2] = { p2[0] - p1[0], p2[1] - p1[1] }; + double v2[2] = { p3[0] - p1[0], p3[1] - p1[1] }; + + if (fabs(atan2(v1[0], v1[1]) - atan2(v2[0], v2[1])) < 0.0001 && + sqrt(v1[0]*v1[0] + v1[1]*v1[1]) < sqrt(v2[0]*v2[0] + v2[1]*v2[1])) { +#if 0 + printf("Point on line: %f/%f %f/%f %f/%f\n", p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]); +#endif + return true; + } + + return false; +} + +static void tess(PolySet *ps, DxfData *dxf, bool up, double h) +{ + GLUtesselator *tobj = gluNewTess(); + + gluTessCallback(tobj, GLU_TESS_VERTEX, (GLvoid(*)())&tess_vertex); + gluTessCallback(tobj, GLU_TESS_BEGIN, (GLvoid(*)())&tess_begin); + gluTessCallback(tobj, GLU_TESS_END, (GLvoid(*)())&tess_end); + gluTessCallback(tobj, GLU_TESS_ERROR, (GLvoid(*)())&tess_error); + + tess_tri.clear(); + QList<tess_vdata> vl; + + gluTessBeginPolygon(tobj, NULL); + + for (int i = 0; i < dxf->paths.count(); i++) { + if (!dxf->paths[i].is_closed) + continue; + gluTessBeginContour(tobj); + if (up != dxf->paths[i].is_inner) { + for (int j = 1; j < dxf->paths[i].points.count(); j++) { + vl.append(tess_vdata()); + vl.last().v[0] = dxf->paths[i].points[j]->x; + vl.last().v[1] = dxf->paths[i].points[j]->y; + vl.last().v[2] = h; + gluTessVertex(tobj, vl.last().v, vl.last().v); + } + } else { + for (int j = dxf->paths[i].points.count() - 1; j > 0; j--) { + vl.append(tess_vdata()); + vl.last().v[0] = dxf->paths[i].points[j]->x; + vl.last().v[1] = dxf->paths[i].points[j]->y; + vl.last().v[2] = h; + gluTessVertex(tobj, vl.last().v, vl.last().v); + } + } + gluTessEndContour(tobj); + } + + gluTessEndPolygon(tobj); + gluDeleteTess(tobj); + +#if 0 + for (int i = 0; i < tess_tri.count(); i++) { + printf("~~~\n"); + printf(" %f %f %f\n", tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]); + printf(" %f %f %f\n", tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]); + printf(" %f %f %f\n", tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]); + } +#endif + + // GLU tessing sometimes generates degenerated triangles. We must find and remove + // them so we can use the triangle array with CGAL.. + for (int i = 0; i < tess_tri.count(); i++) { + if (point_on_line(tess_tri[i].p[0], tess_tri[i].p[1], tess_tri[i].p[2]) || + point_on_line(tess_tri[i].p[1], tess_tri[i].p[2], tess_tri[i].p[0]) || + point_on_line(tess_tri[i].p[2], tess_tri[i].p[0], tess_tri[i].p[1])) { + tess_tri.remove(i--); + } + } + + // GLU tessing might merge points into edges. This is ok for GL displaying but + // creates invalid polyeders for CGAL. So we split this tirangles up again in order + // to create polyeders that are also accepted by CGAL.. + bool added_triangles = true; + while (added_triangles) + { + added_triangles = false; + for (int i = 0; i < tess_tri.count(); i++) + for (int j = 0; j < tess_tri.count(); j++) + for (int k = 0; k < 3; k++) + for (int l = 0; l < 3; l++) { + if (point_on_line(tess_tri[i].p[k], tess_tri[j].p[l], tess_tri[i].p[(k+1)%3])) { + tess_tri.append(tess_triangle(tess_tri[j].p[l], + tess_tri[i].p[(k+1)%3], tess_tri[i].p[(k+2)%3])); + tess_tri[i].p[(k+1)%3] = tess_tri[j].p[l]; + added_triangles = true; + } + } + } + + for (int i = 0; i < tess_tri.count(); i++) { +#if 0 + printf("---\n"); + printf(" %f %f %f\n", tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]); + printf(" %f %f %f\n", tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]); + printf(" %f %f %f\n", tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]); +#endif + ps->append_poly(); + ps->insert_vertex(tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]); + ps->insert_vertex(tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]); + ps->insert_vertex(tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]); + } + + tess_tri.clear(); +} + +PolySet *DxfLinearExtrudeNode::render_polyset(render_mode_e) const +{ + DxfData dxf(fn, fs, fa, filename, layername); + + PolySet *ps = new PolySet(); + ps->convexity = convexity; + + for (int i = 0; i < dxf.paths.count(); i++) + { + if (!dxf.paths[i].is_closed) + continue; + add_slice(ps, &dxf.paths[i], 0, height); + } + + tess(ps, &dxf, false, 0); + tess(ps, &dxf, true, height); + + return ps; +} + +QString DxfLinearExtrudeNode::dump(QString indent) const +{ + if (dump_cache.isEmpty()) { + QString text; + text.sprintf("dxf_linear_extrude(file = \"%s\", layer = \"%s\", height = %f, " + "$fn = %f, $fa = %f, $fs = %f);\n", + filename.toAscii().data(), layername.toAscii().data(), + height, fn, fs, fa); + ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; + } + return dump_cache; +} + @@ -54,7 +54,12 @@ extern const char *parser_input_buffer; [+-]?[0-9][0-9.]* { parserlval.number = atof(yytext); return TOK_NUMBER; } "$"?[a-zA-Z0-9_]+ { parserlval.text = strdup(yytext); return TOK_ID; } -\"[^"]*\" { parserlval.text = strdup(yytext); return TOK_STRING; } + +\"[^"]*\" { + parserlval.text = strdup(yytext+1); + parserlval.text[strlen(parserlval.text)-1] = 0; + return TOK_STRING; +} [\n\r\t ] \/\/[^\n]*\n @@ -645,6 +645,7 @@ void MainWindow::actionExportSTL() { current_win = this; +#ifdef ENABLE_CGAL if (!root_N) { PRINT("Nothing to export! Try building first (press F6)."); current_win = NULL; @@ -736,6 +737,7 @@ void MainWindow::actionExportSTL() PRINT("STL export finished."); delete pd; +#endif /* ENABLE_CGAL */ current_win = NULL; } @@ -191,6 +191,7 @@ void initialize_builtin_modules() register_builtin_primitives(); register_builtin_control(); register_builtin_render(); + register_builtin_dxf_linear_extrude(); } void destroy_builtin_modules() @@ -264,6 +264,7 @@ extern void register_builtin_transform(); extern void register_builtin_primitives(); extern void register_builtin_control(); extern void register_builtin_render(); +extern void register_builtin_dxf_linear_extrude(); class Context { @@ -288,6 +289,33 @@ public: AbstractNode *evaluate_module(const ModuleInstanciation *inst) const; }; +class DxfData +{ +public: + struct Point { + double x, y; + Point() : x(0), y(0) { } + Point(double x, double y) : x(x), y(y) { } + }; + struct Line { + Point *p[2]; + Line(Point *p1, Point *p2) { p[0] = p1; p[1] = p2; } + Line() { p[0] = NULL; p[1] = NULL; } + }; + struct Path { + QList<Point*> points; + bool is_closed, is_inner; + Path() : is_closed(false), is_inner(false) { } + }; + + QList<Point> points; + QList<Path> paths; + + DxfData(double fn, double fs, double fa, QString filename, QString layername = QString()); + + Point *p(double x, double y); +}; + // The CGAL template magic slows down the compilation process by a factor of 5. // So we only include the declaration of AbstractNode where it is needed... #ifdef INCLUDE_ABSTRACT_NODE_DETAILS diff --git a/openscad.pro b/openscad.pro index cd990e1..3d24bd8 100644 --- a/openscad.pro +++ b/openscad.pro @@ -16,8 +16,10 @@ SOURCES += openscad.cc mainwin.cc glview.cc SOURCES += value.cc expr.cc func.cc module.cc context.cc SOURCES += csgterm.cc polyset.cc csgops.cc transform.cc SOURCES += primitives.cc control.cc render.cc +SOURCES += dxfdata.cc dxflinextrude.cc -QMAKE_CXXFLAGS += -O3 -march=pentium +QMAKE_CXXFLAGS += -O0 +// QMAKE_CXXFLAGS += -O3 -march=pentium // QMAKE_CXXFLAGS += -O3 -march=athlon64 QT += opengl @@ -180,6 +180,8 @@ void PolySet::render_edges(colormode_e colormode) const #ifdef ENABLE_CGAL +#undef GEN_SURFACE_DEBUG + class CGAL_Build_PolySet : public CGAL::Modifier_base<CGAL_HDS> { public: @@ -211,23 +213,41 @@ public: } B.begin_surface(vertices.size(), ps->polygons.size()); +#ifdef GEN_SURFACE_DEBUG + printf("=== CGAL Surface ===\n"); +#endif for (int i = 0; i < vertices.size(); i++) { const PolySet::Point *p = &vertices[i]; B.add_vertex(Point(p->x, p->y, p->z)); +#ifdef GEN_SURFACE_DEBUG + printf("%d: %f %f %f\n", i, p->x, p->y, p->z); +#endif } for (int i = 0; i < ps->polygons.size(); i++) { const PolySet::Polygon *poly = &ps->polygons[i]; B.begin_facet(); +#ifdef GEN_SURFACE_DEBUG + printf("F:"); +#endif for (int j = 0; j < poly->size(); j++) { const PolySet::Point *p = &poly->at(j); PointKey_t pk = PointKey(p->x, p->y, p->z); +#ifdef GEN_SURFACE_DEBUG + printf(" %d", vertices_idx[pk]); +#endif B.add_vertex_to_facet(vertices_idx[pk]); } +#ifdef GEN_SURFACE_DEBUG + printf("\n"); +#endif B.end_facet(); } +#ifdef GEN_SURFACE_DEBUG + printf("====================\n"); +#endif B.end_surface(); #undef PointKey |