mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 22:12:32 +00:00
Merge remote-tracking branch 'origin/v0.7.0' into v08attempt1
This commit is contained in:
@@ -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"))
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user