diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 2f635be9e800..5be33f8eaf25 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -1,95 +1,95 @@ - icons/preferences-path.svg - icons/Path-Toolpath.svg + icons/Path-3DSurface.svg + icons/Path-Array.svg + icons/Path-Axis.svg + icons/Path-BaseGeometry.svg + icons/Path-Comment.svg icons/Path-Compound.svg - icons/Path-Shape.svg - icons/Path-Profile.svg icons/Path-Contour.svg - icons/Path-Pocket.svg - icons/Path-Drilling.svg - icons/Path-Job.svg - icons/Path-Dressup.svg - icons/Path-Hop.svg - icons/Path-Datums.svg icons/Path-Copy.svg - icons/Path-ToolTable.svg - icons/Path-LengthOffset.svg - icons/Path-Axis.svg - icons/Path-Stock.svg - icons/Path-Plane.svg - icons/Path-Post.svg - icons/Path-LoadTool.svg - icons/Path-Comment.svg - icons/Path-Stop.svg - icons/Path-Machine.svg - icons/Path-Kurve.svg - icons/Path-FaceProfile.svg - icons/Path-FacePocket.svg - icons/Path-Array.svg icons/Path-Custom.svg - icons/Path-Inspect.svg - icons/Path-ToolChange.svg - icons/Path-SimpleCopy.svg - icons/Path-Engrave.svg - icons/Path-Sanity.svg - icons/Path-3DSurface.svg - icons/Path-Speed.svg - icons/Path-BaseGeometry.svg + icons/Path-Datums.svg icons/Path-Depths.svg + icons/Path-Dressup.svg + icons/Path-Drilling.svg + icons/Path-Engrave.svg + icons/Path-FacePocket.svg + icons/Path-FaceProfile.svg + icons/Path-Face.svg icons/Path-Heights.svg + icons/Path-Hop.svg + icons/Path-Inspect.svg + icons/Path-Job.svg + icons/Path-Kurve.svg + icons/Path-LengthOffset.svg + icons/Path-LoadTool.svg icons/Path-MachineLathe.svg icons/Path-MachineMill.svg + icons/Path-Machine.svg icons/Path-OperationA.svg icons/Path-OperationB.svg + icons/Path-Plane.svg + icons/Path-Pocket.svg + icons/Path-Post.svg icons/Path-Profile-Edges.svg icons/Path-Profile-Face.svg + icons/Path-Profile.svg + icons/Path-Sanity.svg icons/Path-SelectLoop.svg - icons/Path-Face.svg - translations/Path_de.qm + icons/Path-Shape.svg + icons/Path-SimpleCopy.svg + icons/Path-Speed.svg + icons/Path-Stock.svg + icons/Path-Stop.svg + icons/Path-ToolChange.svg + icons/Path-Toolpath.svg + icons/Path-ToolTable.svg + icons/preferences-path.svg + panels/ContourEdit.ui + panels/DlgJobChooser.ui + panels/DlgSelectPostProcessor.ui + panels/DlgToolCopy.ui + panels/DogboneEdit.ui + panels/DrillingEdit.ui + panels/EngraveEdit.ui + panels/HoldingTagsEdit.ui + panels/JobEdit.ui + panels/MillFaceEdit.ui + panels/PocketEdit.ui + panels/ProfileEdgesEdit.ui + panels/ProfileEdit.ui + panels/RemoteEdit.ui + panels/SurfaceEdit.ui + panels/ToolControl.ui + panels/ToolEdit.ui + panels/ToolLibraryEditor.ui + preferences/PathJob.ui translations/Path_af.qm - translations/Path_zh-CN.qm - translations/Path_zh-TW.qm - translations/Path_hr.qm translations/Path_cs.qm - translations/Path_nl.qm + translations/Path_de.qm + translations/Path_el.qm + translations/Path_es-ES.qm translations/Path_fi.qm translations/Path_fr.qm + translations/Path_hr.qm translations/Path_hu.qm + translations/Path_it.qm translations/Path_ja.qm + translations/Path_nl.qm translations/Path_no.qm translations/Path_pl.qm + translations/Path_pt-BR.qm translations/Path_pt-PT.qm translations/Path_ro.qm translations/Path_ru.qm + translations/Path_sk.qm + translations/Path_sl.qm translations/Path_sr.qm - translations/Path_es-ES.qm translations/Path_sv-SE.qm - translations/Path_uk.qm - translations/Path_it.qm - translations/Path_pt-BR.qm - translations/Path_el.qm - translations/Path_sk.qm translations/Path_tr.qm - translations/Path_sl.qm - panels/EngraveEdit.ui - panels/DrillingEdit.ui - panels/PocketEdit.ui - panels/ProfileEdit.ui - panels/SurfaceEdit.ui - panels/RemoteEdit.ui - panels/ToolControl.ui - panels/ToolLibraryEditor.ui - panels/JobEdit.ui - panels/DlgToolCopy.ui - panels/ToolEdit.ui - panels/DlgJobChooser.ui - panels/ContourEdit.ui - panels/MillFaceEdit.ui - panels/ProfileEdgesEdit.ui - panels/DogboneEdit.ui - panels/DlgSelectPostProcessor.ui - preferences/PathJob.ui - panels/HoldingTagsEdit.ui + translations/Path_uk.qm + translations/Path_zh-CN.qm + translations/Path_zh-TW.qm diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 4659f587ff70..e2b69f6b7a7a 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -25,8 +25,7 @@ class PathWorkbench (Workbench): "Path workbench" def __init__(self): - self.__class__.Icon = FreeCAD.getResourceDir( - ) + "Mod/Path/Resources/icons/PathWorkbench.svg" + self.__class__.Icon = FreeCAD.getResourceDir() + "Mod/Path/Resources/icons/PathWorkbench.svg" self.__class__.MenuText = "Path" self.__class__.ToolTip = "Path workbench" @@ -120,11 +119,6 @@ def translate(context, text): # "Path", "Remote Operations")], remotecmdlist) self.appendMenu([translate("Path", "&Path")], extracmdlist) - # Add preferences pages - import os - FreeCADGui.addPreferencePage(FreeCAD.getHomePath( - ) + os.sep + "Mod" + os.sep + "Path" + os.sep + "PathScripts" + os.sep + "DlgSettingsPath.ui", "Path") - Log('Loading Path workbench... done\n') def GetClassName(self): @@ -142,7 +136,7 @@ def ContextMenu(self, recipient): if len(FreeCADGui.Selection.getSelection()) == 1: if FreeCADGui.Selection.getSelection()[0].isDerivedFrom("Path::Feature"): self.appendContextMenu("", ["Path_Inspect"]) - if "Profile" in FreeCADGui.Selection.getSelection()[0].Name: + if "Profile" or "Contour" in FreeCADGui.Selection.getSelection()[0].Name: self.appendContextMenu("", ["Add_Tag"]) self.appendContextMenu("", ["Set_StartPoint"]) self.appendContextMenu("", ["Set_EndPoint"]) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index fc87fe2f8d1d..199f46cc0c81 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -67,6 +67,29 @@ def getAngle(v): return -a return a +class Side: + Left = +1 + Right = -1 + Straight = 0 + On = 0 + + @classmethod + def toString(cls, side): + if side == cls.Left: + return 'Left' + if side == cls.Right: + return 'Right' + return 'On' + + @classmethod + def of(cls, ptRef, pt): + d = -ptRef.x*pt.y + ptRef.y*pt.x + if d < 0: + return cls.Left + if d > 0: + return cls.Right + return cls.Straight + def testPrintAngle(v): print("(%+.2f, %+.2f, %+.2f): %+.2f" % (v.x, v.y, v.z, getAngle(v)/math.pi)) @@ -81,18 +104,73 @@ def testAngle(x=1, y=1): testPrintAngle(FreeCAD.Vector( 1*x,-1*y, 0)) +def testPrintSide(pt1, pt2): + print('(%.2f, %.2f) - (%.2f, %.2f) -> %s' % (pt1.x, pt1.y, pt2.x, pt2.y, Side.toString(Side.of(pt1, pt2)))) + +def testSide(): + testPrintSide(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 1, 0, 0)) + testPrintSide(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector(-1, 0, 0)) + testPrintSide(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 0, 1, 0)) + testPrintSide(FreeCAD.Vector( 1, 0, 0), FreeCAD.Vector( 0,-1, 0)) + +def pathCommandForEdge(edge): + pt = edge.valueAt(edge.LastParameter) + params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} + if type(edge.Curve) == Part.Line: + return Part.Command('G1', params) + + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + offset = pt1 - edge.Curve.Center + params.update({'I': offset.x, 'J': offset.y, 'K': offset.z}) + return Part.Command(cmd, params) + + class Tag: def __init__(self, x, y, width, height, angle, enabled): self.x = x self.y = y - self.width = width - self.height = height - self.angle = angle + self.width = math.fabs(width) + self.height = math.fabs(height) + self.angle = math.fabs(angle) self.enabled = enabled def toString(self): return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + def originAt(self, z): + return FreeCAD.Vector(self.x, self.y, z) + + def createSolidsAt(self, z): + r1 = self.width / 2 + height = self.height + if self.angle == 90 and height > 0: + self.solid = Part.makeCylinder(r1, height) + self.core = self.solid + elif self.angle > 0.0 and height > 0.0: + tangens = math.tan(math.radians(self.angle)) + dr = height / tangens + if dr < r1: + r2 = r1 - dr + self.core = Part.makeCylinder(r2, height) + else: + r2 = 0 + height = r1 * tangens + self.core = None + self.solid = Part.makeCone(r1, r2, height) + else: + # degenerated case - no tag + self.solid = Part.makeSphere(r1 / 10000) + self.core = None + self.solid.translate(self.originAt(z)) + if self.core: + self.core.translate(self.originAt(z)) + @classmethod def FromString(cls, string): try: @@ -150,11 +228,25 @@ def findBottomWire(self, edges): bottom = [e for e in edges if e.Vertexes[0].Point.z == minZ and e.Vertexes[1].Point.z == minZ] wire = Part.Wire(bottom) if wire.isClosed(): - return wire + return Part.Wire(self.sortedBase(bottom)) # if we get here there are already holding tags, or we're not looking at a profile # let's try and insert the missing pieces - another day raise ValueError("Selected path doesn't seem to be a Profile operation.") + def sortedBase(self, base): + # first find the exit point, where base wire is closed + edges = [e for e in self.edges if e.valueAt(e.FirstParameter).z == self.minZ and e.valueAt(e.LastParameter).z != self.maxZ] + exit = sorted(edges, key=lambda e: -e.valueAt(e.LastParameter).z)[0] + pt = exit.valueAt(exit.FirstParameter) + # then find the first base edge, and sort them until done + ordered = [] + while base: + edge = [e for e in base if e.valueAt(e.FirstParameter) == pt][0] + ordered.append(edge) + base.remove(edge) + pt = edge.valueAt(edge.LastParameter) + return ordered + def findZLimits(self, edges): # not considering arcs and spheres in Z direction, find the highes and lowest Z values @@ -268,6 +360,18 @@ def tagAngle(self): def pathLength(self): return self.base.Length + def sortedTags(self, tags): + ordered = [] + for edge in self.base.Edges: + ts = [t for t in tags if DraftGeomUtils.isPtOnEdge(t.originAt(self.minZ), edge)] + 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") + return ordered + + class ObjectDressup: def __init__(self, obj): @@ -286,6 +390,46 @@ def __setstate__(self, state): def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): return self.pathData.generateTags(obj, count, width, height, angle, spacing) + + def tagIntersection(self, face, edge): + p1 = edge.valueAt(edge.FirstParameter) + pts = edge.Curve.intersect(face.Surface) + if pts[0]: + closest = sorted(pts[0], key=lambda pt: (pt - p1).Length)[0] + return closest + return None + + def createPath(self, edges, tagSolids): + commands = [] + i = 0 + while i != len(edges): + edge = edges[i] + while edge: + for solid in tagSolids: + for face in solid.Faces: + pt = self.tagIntersection(face, edge) + if pt: + if pt == edge.valueAt(edge.FirstParameter): + pt + elif pt != edge.valueAt(edge.LastParameter): + parameter = edge.Curve.parameter(pt) + wire = edge.split(parameter) + commands.append(pathCommandForEdge(wire.Edges[0])) + edge = wire.Edges[1] + break; + else: + commands.append(pathCommandForEdge(edge)) + edge = None + i += 1 + break + if not edge: + break + if edge: + commands.append(pathCommandForEdge(edge)) + edge = None + return self.obj.Path + + def execute(self, obj): if not obj.Base: return @@ -323,9 +467,16 @@ def execute(self, obj): 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) + + tags = pathData.sortedTags(tags) + for tag in tags: + tag.createSolidsAt(pathData.minZ) + self.fingerprint = [tag.toString() for tag in tags] self.tags = tags - obj.Path = obj.Base.Path + + #obj.Path = self.createPath(pathData.edges, tags) + obj.Path = self.Base.Path def setTags(self, obj, tags): obj.Tags = [tag.toString() for tag in tags]