Skip to content

Commit a98cae9

Browse files
committed
update modifier system
1 parent ed3f4f8 commit a98cae9

File tree

7 files changed

+138
-104
lines changed

7 files changed

+138
-104
lines changed

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
* Added `compas_model.models.Model.contacts` iterator.
1616
* Added `brep_brep_contacts` for calculation of contacts between elements with brep geometry.
1717
* Added `compas_model.datastructures.BVHNode.add` to take care of `depth` value.
18-
* Added `compas_model.elements.Element.modifiers`.
19-
* Added `compas_model.elements.Element.add_modifier`.
2018
* Added `compas_model.elements.Element.apply_modifiers`.
2119
* Added `compas_model.elements.Element.material` setter.
2220
* Added lazy-computed `compas_model.elements.Element.femesh2` property.
@@ -25,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2523
* Added `compas_model.elements.Element.compute_femesh3` property.
2624
* Added `compas_model.modifiers`.
2725
* Added `compas_model.modifiers.Modifier`.
28-
* Added `compas_model.modifiers.TrimModifier`.
26+
* Added `compas_model.models.Model.add_modifier`.
2927

3028
### Changed
3129

@@ -50,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5048
* Changed `compas_model.model.Model.elements` to property.
5149
* Changed `compas_model.model.Model.materials` to property.
5250
* Changed `compas_model.model.Model.contacts` to property.
51+
* Changed `compas_model.model.Element.compute_modelgeometry` to use new modifier implementation.
5352

5453
### Removed
5554

docs/api/compas_model.modifiers.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@ Classes
1212
:nosignatures:
1313

1414
Modifier
15-
TrimModifier

src/compas_model/elements/element.py

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import TYPE_CHECKING
55
from typing import Optional
66
from typing import Sequence
7+
from typing import Type
78
from typing import TypeVar
89
from typing import Union
910

@@ -117,7 +118,7 @@ def __data__(self) -> dict:
117118
return {
118119
"transformation": self.transformation,
119120
"features": self.features,
120-
"modifiers": self.modifiers,
121+
# "modifiers": self.modifiers,
121122
"name": self.name,
122123
}
123124

