Skip to content

Commit d70313b

Browse files
CHANGE add_modifier takes type and **kwargs.
1 parent 6c350c0 commit d70313b

File tree

12 files changed

+285
-17
lines changed

12 files changed

+285
-17
lines changed

data/frame.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Model::Line::Segments": [{"dtype": "compas.geometry/Line", "data": {"start": [-3000.0, -3000.0, 0.0], "end": [-3000.0, -3000.0, 3800.0]}, "guid": "2fc7fca3-cf83-44a8-9338-31917268590b"}, {"dtype": "compas.geometry/Line", "data": {"start": [-3000.0, 3000.0, 0.0], "end": [-3000.0, 3000.0, 3800.0]}, "guid": "aa520fb7-ff92-43da-b8c1-6a77754650f1"}, {"dtype": "compas.geometry/Line", "data": {"start": [3000.0, 3000.0, 0.0], "end": [3000.0, 3000.0, 3800.0]}, "guid": "29a4ef5b-edf0-4bd6-95f5-20b864c49908"}, {"dtype": "compas.geometry/Line", "data": {"start": [3000.0, -3000.0, 0.0], "end": [3000.0, -3000.0, 3800.0]}, "guid": "2263ec71-b010-499c-9614-3424307e6602"}, {"dtype": "compas.geometry/Line", "data": {"start": [-3000.0, -3000.0, 3800.0], "end": [-3000.0, 3000.0, 3800.0]}, "guid": "0ecd6ac6-1853-485b-8b50-3f07c7ec207d"}, {"dtype": "compas.geometry/Line", "data": {"start": [3000.0, 3000.0, 3800.0], "end": [3000.0, -3000.0, 3800.0]}, "guid": "008c3470-f42b-4184-a2d3-6c0c5416f901"}, {"dtype": "compas.geometry/Line", "data": {"start": [-3000.0, 3000.0, 3800.0], "end": [3000.0, 3000.0, 3800.0]}, "guid": "94af457f-3386-4fb7-9f8f-24d05eed86d2"}, {"dtype": "compas.geometry/Line", "data": {"start": [3000.0, -3000.0, 3800.0], "end": [-3000.0, -3000.0, 3800.0]}, "guid": "7c7014f7-d93c-4b17-a2d8-f8ff41f95a80"}], "Model::Mesh::Floor": [{"dtype": "compas.datastructures/Mesh", "data": {"attributes": {}, "default_vertex_attributes": {"x": 0.0, "y": 0.0, "z": 0.0}, "default_edge_attributes": {}, "default_face_attributes": {}, "vertex": {"0": {"x": -3000.0, "y": -3000.0, "z": 3800.0}, "1": {"x": -3000.0, "y": 3000.0, "z": 3800.0}, "2": {"x": 3000.0, "y": 3000.0, "z": 3800.0}, "3": {"x": 3000.0, "y": -3000.0, "z": 3800.0}}, "face": {"0": [0, 1, 2, 3]}, "facedata": {"0": {}}, "edgedata": {}, "max_vertex": 3, "max_face": 0}, "guid": "82f752d5-cf71-41e6-9aa0-742e41eddc14"}]}

docs/examples/elements/plate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
]
1313
polygon : Polygon = Polygon(points)
1414
plate: PlateElement = PlateElement(polygon=polygon, thickness=0.2)
15-
15+
plate.copy()
1616

