Black code and make profile representation optional for windows.

This commit is contained in:
Dion Moult
2023-02-09 20:13:14 +11:00
parent 25c650f4cd
commit 50d71d5ea1
2 changed files with 85 additions and 100 deletions
@@ -18,27 +18,22 @@
import bpy import bpy
import json
import bmesh
import collections
import ifcopenshell
import blenderbim.tool as tool
import blenderbim.core.geometry as core
from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS
from bpy.types import Operator from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty, BoolProperty from bpy.props import FloatProperty, IntProperty, BoolProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add from bpy_extras.object_utils import AddObjectHelper, object_data_add
import bmesh
from bmesh.types import BMVert from bmesh.types import BMVert
import ifcopenshell
from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS
import blenderbim
import blenderbim.tool as tool
import blenderbim.core.geometry as core
from blenderbim.bim.helper import convert_property_group_from_si from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.helper import get_ifc_context_or_create, replace_ifc_representation_for_object from blenderbim.bim.module.model.helper import replace_ifc_representation_for_object
from mathutils import Vector from mathutils import Vector
from pprint import pprint
from os.path import basename, dirname from os.path import basename, dirname
import json
import collections
V = lambda *x: Vector([float(i) for i in x]) V = lambda *x: Vector([float(i) for i in x])
@@ -46,7 +41,7 @@ V = lambda *x: Vector([float(i) for i in x])
def update_window_modifier_representation(context): def update_window_modifier_representation(context):
obj = context.active_object obj = context.active_object
ifc_element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties props = obj.BIMWindowProperties
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
@@ -77,23 +72,22 @@ def update_window_modifier_representation(context):
} }
representation_data["panel_properties"].append(panel_data) representation_data["panel_properties"].append(panel_data)
# ELEVATION_VIEW representation profile = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Profile", "ELEVATION_VIEW")
ifc_context = get_ifc_context_or_create(ifc_file, "Model", "Profile", "ELEVATION_VIEW") if profile:
representation_data["context"] = ifc_context representation_data["context"] = profile
elevation_representation = ifcopenshell.api.run( elevation_representation = ifcopenshell.api.run(
"geometry.add_window_representation", ifc_file, **representation_data "geometry.add_window_representation", ifc_file, **representation_data
) )
replace_ifc_representation_for_object(ifc_file, ifc_context, obj, elevation_representation) replace_ifc_representation_for_object(ifc_file, profile, obj, elevation_representation)
# MODEL_VIEW representation body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
ifc_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW") representation_data["context"] = body
representation_data["context"] = ifc_context
model_representation = ifcopenshell.api.run("geometry.add_window_representation", ifc_file, **representation_data) model_representation = ifcopenshell.api.run("geometry.add_window_representation", ifc_file, **representation_data)
replace_ifc_representation_for_object(ifc_file, ifc_context, obj, model_representation) replace_ifc_representation_for_object(ifc_file, body, obj, model_representation)
ifc_element.PartitioningType = props.window_type element.PartitioningType = props.window_type
def create_bm_window_frame(bm, size: Vector, thickness: Vector, position: Vector = V(0,0,0).freeze()): def create_bm_window_frame(bm, size: Vector, thickness: Vector, position: Vector = V(0, 0, 0).freeze()):
"""`thickness` of the profile is defined as list in the following order: """`thickness` of the profile is defined as list in the following order:
`(LEFT, TOP, RIGHT, BOTTOM)` `(LEFT, TOP, RIGHT, BOTTOM)`
@@ -119,16 +113,16 @@ def create_bm_window_frame(bm, size: Vector, thickness: Vector, position: Vector
] ]
edges = [ edges = [
(0, (0, 1)), (0, (0, 1)),
(1, (2, 3)), (1, (2, 3)),
(2, (4, 5)), (2, (4, 5)),
(3, (6, 7)), (3, (6, 7)),
(4, (7, 5)), (4, (7, 5)),
(5, (1, 3)), (5, (1, 3)),
(6, (4, 6)), (6, (4, 6)),
(7, (0, 2)), (7, (0, 2)),
(8, (2, 5)), (8, (2, 5)),
(9, (3, 7)), (9, (3, 7)),
(10, (4, 0)), (10, (4, 0)),
(11, (1, 6)), (11, (1, 6)),
] ]
@@ -158,7 +152,7 @@ def create_bm_window_frame(bm, size: Vector, thickness: Vector, position: Vector
return new_verts + translate_verts return new_verts + translate_verts
def create_bm_box(bm, size: Vector = V(1,1,1).freeze(), position: Vector = V(0,0,0).freeze()): def create_bm_box(bm, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0, 0, 0).freeze()):
"""create a box of `size`, position box first vertex at `position`""" """create a box of `size`, position box first vertex at `position`"""
box_verts = bmesh.ops.create_cube(bm, size=1)["verts"] box_verts = bmesh.ops.create_cube(bm, size=1)["verts"]
bmesh.ops.translate(bm, vec=-box_verts[0].co, verts=box_verts) bmesh.ops.translate(bm, vec=-box_verts[0].co, verts=box_verts)
@@ -186,13 +180,9 @@ def create_bm_window(
frame_verts = create_bm_window_frame(bm, frame_size, frame_thickness, frame_position) frame_verts = create_bm_window_frame(bm, frame_size, frame_thickness, frame_position)
# window glass # window glass
glass_size = frame_size - V(frame_thickness*2, 0, frame_thickness*2) glass_size = frame_size - V(frame_thickness * 2, 0, frame_thickness * 2)
glass_size.y = glass_thickness glass_size.y = glass_thickness
glass_position = frame_position + V( glass_position = frame_position + V(frame_thickness, frame_size.y / 2 - glass_thickness / 2, frame_thickness)
frame_thickness,
frame_size.y / 2 - glass_thickness / 2,
frame_thickness
)
glass_verts = create_bm_box(bm, glass_size, glass_position) glass_verts = create_bm_box(bm, glass_size, glass_position)
@@ -344,12 +334,7 @@ class BIM_OT_add_window(Operator):
obj.location = spawn_location obj.location = spawn_location
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW") body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
element = blenderbim.core.root.assign_class( element = blenderbim.core.root.assign_class(
tool.Ifc, tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcWindow", should_add_representation=False
tool.Collector,
tool.Root,
obj=obj,
ifc_class="IfcWindow",
should_add_representation=False
) )
element.PredefinedType = "WINDOW" element.PredefinedType = "WINDOW"
@@ -16,11 +16,11 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from mathutils import Vector, Matrix import collections
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
from math import cos, sin, pi from math import cos, sin, pi
import collections from mathutils import Vector, Matrix
V = lambda *x: Vector([float(i) for i in x]) V = lambda *x: Vector([float(i) for i in x])
sign = lambda x: x and (1, -1)[x < 0] sign = lambda x: x and (1, -1)[x < 0]
@@ -33,30 +33,28 @@ sign = lambda x: x and (1, -1)[x < 0]
class ShapeBuilder: class ShapeBuilder:
def __init__(self, ifc_file): def __init__(self, ifc_file):
self.ifc = ifc_file self.file = ifc_file
def polyline(self, points, closed=False, position_offset=None): def polyline(self, points, closed=False, position_offset=None):
# > points - list of points formatted like ( (x0, y0), (x1, y1) ) # > points - list of points formatted like ( (x0, y0), (x1, y1) )
# < IfcIndexedPolyCurve # < IfcIndexedPolyCurve
segments = [(i, i+1) for i in range(1, len(points))] segments = [(i, i + 1) for i in range(1, len(points))]
if closed: if closed:
segments.append( (len(points),1) ) segments.append((len(points), 1))
if position_offset: if position_offset:
points = [Vector(p) + position_offset for p in points] points = [Vector(p) + position_offset for p in points]
dimensions = len(points[0]) dimensions = len(points[0])
if dimensions == 2: if dimensions == 2:
ifc_points = self.ifc.createIfcCartesianPointList2D(points) ifc_points = self.file.createIfcCartesianPointList2D(points)
elif dimensions == 3: elif dimensions == 3:
ifc_points = self.ifc.createIfcCartesianPointList3D(points) ifc_points = self.file.createIfcCartesianPointList3D(points)
ifc_segments = [ self.ifc.createIfcLineIndex( segment ) for segment in segments] ifc_segments = [self.file.createIfcLineIndex(segment) for segment in segments]
ifc_curve = self.ifc.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments) ifc_curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return ifc_curve return ifc_curve
def get_rectangle_coords(self, def get_rectangle_coords(self, size: Vector = Vector((1.0, 1.0)).freeze(), position: Vector = None):
size:Vector = Vector( (1., 1.) ).freeze(),
position:Vector = None):
dimensions = len(size) dimensions = len(size)
@@ -75,9 +73,7 @@ class ShapeBuilder:
] ]
return points return points
def rectangle(self, def rectangle(self, size: Vector = Vector((1.0, 1.0)).freeze(), position: Vector = None):
size:Vector = Vector( (1., 1.) ).freeze(),
position:Vector = None):
""" """
function supports both 2d and 3d rectangle sizes function supports both 2d and 3d rectangle sizes
@@ -88,12 +84,12 @@ class ShapeBuilder:
# < IfcIndexedPolyCurve # < IfcIndexedPolyCurve
return self.polyline(self.get_rectangle_coords(size, position), closed=True) return self.polyline(self.get_rectangle_coords(size, position), closed=True)
def circle(self, center:Vector = Vector( (0., 0.) ).freeze(), radius = 1.): def circle(self, center: Vector = Vector((0.0, 0.0)).freeze(), radius=1.0):
# < returns IfcCircle # < returns IfcCircle
ifc_center = self.ifc.createIfcAxis2Placement2D(self.ifc.createIfcCartesianPoint(center)) ifc_center = self.file.createIfcAxis2Placement2D(self.file.createIfcCartesianPoint(center))
ifc_curve = self.ifc.createIfcCircle(ifc_center, radius) ifc_curve = self.file.createIfcCircle(ifc_center, radius)
# self.ifc_file.createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center[0:2])) # self.file_file.createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center[0:2]))
return ifc_curve return ifc_curve
# TODO: explain points order for the curve_between_two_points # TODO: explain points order for the curve_between_two_points
@@ -114,9 +110,9 @@ class ShapeBuilder:
diff = V(0.01, 0.01) * diff_sign diff = V(0.01, 0.01) * diff_sign
middle_point = points[0] + diff middle_point = points[0] + diff
points = [points[0], middle_point, points[1]] points = [points[0], middle_point, points[1]]
seg = self.ifc.createIfcArcIndex((1, 2, 3)) seg = self.file.createIfcArcIndex((1, 2, 3))
ifc_points = self.ifc.createIfcCartesianPointList2D(points) ifc_points = self.file.createIfcCartesianPointList2D(points)
curve = self.ifc.createIfcIndexedPolyCurve(Points=ifc_points, Segments=[seg]) curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=[seg])
return curve return curve
def get_trim_points_from_mask(self, x_axis_radius, y_axis_radius, trim_points_mask, position_offset=None): def get_trim_points_from_mask(self, x_axis_radius, y_axis_radius, trim_points_mask, position_offset=None):
@@ -157,24 +153,25 @@ class ShapeBuilder:
Notion: trimmed ellipse also contains polyline between trim points, meaning IfcTrimmedCurve could be used Notion: trimmed ellipse also contains polyline between trim points, meaning IfcTrimmedCurve could be used
for further extrusion. for further extrusion.
""" """
direction = self.ifc.createIfcDirection(ref_x_direction) direction = self.file.createIfcDirection(ref_x_direction)
ifc_position = self.ifc.createIfcAxis2Placement2D( ifc_position = self.file.createIfcAxis2Placement2D(
self.ifc.createIfcCartesianPoint(position), RefDirection=direction self.file.createIfcCartesianPoint(position), RefDirection=direction
)
ifc_ellipse = self.file.createIfcEllipse(
Position=ifc_position, SemiAxis1=x_axis_radius, SemiAxis2=y_axis_radius
) )
ifc_ellipse = self.ifc.createIfcEllipse(Position=ifc_position, SemiAxis1=x_axis_radius, SemiAxis2=y_axis_radius)
if not trim_points: if not trim_points:
if not trim_points_mask: if not trim_points_mask:
return ifc_ellipse return ifc_ellipse
trim_points = self.get_trim_points_from_mask( trim_points = self.get_trim_points_from_mask(
x_axis_radius, y_axis_radius, x_axis_radius, y_axis_radius, trim_points_mask, position_offset=position
trim_points_mask, position_offset=position
) )
trim1 = [self.ifc.createIfcCartesianPoint(trim_points[0])] trim1 = [self.file.createIfcCartesianPoint(trim_points[0])]
trim2 = [self.ifc.createIfcCartesianPoint(trim_points[1])] trim2 = [self.file.createIfcCartesianPoint(trim_points[1])]
trim_ellipse = self.ifc.createIfcTrimmedCurve( trim_ellipse = self.file.createIfcTrimmedCurve(
BasisCurve=ifc_ellipse, Trim1=trim1, Trim2=trim2, SenseAgreement=True, MasterRepresentation="CARTESIAN" BasisCurve=ifc_ellipse, Trim1=trim1, Trim2=trim2, SenseAgreement=True, MasterRepresentation="CARTESIAN"
) )
return trim_ellipse return trim_ellipse
@@ -191,12 +188,11 @@ class ShapeBuilder:
if not isinstance(inner_curves, collections.abc.Iterable): if not isinstance(inner_curves, collections.abc.Iterable):
inner_curves = [inner_curves] inner_curves = [inner_curves]
profile = self.ifc.createIfcArbitraryProfileDefWithVoids( profile = self.file.createIfcArbitraryProfileDefWithVoids(
ProfileName=name, ProfileType=profile_type, ProfileName=name, ProfileType=profile_type, OuterCurve=outer_curve, InnerCurves=inner_curves
OuterCurve=outer_curve, InnerCurves=inner_curves
) )
else: else:
profile = self.ifc.createIfcArbitraryClosedProfileDef( profile = self.file.createIfcArbitraryClosedProfileDef(
ProfileName=name, ProfileType=profile_type, OuterCurve=outer_curve ProfileName=name, ProfileType=profile_type, OuterCurve=outer_curve
) )
return profile return profile
@@ -212,7 +208,7 @@ class ShapeBuilder:
processed_objects = [] processed_objects = []
for c in curve_or_item: for c in curve_or_item:
if create_copy: if create_copy:
c = ifcopenshell.util.element.copy_deep(self.ifc, c) c = ifcopenshell.util.element.copy_deep(self.file, c)
if c.is_a("IfcIndexedPolyCurve"): if c.is_a("IfcIndexedPolyCurve"):
coords = [Vector(co) + translation for co in c.Points.CoordList] coords = [Vector(co) + translation for co in c.Points.CoordList]
@@ -274,7 +270,7 @@ class ShapeBuilder:
processed_objects = [] processed_objects = []
for c in curve_or_item: for c in curve_or_item:
if create_copy: if create_copy:
c = ifcopenshell.util.element.copy_deep(self.ifc, c) c = ifcopenshell.util.element.copy_deep(self.file, c)
if c.is_a("IfcIndexedPolyCurve"): if c.is_a("IfcIndexedPolyCurve"):
coords = [ coords = [
@@ -313,7 +309,7 @@ class ShapeBuilder:
): ):
"""mirror_axes - along which axes mirror will be applied""" """mirror_axes - along which axes mirror will be applied"""
base = point_2d # prevent mutating the argument base = point_2d # prevent mutating the argument
mirror_axes = Vector( [-1 if i > 0 else 1 for i in mirror_axes] ) mirror_axes = Vector([-1 if i > 0 else 1 for i in mirror_axes])
relative_point = base - mirror_point relative_point = base - mirror_point
relative_point = relative_point * mirror_axes relative_point = relative_point * mirror_axes
point_2d = relative_point + mirror_point point_2d = relative_point + mirror_point
@@ -366,7 +362,11 @@ class ShapeBuilder:
processed_objects = [] processed_objects = []
for curve_or_item_el in curve_or_item: for curve_or_item_el in curve_or_item:
for mirror_axes in mirror_axes_data: for mirror_axes in mirror_axes_data:
c = ifcopenshell.util.element.copy_deep(self.ifc, curve_or_item_el) if create_copy else curve_or_item_el c = (
ifcopenshell.util.element.copy_deep(self.file, curve_or_item_el)
if create_copy
else curve_or_item_el
)
if c.is_a("IfcIndexedPolyCurve"): if c.is_a("IfcIndexedPolyCurve"):
inverted_placement_matrix = placement_matrix.inverted() if placement_matrix else None inverted_placement_matrix = placement_matrix.inverted() if placement_matrix else None
@@ -429,9 +429,9 @@ class ShapeBuilder:
trim_coords = [c.Trim1[0].Coordinates, c.Trim2[0].Coordinates] trim_coords = [c.Trim1[0].Coordinates, c.Trim2[0].Coordinates]
trim_coords = [Vector(coords) for coords in trim_coords] trim_coords = [Vector(coords) for coords in trim_coords]
trim_coords = [ trim_coords = [
self.mirror_2d_point(base_position, mirror_axes, mirror_point) self.mirror_2d_point(base_position, mirror_axes, mirror_point) for base_position in trim_coords
for base_position in trim_coords] ]
# if mirror only by 1 axis we need to preserve the counter-clockwise order # if mirror only by 1 axis we need to preserve the counter-clockwise order
# for the trim points # for the trim points
if 0 in mirror_axes: if 0 in mirror_axes:
@@ -477,13 +477,13 @@ class ShapeBuilder:
if position_y_axis: if position_y_axis:
position_z_axis = position_x_axis.cross(position_y_axis) position_z_axis = position_x_axis.cross(position_y_axis)
ifc_position = self.ifc.createIfcAxis2Placement3D( ifc_position = self.file.createIfcAxis2Placement3D(
self.ifc.createIfcCartesianPoint(position), # position self.file.createIfcCartesianPoint(position), # position
self.ifc.createIfcDirection(position_z_axis), # Z-axis / Axis self.file.createIfcDirection(position_z_axis), # Z-axis / Axis
self.ifc.createIfcDirection(position_x_axis), # X-axis / RefDirection self.file.createIfcDirection(position_x_axis), # X-axis / RefDirection
) )
ifc_direction = self.ifc.createIfcDirection(extrusion_vector) ifc_direction = self.file.createIfcDirection(extrusion_vector)
extruded_area = self.ifc.createIfcExtrudedAreaSolid( extruded_area = self.file.createIfcExtrudedAreaSolid(
SweptArea=profile_or_curve, Position=ifc_position, ExtrudedDirection=ifc_direction, Depth=magnitude SweptArea=profile_or_curve, Position=ifc_position, ExtrudedDirection=ifc_direction, Depth=magnitude
) )
return extruded_area return extruded_area
@@ -493,7 +493,7 @@ class ShapeBuilder:
# < IfcShapeRepresentation # < IfcShapeRepresentation
if not isinstance(items, collections.abc.Iterable): if not isinstance(items, collections.abc.Iterable):
items = [items] items = [items]
representation = self.ifc.createIfcShapeRepresentation( representation = self.file.createIfcShapeRepresentation(
ContextOfItems=context, ContextOfItems=context,
RepresentationIdentifier=context.ContextIdentifier, RepresentationIdentifier=context.ContextIdentifier,
RepresentationType="SweptSolid" if items[0].is_a("IfcExtrudedAreaSolid") else "Curve2D", RepresentationType="SweptSolid" if items[0].is_a("IfcExtrudedAreaSolid") else "Curve2D",
@@ -502,4 +502,4 @@ class ShapeBuilder:
return representation return representation
def deep_copy(self, element): def deep_copy(self, element):
return ifcopenshell.util.element.copy_deep(self.ifc, element) return ifcopenshell.util.element.copy_deep(self.file, element)