diff --git a/README.md b/README.md index f58d7eeb..6232d2fd 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This Python code: cube(10), sphere(15) ) - print(scad_render( d)) + print(scad_render(d)) Generates this OpenSCAD code: @@ -49,7 +49,7 @@ Generates this OpenSCAD code: That doesn't seem like such a savings, but the following SolidPython code is a lot shorter (and I think a lot clearer) than the SCAD code it compiles to: - d = cube( 5) + right(5)( sphere(5)) - cylinder( r=2, h=6) + d = cube(5) + right(5)(sphere(5)) - cylinder(r=2, h=6) Generates this OpenSCAD code: @@ -57,10 +57,10 @@ Generates this OpenSCAD code: union(){ cube(5); translate( [5, 0,0]){ - sphere( 5); + sphere(5); } } - cylinder( r=2, h=6); + cylinder(r=2, h=6); } # Advantages @@ -79,9 +79,9 @@ impossible in pure OpenSCAD. Among these are: (You may need to use `sudo pip install solidpython`, depending on your environment.) -* **OR:** Download SolidPython ( Click [here](https://github.com/SolidCode/SolidPython/archive/master.zip) to download directly, or use git to pull it all down) +* **OR:** Download SolidPython (Click [here](https://github.com/SolidCode/SolidPython/archive/master.zip) to download directly, or use git to pull it all down) - ( Note that SolidPython also depends on the [PyEuclid](http://pypi.python.org/pypi/euclid) Vector math library, installable via `sudo pip install euclid`) + (Note that SolidPython also depends on the [PyEuclid](http://pypi.python.org/pypi/euclid) Vector math library, installable via `sudo pip install euclid`) * Unzip the file, probably in ~/Downloads/SolidPython-master * In a terminal, cd to location of file: @@ -115,8 +115,8 @@ impossible in pure OpenSCAD. Among these are: sphere(15) ) -* Call ```scad_render( py_scad_obj)``` to generate SCAD code. This returns a string of valid OpenSCAD code. -* *or*: call ```scad_render_to_file( py_scad_obj, filepath)``` to +* Call ```scad_render(py_scad_obj)``` to generate SCAD code. This returns a string of valid OpenSCAD code. +* *or*: call ```scad_render_to_file(py_scad_obj, filepath)``` to store that code in a file. * If 'filepath' is open in the OpenSCAD IDE and Design => 'Automatic Reload and Compile' is checked (in the OpenSCAD IDE), calling @@ -130,7 +130,7 @@ The best way to learn how SolidPython works is to look at the included example c If you've installed SolidPython, the following line of Python will print(the location of ) the examples directory: - import os, solid; print(os.path.dirname( solid.__file__) + '/examples') + import os, solid; print(os.path.dirname(solid.__file__) + '/examples') Or browse the example code on Github [here](https://github.com/SolidCode/SolidPython/tree/master/solid/examples) @@ -142,24 +142,24 @@ Following Elmo Mäntynen's suggestion, SCAD objects override the basic operators + (union), - (difference), and * (intersection). So - c = cylinder( r=10, h=5) + cylinder( r=2, h=30) + c = cylinder(r=10, h=5) + cylinder(r=2, h=30) is the same as: c = union()( - cylinder( r=10, h=5), - cylinder( r=2, h=30) + cylinder(r=10, h=5), + cylinder(r=2, h=30) ) Likewise: - c = cylinder( r=10, h=5) - c -= cylinder( r=2, h=30) + c = cylinder(r=10, h=5) + c -= cylinder(r=2, h=30) is the same as: c = difference()( - cylinder( r=10, h=5), - cylinder( r=2, h=30) + cylinder(r=10, h=5), + cylinder(r=2, h=30) ) ### First-class Negative Space (Holes) @@ -215,18 +215,18 @@ My apologies. ### Arcs I've found this useful for fillets and rounds. - arc( rad=10, start_degrees=90, end_degrees=210) + arc(rad=10, start_degrees=90, end_degrees=210) draws an arc of radius 10 counterclockwise from 90 to 210 degrees. - arc_inverted( rad=10, start_degrees=0, end_degrees=90) + arc_inverted(rad=10, start_degrees=0, end_degrees=90) 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 -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)` +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)` Note that, for a non-convex figure, inside and outside may be non-intuitive. The simple solution is to manually check that your offset is going in the direction you @@ -235,7 +235,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 -`solid.utils.extrude_along_path( shape_pts, path_pts, scale_factors=None)` +`solid.utils.extrude_along_path(shape_pts, path_pts, scale_factors=None)` See [`solid/examples/path_extrude_example.py`](https://github.com/SolidCode/SolidPython/blob/master/solid/examples/path_extrude_example.py) for use. @@ -243,8 +243,8 @@ See [`solid/examples/path_extrude_example.py`](https://github.com/SolidCode/Soli ### Basic color library You can change an object's color by using the OpenSCAD ```color([rgba_array])``` function: - transparent_blue = color( [0,0,1, 0.5])( cube(10)) # Specify with RGB[A] - red_obj = color( Red)( cube( 10)) # Or use predefined colors + transparent_blue = color( [0,0,1, 0.5])(cube(10)) # Specify with RGB[A] + red_obj = color(Red)(cube(10)) # Or use predefined colors These colors are pre-defined in solid.utils: diff --git a/TODO_SolidPython.txt b/TODO_SolidPython.txt index 4ecf66ec..bd480939 100644 --- a/TODO_SolidPython.txt +++ b/TODO_SolidPython.txt @@ -69,7 +69,7 @@ Completed: -- Improving syntax so it's as close as possible to OpenSCAD's -ETJ 15 Feb 2011 -- By default, let polygon assume that connectivity between specified points (the paths argument) is in the order specified in the points array. Without this, there's always - boilerplate: polygon( points=arr, paths= range(len(arr))) + boilerplate: polygon(points=arr, paths= range(len(arr))) -- Add 'segments' arguments to circle(), sphere() and cylinder()? This breaks with OpenSCAD, but is simpler than circle(10).add_param('$fn', 30) -- Unit testing. diff --git a/solid/examples/animation_example.py b/solid/examples/animation_example.py index 7a711362..94a08fb7 100755 --- a/solid/examples/animation_example.py +++ b/solid/examples/animation_example.py @@ -6,17 +6,17 @@ from solid import * from solid.utils import * -def my_animate( _time=0): +def my_animate(_time=0): # _time will range from 0 to 1, not including 1 rads = _time * 2 * 3.1416 rad = 15 - c = translate( [rad*cos(rads), rad*sin(rads)])( square( 10)) + c = translate( [rad*cos(rads), rad*sin(rads)])(square(10)) return c if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'animation_example.scad') + file_out = os.path.join(out_dir, 'animation_example.scad') print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) @@ -28,7 +28,7 @@ def my_animate( _time=0): # at the bottom of the OpenSCAD window # - FPS & Steps are flexible. For a start, set both to 20 # play around from there - scad_render_animated_file( my_animate, # A function that takes a float argument + scad_render_animated_file(my_animate, # A function that takes a float argument # called '_time' in [0,1) # and returns an OpenSCAD object steps=20, # Number of steps to create one complete motion diff --git a/solid/examples/append_solidpython_code.py b/solid/examples/append_solidpython_code.py index f1f9d4d0..31fb914b 100755 --- a/solid/examples/append_solidpython_code.py +++ b/solid/examples/append_solidpython_code.py @@ -8,13 +8,13 @@ SEGMENTS = 48 def show_appended_python_code(): - a = cylinder( r=10, h=10, center=True) + up(5)( cylinder(r1=10, r2=0, h=10)) + a = cylinder(r=10, h=10, center=True) + up(5)(cylinder(r1=10, r2=0, h=10)) return a if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'append_solidpython_code.scad') + file_out = os.path.join(out_dir, 'append_solidpython_code.scad') a = show_appended_python_code() @@ -24,5 +24,5 @@ def show_appended_python_code(): # = bottom of the generated OpenSCAD code, so the final document # = contains the easy-to-read python code as well as the SCAD. # = ------------------------------------------------------------ = - scad_render_to_file( a, file_out, include_orig_code=True) + scad_render_to_file(a, file_out, include_orig_code=True) diff --git a/solid/examples/basic_geometry.py b/solid/examples/basic_geometry.py index 7bbcc0c2..ca1c748b 100755 --- a/solid/examples/basic_geometry.py +++ b/solid/examples/basic_geometry.py @@ -16,13 +16,13 @@ def basic_geometry(): # left_piece uses standard OpenSCAD grammar (note the commas between # block elements; OpenSCAD doesn't require this) left_piece = union()( - translate( [-15, 0, 0])( - cube( [10, 5, 3], center=True) + translate([-15, 0, 0])( + cube([10, 5, 3], center=True) ), - translate( [-10, 0, 0])( + translate([-10, 0, 0])( difference()( - cylinder( r=5, h=15, center=True), - cylinder( r=4, h=16, center=True) + cylinder(r=5, h=15, center=True), + cylinder(r=4, h=16, center=True) ) ) ) @@ -31,15 +31,15 @@ def basic_geometry(): # - (minus) is equivalent to difference() and * (star) is equivalent to intersection # solid.utils also defines up(), down(), left(), right(), forward(), and back() # for common transforms. - right_piece = right( 15)( cube([10, 5, 3], center=True)) - cyl = cylinder( r=5, h=15, center=True) - cylinder( r=4, h=16, center=True) - right_piece += right(10)( cyl) + right_piece = right(15)(cube([10, 5, 3], center=True)) + cyl = cylinder(r=5, h=15, center=True) - cylinder(r=4, h=16, center=True) + right_piece += right(10)(cyl) return union()(left_piece, right_piece) if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'basic_geometry.scad') + file_out = os.path.join(out_dir, 'basic_geometry.scad') a = basic_geometry() @@ -49,4 +49,4 @@ def basic_geometry(): # the detail of arcs by changing the SEGMENTS variable. This can # be expensive when making lots of small curves, but is otherwise # useful. - scad_render_to_file( a, file_out, file_header='$fn = %s;'%SEGMENTS) \ No newline at end of file + scad_render_to_file(a, file_out, file_header='$fn = %s;'%SEGMENTS) \ No newline at end of file diff --git a/solid/examples/basic_scad_include.py b/solid/examples/basic_scad_include.py index 6751ea7b..aa47cb35 100755 --- a/solid/examples/basic_scad_include.py +++ b/solid/examples/basic_scad_include.py @@ -9,17 +9,17 @@ def demo_scad_include(): # scad_to_include.scad includes a module called steps() scad_path = os.path.join(os.path.dirname(__file__), "scad_to_include.scad") - use( scad_path) # could also use 'include', but that has side-effects; + use(scad_path) # could also use 'include', but that has side-effects; # 'use' just imports without executing any of the imported code return steps(5) if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'scad_include_example.scad') + file_out = os.path.join(out_dir, 'scad_include_example.scad') a = demo_scad_include() print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) - scad_render_to_file( a, file_out) \ No newline at end of file + scad_render_to_file(a, file_out) \ No newline at end of file diff --git a/solid/examples/bom_scad.py b/solid/examples/bom_scad.py index a2cc06b1..82a08333 100755 --- a/solid/examples/bom_scad.py +++ b/solid/examples/bom_scad.py @@ -34,16 +34,16 @@ doohickey_h = 5 def head(): - return cylinder( h=head_height, r =head_rad) + return cylinder(h=head_height, r =head_rad) @bom_part("M3x16 Bolt", 0.12, currency="€") -def m3_16( a=3): +def m3_16(a=3): bolt_height = 16 m = union()( head(), translate( [0,0, -bolt_height])( - cylinder( r=m3_rad, h=bolt_height) + cylinder(r=m3_rad, h=bolt_height) ) ) return m @@ -55,7 +55,7 @@ def m3_12(): m = union()( head(), translate( [ 0, 0, -bolt_height])( - cylinder( r=m3_rad, h=bolt_height) + cylinder(r=m3_rad, h=bolt_height) ) ) return m @@ -63,12 +63,12 @@ def m3_12(): @bom_part("M3 Nut", 0.04, currency="R$") def m3_nut(): - hx = cylinder( r=nut_rad, h=nut_height) + hx = cylinder(r=nut_rad, h=nut_height) hx.add_param('$fn', 6) # make the nut hexagonal n = difference()( hx, translate([0,0,-EPSILON])( - cylinder( r=m3_rad, h=nut_height+2*EPSILON ) + cylinder(r=m3_rad, h=nut_height+2*EPSILON ) ) ) return n @@ -81,9 +81,9 @@ def doohickey(): ) d = difference()( cube([30, 10, doohickey_h], center=True), - translate([-10, 0,0])( hole_cyl), + translate([-10, 0,0])(hole_cyl), hole_cyl, - translate([10,0,0])( hole_cyl) + translate([10,0,0])(hole_cyl) ) return d @@ -102,7 +102,7 @@ def assemble(): if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'BOM_example.scad') + file_out = os.path.join(out_dir, 'BOM_example.scad') a = assemble() @@ -111,4 +111,4 @@ def assemble(): print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) print(bom) - scad_render_to_file( a, file_out) + scad_render_to_file(a, file_out) diff --git a/solid/examples/hole_example.py b/solid/examples/hole_example.py index 34a07b88..3f1f1851 100755 --- a/solid/examples/hole_example.py +++ b/solid/examples/hole_example.py @@ -14,7 +14,7 @@ def pipe_intersection_hole(): pipe_id = 10 seg_length = 30 - outer = cylinder( r=pipe_od, h=seg_length, center=True) + 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 @@ -24,10 +24,10 @@ def pipe_intersection_hole(): # 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; + # 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) + pipe_b = rotate(a=90, v=FORWARD_VEC)(pipe_a) return pipe_a + pipe_b def pipe_intersection_no_hole(): @@ -35,11 +35,11 @@ def pipe_intersection_no_hole(): pipe_id = 10 seg_length = 30 - outer = cylinder( r=pipe_od, h=seg_length, center=True) + 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_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 @@ -60,8 +60,8 @@ def multipart_hole(): # and then insert the same 'bolt' cylinder into it. The entire # bolt rematins. - b = cube( 10, center=True) - c = cylinder( r=2, h=12, center=True) + b = cube(10, center=True) + c = cylinder(r=2, h=12, center=True) # A cube with an explicit hole not_part = b - hole()(c) @@ -70,28 +70,28 @@ def multipart_hole(): is_part = part()(not_part.copy()) # This fits in the holes - bolt = cylinder( r=1.5, h=14, center=True) + up(8)( cylinder( r=2.5, h=2.5, center=True)) + bolt = cylinder(r=1.5, h=14, center=True) + up(8)(cylinder(r=2.5, h=2.5, center=True)) # The section of the bolt inside not_part disappears. The section # of the bolt inside is_part is still there. - a = not_part + bolt + right( 45)( is_part + bolt) + a = not_part + bolt + right(45)(is_part + bolt) return a 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') + file_out = os.path.join(out_dir, 'hole_example.scad') # On the left, pipes with no explicit holes, which can give # unexpected walls where we don't want them. # On the right, we use the hole() function to fix the problem - a = pipe_intersection_no_hole() + right( 45)(pipe_intersection_hole()) + a = pipe_intersection_no_hole() + right(45)(pipe_intersection_hole()) # Below is an example of how to put objects into holes and have them # still appear - b = up( 40)( multipart_hole()) + b = up(40)(multipart_hole()) a += b print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) - scad_render_to_file( a, file_out, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True) + scad_render_to_file(a, file_out, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True) diff --git a/solid/examples/koch.py b/solid/examples/koch.py index 010ab0ce..d2f117db 100755 --- a/solid/examples/koch.py +++ b/solid/examples/koch.py @@ -9,12 +9,12 @@ ONE_THIRD = 1/3.0 -def affine_combination( a, b, weight=0.5): +def affine_combination(a, b, weight=0.5): ''' Note that weight is a fraction of the distance between self and other. So... 0.33 is a point .33 of the way between self and other. ''' - if hasattr( a, 'z'): + if hasattr(a, 'z'): return Point3( (1-weight)*a.x + weight*b.x, (1-weight)*a.y + weight*b.y, (1-weight)*a.z + weight*b.z, @@ -24,7 +24,7 @@ def affine_combination( a, b, weight=0.5): (1-weight)*a.y + weight*b.y, ) -def kochify_3d( a, b, c, +def kochify_3d(a, b, c, ab_weight=0.5, bc_weight=0.5, ca_weight=0.5, pyr_a_weight=ONE_THIRD, pyr_b_weight=ONE_THIRD, pyr_c_weight=ONE_THIRD, pyr_height_weight=ONE_THIRD @@ -36,9 +36,9 @@ def kochify_3d( a, b, c, pyr_height determines how far from the face the new pyramid's point will be ''' triangles = [] - new_a = affine_combination( a, b, ab_weight) - new_b = affine_combination( b, c, bc_weight) - new_c = affine_combination( c, a, ca_weight) + new_a = affine_combination(a, b, ab_weight) + new_b = affine_combination(b, c, bc_weight) + new_c = affine_combination(c, a, ca_weight) triangles.extend( [[a, new_a, new_c], [b, new_b, new_a], [c, new_c, new_b]]) @@ -46,16 +46,16 @@ def kochify_3d( a, b, c, avg_pt_y = a.y*pyr_a_weight + b.y*pyr_b_weight + c.y*pyr_c_weight avg_pt_z = a.z*pyr_a_weight + b.z*pyr_b_weight + c.z*pyr_c_weight - center_pt = Point3( avg_pt_x, avg_pt_y, avg_pt_z) + center_pt = Point3(avg_pt_x, avg_pt_y, avg_pt_z) # The top of the pyramid will be on a normal ab_vec = b - a bc_vec = c - b ca_vec = a - c - normal = ab_vec.cross( bc_vec).normalized() + normal = ab_vec.cross(bc_vec).normalized() avg_side_length = (abs(ab_vec) + abs(bc_vec) + abs(ca_vec))/3 pyr_h = avg_side_length * pyr_height_weight - pyr_pt = LineSegment3( center_pt, normal, pyr_h).p2 + pyr_pt = LineSegment3(center_pt, normal, pyr_h).p2 triangles.extend([[new_a, pyr_pt, new_c], [new_b, pyr_pt, new_a], [new_c, pyr_pt, new_b]]) @@ -63,26 +63,26 @@ def kochify_3d( a, b, c, return triangles -def kochify( seg, height_ratio=0.33, left_loc= 0.33, midpoint_loc=0.5, right_loc= 0.66): +def kochify(seg, height_ratio=0.33, left_loc= 0.33, midpoint_loc=0.5, right_loc= 0.66): a, b = seg.p1, seg.p2 - l = affine_combination( a, b, left_loc) - c = affine_combination( a, b, midpoint_loc) - r = affine_combination( a, b, right_loc) + l = affine_combination(a, b, left_loc) + c = affine_combination(a, b, midpoint_loc) + r = affine_combination(a, b, right_loc) # The point of the new triangle will be height_ratio * abs(seg) long, # and run perpendicular to seg, through c. perp = seg.v.cross().normalized() c_height = height_ratio* abs(seg) - perp_pt = LineSegment2( c, perp, -c_height).p2 + perp_pt = LineSegment2(c, perp, -c_height).p2 # For the moment, assume perp_pt is on the right side of seg. # Will confirm this later if needed - return [ LineSegment2( a, l), - LineSegment2( l, perp_pt), - LineSegment2( perp_pt, r), - LineSegment2( r, b)] + return [ LineSegment2(a, l), + LineSegment2(l, perp_pt), + LineSegment2(perp_pt, r), + LineSegment2(r, b)] -def main_3d( out_dir): +def main_3d(out_dir): gens = 4 # Parameters @@ -103,10 +103,10 @@ def main_3d( out_dir): bx, by, bz = 100, 100,-100 cx, cy, cz = -100, 100, 100 dx, dy, dz = -100, -100, -100 - generations = [ [[ Point3( ax, ay, az), Point3( bx, by, bz), Point3( cx, cy, cz)], - [ Point3( bx, by, bz), Point3( ax, ay, az), Point3( dx, dy, dz)], - [ Point3( ax, ay, az), Point3( cx, cy, cz), Point3( dx, dy, dz)], - [ Point3( cx, cy, cz), Point3( bx, by, bz), Point3( dx, dy, dz)], + generations = [ [[ Point3(ax, ay, az), Point3(bx, by, bz), Point3(cx, cy, cz)], + [ Point3(bx, by, bz), Point3(ax, ay, az), Point3(dx, dy, dz)], + [ Point3(ax, ay, az), Point3(cx, cy, cz), Point3(dx, dy, dz)], + [ Point3(cx, cy, cz), Point3(bx, by, bz), Point3(dx, dy, dz)], ] ] @@ -114,15 +114,15 @@ def main_3d( out_dir): for g in range(1, gens): generations.append([]) for a, b, c in generations[g-1]: - new_tris = kochify_3d( a, b, c, + new_tris = kochify_3d(a, b, c, ab_weight, bc_weight, ca_weight, pyr_a_weight, pyr_b_weight,pyr_c_weight, pyr_height_weight) # new_tris = kochify_3d( a, b, c) - generations[g].extend( new_tris) + generations[g].extend(new_tris) # Put all generations into SCAD - orig_length = abs( generations[0][0][1] - generations[0][0][0]) + orig_length = abs(generations[0][0][1] - generations[0][0][0]) for g, a_gen in enumerate(generations): # Move each generation up in y so it doesn't overlap the others h = orig_length *1.5 * g @@ -137,17 +137,17 @@ def main_3d( out_dir): # Do the SCAD edges = [list(range(len(points)))] - all_polys.add( up( h)( - polyhedron( points=points, faces=faces) + all_polys.add(up(h)( + polyhedron(points=points, faces=faces) ) ) - file_out = os.path.join( out_dir, 'koch_3d.scad') + file_out = os.path.join(out_dir, 'koch_3d.scad') cur_file = __file__ print("%(cur_file)s: SCAD file written to: %(file_out)s"%vars()) - scad_render_to_file( all_polys, file_out, include_orig_code=True) + scad_render_to_file(all_polys, file_out, include_orig_code=True) -def main( out_dir): +def main(out_dir): # Parameters midpoint_weight = 0.5 height_ratio = 0.25 @@ -163,9 +163,9 @@ def main( out_dir): ax, ay = 0, 0 bx, by = 100, 0 cx, cy = 50, 86.6 - base_seg1 = LineSegment2( Point2( ax, ay), Point2( cx, cy)) - base_seg2 = LineSegment2( Point2( cx, cy), Point2( bx, by)) - base_seg3 = LineSegment2( Point2( bx, by), Point2( ax, ay)) + base_seg1 = LineSegment2( Point2(ax, ay), Point2(cx, cy)) + base_seg2 = LineSegment2( Point2(cx, cy), Point2(bx, by)) + base_seg3 = LineSegment2( Point2(bx, by), Point2(ax, ay)) generations = [[base_seg1, base_seg2, base_seg3]] @@ -173,14 +173,14 @@ def main( out_dir): for g in range(1, gens): generations.append([]) for seg in generations[g-1]: - generations[g].extend( kochify( seg, height_ratio, left_loc, midpoint_loc, right_loc)) - # generations[g].extend( kochify( seg)) + generations[g].extend(kochify(seg, height_ratio, left_loc, midpoint_loc, right_loc)) + # generations[g].extend(kochify(seg)) # # Put all generations into SCAD - orig_length = abs( generations[0][0]) + orig_length = abs(generations[0][0]) for g, a_gen in enumerate(generations): points = [s.p1 for s in a_gen ] - # points.append( a_gen[-1].p2) # add the last point + # points.append(a_gen[-1].p2) # add the last point rect_offset = 10 @@ -192,15 +192,15 @@ def main( out_dir): # Do the SCAD edges = [list(range(len(points)))] - all_polys.add( forward( h)( polygon(points=points, paths=edges ))) + all_polys.add(forward(h)(polygon(points=points, paths=edges ))) - file_out = os.path.join( out_dir,'koch.scad') + file_out = os.path.join(out_dir,'koch.scad') cur_file = __file__ print("%(cur_file)s: SCAD file written to: %(file_out)s "%vars()) - scad_render_to_file( all_polys, file_out, include_orig_code=True ) + scad_render_to_file(all_polys, file_out, include_orig_code=True ) if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - main_3d( out_dir) - main( out_dir) + main_3d(out_dir) + main(out_dir) diff --git a/solid/examples/path_extrude_example.py b/solid/examples/path_extrude_example.py index 323041b1..a9b85b9d 100755 --- a/solid/examples/path_extrude_example.py +++ b/solid/examples/path_extrude_example.py @@ -9,22 +9,22 @@ SEGMENTS = 48 -def sinusoidal_ring( rad=25, segments=SEGMENTS): +def sinusoidal_ring(rad=25, segments=SEGMENTS): outline = [] - for i in range( segments): + for i in range(segments): angle = i*360/segments - x = rad * cos( radians(angle)) - y = rad * sin( radians(angle)) + x = rad * cos(radians(angle)) + y = rad * sin(radians(angle)) z = 2*sin(radians(angle*6)) - outline.append( Point3(x,y,z)) + outline.append(Point3(x,y,z)) return outline -def star( num_points=5, outer_rad=15, dip_factor=0.5): +def star(num_points=5, outer_rad=15, dip_factor=0.5): star_pts = [] for i in range(2*num_points): rad = outer_rad - i%2 * dip_factor*outer_rad - angle = radians( 360/(2*num_points) * i) - star_pts.append(Point3( rad*cos(angle), rad*sin(angle), 0)) + angle = radians(360/(2*num_points) * i) + star_pts.append(Point3(rad*cos(angle), rad*sin(angle), 0)) return star_pts @@ -33,8 +33,8 @@ def extrude_example(): # Note the incorrect triangulation at the two ends of the path. This # is because star isn't convex, and the triangulation algorithm for # the two end caps only works for convex shapes. - shape = star( num_points=5) - path = sinusoidal_ring( rad=50) + shape = star(num_points=5) + path = sinusoidal_ring(rad=50) # If scale_factors aren't included, they'll default to # no scaling at each step along path. Here, let's @@ -43,16 +43,16 @@ def extrude_example(): scales[0] = 2 scales[-1] = 2 - extruded = extrude_along_path( shape_pts=shape, path_pts=path, scale_factors=scales) + extruded = extrude_along_path(shape_pts=shape, path_pts=path, scale_factors=scales) return extruded if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'path_extrude_example.scad') + file_out = os.path.join(out_dir, 'path_extrude_example.scad') a = extrude_example() print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) - scad_render_to_file( a, file_out, include_orig_code=True) + scad_render_to_file(a, file_out, include_orig_code=True) diff --git a/solid/examples/scad_to_include.scad b/solid/examples/scad_to_include.scad index edd70fcc..a67ff9bb 100644 --- a/solid/examples/scad_to_include.scad +++ b/solid/examples/scad_to_include.scad @@ -1,5 +1,5 @@ external_var = false; -module steps( howmany=3){ +module steps(howmany=3){ union(){ for (i=[0:howmany-1]){ translate( [i*10,0,0]){ @@ -8,7 +8,7 @@ module steps( howmany=3){ } } - if ( external_var){ + if (external_var){ echo( "external_var passed in as true"); } } diff --git a/solid/examples/screw_thread_example.py b/solid/examples/screw_thread_example.py index 01c2c463..5a173c4c 100755 --- a/solid/examples/screw_thread_example.py +++ b/solid/examples/screw_thread_example.py @@ -12,21 +12,21 @@ inner_rad = 40 screw_height=80 def assembly(): - section = screw_thread.default_thread_section( tooth_height=10, tooth_depth=5) - s = screw_thread.thread( outline_pts=section, inner_rad = inner_rad, + section = screw_thread.default_thread_section(tooth_height=10, tooth_depth=5) + s = screw_thread.thread(outline_pts=section, inner_rad = inner_rad, pitch= screw_height, length=screw_height, segments_per_rot=SEGMENTS) #, neck_in_degrees=90, neck_out_degrees=90) - c = cylinder( r=inner_rad, h=screw_height ) + c = cylinder(r=inner_rad, h=screw_height ) return s + c if __name__ == '__main__': out_dir = sys.argv[1] if len(sys.argv) > 1 else os.curdir - file_out = os.path.join( out_dir, 'screw_thread_example.scad') + file_out = os.path.join(out_dir, 'screw_thread_example.scad') a = assembly() print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) - scad_render_to_file( a, file_out, include_orig_code=True) + scad_render_to_file(a, file_out, include_orig_code=True) diff --git a/solid/examples/sierpinski.py b/solid/examples/sierpinski.py index 1555851d..1c97f732 100755 --- a/solid/examples/sierpinski.py +++ b/solid/examples/sierpinski.py @@ -17,12 +17,12 @@ class SierpinskiTetrahedron(object): def __init__(self, four_points): self.points = four_points - def segments( self): + def segments(self): indices = [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)] return [(self.points[a], self.points[b]) for a,b in indices] - def next_gen( self, midpoint_weight=0.5, jitter_range_vec=None): - midpoints = [weighted_midpoint( s[0], s[1], weight=midpoint_weight, jitter_range_vec=jitter_range_vec) for s in self.segments()] + def next_gen(self, midpoint_weight=0.5, jitter_range_vec=None): + midpoints = [weighted_midpoint(s[0], s[1], weight=midpoint_weight, jitter_range_vec=jitter_range_vec) for s in self.segments()] all_points = self.points + midpoints new_tet_indices = [ (0, 4, 5, 6), (4, 1, 7, 8), @@ -31,30 +31,29 @@ def next_gen( self, midpoint_weight=0.5, jitter_range_vec=None): new_tets = [] for four_ind in new_tet_indices: tet_points = [all_points[i] for i in four_ind] - new_tets.append( SierpinskiTetrahedron( tet_points)) + new_tets.append(SierpinskiTetrahedron(tet_points)) return new_tets - def scale( self, factor): + def scale(self, factor): self.points = [[factor*d for d in p] for p in self.points] - def scad_code( self): + def scad_code(self): faces = [[0,1,2], [0,2,3], [0,3,1], [1,3,2]] - return polyhedron( points=self.points, faces=faces, convexity =1) + return polyhedron(points=self.points, faces=faces, convexity =1) - -def distance( a, b): +def distance(a, b): return math.sqrt((a[0]-b[0])* (a[0]-b[0])+ (a[1]-b[1])* (a[1]-b[1])+ (a[2]-b[2])* (a[2]-b[2])) -def weighted_midpoint( a, b, weight=0.5, jitter_range_vec=None): +def weighted_midpoint(a, b, weight=0.5, jitter_range_vec=None): # ignoring jitter_range_vec for now x = weight*a[0] + (1-weight)*b[0] y = weight*a[1] + (1-weight)*b[1] z = weight*a[2] + (1-weight)*b[2] - dist = distance( a, b) + dist = distance(a, b) if jitter_range_vec: x += (random.random()-.5) * dist * jitter_range_vec[0] @@ -63,14 +62,14 @@ def weighted_midpoint( a, b, weight=0.5, jitter_range_vec=None): return [x,y,z] -def sierpinski_3d( generation, scale= 1, midpoint_weight=0.5, jitter_range_vec=None): +def sierpinski_3d(generation, scale= 1, midpoint_weight=0.5, jitter_range_vec=None): orig_tet = SierpinskiTetrahedron( [ [ 1.0, 1.0, 1.0], - [-1.0, -1.0, 1.0], - [-1.0, 1.0, -1.0], - [ 1.0, -1.0, -1.0]]) + [-1.0, -1.0, 1.0], + [-1.0, 1.0, -1.0], + [ 1.0, -1.0, -1.0]]) all_tets = [orig_tet] - for i in range( generation): - all_tets = [subtet for tet in all_tets for subtet in tet.next_gen( midpoint_weight, jitter_range_vec)] + for i in range(generation): + all_tets = [subtet for tet in all_tets for subtet in tet.next_gen(midpoint_weight, jitter_range_vec)] if scale != 1: for tet in all_tets: @@ -88,17 +87,17 @@ def sierpinski_3d( generation, scale= 1, midpoint_weight=0.5, jitter_range_vec=N # making it more interesting. Try: # jitter_range_vec = [0.5,0, 0] jitter_range_vec = None - all_tets = sierpinski_3d( generations, scale=100, midpoint_weight=midpoint_weight, jitter_range_vec= jitter_range_vec) + all_tets = sierpinski_3d(generations, scale=100, midpoint_weight=midpoint_weight, jitter_range_vec= jitter_range_vec) t = union() for tet in all_tets: # Create the scad code for all tetrahedra - t.add( tet.scad_code()) + t.add(tet.scad_code()) # Draw cubes at all intersections to make the shape manifold. for p in tet.points: - t.add( translate(p).add( cube(5, center=True))) + t.add(translate(p).add(cube(5, center=True))) - file_out = os.path.join( out_dir, 'gasket_%s_gen.scad'%generations) + file_out = os.path.join(out_dir, 'gasket_%s_gen.scad'%generations) print("%(__file__)s: SCAD file written to: \n%(file_out)s"%vars()) - scad_render_to_file( t, file_out) \ No newline at end of file + scad_render_to_file(t, file_out) \ No newline at end of file diff --git a/solid/examples/solidpython_template.py b/solid/examples/solidpython_template.py index 307d6464..35b72e8d 100755 --- a/solid/examples/solidpython_template.py +++ b/solid/examples/solidpython_template.py @@ -18,4 +18,4 @@ def assembly(): if __name__ == '__main__': a = assembly() - scad_render_to_file( a, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True) + scad_render_to_file(a, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True) diff --git a/solid/patch_euclid.py b/solid/patch_euclid.py index 8775a807..127d942c 100644 --- a/solid/patch_euclid.py +++ b/solid/patch_euclid.py @@ -6,10 +6,10 @@ # NOTE: The PyEuclid on PyPi doesn't include several elements added to # the module as of 13 Feb 2013. Add them here until euclid supports them -def as_arr_local( self): +def as_arr_local(self): return [ self.x, self.y, self.z] -def set_length_local( self, length): +def set_length_local(self, length): d = self.magnitude() if d: factor = length/d @@ -18,11 +18,11 @@ def set_length_local( self, length): return self -def _intersect_line3_line3( A, B): +def _intersect_line3_line3(A, B): # Connect A & B # If the length of the connecting segment is 0, they intersect # at the endpoint(s) of the connecting segment - sol = euclid._connect_line3_line3( A, B) + sol = euclid._connect_line3_line3(A, B) # TODO: Ray3 and LineSegment3 would like to be able to know # if their intersection points fall within the segment. if sol.magnitude_squared() < EPSILON: @@ -33,9 +33,9 @@ def _intersect_line3_line3( A, B): def run_patch(): - if 'as_arr' not in dir( Vector3): + if 'as_arr' not in dir(Vector3): Vector3.as_arr = as_arr_local - if 'set_length' not in dir( Vector3): + if 'set_length' not in dir(Vector3): Vector3.set_length = set_length_local if '_intersect_line3' not in dir(Line3): Line3._intersect_line3 = _intersect_line3_line3 diff --git a/solid/screw_thread.py b/solid/screw_thread.py index e49b320a..5b35e867 100755 --- a/solid/screw_thread.py +++ b/solid/screw_thread.py @@ -11,7 +11,7 @@ import solid.patch_euclid solid.patch_euclid.run_patch() -def thread( outline_pts, inner_rad, pitch, length, external=True, segments_per_rot=32,neck_in_degrees=0, neck_out_degrees=0): +def thread(outline_pts, inner_rad, pitch, length, external=True, segments_per_rot=32,neck_in_degrees=0, neck_out_degrees=0): ''' Sweeps outline_pts (an array of points describing a closed polygon in XY) through a spiral. @@ -53,20 +53,20 @@ def thread( outline_pts, inner_rad, pitch, length, external=True, segments_per_r total_angle = 360.0*rotations up_step = float(length) / (rotations*segments_per_rot) # Add one to total_steps so we have total_steps *segments* - total_steps = int(ceil( rotations * segments_per_rot)) + 1 + total_steps = int(ceil(rotations * segments_per_rot)) + 1 step_angle = total_angle/ (total_steps -1) all_points = [] all_tris = [] euc_up = Vector3( *UP_VEC) - poly_sides = len( outline_pts) + poly_sides = len(outline_pts) # Figure out how wide the tooth profile is - min_bb, max_bb = bounding_box( outline_pts) + min_bb, max_bb = bounding_box(outline_pts) outline_w = max_bb[0] - min_bb[0] outline_h = max_bb[1] - min_bb[1] - min_rad = max( 0, inner_rad-outline_w-EPSILON) + min_rad = max(0, inner_rad-outline_w-EPSILON) max_rad = inner_rad + outline_w + EPSILON # outline_pts, since they were created in 2D , are in the XY plane. @@ -76,14 +76,14 @@ def thread( outline_pts, inner_rad, pitch, length, external=True, segments_per_r euc_points = [] for p in outline_pts: # If p is in [x, y] format, make it [x, y, 0] - if len( p) == 2: - p.append( 0) + if len(p) == 2: + p.append(0) # [x, y, z] => [ x+inner_rad, z, y] external_mult = 1 if external else -1 - s = Point3( external_mult*p[0], p[2], p[1]) # adding inner_rad, swapping Y & Z - euc_points.append( s) + s = Point3(external_mult*p[0], p[2], p[1]) # adding inner_rad, swapping Y & Z + euc_points.append(s) - for i in range( total_steps): + for i in range(total_steps): angle = i*step_angle elevation = i*up_step @@ -101,17 +101,17 @@ def thread( outline_pts, inner_rad, pitch, length, external=True, segments_per_r elif angle > total_angle - neck_in_degrees: rad = neck_in_rad + int_ext_mult * (total_angle - angle)/neck_out_degrees * outline_w - elev_vec = Vector3( rad, 0, elevation) + elev_vec = Vector3(rad, 0, elevation) # create new points for p in euc_points: - pt = (p + elev_vec).rotate_around( axis=euc_up, theta=radians( angle)) - all_points.append( pt.as_arr()) + pt = (p + elev_vec).rotate_around(axis=euc_up, theta=radians(angle)) + all_points.append(pt.as_arr()) # Add the connectivity information if i < total_steps -1: ind = i*poly_sides - for j in range( ind, ind + poly_sides - 1): + for j in range(ind, ind + poly_sides - 1): all_tris.append( [ j, j+1, j+poly_sides]) all_tris.append( [ j+1, j+poly_sides+1, j+poly_sides]) all_tris.append( [ ind, ind + poly_sides-1+poly_sides, ind + poly_sides-1]) @@ -119,29 +119,29 @@ def thread( outline_pts, inner_rad, pitch, length, external=True, segments_per_r # End triangle fans for beginning and end last_loop = len(all_points) - poly_sides - for i in range( poly_sides -2): + for i in range(poly_sides -2): all_tris.append( [ 0, i+2, i+1]) all_tris.append( [ last_loop, last_loop + i+1, last_loop + i + 2]) # Make the polyhedron - a = polyhedron( points=all_points, faces=all_tris) + a = polyhedron(points=all_points, faces=all_tris) if external: # Intersect with a cylindrical tube to make sure we fit into # the correct dimensions - tube = cylinder( r=inner_rad+outline_w+EPSILON, h=length, segments=segments_per_rot) - tube -= cylinder( r=inner_rad, h=length, segments=segments_per_rot) + tube = cylinder(r=inner_rad+outline_w+EPSILON, h=length, segments=segments_per_rot) + tube -= cylinder(r=inner_rad, h=length, segments=segments_per_rot) else: # If the threading is internal, intersect with a central cylinder # to make sure nothing else remains - tube = cylinder( r=inner_rad, h=length, segments=segments_per_rot) + tube = cylinder(r=inner_rad, h=length, segments=segments_per_rot) a *= tube return render()(a) -def default_thread_section( tooth_height, tooth_depth): +def default_thread_section(tooth_height, tooth_depth): # An isoceles triangle, tooth_height vertically, tooth_depth wide: res = [ [ 0, -tooth_height/2], [ tooth_depth, 0], @@ -161,11 +161,11 @@ def assembly(): [ -1, 0, 0], [ -1, -1, 0] ] - a = thread( pts, inner_rad=10, pitch= 6, length=2, segments_per_rot=31, + a = thread(pts, inner_rad=10, pitch= 6, length=2, segments_per_rot=31, neck_in_degrees=30, neck_out_degrees=30) - return a + cylinder( 10+EPSILON, 2) + return a + cylinder(10+EPSILON, 2) if __name__ == '__main__': a = assembly() - scad_render_to_file( a) \ No newline at end of file + scad_render_to_file(a) \ No newline at end of file diff --git a/solid/solidpython.py b/solid/solidpython.py index 25ae305f..914fa3c5 100755 --- a/solid/solidpython.py +++ b/solid/solidpython.py @@ -69,14 +69,14 @@ # Modifiers; These are implemented by calling e.g. # obj.set_modifier( '*') or # obj.set_modifier('disable') - # disable( obj) + # disable(obj) # on an existing object. # {'name': 'background', 'args': [], 'kwargs': []}, # %{} # {'name': 'debug', 'args': [], 'kwargs': []} , # #{} # {'name': 'root', 'args': [], 'kwargs': []} , # !{} # {'name': 'disable', 'args': [], 'kwargs': []} , # *{} - {'name': 'intersection_for', 'args': ['n'], 'kwargs': []} , # e.g.: intersection_for( n=[1..6]){} + {'name': 'intersection_for', 'args': ['n'], 'kwargs': []} , # e.g.: intersection_for(n=[1..6]){} # Unneeded {'name': 'assign', 'args': [], 'kwargs': []} # Not really needed for Python. Also needs a **args argument so it accepts anything @@ -84,36 +84,36 @@ # Some functions need custom code in them; put that code here builtin_literals = { - 'polygon': '''class polygon( openscad_object): - def __init__( self, points, paths=None): + 'polygon': '''class polygon(OpenSCADObject): + def __init__(self, points, paths=None): if not paths: - paths = [ list(range( len( points)))] - openscad_object.__init__( self, 'polygon', {'points':points, 'paths': paths}) + paths = [ list(range(len(points)))] + OpenSCADObject.__init__(self, 'polygon', {'points':points, 'paths': paths}) ''', - 'hole':'''class hole( openscad_object): - def __init__( self): - openscad_object.__init__( self, 'hole', {}) - self.set_hole( True) + 'hole':'''class hole(OpenSCADObject): + def __init__(self): + OpenSCADObject.__init__(self, 'hole', {}) + self.set_hole(True) ''', - 'part':'''class part( openscad_object): - def __init__( self): - openscad_object.__init__(self, 'part', {}) - self.set_part_root( True) + 'part':'''class part(OpenSCADObject): + def __init__(self): + OpenSCADObject.__init__(self, 'part', {}) + self.set_part_root(True) ''', # Import, import_dxf, and import_stl all resolve to the same OpenSCAD keyword, 'import' - 'import_':'''class import_( openscad_object): - def __init__( self, file, origin=(0,0), layer=None): - openscad_object.__init__(self, 'import', {'file':file, 'origin':origin, 'layer':layer}) + 'import_':'''class import_(OpenSCADObject): + def __init__(self, file, origin=(0,0), layer=None): + OpenSCADObject.__init__(self, 'import', {'file':file, 'origin':origin, 'layer':layer}) ''', - 'import_dxf':'''class import_dxf( openscad_object): - def __init__( self, file, origin=(0,0), layer=None): - openscad_object.__init__(self, 'import', {'file':file, 'origin':origin, 'layer':layer}) + 'import_dxf':'''class import_dxf(OpenSCADObject): + def __init__(self, file, origin=(0,0), layer=None): + OpenSCADObject.__init__(self, 'import', {'file':file, 'origin':origin, 'layer':layer}) ''', - 'import_stl':'''class import_stl( openscad_object): - def __init__( self, file, origin=(0,0), layer=None): - openscad_object.__init__(self, 'import', {'file':file, 'origin':origin, 'layer':layer}) + 'import_stl':'''class import_stl(OpenSCADObject): + def __init__(self, file, origin=(0,0), layer=None): + OpenSCADObject.__init__(self, 'import', {'file':file, 'origin':origin, 'layer':layer}) ''' } @@ -124,19 +124,19 @@ def __init__( self, file, origin=(0,0), layer=None): # ================================ # = Modifier Convenience Methods = # ================================ -def debug( openscad_obj): +def debug(openscad_obj): openscad_obj.set_modifier("#") return openscad_obj -def background( openscad_obj): +def background(openscad_obj): openscad_obj.set_modifier("%") return openscad_obj -def root( openscad_obj): +def root(openscad_obj): openscad_obj.set_modifier("!") return openscad_obj -def disable( openscad_obj): +def disable(openscad_obj): openscad_obj.set_modifier("*") return openscad_obj @@ -166,41 +166,41 @@ def use(scad_file_path, use_not_include=True): raise Exception("Failed to import SCAD module '%(scad_file_path)s' with error: %(e)s "%vars()) # Once we have a list of all callables and arguments, dynamically - # add openscad_object subclasses for all callables to the calling module's + # add OpenSCADObject subclasses for all callables to the calling module's # namespace. - symbols_dicts = extract_callable_signatures( scad_file_path) + symbols_dicts = extract_callable_signatures(scad_file_path) for sd in symbols_dicts: - class_str = new_openscad_class_str( sd['name'], sd['args'], sd['kwargs'], scad_file_path, use_not_include) + class_str = new_openscad_class_str(sd['name'], sd['args'], sd['kwargs'], scad_file_path, use_not_include) # If this is called from 'include', we have to look deeper in the stack # to find the right module to add the new class to. stack_depth = 2 if use_not_include else 3 - exec(class_str, calling_module( stack_depth).__dict__) + exec(class_str, calling_module(stack_depth).__dict__) return True -def include( scad_file_path): - return use( scad_file_path, use_not_include=False) +def include(scad_file_path): + return use(scad_file_path, use_not_include=False) # ========================================= # = Rendering Python code to OpenSCAD code= # ========================================= -def _find_include_strings( obj): +def _find_include_strings(obj): include_strings = set() - if isinstance( obj, included_openscad_object): - include_strings.add( obj.include_string ) + if isinstance(obj, IncludedOpenSCADObject): + include_strings.add(obj.include_string ) for child in obj.children: - include_strings.update( _find_include_strings( child)) + include_strings.update(_find_include_strings(child)) return include_strings -def scad_render( scad_object, file_header=''): +def scad_render(scad_object, file_header=''): # Make this object the root of the tree root = scad_object # Scan the tree for all instances of - # included_openscad_object, storing their strings - include_strings = _find_include_strings( root) + # IncludedOpenSCADObject, storing their strings + include_strings = _find_include_strings(root) # and render the string includes = ''.join(include_strings) + "\n" @@ -209,7 +209,7 @@ def scad_render( scad_object, file_header=''): def scad_render_animated(func_to_animate, steps=20, back_and_forth=True, filepath=None, file_header=''): # func_to_animate takes a single float argument, _time in [0, 1), and - # returns an openscad_object instance. + # returns an OpenSCADObject instance. # # Outputs an OpenSCAD file with func_to_animate() evaluated at "steps" # points between 0 & 1, with time never evaluated at exactly 1 @@ -238,7 +238,7 @@ def scad_render_animated(func_to_animate, steps=20, back_and_forth=True, filepat # to be animated with an identical number of steps to the way it was # created. -ETJ 28 Mar 2013 scad_obj = func_to_animate() - include_strings = _find_include_strings( scad_obj) + include_strings = _find_include_strings(scad_obj) # and render the string includes = ''.join(include_strings) + "\n" @@ -247,7 +247,7 @@ def scad_render_animated(func_to_animate, steps=20, back_and_forth=True, filepat if back_and_forth: steps *= 2 - for i in range( steps): + for i in range(steps): time = i *1.0/steps end_time = (i+1)*1.0/steps eval_time = time @@ -258,36 +258,35 @@ def scad_render_animated(func_to_animate, steps=20, back_and_forth=True, filepat eval_time = time * 2 else: eval_time = 2 - 2*time - scad_obj = func_to_animate( _time=eval_time) + scad_obj = func_to_animate(_time=eval_time) - scad_str = indent( scad_obj._render()) + scad_str = indent(scad_obj._render()) rendered_string += ( "if ($t >= %(time)s && $t < %(end_time)s){" " %(scad_str)s\n" "}\n"%vars()) return rendered_string -def scad_render_animated_file( func_to_animate, steps=20, back_and_forth=True, filepath=None, file_header='', include_orig_code=True): +def scad_render_animated_file(func_to_animate, steps=20, back_and_forth=True, filepath=None, file_header='', include_orig_code=True): rendered_string = scad_render_animated(func_to_animate, steps, back_and_forth, file_header) return _write_code_to_file(rendered_string, filepath, include_orig_code) -def scad_render_to_file( scad_object, filepath=None, file_header='', include_orig_code=True): - rendered_string = scad_render( scad_object, file_header) +def scad_render_to_file(scad_object, filepath=None, file_header='', include_orig_code=True): + rendered_string = scad_render(scad_object, file_header) return _write_code_to_file(rendered_string, filepath, include_orig_code) - - + def _write_code_to_file(rendered_string, filepath=None, include_orig_code=True): try: - calling_file = os.path.abspath( calling_module(stack_depth=3).__file__) + calling_file = os.path.abspath(calling_module(stack_depth=3).__file__) if include_orig_code: - rendered_string += sp_code_in_scad_comment( calling_file) + rendered_string += sp_code_in_scad_comment(calling_file) # This write is destructive, and ought to do some checks that the write # was successful. # If filepath isn't supplied, place a .scad file with the same name # as the calling module next to it if not filepath: - filepath = os.path.splitext( calling_file)[0] + '.scad' + filepath = os.path.splitext(calling_file)[0] + '.scad' except AttributeError as e: # If no calling_file was found, this is being called from the terminal. # We can't read original code from a file, so don't try, @@ -296,12 +295,12 @@ def _write_code_to_file(rendered_string, filepath=None, include_orig_code=True): if not filepath: filepath = os.path.abspath('.') + "/solid.scad" - f = open( filepath,"w") - f.write( rendered_string) + f = open(filepath,"w") + f.write(rendered_string) f.close() return True -def sp_code_in_scad_comment( calling_file): +def sp_code_in_scad_comment(calling_file): # Once a SCAD file has been created, it's difficult to reconstruct # how it got there, since it has no variables, modules, etc. So, include # the Python code that generated the scad code as comments at the end of @@ -328,7 +327,7 @@ def sp_code_in_scad_comment( calling_file): # ========================= # = Internal Utilities = # ========================= -class openscad_object( object): +class OpenSCADObject(object): def __init__(self, name, params): self.name = name self.params = params @@ -339,17 +338,17 @@ def __init__(self, name, params): self.has_hole_children = False self.is_part_root = False - def set_hole( self, is_hole=True): + def set_hole(self, is_hole=True): self.is_hole = is_hole return self - def set_part_root( self, is_root=True): + def set_part_root(self, is_root=True): self.is_part_root = is_root return self - def find_hole_children( self, path=None): + def find_hole_children(self, path=None): # Because we don't force a copy every time we re-use a node - # (e.g a = cylinder(2, 6); b = right( 10) (a) + # (e.g a = cylinder(2, 6); b = right(10) (a) # the identical 'a' object appears in the tree twice), # we can't count on an object's 'parent' field to trace its # path to the root. Instead, keep track explicitly @@ -357,9 +356,9 @@ def find_hole_children( self, path=None): hole_kids = [] for child in self.children: - path.append( child) + path.append(child) if child.is_hole: - hole_kids.append( child) + hole_kids.append(child) # Mark all parents as having a hole child for p in path: p.has_hole_children = True @@ -368,7 +367,7 @@ def find_hole_children( self, path=None): continue # Otherwise, look below us for children else: - hole_kids += child.find_hole_children( path) + hole_kids += child.find_hole_children(path) path.pop( ) return hole_kids @@ -402,7 +401,7 @@ def _render(self, render_holes=False): # And render after everything else if not render_holes and child.is_hole: continue - s += child._render( render_holes) + s += child._render(render_holes) # Then render self and prepend/wrap it around the children # I've added designated parts and explicit holes to SolidPython. @@ -412,7 +411,7 @@ def _render(self, render_holes=False): elif not self.children: s = self._render_str_no_children() + ";" else: - s = self._render_str_no_children() + " {" + indent( s) + "\n}" + s = self._render_str_no_children() + " {" + indent(s) + "\n}" # If this is the root object or the top of a separate part, # find all holes and subtract them after all positive geometry @@ -428,7 +427,7 @@ def _render(self, render_holes=False): s = "\ndifference(){" + indent(s) + " /* End Holes */ \n}" return s - def _render_str_no_children( self): + def _render_str_no_children(self): s = "\n" + self.modifier + self.name + "(" first = True @@ -465,7 +464,7 @@ def _render_str_no_children( self): s += ")" return s - def _render_hole_children( self): + 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: @@ -473,10 +472,10 @@ def _render_hole_children( self): s = "" for child in self.children: if child.is_hole: - s += child._render( render_holes=True) + s += child._render(render_holes=True) elif child.has_hole_children: # Holes exist in the compiled tree in two pieces: - # The shapes of the holes themselves, ( an object for which + # The shapes of the holes themselves, (an object for which # obj.is_hole is True, and all its children) and the # transforms necessary to put that hole in place, which # are inherited from non-hole geometry. @@ -498,29 +497,29 @@ def _render_hole_children( self): if self.name in non_rendered_classes: pass else: - s = self._render_str_no_children() + "{" + indent( s) + "\n}" + s = self._render_str_no_children() + "{" + indent(s) + "\n}" return s def add(self, child): ''' - if child is a single object, assume it's an openscad_object and + if child is a single object, assume it's an OpenSCADObject and add it to self.children - if child is a list, assume its members are all openscad_objects and + if child is a list, assume its members are all OpenSCADObjects and add them all to self.children ''' - if isinstance( child, (list, tuple)): + if isinstance(child, (list, tuple)): # __call__ passes us a list inside a tuple, but we only care # about the list, so skip single-member tuples containing lists - if len( child) == 1 and isinstance(child[0], (list, tuple)): + if len(child) == 1 and isinstance(child[0], (list, tuple)): child = child[0] - [self.add( c ) for c in child] + [self.add(c ) for c in child] else: - self.children.append( child) - child.set_parent( self) + self.children.append(child) + child.set_parent(self) return self - def set_parent( self, parent): + def set_parent(self, parent): self.parent = parent def add_param(self, k, v): @@ -529,7 +528,7 @@ def add_param(self, k, v): self.params[k] = v return self - def copy( self): + 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 @@ -545,15 +544,15 @@ def copy( self): self.params['segments'] = self.params.pop('$fn') other = globals()[ self.name]( **self.params) - other.set_modifier( self.modifier) - other.set_hole( self.is_hole) - other.set_part_root( self.is_part_root) + other.set_modifier(self.modifier) + other.set_hole(self.is_hole) + other.set_part_root(self.is_part_root) other.has_hole_children = self.has_hole_children for c in self.children: - other.add( c.copy()) + other.add(c.copy()) return other - def __call__( self, *args): + def __call__(self, *args): ''' Adds all objects in args to self. This enables OpenSCAD-like syntax, e.g.: @@ -567,33 +566,33 @@ def __call__( self, *args): def __add__(self, x): ''' This makes u = a+b identical to: - u = union()( a, b ) + u = union()(a, b ) ''' return union()(self, x) def __sub__(self, x): ''' This makes u = a - b identical to: - u = difference()( a, b ) + u = difference()(a, b ) ''' return difference()(self, x) def __mul__(self, x): ''' This makes u = a * b identical to: - u = intersection()( a, b ) + u = intersection()(a, b ) ''' return intersection()(self, x) -class included_openscad_object( openscad_object): +class IncludedOpenSCADObject(OpenSCADObject): ''' - Identical to openscad_object, but each subclass of included_openscad_object + Identical to OpenSCADObject, but each subclass of IncludedOpenSCADObject represents imported scad code, so each instance needs to store the path to the scad file it's included from. ''' - def __init__( self, name, params, include_file_path, use_not_include=False, **kwargs): - self.include_file_path = self._get_include_path( include_file_path) + def __init__(self, name, params, include_file_path, use_not_include=False, **kwargs): + self.include_file_path = self._get_include_path(include_file_path) if use_not_include: self.include_string = 'use <%s>\n'%self.include_file_path @@ -602,9 +601,9 @@ def __init__( self, name, params, include_file_path, use_not_include=False, **kw # Just pass any extra arguments straight on to OpenSCAD; it'll accept them if kwargs: - params.update( kwargs) + params.update(kwargs) - openscad_object.__init__(self, name, params) + OpenSCADObject.__init__(self, name, params) def _get_include_path(self, include_file_path): # Look through sys.path for anyplace we can find a valid file ending @@ -622,7 +621,7 @@ def _get_include_path(self, include_file_path): "%(include_file_path)s in sys.path"%vars()) -def calling_module( stack_depth=2): +def calling_module(stack_depth=2): ''' Returns the module *2* back in the frame stack. That means: code in module A calls code in module B, which asks calling_module() @@ -636,7 +635,7 @@ def calling_module( stack_depth=2): Got that? ''' frm = inspect.stack()[stack_depth] - calling_mod = inspect.getmodule( frm[0]) + calling_mod = inspect.getmodule(frm[0]) # If calling_mod is None, this is being called from an interactive session. # Return that module. (Note that __main__ doesn't have a __file__ attr, # but that's caught elsewhere.) @@ -644,7 +643,7 @@ def calling_module( stack_depth=2): import __main__ as calling_mod return calling_mod -def new_openscad_class_str( class_name, args=[], kwargs=[], include_file_path=None, use_not_include=True): +def new_openscad_class_str(class_name, args=[], kwargs=[], include_file_path=None, use_not_include=True): args_str = '' args_pairs = '' @@ -668,15 +667,15 @@ def new_openscad_class_str( class_name, args=[], kwargs=[], include_file_path=No # NOTE the explicit import of 'solid' below. This is a fix for: # https://github.com/SolidCode/SolidPython/issues/20 -ETJ 16 Jan 2014 result = ("import solid\n" - "class %(class_name)s( solid.included_openscad_object):\n" + "class %(class_name)s(solid.IncludedOpenSCADObject):\n" " def __init__(self%(args_str)s, **kwargs):\n" - " solid.included_openscad_object.__init__(self, '%(class_name)s', {%(args_pairs)s }, include_file_path='%(include_file_path)s', use_not_include=%(use_not_include)s, **kwargs )\n" + " solid.IncludedOpenSCADObject.__init__(self, '%(class_name)s', {%(args_pairs)s }, include_file_path='%(include_file_path)s', use_not_include=%(use_not_include)s, **kwargs )\n" " \n" "\n"%vars()) else: - result = ("class %(class_name)s( openscad_object):\n" + result = ("class %(class_name)s(OpenSCADObject):\n" " def __init__(self%(args_str)s):\n" - " openscad_object.__init__(self, '%(class_name)s', {%(args_pairs)s })\n" + " OpenSCADObject.__init__(self, '%(class_name)s', {%(args_pairs)s })\n" " \n" "\n"%vars()) @@ -708,19 +707,19 @@ def indent(s): # =========== # = Parsing = # =========== -def extract_callable_signatures( scad_file_path): +def extract_callable_signatures(scad_file_path): with open(scad_file_path) as f: scad_code_str = f.read() - return parse_scad_callables( scad_code_str) + return parse_scad_callables(scad_code_str) -def parse_scad_callables( scad_code_str): +def parse_scad_callables(scad_code_str): callables = [] # Note that this isn't comprehensive; tuples or nested data structures in # a module definition will defeat it. # Current implementation would throw an error if you tried to call a(x, y) - # since Python would expect a( x); OpenSCAD itself ignores extra arguments, + # since Python would expect a(x); OpenSCAD itself ignores extra arguments, # but that's not really preferable behavior # TODO: write a pyparsing grammar for OpenSCAD, or, even better, use the yacc parse grammar @@ -739,7 +738,7 @@ def parse_scad_callables( scad_code_str): # remove all comments from SCAD code scad_code_str = re.sub(no_comments_re,'', scad_code_str) # get all SCAD callables - mod_matches = re.finditer( mod_re, scad_code_str) + mod_matches = re.finditer(mod_re, scad_code_str) for m in mod_matches: callable_name = m.group('callable_name') @@ -747,13 +746,13 @@ def parse_scad_callables( scad_code_str): kwargs = [] all_args = m.group('all_args') if all_args: - arg_matches = re.finditer( args_re, all_args) + arg_matches = re.finditer(args_re, all_args) for am in arg_matches: arg_name = am.group('arg_name') if am.group('default_val'): - kwargs.append( arg_name) + kwargs.append(arg_name) else: - args.append( arg_name) + args.append(arg_name) callables.append( { 'name':callable_name, 'args': args, 'kwargs':kwargs}) @@ -766,7 +765,7 @@ def parse_scad_callables( scad_code_str): if sym_dict['name'] in builtin_literals: class_str = builtin_literals[ sym_dict['name']] else: - class_str = new_openscad_class_str( sym_dict['name'], sym_dict['args'], sym_dict['kwargs']) + class_str = new_openscad_class_str(sym_dict['name'], sym_dict['args'], sym_dict['kwargs']) exec(class_str) diff --git a/solid/t_slots.py b/solid/t_slots.py index b2564c2a..59579053 100755 --- a/solid/t_slots.py +++ b/solid/t_slots.py @@ -21,7 +21,7 @@ # It might be easier to have the edges NOT overlap at all and then have tabs # for the slots added programmatically. -ETJ 06 Mar 2013 -def t_slot_holes( poly, point=None, edge_vec=RIGHT_VEC, screw_vec=DOWN_VEC, screw_type='m3', screw_length=16, material_thickness=DFM, kerf=0 ): +def t_slot_holes(poly, point=None, edge_vec=RIGHT_VEC, screw_vec=DOWN_VEC, screw_type='m3', screw_length=16, material_thickness=DFM, kerf=0 ): ''' Cuts a screw hole and two notches in poly so they'll interface with the features cut by t_slot() @@ -38,29 +38,29 @@ def t_slot_holes( poly, point=None, edge_vec=RIGHT_VEC, screw_vec=DOWN_VEC, scre TODO: add kerf calculations ''' point = point if point else ORIGIN - point = euclidify( point, Point3) - screw_vec = euclidify( screw_vec, Vector3) - edge_vec = euclidify( edge_vec, Vector3) + point = euclidify(point, Point3) + screw_vec = euclidify(screw_vec, Vector3) + edge_vec = euclidify(edge_vec, Vector3) - src_up = screw_vec.cross( edge_vec) + src_up = screw_vec.cross(edge_vec) a_hole = square( [tab_width, material_thickness], center=True) move_hole = tab_offset + tab_width/2 - tab_holes = left( move_hole)( a_hole) + right( move_hole)( a_hole) + tab_holes = left(move_hole)(a_hole) + right(move_hole)(a_hole) # Only valid for m3-m5 screws now - screw_dict = screw_dimensions.get( screw_type.lower()) + screw_dict = screw_dimensions.get(screw_type.lower()) if screw_dict: screw_w = screw_dict['screw_outer_diam'] else: raise ValueError( "Don't have screw dimensions for requested screw size %s"%screw_type) # add the screw hole - tab_holes += circle( screw_w/2) # NOTE: needs any extra space? + tab_holes += circle(screw_w/2) # NOTE: needs any extra space? - tab_holes = transform_to_point( tab_holes, point, dest_normal=screw_vec, src_normal=UP_VEC, src_up=src_up) + tab_holes = transform_to_point(tab_holes, point, dest_normal=screw_vec, src_normal=UP_VEC, src_up=src_up) return poly - tab_holes @@ -85,20 +85,20 @@ def t_slot( poly, point=None, screw_vec=DOWN_VEC, face_normal=UP_VEC, scre TODO: include kerf in calculations ''' point = point if point else ORIGIN - point = euclidify( point, Point3) - screw_vec = euclidify( screw_vec, Vector3) - face_normal = euclidify( face_normal, Vector3) + point = euclidify(point, Point3) + screw_vec = euclidify(screw_vec, Vector3) + face_normal = euclidify(face_normal, Vector3) - tab = tab_poly( material_thickness=material_thickness) - slot = nut_trap_slot( screw_type, screw_length, material_thickness=material_thickness) + tab = tab_poly(material_thickness=material_thickness) + slot = nut_trap_slot(screw_type, screw_length, material_thickness=material_thickness) # NOTE: dest_normal & src_normal are the same. This should matter, right? - tab = transform_to_point( tab, point, dest_normal=face_normal, src_normal=face_normal, src_up=-screw_vec) - slot = transform_to_point( slot, point, dest_normal=face_normal, src_normal=face_normal, src_up=-screw_vec) + tab = transform_to_point(tab, point, dest_normal=face_normal, src_normal=face_normal, src_up=-screw_vec) + slot = transform_to_point(slot, point, dest_normal=face_normal, src_normal=face_normal, src_up=-screw_vec) return poly + tab - slot -def tab_poly( material_thickness=DFM): +def tab_poly(material_thickness=DFM): r = [ [ tab_width + tab_offset, -EPSILON], [ tab_offset, -EPSILON], @@ -109,7 +109,7 @@ def tab_poly( material_thickness=DFM): tab_pts = l + r tab_faces = [[0,1,2,3], [4,5,6,7]] - tab = polygon( tab_pts, tab_faces) + tab = polygon(tab_pts, tab_faces) # Round off the top points so tabs slide in more easily round_tabs = False @@ -119,13 +119,13 @@ def tab_poly( material_thickness=DFM): [l[1], l[2], l[3]], [l[2], l[3], l[0]], ] - tab = fillet_2d( three_point_sets=points_to_round, orig_poly=tab, + tab = fillet_2d(three_point_sets=points_to_round, orig_poly=tab, fillet_rad=1, remove_material=True) return tab -def nut_trap_slot( screw_type='m3', screw_length=16, material_thickness=DFM): +def nut_trap_slot(screw_type='m3', screw_length=16, material_thickness=DFM): # This shape has a couple uses. # 1) Right angle joint between two pieces of material. # A bolt goes through the second piece and into the first. @@ -137,7 +137,7 @@ def nut_trap_slot( screw_type='m3', screw_length=16, material_thickness=DFM): # Only valid for m3-m5 screws now - screw_dict = screw_dimensions.get( screw_type.lower()) + screw_dict = screw_dimensions.get(screw_type.lower()) if screw_dict: screw_w = screw_dict['screw_outer_diam'] screw_w2 = screw_w/2 @@ -164,11 +164,11 @@ def nut_trap_slot( screw_type='m3', screw_length=16, material_thickness=DFM): # TODO: round off top corners of slot # Add circles around t edges to prevent acrylic breakage - slot = polygon( slot_pts) + slot = polygon(slot_pts) slot = union()( slot, - translate( [nut_hole_x, nut_loc])( circle( tab_curve_rad)), - translate( [-nut_hole_x, nut_loc])( circle( tab_curve_rad)) + translate( [nut_hole_x, nut_loc])(circle(tab_curve_rad)), + translate( [-nut_hole_x, nut_loc])(circle(tab_curve_rad)) ) return render()(slot) @@ -179,4 +179,4 @@ def assembly(): if __name__ == '__main__': a = assembly() - scad_render_to_file( a, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True) + scad_render_to_file(a, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True) diff --git a/solid/test/test_screw_thread.py b/solid/test/test_screw_thread.py index 899c69d7..f27cdc97 100755 --- a/solid/test/test_screw_thread.py +++ b/solid/test/test_screw_thread.py @@ -16,30 +16,30 @@ class TestScrewThread(DiffOutput): def test_thread(self): tooth_height = 10 tooth_depth = 5 - outline = default_thread_section( tooth_height=tooth_height, tooth_depth=tooth_depth) - actual_obj = thread( outline_pts=outline, inner_rad=20, pitch=tooth_height, + outline = default_thread_section(tooth_height=tooth_height, tooth_depth=tooth_depth) + actual_obj = thread(outline_pts=outline, inner_rad=20, pitch=tooth_height, length=0.75*tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45) - actual = scad_render( actual_obj) + actual = scad_render(actual_obj) expected = '\n\nrender() {\n\tintersection() {\n\t\tpolyhedron(faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], [3, 4, 6], [4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], [8, 11, 10], [6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], [9, 14, 11], [9, 12, 14], [12, 13, 15], [13, 16, 15], [13, 14, 16], [14, 17, 16], [12, 17, 14], [12, 15, 17], [15, 16, 18], [16, 19, 18], [16, 17, 19], [17, 20, 19], [15, 20, 17], [15, 18, 20], [0, 2, 1], [18, 19, 20]], points = [[14.9900000000, 0.0000000000, -5.0000000000], [19.9900000000, 0.0000000000, 0.0000000000], [14.9900000000, 0.0000000000, 5.0000000000], [14.1421356237, 14.1421356237, -3.7500000000], [17.6776695297, 17.6776695297, 1.2500000000], [14.1421356237, 14.1421356237, 6.2500000000], [0.0000000000, 20.0000000000, -2.5000000000], [0.0000000000, 25.0000000000, 2.5000000000], [0.0000000000, 20.0000000000, 7.5000000000], [-14.1421356237, 14.1421356237, -1.2500000000], [-17.6776695297, 17.6776695297, 3.7500000000], [-14.1421356237, 14.1421356237, 8.7500000000], [-20.0000000000, 0.0000000000, 0.0000000000], [-25.0000000000, 0.0000000000, 5.0000000000], [-20.0000000000, 0.0000000000, 10.0000000000], [-14.1421356237, -14.1421356237, 1.2500000000], [-17.6776695297, -17.6776695297, 6.2500000000], [-14.1421356237, -14.1421356237, 11.2500000000], [-0.0000000000, -14.9900000000, 2.5000000000], [-0.0000000000, -19.9900000000, 7.5000000000], [-0.0000000000, -14.9900000000, 12.5000000000]]);\n\t\tdifference() {\n\t\t\tcylinder($fn = 8, h = 7.5000000000, r = 25.0100000000);\n\t\t\tcylinder($fn = 8, h = 7.5000000000, r = 20);\n\t\t}\n\t}\n}' - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) - def test_thread_internal( self): + def test_thread_internal(self): tooth_height = 10 tooth_depth = 5 - outline = default_thread_section( tooth_height=tooth_height, tooth_depth=tooth_depth) - actual_obj = thread( outline_pts=outline, inner_rad=20, pitch=2*tooth_height, + outline = default_thread_section(tooth_height=tooth_height, tooth_depth=tooth_depth) + actual_obj = thread(outline_pts=outline, inner_rad=20, pitch=2*tooth_height, length=2*tooth_height, segments_per_rot=SEGMENTS, neck_in_degrees=45, neck_out_degrees=45, external=False) - actual = scad_render( actual_obj) + actual = scad_render(actual_obj) expected = '\n\nrender() {\n\tintersection() {\n\t\tpolyhedron(faces = [[0, 1, 3], [1, 4, 3], [1, 2, 4], [2, 5, 4], [0, 5, 2], [0, 3, 5], [3, 4, 6], [4, 7, 6], [4, 5, 7], [5, 8, 7], [3, 8, 5], [3, 6, 8], [6, 7, 9], [7, 10, 9], [7, 8, 10], [8, 11, 10], [6, 11, 8], [6, 9, 11], [9, 10, 12], [10, 13, 12], [10, 11, 13], [11, 14, 13], [9, 14, 11], [9, 12, 14], [12, 13, 15], [13, 16, 15], [13, 14, 16], [14, 17, 16], [12, 17, 14], [12, 15, 17], [15, 16, 18], [16, 19, 18], [16, 17, 19], [17, 20, 19], [15, 20, 17], [15, 18, 20], [18, 19, 21], [19, 22, 21], [19, 20, 22], [20, 23, 22], [18, 23, 20], [18, 21, 23], [21, 22, 24], [22, 25, 24], [22, 23, 25], [23, 26, 25], [21, 26, 23], [21, 24, 26], [0, 2, 1], [24, 25, 26]], points = [[25.0100000000, 0.0000000000, -5.0000000000], [20.0100000000, 0.0000000000, 0.0000000000], [25.0100000000, 0.0000000000, 5.0000000000], [14.1421356237, 14.1421356237, -2.5000000000], [10.6066017178, 10.6066017178, 2.5000000000], [14.1421356237, 14.1421356237, 7.5000000000], [0.0000000000, 20.0000000000, 0.0000000000], [0.0000000000, 15.0000000000, 5.0000000000], [0.0000000000, 20.0000000000, 10.0000000000], [-14.1421356237, 14.1421356237, 2.5000000000], [-10.6066017178, 10.6066017178, 7.5000000000], [-14.1421356237, 14.1421356237, 12.5000000000], [-20.0000000000, 0.0000000000, 5.0000000000], [-15.0000000000, 0.0000000000, 10.0000000000], [-20.0000000000, 0.0000000000, 15.0000000000], [-14.1421356237, -14.1421356237, 7.5000000000], [-10.6066017178, -10.6066017178, 12.5000000000], [-14.1421356237, -14.1421356237, 17.5000000000], [-0.0000000000, -20.0000000000, 10.0000000000], [-0.0000000000, -15.0000000000, 15.0000000000], [-0.0000000000, -20.0000000000, 20.0000000000], [14.1421356237, -14.1421356237, 12.5000000000], [10.6066017178, -10.6066017178, 17.5000000000], [14.1421356237, -14.1421356237, 22.5000000000], [25.0100000000, -0.0000000000, 15.0000000000], [20.0100000000, -0.0000000000, 20.0000000000], [25.0100000000, -0.0000000000, 25.0000000000]]);\n\t\tcylinder($fn = 8, h = 20, r = 20);\n\t}\n}' - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) def test_default_thread_section(self): expected = [[0, -5], [5, 0], [0, 5]] - actual = default_thread_section( tooth_height=10, tooth_depth=5) - self.assertEqual( expected, actual) + actual = default_thread_section(tooth_height=10, tooth_depth=5) + self.assertEqual(expected, actual) diff --git a/solid/test/test_solidpython.py b/solid/test/test_solidpython.py index 2def8b08..93921f02 100755 --- a/solid/test/test_solidpython.py +++ b/solid/test/test_solidpython.py @@ -50,55 +50,55 @@ def expand_scad_path(self, filename): return full_path return None - def test_infix_union( self): + def test_infix_union(self): a = cube(2) - b = sphere( 2) + b = sphere(2) expected = '\n\nunion() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}' - actual = scad_render( a+b) - self.assertEqual( expected, actual) + actual = scad_render(a+b) + self.assertEqual(expected, actual) - def test_infix_difference( self): + def test_infix_difference(self): a = cube(2) - b = sphere( 2) + b = sphere(2) expected = '\n\ndifference() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}' - actual = scad_render( a-b) - self.assertEqual( expected, actual) + actual = scad_render(a-b) + self.assertEqual(expected, actual) - def test_infix_intersection( self): + def test_infix_intersection(self): a = cube(2) - b = sphere( 2) + b = sphere(2) expected = '\n\nintersection() {\n\tcube(size = 2);\n\tsphere(r = 2);\n}' - actual = scad_render( a*b) - self.assertEqual( expected, actual) + actual = scad_render(a*b) + self.assertEqual(expected, actual) - def test_parse_scad_callables( self): + def test_parse_scad_callables(self): test_str = ("" "module hex (width=10, height=10, \n" " flats= true, center=false){}\n" "function righty (angle=90) = 1;\n" - "function lefty( avar) = 2;\n" - "module more( a=[something, other]) {}\n" + "function lefty(avar) = 2;\n" + "module more(a=[something, other]) {}\n" "module pyramid(side=10, height=-1, square=false, centerHorizontal=true, centerVertical=false){}\n" - "module no_comments( arg=10, //test comment\n" + "module no_comments(arg=10, //test comment\n" "other_arg=2, /* some extra comments\n" "on empty lines */\n" "last_arg=4){}\n" - "module float_arg( arg=1.0){}\n") + "module float_arg(arg=1.0){}\n") expected = [{'args': [], 'name': 'hex', 'kwargs': ['width', 'height', 'flats', 'center']}, {'args': [], 'name': 'righty', 'kwargs': ['angle']}, {'args': ['avar'], 'name': 'lefty', 'kwargs': []}, {'args': [], 'name': 'more', 'kwargs': ['a']}, {'args': [], 'name': 'pyramid', 'kwargs': ['side', 'height', 'square', 'centerHorizontal', 'centerVertical']}, {'args': [], 'name': 'no_comments', 'kwargs': ['arg', 'other_arg', 'last_arg']}, {'args': [], 'name': 'float_arg', 'kwargs': ['arg']}] - actual = parse_scad_callables( test_str) - self.assertEqual( expected, actual) + actual = parse_scad_callables(test_str) + self.assertEqual(expected, actual) - def test_use( self): + def test_use(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") - use( include_file) + use(include_file) a = steps(3) - actual = scad_render( a) + actual = scad_render(a) - abs_path = a._get_include_path( include_file) + abs_path = a._get_include_path(include_file) expected = "use <%s>\n\n\nsteps(howmany = 3);"%abs_path - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) - def test_include( self): + def test_include(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") self.assertIsNotNone( include_file, @@ -107,80 +107,80 @@ def test_include( self): include(include_file) a = steps(3) - actual = scad_render( a) - abs_path = a._get_include_path( include_file) + actual = scad_render(a) + abs_path = a._get_include_path(include_file) expected = "include <%s>\n\n\nsteps(howmany = 3);"%abs_path - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) - def test_extra_args_to_included_scad( self): + def test_extra_args_to_included_scad(self): include_file = self.expand_scad_path("examples/scad_to_include.scad") use(include_file) - a = steps( 3, external_var=True) - actual = scad_render( a) + a = steps(3, external_var=True) + actual = scad_render(a) - abs_path = a._get_include_path( include_file) + abs_path = a._get_include_path(include_file) expected = "use <%s>\n\n\nsteps(external_var = true, howmany = 3);"%abs_path - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) - def test_background( self): + def test_background(self): a = cube(10) expected = '\n\n%cube(size = 10);' - actual = scad_render( background( a)) - self.assertEqual( expected, actual) + actual = scad_render(background(a)) + self.assertEqual(expected, actual) - def test_debug( self): + def test_debug(self): a = cube(10) expected = '\n\n#cube(size = 10);' - actual = scad_render( debug( a)) - self.assertEqual( expected, actual) + actual = scad_render(debug(a)) + self.assertEqual(expected, actual) - def test_disable( self): + def test_disable(self): a = cube(10) expected = '\n\n*cube(size = 10);' - actual = scad_render( disable( a)) - self.assertEqual( expected, actual) + actual = scad_render(disable(a)) + self.assertEqual(expected, actual) - def test_root( self): + def test_root(self): a = cube(10) expected = '\n\n!cube(size = 10);' - actual = scad_render( root( a)) - self.assertEqual( expected, actual) + actual = scad_render(root(a)) + self.assertEqual(expected, actual) - def test_explicit_hole( self): - a = cube( 10, center=True) + hole()( cylinder(2, 20, center=True)) + def test_explicit_hole(self): + a = cube(10, center=True) + hole()(cylinder(2, 20, center=True)) expected = '\n\ndifference(){\n\tunion() {\n\t\tcube(center = true, size = 10);\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\tcylinder(center = true, h = 20, r = 2);\n\t} /* End Holes */ \n}' - actual = scad_render( a) - self.assertEqual( expected, actual) + actual = scad_render(a) + self.assertEqual(expected, actual) - def test_hole_transform_propagation( self): + def test_hole_transform_propagation(self): # earlier versions of holes had problems where a hole # that was used a couple places wouldn't propagate correctly. # Confirm that's still happening as it's supposed to h = hole()( - rotate( a=90, v=[0, 1, 0])( + rotate(a=90, v=[0, 1, 0])( cylinder(2, 20, center=True) ) ) - h_vert = rotate( a=-90, v=[0, 1, 0])( + h_vert = rotate(a=-90, v=[0, 1, 0])( h ) - a = cube( 10, center=True) + h + h_vert + a = cube(10, center=True) + h + h_vert expected = '\n\ndifference(){\n\tunion() {\n\t\tunion() {\n\t\t\tcube(center = true, size = 10);\n\t\t}\n\t\trotate(a = -90, v = [0, 1, 0]) {\n\t\t}\n\t}\n\t/* Holes Below*/\n\tunion(){\n\t\tunion(){\n\t\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t\t}\n\t\t}\n\t\trotate(a = -90, v = [0, 1, 0]){\n\t\t\trotate(a = 90, v = [0, 1, 0]) {\n\t\t\t\tcylinder(center = true, h = 20, r = 2);\n\t\t\t}\n\t\t}\n\t} /* End Holes */ \n}' - actual = scad_render( a) - self.assertEqual( expected, actual) + actual = scad_render(a) + self.assertEqual(expected, actual) - def test_separate_part_hole( self): + def test_separate_part_hole(self): # Make two parts, a block with hole, and a cylinder that # fits inside it. Make them separate parts, meaning # holes will be defined at the level of the part_root node, # not the overall node. This allows us to preserve holes as # first class space, but then to actually fill them in with # the parts intended to fit in them. - b = cube( 10, center=True) - c = cylinder( r=2, h=12, center=True) + b = cube(10, center=True) + c = cylinder(r=2, h=12, center=True) p1 = b - hole()(c) # Mark this cube-with-hole as a separate part from the cylinder @@ -189,62 +189,62 @@ def test_separate_part_hole( self): # This fits in the hole. If p1 is set as a part_root, it will all appear. # If not, the portion of the cylinder inside the cube will not appear, # since it would have been removed by the hole in p1 - p2 = cylinder( r=1.5, h=14, center=True) + p2 = cylinder(r=1.5, h=14, center=True) a = p1 + p2 expected = '\n\nunion() {\n\tdifference(){\n\t\tdifference() {\n\t\t\tcube(center = true, size = 10);\n\t\t}\n\t\t/* Holes Below*/\n\t\tdifference(){\n\t\t\tcylinder(center = true, h = 12, r = 2);\n\t\t} /* End Holes */ \n\t}\n\tcylinder(center = true, h = 14, r = 1.5000000000);\n}' - actual = scad_render( a) - self.assertEqual( expected, actual) + actual = scad_render(a) + self.assertEqual(expected, actual) - def test_scad_render_animated_file( self): - def my_animate( _time=0): + def test_scad_render_animated_file(self): + def my_animate(_time=0): import math # _time will range from 0 to 1, not including 1 rads = _time * 2 * math.pi rad = 15 - c = translate( [rad*math.cos(rads), rad*math.sin(rads)])( square( 10)) + c = translate( [rad*math.cos(rads), rad*math.sin(rads)])(square(10)) return c tmp = tempfile.NamedTemporaryFile() - scad_render_animated_file( my_animate, steps=2, back_and_forth=False, + scad_render_animated_file(my_animate, steps=2, back_and_forth=False, filepath=tmp.name, include_orig_code=False) tmp.seek(0) actual = tmp.read() expected = b'\nif ($t >= 0.0 && $t < 0.5){ \n\ttranslate(v = [15.0000000000, 0.0000000000]) {\n\t\tsquare(size = 10);\n\t}\n}\nif ($t >= 0.5 && $t < 1.0){ \n\ttranslate(v = [-15.0000000000, 0.0000000000]) {\n\t\tsquare(size = 10);\n\t}\n}\n' tmp.close() - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) - def test_scad_render_to_file( self): + def test_scad_render_to_file(self): a = circle(10) # No header, no included original code tmp = tempfile.NamedTemporaryFile() - scad_render_to_file( a, filepath=tmp.name, include_orig_code=False) + scad_render_to_file(a, filepath=tmp.name, include_orig_code=False) tmp.seek(0) actual = tmp.read() expected = b'\n\ncircle(r = 10);' tmp.close() - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) # Header tmp = tempfile.NamedTemporaryFile() - scad_render_to_file( a, filepath=tmp.name, include_orig_code=False, + scad_render_to_file(a, filepath=tmp.name, include_orig_code=False, file_header='$fn = 24;') tmp.seek(0) actual = tmp.read() expected = b'$fn = 24;\n\ncircle(r = 10);' tmp.close() - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) # TODO: test include_orig_code=True, but that would have to # be done from a separate file, or include everything in this one -def single_test( test_dict): +def single_test(test_dict): name, args, kwargs, expected = test_dict['name'], test_dict['args'], test_dict['kwargs'], test_dict['expected'] - def test( self): + def test(self): call_str= name + "(" for k, v in args.items(): call_str += "%s=%s, "%(k,v) @@ -252,18 +252,18 @@ def test( self): call_str += "%s=%s, "%(k,v) call_str += ')' - scad_obj = eval( call_str) - actual = scad_render( scad_obj) + scad_obj = eval(call_str) + actual = scad_render(scad_obj) - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) return test def generate_cases_from_templates(): for test_dict in scad_test_case_templates: - test = single_test( test_dict) + test = single_test(test_dict) test_name = "test_%(name)s"%test_dict - setattr( TestSolidPython, test_name, test) + setattr(TestSolidPython, test_name, test) if __name__ == '__main__': diff --git a/solid/test/test_utils.py b/solid/test/test_utils.py index c7a7bc01..72d5b7c5 100755 --- a/solid/test/test_utils.py +++ b/solid/test/test_utils.py @@ -11,7 +11,7 @@ from solid.test.ExpandedTestCase import DiffOutput -tri = [Point3( 0,0,0), Point3( 10,0,0), Point3(0,10,0)] +tri = [Point3(0,0,0), Point3(10,0,0), Point3(0,10,0)] scad_test_cases = [ ( up, [2], '\n\ntranslate(v = [0, 0, 2]);'), ( down, [2], '\n\ntranslate(v = [0, 0, -2]);'), @@ -33,9 +33,9 @@ ( euclidify, [[0,0,0]], 'Vector3(0.00, 0.00, 0.00)'), ( 'euclidify_recursive', euclidify, [[[0,0,0], [1,0,0]]], '[Vector3(0.00, 0.00, 0.00), Vector3(1.00, 0.00, 0.00)]'), ( 'euclidify_Vector', euclidify, [Vector3(0,0,0)], 'Vector3(0.00, 0.00, 0.00)'), - ( 'euclidify_recursive_Vector', euclidify, [[Vector3( 0,0,0), Vector3( 0,0,1)]], '[Vector3(0.00, 0.00, 0.00), Vector3(0.00, 0.00, 1.00)]'), + ( 'euclidify_recursive_Vector', euclidify, [[Vector3(0,0,0), Vector3(0,0,1)]], '[Vector3(0.00, 0.00, 0.00), Vector3(0.00, 0.00, 1.00)]'), ( euc_to_arr, [Vector3(0,0,0)], '[0, 0, 0]'), - ( 'euc_to_arr_recursive', euc_to_arr, [[Vector3( 0,0,0), Vector3( 0,0,1)]], '[[0, 0, 0], [0, 0, 1]]'), + ( 'euc_to_arr_recursive', euc_to_arr, [[Vector3(0,0,0), Vector3(0,0,1)]], '[[0, 0, 0], [0, 0, 1]]'), ( 'euc_to_arr_arr', euc_to_arr, [[0,0,0]], '[0, 0, 0]'), ( 'euc_to_arr_arr_recursive', euc_to_arr, [[[0,0,0], [1,0,0]]], '[[0, 0, 0], [1, 0, 0]]'), ( is_scad, [cube(2)], 'True'), @@ -44,7 +44,7 @@ ( 'transform_to_point_single_pt3', transform_to_point, [Point3(1,0,0), [2,2,2], [3,3,1]], 'Point3(2.71, 1.29, 2.00)'), ( 'transform_to_point_arr_arr', transform_to_point, [[[1,0,0], [0,1,0], [0,0,1]] , [2,2,2], [3,3,1]], '[Point3(2.71, 1.29, 2.00), Point3(1.84, 1.84, 2.97), Point3(1.31, 1.31, 1.77)]'), ( 'transform_to_point_pt3_arr', transform_to_point, [[Point3(1,0,0), Point3(0,1,0), Point3(0,0,1)], [2,2,2], [3,3,1]], '[Point3(2.71, 1.29, 2.00), Point3(1.84, 1.84, 2.97), Point3(1.31, 1.31, 1.77)]') , - ( 'transform_to_point_redundant', transform_to_point, [ [Point3( 0,0,0), Point3( 10,0,0), Point3(0,10,0)], [2,2,2], Vector3(0,0,1), Point3(0,0,0), Vector3(0,1,0), Vector3(0,0,1)], '[Point3(2.00, 2.00, 2.00), Point3(-8.00, 2.00, 2.00), Point3(2.00, 12.00, 2.00)]'), + ( 'transform_to_point_redundant', transform_to_point, [ [Point3(0,0,0), Point3(10,0,0), Point3(0,10,0)], [2,2,2], Vector3(0,0,1), Point3(0,0,0), Vector3(0,1,0), Vector3(0,0,1)], '[Point3(2.00, 2.00, 2.00), Point3(-8.00, 2.00, 2.00), Point3(2.00, 12.00, 2.00)]'), ( 'offset_points_inside', offset_points, [tri, 2, True], '[Point3(2.00, 2.00, 0.00), Point3(5.17, 2.00, 0.00), Point3(2.00, 5.17, 0.00)]'), ( 'offset_points_outside', offset_points, [tri, 2, False], '[Point3(-2.00, -2.00, 0.00), Point3(14.83, -2.00, 0.00), Point3(-2.00, 14.83, 0.00)]'), ( 'offset_points_open_poly', offset_points, [tri, 2, False, False], '[Point3(0.00, -2.00, 0.00), Point3(14.83, -2.00, 0.00), Point3(1.41, 11.41, 0.00)]'), @@ -57,15 +57,15 @@ class TestSPUtils(DiffOutput): def test_split_body_planar(self): offset = [10, 10, 10] - body = translate( offset)( sphere( 20)) + body = translate(offset)(sphere(20)) body_bb = BoundingBox( [40, 40, 40], offset) actual = [] for split_dir in [ RIGHT_VEC, FORWARD_VEC, UP_VEC]: - actual_tuple = split_body_planar( body, body_bb, cutting_plane_normal=split_dir, cut_proportion=0.25) - actual.append( actual_tuple) + actual_tuple = split_body_planar(body, body_bb, cutting_plane_normal=split_dir, cut_proportion=0.25) + actual.append(actual_tuple) # Ignore the bounding box object that come back, taking only the SCAD objects - actual = [scad_render( a) for splits in actual for a in splits[::2] ] + actual = [scad_render(a) for splits in actual for a in splits[::2] ] expected = ['\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [-5.0000000000, 10, 10]) {\n\t\tcube(center = true, size = [10.0000000000, 40, 40]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [15.0000000000, 10, 10]) {\n\t\tcube(center = true, size = [30.0000000000, 40, 40]);\n\t}\n}', @@ -74,67 +74,67 @@ def test_split_body_planar(self): '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [10, 10, -5.0000000000]) {\n\t\tcube(center = true, size = [40, 40, 10.0000000000]);\n\t}\n}', '\n\nintersection() {\n\ttranslate(v = [10, 10, 10]) {\n\t\tsphere(r = 20);\n\t}\n\ttranslate(v = [10, 10, 15.0000000000]) {\n\t\tcube(center = true, size = [40, 40, 30.0000000000]);\n\t}\n}' ] - self.assertEqual( actual, expected) + self.assertEqual(actual, expected) - def test_fillet_2d_add( self): + def test_fillet_2d_add(self): pts = [ [0,5], [5,5], [5,0], [10,0], [10,10], [0,10],] - p = polygon( pts) - newp = fillet_2d( euclidify(pts[0:3], Point3), orig_poly=p, fillet_rad=2, remove_material=False) + p = polygon(pts) + newp = fillet_2d(euclidify(pts[0:3], Point3), orig_poly=p, fillet_rad=2, remove_material=False) expected = '\n\nunion() {\n\tpolygon(paths = [[0, 1, 2, 3, 4, 5]], points = [[0, 5], [5, 5], [5, 0], [10, 0], [10, 10], [0, 10]]);\n\ttranslate(v = [3.0000000000, 3.0000000000, 0.0000000000]) {\n\t\tdifference() {\n\t\t\tintersection() {\n\t\t\t\trotate(a = 358.0000000000) {\n\t\t\t\t\ttranslate(v = [-998, 0]) {\n\t\t\t\t\t\tsquare(center = false, size = [1000, 1000]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trotate(a = 452.0000000000) {\n\t\t\t\t\ttranslate(v = [-998, -1000]) {\n\t\t\t\t\t\tsquare(center = false, size = [1000, 1000]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcircle(r = 2);\n\t\t}\n\t}\n}' - actual = scad_render( newp) - self.assertEqual( expected, actual) + actual = scad_render(newp) + self.assertEqual(expected, actual) - def test_fillet_2d_remove( self): + def test_fillet_2d_remove(self): pts = tri - poly = polygon( euc_to_arr( tri)) + poly = polygon(euc_to_arr(tri)) - newp = fillet_2d( tri, orig_poly=poly, fillet_rad=2, remove_material=True) + newp = fillet_2d(tri, orig_poly=poly, fillet_rad=2, remove_material=True) expected = '\n\ndifference() {\n\tpolygon(paths = [[0, 1, 2]], points = [[0, 0, 0], [10, 0, 0], [0, 10, 0]]);\n\ttranslate(v = [5.1715728753, 2.0000000000, 0.0000000000]) {\n\t\tdifference() {\n\t\t\tintersection() {\n\t\t\t\trotate(a = 268.0000000000) {\n\t\t\t\t\ttranslate(v = [-998, 0]) {\n\t\t\t\t\t\tsquare(center = false, size = [1000, 1000]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trotate(a = 407.0000000000) {\n\t\t\t\t\ttranslate(v = [-998, -1000]) {\n\t\t\t\t\t\tsquare(center = false, size = [1000, 1000]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcircle(r = 2);\n\t\t}\n\t}\n}' - actual = scad_render( newp) + actual = scad_render(newp) if expected != actual: print(''.join(difflib.unified_diff(expected, actual))) - self.assertEqual( expected, actual) + self.assertEqual(expected, actual) -def test_generator_scad( func, args, expected): +def test_generator_scad(func, args, expected): def test_scad(self): scad_obj = func( *args) - actual = scad_render( scad_obj) - self.assertEqual( expected, actual) + actual = scad_render(scad_obj) + self.assertEqual(expected, actual) return test_scad -def test_generator_no_scad( func, args, expected): - def test_no_scad( self): - actual = str( func( *args)) - self.assertEqual( expected, actual) +def test_generator_no_scad(func, args, expected): + def test_no_scad(self): + actual = str(func( *args)) + self.assertEqual(expected, actual) return test_no_scad -def read_test_tuple( test_tuple): - if len( test_tuple) == 3: +def read_test_tuple(test_tuple): + if len(test_tuple) == 3: # If test name not supplied, create it programmatically func, args, expected = test_tuple test_name = 'test_%s'%func.__name__ - elif len( test_tuple) == 4: + elif len(test_tuple) == 4: test_name, func, args, expected = test_tuple test_name = 'test_%s'%test_name else: - print("test_tuple has %d args :%s"%( len(test_tuple), test_tuple) ) + print("test_tuple has %d args :%s"%(len(test_tuple), test_tuple) ) return test_name, func, args, expected def create_tests( ): for test_tuple in scad_test_cases: - test_name, func, args, expected = read_test_tuple( test_tuple) - test = test_generator_scad( func, args, expected) - setattr( TestSPUtils, test_name, test) + test_name, func, args, expected = read_test_tuple(test_tuple) + test = test_generator_scad(func, args, expected) + setattr(TestSPUtils, test_name, test) for test_tuple in other_test_cases: - test_name, func, args, expected = read_test_tuple( test_tuple) - test = test_generator_no_scad( func, args, expected) - setattr( TestSPUtils, test_name, test) + test_name, func, args, expected = read_test_tuple(test_tuple) + test = test_generator_no_scad(func, args, expected) + setattr(TestSPUtils, test_name, test) if __name__ == '__main__': create_tests( ) diff --git a/solid/utils.py b/solid/utils.py index 66d6fb5d..91a93763 100755 --- a/solid/utils.py +++ b/solid/utils.py @@ -11,25 +11,25 @@ X, Y, Z = (0, 1, 2) -ORIGIN = ( 0, 0, 0) -UP_VEC = ( 0, 0, 1) -RIGHT_VEC = ( 1, 0, 0) -FORWARD_VEC = ( 0, 1, 0) -DOWN_VEC = ( 0, 0,-1) +ORIGIN = (0, 0, 0) +UP_VEC = (0, 0, 1) +RIGHT_VEC = (1, 0, 0) +FORWARD_VEC = (0, 1, 0) +DOWN_VEC = (0, 0,-1) LEFT_VEC = (-1, 0, 0) -BACK_VEC = ( 0,-1, 0) +BACK_VEC = (0,-1, 0) # ========== # = Colors = # ========== -Red = ( 1, 0, 0) -Green = ( 0, 1, 0) -Blue = ( 0, 0, 1) -Cyan = ( 0, 1, 1) -Magenta = ( 1, 0, 1) -Yellow = ( 1, 1, 0) -Black = ( 0, 0, 0) -White = ( 1, 1, 1) +Red = (1, 0, 0) +Green = (0, 1, 0) +Blue = (0, 0, 1) +Cyan = (0, 1, 1) +Magenta = (1, 0, 1) +Yellow = (1, 1, 0) +Black = (0, 0, 0) +White = (1, 1, 1) Oak = (0.65, 0.50, 0.40) Pine = (0.85, 0.70, 0.45) Birch = (0.90, 0.80, 0.60) @@ -45,17 +45,17 @@ # ======================== # = Degrees <==> Radians = # ======================== -def degrees( x_radians): +def degrees(x_radians): return 360.0*x_radians/TAU -def radians( x_degrees): +def radians(x_degrees): return x_degrees/360.0*TAU # ============== # = Grid Plane = # ============== -def grid_plane( grid_unit=12, count=10, line_weight=0.1, plane='xz'): +def grid_plane(grid_unit=12, count=10, line_weight=0.1, plane='xz'): # Draws a grid of thin lines in the specified plane. Helpful for # reference during debugging. @@ -65,27 +65,27 @@ def grid_plane( grid_unit=12, count=10, line_weight=0.1, plane='xz'): for i in range(-count/2, count/2+1): if 'xz' in plane: # xz-plane - h = up( i*grid_unit)( cube( [ l, line_weight, line_weight], center=True)) - v = right(i*grid_unit)( cube( [ line_weight, line_weight, l], center=True)) + h = up( i*grid_unit)(cube( [ l, line_weight, line_weight], center=True)) + v = right(i*grid_unit)(cube( [ line_weight, line_weight, l], center=True)) t.add([h,v]) # xy plane if 'xy' in plane: - h = forward(i*grid_unit)( cube([ l, line_weight, line_weight], center=True)) - v = right( i*grid_unit)( cube( [ line_weight, l, line_weight], center=True)) + h = forward(i*grid_unit)(cube([ l, line_weight, line_weight], center=True)) + v = right( i*grid_unit)(cube( [ line_weight, l, line_weight], center=True)) t.add([h,v]) # yz plane if 'yz' in plane: - h = up( i*grid_unit)( cube([ line_weight, l, line_weight], center=True)) - v = forward( i*grid_unit)( cube([ line_weight, line_weight, l], center=True)) + h = up( i*grid_unit)(cube([ line_weight, l, line_weight], center=True)) + v = forward(i*grid_unit)(cube([ line_weight, line_weight, l], center=True)) t.add([h,v]) return t -def distribute_in_grid( objects, max_bounding_box, rows_and_cols=None): +def distribute_in_grid(objects, max_bounding_box, rows_and_cols=None): # Translate each object in objects in a grid with each cell of size # max_bounding_box. # If @@ -99,7 +99,7 @@ def distribute_in_grid( objects, max_bounding_box, rows_and_cols=None): # Distributes object in a grid in the xy plane # with objects spaced max_bounding_box apart - if isinstance( max_bounding_box, (list, tuple)): + if isinstance(max_bounding_box, (list, tuple)): x_trans, y_trans = max_bounding_box[0:2] elif isinstance(max_bounding_box, (int, long, float, complex)): x_trans = y_trans = max_bounding_box @@ -116,13 +116,13 @@ def distribute_in_grid( objects, max_bounding_box, rows_and_cols=None): if rows_and_cols: grid_w, grid_h = rows_and_cols else: - grid_w = grid_h = int(ceil( sqrt(len(objects)))) + grid_w = grid_h = int(ceil(sqrt(len(objects)))) objs_placed = 0 - for y in range( grid_h): - for x in range( grid_w): + for y in range(grid_h): + for x in range(grid_w): if objs_placed < len(objects): - ret.append(translate( [x*x_trans, y*y_trans])( objects[objs_placed])) + ret.append(translate( [x*x_trans, y*y_trans])(objects[objs_placed])) objs_placed += 1 else: break @@ -131,53 +131,53 @@ def distribute_in_grid( objects, max_bounding_box, rows_and_cols=None): # ============== # = Directions = # ============== -def up( z): +def up(z): return translate( [0,0,z]) -def down( z): +def down(z): return translate( [0,0,-z]) -def right( x): +def right(x): return translate( [x, 0,0]) -def left( x): +def left(x): return translate( [-x, 0,0]) def forward(y): return translate( [0,y,0]) -def back( y): +def back(y): return translate( [0,-y,0]) # =========================== # = Box-alignment rotations = # =========================== -def rot_z_to_up( obj): +def rot_z_to_up(obj): # NOTE: Null op - return rotate( a=0, v=FORWARD_VEC)(obj) + return rotate(a=0, v=FORWARD_VEC)(obj) -def rot_z_to_down( obj): - return rotate( a=180, v=FORWARD_VEC)(obj) +def rot_z_to_down(obj): + return rotate(a=180, v=FORWARD_VEC)(obj) -def rot_z_to_right( obj): - return rotate( a=90, v=FORWARD_VEC)(obj) +def rot_z_to_right(obj): + return rotate(a=90, v=FORWARD_VEC)(obj) -def rot_z_to_left( obj): - return rotate( a=-90, v=FORWARD_VEC)(obj) +def rot_z_to_left(obj): + return rotate(a=-90, v=FORWARD_VEC)(obj) -def rot_z_to_forward( obj): - return rotate( a=-90, v=RIGHT_VEC)(obj) +def rot_z_to_forward(obj): + return rotate(a=-90, v=RIGHT_VEC)(obj) -def rot_z_to_back( obj): - return rotate( a=90, v=RIGHT_VEC)(obj) +def rot_z_to_back(obj): + return rotate(a=90, v=RIGHT_VEC)(obj) # ================================ # = Box-aligment and translation = # ================================ -def box_align( obj, direction_func=up, distance=0 ): +def box_align(obj, direction_func=up, distance=0 ): # Given a box side (up, left, etc) and a distance, # rotate obj (assumed to be facing up) in the # correct direction and move it distance in that @@ -191,63 +191,63 @@ def box_align( obj, direction_func=up, distance=0 ): back: rot_z_to_back, } - assert( direction_func in trans_and_rot) + assert(direction_func in trans_and_rot) rot = trans_and_rot[ direction_func] - return direction_func( distance)( rot( obj)) + return direction_func(distance)(rot(obj)) # ======================= # = 90-degree Rotations = # ======================= -def rot_z_to_x( obj): - return rotate( a=90, v=FORWARD_VEC)(obj) +def rot_z_to_x(obj): + return rotate(a=90, v=FORWARD_VEC)(obj) -def rot_z_to_neg_x( obj): - return rotate( a=-90, v=FORWARD_VEC)(obj) +def rot_z_to_neg_x(obj): + return rotate(a=-90, v=FORWARD_VEC)(obj) -def rot_z_to_neg_y( obj): - return rotate( a=90, v=RIGHT_VEC)(obj) +def rot_z_to_neg_y(obj): + return rotate(a=90, v=RIGHT_VEC)(obj) -def rot_z_to_y( obj): - return rotate( a=-90, v=RIGHT_VEC)(obj) +def rot_z_to_y(obj): + return rotate(a=-90, v=RIGHT_VEC)(obj) -def rot_x_to_y( obj): - return rotate( a=90, v=UP_VEC)(obj) +def rot_x_to_y(obj): + return rotate(a=90, v=UP_VEC)(obj) -def rot_x_to_neg_y( obj): - return rotate( a=-90, v=UP_VEC)(obj) +def rot_x_to_neg_y(obj): + return rotate(a=-90, v=UP_VEC)(obj) # ======= # = Arc = # ======= -def arc( rad, start_degrees, end_degrees, segments=None): +def arc(rad, start_degrees, end_degrees, segments=None): # Note: the circle that this arc is drawn from gets segments, # not the arc itself. That means a quarter-circle arc will # have segments/4 segments. - bottom_half_square = back( rad)(square( [3*rad, 2*rad], center=True)) - top_half_square = forward( rad)( square( [3*rad, 2*rad], center=True)) + bottom_half_square = back(rad)(square( [3*rad, 2*rad], center=True)) + top_half_square = forward(rad)(square( [3*rad, 2*rad], center=True)) - start_shape = circle( rad, segments=segments) + start_shape = circle(rad, segments=segments) if abs( (end_degrees - start_degrees)%360) <= 180: end_angle = end_degrees - 180 ret = difference()( start_shape, - rotate( a=start_degrees)( bottom_half_square.copy()), - rotate( a= end_angle)( bottom_half_square.copy()) + rotate(a=start_degrees)( bottom_half_square.copy()), + rotate(a= end_angle)( bottom_half_square.copy()) ) else: ret = intersection( )( start_shape, union()( - rotate( a=start_degrees)( top_half_square.copy()), - rotate( a=end_degrees)( bottom_half_square.copy()) + rotate(a=start_degrees)( top_half_square.copy()), + rotate(a=end_degrees)( bottom_half_square.copy()) ) ) return ret -def arc_inverted( rad, start_degrees, end_degrees, segments=None): +def arc_inverted(rad, start_degrees, end_degrees, segments=None): # Return the segment of an arc *outside* the circle of radius rad, # bounded by two tangents to the circle. This is the shape # needed for fillets. @@ -289,13 +289,13 @@ def arc_inverted( rad, start_degrees, end_degrees, segments=None): wide = 1000 high = 1000 - top_half_square = translate( [-(wide-rad), 0])( square([wide, high], center=False)) - bottom_half_square = translate( [-(wide-rad), -high])( square([wide, high], center=False)) + top_half_square = translate( [-(wide-rad), 0])(square([wide, high], center=False)) + bottom_half_square = translate( [-(wide-rad), -high])(square([wide, high], center=False)) - a = rotate( start_degrees)( top_half_square) - b = rotate( end_degrees)( bottom_half_square) + a = rotate(start_degrees)(top_half_square) + b = rotate(end_degrees)(bottom_half_square) - ret = (a*b) - circle( rad, segments=segments) + ret = (a*b) - circle(rad, segments=segments) return ret @@ -316,28 +316,28 @@ class BoundingBox(object): # Basically you can use a BoundingBox to describe the extents of an object # the moment it's created, but once you perform any CSG operation on it, it's # more or less useless. - def __init__( self, size, loc=None): + def __init__(self, size, loc=None): loc = loc if loc else [0,0,0] # self.w, self.h, self.d = size # self.x, self.y, self.z = loc - self.set_size( size) - self.set_position( loc) + self.set_size(size) + self.set_position(loc) - def size( self): + def size(self): return [ self.w, self.h, self.d] - def position( self): + def position(self): return [ self.x, self.y, self.z] - def set_position( self, position): + def set_position(self, position): self.x, self.y, self.z = position def set_size(self, size): self.w, self.h, self.d = size - def split_planar( self, cutting_plane_normal= RIGHT_VEC, cut_proportion=0.5, add_wall_thickness=0): + def split_planar(self, cutting_plane_normal= RIGHT_VEC, cut_proportion=0.5, add_wall_thickness=0): cpd = {RIGHT_VEC: 0, LEFT_VEC:0, FORWARD_VEC:1, BACK_VEC:1, UP_VEC:2, DOWN_VEC:2} - cutting_plane = cpd.get( cutting_plane_normal, 2) + cutting_plane = cpd.get(cutting_plane_normal, 2) # Figure what the cutting plane offset should be dim_center = self.position()[cutting_plane] @@ -369,31 +369,31 @@ def split_planar( self, cutting_plane_normal= RIGHT_VEC, cut_proportion=0.5, add loc_offset = -add_wall_thickness/2 + i*add_wall_thickness part_loc[ cutting_plane] += loc_offset - part_bbs.append( BoundingBox( part_size, part_loc)) + part_bbs.append(BoundingBox(part_size, part_loc)) a_sum += part * dim return part_bbs - def cube( self, larger=False): + def cube(self, larger=False): c_size = self.size() if not larger else [s + 2*EPSILON for s in self.size()] - c = translate( self.position())( - cube( c_size, center=True) + c = translate(self.position())( + cube(c_size, center=True) ) return c - def min( self, which_dim=None): - min_pt = [p-s/2 for p, s in zip( self.position(), self.size())] + def min(self, which_dim=None): + min_pt = [p-s/2 for p, s in zip(self.position(), self.size())] if which_dim: return min_pt[ which_dim] else: return min_pt - def max( self, which_dim=None): - max_pt = [p+s/2 for p, s in zip( self.position(), self.size())] + def max(self, which_dim=None): + max_pt = [p+s/2 for p, s in zip(self.position(), self.size())] if which_dim: return max_pt[ which_dim] else: @@ -403,7 +403,7 @@ def max( self, which_dim=None): # =================== # = Model Splitting = # =================== -def split_body_planar( obj, obj_bb, cutting_plane_normal=UP_VEC, cut_proportion=0.5, dowel_holes=False, dowel_rad=4.5, hole_depth=15, add_wall_thickness=0): +def split_body_planar(obj, obj_bb, cutting_plane_normal=UP_VEC, cut_proportion=0.5, dowel_holes=False, dowel_rad=4.5, hole_depth=15, add_wall_thickness=0): # Split obj along the specified plane, returning two pieces and # general bounding boxes for each. # Note that the bounding boxes are NOT accurate to the sections, @@ -414,7 +414,7 @@ def split_body_planar( obj, obj_bb, cutting_plane_normal=UP_VEC, cut_proportion= # back together with short dowels. # Find the splitting bounding boxes - part_bbs = obj_bb.split_planar( cutting_plane_normal, cut_proportion, add_wall_thickness=add_wall_thickness) + part_bbs = obj_bb.split_planar(cutting_plane_normal, cut_proportion, add_wall_thickness=add_wall_thickness) # And intersect the bounding boxes with the object itself slices = [obj*part_bb.cube() for part_bb in part_bbs] @@ -424,13 +424,13 @@ def split_body_planar( obj, obj_bb, cutting_plane_normal=UP_VEC, cut_proportion= # separated by one dowel-width if dowel_holes: cpd = {RIGHT_VEC: 0, LEFT_VEC:0, FORWARD_VEC:1, BACK_VEC:1, UP_VEC:2, DOWN_VEC:2} - cutting_plane = cpd.get( cutting_plane_normal, 2) + cutting_plane = cpd.get(cutting_plane_normal, 2) - dowel = cylinder( r=dowel_rad, h=hole_depth*2, center=True) + dowel = cylinder(r=dowel_rad, h=hole_depth*2, center=True) # rotate dowels to correct axis if cutting_plane != 2: rot_vec = RIGHT_VEC if cutting_plane == 1 else FORWARD_VEC - dowel = rotate( a=90, v=rot_vec)( dowel) + dowel = rotate(a=90, v=rot_vec)(dowel) cut_point = part_bbs[0].position()[ cutting_plane] + part_bbs[0].size()[ cutting_plane]/2 @@ -443,8 +443,8 @@ def split_body_planar( obj, obj_bb, cutting_plane_normal=UP_VEC, cut_proportion= dowel_trans_b = dowel_trans_a[:] dowel_trans_b[ separation_index] += 4*dowel_rad - dowel_a = translate( dowel_trans_a)(dowel) - dowel_b = translate( dowel_trans_b)(dowel) + dowel_a = translate(dowel_trans_a)(dowel) + dowel_b = translate(dowel_trans_b)(dowel) dowels = dowel_a + dowel_b # subtract dowels from each slice @@ -453,10 +453,10 @@ def split_body_planar( obj, obj_bb, cutting_plane_normal=UP_VEC, cut_proportion= slices_and_bbs = [slices[0], part_bbs[0], slices[1], part_bbs[1]] return slices_and_bbs -def section_cut_xz( body, y_cut_point=0): +def section_cut_xz(body, y_cut_point=0): big_w = 10000 d = 2 - c = forward( d/2 + y_cut_point)( cube( [big_w, d, big_w], center=True)) + c = forward(d/2 + y_cut_point)(cube( [big_w, d, big_w], center=True)) return c * body # ===================== @@ -467,7 +467,7 @@ def section_cut_xz( body, y_cut_point=0): # bill_of_materials() # to generate a report. Se examples/bom_scad.py for usage g_parts_dict = {} -def bom_part( description='', per_unit_price=None, currency='US$'): +def bom_part(description='', per_unit_price=None, currency='US$'): def wrap(f): name = description if description else f.__name__ g_parts_dict[name] = [0, currency, per_unit_price] @@ -533,13 +533,13 @@ def bill_of_materials_justified(): # ================ # = Bounding Box = # ================ -def bounding_box( points): +def bounding_box(points): all_x = []; all_y = []; all_z = [] for p in points: - all_x.append( p[0]) - all_y.append( p[1]) + all_x.append(p[0]) + all_y.append(p[1]) if len(p) > 2: - all_z.append( p[2]) + all_z.append(p[2]) else: all_z.append(0) @@ -557,28 +557,28 @@ def bounding_box( points): } -def screw( screw_type='m3', screw_length=16): +def screw(screw_type='m3', screw_length=16): dims = screw_dimensions[screw_type.lower()] shaft_rad = dims['screw_outer_diam']/2 cap_rad = dims['cap_diam']/2 cap_height = dims['cap_height'] ret = union()( - cylinder( shaft_rad, screw_length), + cylinder(shaft_rad, screw_length), up(screw_length)( - cylinder( cap_rad, cap_height) + cylinder(cap_rad, cap_height) ) ) return ret -def nut( screw_type='m3'): +def nut(screw_type='m3'): dims = screw_dimensions[screw_type.lower()] outer_rad = dims['nut_outer_diam'] inner_rad = dims['screw_outer_diam'] ret = difference()( - circle( outer_rad, segments=6), - circle( inner_rad) + circle(outer_rad, segments=6), + circle(inner_rad) ) return ret @@ -595,7 +595,7 @@ def nut( screw_type='m3'): import solid.patch_euclid solid.patch_euclid.run_patch() - def euclidify( an_obj, intended_class=Vector3): + def euclidify(an_obj, intended_class=Vector3): # If an_obj is an instance of the appropriate PyEuclid class, # return it. Otherwise, try to turn an_obj into the appropriate # class and throw an exception on failure @@ -606,10 +606,10 @@ def euclidify( an_obj, intended_class=Vector3): ret = an_obj # See if this is an array of arrays. If so, convert all sublists - if isinstance( an_obj, (list, tuple)): - if isinstance( an_obj[0], (list,tuple)): + if isinstance(an_obj, (list, tuple)): + if isinstance(an_obj[0], (list,tuple)): ret = [intended_class(*p) for p in an_obj] - elif isinstance( an_obj[0], intended_class): + elif isinstance(an_obj[0], intended_class): # this array is already euclidified; return it ret = an_obj else: @@ -618,7 +618,7 @@ def euclidify( an_obj, intended_class=Vector3): except: raise TypeError( "Object: %s ought to be PyEuclid class %s or " "able to form one, but is not."%(an_obj, intended_class.__name__)) - elif not isinstance( an_obj, intended_class): + elif not isinstance(an_obj, intended_class): try: ret = intended_class( *an_obj) except: @@ -626,22 +626,22 @@ def euclidify( an_obj, intended_class=Vector3): "able to form one, but is not."%(an_obj, intended_class.__name__)) return ret - def euc_to_arr( euc_obj_or_list): # Inverse of euclidify() + def euc_to_arr(euc_obj_or_list): # Inverse of euclidify() # Call as_arr on euc_obj_or_list or on all its members if it's a list if hasattr(euc_obj_or_list, "as_arr"): return euc_obj_or_list.as_arr() - elif isinstance( euc_obj_or_list, (list, tuple)) and hasattr(euc_obj_or_list[0], 'as_arr'): - return [euc_to_arr( p) for p in euc_obj_or_list] + elif isinstance(euc_obj_or_list, (list, tuple)) and hasattr(euc_obj_or_list[0], 'as_arr'): + return [euc_to_arr(p) for p in euc_obj_or_list] else: # euc_obj_or_list is neither an array-based PyEuclid object, # nor a list of them. Assume it's a list of points or vectors, # and return the list unchanged. We could be wrong about this, though. return euc_obj_or_list - def is_scad( obj): - return isinstance( obj, openscad_object) + def is_scad(obj): + return isinstance(obj, OpenSCADObject) - def scad_matrix( euclid_matrix4): + def scad_matrix(euclid_matrix4): a = euclid_matrix4 return [[a.a, a.b, a.c, a.d], [a.e, a.f, a.g, a.h], @@ -653,7 +653,7 @@ def scad_matrix( euclid_matrix4): # ============== # = Transforms = # ============== - def transform_to_point( body, dest_point, dest_normal, src_point=Point3(0,0,0), src_normal=Vector3(0,1,0), src_up=Vector3(0,0,1)): + def transform_to_point(body, dest_point, dest_normal, src_point=Point3(0,0,0), src_normal=Vector3(0,1,0), src_up=Vector3(0,0,1)): # Transform body to dest_point, looking at dest_normal. # Orientation & offset can be changed by supplying the src arguments @@ -661,30 +661,30 @@ def transform_to_point( body, dest_point, dest_normal, src_point=Point3(0,0,0), # -- an openSCAD object # -- a list of 3-tuples or PyEuclid Point3s # -- a single 3-tuple or Point3 - dest_point = euclidify( dest_point, Point3) - dest_normal = euclidify( dest_normal, Vector3) + dest_point = euclidify(dest_point, Point3) + dest_normal = euclidify(dest_normal, Vector3) at = dest_point + dest_normal - EUC_UP = euclidify( UP_VEC) - EUC_FORWARD = euclidify( FORWARD_VEC) - EUC_ORIGIN = euclidify( ORIGIN, Vector3) + EUC_UP = euclidify(UP_VEC) + EUC_FORWARD = euclidify(FORWARD_VEC) + EUC_ORIGIN = euclidify(ORIGIN, Vector3) # if dest_normal and src_up are parallel, the transform collapses # all points to dest_point. Instead, use EUC_FORWARD if needed - if dest_normal.cross( src_up) == EUC_ORIGIN: - if src_up.cross( EUC_UP) == EUC_ORIGIN: + if dest_normal.cross(src_up) == EUC_ORIGIN: + if src_up.cross(EUC_UP) == EUC_ORIGIN: src_up = EUC_FORWARD else: src_up = EUC_UP - look_at_matrix = Matrix4.new_look_at( eye=dest_point, at=at, up=src_up ) + look_at_matrix = Matrix4.new_look_at(eye=dest_point, at=at, up=src_up ) - if is_scad( body): + if is_scad(body): # If the body being altered is a SCAD object, do the matrix mult # in OpenSCAD - sc_matrix = scad_matrix( look_at_matrix) - res = multmatrix( m=sc_matrix)( body) + sc_matrix = scad_matrix(look_at_matrix) + res = multmatrix(m=sc_matrix)(body) else: - body = euclidify( body, Point3) - if isinstance( body, (list, tuple)): + body = euclidify(body, Point3) + if isinstance(body, (list, tuple)): res = [look_at_matrix * p for p in body] else: res = look_at_matrix * body @@ -694,37 +694,37 @@ def transform_to_point( body, dest_point, dest_normal, src_point=Point3(0,0,0), # ======================================== # = Vector drawing: 3D arrow from a line = # = -------------- ======================= - def draw_segment( euc_line=None, endless=False, arrow_rad=7, vec_color=None): + def draw_segment(euc_line=None, endless=False, arrow_rad=7, vec_color=None): # Draw a tradtional arrow-head vector in 3-space. vec_arrow_rad = arrow_rad vec_arrow_head_rad = vec_arrow_rad * 1.5 vec_arrow_head_length = vec_arrow_rad * 3 - if isinstance( euc_line, Vector3): + if isinstance(euc_line, Vector3): p = Point3( *ORIGIN) v = euc_line - elif isinstance( euc_line, Line3): + elif isinstance(euc_line, Line3): p = euc_line.p v = -euc_line.v - elif isinstance( euc_line, list) or isinstance( euc_line, tuple): + elif isinstance(euc_line, list) or isinstance(euc_line, tuple): # TODO: This assumes p & v are PyEuclid classes. # Really, they could as easily be two 3-tuples. Should # check for this. p, v = euc_line[0], euc_line[1] shaft_length = v.magnitude() - vec_arrow_head_length - arrow = cylinder( r= vec_arrow_rad, h = shaft_length) - arrow += up( shaft_length )( + arrow = cylinder(r= vec_arrow_rad, h = shaft_length) + arrow += up(shaft_length )( cylinder(r1=vec_arrow_head_rad, r2=0, h = vec_arrow_head_length) ) if endless: - endless_length = max( v.magnitude()*10, 200) - arrow += cylinder( r=vec_arrow_rad/3, h = endless_length, center=True) + endless_length = max(v.magnitude()*10, 200) + arrow += cylinder(r=vec_arrow_rad/3, h = endless_length, center=True) - arrow = transform_to_point( body=arrow, dest_point=p, dest_normal=v) + arrow = transform_to_point(body=arrow, dest_point=p, dest_normal=v) if vec_color: - arrow = color( vec_color)(arrow) + arrow = color(vec_color)(arrow) return arrow @@ -732,17 +732,17 @@ def draw_segment( euc_line=None, endless=False, arrow_rad=7, vec_color=None): # = Offset = # = ------ = LEFT, RIGHT = radians(90), radians(-90) - def offset_polygon( point_arr, offset, inside=True, closed_poly=True): + def offset_polygon(point_arr, offset, inside=True, closed_poly=True): # returns a closed solidPython polygon offset by offset distance # from the polygon described by point_arr. - op = offset_points( point_arr, offset=offset, inside=inside, closed_poly=closed_poly) - return polygon( euc_to_arr(op)) + op = offset_points(point_arr, offset=offset, inside=inside, closed_poly=closed_poly) + return polygon(euc_to_arr(op)) - def offset_points( point_arr, offset, inside=True, closed_poly=True): + def offset_points(point_arr, offset, inside=True, closed_poly=True): # Given a set of points, return a set of points offset from # them. # To get reasonable results, the points need to be all in a plane. - # ( Non-planar point_arr will still return results, but what constitutes + # (Non-planar point_arr will still return results, but what constitutes # 'inside' or 'outside' would be different in that situation.) # # What direction inside and outside lie in is determined by the first @@ -769,10 +769,10 @@ def offset_points( point_arr, offset, inside=True, closed_poly=True): # Using the first three points in point_arr, figure out which direction # is inside and what plane to put the points in - point_arr = euclidify( point_arr[:], Point3) + point_arr = euclidify(point_arr[:], Point3) in_dir = _inside_direction( *point_arr[0:3]) normal = _three_point_normal( *point_arr[0:3]) - direction = in_dir if inside else _other_dir( in_dir) + direction = in_dir if inside else _other_dir(in_dir) # Generate offset points for the correct direction # for all of point_arr. @@ -780,65 +780,65 @@ def offset_points( point_arr, offset, inside=True, closed_poly=True): offset_pts = [] point_arr += point_arr[ 0:2] # Add first two points to the end as well if closed_poly: - for i in range( len(point_arr) - 1): + for i in range(len(point_arr) - 1): a, b = point_arr[i:i+2] - par_seg = _parallel_seg( a, b, normal=normal, offset=offset, direction=direction ) - segs.append( par_seg) + par_seg = _parallel_seg(a, b, normal=normal, offset=offset, direction=direction ) + segs.append(par_seg) if len(segs) > 1: int_pt = segs[-2].intersect(segs[-1]) if int_pt: - offset_pts.append( int_pt) + offset_pts.append(int_pt) # When calculating based on a closed curve, we can't find the # first offset point until all others have been calculated. # Now that we've done so, put the last point back to first place last = offset_pts[-1] - offset_pts.insert( 0, last) - del( offset_pts[-1]) + offset_pts.insert(0, last) + del(offset_pts[-1]) else: - for i in range( len(point_arr)-2): + for i in range(len(point_arr)-2): a, b = point_arr[i:i+2] - par_seg = _parallel_seg( a, b, normal=normal, offset=offset, direction=direction ) - segs.append( par_seg) + par_seg = _parallel_seg(a, b, normal=normal, offset=offset, direction=direction ) + segs.append(par_seg) # In an open poly, first and last points will be parallel # to the first and last segments, not intersecting other segs if i == 0: - offset_pts.append( par_seg.p1) + offset_pts.append(par_seg.p1) elif i == len(point_arr) - 3: - offset_pts.append( segs[-2].p2) + offset_pts.append(segs[-2].p2) else: int_pt = segs[-2].intersect(segs[-1]) if int_pt: - offset_pts.append( int_pt) + offset_pts.append(int_pt) return offset_pts # ================== # = Offset helpers = # ================== - def _parallel_seg( p, q, offset, normal=Vector3( 0, 0, 1), direction=LEFT): + def _parallel_seg(p, q, offset, normal=Vector3(0, 0, 1), direction=LEFT): # returns a PyEuclid Line3 parallel to pq, in the plane determined # by p,normal, to the left or right of pq. v = q - p angle = direction - rot_v = v.rotate_around( axis=normal, theta=angle) - rot_v.set_length( offset) - return Line3( p+rot_v, v ) + rot_v = v.rotate_around(axis=normal, theta=angle) + rot_v.set_length(offset) + return Line3(p+rot_v, v ) - def _inside_direction( a, b, c, offset=10): + def _inside_direction(a, b, c, offset=10): # determines which direction (LEFT, RIGHT) is 'inside' the triangle # made by a, b, c. If ab and bc are parallel, return LEFT - x = _three_point_normal( a, b, c) + x = _three_point_normal(a, b, c) # Make two vectors (left & right) for each segment. - l_segs = [_parallel_seg( p, q, normal=x, offset=offset, direction=LEFT) for p,q in ( (a,b), (b,c))] - r_segs = [_parallel_seg( p, q, normal=x, offset=offset, direction=RIGHT) for p,q in ( (a,b), (b,c))] + l_segs = [_parallel_seg(p, q, normal=x, offset=offset, direction=LEFT) for p,q in ( (a,b), (b,c))] + r_segs = [_parallel_seg(p, q, normal=x, offset=offset, direction=RIGHT) for p,q in ( (a,b), (b,c))] # Find their intersections. - p1 = l_segs[0].intersect( l_segs[1]) - p2 = r_segs[0].intersect( r_segs[1]) + p1 = l_segs[0].intersect(l_segs[1]) + p2 = r_segs[0].intersect(r_segs[1]) # The only way I've figured out to determine which direction is # 'inside' or 'outside' a joint is to calculate both inner and outer @@ -847,30 +847,30 @@ def _inside_direction( a, b, c, offset=10): # way to figure this out. -ETJ 21 Dec 2012 # The point that's closer to point a is the inside point. - if a.distance( p1) <= a.distance( p2): + if a.distance(p1) <= a.distance(p2): return LEFT else: return RIGHT - def _other_dir( left_or_right): + def _other_dir(left_or_right): if left_or_right == LEFT: return RIGHT else: return LEFT - def _three_point_normal( a, b, c): + def _three_point_normal(a, b, c): ab = b - a bc = c - b - seg_ab = Line3( a, ab) - seg_bc = Line3( b, bc) - x = seg_ab.v.cross( seg_bc.v) + seg_ab = Line3(a, ab) + seg_bc = Line3(b, bc) + x = seg_ab.v.cross(seg_bc.v) return x # ============= # = 2D Fillet = # ============= - def _widen_angle_for_fillet( start_degrees, end_degrees): + def _widen_angle_for_fillet(start_degrees, end_degrees): # Fix start/end degrees as needed; find a way to make an acute angle if end_degrees < start_degrees: end_degrees += 360 @@ -881,7 +881,7 @@ def _widen_angle_for_fillet( start_degrees, end_degrees): epsilon_degrees = 2 return start_degrees - epsilon_degrees, end_degrees + epsilon_degrees - def fillet_2d( three_point_sets, orig_poly, fillet_rad, remove_material=True): + def fillet_2d(three_point_sets, orig_poly, fillet_rad, remove_material=True): # NOTE: three_point_sets must be a list of sets of three points # (i.e., a list of 3-tuples of points), even if only one fillet is being done: # e.g. [[a, b, c]] @@ -892,7 +892,7 @@ def fillet_2d( three_point_sets, orig_poly, fillet_rad, remove_material=True): # Note that if rad is greater than a.distance(b) or c.distance(b), for a # 90-degree corner, the returned shape will include a jagged edge. - # TODO: use fillet_rad = min( fillet_rad, a.distance(b), c.distance(b)) + # TODO: use fillet_rad = min(fillet_rad, a.distance(b), c.distance(b)) # If a shape is being filleted in several places, it is FAR faster # to add/ remove its set of shapes all at once rather than @@ -904,7 +904,7 @@ def fillet_2d( three_point_sets, orig_poly, fillet_rad, remove_material=True): # NOTE that if material is being added (fillets) or removed (rounds) # each must be called separately. - if len( three_point_sets) == 3 and isinstance( three_point_sets[0], (Vector2, Vector3)): + if len(three_point_sets) == 3 and isinstance(three_point_sets[0], (Vector2, Vector3)): three_point_sets = [three_point_sets] arc_objs = [] @@ -912,39 +912,39 @@ def fillet_2d( three_point_sets, orig_poly, fillet_rad, remove_material=True): assert len(three_points) in (2,3) # make two vectors out of the three points passed in - a, b, c = euclidify( three_points, Point3) + a, b, c = euclidify(three_points, Point3) # Find the center of the arc we'll have to make offset = offset_points( [a, b, c], offset=fillet_rad, inside=True) center_pt = offset[1] - a2, b2, c2, cp2 = [Point2( p.x, p.y) for p in (a,b,c, center_pt)] + a2, b2, c2, cp2 = [Point2(p.x, p.y) for p in (a,b,c, center_pt)] - a2b2 = LineSegment2( a2, b2) - c2b2 = LineSegment2( c2, b2) + a2b2 = LineSegment2(a2, b2) + c2b2 = LineSegment2(c2, b2) # Find the point on each segment where the arc starts; Point2.connect() # returns a segment with two points; Take the one that's not the center - afs = cp2.connect( a2b2) - cfs = cp2.connect( c2b2) + afs = cp2.connect(a2b2) + cfs = cp2.connect(c2b2) afp, cfp = [seg.p1 if seg.p1 != cp2 else seg.p2 for seg in (afs, cfs)] - a_degs, c_degs = [ (degrees(math.atan2( seg.v.y, seg.v.x)))%360 for seg in (afs, cfs)] + a_degs, c_degs = [ (degrees(math.atan2(seg.v.y, seg.v.x)))%360 for seg in (afs, cfs)] start_degs = a_degs end_degs = c_degs # Widen start_degs and end_degs slightly so they overlap the areas # they're supposed to join/ remove. - start_degs, end_degs = _widen_angle_for_fillet( start_degs, end_degs) + start_degs, end_degs = _widen_angle_for_fillet(start_degs, end_degs) - arc_obj = translate( center_pt.as_arr() )( - arc_inverted( rad=fillet_rad, start_degrees=start_degs, end_degrees=end_degs) + arc_obj = translate(center_pt.as_arr() )( + arc_inverted(rad=fillet_rad, start_degrees=start_degs, end_degrees=end_degs) ) - arc_objs.append( arc_obj) + arc_objs.append(arc_obj) if remove_material: poly = orig_poly - arc_objs @@ -957,12 +957,12 @@ def fillet_2d( three_point_sets, orig_poly, fillet_rad, remove_material=True): # ========================== # = Extrusion along a path = # = ---------------------- = - def extrude_along_path( shape_pts, path_pts, scale_factors=None): # Possible: twist + def extrude_along_path(shape_pts, path_pts, scale_factors=None): # Possible: twist # Extrude the convex curve defined by shape_pts along path_pts. # -- For predictable results, shape_pts must be planar, convex, and lie # in the XY plane centered around the origin. # - # -- len( scale_factors) should equal len( path_pts). If not present, scale + # -- len(scale_factors) should equal len(path_pts). If not present, scale # will be assumed to be 1.0 for each point in path_pts # -- Future additions might include corner styles (sharp, flattened, round) # or a twist factor @@ -973,12 +973,12 @@ def extrude_along_path( shape_pts, path_pts, scale_factors=None): # Possible: tw scale_factors = [1.0] * len(path_pts) # Make sure we've got Euclid Point3's for all elements - shape_pts = euclidify( shape_pts, Point3) - path_pts = euclidify( path_pts, Point3) + shape_pts = euclidify(shape_pts, Point3) + path_pts = euclidify(path_pts, Point3) src_up = Vector3( *UP_VEC) - for which_loop in range( len( path_pts) ): + for which_loop in range(len(path_pts) ): path_pt = path_pts[which_loop] scale = scale_factors[which_loop] @@ -992,7 +992,7 @@ def extrude_along_path( shape_pts, path_pts, scale_factors=None): # Possible: tw tangent = v_prev + v_next elif which_loop == 0: tangent = path_pts[which_loop+1] - path_pt - elif which_loop == len( path_pts) - 1: + elif which_loop == len(path_pts) - 1: tangent = path_pt - path_pts[ which_loop -1] # Scale points @@ -1005,7 +1005,7 @@ def extrude_along_path( shape_pts, path_pts, scale_factors=None): # Possible: tw # Rotate & translate - this_loop = transform_to_point( this_loop, dest_point=path_pt, dest_normal=tangent, src_up=src_up) + this_loop = transform_to_point(this_loop, dest_point=path_pt, dest_normal=tangent, src_up=src_up) # Add the transformed points to our final list polyhedron_pts += this_loop @@ -1014,7 +1014,7 @@ def extrude_along_path( shape_pts, path_pts, scale_factors=None): # Possible: tw segment_start = which_loop*shape_pt_count segment_end = segment_start + shape_pt_count - 1 if which_loop < len(path_pts) - 1: - for i in range( segment_start, segment_end): + for i in range(segment_start, segment_end): facet_indices.append( [i, i+shape_pt_count, i+1]) facet_indices.append( [i+1, i+shape_pt_count, i+shape_pt_count+1]) facet_indices.append( [segment_start, segment_end, segment_end + shape_pt_count]) @@ -1024,13 +1024,13 @@ def extrude_along_path( shape_pts, path_pts, scale_factors=None): # Possible: tw for i in range(1, shape_pt_count - 1): facet_indices.append( [0, i, i+1]) - # And the end ( could be rolled into the earlier loop) + # And the end (could be rolled into the earlier loop) # FIXME: concave cross-sections will cause this end-capping algorithm to fail - end_cap_base = len( polyhedron_pts) - shape_pt_count - for i in range( end_cap_base + 1, len(polyhedron_pts) -1): + end_cap_base = len(polyhedron_pts) - shape_pt_count + for i in range(end_cap_base + 1, len(polyhedron_pts) -1): facet_indices.append( [ end_cap_base, i+1, i]) - return polyhedron( points = euc_to_arr(polyhedron_pts), faces=facet_indices) + return polyhedron(points = euc_to_arr(polyhedron_pts), faces=facet_indices) except Exception as e: @@ -1099,7 +1099,7 @@ def frange(*args): # ===================== # = D e b u g g i n g = # ===================== -def obj_tree_str( sp_obj, vars_to_print=None): +def obj_tree_str(sp_obj, vars_to_print=None): # For debugging. This prints a string of all of an object's # children, with whatever attributes are specified in vars_to_print @@ -1111,15 +1111,15 @@ def obj_tree_str( sp_obj, vars_to_print=None): parent_sign = "\nL " if sp_obj.parent else "\n* " # Print object - s = parent_sign + str( sp_obj) + "\t" + s = parent_sign + str(sp_obj) + "\t" # Extra desired fields for v in vars_to_print: - if hasattr( sp_obj, v): - s += "%s: %s\t"%( v, getattr(sp_obj, v)) + if hasattr(sp_obj, v): + s += "%s: %s\t"%(v, getattr(sp_obj, v)) # Add all children for c in sp_obj.children: - s += indent( obj_tree_str(c, vars_to_print)) + s += indent(obj_tree_str(c, vars_to_print)) return s