4040
4141import bmesh
4242import bpy
43+ import logging
4344import ifcopenshell
4445import ifcopenshell .api
4546import ifcopenshell .api .document
5051import ifcopenshell .util .element
5152import ifcopenshell .util .representation
5253import ifcopenshell .util .selector
54+ import ifcopenshell .util .shape_builder
5355import ifcopenshell .util .unit
5456import numpy as np
5557import shapely
5961from lxml import etree
6062from mathutils import Color , Matrix , Vector
6163
64+ import bonsai .bim .import_ifc
6265import bonsai .bim .export_ifc
6366import bonsai .bim .handler
6467import bonsai .bim .helper
@@ -138,7 +141,7 @@ def _execute(self, context):
138141 element .ApplicableOccurrence = f"IfcAnnotation/{ object_type } "
139142
140143 if props .create_representation_for_type and object_type == "IMAGE" :
141- bpy .ops .bim .add_reference_image ("INVOKE_DEFAULT" , use_existing_object_by_name = obj .name )
144+ bpy .ops .bim .add_reference_image ("INVOKE_DEFAULT" , existing_object_by_name = obj .name )
142145
143146
144147class EnableAddAnnotationType (bpy .types .Operator ):
@@ -1759,7 +1762,7 @@ def _execute(self, context):
17591762 enable_editing = True ,
17601763 )
17611764 if props .object_type == "IMAGE" :
1762- bpy .ops .bim .add_reference_image ("INVOKE_DEFAULT" , use_existing_object_by_name = obj .name )
1765+ bpy .ops .bim .add_reference_image ("INVOKE_DEFAULT" , existing_object_by_name = obj .name )
17631766
17641767
17651768class AddSheet (bpy .types .Operator , tool .Ifc .Operator ):
@@ -3802,27 +3805,70 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
38023805 use_relative_path : bpy .props .BoolProperty (name = "Use Relative Path" , default = True )
38033806 filter_image : bpy .props .BoolProperty (default = True , options = {"HIDDEN" , "SKIP_SAVE" })
38043807 filter_folder : bpy .props .BoolProperty (default = True , options = {"HIDDEN" , "SKIP_SAVE" })
3805-
3806- override_existing_image : bpy .props .BoolProperty (
3807- name = "Override Existing Image" ,
3808- default = True ,
3809- description = (
3810- "Override image if it was previously loaded to Blender. If disabled, will always create a new image"
3811- ),
3808+ x_length : bpy .props .FloatProperty (
3809+ name = "X Length" ,
3810+ description = "Width of the reference image" ,
3811+ default = 1.0 ,
3812+ min = 0.001 ,
3813+ soft_min = 0.01 ,
3814+ precision = 3 ,
3815+ unit = "LENGTH" ,
38123816 )
3813- use_existing_object_by_name : bpy .props .StringProperty (
3814- name = "Use Existing Object By Name" ,
3815- description = "Existing object name to add a style with reference image to. If not provided will create a new object." ,
3816- options = {"SKIP_SAVE" },
3817+ y_length : bpy .props .FloatProperty (
3818+ name = "Y Length" ,
3819+ description = "Height of the reference image" ,
3820+ default = 1.0 ,
3821+ min = 0.001 ,
3822+ soft_min = 0.01 ,
3823+ precision = 3 ,
3824+ unit = "LENGTH" ,
38173825 )
3818- size : bpy .props .FloatProperty (name = "Size" , description = "Size of the reference image" , default = 1.0 , unit = "LENGTH" )
3826+
3827+ @classmethod
3828+ def poll (cls , context ):
3829+ if not tool .Ifc .get ():
3830+ cls .poll_message_set ("No IFC project is loaded." )
3831+ return False
3832+ return True
3833+
3834+ def invoke (self , context , event ):
3835+ self ._last_filepath = ""
3836+ return super ().invoke (context , event )
3837+
3838+ def check (self , context ):
3839+ if not hasattr (self , "_last_filepath" ):
3840+ self ._last_filepath = ""
3841+
3842+ if self .filepath and self .filepath != self ._last_filepath :
3843+ self ._last_filepath = self .filepath
3844+
3845+ abs_path = Path (self .filepath ).absolute ().resolve ()
3846+ if abs_path .exists () and abs_path .is_file ():
3847+ image = load_image (abs_path .name , str (abs_path .parent ), check_existing = False )
3848+ image_width_px = image .size [0 ]
3849+ image_height_px = image .size [1 ]
3850+ aspect_ratio = image_width_px / image_height_px
3851+
3852+ if aspect_ratio >= 1.0 :
3853+ self .x_length = 1.0
3854+ self .y_length = 1.0 / aspect_ratio
3855+ else :
3856+ self .x_length = aspect_ratio
3857+ self .y_length = 1.0
3858+
3859+ bpy .data .images .remove (image )
3860+ return True
3861+
3862+ return False
38193863
38203864 def draw (self , context ):
3865+ layout = self .layout
38213866 if Path (tool .Ifc .get_path ()).is_file ():
3822- self .layout .prop (self , "use_relative_path" )
3823- self .layout .prop (self , "override_existing_image" )
3824- self .layout .prop (self , "use_existing_object_by_name" )
3825- self .layout .prop (self , "size" )
3867+ layout .prop (self , "use_relative_path" )
3868+ else :
3869+ self .use_relative_path = False
3870+ layout .prop (self , "x_length" )
3871+ layout .prop (self , "y_length" )
38263872
38273873 def _execute (self , context ):
38283874 space = tool .Blender .get_view3d_space ()
@@ -3837,127 +3883,66 @@ def _execute(self, context):
38373883 image_filepath = Path (tool .Ifc .get_uri (self .filepath , use_relative_path = self .use_relative_path ))
38383884 ifc_file = tool .Ifc .get ()
38393885
3840- if self .override_existing_image :
3841- params = {"check_existing" : True , "force_reload" : True }
3842- else :
3843- params = {"check_existing" : False }
3886+ params = {"check_existing" : False }
38443887 image = load_image (abs_path .name , str (abs_path .parent ), ** params )
38453888
3846- aspect_ratio = image .size [0 ] / image .size [1 ]
3847- if aspect_ratio >= 1.0 : # Landscape
3848- x_length = self .size
3849- y_length = self .size / aspect_ratio
3850- else :
3851- x_length = self .size / aspect_ratio
3852- y_length = self .size
3853-
3854- def bm_add_image_plane (mesh ):
3855- bm = tool .Blender .get_bmesh_for_mesh (mesh , clean = True )
3856-
3857- unit_scale = ifcopenshell .util .unit .calculate_unit_scale (ifc_file )
3858- plane_scale = Vector ((x_length / 2.0 , y_length / 2.0 , 1.0 ))
3859- matrix = Matrix .LocRotScale (None , None , plane_scale )
3860- bmesh .ops .create_grid (bm , x_segments = 1 , y_segments = 1 , size = 1 , matrix = matrix , calc_uvs = False )
3861-
3862- if not bm .loops .layers .uv :
3863- uv_layer = bm .loops .layers .uv .new ()
3864- else :
3865- uv_layer = bm .loops .layers .uv .active
3866-
3867- min_x = min (v .co .x for v in bm .verts )
3868- max_x = max (v .co .x for v in bm .verts )
3869- min_y = min (v .co .y for v in bm .verts )
3870- max_y = max (v .co .y for v in bm .verts )
3871-
3872- width = max_x - min_x
3873- height = max_y - min_y
3874-
3875- for face in bm .faces :
3876- for loop in face .loops :
3877- vert = loop .vert
3878- u = (vert .co .x - min_x ) / width if width > 0 else 0.5
3879- v = (vert .co .y - min_y ) / height if height > 0 else 0.5
3880-
3881- u = max (0.0 , min (1.0 , u ))
3882- v = max (0.0 , min (1.0 , v ))
3883- loop [uv_layer ].uv = (u , v )
3889+ mesh = bpy .data .meshes .new (image_filepath .stem )
3890+ obj = bpy .data .objects .new (image_filepath .stem , mesh )
3891+ element = tool .Drawing .run_root_assign_class (
3892+ obj = obj , ifc_class = "IfcAnnotation" , predefined_type = "IMAGE" , should_add_representation = False
3893+ )
38843894
3885- tool .Blender .apply_bmesh (mesh , bm )
3895+ builder = ifcopenshell .util .shape_builder .ShapeBuilder (ifc_file )
3896+ unit_scale = ifcopenshell .util .unit .calculate_unit_scale (ifc_file )
3897+ hx = self .x_length * 0.5 / unit_scale
3898+ hy = self .y_length * 0.5 / unit_scale
3899+ verts = [(- hx , - hy , 0.0 ), ( hx , - hy , 0.0 ), ( hx , hy , 0.0 ), (- hx , hy , 0.0 )]
3900+ item = builder .mesh (verts , [[0 , 1 , 2 , 3 ]])
38863901
3887- if self .use_existing_object_by_name :
3888- obj = bpy .data .objects [self .use_existing_object_by_name ]
3889- bm_add_image_plane (obj .data )
3890- bpy .ops .bim .update_representation (obj = obj .name , ifc_representation_class = "" )
3891- else :
3892- temp_mesh = bpy .data .meshes .new ("temp_mesh" )
3893- bm_add_image_plane (temp_mesh )
3894- obj = bpy .data .objects .new (image_filepath .stem , temp_mesh )
3895- tool .Drawing .run_root_assign_class (
3896- obj = obj ,
3897- ifc_class = "IfcAnnotation" ,
3898- predefined_type = "IMAGE" ,
3899- should_add_representation = True ,
3900- context = ifcopenshell .util .representation .get_context (ifc_file , "Model" , "Body" , "MODEL_VIEW" ),
3901- ifc_representation_class = None ,
3902- )
3903- tool .Blender .remove_data_block (temp_mesh )
3902+ ifc_context = ifcopenshell .util .representation .get_context (ifc_file , "Model" , "Body" , "MODEL_VIEW" )
3903+ representation = builder .get_representation (ifc_context , [item ])
3904+ ifcopenshell .api .geometry .assign_representation (ifc_file , element , representation )
39043905
3905- element = tool .Ifc .get_entity (obj )
3906- if element and isinstance (obj .data , bpy .types .Mesh ):
3907- representation = ifcopenshell .util .representation .get_representation (element , "Model" , "Body" , "MODEL_VIEW" )
3908- if representation and representation .Items :
3909- item_id = representation .Items [0 ].id ()
3910- num_faces = len (obj .data .polygons )
3911- obj .data ["ios_item_ids" ] = [item_id ] * num_faces
3912- tool .Blender .Attribute .fill_attribute (obj .data , "ios_item_ids" , "FACE" , "INT" , [item_id ] * num_faces )
3913-
3914- for item in representation .Items :
3915- if item .is_a ("IfcPolygonalFaceSet" ) and item .Coordinates :
3916- new_coords = []
3917- for vertex in obj .data .vertices :
3918- co = obj .matrix_world @ vertex .co
3919- new_coords .append ([co .x , co .y , co .z ])
3920- item .Coordinates .CoordList = new_coords
3921-
3922- tool .Blender .set_active_object (obj )
3923-
3924- material = bpy .data .materials .new (name = image_filepath .stem )
3925- obj .data .materials .append (None ) # new slot
3926- obj .material_slots [0 ].material = material
3927- bpy .ops .bim .add_style ()
3928-
3929- style = tool .Ifc .get_entity (material )
3930- assert style
3931- tool .Style .assign_style_to_object (style , obj )
3906+ style = ifcopenshell .api .style .add_style (tool .Ifc .get (), name = image_filepath .stem )
3907+ ifcopenshell .api .style .assign_representation_styles (
3908+ ifc_file , shape_representation = representation , styles = [style ]
3909+ )
39323910
39333911 # TODO: IfcSurfaceStyleRendering is unnecessary here, added it only because
39343912 # we don't support IfcSurfaceStyleWithTextures without Rendering yet
39353913 shading_attributes = {
3936- "SurfaceColour" : {
3937- "Red" : 1.0 ,
3938- "Green" : 1.0 ,
3939- "Blue" : 1.0 ,
3940- },
3914+ "SurfaceColour" : {"Red" : 1.0 , "Green" : 1.0 , "Blue" : 1.0 },
39413915 "Transparency" : 0.0 ,
39423916 "ReflectanceMethod" : "NOTDEFINED" ,
39433917 }
39443918 ifcopenshell .api .style .add_surface_style (
3945- tool .Ifc .get (),
3946- style = style ,
3947- ifc_class = "IfcSurfaceStyleRendering" ,
3948- attributes = shading_attributes ,
3919+ tool .Ifc .get (), style = style , ifc_class = "IfcSurfaceStyleRendering" , attributes = shading_attributes
39493920 )
3950- texture = ifc_file .create_entity ("IfcImageTexture" , Mode = "DIFFUSE" , URLReference = image_filepath .as_posix ())
3921+
3922+ if tool .Ifc .get_schema () == "IFC2X3" :
3923+ texture = ifc_file .create_entity (
3924+ "IfcImageTexture" ,
3925+ RepeatS = True ,
3926+ RepeatT = True ,
3927+ TextureType = "TEXTURE" ,
3928+ UrlReference = image_filepath .as_posix (),
3929+ )
3930+ else :
3931+ texture = ifc_file .create_entity ("IfcImageTexture" , Mode = "DIFFUSE" , URLReference = image_filepath .as_posix ())
3932+ ifc_file .create_entity ("IfcTextureCoordinateGenerator" , Maps = [texture ], Mode = "COORD" )
3933+
39513934 textures = [texture ]
3952- ifc_file .create_entity ("IfcTextureCoordinateGenerator" , Maps = textures , Mode = "COORD" ) # UV map
39533935 ifcopenshell .api .style .add_surface_style (
3954- ifc_file ,
3955- style = style ,
3956- ifc_class = "IfcSurfaceStyleWithTextures" ,
3957- attributes = {"Textures" : textures },
3936+ ifc_file , style = style , ifc_class = "IfcSurfaceStyleWithTextures" , attributes = {"Textures" : textures }
39583937 )
3959- tool .Style .reload_material_from_ifc (material )
3960- tool .Geometry .record_object_materials (obj )
3938+
3939+ logger = logging .getLogger ("ImportIFC" )
3940+ ifc_import_settings = bonsai .bim .import_ifc .IfcImportSettings .factory (bpy .context , None , logger )
3941+ ifc_importer = bonsai .bim .import_ifc .IfcImporter (ifc_import_settings )
3942+ ifc_importer .file = tool .Ifc .get ()
3943+ ifc_importer .create_style (style )
3944+
3945+ bonsai .core .geometry .switch_representation (tool .Ifc , tool .Geometry , obj = obj , representation = representation )
39613946
39623947
39633948class ConvertSVGToDXF (bpy .types .Operator ):
0 commit comments