1717
# Vizualize.
1818
viewer = Viewer()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from compas.geometry import Rotation
2+
from compas.geometry import Translation
3+
from compas_model.elements import BeamElement
4+
from compas_model.interactions import BooleanModifier
5+
from compas_model.models import Model
6+
from compas_viewer import Viewer
7+
8+
# Create an element.
9+
beam0: BeamElement = BeamElement(0.2, 0.3, 3, name="beam0")
10+
beam1 : BeamElement = beam0.copy()
11+
beam1.name = "beam1"
12+
13+
# Element transformation can be set or modified as an attribute.
14+
beam0.transformation = Rotation.from_axis_and_angle([0, 1, 0], 3.14/2, beam0.axis.midpoint)
15+
beam0.transformation = Translation.from_vector([0, 0, 1.5])* beam0.transformation # Rotate then translate
16+
17+
# But the transformation is applied when beam.modelgeometry is computed.
18+
model = Model()
19+
model.add_element(beam0)
20+
model.add_element(beam1)
21+
22+
model.add_interaction(beam1, beam0)
23+
model.add_modifier(beam1, beam0, BooleanModifier)
24+
25+
# Vizualize.
26+
viewer = Viewer()
27+
for element in model.elements():
28+
viewer.scene.add(element.modelgeometry, hide_coplanaredges=True)
29+
viewer.show()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from compas.geometry import Rotation
2+
from compas.geometry import Translation
3+
from compas_model.elements import BeamElement
4+
from compas_model.interactions import SlicerModifier
5+
from compas_model.models import Model
6+
from compas_viewer import Viewer
7+
8+
# Create an element.
9+
beam0: BeamElement = BeamElement(0.2, 0.3, 3, name="beam0")
10+
beam1 : BeamElement = beam0.copy()
11+
beam1.name = "beam1"
12+
13+
# Element transformation can be set or modified as an attribute.
14+
beam0.transformation = Rotation.from_axis_and_angle([0, 1, 0], 3.14/2, beam0.axis.midpoint)
15+
beam0.transformation = Translation.from_vector([0, 0, 1.5])* beam0.transformation # Rotate then translate
16+
17+
# But the transformation is applied when beam.modelgeometry is computed.
18+
model = Model()
19+
model.add_element(beam0)
20+
model.add_element(beam1)
21+
22+
model.add_interaction(beam0, beam1)
23+
model.add_modifier(beam0, beam1, SlicerModifier)
24+
25+
# Vizualize.
26+
viewer = Viewer()
27+
for element in model.elements():
28+
viewer.scene.add(element.modelgeometry, hide_coplanaredges=True)
29+
viewer.show()
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from pathlib import Path
2+
3+
from compas import json_dump
4+
from compas import json_load
5+
from compas.datastructures import Mesh
6+
from compas.geometry import Frame
7+
from compas.geometry import Line
8+
from compas.geometry import Point
9+
from compas.geometry import Translation
10+
from compas.geometry import Vector
11+
from compas.geometry.transformation import Transformation
12+
from compas.tolerance import TOL
13+
from compas_model.elements import BeamElement
14+
from compas_model.elements import ColumnElement
15+
from compas_model.interactions import BooleanModifier
16+
from compas_model.models import Model
17+
from compas_viewer import Viewer
18+
from compas_viewer.config import Config
19+
20+
# =============================================================================
21+
# Create Geometry
22+
# =============================================================================
23+
points = [
24+
Point(-3000, -3000, 0),
25+
Point(-3000, 3000, 0),
26+
Point(3000, 3000, 0),
27+
Point(3000, -3000, 0),
28+
Point(-3000, -3000, 3800),
29+
Point(-3000, 3000, 3800),
30+
Point(3000, 3000, 3800),
31+
Point(3000, -3000, 3800),
32+
]
33+
34+
lines = [
35+
Line(points[0], points[4]),
36+
Line(points[1], points[5]),
37+
Line(points[2], points[6]),
38+
Line(points[3], points[7]),
39+
Line(points[4], points[5]),
40+
Line(points[6], points[7]),
41+
Line(points[5], points[6]),
42+
Line(points[7], points[4]),
43+
]
44+
45+
mesh = Mesh.from_vertices_and_faces(points[4:], [[0, 1, 2, 3]])
46+
47+
# =============================================================================
48+
# Serialize Geometry into a JSON file
49+
# =============================================================================
50+
model_input = {"Model::Line::Segments": lines, "Model::Mesh::Floor": [mesh]}
51+
json_dump(model_input, Path("data/frame.json"))
52+
53+
54+
# =============================================================================
55+
# Load Geometry from JSON and Create Model
56+
# =============================================================================
57+
rhino_geometry = json_load(Path("data/frame.json"))
58+
lines = rhino_geometry["Model::Line::Segments"]
59+
60+
model = Model()
61+
62+
# Add columns
63+
columns = []
64+
for i in range(4):
65+
column = ColumnElement(300, 300, lines[i].length)
66+
column.transformation = Transformation.from_frame_to_frame(Frame.worldXY(), Frame(lines[i].start))
67+
model.add_element(column)
68+
columns.append(column)
69+
70+
# Add beams
71+
beams = []
72+
for i in range(4, len(lines)):
73+
beam = BeamElement(width=300, depth=700, length=lines[i].length)
74+
target_frame = Frame(
75+
lines[i].start, Vector.Zaxis().cross(lines[i].vector), Vector.Zaxis()
76+
)
77+
beam.transformation = (
78+
Transformation.from_frame_to_frame(Frame.worldXY(), target_frame)
79+
* Translation.from_vector([0, beam.depth * -0.5, 0])
80+
)
81+
beam.extend(150)
82+
model.add_element(beam)
83+
beams.append(beam)
84+
85+
# Add interactions and modifiers
86+
for column in columns:
87+
for beam in beams:
88+
model.add_interaction(column, beam)
89+
model.add_modifier(column, beam) # column -> cuts -> beam
90+
91+
# =============================================================================
92+
# Visualize Final Model
93+
# =============================================================================
94+
TOL.lineardeflection = 100
95+
96+
config = Config()
97+
config.camera.target = [0, 0, 100]
98+
config.camera.position = [10000, -10000, 10000]
99+
config.camera.near = 10
100+
config.camera.far = 100000
101+
102+
viewer = Viewer(config=config)
103+
for element in model.elements():
104+
viewer.scene.add(element.modelgeometry, hide_coplanaredges=True)
105+
viewer.show()

