mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Bonsai: report a clean error instead of a traceback for unsupported bend profiles
FitFlowSegments calls bpy.ops.bim.mep_add_bend/transition/obstruction as nested operator invocations. Blender re-raises any ERROR-level report from a nested bpy.ops.X() call as a RuntimeError to the Python caller, so a clean self.report + CANCELLED from those nested operators (e.g. "profile class not supported for a bend") was surfacing as an uncaught exception instead. That exception then propagated further through the S,Y hotkey, triggering Bonsai's generic "experienced an error" popup with a full traceback rather than the intended single-line message. Catch the RuntimeError at both nesting points (FitFlowSegments's calls to the mep_add_* operators, and the S,Y hotkey's call to fit_flow_segments) and re-surface it as a single clean self.report instead of letting it propagate uncaught. Fixes #5450. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -159,6 +159,29 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Fit Flow Segments"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _call_fitting_op(self, op) -> bool:
|
||||
"""Invoke a nested ``bim.mep_add_*`` operator, converting its reported
|
||||
error into a clean ``self.report`` instead of letting it surface as a
|
||||
raw developer traceback.
|
||||
|
||||
Blender raises a ``RuntimeError`` back to the Python caller whenever an
|
||||
operator invoked via ``bpy.ops.X()`` reports an ``ERROR`` (e.g. an
|
||||
unsupported profile class for a bend), regardless of the nested
|
||||
operator's own clean ``self.report`` + ``CANCELLED``. Left uncaught,
|
||||
that exception bubbles all the way up to whatever ultimately called
|
||||
``bim.fit_flow_segments`` (e.g. a hotkey), producing Bonsai's generic
|
||||
"experienced an error" popup with a full traceback (see #5450) instead
|
||||
of the single-line message the nested operator already produced.
|
||||
|
||||
Returns True on success, False if the nested operator reported an
|
||||
error (already surfaced via ``self.report``)."""
|
||||
try:
|
||||
op()
|
||||
except RuntimeError as e:
|
||||
self.report({"ERROR"}, str(e).removeprefix("Error: ").strip())
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
# TODO: need to add ui for parameters:
|
||||
# - obstruction cap thickness
|
||||
@@ -184,7 +207,8 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
if total_selected_objs == 1:
|
||||
fitting_type = "OBSTRUCTION"
|
||||
bpy.ops.bim.mep_add_obstruction()
|
||||
if not self._call_fitting_op(bpy.ops.bim.mep_add_obstruction):
|
||||
return
|
||||
|
||||
elif total_selected_objs == 2:
|
||||
# Shorten the axis by the profile size to allow for fuzzy intersections
|
||||
@@ -211,7 +235,8 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
is_on_axis2 = tool.Cad.is_point_on_edge(intersect2, axis2)
|
||||
if not is_on_axis1 and not is_on_axis2:
|
||||
fitting_type = "BEND"
|
||||
bpy.ops.bim.mep_add_bend()
|
||||
if not self._call_fitting_op(bpy.ops.bim.mep_add_bend):
|
||||
return
|
||||
elif is_on_axis1 and is_on_axis2:
|
||||
fitting_type = "CROSS"
|
||||
else:
|
||||
@@ -219,7 +244,8 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif total_profiles == 2:
|
||||
if is_parallel:
|
||||
fitting_type = "TRANSITION"
|
||||
bpy.ops.bim.mep_add_transition()
|
||||
if not self._call_fitting_op(bpy.ops.bim.mep_add_transition):
|
||||
return
|
||||
|
||||
elif total_selected_objs == 3:
|
||||
if total_profiles > 1:
|
||||
|
||||
@@ -1411,7 +1411,18 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
# bim.fit_flow_segments() reports a clean single-line ERROR (e.g.
|
||||
# an unsupported profile class for a bend, see #5450) when it
|
||||
# can't build a fitting. Blender re-raises any ERROR-level report
|
||||
# from a nested bpy.ops.X() call as a RuntimeError to the Python
|
||||
# caller, so without this guard that clean message would surface
|
||||
# as an uncaught exception here, producing Bonsai's generic
|
||||
# "experienced an error" popup with a full traceback instead of
|
||||
# the intended one-line message.
|
||||
try:
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
except RuntimeError as e:
|
||||
self.report({"ERROR"}, str(e).removeprefix("Error: ").strip())
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.extend_profile(join_type="V")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user