Skip to content

Commit 8fa53a2

Browse files
committed
Added examples, gitignore
1 parent ea5c7c2 commit 8fa53a2

File tree

5 files changed

+477
-0
lines changed

5 files changed

+477
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.scad

examples/koch.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#! /usr/bin/python
2+
# -*- coding: UTF-8 -*-
3+
import os, sys, re
4+
5+
sys.path.append(os.path.join( os.getenv('HOME'), 'Desktop', 'SolidPython'))
6+
7+
from pyopenscad import *
8+
from sp_utils import *
9+
10+
sys.path.append( os.path.join( os.getenv('HOME'), 'Desktop','pyeuclid'))
11+
from euclid import *
12+
13+
ONE_THIRD = 1/3.0
14+
15+
def kochify_3d( a, b, c,
16+
ab_weight=0.5, bc_weight=0.5, ca_weight=0.5,
17+
pyr_a_weight=ONE_THIRD, pyr_b_weight=ONE_THIRD, pyr_c_weight=ONE_THIRD,
18+
pyr_height_weight=ONE_THIRD
19+
):
20+
'''
21+
Point3s a, b, and c must be coplanar and define a face
22+
ab_weight, etc define the subdivision of the original face
23+
pyr_a_weight, etc define where the point of the new pyramid face will go
24+
pyr_height determines how far from the face the new pyramid's point will be
25+
'''
26+
triangles = []
27+
new_a = a.affine_combination( b, ab_weight)
28+
new_b = b.affine_combination( c, bc_weight)
29+
new_c = c.affine_combination( a, ca_weight)
30+
31+
triangles.extend( [[a, new_a, new_c], [b, new_b, new_a], [c, new_c, new_b]])
32+
33+
avg_pt_x = a.x*pyr_a_weight + b.x*pyr_b_weight + c.x*pyr_c_weight
34+
avg_pt_y = a.y*pyr_a_weight + b.y*pyr_b_weight + c.y*pyr_c_weight
35+
avg_pt_z = a.z*pyr_a_weight + b.z*pyr_b_weight + c.z*pyr_c_weight
36+
37+
center_pt = Point3( avg_pt_x, avg_pt_y, avg_pt_z)
38+
39+
# The top of the pyramid will be on a normal
40+
ab_vec = b - a
41+
bc_vec = c - b
42+
ca_vec = a - c
43+
normal = ab_vec.cross( bc_vec).normalized()
44+
avg_side_length = (abs(ab_vec) + abs(bc_vec) + abs(ca_vec))/3
45+
pyr_h = avg_side_length * pyr_height_weight
46+
pyr_pt = LineSegment3( center_pt, normal, pyr_h).p2
47+
48+
49+
triangles.extend([[new_a, pyr_pt, new_c], [new_b, pyr_pt, new_a], [new_c, pyr_pt, new_b]])
50+
51+
return triangles
52+
53+
54+
def kochify( seg, height_ratio=0.33, left_loc= 0.33, midpoint_loc=0.5, right_loc= 0.66):
55+
a, b = seg.p1, seg.p2
56+
l = a.affine_combination( b, left_loc)
57+
c = a.affine_combination( b, midpoint_loc)
58+
r = a.affine_combination( b, right_loc)
59+
# The point of the new triangle will be height_ratio * abs(seg) long,
60+
# and run perpendicular to seg, through c.
61+
perp = seg.v.cross().normalized()
62+
63+
c_height = height_ratio* abs(seg)
64+
perp_pt = LineSegment2( c, perp, -c_height).p2
65+
66+
# For the moment, assume perp_pt is on the right side of seg.
67+
# Will confirm this later if needed
68+
return [ LineSegment2( a, l),
69+
LineSegment2( l, perp_pt),
70+
LineSegment2( perp_pt, r),
71+
LineSegment2( r, b)]
72+
73+
def main_3d():
74+
gens = 4
75+
76+
# Parameters
77+
ab_weight = 0.5
78+
bc_weight = 0.5
79+
ca_weight = 0.5
80+
pyr_a_weight = ONE_THIRD
81+
pyr_b_weight = ONE_THIRD
82+
pyr_c_weight = ONE_THIRD
83+
pyr_height_weight = ONE_THIRD
84+
pyr_height_weight = ONE_THIRD
85+
# pyr_height_weight = .25
86+
87+
all_polys = union()
88+
89+
# setup
90+
ax, ay, az = 100, -100, 100
91+
bx, by, bz = 100, 100,-100
92+
cx, cy, cz = -100, 100, 100
93+
dx, dy, dz = -100, -100, -100
94+
generations = [ [[ Point3( ax, ay, az), Point3( bx, by, bz), Point3( cx, cy, cz)],
95+
[ Point3( bx, by, bz), Point3( ax, ay, az), Point3( dx, dy, dz)],
96+
[ Point3( ax, ay, az), Point3( cx, cy, cz), Point3( dx, dy, dz)],
97+
[ Point3( cx, cy, cz), Point3( bx, by, bz), Point3( dx, dy, dz)],
98+
]
99+
]
100+
101+
# Recursively generate snowflake segments
102+
for g in range(1, gens):
103+
generations.append([])
104+
for a, b, c in generations[g-1]:
105+
new_tris = kochify_3d( a, b, c,
106+
ab_weight, bc_weight, ca_weight,
107+
pyr_a_weight, pyr_b_weight,pyr_c_weight,
108+
pyr_height_weight)
109+
# new_tris = kochify_3d( a, b, c)
110+
generations[g].extend( new_tris)
111+
112+
# Put all generations into SCAD
113+
orig_length = abs( generations[0][0][1] - generations[0][0][0])
114+
for g, a_gen in enumerate(generations):
115+
# Move each generation up in y so it doesn't overlap the others
116+
h = orig_length *1.5 * g
117+
118+
# Build the points and triangles arrays that SCAD needs
119+
tris = []
120+
points = []
121+
for a,b,c in a_gen:
122+
points.extend([ [a.x, a.y, a.z], [b.x, b.y, b.z], [c.x, c.y, c.z]])
123+
t = len(points)
124+
tris.append([t-3, t-2, t-1])
125+
126+
# Do the SCAD
127+
edges = [range(len(points))]
128+
all_polys.add( up( h)(
129+
polyhedron( points, tris)
130+
)
131+
)
132+
133+
scad_render_to_file( all_polys, os.path.join( os.getenv('HOME'), 'Desktop', 'koch_3d.scad'))
134+
135+
def main():
136+
# Parameters
137+
midpoint_weight = 0.5
138+
height_ratio = 0.25
139+
left_loc = ONE_THIRD
140+
midpoint_loc = 0.5
141+
right_loc = 2*ONE_THIRD
142+
gens = 5
143+
144+
# Results
145+
all_polys = union()
146+
147+
# setup
148+
ax, ay = 0, 0
149+
bx, by = 100, 0
150+
cx, cy = 50, 86.6
151+
base_seg1 = LineSegment2( Point2( ax, ay), Point2( cx, cy))
152+
base_seg2 = LineSegment2( Point2( cx, cy), Point2( bx, by))
153+
base_seg3 = LineSegment2( Point2( bx, by), Point2( ax, ay))
154+
generations = [[base_seg1, base_seg2, base_seg3]]
155+
156+
157+
# Recursively generate snowflake segments
158+
for g in range(1, gens):
159+
generations.append([])
160+
for seg in generations[g-1]:
161+
generations[g].extend( kochify( seg, height_ratio, left_loc, midpoint_loc, right_loc))
162+
# generations[g].extend( kochify( seg))
163+
164+
# # Put all generations into SCAD
165+
orig_length = abs( generations[0][0])
166+
for g, a_gen in enumerate(generations):
167+
points = [s.p1 for s in a_gen ]
168+
# points.append( a_gen[-1].p2) # add the last point
169+
170+
rect_offset = 10
171+
172+
# Just use arrays for points so SCAD understands
173+
points = [[p.x, p.y] for p in points]
174+
175+
# Move each generation up in y so it doesn't overlap the others
176+
h = orig_length *1.5 * g
177+
178+
# Do the SCAD
179+
edges = [range(len(points))]
180+
all_polys.add( forward( h)( polygon(points=points, paths=edges )))
181+
182+
scad_render_to_file( all_polys, os.path.join( os.getenv('HOME'), 'Desktop', 'koch.scad'))
183+
184+
if __name__ == '__main__':
185+
main_3d()
186+
main()

