mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-16 18:44:47 +00:00
680 lines
28 KiB
Python
680 lines
28 KiB
Python
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
|
#
|
|
# This file is part of BlenderBIM Add-on.
|
|
#
|
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# 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 bpy
|
|
import time
|
|
import json
|
|
import tempfile
|
|
import logging
|
|
import webbrowser
|
|
import ifcopenshell
|
|
import blenderbim.bim.handler
|
|
from . import export_ifc
|
|
from . import schema
|
|
from blenderbim.bim.ifc import IfcStore
|
|
from mathutils import Vector, Matrix, Euler
|
|
from math import radians
|
|
|
|
|
|
class ExportIFC(bpy.types.Operator):
|
|
bl_idname = "export_ifc.bim"
|
|
bl_label = "Export IFC"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
filename_ext = ".ifc"
|
|
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version")
|
|
json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False)
|
|
|
|
def invoke(self, context, event):
|
|
if not IfcStore.get_file():
|
|
self.report({"ERROR"}, "No IFC project is available for export - create or import a project first.")
|
|
return {"FINISHED"}
|
|
if context.scene.BIMProperties.ifc_file:
|
|
self.filepath = context.scene.BIMProperties.ifc_file
|
|
return self.execute(context)
|
|
if not self.filepath:
|
|
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".ifc")
|
|
WindowManager = context.window_manager
|
|
WindowManager.fileselect_add(self)
|
|
return {"RUNNING_MODAL"}
|
|
|
|
def execute(self, context):
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
def _execute(self, context):
|
|
start = time.time()
|
|
logger = logging.getLogger("ExportIFC")
|
|
path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log")
|
|
if not os.access(context.scene.BIMProperties.data_dir, os.W_OK):
|
|
path_log = os.path.join(tempfile.mkdtemp(), "process.log")
|
|
logging.basicConfig(
|
|
filename=path_log,
|
|
filemode="a",
|
|
level=logging.DEBUG,
|
|
)
|
|
extension = self.filepath.split(".")[-1]
|
|
if extension == "ifczip":
|
|
output_file = bpy.path.ensure_ext(self.filepath, ".ifczip")
|
|
elif extension == "ifcjson":
|
|
output_file = bpy.path.ensure_ext(self.filepath, ".ifcjson")
|
|
else:
|
|
output_file = bpy.path.ensure_ext(self.filepath, ".ifc")
|
|
|
|
settings = export_ifc.IfcExportSettings.factory(context, output_file, logger)
|
|
settings.json_version = self.json_version
|
|
settings.json_compact = self.json_compact
|
|
|
|
ifc_exporter = export_ifc.IfcExporter(settings)
|
|
settings.logger.info("Starting export")
|
|
ifc_exporter.export()
|
|
settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start))
|
|
print("Export finished in {:.2f} seconds".format(time.time() - start))
|
|
scene = context.scene
|
|
if not scene.DocProperties.ifc_files:
|
|
new = scene.DocProperties.ifc_files.add()
|
|
new.name = output_file
|
|
if not scene.BIMProperties.ifc_file:
|
|
scene.BIMProperties.ifc_file = output_file
|
|
if bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath:
|
|
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
|
|
blenderbim.bim.handler.purge_module_data()
|
|
return {"FINISHED"}
|
|
|
|
|
|
class ImportIFC(bpy.types.Operator):
|
|
bl_idname = "import_ifc.bim"
|
|
bl_label = "Import IFC"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context):
|
|
bpy.ops.bim.load_project('INVOKE_DEFAULT')
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OpenUri(bpy.types.Operator):
|
|
bl_idname = "bim.open_uri"
|
|
bl_label = "Open URI"
|
|
uri: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
webbrowser.open(self.uri)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SelectIfcFile(bpy.types.Operator):
|
|
bl_idname = "bim.select_ifc_file"
|
|
bl_label = "Select IFC File"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
|
|
|
def execute(self, context):
|
|
if os.path.exists(self.filepath) and "ifc" in os.path.splitext(self.filepath)[1]:
|
|
context.scene.BIMProperties.ifc_file = self.filepath
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
context.window_manager.fileselect_add(self)
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
class SelectDataDir(bpy.types.Operator):
|
|
bl_idname = "bim.select_data_dir"
|
|
bl_label = "Select Data Directory"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
|
|
def execute(self, context):
|
|
context.scene.BIMProperties.data_dir = os.path.dirname(self.filepath)
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
context.window_manager.fileselect_add(self)
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
class SelectSchemaDir(bpy.types.Operator):
|
|
bl_idname = "bim.select_schema_dir"
|
|
bl_label = "Select Schema Directory"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
|
|
def execute(self, context):
|
|
context.scene.BIMProperties.schema_dir = os.path.dirname(self.filepath)
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
context.window_manager.fileselect_add(self)
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
class OpenUpstream(bpy.types.Operator):
|
|
bl_idname = "bim.open_upstream"
|
|
bl_label = "Open Upstream Reference"
|
|
page: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
if self.page == "home":
|
|
webbrowser.open("https://blenderbim.org/")
|
|
elif self.page == "docs":
|
|
webbrowser.open("https://blenderbim.org/docs/")
|
|
elif self.page == "wiki":
|
|
webbrowser.open("https://wiki.osarch.org/index.php?title=Category:BlenderBIM_Add-on")
|
|
elif self.page == "community":
|
|
webbrowser.open("https://community.osarch.org/")
|
|
return {"FINISHED"}
|
|
|
|
|
|
class AddSectionPlane(bpy.types.Operator):
|
|
bl_idname = "bim.add_section_plane"
|
|
bl_label = "Add Temporary Section Cutaway"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context):
|
|
obj = self.create_section_obj(context)
|
|
if not self.has_section_override_node():
|
|
self.create_section_compare_node()
|
|
self.create_section_override_node(obj, context)
|
|
else:
|
|
self.append_obj_to_section_override_node(obj)
|
|
self.add_default_material_if_none_exists(context)
|
|
self.override_materials()
|
|
return {"FINISHED"}
|
|
|
|
def create_section_obj(self, context):
|
|
section = bpy.data.objects.new("Section", None)
|
|
section.empty_display_type = "SINGLE_ARROW"
|
|
section.empty_display_size = 5
|
|
section.show_in_front = True
|
|
if (
|
|
context.active_object
|
|
and context.active_object.select_get()
|
|
and isinstance(context.active_object.data, bpy.types.Camera)
|
|
):
|
|
section.matrix_world = (
|
|
context.active_object.matrix_world @ Euler((radians(180.0), 0.0, 0.0), "XYZ").to_matrix().to_4x4()
|
|
)
|
|
else:
|
|
section.rotation_euler = Euler((radians(180.0), 0.0, 0.0), "XYZ")
|
|
section.location = context.scene.cursor.location
|
|
collection = bpy.data.collections.get("Sections")
|
|
if not collection:
|
|
collection = bpy.data.collections.new("Sections")
|
|
context.scene.collection.children.link(collection)
|
|
collection.objects.link(section)
|
|
return section
|
|
|
|
def has_section_override_node(self):
|
|
return bpy.data.node_groups.get("Section Override")
|
|
|
|
def create_section_compare_node(self):
|
|
group = bpy.data.node_groups.new("Section Compare", type="ShaderNodeTree")
|
|
group_input = group.nodes.new(type="NodeGroupInput")
|
|
group_output = group.nodes.new(type="NodeGroupOutput")
|
|
separate_xyz_a = group.nodes.new(type="ShaderNodeSeparateXYZ")
|
|
separate_xyz_b = group.nodes.new(type="ShaderNodeSeparateXYZ")
|
|
gt_a = group.nodes.new(type="ShaderNodeMath")
|
|
gt_a.operation = "GREATER_THAN"
|
|
gt_a.inputs[1].default_value = 0
|
|
gt_b = group.nodes.new(type="ShaderNodeMath")
|
|
gt_b.operation = "GREATER_THAN"
|
|
gt_b.inputs[1].default_value = 0
|
|
add = group.nodes.new(type="ShaderNodeMath")
|
|
compare = group.nodes.new(type="ShaderNodeMath")
|
|
compare.operation = "COMPARE"
|
|
compare.inputs[1].default_value = 2
|
|
group.links.new(group_input.outputs[""], separate_xyz_a.inputs[0])
|
|
group.links.new(group_input.outputs[""], separate_xyz_b.inputs[0])
|
|
group.links.new(separate_xyz_a.outputs[2], gt_a.inputs[0])
|
|
group.links.new(separate_xyz_b.outputs[2], gt_b.inputs[0])
|
|
group.links.new(gt_a.outputs[0], add.inputs[0])
|
|
group.links.new(gt_b.outputs[0], add.inputs[1])
|
|
group.links.new(add.outputs[0], compare.inputs[0])
|
|
group.links.new(compare.outputs[0], group_output.inputs[""])
|
|
|
|
def create_section_override_node(self, obj, context):
|
|
group = bpy.data.node_groups.new("Section Override", type="ShaderNodeTree")
|
|
|
|
group_input = group.nodes.new(type="NodeGroupInput")
|
|
group_output = group.nodes.new(type="NodeGroupOutput")
|
|
|
|
backfacing = group.nodes.new(type="ShaderNodeNewGeometry")
|
|
backfacing_mix = group.nodes.new(type="ShaderNodeMixShader")
|
|
emission = group.nodes.new(type="ShaderNodeEmission")
|
|
emission.inputs[0].default_value = list(context.scene.BIMProperties.section_plane_colour) + [1]
|
|
|
|
group.links.new(backfacing.outputs["Backfacing"], backfacing_mix.inputs[0])
|
|
group.links.new(group_input.outputs[""], backfacing_mix.inputs[1])
|
|
group.links.new(emission.outputs["Emission"], backfacing_mix.inputs[2])
|
|
|
|
transparent = group.nodes.new(type="ShaderNodeBsdfTransparent")
|
|
section_mix = group.nodes.new(type="ShaderNodeMixShader")
|
|
section_mix.name = "Section Mix"
|
|
|
|
group.links.new(transparent.outputs["BSDF"], section_mix.inputs[1])
|
|
group.links.new(backfacing_mix.outputs["Shader"], section_mix.inputs[2])
|
|
|
|
group.links.new(section_mix.outputs["Shader"], group_output.inputs[""])
|
|
|
|
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
|
|
cut_obj.object = obj
|
|
section_compare = group.nodes.new(type="ShaderNodeGroup")
|
|
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
|
|
section_compare.name = "Last Section Compare"
|
|
value = group.nodes.new(type="ShaderNodeValue")
|
|
value.name = "Mock Section"
|
|
group.links.new(cut_obj.outputs["Object"], section_compare.inputs[0])
|
|
group.links.new(value.outputs[0], section_compare.inputs[1])
|
|
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
|
|
|
|
def append_obj_to_section_override_node(self, obj):
|
|
group = bpy.data.node_groups.get("Section Override")
|
|
cut_obj = group.nodes.new(type="ShaderNodeTexCoord")
|
|
cut_obj.object = obj
|
|
section_compare = group.nodes.new(type="ShaderNodeGroup")
|
|
section_compare.node_tree = bpy.data.node_groups.get("Section Compare")
|
|
|
|
last_compare = group.nodes.get("Last Section Compare")
|
|
last_compare.name = "Section Compare"
|
|
mock_section = group.nodes.get("Mock Section")
|
|
section_mix = group.nodes.get("Section Mix")
|
|
|
|
group.links.new(last_compare.outputs[0], section_compare.inputs[0])
|
|
group.links.new(mock_section.outputs[0], section_compare.inputs[1])
|
|
group.links.new(cut_obj.outputs["Object"], last_compare.inputs[1])
|
|
group.links.new(section_compare.outputs[0], section_mix.inputs[0])
|
|
|
|
section_compare.name = "Last Section Compare"
|
|
|
|
def add_default_material_if_none_exists(self, context):
|
|
material = bpy.data.materials.get("Section Override")
|
|
if not material:
|
|
material = bpy.data.materials.new("Section Override")
|
|
material.use_nodes = True
|
|
|
|
if context.scene.BIMProperties.should_section_selected_objects:
|
|
objects = list(context.selected_objects)
|
|
else:
|
|
objects = list(context.visible_objects)
|
|
|
|
for obj in objects:
|
|
aggregate = obj.instance_collection
|
|
if aggregate and "IfcRelAggregates/" in aggregate.name:
|
|
for part in aggregate.objects:
|
|
objects.append(part)
|
|
if not (obj.data and hasattr(obj.data, "materials") and obj.data.materials and obj.data.materials[0]):
|
|
if obj.data and hasattr(obj.data, "materials"):
|
|
if len(obj.material_slots):
|
|
obj.material_slots[0].material = material
|
|
else:
|
|
obj.data.materials.append(material)
|
|
|
|
def override_materials(self):
|
|
override = bpy.data.node_groups.get("Section Override")
|
|
for material in bpy.data.materials:
|
|
material.use_nodes = True
|
|
if material.node_tree.nodes.get("Section Override"):
|
|
continue
|
|
material.blend_method = "HASHED"
|
|
material.shadow_method = "HASHED"
|
|
material_output = self.get_node(material.node_tree.nodes, "OUTPUT_MATERIAL")
|
|
if not material_output:
|
|
continue
|
|
from_socket = material_output.inputs[0].links[0].from_socket
|
|
section_override = material.node_tree.nodes.new(type="ShaderNodeGroup")
|
|
section_override.name = "Section Override"
|
|
section_override.node_tree = override
|
|
material.node_tree.links.new(from_socket, section_override.inputs[0])
|
|
material.node_tree.links.new(section_override.outputs[0], material_output.inputs[0])
|
|
|
|
def get_node(self, nodes, node_type):
|
|
for node in nodes:
|
|
if node.type == node_type:
|
|
return node
|
|
|
|
|
|
class RemoveSectionPlane(bpy.types.Operator):
|
|
bl_idname = "bim.remove_section_plane"
|
|
bl_label = "Remove Temporary Section Cutaway"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object and bpy.data.node_groups.get("Section Override")
|
|
|
|
def execute(self, context):
|
|
name = context.active_object.name
|
|
section_override = bpy.data.node_groups.get("Section Override")
|
|
for node in section_override.nodes:
|
|
if node.type != "TEX_COORD" or node.object.name != name:
|
|
continue
|
|
section_compare = node.outputs["Object"].links[0].to_node
|
|
# If the tex coord links to section_compare.inputs[1], it is called 'Input_3'
|
|
if node.outputs["Object"].links[0].to_socket.identifier == "Input_3":
|
|
section_override.links.new(
|
|
section_compare.inputs[0].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
|
|
)
|
|
else: # If it links to section_compare.inputs[0]
|
|
if section_compare.inputs[1].links[0].from_node.name == "Mock Section":
|
|
# Then it is the very last section. Purge everything.
|
|
self.purge_all_section_data(context)
|
|
return {"FINISHED"}
|
|
section_override.links.new(
|
|
section_compare.inputs[1].links[0].from_socket, section_compare.outputs[0].links[0].to_socket
|
|
)
|
|
section_override.nodes.remove(section_compare)
|
|
section_override.nodes.remove(node)
|
|
|
|
old_last_compare = section_override.nodes.get("Last Section Compare")
|
|
old_last_compare.name = "Section Compare"
|
|
section_mix = section_override.nodes.get("Section Mix")
|
|
new_last_compare = section_mix.inputs[0].links[0].from_node
|
|
new_last_compare.name = "Last Section Compare"
|
|
bpy.ops.object.delete({"selected_objects": [context.active_object]})
|
|
return {"FINISHED"}
|
|
|
|
def purge_all_section_data(self, context):
|
|
bpy.data.materials.remove(bpy.data.materials.get("Section Override"))
|
|
for material in bpy.data.materials:
|
|
if not material.node_tree:
|
|
continue
|
|
override = material.node_tree.nodes.get("Section Override")
|
|
if not override:
|
|
continue
|
|
material.node_tree.links.new(
|
|
override.inputs[0].links[0].from_socket, override.outputs[0].links[0].to_socket
|
|
)
|
|
material.node_tree.nodes.remove(override)
|
|
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Override"))
|
|
bpy.data.node_groups.remove(bpy.data.node_groups.get("Section Compare"))
|
|
bpy.ops.object.delete({"selected_objects": [context.active_object]})
|
|
|
|
|
|
class ReloadIfcFile(bpy.types.Operator):
|
|
bl_idname = "bim.reload_ifc_file"
|
|
bl_label = "Reload IFC File"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context):
|
|
# TODO: reimplement. See #1222.
|
|
return {"FINISHED"}
|
|
|
|
|
|
class AddIfcFile(bpy.types.Operator):
|
|
bl_idname = "bim.add_ifc_file"
|
|
bl_label = "Add IFC File"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context):
|
|
context.scene.DocProperties.ifc_files.add()
|
|
return {"FINISHED"}
|
|
|
|
|
|
class RemoveIfcFile(bpy.types.Operator):
|
|
bl_idname = "bim.remove_ifc_file"
|
|
bl_label = "Remove IFC File"
|
|
index: bpy.props.IntProperty()
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context):
|
|
context.scene.DocProperties.ifc_files.remove(self.index)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SetOverrideColour(bpy.types.Operator):
|
|
bl_idname = "bim.set_override_colour"
|
|
bl_label = "Set Override Colour"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.selected_objects
|
|
|
|
def execute(self, context):
|
|
for obj in context.selected_objects:
|
|
obj.color = context.scene.BIMProperties.override_colour
|
|
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
|
area.spaces[0].shading.color_type = "OBJECT"
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SetViewportShadowFromSun(bpy.types.Operator):
|
|
bl_idname = "bim.set_viewport_shadow_from_sun"
|
|
bl_label = "Set Viewport Shadow from Sun"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object
|
|
|
|
def execute(self, context):
|
|
# The vector used for the light direction is a bit funny
|
|
mat = Matrix(((-1.0, 0.0, 0.0, 0.0), (0.0, 0, 1.0, 0.0), (-0.0, -1.0, 0, 0.0), (0.0, 0.0, 0.0, 1.0)))
|
|
context.scene.display.light_direction = mat.inverted() @ (
|
|
context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
|
)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SnapSpacesTogether(bpy.types.Operator):
|
|
bl_idname = "bim.snap_spaces_together"
|
|
bl_label = "Snap Spaces Together"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.selected_objects
|
|
|
|
def execute(self, context):
|
|
threshold = 0.5
|
|
processed_polygons = set()
|
|
selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
|
|
for obj in selected_mesh_objects:
|
|
for polygon in obj.data.polygons:
|
|
center = obj.matrix_world @ polygon.center
|
|
distance = None
|
|
for obj2 in selected_mesh_objects:
|
|
if obj2 == obj:
|
|
continue
|
|
result = obj2.ray_cast(obj2.matrix_world.inverted() @ center, polygon.normal, distance=threshold)
|
|
if not result[0]:
|
|
continue
|
|
hit = obj2.matrix_world @ result[1]
|
|
distance = (hit - center).length / 2
|
|
if distance < 0.01:
|
|
distance = None
|
|
break
|
|
|
|
if (obj2.name, result[3]) in processed_polygons:
|
|
distance *= 2
|
|
continue
|
|
|
|
offset = polygon.normal * distance * -1
|
|
processed_polygons.add((obj2.name, result[3]))
|
|
for v in obj2.data.polygons[result[3]].vertices:
|
|
obj2.data.vertices[v].co += offset
|
|
break
|
|
if distance:
|
|
offset = polygon.normal * distance
|
|
processed_polygons.add((obj.name, polygon.index))
|
|
for v in polygon.vertices:
|
|
obj.data.vertices[v].co += offset
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SelectExternalMaterialDir(bpy.types.Operator):
|
|
bl_idname = "bim.select_external_material_dir"
|
|
bl_label = "Select Material File"
|
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
|
|
|
def execute(self, context):
|
|
# TODO: this is dead code, awaiting reimplementation. See #1222.
|
|
context.active_object.active_material.BIMMaterialProperties.location = self.filepath
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
context.window_manager.fileselect_add(self)
|
|
return {"RUNNING_MODAL"}
|
|
|
|
|
|
class FetchExternalMaterial(bpy.types.Operator):
|
|
bl_idname = "bim.fetch_external_material"
|
|
bl_label = "Fetch External Material"
|
|
|
|
def execute(self, context):
|
|
# TODO: this is dead code, awaiting reimplementation. See #1222.
|
|
location = context.active_object.active_material.BIMMaterialProperties.location
|
|
if location[-6:] != ".mpass":
|
|
return {"FINISHED"}
|
|
if not os.path.isabs(location):
|
|
location = os.path.join(context.scene.BIMProperties.data_dir, location)
|
|
with open(location) as f:
|
|
self.material_pass = json.load(f)
|
|
if context.scene.render.engine == "BLENDER_EEVEE" and "eevee" in self.material_pass:
|
|
self.fetch_eevee_or_cycles("eevee", context)
|
|
elif context.scene.render.engine == "CYCLES" and "cycles" in self.material_pass:
|
|
self.fetch_eevee_or_cycles("cycles", context)
|
|
return {"FINISHED"}
|
|
|
|
def fetch_eevee_or_cycles(self, name, context):
|
|
identification = context.active_object.active_material.BIMMaterialProperties.identification
|
|
uri = self.material_pass[name]["uri"]
|
|
if not os.path.isabs(uri):
|
|
uri = os.path.join(context.scene.BIMProperties.data_dir, uri)
|
|
bpy.ops.wm.link(filename=identification, directory=os.path.join(uri, "Material"))
|
|
for material in bpy.data.materials:
|
|
if material.name == identification and material.library:
|
|
context.active_object.material_slots[0].material = material
|
|
return
|
|
|
|
|
|
class FetchObjectPassport(bpy.types.Operator):
|
|
bl_idname = "bim.fetch_object_passport"
|
|
bl_label = "Fetch Object Passport"
|
|
|
|
def execute(self, context):
|
|
# TODO: this is dead code, awaiting reimplementation. See #1222.
|
|
for reference in context.active_object.BIMObjectProperties.document_references:
|
|
reference = context.scene.BIMProperties.document_references[reference.name]
|
|
if reference.location[-6:] == ".blend":
|
|
self.fetch_blender(reference, context)
|
|
return {"FINISHED"}
|
|
|
|
def fetch_blender(self, reference, context):
|
|
bpy.ops.wm.link(filename=reference.name, directory=os.path.join(reference.location, "Mesh"))
|
|
context.active_object.data = bpy.data.meshes[reference.name]
|
|
|
|
|
|
class CopyPropertyToSelection(bpy.types.Operator):
|
|
bl_idname = "bim.copy_property_to_selection"
|
|
bl_label = "Copy Property To Selection"
|
|
pset_name: bpy.props.StringProperty()
|
|
prop_name: bpy.props.StringProperty()
|
|
prop_value: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
# TODO: this is dead code, awaiting reimplementation. See #1222.
|
|
for obj in context.selected_objects:
|
|
if "/" not in obj.name:
|
|
continue
|
|
pset = obj.BIMObjectProperties.psets.get(self.pset_name)
|
|
if not pset:
|
|
applicable_psets = schema.ifc.psetqto.get_applicable(obj.name.split("/")[0], pset_only=True)
|
|
for pset_template in applicable_psets:
|
|
if pset_template.Name == self.pset_name:
|
|
break
|
|
else:
|
|
continue
|
|
pset = obj.BIMObjectProperties.psets.add()
|
|
pset.name = self.pset_name
|
|
for template_prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
|
|
prop = pset.properties.add()
|
|
prop.name = template_prop_name
|
|
prop = pset.properties.get(self.prop_name)
|
|
if prop:
|
|
prop.string_value = self.prop_value
|
|
return {"FINISHED"}
|
|
|
|
|
|
class CopyAttributeToSelection(bpy.types.Operator):
|
|
bl_idname = "bim.copy_attribute_to_selection"
|
|
bl_label = "Copy Attribute To Selection"
|
|
attribute_name: bpy.props.StringProperty()
|
|
attribute_value: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
# TODO: this is dead code, awaiting reimplementation. See #1222.
|
|
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(context.scene.BIMProperties.export_schema)
|
|
self.applicable_attributes_cache = {}
|
|
for obj in context.selected_objects:
|
|
if "/" not in obj.name:
|
|
continue
|
|
attribute = obj.BIMObjectProperties.attributes.get(self.attribute_name)
|
|
if not attribute:
|
|
applicable_attributes = self.get_applicable_attributes(obj.name.split("/")[0])
|
|
if self.attribute_name not in applicable_attributes:
|
|
continue
|
|
attribute = obj.BIMObjectProperties.attributes.add()
|
|
attribute.name = self.attribute_name
|
|
attribute.string_value = self.attribute_value
|
|
return {"FINISHED"}
|
|
|
|
def get_applicable_attributes(self, ifc_class):
|
|
if ifc_class not in self.applicable_attributes_cache:
|
|
self.applicable_attributes_cache[ifc_class] = [
|
|
a.name() for a in self.schema.declaration_by_name(ifc_class).all_attributes()
|
|
]
|
|
return self.applicable_attributes_cache[ifc_class]
|
|
|
|
|
|
class OverrideDelete(bpy.types.Operator):
|
|
bl_idname = "object.delete"
|
|
bl_label = "Delete"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object is not None
|
|
|
|
def execute(self, context):
|
|
if IfcStore.get_file():
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
for obj in context.selected_objects:
|
|
bpy.data.objects.remove(obj)
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_confirm(self, event)
|
|
|
|
def _execute(self, context):
|
|
for obj in context.selected_objects:
|
|
if obj.BIMObjectProperties.ifc_definition_id:
|
|
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
|
if element.is_a("IfcOpeningElement"):
|
|
self.delete_opening_element(element)
|
|
elif element.HasOpenings:
|
|
for rel in element.HasOpenings:
|
|
self.delete_opening_element(rel.RelatedOpeningElement)
|
|
bpy.data.objects.remove(obj)
|
|
return {"FINISHED"}
|
|
|
|
def delete_opening_element(self, element):
|
|
obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id())
|
|
bpy.ops.bim.remove_opening(opening_id=element.id(), obj=obj.name)
|