mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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):
|
||||
self.file = IfcStore.get_file()
|
||||
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)
|
||||
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
|
||||
for selectable_part_obj in selectable_parts_objs:
|
||||
selectable_part_obj.select_set(True)
|
||||
# obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
|
||||
for obj in context.selected_objects:
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
|
||||
if parts:
|
||||
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"}
|
||||
|
||||
|
||||
@@ -217,18 +222,43 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
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))
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
|
||||
# obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
# 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:
|
||||
bpy.ops.bim.select_parts(obj=aggregate_obj.name)
|
||||
if aggregate_obj in context.selectable_objects:
|
||||
tool.Blender.set_objects_selection(
|
||||
context,
|
||||
aggregate_obj,
|
||||
[aggregate_obj],
|
||||
clear_previous_selection=not self.select_parts,
|
||||
)
|
||||
all_objs = []
|
||||
for part in all_parts:
|
||||
if part.IsDecomposedBy:
|
||||
for subpart in part.IsDecomposedBy[0].RelatedObjects:
|
||||
all_parts.append(subpart)
|
||||
all_objs.append(part)
|
||||
|
||||
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"}
|
||||
|
||||
|
||||
@@ -292,28 +322,51 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.select_linked_aggregates"
|
||||
bl_label = "Select linked aggregates"
|
||||
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):
|
||||
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:
|
||||
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 []:
|
||||
for element in rel.RelatedObjects:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj.select_set(True)
|
||||
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
|
||||
]
|
||||
|
||||
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"}
|
||||
|
||||
@@ -139,7 +139,10 @@ class BIM_PT_linked_aggregate(Panel):
|
||||
if type(AggregateData.data['total_linked_aggregate']) is int:
|
||||
if AggregateData.data['total_linked_aggregate'] > 0:
|
||||
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")
|
||||
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
|
||||
else:
|
||||
|
||||
@@ -2373,6 +2373,7 @@ class LoadSheets(bpy.types.Operator, Operator):
|
||||
if not filepath.is_file():
|
||||
sheet_name = f"{sheet_prop.identification} - {sheet_prop.name}"
|
||||
sheets_not_found.append(f'"{sheet_name}" - {document_uri}')
|
||||
core.regenerate_sheet(tool.Drawing, sheet)
|
||||
|
||||
if 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):
|
||||
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)
|
||||
if not resource_path:
|
||||
self.resource_paths[resource] = None
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
# 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/>.
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def enable_editing_text(drawing, obj=None):
|
||||
drawing.enable_editing_text(obj)
|
||||
@@ -89,6 +91,12 @@ def add_sheet(ifc, drawing, titleblock=None):
|
||||
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):
|
||||
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)
|
||||
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
|
||||
def delete_collection(cls, collection):
|
||||
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
|
||||
survey points and contours.
|
||||
- `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
|
||||
simulation. It allows analysis of solar, daylight, energy, and CFD.
|
||||
- `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
|
||||
: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))
|
||||
x = np.array(placement.RefDirection.DirectionRatios if placement.RefDirection else (1, 0, 0))
|
||||
o = placement.Location.Coordinates
|
||||
elif placement.is_a("IfcAxis2Placement2D"):
|
||||
location = placement.Location
|
||||
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))
|
||||
if placement.RefDirection:
|
||||
x = np.array(placement.RefDirection.DirectionRatios)
|
||||
@@ -73,6 +84,13 @@ def get_axis2placement(placement: ifcopenshell.entity_instance) -> MatrixType:
|
||||
else:
|
||||
x = np.array((1, 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)
|
||||
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ class FormatTransformer(lark.Transformer):
|
||||
|
||||
def imperial_length(self, args):
|
||||
if len(args) == 2:
|
||||
input_unit = "foot"
|
||||
input_unit, output_unit = "foot", "foot"
|
||||
value, precision = args
|
||||
else:
|
||||
value, precision, input_unit, output_unit = args
|
||||
@@ -524,7 +524,10 @@ class FacetTransformer(lark.Transformer):
|
||||
name = name.children[0].value
|
||||
|
||||
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)
|
||||
|
||||
self.elements = set(filter(filter_function, self.elements))
|
||||
|
||||
@@ -604,8 +604,8 @@ def format_length(
|
||||
decimal_places: int = 2,
|
||||
suppress_zero_inches=True,
|
||||
unit_system: Literal["metric", "imperial"] = "imperial",
|
||||
input_unit="foot",
|
||||
output_unit="foot",
|
||||
input_unit: Literal["foot", "inch"] = "foot",
|
||||
output_unit: Literal["foot", "inch"] = "foot",
|
||||
) -> str:
|
||||
"""Formats a length for readability and imperial formatting
|
||||
|
||||
|
||||
@@ -165,6 +165,11 @@ class TestFilterElements(test.bootstrap.IFC4):
|
||||
element.Description = "Foobar"
|
||||
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):
|
||||
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")
|
||||
|
||||
@@ -15,7 +15,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"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]
|
||||
Homepage = "http://ifcopenshell.org"
|
||||
|
||||
Reference in New Issue
Block a user