mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
shape_builder - remove mathutils dependency completely #5192
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user