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)
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
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)
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]:
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
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")
diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py
index 655528b72d..eaf736fb6f 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()
@@ -457,6 +462,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]))
@@ -479,8 +489,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):
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()
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:
diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py
index 4e1b651148..5f7050e634 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)
@@ -222,7 +241,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 +360,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 +481,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)
diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py
index d5c40c019d..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')
@@ -159,7 +160,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',
@@ -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
@@ -433,35 +435,41 @@ 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)
+ 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
@@ -608,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
@@ -712,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
@@ -862,16 +884,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
@@ -925,7 +947,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
@@ -1182,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]
@@ -1309,13 +1334,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)...
@@ -1367,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
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)
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
+
diff --git a/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp b/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp
index ce315d2f1d..af55c417d9 100644
--- a/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp
+++ b/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp
@@ -356,8 +356,7 @@ std::string DrawViewSpreadsheet::getSheetImage(void)
<< " fill=\"" << fcolor << "\">" << celltext << "" << endl;
}
}
- cellheight = sheet->getRowHeight(address.row());
- rowoffset = rowoffset + cellheight;
+ rowoffset = rowoffset + sheet->getRowHeight(address.row());
}
result << " " << endl;
rowoffset = 0.0;