summaryrefslogtreecommitdiff
path: root/make.py
blob: 523b10f5c6f2d00fae9b197931f8e8bb651d4306 (plain)
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
import numpy as np
from chroma.geometry import Mesh
from chroma.transform import rotate
from chroma.itertoolset import *

def mesh_grid(grid):
    return np.vstack(zip(grid[:-1].flatten(),grid[1:].flatten(),np.roll(grid[1:],-1,1).flatten()) + zip(grid[:-1].flatten(),np.roll(grid[1:],-1,1).flatten(),np.roll(grid[:-1],-1,1).flatten()))

def linear_extrude(x1, y1, height, x2=None, y2=None, center=None):
    """
    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.

    .. 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), 5.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(repeat(0,n),repeat(0,n),repeat(-height/2.0,n)),izip(x1,y1,repeat(-height/2.0,n)),izip(x2,y2,repeat(height/2.0,n)),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.
    """
    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=1, height=None):
    "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)

def cylinder(radius=1, height=2, 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=2, 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=1, 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=1, offset=3, 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)