Module piecad.primitives_3d
Create 3D objects such as spheres and cubes.
Functions
def cone(height: float,
radius_low: float,
radius_high: float = 0.2,
segments: int = -1,
center: bool = False) ‑> Obj3d-
Expand source code
def cone( height: float, radius_low: float, radius_high: float = 0.2, segments: int = -1, center: bool = False, ) -> Obj3d: """ Make a cone with given radii and height. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). Why does `radius_high` not default to 0? Pointy things don't 3d print very well. If the model is not to be printed, by all means set it to 0. By default, the cone bottom is centered at `(0,0,0)`. When `center` is `True`, the cone will be centered on `(0,0,0)`. (In other words, the bottom of the cone will be at `(0,0,-height/2.0`.) <iframe width="100%" height="220" src="examples/cone.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGT("height", height, 0) _chkGT("radius_low", radius_low, 0) _chkGE("radius_high", radius_high, 0) _chkGE("segments", segments, 3) return Obj3d( _m.Manifold.cylinder(height, radius_low, radius_high, segments, center) )
Make a cone with given radii and height.
For
segments
see the documentation ofset_default_segments
.Why does
radius_high
not default to 0? Pointy things don't 3d print very well. If the model is not to be printed, by all means set it to 0.By default, the cone bottom is centered at
(0,0,0)
. Whencenter
isTrue
, the cone will be centered on(0,0,0)
. (In other words, the bottom of the cone will be at(0,0,-height/2.0
.) def cube(size: float, center: bool = False) ‑> Obj3d
-
Expand source code
def cube(size: float, center: bool = False) -> Obj3d: """ Make a cube of with sides of the given size. By default, the bottom front left corner of the cube will be at `(0,0,0)`. When `center` is `True` it will cause the cube to be centered at `(0,0,0)`. """ # Too many models on one page. <iframe width="100%" height="220" src="examples/cube.html"></iframe> if type(size) == list or type(size) == tuple: return cuboid(size, center) return Obj3d(_m.Manifold.cube((size, size, size), center))
Make a cube of with sides of the given size.
By default, the bottom front left corner of the cube will be at
(0,0,0)
. Whencenter
isTrue
it will cause the cube to be centered at(0,0,0)
. def cuboid(size: list[float, float, float], center: bool = False) ‑> Obj3d
-
Expand source code
def cuboid(size: list[float, float, float], center: bool = False) -> Obj3d: """ Make a cuboid with the x, y, and z values given in size. By default, the bottom front left corner of the cuboid will be at `(0,0,0)`. When `center` is `True` it will cause the cube to be centered at `(0,0,0)`. """ # Too many models on one page. <iframe width="100%" height="220" src="examples/cuboid.html"></iframe> if type(size) == float or type(size) == int: return cube(size) return Obj3d(_m.Manifold.cube(size, center))
Make a cuboid with the x, y, and z values given in size.
By default, the bottom front left corner of the cuboid will be at
(0,0,0)
. Whencenter
isTrue
it will cause the cube to be centered at(0,0,0)
. def cylinder(height: float, radius: float, segments: int = -1, center=False) ‑> Obj3d
-
Expand source code
def cylinder(height: float, radius: float, segments: int = -1, center=False) -> Obj3d: """ Make a cylinder of a given radius and height. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). By default, the cylinder bottom is centered at `(0,0,0)`. When `center` is `True`, the cylinder will centered on `(0,0,0)`. (In other words, the bottom of the cylinder will be at `(0,0,-height/2.0`.) """ if segments == -1: segments = config["DefaultSegments"] _chkGT("height", height, 0) _chkGT("radius", radius, 0) _chkGE("segments", segments, 3) cyl = circle(radius, segments).extrude(height) if center: cyl = cyl.translate((0, 0, -height / 2.0)) return cyl
Make a cylinder of a given radius and height.
For
segments
see the documentation ofset_default_segments
.By default, the cylinder bottom is centered at
(0,0,0)
. Whencenter
isTrue
, the cylinder will centered on(0,0,0)
. (In other words, the bottom of the cylinder will be at(0,0,-height/2.0
.) def ellipsoid(radii: tuple[float, float, float], segments: int = -1, center=False) ‑> Obj3d
-
Expand source code
def ellipsoid( radii: tuple[float, float, float], segments: int = -1, center=False ) -> Obj3d: """ Make an ellipsoid which is elliptical on all three radii. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). The ellipsoid is centered at `(0,0,0)`. <iframe width="100%" height="220" src="examples/ellipsoid.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkV3("radius", radii) _chkGE("segments", segments, 3) return sphere(1, segments=segments).scale(radii)
Make an ellipsoid which is elliptical on all three radii.
For
segments
see the documentation ofset_default_segments
.The ellipsoid is centered at
(0,0,0)
. def elliptical_cylinder(height: float, radii: tuple[float, float], segments: int = -1, center=False) ‑> Obj3d
-
Expand source code
def elliptical_cylinder( height: float, radii: tuple[float, float], segments: int = -1, center=False ) -> Obj3d: """ Make a elliptically shaped cylinder of given radii and height. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). By default, the elliptical cylinder bottom is centered at `(0,0,0)`. When `center` is `True`, the cylinder will centered on `(0,0,0)`. (In other words, the bottom of the elliptical cylinder will be at `(0,0,-height/2.0`.) <iframe width="100%" height="220" src="examples/elliptical_cylinder.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGT("height", height, 0) _chkV2("radii", radii) _chkGE("segments", segments, 3) ecyl = extrude(ellipse(radii, segments), height) if center: ecyl = ecyl.translate((0, 0, -height / 2.0)) return ecyl
Make a elliptically shaped cylinder of given radii and height.
For
segments
see the documentation ofset_default_segments
.By default, the elliptical cylinder bottom is centered at
(0,0,0)
. Whencenter
isTrue
, the cylinder will centered on(0,0,0)
. (In other words, the bottom of the elliptical cylinder will be at(0,0,-height/2.0
.) def extrude(obj: Obj2d,
height: float) ‑> Obj3d-
Expand source code
def extrude(obj: Obj2d, height: float) -> Obj3d: """ Create a Obj3d solid from Obj2d of given height. The 2d object will be copied and moved up to `height`. Lines will be added creating an `obj`-shaped 3d solid. <iframe width="100%" height="220" src="examples/extrude.html"></iframe> """ _chkTY("obj", obj, Obj2d) _chkGT("height", height, 0) return Obj3d(_m.Manifold.extrude(obj.mo, height))
Create a Obj3d solid from Obj2d of given height.
The 2d object will be copied and moved up to
height
. Lines will be added creating anobj
-shaped 3d solid. def extrude_chaining(pairs: list[tuple[float, Obj2d]],
is_convex: bool = False,
diagnose: str = None) ‑> Obj3d-
Expand source code
def extrude_chaining( pairs: list[tuple[float, Obj2d]], is_convex: bool = False, diagnose: str = None ) -> Obj3d: """ Extrude multiple 2d objects into a single 3d Object. ALL 2D OBJECTS MUST HAVE THE SAME NUMBER OF POINTS. Parameter `pairs` is a list of pairs: `[[height, Obj2d], ...]`. This list controls an extrusion of 2d shapes chained together into one 3d object. The `height` is cumulative. You are always specifing the exact current height to be output for the current object. This is done so that you can have numerically robust dimensions in your object. If relative heights were used, extuding something like a sphere is would end up with a sphere that's height was not precisely the desired height. If parameter `is_convex` is set to `True` a much faster triangulation algoritm is used, which only works on a convex shape. If you don't understand what this means, leave this option alone. Caps are automatically generated from the first and last shapes. If `diagnose` is not `None`, it should contain a string of a filename where the faulty object is stored (to look at with meshlab, for example). Use of the `.obj` extension is recommended. <iframe width="100%" height="550" src="examples/extrude_chaining.html"></iframe> """ vertex_list = [] triangles = [] _chkGT("pairs length", len(pairs), 1) def add_cap(v_off, polys, top): if not is_convex or len(polys) != 1: tris = _m.triangulate(polys) for t in tris: if top: triangles.append((t[0] + v_off, t[1] + v_off, t[2] + v_off)) else: # Bottom caps are reversed. triangles.append((t[2] + v_off, t[1] + v_off, t[0] + v_off)) else: # Fan triangulation n = len(poly) # We have only one to deal with chosen = 0 for i in range(1, n - 1): cur = i next = i + 1 if top: triangles.append((chosen + v_off, cur + v_off, next + v_off)) else: # Bottom caps are clockwise. triangles.append((next + v_off, cur + v_off, chosen + v_off)) v_offs = [] v_offs.append(0) o2d_polys = [] for h, o2d in pairs: if o2d.is_empty(): raise ValidationError( f"At pairs index: {cur_idx}, empty shape is not allowed" ) polys = o2d.to_paths() o2d_polys.append(polys) for poly in polys: for vert in poly: vertex_list.append((vert[0], vert[1], h)) v_offs.append(len(vertex_list)) v_offs.pop() prev_polys = o2d_polys[0] prev_vo = 0 add_cap(prev_vo, prev_polys, top=False) last = len(pairs) cur_idx = 1 while cur_idx < last: cur_polys = o2d_polys[cur_idx] cur_vo = v_offs[cur_idx] if len(cur_polys) != len(prev_polys): raise ValidationError( f"At pairs index: {cur_idx}, previous shape does not match current shape" ) for i in range(0, len(cur_polys)): b = prev_polys[i] t = cur_polys[i] if len(b) != len(t): raise ValidationError( f"At pairs index: {cur_idx}, poly: {i}, previous shape does not match current shape" ) bottom_p = 0 + prev_vo top_p = 0 + cur_vo _len = len(b) for j in range(0, _len): next_bottom_p = ((j + 1) % _len) + prev_vo next_top_p = ((j + 1) % _len) + cur_vo triangles.append((bottom_p, next_bottom_p, next_top_p)) triangles.append((bottom_p, next_top_p, top_p)) bottom_p = next_bottom_p top_p = next_top_p prev_vo = cur_vo cur_vo += _len prev_polys = cur_polys prev_vo = v_offs[cur_idx] cur_idx += 1 add_cap(prev_vo, prev_polys, top=True) vertex_list = _np.array(vertex_list, _np.float32) triangles = _np.array(triangles, _np.uint32) mesh = _m.Mesh(vertex_list, triangles) if diagnose != None: import trimesh dot_idx = diagnose.rindex(".") ext = diagnose[dot_idx + 1 :] mesh_output = trimesh.Trimesh(vertices=vertex_list, faces=triangles) trimesh.exchange.export.export_mesh(mesh_output, diagnose, ext) mo = _m.Manifold(mesh) if mo.is_empty(): raise ValidationError(f"Error creating Manifold: {mo.status()}.") return Obj3d(mo)
Extrude multiple 2d objects into a single 3d Object.
ALL 2D OBJECTS MUST HAVE THE SAME NUMBER OF POINTS.
Parameter
pairs
is a list of pairs:[[height, Obj2d], …]
. This list controls an extrusion of 2d shapes chained together into one 3d object.The
height
is cumulative. You are always specifing the exact current height to be output for the current object. This is done so that you can have numerically robust dimensions in your object. If relative heights were used, extuding something like a sphere is would end up with a sphere that's height was not precisely the desired height.If parameter
is_convex
is set toTrue
a much faster triangulation algoritm is used, which only works on a convex shape. If you don't understand what this means, leave this option alone.Caps are automatically generated from the first and last shapes.
If
diagnose
is notNone
, it should contain a string of a filename where the faulty object is stored (to look at with meshlab, for example). Use of the.obj
extension is recommended. def extrude_transforming(obj: Obj2d,
height: float,
num_twist_divisions: int = 0,
twist: float = 0,
scale: tuple[float, float] = (1.0, 1.0)) ‑> Obj3d-
Expand source code
def extrude_transforming( obj: Obj2d, height: float, num_twist_divisions: int = 0, twist: float = 0, scale: tuple[float, float] = (1.0, 1.0), ) -> Obj3d: """ Create a Obj3d solid from Obj2d of given height. The 2d object will be copied and moved up to `height`. Lines will be added creating an `obj`-shaped 3d solid. Parameter `num_twist_divisions` should only be used when `twist` is greater than zero. Parameter `twist` will cause a circular rotation for each `num_twist_divisions`. Parammeter `scale` specifes scaling factors applied at each division. <iframe width="100%" height="300" src="examples/extrude_transforming.html"></iframe> """ _chkTY("obj", obj, Obj2d) _chkGT("height", height, 0) _chkGE("num_twist_divisions", num_twist_divisions, 0) _chkGE("twist", twist, 0) _chkV2("scale", scale) return Obj3d( _m.Manifold.extrude( obj.mo, height, num_twist_divisions, twist, scale, ) )
Create a Obj3d solid from Obj2d of given height.
The 2d object will be copied and moved up to
height
. Lines will be added creating anobj
-shaped 3d solid.Parameter
num_twist_divisions
should only be used whentwist
is greater than zero.Parameter
twist
will cause a circular rotation for eachnum_twist_divisions
.Parammeter
scale
specifes scaling factors applied at each division. def geodesic_sphere(radius, segments=-1)
-
Expand source code
def geodesic_sphere(radius, segments=-1): """ Create a geodesic sphere of a given radius. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). <iframe width="100%" height="220" src="examples/geodesic_sphere.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGT("radius", radius, 0) _chkGE("segments", segments, 3) return Obj3d(_m.Manifold.sphere(radius, segments))
Create a geodesic sphere of a given radius.
For
segments
see the documentation ofset_default_segments
. def polyhedron(vertices: list[tuple[float, float, float]], faces: list[tuple[int, int, int]]) ‑> Obj3d
-
Expand source code
def polyhedron( vertices: list[tuple[float, float, float]], faces: list[tuple[int, int, int]] ) -> Obj3d: """ Create an Obj3d from points and a list of triangles using those points. THIS IS AN ADVANCED FUNCTION. If you don't already understand the "directions" below... REALLY, try doing what you want to do another way. * Faces have to be wound counter-clockwise. * All faces must be triangles. * Faces are integer indices into the vertices list. You eventually will get a message from the Manifold package saying it is not "manifold". That isn't really helpful. Try adding these lines just before the call to polyhedron. ```python import trimesh mesh_output = trimesh.Trimesh(vertices=vertices, faces=triangles) trimesh.exchange.export.export_mesh(mesh_output, "mesh.obj", "obj") ``` Then use a program like `meshlab` to look at where things are not manifold. """ vertices = _np.array(vertices, _np.float32) faces = _np.array(faces, _np.uint32) mesh = _m.Mesh(vertices, faces) mo = _m.Manifold(mesh) if mo.is_empty(): raise ValidationError(f"Error creating Manifold: {mo.status()}.") return Obj3d(mo)
Create an Obj3d from points and a list of triangles using those points.
THIS IS AN ADVANCED FUNCTION.
If you don't already understand the "directions" below… REALLY, try doing what you want to do another way.
-
Faces have to be wound counter-clockwise.
-
All faces must be triangles.
-
Faces are integer indices into the vertices list.
You eventually will get a message from the Manifold package saying it is not "manifold".
That isn't really helpful.
Try adding these lines just before the call to polyhedron.
import trimesh mesh_output = trimesh.Trimesh(vertices=vertices, faces=triangles) trimesh.exchange.export.export_mesh(mesh_output, "mesh.obj", "obj")
Then use a program like
meshlab
to look at where things are not manifold. -
def pyramid(height: int, num_sides: int, radius: float) ‑> Obj3d
-
Expand source code
def pyramid(height: int, num_sides: int, radius: float) -> Obj3d: """ Make a regular pyramid with the given height and number of sides. The `radius` specifies the circle on which the corners of the pyramid will be built. <iframe width="100%" height="220" src="examples/pyramid.html"></iframe> """ _chkGT("height", height, 0) _chkGE("num_sides", num_sides, 3) _chkGT("radius", radius, 0) return extrude_transforming( circle(radius, segments=num_sides), height=height, scale=(0, 0) )
Make a regular pyramid with the given height and number of sides.
The
radius
specifies the circle on which the corners of the pyramid will be built. def revolve(obj: Obj2d,
revolve_degrees: float = 360.0,
segments: int = -1) ‑> Obj3d-
Expand source code
def revolve(obj: Obj2d, revolve_degrees: float = 360.0, segments: int = -1) -> Obj3d: """ Create a Obj3d by revolving an Obj2d around the Y-axis, then rotating it so that Y becomes Z. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). <iframe width="100%" height="220" src="examples/revolve.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkTY("obj", obj, Obj2d) _chkGE("segments", segments, 3) _chkGT("revolve_degrees", revolve_degrees, 0) return Obj3d(_m.Manifold.revolve(obj.mo, segments, revolve_degrees))
Create a Obj3d by revolving an Obj2d around the Y-axis, then rotating it so that Y becomes Z.
For
segments
see the documentation ofset_default_segments
. def rounded_cuboid(size: list[float, float, float],
rounding_radius=2.0,
segments: int = -1,
center: bool = False) ‑> Obj3d-
Expand source code
def rounded_cuboid( size: list[float, float, float], rounding_radius=2.0, segments: int = -1, center: bool = False, ) -> Obj3d: """ Make a rounded_cuboid with the x, y, and z values given in size. Parameter `rounding_radius` is the size of the rounded lip at top and bottom. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). By default, the bottom front left corner of the rounded cuboid will be at `(0,0,0)`. When `center` is `True` it will cause the rounded cuboid to be centered at `(0,0,0)`. <iframe width="100%" height="250" src="examples/rounded_cuboid.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGE("segments", segments, 3) _chkGE("rounding_radius", rounding_radius, 0) _chkV3("size", size) if type(size) == float or type(size) == int: size = (size, size, size) l = [] res = config["LayerResolution"] arc_segs = rounding_radius / res deg_per_arc_seg = 90.0 / arc_segs deg = 0.0 cur_z = 0 x, y, z = size rr = rounding_radius ix = x - 2 * rr iy = y - 2 * rr smallest_rr = sin(deg_per_arc_seg) / 2.0 l.append( (cur_z, rounded_rectangle((ix, iy), smallest_rr, segments).translate((rr, rr))) ) deg += deg_per_arc_seg while deg < 90.0: delta = rr * sin(deg) cur_z = rr - rr * cos(deg) l.append( ( cur_z, rounded_rectangle( (ix + 2 * delta, iy + 2 * delta), delta, segments ).translate((rr - delta, rr - delta)), ) ) deg += deg_per_arc_seg cur_z = rr l.append((cur_z, rounded_rectangle((x, y), rr, segments))) cur_z = z - rr l.append((cur_z, rounded_rectangle((x, y), rr, segments))) deg = 90.0 deg -= deg_per_arc_seg while deg > 0.0: delta = rr * sin(deg) cur_z = z - rr + rr * cos(deg) l.append( ( cur_z, rounded_rectangle( ((ix) + 2 * delta, (iy) + 2 * delta), delta, segments ).translate((rr - delta, rr - delta)), ) ) deg -= deg_per_arc_seg cur_z = z l.append( (cur_z, rounded_rectangle((ix, iy), smallest_rr, segments).translate((rr, rr))) ) o = extrude_chaining(l, is_convex=True) if center: o = o.translate((-x / 2.0, -y / 2.0, -z / 2.0)) return o
Make a rounded_cuboid with the x, y, and z values given in size.
Parameter
rounding_radius
is the size of the rounded lip at top and bottom.For
segments
see the documentation ofset_default_segments
.By default, the bottom front left corner of the rounded cuboid will be at
(0,0,0)
. Whencenter
isTrue
it will cause the rounded cuboid to be centered at(0,0,0)
. def rounded_cylinder(height: float,
radius: float,
rounding_radius: float = 2.0,
segments: int = -1,
center: bool = False) ‑> Obj3d-
Expand source code
def rounded_cylinder( height: float, radius: float, rounding_radius: float = 2.0, segments: int = -1, center: bool = False, ) -> Obj3d: """ Make a rounded cylinder of a given radius and height. Parameter `rounding_radius` is the size of the rounded lip at top and bottom. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). By default, the rounded cylinder bottom is centered at `(0,0,0)`. When `center` is `True`, the rounded cylinder will centered on `(0,0,0)`. (In other words, the bottom of the rounded cylinder will be at `(0,0,-height/2.0`.) <iframe width="100%" height="250" src="examples/rounded_cylinder.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGE("segments", segments, 3) _chkGT("radius", radius, 0) rr = ( rounded_rectangle((2 * radius, height), rounding_radius, segments) .translate((-radius, 0)) .piecut(90, 270) ) o3 = revolve(rr, segments=segments) if center: o3 = o3.translate((0, 0, -height / 2.0)) return o3
Make a rounded cylinder of a given radius and height.
Parameter
rounding_radius
is the size of the rounded lip at top and bottom.For
segments
see the documentation ofset_default_segments
.By default, the rounded cylinder bottom is centered at
(0,0,0)
. Whencenter
isTrue
, the rounded cylinder will centered on(0,0,0)
. (In other words, the bottom of the rounded cylinder will be at(0,0,-height/2.0
.) def sphere(radius: float, segments: int = -1) ‑> Obj3d
-
Expand source code
def sphere(radius: float, segments: int = -1) -> Obj3d: """ Create a classical sphere of a given radius. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). <iframe width="100%" height="220" src="examples/sphere.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGE("radius", radius, 0) _chkGE("segments", segments, 3) circ = circle(radius, 2 * segments).piecut(90, 270) return revolve(circ, segments=segments)
Create a classical sphere of a given radius.
For
segments
see the documentation ofset_default_segments
. def torus(outer_radius: float, inner_radius: float, segments=-1)
-
Expand source code
def torus(outer_radius: float, inner_radius: float, segments=-1): """ Create a torus with the specified radii. For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments). <iframe width="100%" height="220" src="examples/torus.html"></iframe> """ if segments == -1: segments = config["DefaultSegments"] _chkGT("outer_radius", outer_radius, 0) _chkGT("inner_radius", inner_radius, 0) _chkGE("segments", segments, 3) if inner_radius >= outer_radius: raise ValidationError( "Parameter inner_radius must be smaller than outer_radius." ) sz = (outer_radius - inner_radius) / 2.0 circ = circle(sz, segments).translate((outer_radius - sz, outer_radius - sz)) return revolve(circ, segments=segments).translate((0, 0, -outer_radius + sz))
Create a torus with the specified radii.
For
segments
see the documentation ofset_default_segments
.