Skip to content
Browse files

Merge branch 'sp_persistent_holes_20130219'

* sp_persistent_holes_20130219:
  readme
  Added Hole to README.md; Improved headline hierarchy
  -- Changed add() so that objects are always copied when re-used in Python code.  This was needed so that we could re-use hole() objects without having to copy() them first -- Added hole_example.py
  Made hole transforms more efficient
  Added inefficient but functional hole behavior.  Calling obj.set_hole( True) for any SolidPython object will cause it to be subtracted from the scene after everything else.
  very littered first pass at persistent holes.
  • Loading branch information...
2 parents 9369e78 + b38f200 commit a918209ed5b7501f73565780055899ee2f2eaa75 @etjones etjones committed Feb 19, 2013
Showing with 176 additions and 37 deletions.
  1. +32 −23 README.md
  2. +57 −0 solid/examples/hole_example.py
  3. +87 −14 solid/solidpython.py
View
55 README.md
@@ -7,6 +7,7 @@ SolidPython
- [Example Code](#example-code)
- [Extra syntactic sugar](#extra-syntactic-sugar)
- [Basic operators](#basic-operators)
+ - [First-class Negative Space (Holes)](#first-class-negative-space-holes)
- [solid.utils](#solidutils)
- [Directions: (up, down, left, right, forward, back) for arranging things:](#directions-up-down-left-right-forward-back-for-arranging-things)
- [Arcs](#arcs)
@@ -20,8 +21,7 @@ SolidPython
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
-SolidPython: OpenSCAD for Python<a id="solidpython--openscad-for-python"></a>
------------------------
+# SolidPython: OpenSCAD for Python<a id="solidpython--openscad-for-python"></a>
SolidPython is a generalization of Phillip Tiefenbacher's openscad module,
found on [Thingiverse](http://www.thingiverse.com/thing:1481). It generates valid OpenSCAD
@@ -61,8 +61,7 @@ Generates this OpenSCAD code:
cylinder( r=2, h=6);
}
-Advantages<a id="advantages"></a>
-----------
+# Advantages<a id="advantages"></a>
Because you're using Python, a lot of things are easy that would be hard or
impossible in pure OpenSCAD. Among these are:
@@ -71,8 +70,7 @@ impossible in pure OpenSCAD. Among these are:
* recursion
* external libraries (images! 3D geometry! web-scraping! ...)
-Installing SolidPython<a id="installing-solidpython"></a>
-----------------------
+# Installing SolidPython<a id="installing-solidpython"></a>
* Install via [PyPI](python setup.py sdist bdist_wininst upload):
sudo easy_install solidpython
@@ -93,8 +91,7 @@ Installing SolidPython<a id="installing-solidpython"></a>
sudo python setup.py --install
-Using SolidPython<a id="using-solidpython"></a>
--------------------------
+# Using SolidPython<a id="using-solidpython"></a>
* Include SolidPython at the top of your Python file:
from solid import *
@@ -127,8 +124,7 @@ Using SolidPython<a id="using-solidpython"></a>
* Alternately, you could call OpenSCAD's command line and render straight
to STL.
-Example Code<a id="example-code"></a>
-------------
+# Example Code<a id="example-code"></a>
The best way to learn how SolidPython works is to look at the included example code.
If you've installed SolidPython, the following line of Python will print the location of
the examples directory:
@@ -139,8 +135,7 @@ Or browse the example code on Github [here](https://github.com/SolidCode/SolidPy
Adding your own code to the example file `solid/examples/solidpython_template.py` will make some of the setup easier.
-Extra syntactic sugar<a id="extra-syntactic-sugar"></a>
----------------------
+# Extra syntactic sugar<a id="extra-syntactic-sugar"></a>
### Basic operators<a id="basic-operators"></a>
Following Elmo Mäntynen's suggestion, SCAD objects override
the basic operators + (union), - (difference), and * (intersection).
@@ -166,10 +161,27 @@ is the same as:
cylinder( r=2, h=30)
)
+### First-class Negative Space (Holes)<a id="first-class-negative-space-holes"></a>
+OpenSCAD requires you to be very careful with the order in which you add or
+subtract objects. SolidPython's `hole()` function makes this process easier.
+Consider making a joint where two pipes come together. In OpenSCAD you need to
+make two cylinders, union them, then make two smaller cylinders, union
+them, then subtract the smaller from the larger.
-solid.utils<a id="solidutils"></a>
------------
+Using hole(), you can make a pipe, specify that its center should remain open,
+and then add two pipes together knowing that the central void area will stay
+empty no matter what other objects are added to that structure.
+
+Example:
+
+ outer = cylinder(r=pipe_od, h=seg_length)
+ inner = cylinder(r=pipe_id, h=seg_length)
+ pipe_a = outer - hole()(inner)
+
+See `solid/examples/hole_example.py` for the complete picture.
+
+# solid.utils<a id="solidutils"></a>
SolidPython includes a number of useful functions in solid/utils.py. Currently these include:
### Directions: (up, down, left, right, forward, back) for arranging things:<a id="directions-up-down-left-right-forward-back-for-arranging-things"></a>
@@ -200,7 +212,7 @@ draws an arc of radius 10 counterclockwise from 90 to 210 degrees.
draws the portion of a 10x10 square NOT in a 90 degree circle of radius 10.
This is the shape you need to add to make fillets or remove to make rounds.
-###Offsets<a id="offsets"></a>
+### Offsets<a id="offsets"></a>
To offset a set of points in one direction or another ( inside or outside a closed
figure, for example) use `solid.utils.offset_points( point_arr, offset, inside=True)`
@@ -210,7 +222,7 @@ intend, and change the boolean value of `inside` if you're not happy.
See the code for futher explanation. Improvements on the inside/outside algorithm would be welcome.
-###Extrude Along Path<a id="extrude_along_path"></a>
+### Extrude Along Path<a id="extrude_along_path"></a>
`solid.utils.extrude_along_path( shape_pts, path_pts, scale_factors=None)`
See `solid/examples/path_extrude_example.py` for use.
@@ -234,9 +246,8 @@ These colors are pre-defined in solid.utils:
<tr><td>* FiberBoard </td></tr>
</table>
-I took this from someone on Thingiverse and I'm
-ashamed that I can't find the original source. I owe someone some
-attribution.
+I took this from someone on Thingiverse and I'm ashamed that I can't find the
+original source. I owe someone some attribution.
### Bill Of Materials<a id="bill-of-materials"></a>
Put ```@part()``` before any method that defines a part, then
@@ -247,14 +258,12 @@ The example file `solid/examples/bom_scad.py` illustrates this. Check it out.
-solid.screw_thread<a id="solidscrew_thread"></a>
-------------------
+## solid.screw_thread<a id="solidscrew_thread"></a>
solid.screw_thread includes a method, thread() that makes internal and external
screw threads.
See `solid/examples/screw_thread_example.py` for more details.
-Contact<a id="contact"></a>
--------
+# Contact<a id="contact"></a>
Enjoy, and please send any questions or bug reports to me at ```evan_t_jones@mac.com```. Cheers!
Evan
View
57 solid/examples/hole_example.py
@@ -0,0 +1,57 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+from __future__ import division
+import os, sys, re
+
+# Assumes SolidPython is in site-packages or elsewhwere in sys.path
+from solid import *
+from solid.utils import *
+
+SEGMENTS = 120
+
+def pipe_intersection_hole():
+ pipe_od = 12
+ pipe_id = 10
+ seg_length = 30
+
+ outer = cylinder( r=pipe_od, h=seg_length, center=True)
+ inner = cylinder(r=pipe_id, h=seg_length+2, center=True)
+
+ # By declaring that the internal void of pipe_a should
+ # explicitly remain empty, the combination of both pipes
+ # is empty all the way through.
+
+ # Any OpenSCAD / SolidPython object can be declared a hole(),
+ # and after that will always be empty
+ pipe_a = outer + hole()(inner)
+ # Note that "pipe_a = outer - hole()( inner)" would work identically;
+ # inner will always be subtracted now that it's a hole
+
+ pipe_b = rotate( a=90, v=FORWARD_VEC)( pipe_a)
+ return pipe_a + pipe_b
+
+def pipe_intersection_no_hole():
+ pipe_od = 12
+ pipe_id = 10
+ seg_length = 30
+
+ outer = cylinder( r=pipe_od, h=seg_length, center=True)
+ inner = cylinder(r=pipe_id, h=seg_length+2, center=True)
+ pipe_a = outer - inner
+
+ pipe_b = rotate( a=90, v=FORWARD_VEC)( pipe_a)
+ # pipe_a and pipe_b are both hollow, but because
+ # their central voids aren't explicitly holes,
+ # the union of both pipes has unwanted internal walls
+
+ return pipe_a + pipe_b
+
+if __name__ == '__main__':
+ out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir
+ file_out = os.path.join( out_dir, 'hole_example.scad')
+
+ a = pipe_intersection_no_hole() + right( 45)(pipe_intersection_hole())
+
+ print "%(__file__)s: SCAD file written to: \n%(file_out)s \n"%vars()
+ scad_render_to_file( a, file_out, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True)
+
View
101 solid/solidpython.py
@@ -28,6 +28,7 @@
{'name': 'union', 'args': [], 'kwargs': []} ,
{'name': 'intersection', 'args': [], 'kwargs': []} ,
{'name': 'difference', 'args': [], 'kwargs': []} ,
+ {'name': 'hole', 'args': [], 'kwargs': []} ,
# Transforms
{'name': 'translate', 'args': [], 'kwargs': ['v']} ,
@@ -73,7 +74,13 @@ def __init__( self, points, paths=None):
paths = [ range( len( points))]
openscad_object.__init__( self, 'polygon', {'points':points, 'paths': paths})
-'''
+''',
+ 'hole':'''class hole( openscad_object):
+ def __init__( self):
+ openscad_object.__init__( self, 'union', {})
+ self.set_hole( True)
+
+ '''
}
@@ -185,6 +192,26 @@ def __init__(self, name, params):
self.children = []
self.modifier = ""
self.parent= None
+ self.is_hole = False
+ self.has_hole_children = False
+
+ def set_hole( self, is_hole=True):
+ self.is_hole = is_hole
+
+ def find_hole_children( self):
+ hole_kids = []
+ for child in self.children:
+ if child.is_hole:
+ hole_kids.append( child)
+ # Mark that there are holes below all upper nodes,
+ # so the necessary transforms can be written later
+ p = child
+ while p.parent:
+ p = p.parent
+ p.has_hole_children = True
+ else:
+ hole_kids += child.find_hole_children()
+ return hole_kids
def set_modifier(self, m):
# Used to add one of the 4 single-character modifiers: #(debug) !(root) %(background) or *(disable)
@@ -200,13 +227,9 @@ def set_modifier(self, m):
self.modifier = string_vals.get(m.lower(), '')
return self
- def _render(self):
- '''
- NOTE: In general, you won't want to call this method. For most purposes,
- you really want scad_render(),
- Calling obj._render won't include necessary 'use' or 'include' statements
- '''
- s = "\n" + self.modifier + self.name + "("
+ def _render_str_no_children( self):
+ s = ""
+ s += "\n" + self.modifier + self.name + "("
first = True
# OpenSCAD doesn't have a 'segments' argument, but it does
@@ -238,13 +261,54 @@ def _render(self):
s += k + " = " + py2openscad(v)
s += ")"
+ return s
+
+ def _render(self, render_holes=False):
+ '''
+ NOTE: In general, you won't want to call this method. For most purposes,
+ you really want scad_render(),
+ Calling obj._render won't include necessary 'use' or 'include' statements
+ '''
+ s = self._render_str_no_children()
+
if self.children != None and len(self.children) > 0:
s += " {"
for child in self.children:
- s += indent(child._render())
+ # Don't immediately render hole children.
+ # Add them to the parent's hole list,
+ # And render after everything else
+ if not render_holes and child.is_hole:
+ continue
+ s += indent(child._render( render_holes))
s += "\n}"
else:
s += ";"
+
+ # If this is the root object, find all holes
+ # and subtract them after all positive geometry is rendered
+ if not self.parent:
+ hole_children = self.find_hole_children()
+
+ if len(hole_children) > 0:
+ s += "\n/* All Holes Below*/"
+ s += self._render_hole_children()
+
+ # wrap everything in the difference
+ s = "difference() {" + indent(s) + "\n}"
+ return s
+
+ def _render_hole_children( self):
+ # Run down the tree, rendering only those nodes
+ # that are holes or have holes beneath them
+ if not self.has_hole_children:
+ return ""
+ s = self._render_str_no_children() + "{"
+ for child in self.children:
+ if child.is_hole:
+ s += indent( child._render())
+ elif child.has_hole_children:
+ s += indent( child._render_hole_children())
+ s += "\n}"
return s
def add(self, child):
@@ -255,11 +319,12 @@ def add(self, child):
if child is a list, assume its members are all openscad_objects and
add them all to self.children
'''
- if isinstance( child, list) or isinstance( child, tuple):
- [self.add( c) for c in child]
+ if isinstance( child, (list, tuple)):
+ [self.add( c.copy() ) for c in child]
else:
- self.children.append(child)
- child.set_parent( self)
+ c = child.copy()
+ self.children.append( c)
+ c.set_parent( self)
return self
def set_parent( self, parent):
@@ -273,7 +338,14 @@ def copy( self):
# Provides a copy of this object and all children,
# but doesn't copy self.parent, meaning the new object belongs
# to a different tree
- other = openscad_object( self.name, self.params)
+ # If we're copying a scad object, we know it is an instance of
+ # a dynamically created class called self.name.
+ # Initialize an instance of that class with the same params
+ # that created self, the object being copied.
+ other = globals()[ self.name]( **self.params)
+ other.set_modifier( self.modifier)
+ other.set_hole( self.is_hole)
+ other.has_hole_children = self.has_hole_children
for c in self.children:
other.add( c.copy())
return other
@@ -382,6 +454,7 @@ def __init__(self%(args_str)s):
openscad_object.__init__(self, '%(class_name)s', {%(args_pairs)s })
'''%vars()
+
return result
def py2openscad(o):

0 comments on commit a918209

Please sign in to comment.
Something went wrong with that request. Please try again.