diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 7ef2a1603b..25965966b3 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -3688,18 +3688,92 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): description="Existing object name to add a style with reference image to. If not provided will create a new object.", options={"SKIP_SAVE"}, ) + + x_length: bpy.props.FloatProperty( + name="X Length", + description="Width of the reference image in project units", + default=1.0, + min=0.001, + soft_min=0.01, + precision=3, + ) + y_length: bpy.props.FloatProperty( + name="Y Length", + description="Height of the reference image in project units", + default=1.0, + min=0.001, + soft_min=0.01, + precision=3, + ) + + _show_dimensions_dialog: bpy.props.BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"}) def draw(self, context): layout = self.layout - if Path(tool.Ifc.get_path()).is_file(): - layout.prop(self, "use_relative_path") + + if getattr(self, '_show_dimensions_dialog', False): + if tool.Ifc.get(): + length_unit = ifcopenshell.util.unit.get_project_unit(tool.Ifc.get(), "LENGTHUNIT") + if length_unit: + unit_name = ifcopenshell.util.unit.get_full_unit_name(length_unit).lower() + else: + unit_name = "project units" + layout.label(text=f"Set Reference Image Dimensions (in {unit_name}):") + else: + layout.label(text="Set Reference Image Dimensions (in project units):") + layout.separator() + layout.prop(self, "x_length") + layout.prop(self, "y_length") else: - self.use_relative_path = False - layout.label(text="Save the .ifc file first ") - layout.label(text="to use relative paths.") - layout.prop(self, "override_existing_image") - layout.prop(self, "use_existing_object_by_name") + if Path(tool.Ifc.get_path()).is_file(): + layout.prop(self, "use_relative_path") + else: + self.use_relative_path = False + layout.label(text="Save the .ifc file first ") + layout.label(text="to use relative paths.") + layout.prop(self, "override_existing_image") + layout.prop(self, "use_existing_object_by_name") + + def invoke(self, context, event): + if not getattr(self, '_show_dimensions_dialog', False): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + else: + return context.window_manager.invoke_props_dialog(self) + def execute(self, context): + if not getattr(self, '_show_dimensions_dialog', False): + abs_path = Path(self.filepath).absolute().resolve() + if self.override_existing_image: + params = {"check_existing": True, "force_reload": True} + else: + params = {"check_existing": False} + + try: + image = load_image(abs_path.name, str(abs_path.parent), **params) + + image_width_px = image.size[0] + image_height_px = image.size[1] + aspect_ratio = image_width_px / image_height_px + + if aspect_ratio >= 1.0: + self.x_length = 1.0 + self.y_length = 1.0 / aspect_ratio + else: + self.x_length = aspect_ratio + self.y_length = 1.0 + + bpy.data.images.remove(image) + + except Exception as e: + self.report({'ERROR'}, f"Failed to load image: {str(e)}") + return {'CANCELLED'} + + self._show_dimensions_dialog = True + return context.window_manager.invoke_props_dialog(self) + + return self._execute(context) + def _execute(self, context): space = tool.Blender.get_view3d_space() if space.shading.color_type != "TEXTURE": @@ -3717,11 +3791,13 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): params = {"check_existing": True, "force_reload": True} else: params = {"check_existing": False} - image = load_image(abs_path.name, abs_path.parent, **params) + image = load_image(abs_path.name, str(abs_path.parent), **params) def bm_add_image_plane(mesh): bm = tool.Blender.get_bmesh_for_mesh(mesh, clean=True) - plane_scale = (Vector(image.size) / min(image.size)).to_3d() + + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) + plane_scale = Vector((self.x_length * unit_scale / 2.0, self.y_length * unit_scale / 2.0, 1.0)) matrix = Matrix.LocRotScale(None, None, plane_scale) bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=1, matrix=matrix, calc_uvs=False) @@ -3730,12 +3806,22 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): else: uv_layer = bm.loops.layers.uv.active - aspect_ratio = image.size[1] / image.size[0] + min_x = min(v.co.x for v in bm.verts) + max_x = max(v.co.x for v in bm.verts) + min_y = min(v.co.y for v in bm.verts) + max_y = max(v.co.y for v in bm.verts) + + width = max_x - min_x + height = max_y - min_y + for face in bm.faces: for loop in face.loops: vert = loop.vert - v = (vert.co.y * 0.5) + 0.5 - u = (vert.co.x * 0.5 * aspect_ratio) + 0.5 + u = (vert.co.x - min_x) / width if width > 0 else 0.5 + v = (vert.co.y - min_y) / height if height > 0 else 0.5 + + u = max(0.0, min(1.0, u)) + v = max(0.0, min(1.0, v)) loop[uv_layer].uv = (u, v) tool.Blender.apply_bmesh(mesh, bm) @@ -3767,6 +3853,14 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): num_faces = len(obj.data.polygons) obj.data["ios_item_ids"] = [item_id] * num_faces tool.Blender.Attribute.fill_attribute(obj.data, "ios_item_ids", "FACE", "INT", [item_id] * num_faces) + + for item in representation.Items: + if item.is_a('IfcPolygonalFaceSet') and item.Coordinates: + new_coords = [] + for vertex in obj.data.vertices: + co = obj.matrix_world @ vertex.co + new_coords.append([co.x, co.y, co.z]) + item.Coordinates.CoordList = new_coords tool.Blender.set_active_object(obj) @@ -3807,6 +3901,8 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): ) tool.Style.reload_material_from_ifc(material) tool.Geometry.record_object_materials(obj) + + return {'FINISHED'} class ConvertSVGToDXF(bpy.types.Operator):