Assembly: ExplodedViews: fix their use in techdraw (#24769)
* Assembly: ExplodedViews: Add getExplodedShape for techdraw to use * TechDraw: ShapeExtractor fix assembly exploded views * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Assembly: Fix variable casing in CommandCreateView.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kacper Donat <kadet1090@gmail.com>
This commit is contained in:
@@ -153,6 +153,101 @@ class ExplodedView:
|
||||
|
||||
UtilsAssembly.restoreAssemblyPartsPlacements(self.getAssembly(viewObj), self.initialPlcs)
|
||||
|
||||
def _calculateExplodedPlacements(self, viewObj):
|
||||
"""
|
||||
Internal helper to calculate final placements for an exploded view without
|
||||
applying them.
|
||||
Returns:
|
||||
- A dictionary mapping {part_object: final_placement}.
|
||||
- A list of [start_pos, end_pos] for explosion lines.
|
||||
"""
|
||||
final_placements = {}
|
||||
line_positions = []
|
||||
factor = 1
|
||||
|
||||
assembly = self.getAssembly(viewObj)
|
||||
# Get a snapshot of the assembly's current, un-exploded state
|
||||
calculated_placements = UtilsAssembly.saveAssemblyPartsPlacements(assembly)
|
||||
|
||||
com, size = UtilsAssembly.getComAndSize(assembly)
|
||||
|
||||
for move in viewObj.Group:
|
||||
if not UtilsAssembly.isRefValid(move.References, 1):
|
||||
continue
|
||||
|
||||
if move.MoveType == "Radial":
|
||||
distance = move.MovementTransform.Base.Length
|
||||
factor = 4 * distance / size
|
||||
|
||||
subs = move.References[1]
|
||||
for sub in subs:
|
||||
ref = [move.References[0], [sub]]
|
||||
obj = UtilsAssembly.getObject(ref)
|
||||
if not obj or not hasattr(obj, "Placement"):
|
||||
continue
|
||||
|
||||
# Use the placement from our calculation dictionary, which tracks
|
||||
# changes from previous steps.
|
||||
current_placement = calculated_placements.get(obj.Name, obj.Placement)
|
||||
|
||||
# Use the part's bounding box center relative to its own coordinate system
|
||||
# and transform it by the current placement to get the world start position.
|
||||
local_center = obj.Shape.BoundBox.Center
|
||||
start_pos = current_placement.multVec(local_center)
|
||||
|
||||
if move.MoveType == "Radial":
|
||||
obj_com, obj_size = UtilsAssembly.getComAndSize(obj)
|
||||
init_vec = obj_com - com
|
||||
new_base = current_placement.Base + init_vec * factor
|
||||
new_placement = App.Placement(new_base, current_placement.Rotation)
|
||||
else:
|
||||
new_placement = move.MovementTransform * current_placement
|
||||
|
||||
# Store the newly calculated placement for this part
|
||||
calculated_placements[obj.Name] = new_placement
|
||||
final_placements[obj] = new_placement
|
||||
|
||||
end_pos = new_placement.multVec(local_center)
|
||||
line_positions.append([start_pos, end_pos])
|
||||
|
||||
return final_placements, line_positions
|
||||
|
||||
def getExplodedShape(self, viewObj):
|
||||
"""
|
||||
Generates a compound shape of the exploded assembly in memory
|
||||
without modifying the document. Returns a single Part.Compound.
|
||||
"""
|
||||
final_placements, line_positions = self._calculateExplodedPlacements(viewObj)
|
||||
|
||||
exploded_shapes = []
|
||||
|
||||
# We need to include ALL parts of the assembly, not just the moved ones.
|
||||
assembly = self.getAssembly(viewObj)
|
||||
all_parts = UtilsAssembly.getMovablePartsWithin(
|
||||
assembly, True
|
||||
) # Or however you get all parts
|
||||
|
||||
for part in all_parts:
|
||||
# Get the original shape. It's crucial to use .copy()
|
||||
shape_copy = part.Shape.copy()
|
||||
|
||||
# If the part was moved, use its calculated final placement.
|
||||
# Otherwise, use its current placement from the document.
|
||||
final_plc = final_placements.get(part, part.Placement)
|
||||
|
||||
shape_copy.transformShape(final_plc.toMatrix())
|
||||
exploded_shapes.append(shape_copy)
|
||||
|
||||
# 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)
|
||||
|
||||
if exploded_shapes:
|
||||
return Compound(exploded_shapes)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class ViewProviderExplodedView:
|
||||
def __init__(self, vobj):
|
||||
|
||||
@@ -93,14 +93,11 @@ TopoDS_Shape ShapeExtractor::getShapes(const std::vector<App::DocumentObject*> l
|
||||
// Copy the pointer as not const so it can be changed if needed.
|
||||
App::DocumentObject* obj = l;
|
||||
|
||||
bool isExplodedView = false;
|
||||
auto proxy = dynamic_cast<App::PropertyPythonObject*>(l->getPropertyByName("Proxy"));
|
||||
Base::PyGILStateLocker lock;
|
||||
if (proxy && proxy->getValue().hasAttr("saveAssemblyAndExplode")) {
|
||||
isExplodedView = true;
|
||||
|
||||
if (proxy && proxy->getValue().hasAttr("getExplodedShape")) {
|
||||
Py::Object explodedViewPy = proxy->getValue();
|
||||
Py::Object attr = explodedViewPy.getAttr("saveAssemblyAndExplode");
|
||||
Py::Object attr = explodedViewPy.getAttr("getExplodedShape");
|
||||
|
||||
if (attr.ptr() && attr.isCallable()) {
|
||||
Py::Tuple args(1);
|
||||
@@ -108,18 +105,16 @@ TopoDS_Shape ShapeExtractor::getShapes(const std::vector<App::DocumentObject*> l
|
||||
Py::Callable methode(attr);
|
||||
Py::Object pyResult = methode.apply(args);
|
||||
|
||||
if (PyObject_TypeCheck(pyResult.ptr(), &(Part::TopoShapePy::Type))) {
|
||||
if (pyResult.ptr()
|
||||
&& PyObject_TypeCheck(pyResult.ptr(), &(Part::TopoShapePy::Type))) {
|
||||
auto* shapepy = static_cast<Part::TopoShapePy*>(pyResult.ptr());
|
||||
const TopoDS_Shape& shape = shapepy->getTopoShapePtr()->getShape();
|
||||
sourceShapes.push_back(shape);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* inObj : l->getInList()) {
|
||||
if (inObj->isDerivedFrom<App::Part>()) {
|
||||
// we replace obj by the assembly
|
||||
obj = inObj;
|
||||
break;
|
||||
// The python script returns the complete exploded view shape (parts + lines).
|
||||
// We add it and immediately continue to the next object in the source links,
|
||||
// skipping the default shape extraction logic below.
|
||||
sourceShapes.push_back(shape);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,17 +140,6 @@ TopoDS_Shape ShapeExtractor::getShapes(const std::vector<App::DocumentObject*> l
|
||||
sourceShapes.insert(sourceShapes.end(), shapeList.begin(), shapeList.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (isExplodedView) {
|
||||
Py::Object explodedViewPy = proxy->getValue();
|
||||
|
||||
Py::Object attr = explodedViewPy.getAttr("restoreAssembly");
|
||||
if (attr.ptr() && attr.isCallable()) {
|
||||
Py::Tuple args(1);
|
||||
args.setItem(0, Py::asObject(l->getPyObject()));
|
||||
Py::Callable(attr).apply(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BRep_Builder builder;
|
||||
|
||||
Reference in New Issue
Block a user