src/compas_model/elements/beam.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
from compas.geometry import bounding_box
1313
from compas.geometry import identity_matrix
1414
from compas.geometry import intersection_line_plane
15+
from compas.geometry import is_point_in_polygon_xy
1516
from compas.geometry import oriented_bounding_box
1617
from compas_model.elements.element import Element
1718
from compas_model.elements.element import Feature
19+
from compas_model.interactions import BooleanModifier
20+
from compas_model.interactions import Modifier
21+
from compas_model.interactions import SlicerModifier
1822

1923

2024
class BeamFeature(Feature):
@@ -99,7 +103,7 @@ def __init__(
99103
self.depth: float = depth
100104
self._length: float = length
101105

102-
self.points: list[list[float]] = [[-width * 1, -depth * 0.5, 0], [-width * 1, depth * 0.5, 0], [width * 0, depth * 0.5, 0], [width * 0, -depth * 0.5, 0]]
106+
self.points: list[list[float]] = [[-width * 0.5, -depth * 0.5, 0], [-width * 0.5, depth * 0.5, 0], [width * 0.5, depth * 0.5, 0], [width * 0.5, -depth * 0.5, 0]]
103107

104108
self.section: Polygon = Polygon(self.points)
105109
self.axis: Line = Line([0, 0, 0], [0, 0, length])
@@ -117,7 +121,7 @@ def compute_elementgeometry(self) -> Mesh:
117121
box: Box = Box.from_width_height_depth(self.width, self.length, self.depth)
118122
box.translate(
119123
[
120-
self.width * -0.5,
124+
0,
121125
0,
122126
self.length * 0.5,
123127
]
@@ -239,3 +243,43 @@ def compute_top_and_bottom_polygons(self) -> tuple[Polygon, Polygon]:
239243
points0.append(result0)
240244
points1.append(result1)
241245
return Polygon(points0), Polygon(points1)
246+
247+
def _add_modifier_with_beam(self, target_element: "BeamElement", modifier_type: type[Modifier] = None, **kwargs) -> Modifier:
248+
if not modifier_type:
249+
raise ValueError("Modifier type is not defined, please define a modifier type e.g. SlicerModfier.")
250+
251+
if isinstance(modifier_type, type) and issubclass(modifier_type, BooleanModifier):
252+
return BooleanModifier(self.elementgeometry.transformed(self.modeltransformation))
253+
254+
if isinstance(modifier_type, type) and issubclass(modifier_type, SlicerModifier):
255+
return self._create_slicer_modifier(target_element)
256+
257+
raise ValueError(f"Unknown modifier type: {modifier_type}")
258+
259+
def _create_slicer_modifier(self, target_element: "BeamElement") -> Modifier:
260+
mesh = self.elementgeometry.transformed(self.modeltransformation)
261+
axis = target_element.axis.transformed(target_element.modeltransformation)
262+
263+
p0 = axis.start
264+
p1 = axis.end
265+
266+
closest_distance_to_end_point = float("inf")
267+
closest_face = 0
268+
for face in self.elementgeometry.faces():
269+
polygon = mesh.face_polygon(face)
270+
frame = polygon.frame
271+
result = intersection_line_plane(axis, Plane.from_frame(frame))
272+
if result:
273+
point = Point(*result)
274+
xform = Transformation.from_frame_to_frame(frame, Frame.worldXY())
275+
point = point.transformed(xform)
276+
polygon = polygon.transformed(xform)
277+
if is_point_in_polygon_xy(point, polygon):
278+
d = max(p0.distance_to_point(point), p1.distance_to_point(point))
279+
if d < closest_distance_to_end_point:
280+
closest_distance_to_end_point = d
281+
closest_face = face
282+
283+
plane = Plane.from_frame(mesh.face_polygon(closest_face).frame)
284+
plane = Plane(plane.point, -plane.normal)
285+
return SlicerModifier(plane)

src/compas_model/elements/column.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
from compas.geometry import intersection_line_plane
1414
from compas.geometry import oriented_bounding_box
1515
from compas.itertools import pairwise
16-
from compas_model.elements.element import Element
16+
from compas_model.elements import BeamElement
17+
from compas_model.elements import Element
1718
from compas_model.elements.element import Feature
19+
from compas_model.interactions import BooleanModifier
20+
from compas_model.interactions import Modifier
1821

1922

2023
class ColumnFeature(Feature):
@@ -223,3 +226,6 @@ def compute_point(self) -> Point:
223226

224227
xform: Transformation = identity_matrix if self.modeltransformation is None else self.modeltransformation
225228
return self.frame.point.transformed(xform)
229+
230+
def _add_modifier_with_beam(self, target_element: "BeamElement", modifier_type: type[Modifier] = None, **kwargs) -> Modifier:
231+
return BooleanModifier(self.elementgeometry.transformed(self.modeltransformation))

src/compas_model/elements/element.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from compas_model.materials import Material
2121

2222
if TYPE_CHECKING:
23+
from compas_model.elements import Element
2324
from compas_model.models import ElementNode
2425
from compas_model.models import Model
2526

@@ -477,3 +478,38 @@ def contacts(self, other: "Element", tolerance: float = 1e-6, minimum_area: floa
477478
tolerance=tolerance,
478479
minimum_area=minimum_area,
479480
)
481+
482+
def add_modifier(self, target_element: "Element", modifier_type: type[Modifier] = None, **kwargs) -> Modifier:
483+
"""Computes the contact interaction of the geometry of the elements that is used in the model's add_contact method.
484+
485+
Parameters
486+
----------
487+
target_element : Element
488+
The target element to compute the contact interaction.
489+
modifier_type : type[Modifier] | None
490+
The type of Modifier to be used. If not provided, the default modifier will be used.
491+
kwargs : dict
492+
The keyword arguments to be passed to the contact interaction.
493+
494+
Returns
495+
-------
496+
Modifier
497+
The ContactInteraction that is applied to the neighboring element. One pair can have one or multiple variants.
498+
499+
Raises
500+
------
501+
ValueError
502+
If the target element type is not supported.
503+
"""
504+
# Traverse up to the class one before the Element class
505+
parent_class = target_element.__class__
506+
while parent_class.__bases__[0].__name__ != "Element":
507+
parent_class = parent_class.__bases__[0]
508+
509+
parent_class_name = parent_class.__name__.lower().replace("element", "")
510+
method_name = f"_add_modifier_with_{parent_class_name}"
511+
method = getattr(self, method_name, None)
512+
513+
if method is None:
514+
raise ValueError(f"Unsupported target element type: {type(target_element)}")
515+
return method(target_element, modifier_type, **kwargs)

src/compas_model/elements/plate.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,32 @@ class PlateElement(Element):
3131
The base polygon of the plate.
3232
thickness : float
3333
The total offset thickness above and blow the polygon
34+
is_support : bool
35+
Flag indicating that the block is a support.
36+
transformation : :class:`compas.geometry.Transformation`, optional
37+
The transformation of the block.
38+
features : list[:class:`PlateFeature`], optional
39+
The features of the block.
3440
name : str, optional
35-
The name of the element.
36-
shape : :class:`compas.datastructures.Mesh`, optional
37-
The base shape of the element.
41+
The name of the block.
3842
3943
Attributes
4044
----------
41-
shape : :class:`compas.datastructure.Mesh`
42-
The base shape of the block.
45+
bottom : :class:`compas.geometry.Polygon`
46+
The base polygon of the plate.
47+
top : :class:`compas.geometry.Polygon`
48+
The top polygon of the plate.
49+
thickness : float
50+
The total offset thickness above and blow the polygon
4351
is_support : bool
4452
Flag indicating that the block is a support.
53+
transformation : :class:`compas.geometry.Transformation`
54+
The transformation of the block.
55+
features : list[:class:`PlateFeature`]
56+
The features of the Plate.
57+
name : str
58+
The name of the block.
59+
4560
4661
"""
4762

src/compas_model/interactions/modifiers/boolean_modifier.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ def apply(self, target: Union[Brep, Mesh]):
4646
if isinstance(target, Brep) and isinstance(self.source, Brep):
4747
try:
4848
return Brep.from_boolean_difference(target, self.source)
49-
except Exception:
49+
except Exception as e:
5050
print("Boolean difference is not successful.")
51+
print(str(e))
5152
return target
5253
else:
5354
from compas_cgal.booleans import boolean_difference_mesh_mesh
5455

5556
mesh0: Mesh = target.copy() if not isinstance(target, Brep) else Mesh.from_polygons(target.to_polygons())
5657
mesh1: Mesh = self.source.copy() if not isinstance(self.source, Brep) else Mesh.from_polygons(self.source.to_polygons())
57-
print(mesh0)
58-
print(mesh1)
58+
5959
A = mesh0.to_vertices_and_faces(triangulated=True)
6060
B = mesh1.to_vertices_and_faces(triangulated=True)
6161

0 commit comments

Comments
 (0)