mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-01 09:26:26 +00:00
Merge branch 'v0.7.0' into covering_tool
This commit is contained in:
@@ -169,7 +169,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f0e03c7-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
|
||||
@@ -32,9 +32,16 @@ class SelectFMIfcFile(bpy.types.Operator):
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMFMProperties.ifc_file = self.filepath
|
||||
props = context.scene.BIMFMProperties
|
||||
props.ifc_files.clear()
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
for f in self.files:
|
||||
new = props.ifc_files.add()
|
||||
new.name = os.path.join(dirname, f.name)
|
||||
props.ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -42,6 +49,7 @@ class SelectFMIfcFile(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
|
||||
class ExecuteIfcFM(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifcfm"
|
||||
bl_label = "Execute IfcFM"
|
||||
@@ -66,17 +74,32 @@ class ExecuteIfcFM(bpy.types.Operator):
|
||||
|
||||
props = context.scene.BIMFMProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
filepaths = []
|
||||
if not (ifc_file and props.should_load_from_memory):
|
||||
ifc_file = ifcopenshell.open(props.ifc_file)
|
||||
if len(props.ifc_files):
|
||||
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
|
||||
filepaths = [f.name for f in props.ifc_files]
|
||||
else:
|
||||
ifc_files = [ifcopenshell.open(props.ifc_file)]
|
||||
else:
|
||||
ifc_files = [ifc_file]
|
||||
|
||||
parser = ifcfm.Parser(preset=props.engine)
|
||||
parser.parse(ifc_file)
|
||||
writer = ifcfm.Writer(parser)
|
||||
writer.write()
|
||||
if props.format == "csv":
|
||||
writer.write_csv('tmp/')
|
||||
elif props.format == "ods":
|
||||
writer.write_ods(self.filepath)
|
||||
elif props.format == "xlsx":
|
||||
writer.write_xlsx(self.filepath)
|
||||
for i, ifc_file in enumerate(ifc_files):
|
||||
if filepaths:
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
prefix, _ = os.path.splitext(os.path.basename(filepaths[i]))
|
||||
basename = os.path.basename(self.filepath)
|
||||
filepath = os.path.join(dirname, f"{prefix}-{basename}")
|
||||
else:
|
||||
filepath = self.filepath
|
||||
parser = ifcfm.Parser(preset=props.engine)
|
||||
parser.parse(ifc_file)
|
||||
writer = ifcfm.Writer(parser)
|
||||
writer.write()
|
||||
if props.format == "csv":
|
||||
writer.write_csv('tmp/')
|
||||
elif props.format == "ods":
|
||||
writer.write_ods(filepath)
|
||||
elif props.format == "xlsx":
|
||||
writer.write_xlsx(filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -32,6 +33,7 @@ from bpy.props import (
|
||||
|
||||
class BIMFMProperties(PropertyGroup):
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
engine: EnumProperty(
|
||||
items=[
|
||||
|
||||
@@ -42,7 +42,10 @@ class BIM_PT_fm(Panel):
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_file")
|
||||
if props.ifc_files:
|
||||
row.label(text=f"{len(props.ifc_files)} Files Selected")
|
||||
else:
|
||||
row.prop(props, "ifc_file")
|
||||
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
|
||||
@@ -23,6 +23,7 @@ classes = (
|
||||
operator.AddRepresentation,
|
||||
operator.CopyRepresentation,
|
||||
operator.EditObjectPlacement,
|
||||
operator.FlipObject,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
|
||||
@@ -1203,9 +1203,64 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
else:
|
||||
parts = ifcopenshell.util.element.get_parts(instance_entity)
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
if not pset:
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [part.GlobalId],
|
||||
}
|
||||
data = [data_children]
|
||||
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(entity),
|
||||
related_obj=tool.Ifc.get_object(new_part),
|
||||
)
|
||||
duplicate_children(new_part)
|
||||
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
|
||||
else:
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
else:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [],
|
||||
}
|
||||
data = [data_children]
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
@@ -1226,6 +1281,10 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
|
||||
# This is needed to make sure the new object gets unlink from
|
||||
# the old object assembly collection
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
|
||||
# Copy the actual class
|
||||
new_entity = blenderbim.core.root.copy_class(
|
||||
@@ -1234,7 +1293,9 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
|
||||
if new_entity:
|
||||
tool.Model.handle_array_on_copied_element(new_entity)
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
|
||||
if not new_entity.is_a("IfcElementAssembly"):
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
@@ -1662,3 +1723,19 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
if self.edited_objs:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class FlipObject(bpy.types.Operator):
|
||||
bl_idname = "bim.flip_object"
|
||||
bl_label = "Flip Object"
|
||||
bl_description = "Flip object's local axes, keep the position"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
flip_local_axes: bpy.props.EnumProperty(
|
||||
name="Flip Local Axes", items=(("XY", "XY", ""), ("YZ", "YZ", ""), ("XZ", "XZ", "")), default="XY"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
tool.Geometry.flip_object(obj, self.flip_local_axes)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -56,7 +56,12 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def map_conversion(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not map_conversion:
|
||||
return {}
|
||||
del map_conversion["id"]
|
||||
return map_conversion
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
@@ -72,8 +77,8 @@ class GeoreferenceData:
|
||||
def map_derived_angle(cls):
|
||||
if (
|
||||
cls.data["map_conversion"]
|
||||
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
|
||||
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
|
||||
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None
|
||||
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None
|
||||
):
|
||||
return str(
|
||||
round(
|
||||
@@ -88,7 +93,12 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def projected_crs(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
projected_crs = ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS")
|
||||
if not projected_crs:
|
||||
return {}
|
||||
del projected_crs["id"]
|
||||
return projected_crs
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
|
||||
@@ -78,10 +78,14 @@ class BIM_PT_gis(Panel):
|
||||
def draw_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.label(text="IFC2X3 Fallback In Use", icon="ERROR")
|
||||
|
||||
if not GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Not Georeferenced")
|
||||
if tool.Ifc.get_schema != "IFC2X3":
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
|
||||
if props.has_blender_offset:
|
||||
@@ -110,8 +114,9 @@ class BIM_PT_gis(Panel):
|
||||
if GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
|
||||
for key, value in GeoreferenceData.data["projected_crs"].items():
|
||||
if not value:
|
||||
@@ -184,4 +189,4 @@ class BIM_PT_gis_utilities(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
|
||||
@@ -169,7 +169,6 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item]["count"] = 1
|
||||
|
||||
if (self.keep_objs) & (self.item < (len(data) - 1)):
|
||||
self.report(
|
||||
@@ -188,6 +187,8 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Blender.Modifier.Array.bake_children_transform(element, self.item)
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, False)
|
||||
|
||||
if not self.keep_objs:
|
||||
data[self.item]["count"] = 1
|
||||
tool.Model.regenerate_array(parent, data, self.keep_objs)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
@@ -393,18 +393,7 @@ class FlipFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.FillsVoids:
|
||||
continue
|
||||
|
||||
flip_matrix = Matrix.Rotation(pi, 4, "Z")
|
||||
|
||||
bottom_left = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||
top_right = obj.matrix_world @ Vector(obj.bound_box[6])
|
||||
center = obj.matrix_world.translation.copy()
|
||||
center_offset = center - bottom_left
|
||||
flipped_center = top_right - center_offset
|
||||
|
||||
obj.matrix_world = obj.matrix_world @ flip_matrix
|
||||
obj.matrix_world.translation.xy = flipped_center.xy
|
||||
bpy.context.view_layer.update()
|
||||
tool.Geometry.flip_object(obj, "XY")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class DeleteSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="WARNING. The graph will be removed permamently.")
|
||||
self.layout.label(text="WARNING. The graph will be removed permanently.")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
@@ -1262,8 +1262,13 @@ class DumbWallJoiner:
|
||||
results["is_sloped"] = True
|
||||
results["height"] = (item.Depth * self.unit_scale) / (1 / cos(results["x_angle"]))
|
||||
break
|
||||
elif item.is_a("IfcBooleanClippingResult"):
|
||||
elif item.is_a("IfcBooleanClippingResult"): # should be before IfcBooleanResult check
|
||||
item = item.FirstOperand
|
||||
elif item.is_a("IfcBooleanResult"):
|
||||
if item.FirstOperand.is_a("IfcExtrudedAreaSolid") or item.FirstOperand.is_a("IfcBooleanResult"):
|
||||
item = item.FirstOperand
|
||||
else:
|
||||
item = item.SecondOperand
|
||||
else:
|
||||
break
|
||||
return results
|
||||
|
||||
@@ -358,6 +358,7 @@ class BimToolUI:
|
||||
cls.layout.operator("bim.mep_connect_elements")
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
@@ -668,6 +669,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_class in ("IfcBeam", "IfcColumn"):
|
||||
bpy.ops.bim.flip_object(flip_local_axes="XZ")
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
|
||||
|
||||
@@ -822,10 +822,13 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
|
||||
props = context.scene.BIMSearchProperties
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
value = ifcopenshell.util.selector.get_element_value(element, props.element_key)
|
||||
key = props.element_key
|
||||
if props.element_key == "PredefinedType":
|
||||
key = "predefined_type"
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
for obj in context.visible_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if ifcopenshell.util.selector.get_element_value(element, props.element_key) == value:
|
||||
if ifcopenshell.util.selector.get_element_value(element, key) == value:
|
||||
obj.select_set(True)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# 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/>.
|
||||
|
||||
import os
|
||||
import os
|
||||
import bpy
|
||||
import time
|
||||
import tempfile
|
||||
@@ -72,8 +72,10 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Tester.report = report
|
||||
props.specifications.clear()
|
||||
for spec in report:
|
||||
print(spec)
|
||||
new_spec = props.specifications.add()
|
||||
new_spec.name = spec["name"]
|
||||
new_spec.description = spec["description"]
|
||||
new_spec.status = spec["status"]
|
||||
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -37,6 +37,7 @@ def update_active_specification_index(self, context):
|
||||
|
||||
class Specification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
description: StringProperty(name="Description")
|
||||
status: BoolProperty(default=False, name="Status")
|
||||
|
||||
|
||||
|
||||
@@ -107,8 +107,9 @@ class BIM_PT_tester(Panel):
|
||||
class BIM_UL_tester_specifications(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row = layout.split(factor=0.3, align=True)
|
||||
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
|
||||
row.label(text=item.description)
|
||||
|
||||
|
||||
class BIM_UL_tester_failed_entities(UIList):
|
||||
|
||||
@@ -237,7 +237,7 @@ class FileAssociate(bpy.types.Operator):
|
||||
# tried to do the regitsry change from powershell/cmd - but even admin rights are not enough
|
||||
# this is why we're using .reg
|
||||
reg_change_path = os.path.join(src_dir, "windows_bbim_association.reg")
|
||||
subprocess.run(["cmd", "/c", reg_change_path])
|
||||
subprocess.run(["cmd", "/c", "call", reg_change_path])
|
||||
|
||||
ps_script_path = os.path.join(src_dir, "windows_bbim_association.ps1")
|
||||
# NOTE: call powershell with RunAs to get admin rights from user
|
||||
|
||||
@@ -71,13 +71,13 @@ def set_cursor_location(georeference):
|
||||
|
||||
|
||||
def convert_local_to_global(georeference):
|
||||
coordinates = georeference.xyz2enh(georeference.get_coordinates("input"), georeference.get_map_conversion())
|
||||
coordinates = georeference.xyz2enh(georeference.get_coordinates("input"))
|
||||
georeference.set_coordinates("output", coordinates)
|
||||
georeference.set_cursor_location(coordinates)
|
||||
|
||||
|
||||
def convert_global_to_local(georeference):
|
||||
coordinates = georeference.enh2xyz(georeference.get_coordinates("input"), georeference.get_map_conversion())
|
||||
coordinates = georeference.enh2xyz(georeference.get_coordinates("input"))
|
||||
georeference.set_coordinates("output", coordinates)
|
||||
georeference.set_cursor_location(coordinates)
|
||||
|
||||
@@ -86,4 +86,4 @@ def convert_angle_to_coord(georeference, type):
|
||||
georeference.set_vector_coordinates(vector_coordinates,type)
|
||||
|
||||
def import_plot(georeference, filepath):
|
||||
georeference.import_plot(filepath, georeference.get_map_conversion())
|
||||
georeference.import_plot(filepath)
|
||||
|
||||
@@ -95,30 +95,37 @@ def select_similar_container(ifc, spatial, obj=None):
|
||||
def select_product(spatial, product):
|
||||
spatial.select_products([product])
|
||||
|
||||
|
||||
def load_container_manager(spatial):
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def edit_container_attributes(spatial, entity=None):
|
||||
spatial.edit_container_attributes(entity)
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def contract_container(spatial, container=None):
|
||||
spatial.contract_container(container)
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def expand_container(spatial, container=None):
|
||||
spatial.expand_container(container)
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def delete_container(ifc, spatial, geometry, container=None):
|
||||
geometry.delete_ifc_object(ifc.get_object(container))
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def select_decomposed_elements(spatial):
|
||||
container = spatial.get_active_container()
|
||||
if container:
|
||||
spatial.select_products(spatial.get_decomposed_elements(container))
|
||||
|
||||
|
||||
#HERE STARTS SPATIAL TOOL
|
||||
def generate_space(ifc, spatial, model, Type):
|
||||
active_obj = spatial.get_active_obj()
|
||||
@@ -139,6 +146,7 @@ def generate_space(ifc, spatial, model, Type):
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() ##mat
|
||||
|
||||
|
||||
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
|
||||
|
||||
if not space_polygon:
|
||||
@@ -188,6 +196,7 @@ def generate_spaces_from_walls(ifc, spatial, collector):
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
spatial.assign_container_to_obj(obj)
|
||||
|
||||
|
||||
def toggle_space_visibility(ifc, spatial):
|
||||
model = ifc.get()
|
||||
@@ -195,4 +204,3 @@ def toggle_space_visibility(ifc, spatial):
|
||||
if not spaces:
|
||||
return
|
||||
spatial.toggle_spaces_visibility_wired_and_textured(spaces)
|
||||
|
||||
|
||||
@@ -384,12 +384,11 @@ class Georeference:
|
||||
def angle2coords(cls, angle, type): pass
|
||||
def disable_editing(cls): pass
|
||||
def enable_editing(cls): pass
|
||||
def enh2xyz(cls, map_conversion, coordinates): pass
|
||||
def enh2xyz(cls, coordinates): pass
|
||||
def get_angle(cls, type): pass
|
||||
def get_coordinates(cls, io): pass
|
||||
def get_cursor_location(cls): pass
|
||||
def get_map_conversion_attributes(cls): pass
|
||||
def get_map_conversion(cls): pass
|
||||
def get_projected_crs_attributes(cls): pass
|
||||
def get_true_north_attributes(cls): pass
|
||||
def import_map_conversion(cls): pass
|
||||
@@ -402,7 +401,7 @@ class Georeference:
|
||||
def set_ifc_grid_north(cls): pass
|
||||
def set_ifc_true_north(cls): pass
|
||||
def set_vector_coordinates(cls, vector_coordinates, type): pass
|
||||
def xyz2enh(cls, map_conversion, coordinates): pass
|
||||
def xyz2enh(cls, coordinates): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ifc]
|
||||
[-]
|
||||
@@ -343,6 +343,8 @@ class Blender:
|
||||
"max_y": bound_box[6][1],
|
||||
"min_z": bound_box[0][2],
|
||||
"max_z": bound_box[6][2],
|
||||
"min_point": Vector(bound_box[0]),
|
||||
"max_point": Vector(bound_box[6]),
|
||||
"center": (Vector(bound_box[6]) + Vector(bound_box[0])) / 2,
|
||||
}
|
||||
return bbox_dict
|
||||
|
||||
@@ -28,7 +28,7 @@ import blenderbim.core.style
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.import_ifc
|
||||
from math import radians
|
||||
from math import radians, pi
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -641,3 +641,25 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
@classmethod
|
||||
def get_model_representations(cls):
|
||||
return tool.Ifc.get().by_type("IfcShapeRepresentation")
|
||||
|
||||
@classmethod
|
||||
def flip_object(cls, obj, flip_local_axes):
|
||||
assert len(flip_local_axes) == 2, "flip_local_axes must be two axes to flip"
|
||||
rotation_axis = next(i for i in "XYZ" if i not in flip_local_axes)
|
||||
rotation_axis_i = "XYZ".index(rotation_axis)
|
||||
|
||||
bb_data = tool.Blender.get_object_bounding_box(obj)
|
||||
# min max points of rotated plane of origin based bounding box
|
||||
min_point = Vector([min(i, 0) for i in bb_data["min_point"]])
|
||||
max_point = Vector([max(i, 0) for i in bb_data["max_point"]])
|
||||
# keep it in rotated plane only
|
||||
max_point[rotation_axis_i] = min_point[rotation_axis_i]
|
||||
|
||||
# to compensate for flipped two axes
|
||||
# we adjust new max point to match previous min point (or vice versa)
|
||||
original_min_point = obj.matrix_world @ min_point
|
||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(pi, 4, rotation_axis)
|
||||
new_max_point = obj.matrix_world @ max_point
|
||||
obj.matrix_world.translation += original_min_point - new_max_point
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@@ -174,15 +174,7 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_map_conversion(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
return context.HasCoordinateOperation[0]
|
||||
|
||||
@classmethod
|
||||
def xyz2enh(cls, coordinates, map_conversion):
|
||||
def xyz2enh(cls, coordinates):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
coordinates = ifcopenshell.util.geolocation.xyz2enh(
|
||||
@@ -196,53 +188,12 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
float(props.blender_x_axis_ordinate),
|
||||
1.0,
|
||||
)
|
||||
if map_conversion:
|
||||
unit = map_conversion.TargetCRS.MapUnit
|
||||
e = map_conversion.Eastings
|
||||
n = map_conversion.Northings
|
||||
h = map_conversion.OrthogonalHeight
|
||||
if unit:
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
e = ifcopenshell.util.unit.convert(e, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
n = ifcopenshell.util.unit.convert(n, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
h = ifcopenshell.util.unit.convert(h, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
coordinates = ifcopenshell.util.geolocation.xyz2enh(
|
||||
coordinates[0],
|
||||
coordinates[1],
|
||||
coordinates[2],
|
||||
e,
|
||||
n,
|
||||
h,
|
||||
map_conversion.XAxisAbscissa or 1.0,
|
||||
map_conversion.XAxisOrdinate or 0.0,
|
||||
map_conversion.Scale or 1.0,
|
||||
)
|
||||
return coordinates
|
||||
return ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *coordinates)
|
||||
|
||||
@classmethod
|
||||
def enh2xyz(cls, coordinates, map_conversion):
|
||||
def enh2xyz(cls, coordinates):
|
||||
coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if map_conversion:
|
||||
unit = map_conversion.TargetCRS.MapUnit
|
||||
e = map_conversion.Eastings
|
||||
n = map_conversion.Northings
|
||||
h = map_conversion.OrthogonalHeight
|
||||
if unit:
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
e = ifcopenshell.util.unit.convert(e, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
n = ifcopenshell.util.unit.convert(n, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
h = ifcopenshell.util.unit.convert(h, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
coordinates = ifcopenshell.util.geolocation.enh2xyz(
|
||||
coordinates[0],
|
||||
coordinates[1],
|
||||
coordinates[2],
|
||||
e,
|
||||
n,
|
||||
h,
|
||||
map_conversion.XAxisAbscissa or 1.0,
|
||||
map_conversion.XAxisOrdinate or 0.0,
|
||||
map_conversion.Scale or 1.0,
|
||||
)
|
||||
if props.has_blender_offset:
|
||||
coordinates = ifcopenshell.util.geolocation.enh2xyz(
|
||||
coordinates[0],
|
||||
@@ -316,7 +267,7 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
rows = parse_csv(filepath)
|
||||
vertices = []
|
||||
for row in rows:
|
||||
coordinates = cls.enh2xyz([float(row[0]), float(row[1]), float(row[2])], map_conversion)
|
||||
coordinates = cls.enh2xyz([float(row[0]), float(row[1]), float(row[2])])
|
||||
vertices.append(coordinates)
|
||||
|
||||
mesh = bpy.data.meshes.new("mesh")
|
||||
|
||||
@@ -97,8 +97,7 @@ class TestSetCursorLocation:
|
||||
class TestConvertLocalToGlobal:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
|
||||
georeference.get_map_conversion().should_be_called().will_return("map_conversion")
|
||||
georeference.xyz2enh("coordinates", "map_conversion").should_be_called().will_return("enh")
|
||||
georeference.xyz2enh("coordinates").should_be_called().will_return("enh")
|
||||
georeference.set_coordinates("output", "enh").should_be_called()
|
||||
georeference.set_cursor_location("enh").should_be_called()
|
||||
subject.convert_local_to_global(georeference)
|
||||
@@ -107,8 +106,7 @@ class TestConvertLocalToGlobal:
|
||||
class TestConvertGlobalToLocal:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
|
||||
georeference.get_map_conversion().should_be_called().will_return("map_conversion")
|
||||
georeference.enh2xyz("coordinates", "map_conversion").should_be_called().will_return("xyz")
|
||||
georeference.enh2xyz("coordinates").should_be_called().will_return("xyz")
|
||||
georeference.set_coordinates("output", "xyz").should_be_called()
|
||||
georeference.set_cursor_location("xyz").should_be_called()
|
||||
subject.convert_global_to_local(georeference)
|
||||
|
||||
@@ -247,25 +247,21 @@ class TestSetBlenderTrueNorth(NewFile):
|
||||
assert round(math.degrees(bpy.context.scene.sun_pos_properties.north_offset)) == 45
|
||||
|
||||
|
||||
class TestGetMapConversion(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
assert subject.get_map_conversion().is_a("IfcMapConversion")
|
||||
|
||||
|
||||
class TestXyz2Enh(NewFile):
|
||||
def test_run(self):
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], None) == [0.0, 0.0, 0.0]
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (0.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_blender_offset(self):
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.has_blender_offset = True
|
||||
props.blender_eastings = "1.0"
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], None) == (1.0, 0.0, 0.0)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_map_conversion(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -275,7 +271,7 @@ class TestXyz2Enh(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Eastings = 1.0
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], map_conversion) == (1.0, 0.0, 0.0)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
|
||||
|
||||
def test_applying_both_blender_offset_and_map_conversion(self):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -288,18 +284,24 @@ class TestXyz2Enh(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Northings = 1.0
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], map_conversion) == (1.0, 1.0, 0.0)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 1.0, 0.0)
|
||||
|
||||
|
||||
class TestEnh2Xyz(NewFile):
|
||||
def test_run(self):
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], None) == [0.0, 0.0, 0.0]
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (0.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_blender_offset(self):
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.has_blender_offset = True
|
||||
props.blender_eastings = "1.0"
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], None) == (-1.0, 0.0, 0.0)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_map_conversion(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -309,7 +311,7 @@ class TestEnh2Xyz(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Eastings = 1.0
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], map_conversion) == (-1.0, 0.0, 0.0)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
|
||||
|
||||
def test_applying_both_blender_offset_and_map_conversion(self):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -322,7 +324,7 @@ class TestEnh2Xyz(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Northings = 1.0
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], map_conversion) == (-1.0, -1.0, 0.0)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, -1.0, 0.0)
|
||||
|
||||
|
||||
class TestSetIfcGridNorth(NewFile):
|
||||
|
||||
@@ -52,12 +52,14 @@ class TestGenerateOccurrenceName(NewFile):
|
||||
bpy.context.scene.BIMModelProperties.occurrence_name_function = '"Foobar"'
|
||||
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
|
||||
|
||||
|
||||
class TestGetManualBooleans(NewFile):
|
||||
def test_run(self):
|
||||
assert isinstance(subject(), blenderbim.core.tool.Model)
|
||||
|
||||
def test_len_returned_boolean(self):
|
||||
def setup_profile_represntation(self, clippings=[]):
|
||||
ifc = ifcopenshell.file()
|
||||
self.ifc = ifc
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
length = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT")
|
||||
@@ -65,17 +67,43 @@ class TestGetManualBooleans(NewFile):
|
||||
ifcopenshell.api.run("unit.assign_unit", ifc)
|
||||
element = ifc.createIfcColumn()
|
||||
hea100 = ifc.create_entity(
|
||||
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
|
||||
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
|
||||
"IfcIShapeProfileDef",
|
||||
ProfileName="HEA100",
|
||||
ProfileType="AREA",
|
||||
OverallWidth=100,
|
||||
OverallDepth=96,
|
||||
WebThickness=5,
|
||||
FlangeThickness=8,
|
||||
FilletRadius=12,
|
||||
)
|
||||
model3d = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
|
||||
body = ifcopenshell.api.run("context.add_context", ifc,context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
|
||||
representation = ifcopenshell.api.run("geometry.add_profile_representation", ifc, context=body, profile=hea100, depth=5)
|
||||
body = ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
ifc,
|
||||
context_type="Model",
|
||||
context_identifier="Body",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=model3d,
|
||||
)
|
||||
representation = ifcopenshell.api.run(
|
||||
"geometry.add_profile_representation", ifc, context=body, profile=hea100, depth=5, clippings=clippings
|
||||
)
|
||||
ifcopenshell.api.run("geometry.assign_representation", ifc, product=element, representation=representation)
|
||||
return element, representation
|
||||
|
||||
def test_manual_booleans(self):
|
||||
element, representation = self.setup_profile_represntation()
|
||||
matrix = np.eye(4)
|
||||
matrix = ifcopenshell.util.placement.rotation(45,"X") @ matrix
|
||||
matrix[:,3][0:3] = (0, 0, 3)
|
||||
matrix = matrix.tolist()
|
||||
ifcopenshell.api.run("geometry.add_boolean", ifc, representation = representation, type = "IfcHalfSpaceSolid", matrix = matrix)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.add_boolean", self.ifc, representation=representation, type="IfcHalfSpaceSolid", matrix=matrix
|
||||
)
|
||||
assert len(subject.get_manual_booleans(element)) == 1
|
||||
|
||||
def test_automatic_booleans_ignored(self):
|
||||
clipping = {
|
||||
"type": "IfcBooleanClippingResult",
|
||||
"operand_type": "IfcHalfSpaceSolid",
|
||||
"matrix": np.eye(4).tolist(),
|
||||
}
|
||||
element, representation = self.setup_profile_represntation(clippings=[clipping])
|
||||
assert len(subject.get_manual_booleans(element)) == 0
|
||||
|
||||
Reference in New Issue
Block a user