From eda4266fe2fc67a75c2a4e6770cf934b1573bce5 Mon Sep 17 00:00:00 2001 From: Chris Nisbet Date: Sun, 28 Mar 2021 16:47:24 +1300 Subject: [PATCH 01/20] Path: vcarve - Fix depth calculation Depths were being calculated incorrectly due to MIC distance getting divided by scaling factor rather than multiplied by it. The scaling factor is the inverse of the tan of half the cutting tool angle, so for a 30 degree bit (say), the scaling factor would be 1 / tan(30/2), or 3.7320. When given an MIC (which is a radius) of 3.175 (say), and cutting to the same width with a 30 degree bit, the depth should be 3.175 * 3.7320 (11..849), not 3.175 / 3.7320 (0.8509) as it is currently. --- src/Mod/Path/PathScripts/PathVcarve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 22c974d108..4b93ed1ad3 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -186,7 +186,7 @@ class _Geometry(object): def _calculate_depth(MIC, geom): # given a maximum inscribed circle (MIC) and tool angle, # return depth of cut relative to zStart. - depth = geom.start - round(MIC / geom.scale, 4) + depth = geom.start - round(MIC * geom.scale, 4) PathLog.debug('zStart value: {} depth: {}'.format(geom.start, depth)) return max(depth, geom.stop) From 55453bd69dc27108898609191c74651d469acef9 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 18 Mar 2021 23:21:41 -0500 Subject: [PATCH 02/20] Path: Fix update of Extensions `Default Length` GUI spin box Fix the bug hindering update of the Default Length spin box in the Extensions tab after editing and change of focus. With the fix, the spin box updates after change of focus, as do other spin boxes in the Path workbench. Used method found in PathDrillingGui module. --- src/Mod/Path/PathScripts/PathPocketShapeGui.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index 4e1b651148..e6aac3eae2 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -222,7 +222,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): if obj.ExtensionCorners != self.form.extendCorners.isChecked(): self.form.extendCorners.toggle() - self.defaultLength.updateSpinBox() + self.updateQuantitySpinBoxes() self.extensions = obj.Proxy.getExtensions(obj) # pylint: disable=attribute-defined-outside-init self.setExtensions(self.extensions) @@ -341,11 +341,15 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.form.extensionTree.blockSignals(False) + def updateQuantitySpinBoxes(self, index = None): + self.defaultLength.updateSpinBox() + def updateData(self, obj, prop): PathLog.track(obj.Label, prop, self.blockUpdateData) if not self.blockUpdateData: if prop in ['Base', 'ExtensionLengthDefault']: self.setExtensions(obj.Proxy.getExtensions(obj)) + self.updateQuantitySpinBoxes() def restoreSelection(self, selection): PathLog.track() @@ -458,6 +462,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.form.buttonClear.clicked.connect(self.extensionsClear) self.form.buttonDisable.clicked.connect(self.extensionsDisable) self.form.buttonEnable.clicked.connect(self.extensionsEnable) + self.form.defaultLength.editingFinished.connect(self.updateQuantitySpinBoxes) self.model.itemChanged.connect(self.updateItemEnabled) From de866634e2de6bf9c67e935850596624e76f2c4e Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 21 Feb 2021 14:13:03 -0600 Subject: [PATCH 03/20] Path: Set `CustomPoint2` and `CustomPoint1` defaults equal Setting these two default values equal will aid in code execution and error detection. Subsequent fixes will rely on this condition. --- src/Mod/Path/PathScripts/PathSlot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index d5c40c019d..1b9ee73f82 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -159,7 +159,7 @@ class ObjectSlot(PathOp.ObjectOp): 'CustomPoint1': FreeCAD.Vector(0.0, 0.0, 0.0), 'ExtendPathStart': 0.0, 'Reference1': 'Center of Mass', - 'CustomPoint2': FreeCAD.Vector(10.0, 10.0, 0.0), + 'CustomPoint2': FreeCAD.Vector(0.0, 0.0, 0.0), 'ExtendPathEnd': 0.0, 'Reference2': 'Center of Mass', 'LayerMode': 'Multi-pass', From b372cc75495bcdbf9ff1672f4b874186250297ef Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 21 Feb 2021 14:15:41 -0600 Subject: [PATCH 04/20] Path: Add check for `CustomPoint` input If custom points are same, the user has not entered acceptable values for path generation with custom points. --- src/Mod/Path/PathScripts/PathSlot.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index 1b9ee73f82..bdafd00e87 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -433,7 +433,12 @@ class ObjectSlot(PathOp.ObjectOp): # Use custom inputs here p1 = obj.CustomPoint1 p2 = obj.CustomPoint2 - if p1.z == p2.z: + if p1 == p2: + msg = translate('PathSlot', + 'Custom points are identical.') + FreeCAD.Console.PrintError(msg + '\n') + return False + elif p1.z == p2.z: pnts = (p1, p2) else: msg = translate('PathSlot', From ccba37af3e4ab2302e25afa70fd3924beb64812d Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 21 Feb 2021 14:16:33 -0600 Subject: [PATCH 05/20] Path: Fix error when no `obj.Base` features selected --- src/Mod/Path/PathScripts/PathSlot.py | 43 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index bdafd00e87..8d81b52642 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -440,33 +440,34 @@ class ObjectSlot(PathOp.ObjectOp): return False elif p1.z == p2.z: pnts = (p1, p2) + featureCount = 2 else: msg = translate('PathSlot', 'Custom points not at same Z height.') FreeCAD.Console.PrintError(msg + '\n') return False - - baseGeom = obj.Base[0] - base, subsList = baseGeom - self.base = base - - featureCount = len(subsList) - if featureCount == 1: - PathLog.debug('Reference 1: {}'.format(obj.Reference1)) - sub1 = subsList[0] - shape_1 = getattr(base.Shape, sub1) - self.shape1 = shape_1 - pnts = self._processSingle(obj, shape_1, sub1) else: - PathLog.debug('Reference 1: {}'.format(obj.Reference1)) - PathLog.debug('Reference 2: {}'.format(obj.Reference2)) - sub1 = subsList[0] - sub2 = subsList[1] - shape_1 = getattr(base.Shape, sub1) - shape_2 = getattr(base.Shape, sub2) - self.shape1 = shape_1 - self.shape2 = shape_2 - pnts = self._processDouble(obj, shape_1, sub1, shape_2, sub2) + baseGeom = obj.Base[0] + base, subsList = baseGeom + self.base = base + + featureCount = len(subsList) + if featureCount == 1: + PathLog.debug('Reference 1: {}'.format(obj.Reference1)) + sub1 = subsList[0] + shape_1 = getattr(base.Shape, sub1) + self.shape1 = shape_1 + pnts = self._processSingle(obj, shape_1, sub1) + else: + PathLog.debug('Reference 1: {}'.format(obj.Reference1)) + PathLog.debug('Reference 2: {}'.format(obj.Reference2)) + sub1 = subsList[0] + sub2 = subsList[1] + shape_1 = getattr(base.Shape, sub1) + shape_2 = getattr(base.Shape, sub2) + self.shape1 = shape_1 + self.shape2 = shape_2 + pnts = self._processDouble(obj, shape_1, sub1, shape_2, sub2) if not pnts: return False From b76b1fae97459214c646a2ee2a94c0e6a6eaaabf Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Wed, 24 Feb 2021 20:53:15 -0600 Subject: [PATCH 06/20] Path: Fix variable naming to use lowercase --- src/Mod/Path/PathScripts/PathSlot.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index 8d81b52642..8007797be3 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -868,16 +868,16 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleComplexFace(self, obj, shape): """Determine slot path endpoints from a single complex face.""" PathLog.debug('_processSingleComplexFace()') - PNTS = list() + pnts = list() - def zVal(V): - return V.z + def zVal(p): + return p.z for E in shape.Wires[0].Edges: p = self._findLowestEdgePoint(E) - PNTS.append(p) - PNTS.sort(key=zVal) - return (PNTS[0], PNTS[1]) + pnts.append(p) + pnts.sort(key=zVal) + return (pnts[0], pnts[1]) def _processSingleVertFace(self, obj, shape): """Determine slot path endpoints from a single vertically oriented face From 8f2864acd812d58423232632296e75cb1c73b399 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Wed, 24 Feb 2021 20:54:10 -0600 Subject: [PATCH 07/20] Path: Fix comment syntax --- src/Mod/Path/PathScripts/PathSlot.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index 8007797be3..b38bbe124c 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -931,7 +931,7 @@ class ObjectSlot(PathOp.ObjectOp): # Check that all Z values are equal (isRoughly same) if (abs(z1 - z2) > tolrnc or abs(z1 - z3) > tolrnc ): -# abs(z2 - z3) > tolrnc): 3rd test redundant. + # abs(z2 - z3) > tolrnc): 3rd test redundant. return False return True @@ -1315,13 +1315,13 @@ class ObjectSlot(PathOp.ObjectOp): def _isParallel(self, dYdX1, dYdX2): """Determine if two orientation vectors are parallel.""" + # if dYdX1.add(dYdX2).Length == 0: + # return True + # if ((dYdX1.x + dYdX2.x) / 2.0 == dYdX1.x and + # (dYdX1.y + dYdX2.y) / 2.0 == dYdX1.y): + # return True + # return False return (dYdX1.cross(dYdX2) == FreeCAD.Vector(0,0,0) ) - # if dYdX1.add(dYdX2).Length == 0: - # return True - # if ((dYdX1.x + dYdX2.x) / 2.0 == dYdX1.x and - # (dYdX1.y + dYdX2.y) / 2.0 == dYdX1.y): - # return True - # return False def _makePerpendicular(self, p1, p2, length): """_makePerpendicular(p1, p2, length)... From 79cfcc0ff68b84c2877162e6fef6f56a38899d21 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Wed, 24 Feb 2021 20:57:19 -0600 Subject: [PATCH 08/20] Path: Improve horizontal-planar and curved face handling These changes allow for better identification of horizontally planar faces, and also improve handling of circular arc faces that failed previously for use with ball-end bits. This fix also improves slot creation based on selection of two arc edges for ball-end slot creation. --- src/Mod/Path/PathScripts/PathSlot.py | 71 ++++++++++++++++++---------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index b38bbe124c..b55a59f2b6 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -41,6 +41,7 @@ import math from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader('Part', globals(), 'Part') Arcs = LazyLoader('draftgeoutils.arcs', globals(), 'draftgeoutils.arcs') +PathGeom = LazyLoader('PathScripts.PathGeom', globals(), 'PathScripts.PathGeom') if FreeCAD.GuiUp: FreeCADGui = LazyLoader('FreeCADGui', globals(), 'FreeCADGui') @@ -353,6 +354,7 @@ class ObjectSlot(PathOp.ObjectOp): self.arcMidPnt = None self.arcRadius = 0.0 self.newRadius = 0.0 + self.featureDetails = ["", ""] self.isDebug = False if PathLog.getLevel(PathLog.thisModule()) != 4 else True self.showDebugObjects = False self.stockZMin = self.job.Stock.Shape.BoundBox.ZMin @@ -614,21 +616,26 @@ class ObjectSlot(PathOp.ObjectOp): PathLog.debug('_finishLine() Perp, featureCnt == 2') if perpZero: (p1, p2) = pnts - pnts = self._makePerpendicular(p1, p2, 10.0) # 10.0 offset below + initPerpDist = p1.sub(p2).Length + pnts = self._makePerpendicular(p1, p2, initPerpDist) # 10.0 offset below else: # Modify path points if user selected two parallel edges if (featureCnt == 2 and self.shapeType1 == 'Edge' and - self.shapeType2 == 'Edge' and self._isParallel(self.dYdX1, self.dYdX2)): - (p1, p2) = pnts - edg1_len = self.shape1.Length - edg2_len = self.shape2.Length - set_length = max(edg1_len, edg2_len) - pnts = self._makePerpendicular(p1, p2, 10.0 + set_length) # 10.0 offset below - if edg1_len != edg2_len: - msg = obj.Label + ' ' - msg += translate('PathSlot', - 'Verify slot path start and end points.') - FreeCAD.Console.PrintWarning(msg + '\n') + self.shapeType2 == 'Edge'): + if self.featureDetails[0] == "arc" and self.featureDetails[1] == "arc": + perpZero = False + elif self._isParallel(self.dYdX1, self.dYdX2): + PathLog.debug('_finishLine() StE, featureCnt == 2 // edges') + (p1, p2) = pnts + edg1_len = self.shape1.Length + edg2_len = self.shape2.Length + set_length = max(edg1_len, edg2_len) + pnts = self._makePerpendicular(p1, p2, 10.0 + set_length) # 10.0 offset below + if edg1_len != edg2_len: + msg = obj.Label + ' ' + msg += translate('PathSlot', + 'Verify slot path start and end points.') + FreeCAD.Console.PrintWarning(msg + '\n') else: perpZero = False @@ -718,21 +725,30 @@ class ObjectSlot(PathOp.ObjectOp): pnts = False norm = shape_1.normalAt(0.0, 0.0) PathLog.debug('{}.normalAt(): {}'.format(sub1, norm)) - if norm.z == 1 or norm.z == -1: - pnts = self._processSingleHorizFace(obj, shape_1) - elif norm.z == 0: - faceType = self._getVertFaceType(shape_1) - if faceType: - (geo, shp) = faceType - if geo == 'Face': - pnts = self._processSingleComplexFace(obj, shp) - if geo == 'Wire': - pnts = self._processSingleVertFace(obj, shp) - if geo == 'Edge': - pnts = self._processSingleVertFace(obj, shp) + + if PathGeom.isRoughly(shape_1.BoundBox.ZMax, shape_1.BoundBox.ZMin): + # Horizontal face + if norm.z == 1 or norm.z == -1: + pnts = self._processSingleHorizFace(obj, shape_1) + elif norm.z == 0: + faceType = self._getVertFaceType(shape_1) + if faceType: + (geo, shp) = faceType + if geo == 'Face': + pnts = self._processSingleComplexFace(obj, shp) + if geo == 'Wire': + pnts = self._processSingleVertFace(obj, shp) + if geo == 'Edge': + pnts = self._processSingleVertFace(obj, shp) else: + if len(shape_1.Edges) == 4: + pnts = self._processSingleHorizFace(obj, shape_1) + else: + pnts = self._processSingleComplexFace(obj, shape_1) + + if not pnts: msg = translate('PathSlot', - 'The selected face is not oriented horizontally or vertically.') + 'The selected face is inaccessible.') FreeCAD.Console.PrintError(msg + '\n') return False @@ -1188,6 +1204,9 @@ class ObjectSlot(PathOp.ObjectOp): p = self._getHighestPoint(shape) elif cat == 'Edge': + featDetIdx = pNum - 1 + if shape.Curve.TypeId == 'Part::GeomCircle': + self.featureDetails[featDetIdx] = "arc" # calculate slope between end vertexes v0 = shape.Edges[0].Vertexes[0] v1 = shape.Edges[0].Vertexes[1] @@ -1373,7 +1392,7 @@ class ObjectSlot(PathOp.ObjectOp): def _findLowestEdgePoint(self, E): zMin = E.BoundBox.ZMin eLen = E.Length - L0 = 0 + L0 = 0.0 L1 = eLen p0 = None p1 = None From 9da9bf0f93ecba7fab942b5a7c841776ba9534a6 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:49:13 -0500 Subject: [PATCH 09/20] Path: Fix outside extension of circular faces with inner holes Fix bug identified by MLampert. This fix addresses extensions for circular faces that have non-extended holes in the face. . Unlike polygon extensions that only need one wire to define the extension shape, some circular extensions require two wires (a face would be better) to define the extension area. . Restructure to accommodate future `Extensions` improvements --- src/Mod/Path/PathScripts/PathPocketShape.py | 45 +++++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 190e0d26e0..68d8c29a00 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -139,6 +139,7 @@ class Extension(object): self.sub = sub self.length = length self.direction = direction + self.extFaces = list() self.wire = None @@ -197,7 +198,22 @@ class Extension(object): return self._getDirectedNormal(e0.valueAt(midparam), normal.normalize()) + def getExtensionFaces(self, extensionWire): + '''getExtensionFace(extensionWire)... + A public helper method to retrieve the requested extension as a face, + rather than a wire becuase some extensions require a face shape + for definition that allows for two wires for boundary definition. + ''' + + if self.extFaces: + return self.extFaces + + return [Part.Face(extensionWire)] + def getWire(self): + '''getWire()... Public method to retrieve the extension area, pertaining to the feature + and sub element provided at class instantiation, as a closed wire. If no closed wire + is possible, a `None` value is returned.''' PathLog.track() if PathGeom.isRoughly(0, self.length.Value) or not self.sub: PathLog.debug("no extension, length=%.2f, sub=%s" % (self.length.Value, self.sub)) @@ -233,7 +249,9 @@ class Extension(object): e2 = Part.makeLine(edge.valueAt(edge.LastParameter), e3.valueAt(e3.LastParameter)) return Part.Wire([e0, edge, e2, e3]) - return Part.Wire([e3]) + extWire = Part.Wire([e3]) + self.extFaces = [self._makeCircularExtFace(edge, extWire)] + return extWire # the extension is bigger than the hole - so let's just cover the whole hole if endPoints(edge): @@ -258,6 +276,25 @@ class Extension(object): return extendWire(feature, sub, self.length.Value) + def _makeCircularExtFace(self, edge, extWire): + '''_makeCircularExtensionFace(edge, extWire)... + Create proper circular extension face shape. Incoming edge is expected to be a circle. + ''' + # Add original outer wire to cut faces if necessary + edgeFace = Part.Face(Part.Wire([edge])) + edgeFace.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - edgeFace.BoundBox.ZMin)) + extWireFace = Part.Face(extWire) + extWireFace.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - extWireFace.BoundBox.ZMin)) + + if extWireFace.Area >= edgeFace.Area: + extensionFace = extWireFace.cut(edgeFace) + else: + extensionFace = edgeFace.cut(extWireFace) + extensionFace.translate(FreeCAD.Vector(0.0, 0.0, edge.BoundBox.ZMin)) + + return extensionFace +# Eclass + class ObjectPocket(PathPocketBase.ObjectPocket): '''Proxy object for Pocket operation.''' @@ -385,9 +422,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket): for ext in self.getExtensions(obj): wire = ext.getWire() if wire: - face = Part.Face(wire) - self.horiz.append(face) - self.exts.append(face) + for face in ext.getExtensionFaces(wire): + self.horiz.append(face) + self.exts.append(face) # Place all self.horiz faces into same working plane for h in self.horiz: From d08aef58a9521a72e6ea86f5dee91c107c247e40 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 10 Apr 2021 14:05:52 -0500 Subject: [PATCH 10/20] Path: Correct GUI visualization of circular-face visualizations Path: Fix internal circular extension visualization --- .../Path/PathScripts/PathPocketShapeGui.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index 4e1b651148..c43000d0bd 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -75,6 +75,7 @@ class _Extension(object): hnt = coin.SoShapeHints() if not ext is None: + numVert = list() # track number of verticies in each polygon face try: wire = ext.getWire() except FreeCAD.Base.FreeCADError: @@ -86,8 +87,22 @@ class _Extension(object): p2 = list(reversed(p1)) polygon = [(p.x, p.y, p.z) for p in (p0 + p2)] else: - poly = [p for p in wire.discretize(Deflection=0.02)][:-1] - polygon = [(p.x, p.y, p.z) for p in poly] + if ext.extFaces: + # Create polygon for each extension face in compound extensions + allPolys = list() + extFaces = ext.getExtensionFaces(wire) + for f in extFaces: + pCnt = 0 + for w in f.Wires: + poly = [p for p in w.discretize(Deflection=0.01)] + pCnt += len(poly) + allPolys.extend(poly) + numVert.append(pCnt) + polygon = [(p.x, p.y, p.z) for p in allPolys] + else: + # poly = [p for p in wire.discretize(Deflection=0.02)][:-1] + poly = [p for p in wire.discretize(Deflection=0.02)] + polygon = [(p.x, p.y, p.z) for p in poly] crd.point.setValues(polygon) else: return None @@ -98,6 +113,10 @@ class _Extension(object): hnt.faceType = coin.SoShapeHints.UNKNOWN_FACE_TYPE hnt.vertexOrdering = coin.SoShapeHints.CLOCKWISE + if numVert: + # Transfer vertex counts for polygon faces + fce.numVertices.setValues(tuple(numVert)) + sep.addChild(pos) sep.addChild(mat) sep.addChild(hnt) From b922ae5b1527042874b2cecccf22fa155c0c4b5e Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 27 Apr 2021 14:56:05 -0500 Subject: [PATCH 11/20] fix bug adding TC from job dialog. When adding tool controllers from the job dialog, it will now try to match the toolbit file with the currently selected library and add use the toolnumber from there. If no match is found, it will autoincrement from the existing tool controllers in the job --- src/Mod/Path/PathScripts/PathJob.py | 5 +++++ src/Mod/Path/PathScripts/PathJobGui.py | 28 ++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 655528b72d..2f9958800b 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -457,6 +457,11 @@ class ObjectJob: self.obj.Operations.Group = group op.Path.Center = self.obj.Operations.Path.Center + def nextToolNumber(self): + # returns the next available toolnumber in the job + group = self.obj.Tools.Group + return sorted([t.ToolNumber for t in group])[-1] + 1 + def addToolController(self, tc): group = self.obj.Tools.Group PathLog.debug("addToolController(%s): %s" % (tc.Label, [t.Label for t in group])) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index 865a7445ce..ce62fd0923 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -27,11 +27,11 @@ import math import traceback from pivy import coin from PySide import QtCore, QtGui +import json import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathJobDlg as PathJobDlg @@ -520,7 +520,7 @@ class StockFromExistingEdit(StockEdit): stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex()) if not (hasattr(obj.Stock, 'Objects') and len(obj.Stock.Objects) == 1 and obj.Stock.Objects[0] == stock): if stock: - stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix , 'Stock') + stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix, 'Stock') stock.ViewObject.Visibility = True PathStock.SetupStockObject(stock, PathStock.StockType.Unknown) stock.Proxy.execute(stock) @@ -546,7 +546,7 @@ class StockFromExistingEdit(StockEdit): index = -1 for i, solid in enumerate(self.candidates(obj)): self.form.stockExisting.addItem(solid.Label, solid) - label="{}-{}".format(self.StockLabelPrefix, solid.Label) + label = "{}-{}".format(self.StockLabelPrefix, solid.Label) if label == stockName: index = i @@ -864,13 +864,33 @@ class TaskPanel: self.toolControllerSelect() def toolControllerAdd(self): + # adding a TC from a toolbit directly. + # Try to find a tool number from the currently selected lib. Otherwise + # use next available number + if PathPreferences.toolsUseLegacyTools(): PathToolLibraryEditor.CommandToolLibraryEdit().edit(self.obj, self.updateToolController) else: tools = PathToolBitGui.LoadTools() + + curLib = PathPreferences.lastFileToolLibrary() + + library = None + if curLib is not None: + with open(curLib) as fp: + library = json.load(fp) + for tool in tools: - tc = PathToolControllerGui.Create(name=tool.Label, tool=tool) + toolNum = self.obj.Proxy.nextToolNumber() + if library is not None: + for toolBit in library['tools']: + + if toolBit['path'] == tool.File: + toolNum = toolBit['nr'] + + tc = PathToolControllerGui.Create(name=tool.Label, tool=tool, toolNumber=toolNum) self.obj.Proxy.addToolController(tc) + FreeCAD.ActiveDocument.recompute() self.updateToolController() From 841b792c73a0f86f91db73252de7b46e956db80b Mon Sep 17 00:00:00 2001 From: Heewa Barfchin Date: Mon, 17 May 2021 18:13:16 -0400 Subject: [PATCH 12/20] Path: check for empty before using - fixes #4645 In a few locations, python objects are used without checking if they exist and are non-null, which throws missing attribute exceptions. The fix is to simply check first. --- src/Mod/Path/PathScripts/PathJob.py | 80 ++++++++++++++++------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 655528b72d..7fa8a6857b 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -171,7 +171,7 @@ class ObjectJob: obj.Stock.ViewObject.Visibility = False def setupSetupSheet(self, obj): - if not hasattr(obj, 'SetupSheet'): + if not getattr(obj, 'SetupSheet', None): obj.addProperty('App::PropertyLink', 'SetupSheet', 'Base', QtCore.QT_TRANSLATE_NOOP('PathJob', 'SetupSheet holding the settings for this job')) obj.SetupSheet = PathSetupSheet.Create() if obj.SetupSheet.ViewObject: @@ -223,53 +223,58 @@ class ObjectJob: PathLog.track(obj.Label, arg2) doc = obj.Document - # the first to tear down are the ops, they depend on other resources - PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()]) - while obj.Operations.Group: - op = obj.Operations.Group[0] - if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()): - PathUtil.clearExpressionEngine(op) - doc.removeObject(op.Name) - obj.Operations.Group = [] - doc.removeObject(obj.Operations.Name) - obj.Operations = None + if getattr(obj, 'Operations', None): + # the first to tear down are the ops, they depend on other resources + PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()]) + while obj.Operations.Group: + op = obj.Operations.Group[0] + if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()): + PathUtil.clearExpressionEngine(op) + doc.removeObject(op.Name) + obj.Operations.Group = [] + doc.removeObject(obj.Operations.Name) + obj.Operations = None # stock could depend on Model, so delete it first - if obj.Stock: + if getattr(obj, 'Stock', None): PathLog.debug('taking down stock') PathUtil.clearExpressionEngine(obj.Stock) doc.removeObject(obj.Stock.Name) obj.Stock = None # base doesn't depend on anything inside job - for base in obj.Model.Group: - PathLog.debug("taking down base %s" % base.Label) - self.removeBase(obj, base, False) - obj.Model.Group = [] - doc.removeObject(obj.Model.Name) - obj.Model = None + if getattr(obj, 'Model', None): + for base in obj.Model.Group: + PathLog.debug("taking down base %s" % base.Label) + self.removeBase(obj, base, False) + obj.Model.Group = [] + doc.removeObject(obj.Model.Name) + obj.Model = None # Tool controllers might refer to either legacy tool or toolbit - PathLog.debug('taking down tool controller') - for tc in obj.Tools.Group: - if hasattr(tc.Tool, "Proxy"): - PathUtil.clearExpressionEngine(tc.Tool) - doc.removeObject(tc.Tool.Name) - PathUtil.clearExpressionEngine(tc) - tc.Proxy.onDelete(tc) - doc.removeObject(tc.Name) - obj.Tools.Group = [] - doc.removeObject(obj.Tools.Name) - obj.Tools = None + if getattr(obj, 'Tools', None): + PathLog.debug('taking down tool controller') + for tc in obj.Tools.Group: + if hasattr(tc.Tool, "Proxy"): + PathUtil.clearExpressionEngine(tc.Tool) + doc.removeObject(tc.Tool.Name) + PathUtil.clearExpressionEngine(tc) + tc.Proxy.onDelete(tc) + doc.removeObject(tc.Name) + obj.Tools.Group = [] + doc.removeObject(obj.Tools.Name) + obj.Tools = None # SetupSheet - PathUtil.clearExpressionEngine(obj.SetupSheet) - doc.removeObject(obj.SetupSheet.Name) - obj.SetupSheet = None + if getattr(obj, 'SetupSheet', None): + PathUtil.clearExpressionEngine(obj.SetupSheet) + doc.removeObject(obj.SetupSheet.Name) + obj.SetupSheet = None + return True def fixupOperations(self, obj): - if obj.Operations.ViewObject: + if getattr(obj.Operations, 'ViewObject', None): try: obj.Operations.ViewObject.DisplayMode except Exception: # pylint: disable=broad-except @@ -409,7 +414,7 @@ class ObjectJob: return None def execute(self, obj): - if hasattr(obj, 'Operations'): + if getattr(obj, 'Operations', None): obj.Path = obj.Operations.Path self.getCycleTime() @@ -479,8 +484,11 @@ class ObjectJob: ops.append(op) for sub in op.Group: collectBaseOps(sub) - for op in self.obj.Operations.Group: - collectBaseOps(op) + + if getattr(self.obj, 'Operations', None) and getattr(self.obj.Operations, 'Group', None): + for op in self.obj.Operations.Group: + collectBaseOps(op) + return ops def setCenterOfRotation(self, center): From 78d3d0039ff26794b418769b61575a9ff5f9f8bb Mon Sep 17 00:00:00 2001 From: luz paz Date: Mon, 17 May 2021 10:42:22 -0400 Subject: [PATCH 13/20] Crowdin: fix Draft mirror code typo ref: https://crowdin.com/translate/freecad/548/en-en?filter=basic&value=2#6587132 --- src/Mod/Draft/draftfunctions/mirror.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/draftfunctions/mirror.py b/src/Mod/Draft/draftfunctions/mirror.py index 189e424424..dc3ea48cc2 100644 --- a/src/Mod/Draft/draftfunctions/mirror.py +++ b/src/Mod/Draft/draftfunctions/mirror.py @@ -110,7 +110,7 @@ def mirror(objlist, p1, p2): for obj in objlist: mir = App.ActiveDocument.addObject("Part::Mirroring", "mirror") - mir.Label = obj.Label + " (" + translate("draft","mirrored" + ")") + mir.Label = obj.Label + " (" + translate("draft","mirrored") + ") " mir.Source = obj mir.Base = p1 mir.Normal = pnorm From cd490c71432e599bc10d79d11ef8b0b8d8595252 Mon Sep 17 00:00:00 2001 From: luz paz Date: Fri, 21 May 2021 08:41:01 -0400 Subject: [PATCH 14/20] StartWB: Add missing tooltip for 'Show tips' preference dialog --- src/Mod/Start/Gui/DlgStartPreferences.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mod/Start/Gui/DlgStartPreferences.ui b/src/Mod/Start/Gui/DlgStartPreferences.ui index e8985acfe0..f0bb823747 100644 --- a/src/Mod/Start/Gui/DlgStartPreferences.ui +++ b/src/Mod/Start/Gui/DlgStartPreferences.ui @@ -159,6 +159,9 @@ By using ";;" to separate paths, you can add several folders here Qt::RightToLeft + + Displays help tips in the Start workbench Documents tab + From dbd7dbd3323615fa3120013a9de7b398327ec0a4 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 21 May 2021 15:35:29 +0200 Subject: [PATCH 15/20] Draft: Fixed use of double quotes in Draft Texts --- src/Mod/Draft/draftguitools/gui_texts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Draft/draftguitools/gui_texts.py b/src/Mod/Draft/draftguitools/gui_texts.py index 06ec46c812..ef40496ffb 100644 --- a/src/Mod/Draft/draftguitools/gui_texts.py +++ b/src/Mod/Draft/draftguitools/gui_texts.py @@ -86,6 +86,7 @@ class Text(gui_base_original.Creator): def createObject(self): """Create the actual object in the current document.""" text_list = self.text + text_list = [text.replace("\"","\\\"") for text in text_list] # If the last element is an empty string "" we remove it if not text_list[-1]: From c2c4d647048e18f8f0865c4cec0eab79a6217248 Mon Sep 17 00:00:00 2001 From: mapeze Date: Fri, 21 May 2021 16:51:52 +0200 Subject: [PATCH 16/20] Fix TechDraw View Spreadsheet with merged cells, 2 --- src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp b/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp index eb1007a836..af55c417d9 100644 --- a/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp +++ b/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp @@ -356,7 +356,7 @@ std::string DrawViewSpreadsheet::getSheetImage(void) << " fill=\"" << fcolor << "\">" << celltext << "" << endl; } } - rowoffset = rowoffset + cellheight; + rowoffset = rowoffset + sheet->getRowHeight(address.row()); } result << " " << endl; rowoffset = 0.0; From 630b0f2af4bb8b2559f02ff6cea6dbc455f04d90 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sat, 22 May 2021 16:29:50 +0200 Subject: [PATCH 17/20] Draft: Do not move children of App::Link objects This caused the original object to be moved when using the Draft move tool to move a link object, when the original object has MoveWithHost set (e.g. a Window). There was already a similar exception for clones, so it makes sense to extend this for links too. Note that there seem to be more problems with the "MoveWithHost" mechanism and fixing them might completely refactor this code, but until then, this is a simple and targeted fix that at least makes moving links to windows work as expected. See https://forum.freecadweb.org/viewtopic.php?f=23&t=57223 for discussion of this bug, its fix and the additional problems mentioned. --- src/Mod/Draft/draftutils/groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/draftutils/groups.py b/src/Mod/Draft/draftutils/groups.py index c1557fb552..25118adc94 100644 --- a/src/Mod/Draft/draftutils/groups.py +++ b/src/Mod/Draft/draftutils/groups.py @@ -297,7 +297,7 @@ def get_movable_children(objectslist, recursive=True): for obj in objectslist: # Skips some objects that should never move their children if utils.get_type(obj) not in ("Clone", "SectionPlane", - "Facebinder", "BuildingPart"): + "Facebinder", "BuildingPart", "App::Link"): children = obj.OutList if (hasattr(obj, "Proxy") and obj.Proxy and hasattr(obj.Proxy, "getSiblings") From 2bbcb4994a93973e41d604d817041f93755ba140 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sat, 22 May 2021 17:28:39 +0200 Subject: [PATCH 18/20] Arch: Simplify some ifs in ArchComponent This commit does not change behavior. --- src/Mod/Arch/ArchComponent.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index a94396d6ae..a89be869a9 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -737,20 +737,18 @@ class Component(ArchIFC.IfcProduct): base = base.fuse(add) elif hasattr(o,'Shape'): - if o.Shape: - if not o.Shape.isNull(): - if o.Shape.Solids: - s = o.Shape.copy() - if placement: - s.Placement = s.Placement.multiply(placement) - if base: - if base.Solids: - try: - base = base.fuse(s) - except Part.OCCError: - print("Arch: unable to fuse object ", obj.Name, " with ", o.Name) - else: - base = s + if o.Shape and not o.Shape.isNull() and o.Shape.Solids: + s = o.Shape.copy() + if placement: + s.Placement = s.Placement.multiply(placement) + if base: + if base.Solids: + try: + base = base.fuse(s) + except Part.OCCError: + print("Arch: unable to fuse object ", obj.Name, " with ", o.Name) + else: + base = s # treat subtractions subs = obj.Subtractions @@ -1420,11 +1418,7 @@ class ViewProviderComponent: if hasattr(self,"Object"): c = [] if hasattr(self.Object,"Base"): - if Draft.getType(self.Object) != "Wall": - c = [self.Object.Base] - elif Draft.getType(self.Object.Base) == "Space": - c = [] - else: + if not (Draft.getType(self.Object) == "Wall" and Draft.getType(self.Object.Base) == "Space"): c = [self.Object.Base] if hasattr(self.Object,"Additions"): c.extend(self.Object.Additions) From 1cabfd8e6b6d08340927241e3743768e8e4cb85e Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sat, 22 May 2021 17:30:23 +0200 Subject: [PATCH 19/20] Draft: Simplify code using getattr default value This uses the default value that can be passed to getattr to simplify code that uses it. By choosing an appropriate default value, a separate call to hasattr can be avoided and in some cases duplicate code can be avoided. This applies this trick where possible in wire.py and circle.py. This commit does not change behavior. --- src/Mod/Draft/draftobjects/circle.py | 5 +-- src/Mod/Draft/draftobjects/wire.py | 60 ++++++++++++---------------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/Mod/Draft/draftobjects/circle.py b/src/Mod/Draft/draftobjects/circle.py index a3a520aa5b..3a7af22c91 100644 --- a/src/Mod/Draft/draftobjects/circle.py +++ b/src/Mod/Draft/draftobjects/circle.py @@ -79,10 +79,7 @@ class Circle(DraftObject): if obj.FirstAngle.Value == obj.LastAngle.Value: shape = Part.Wire(shape) - if hasattr(obj,"MakeFace"): - if obj.MakeFace: - shape = Part.Face(shape) - else: + if getattr(obj,"MakeFace",True): shape = Part.Face(shape) obj.Shape = shape diff --git a/src/Mod/Draft/draftobjects/wire.py b/src/Mod/Draft/draftobjects/wire.py index d4a0731283..71becfb5a9 100644 --- a/src/Mod/Draft/draftobjects/wire.py +++ b/src/Mod/Draft/draftobjects/wire.py @@ -102,10 +102,7 @@ class Wire(DraftObject): if obj.Base.isDerivedFrom("Sketcher::SketchObject"): shape = obj.Base.Shape.copy() if obj.Base.Shape.isClosed(): - if hasattr(obj,"MakeFace"): - if obj.MakeFace: - shape = Part.Face(shape) - else: + if getattr(obj,"MakeFace",True): shape = Part.Face(shape) obj.Shape = shape elif obj.Base and obj.Tool: @@ -126,21 +123,20 @@ class Wire(DraftObject): obj.Points.pop() if obj.Closed and (len(obj.Points) > 2): pts = obj.Points - if hasattr(obj,"Subdivisions"): - if obj.Subdivisions > 0: - npts = [] - for i in range(len(pts)): - p1 = pts[i] - npts.append(pts[i]) - if i == len(pts)-1: - p2 = pts[0] - else: - p2 = pts[i+1] - v = p2.sub(p1) - v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) - for j in range(obj.Subdivisions): - npts.append(p1.add(App.Vector(v).multiply(j+1))) - pts = npts + if getattr(obj,"Subdivisions",0) > 0: + npts = [] + for i in range(len(pts)): + p1 = pts[i] + npts.append(pts[i]) + if i == len(pts)-1: + p2 = pts[0] + else: + p2 = pts[i+1] + v = p2.sub(p1) + v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) + for j in range(obj.Subdivisions): + npts.append(p1.add(App.Vector(v).multiply(j+1))) + pts = npts shape = Part.makePolygon(pts+[pts[0]]) if "ChamferSize" in obj.PropertiesList: if obj.ChamferSize.Value != 0: @@ -153,10 +149,7 @@ class Wire(DraftObject): if w: shape = w try: - if hasattr(obj,"MakeFace"): - if obj.MakeFace: - shape = Part.Face(shape) - else: + if getattr(obj,"MakeFace",True): shape = Part.Face(shape) except Part.OCCError: pass @@ -166,18 +159,15 @@ class Wire(DraftObject): lp = obj.Points[0] for p in pts: if not DraftVecUtils.equals(lp,p): - if hasattr(obj,"Subdivisions"): - if obj.Subdivisions > 0: - npts = [] - v = p.sub(lp) - v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) - edges.append(Part.LineSegment(lp,lp.add(v)).toShape()) - lv = lp.add(v) - for j in range(obj.Subdivisions): - edges.append(Part.LineSegment(lv,lv.add(v)).toShape()) - lv = lv.add(v) - else: - edges.append(Part.LineSegment(lp,p).toShape()) + if getattr(obj,"Subdivisions",0) > 0: + npts = [] + v = p.sub(lp) + v = DraftVecUtils.scaleTo(v,v.Length/(obj.Subdivisions+1)) + edges.append(Part.LineSegment(lp,lp.add(v)).toShape()) + lv = lp.add(v) + for j in range(obj.Subdivisions): + edges.append(Part.LineSegment(lv,lv.add(v)).toShape()) + lv = lv.add(v) else: edges.append(Part.LineSegment(lp,p).toShape()) lp = p From d928f3776115028acdfca26ea8ef41f7d7f5de3c Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sat, 22 May 2021 18:24:56 +0200 Subject: [PATCH 20/20] Draft: Fix "Show groups in layer list drop-down button" preference The code that was intended to handle this preference looked inside Mod/BIM instad of Mod/Draft for the preference. This seems to have been broken since the preference was first introduced in commit 9976a5ece0 (Draft: Turned autogroup button into layers selector (added pref option to restore old groups-based system)). This also removes a stray "-" that was probably a leftover from a merge conflict, introduced in commit 0547d23660 (Draft: move Draft_AutoGroup to gui_groups module). Since -True is still true and -False is still false, this did not actually break the code, though. See https://forum.freecadweb.org/viewtopic.php?t=42018 for related discussion. --- src/Mod/Draft/draftguitools/gui_groups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_groups.py b/src/Mod/Draft/draftguitools/gui_groups.py index 705b920f96..a5d2b22988 100644 --- a/src/Mod/Draft/draftguitools/gui_groups.py +++ b/src/Mod/Draft/draftguitools/gui_groups.py @@ -258,7 +258,7 @@ class SetAutoGroup(gui_base.GuiCommandSimplest): s = Gui.Selection.getSelection() if len(s) == 1: if (utils.get_type(s[0]) == "Layer") or \ -- (App.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM").GetBool("AutogroupAddGroups", False) + (App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("AutogroupAddGroups", False) and (s[0].isDerivedFrom("App::DocumentObjectGroup") or utils.get_type(s[0]) in ["Site", "Building", "Floor", "BuildingPart"])): @@ -269,7 +269,7 @@ class SetAutoGroup(gui_base.GuiCommandSimplest): # including the options "None" and "Add new layer". self.groups = ["None"] gn = [o.Name for o in self.doc.Objects if utils.get_type(o) == "Layer"] - if App.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM").GetBool("AutogroupAddGroups", False): + if App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("AutogroupAddGroups", False): gn.extend(groups.get_group_names()) if gn: self.groups.extend(gn)