Merge branch 'v0.7.0' into covering_tool

This commit is contained in:
Massimo Fabbro
2023-10-05 21:23:11 +02:00
committed by GitHub
49 changed files with 1307 additions and 327 deletions
+1 -1
View File
@@ -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):
+1 -1
View File
@@ -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)
+9 -1
View File
@@ -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)
+2 -3
View File
@@ -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
+23 -1
View File
@@ -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()
+5 -54
View File
@@ -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)
+20 -18
View File
@@ -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):
+37 -9
View File
@@ -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