Merge remote-tracking branch 'origin/v0.7.0' into v08attempt1

This commit is contained in:
Thomas Krijnen
2024-04-21 11:26:06 +02:00
12 changed files with 152 additions and 46 deletions
@@ -190,12 +190,17 @@ class BIM_OT_select_parts(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object # obj = bpy.data.objects.get(self.obj) or context.active_object
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
parts_objs = set(tool.Ifc.get_object(part) for part in parts) for obj in context.selected_objects:
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs) parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
for selectable_part_obj in selectable_parts_objs: if parts:
selectable_part_obj.select_set(True) parts_objs = set(tool.Ifc.get_object(part) for part in parts)
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
for selectable_part_obj in selectable_parts_objs:
selectable_part_obj.select_set(True)
else:
obj.select_set(False)
return {"FINISHED"} return {"FINISHED"}
@@ -217,18 +222,43 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) or context.active_object
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj)) # obj = bpy.data.objects.get(self.obj) or context.active_object
aggregate_obj = tool.Ifc.get_object(aggregate) # aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
# aggregate_obj = tool.Ifc.get_object(aggregate)
all_parts = []
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element:
aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate:
all_parts.append(aggregate)
obj.select_set(False)
else:
pass
if not element:
obj.select_set(False)
if self.select_parts: if self.select_parts:
bpy.ops.bim.select_parts(obj=aggregate_obj.name) all_objs = []
if aggregate_obj in context.selectable_objects: for part in all_parts:
tool.Blender.set_objects_selection( if part.IsDecomposedBy:
context, for subpart in part.IsDecomposedBy[0].RelatedObjects:
aggregate_obj, all_parts.append(subpart)
[aggregate_obj], all_objs.append(part)
clear_previous_selection=not self.select_parts,
) for element in all_objs:
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
else:
for aggregate_element in all_parts:
aggregate_obj = tool.Ifc.get_object(aggregate_element)
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
return {"FINISHED"} return {"FINISHED"}
@@ -292,28 +322,51 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
bl_idname = "bim.select_linked_aggregates" bl_idname = "bim.select_linked_aggregates"
bl_label = "Select linked aggregates" bl_label = "Select linked aggregates"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
select_parts: bpy.props.BoolProperty(default=False)
@classmethod
def description(cls, context, properties):
if properties.select_parts:
return "Select all aggregates, subaggregates and all their parts"
else:
return "Select all aggregates"
def _execute(self, context): def _execute(self, context):
element = tool.Ifc.get_entity(bpy.context.active_object)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
return []
if not element:
return []
linked_aggregate_group = [
r.RelatingGroup
for r in getattr(aggregate, "HasAssignments", []) or []
if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
for obj in context.selected_objects: for obj in context.selected_objects:
obj.select_set(False) obj.select_set(False)
element = tool.Ifc.get_entity(obj)
aggregate = ifcopenshell.util.element.get_aggregate(element)
if not aggregate:
continue
if not element:
continue
for rel in linked_aggregate_group[0].IsGroupedBy or []: linked_aggregate_group = [
for element in rel.RelatedObjects: r.RelatingGroup
obj = tool.Ifc.get_object(element) for r in getattr(aggregate, "HasAssignments", []) or []
obj.select_set(True) if r.is_a("IfcRelAssignsToGroup")
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
]
group_rel = linked_aggregate_group[0].IsGroupedBy or []
for group_link in group_rel:
parts = list(group_link.RelatedObjects)
if self.select_parts:
parts_objs = []
for part in parts:
if part.IsDecomposedBy:
for subpart in part.IsDecomposedBy[0].RelatedObjects:
parts.append(subpart)
parts_objs.append(part)
for element in parts_objs:
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
else:
for element in parts:
obj = tool.Ifc.get_object(element)
obj.select_set(True)
return {"FINISHED"} return {"FINISHED"}
@@ -139,7 +139,10 @@ class BIM_PT_linked_aggregate(Panel):
if type(AggregateData.data['total_linked_aggregate']) is int: if type(AggregateData.data['total_linked_aggregate']) is int:
if AggregateData.data['total_linked_aggregate'] > 0: if AggregateData.data['total_linked_aggregate'] > 0:
row.label(text=f"{AggregateData.data['total_linked_aggregate']} Linked Aggregates") row.label(text=f"{AggregateData.data['total_linked_aggregate']} Linked Aggregates")
row.operator("bim.select_linked_aggregates", text="", icon="RESTRICT_SELECT_OFF") op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_DATA_POINTCLOUD")
op.select_parts = False
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_OB_POINTCLOUD")
op.select_parts = True
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH") row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X") op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
else: else:
@@ -2373,6 +2373,7 @@ class LoadSheets(bpy.types.Operator, Operator):
if not filepath.is_file(): if not filepath.is_file():
sheet_name = f"{sheet_prop.identification} - {sheet_prop.name}" sheet_name = f"{sheet_prop.identification} - {sheet_prop.name}"
sheets_not_found.append(f'"{sheet_name}" - {document_uri}') sheets_not_found.append(f'"{sheet_name}" - {document_uri}')
core.regenerate_sheet(tool.Drawing, sheet)
if sheets_not_found: if sheets_not_found:
self.report({"ERROR"}, "Some sheets svg files are missing:\n" + "\n".join(sheets_not_found)) self.report({"ERROR"}, "Some sheets svg files are missing:\n" + "\n".join(sheets_not_found))
@@ -93,7 +93,7 @@ class SvgWriter:
def setup_drawing_resource_paths(self, element): def setup_drawing_resource_paths(self, element):
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing") pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns"): for resource in ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles"):
resource_path = pset.get(resource) resource_path = pset.get(resource)
if not resource_path: if not resource_path:
self.resource_paths[resource] = None self.resource_paths[resource] = None
@@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
def enable_editing_text(drawing, obj=None): def enable_editing_text(drawing, obj=None):
drawing.enable_editing_text(obj) drawing.enable_editing_text(obj)
@@ -89,6 +91,12 @@ def add_sheet(ifc, drawing, titleblock=None):
drawing.import_sheets() drawing.import_sheets()
def regenerate_sheet(drawing, sheet=None):
titleblock_uri = drawing.get_document_uri(sheet, "TITLEBLOCK")
drawing.create_svg_sheet(sheet, drawing.sanitise_filename(Path(titleblock_uri).stem))
drawing.add_drawings(sheet)
def open_sheet(drawing, sheet=None): def open_sheet(drawing, sheet=None):
drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT")) drawing.open_layout_svg(drawing.get_document_uri(sheet, "LAYOUT"))
+15
View File
@@ -247,6 +247,21 @@ class Drawing(blenderbim.core.tool.Drawing):
sheet_builder.create(uri, titleblock) sheet_builder.create(uri, titleblock)
return uri return uri
@classmethod
def add_drawings(cls, sheet):
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = bpy.context.scene.BIMProperties.data_dir
sheet_reference = None
drawing_names = []
for reference in cls.get_document_references(sheet):
if reference.Description == "LAYOUT":
sheet_reference = reference
elif reference.Description == "DRAWING":
drawing_names.append(Path(reference.Location).stem)
for annotation in [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]:
if annotation.Name in drawing_names:
sheet_builder.add_drawing(sheet_reference, annotation, sheet)
@classmethod @classmethod
def delete_collection(cls, collection): def delete_collection(cls, collection):
bpy.data.collections.remove(collection, do_unlink=True) bpy.data.collections.remove(collection, do_unlink=True)
+1 -1
View File
@@ -36,7 +36,7 @@ Some of these add-ons are not shipped with Blender:
import GIS data, grab elevation data from the web, and generate TINs from import GIS data, grab elevation data from the web, and generate TINs from
survey points and contours. survey points and contours.
- `Ladybug Tools for Blender - `Ladybug Tools for Blender
<https://blenderbim.org/builds/ladybug-blender-220127.zip>`__ - Ladybug Tools <https://github.com/Andrej730/ladybug-blender/releases/download/ladybug-blender-240419/ladybug-blender-240419.zip>`__ - Ladybug Tools
is an extension of Sverchok for environmental analysis and building physics is an extension of Sverchok for environmental analysis and building physics
simulation. It allows analysis of solar, daylight, energy, and CFD. simulation. It allows analysis of solar, daylight, energy, and CFD.
- `Topologic <https://topologic.app/>`__ - Perform spatial and topological - `Topologic <https://topologic.app/>`__ - Perform spatial and topological
@@ -61,11 +61,22 @@ def get_axis2placement(placement: ifcopenshell.entity_instance) -> MatrixType:
:return: A 4x4 numpy matrix :return: A 4x4 numpy matrix
:rtype: np.ndarray[np.ndarray[float]] :rtype: np.ndarray[np.ndarray[float]]
""" """
if placement.is_a("IfcAxis2Placement3D"): ifc_class = placement.is_a()
if ifc_class in ("IfcAxis2Placement3D", "IfcAxis2PlacementLinear"):
z = np.array(placement.Axis.DirectionRatios if placement.Axis else (0, 0, 1)) z = np.array(placement.Axis.DirectionRatios if placement.Axis else (0, 0, 1))
x = np.array(placement.RefDirection.DirectionRatios if placement.RefDirection else (1, 0, 0)) x = np.array(placement.RefDirection.DirectionRatios if placement.RefDirection else (1, 0, 0))
o = placement.Location.Coordinates location = placement.Location
elif placement.is_a("IfcAxis2Placement2D"): if coordinates := getattr(location, "Coordinates", None):
o = coordinates
else:
ifc_class = location.is_a("IfcPointByDistanceExpression")
print(
f'WARNING. Placement location of type "{ifc_class}" '
f'is not yet supported and placement {placement} may be placed incorrectly.'
)
o = (0.0, 0.0, 0.0)
elif ifc_class == "IfcAxis2Placement2D":
z = np.array((0, 0, 1)) z = np.array((0, 0, 1))
if placement.RefDirection: if placement.RefDirection:
x = np.array(placement.RefDirection.DirectionRatios) x = np.array(placement.RefDirection.DirectionRatios)
@@ -73,6 +84,13 @@ def get_axis2placement(placement: ifcopenshell.entity_instance) -> MatrixType:
else: else:
x = np.array((1, 0, 0)) x = np.array((1, 0, 0))
o = (*placement.Location.Coordinates, 0.0) o = (*placement.Location.Coordinates, 0.0)
elif ifc_class == "IfcAxis1Placement":
axis = placement.Axis
z = np.array(axis.DirectionRatios if axis else (0, 0, 1))
x = np.array((1, 0, 0))
o = placement.Location.Coordinates
return a2p(o, z, x) return a2p(o, z, x)
@@ -233,7 +233,7 @@ class FormatTransformer(lark.Transformer):
def imperial_length(self, args): def imperial_length(self, args):
if len(args) == 2: if len(args) == 2:
input_unit = "foot" input_unit, output_unit = "foot", "foot"
value, precision = args value, precision = args
else: else:
value, precision, input_unit, output_unit = args value, precision, input_unit, output_unit = args
@@ -524,7 +524,10 @@ class FacetTransformer(lark.Transformer):
name = name.children[0].value name = name.children[0].value
def filter_function(element): def filter_function(element):
element_value = getattr(element, name, None) if name == "PredefinedType":
element_value = ifcopenshell.util.element.get_predefined_type(element)
else:
element_value = getattr(element, name, None)
return self.compare(element_value, comparison, value) return self.compare(element_value, comparison, value)
self.elements = set(filter(filter_function, self.elements)) self.elements = set(filter(filter_function, self.elements))
@@ -604,8 +604,8 @@ def format_length(
decimal_places: int = 2, decimal_places: int = 2,
suppress_zero_inches=True, suppress_zero_inches=True,
unit_system: Literal["metric", "imperial"] = "imperial", unit_system: Literal["metric", "imperial"] = "imperial",
input_unit="foot", input_unit: Literal["foot", "inch"] = "foot",
output_unit="foot", output_unit: Literal["foot", "inch"] = "foot",
) -> str: ) -> str:
"""Formats a length for readability and imperial formatting """Formats a length for readability and imperial formatting
@@ -165,6 +165,11 @@ class TestFilterElements(test.bootstrap.IFC4):
element.Description = "Foobar" element.Description = "Foobar"
assert subject.filter_elements(self.file, "IfcWall, Name=Foo, Description=Foobar") == {element} assert subject.filter_elements(self.file, "IfcWall, Name=Foo, Description=Foobar") == {element}
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
element_type.PredefinedType = "SOLIDWALL"
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type)
assert subject.filter_elements(self.file, "IfcWall, PredefinedType=SOLIDWALL") == {element}
def test_selecting_by_type(self): def test_selecting_by_type(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
+1 -1
View File
@@ -15,7 +15,7 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
] ]
dependencies = ["ifcopenshell", "dateutil", "xmlschema", "numpy"] dependencies = ["ifcopenshell", "python-dateutil", "xmlschema", "numpy"]
[project.urls] [project.urls]
Homepage = "http://ifcopenshell.org" Homepage = "http://ifcopenshell.org"