finger_joint.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#! /usr/bin/python
2+
# -*- coding: UTF-8 -*-
3+
from __future__ import division
4+
import os, sys, re
5+
6+
# Assumes SolidPython is in site-packages or elsewhwere in sys.path
7+
from SolidPython.pyopenscad import *
8+
from SolidPython.sp_utils import *
9+
10+
SEGMENTS = 48
11+
12+
def finger_joint( poly, p_a, p_b, poly_sheet_thick, other_sheet_thick, joint_width=None, kerf=0.1):
13+
if not joint_width: joint_width = poly_sheet_thick
14+
15+
# Should get this passed in from poly, really
16+
edge_length = 50
17+
18+
k2 = kerf/2
19+
points = [ [ 0,-k2],
20+
[joint_width + k2, -k2],
21+
[joint_width + k2, other_sheet_thick - k2],
22+
[2*(joint_width + k2), other_sheet_thick -k2],
23+
[2*(joint_width + k2), -k2],
24+
]
25+
26+
27+
def assembly():
28+
29+
a = finger_joint( p, poly_sheet_thick=4.75, other_sheet_thick=4.75, joint_width=None, kerf=0.1)
30+
31+
return a
32+
33+
if __name__ == '__main__':
34+
a = assembly()
35+
scad_render_to_file( a, file_header='$fn = %s;'%SEGMENTS, include_orig_code=True)

