shape_builder - remove mathutils dependency completely #5192

This commit is contained in:
Andrej730
2024-12-17 15:55:02 +05:00
parent b4a0a5df6c
commit 0e8eeb18ac
11 changed files with 170 additions and 118 deletions
+34 -33
View File
@@ -27,7 +27,6 @@ import ifcopenshell.util.element
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.unit import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.geometry import bonsai.core.geometry
import bonsai.core.geometry as core import bonsai.core.geometry as core
@@ -40,6 +39,8 @@ from typing import Union, Any, Optional
import json import json
import collections import collections
V_ = tool.Blender.V_
def update_door_modifier_representation(context: bpy.types.Context) -> None: def update_door_modifier_representation(context: bpy.types.Context) -> None:
obj = context.active_object obj = context.active_object
@@ -188,15 +189,15 @@ def bm_sort_out_geom(
def bm_mirror( def bm_mirror(
bm: bmesh.types.BMesh, bm: bmesh.types.BMesh,
verts: list[bmesh.types.BMVert], verts: list[bmesh.types.BMVert],
mirror_axes: Vector = V(1, 0, 0).freeze(), mirror_axes: Vector = V_(1, 0, 0).freeze(),
mirror_point: Vector = V(0, 0, 0).freeze(), mirror_point: Vector = V_(0, 0, 0).freeze(),
create_copy: bool = False, create_copy: bool = False,
) -> list[bmesh.types.BMVert]: ) -> list[bmesh.types.BMVert]:
matrix = Matrix.Translation(mirror_point) matrix = Matrix.Translation(mirror_point)
for i, v in enumerate(mirror_axes): for i, v in enumerate(mirror_axes):
if not v: if not v:
continue continue
mirror_axis = V(0, 0, 0) mirror_axis = V_(0, 0, 0)
mirror_axis[i] = 1.0 mirror_axis[i] = 1.0
matrix = matrix @ Matrix.Scale(-1, 4, mirror_axis) matrix = matrix @ Matrix.Scale(-1, 4, mirror_axis)
matrix = matrix @ Matrix.Translation(-mirror_point) matrix = matrix @ Matrix.Translation(-mirror_point)
@@ -219,9 +220,9 @@ def create_bm_extruded_profile(
points: list[Vector], points: list[Vector],
edges: Optional[list[tuple[int, int]]] = None, edges: Optional[list[tuple[int, int]]] = None,
faces: Optional[list[list[int]]] = None, faces: Optional[list[list[int]]] = None,
position: Vector = V(0, 0, 0).freeze(), position: Vector = V_(0, 0, 0).freeze(),
magnitude: float = 1.0, magnitude: float = 1.0,
extrusion_vector: Vector = V(0, 0, 1).freeze(), extrusion_vector: Vector = V_(0, 0, 1).freeze(),
) -> list[bmesh.types.BMVert]: ) -> list[bmesh.types.BMVert]:
bm.verts.index_update() bm.verts.index_update()
bm.edges.index_update() bm.edges.index_update()
@@ -250,7 +251,7 @@ def create_bm_extruded_profile(
def create_bm_door_lining( def create_bm_door_lining(
bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze() bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V_(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]: ) -> list[bmesh.types.BMVert]:
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)` """`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
@@ -363,9 +364,9 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
glass_thickness = 0.01 glass_thickness = 0.01
# handle dimensions (hardcoded) # handle dimensions (hardcoded)
handle_size = V(120, 40, 20) * 0.001 handle_size = V_(120, 40, 20) * 0.001
handle_offset = V(60, 0, 1000) * 0.001 # to the handle center handle_offset = V_(60, 0, 1000) * 0.001 # to the handle center
handle_center_offset = V(handle_size.y / 2, 0, handle_size.z) / 2 handle_center_offset = V_(handle_size.y / 2, 0, handle_size.z) / 2
if transfom_offset: if transfom_offset:
panel_height = transfom_offset + transom_thickness - lining_to_panel_offset_x - threshold_thickness panel_height = transfom_offset + transom_thickness - lining_to_panel_offset_x - threshold_thickness
@@ -377,7 +378,7 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
bm = bmesh.new() bm = bmesh.new()
# add lining # add lining
lining_size = V(overall_width, lining_depth, lining_height) lining_size = V_(overall_width, lining_depth, lining_height)
lining_thickness = [side_lining_thickness, top_lining_thickness] lining_thickness = [side_lining_thickness, top_lining_thickness]
lining_verts = create_bm_door_lining(bm, lining_size, lining_thickness) lining_verts = create_bm_door_lining(bm, lining_size, lining_thickness)
@@ -385,8 +386,8 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
if not threshold_thickness: if not threshold_thickness:
threshold_verts = [] threshold_verts = []
else: else:
threshold_size = V(threshold_width, threshold_depth, threshold_thickness) threshold_size = V_(threshold_width, threshold_depth, threshold_thickness)
threshold_position = V(side_lining_thickness, threshold_offset, 0) threshold_position = V_(side_lining_thickness, threshold_offset, 0)
threshold_verts = create_bm_box(bm, threshold_size, threshold_position) threshold_verts = create_bm_box(bm, threshold_size, threshold_position)
# add casings # add casings
@@ -400,12 +401,12 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
] ]
outer_casing_thickness = inner_casing_thickness.copy() if double_swing_door else casing_thickness outer_casing_thickness = inner_casing_thickness.copy() if double_swing_door else casing_thickness
casing_size = V(overall_width + casing_wall_overlap * 2, casing_depth, overall_height + casing_wall_overlap) casing_size = V_(overall_width + casing_wall_overlap * 2, casing_depth, overall_height + casing_wall_overlap)
casing_position = V(-casing_wall_overlap, -casing_depth, 0) casing_position = V_(-casing_wall_overlap, -casing_depth, 0)
outer_casing_verts = create_bm_door_lining(bm, casing_size, outer_casing_thickness, casing_position) outer_casing_verts = create_bm_door_lining(bm, casing_size, outer_casing_thickness, casing_position)
casing_verts.extend(outer_casing_verts) casing_verts.extend(outer_casing_verts)
inner_casing_position = V(-casing_wall_overlap, lining_depth, 0) inner_casing_position = V_(-casing_wall_overlap, lining_depth, 0)
inner_casing_verts = create_bm_door_lining(bm, casing_size, inner_casing_thickness, inner_casing_position) inner_casing_verts = create_bm_door_lining(bm, casing_size, inner_casing_thickness, inner_casing_position)
casing_verts.extend(inner_casing_verts) casing_verts.extend(inner_casing_verts)
@@ -417,12 +418,12 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
door_verts.extend(create_bm_box(bm, panel_size, panel_position)) door_verts.extend(create_bm_box(bm, panel_size, panel_position))
# add door handle # add door handle
handle_points = [ handle_points = [
V(0, 0, 0), V_(0, 0, 0),
V(0, -handle_size.y, 0), V_(0, -handle_size.y, 0),
V(handle_size.x, -handle_size.y, 0), V_(handle_size.x, -handle_size.y, 0),
V(handle_size.x, -handle_size.y / 2, 0), V_(handle_size.x, -handle_size.y / 2, 0),
V(handle_size.y / 2, -handle_size.y / 2, 0), V_(handle_size.y / 2, -handle_size.y / 2, 0),
V(handle_size.y / 2, 0, 0), V_(handle_size.y / 2, 0, 0),
] ]
handle_position = panel_position + handle_offset - handle_center_offset handle_position = panel_position + handle_offset - handle_center_offset
door_handle_verts = create_bm_extruded_profile( door_handle_verts = create_bm_extruded_profile(
@@ -432,22 +433,22 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
if door_swing_type == "LEFT": if door_swing_type == "LEFT":
bm_mirror( bm_mirror(
bm, door_handle_verts, mirror_axes=V(1, 0, 0), mirror_point=panel_position + V(panel_size.x / 2, 0, 0) bm, door_handle_verts, mirror_axes=V_(1, 0, 0), mirror_point=panel_position + V_(panel_size.x / 2, 0, 0)
) )
door_handle_mirrored_verts = bm_mirror( door_handle_mirrored_verts = bm_mirror(
bm, bm,
door_handle_verts, door_handle_verts,
mirror_axes=V(0, 1, 0), mirror_axes=V_(0, 1, 0),
mirror_point=handle_position + V(0, panel_size.y / 2, 0), mirror_point=handle_position + V_(0, panel_size.y / 2, 0),
create_copy=True, create_copy=True,
) )
door_verts.extend(door_handle_mirrored_verts) door_verts.extend(door_handle_mirrored_verts)
return door_verts return door_verts
door_verts = [] door_verts = []
panel_size = V(panel_width, panel_depth, panel_height) panel_size = V_(panel_width, panel_depth, panel_height)
panel_position = V(lining_to_panel_offset_x, lining_to_panel_offset_y, threshold_thickness) panel_position = V_(lining_to_panel_offset_x, lining_to_panel_offset_y, threshold_thickness)
if double_door: if double_door:
# keeping a little space between doors for readibility # keeping a little space between doors for readibility
@@ -455,8 +456,8 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
panel_size.x = panel_size.x / 2 - double_door_offset panel_size.x = panel_size.x / 2 - double_door_offset
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT")) door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
mirror_point = panel_position + V(door_opening_width / 2, 0, 0) mirror_point = panel_position + V_(door_opening_width / 2, 0, 0)
door_verts.extend(bm_mirror(bm, door_verts, V(1, 0, 0), mirror_point, create_copy=True)) door_verts.extend(bm_mirror(bm, door_verts, V_(1, 0, 0), mirror_point, create_copy=True))
else: else:
door_swing_type = "LEFT" if door_type.endswith("LEFT") else "RIGHT" door_swing_type = "LEFT" if door_type.endswith("LEFT") else "RIGHT"
door_verts.extend(create_bm_door_panel(panel_size, panel_position, door_swing_type)) door_verts.extend(create_bm_door_panel(panel_size, panel_position, door_swing_type))
@@ -474,9 +475,9 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
transom_thickness, transom_thickness,
] ]
window_lining_thickness.append(transom_thickness) window_lining_thickness.append(transom_thickness)
window_lining_size = V(overall_width, lining_depth, window_lining_height) window_lining_size = V_(overall_width, lining_depth, window_lining_height)
window_position = V(0, 0, overall_height - window_lining_height) window_position = V_(0, 0, overall_height - window_lining_height)
frame_size = V(door_opening_width, frame_depth, frame_height) frame_size = V_(door_opening_width, frame_depth, frame_height)
window_lining_verts, frame_verts, glass_verts = create_bm_window( window_lining_verts, frame_verts, glass_verts = create_bm_window(
bm, bm,
window_lining_size, window_lining_size,
@@ -490,7 +491,7 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
) )
lining_offset_verts = lining_verts + door_verts + window_lining_verts + frame_verts + glass_verts lining_offset_verts = lining_verts + door_verts + window_lining_verts + frame_verts + glass_verts
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=lining_offset_verts) bmesh.ops.translate(bm, vec=V_(0, lining_offset, 0), verts=lining_offset_verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
if bpy.context.active_object.mode == "EDIT": if bpy.context.active_object.mode == "EDIT":
@@ -23,7 +23,6 @@ import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.unit import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
import bonsai.core.root import bonsai.core.root
import bonsai.core.geometry import bonsai.core.geometry
import bonsai.tool as tool import bonsai.tool as tool
@@ -125,6 +124,7 @@ def update_railing_modifier_bmesh(context: bpy.types.Context) -> None:
""" """
obj = context.active_object obj = context.active_object
props = obj.BIMRailingProperties props = obj.BIMRailingProperties
V_ = tool.Blender.V_
# NOTE: using Data since bmesh update will hapen very often # NOTE: using Data since bmesh update will hapen very often
if not RailingData.is_loaded: if not RailingData.is_loaded:
@@ -171,8 +171,8 @@ def update_railing_modifier_bmesh(context: bpy.types.Context) -> None:
v0, v1 = main_edge.verts v0, v1 = main_edge.verts
edge_dissolving_verts.extend([v0, v1]) edge_dissolving_verts.extend([v0, v1])
edge_dir = ((v1.co - v0.co) * V(1, 1, 0)).normalized() edge_dir = ((v1.co - v0.co) * V_(1, 1, 0)).normalized()
ortho_vector = edge_dir.cross(V(0, 0, 1)) ortho_vector = edge_dir.cross(V_(0, 0, 1))
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"] extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
extruded_verts = bm_sort_out_geom(extruded_geom)["verts"] extruded_verts = bm_sort_out_geom(extruded_geom)["verts"]
+10 -9
View File
@@ -31,11 +31,12 @@ import ifcopenshell.util.element
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.shape_builder import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
from bmesh.types import BMVert from bmesh.types import BMVert
from mathutils import Vector from mathutils import Vector
from typing import Optional from typing import Optional
V_ = tool.Blender.V_
def update_window_modifier_representation(context: bpy.types.Context) -> None: def update_window_modifier_representation(context: bpy.types.Context) -> None:
obj = context.active_object obj = context.active_object
@@ -131,7 +132,7 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None:
def create_bm_window_frame( def create_bm_window_frame(
bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze() bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V_(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]: ) -> list[bmesh.types.BMVert]:
"""`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)`
@@ -198,7 +199,7 @@ def create_bm_window_frame(
def create_bm_box( def create_bm_box(
bm: bmesh.types.BMesh, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0, 0, 0).freeze() bm: bmesh.types.BMesh, size: Vector = V_(1, 1, 1).freeze(), position: Vector = V_(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]: ) -> list[bmesh.types.BMVert]:
"""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"]
@@ -230,13 +231,13 @@ def create_bm_window(
window_lining_verts = create_bm_window_frame(bm, lining_size, lining_thickness) window_lining_verts = create_bm_window_frame(bm, lining_size, lining_thickness)
# window frame # window frame
frame_position = V(x_offsets[0], lining_to_panel_offset_y_full, x_offsets[3]) frame_position = V_(x_offsets[0], lining_to_panel_offset_y_full, x_offsets[3])
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(frame_thickness, frame_size.y / 2 - glass_thickness / 2, frame_thickness) glass_position = frame_position + V_(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)
@@ -324,7 +325,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
frame_thickness = props.frame_thickness[panel_i] frame_thickness = props.frame_thickness[panel_i]
lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y
# add window # add window
window_lining_size = V( window_lining_size = V_(
panel_width, panel_width,
lining_depth, lining_depth,
panel_height, panel_height,
@@ -358,7 +359,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
frame_size.x -= x_offsets[0] + x_offsets[2] frame_size.x -= x_offsets[0] + x_offsets[2]
frame_size.z -= x_offsets[1] + x_offsets[3] frame_size.z -= x_offsets[1] + x_offsets[3]
window_position = V(accumulated_width, 0, accumulated_height[column_i]) window_position = V_(accumulated_width, 0, accumulated_height[column_i])
lining_verts, panel_verts, glass_verts = create_bm_window( lining_verts, panel_verts, glass_verts = create_bm_window(
bm, bm,
window_lining_size, window_lining_size,
@@ -377,7 +378,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
accumulated_height[column_i] += panel_height accumulated_height[column_i] += panel_height
accumulated_width += panel_width accumulated_width += panel_width
bmesh.ops.translate(bm, vec=V(0, lining_offset, 0), verts=bm.verts) bmesh.ops.translate(bm, vec=V_(0, lining_offset, 0), verts=bm.verts)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
if bpy.context.active_object.mode == "EDIT": if bpy.context.active_object.mode == "EDIT":
+5
View File
@@ -1295,3 +1295,8 @@ class Blender(bonsai.core.tool.Blender):
if len(blender_binary_path.parents) > 3 and blender_binary_path.parents[2].name == "WindowsApps": if len(blender_binary_path.parents) > 3 and blender_binary_path.parents[2].name == "WindowsApps":
return blender_binary_path.parents[1].name.rsplit("__", 1)[-1] return blender_binary_path.parents[1].name.rsplit("__", 1)[-1]
return None return None
@classmethod
def V_(cls, *args: float) -> Vector:
"""Just a shortcut for creating mathutils Vector."""
return Vector(args)
+22 -19
View File
@@ -39,13 +39,16 @@ from mathutils import Matrix, Vector
from copy import deepcopy from copy import deepcopy
from functools import partial from functools import partial
from bonsai.bim import import_ifc from bonsai.bim import import_ifc
# TODO: This line is somehow keeping the world from falling apart with a circular import error.
from bonsai.bim.module.geometry.helper import Helper from bonsai.bim.module.geometry.helper import Helper
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
from bonsai.bim.module.model.opening import FilledOpeningGenerator from bonsai.bim.module.model.opening import FilledOpeningGenerator
from ifcopenshell.util.shape_builder import V, ShapeBuilder from ifcopenshell.util.shape_builder import ShapeBuilder
from typing import Optional, Union, TypeVar, Any, Iterable, Literal from typing import Optional, Union, TypeVar, Any, Iterable, Literal
T = TypeVar("T") T = TypeVar("T")
V_ = tool.Blender.V_
class Model(bonsai.core.tool.Model): class Model(bonsai.core.tool.Model):
@@ -1122,7 +1125,7 @@ class Model(bonsai.core.tool.Model):
custom_tread_run = any(run != 0 for run in custom_first_last_tread_run) custom_tread_run = any(run != 0 for run in custom_first_last_tread_run)
nosing_overlap = max(nosing_length, 0) nosing_overlap = max(nosing_length, 0)
nosing_tread_gap = -min(nosing_length, 0) nosing_tread_gap = -min(nosing_length, 0)
nosing_overlap_offset = -V(nosing_overlap, 0) nosing_overlap_offset = -V_(nosing_overlap, 0)
def define_generic_stair_treads(): def define_generic_stair_treads():
vertices.append(Vector([0, 0])) vertices.append(Vector([0, 0]))
@@ -1134,16 +1137,16 @@ class Model(bonsai.core.tool.Model):
default_tread_edges = np.array(((0, 1), (1, 2))) default_tread_edges = np.array(((0, 1), (1, 2)))
# horizontal tread line # horizontal tread line
if nosing_overlap == 0: if nosing_overlap == 0:
default_tread_verts = (V(0, tread_rise), V(tread_run, tread_rise)) default_tread_verts = (V_(0, tread_rise), V_(tread_run, tread_rise))
elif nosing_depth == 0: elif nosing_depth == 0:
default_tread_verts = (V(-nosing_overlap, tread_rise), V(tread_run, tread_rise)) default_tread_verts = (V_(-nosing_overlap, tread_rise), V_(tread_run, tread_rise))
else: # nosing_overlap > 0 nosing_depth > 0 else: # nosing_overlap > 0 nosing_depth > 0
# kind of L shape # kind of L shape
default_tread_verts = ( default_tread_verts = (
V(0, tread_rise - nosing_depth), V_(0, tread_rise - nosing_depth),
V(-nosing_overlap, tread_rise - nosing_depth), V_(-nosing_overlap, tread_rise - nosing_depth),
V(-nosing_overlap, tread_rise), V_(-nosing_overlap, tread_rise),
V(tread_run, tread_rise), V_(tread_run, tread_rise),
) )
add_edges = ((2, 3), (3, 4)) add_edges = ((2, 3), (3, 4))
default_tread_edges = np.concatenate((default_tread_edges, add_edges)) default_tread_edges = np.concatenate((default_tread_edges, add_edges))
@@ -1166,7 +1169,7 @@ class Model(bonsai.core.tool.Model):
return default_tread_offset, default_tread_verts return default_tread_offset, default_tread_verts
# treads # treads
current_offset = V(0, 0) current_offset = V_(0, 0)
for i in range(number_of_risers): for i in range(number_of_risers):
last_vert_i = len(vertices) - 1 last_vert_i = len(vertices) - 1
tread_offset, tread_verts = get_tread_data(i) tread_offset, tread_verts = get_tread_data(i)
@@ -1178,9 +1181,9 @@ class Model(bonsai.core.tool.Model):
if stair_type == "WOOD/STEEL": if stair_type == "WOOD/STEEL":
builder = ShapeBuilder(None) builder = ShapeBuilder(None)
# full tread rectangle # full tread rectangle
get_tread_verts = partial(builder.get_rectangle_coords, position=V(0, -(tread_depth - tread_rise))) get_tread_verts = partial(builder.get_rectangle_coords, position=V_(0, -(tread_depth - tread_rise)))
default_tread_verts = get_tread_verts(size=V(tread_run + nosing_overlap, tread_depth)) default_tread_verts = get_tread_verts(size=V_(tread_run + nosing_overlap, tread_depth))
default_tread_offset = V(tread_run + nosing_tread_gap, tread_rise) default_tread_offset = V_(tread_run + nosing_tread_gap, tread_rise)
def get_tread_data(i): def get_tread_data(i):
if custom_tread_run: if custom_tread_run:
@@ -1193,12 +1196,12 @@ class Model(bonsai.core.tool.Model):
if current_tread_run: if current_tread_run:
tread_offset = default_tread_offset.copy() tread_offset = default_tread_offset.copy()
tread_offset.x = current_tread_run + nosing_tread_gap tread_offset.x = current_tread_run + nosing_tread_gap
tread_verts = get_tread_verts(size=V(current_tread_run + nosing_overlap, tread_depth)) tread_verts = get_tread_verts(size=V_(current_tread_run + nosing_overlap, tread_depth))
return tread_offset, tread_verts return tread_offset, tread_verts
return default_tread_offset, default_tread_verts return default_tread_offset, default_tread_verts
# each tread is a separate shape # each tread is a separate shape
cur_offset = V(0, 0) cur_offset = V_(0, 0)
for i in range(number_of_risers): for i in range(number_of_risers):
tread_offset, tread_verts = get_tread_data(i) tread_offset, tread_verts = get_tread_data(i)
cur_trade_shape = [v + cur_offset + nosing_overlap_offset for v in tread_verts] cur_trade_shape = [v + cur_offset + nosing_overlap_offset for v in tread_verts]
@@ -1219,7 +1222,7 @@ class Model(bonsai.core.tool.Model):
# close the shape # close the shape
last_vert_i = len(vertices) last_vert_i = len(vertices)
vertices.append(vertices[-1] * V(1, 0)) vertices.append(vertices[-1] * V_(1, 0))
edges.extend([(last_vert_i - 1, last_vert_i), (last_vert_i, 0)]) edges.extend([(last_vert_i - 1, last_vert_i), (last_vert_i, 0)])
# flip edges direction for ccw polygon winding order # flip edges direction for ccw polygon winding order
@@ -1233,15 +1236,15 @@ class Model(bonsai.core.tool.Model):
# from the tread diagonal line # from the tread diagonal line
# we're going it define that line, sample it and abrupt it in case it meets a slab # we're going it define that line, sample it and abrupt it in case it meets a slab
# graph: https://www.desmos.com/calculator/bilmnti3cp # graph: https://www.desmos.com/calculator/bilmnti3cp
tread_diagonal_dir = V(tread_run, tread_rise).normalized() tread_diagonal_dir = V_(tread_run, tread_rise).normalized()
# td_vector is clockwise orthogonal vector # td_vector is clockwise orthogonal vector
td_vector = tread_diagonal_dir.yx * V(1, -1) * tread_depth td_vector = tread_diagonal_dir.yx * V_(1, -1) * tread_depth
stair_tan = tread_rise / tread_run stair_tan = tread_rise / tread_run
# s0 is just a sampled point from the bottom line # s0 is just a sampled point from the bottom line
# we stick to the third point as the first point # we stick to the third point as the first point
# is affected by customized tread run # is affected by customized tread run
s0 = V(custom_first_last_tread_run[0] or tread_run, tread_rise) + td_vector s0 = V_(custom_first_last_tread_run[0] or tread_run, tread_rise) + td_vector
# comes from y = stair_tan * x + b # comes from y = stair_tan * x + b
b = s0.y - stair_tan * s0.x b = s0.y - stair_tan * s0.x
@@ -1250,7 +1253,7 @@ class Model(bonsai.core.tool.Model):
y = stair_tan * x + b y = stair_tan * x + b
elif x is None: elif x is None:
x = (y - b) / stair_tan x = (y - b) / stair_tan
return V(x, y) return V_(x, y)
# top nib # top nib
last_vert = vertices[-1] last_vert = vertices[-1]
@@ -16,18 +16,24 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
from math import cos, tan, pi from math import cos, tan, pi
from pathlib import Path from pathlib import Path
from itertools import chain from itertools import chain
from ifcopenshell.util.shape_builder import ShapeBuilder, V from ifcopenshell.util.shape_builder import ShapeBuilder, V, np_to_3d, np_normalized
from typing import Optional, Union
GeneratorOutput = Union[list[ifcopenshell.entity_instance], list[list[ifcopenshell.entity_instance]]]
class LibraryGenerator: class LibraryGenerator:
def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"): def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"):
ifcopenshell.api.pre_listeners = {} ifcopenshell.api.pre_listeners = {}
ifcopenshell.api.post_listeners = {} ifcopenshell.api.post_listeners = {}
np_X, np_Y, np_Z = 0, 1, 2
self.materials = {} self.materials = {}
@@ -65,10 +71,16 @@ class LibraryGenerator:
builder = ShapeBuilder(self.file) builder = ShapeBuilder(self.file)
def create_box_objects(width, depth, height, shift_to_center=False, return_representations=False): def create_box_objects(
width: float,
depth: float,
height: float,
shift_to_center: bool = False,
return_representations: bool = False,
) -> Union[list[ifcopenshell.entity_instance], list[list[ifcopenshell.entity_instance]]]:
output = [] output = []
rectangle = builder.rectangle(size=V(width, depth)) rectangle = builder.rectangle(size=(width, depth))
box = builder.extrude(builder.profile(rectangle), height) box = builder.extrude(builder.profile(rectangle), height)
if return_representations: if return_representations:
@@ -86,18 +98,20 @@ class LibraryGenerator:
if shift_to_center: if shift_to_center:
shift_to_center = V(-width / 2, -depth / 2) shift_to_center = V(-width / 2, -depth / 2)
builder.translate(output[0], shift_to_center.to_3d()) builder.translate(output[0], np_to_3d(shift_to_center))
builder.translate(output[1], shift_to_center) builder.translate(output[1], shift_to_center)
return output return output
def create_fillet_rectangle(size=None, position=None, fillet_radius=50.0): def create_fillet_rectangle(
size: Optional[np.ndarray] = None, position: Optional[np.ndarray] = None, fillet_radius: float = 50.0
) -> ifcopenshell.entity_instance:
"""`fillet_radius` is either float or list of floats for each corner """`fillet_radius` is either float or list of floats for each corner
in counter-clockwise order starting from bottom left""" in counter-clockwise order starting from bottom left"""
kwargs = dict() kwargs = dict()
if size: if size is not None:
kwargs["size"] = size kwargs["size"] = size
if position: if position is not None:
kwargs["position"] = position kwargs["position"] = position
_, _, rectangle = builder.get_simple_2dcurve_data( _, _, rectangle = builder.get_simple_2dcurve_data(
@@ -191,11 +205,13 @@ class LibraryGenerator:
) )
# chairs # chairs
def create_fancy_chair(width, depth, height, seat_level, return_representations=False): def create_fancy_chair(
width: float, depth: float, height: float, seat_level: float, return_representations: bool = False
) -> GeneratorOutput:
thickness = 50.0 thickness = 50.0
shift_to_center = V(-width / 2, 0) shift_to_center = V(-width / 2, 0)
mirror_axis = V(0, 1) mirror_axis = V(0, 1)
output = [] output: GeneratorOutput = []
items_to_adjust = [] items_to_adjust = []
items_3d = [] items_3d = []
@@ -292,7 +308,9 @@ class LibraryGenerator:
"IfcFurnitureType", "Generic Small Bedside Table", representation_3d, representation_2d "IfcFurnitureType", "Generic Small Bedside Table", representation_3d, representation_2d
) )
def create_fancy_rectangle_table(width, depth, height, return_representations=False): def create_fancy_rectangle_table(
width: float, depth: float, height: float, return_representations=False
) -> GeneratorOutput:
output = [] output = []
leg_size = 50.0 leg_size = 50.0
countertop_thickness = 50.0 countertop_thickness = 50.0
@@ -312,7 +330,7 @@ class LibraryGenerator:
legs_profiles = [builder.profile(leg) for leg in legs_curves] legs_profiles = [builder.profile(leg) for leg in legs_curves]
legs = [builder.extrude(leg, height - countertop_thickness) for leg in legs_profiles] legs = [builder.extrude(leg, height - countertop_thickness) for leg in legs_profiles]
items = [countertop] + legs items = [countertop] + legs
builder.translate(items, shift_to_center.to_3d()) builder.translate(items, np_to_3d(shift_to_center))
if return_representations: if return_representations:
representation_3d = builder.get_representation(context=self.representations["model_body"], items=items) representation_3d = builder.get_representation(context=self.representations["model_body"], items=items)
@@ -336,17 +354,17 @@ class LibraryGenerator:
# tables # tables
def create_rectangle_table_with_chairs( def create_rectangle_table_with_chairs(
table_width, table_width: float,
table_depth, table_depth: float,
table_height, table_height: float,
chair_width, chair_width: float,
chair_depth, chair_depth: float,
chair_height, chair_height: float,
seat_level, seat_level: float,
table_chair_gap, table_chair_gap: float,
number_of_seats, number_of_seats: int,
side_seats=False, side_seats: bool = False,
): ) -> tuple[ifcopenshell.entity_instance, ifcopenshell.entity_instance]:
items_table_3d, items_table_2d = create_fancy_rectangle_table(table_width, table_depth, table_height) items_table_3d, items_table_2d = create_fancy_rectangle_table(table_width, table_depth, table_height)
items_chair_3d, items_chair_2d = create_fancy_chair(chair_width, chair_depth, chair_height, seat_level) items_chair_3d, items_chair_2d = create_fancy_chair(chair_width, chair_depth, chair_height, seat_level)
@@ -860,7 +878,7 @@ class LibraryGenerator:
seat_back = builder.extrude(seat_back, height) seat_back = builder.extrude(seat_back, height)
items = armrests + seats + [seat_back] items = armrests + seats + [seat_back]
builder.translate(items, shift_to_center.to_3d()) builder.translate(items, np_to_3d(shift_to_center))
# builder.mirror(items, mirror_axis) # builder.mirror(items, mirror_axis)
representation_3d = builder.get_representation(self.representations["model_body"], items=items) representation_3d = builder.get_representation(self.representations["model_body"], items=items)
@@ -1103,7 +1121,7 @@ class LibraryGenerator:
) )
items_3d_to_mirror.append(cistern_3d) items_3d_to_mirror.append(cistern_3d)
builder.translate(items_3d, shift_to_center.to_3d()) builder.translate(items_3d, np_to_3d(shift_to_center))
builder.mirror(items_3d_to_mirror, mirror_axes=mirror_axis) builder.mirror(items_3d_to_mirror, mirror_axes=mirror_axis)
items_3d += items_3d_to_mirror items_3d += items_3d_to_mirror
@@ -1167,7 +1185,7 @@ class LibraryGenerator:
mask_curve_profile, bottom_mask_height, position=V(0, 0, bottom_height) mask_curve_profile, bottom_mask_height, position=V(0, 0, bottom_height)
) )
builder.translate(items_3d_to_center, shift_to_center.to_3d()) builder.translate(items_3d_to_center, np_to_3d(shift_to_center))
items_3d = items_3d_to_center + [triangle_bottom_extruded, triangle_bottom_wall_mask] items_3d = items_3d_to_center + [triangle_bottom_extruded, triangle_bottom_wall_mask]
builder.mirror(items_3d, mirror_axis) builder.mirror(items_3d, mirror_axis)
@@ -1263,7 +1281,7 @@ class LibraryGenerator:
basin_downside = builder.extrude(rectangle_second_3d, basin_downside_thickness) basin_downside = builder.extrude(rectangle_second_3d, basin_downside_thickness)
items_3d = [main_basin, basin_downside] items_3d = [main_basin, basin_downside]
builder.translate(items_3d, shift_to_center.to_3d()) builder.translate(items_3d, np_to_3d(shift_to_center))
representation_3d = builder.get_representation(self.representations["model_body"], items_3d) representation_3d = builder.get_representation(self.representations["model_body"], items_3d)
return representation_3d, representation_2d return representation_3d, representation_2d
@@ -1424,19 +1442,21 @@ class LibraryGenerator:
"IfcFurnitureType", "Neufert Commercial Office Desk and Chair", representation_3d, representation_2d "IfcFurnitureType", "Neufert Commercial Office Desk and Chair", representation_3d, representation_2d
) )
def create_dishwasher(width, depth, height): def create_dishwasher(
width: float, depth: float, height: float
) -> tuple[ifcopenshell.entity_instance, ifcopenshell.entity_instance]:
items_3d, items_2d = create_box_objects(width, depth, height) items_3d, items_2d = create_box_objects(width, depth, height)
circle_first_r = width * 0.1 circle_first_r = width * 0.1
circle_second_r = width * 0.3 circle_second_r = width * 0.3
center = V(width / 2, depth / 2) center = V(width / 2, depth / 2)
k = center.y / center.x k = center[np_Y] / center[np_X]
circle_first = builder.circle(center=center, radius=circle_second_r) circle_first = builder.circle(center=center, radius=circle_second_r)
circle_second = builder.circle(center=center, radius=circle_first_r) circle_second = builder.circle(center=center, radius=circle_first_r)
polyline = builder.polyline([(0.0, 0.0), center - center.normalized() * circle_second_r]) polyline = builder.polyline([(0.0, 0.0), center - np_normalized(center) * circle_second_r])
mirrored_polylines = builder.mirror( mirrored_polylines = builder.mirror(
polyline, mirror_axes=[V(1, 0), V(0, 1), V(1, 1)], mirror_point=center, create_copy=True polyline, mirror_axes=[(1, 0), (0, 1), (1, 1)], mirror_point=center, create_copy=True
) )
items_2d.extend([circle_first, circle_second, polyline] + mirrored_polylines) items_2d.extend([circle_first, circle_second, polyline] + mirrored_polylines)
@@ -1591,13 +1611,13 @@ class LibraryGenerator:
position=V(rect_offset / 2, rect_offset / 2) + sink_offset, position=V(rect_offset / 2, rect_offset / 2) + sink_offset,
) )
circle_position = sink_offset + V((width - sink_offset.x) / 2, depth - rect_offset * 2 - sink_radius) circle_position = sink_offset + V((width - sink_offset[np_X]) / 2, depth - rect_offset * 2 - sink_radius)
sink_circle = builder.circle(circle_position, radius=sink_radius) sink_circle = builder.circle(circle_position, radius=sink_radius)
faucet = builder.polyline( faucet = builder.polyline(
( (
((width - sink_offset.x) / 2, faucet_depth), ((width - sink_offset[np_X]) / 2, faucet_depth),
((width - sink_offset.x) / 2, faucet_depth - faucet_length), ((width - sink_offset[np_X]) / 2, faucet_depth - faucet_length),
) )
) )
builder.translate(faucet, sink_offset) builder.translate(faucet, sink_offset)
@@ -1626,7 +1646,7 @@ class LibraryGenerator:
sink_table_bottom = builder.extrude(sink_table, height - sink_height) sink_table_bottom = builder.extrude(sink_table, height - sink_height)
items_3d.append(sink_table_bottom) items_3d.append(sink_table_bottom)
builder.translate(items_3d, shift_to_center.to_3d()) builder.translate(items_3d, np_to_3d(shift_to_center))
representation_2d = builder.get_representation(self.representations["plan_body"], items=items_2d) representation_2d = builder.get_representation(self.representations["plan_body"], items=items_2d)
representation_3d = builder.get_representation(self.representations["model_body"], items=items_3d) representation_3d = builder.get_representation(self.representations["model_body"], items=items_3d)
@@ -26,7 +26,7 @@ import bonsai.tool as tool
from math import cos, sin, tan, pi from math import cos, sin, tan, pi
from pathlib import Path from pathlib import Path
from itertools import chain from itertools import chain
from ifcopenshell.util.shape_builder import ShapeBuilder, V from ifcopenshell.util.shape_builder import ShapeBuilder
from collections import namedtuple from collections import namedtuple
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from random import uniform from random import uniform
@@ -24,7 +24,7 @@ import ifcopenshell.api
import boltspy as bolts import boltspy as bolts
from math import cos, pi from math import cos, pi
from pathlib import Path from pathlib import Path
from ifcopenshell.util.shape_builder import ShapeBuilder, V from ifcopenshell.util.shape_builder import ShapeBuilder, V, ifc_safe_vector_type
class LibraryGenerator: class LibraryGenerator:
@@ -201,20 +201,20 @@ class LibraryGenerator:
if mode == "LLBB": if mode == "LLBB":
offset = profile.Depth/2 + profiles_gap offset = profile.Depth/2 + profiles_gap
if mirrored: if mirrored:
transform.Axis1 = self.file.createIfcDirection(V(0, 1)) transform.Axis1 = self.file.createIfcDirection(ifc_safe_vector_type(V(0, 1)))
transform.Axis2 = self.file.createIfcDirection(V(-1, 0)) transform.Axis2 = self.file.createIfcDirection(ifc_safe_vector_type(V(-1, 0)))
transform.LocalOrigin.Coordinates = V(-offset, 0) transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(-offset, 0))
else: else:
transform.LocalOrigin.Coordinates = V(offset, 0) transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(offset, 0))
transform.Axis1 = self.file.createIfcDirection(V(0, 1)) transform.Axis1 = self.file.createIfcDirection(ifc_safe_vector_type(V(0, 1)))
transform.Axis2 = self.file.createIfcDirection(V(1, 0)) transform.Axis2 = self.file.createIfcDirection(ifc_safe_vector_type(V(1, 0)))
elif mode == "SLBB": elif mode == "SLBB":
offset = profile.Width/2 + profiles_gap offset = profile.Width/2 + profiles_gap
if mirrored: if mirrored:
transform.Axis1 = self.file.createIfcDirection(V(-1, 0)) transform.Axis1 = self.file.createIfcDirection(ifc_safe_vector_type(V(-1, 0)))
transform.LocalOrigin.Coordinates = V(-offset, 0) transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(-offset, 0))
else: else:
transform.LocalOrigin.Coordinates = V(offset, 0) transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(offset, 0))
return derived_profile return derived_profile
+1 -1
View File
@@ -25,7 +25,7 @@ import bonsai.tool as tool
from test.bim.bootstrap import NewFile from test.bim.bootstrap import NewFile
from bonsai.tool.loader import Loader as subject from bonsai.tool.loader import Loader as subject
import numpy as np import numpy as np
from ifcopenshell.util.shape_builder import ShapeBuilder, V from ifcopenshell.util.shape_builder import ShapeBuilder
from mathutils import Vector from mathutils import Vector
from pathlib import Path from pathlib import Path
@@ -16,6 +16,7 @@
# 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 __future__ import annotations
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
import collections import collections
@@ -27,21 +28,42 @@ import ifcopenshell.util.placement
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.unit import ifcopenshell.util.unit
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt from math import cos, sin, pi, tan, radians, degrees, atan, sqrt
from typing import Union, Optional, Literal, Any, Sequence from typing import Union, Optional, Literal, Any, Sequence, TYPE_CHECKING
from itertools import chain from itertools import chain
from mathutils import Vector, Matrix from mathutils import Vector
V = lambda *x: Vector([float(i) for i in x])
PRECISION = 1.0e-5 PRECISION = 1.0e-5
VectorTuple = type[tuple[float, float, float]]
"tuple of 3 `float` values"
# Support both numpy arrays and python sequences as inputs. if TYPE_CHECKING:
VectorType = Union[Sequence[float], Vector, np.ndarray] # NOTE: mathutils is never used at runtime in ifcopenshell,
# only for type checking to ensure methods are compatible with
# Blender vectors.
from mathutils import Vector
# Support both numpy arrays and python sequences as inputs.
VectorType = Union[Sequence[float], Vector, np.ndarray]
else:
# Ensure it's exportable, so other modules can reuse it for typing.
VectorType = ...
SequenceOfVectors = Union[Sequence[VectorType], np.ndarray] SequenceOfVectors = Union[Sequence[VectorType], np.ndarray]
def V(*args: Union[float, VectorType, SequenceOfVectors]) -> npt.NDArray[np.float64]:
"""Convert floats / vector / sequence of vectors to numpy array.
Note that `float` argument type also allows passing ints,
which will be converted to floats (a double type) as IfcOpenShell is strict
about setting int/float attributes.
"""
if isinstance(args[0], (float, int)):
return np.array(args, dtype="d")
assert len(args) == 1, "Only single argument is supported if providing a vector or a sequence of them."
return np.array(args[0], dtype="d")
def ifc_safe_vector_type(v: Union[VectorType, SequenceOfVectors]) -> Any: def ifc_safe_vector_type(v: Union[VectorType, SequenceOfVectors]) -> Any:
"""Convert vector / sequence of vectors to a list of floats """Convert vector / sequence of vectors to a list of floats
that's safe to save IFC attribute. that's safe to save IFC attribute.
@@ -9,7 +9,7 @@ import ifcopenshell.api.unit
import ifcopenshell.geom import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.shape import ifcopenshell.util.shape
from ifcopenshell.util.shape_builder import ShapeBuilder, V from ifcopenshell.util.shape_builder import ShapeBuilder
from typing import get_args from typing import get_args