diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index f01eb9b64..32ce98cae 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -45,6 +45,7 @@ SET(PathScripts_SRCS PathScripts/PathPost.py PathScripts/PathPostProcessor.py PathScripts/PathPreferences.py + PathScripts/PathPreferencesPathDressup.py PathScripts/PathPreferencesPathJob.py PathScripts/PathProfile.py PathScripts/PathProfileEdges.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 5e701bc5c..b6800333c 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -58,6 +58,7 @@ panels/JobEdit.ui panels/MillFaceEdit.ui panels/PocketEdit.ui + panels/PointEdit.ui panels/ProfileEdgesEdit.ui panels/ProfileEdit.ui panels/RemoteEdit.ui @@ -65,6 +66,7 @@ panels/ToolControl.ui panels/ToolEdit.ui panels/ToolLibraryEditor.ui + preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm translations/Path_cs.qm diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index d8289aca7..0fca680a7 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -6,8 +6,8 @@ 0 0 - 363 - 530 + 399 + 564 @@ -16,6 +16,12 @@ + + + 0 + 0 + + QFrame::NoFrame @@ -27,8 +33,8 @@ 0 0 - 345 - 476 + 381 + 510 @@ -48,13 +54,6 @@ - - - - <html><head/><body><p>Specify the resulting width of tags at the base.</p><p>The initial default width is based on the longest edge found in the base path.</p></body></html> - - - @@ -62,13 +61,6 @@ - - - - <html><head/><body><p>Height of holding tags.</p></body></html> - - - @@ -76,10 +68,50 @@ + + + + <html><head/><body><p>Width of the resulting holding tag.</p></body></html> + + + + + + + <html><head/><body><p>Height of holding tag.</p><p>Note that resulting tag might be smaller if the tag's width and angle result in a triangular shape.</p></body></html> + + + - <html><head/><body><p>Angle of ascend and descend of the tool for the holding tag cutout.</p><p><br/></p><p>If the angle is too flat for the given width to reach the specified height the resulting tag will have a triangular shape and not as high as specified.</p></body></html> + <html><head/><body><p>Plunge angle for ascent and descent of holding tag.</p></body></html> + + + 5.000000000000000 + + + 90.000000000000000 + + + 15.000000000000000 + + + 45.000000000000000 + + + + + + + Radius + + + + + + + <html><head/><body><p>Radius of the fillet at the top.</p><p>If the radius is too big for the tag shape it gets reduced to the maximum possible radius - resulting in a spherical shape.</p></body></html> @@ -89,14 +121,14 @@ - <html><head/><body><p>List of current tags.</p><p>Edit coordinates to move tag or invoker snapper tool.</p></body></html> + <html><head/><body><p>List of current tags. Edit coordinates by double click or Edit button.</p><p>Tags are automatically disabled if they overlap with the previous tag, or don't lie on the base wire.</p></body></html> - + false @@ -106,13 +138,23 @@ - + Add... + + + + false + + + Edit... + + + @@ -123,7 +165,11 @@ - + + + 2 + + @@ -154,6 +200,13 @@ + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
diff --git a/src/Mod/Path/Gui/Resources/panels/PointEdit.ui b/src/Mod/Path/Gui/Resources/panels/PointEdit.ui new file mode 100644 index 000000000..454bcabaf --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PointEdit.ui @@ -0,0 +1,78 @@ + + + Form + + + + 0 + 0 + 362 + 182 + + + + Point Edit + + + + + + + + + Global X + + + + + + + + + + Global Y + + + + + + + + + + false + + + Global Z + + + + + + + false + + + + + + + + + + QDialogButtonBox::Save + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui b/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui new file mode 100644 index 000000000..c5085d5fa --- /dev/null +++ b/src/Mod/Path/Gui/Resources/preferences/PathDressupHoldingTags.ui @@ -0,0 +1,147 @@ + + + Form + + + + 0 + 0 + 477 + 478 + + + + Form + + + + + + Tag Parameters + + + + + + <html><head/><body><p>Set the default width of holding tags.</p><p>If the width is set to 0 the dressup will try to guess a reasonable value based on the path itself.</p></body></html> + + + + + + + Default Width + + + + + + + <html><head/><body><p>Plunge angle for the holding tags ascent and descent.</p></body></html> + + + 5.000000000000000 + + + 90.000000000000000 + + + 15.000000000000000 + + + 45.000000000000000 + + + + + + + <html><head/><body><p>Default height of holding tags.</p><p>If the specified height is 0 the dressup will use half the height of the part. Should the height be bigger than the height of the part the dressup will reduce the height to the height of the part.</p></body></html> + + + + + + + Default Angle + + + + + + + Default Height + + + + + + + <html><head/><body><p>Radius of the fillet on the tag's top edge.</p><p>If the radius is bigger than than the tag shape itself supports the resulting shape will be that of a dome.</p></body></html> + + + + + + + Default Radius + + + + + + + + + + Tag Generation + + + + + + Initial # Tags + + + + + + + <html><head/><body><p>Specify the number of tags generated when a new dressup is created.</p></body></html> + + + 2 + + + 4 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 22b13882b..dd0c1d6f3 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -31,8 +31,9 @@ class PathWorkbench (Workbench): def Initialize(self): # Add preferences pages - before loading PathGui to properly order pages of Path group - from PathScripts import PathPreferencesPathJob - FreeCADGui.addPreferencePage(PathPreferencesPathJob.Page, "Path") + from PathScripts import PathPreferencesPathJob, PathPreferencesPathDressup + FreeCADGui.addPreferencePage(PathPreferencesPathJob.JobPreferencesPage, "Path") + FreeCADGui.addPreferencePage(PathPreferencesPathDressup.DressupPreferencesPage, "Path") # load the builtin modules import Path diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index d05292806..34908fe57 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -23,8 +23,11 @@ # *************************************************************************** import FreeCAD import FreeCADGui +import Draft import DraftGeomUtils +import DraftGui import Path +import PathScripts.PathPreferencesPathDressup as PathPreferencesPathDressup import Part import copy import math @@ -35,21 +38,15 @@ import time from DraftGui import todo from PathScripts import PathUtils from PathScripts.PathGeom import * +from PathScripts.PathPreferences import * from PySide import QtCore, QtGui +from pivy import coin """Holding Tags Dressup object and FreeCAD command""" # Qt tanslation handling -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig, _encoding) - -except AttributeError: - - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig) +def translate(text, context = "PathDressup_HoldingTags", disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) debugDressup = False @@ -99,27 +96,80 @@ def debugCone(vector, r1, r2, height, label, color = None): if color: obj.ViewObject.ShapeColor = color -class Tag: + +class HoldingTagsPreferences: + DefaultHoldingTagWidth = 'DefaultHoldingTagWidth' + DefaultHoldingTagHeight = 'DefaultHoldingTagHeight' + DefaultHoldingTagAngle = 'DefaultHoldingTagAngle' + DefaultHoldingTagRadius = 'DefaultHoldingTagRadius' + DefaultHoldingTagCount = 'DefaultHoldingTagCount' @classmethod - def FromString(cls, string): - try: - t = eval(string) - return Tag(t[0], t[1], t[2], t[3], t[4], t[5]) - except: - return None + def defaultWidth(cls, ifNotSet): + value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagWidth, ifNotSet) + if value == 0.0: + return ifNotSet + return value - def toString(self): - return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + @classmethod + def defaultHeight(cls, ifNotSet): + value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagHeight, ifNotSet) + if value == 0.0: + return ifNotSet + return value - def __init__(self, x, y, width, height, angle, enabled=True): - debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle/math.pi, enabled)) + @classmethod + def defaultAngle(cls, ifNotSet = 45.0): + value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagAngle, ifNotSet) + if value < 10.0: + return ifNotSet + return value + + @classmethod + def defaultCount(cls, ifNotSet = 4): + value = PathPreferences.preferences().GetUnsigned(cls.DefaultHoldingTagCount, ifNotSet) + if value < 2: + return float(ifNotSet) + return float(value) + + @classmethod + def defaultRadius(cls, ifNotSet = 0.0): + return PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagRadius, ifNotSet) + + + def __init__(self): + self.form = FreeCADGui.PySideUic.loadUi(":/preferences/PathDressupHoldingTags.ui") + self.label = translate('Holding Tags') + + def loadSettings(self): + self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.defaultWidth(0), FreeCAD.Units.Length).UserString) + self.form.ifHeight.setText(FreeCAD.Units.Quantity(self.defaultHeight(0), FreeCAD.Units.Length).UserString) + self.form.dsbAngle.setValue(self.defaultAngle()) + self.form.ifRadius.setText(FreeCAD.Units.Quantity(self.defaultRadius(), FreeCAD.Units.Length).UserString) + self.form.sbCount.setValue(self.defaultCount()) + + def saveSettings(self): + pref = PathPreferences.preferences() + pref.SetFloat(self.DefaultHoldingTagWidth, FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value) + pref.SetFloat(self.DefaultHoldingTagHeight, FreeCAD.Units.Quantity(self.form.ifHeight.text()).Value) + pref.SetFloat(self.DefaultHoldingTagAngle, self.form.dsbAngle.value()) + pref.SetFloat(self.DefaultHoldingTagRadius, FreeCAD.Units.Quantity(self.form.ifRadius.text())) + pref.SetUnsigned(self.DefaultHoldingTagCount, self.form.sbCount.value()) + + @classmethod + def preferencesPage(cls): + return HoldingTagsPreferences() + +class Tag: + def __init__(self, x, y, width, height, angle, radius, enabled=True): + debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle, radius, enabled)) self.x = x self.y = y self.width = math.fabs(width) self.height = math.fabs(height) self.actualHeight = self.height self.angle = math.fabs(angle) + self.radius = radius self.enabled = enabled def fullWidth(self): @@ -140,18 +190,27 @@ class Tag: r1 = self.fullWidth() / 2 self.r1 = r1 self.r2 = r1 - height = self.height + height = self.height * 1.01 + radius = 0 if self.angle == 90 and height > 0: + # cylinder self.solid = Part.makeCylinder(r1, height) + radius = min(min(self.radius, r1), self.height) debugPrint("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: - tangens = math.tan(math.radians(self.angle)) + # cone + rad = math.radians(self.angle) + tangens = math.tan(rad) dr = height / tangens if dr < r1: + # with top r2 = r1 - dr + s = height / math.sin(rad) + radius = min(r2, s) * math.tan((math.pi - rad)/2) * 0.95 else: + # triangular r2 = 0 - height = r1 * tangens + height = r1 * tangens * 1.01 self.actualHeight = height self.r2 = r2 debugPrint("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) @@ -165,10 +224,14 @@ class Tag: debugPrint("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) debugPrint("solid.translate(%s)" % self.originAt(z)) - self.solid.translate(self.originAt(z)) + self.solid.translate(self.originAt(z - 0.01 * height)) + self.realRadius = radius + if radius != 0: + debugPrint("makeFillet(%.4f)" % radius) + self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]]) def filterIntersections(self, pts, face): - if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: + if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder or type(face.Surface) == Part.Toroid: #print("it's a cone/cylinder, checking z") return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) if type(face.Surface) == Part.Plane: @@ -176,7 +239,7 @@ class Tag: c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius or PathGeom.isRoughly((pt - c.Center).Length, c.Radius), pts) - #print("==== we got a %s" % face.Surface) + print("==== we got a %s" % face.Surface) def isPointOnEdge(self, pt, edge): param = edge.Curve.parameter(pt) @@ -200,26 +263,9 @@ class Tag: el = edge.valueAt(edge.LastParameter) #print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) - pts = [] - for index, face in enumerate(solid.Faces): - i = edge.Curve.intersect(face.Surface)[0] - #print i - ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) - pts.extend(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)) - if len(ps) != len(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)): - filtered = filter(lambda pt: self.isPointOnEdge(pt, edge), ps) - #print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) - for p in ps: - included = '+' if p in filtered else '-' - #print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) - if pts: - closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - #for p in pts: - # print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) - #print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) - return closest - - #print("-------- -> None") + vertexes = edge.common(solid).Vertexes + if vertexes: + return sorted(vertexes, key=lambda v: (v.Point - refPt).Length)[0].Point return None def intersects(self, edge, param): @@ -228,6 +274,18 @@ class Tag: return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param)) return None + def bbEdges(self): + edges = [] + for i in range(12): + p1, p2 = self.solid.BoundBox.getEdge(i) + edges.append(Part.Edge(Part.LineSegment(p1, p2))) + return edges + + def bbShow(self): + for e in self.bbEdges(): + Part.show(e) + + class MapWireToTag: def __init__(self, edge, tag, i): debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z)) @@ -245,9 +303,14 @@ class MapWireToTag: debugEdge(e, '++++++++ .') self.commands = PathGeom.cmdsForEdge(e) debugEdge(tail, '.........-') + self.initialEdge = edge self.tail = tail self.edges = [] self.entry = i + if tail: + debugPrint("MapWireToTag(%s - %s)" % (i, tail.valueAt(tail.FirstParameter))) + else: + debugPrint("MapWireToTag(%s - )" % i) self.complete = False self.haveProblem = False @@ -273,63 +336,114 @@ class MapWireToTag: return 2 return 0 - def cleanupEdges(self, edges, baseEdge): + def cleanupEdges(self, edges): # want to remove all edges from the wire itself, and all internal struts #print("+cleanupEdges") - #print(" base:") - debugEdge(baseEdge, ' ') #print(" edges:") for e in edges: debugEdge(e, ' ') #print(":") + self.edgesCleanup = [copy.copy(edges)] - haveEntry = False + # remove any edge that has a point inside the tag solid + # and collect all edges that are connected to the entry and/or exit + self.entryEdges = [] + self.exitEdges = [] + self.edgePoints = [] for e in copy.copy(edges): - if PathGeom.edgesMatch(e, baseEdge): - debugEdge(e, '......... X0') + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + self.edgePoints.append(p1) + self.edgePoints.append(p2) + if self.tag.solid.isInside(p1, PathGeom.Tolerance, False) or self.tag.solid.isInside(p2, PathGeom.Tolerance, False): edges.remove(e) - elif self.isStrut(e): - typ = self.isEntryOrExitStrut(e) - debugEdge(e, '......... |%d' % typ) - if 0 == typ: # neither entry nor exit - debugEdge(e, '......... X1') - edges.remove(e) - elif 1 == typ: - haveEntry = True + debugEdge(e, '......... X0', False) + else: + if PathGeom.pointsCoincide(p1, self.entry) or PathGeom.pointsCoincide(p2, self.entry): + self.entryEdges.append(e) + if PathGeom.pointsCoincide(p1, self.exit) or PathGeom.pointsCoincide(p2, self.exit): + self.exitEdges.append(e) + self.edgesCleanup.append(copy.copy(edges)) + # if there are no edges connected to entry/exit, it means the plunge in/out is vertical + # we need to add in the missing segment and collect the new entry/exit edges. + if not self.entryEdges: + print("fill entryEdges ...") + self.realEntry = sorted(self.edgePoints, key=lambda p: (p - self.entry).Length)[0] + self.entryEdges = filter(lambda e: PathGeom.edgeConnectsTo(e, self.realEntry), edges) + edges.append(Part.Edge(Part.LineSegment(self.entry, self.realEntry))) + else: + self.realEntry = None + if not self.exitEdges: + print("fill exitEdges ...") + self.realExit = sorted(self.edgePoints, key=lambda p: (p - self.exit).Length)[0] + self.exitEdges = filter(lambda e: PathGeom.edgeConnectsTo(e, self.realExit), edges) + edges.append(Part.Edge(Part.LineSegment(self.realExit, self.exit))) + else: + self.realExit = None + self.edgesCleanup.append(copy.copy(edges)) + + # if there are 2 edges attached to entry/exit, throw away the one that is "lower" + if len(self.entryEdges) > 1: + debugEdge(self.entryEdges[0], ' entry[0]', False) + debugEdge(self.entryEdges[1], ' entry[1]', False) + if self.entryEdges[0].BoundBox.ZMax < self.entryEdges[1].BoundBox.ZMax: + edges.remove(self.entryEdges[0]) + debugEdge(e, '......... X1', False) + else: + edges.remove(self.entryEdges[1]) + debugEdge(e, '......... X2', False) + if len(self.exitEdges) > 1: + debugEdge(self.exitEdges[0], ' exit[0]', False) + debugEdge(self.exitEdges[1], ' exit[1]', False) + if self.exitEdges[0].BoundBox.ZMax < self.exitEdges[1].BoundBox.ZMax: + if self.exitEdges[0] in edges: + edges.remove(self.exitEdges[0]) + debugEdge(e, '......... X3', False) + else: + if self.exitEdges[1] in edges: + edges.remove(self.exitEdges[1]) + debugEdge(e, '......... X4', False) + + self.edgesCleanup.append(copy.copy(edges)) + return edges + + def orderAndFlipEdges(self, edges): #print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) - # the remaininng edges for the path from xy(baseEdge) along the tags surface + self.edgesOrder = [] outputEdges = [] - p0 = baseEdge.valueAt(baseEdge.FirstParameter) - ignoreZ = False - if not haveEntry: - ignoreZ = True - p0 = PathGeom.xy(p0) + p0 = self.entry lastP = p0 while edges: - #print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) + #print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z)) for e in edges: p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(PathGeom.xy(p1) if ignoreZ else p1, p0): + if PathGeom.pointsCoincide(p1, p0): outputEdges.append((e, False)) edges.remove(e) lastP = None - ignoreZ = False p0 = p2 debugEdge(e, ">>>>> no flip") break - elif PathGeom.pointsCoincide(PathGeom.xy(p2) if ignoreZ else p2, p0): + elif PathGeom.pointsCoincide(p2, p0): outputEdges.append((e, True)) edges.remove(e) lastP = None - ignoreZ = False p0 = p1 debugEdge(e, ">>>>> flip") break else: debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z)) if lastP == p0: + self.edgesOrder.append(outputEdges) + self.edgesOrder.append(edges) + print('ordered edges:') + for e, flip in outputEdges: + debugEdge(e, ' %c ' % ('<' if flip else '>'), False) + print('remaining edges:') + for e in edges: + debugEdge(e, ' ', False) raise ValueError("No connection to %s" % (p0)) elif lastP: debugPrint("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) @@ -344,40 +458,50 @@ class MapWireToTag: p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) return PathGeom.pointsCoincide(p1, p2) - def cmdsForEdge(self, edge): - cmds = [] - - # OCC doesn't like it very much if the shapes align with each other. So if we have a slightly - # extended edge for the last edge in list we'll use that instead for stable results. - if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.lastEdge.valueAt(self.lastEdge.FirstParameter)): - shell = self.lastEdge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + def shell(self): + if len(self.edges) > 1: + wire = Part.Wire(self.initialEdge) else: - shell = edge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) - shape = shell.common(self.tag.solid) + edge = self.edges[0] + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter)): + wire = Part.Wire(self.finalEdge) + elif hasattr(self, 'initialEdge') and PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.initialEdge.valueAt(self.initialEdge.FirstParameter)): + wire = Part.Wire(self.initialEdge) + else: + wire = Part.Wire(edge) - if not shape.Edges: - self.haveProblem = True + for edge in self.edges[1:]: + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter)): + wire.add(self.finalEdge) + else: + wire.add(edge) - for e,flip in self.cleanupEdges(shape.Edges, edge): - debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) - cmds.extend(PathGeom.cmdsForEdge(e, flip, False)) - return cmds + shell = wire.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + return shell.removeShape(filter(lambda f: PathGeom.isRoughly(f.Area, 0), shell.Faces)) def commandsForEdges(self): - commands = [] - for e in self.edges: - if self.isStrut(e): - continue - commands.extend(self.cmdsForEdge(e)) - return commands + if self.edges: + shape = self.shell().common(self.tag.solid) + commands = [] + for e,flip in self.orderAndFlipEdges(self.cleanupEdges(shape.Edges)): + debugEdge(e, '++++++++ %s' % ('<' if flip else '>'), False) + commands.extend(PathGeom.cmdsForEdge(e, flip, False)) + return commands + return [] def add(self, edge): self.tail = None - self.lastEdge = edge # see cmdsForEdge - if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): + self.finalEdge = edge + if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), PathGeom.Tolerance, True): self.addEdge(edge) else: i = self.tag.intersects(edge, edge.LastParameter) + if not i: + self.offendingEdge = edge + debugEdge(edge, 'offending Edge:', False) + o = self.tag.originAt(self.tag.z) + print('originAt: (%.2f, %.2f, %.2f)' % (o.x, o.y, o.z)) + i = edge.valueAt(edge.FirstParameter) if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): self.tail = edge else: @@ -395,7 +519,7 @@ class _RapidEdges: def __init__(self, rapid): self.rapid = rapid - def isRapid(self, edge, removeIfFound=True): + def isRapid(self, edge): if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: v0 = edge.Vertexes[0] v1 = edge.Vertexes[1] @@ -403,15 +527,13 @@ class _RapidEdges: r0 = r.Vertexes[0] r1 = r.Vertexes[1] if PathGeom.isRoughly(r0.X, v0.X) and PathGeom.isRoughly(r0.Y, v0.Y) and PathGeom.isRoughly(r0.Z, v0.Z) and PathGeom.isRoughly(r1.X, v1.X) and PathGeom.isRoughly(r1.Y, v1.Y) and PathGeom.isRoughly(r1.Z, v1.Z): - if removeIfFound: - self.rapid.remove(r) return True return False - def p(self): - print('rapid:') + def debugPrint(self): + debugPrint('rapid:') for r in self.rapid: - debugEdge(r, ' ', True) + debugEdge(r, ' ') class PathData: def __init__(self, obj): @@ -454,7 +576,7 @@ class PathData: edges = sorted(self.base.Edges, key=lambda e: e.Length) return (edges[0], edges[-1]) - def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): + def generateTags(self, obj, count, width=None, height=None, angle=None, radius=None, spacing=None): debugPrint("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -462,18 +584,12 @@ class PathData: if spacing: tagDistance = spacing else: - if count: - tagDistance = self.base.Length / count - else: - tagDistance = self.base.Length / 4 - if width: - W = width - else: - W = self.defaultTagWidth() - if height: - H = height - else: - H = self.defaultTagHeight() + tagDistance = self.base.Length / (count if count else 4) + + W = width if width else self.defaultTagWidth() + H = height if height else self.defaultTagHeight() + A = angle if angle else self.defaultTagAngle() + R = radius if radius else self.defaultTagRadius() # start assigning tags on the longest segment @@ -521,7 +637,7 @@ class PathData: distance = (edge.LastParameter - edge.FirstParameter) / count for j in range(0, count): tag = edge.Curve.value((j+0.5) * distance) - tags.append(Tag(tag.x, tag.y, W, H, angle, True)) + tags.append(Tag(tag.x, tag.y, W, H, A, R, True)) return tags @@ -542,14 +658,23 @@ class PathData: def defaultTagHeight(self): if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): - return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value / 2 - return (self.maxZ - self.minZ) / 2 + pathHeight = (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value + else: + pathHeight = self.maxZ - self.minZ + height = HoldingTagsPreferences.defaultHeight(pathHeight / 2) + if height > pathHeight: + return pathHeight + return height def defaultTagWidth(self): - return self.shortestAndLongestPathEdge()[1].Length / 10 + width = self.shortestAndLongestPathEdge()[1].Length / 10 + return HoldingTagsPreferences.defaultWidth(width) def defaultTagAngle(self): - return 45 + return HoldingTagsPreferences.defaultAngle() + + def defaultTagRadius(self): + return HoldingTagsPreferences.defaultRadius() def sortedTags(self, tags): ordered = [] @@ -558,19 +683,29 @@ class PathData: for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.valueAt(edge.FirstParameter)).Length): tags.remove(t) ordered.append(t) - if tags: - raise ValueError("There's something really wrong here") + # disable all tags that are not on the base wire. + for tag in tags: + FreeCAD.Console.PrintMessage("Tag #%d not on base wire - disabling\n" % len(ordered)) + tag.enabled = False + ordered.append(tag) return ordered + def pointIsOnPath(self, p): + for e in self.edges: + if DraftGeomUtils.isPtOnEdge(p, e): + return True + return False + class ObjectDressup: def __init__(self, obj): self.obj = obj obj.addProperty("App::PropertyLink", "Base","Base", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "The base path to modify")) - obj.addProperty("App::PropertyFloat", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) - obj.addProperty("App::PropertyFloat", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) - obj.addProperty("App::PropertyFloat", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) + obj.addProperty("App::PropertyLength", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) + obj.addProperty("App::PropertyLength", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) + obj.addProperty("App::PropertyAngle", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) + obj.addProperty("App::PropertyLength", "Radius", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Radius of the fillet on the top the tag.")) obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags")) obj.addProperty("App::PropertyIntegerList", "Disabled", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags")) obj.Proxy = self @@ -583,7 +718,7 @@ class ObjectDressup: def generateTags(self, obj, count): if hasattr(self, "pathData"): - self.tags = self.pathData.generateTags(obj, count, obj.Width, obj.Height, obj.Angle, None) + self.tags = self.pathData.generateTags(obj, count, obj.Width.Value, obj.Height.Value, obj.Angle, obj.Radius.Value, None) obj.Positions = [tag.originAt(0) for tag in self.tags] obj.Disabled = [] return False @@ -604,7 +739,7 @@ class ObjectDressup: return True def createPath(self, edges, tags, rapid): - print("createPath") + #print("createPath") commands = [] lastEdge = 0 lastTag = 0 @@ -647,7 +782,7 @@ class ObjectDressup: # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') - if rapid.isRapid(edge, True): + if rapid.isRapid(edge): v = edge.Vertexes[1] commands.append(Path.Command('G0', {'X': v.X, 'Y': v.Y, 'Z': v.Z})) else: @@ -662,6 +797,40 @@ class ObjectDressup: def problems(self): return filter(lambda m: m.haveProblem, self.mappers) + def createTagsPositionDisabled(self, obj, positionsIn, disabledIn): + rawTags = [] + for i, pos in enumerate(positionsIn): + tag = Tag(pos.x, pos.y, obj.Width.Value, obj.Height.Value, obj.Angle, obj.Radius, not i in disabledIn) + tag.createSolidsAt(self.pathData.minZ, self.toolRadius) + rawTags.append(tag) + # disable all tags that intersect with their previous tag + prev = None + tags = [] + positions = [] + disabled = [] + for i, tag in enumerate(self.pathData.sortedTags(rawTags)): + if tag.enabled: + if prev: + if prev.solid.common(tag.solid).Faces: + FreeCAD.Console.PrintMessage("Tag #%d intersects with previous tag - disabling\n" % i) + debugPrint("this tag = %d [%s]" % (i, tag.solid.BoundBox)) + tag.enabled = False + elif self.pathData.edges: + e = self.pathData.edges[0] + p0 = e.valueAt(e.FirstParameter) + p1 = e.valueAt(e.LastParameter) + if tag.solid.isInside(p0, PathGeom.Tolerance, True) or tag.solid.isInside(p1, PathGeom.Tolerance, True): + FreeCAD.Console.PrintMessage("Tag #%d intersects with starting point - disabling\n" % i) + tag.enabled = False + if tag.enabled: + prev = tag + debugPrint("previousTag = %d [%s]" % (i, prev)) + else: + disabled.append(i) + tags.append(tag) + positions.append(tag.originAt(0)) + return (tags, positions, disabled) + def execute(self, obj): #pr = cProfile.Profile() #pr.enable() @@ -686,38 +855,42 @@ class ObjectDressup: self.tags = [] if hasattr(obj, "Positions"): - for i, pos in enumerate(obj.Positions): - tag = Tag(pos.x, pos.y, obj.Width, obj.Height, obj.Angle, not i in obj.Disabled) - tag.createSolidsAt(pathData.minZ, self.toolRadius) - self.tags.append(tag) + self.tags, positions, disabled = self.createTagsPositionDisabled(obj, obj.Positions, obj.Disabled) + if obj.Disabled != disabled: + debugPrint("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) + obj.Positions = positions + obj.Disabled = disabled if not self.tags: print("execute - no tags") obj.Path = obj.Base.Path return + self.processTags(obj) + + def processTags(self, obj): tagID = 0 if debugDressup: for tag in self.tags: tagID += 1 if tag.enabled: - print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) - debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - if tag.angle != 90: - debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) - else: - debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) + debugPrint("x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ)) + #debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + #if tag.angle != 90: + # debugCone(tag.originAt(self.pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + #else: + # debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) - obj.Path = self.createPath(pathData.edges, self.tags, pathData.rapid) - print("execute - done") + obj.Path = self.createPath(self.pathData.edges, self.tags, self.pathData.rapid) + #print("execute - done") - def setup(self, obj, generate=None): - print("setup") + def setup(self, obj, generate=False): + debugPrint("setup") self.obj = obj try: pathData = PathData(obj) except ValueError: - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) + FreeCAD.Console.PrintError(translate("Cannot insert holding tags for this path - please select a Profile path\n")) return None self.toolRadius = 5 @@ -735,82 +908,104 @@ class ObjectDressup: obj.Height = self.pathData.defaultTagHeight() obj.Width = self.pathData.defaultTagWidth() obj.Angle = self.pathData.defaultTagAngle() - self.generateTags(obj, generate) + obj.Radius = self.pathData.defaultTagRadius() + count = HoldingTagsPreferences.defaultCount() + self.generateTags(obj, count) return self.pathData def setXyEnabled(self, triples): + debugPrint("setXyEnabled") + if not hasattr(self, 'pathData'): + self.setup(self.obj) positions = [] disabled = [] for i, (x, y, enabled) in enumerate(triples): + #print("%d: (%.2f, %.2f) %d" % (i, x, y, enabled)) positions.append(FreeCAD.Vector(x, y, 0)) if not enabled: disabled.append(i) - self.obj.Positions = positions - self.obj.Disabled = disabled - self.execute(self.obj) + self.tags, self.obj.Positions, self.obj.Disabled = self.createTagsPositionDisabled(self.obj, positions, disabled) + self.processTags(self.obj) + + def pointIsOnPath(self, obj, point): + if not hasattr(self, 'pathData'): + self.setup(obj) + return self.pathData.pointIsOnPath(point) + + @classmethod + def preferencesPage(cls): + return HoldingTagsPreferences() + +PathPreferencesPathDressup.RegisterDressup(ObjectDressup) class TaskPanel: DataX = QtCore.Qt.ItemDataRole.UserRole DataY = QtCore.Qt.ItemDataRole.UserRole + 1 + DataID = QtCore.Qt.ItemDataRole.UserRole + 2 - def __init__(self, obj, jvoVisibility=None): + def __init__(self, obj, viewProvider, jvoVisibility=None): self.obj = obj self.obj.Proxy.obj = obj - self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") + self.viewProvider = viewProvider + self.form = QtGui.QWidget() + self.formTags = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") + self.formPoint = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui") + self.layout = QtGui.QVBoxLayout(self.form) + #self.form.setGeometry(self.formTags.geometry()) + self.form.setWindowTitle(self.formTags.windowTitle()) + self.form.setSizePolicy(self.formTags.sizePolicy()) + self.formTags.setParent(self.form) + self.formPoint.setParent(self.form) + self.layout.addWidget(self.formTags) + self.layout.addWidget(self.formPoint) + self.formPoint.hide() self.jvo = PathUtils.findParentJob(obj).ViewObject if jvoVisibility is None: - FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + FreeCAD.ActiveDocument.openTransaction(translate("Edit HoldingTags Dress-up")) self.jvoVisible = self.jvo.isVisible() if self.jvoVisible: self.jvo.hide() else: self.jvoVisible = jvoVisibility + self.pt = FreeCAD.Vector(0, 0, 0) def reject(self): - print("reject") FreeCAD.ActiveDocument.abortTransaction() self.cleanup() def accept(self): - print("accept") self.getFields() FreeCAD.ActiveDocument.commitTransaction() self.cleanup() FreeCAD.ActiveDocument.recompute() def cleanup(self): + self.removeGlobalCallbacks() + self.viewProvider.clearTaskPanel() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() - FreeCADGui.Selection.removeObserver(self.s) if self.jvoVisible: self.jvo.show() - def closeDialog(self): - print("closed") - - def open(self): - self.s = SelObserver() - # install the function mode resident - FreeCADGui.Selection.addObserver(self.s) - def getTags(self, includeCurrent): tags = [] - index = self.form.lwTags.currentRow() - for i in range(0, self.form.lwTags.count()): - item = self.form.lwTags.item(i) + index = self.formTags.lwTags.currentRow() + for i in range(0, self.formTags.lwTags.count()): + item = self.formTags.lwTags.item(i) enabled = item.checkState() == QtCore.Qt.CheckState.Checked x = item.data(self.DataX) y = item.data(self.DataY) - print("(%.2f, %.2f) i=%d/%s" % (x, y, i, index)) + #print("(%.2f, %.2f) i=%d/%s" % (x, y, i, index)) if includeCurrent or i != index: tags.append((x, y, enabled)) return tags def getTagParameters(self): - self.obj.Width = self.form.dsbWidth.value() - self.obj.Height = self.form.dsbHeight.value() - self.obj.Angle = self.form.dsbAngle.value() + self.obj.Width = FreeCAD.Units.Quantity(self.formTags.ifWidth.text()).Value + self.obj.Height = FreeCAD.Units.Quantity(self.formTags.ifHeight.text()).Value + self.obj.Angle = self.formTags.dsbAngle.value() + self.obj.Radius = FreeCAD.Units.Quantity(self.formTags.ifRadius.text()).Value def getFields(self): self.getTagParameters() @@ -818,14 +1013,15 @@ class TaskPanel: self.obj.Proxy.setXyEnabled(tags) def updateTagsView(self): - print("updateTagsView") - self.form.lwTags.blockSignals(True) - self.form.lwTags.clear() + #print("updateTagsView") + self.formTags.lwTags.blockSignals(True) + self.formTags.lwTags.clear() for i, pos in enumerate(self.obj.Positions): lbl = "%d: (%.2f, %.2f)" % (i, pos.x, pos.y) item = QtGui.QListWidgetItem(lbl) item.setData(self.DataX, pos.x) item.setData(self.DataY, pos.y) + item.setData(self.DataID, i) if i in self.obj.Disabled: item.setCheckState(QtCore.Qt.CheckState.Unchecked) else: @@ -834,21 +1030,12 @@ class TaskPanel: flags |= QtCore.Qt.ItemFlag.ItemIsEnabled flags |= QtCore.Qt.ItemFlag.ItemIsUserCheckable item.setFlags(flags) - self.form.lwTags.addItem(item) - self.form.lwTags.blockSignals(False) - - def cleanupUI(self): - print("cleanupUI") - if debugDressup: - for obj in FreeCAD.ActiveDocument.Objects: - if obj.Name.startswith('tag'): - FreeCAD.ActiveDocument.removeObject(obj.Name) + self.formTags.lwTags.addItem(item) + self.formTags.lwTags.blockSignals(False) + self.whenTagSelectionChanged() def generateNewTags(self): - print("generateNewTags") - self.cleanupUI() - - count = self.form.sbCount.value() + count = self.formTags.sbCount.value() if not self.obj.Proxy.generateTags(self.obj, count): self.obj.Proxy.execute(self.obj) @@ -864,121 +1051,261 @@ class TaskPanel: #FreeCAD.ActiveDocument.recompute() def whenCountChanged(self): - print("whenCountChanged") - count = self.form.sbCount.value() - self.form.pbGenerate.setEnabled(count) + count = self.formTags.sbCount.value() + self.formTags.pbGenerate.setEnabled(count) + + def selectTagWithId(self, index): + self.formTags.lwTags.setCurrentRow(index) def whenTagSelectionChanged(self): - print('whenTagSelectionChanged') - item = self.form.lwTags.currentItem() - self.form.pbDelete.setEnabled(not item is None) + index = self.formTags.lwTags.currentRow() + count = self.formTags.lwTags.count() + self.formTags.pbDelete.setEnabled(index != -1 and count > 2) + self.formTags.pbEdit.setEnabled(index != -1) + self.viewProvider.selectTag(index) def deleteSelectedTag(self): self.obj.Proxy.setXyEnabled(self.getTags(False)) self.updateTagsView() - def addNewTagAt(self, point, what): - if what == self.obj: - print("%s '%s'" %( point, what.Name)) + def addNewTagAt(self, point, obj): + if self.obj.Proxy.pointIsOnPath(self.obj, point): + #print("addNewTagAt(%s)" % (point)) tags = self.tags tags.append((point.x, point.y, True)) self.obj.Proxy.setXyEnabled(tags) - panel = TaskPanel(self.obj, self.jvoVisible) - todo.delay(FreeCADGui.Control.closeDialog, None) - todo.delay(FreeCADGui.Control.showDialog, panel) - todo.delay(panel.setupUi, None) + self.updateTagsView() + else: + print("ignore new tag at %s" % (point)) + self.formPoint.hide() + self.formTags.show() def addNewTag(self): self.tags = self.getTags(True) - FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt, extradlg=[self]) + self.getPoint(self.addNewTagAt) + + def editTagAt(self, point, obj): + if (obj or point != FreeCAD.Vector()) and self.obj.Proxy.pointIsOnPath(self.obj, point): + tags = [] + for i, (x, y, enabled) in enumerate(self.tags): + if i == self.editItem: + tags.append((point.x, point.y, enabled)) + else: + tags.append((x, y, enabled)) + self.obj.Proxy.setXyEnabled(tags) + self.updateTagsView() + self.formPoint.hide() + self.formTags.show() + + def editTag(self, item): + if item: + self.tags = self.getTags(True) + self.editItem = item.data(self.DataID) + x = item.data(self.DataX) + y = item.data(self.DataY) + self.getPoint(self.editTagAt, FreeCAD.Vector(x, y, 0)) + + def editSelectedTag(self): + self.editTag(self.formTags.lwTags.currentItem()) + + def removeGlobalCallbacks(self): + if hasattr(self, 'view') and self.view: + if self.pointCbClick: + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.pointCbClick) + self.pointCbClick = None + if self.pointCbMove: + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.pointCbMove) + self.pointCbMove = None + self.view = None + + def getPoint(self, whenDone, start=None): + + def displayPoint(p): + self.formPoint.ifValueX.setText(FreeCAD.Units.Quantity(p.x, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueY.setText(FreeCAD.Units.Quantity(p.y, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueZ.setText(FreeCAD.Units.Quantity(p.z, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueX.setFocus() + self.formPoint.ifValueX.selectAll() + + def mouseMove(cb): + event = cb.getEvent() + pos = event.getPosition() + cntrl = event.wasCtrlDown() + shift = event.wasShiftDown() + self.pt = FreeCADGui.Snapper.snap(pos, lastpoint=start, active=cntrl, constrain=shift) + plane = FreeCAD.DraftWorkingPlane + p = plane.getLocalCoords(self.pt) + displayPoint(p) + + def click(cb): + event = cb.getEvent() + if event.getButton() == 1 and event.getState() == coin.SoMouseButtonEvent.DOWN: + accept() + + def accept(): + self.pointAccept() + + def cancel(): + self.pointCancel() + + self.pointWhenDone = whenDone + self.formTags.hide() + self.formPoint.show() + if start: + displayPoint(start) + else: + displayPoint(FreeCAD.Vector(0,0,0)) + + self.view = Draft.get3DView() + self.pointCbClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click) + self.pointCbMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove) def setupSpinBox(self, widget, val, decimals = 2): - widget.setMinimum(0) if decimals: widget.setDecimals(decimals) widget.setValue(val) def setFields(self): self.updateTagsView() - self.setupSpinBox(self.form.sbCount, len(self.obj.Positions), None) - self.setupSpinBox(self.form.dsbHeight, self.obj.Height) - self.setupSpinBox(self.form.dsbWidth, self.obj.Width) - self.setupSpinBox(self.form.dsbAngle, self.obj.Angle, 0) - self.form.dsbAngle.setMaximum(90) - self.form.dsbAngle.setSingleStep(5.) - - def updateModelHeight(self): - print('updateModelHeight') - self.updateModel() - - def updateModelWidth(self): - print('updateModelWidth') - self.updateModel() - - def updateModelAngle(self): - print('updateModelAngle') - self.updateModel() - - def updateModelTags(self): - print('updateModelTags') - self.updateModel() + self.formTags.sbCount.setValue(len(self.obj.Positions)) + self.formTags.ifHeight.setText(FreeCAD.Units.Quantity(self.obj.Height, FreeCAD.Units.Length).UserString) + self.formTags.ifWidth.setText(FreeCAD.Units.Quantity(self.obj.Width, FreeCAD.Units.Length).UserString) + self.formTags.dsbAngle.setValue(self.obj.Angle) + self.formTags.ifRadius.setText(FreeCAD.Units.Quantity(self.obj.Radius, FreeCAD.Units.Length).UserString) def setupUi(self): self.setFields() self.whenCountChanged() - self.form.sbCount.valueChanged.connect(self.whenCountChanged) - self.form.pbGenerate.clicked.connect(self.generateNewTags) + self.formTags.sbCount.valueChanged.connect(self.whenCountChanged) + self.formTags.pbGenerate.clicked.connect(self.generateNewTags) - self.form.dsbHeight.editingFinished.connect(self.updateModelHeight) - self.form.dsbWidth.editingFinished.connect(self.updateModelWidth) - self.form.dsbAngle.editingFinished.connect(self.updateModelAngle) - self.form.lwTags.itemChanged.connect(self.updateModelTags) - self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) + self.formTags.ifHeight.editingFinished.connect(self.updateModel) + self.formTags.ifWidth.editingFinished.connect(self.updateModel) + self.formTags.dsbAngle.editingFinished.connect(self.updateModel) + self.formTags.ifRadius.editingFinished.connect(self.updateModel) + self.formTags.lwTags.itemChanged.connect(self.updateModel) + self.formTags.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) + self.formTags.lwTags.itemActivated.connect(self.editTag) - self.form.pbDelete.clicked.connect(self.deleteSelectedTag) - self.form.pbAdd.clicked.connect(self.addNewTag) + self.formTags.pbDelete.clicked.connect(self.deleteSelectedTag) + self.formTags.pbEdit.clicked.connect(self.editSelectedTag) + self.formTags.pbAdd.clicked.connect(self.addNewTag) -class SelObserver: - def __init__(self): - import PathScripts.PathSelection as PST - PST.eselect() + self.formPoint.buttonBox.accepted.connect(self.pointAccept) + self.formPoint.ifValueX.editingFinished.connect(self.updatePoint) + self.formPoint.ifValueY.editingFinished.connect(self.updatePoint) + self.formPoint.ifValueZ.editingFinished.connect(self.updatePoint) - def __del__(self): - import PathScripts.PathSelection as PST - PST.clear() + self.viewProvider.turnMarkerDisplayOn(True) - def addSelection(self, doc, obj, sub, pnt): - #FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') - FreeCADGui.updateGui() + def pointFinish(self, ok): + self.removeGlobalCallbacks(); + obj = FreeCADGui.Snapper.lastSnappedObject + FreeCADGui.Snapper.off() + self.pointWhenDone(self.pt if ok else None, obj if ok else None) + + def pointReject(self): + self.pointFinish(False) + + def pointAccept(self): + self.pointFinish(True) + + def updatePoint(self): + x = FreeCAD.Units.Quantity(self.formPoint.ifValueX.text()).Value + y = FreeCAD.Units.Quantity(self.formPoint.ifValueY.text()).Value + z = FreeCAD.Units.Quantity(self.formPoint.ifValueZ.text()).Value + self.pt = FreeCAD.Vector(x, y, z) + +class HoldingTagMarker: + def __init__(self, point, colors): + self.point = point + self.color = colors + self.sep = coin.SoSeparator() + self.pos = coin.SoTranslation() + self.pos.translation = (point.x, point.y, point.z) + self.sphere = coin.SoSphere() + self.material = coin.SoMaterial() + self.sep.addChild(self.pos) + self.sep.addChild(self.material) + self.sep.addChild(self.sphere) + self.enabled = True + self.selected = False + + def setSelected(self, select): + self.selected = select + self.sphere.radius = 1.5 if select else 1.0 + self.setEnabled(self.enabled) + + def setEnabled(self, enabled): + self.enabled = enabled + if enabled: + self.material.diffuseColor = self.color[0] if not self.selected else self.color[2] + self.material.transparency = 0.0 + else: + self.material.diffuseColor = self.color[1] if not self.selected else self.color[2] + self.material.transparency = 0.6 class ViewProviderDressup: def __init__(self, vobj): vobj.Proxy = self + def setupColors(self): + def colorForColorValue(val): + v = [((val >> n) & 0xff) / 255. for n in [24, 16, 8, 0]] + return coin.SbColor(v[0], v[1], v[2]) + + pref = PathPreferences.preferences() + # R G B A + npc = pref.GetUnsigned("DefaultPathMarkerColor", (( 85*256 + 255)*256 + 0)*256 + 255) + hpc = pref.GetUnsigned("DefaultHighlightPathColor", ((255*256 + 125)*256 + 0)*256 + 255) + dpc = pref.GetUnsigned("DefaultDisabledPathColor", ((205*256 + 205)*256 + 205)*256 + 154) + self.colors = [colorForColorValue(npc), colorForColorValue(dpc), colorForColorValue(hpc)] + def attach(self, vobj): - self.Object = vobj.Object - return + self.setupColors() + self.obj = vobj.Object + self.tags = [] + self.switch = coin.SoSwitch() + vobj.RootNode.addChild(self.switch) + self.turnMarkerDisplayOn(False) + + def turnMarkerDisplayOn(self, display): + sw = coin.SO_SWITCH_ALL if display else coin.SO_SWITCH_NONE + self.switch.whichChild = sw + def claimChildren(self): - for i in self.Object.Base.InList: + for i in self.obj.Base.InList: if hasattr(i, "Group"): group = i.Group for g in group: - if g.Name == self.Object.Base.Name: + if g.Name == self.obj.Base.Name: group.remove(g) i.Group = group - print i.Group + #print i.Group #FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False - return [self.Object.Base] + return [self.obj.Base] def setEdit(self, vobj, mode=0): + panel = TaskPanel(vobj.Object, self) + self.setupTaskPanel(panel) + return True + + def setupTaskPanel(self, panel): + self.panel = panel FreeCADGui.Control.closeDialog() - panel = TaskPanel(vobj.Object) FreeCADGui.Control.showDialog(panel) panel.setupUi() - return True + FreeCADGui.Selection.addSelectionGate(self) + FreeCADGui.Selection.addObserver(self) + + def clearTaskPanel(self): + self.panel = None + FreeCADGui.Selection.removeObserver(self) + FreeCADGui.Selection.removeSelectionGate() + self.turnMarkerDisplayOn(False) def __getstate__(self): return None @@ -992,6 +1319,42 @@ class ViewProviderDressup: PathUtils.addToJob(arg1.Object.Base) return True + def updateData(self, obj, propName): + if 'Disabled' == propName: + for tag in self.tags: + self.switch.removeChild(tag.sep) + tags = [] + for i, p in enumerate(obj.Positions): + tag = HoldingTagMarker(p, self.colors) + tag.setEnabled(not i in obj.Disabled) + tags.append(tag) + self.switch.addChild(tag.sep) + self.tags = tags + + def selectTag(self, index): + #print("selectTag(%s)" % index) + for i, tag in enumerate(self.tags): + tag.setSelected(i == index) + + def tagAtPoint(self, point): + p = FreeCAD.Vector(point[0], point[1], point[2]) + for i, tag in enumerate(self.tags): + if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.1): + return i + return -1 + + # SelectionObserver interface + def allow(self, doc, obj, sub): + if obj == self.obj: + return True + return False + + def addSelection(self, doc, obj, sub, point): + i = self.tagAtPoint(point) + if self.panel: + self.panel.selectTagWithId(i) + FreeCADGui.updateGui() + class CommandPathDressupHoldingTags: def GetResources(self): @@ -1011,18 +1374,18 @@ class CommandPathDressupHoldingTags: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select one path object\n")) + FreeCAD.Console.PrintError(translate("Please select one path object\n")) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "The selected object is not a path\n")) + FreeCAD.Console.PrintError(translate("The selected object is not a path\n")) return if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select a Profile object")) + FreeCAD.Console.PrintError(translate("Please select a Profile object")) return # everything ok! - FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Create HoldingTags Dress-up")) + FreeCAD.ActiveDocument.openTransaction(translate("Create HoldingTags Dress-up")) FreeCADGui.addModule("PathScripts.PathDressupHoldingTags") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "HoldingTagsDressup")') @@ -1031,10 +1394,11 @@ class CommandPathDressupHoldingTags: FreeCADGui.doCommand('PathScripts.PathDressupHoldingTags.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') - FreeCADGui.doCommand('dbo.setup(obj, 4.)') + FreeCADGui.doCommand('dbo.setup(obj, True)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() + if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('PathDressup_HoldingTags', CommandPathDressupHoldingTags()) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 388b10952..ed8539873 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -29,6 +29,8 @@ import Path from FreeCAD import Vector +PathGeomTolerance = 0.000001 + class Side: """Class to determine and define the side a Path is on, or Vectors are in relation to each other.""" Left = +1 @@ -70,22 +72,24 @@ class PathGeom: CmdMoveArc = CmdMoveCW + CmdMoveCCW CmdMove = CmdMoveStraight + CmdMoveArc + Tolerance = PathGeomTolerance + @classmethod - def isRoughly(cls, float1, float2, error=0.0000001): - """(float1, float2, [error=0.0000001]) - Returns true if the two values are the same within a given error.""" + def isRoughly(cls, float1, float2, error=PathGeomTolerance): + """(float1, float2, [error=%s]) + Returns true if the two values are the same within a given error.""" % PathGeomTolerance return math.fabs(float1 - float2) <= error @classmethod - def pointsCoincide(cls, p1, p2, error=0.0000001): - """(p1, p2, [error=0.0000001]) - Return True if two points are roughly identical (see also isRoughly).""" + def pointsCoincide(cls, p1, p2, error=PathGeomTolerance): + """(p1, p2, [error=%s]) + Return True if two points are roughly identical (see also isRoughly).""" % PathGeomTolerance return cls.isRoughly(p1.x, p2.x, error) and cls.isRoughly(p1.y, p2.y, error) and cls.isRoughly(p1.z, p2.z, error) @classmethod - def edgesMatch(cls, e0, e1, error=0.0000001): - """(e0, e1, [error=0.0000001] - Return true if the edges start and end at the same point and have the same type of curve.""" + def edgesMatch(cls, e0, e1, error=PathGeomTolerance): + """(e0, e1, [error=%s] + Return true if the edges start and end at the same point and have the same type of curve.""" % PathGeomTolerance if type(e0.Curve) != type(e1.Curve): return False if not cls.pointsCoincide(e0.valueAt(e0.FirstParameter), e1.valueAt(e1.FirstParameter)): @@ -95,9 +99,9 @@ class PathGeom: return True @classmethod - def edgeConnectsTo(cls, edge, vector): - """(edge, vector) - Returns True if edge connects to given vector.""" + def edgeConnectsTo(cls, edge, vector, error=PathGeomTolerance): + """(edge, vector, error=%f) + Returns True if edge connects to given vector.""" % PathGeomTolerance return cls.pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or cls.pointsCoincide(edge.valueAt(edge.LastParameter), vector) @classmethod @@ -106,7 +110,7 @@ class PathGeom: Returns the angle [-pi,pi] of a vector using the X-axis as the reference. Positive angles for vertexes in the upper hemishpere (positive y values) and negative angles for the lower hemishpere.""" - a = vector.getAngle(FreeCAD.Vector(1,0,0)) + a = vector.getAngle(Vector(1,0,0)) if vector.y < 0: return -a return a @@ -132,7 +136,7 @@ class PathGeom: x = cmd.Parameters.get(X, defaultPoint.x) y = cmd.Parameters.get(Y, defaultPoint.y) z = cmd.Parameters.get(Z, defaultPoint.z) - return FreeCAD.Vector(x, y, z) + return Vector(x, y, z) @classmethod def xy(cls, point): @@ -162,12 +166,12 @@ class PathGeom: p1 = pt p3 = edge.valueAt(edge.LastParameter) p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - if type(edge.Curve) == Part.Circle or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve): + if (type(edge.Curve) == Part.Circle and cls.pointsCoincide(edge.Curve.Axis, Vector(0, 0, 1))) or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve): if Side.Left == Side.of(p2 - p1, p3 - p2): cmd = 'G3' else: cmd = 'G2' - #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center pa = PathGeom.xy(p1) @@ -232,7 +236,7 @@ class PathGeom: #print("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d)) #print("arc: R=%.2f angle=%.2f" % (R, angle/math.pi)) if startPoint.z == endPoint.z: - midPoint = center + FreeCAD.Vector(math.cos(angle), math.sin(angle), 0) * R + midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) # It's a Helix @@ -255,7 +259,7 @@ class PathGeom: return None @classmethod - def wireForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)): + def wireForPath(cls, path, startPoint = Vector(0, 0, 0)): """(path, [startPoint=Vector(0,0,0)]) Returns a wire representing all move commands found in the given path.""" edges = [] @@ -271,7 +275,7 @@ class PathGeom: return (Part.Wire(edges), rapid) @classmethod - def wiresForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)): + def wiresForPath(cls, path, startPoint = Vector(0, 0, 0)): """(path, [startPoint=Vector(0,0,0)]) Returns a collection of wires, each representing a continuous cutting Path in path.""" wires = [] @@ -306,7 +310,7 @@ class PathGeom: #print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) #print("- %s -> %s" % (cmd, command)) - return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) + return cls.edgeForCmd(command, Vector(p1.x, p1.y, z0)) @classmethod @@ -316,9 +320,9 @@ class PathGeom: p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) p3 = edge.valueAt(edge.LastParameter) - p01 = FreeCAD.Vector(p1.x, p1.y, z) - p02 = FreeCAD.Vector(p2.x, p2.y, z) - p03 = FreeCAD.Vector(p3.x, p3.y, z) + p01 = Vector(p1.x, p1.y, z) + p02 = Vector(p2.x, p2.y, z) + p03 = Vector(p3.x, p3.y, z) return Part.Edge(Part.Arc(p01, p02, p03)) @classmethod @@ -364,6 +368,6 @@ class PathGeom: else: # it's a helix arc = cls.helixToArc(edge, 0) - aes = cls.splitArcAt(arc, FreeCAD.Vector(pt.x, pt.y, 0)) + aes = cls.splitArcAt(arc, Vector(pt.x, pt.y, 0)) return [cls.arcToHelix(aes[0], p1.z, p2.z), cls.arcToHelix(aes[1], p2.z, p3.z)] diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index ab155ab2f..e1fe30912 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -31,6 +31,10 @@ class PathPreferences: PostProcessorDefaultArgs = "PostProcessorDefaultArgs" PostProcessorBlacklist = "PostProcessorBlacklist" + @classmethod + def preferences(cls): + return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") + @classmethod def allAvailablePostProcessors(cls): path = FreeCAD.getHomePath() + ("Mod/Path/PathScripts/") @@ -58,28 +62,28 @@ class PathPreferences: @classmethod def defaultPostProcessor(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.PostProcessorDefault, "") + pref = cls.preferences() + return pref.GetString(cls.PostProcessorDefault, "") @classmethod def defaultPostProcessorArgs(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.PostProcessorDefaultArgs, "") + pref = cls.preferences() + return pref.GetString(cls.PostProcessorDefaultArgs, "") @classmethod def postProcessorBlacklist(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - blacklist = preferences.GetString(cls.PostProcessorBlacklist, "") + pref = cls.preferences() + blacklist = pref.GetString(cls.PostProcessorBlacklist, "") if not blacklist: return [] return eval(blacklist) @classmethod def savePostProcessorDefaults(cls, processor, args, blacklist): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - preferences.SetString(cls.PostProcessorDefault, processor) - preferences.SetString(cls.PostProcessorDefaultArgs, args) - preferences.SetString(cls.PostProcessorBlacklist, "%s" % (blacklist)) + pref = cls.preferences() + pref.SetString(cls.PostProcessorDefault, processor) + pref.SetString(cls.PostProcessorDefaultArgs, args) + pref.SetString(cls.PostProcessorBlacklist, "%s" % (blacklist)) DefaultOutputFile = "DefaultOutputFile" @@ -87,16 +91,16 @@ class PathPreferences: @classmethod def saveOutputFileDefaults(cls, file, policy): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - preferences.SetString(cls.DefaultOutputFile, file) - preferences.SetString(cls.DefaultOutputPolicy, policy) + pref = cls.preferences() + pref.SetString(cls.DefaultOutputFile, file) + pref.SetString(cls.DefaultOutputPolicy, policy) @classmethod def defaultOutputFile(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.DefaultOutputFile, "") + pref = cls.preferences() + return pref.GetString(cls.DefaultOutputFile, "") @classmethod def defaultOutputPolicy(cls): - preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - return preferences.GetString(cls.DefaultOutputPolicy, "") + pref = cls.preferences() + return pref.GetString(cls.DefaultOutputPolicy, "") diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py b/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py new file mode 100644 index 000000000..a23b370e8 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +from PySide import QtCore, QtGui +from PathScripts.PathPreferences import PathPreferences + +# Qt tanslation handling +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) + +except AttributeError: + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) + +_dressups = [] + +def RegisterDressup(dressup): + _dressups.append(dressup) + +class DressupPreferencesPage: + def __init__(self, parent=None): + self.form = QtGui.QToolBox() + self.form.setWindowTitle(translate('PathPreferencesPathDressup', 'Dressups')) + pages = [] + for dressup in _dressups: + page = dressup.preferencesPage() + if hasattr(page, 'icon') and page.icon: + self.form.addItem(page.form, page.icon, page.label) + else: + self.form.addItem(page.form, page.label) + pages.append(page) + self.pages = pages + + def saveSettings(self): + for page in self.pages: + page.saveSettings() + + def loadSettings(self): + for page in self.pages: + page.loadSettings() + diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py index 25e12f28a..cb374133d 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py @@ -29,7 +29,7 @@ from PathScripts.PathPreferences import PathPreferences from PathScripts.PathPostProcessor import PostProcessor -class Page: +class JobPreferencesPage: def __init__(self, parent=None): self.form = FreeCADGui.PySideUic.loadUi(":preferences/PathJob.ui")