diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index 04aa2eb38b..19335f75c7 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -31,6 +31,7 @@ classes = ( workspace.Hotkey, wall.AlignWall, wall.ChangeExtrusionDepth, + wall.ChangeExtrusionXAngle, wall.ChangeLayerLength, wall.FlipWall, wall.JoinWall, diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index fd8c4a9165..0e726c5b0a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -229,6 +229,7 @@ class BIMModelProperties(PropertyGroup): y: bpy.props.FloatProperty(name="Y", default=0.5) z: bpy.props.FloatProperty(name="Z", default=0.5) rl: bpy.props.FloatProperty(name="RL", default=1) + x_angle: bpy.props.FloatProperty(name="X Angle", default=0) type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page) type_template: bpy.props.EnumProperty( items=( diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 1865357bca..f80bd64e24 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -34,7 +34,7 @@ import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.pset.data import Data as PsetData from ifcopenshell.api.material.data import Data as MaterialData -from math import pi, degrees +from math import pi, sin, cos, degrees, radians from mathutils import Vector, Matrix @@ -243,6 +243,37 @@ class ChangeExtrusionDepth(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} +class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.change_extrusion_x_angle" + bl_label = "Change Extrusion X Angle" + bl_options = {"REGISTER", "UNDO"} + x_angle: bpy.props.FloatProperty() + + @classmethod + def poll(cls, context): + return context.selected_objects + + def _execute(self, context): + wall_objs = [] + x_angle = radians(self.x_angle) + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + return + representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + if not representation: + return + extrusion = tool.Model.get_extrusion(representation) + if not extrusion: + return + extrusion.ExtrudedDirection.DirectionRatios = (0.0, sin(x_angle), cos(x_angle)) + if element.is_a("IfcWall"): + wall_objs.append(obj) + if wall_objs: + DumbWallRecalculator().recalculate(wall_objs) + return {"FINISHED"} + + class ChangeLayerLength(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.change_layer_length" bl_label = "Change Layer Length" @@ -419,6 +450,7 @@ class DumbWallGenerator: self.length = props.length * self.unit_scale self.rotation = 0.0 self.location = Vector((0, 0, 0)) + self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle) if self.has_sketch(): return # For now @@ -571,6 +603,7 @@ class DumbWallGenerator: offset=self.layers["offset"], length=self.length, height=self.height, + x_angle=self.x_angle, ) ifcopenshell.api.run( "geometry.assign_representation", tool.Ifc.get(), product=element, representation=representation @@ -993,7 +1026,9 @@ class DumbWallJoiner: axis = body = tool.Model.get_wall_axis(obj)["reference"] self.axis = copy.deepcopy(axis) self.body = copy.deepcopy(body) - height = self.get_height(tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)) + extrusion_data = self.get_extrusion_data(tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)) + height = extrusion_data["height"] + x_angle = extrusion_data["x_angle"] self.clippings = [] layers = tool.Model.get_material_layer_parameters(element) @@ -1045,6 +1080,7 @@ class DumbWallJoiner: context=self.body_context, length=length, height=height, + x_angle=x_angle, offset=layers["offset"], thickness=layers["thickness"], clippings=self.clippings, @@ -1106,18 +1142,21 @@ class DumbWallJoiner: ) ) - def get_height(self, representation): - height = 3.0 + def get_extrusion_data(self, representation): + results = {"height": 3.0, "x_angle": 0} item = representation.Items[0] while True: if item.is_a("IfcExtrudedAreaSolid"): - height = item.Depth * self.unit_scale + results["height"] = item.Depth * self.unit_scale + x, y, z = item.ExtrudedDirection.DirectionRatios + if not tool.Cad.is_x(x, 0) or not tool.Cad.is_x(y, 0) or not tool.Cad.is_x(z, 1): + results["x_angle"] = Vector((0, 1)).angle(Vector((y, z))) break elif item.is_a("IfcBooleanClippingResult"): item = item.FirstOperand else: break - return height + return results def join(self, wall1, wall2, connection1, connection2, is_relating=True, description="BUTT"): element1 = tool.Ifc.get_entity(wall1) diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index dcd02ff06b..fc78bbcfe2 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -101,6 +101,9 @@ class BimToolUI: row = cls.layout.row(align=True) row.prop(data=cls.props, property="length", text="Length") + + row = cls.layout.row(align=True) + row.prop(data=cls.props, property="x_angle", text="X Angle") elif cls.props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"): row = cls.layout.row(align=True) row.prop(data=cls.props, property="cardinal_point", text="Axis") @@ -125,6 +128,11 @@ class BimToolUI: op = row.operator("bim.change_layer_length", icon="FILE_REFRESH", text="") op.length = cls.props.length + row = cls.layout.row(align=True) + row.prop(data=cls.props, property="x_angle", text="X Angle") + op = row.operator("bim.change_extrusion_x_angle", icon="FILE_REFRESH", text="") + op.x_angle = cls.props.x_angle + row = cls.layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_E") diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py index c42ac95a86..20a4e5da52 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . +from math import sin, cos import ifcopenshell.util.unit @@ -28,10 +29,12 @@ class Usecase: "height": 3.0, "offset": 0.0, "thickness": 0.2, + # Sloped walls along the wall's X axis, provided in radians + "x_angle": 0, # Planes are defined as a matrix. The XY plane is the clipping boundary and +Z is removed. # [{"type": "IfcBooleanClippingResult", "operand_type": "IfcHalfSpaceSolid", "matrix": [...]}, {...}] "clippings": [], # A list of planes that define clipping half space solids - "booleans": [], # Any existing IfcBooleanResults + "booleans": [], # Any existing IfcBooleanResults } for key, value in settings.items(): self.settings[key] = value @@ -59,6 +62,12 @@ class Usecase: curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points]) else: curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points), None, False) + if self.settings["x_angle"]: + extrusion_direction = self.file.createIfcDirection( + (0.0, sin(self.settings["x_angle"]), cos(self.settings["x_angle"])) + ) + else: + extrusion_direction = self.file.createIfcDirection((0.0, 0.0, 1.0)) extrusion = self.file.createIfcExtrudedAreaSolid( self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve), self.file.createIfcAxis2Placement3D( @@ -66,7 +75,7 @@ class Usecase: self.file.createIfcDirection((0.0, 0.0, 1.0)), self.file.createIfcDirection((1.0, 0.0, 0.0)), ), - self.file.createIfcDirection((0.0, 0.0, 1.0)), + extrusion_direction, self.convert_si_to_unit(self.settings["height"]), ) if self.settings["booleans"]: