Skip to content

Commit e048e24

Browse files
authored
Merge pull request #48 from BlockResearchGroup/element_is_dirty
Element is dirty
2 parents 2fa0149 + 69befea commit e048e24

File tree

5 files changed

+205
-1
lines changed

5 files changed

+205
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
* Added `Element.compute_elementgeometry()`.
1717
* Added `Element.compute_modelgeometry()` to replace `Element.compute_geometry()`.
1818
* Added `Element.compute_modeltransformation()` to replace `Element.compute_worldtransformation()`.
19+
* Added `Element.is_dirty`.
1920

2021
### Changed
2122

src/compas_model/elements/element.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class Element(Data):
9292
Scaling factor to inflate the AABB with.
9393
inflate_obb : float
9494
Scaling factor to inflate the OBB with.
95+
is_dirty : bool
96+
Flag to indicate that modelgeometry has to be recomputed.
9597
9698
"""
9799

@@ -140,6 +142,8 @@ def __init__(
140142
self.inflate_aabb = 0.0
141143
self.inflate_obb = 0.0
142144

145+
self._is_dirty = True
146+
143147
# this is not entirely correct
144148
def __repr__(self) -> str:
145149
return f"Element(frame={self.frame!r}, name={self.name})"
@@ -177,6 +181,19 @@ def parent(self) -> "ElementNode":
177181
def features(self) -> list[Feature]:
178182
return self._features
179183

184+
@property
185+
def is_dirty(self):
186+
return self._is_dirty
187+
188+
@is_dirty.setter
189+
def is_dirty(self, value):
190+
self._is_dirty = value
191+
192+
if value:
193+
elements = list(self.model.elements())
194+
for neighbor in self.model.graph.neighbors_out(self.graphnode):
195+
elements[neighbor].is_dirty = value
196+
180197
# ==========================================================================
181198
# Computed attributes
182199
# ==========================================================================
@@ -290,7 +307,18 @@ def compute_modelgeometry(self) -> Union[Brep, Mesh]:
290307
:class:`compas.datastructures.Mesh` | :class:`compas.geometry.Brep`
291308
292309
"""
293-
raise NotImplementedError
310+
graph = self.model.graph
311+
elements = list(self.model.elements()) # noqa: F841
312+
xform = self.modeltransformation
313+
modelgeometry = self.elementgeometry.transformed(xform)
314+
315+
for neighbor in graph.neighbors_in(self.graphnode):
316+
for interaction in graph.edge_interactions((neighbor, self.graphnode)):
317+
pass # TODO: apply interaction
318+
319+
self.is_dirty = False
320+
321+
return modelgeometry
294322