@@ -126,7 +127,6 @@ def __init__(
126127
geometry: Optional[Union[Brep, Mesh]] = None,
127128
transformation: Optional[Transformation] = None,
128129
features: Optional[Sequence[Feature | FeatureType]] = None,
129-
modifiers: Optional[Sequence[Modifier]] = None,
130130
name: Optional[str] = None,
131131
) -> None:
132132
super().__init__(name=name)
@@ -138,7 +138,7 @@ def __init__(
138138
self._transformation = transformation
139139
self._geometry = geometry
140140
self._features = list(features or [])
141-
self._modifiers = list(modifiers or [])
141+
# self._modifiers = []
142142
self._material = None
143143

144144
self._aabb = None
@@ -201,9 +201,9 @@ def children(self) -> list["Element"]:
201201
def features(self) -> list[Feature]:
202202
return self._features
203203

204-
@property
205-
def modifiers(self) -> list[Modifier]:
206-
return self._modifiers
204+
# @property
205+
# def modifiers(self) -> list[Type[Modifier]]:
206+
# return self._modifiers
207207

208208
@property
209209
def femesh2(self) -> Mesh:
@@ -217,20 +217,22 @@ def femesh3(self) -> Polyhedron:
217217
self._femesh3 = self.compute_femesh3()
218218
return self._femesh3
219219

220-
@property
221-
def is_dirty(self) -> bool:
222-
return self._is_dirty
220+
# @property
221+
# def is_dirty(self) -> bool:
222+
# return self._is_dirty
223223

224-
@is_dirty.setter
225-
def is_dirty(self, value: bool):
226-
self._is_dirty = value
224+
# @is_dirty.setter
225+
# def is_dirty(self, value: bool):
226+
# self._is_dirty = value
227227

228-
if value:
229-
# this is potentially expensive and wasteful
230-
# perhaps this mapping needs to be managed on the model level
231-
elements: dict[int, Element] = {element.graphnode: element for element in self.model.elements}
232-
for neighbor in self.model.graph.neighbors_out(self.graphnode):
233-
elements[neighbor].is_dirty = value
228+
# if value:
229+
# # this is potentially expensive and wasteful
230+
# # perhaps this mapping needs to be managed on the model level
231+
# elements: dict[int, Element] = {
232+
# element.graphnode: element for element in self.model.elements
233+
# }
234+
# for neighbor in self.model.graph.neighbors_out(self.graphnode):
235+
# elements[neighbor].is_dirty = value
234236

235237
# ==========================================================================
236238
# Computed attributes
@@ -343,11 +345,13 @@ def compute_modelgeometry(self) -> Union[Brep, Mesh]:
343345
xform = self.modeltransformation
344346
modelgeometry = self.elementgeometry.transformed(xform)
345347

346-
if self.modifiers:
347-
for modifier in self.modifiers:
348-
modelgeometry = modifier.apply(modelgeometry)
348+
for nbr in self.model.graph.neighbors_in(self.graphnode):
349+
modifiers: list[Modifier] = self.model.graph.edge_attribute((nbr, self.graphnode), name="modifiers") # type: ignore
350+
if modifiers:
351+
for modifier in modifiers:
352+
modelgeometry = modifier.apply(modelgeometry)
349353

350-
self.is_dirty = False
354+
# self.is_dirty = False
351355

352356
return modelgeometry
353357

@@ -456,16 +460,6 @@ def apply_features(self) -> Union[Mesh, Brep]:
456460
"""
457461
raise NotImplementedError
458462

459-
def apply_modifiers(self) -> Union[Mesh, Brep]:
460-
"""Apply the modifiers to the (base) geometry.
461-
462-
Returns
463-
-------
464-
Mesh | Brep
465-
466-
"""
467-
raise NotImplementedError
468-
469463
# ==========================================================================
470464
# Transformations
471465
# ==========================================================================
@@ -507,7 +501,7 @@ def transformed(self, transformation: Transformation) -> "Element":
507501
# ==========================================================================
508502

509503
def add_feature(self, feature: Feature) -> None:
510-
"""Add a feature to the list of features of the lement.
504+
"""Add a feature to the list of features of the element.
511505
512506
Parameters
513507
----------
@@ -521,25 +515,29 @@ def add_feature(self, feature: Feature) -> None:
521515
"""
522516
self.features.append(feature)
523517

524-
def add_modifier(self, modifier: Modifier) -> None:
525-
"""Computes the modifier to be applied to the target element.
518+
# def add_modifier(self, modifiertype: Type[Modifier]) -> None:
519+
# """Add a modifier type to the registered modifiers of the element.
526520

527-
Parameters
528-
----------
529-
modifier : :class:`Modifier`
530-
The modifier instance.
521+
# These modifiers can be applied automatically to connected target elements
522+
# that are compatible with the type of modification,
523+
# if such compatibility requirement is specified.
531524

532-
Returns
533-
-------
534-
None
525+
# Parameters
526+
# ----------
527+
# modifiertype : Type[:class:`Modifier`]
528+
# The modifier type.
535529

536-
Raises
537-
------
538-
ValueError
539-
If the target element type is not supported.
530+
# Returns
531+
# -------
532+
# None
540533

541-
"""
542-
self.modifiers.append(modifier)
534+
# Raises
535+
# ------
536+
# ValueError
537+
# If the target element type is not supported.
538+
539+
# """
540+
# self.modifiers.append(modifiertype)
543541

544542
def contacts(self, other: "Element", tolerance: float = 1e-6, minimum_area: float = 1e-2) -> list[Contact]:
545543
"""Compute the contacts between this element and another element.

src/compas_model/models/model.py

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Generator
33
from typing import Iterator
44
from typing import Optional
5+
from typing import Type
56
from typing import TypeVar
67
from typing import Union
78

@@ -12,6 +13,7 @@
1213
from compas_model.elements import Group
1314
from compas_model.interactions import Contact
1415
from compas_model.materials import Material
16+
from compas_model.modifiers import Modifier
1517

1618
from .bvh import ElementBVH
1719
from .bvh import ElementOBBNode
@@ -120,9 +122,9 @@ def __init__(self, name=None):
120122
self._tree = ElementTree()
121123
self._graph = InteractionGraph()
122124
self._graph.update_default_node_attributes(element=None)
123-
# type of contacts is list[Contacts]
125+
self._graph.update_default_edge_attributes(modifiers=None)
124126
self._graph.update_default_edge_attributes(contacts=None)
125-
# computed
127+
126128
self._bvh = None
127129
self._kdtree = None
128130

@@ -297,7 +299,7 @@ def remove_element(self, element: Element) -> None:
297299
if guid not in self._elements:
298300
raise Exception("Element not in the model.")
299301

300-
self._elements[guid].is_dirty = True
302+
# self._elements[guid].is_dirty = True
301303

302304
del self._elements[guid]
303305

@@ -457,6 +459,15 @@ def add_interaction(self, a: Element, b: Element) -> tuple[int, int]:
457459
Exception
458460
If one or both of the elements are not in the graph.
459461
462+
Notes
463+
-----
464+
In future implementations, adding an interaction should implicitly take care of adding modifiers
465+
onto the interaction edges, based on the registered modifiers of the source nodes.
466+
467+
In the current implementation, modifiers have to be added explicitly using :meth:`add_modifiers`.
468+
This method will add an interaction edge from the source of the modifier to its target if needed
469+
and store the modifier object on it.
470+
460471
"""
461472
node_a = a.graphnode
462473
node_b = b.graphnode
@@ -467,9 +478,17 @@ def add_interaction(self, a: Element, b: Element) -> tuple[int, int]:
467478
if not self.graph.has_node(node_a) or not self.graph.has_node(node_b):
468479
raise Exception("Something went wrong: the elements are not in the interaction graph.")
469480

470-
edge = self._graph.add_edge(node_a, node_b)
481+
edge = self.graph.add_edge(node_a, node_b)
471482

472-
self._elements[str(b.guid)].is_dirty = True
483+
# modifiers = []
484+
# if a.modifiers:
485+
# for modifiertype in a.modifiers:
486+
# # modifiertype.applies_to
487+
# # modifiers.append()
488+
# pass
489+
490+
# if modifiers:
491+
# self.graph.edge_attribute(edge, name="modifiers", value=modifiers)
473492

474493
return edge
475494

@@ -486,8 +505,8 @@ def remove_interaction(self, a: Element, b: Element) -> None:
486505
None
487506
488507
"""
489-
elements = list(self.elements)
490-
elements[b.graphnode].is_dirty = True
508+
# elements = list(self.elements)
509+
# elements[b.graphnode].is_dirty = True
491510

492511
edge = a.graphnode, b.graphnode
493512
if self.graph.has_edge(edge):
@@ -521,6 +540,50 @@ def has_interaction(self, a: Element, b: Element) -> bool:
521540
result = self.graph.has_edge(edge)
522541
return result
523542

543+
# =============================================================================
544+
# Modifiers (temp)
545+
# =============================================================================
546+
547+
def add_modifier(
548+
self,
549+
source: Element,
550+
target: Element,
551+
modifiertype: Type[Modifier],
552+
) -> list[Modifier]:
553+
"""Add a modifier between two elements, with one the source of the modifier and the other the target.
554+
555+
Parameters
556+
----------
557+
source : :class:`compas_model.elements.Element`
558+
The source element.
559+
target : :class:`compas_model.elements.Element`
560+
The target element.
561+
modifiertype : Type[:class:`compas_model.modifiers.Modifier`]
562+
The type of modifier.
563+
564+
Returns
565+
-------
566+
list[Modifier]
567+
All modifiers stored on the interaction edge between source and target.
568+
569+
Notes
570+
-----
571+
This element should implement the protocol specified by the modifier.
572+
The methods of the source element defined by the protocol are used to compute the tools involved in the modification.
573+
The tools are used by the modifier to apply the modification to the model geometry of the target element.
574+
575+
The modifier defines the protocol for the modification.
576+
The protocol should be implemented by the source element.
577+
The protocol methods of the source element are used to compute the modification tool.
578+
The modifier applies the modification to the target using this tool.
579+
580+
"""
581+
edge = self.add_interaction(source, target)
582+
modifiers = self.graph.edge_attribute(edge, name="modifiers") or []
583+
modifiers.append(modifiertype(source))
584+
self.graph.edge_attribute(edge, name="modifiers", value=modifiers)
585+
return modifiers
586+
524587
# =============================================================================
525588
# Compute
526589
# =============================================================================
@@ -571,6 +634,14 @@ def compute_kdtree(self) -> KDTree:
571634
def compute_contacts(self, tolerance=1e-6, minimum_area=1e-2) -> None:
572635
"""Compute the contacts between the block elements of this model.
573636
637+
Computing contacts is done independently of the edges of the interaction graph.
638+
If contacts are found between two elements with an existing edge, the contacts attribute of the edge will be replaced.
639+
If there is no pre-existing edge, one will be added.
640+
No element pairs are excluded in the search based on the existence of an edge between their nodes in the interaction graph.
641+
642+
The search is conducted entirely based on the BVH of the elements contained in the model.
643+
It is a spatial search that creates topological connections between elements based on their geometrical interaction.
644+
574645
Parameters
575646
----------
576647
tolerance : float, optional
@@ -592,6 +663,7 @@ def compute_contacts(self, tolerance=1e-6, minimum_area=1e-2) -> None:
592663
contacts = element.contacts(nbr, tolerance=tolerance, minimum_area=minimum_area)
593664
if contacts:
594665
self.graph.add_edge(u, v, contacts=contacts)
666+
595667
else:
596668
edge = (u, v) if self.graph.has_edge((u, v)) else (v, u)
597669
contacts = self.graph.edge_attribute(edge, name="contacts")
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from .modifier import Modifier
2-
from .trim import TrimModifier
32

43
__all__ = [
54
"Modifier",
6-
"TrimModifier",
75
]

src/compas_model/modifiers/modifier.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@ class Modifier(Data):
2020
def __data__(self) -> dict:
2121
return {"name": self.name}
2222

23-
def __init__(self, name: Optional[str] = None) -> None:
23+
def __init__(
24+
self,
25+
source,
26+
name: Optional[str] = None,
27+
) -> None:
2428
super().__init__(name=name)
2529

30+
self.source = source
31+
2632
def __repr__(self):
2733
return f'{self.__class__.__name__}(name="{self.name}")'
2834

29-
def apply(self, target: Union[Brep, Mesh]) -> Union[Brep, Mesh]:
35+
def apply(
36+
self,
37+
targetgeometry: Union[Brep, Mesh],
38+
) -> Union[Brep, Mesh]:
3039
"""Apply the interaction to the target geometry.
3140
3241
Parameters

0 commit comments

Comments
 (0)