|
1 | | -import compas.datastructures # noqa: F401 |
| 1 | +import numpy as np |
2 | 2 | from compas.datastructures import Mesh |
3 | 3 | from compas.geometry import Box |
| 4 | +from compas.geometry import Frame |
| 5 | +from compas.geometry import Point |
| 6 | +from compas.geometry import Polygon |
| 7 | +from compas.geometry import Vector |
4 | 8 | from compas.geometry import bounding_box |
5 | 9 | from compas.geometry import oriented_bounding_box |
6 | 10 | from compas.itertools import pairwise |
| 11 | +from numpy.typing import NDArray |
7 | 12 |
|
8 | 13 | from compas_model.elements import Element |
9 | | -from compas_model.elements import Feature |
10 | | - |
11 | | - |
12 | | -class PlateFeature(Feature): |
13 | | - pass |
14 | 14 |
|
15 | 15 |
|
16 | 16 | class PlateElement(Element): |
17 | | - """Class representing block elements. |
| 17 | + """Class representing a block element. |
18 | 18 |
|
19 | 19 | Parameters |
20 | 20 | ---------- |
21 | | - shape : :class:`compas.datastructures.Mesh` |
22 | | - The base shape of the block. |
23 | | - features : list[:class:`PlateFeature`], optional |
24 | | - Additional block features. |
25 | | - is_support : bool, optional |
26 | | - Flag indicating that the block is a support. |
| 21 | + polygon : :class:`compas.geometry.Polygon` |
| 22 | + The base polygon of the plate. |
| 23 | + thickness : float |
| 24 | + The total offset thickness above and blow the polygon |
27 | 25 | frame : :class:`compas.geometry.Frame`, optional |
28 | 26 | The coordinate frame of the block. |
29 | 27 | name : str, optional |
30 | 28 | The name of the element. |
| 29 | + shape : :class:`compas.datastructures.Mesh`, optional |
| 30 | + The base shape of the element. |
31 | 31 |
|
32 | 32 | Attributes |
33 | 33 | ---------- |
34 | 34 | shape : :class:`compas.datastructure.Mesh` |
35 | 35 | The base shape of the block. |
36 | | - features : list[:class:`PlateFeature`] |
37 | | - A list of additional block features. |
38 | 36 | is_support : bool |
39 | 37 | Flag indicating that the block is a support. |
40 | 38 |
|
41 | 39 | """ |
42 | 40 |
|
43 | 41 | @property |
44 | | - def __data__(self): |
45 | | - # type: () -> dict |
46 | | - data = super(PlateElement, self).__data__ |
47 | | - data["bottom"] = self._bottom |
48 | | - data["top"] = self._top |
49 | | - data["features"] = self.features |
| 42 | + def __data__(self) -> dict[str, any]: |
| 43 | + data: dict[str, any] = super(PlateElement, self).__data__ |
| 44 | + data["polygon"] = self.polygon |
| 45 | + data["thickness"] = self.thickness |
| 46 | + data["frame"] = self.frame |
| 47 | + data["name"] = self.name |
| 48 | + data["shape"] = self.shape |
50 | 49 | return data |
51 | 50 |
|
52 | | - def __init__(self, bottom, top, features=None, frame=None, name=None): |
53 | | - # type: (compas.geometry.Polygon, compas.geometry.Polygon, list[PlateFeature] | None, compas.geometry.Frame | None, str | None) -> None |
| 51 | + @classmethod |
| 52 | + def __from_data__(cls, data: dict[str, any]) -> "PlateElement": |
| 53 | + return cls(polygon=data["polygon"], thickness=data["thickness"], frame=data["frame"], name=data["name"], shape=data["shape"]) |
54 | 54 |
|
| 55 | + def __init__(self, polygon: Polygon, thickness: float, frame: Frame = None, name: str = None, shape=None) -> "PlateElement": |
55 | 56 | super(PlateElement, self).__init__(frame=frame, name=name) |
56 | | - self._bottom = bottom |
57 | | - self._top = top |
58 | | - self.shape = self.compute_shape() |
59 | | - self.features = features or [] # type: list[PlateFeature] |
| 57 | + self.polygon: Polygon = polygon |
| 58 | + self.thickness: float = thickness |
| 59 | + normal: Vector = polygon.normal |
| 60 | + down: Vector = normal * (0.0 * thickness) |
| 61 | + up: Vector = normal * (-1.0 * thickness) |
| 62 | + self.bottom: Polygon = polygon.copy() |
| 63 | + for point in self.bottom.points: |
| 64 | + point += down |
| 65 | + self.top: Polygon = polygon.copy() |
| 66 | + for point in self.top.points: |
| 67 | + point += up |
| 68 | + self.shape: Mesh = shape if shape else self.compute_shape() |
| 69 | + if not self.name: |
| 70 | + self.name = self.__class__.__name__ |
| 71 | + |
| 72 | + # def __init__(self, bottom: Polygon, top: Polygon, frame: Frame = None, name: str = None, shape: Mesh = None): |
| 73 | + # super(PlateElement, self).__init__(frame=frame, name=name) |
| 74 | + # self.bottom: Polygon = bottom |
| 75 | + # self.top: Polygon = top |
| 76 | + # self.shape: Mesh = shape if shape else self.compute_shape() |
| 77 | + # if not self.name: |
| 78 | + # self.name = self.__class__.__name__ |
60 | 79 |
|
61 | 80 | @property |
62 | | - def face_polygons(self): |
63 | | - # type: () -> list[compas.geometry.Polygon] |
| 81 | + def face_polygons(self) -> list[Polygon]: |
64 | 82 | return [self.geometry.face_polygon(face) for face in self.geometry.faces()] # type: ignore |
65 | 83 |
|
66 | | - def compute_shape(self): |
67 | | - # type: () -> compas.datastructures.Mesh |
68 | | - """Compute the shape of the plate from the given polygons and features. |
| 84 | + def compute_shape(self) -> Mesh: |
| 85 | + """Compute the shape of the plate from the given polygons. |
69 | 86 | This shape is relative to the frame of the element. |
70 | 87 |
|
71 | 88 | Returns |
72 | 89 | ------- |
73 | 90 | :class:`compas.datastructures.Mesh` |
74 | 91 |
|
75 | 92 | """ |
76 | | - offset = len(self._bottom) |
77 | | - vertices = self._bottom + self._top # type: ignore |
78 | | - bottom = list(range(offset)) |
79 | | - top = [i + offset for i in bottom] |
80 | | - faces = [bottom[::-1], top] |
| 93 | + offset: int = len(self.bottom) |
| 94 | + vertices: list[Point] = self.bottom.points + self.top.points # type: ignore |
| 95 | + bottom: list[int] = list(range(offset)) |
| 96 | + top: list[int] = [i + offset for i in bottom] |
| 97 | + faces: list[list[int]] = [bottom[::-1], top] |
81 | 98 | for (a, b), (c, d) in zip(pairwise(bottom + bottom[:1]), pairwise(top + top[:1])): |
82 | 99 | faces.append([a, b, d, c]) |
83 | | - mesh = Mesh.from_vertices_and_faces(vertices, faces) |
| 100 | + mesh: Mesh = Mesh.from_vertices_and_faces(vertices, faces) |
84 | 101 | return mesh |
85 | 102 |
|
86 | 103 | # ============================================================================= |
87 | 104 | # Implementations of abstract methods |
88 | 105 | # ============================================================================= |
89 | 106 |
|
90 | | - def compute_geometry(self, include_features=False): |
91 | | - geometry = self.shape |
92 | | - if include_features: |
93 | | - if self.features: |
94 | | - for feature in self.features: |
95 | | - geometry = feature.apply(geometry) |
96 | | - geometry.transform(self.worldtransformation) |
97 | | - return geometry |
98 | | - |
99 | | - def compute_aabb(self, inflate=0.0): |
100 | | - points = self.geometry.vertices_attributes("xyz") # type: ignore |
101 | | - box = Box.from_bounding_box(bounding_box(points)) |
| 107 | + def compute_aabb(self, inflate: float = 0.0) -> Box: |
| 108 | + points: list[Point] = self.geometry.vertices_attributes("xyz") |
| 109 | + box: Box = Box.from_bounding_box(bounding_box(points)) |
102 | 110 | box.xsize += inflate |
103 | 111 | box.ysize += inflate |
104 | 112 | box.zsize += inflate |
105 | 113 | return box |
106 | 114 |
|
107 | | - def compute_obb(self, inflate=0.0): |
108 | | - points = self.geometry.vertices_attributes("xyz") # type: ignore |
109 | | - box = Box.from_bounding_box(oriented_bounding_box(points)) |
| 115 | + def compute_obb(self, inflate: float = 0.0) -> Box: |
| 116 | + points: list[Point] = self.geometry.vertices_attributes("xyz") |
| 117 | + box: Box = Box.from_bounding_box(oriented_bounding_box(points)) |
110 | 118 | box.xsize += inflate |
111 | 119 | box.ysize += inflate |
112 | 120 | box.zsize += inflate |
113 | 121 | return box |
114 | 122 |
|
115 | | - def compute_collision_mesh(self): |
116 | | - # TODO: (TvM) make this a pluggable with default implementation in core and move import to top |
| 123 | + def compute_collision_mesh(self) -> Mesh: |
117 | 124 | from compas.geometry import convex_hull_numpy |
118 | 125 |
|
119 | | - points = self.geometry.vertices_attributes("xyz") # type: ignore |
120 | | - vertices, faces = convex_hull_numpy(points) |
121 | | - vertices = [points[index] for index in vertices] # type: ignore |
| 126 | + points: list[Point] = self.geometry.vertices_attributes("xyz") |
| 127 | + faces: NDArray[np.intc] = convex_hull_numpy(points) |
| 128 | + vertices: list[Point] = [points[index] for index in range(len(points))] |
122 | 129 | return Mesh.from_vertices_and_faces(vertices, faces) |
123 | 130 |
|
124 | 131 | # ============================================================================= |
125 | 132 | # Constructors |
126 | 133 | # ============================================================================= |
127 | 134 |
|
128 | | - @classmethod |
129 | | - def from_polygon_and_thickness(cls, polygon, thickness, features=None, frame=None, name=None): |
130 | | - # type: (compas.geometry.Polygon, float, list[PlateFeature] | None, compas.geometry.Frame | None, str | None) -> PlateElement |
131 | | - """Create a plate element from a polygon and a thickness. |
132 | | -
|
133 | | - Parameters |
134 | | - ---------- |
135 | | - polygon : :class:`compas.geometry.Polygon` |
136 | | - The base polygon of the plate. |
137 | | - thickness : float |
138 | | - The total offset thickness above and blow the polygon. |
139 | | -
|
140 | | - Returns |
141 | | - ------- |
142 | | - :class:`PlateElement` |
143 | | -
|
144 | | - """ |
145 | | - normal = polygon.normal |
146 | | - down = normal * (-0.5 * thickness) |
147 | | - up = normal * (+0.5 * thickness) |
148 | | - bottom = polygon.copy() |
149 | | - for point in bottom.points: |
150 | | - point += down |
151 | | - top = polygon.copy() |
152 | | - for point in top.points: |
153 | | - point += up |
154 | | - plate = cls(bottom, top) |
155 | | - return plate |
| 135 | + def rebuild(self, polygon: Polygon) -> "PlateElement": |
| 136 | + return self |
0 commit comments