typing, format

This commit is contained in:
Andrej730
2024-03-28 12:26:19 +05:00
parent 78f235ea7c
commit 4ce7a898c9
4 changed files with 44 additions and 31 deletions
@@ -23,10 +23,11 @@ import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
from math import pi, pow from math import pi, pow
from mathutils import Vector, Matrix, geometry from mathutils import Vector, Matrix, geometry
from typing import Union
class Helper: class Helper:
def __init__(self, file): def __init__(self, file: ifcopenshell.file):
self.file = file self.file = file
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
@@ -34,7 +35,7 @@ class Helper:
# edge that shares a single vertex only with that face to find the extrusion # edge that shares a single vertex only with that face to find the extrusion
# edge. A face with the normal facing down is prioritised. A limited # edge. A face with the normal facing down is prioritised. A limited
# dissolve ensure that faces are quads and not tris. # dissolve ensure that faces are quads and not tris.
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh): def auto_detect_rectangle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict:
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(mesh) bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
@@ -115,7 +116,9 @@ class Helper:
return {"profile": profile, "extrusion": extrusion} return {"profile": profile, "extrusion": extrusion}
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh): def auto_detect_arbitrary_profile_with_voids(
self, obj: bpy.types.Object, mesh: bpy.types.Mesh
) -> Union[tuple, dict]:
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []} groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
for i, group in enumerate(obj.vertex_groups): for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name: if "IFCARCINDEX" in group.name:
@@ -148,18 +151,18 @@ class Helper:
if total_groups > 1: # A vert can only belong to one group if total_groups > 1: # A vert can only belong to one group
return (False, "AMBIGUOUS_SPECIAL_VERTEX") return (False, "AMBIGUOUS_SPECIAL_VERTEX")
elif is_circle: elif is_circle:
pass # Circles are allowed to be unclosed pass # Circles are allowed to be unclosed
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
return (False, "UNCLOSED_LOOP") return (False, "UNCLOSED_LOOP")
for group_type, group_counts in group_verts.items(): for group_type, group_counts in group_verts.items():
if group_type == "IFCARCINDEX": if group_type == "IFCARCINDEX":
for group_count in group_counts.values(): for group_count in group_counts.values():
if group_count != 3: # Each arc needs 3 verts if group_count != 3: # Each arc needs 3 verts
return (False, "3POINT_ARC") return (False, "3POINT_ARC")
elif group_type == "IFCCIRCLE": elif group_type == "IFCCIRCLE":
for group_count in group_counts.values(): for group_count in group_counts.values():
if group_count != 2: # Each circle needs 2 verts if group_count != 2: # Each circle needs 2 verts
return (False, "CIRCLE") return (False, "CIRCLE")
loop_edges = set(bm.edges) loop_edges = set(bm.edges)
@@ -269,7 +272,7 @@ class Helper:
inner_loops.remove(outer_loop) inner_loops.remove(outer_loop)
# Copy vectors to prevent random data mangling after bmesh is freed. # Copy vectors to prevent random data mangling after bmesh is freed.
points = [Vector(list(v.co)) for v in bm.verts] points = [v.co.copy() for v in bm.verts]
bm.to_mesh(mesh) bm.to_mesh(mesh)
mesh.update() mesh.update()
@@ -20,9 +20,12 @@ import bpy
import json import json
import bmesh import bmesh
import ifcopenshell import ifcopenshell
import ifcopenshell.util.type import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.unit
import ifcopenshell.util.type
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.core.type import blenderbim.core.type
import blenderbim.core.geometry import blenderbim.core.geometry
@@ -698,9 +701,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
new_footprint = ifcopenshell.api.run( new_footprint = ifcopenshell.api.run(
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves "geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
) )
old_footprint = ifcopenshell.util.representation.get_representation( old_footprint = ifcopenshell.util.representation.get_representation(element, "Plan", "FootPrint", "SKETCH_VIEW")
element, "Plan", "FootPrint", "SKETCH_VIEW"
)
if old_footprint: if old_footprint:
for inverse in tool.Ifc.get().get_inverse(old_footprint): for inverse in tool.Ifc.get().get_inverse(old_footprint):
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint) ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
+1 -1
View File
@@ -519,7 +519,7 @@ class Misc:
class Model: class Model:
def convert_si_to_unit(cls, value): pass def convert_si_to_unit(cls, value): pass
def convert_unit_to_si(cls, value): pass def convert_unit_to_si(cls, value): pass
def export_curve(cls, position, edge_indices, points=None): pass def export_curve(cls, position, edge_indices): pass
def export_points(cls, position, indices): pass def export_points(cls, position, indices): pass
def export_profile(cls, obj, position=None): pass def export_profile(cls, obj, position=None): pass
def generate_occurrence_name(cls, element_type, ifc_class): pass def generate_occurrence_name(cls, element_type, ifc_class): pass
+27 -18
View File
@@ -20,6 +20,7 @@ import bpy
import json import json
import bmesh import bmesh
import collections import collections
import collections.abc
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
@@ -36,23 +37,26 @@ from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.helper import Helper from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from ifcopenshell.util.shape_builder import V, ShapeBuilder from ifcopenshell.util.shape_builder import V, ShapeBuilder
from typing import Optional, Union, TypeVar, Any
T = TypeVar("T")
class Model(blenderbim.core.tool.Model): class Model(blenderbim.core.tool.Model):
@classmethod @classmethod
def convert_si_to_unit(cls, value): def convert_si_to_unit(cls, value: T) -> T:
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
return [v / cls.unit_scale for v in value] return [v / cls.unit_scale for v in value]
return value / cls.unit_scale return value / cls.unit_scale
@classmethod @classmethod
def convert_unit_to_si(cls, value): def convert_unit_to_si(cls, value: T) -> T:
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
return [v * cls.unit_scale for v in value] return [v * cls.unit_scale for v in value]
return value * cls.unit_scale return value * cls.unit_scale
@classmethod @classmethod
def convert_data_to_project_units(cls, data, non_si_props=[]): def convert_data_to_project_units(cls, data: dict[str, Any], non_si_props: list[str] = []) -> dict[str, Any]:
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in data: for prop_name in data:
if prop_name in non_si_props: if prop_name in non_si_props:
@@ -65,7 +69,7 @@ class Model(blenderbim.core.tool.Model):
return data return data
@classmethod @classmethod
def convert_data_to_si_units(cls, data, non_si_props=[]): def convert_data_to_si_units(cls, data: dict[str, Any], non_si_props: list[str] = []) -> dict[str, Any]:
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in data: for prop_name in data:
if prop_name in non_si_props: if prop_name in non_si_props:
@@ -78,34 +82,35 @@ class Model(blenderbim.core.tool.Model):
return data return data
@classmethod @classmethod
def export_curve(cls, position, edge_indices, points=None): def export_curve(cls, position: Matrix, edge_indices: list[tuple[int, int]]) -> ifcopenshell.entity_instance:
position_i = position.inverted() position_i = position.inverted()
ifc_file = tool.Ifc.get()
if len(edge_indices) == 2: if len(edge_indices) == 2:
diameter = edge_indices[0] diameter = edge_indices[0]
p1 = cls.bm.verts[diameter[0]].co p1 = cls.bm.verts[diameter[0]].co
p2 = cls.bm.verts[diameter[1]].co p2 = cls.bm.verts[diameter[1]].co
center = cls.convert_si_to_unit(list(position_i @ p1.lerp(p2, 0.5))) center = cls.convert_si_to_unit(list(position_i @ p1.lerp(p2, 0.5)))
radius = cls.convert_si_to_unit((p1 - p2).length / 2) radius = cls.convert_si_to_unit((p1 - p2).length / 2)
return tool.Ifc.get().createIfcCircle( return ifc_file.createIfcCircle(
tool.Ifc.get().createIfcAxis2Placement2D(tool.Ifc.get().createIfcCartesianPoint(center[0:2])), radius ifc_file.createIfcAxis2Placement2D(ifc_file.createIfcCartesianPoint(center[0:2])), radius
) )
if tool.Ifc.get().schema == "IFC2X3": if ifc_file.schema == "IFC2X3":
points = [] points = []
for edge in edge_indices: for edge in edge_indices:
local_point = (position_i @ Vector(cls.bm.verts[edge[0]].co)).to_2d() local_point = (position_i @ Vector(cls.bm.verts[edge[0]].co)).to_2d()
points.append(tool.Ifc.get().createIfcCartesianPoint(cls.convert_si_to_unit(local_point))) points.append(ifc_file.createIfcCartesianPoint(cls.convert_si_to_unit(local_point)))
points.append(points[0]) points.append(points[0])
return tool.Ifc.get().createIfcPolyline(points) return ifc_file.createIfcPolyline(points)
segments = [] segments = []
for segment in edge_indices: for segment in edge_indices:
if len(segment) == 2: if len(segment) == 2:
segments.append(tool.Ifc.get().createIfcLineIndex([i + 1 for i in segment])) segments.append(ifc_file.createIfcLineIndex([i + 1 for i in segment]))
elif len(segment) == 3: elif len(segment) == 3:
segments.append(tool.Ifc.get().createIfcArcIndex([i + 1 for i in segment])) segments.append(ifc_file.createIfcArcIndex([i + 1 for i in segment]))
return tool.Ifc.get().createIfcIndexedPolyCurve(cls.points, segments, False) return ifc_file.createIfcIndexedPolyCurve(cls.points, segments, False)
@classmethod @classmethod
def export_points(cls, position, indices): def export_points(cls, position: Matrix, indices: list[Vector]) -> ifcopenshell.entity_instance:
position_i = position.inverted() position_i = position.inverted()
points = [] points = []
for point in indices: for point in indices:
@@ -114,7 +119,10 @@ class Model(blenderbim.core.tool.Model):
return tool.Ifc.get().createIfcCartesianPointList2D(points) return tool.Ifc.get().createIfcCartesianPointList2D(points)
@classmethod @classmethod
def export_profile(cls, obj, position=None): def export_profile(
cls, obj: bpy.types.Object, position: Optional[Matrix] = None
) -> Union[ifcopenshell.entity_instance, None]:
"""Returns `None` in case if profile was invalid."""
if position is None: if position is None:
position = Matrix() position = Matrix()
@@ -150,7 +158,7 @@ class Model(blenderbim.core.tool.Model):
return profile return profile
@classmethod @classmethod
def export_surface(cls, obj): def export_surface(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
p1, p2, p3 = [v.co.copy() for v in obj.data.vertices[0:3]] p1, p2, p3 = [v.co.copy() for v in obj.data.vertices[0:3]]
edge1 = p2 - p1 edge1 = p2 - p1
@@ -202,7 +210,7 @@ class Model(blenderbim.core.tool.Model):
return surface return surface
@classmethod @classmethod
def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str): def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str:
props = bpy.context.scene.BIMModelProperties props = bpy.context.scene.BIMModelProperties
if props.occurrence_name_style == "CLASS": if props.occurrence_name_style == "CLASS":
return ifc_class[3:] return ifc_class[3:]
@@ -216,7 +224,8 @@ class Model(blenderbim.core.tool.Model):
return "Instance" return "Instance"
@classmethod @classmethod
def get_extrusion(cls, representation): def get_extrusion(cls, representation: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""return first found IfcExtrudedAreaSolid"""
item = representation.Items[0] item = representation.Items[0]
while True: while True:
if item.is_a("IfcExtrudedAreaSolid"): if item.is_a("IfcExtrudedAreaSolid"):