Skip to content

Commit 0811f18

Browse files
Feat: Add implicit complement and default material properties
1 parent 418e576 commit 0811f18

2 files changed

Lines changed: 148 additions & 2 deletions

File tree

src/pydagmc/dagnav.py

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,83 @@ def groups_by_name(self) -> Dict[str, Group]:
170170
def group_names(self) -> list[str]:
171171
return self.groups_by_name.keys()
172172

173+
@property
174+
def implicit_complement(self) -> Optional[Volume]:
175+
"""
176+
Returns the implicit complement volume.
177+
178+
The implicit complement volume is identified as the volume
179+
that has the material 'Graveyard'.
180+
"""
181+
for volume in self.volumes:
182+
if volume.material == 'Graveyard':
183+
return volume
184+
return None
185+
186+
@property
187+
def implicit_complement_material(self) -> Optional[str]:
188+
"""
189+
The material of the implicit complement.
190+
"""
191+
# Find group with name like 'mat:*_comp'
192+
for group in self.groups:
193+
if group.name and group.name.startswith('mat:') and group.name.endswith('_comp'):
194+
return group.name.removeprefix('mat:').removesuffix('_comp')
195+
return None
196+
197+
@implicit_complement_material.setter
198+
def implicit_complement_material(self, material_name: Optional[str]):
199+
"""
200+
Set the material of the implicit complement.
201+
"""
202+
# First, find and unset any existing implicit complement material group
203+
for group in self.groups:
204+
if group.name and group.name.startswith('mat:') and group.name.endswith('_comp'):
205+
group.name = group.name.removesuffix('_comp')
206+
break
207+
208+
if material_name is not None:
209+
# Find the group for the new material
210+
material_group_name = f"mat:{material_name}"
211+
if material_group_name in self.groups_by_name:
212+
group = self.groups_by_name[material_group_name]
213+
group.name = f"{group.name}_comp"
214+
else:
215+
# If the group does not exist, we need to find the graveyard and assign it.
216+
graveyard = self.implicit_complement
217+
if graveyard is None:
218+
raise ValueError("Could not identify the implicit complement volume.")
219+
graveyard.material = material_name
220+
graveyard.material_group.name = f"{graveyard.material_group.name}_comp"
221+
222+
@property
223+
def default_material(self) -> Optional[str]:
224+
"""
225+
The default material for volumes without a material.
226+
"""
227+
for group in self.groups:
228+
if group.name and group.name.startswith('pydagmc:default_material:'):
229+
return group.name.split(':')[-1]
230+
return None
231+
232+
@default_material.setter
233+
def default_material(self, material_name: Optional[str]):
234+
"""
235+
Set the default material for volumes without a material.
236+
"""
237+
# First, remove any existing default material marker
238+
for group in self.groups:
239+
if group.name and group.name.startswith('pydagmc:default_material:'):
240+
group.delete()
241+
break
242+
243+
if material_name is not None:
244+
# Assign the material to all volumes without one.
245+
for volume in self.volumes_without_material:
246+
volume.material = material_name
247+
# Create a marker group to store the default material name
248+
self.create_group(name=f'pydagmc:default_material:{material_name}')
249+
173250
def __repr__(self):
174251
return f'{type(self).__name__}: {len(self.volumes)} Volumes, {len(self.surfaces)} Surfaces, {len(self.groups)} Groups'
175252

@@ -670,11 +747,17 @@ def material_group(self) -> Optional[Group]:
670747
@property
671748
def material(self) -> Optional[str]:
672749
"""Name of the material assigned to this volume."""
673-
return self._metadata_group_name(self._material_prefix)
750+
name = self._metadata_group_name(self._material_prefix)
751+
if name:
752+
return name.removesuffix('_comp')
753+
return None
674754