screw_thread.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#! /usr/bin/python
2+
# -*- coding: UTF-8 -*-
3+
import os, sys, re
4+
5+
# Make sure we have access to pyopenscad
6+
from SolidPython.pyopenscad import *
7+
from SolidPython.sp_utils import *
8+
from pyeuclid.euclid import *
9+
10+
def thread( outline_pts, inner_rad, pitch, length, segments_per_rot=32,
11+
neck_in_degrees=0, neck_out_degrees=0):
12+
'''
13+
Sweeps outline_pts (an array of points describing a closed polygon in XY)
14+
through a spiral.
15+
16+
This is done by creating and returning one huge polyhedron, with potentially
17+
thousands of faces. An alternate approach would make one single polyhedron,
18+
then repeat it over and over in the spiral shape, unioning them all together.
19+
This would create a similar number of SCAD objects and operations, but still
20+
require a lot of transforms and unions to be done in the SCAD code rather than
21+
in the python, as here. Also would take some doing to make the neck-in work
22+
as well. Not sure how the two approaches compare in terms of render-time.
23+
-ETJ 16 Mar 2011
24+
25+
'''
26+
a = union()
27+
rotations = float(length)/pitch
28+
29+
total_angle = 360.0*rotations
30+
up_step = float(length) / (rotations*segments_per_rot)
31+
total_steps = int(ceil( rotations * segments_per_rot))
32+
step_angle = total_angle/ total_steps
33+
34+
all_points = []
35+
all_tris = []
36+
euc_up = Vector3( *UP_VEC)
37+
poly_sides = len( outline_pts)
38+
39+
# Figure out how wide the tooth profile is
40+
min_bb, max_bb = bounding_box( outline_pts)
41+
outline_w = max_bb[0] - min_bb[0]
42+
43+
min_rad = max( 0, inner_rad-outline_w-EPSILON)
44+
45+
# outline_pts, since they were created in 2D , are in the XY plane.
46+
# But spirals move a profile in XZ around the Z-axis. So swap Y and Z
47+
# co-ords... and hope users know about this
48+
# Also add inner_rad to the profile
49+
euc_points = []
50+
for p in outline_pts:
51+
# If p is in [x, y] format, make it [x, y, 0]
52+
if len( p) == 2:
53+
p.append( 0)
54+
# [x, y, z] => [ x+inner_rad, z, y]
55+
s = Point3( p[0], p[2], p[1]) # adding inner_rad, swapping Y & Z
56+
euc_points.append( s)
57+
58+
for i in range( total_steps):
59+
angle = i*step_angle
60+
elevation = i*up_step
61+
if angle > total_angle:
62+
angle = total_angle
63+
elevation = length
64+
65+
rad = inner_rad
66+
if angle < neck_in_degrees:
67+
rad = min_rad + angle/neck_in_degrees * outline_w
68+
elif angle > total_angle - neck_out_degrees:
69+
rad =min_rad + (total_angle - angle)/neck_out_degrees * outline_w
70+
71+
elev_vec = Vector3( rad, 0, elevation)
72+
73+
for p in euc_points:
74+
pt = (p + elev_vec).rotate_around( axis=euc_up, theta=radians( angle))
75+
all_points.append( pt.as_arr())
76+
77+
# Add the connectivity information
78+
if i < total_steps -1:
79+
ind = i*poly_sides
80+
for j in range( ind, ind + poly_sides - 1):
81+
all_tris.append( [ j, j+poly_sides, j+1])
82+
all_tris.append( [ j+1, j+poly_sides, j+poly_sides+1])
83+
all_tris.append( [ ind + poly_sides-1, ind + poly_sides-1+poly_sides, ind])
84+
all_tris.append( [ ind, ind +poly_sides-1+poly_sides, ind + poly_sides])
85+
86+
# End triangle fans for beginning and end
87+
last_loop = len(all_points) - poly_sides
88+
for i in range( poly_sides -2):
89+
all_tris.append( [ 0, i+1, i+2])
90+
all_tris.append( [ last_loop, last_loop + i + 2, last_loop + i+1])
91+
92+
93+
# Make the polyhedron
94+
a = polyhedron( points=all_points, triangles=all_tris)
95+
96+
# Subtract the center, to remove the neck-in pieces
97+
# subtract above and below to make sure the entire screw fits within height 'length'
98+
cube_side = 2*(inner_rad + EPSILON + outline_w)
99+
subs = union()(
100+
cylinder( inner_rad, length),
101+
down( cube_side/2)( cube( cube_side, center=True)),
102+
up( cube_side/2 + length)( cube( cube_side, center=True))
103+
)
104+
return render()(a - subs)
105+
106+
def assembly():
107+
# Scad code here
108+
a = union()
109+
110+
rad = 5
111+
pts = [ [ 0, -1, 0],
112+
[ 1, 0, 0],
113+
[ 0, 1, 0],
114+
[ -1, 0, 0],
115+
[ -1, -1, 0] ]
116+
117+
a = thread( pts, inner_rad=10, pitch= 6, length=2, segments_per_rot=31,
118+
neck_in_degrees=30, neck_out_degrees=30)
119+
120+
return a + cylinder( 10+EPSILON, 2)
121+
122+
if __name__ == '__main__':
123+
a = assembly()
124+
scad_render_to_file( a)

0 commit comments

Comments
 (0)