1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
import numpy as np
from chroma.geometry import Mesh
from chroma.transform import rotate
from chroma.itertoolset import *
def mesh_grid(grid):
begin = grid[:-1].flatten()
end = grid[1:].flatten()
begin_roll = np.roll(grid[:-1],-1,1).flatten()
end_roll = np.roll(grid[1:],-1,1).flatten()
mesh = np.empty(shape=(2*len(begin),3), dtype=begin.dtype)
mesh[:len(begin),0] = begin
mesh[:len(begin),1] = end
mesh[:len(begin),2] = end_roll
mesh[len(begin):,0] = begin
mesh[len(begin):,1] = end_roll
mesh[len(begin):,2] = begin_roll
return mesh
def linear_extrude(x1, y1, height, x2=None, y2=None, center=None, endcaps=True):
"""
Return the solid mesh formed by linearly extruding the polygon formed by
the x and y points `x1` and `y1` by a distance `height`. If `x2` and `y2`
are given extrude by connecting the points `x1` and `y1` to `x2` and `y2`;
this allows the creation of tapered solids. If `endcaps` is False, then
the triangles on the endcaps will be left off, and the mesh will not be
closed.
.. note::
The path traced by the points `x` and `y` should go counter-clockwise,
otherwise the mesh will be inside out.
Example:
>>> # create a hexagon prism
>>> angles = np.linspace(0, 2*np.pi, 6, endpoint=False)
>>> m = linear_extrude(np.cos(angles), np.sin(angles), 2.0)
"""
if len(x1) != len(y1):
raise Exception('`x` and `y` arrays must have the same length.')
if x2 is None:
x2 = x1
if y2 is None:
y2 = y1
if len(x2) != len(y2) or len(x2) != len(x1):
raise Exception('`x` and `y` arrays must have the same length.')
n = len(x1)
vertex_iterators = [izip(x1,y1,repeat(-height/2.0,n)),
izip(x2,y2,repeat(height/2.0,n))]
if endcaps:
vertex_iterators = [izip(repeat(0,n),repeat(0,n),repeat(-height/2.0,n))] \
+ vertex_iterators \
+ [izip(repeat(0,n),repeat(0,n),repeat(height/2.0,n))]
vertices = np.fromiter(flatten(roundrobin(*vertex_iterators)), float)
vertices = vertices.reshape((len(vertices)//3,3))
if center is not None:
vertices += center
triangles = mesh_grid(np.arange(len(vertices)).reshape((len(x1),len(vertices)//len(x1))).transpose()[::-1])
return Mesh(vertices, triangles, remove_duplicate_vertices=True)
def rotate_extrude(x, y, nsteps=64):
"""
Return the solid mesh formed by extruding the profile defined by the x and
y points `x` and `y` around the y axis.
.. note::
The path traced by the points `x` and `y` should go counter-clockwise,
otherwise the mesh will be inside out.
Example:
>>> # create a bipyramid
>>> m = rotate_extrude([0,1,0], [-1,0,1], nsteps=4)
"""
if len(x) != len(y):
raise Exception('`x` and `y` arrays must have the same length.')
points = np.array([x,y,np.zeros(len(x))]).transpose()
steps = np.linspace(0, 2*np.pi, nsteps, endpoint=False)
vertices = np.vstack([rotate(points,angle,(0,-1,0)) for angle in steps])
triangles = mesh_grid(np.arange(len(vertices)).reshape((len(steps),len(points))).transpose()[::-1])
return Mesh(vertices, triangles, remove_duplicate_vertices=True)
def box(dx, dy, dz, center=(0,0,0)):
"Return a box with linear dimensions `dx`, `dy`, and `dz`."
return linear_extrude([-dx/2.0,dx/2.0,dx/2.0,-dx/2.0],[-dy/2.0,-dy/2.0,dy/2.0,dy/2.0],height=dz,center=center)
def cube(size, height=None, center=(0,0,0)):
"Return a cube mesh whose sides have length `size`."
if height is None:
height = size
return linear_extrude([-size/2.0,size/2.0,size/2.0,-size/2.0],[-size/2.0,-size/2.0,size/2.0,size/2.0], height=size, center=center)
def cylinder_along_z(radius, height, points=100):
angles = np.linspace(0, 2*np.pi, points, endpoint=False)
return linear_extrude(radius*np.cos(angles), radius*np.sin(angles), height)
def cylinder(radius, height, radius2=None, nsteps=64):
"""
Return a cylinder mesh with a radius of length `radius`, and a height of
length `height`. If `radius2` is specified, return a cone shaped cylinder
with bottom radius `radius`, and top radius `radius2`.
"""
if radius2 is None:
radius2 = radius
return rotate_extrude([0,radius,radius2,0], [-height/2.0, -height/2.0, height/2.0, height/2.0], nsteps)
def segmented_cylinder(radius, height, nsteps=64, nsegments=100):
"""
Return a cylinder mesh segmented into `nsegments` points along its profile.
"""
nsegments_radius = int((nsegments*radius/(2*radius+height))/2)
nsegments_height = int((nsegments*height/(2*radius+height))/2)
x = np.concatenate([np.linspace(0,radius,nsegments_radius,endpoint=False),[radius]*nsegments_height,np.linspace(radius,0,nsegments_radius,endpoint=False),[0]])
y = np.concatenate([[-height/2.0]*nsegments_radius,np.linspace(-height/2.0,height/2.0,nsegments_height,endpoint=False),[height/2.0]*(nsegments_radius+1)])
return rotate_extrude(x, y, nsteps)
def sphere(radius, nsteps=64):
"Return a sphere mesh."
profile_angles = np.linspace(-np.pi/2, np.pi/2, nsteps)
return rotate_extrude(radius*np.cos(profile_angles), radius*np.sin(profile_angles), nsteps)
def torus(radius, offset, nsteps=64, circle_steps=None):
"""
Return a torus mesh. `offset` is the distance from the center of the torus
to the center of the torus barrel. `radius` is the radius of the torus
barrel. `nsteps` is the number of steps in the rotational extrusion of the
circle. `circle_steps` if specified is the number of steps around the
circumference of the torus barrel, else it defaults to `nsteps`.
"""
if circle_steps is None:
circle_steps = nsteps
profile_angles = np.linspace(0, 2*np.pi, circle_steps)
return rotate_extrude(np.cos(profile_angles) + offset, np.sin(profile_angles), nsteps)
def convex_polygon(x, y):
"""
Return a polygon mesh in the x-y plane. `x` and `y` are the x and
y coordinates for the points in the polygon. The simple triangulation
method used here requires that the polygon be convex and the points
are specified in order.
"""
vertices = np.column_stack( (x, y, np.zeros_like(x)) )
# Every triangle includes
triangles = np.empty(shape=(len(vertices)-2,3), dtype=np.int32)
triangles[:,0] = 0 # Every triangle includes vertex zero
triangles[:,1] = np.arange(1, len(vertices)-1)
triangles[:,2] = np.arange(2, len(vertices))
return Mesh(vertices=vertices, triangles=triangles)
|