From cb800814730218bb5439a7cad5a1614ea6fc4f31 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 1 Aug 2024 13:42:03 +0500 Subject: [PATCH] fix covering test and fix bug adding coverings in non-meters projects Noticed that adding a covering in millimeters project didn't worked - the reason was that shapely was using tolerance in ifc project units when get_obj_base_points was returning points in si (blender always operates in meters internally). I've changed get_obj_base_points to return data in project units and get_bmesh_from_polygon to convert it back to meters for Blender. It's probably would be better to always operate in si until we need to save data to ifc but I'm not that familiar with that module to just change it everywhere. Ping @maxfb87 just in case. --- src/blenderbim/blenderbim/tool/spatial.py | 40 +++++++++---------- .../test/bim/feature/covering.feature | 33 ++++++++++++++- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index f7bffc8fbd..674a95ba60 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -490,13 +490,15 @@ class Spatial(blenderbim.core.tool.Spatial): selected_objects = bpy.context.selected_objects boundary_elements = cls.get_boundary_elements(selected_objects) polys = cls.get_polygons(boundary_elements) - converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) + converted_tolerance = cls.get_converted_tolerance(tolerance_si=0.03) union = shapely.ops.unary_union(polys).buffer( converted_tolerance, cap_style=shapely.constructive.BufferCapStyle.flat, join_style=shapely.constructive.BufferJoinStyle.mitre, ) - union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=0.1)) + union = cls.get_purged_inner_holes_poly( + union_geom=union, min_area=cls.get_converted_tolerance(tolerance_si=0.1) + ) return union @classmethod @@ -526,28 +528,20 @@ class Spatial(blenderbim.core.tool.Spatial): @classmethod def get_obj_base_points(cls, obj: bpy.types.Object) -> dict[str, tuple[float, float]]: - x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box] - y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box] + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + bbox_ws = [obj.matrix_world @ Vector(v) / si_conversion for v in obj.bound_box] return { - "low_left": (x_values[0], y_values[0]), - "high_left": (x_values[3], y_values[3]), - "low_right": (x_values[4], y_values[4]), - "high_right": (x_values[7], y_values[7]), + "low_left": (bbox_ws[0].x, bbox_ws[0].y), + "high_left": (bbox_ws[3].x, bbox_ws[3].y), + "low_right": (bbox_ws[4].x, bbox_ws[4].y), + "high_right": (bbox_ws[7].x, bbox_ws[7].y), } @classmethod - def get_converted_tolerance(cls, tolerance: float) -> float: + def get_converted_tolerance(cls, tolerance_si: float) -> float: model = tool.Ifc.get() - project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT") - prefix = getattr(project_unit, "Prefix", None) - - return ifcopenshell.util.unit.convert( - value=tolerance, - from_prefix=None, - from_unit="METRE", - to_prefix=prefix, - to_unit=project_unit.Name, - ) + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(model) + return tolerance_si / si_conversion @classmethod def get_purged_inner_holes_poly(cls, union_geom: Polygon, min_area: float) -> Polygon: @@ -580,7 +574,7 @@ class Spatial(blenderbim.core.tool.Spatial): @classmethod def get_buffered_poly_from_linear_ring(cls, linear_ring: shapely.LinearRing) -> Polygon: poly = Polygon(linear_ring) - converted_tolerance = cls.get_converted_tolerance(tolerance=0.03) + converted_tolerance = cls.get_converted_tolerance(tolerance_si=0.03) poly = poly.buffer( converted_tolerance, single_sided=True, @@ -597,8 +591,10 @@ class Spatial(blenderbim.core.tool.Spatial): bm.edges.index_update() mat_invert = mat.inverted() - - new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]] + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + new_verts = [ + bm.verts.new(mat_invert @ (Vector([v[0], v[1], 0]) * si_conversion)) for v in poly.exterior.coords[0:-1] + ] [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) diff --git a/src/blenderbim/test/bim/feature/covering.feature b/src/blenderbim/test/bim/feature/covering.feature index baa38b21e9..0800e0edb5 100644 --- a/src/blenderbim/test/bim/feature/covering.feature +++ b/src/blenderbim/test/bim/feature/covering.feature @@ -8,7 +8,36 @@ Scenario: Execute generate flooring coverings from walls And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + # 1st wall And I press "bim.add_constr_type_instance" And the object "IfcWall/Wall" is selected - When I press "bim.add_instance_flooring_coverings_from_walls" - Then nothing happens + And I press "bim.change_layer_length(length=3.6)" + # 2nd wall + And the cursor is at "3.6,0.1,3" + And I set "scene.BIMModelProperties.length" to "2.0" + And I press "bim.add_constr_type_instance" + # 3rd wall + And the cursor is at "3.5,2.1,3" + And I set "scene.BIMModelProperties.length" to "3.5" + And I press "bim.add_constr_type_instance" + # 4th wall + And the cursor is at "0,2.0,0" + And I set "scene.BIMModelProperties.length" to "1.9" + And I press "bim.add_constr_type_instance" + # add_instance_flooring_coverings_from_walls is expecting FLOORING predefined type. + And the object "IfcCoveringType/COV30" is selected + And I press "bim.enable_editing_attributes(obj='IfcCoveringType/COV30')" + And I set "active_object.BIMAttributeProperties.attributes[6].enum_value" to "FLOORING" + And I press "bim.edit_attributes(obj='IfcCoveringType/COV30')" + # Run the operator. + When the object "IfcWall/Wall" is selected + And additionally the object "IfcWall/Wall.001" is selected + And additionally the object "IfcWall/Wall.002" is selected + And additionally the object "IfcWall/Wall.003" is selected + And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_instance_flooring_coverings_from_walls" + Then the object "IfcCovering/Covering0" exists + And the object "IfcCovering/Covering0" is at "1.8,1.05,0.0" + And the object "IfcCovering/Covering0" dimensions are "3.4,1.9,0.03"