mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 10:51:13 +00:00
Apply black formatting and ruff isort fixes
Pre-commit checklist: black + ruff check. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -124,14 +124,16 @@ def class_items(self, context) -> EnumItems:
|
|||||||
|
|
||||||
def type_items(self, context) -> EnumItems:
|
def type_items(self, context) -> EnumItems:
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"TYPE", "No types defined",
|
"TYPE",
|
||||||
|
"No types defined",
|
||||||
lambda ifc: [(str(e.id()), _label(e, e.is_a()), "") for e in ifc.by_type("IfcTypeProduct")],
|
lambda ifc: [(str(e.id()), _label(e, e.is_a()), "") for e in ifc.by_type("IfcTypeProduct")],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def material_items(self, context) -> EnumItems:
|
def material_items(self, context) -> EnumItems:
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"MATERIAL", "No materials defined",
|
"MATERIAL",
|
||||||
|
"No materials defined",
|
||||||
lambda ifc: [(str(e.id()), _label(e), "") for e in ifc.by_type("IfcMaterial")],
|
lambda ifc: [(str(e.id()), _label(e), "") for e in ifc.by_type("IfcMaterial")],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,7 +142,8 @@ def profile_items(self, context) -> EnumItems:
|
|||||||
# ProfileName is optional. Skip unnamed profiles — they can't be
|
# ProfileName is optional. Skip unnamed profiles — they can't be
|
||||||
# meaningfully picked from a flat list.
|
# meaningfully picked from a flat list.
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"PROFILE", "No named profiles",
|
"PROFILE",
|
||||||
|
"No named profiles",
|
||||||
lambda ifc: [
|
lambda ifc: [
|
||||||
(str(e.id()), f"{e.is_a()}: {e.ProfileName}", "")
|
(str(e.id()), f"{e.is_a()}: {e.ProfileName}", "")
|
||||||
for e in ifc.by_type("IfcProfileDef")
|
for e in ifc.by_type("IfcProfileDef")
|
||||||
@@ -151,12 +154,9 @@ def profile_items(self, context) -> EnumItems:
|
|||||||
|
|
||||||
def drawing_items(self, context) -> EnumItems:
|
def drawing_items(self, context) -> EnumItems:
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"DRAWING", "No drawings defined",
|
"DRAWING",
|
||||||
lambda ifc: [
|
"No drawings defined",
|
||||||
(str(e.id()), _label(e), "")
|
lambda ifc: [(str(e.id()), _label(e), "") for e in ifc.by_type("IfcAnnotation") if e.ObjectType == "DRAWING"],
|
||||||
for e in ifc.by_type("IfcAnnotation")
|
|
||||||
if e.ObjectType == "DRAWING"
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -184,7 +184,8 @@ def system_items(self, context) -> EnumItems:
|
|||||||
# IfcStructuralAnalysisModel is a structural-grouping container, not a
|
# IfcStructuralAnalysisModel is a structural-grouping container, not a
|
||||||
# distribution system — excluded to match Bonsai's other system pickers.
|
# distribution system — excluded to match Bonsai's other system pickers.
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"SYSTEM", "No systems defined",
|
"SYSTEM",
|
||||||
|
"No systems defined",
|
||||||
lambda ifc: [
|
lambda ifc: [
|
||||||
(str(e.id()), _label(e, e.is_a()), "")
|
(str(e.id()), _label(e, e.is_a()), "")
|
||||||
for e in ifc.by_type("IfcSystem")
|
for e in ifc.by_type("IfcSystem")
|
||||||
@@ -197,16 +198,15 @@ def group_items(self, context) -> EnumItems:
|
|||||||
# include_subtypes=False so IfcSystem and IfcZone instances don't appear
|
# include_subtypes=False so IfcSystem and IfcZone instances don't appear
|
||||||
# under Group as well — those get their own picker entries.
|
# under Group as well — those get their own picker entries.
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"GROUP", "No groups defined",
|
"GROUP",
|
||||||
lambda ifc: [
|
"No groups defined",
|
||||||
(str(e.id()), _label(e), "")
|
lambda ifc: [(str(e.id()), _label(e), "") for e in ifc.by_type("IfcGroup", include_subtypes=False)],
|
||||||
for e in ifc.by_type("IfcGroup", include_subtypes=False)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def zone_items(self, context) -> EnumItems:
|
def zone_items(self, context) -> EnumItems:
|
||||||
return _build_items(
|
return _build_items(
|
||||||
"ZONE", "No zones defined",
|
"ZONE",
|
||||||
|
"No zones defined",
|
||||||
lambda ifc: [(str(e.id()), _label(e), "") for e in ifc.by_type("IfcZone")],
|
lambda ifc: [(str(e.id()), _label(e), "") for e in ifc.by_type("IfcZone")],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import bpy
|
|||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d, region_2d_to_location_3d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d, region_2d_to_location_3d
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Public iteration order
|
# Public iteration order
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -236,9 +235,7 @@ def view_axis_parallel_face_mask(
|
|||||||
msg = f"expected 6 face normals, got {len(face_normals_world)}"
|
msg = f"expected 6 face normals, got {len(face_normals_world)}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
vx, vy, vz = view_dir_world
|
vx, vy, vz = view_dir_world
|
||||||
return tuple(
|
return tuple(abs(n[0] * vx + n[1] * vy + n[2] * vz) >= threshold for n in face_normals_world)
|
||||||
abs(n[0] * vx + n[1] * vy + n[2] * vz) >= threshold for n in face_normals_world
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -290,9 +287,7 @@ def compute_face_resize(
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _compute_face_quad_scale(
|
def _compute_face_quad_scale(bmin: Any, bmax: Any, axis: int, is_max: bool) -> tuple[float, float]:
|
||||||
bmin: Any, bmax: Any, axis: int, is_max: bool
|
|
||||||
) -> tuple[float, float]:
|
|
||||||
"""Return ``(w, h)`` for the face quad's scale matrix."""
|
"""Return ``(w, h)`` for the face quad's scale matrix."""
|
||||||
w_axis, h_axis = _QUAD_PERP_AXES[(axis, is_max)]
|
w_axis, h_axis = _QUAD_PERP_AXES[(axis, is_max)]
|
||||||
w = float(bmax[w_axis] - bmin[w_axis])
|
w = float(bmax[w_axis] - bmin[w_axis])
|
||||||
@@ -327,9 +322,7 @@ def _shared_edge_corner_keys(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _face_corner_keys(
|
def _face_corner_keys(axis: int, is_max: bool) -> tuple[
|
||||||
axis: int, is_max: bool
|
|
||||||
) -> tuple[
|
|
||||||
tuple[int, int, int],
|
tuple[int, int, int],
|
||||||
tuple[int, int, int],
|
tuple[int, int, int],
|
||||||
tuple[int, int, int],
|
tuple[int, int, int],
|
||||||
@@ -406,13 +399,9 @@ def _compute_face_basis(
|
|||||||
orient: Any,
|
orient: Any,
|
||||||
) -> tuple[Any, Any]:
|
) -> tuple[Any, Any]:
|
||||||
"""World-space (translation, outward-normal-direction) for one face."""
|
"""World-space (translation, outward-normal-direction) for one face."""
|
||||||
rotated_face_local = (
|
rotated_face_local = cage_rotation.to_3x3() @ (face_local - pivot_local) + pivot_local
|
||||||
cage_rotation.to_3x3() @ (face_local - pivot_local) + pivot_local
|
|
||||||
)
|
|
||||||
face_world = mw @ rotated_face_local
|
face_world = mw @ rotated_face_local
|
||||||
world_axis = (
|
world_axis = (mw_rot @ cage_rotation.to_3x3() @ (orient.to_3x3() @ Vector((0.0, 0.0, 1.0)))).normalized()
|
||||||
mw_rot @ cage_rotation.to_3x3() @ (orient.to_3x3() @ Vector((0.0, 0.0, 1.0)))
|
|
||||||
).normalized()
|
|
||||||
return face_world, world_axis
|
return face_world, world_axis
|
||||||
|
|
||||||
|
|
||||||
@@ -431,13 +420,7 @@ def _compose_face_matrix_basis(
|
|||||||
world-space face rectangle, including the host's scale.
|
world-space face rectangle, including the host's scale.
|
||||||
"""
|
"""
|
||||||
quad_scale = Matrix.Diagonal((w, h, 1.0, 1.0))
|
quad_scale = Matrix.Diagonal((w, h, 1.0, 1.0))
|
||||||
return (
|
return Matrix.Translation(face_world) @ mw_rot_scale.to_4x4() @ cage_rotation @ orient @ quad_scale
|
||||||
Matrix.Translation(face_world)
|
|
||||||
@ mw_rot_scale.to_4x4()
|
|
||||||
@ cage_rotation
|
|
||||||
@ orient
|
|
||||||
@ quad_scale
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _compute_box_corners_world(
|
def _compute_box_corners_world(
|
||||||
@@ -499,9 +482,7 @@ def _world_radius_to_screen_pixels(
|
|||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
right = Vector((1.0, 0.0, 0.0))
|
right = Vector((1.0, 0.0, 0.0))
|
||||||
sample_world = center_world + right * world_radius
|
sample_world = center_world + right * world_radius
|
||||||
return _world_segment_to_screen_pixels(
|
return _world_segment_to_screen_pixels(region, rv3d, center_world, sample_world, min_pixels=min_pixels)
|
||||||
region, rv3d, center_world, sample_world, min_pixels=min_pixels
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _world_segment_to_screen_pixels(
|
def _world_segment_to_screen_pixels(
|
||||||
@@ -601,9 +582,7 @@ class BIM_GT_box_face_quad(bpy.types.Gizmo): # noqa: N801 — Blender bl_idname
|
|||||||
# Freeze the projection plane at invoke — projection-plane
|
# Freeze the projection plane at invoke — projection-plane
|
||||||
# drift on tilted axes causes exponential delta runaway.
|
# drift on tilted axes causes exponential delta runaway.
|
||||||
self.depth_point = self.matrix_basis.translation.copy()
|
self.depth_point = self.matrix_basis.translation.copy()
|
||||||
self.start_location = region_2d_to_location_3d(
|
self.start_location = region_2d_to_location_3d(region, rv3d, (event.mouse_x, event.mouse_y), self.depth_point)
|
||||||
region, rv3d, (event.mouse_x, event.mouse_y), self.depth_point
|
|
||||||
)
|
|
||||||
|
|
||||||
if getattr(self, "_group", None) is not None:
|
if getattr(self, "_group", None) is not None:
|
||||||
self._group._lock_for(self)
|
self._group._lock_for(self)
|
||||||
@@ -629,9 +608,7 @@ class BIM_GT_box_face_quad(bpy.types.Gizmo): # noqa: N801 — Blender bl_idname
|
|||||||
rv3d = context.region_data
|
rv3d = context.region_data
|
||||||
if region is None or rv3d is None:
|
if region is None or rv3d is None:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
end_location = region_2d_to_location_3d(
|
end_location = region_2d_to_location_3d(region, rv3d, (event.mouse_x, event.mouse_y), self.depth_point)
|
||||||
region, rv3d, (event.mouse_x, event.mouse_y), self.depth_point
|
|
||||||
)
|
|
||||||
delta = (end_location - self.start_location).dot(self.axis)
|
delta = (end_location - self.start_location).dot(self.axis)
|
||||||
if "SNAP" in tweak:
|
if "SNAP" in tweak:
|
||||||
delta = round(delta, 1)
|
delta = round(delta, 1)
|
||||||
@@ -639,9 +616,7 @@ class BIM_GT_box_face_quad(bpy.types.Gizmo): # noqa: N801 — Blender bl_idname
|
|||||||
delta /= 10.0
|
delta /= 10.0
|
||||||
self.move_set_cb(self.init_value + delta)
|
self.move_set_cb(self.init_value + delta)
|
||||||
if context.area:
|
if context.area:
|
||||||
context.area.header_text_set(
|
context.area.header_text_set(f"Value: {self.move_get_cb():.3f} ({delta:.3f})")
|
||||||
f"Value: {self.move_get_cb():.3f} ({delta:.3f})"
|
|
||||||
)
|
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
|
||||||
@@ -740,15 +715,11 @@ def apply_face_quad_layout(
|
|||||||
for route_axis, route_is_max in FACE_ROUTES:
|
for route_axis, route_is_max in FACE_ROUTES:
|
||||||
axis_local = Vector(face_outward_axis_local(route_axis, route_is_max))
|
axis_local = Vector(face_outward_axis_local(route_axis, route_is_max))
|
||||||
n_world = (mw_rot @ cage_rotation_3x3 @ axis_local).normalized()
|
n_world = (mw_rot @ cage_rotation_3x3 @ axis_local).normalized()
|
||||||
face_normals_world.append(
|
face_normals_world.append((float(n_world.x), float(n_world.y), float(n_world.z)))
|
||||||
(float(n_world.x), float(n_world.y), float(n_world.z))
|
|
||||||
)
|
|
||||||
front = front_facing_face_mask(tuple(face_normals_world), view_dir_tuple)
|
front = front_facing_face_mask(tuple(face_normals_world), view_dir_tuple)
|
||||||
|
|
||||||
box_center_world = mw @ pivot_local
|
box_center_world = mw @ pivot_local
|
||||||
corners_world = _compute_box_corners_world(
|
corners_world = _compute_box_corners_world(bmin, bmax, pivot_local, cage_rotation_3x3, mw)
|
||||||
bmin, bmax, pivot_local, cage_rotation_3x3, mw
|
|
||||||
)
|
|
||||||
route_to_index = {route: i for i, route in enumerate(FACE_ROUTES)}
|
route_to_index = {route: i for i, route in enumerate(FACE_ROUTES)}
|
||||||
|
|
||||||
for i, route in enumerate(FACE_ROUTES):
|
for i, route in enumerate(FACE_ROUTES):
|
||||||
@@ -787,9 +758,7 @@ def apply_face_quad_layout(
|
|||||||
orient,
|
orient,
|
||||||
)
|
)
|
||||||
w, h = _compute_face_quad_scale(bmin, bmax, axis_b, is_max_b)
|
w, h = _compute_face_quad_scale(bmin, bmax, axis_b, is_max_b)
|
||||||
quad_gz.matrix_basis = _compose_face_matrix_basis(
|
quad_gz.matrix_basis = _compose_face_matrix_basis(face_world, mw_rot_scale, cage_rotation, orient, w, h)
|
||||||
face_world, mw_rot_scale, cage_rotation, orient, w, h
|
|
||||||
)
|
|
||||||
quad_gz.axis = world_axis
|
quad_gz.axis = world_axis
|
||||||
if getattr(quad_gz, "_last_geometry_state", None) != "solid":
|
if getattr(quad_gz, "_last_geometry_state", None) != "solid":
|
||||||
quad_gz.custom_shape = quad_gz.new_custom_shape("TRIS", _QUAD_TRIS)
|
quad_gz.custom_shape = quad_gz.new_custom_shape("TRIS", _QUAD_TRIS)
|
||||||
@@ -800,16 +769,9 @@ def apply_face_quad_layout(
|
|||||||
# Back-facing: anchor at the back face centre; build halo strips
|
# Back-facing: anchor at the back face centre; build halo strips
|
||||||
# in the planes of the adjacent FRONT faces, extruded outside
|
# in the planes of the adjacent FRONT faces, extruded outside
|
||||||
# the silhouette toward this face's outward normal.
|
# the silhouette toward this face's outward normal.
|
||||||
face_world = mw @ (
|
face_world = mw @ (cage_rotation_3x3 @ (face_midpoints_local[route] - pivot_local) + pivot_local)
|
||||||
cage_rotation_3x3 @ (face_midpoints_local[route] - pivot_local)
|
|
||||||
+ pivot_local
|
|
||||||
)
|
|
||||||
quad_gz.matrix_basis = Matrix.Translation(face_world)
|
quad_gz.matrix_basis = Matrix.Translation(face_world)
|
||||||
quad_gz.axis = (
|
quad_gz.axis = (mw_rot @ cage_rotation_3x3 @ Vector(face_outward_axis_local(axis_b, is_max_b))).normalized()
|
||||||
mw_rot
|
|
||||||
@ cage_rotation_3x3
|
|
||||||
@ Vector(face_outward_axis_local(axis_b, is_max_b))
|
|
||||||
).normalized()
|
|
||||||
|
|
||||||
adjacent_front_routes = [
|
adjacent_front_routes = [
|
||||||
(axis_a, is_max_a)
|
(axis_a, is_max_a)
|
||||||
@@ -827,9 +789,7 @@ def apply_face_quad_layout(
|
|||||||
face_world_margin = 0.0
|
face_world_margin = 0.0
|
||||||
if region is not None:
|
if region is not None:
|
||||||
sample_end = box_center_world + quad_gz.axis * 1.0
|
sample_end = box_center_world + quad_gz.axis * 1.0
|
||||||
screen_step = _world_segment_to_screen_pixels(
|
screen_step = _world_segment_to_screen_pixels(region, rv3d, box_center_world, sample_end, min_pixels=0.0)
|
||||||
region, rv3d, box_center_world, sample_end, min_pixels=0.0
|
|
||||||
)
|
|
||||||
if screen_step > 0.0:
|
if screen_step > 0.0:
|
||||||
face_world_margin = _FACE_QUAD_HALO_TARGET_PIXELS / screen_step
|
face_world_margin = _FACE_QUAD_HALO_TARGET_PIXELS / screen_step
|
||||||
if face_world_margin <= 0.0 or not adjacent_front_routes:
|
if face_world_margin <= 0.0 or not adjacent_front_routes:
|
||||||
@@ -866,9 +826,7 @@ def apply_face_quad_layout(
|
|||||||
float(wp1.y - face_world.y),
|
float(wp1.y - face_world.y),
|
||||||
float(wp1.z - face_world.z),
|
float(wp1.z - face_world.z),
|
||||||
)
|
)
|
||||||
all_tris.extend(
|
all_tris.extend(_build_strip_tris_relative(local_p0, local_p1, extrusion_local))
|
||||||
_build_strip_tris_relative(local_p0, local_p1, extrusion_local)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not locked:
|
if not locked:
|
||||||
quad_gz.hide = False
|
quad_gz.hide = False
|
||||||
@@ -907,12 +865,12 @@ def apply_face_quad_layout(
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AXIS_COLOR",
|
"AXIS_COLOR",
|
||||||
"BIM_GT_box_face_outline",
|
|
||||||
"BIM_GT_box_face_quad",
|
|
||||||
"FACE_QUAD_ALPHA",
|
"FACE_QUAD_ALPHA",
|
||||||
"FACE_QUAD_ALPHA_HIGHLIGHT",
|
"FACE_QUAD_ALPHA_HIGHLIGHT",
|
||||||
"FACE_QUAD_SELECT_BIAS",
|
"FACE_QUAD_SELECT_BIAS",
|
||||||
"FACE_ROUTES",
|
"FACE_ROUTES",
|
||||||
|
"BIM_GT_box_face_outline",
|
||||||
|
"BIM_GT_box_face_quad",
|
||||||
"apply_face_quad_layout",
|
"apply_face_quad_layout",
|
||||||
"compute_face_resize",
|
"compute_face_resize",
|
||||||
"face_outward_axis_local",
|
"face_outward_axis_local",
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import bonsai.tool as tool
|
|||||||
|
|
||||||
from . import face_quad
|
from . import face_quad
|
||||||
|
|
||||||
|
|
||||||
# Local-frame bounds of the empty's CUBE display. The display spans
|
# Local-frame bounds of the empty's CUBE display. The display spans
|
||||||
# ``[-empty_display_size, +empty_display_size]^3``; Bonsai always sets
|
# ``[-empty_display_size, +empty_display_size]^3``; Bonsai always sets
|
||||||
# ``empty_display_size = 1.0`` on clip-box hosts, so the local box is
|
# ``empty_display_size = 1.0`` on clip-box hosts, so the local box is
|
||||||
@@ -91,9 +90,7 @@ def _make_face_get_cb(gz: Any, group: Any, axis: int, is_max: bool):
|
|||||||
if empty is None:
|
if empty is None:
|
||||||
return 0.0
|
return 0.0
|
||||||
existing = getattr(gz, "_drag_snapshot", None)
|
existing = getattr(gz, "_drag_snapshot", None)
|
||||||
if existing is not None and existing.get("empty_name") == getattr(
|
if existing is not None and existing.get("empty_name") == getattr(empty, "name", None):
|
||||||
empty, "name", None
|
|
||||||
):
|
|
||||||
return float(existing["world_half"])
|
return float(existing["world_half"])
|
||||||
|
|
||||||
world_half = _world_half_extent(empty, axis)
|
world_half = _world_half_extent(empty, axis)
|
||||||
@@ -119,9 +116,7 @@ def _make_ctrl_click_cb(axis: int, is_max: bool):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def _callback(_context: Any, _event: Any) -> None:
|
def _callback(_context: Any, _event: Any) -> None:
|
||||||
bpy.ops.bim.align_view_to_clip_face(
|
bpy.ops.bim.align_view_to_clip_face("INVOKE_DEFAULT", axis=axis, is_max=is_max)
|
||||||
"INVOKE_DEFAULT", axis=axis, is_max=is_max
|
|
||||||
)
|
|
||||||
|
|
||||||
return _callback
|
return _callback
|
||||||
|
|
||||||
@@ -206,12 +201,8 @@ class OBJECT_GGT_bim_clip_box(bpy.types.GizmoGroup): # noqa: N801 — Blender b
|
|||||||
space_type=cls.bl_space_type,
|
space_type=cls.bl_space_type,
|
||||||
region_type=cls.bl_region_type,
|
region_type=cls.bl_region_type,
|
||||||
)
|
)
|
||||||
km.keymap_items.new(
|
km.keymap_items.new("gizmogroup.gizmo_tweak", type="LEFTMOUSE", value="CLICK_DRAG")
|
||||||
"gizmogroup.gizmo_tweak", type="LEFTMOUSE", value="CLICK_DRAG"
|
km.keymap_items.new("gizmogroup.gizmo_tweak", type="LEFTMOUSE", value="PRESS", ctrl=True)
|
||||||
)
|
|
||||||
km.keymap_items.new(
|
|
||||||
"gizmogroup.gizmo_tweak", type="LEFTMOUSE", value="PRESS", ctrl=True
|
|
||||||
)
|
|
||||||
return km
|
return km
|
||||||
|
|
||||||
def setup(self, context: Any) -> None:
|
def setup(self, context: Any) -> None:
|
||||||
@@ -230,9 +221,7 @@ class OBJECT_GGT_bim_clip_box(bpy.types.GizmoGroup): # noqa: N801 — Blender b
|
|||||||
gz._last_geometry_state = "solid"
|
gz._last_geometry_state = "solid"
|
||||||
gz._strips_cache_key = None
|
gz._strips_cache_key = None
|
||||||
gz.color = face_quad.AXIS_COLOR[axis]
|
gz.color = face_quad.AXIS_COLOR[axis]
|
||||||
gz.color_highlight = tuple(
|
gz.color_highlight = tuple(min(1.0, c + 0.3) for c in face_quad.AXIS_COLOR[axis])
|
||||||
min(1.0, c + 0.3) for c in face_quad.AXIS_COLOR[axis]
|
|
||||||
)
|
|
||||||
gz.alpha = face_quad.FACE_QUAD_ALPHA
|
gz.alpha = face_quad.FACE_QUAD_ALPHA
|
||||||
gz.alpha_highlight = face_quad.FACE_QUAD_ALPHA_HIGHLIGHT
|
gz.alpha_highlight = face_quad.FACE_QUAD_ALPHA_HIGHLIGHT
|
||||||
gz.use_draw_modal = True
|
gz.use_draw_modal = True
|
||||||
|
|||||||
@@ -159,9 +159,7 @@ class BIM_OT_add_clip_box(bpy.types.Operator):
|
|||||||
# Default to a 20m cube (scale 10 around [-1, +1] local cube) so
|
# Default to a 20m cube (scale 10 around [-1, +1] local cube) so
|
||||||
# the volume covers a typical building storey or two rather than
|
# the volume covers a typical building storey or two rather than
|
||||||
# the meaningless 2m unit cube. The user resizes with S.
|
# the meaningless 2m unit cube. The user resizes with S.
|
||||||
matrix = Matrix.Translation(context.scene.cursor.location.copy()) @ Matrix.Diagonal(
|
matrix = Matrix.Translation(context.scene.cursor.location.copy()) @ Matrix.Diagonal((10.0, 10.0, 10.0, 1.0))
|
||||||
(10.0, 10.0, 10.0, 1.0)
|
|
||||||
)
|
|
||||||
tool.ClipBox.create_clip_box_empty(context, matrix, name=CLIP_BOX_NAME)
|
tool.ClipBox.create_clip_box_empty(context, matrix, name=CLIP_BOX_NAME)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ from bpy.types import Menu, Panel, UIList
|
|||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
# Per-kind icon for the source-picker menu. Picked from Blender's built-in
|
# Per-kind icon for the source-picker menu. Picked from Blender's built-in
|
||||||
# icon set; semantically close to the kind so users can scan the menu visually.
|
# icon set; semantically close to the kind so users can scan the menu visually.
|
||||||
_SOURCE_MENU_ENTRIES: tuple[tuple[str, str, str], ...] = (
|
_SOURCE_MENU_ENTRIES: tuple[tuple[str, str, str], ...] = (
|
||||||
|
|||||||
@@ -2464,9 +2464,7 @@ class Blender(bonsai.core.tool.Blender):
|
|||||||
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||||
shader.bind()
|
shader.bind()
|
||||||
shader.uniform_float("color", fill_color)
|
shader.uniform_float("color", fill_color)
|
||||||
batch = batch_for_shader(
|
batch = batch_for_shader(shader, "TRIS", {"pos": verts}, indices=tri_indices)
|
||||||
shader, "TRIS", {"pos": verts}, indices=tri_indices
|
|
||||||
)
|
|
||||||
batch.draw(shader)
|
batch.draw(shader)
|
||||||
if outline_color is not None and line_indices:
|
if outline_color is not None and line_indices:
|
||||||
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||||
@@ -2477,9 +2475,7 @@ class Blender(bonsai.core.tool.Blender):
|
|||||||
prev_width = gpu.state.line_width_get()
|
prev_width = gpu.state.line_width_get()
|
||||||
gpu.state.line_width_set(outline_width)
|
gpu.state.line_width_set(outline_width)
|
||||||
try:
|
try:
|
||||||
batch = batch_for_shader(
|
batch = batch_for_shader(shader, "LINES", {"pos": verts}, indices=line_indices)
|
||||||
shader, "LINES", {"pos": verts}, indices=line_indices
|
|
||||||
)
|
|
||||||
batch.draw(shader)
|
batch.draw(shader)
|
||||||
finally:
|
finally:
|
||||||
gpu.state.line_width_set(prev_width)
|
gpu.state.line_width_set(prev_width)
|
||||||
|
|||||||
@@ -44,13 +44,9 @@ class TestAddClipBoxForSourceSpatial(NewFile):
|
|||||||
storey = ifc.create_entity("IfcBuildingStorey")
|
storey = ifc.create_entity("IfcBuildingStorey")
|
||||||
wall_a, _ = _make_ifc_cube(ifc, "IfcWall", location=(0.0, 0.0, 0.0), size=2.0)
|
wall_a, _ = _make_ifc_cube(ifc, "IfcWall", location=(0.0, 0.0, 0.0), size=2.0)
|
||||||
wall_b, _ = _make_ifc_cube(ifc, "IfcWall", location=(4.0, 0.0, 0.0), size=2.0)
|
wall_b, _ = _make_ifc_cube(ifc, "IfcWall", location=(4.0, 0.0, 0.0), size=2.0)
|
||||||
ifcopenshell.api.spatial.assign_container(
|
ifcopenshell.api.spatial.assign_container(ifc, products=[wall_a, wall_b], relating_structure=storey)
|
||||||
ifc, products=[wall_a, wall_b], relating_structure=storey
|
|
||||||
)
|
|
||||||
|
|
||||||
result = bpy.ops.bim.add_clip_box_for_source(
|
result = bpy.ops.bim.add_clip_box_for_source(source_kind="SPATIAL", source_id=str(storey.id()))
|
||||||
source_kind="SPATIAL", source_id=str(storey.id())
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == {"FINISHED"}
|
assert result == {"FINISHED"}
|
||||||
scene_props = tool.ClipBox.get_scene_props()
|
scene_props = tool.ClipBox.get_scene_props()
|
||||||
@@ -71,9 +67,7 @@ class TestAddClipBoxForSourceClass(NewFile):
|
|||||||
_make_ifc_cube(ifc, "IfcWall", location=(4.0, 0.0, 0.0), size=2.0)
|
_make_ifc_cube(ifc, "IfcWall", location=(4.0, 0.0, 0.0), size=2.0)
|
||||||
_make_ifc_cube(ifc, "IfcWindow", location=(20.0, 0.0, 0.0), size=2.0)
|
_make_ifc_cube(ifc, "IfcWindow", location=(20.0, 0.0, 0.0), size=2.0)
|
||||||
|
|
||||||
result = bpy.ops.bim.add_clip_box_for_source(
|
result = bpy.ops.bim.add_clip_box_for_source(source_kind="CLASS", source_id="IfcWall")
|
||||||
source_kind="CLASS", source_id="IfcWall"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == {"FINISHED"}
|
assert result == {"FINISHED"}
|
||||||
scene_props = tool.ClipBox.get_scene_props()
|
scene_props = tool.ClipBox.get_scene_props()
|
||||||
@@ -93,9 +87,7 @@ class TestAddClipBoxForSourceEmpty(NewFile):
|
|||||||
walltype = ifc.create_entity("IfcWallType")
|
walltype = ifc.create_entity("IfcWallType")
|
||||||
# No occurrences linked — TYPE source resolves to 0 elements.
|
# No occurrences linked — TYPE source resolves to 0 elements.
|
||||||
with pytest.raises(RuntimeError, match="No elements found"):
|
with pytest.raises(RuntimeError, match="No elements found"):
|
||||||
bpy.ops.bim.add_clip_box_for_source(
|
bpy.ops.bim.add_clip_box_for_source(source_kind="TYPE", source_id=str(walltype.id()))
|
||||||
source_kind="TYPE", source_id=str(walltype.id())
|
|
||||||
)
|
|
||||||
scene_props = tool.ClipBox.get_scene_props()
|
scene_props = tool.ClipBox.get_scene_props()
|
||||||
assert len(scene_props.clip_boxes) == 0
|
assert len(scene_props.clip_boxes) == 0
|
||||||
|
|
||||||
@@ -105,9 +97,7 @@ class TestAddClipBoxForSourceEmpty(NewFile):
|
|||||||
from bonsai.bim.module.clip_box import data as clip_data
|
from bonsai.bim.module.clip_box import data as clip_data
|
||||||
|
|
||||||
with pytest.raises(RuntimeError, match="No source selected"):
|
with pytest.raises(RuntimeError, match="No source selected"):
|
||||||
bpy.ops.bim.add_clip_box_for_source(
|
bpy.ops.bim.add_clip_box_for_source(source_kind="SPATIAL", source_id=clip_data.NO_OPTIONS_ID)
|
||||||
source_kind="SPATIAL", source_id=clip_data.NO_OPTIONS_ID
|
|
||||||
)
|
|
||||||
scene_props = tool.ClipBox.get_scene_props()
|
scene_props = tool.ClipBox.get_scene_props()
|
||||||
assert len(scene_props.clip_boxes) == 0
|
assert len(scene_props.clip_boxes) == 0
|
||||||
|
|
||||||
|
|||||||
@@ -153,9 +153,7 @@ def _exec_align_view(axis: int, is_max: bool):
|
|||||||
if region is None:
|
if region is None:
|
||||||
continue
|
continue
|
||||||
with bpy.context.temp_override(area=area, region=region):
|
with bpy.context.temp_override(area=area, region=region):
|
||||||
result = bpy.ops.bim.align_view_to_clip_face(
|
result = bpy.ops.bim.align_view_to_clip_face("EXEC_DEFAULT", axis=axis, is_max=is_max)
|
||||||
"EXEC_DEFAULT", axis=axis, is_max=is_max
|
|
||||||
)
|
|
||||||
assert result == {"FINISHED"}
|
assert result == {"FINISHED"}
|
||||||
return bpy.context.space_data.region_3d
|
return bpy.context.space_data.region_3d
|
||||||
pytest.skip("No VIEW_3D area available")
|
pytest.skip("No VIEW_3D area available")
|
||||||
@@ -190,7 +188,9 @@ class TestAlignViewToClipFace(NewFile):
|
|||||||
|
|
||||||
expected_up = (clip_box.matrix_world.to_quaternion() @ Vector((0.0, 0.0, 1.0))).normalized()
|
expected_up = (clip_box.matrix_world.to_quaternion() @ Vector((0.0, 0.0, 1.0))).normalized()
|
||||||
up_world = rv3d.view_rotation @ Vector((0.0, 1.0, 0.0))
|
up_world = rv3d.view_rotation @ Vector((0.0, 1.0, 0.0))
|
||||||
assert (up_world - expected_up).length < 1e-3, (
|
assert (
|
||||||
|
up_world - expected_up
|
||||||
|
).length < 1e-3, (
|
||||||
f"Side-face view must have box-local +Z as up; expected {tuple(expected_up)}, got {tuple(up_world)}"
|
f"Side-face view must have box-local +Z as up; expected {tuple(expected_up)}, got {tuple(up_world)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -217,9 +217,9 @@ class TestAlignViewToClipFace(NewFile):
|
|||||||
|
|
||||||
expected_up = (clip_box.matrix_world.to_quaternion() @ Vector((0.0, 0.0, 1.0))).normalized()
|
expected_up = (clip_box.matrix_world.to_quaternion() @ Vector((0.0, 0.0, 1.0))).normalized()
|
||||||
up_world = rv3d.view_rotation @ Vector((0.0, 1.0, 0.0))
|
up_world = rv3d.view_rotation @ Vector((0.0, 1.0, 0.0))
|
||||||
assert (up_world - expected_up).length < 1e-3, (
|
assert (
|
||||||
f"X-rotated box must use box-local Z; expected {tuple(expected_up)}, got {tuple(up_world)}"
|
up_world - expected_up
|
||||||
)
|
).length < 1e-3, f"X-rotated box must use box-local Z; expected {tuple(expected_up)}, got {tuple(up_world)}"
|
||||||
|
|
||||||
def test_align_view_uses_box_local_y_up_for_top_face(self):
|
def test_align_view_uses_box_local_y_up_for_top_face(self):
|
||||||
# Top face (local +Z outward) follows Blender's numpad-7
|
# Top face (local +Z outward) follows Blender's numpad-7
|
||||||
@@ -257,9 +257,9 @@ class TestNotPersistedToProjectPset(NewFile):
|
|||||||
|
|
||||||
# Whatever the pset stores, it must not carry this scene-only toggle.
|
# Whatever the pset stores, it must not carry this scene-only toggle.
|
||||||
for key in pset:
|
for key in pset:
|
||||||
assert "clip_only_ifc" not in key.lower(), (
|
assert (
|
||||||
f"Project pset unexpectedly carries the scene-only toggle (key {key!r})"
|
"clip_only_ifc" not in key.lower()
|
||||||
)
|
), f"Project pset unexpectedly carries the scene-only toggle (key {key!r})"
|
||||||
|
|
||||||
def test_load_from_pset_does_not_touch_clip_only_ifc_products(self):
|
def test_load_from_pset_does_not_touch_clip_only_ifc_products(self):
|
||||||
# Round-trip: set the toggle on the Scene, simulate a pset load, and
|
# Round-trip: set the toggle on the Scene, simulate a pset load, and
|
||||||
|
|||||||
@@ -154,9 +154,7 @@ class TestFrontFacingFaceMask:
|
|||||||
|
|
||||||
def test_wrong_length_raises(self):
|
def test_wrong_length_raises(self):
|
||||||
with pytest.raises(ValueError, match="expected 6 face normals"):
|
with pytest.raises(ValueError, match="expected 6 face normals"):
|
||||||
face_quad.front_facing_face_mask(
|
face_quad.front_facing_face_mask([(1.0, 0.0, 0.0), (-1.0, 0.0, 0.0)], (0.0, 0.0, -1.0))
|
||||||
[(1.0, 0.0, 0.0), (-1.0, 0.0, 0.0)], (0.0, 0.0, -1.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------- apply_face_quad_layout (front/back) ---
|
# ----------------------------------------------------- apply_face_quad_layout (front/back) ---
|
||||||
@@ -259,9 +257,7 @@ class TestApplyFaceQuadLayout:
|
|||||||
quads = [_FakeQuad() for _ in range(6)]
|
quads = [_FakeQuad() for _ in range(6)]
|
||||||
outlines = [_FakeOutline() for _ in range(6)]
|
outlines = [_FakeOutline() for _ in range(6)]
|
||||||
# View toward +X: the +X face is at world +X for a standard box.
|
# View toward +X: the +X face is at world +X for a standard box.
|
||||||
view_rotation = Vector((0.0, 0.0, -1.0)).rotation_difference(
|
view_rotation = Vector((0.0, 0.0, -1.0)).rotation_difference(Vector((-1.0, 0.0, 0.0)))
|
||||||
Vector((-1.0, 0.0, 0.0))
|
|
||||||
)
|
|
||||||
rv3d = _FakeRV3D(view_rotation, Matrix.Identity(4))
|
rv3d = _FakeRV3D(view_rotation, Matrix.Identity(4))
|
||||||
# Negative X scale (mirroring the cube along world X).
|
# Negative X scale (mirroring the cube along world X).
|
||||||
mw = Matrix.Diagonal((-1.0, 1.0, 1.0, 1.0))
|
mw = Matrix.Diagonal((-1.0, 1.0, 1.0, 1.0))
|
||||||
|
|||||||
@@ -48,16 +48,14 @@ def test_every_dispatch_target_lives_in_data_module():
|
|||||||
# against typos in the dispatch table that would otherwise only surface
|
# against typos in the dispatch table that would otherwise only surface
|
||||||
# at the first dialog open.
|
# at the first dialog open.
|
||||||
for kind, fn in operator._SOURCE_ID_DISPATCH.items():
|
for kind, fn in operator._SOURCE_ID_DISPATCH.items():
|
||||||
assert getattr(data, fn.__name__, None) is fn, (
|
assert (
|
||||||
f"Dispatch target for {kind} ({fn.__name__}) is not exported from data.py"
|
getattr(data, fn.__name__, None) is fn
|
||||||
)
|
), f"Dispatch target for {kind} ({fn.__name__}) is not exported from data.py"
|
||||||
|
|
||||||
|
|
||||||
def test_every_menu_entry_is_a_known_kind():
|
def test_every_menu_entry_is_a_known_kind():
|
||||||
for kind, label, icon in ui._SOURCE_MENU_ENTRIES:
|
for kind, label, icon in ui._SOURCE_MENU_ENTRIES:
|
||||||
assert kind in operator.SOURCE_KIND_LABELS, (
|
assert kind in operator.SOURCE_KIND_LABELS, f"Menu kind {kind!r} (label={label!r}) is not in SOURCE_KIND_LABELS"
|
||||||
f"Menu kind {kind!r} (label={label!r}) is not in SOURCE_KIND_LABELS"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_every_label_has_a_menu_entry():
|
def test_every_label_has_a_menu_entry():
|
||||||
|
|||||||
@@ -201,9 +201,7 @@ class TestIterElementsForSource(NewFile):
|
|||||||
storey = ifc.create_entity("IfcBuildingStorey")
|
storey = ifc.create_entity("IfcBuildingStorey")
|
||||||
wall_a = ifc.create_entity("IfcWall")
|
wall_a = ifc.create_entity("IfcWall")
|
||||||
wall_b = ifc.create_entity("IfcWall")
|
wall_b = ifc.create_entity("IfcWall")
|
||||||
ifcopenshell.api.spatial.assign_container(
|
ifcopenshell.api.spatial.assign_container(ifc, products=[wall_a, wall_b], relating_structure=storey)
|
||||||
ifc, products=[wall_a, wall_b], relating_structure=storey
|
|
||||||
)
|
|
||||||
|
|
||||||
result = tool.ClipBox.iter_elements_for_source("SPATIAL", str(storey.id()))
|
result = tool.ClipBox.iter_elements_for_source("SPATIAL", str(storey.id()))
|
||||||
|
|
||||||
@@ -215,9 +213,7 @@ class TestIterElementsForSource(NewFile):
|
|||||||
wall_type = ifc.create_entity("IfcWallType")
|
wall_type = ifc.create_entity("IfcWallType")
|
||||||
wall_a = ifc.create_entity("IfcWall")
|
wall_a = ifc.create_entity("IfcWall")
|
||||||
wall_b = ifc.create_entity("IfcWall")
|
wall_b = ifc.create_entity("IfcWall")
|
||||||
ifcopenshell.api.type.assign_type(
|
ifcopenshell.api.type.assign_type(ifc, related_objects=[wall_a, wall_b], relating_type=wall_type)
|
||||||
ifc, related_objects=[wall_a, wall_b], relating_type=wall_type
|
|
||||||
)
|
|
||||||
|
|
||||||
result = tool.ClipBox.iter_elements_for_source("TYPE", str(wall_type.id()))
|
result = tool.ClipBox.iter_elements_for_source("TYPE", str(wall_type.id()))
|
||||||
|
|
||||||
@@ -263,9 +259,7 @@ class TestComputeMatrixForSource(NewFile):
|
|||||||
storey = ifc.create_entity("IfcBuildingStorey")
|
storey = ifc.create_entity("IfcBuildingStorey")
|
||||||
wall_a, _ = _make_ifc_cube(ifc, "IfcWall", location=(0.0, 0.0, 0.0), size=2.0)
|
wall_a, _ = _make_ifc_cube(ifc, "IfcWall", location=(0.0, 0.0, 0.0), size=2.0)
|
||||||
wall_b, _ = _make_ifc_cube(ifc, "IfcWall", location=(4.0, 0.0, 0.0), size=2.0)
|
wall_b, _ = _make_ifc_cube(ifc, "IfcWall", location=(4.0, 0.0, 0.0), size=2.0)
|
||||||
ifcopenshell.api.spatial.assign_container(
|
ifcopenshell.api.spatial.assign_container(ifc, products=[wall_a, wall_b], relating_structure=storey)
|
||||||
ifc, products=[wall_a, wall_b], relating_structure=storey
|
|
||||||
)
|
|
||||||
|
|
||||||
matrix = tool.ClipBox.compute_matrix_for_source("SPATIAL", str(storey.id()))
|
matrix = tool.ClipBox.compute_matrix_for_source("SPATIAL", str(storey.id()))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user