675755
@material.setter
676756
def material(self, name: str):
757+
is_comp = self.material_group and self.material_group.name.endswith('_comp')
677758
self._set_metadata_group(self._material_prefix, name)
759+
if is_comp:
760+
self.material_group.name += '_comp'
678761

679762
@property
680763
def surfaces(self) -> list[Surface]:
@@ -732,11 +815,13 @@ def name(self) -> Optional[str]:
732815

733816
@name.setter
734817
def name(self, val: str):
735-
if val.lower() in self.model.group_names:
818+
current_name = self.name
819+
if val.lower() in self.model.group_names and val.lower() != current_name.lower():
736820
raise ValueError(f'Group {val} already used in model.')
737821

738822
self.model.mb.tag_set_data(self.model.name_tag, self.handle, val)
739823

824+
740825
def _get_geom_ent_by_id(self, entity_type, id):
741826
category_ents = self.model.mb.get_entities_by_type_and_tag(self.handle, types.MBENTITYSET, [self.model.category_tag], [entity_type])
742827
ids = self.model.mb.tag_get_data(self.model.id_tag, category_ents, flat=True)

test/test_dagnav.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,3 +1017,64 @@ def test_geometryset_check_tags_errors(request):
10171017
with pytest.raises(ValueError, match="has no category or geom_dimension"):
10181018
_ = pydagmc.Surface(model, raw_handle_surf_missing_all)
10191019

1020+
def test_implicit_complement_material(fuel_pin_model):
1021+
model = fuel_pin_model
1022+
1023+
# The graveyard volume is volume 6 in the test model
1024+
graveyard = model.volumes_by_id[6]
1025+
assert model.implicit_complement == graveyard
1026+
1027+
# Initially, no implicit complement material is set
1028+
assert model.implicit_complement_material is None
1029+
assert graveyard.material == 'Graveyard'
1030+
1031+
# Set the implicit complement material to the existing material of the graveyard
1032+
model.implicit_complement_material = 'Graveyard'
1033+
assert model.implicit_complement_material == 'Graveyard'
1034+
assert graveyard.material_group.name == 'mat:Graveyard_comp'
1035+
assert graveyard.material == 'Graveyard' # Should not change
1036+
1037+
# Set the implicit complement material to a new material
1038+
model.implicit_complement_material = 'water'
1039+
assert model.implicit_complement_material == 'water'
1040+
assert 'mat:Graveyard' in model.groups_by_name
1041+
assert len(model.groups_by_name['mat:Graveyard'].volumes) == 0
1042+
assert 'mat:water_comp' in model.groups_by_name
1043+
assert graveyard in model.groups_by_name['mat:water_comp'].volumes
1044+
assert graveyard.material == 'water'
1045+
assert graveyard.material_group.name == 'mat:water_comp'
1046+
1047+
# Set back to None
1048+
model.implicit_complement_material = None
1049+
assert model.implicit_complement_material is None
1050+
assert graveyard.material_group.name == 'mat:water'
1051+
assert graveyard.material == 'water'
1052+
1053+
def test_default_material(fuel_pin_model):
1054+
model = fuel_pin_model
1055+
1056+
# Initially, no default material is set
1057+
assert model.default_material is None
1058+
1059+
# Create a new volume, which should not have a material
1060+
new_volume = model.create_volume()
1061+
assert new_volume.material is None
1062+
1063+
# Check that the new volume is in the list of volumes without material
1064+
volumes_without_material = model.volumes_without_material
1065+
assert new_volume in volumes_without_material
1066+
1067+
# Set the default material
1068+
model.default_material = 'steel'
1069+
assert model.default_material == 'steel'
1070+
1071+
# Check that the new volume now has the default material
1072+
assert new_volume.material == 'steel'
1073+
1074+
# Unset the default material
1075+
model.default_material = None
1076+
assert model.default_material is None
1077+
1078+
# The material should remain assigned to the volume
1079+
assert new_volume.material == 'steel'
1080+

0 commit comments

Comments
 (0)