def pyramids(self) -> Iterable[MeshTransformer]: """ Yields all pyramids of the sierpinsky pyramid as individual :class:`MeshTransformer` objects. """ faces = self.faces() for vertices in self: mesh = MeshTransformer() mesh.add_mesh(vertices=vertices, faces=faces) yield mesh
def cube(center: bool = True) -> MeshTransformer: """ Create a `cube <https://en.wikipedia.org/wiki/Cube>`_ as :class:`~ezdxf.render.MeshTransformer` object. Args: center: 'mass' center of cube, ``(0, 0, 0)`` if ``True``, else first corner at ``(0, 0, 0)`` Returns: :class:`~ezdxf.render.MeshTransformer` """ mesh = MeshTransformer() vectices = _cube0_vertices if center else _cube_vertices mesh.add_mesh(vertices=vectices, faces=cube_faces) return mesh
def mesh(self) -> MeshTransformer: """Returns geometry as one :class:`MeshTransformer` object.""" faces = self.faces() mesh = MeshVertexMerger() for vertices in self: mesh.add_mesh(vertices=vertices, faces=faces) # type: ignore return MeshTransformer.from_builder(mesh)
def cone(count: int = 16, radius: float = 1.0, apex: 'Vertex' = (0, 0, 1), caps=True, ngons=True) -> MeshTransformer: """ Create a `cone <https://en.wikipedia.org/wiki/Cone>`_ as :class:`~ezdxf.render.MeshTransformer` object, the base center is fixed in the origin (0, 0, 0). Args: count: edge count of basis_vector radius: radius of basis_vector apex: tip of the cone caps: add a bottom face if ``True`` ngons: use ngons for caps if ``True`` else subdivide caps into triangles Returns: :class:`~ezdxf.render.MeshTransformer` """ mesh = MeshVertexMerger() base_circle = list(circle(count, radius, close=True)) for p1, p2 in zip(base_circle, base_circle[1:]): mesh.add_face([p1, p2, apex]) if caps: base_circle = reversed(base_circle) # for correct outside pointing normals if ngons: mesh.add_face(base_circle) else: for face in ngon_to_triangles(base_circle): mesh.add_face(face) return MeshTransformer.from_builder(mesh)
def from_profiles_linear(profiles: Iterable[Iterable['Vertex']], close: bool = True, caps: bool = False) -> MeshTransformer: """ Create MESH entity by linear connected `profiles`. Args: profiles: list of profiles close: close profile polygon if ``True`` caps: close hull with bottom cap and top cap (as N-gons) Returns: :class:`~ezdxf.render.MeshTransformer` """ mesh = MeshVertexMerger() profiles = list(profiles) # generator -> list if close: profiles = [close_polygon(p) for p in profiles] if caps: mesh.add_face(profiles[0]) mesh.add_face(profiles[-1]) for profile1, profile2 in zip(profiles, profiles[1:]): prev_v1, prev_v2 = None, None for v1, v2 in zip(profile1, profile2): if prev_v1 is not None: mesh.add_face([prev_v1, v1, v2, prev_v2]) prev_v1 = v1 prev_v2 = v2 return MeshTransformer.from_builder(mesh)
def cubes(self) -> Iterable[MeshTransformer]: """ Yields all cubes of the menger sponge as individual :class:`MeshTransformer` objects. """ faces = self.faces() for vertices in self: mesh = MeshVertexMerger() mesh.add_mesh(vertices=vertices, faces=faces) yield MeshTransformer.from_builder(mesh)
def test_render_polyface(cube_polyface, msp): t = MeshTransformer.from_polyface(cube_polyface) assert len(t.vertices) == 24 # unoptimized mesh builder assert len(t.faces) == 6 t.render_polyface(msp) new_polyface = msp[-1] assert new_polyface.dxftype() == "POLYLINE" assert new_polyface.is_poly_face_mesh is True assert len(new_polyface.vertices) == 8 + 6 assert new_polyface.vertices[0] is not cube_polyface.vertices[0]
def cone_2p( count: int = 16, radius: float = 1.0, base_center=(0, 0, 0), apex=(0, 0, 1)) -> MeshTransformer: """ Create a `cone <https://en.wikipedia.org/wiki/Cone>`_ as :class:`~ezdxf.render.MeshTransformer` object from two points, `base_center` is the center of the base circle and `apex` as the tip of the cone. Args: count: edge count of basis radius: radius of basis base_center: center point of base circle apex: tip of the cone Returns: :class:`~ezdxf.render.MeshTransformer` .. versionadded:: 0.11 """ # Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the MIT license. # Python port Copyright (c) 2012 Tim Knip (http://www.floorplanner.com), under the MIT license. # Additions by Alex Pletzer (Pennsylvania State University) # Adaptation for ezdxf, Copyright (c) 2020, Manfred Moitzi, MIT License. start = Vector(base_center) end = Vector(apex) slices = int(count) ray = end - start z_axis = ray.normalize() is_y = (fabs(z_axis.y) > 0.5) x_axis = Vector(float(is_y), float(not is_y), 0).cross(z_axis).normalize() y_axis = x_axis.cross(z_axis).normalize() mesh = MeshVertexMerger() def vertex(angle) -> Vector: # radial direction pointing out out = x_axis * cos(angle) + y_axis * sin(angle) return start + out * radius dt = pi * 2.0 / slices for i in range(0, slices): t0 = i * dt i1 = (i + 1) % slices t1 = i1 * dt # coordinates and associated normal pointing outwards of the cone's # side p0 = vertex(t0) p1 = vertex(t1) # polygon on the low side (disk sector) mesh.add_face([start, p0, p1]) # polygon extending from the low side to the tip mesh.add_face([p0, end, p1]) return MeshTransformer.from_builder(mesh)
def cylinder_2p( count: int = 16, radius: float = 1, base_center=(0, 0, 0), top_center=(0, 0, 1), ) -> MeshTransformer: """ Create a `cylinder <https://en.wikipedia.org/wiki/Cylinder>`_ as :class:`~ezdxf.render.MeshTransformer` object from two points, `base_center` is the center of the base circle and, `top_center` the center of the top circle. Args: count: profiles edge count radius: radius for bottom profile base_center: center of base circle top_center: center of top circle Returns: :class:`~ezdxf.render.MeshTransformer` .. versionadded:: 0.11 """ # Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the MIT license. # Python port Copyright (c) 2012 Tim Knip (http://www.floorplanner.com), under the MIT license. # Additions by Alex Pletzer (Pennsylvania State University) # Adaptation for ezdxf, Copyright (c) 2020, Manfred Moitzi, MIT License. start = Vector(base_center) end = Vector(top_center) radius = float(radius) slices = int(count) ray = end - start z_axis = ray.normalize() is_y = (fabs(z_axis.y) > 0.5) x_axis = Vector(float(is_y), float(not is_y), 0).cross(z_axis).normalize() y_axis = x_axis.cross(z_axis).normalize() mesh = MeshVertexMerger() def vertex(stack, angle): out = (x_axis * cos(angle)) + (y_axis * sin(angle)) return start + (ray * stack) + (out * radius) dt = pi * 2 / float(slices) for i in range(0, slices): t0 = i * dt i1 = (i + 1) % slices t1 = i1 * dt mesh.add_face([start, vertex(0, t0), vertex(0, t1)]) mesh.add_face( [vertex(0, t1), vertex(0, t0), vertex(1, t0), vertex(1, t1)]) mesh.add_face([end, vertex(1, t1), vertex(1, t0)]) return MeshTransformer.from_builder(mesh)
def test_render_polyface(cube_polyface): doc = ezdxf.new() msp = doc.modelspace() t = MeshTransformer.from_polyface(cube_polyface) assert len(t.vertices) == 24 # unoptimized mesh builder assert len(t.faces) == 6 t.render_polyface(msp) new_polyface = msp[-1] assert new_polyface.dxftype() == 'POLYLINE' assert new_polyface.is_poly_face_mesh is True assert len(new_polyface.vertices) == 8 + 6 assert new_polyface.vertices[0] is not cube_polyface.vertices[0]
def extrude( profile: Iterable["Vertex"], path: Iterable["Vertex"], close=True ) -> MeshTransformer: """Extrude a `profile` polygon along a `path` polyline, vertices of profile should be in counter clockwise order. Args: profile: sweeping profile as list of (x, y, z) tuples in counter clockwise order path: extrusion path as list of (x, y, z) tuples close: close profile polygon if ``True`` Returns: :class:`~ezdxf.render.MeshTransformer` """ def add_hull(bottom_profile, top_profile): prev_bottom = bottom_profile[0] prev_top = top_profile[0] for bottom, top in zip(bottom_profile[1:], top_profile[1:]): face = ( prev_bottom, bottom, top, prev_top, ) # counter clock wise: normals outwards mesh.faces.append(face) prev_bottom = bottom prev_top = top mesh = MeshVertexMerger() profile = Vec3.list(profile) if close: profile = close_polygon(profile) path = Vec3.list(path) start_point = path[0] # type: ignore bottom_indices = mesh.add_vertices(profile) # base profile for target_point in path[1:]: # type: ignore translation_vector = target_point - start_point # profile will just be translated profile = [vec + translation_vector for vec in profile] top_indices = mesh.add_vertices(profile) add_hull(bottom_indices, top_indices) bottom_indices = top_indices start_point = target_point return MeshTransformer.from_builder(mesh)
def from_profiles_linear(profiles: Iterable[Iterable['Vertex']], close=True, caps=False, ngons=True) -> MeshTransformer: """ Create MESH entity by linear connected `profiles`. Args: profiles: list of profiles close: close profile polygon if ``True`` caps: close hull with bottom cap and top cap ngons: use ngons for caps if ``True`` else subdivide caps into triangles Returns: :class:`~ezdxf.render.MeshTransformer` """ mesh = MeshVertexMerger() profiles = list(profiles) if close: profiles = [close_polygon(p) for p in profiles] if caps: base = reversed(profiles[0]) # for correct outside pointing normals top = profiles[-1] if ngons: mesh.add_face(base) mesh.add_face(top) else: for face in ngon_to_triangles(base): mesh.add_face(face) for face in ngon_to_triangles(top): mesh.add_face(face) for profile1, profile2 in zip(profiles, profiles[1:]): prev_v1, prev_v2 = None, None for v1, v2 in zip(profile1, profile2): if prev_v1 is not None: mesh.add_face([prev_v1, v1, v2, prev_v2]) prev_v1 = v1 prev_v2 = v2 return MeshTransformer.from_builder(mesh)
def cone(count: int = 16, radius: float = 1.0, apex: 'Vertex' = (0, 0, 1), caps: bool = True) -> MeshTransformer: """ Create a `cone <https://en.wikipedia.org/wiki/Cone>`_ as :class:`~ezdxf.render.MeshTransformer` object, the base center is fixed in the origin (0, 0, 0). Args: count: edge count of basis radius: radius of basis apex: tip of the cone caps: add a bottom face if ``True`` Returns: :class:`~ezdxf.render.MeshTransformer` """ mesh = MeshVertexMerger() base_circle = list(circle(count, radius, close=True)) for p1, p2 in zip(base_circle, base_circle[1:]): mesh.add_face([p1, p2, apex]) if caps: mesh.add_face(base_circle) return MeshTransformer.from_builder(mesh)
def sphere(count: int = 16, stacks: int = 8, radius: float = 1, quads=True) -> MeshTransformer: """ Create a `sphere <https://en.wikipedia.org/wiki/Sphere>`_ as :class:`~ezdxf.render.MeshTransformer` object, center is fixed at origin (0, 0, 0). Args: count: longitudinal slices stacks: latitude slices radius: radius of sphere quads: use quads for body faces if ``True`` else triangles Returns: :class:`~ezdxf.render.MeshTransformer` .. versionadded:: 0.11 """ radius = float(radius) slices = int(count) stacks_2 = int(stacks) // 2 # stacks from -stack/2 to +stack/2 delta_theta = pi * 2.0 / float(slices) delta_phi = pi / float(stacks) mesh = MeshVertexMerger() def radius_of_stack(stack: float) -> float: return radius * cos(delta_phi * stack) def vertex(slice_: float, r: float, z: float) -> Vector: actual_theta = delta_theta * slice_ return Vector(cos(actual_theta) * r, sin(actual_theta) * r, z) def cap_triangles(stack, top=False): z = sin(stack * delta_phi) * radius cap_vertex = Vector(0, 0, radius) if top else Vector(0, 0, -radius) r1 = radius_of_stack(stack) for slice_ in range(slices): v1 = vertex(slice_, r1, z) v2 = vertex(slice_ + 1, r1, z) if top: mesh.add_face((v1, v2, cap_vertex)) else: mesh.add_face((cap_vertex, v2, v1)) # bottom triangle faces cap_triangles(-stacks_2 + 1, top=False) # add body faces for actual_stack in range(-stacks_2 + 1, stacks_2 - 1): next_stack = actual_stack + 1 r1 = radius_of_stack(actual_stack) r2 = radius_of_stack(next_stack) z1 = sin(delta_phi * actual_stack) * radius z2 = sin(delta_phi * next_stack) * radius for i in range(slices): v1 = vertex(i, r1, z1) v2 = vertex(i + 1, r1, z1) v3 = vertex(i + 1, r2, z2) v4 = vertex(i, r2, z2) if quads: mesh.add_face([v1, v2, v3, v4]) else: center = vertex( i + 0.5, radius_of_stack(actual_stack + 0.5), sin(delta_phi * (actual_stack + 0.5)) * radius, ) mesh.add_face([v1, v2, center]) mesh.add_face([v2, v3, center]) mesh.add_face([v3, v4, center]) mesh.add_face([v4, v1, center]) # top triangle faces cap_triangles(stacks_2 - 1, top=True) return MeshTransformer.from_builder(mesh)