295323
def compute_aabb(self) -> Box:
296324
"""Computes the Axis Aligned Bounding Box (AABB) of the geometry of the element.

src/compas_model/models/model.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,8 @@ def add_interaction(self, a: Element, b: Element, interaction: Optional[Interact
379379
interactions.append(interaction)
380380
self.graph.edge_attribute(edge, name="interactions", value=interactions)
381381

382+
self._guid_element[str(b.guid)].is_dirty = True
383+
382384
return edge
383385

384386
def remove_element(self, element: Element) -> None:
@@ -397,6 +399,9 @@ def remove_element(self, element: Element) -> None:
397399
guid = str(element.guid)
398400
if guid not in self._guid_element:
399401
raise Exception("Element not in the model.")
402+
403+
self._guid_element[guid].is_dirty = True
404+
400405
del self._guid_element[guid]
401406

402407
self.graph.delete_node(element.graphnode)
@@ -419,6 +424,9 @@ def remove_interaction(self, a: Element, b: Element, interaction: Optional[Inter
419424
if interaction:
420425
raise NotImplementedError
421426

427+
elements = list(self.elements())
428+
elements[b.graphnode].is_dirty = True
429+
422430
edge = a.graphnode, b.graphnode
423431
if self.graph.has_edge(edge):
424432
self.graph.delete_edge(edge)

src/compas_model/viewers/blockmodelviewer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
from compas_model.elements import BlockGeometry
77
from compas_model.interactions import ContactInterface
88
from compas_model.models import Model
9+
from compas_viewer import Viewer
10+
from compas_viewer.components import Button
11+
from compas_viewer.components.slider import Slider
12+
from compas_viewer.scene import GroupObject
913

1014
try:
1115
from compas_viewer import Viewer

tests/test_element.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
from compas_model.models import Model
2+
from compas_model.elements import Element
3+
from compas_model.interactions import Interaction
4+
from compas.datastructures import Mesh
5+
from typing import Optional
6+
7+
from compas.geometry import Frame
8+
from compas.geometry import Box
9+
from compas.geometry import Transformation
10+
11+
12+
class MyElement(Element):
13+
"""Class representing an element for testing."""
14+
15+
def __init__(
16+
self,
17+
size: float = 0.1,
18+
frame: Frame = Frame.worldXY(),
19+
transformation: Optional[Transformation] = None,
20+
name: Optional[str] = None,
21+
) -> "MyElement":
22+
super().__init__(frame=frame, transformation=transformation, name=name)
23+
24+
self.size: float = size
25+
26+
def compute_elementgeometry(self) -> Mesh:
27+
"""Element geometry in the local frame.
28+
29+
Returns
30+
-------
31+
:class:`compas.datastructures.Mesh`
32+
33+
"""
34+
35+
return Box(self.size, self.size, self.size, self.frame).to_mesh()
36+
37+
38+
def test_is_dirty_setter():
39+
model = Model()
40+
a = MyElement(name="a")
41+
b = MyElement(name="b")
42+
c = MyElement(name="c")
43+
d = MyElement(name="d")
44+
model.add_element(a)
45+
model.add_element(b)
46+
model.add_element(c)
47+
model.add_element(d)
48+
model.add_interaction(a, c, interaction=Interaction(name="i_a_c")) # a affects c
49+
model.add_interaction(a, b, interaction=Interaction(name="i_b_c")) # a affects b
50+
a.is_dirty = False
51+
b.is_dirty = False
52+
c.is_dirty = False
53+
d.is_dirty = False
54+
55+
elements = list(model.elements())
56+
57+
assert not elements[0].is_dirty
58+
assert not elements[1].is_dirty
59+
assert not elements[2].is_dirty
60+
assert not elements[3].is_dirty
61+
62+
elements[0].is_dirty = True
63+
64+
assert elements[0].is_dirty
65+
assert elements[1].is_dirty
66+
assert elements[2].is_dirty
67+
assert not elements[3].is_dirty
68+
69+
70+
def test_is_dirty_add_interaction():
71+
model = Model()
72+
a = MyElement(name="a")
73+
b = MyElement(name="b")
74+
c = MyElement(name="c")
75+
d = MyElement(name="d")
76+
model.add_element(a)
77+
model.add_element(b)
78+
model.add_element(c)
79+
model.add_element(d)
80+
81+
model.add_interaction(a, b, interaction=Interaction(name="i_a_b")) # c affects a
82+
for element in model.elements():
83+
element.modelgeometry # All elements is_dirty is set to False
84+
model.add_interaction(a, c, interaction=Interaction(name="i_a_c")) # c affects b
85+
86+
elements = list(model.elements())
87+
assert not elements[0].is_dirty
88+
assert not elements[1].is_dirty
89+
assert elements[2].is_dirty
90+
assert not elements[3].is_dirty
91+
92+
93+
def test_is_dirty_remove_interaction():
94+
model = Model()
95+
a = MyElement(name="a")
96+
b = MyElement(name="b")
97+
c = MyElement(name="c")
98+
d = MyElement(name="d")
99+
model.add_element(a)
100+
model.add_element(b)
101+
model.add_element(c)
102+
model.add_element(d)
103+
model.add_interaction(a, b, interaction=Interaction(name="i_a_b")) # a affects b
104+
model.add_interaction(a, c, interaction=Interaction(name="i_a_c")) # a affects c
105+
106+
for element in model.elements():
107+
element.is_dirty = False
108+
model.remove_interaction(a, b) # a affects b
109+
model.remove_interaction(a, c) # a affects c
110+
111+
elements = list(model.elements())
112+
assert not elements[0].is_dirty
113+
assert elements[1].is_dirty
114+
assert elements[2].is_dirty
115+
assert not elements[3].is_dirty
116+
117+
118+
def test_is_dirty_remove_element_0():
119+
model = Model()
120+
a = MyElement(name="a")
121+
b = MyElement(name="b")
122+
c = MyElement(name="c")
123+
d = MyElement(name="d")
124+
model.add_element(a)
125+
model.add_element(b)
126+
model.add_element(c)
127+
model.add_element(d)
128+
model.add_interaction(a, b, interaction=Interaction(name="i_a_b")) # a affects b
129+
model.add_interaction(a, c, interaction=Interaction(name="i_a_c")) # a affects c
130+
131+
for element in model.elements():
132+
element.modelgeometry # All element is_dirty is set to False
133+
134+
model.remove_element(a) # b and c is_dirty is set to True
135+
136+
elements = list(model.elements())
137+
assert elements[0].is_dirty
138+
assert elements[1].is_dirty
139+
assert not elements[2].is_dirty
140+
141+
142+
def test_is_dirty_remove_element_1():
143+
model = Model()
144+
a = MyElement(name="a")
145+
b = MyElement(name="b")
146+
c = MyElement(name="c")
147+
d = MyElement(name="d")
148+
model.add_element(a)
149+
model.add_element(b)
150+
model.add_element(c)
151+
model.add_element(d)
152+
model.add_interaction(a, b, interaction=Interaction(name="i_a_b")) # a affects b
153+
model.add_interaction(a, c, interaction=Interaction(name="i_a_c")) # a affects c
154+
155+
for element in model.elements():
156+
element.modelgeometry # All element is_dirty is set to False
157+
158+
model.remove_element(b) # b and c is_dirty is set to True
159+
160+
elements = list(model.elements())
161+
assert not elements[0].is_dirty
162+
assert not elements[1].is_dirty
163+
assert not elements[2].is_dirty

0 commit comments

Comments
 (0)