Fix all ty diagnostics on ifcviewer-wgpu (ci-lint ty-ios + ty-bonsai)

This branch carried v0.8.0's strict `[tool.ty.rules] all = "error"` config but
not the source fixes that were made upstream to satisfy it, so both ci-lint ty
gates were failing: `poe ty-ios` reported 256 diagnostics and `poe ty-bonsai`
258. Both are now clean.

Most fixes are ported from v0.8.0 and follow two idioms: initialise a name
before a conditional that may not bind it (plus an `assert` where the invariant
is real but not provable), and close an exhaustive `if`/`elif` chain with
`else: assert False, <discriminant>`.

The branch's own newer accessors are preserved throughout - `.file`,
`.declaration`, `file.types()`, `get_max_id()` are kept rather than reverted to
`wrapped_data.*`, and non-ty upstream changes (notably the in-progress geometry
cache removal) are deliberately not pulled in.

Notable fixes that are not straight ports:

* ifcopenshell_wrapper.pyi: `entity_instance.file` was declared as
  `def file(self) -> file`, where the property name shadows the `class file`
  below it, so the annotation resolved to `Unknown`. Every `element.file` in
  the codebase was therefore unchecked. Qualifying it to `ifcopenshell.file`
  restores `.schema` to its Literal union and surfaces no new diagnostics.

* model/wall.py: a duplicated merge fragment in the void-straddle path ran an
  always-true `if void_straddles:` that read `new_opening` from the mutually
  exclusive branch (stale value, or NameError on the first iteration), followed
  by an unreachable duplicate `elif`. Removing it makes the file match v0.8.0.

* light/operator.py: upstream's own fix unpacks three targets from two values
  and raises ValueError unconditionally; corrected to `None, None, None`.

* assign_system.py, validate.py, geom/main.py: walrus-in-genexp is valid at
  runtime (PEP 572 binds in the containing scope) but ty does not model it;
  rewritten as explicit loops, matching upstream.

