From b72f635bb230d4fb86ba041d186bf1c9570fb807 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Sat, 22 Nov 2025 19:11:12 +0100 Subject: [PATCH 1/2] App: Handle uncleared Python exceptions during object restoration When a Python-based object throws an exception during the restoration process (e.g. inside `onDocumentRestored`), the error is caught by a generic `catch(...)` block in `Document::afterRestore`. Previously, this block swallowed the C++ exception wrapper but failed to clear the underlying Python error state. Leaving the interpreter in this "dirty" state caused a segmentation fault or `SystemError` later in the loading process when C++ attempted to interact with the Python API (specifically in `App::Application::setActiveDocument`). This commit adds a check for `PyErr_Occurred()` inside the catch block. If an error is detected, the traceback is printed to the console for debugging, and `PyErr_Clear()` is called to reset the interpreter state, allowing the application to continue loading the document without crashing. --- src/App/Document.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/App/Document.cpp b/src/App/Document.cpp index aa46a3939c..aae215db62 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -2092,6 +2092,16 @@ bool Document::afterRestore(const std::vector& objArray, bool c FC_ERR("Failed to restore " << obj->getFullName() << ": " << e.what()); } catch (...) { + + // If a Python exception occurred, it must be cleared immediately. + // Otherwise, the interpreter remains in a dirty state, causing + // Segfaults later when FreeCAD interacts with Python. + if (PyErr_Occurred()) { + Base::Console().error("Python error during object restore:\n"); + PyErr_Print(); // Print the traceback to stderr/Console + PyErr_Clear(); // Reset the interpreter state + } + d->addRecomputeLog("Unknown exception on restore", obj); FC_ERR("Failed to restore " << obj->getFullName() << ": " << "unknown exception"); } From 6d4629f39beab90b4cbe0ce7399480f6a4cb2858 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Sat, 22 Nov 2025 19:39:57 +0100 Subject: [PATCH 2/2] Assembly: Prevent zero-length line creation in ExplodedView Safeguards the generation of explosion trail lines to avoid creating invalid geometry. Previously, if a part displacement was zero (or effectively zero), `Part.LineSegment` would attempt to create a line with identical start and end points, causing OpenCascade to throw a `Part.OCCError`. This resulted in failures during document restoration and recomputes. This commit introduces a `_createSafeLine` helper method that checks the distance against a `1e-7` (near-zero) tolerance before attempting to generate the shape. --- src/Mod/Assembly/CommandCreateView.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Mod/Assembly/CommandCreateView.py b/src/Mod/Assembly/CommandCreateView.py index 1ef1c2ffec..1ecffbe07d 100644 --- a/src/Mod/Assembly/CommandCreateView.py +++ b/src/Mod/Assembly/CommandCreateView.py @@ -132,6 +132,14 @@ class ExplodedView: return obj return None + def _createSafeLine(self, start, end): + """Creates a LineSegment shape only if points are not coincident.""" + from Part import Precision + + if (start - end).Length > Precision.confusion(): + return LineSegment(start, end).toShape() + return None + def saveAssemblyAndExplode(self, viewObj): self.initialPlcs = UtilsAssembly.saveAssemblyPartsPlacements(self.getAssembly(viewObj)) @@ -140,8 +148,9 @@ class ExplodedView: lines = [] for startPos, endPos in self.positions: - line = LineSegment(startPos, endPos).toShape() - lines.append(line) + line = self._createSafeLine(startPos, endPos) + if line: + lines.append(line) if lines: return Compound(lines) @@ -244,8 +253,9 @@ class ExplodedView: # Add shapes for the explosion lines for start_pos, end_pos in line_positions: - line = LineSegment(start_pos, end_pos).toShape() - exploded_shapes.append(line) + line = self._createSafeLine(start_pos, end_pos) + if line: + exploded_shapes.append(line) if exploded_shapes: return Compound(exploded_shapes)