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.schema
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
import bonsai.tool as tool
import bonsai.core.geometry
import bonsai.core.geometry as core
@@ -40,6 +39,8 @@ from typing import Union, Any, Optional
import json
import collections
V_ = tool.Blender.V_
def update_door_modifier_representation(context: bpy.types.Context) -> None:
obj = context.active_object
@@ -188,15 +189,15 @@ def bm_sort_out_geom(
def bm_mirror(
bm: bmesh.types.BMesh,
verts: list[bmesh.types.BMVert],
mirror_axes: Vector = V(1, 0, 0).freeze(),
mirror_point: Vector = V(0, 0, 0).freeze(),
mirror_axes: Vector = V_(1, 0, 0).freeze(),
mirror_point: Vector = V_(0, 0, 0).freeze(),
create_copy: bool = False,
) -> list[bmesh.types.BMVert]:
matrix = Matrix.Translation(mirror_point)
for i, v in enumerate(mirror_axes):
if not v:
continue
mirror_axis = V(0, 0, 0)
mirror_axis = V_(0, 0, 0)
mirror_axis[i] = 1.0
matrix = matrix @ Matrix.Scale(-1, 4, mirror_axis)
matrix = matrix @ Matrix.Translation(-mirror_point)
@@ -219,9 +220,9 @@ def create_bm_extruded_profile(
points: list[Vector],
edges: Optional[list[tuple[int, 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,
extrusion_vector: Vector = V(0, 0, 1).freeze(),
extrusion_vector: Vector = V_(0, 0, 1).freeze(),
) -> list[bmesh.types.BMVert]:
bm.verts.index_update()
bm.edges.index_update()
@@ -250,7 +251,7 @@ def create_bm_extruded_profile(
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]:
"""`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
# handle dimensions (hardcoded)
handle_size = V(120, 40, 20) * 0.001
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_size = V_(120, 40, 20) * 0.001
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
if transfom_offset:
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()
# 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_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:
threshold_verts = []
else:
threshold_size = V(threshold_width, threshold_depth, threshold_thickness)
threshold_position = V(side_lining_thickness, threshold_offset, 0)
threshold_size = V_(threshold_width, threshold_depth, threshold_thickness)
threshold_position = V_(side_lining_thickness, threshold_offset, 0)
threshold_verts = create_bm_box(bm, threshold_size, threshold_position)
# 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
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_size = V_(overall_width + casing_wall_overlap * 2, casing_depth, overall_height + casing_wall_overlap)
casing_position = V_(-casing_wall_overlap, -casing_depth, 0)
outer_casing_verts = create_bm_door_lining(bm, casing_size, outer_casing_thickness, casing_position)
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)
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))
# add door handle
handle_points = [
V(0, 0, 0),
V(0, -handle_size.y, 0),
V(handle_size.x, -handle_size.y, 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, 0, 0),
V_(0, 0, 0),
V_(0, -handle_size.y, 0),
V_(handle_size.x, -handle_size.y, 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, 0, 0),
]
handle_position = panel_position + handle_offset - handle_center_offset
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":
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(
bm,
door_handle_verts,
mirror_axes=V(0, 1, 0),
mirror_point=handle_position + V(0, panel_size.y / 2, 0),
mirror_axes=V_(0, 1, 0),
mirror_point=handle_position + V_(0, panel_size.y / 2, 0),
create_copy=True,
)
door_verts.extend(door_handle_mirrored_verts)
return door_verts
door_verts = []
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_size = V_(panel_width, panel_depth, panel_height)
panel_position = V_(lining_to_panel_offset_x, lining_to_panel_offset_y, threshold_thickness)
if double_door:
# 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
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
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))
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))
else:
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))
@@ -474,9 +475,9 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
transom_thickness,
]
window_lining_thickness.append(transom_thickness)
window_lining_size = V(overall_width, lining_depth, window_lining_height)
window_position = V(0, 0, overall_height - window_lining_height)
frame_size = V(door_opening_width, frame_depth, frame_height)
window_lining_size = V_(overall_width, lining_depth, window_lining_height)
window_position = V_(0, 0, overall_height - window_lining_height)
frame_size = V_(door_opening_width, frame_depth, frame_height)
window_lining_verts, frame_verts, glass_verts = create_bm_window(
bm,
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
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)
if bpy.context.active_object.mode == "EDIT":
@@ -23,7 +23,6 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.representation
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
import bonsai.core.root
import bonsai.core.geometry
import bonsai.tool as tool
@@ -125,6 +124,7 @@ def update_railing_modifier_bmesh(context: bpy.types.Context) -> None:
"""
obj = context.active_object
props = obj.BIMRailingProperties
V_ = tool.Blender.V_
# NOTE: using Data since bmesh update will hapen very often
if not RailingData.is_loaded:
@@ -171,8 +171,8 @@ def update_railing_modifier_bmesh(context: bpy.types.Context) -> None:
v0, v1 = main_edge.verts
edge_dissolving_verts.extend([v0, v1])
edge_dir = ((v1.co - v0.co) * V(1, 1, 0)).normalized()
ortho_vector = edge_dir.cross(V(0, 0, 1))
edge_dir = ((v1.co - v0.co) * V_(1, 1, 0)).normalized()
ortho_vector = edge_dir.cross(V_(0, 0, 1))
extruded_geom = bmesh.ops.extrude_edge_only(bm, edges=[main_edge])["geom"]
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.shape_builder
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
from bmesh.types import BMVert
from mathutils import Vector
from typing import Optional
V_ = tool.Blender.V_
def update_window_modifier_representation(context: bpy.types.Context) -> None:
obj = context.active_object
@@ -131,7 +132,7 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None:
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]:
"""`thickness` of the profile is defined as list in the following order:
`(LEFT, TOP, RIGHT, BOTTOM)`
@@ -198,7 +199,7 @@ def create_bm_window_frame(
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]:
"""create a box of `size`, position box first vertex at `position`"""
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 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)
# 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_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)
@@ -324,7 +325,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
frame_thickness = props.frame_thickness[panel_i]
lining_to_panel_offset_y_full = (lining_depth - frame_depth) + lining_to_panel_offset_y
# add window
window_lining_size = V(
window_lining_size = V_(
panel_width,
lining_depth,
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.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(
bm,
window_lining_size,
@@ -377,7 +378,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
accumulated_height[column_i] += panel_height
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)
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":
return blender_binary_path.parents[1].name.rsplit("__", 1)[-1]
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 functools import partial
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.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
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
T = TypeVar("T")
V_ = tool.Blender.V_
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)
nosing_overlap = max(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():
vertices.append(Vector([0, 0]))
@@ -1134,16 +1137,16 @@ class Model(bonsai.core.tool.Model):
default_tread_edges = np.array(((0, 1), (1, 2)))
# horizontal tread line
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:
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
# kind of L shape
default_tread_verts = (
V(0, tread_rise - nosing_depth),
V(-nosing_overlap, tread_rise - nosing_depth),
V(-nosing_overlap, tread_rise),
V(tread_run, tread_rise),
V_(0, tread_rise - nosing_depth),
V_(-nosing_overlap, tread_rise - nosing_depth),
V_(-nosing_overlap, tread_rise),
V_(tread_run, tread_rise),
)
add_edges = ((2, 3), (3, 4))
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
# treads
current_offset = V(0, 0)
current_offset = V_(0, 0)
for i in range(number_of_risers):
last_vert_i = len(vertices) - 1
tread_offset, tread_verts = get_tread_data(i)
@@ -1178,9 +1181,9 @@ class Model(bonsai.core.tool.Model):
if stair_type == "WOOD/STEEL":
builder = ShapeBuilder(None)
# full tread rectangle
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_offset = V(tread_run + nosing_tread_gap, 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_offset = V_(tread_run + nosing_tread_gap, tread_rise)
def get_tread_data(i):
if custom_tread_run:
@@ -1193,12 +1196,12 @@ class Model(bonsai.core.tool.Model):
if current_tread_run:
tread_offset = default_tread_offset.copy()
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 default_tread_offset, default_tread_verts
# each tread is a separate shape
cur_offset = V(0, 0)
cur_offset = V_(0, 0)
for i in range(number_of_risers):
tread_offset, tread_verts = get_tread_data(i)
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
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)])
# flip edges direction for ccw polygon winding order
@@ -1233,15 +1236,15 @@ class Model(bonsai.core.tool.Model):
# from the tread diagonal line
# 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
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 = 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
# s0 is just a sampled point from the bottom line
# we stick to the third point as the first point
# 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
b = s0.y - stair_tan * s0.x
@@ -1250,7 +1253,7 @@ class Model(bonsai.core.tool.Model):
y = stair_tan * x + b
elif x is None:
x = (y - b) / stair_tan
return V(x, y)
return V_(x, y)
# top nib
last_vert = vertices[-1]
@@ -16,18 +16,24 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from math import cos, tan, pi
from pathlib import Path
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:
def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"):
ifcopenshell.api.pre_listeners = {}
ifcopenshell.api.post_listeners = {}
np_X, np_Y, np_Z = 0, 1, 2
self.materials = {}
@@ -65,10 +71,16 @@ class LibraryGenerator:
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 = []
rectangle = builder.rectangle(size=V(width, depth))
rectangle = builder.rectangle(size=(width, depth))
box = builder.extrude(builder.profile(rectangle), height)
if return_representations:
@@ -86,18 +98,20 @@ class LibraryGenerator:
if shift_to_center:
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)
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
in counter-clockwise order starting from bottom left"""
kwargs = dict()
if size:
if size is not None:
kwargs["size"] = size
if position:
if position is not None:
kwargs["position"] = position
_, _, rectangle = builder.get_simple_2dcurve_data(
@@ -191,11 +205,13 @@ class LibraryGenerator:
)
# 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
shift_to_center = V(-width / 2, 0)
mirror_axis = V(0, 1)
output = []
output: GeneratorOutput = []
items_to_adjust = []
items_3d = []
@@ -292,7 +308,9 @@ class LibraryGenerator:
"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 = []
leg_size = 50.0
countertop_thickness = 50.0
@@ -312,7 +330,7 @@ class LibraryGenerator:
legs_profiles = [builder.profile(leg) for leg in legs_curves]
legs = [builder.extrude(leg, height - countertop_thickness) for leg in legs_profiles]
items = [countertop] + legs
builder.translate(items, shift_to_center.to_3d())
builder.translate(items, np_to_3d(shift_to_center))
if return_representations:
representation_3d = builder.get_representation(context=self.representations["model_body"], items=items)
@@ -336,17 +354,17 @@ class LibraryGenerator:
# tables
def create_rectangle_table_with_chairs(
table_width,
table_depth,
table_height,
chair_width,
chair_depth,
chair_height,
seat_level,
table_chair_gap,
number_of_seats,
side_seats=False,
):
table_width: float,
table_depth: float,
table_height: float,
chair_width: float,
chair_depth: float,
chair_height: float,
seat_level: float,
table_chair_gap: float,
number_of_seats: int,
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_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)
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)
representation_3d = builder.get_representation(self.representations["model_body"], items=items)
@@ -1103,7 +1121,7 @@ class LibraryGenerator:
)
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)
items_3d += items_3d_to_mirror
@@ -1167,7 +1185,7 @@ class LibraryGenerator:
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]
builder.mirror(items_3d, mirror_axis)
@@ -1263,7 +1281,7 @@ class LibraryGenerator:
basin_downside = builder.extrude(rectangle_second_3d, basin_downside_thickness)
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)
return representation_3d, representation_2d
@@ -1424,19 +1442,21 @@ class LibraryGenerator:
"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)
circle_first_r = width * 0.1
circle_second_r = width * 0.3
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_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(
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)
@@ -1591,13 +1611,13 @@ class LibraryGenerator:
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)
faucet = builder.polyline(
(
((width - sink_offset.x) / 2, faucet_depth),
((width - sink_offset.x) / 2, faucet_depth - faucet_length),
((width - sink_offset[np_X]) / 2, faucet_depth),
((width - sink_offset[np_X]) / 2, faucet_depth - faucet_length),
)
)
builder.translate(faucet, sink_offset)
@@ -1626,7 +1646,7 @@ class LibraryGenerator:
sink_table_bottom = builder.extrude(sink_table, height - sink_height)
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_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 pathlib import Path
from itertools import chain
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from ifcopenshell.util.shape_builder import ShapeBuilder
from collections import namedtuple
from mathutils import Vector, Matrix
from random import uniform
@@ -24,7 +24,7 @@ import ifcopenshell.api
import boltspy as bolts
from math import cos, pi
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:
@@ -201,20 +201,20 @@ class LibraryGenerator:
if mode == "LLBB":
offset = profile.Depth/2 + profiles_gap
if mirrored:
transform.Axis1 = self.file.createIfcDirection(V(0, 1))
transform.Axis2 = self.file.createIfcDirection(V(-1, 0))
transform.LocalOrigin.Coordinates = V(-offset, 0)
transform.Axis1 = self.file.createIfcDirection(ifc_safe_vector_type(V(0, 1)))
transform.Axis2 = self.file.createIfcDirection(ifc_safe_vector_type(V(-1, 0)))
transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(-offset, 0))
else:
transform.LocalOrigin.Coordinates = V(offset, 0)
transform.Axis1 = self.file.createIfcDirection(V(0, 1))
transform.Axis2 = self.file.createIfcDirection(V(1, 0))
transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(offset, 0))
transform.Axis1 = self.file.createIfcDirection(ifc_safe_vector_type(V(0, 1)))
transform.Axis2 = self.file.createIfcDirection(ifc_safe_vector_type(V(1, 0)))
elif mode == "SLBB":
offset = profile.Width/2 + profiles_gap
if mirrored:
transform.Axis1 = self.file.createIfcDirection(V(-1, 0))
transform.LocalOrigin.Coordinates = V(-offset, 0)
transform.Axis1 = self.file.createIfcDirection(ifc_safe_vector_type(V(-1, 0)))
transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(-offset, 0))
else:
transform.LocalOrigin.Coordinates = V(offset, 0)
transform.LocalOrigin.Coordinates = ifc_safe_vector_type(V(offset, 0))
return derived_profile
+1 -1
View File
@@ -25,7 +25,7 @@ import bonsai.tool as tool
from test.bim.bootstrap import NewFile
from bonsai.tool.loader import Loader as subject
import numpy as np
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from ifcopenshell.util.shape_builder import ShapeBuilder
from mathutils import Vector
from pathlib import Path
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import numpy as np
import numpy.typing as npt
import collections
@@ -27,21 +28,42 @@ import ifcopenshell.util.placement
import ifcopenshell.util.representation
import ifcopenshell.util.unit
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 mathutils import Vector, Matrix
from mathutils import Vector
V = lambda *x: Vector([float(i) for i in x])
PRECISION = 1.0e-5
VectorTuple = type[tuple[float, float, float]]
"tuple of 3 `float` values"
# Support both numpy arrays and python sequences as inputs.
VectorType = Union[Sequence[float], Vector, np.ndarray]
if TYPE_CHECKING:
# 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]
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:
"""Convert vector / sequence of vectors to a list of floats
that's safe to save IFC attribute.
@@ -9,7 +9,7 @@ import ifcopenshell.api.unit
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.shape
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from ifcopenshell.util.shape_builder import ShapeBuilder
from typing import get_args