Verified: poe ty-ios, poe ty-bonsai, ruff check src/ nix/, black --check .,
and compileall -W error at py3.10 (ifcopenshell-python) and py3.11 (bonsai).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-27 19:14:39 +10:00
parent a522f31ba4
commit 19a1d88970
106 changed files with 430 additions and 89 deletions
+4 -1
View File
@@ -18,7 +18,9 @@
from __future__ import annotations
import hashlib
import os
import shutil
import tempfile
import traceback
import uuid
@@ -29,6 +31,7 @@ from typing import Literal, NotRequired, Optional, TypedDict, Union
import bpy
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper
from ifcopenshell.file import UndoSystemError
@@ -43,7 +46,7 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
class OperationData(TypedDict):
id: int
guid: NotRequired[str]
obj: str
obj: NotRequired[str]
class EditObjectOperationData(TypedDict):
+2
View File
@@ -1095,12 +1095,14 @@ class IfcImporter:
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
polyline = None
for edge in edges:
v1 = vertices[edge[0]]
if v1 != v2:
polyline = curve.splines.new("POLY")
polyline.points[-1].co = mathutils.Vector(v1)
v2 = vertices[edge[1]]
assert polyline is not None
polyline.points.add(1)
polyline.points[-1].co = mathutils.Vector(v2)
edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(geometry).tolist()
@@ -1082,7 +1082,8 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
inner_boundaries.append(tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False))
else:
pass # TODO
# TODO:
raise NotImplementedError(tool.Ifc.get().schema)
surface.OuterBoundary = outer_boundary
surface.InnerBoundaries = inner_boundaries
@@ -400,6 +400,7 @@ class CadOffset(bpy.types.Operator):
[verts.update(e.verts) for e in edges]
# Use the viewport angle to determine the offset direction
wp = None
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
# Don't ask me, I don't know.
@@ -409,6 +410,7 @@ class CadOffset(bpy.types.Operator):
z = area.spaces.active.region_3d.view_rotation @ Vector((0, 0, 1))
wp = Matrix([x, y, z, Vector((0, 0, 0))]).to_4x4().transposed()
break
assert wp is not None
rotation = Matrix.Rotation(pi / 2, 2, "Z")
rotation_i = Matrix.Rotation(-pi / 2, 2, "Z")
@@ -478,6 +478,7 @@ class ChangeClassificationLevel(bpy.types.Operator):
def execute(self, context):
props = tool.Classification.get_classification_props()
props.available_library_references.clear()
reference = None
for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
@@ -485,6 +486,7 @@ class ChangeClassificationLevel(bpy.types.Operator):
new.ifc_definition_id = reference.id()
new.has_references = bool(reference.HasReferences)
new.referenced_source
assert reference
if reference.ReferencedSource.is_a("IfcClassificationReference"):
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
else:
@@ -156,6 +156,8 @@ class CostSchedulesData:
values = root_element.CostValues
elif root_element.is_a("IfcConstructionResource"):
values = root_element.BaseCosts
else:
assert False, root_element
for cost_value in values or []:
cls._load_cost_value(root_element, data, cost_value)
# data["CostValues"].append(cost_value.id())
@@ -425,10 +425,12 @@ class BaseDecorator:
blf.size(font_id, font_size_px)
w, h = None, None
if box_alignment or center or vcenter:
w, h = blf.dimensions(font_id, text)
if box_alignment:
assert w is not None and h is not None
box_alignment_offset = Vector((0, 0))
if "bottom" in box_alignment:
pass
@@ -450,10 +452,12 @@ class BaseDecorator:
else:
# horizontal centering
if center:
assert w is not None
pos -= Vector((cos, sin)) * w * 0.5
# vertical centering
if vcenter:
assert h is not None
pos -= Vector((-sin, cos)) * h * 0.5
# side-shifting
@@ -1001,6 +1005,8 @@ class FallDecorator(BaseDecorator):
O = A.copy()
O.z = B.z
run = (B - O).length
angle_tg = None
if run != 0:
angle_tg = rise / run
angle = round(degrees(atan(angle_tg)))
@@ -1018,6 +1024,7 @@ class FallDecorator(BaseDecorator):
elif object_type == "SLOPE_PERCENT":
if angle == 90:
return "-"
assert angle_tg is not None
return f"{round(angle_tg * 100)} %"
return "NO DATA"
@@ -1249,6 +1256,7 @@ class SectionLevelDecorator(BaseDecorator):
}
# process edges
text_position, text_dir = None, None
for edge in edges_original:
v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]]
start_i = len(output_verts)
@@ -1267,6 +1275,7 @@ class SectionLevelDecorator(BaseDecorator):
add_verts_sequence([v0 + gap, v1 - gap], start_i, **out_kwargs)
self.draw_lines(context, obj, output_verts, output_edges)
assert text_position is not None and text_dir is not None
self.draw_labels(context, obj, self.get_splines(obj), text_position.to_2d(), text_dir.to_2d())
def draw_labels(self, context, obj, splines, text_position, text_dir):
@@ -1554,43 +1563,53 @@ class SectionDecorator(BaseDecorator):
v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]]
start_i = len(output_verts)
circle_head = None
if display_start_circle or display_end_circle:
circle_head = get_circle_head(circle_size)
if display_start_symbol or display_end_symbol or connect_markers:
triangle_head, divider_offset, edge_dir_circle = None, None, None
display_symbol = display_start_symbol or display_end_symbol
if display_symbol or connect_markers:
edge_dir = (v1 - v0).normalized()
side = (edge_dir.yx * Vector((1, -1))).to_3d()
edge_dir_circle = edge_dir * circle_size
if display_start_symbol or display_end_symbol:
triangle_head = get_triangle_head(edge_dir, -side, triangle_length, triangle_width)
divider_offset = []
divider_offset.append(edge_dir_circle if connect_markers else edge_dir_circle * 3)
divider_offset.append(edge_dir_circle)
if display_symbol:
triangle_head = get_triangle_head(edge_dir, -side, triangle_length, triangle_width)
divider_offset = []
divider_offset.append(edge_dir_circle if connect_markers else edge_dir_circle * 3)
divider_offset.append(edge_dir_circle)
if display_start_circle:
assert circle_head is not None
start_i = add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True)
# circle middle divider
if not display_start_symbol:
assert divider_offset is not None
start_i = add_verts_sequence(
[v0 + divider_offset[0], v0 - divider_offset[1]], start_i, **out_kwargs
)
if display_start_symbol:
assert triangle_head is not None
start_i = add_verts_sequence([v + v0 for v in triangle_head], start_i, **out_kwargs, closed=True)
if display_end_circle:
assert circle_head is not None
start_i = add_verts_sequence([v + v1 for v in circle_head], start_i, **out_kwargs, closed=True)
# circle middle divider
if not display_end_symbol:
assert divider_offset is not None
start_i = add_verts_sequence(
[v1 + divider_offset[1], v1 - divider_offset[0]], start_i, **out_kwargs
)
if display_end_symbol:
assert triangle_head is not None
start_i = add_verts_sequence([v + v1 for v in triangle_head], start_i, **out_kwargs, closed=True)
if connect_markers:
assert edge_dir_circle is not None
gap = []
gap.append(edge_dir_circle if display_start_symbol else Vector((0, 0, 0)))
gap.append(edge_dir_circle if display_end_symbol else Vector((0, 0, 0)))
@@ -1889,6 +1908,8 @@ class CutDecorator:
layer_set = material
offset = 0
sense_factor = 1
else:
assert False, material
if len(layer_set.MaterialLayers) == 1:
material = layer_set.MaterialLayers[0].Material
@@ -1915,6 +1936,8 @@ class CutDecorator:
co = Vector((0.0, 0.0, offset))
no = tool.Drawing.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
else:
assert False, usage
no *= sense_factor
last_i = len(layer_set.MaterialLayers) - 1
@@ -225,9 +225,11 @@ def format_distance(
unit_system, unit_length, unit_fraction = unit_mapping[custom_unit]
value *= unit_scale
tx_dist = None
# Imperial Formatting
if unit_system == "IMPERIAL":
toInches = None
if in_unit_length:
if unit_length == "INCHES":
toInches = 1
@@ -241,6 +243,7 @@ def format_distance(
toInches = 1550
inPerFoot = 144
assert toInches is not None
decInches = value * toInches
decFeet = decInches / 12
@@ -383,6 +386,7 @@ def format_distance(
if precision and isinstance(precision, float):
value = precision * round(float(value) / precision)
fmt = None
if decimal_places is not None:
fmt = "%1." + str(decimal_places) + "f"
@@ -465,6 +469,7 @@ def format_distance(
assert f"Unexpected unit_system - '{unit_system}'."
# tx_dist = fmt % value
assert tx_dist is not None
return tx_dist
@@ -781,6 +781,8 @@ class CreateDrawing(bpy.types.Operator):
layer_set = material
offset = 0
sense_factor = 1
else:
assert False, material
camera_matrix_i = context.scene.camera.matrix_world.inverted()
@@ -822,6 +824,8 @@ class CreateDrawing(bpy.types.Operator):
co = Vector((0.0, 0.0, offset))
no = tool.Drawing.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
else:
assert False, usage
no *= sense_factor
last_i = len(layer_set.MaterialLayers) - 1
for i, layer in enumerate(layer_set.MaterialLayers):
@@ -989,6 +993,10 @@ class CreateDrawing(bpy.types.Operator):
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
ifc = tool.Ifc.get()
semantics = None
pairs = None
# in case of printing multiple drawings we need to sync just once
if self.sync and self.drawing_index == 0:
with profile("sync"):
@@ -110,12 +110,14 @@ class Scheduler:
y = self.margin
rows = list(sheet.iter_rows())
total_rows = len(rows)
x = None
for i, row in enumerate(rows):
# The last row may contain only null values
if i == (total_rows - 1) and not [c for c in row if c.value is not None]:
continue
x = self.margin
unmerged_height = None
for cell in row:
if isinstance(cell, openpyxl.cell.cell.MergedCell):
column_letter = openpyxl.utils.get_column_letter(cell.column)
@@ -230,8 +232,11 @@ class Scheduler:
)
x += unmerged_width
assert unmerged_height is not None
y += unmerged_height
assert x is not None
total_width = x + self.margin
total_height = y + self.margin
self.svg["width"] = "{}mm".format(total_width)
@@ -375,6 +380,7 @@ class Scheduler:
tri = 0
stop_iterating_over_rows = False
# TODO: row spans support?
x = None
for tr in table.getElementsByType(TableRow):
if stop_iterating_over_rows:
break
@@ -491,6 +497,7 @@ class Scheduler:
tri += 1
y += height
assert x is not None
total_width = x + self.margin
total_height = y + self.margin
self.svg["width"] = "{}mm".format(total_width)
@@ -197,10 +197,12 @@ void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False):
"""Add sequence of verts to output lists, returns next vertex index"""
i = None
for i, v in enumerate(verts[:-1], start_i):
output_verts.append(v)
output_edges.append((i, i + 1))
output_verts.append(verts[-1])
assert i is not None
if closed:
output_edges.append((i + 1, start_i))
return i + 2
@@ -1445,6 +1445,8 @@ class SvgWriter:
O = A.copy()
O.z = B.z
run = (B - O).length
angle_tg = None
if run != 0:
angle_tg = rise / run
angle = round(degrees(atan(angle_tg)))
@@ -1462,6 +1464,7 @@ class SvgWriter:
elif object_type == "SLOPE_PERCENT":
if angle == 90:
return "-"
assert angle_tg is not None
return f"{round(angle_tg * 100)} %"
tag = element.Description or get_label_text()
@@ -1032,6 +1032,7 @@ class BIM_UL_sheets(bpy.types.UIList):
if self.filter_name:
filter_name = self.filter_name.lower()
active_sheet = None
active_sheet_index = None
for sheet in data.sheets:
if sheet.is_sheet:
active_sheet = sheet
@@ -1039,6 +1040,7 @@ class BIM_UL_sheets(bpy.types.UIList):
if filter_name in sheet.name.lower() or filter_name in sheet.identification.lower():
flt_flags.append(self.bitflag_filter_item)
if not sheet.is_sheet:
assert active_sheet_index is not None
flt_flags[active_sheet_index] = self.bitflag_filter_item
else:
flt_flags.append(0)
@@ -75,9 +75,13 @@ class Helper:
for face in bm.faces:
if len(face.verts) > 4:
potential_faces.append(face)
# TODO: replace with next(..., None)
face = None
for face in potential_faces:
if face.normal.z < -0.1:
break
assert face is not None
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
@@ -108,10 +112,12 @@ class Helper:
if not potential_faces:
potential_faces = bm.faces
# TODO: replace with next(..., None)
face = None
for face in potential_faces:
if face.normal.z < -0.1:
break
assert face is not None
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
@@ -145,9 +151,12 @@ class Helper:
if total_verts > 4:
potential_faces.append(face)
# TODO: replace with next(..., None)
face = None
for face in potential_faces:
if face.normal.z < -0.1:
break
assert face is not None
end_faces = []
end_face_normal = face.normal
@@ -3524,6 +3524,7 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
if props.representation_item_shape_aspect == "NEW":
active_representation = tool.Geometry.get_active_representation(obj)
# find IfcProductRepresentationSelect based on current representation
product_shape = None
if hasattr(element, "Representation"): # IfcProduct
product_shape = element.Representation
else: # IfcTypeProduct
@@ -3533,6 +3534,7 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
previous_shape_aspect_id = props.active_item.shape_aspect_id
# will be None if item didn't had a shape aspect
previous_shape_aspect = tool.Ifc.get_entity_by_id(previous_shape_aspect_id)
assert product_shape is not None
shape_aspect = tool.Geometry.create_shape_aspect(
product_shape, active_representation, [representation_item], previous_shape_aspect
)
@@ -3879,6 +3881,8 @@ class AddSweptAreaSolidItem(bpy.types.Operator, tool.Ifc.Operator):
curve = builder.rectangle(size=Vector((0.5, 0.5)) / unit_scale)
elif self.shape == "CYLINDER":
curve = builder.circle(radius=0.25 / unit_scale)
else:
assert False, self.shape
item = builder.extrude(
curve,
magnitude=0.5 / unit_scale,
@@ -156,6 +156,7 @@ class RadianceRender(bpy.types.Operator):
print(f"Quality: {quality}, Detail: {detail}, Variability: {variability}")
print(f"Output directory: {output_dir}")
hdr_image_path, hdr_mask_path, sky_map_cal_path = None, None, None
if use_hdr:
hdr_image = "noon_grass_2k.hdr"
hdr_mask = "noon_grass_2k_mask.hdr"
@@ -254,6 +255,9 @@ class RadianceRender(bpy.types.Operator):
# 4 0 0 -1 180
if use_hdr and choose_hdr_image == "Noon":
assert hdr_image_path is not None
assert hdr_mask_path is not None
assert sky_map_cal_path is not None
with open(sky_file_path, "w") as f:
f.write(sky_description_str)
@@ -564,6 +564,7 @@ class SelectAllArrayObjects(bpy.types.Operator):
except RuntimeError:
self.report({"ERROR"}, f"Objects that don't have an array parent, were deselected.")
object.select_set(False)
continue
array_objects = tool.Array.get_all_objects(parent_element)
tool.Blender.set_objects_selection(
@@ -408,6 +408,8 @@ class MEPGenerator:
compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision)
elif isinstance(fitting_value, list):
compare = tool.Cad.are_vectors_equal(requested_value, Vector(fitting_value), precision)
else:
assert False, f"{key} {second_key}"
return compare
ignore_keys = []
@@ -476,12 +478,14 @@ class MEPGenerator:
if predefined_type == "OBSTRUCTION":
return packed_data
start_port = None
for port in ports:
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
if tool.Cad.is_x(port_local_position.length, 0.0):
start_port = port
break
assert start_port is not None
connected_port = tool.System.get_connected_port(start_port)
connected_element = tool.System.get_port_relating_element(connected_port)
element_type = ifcopenshell.util.element.get_type(connected_element)
@@ -325,7 +325,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
props.relating_type_id = str(self.relating_type_id)
building_obj = None
building_obj, building_element = None, None
if len(context.selected_objects) == 1 and context.active_object:
building_obj = context.active_object
building_element = tool.Ifc.get_entity(building_obj)
@@ -593,6 +593,8 @@ class DumbProfileJoiner:
axisl = (profile2.matrix_world.inverted() @ axis1[1]) - (profile2.matrix_world.inverted() @ axis1[0])
elif connection1 == "ATSTART":
axisl = (profile2.matrix_world.inverted() @ axis1[0]) - (profile2.matrix_world.inverted() @ axis1[1])
else:
assert False, connection1
xy_angle = degrees(Vector((1, 0)).angle_signed(axisl.normalized().to_2d()))
if xy_angle >= -135 and xy_angle <= -45:
closest_plane = "bottom"
@@ -617,6 +619,8 @@ class DumbProfileJoiner:
axisl = (profile1.matrix_world.inverted() @ axis2[1]) - (profile1.matrix_world.inverted() @ axis2[0])
elif connection2 == "ATSTART":
axisl = (profile1.matrix_world.inverted() @ axis2[0]) - (profile1.matrix_world.inverted() @ axis2[1])
else:
assert False, connection2
xy_angle2 = degrees(Vector((1, 0)).angle_signed(axisl.normalized().to_2d()))
if xy_angle2 >= -135 and xy_angle2 <= -45:
closest_plane2 = "bottom"
@@ -844,6 +848,8 @@ class DumbProfileJoiner:
else:
y_axis = obj.matrix_world.to_quaternion() @ Vector((0, 1, 0))
z_axis = obj.matrix_world.to_quaternion() @ Vector((-1, 0, 0))
else:
assert False, plane
return self.create_matrix(p, x_axis, y_axis, z_axis)
def create_matrix(self, p: Vector, x: Vector, y: Vector, z: Vector) -> Matrix:
@@ -508,6 +508,7 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
converter.run()
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
curve = None
for path in converter.paths:
points = []
lines = path[0]
@@ -517,6 +518,7 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
points.append(tool.Ifc.get().createIfcCartesianPoint(local_point))
points.append(points[0])
curve = tool.Ifc.get().createIfcPolyline(points)
assert curve
profile.OuterCurve = curve
old_profile = extrusion.SweptArea
+2 -9
View File
@@ -1577,6 +1577,7 @@ class DumbWallJoiner:
# Get the ATEND connection from wall1 to use it in wall2
relating_element = None
connections = element1.ConnectedTo
relating_connection, description = ..., ...
for conn in connections:
if conn.is_a("IfcRelConnectsPathElements") and conn.RelatingConnectionType == "ATEND":
relating_element = conn.RelatedElement
@@ -1591,6 +1592,7 @@ class DumbWallJoiner:
description = conn.Description
bonsai.core.geometry.remove_connection(tool.Geometry, connection=conn)
if relating_element:
assert relating_connection is not ... and description is not ...
ifcopenshell.api.geometry.connect_path(
tool.Ifc.get(),
relating_element=relating_element,
@@ -1672,15 +1674,6 @@ class DumbWallJoiner:
# copy to element2 so its body gets cut.
_add_void_copy(element2, opening)
if void_straddles:
# Filling moved to element2, but void straddles — add a
# pure-void copy back to element1 so its body still gets cut.
_add_void_copy(element1, new_opening)
elif void_straddles:
# Filling stays on element1, but void straddles — add a pure-void
# copy to element2 so its body gets cut.
_add_void_copy(element2, opening)
p1, p2 = ifcopenshell.util.representation.get_reference_line(element1)
p3 = (wall1.matrix_world.inverted() @ intersect.to_3d()).to_2d() / unit_scale
self.set_axis(element1, p1, p3)
@@ -18,7 +18,7 @@
import json
from pathlib import Path
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING
import bpy
import ifcopenshell
@@ -122,8 +122,8 @@ class ExecuteIfcPatch(bpy.types.Operator):
if props.should_load_from_memory and tool.Ifc.get():
args["file"] = tool.Ifc.get()
else:
args["input"] = cast(str, props.ifc_patch_input)
args["file"] = cast(ifcopenshell.file, ifcopenshell.open(props.ifc_patch_input))
args["input"] = props.ifc_patch_input
args["file"] = ifcopenshell.open(props.ifc_patch_input)
# Store this in case the patch recipe resets the Blender session, such as by loading a new project.
ifc_patch_output = props.ifc_patch_output or props.ifc_patch_input
@@ -714,6 +714,8 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
representations = element.RepresentationMaps or []
elif element.is_a("IfcProduct"):
representations = [element.Representation] if element.Representation else []
else:
assert False, element
for representation in representations or []:
for element in self.file.traverse(representation):
if not element.is_a("IfcRepresentationItem") or not element.StyledByItem:
@@ -2033,6 +2035,7 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
project_props = tool.Project.get_project_props()
prefs = tool.Blender.get_addon_preferences()
project_props.use_relative_project_path = self.use_relative_path
old_history_size, old_undo_steps = None, None
if prefs.should_disable_undo_on_save:
old_history_size = tool.Ifc.get().history_size
old_undo_steps = context.preferences.edit.undo_steps
@@ -2040,6 +2043,7 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
context.preferences.edit.undo_steps = 0
IfcStore.execute_ifc_operator(self, context)
if prefs.should_disable_undo_on_save:
assert old_history_size is not None and old_undo_steps is not None
tool.Ifc.get().history_size = old_history_size
context.preferences.edit.undo_steps = old_undo_steps
return {"FINISHED"}
@@ -113,6 +113,8 @@ class EditPset(bpy.types.Operator, tool.Ifc.Operator):
elif props.active_pset_type == "QTO":
pset = ifcopenshell.api.pset.add_qto(self.file, product=element, name=props.active_pset_name)
props.active_pset_id = pset.id()
else:
assert False
if self.properties:
properties = json.loads(self.properties)
@@ -228,6 +228,8 @@ def get_qto_name(self: "PsetProperties", context: bpy.types.Context) -> tool.Ble
if "bpy.data.objects" in pset_type:
if prop_type == "PsetProperties":
results = get_object_qto_name(self, context)
else:
assert False
elif prop_type == "TaskPsetProperties":
results = get_task_qto_names(self, context)
elif prop_type == "ResourcePsetProperties":
+1
View File
@@ -480,6 +480,7 @@ class BIM_PT_material_psets(Panel):
def draw(self, context):
assert self.layout
props = tool.Material.get_material_props()
ifc_definition_id = None
if material := props.active_material:
ifc_definition_id = material.ifc_definition_id
@@ -635,6 +635,8 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
z_axis = tuple(local_y) if direction_sense == "POSITIVE" else tuple(-local_y)
elif usage.LayerSetDirection == "AXIS3":
z_axis = tuple(local_z) if direction_sense == "POSITIVE" else tuple(-local_z)
else:
assert False, usage.LayerSetDirection
item = builder.extrude(
profile,
@@ -764,6 +766,8 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
WebThickness=default_web_thickness / unit_scale,
FlangeThickness=default_flange_thickness / unit_scale,
)
else:
assert False, representation_template
rel = ifcopenshell.api.material.assign_material(
tool.Ifc.get(), products=[element], type="IfcMaterialProfileSet"
@@ -1009,6 +1009,7 @@ class ColourByProperty(Operator):
palette = props.palette
is_qualitative = palette in ("tab10", "paired")
colours = None
if is_qualitative:
colours = tool.Search.get_qualitative_palette(palette)
@@ -1035,6 +1036,7 @@ class ColourByProperty(Operator):
if value in colourscheme:
colourscheme[value]["total"] += 1
else:
assert colours is not None
colourscheme[value] = {"colour": next(colours)[0:3], "total": 1}
obj.color = (*colourscheme[value]["colour"], 1)
else:
@@ -1139,6 +1141,7 @@ class SelectByProperty(Operator):
is_qualitative = palette in ("tab10", "paired")
values = None
if not is_qualitative:
values = []
for colour in props.colourscheme:
+4 -2
View File
@@ -281,11 +281,11 @@ class BIM_PT_work_schedules(Panel):
def draw_task_operators(self) -> None:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
ifc_definition_id = None
task, ifc_definition_id = None, None
if self.tprops.tasks and self.props.active_task_index < len(self.tprops.tasks):
task = self.tprops.tasks[self.props.active_task_index]
ifc_definition_id = task.ifc_definition_id
if ifc_definition_id:
if task and ifc_definition_id:
if self.props.active_task_id:
if self.props.editing_task_type == "TASKTIME":
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
@@ -341,6 +341,8 @@ class BIM_PT_work_schedules(Panel):
row.prop(self.props, "other_columns", text="")
column_type, name = self.props.other_columns.split(".")
data_type = "string"
else:
assert False, column_type
row.operator("bim.set_task_sort_column", text="", icon="SORTALPHA").column = f"{column_type}.{name}"
row.prop(
self.props, "is_sort_reversed", text="", icon="SORT_DESC" if self.props.is_sort_reversed else "SORT_ASC"
@@ -516,7 +516,7 @@ class SetContainerVisibility(bpy.types.Operator):
if self.mode == "ISOLATE":
if tool.Ifc.get_schema() == "IFC2X3":
containers = tool.Ifc.get().by_type("IfcSpatialStructureElement")
elif tool.Ifc.get_schema() != "IFC2X3":
else:
containers = set(tool.Ifc.get().by_type("IfcSpatialElement"))
containers -= set(tool.Ifc.get().by_type("IfcSpatialZone"))
for container in containers:
@@ -125,6 +125,7 @@ class BIM_PT_spatial_decomposition(Panel):
row.label(text="Warning: No Default Container", icon="ERROR")
row.operator("bim.import_spatial_decomposition", icon="FILE_REFRESH", text="")
ifc_definition_id = None
if self.props.active_container:
ifc_definition_id = self.props.active_container.ifc_definition_id
row = self.layout.row(align=True)
@@ -170,6 +171,7 @@ class BIM_PT_spatial_decomposition(Panel):
if not self.props.active_container:
return
assert ifc_definition_id is not None
container_has_elements = bool(self.props.total_elements)
if container_has_elements:
@@ -71,7 +71,11 @@ class LoadByDirection(TypedDict):
ProcessedLoad = TypedDict(
"ProcessedLoad",
{"linear loads": LoadByDirection, "max linear load": float, "discrete loads": list[list[DiscreteConfigItem]]},
{
"linear loads": dict[str, LoadByDirection] | None,
"max linear load": float,
"discrete loads": list[list[DiscreteConfigItem]],
},
)
@@ -845,7 +849,9 @@ class ShaderInfo:
v = l1[1] + fac * (pos - l1[0])
return v
def interpolate(self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int, key: str) -> np.ndarray:
def interpolate(
self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int, key: Literal["load values"]
) -> np.ndarray:
"""interpolate the result vectors between load poits"""
result = np.zeros(6)
for i in range(6):
+1
View File
@@ -102,6 +102,7 @@ class BIM_PT_styles(Panel):
# style ui tools
if active_style:
style = active_style
row = self.layout.row(align=True)
if material := style.blender_material:
msprops = tool.Style.get_material_style_props(material)
@@ -72,6 +72,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
opening_objects = [obj for obj in selected_objects if obj != target_object]
obj1 = ...
for opening_obj in opening_objects:
element1 = tool.Ifc.get_entity(target_object)
obj1 = target_object
@@ -196,6 +197,7 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
bpy.data.objects.remove(obj2)
tool.Model.purge_scene_openings()
assert obj1 is not ...
context.view_layer.objects.active = obj1
return {"FINISHED"}
+4
View File
@@ -285,11 +285,13 @@ class GizmoPreferences(bpy.types.PropertyGroup):
draw_gizmos_in_3d_viewport: bool
_gizmo_pref_entry = None
for _gizmo_pref_entry in tool.Parametric.EDIT_TYPES:
GizmoPreferences.__annotations__[_gizmo_pref_entry.name] = BoolProperty(
name=_gizmo_pref_entry.name.replace("_", " ").title(),
default=True,
)
assert _gizmo_pref_entry is not None
del _gizmo_pref_entry
@@ -395,12 +397,14 @@ class DefaultParameters(bpy.types.PropertyGroup):
and gives the create operator a preset to copy from."""
_default_params_entry = None
for _default_params_entry in tool.Parametric.EDIT_TYPES:
if not _default_params_entry.has_default_parameters:
continue
DefaultParameters.__annotations__[_default_params_entry.name] = bpy.props.PointerProperty(
type=getattr(_model_prop, _default_params_entry.props_attr),
)
assert _default_params_entry is not None
del _default_params_entry
+4
View File
@@ -74,6 +74,7 @@ def add_instance_ceiling_covering_from_cursor(
if not relating_type.is_a("IfcCoveringType"):
relating_type = None
ceiling_height = None
if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
else:
@@ -87,6 +88,7 @@ def add_instance_ceiling_covering_from_cursor(
obj = spatial.create_object("Covering")
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
assert ceiling_height is not None
spatial.translate_obj_to_z_location(obj, z + ceiling_height)
spatial.assign_type_to_obj(obj)
spatial.set_covering_representation_from_polygon(obj, space_polygon, polygon_is_si=True)
@@ -101,6 +103,8 @@ def regen_selected_covering_object(root: type[tool.Root], spatial: type[tool.Spa
if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_obj(active_obj)
else:
assert False, "Object has to be active and selected."
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
+1
View File
@@ -526,6 +526,7 @@ def add_annotation(
context = drawing_tool.create_annotation_context(target_view, object_type)
drawing_tool.show_decorations()
relating_type_rep = None
obj = drawing_tool.create_annotation_object(drawing, object_type)
element = ifc.get_entity(obj)
if not element: # Brand new annotation
+2
View File
@@ -981,6 +981,7 @@ class Cad:
has_found_connected_edge = True
loops.append(loop)
new_verts = None
for loop in loops:
all_verts = {v.index for e in loop for v in e.verts}
possible_v1s = []
@@ -1085,6 +1086,7 @@ class Cad:
v1 = v2
assert new_verts is not None
return new_verts
@classmethod
+2
View File
@@ -280,6 +280,8 @@ class Cost(bonsai.core.tool.Cost):
new = props.cost_item_processes.add()
elif related_object.is_a("IfcResource"):
new = props.cost_item_resources.add()
else:
assert False, related_object
new.ifc_definition_id = related_object.id()
new.name = related_object.Name or "Unnamed"
+1
View File
@@ -2604,6 +2604,7 @@ class Drawing(bonsai.core.tool.Drawing):
if not obj:
continue
current_representation = tool.Geometry.get_active_representation(obj)
current_representation_subcontext = None
if current_representation:
subcontext = current_representation.ContextOfItems
current_representation_subcontext = tool.Geometry.get_subcontext_parameters(subcontext)
+2
View File
@@ -45,6 +45,7 @@ class Feature(bonsai.core.tool.Feature):
featured_element = tool.Ifc.get_entity(featured_obj)
has_visible_openings = False
element_had_openings = None
for opening in [r.RelatedOpeningElement for r in featured_element.HasOpenings]:
if tool.Ifc.get_object(opening):
has_visible_openings = True
@@ -73,6 +74,7 @@ class Feature(bonsai.core.tool.Feature):
if voided_obj.data:
if tool.Ifc.is_edited(voided_obj):
voided_element_ = tool.Ifc.get_entity(voided_obj)
assert element_had_openings is not None
if element_had_openings or (voided_element_ != featured_element and voided_element_.HasOpenings):
voided_obj.scale = (1.0, 1.0, 1.0)
tool.Ifc.finish_edit(voided_obj)
+7 -2
View File
@@ -33,7 +33,6 @@ from typing import (
Optional,
TypeGuard,
Union,
cast,
get_args,
)
@@ -768,6 +767,7 @@ class Geometry(bonsai.core.tool.Geometry):
# its centroid not obscured (tested via raycasting) by any other
# face.
distance = max(obj.dimensions.xyz)
min_y, max_z = None, None
if axis == "+Z":
max_z = max([co[2] for co in obj.bound_box]) + 0.002
direction = Vector((0, 0, -1))
@@ -782,8 +782,10 @@ class Geometry(bonsai.core.tool.Geometry):
if direction.dot(face.normal) > 0:
continue
if axis == "+Z":
assert max_z is not None
face_centroid_at_max = Vector((*face.calc_center_median().xy, max_z))
elif axis == "-Y":
assert min_y is not None
centroid = face.calc_center_median()
face_centroid_at_max = Vector((centroid.x, min_y, centroid.z))
face_centroid_at_max = obj.matrix_world @ face_centroid_at_max
@@ -1904,6 +1906,7 @@ class Geometry(bonsai.core.tool.Geometry):
"""NOTE: we assume that all items belonged to the same representation and to the same shape aspect"""
ifc_file = tool.Ifc.get()
previous_shape_aspect = None
base_representation = None
for inverse in ifc_file.get_inverse(representation_items[0]):
if inverse.is_a("IfcShapeRepresentation"):
if inverse.OfShapeAspect:
@@ -1917,6 +1920,7 @@ class Geometry(bonsai.core.tool.Geometry):
# remove item from previous shape aspect
if previous_shape_aspect:
cls.remove_representation_items_from_shape_aspect(representation_items, previous_shape_aspect)
assert base_representation
shape_aspect_representation = cls.get_shape_aspect_representation(
shape_aspect, base_representation, create_new=True
)
@@ -2205,7 +2209,7 @@ class Geometry(bonsai.core.tool.Geometry):
setattr(item, attribute.name, attribute.get_value())
if item.is_a("IfcSweptAreaSolid"):
item_profile = cast(str, props.item_profile)
item_profile = props.item_profile
profile = item.SweptArea
profile_name: Union[str, None] = profile.ProfileName
if item_profile == "-":
@@ -2230,6 +2234,7 @@ class Geometry(bonsai.core.tool.Geometry):
assert item
obj.data.clear_geometry()
cartesian_point_offset = None
if item.is_a("IfcHalfSpaceSolid"):
bm = bmesh.new()
bmesh.ops.create_grid(bm, size=0.5)
+6 -1
View File
@@ -197,7 +197,6 @@ class Loader(bonsai.core.tool.Loader):
cls, blender_material: bpy.types.Material, surface_style: ifcopenshell.entity_instance
) -> None:
surface_style = cls.surface_style_to_dict(surface_style)
surface_style: dict[str, Any]
cls.create_surface_style_shading(blender_material, surface_style)
@@ -1100,6 +1099,8 @@ class Loader(bonsai.core.tool.Loader):
co = Vector((0.0, 0.0, offset))
no = cls.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
else:
assert False, usage.LayerSetDirection
no *= sense_factor
# Cache this
body = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -1109,6 +1110,7 @@ class Loader(bonsai.core.tool.Loader):
if style := tool.Ifc.get_entity(material):
styles[style] = i
last_i = len(layer_set.MaterialLayers) - 1
bisect_geom = None
for i, layer in enumerate(layer_set.MaterialLayers):
if i != last_i:
prev_co = co.copy()
@@ -1122,6 +1124,7 @@ class Loader(bonsai.core.tool.Loader):
if (material_index := styles.get(style, None)) is None:
material_index = len(mesh.materials)
mesh.materials.append(tool.Ifc.get_object(style))
assert bisect_geom is not None
if i == last_i:
for face in bisect_geom["geom"]:
if isinstance(face, bmesh.types.BMFace):
@@ -1287,6 +1290,7 @@ class Loader(bonsai.core.tool.Loader):
polyline.material_index = material_index
return polyline
item = None
for item_data, item_style in zip(rep_items, item_styles):
item = item_data["item"]
@@ -1314,6 +1318,7 @@ class Loader(bonsai.core.tool.Loader):
polyline.points.add(1)
polyline.points[-1].co = native_data["matrix"] @ Vector(v2)
assert item is not None
curve.bevel_depth = unit_scale * item.Radius
thickness = None
if (inner_radius := item.InnerRadius) and (thickness := max(item.Radius - inner_radius, 0)):
+2
View File
@@ -220,10 +220,12 @@ class Misc(bonsai.core.tool.Misc):
related_objects.append((element, ifcopenshell.util.placement.get_storey_elevation(element)))
related_objects = sorted(related_objects, key=lambda e: e[1])
storey_elevation = None
i = None
for i, related_object in enumerate(related_objects):
if related_object[0] == storey:
storey_elevation = related_object[1]
break
assert i is not None
if i + total_storeys < len(related_objects):
next_storey_elevation = related_objects[i + total_storeys][1]
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
+2
View File
@@ -641,6 +641,8 @@ del _edit_type_names
# call sites can reference ``tool.Parametric.ROOF`` directly. Renaming a
# registry entry renames the constant; a typo at the call site surfaces as
# AttributeError at module load.
_entry = None
for _entry in Parametric.EDIT_TYPES:
setattr(Parametric, _entry.name.upper(), _entry)
assert _entry is not None
del _entry
+2
View File
@@ -370,6 +370,7 @@ class Project(bonsai.core.tool.Project):
props = cls.get_project_props()
active_library_breadcrumb = props.get_active_library_breadcrumb()
change_back = False
name = breadcrumb_type = library_id = None
if active_library_breadcrumb:
name = active_library_breadcrumb.name
breadcrumb_type = active_library_breadcrumb.breadcrumb_type
@@ -378,6 +379,7 @@ class Project(bonsai.core.tool.Project):
bpy.ops.bim.rewind_library()
if change_back:
assert name is not None and breadcrumb_type is not None and library_id is not None
bpy.ops.bim.change_library_element(
element_name=name,
breadcrumb_type=breadcrumb_type,
+3
View File
@@ -124,6 +124,9 @@ class Pset(bonsai.core.tool.Pset):
return bpy.context.scene.GroupPsetProperties
elif obj_type == "Zone":
return bpy.context.scene.ZonePsetProperties
elif obj_type == "Cost":
# No psets for cost items currently.
assert False, obj_type
assert_never(obj_type)
@classmethod
+1 -1
View File
@@ -232,7 +232,7 @@ class Raycast(bonsai.core.tool.Raycast):
return final_2d, v2
@classmethod
def intersect_mouse_2d_bounding_box(cls, mouse_pos: tuple[int, int], bbox: list[float, float, float, float]):
def intersect_mouse_2d_bounding_box(cls, mouse_pos: tuple[int, int], bbox: list[float]):
x, y = mouse_pos
xmin, xmax, ymin, ymax = bbox
+4
View File
@@ -505,6 +505,8 @@ class Search(bonsai.core.tool.Search):
(0.773, 0.922, 0.816),
(0.871, 0.957, 0.894),
]
else:
assert False, theme
if value < min_val:
value = min_val
@@ -574,8 +576,10 @@ class ImportFilterQueryTransformer(lark.Transformer):
new = self.filter_groups.add()
global_ids = []
is_first_group = len(self.filter_groups) == 1
new2 = None
for filter_index, arg in enumerate(args):
if arg["type"] == "instance" and global_ids:
assert new2
if "bpy.data.texts" in new2.value:
data_name = new2.value.split("bpy.data.texts")[1][2:-2]
bpy.data.texts[data_name].write("," + arg["value"])
+2
View File
@@ -1158,6 +1158,7 @@ class Sequence(bonsai.core.tool.Sequence):
props.task_input_colors.clear()
for group, data in groups.items():
for predefined_type in data["PredefinedType"]:
predefined_type_item = None
if group in ("CREATION", "OPERATION", "MOVEMENT_TO"):
predefined_type_item = props.task_output_colors.add()
elif group in ("MOVEMENT_FROM",):
@@ -1168,6 +1169,7 @@ class Sequence(bonsai.core.tool.Sequence):
predefined_type_item2.name = predefined_type
predefined_type_item2.color = data["Color"]
# TO DO: consider cases where users confuses inputs and outputs
assert predefined_type_item is not None
predefined_type_item.name = predefined_type
predefined_type_item.color = data["Color"]
+5
View File
@@ -224,6 +224,7 @@ class Snap(bonsai.core.tool.Snap):
# Get axis that are closer than the stick factor threshold
elegible_axis = []
axis = None
for axis in snap_axis:
if not axis:
@@ -326,6 +327,7 @@ class Snap(bonsai.core.tool.Snap):
detected_snaps: list[dict[str, Any]] = []
def select_plane_method():
plane_origin, plane_normal = None, None
if not last_polyline_point:
plane_origin = Vector((0, 0, 0))
plane_normal = Vector((0, 0, 1))
@@ -357,6 +359,7 @@ class Snap(bonsai.core.tool.Snap):
plane_origin = Vector((last_polyline_point.x, last_polyline_point.y, last_polyline_point.z))
plane_normal = Vector((1, 0, 0))
assert plane_origin and plane_normal
plane_normal = tool.Polyline.use_transform_orientations(plane_normal)
return plane_origin, plane_normal
@@ -583,6 +586,7 @@ class Snap(bonsai.core.tool.Snap):
snaps_by_group = filter_snapping_points_by_group(detected_snaps)
edges = [] # Get edges to create edge-intersection snap
axis_start, axis_end = ..., ...
for snapping_point in snaps_by_group:
if snapping_point["group"] in {"Polyline", "Measure", "Wireframe", "Object"}:
if snapping_point["type"] == "Edge":
@@ -607,6 +611,7 @@ class Snap(bonsai.core.tool.Snap):
if point["type"] == "Axis":
if ordered_snaps[0]["type"] not in {"Axis", "Plane"}:
obj = ordered_snaps[0]["object"]
assert axis_start is not ... and axis_end is not ...
mixed_snap = cls.mix_snap_and_axis(ordered_snaps[0], axis_start, axis_end)
for mixed_point in mixed_snap:
snap_point = {
+6
View File
@@ -304,12 +304,14 @@ class Spatial(bonsai.core.tool.Spatial):
while True:
has_parent = None
new_current_results = None
for key in current_results:
if flat_key.startswith(key):
has_parent = True
new_current_results = current_results[key]["children"]
break
if has_parent:
assert new_current_results is not None
current_results = new_current_results
else:
break
@@ -977,12 +979,15 @@ class Spatial(bonsai.core.tool.Spatial):
def get_purged_inner_holes_poly(cls, union_geom: Polygon, min_area: float) -> Polygon:
interiors_list = []
new_poly = None
poly = None
if union_geom.geom_type == "MultiPolygon":
for poly in union_geom.geoms:
interiors_list = cls.get_poly_valid_interior_list(
poly=poly, min_area=min_area, interiors_list=interiors_list
)
assert poly is not None
new_poly = Polygon(poly.exterior.coords, holes=interiors_list)
if union_geom.geom_type == "Polygon":
@@ -991,6 +996,7 @@ class Spatial(bonsai.core.tool.Spatial):
)
new_poly = Polygon(union_geom.exterior.coords, holes=interiors_list)
assert new_poly is not None
return new_poly
@classmethod
@@ -216,6 +216,7 @@ def update_translations_from_po(po_directory: Path, translations_module: Path):
if BPY_IS_LOADED:
import bpy
class SetupTranslationUI(bpy.types.Operator):
bl_idname = "bim.setup_translation_ui"
@@ -82,6 +82,7 @@ class Generator:
}
""".replace("{entity}", location.split("#")[-1]))
# filter parents for the brick entity
parent = None
for row in query:
parent = row.get("parent").toPython()
if "brickschema.org" in parent and parent in references.keys():
@@ -1036,6 +1036,7 @@ class LibraryGenerator:
seat_width_offset = 0.7 * width / 2 if cistern_depth else width / 2
seat_start_width_offset = 0.6 * width
cistern_3d = None
if cistern_height:
cistern = builder.rectangle(size=V(width, cistern_depth), position=shift_to_center)
cistern_3d = ifcopenshell.util.element.copy_deep(self.file, cistern)
@@ -1118,6 +1119,7 @@ class LibraryGenerator:
# cistern
if cistern_height:
assert cistern_3d
cistern_3d = builder.extrude(
cistern_3d, cistern_height + seat_level / 2, position=V(0, 0, seat_level / 2)
)
@@ -143,6 +143,7 @@ class LibraryGenerator:
if "unused" in ifc_params:
del ifc_params["unused"]
profiles_gap = ...
if prof_type == "profile_hollow*_square":
ifc_params["YDim"] = ifc_params["XDim"]
elif ifc_profile_name == "IfcCircleHollowProfileDef":
@@ -160,6 +161,7 @@ class LibraryGenerator:
profile = self.file.create_entity(ifc_profile_name, ProfileName=prof_name, ProfileType="AREA", **ifc_params)
if prof_type == "profile_l*lbeam_2l":
assert profiles_gap is not ...
profile.ProfileName = None # to avoid name confusion
mode = "SLBB" if prof_name.endswith("_SLBB") else "LLBB"
profile = self.create_double_l_profile(profile, prof_name, profiles_gap, mode)
+2 -2
View File
@@ -30,7 +30,7 @@ from collections.abc import Generator
from inspect import signature
from math import radians
from pathlib import Path
from typing import Any, Union
from typing import Any, Union, cast
import bpy
import ifcopenshell
@@ -140,7 +140,7 @@ class PanelSpy:
else:
props = kwargs.get("data")
name = kwargs.get("property")
props: bpy.types.bpy_struct
props = cast(bpy.types.bpy_struct, props)
text = kwargs.get("text", props.bl_rna.properties[name].name)
icon = kwargs.get("icon", None)
prop_type = props.bl_rna.properties[name].type