diff options
author | don bright <hugh.m.bright@gmail.com> | 2013-03-10 02:28:43 (GMT) |
---|---|---|
committer | don bright <hugh.m.bright@gmail.com> | 2013-03-10 02:28:43 (GMT) |
commit | 3cf6c24d834295eb9f409cece0b9aec8f2296fa2 (patch) | |
tree | 7e8eb6c567b5fdcbbf8cee532b7858adc9b4f08d | |
parent | 1221b66edb06e1b4f009b0ce3ebee1fb1651aa4e (diff) |
beginning of resize() command implementation.
-rw-r--r-- | src/CGALEvaluator.cc | 36 | ||||
-rw-r--r-- | src/CGALEvaluator.h | 1 | ||||
-rw-r--r-- | src/CGAL_Nef_polyhedron.h | 2 | ||||
-rw-r--r-- | src/CGAL_Nef_polyhedron_DxfData.cc | 82 | ||||
-rw-r--r-- | src/PolySetCGALEvaluator.cc | 116 | ||||
-rw-r--r-- | src/cgaladv.cc | 21 | ||||
-rw-r--r-- | src/cgaladvnode.h | 5 | ||||
-rw-r--r-- | src/cgalutils.cc | 4 | ||||
-rw-r--r-- | src/cgalutils.h | 103 | ||||
-rw-r--r-- | testdata/scad/features/resize-tests.scad | 42 |
10 files changed, 293 insertions, 119 deletions
diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 4deb3b3..b0984ce 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -179,6 +179,39 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) return N; } + +CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) +{ + // Based on resize() in Giles Bathgate's RapCAD + CGAL_Nef_polyhedron N; + N = applyToChildren(node, CGE_UNION); + if (N.isNull()) { + PRINT("WARNING: resize() of null polyhedron"); + return N; + } + + int dim = N.dim; + if (dim==2) N.convertTo3d(); + + CGAL_Iso_cuboid_3 bb = bounding_box( *N.p3 ); + Eigen::Matrix<NT,3,1> scale, bbox_size; + scale << 1,1,1; + bbox_size << bb.xmax()-bb.xmin(), bb.ymax()-bb.ymin(), bb.zmax()-bb.zmin(); + for (int i=0;i<3;i++) + if (node.newsize[i] && bbox_size[i]!=NT(0)) + scale[i] = NT(node.newsize[i]) / NT(bbox_size[i]); + CGAL_Aff_transformation t( scale[0], 0, 0, 0, + 0, scale[1], 0, 0, + 0, 0, scale[2], 0, 1); + N.p3->transform( t ); + + if (dim==2) N.convertTo2d(); + + return N; +} + + + /* Typical visitor behavior: o In prefix: Check if we're cached -> prune @@ -358,6 +391,9 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) case HULL: N = applyHull(node); break; + case RESIZE: + N = applyResize(node); + break; } } else { diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index 42af5a1..818f520 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -34,6 +34,7 @@ private: void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CGALEvaluator::CsgOp op); CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op); CGAL_Nef_polyhedron applyHull(const CgaladvNode &node); + CGAL_Nef_polyhedron applyResize(const CgaladvNode &node); std::string currindent; typedef std::pair<const AbstractNode *, CGAL_Nef_polyhedron> ChildItem; diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index d949a2a..03eaaa6 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -27,6 +27,8 @@ public: int weight() const; class PolySet *convertToPolyset(); class DxfData *convertToDxfData() const; + void convertTo2d(); + void convertTo3d(); int dim; shared_ptr<CGAL_Nef_polyhedron2> p2; diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 0d0b8f0..e642612 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -29,7 +29,6 @@ #include "CGAL_Nef_polyhedron.h" #include "cgal.h" #include "cgalutils.h" -#include "svg.h" #ifdef ENABLE_CGAL @@ -89,4 +88,85 @@ std::string CGAL_Nef_polyhedron::dump() const return std::string("Nef Polyhedron with dimension != 2 or 3"); } +// use a try/catch block around any calls to this +void CGAL_Nef_polyhedron::convertTo2d() +{ + logstream log(5); + if (dim!=3) return; + assert(this->p3); + ZRemover zremover; + CGAL_Nef_polyhedron3::Volume_const_iterator i; + CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; + CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; + for ( i = this->p3->volumes_begin(); i != this->p3->volumes_end(); ++i ) { + log << "<!-- volume begin. mark: " << i->mark() << " -->\n"; + for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { + log << "<!-- shell. mark: " << i->mark() << " -->\n"; + sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); + this->p3->visit_shell_objects( sface_handle , zremover ); + log << "<!-- shell. end. -->\n"; + } + log << "<!-- volume end. -->\n"; + } + this->p3.reset(); + this->p2 = zremover.output_nefpoly2d; + this->dim = 2; +} + + +std::vector<CGAL_Point_3> face2to3( + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c1, + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c2, + CGAL_Nef_polyhedron2::Explorer explorer ) +{ + std::vector<CGAL_Point_3> result; + CGAL_For_all(c1, c2) { + if ( explorer.is_standard( explorer.target(c1) ) ) { + //CGAL_Point_2e source = explorer.point( explorer.source( c1 ) ); + CGAL_Point_2e target = explorer.point( explorer.target( c1 ) ); + if (c1->mark()) { + CGAL_Point_3 tmp( target.x(), target.y(), 0 ); + result.push_back( tmp ); + } + } + } + return result; +} + + +// use a try/catch block around any calls to this +void CGAL_Nef_polyhedron::convertTo3d() +{ + if (dim!=2) return; + assert(this->p2); + CGAL_Nef_polyhedron2::Explorer explorer = this->p2->explorer(); + CGAL_Nef_polyhedron2::Explorer::Face_const_iterator i; + + this->p3.reset( new CGAL_Nef_polyhedron3() ); + + for ( i = explorer.faces_begin(); i!= explorer.faces_end(); ++i ) { + + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c1 + = explorer.face_cycle( i ), c2 ( c1 ); + std::vector<CGAL_Point_3> body_pts = face2to3( c1, c2, explorer ); + CGAL_Nef_polyhedron3 body( body_pts.begin(), body_pts.end() ); + + CGAL_Nef_polyhedron3 holes; + CGAL_Nef_polyhedron2::Explorer::Hole_const_iterator j; + for ( j = explorer.holes_begin( i ); j!= explorer.holes_end( i ); ++j ) { + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c3( j ), c4 ( c3 ); + std::vector<CGAL_Point_3> hole_pts = face2to3( c3, c4, explorer ); + CGAL_Nef_polyhedron3 hole( hole_pts.begin(), hole_pts.end() ); + holes = holes.join( hole ); + } + + body = body.difference( holes ); + *(this->p3) = this->p3->join( body ); + } + + this->p2.reset(); + this->dim = 3; +} + + #endif // ENABLE_CGAL diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 224e657..0aa9e9e 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -20,104 +20,6 @@ #include <boost/foreach.hpp> #include <vector> -/* - -ZRemover - -This class converts one or more already 'flat' Nef3 polyhedra into a Nef2 -polyhedron by stripping off the 'z' coordinates from the vertices. The -resulting Nef2 poly is accumulated in the 'output_nefpoly2d' member variable. - -The 'z' coordinates will either be all 0s, for an xy-plane intersected Nef3, -or, they will be a mixture of -eps and +eps, for a thin-box intersected Nef3. - -Notes on CGAL's Nef Polyhedron2: - -1. The 'mark' on a 2d Nef face is important when doing unions/intersections. - If the 'mark' of a face is wrong the resulting nef2 poly will be unexpected. -2. The 'mark' can be dependent on the points fed to the Nef2 constructor. - This is why we iterate through the 3d faces using the halfedge cycle - source()->target() instead of the ordinary source()->source(). The - the latter can generate sequences of points that will fail the - the CGAL::is_simple_2() test, resulting in improperly marked nef2 polys. -3. 3d facets have 'two sides'. we throw out the 'down' side to prevent dups. - -The class uses the 'visitor' pattern from the CGAL manual. See also -http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html -http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3_ref/Class_Nef_polyhedron3.html -OGL_helper.h -*/ - -class ZRemover { -public: - logstream log; - CGAL_Nef_polyhedron2::Boundary boundary; - shared_ptr<CGAL_Nef_polyhedron2> tmpnef2d; - shared_ptr<CGAL_Nef_polyhedron2> output_nefpoly2d; - CGAL::Direction_3<CGAL_Kernel3> up; - ZRemover() - { - output_nefpoly2d.reset( new CGAL_Nef_polyhedron2() ); - boundary = CGAL_Nef_polyhedron2::INCLUDED; - up = CGAL::Direction_3<CGAL_Kernel3>(0,0,1); - log = logstream(5); - } - void visit( CGAL_Nef_polyhedron3::Vertex_const_handle ) {} - void visit( CGAL_Nef_polyhedron3::Halfedge_const_handle ) {} - void visit( CGAL_Nef_polyhedron3::SHalfedge_const_handle ) {} - void visit( CGAL_Nef_polyhedron3::SHalfloop_const_handle ) {} - void visit( CGAL_Nef_polyhedron3::SFace_const_handle ) {} - void visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) { - log << " <!-- Halffacet visit. Mark: " << hfacet->mark() << " -->\n"; - if ( hfacet->plane().orthogonal_direction() != this->up ) { - log << " <!-- down-facing half-facet. skipping -->\n"; - log << " <!-- Halffacet visit end-->\n"; - return; - } - - // possible optimization - throw out facets that are 'side facets' between - // the top & bottom of the big thin box. (i.e. mixture of z=-eps and z=eps) - - CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator fci; - int contour_counter = 0; - CGAL_forall_facet_cycles_of( fci, hfacet ) { - if ( fci.is_shalfedge() ) { - CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(fci), cend(c1); - std::vector<CGAL_Nef_polyhedron2::Explorer::Point> contour; - CGAL_For_all( c1, cend ) { - CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->target()->point(); - CGAL_Nef_polyhedron2::Explorer::Point point2d( point3d.x(), point3d.y() ); - contour.push_back( point2d ); - } - - if (contour.size()==0) continue; - - log << " <!-- is_simple_2:" << CGAL::is_simple_2( contour.begin(), contour.end() ) << " --> \n"; - - tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) ); - - if ( contour_counter == 0 ) { - log << " <!-- contour is a body. make union(). " << contour.size() << " points. -->\n" ; - *(output_nefpoly2d) += *(tmpnef2d); - } else { - log << " <!-- contour is a hole. make intersection(). " << contour.size() << " points. -->\n"; - *(output_nefpoly2d) *= *(tmpnef2d); - } - - log << "\n<!-- ======== output tmp nef: ==== -->\n" - << OpenSCAD::dump_svg( *tmpnef2d ) << "\n" - << "\n<!-- ======== output accumulator: ==== -->\n" - << OpenSCAD::dump_svg( *output_nefpoly2d ) << "\n"; - - contour_counter++; - } else { - log << " <!-- trivial facet cycle skipped -->\n"; - } - } // next facet cycle (i.e. next contour) - log << " <!-- Halffacet visit end -->\n"; - } // visit() -}; - PolySetCGALEvaluator::PolySetCGALEvaluator(CGALEvaluator &cgalevaluator) : PolySetEvaluator(cgalevaluator.getTree()), cgalevaluator(cgalevaluator) { @@ -186,24 +88,10 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) return NULL; } - // remove z coordinates to make CGAL_Nef_polyhedron2 log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; try { - ZRemover zremover; - CGAL_Nef_polyhedron3::Volume_const_iterator i; - CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; - CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; - for ( i = sum.p3->volumes_begin(); i != sum.p3->volumes_end(); ++i ) { - log << "<!-- volume. mark: " << i->mark() << " -->\n"; - for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { - log << "<!-- shell. mark: " << i->mark() << " -->\n"; - sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); - sum.p3->visit_shell_objects( sface_handle , zremover ); - log << "<!-- shell. end. -->\n"; - } - log << "<!-- volume end. -->\n"; - } - nef_poly.p2 = zremover.output_nefpoly2d; + sum.convertTo2d(); + nef_poly.p2 = sum.p2; } catch (const CGAL::Failure_exception &e) { PRINTB("CGAL error in projection node while flattening: %s", e.what()); } diff --git a/src/cgaladv.cc b/src/cgaladv.cc index 1773a90..073a908 100644 --- a/src/cgaladv.cc +++ b/src/cgaladv.cc @@ -58,6 +58,9 @@ AbstractNode *CgaladvModule::evaluate(const Context *ctx, const ModuleInstantiat if (type == SUBDIV) argnames += "type", "level", "convexity"; + if (type == RESIZE) + argnames += "newsize"; + Context c(ctx); c.args(argnames, argexpr, inst->argnames, inst->argvalues); @@ -78,6 +81,17 @@ AbstractNode *CgaladvModule::evaluate(const Context *ctx, const ModuleInstantiat level = c.lookup_variable("level", true); } + if (type == RESIZE) { + Value ns = c.lookup_variable("newsize"); + node->newsize << 0,0,0; + if ( ns.type() == Value::VECTOR ) { + Value::VectorType v = ns.toVector(); + if ( v.size() >= 1 ) node->newsize[0] = v[0].toDouble(); + if ( v.size() >= 2 ) node->newsize[1] = v[1].toDouble(); + if ( v.size() >= 3 ) node->newsize[2] = v[2].toDouble(); + } + } + node->convexity = (int)convexity.toDouble(); node->path = path; node->subdiv_type = subdiv_type.toString(); @@ -112,6 +126,9 @@ std::string CgaladvNode::name() const case HULL: return "hull"; break; + case RESIZE: + return "resize"; + break; default: assert(false); } @@ -135,6 +152,9 @@ std::string CgaladvNode::toString() const case HULL: stream << "()"; break; + case RESIZE: + stream << "(newsize = " << this->newsize << ")"; + break; default: assert(false); } @@ -148,4 +168,5 @@ void register_builtin_cgaladv() Builtins::init("glide", new CgaladvModule(GLIDE)); Builtins::init("subdiv", new CgaladvModule(SUBDIV)); Builtins::init("hull", new CgaladvModule(HULL)); + Builtins::init("resize", new CgaladvModule(RESIZE)); } diff --git a/src/cgaladvnode.h b/src/cgaladvnode.h index 8e769bf..097d2b4 100644 --- a/src/cgaladvnode.h +++ b/src/cgaladvnode.h @@ -4,12 +4,14 @@ #include "node.h" #include "visitor.h" #include "value.h" +#include "linalg.h" enum cgaladv_type_e { MINKOWSKI, GLIDE, SUBDIV, - HULL + HULL, + RESIZE }; class CgaladvNode : public AbstractNode @@ -29,6 +31,7 @@ public: Value path; std::string subdiv_type; int convexity, level; + Vector3d newsize; cgaladv_type_e type; }; diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 51838df..66d4b18 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -147,7 +147,7 @@ CGAL_Polyhedron *createPolyhedronFromPolySet(const PolySet &ps) CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ) { - CGAL_Iso_cuboid_3 result(-1,-1,-1,1,1,1); + CGAL_Iso_cuboid_3 result(0,0,0,0,0,0); CGAL_Nef_polyhedron3::Vertex_const_iterator vi; std::vector<CGAL_Nef_polyhedron3::Point_3> points; // can be optimized by rewriting bounding_box to accept vertices @@ -160,7 +160,7 @@ CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ) CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ) { - CGAL_Iso_rectangle_2e result(-1,-1,1,1); + CGAL_Iso_rectangle_2e result(0,0,0,0); CGAL_Nef_polyhedron2::Explorer explorer = N.explorer(); CGAL_Nef_polyhedron2::Explorer::Vertex_const_iterator vi; std::vector<CGAL_Point_2e> points; diff --git a/src/cgalutils.h b/src/cgalutils.h index 9093c3f..8f10519 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -2,10 +2,111 @@ #define CGALUTILS_H_ #include <cgalfwd.h> - class PolySet *createPolySetFromPolyhedron(const CGAL_Polyhedron &p); CGAL_Polyhedron *createPolyhedronFromPolySet(const class PolySet &ps); CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ); +#include "svg.h" +#include "printutils.h" + +/* + +ZRemover + +This class converts one or more Nef3 polyhedra into a Nef2 polyhedron by +stripping off the 'z' coordinates from the vertices. The resulting Nef2 +poly is accumulated in the 'output_nefpoly2d' member variable. + +The 'z' coordinates will either be all 0s, for an xy-plane intersected Nef3, +or, they will be a mixture of -eps and +eps, for a thin-box intersected Nef3. + +Notes on CGAL's Nef Polyhedron2: + +1. The 'mark' on a 2d Nef face is important when doing unions/intersections. + If the 'mark' of a face is wrong the resulting nef2 poly will be unexpected. +2. The 'mark' can be dependent on the points fed to the Nef2 constructor. + This is why we iterate through the 3d faces using the halfedge cycle + source()->target() instead of the ordinary source()->source(). The + the latter can generate sequences of points that will fail the + the CGAL::is_simple_2() test, resulting in improperly marked nef2 polys. +3. 3d facets have 'two sides'. we throw out the 'down' side to prevent dups. + +The class uses the 'visitor' pattern from the CGAL manual. See also +http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html +http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3_ref/Class_Nef_polyhedron3.html +OGL_helper.h +*/ + +class ZRemover { +public: + logstream log; + CGAL_Nef_polyhedron2::Boundary boundary; + boost::shared_ptr<CGAL_Nef_polyhedron2> tmpnef2d; + boost::shared_ptr<CGAL_Nef_polyhedron2> output_nefpoly2d; + CGAL::Direction_3<CGAL_Kernel3> up; + ZRemover() + { + output_nefpoly2d.reset( new CGAL_Nef_polyhedron2() ); + boundary = CGAL_Nef_polyhedron2::INCLUDED; + up = CGAL::Direction_3<CGAL_Kernel3>(0,0,1); + log = logstream(5); + } + void visit( CGAL_Nef_polyhedron3::Vertex_const_handle ) {} + void visit( CGAL_Nef_polyhedron3::Halfedge_const_handle ) {} + void visit( CGAL_Nef_polyhedron3::SHalfedge_const_handle ) {} + void visit( CGAL_Nef_polyhedron3::SHalfloop_const_handle ) {} + void visit( CGAL_Nef_polyhedron3::SFace_const_handle ) {} + void visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) { + log << " <!-- Halffacet visit. Mark: " << hfacet->mark() << " -->\n"; + if ( hfacet->plane().orthogonal_direction() != this->up ) { + log << " <!-- down-facing half-facet. skipping -->\n"; + log << " <!-- Halffacet visit end-->\n"; + return; + } + + // possible optimization - throw out facets that are 'side facets' between + // the top & bottom of the big thin box. (i.e. mixture of z=-eps and z=eps) + + CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator fci; + int contour_counter = 0; + CGAL_forall_facet_cycles_of( fci, hfacet ) { + if ( fci.is_shalfedge() ) { + CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(fci), cend(c1); + std::vector<CGAL_Nef_polyhedron2::Explorer::Point> contour; + CGAL_For_all( c1, cend ) { + CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->target()->point(); + CGAL_Nef_polyhedron2::Explorer::Point point2d( point3d.x(), point3d.y() ); + contour.push_back( point2d ); + } + + if (contour.size()==0) continue; + + log << " <!-- is_simple_2:" << CGAL::is_simple_2( contour.begin(), contour.end() ) << " --> \n"; + + tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) ); + + if ( contour_counter == 0 ) { + log << " <!-- contour is a body. make union(). " << contour.size() << " points. -->\n" ; + *(output_nefpoly2d) += *(tmpnef2d); + } else { + log << " <!-- contour is a hole. make intersection(). " << contour.size() << " points. -->\n"; + *(output_nefpoly2d) *= *(tmpnef2d); + } + + log << "\n<!-- ======== output tmp nef: ==== -->\n" + << OpenSCAD::dump_svg( *tmpnef2d ) << "\n" + << "\n<!-- ======== output accumulator: ==== -->\n" + << OpenSCAD::dump_svg( *output_nefpoly2d ) << "\n"; + + contour_counter++; + } else { + log << " <!-- trivial facet cycle skipped -->\n"; + } + } // next facet cycle (i.e. next contour) + log << " <!-- Halffacet visit end -->\n"; + } // visit() +}; + + #endif diff --git a/testdata/scad/features/resize-tests.scad b/testdata/scad/features/resize-tests.scad new file mode 100644 index 0000000..f2e9148 --- /dev/null +++ b/testdata/scad/features/resize-tests.scad @@ -0,0 +1,42 @@ +// bottom row = reference +// middle row = should match reference +// top row = should be inscribed in middle row in 'top' view + +$fn=10; + +color("red") { +translate([0, 0,-10]) cube(); +translate([0,10,-10]) cube([5,1,1]); +translate([0,20,-10]) cube([1,6,1]); +translate([0,30,-10]) cube([1,1,7]); +translate([0,40,-10]) cube([5,6,1]); +translate([0,60,-10]) cube([1,6,7]); +translate([0,50,-10]) cube([5,1,7]); +translate([0,70,-10]) cube([8,9,1]); +translate([0,80,-10]) cube([9,1,1]); +translate([0,90,-10]) cube([5,6,1]); +} + +translate([0, 0,0]) cube(); +translate([0,10,0]) resize([5,0,0]) cube(); +translate([0,20,0]) resize([0,6,0]) cube(); +translate([0,30,0]) resize([0,0,7]) cube(); +translate([0,40,0]) resize([5,6,0]) cube(); +translate([0,60,0]) resize([0,6,7]) cube(); +translate([0,50,0]) resize([5,0,7]) cube(); +translate([0,70,0]) resize([8,9]) cube(); +translate([0,80,0]) resize([9]) cube(); +translate([0,90,0]) resize([5,6,7]) cube(); + +color("blue"){ +translate([0, 0,10]) cube(); +translate([2.5,10.5,10]) resize([5,0,0]) sphere(0.5); +translate([0.5,23,10]) resize([0,6,0]) sphere(0.5); +translate([0.5,30.5,10]) resize([0,0,7]) sphere(0.5); +translate([2.5,43,10]) resize([5,6,0]) sphere(0.5); +translate([0.5,63,10]) resize([0,6,7]) sphere(0.5); +translate([2.5,50.5,10]) resize([5,0,7]) sphere(0.5); +translate([4,74.5,10]) resize([8,9]) sphere(0.5); +translate([4.5,80.5,10]) resize([9]) sphere(0.5); +translate([2.5,93,10]) resize([5,6,7]) sphere(0.5); +}
\ No newline at end of file |