From c9518817c6af6a2518e112b25b72d878a38c8dd6 Mon Sep 17 00:00:00 2001 From: tarman3 Date: Mon, 8 Sep 2025 16:23:35 +0300 Subject: [PATCH] CAM: Path.Op.Area - Offer side --- src/Mod/CAM/Path/Op/Area.py | 82 ++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/src/Mod/CAM/Path/Op/Area.py b/src/Mod/CAM/Path/Op/Area.py index d326635957..8ed6074d4b 100644 --- a/src/Mod/CAM/Path/Op/Area.py +++ b/src/Mod/CAM/Path/Op/Area.py @@ -27,6 +27,7 @@ import Path import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils +from Path.Geom import isRoughly # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -129,31 +130,18 @@ class ObjectOp(PathOp.ObjectOp): changes. Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.""" # Path.Log.track(obj.Label, prop) - if prop in ["AreaParams", "PathParams", "removalshape"]: + if prop in ("AreaParams", "PathParams", "removalshape"): obj.setEditorMode(prop, 2) - if hasattr(obj, "Side") and prop == "Base" and len(obj.Base) == 1: - (base, subNames) = obj.Base[0] - bb = base.Shape.BoundBox # parent boundbox - - if "Face" in subNames[0]: - face = base.Shape.getElement(subNames[0]) - fbb = face.BoundBox # face boundbox - if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: - obj.Side = "Outside" - else: - obj.Side = "Inside" - - elif "Edge" in subNames[0]: - edges = [ - base.Shape.getElement(sub).copy() for sub in subNames if sub.startswith("Edge") - ] - wire = Part.Wire(Part.__sortEdges__(edges)) - wbb = wire.BoundBox # wire boundbox - if not wire.isClosed() or (bb.XLength == wbb.XLength and bb.YLength == wbb.YLength): - obj.Side = "Outside" - else: - obj.Side = "Inside" + if ( + getattr(self, "init", False) + and hasattr(obj, "Side") + and prop == "FinalDepth" + and obj.Base + ): + # Offer side only while creating new operation + self.init = False + self.opSetDefaultSide(obj) self.areaOpOnChanged(obj, prop) @@ -213,12 +201,60 @@ class ObjectOp(PathOp.ObjectOp): ) self.areaOpSetDefaultValues(obj, job) + self.init = True # using for offer 'Side' while creating new operation def areaOpSetDefaultValues(self, obj, job): """areaOpSetDefaultValues(obj, job) ... overwrite to set initial values of operation specific properties. Can safely be overwritten by subclasses.""" pass + def opSetDefaultSide(self, obj): + """setDefaltSide(obj) ... offer side while creating new operation""" + (base, subNames) = obj.Base[0] + + # find parent boundbox + if isinstance(base.Shape, Part.Compound): + bbs = [shape.BoundBox for shape in base.Shape.SubShapes] + else: + bbs = [base.Shape.BoundBox] + + subBb = None + if "Face" in subNames[0]: + faces = [base.Shape.getElement(sub) for sub in subNames if sub.startswith("Face")] + vFaces = [f for f in faces if not Path.Geom.isHorizontal(f)] + if vFaces: + # check if vertical faces creates a closed area + fzMin = min(e.BoundBox.ZMin for f in vFaces for e in f.Edges) + bottomEdges = [ + e for f in vFaces for e in f.Edges if isRoughly(e.BoundBox.ZMax, fzMin) + ] + wire = Part.Wire(Part.__sortEdges__(bottomEdges)) + if not wire.isClosed(): + # for open area always offer 'Outside' + obj.Side = "Outside" + return + shape = Part.Compound(faces) + subBb = shape.BoundBox + elif "Edge" in subNames[0]: + edges = [base.Shape.getElement(sub) for sub in subNames if sub.startswith("Edge")] + wire = Part.Wire(Part.__sortEdges__(edges)) + if not wire.isClosed(): + # for open wire always offer 'Outside' + obj.Side = "Outside" + return + else: + subBb = wire.BoundBox + + if subBb: + for bb in bbs: + if not bb.isInside(subBb): + continue + if isRoughly(bb.XLength, subBb.XLength) and isRoughly(bb.YLength, subBb.YLength): + obj.Side = "Outside" + else: + obj.Side = "Inside" + return + def getMiddlePointLongestEdge(self, shape): """getMiddlePointLongestEdge(shape) ... return middle point of longest edge from shape.""" candidate = shape.Edges[0]