diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 2921825c45..a8af95e4a4 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -105,6 +105,7 @@ panels/PageDepthsEdit.ui panels/PageDiametersEdit.ui panels/PageHeightsEdit.ui + panels/PageOpAdaptiveEdit.ui panels/PageOpCustomEdit.ui panels/PageOpDeburrEdit.ui panels/PageOpDrillingEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpAdaptiveEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpAdaptiveEdit.ui new file mode 100644 index 0000000000..dde5b650e4 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpAdaptiveEdit.ui @@ -0,0 +1,276 @@ + + + Form + + + + 0 + 0 + 437 + 764 + + + + Form + + + + + + + + + Tool Controller + + + + + + + + + + Coolant + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + <html><head/><body><p>How much to lift the tool up during the rapid linking moves over cleared regions.</p><p>If linking path is not clear tool is raised to clearence height.</p></body></html> + + + + + + + Lift Distance + + + + + + + <html><head/><body><p>If &gt;0 it limits the helix ramp diameter</p><p>otherwise the 75 percent of tool diameter is used</p></body></html> + + + + + + + Helix Ramp Angle + + + + + + + <html><head/><body><p>Angle of the helix entry cone</p></body></html> + + + + + + + Stock to Leave + + + + + + + Cut Region + + + + + + + <html><head/><body><p>Cut inside or outside of the selected shapes</p></body></html> + + + + + + + Step Over Percent + + + + + + + Use Outline + + + + + + + <html><head/><body><p>Max length of keep-tool-down linking path compared to direct distance between points.</p><p>If exceeded link will be done by raising the tool to clearence height.</p></body></html> + + + + + + + Helix Max Diameter + + + + + + + Helix Cone Angle + + + + + + + Keep Tool Down Ratio + + + + + + + <html><head/><body><p>How much material to leave (i.e. for finishing operation)</p></body></html> + + + + + + + Force Clearing Inside-out + + + + + + + Operation Type + + + + + + + <html><head/><body><p>Angle of the helix ramp entry</p></body></html> + + + + + + + Finishing Profile + + + + + + + <html><head/><body><p>Type of adaptive operation</p></body></html> + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Accuracy vs Performance + + + + + + + <html><head/><body><p>Influences calculation performance vs stability and accuracy</p></body></html> + + + 5 + + + 15 + + + 10 + + + 10 + + + Qt::Horizontal + + + 1 + + + + + + + + + + <html><head/><body><p>Optimal value for tool stepover</p></body></html> + + + 1 + + + 100 + + + + + + + + + + Stop + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpThreadMillingEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpThreadMillingEdit.ui index 8de86b656a..cd24476351 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpThreadMillingEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpThreadMillingEdit.ui @@ -30,18 +30,8 @@ - 1 + -1 - - - Left Hand - - - - - Right Hand - - @@ -52,23 +42,7 @@ - - - - Custom - - - - - Metric - internal - - - - - SAE - internal - - - + @@ -208,18 +182,7 @@ - - - - Climb - - - - - Conventional - - - + @@ -236,7 +199,7 @@ Gui::QuantitySpinBox - QDoubleSpinBox + QWidget
Gui/QuantitySpinBox.h
diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 820133a723..32f44f5dfb 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -150,7 +150,7 @@ class PathWorkbench(Workbench): projcmdlist.append("Path_Sanity") prepcmdlist.append("Path_Shape") extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) - specialcmdlist.append("Path_Thread_Milling") + specialcmdlist.append("Path_ThreadMilling") twodopcmdlist.append("Path_Slot") if PathPreferences.advancedOCLFeaturesEnabled(): diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index 42a6a9dbee..f7241105d0 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -32,17 +32,7 @@ import time import json import math import area - -from PySide import QtCore - -# lazily loaded modules -from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') -# TechDraw = LazyLoader('TechDraw', globals(), 'TechDraw') -FeatureExtensions = LazyLoader('PathScripts.PathFeatureExtensions', - globals(), - 'PathScripts.PathFeatureExtensions') -DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils') +from PySide.QtCore import QT_TRANSLATE_NOOP if FreeCAD.GuiUp: from pivy import coin @@ -50,14 +40,25 @@ if FreeCAD.GuiUp: __doc__ = "Class and implementation of the Adaptive path operation." +# lazily loaded modules +from lazy_loader.lazy_loader import LazyLoader -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Part = LazyLoader("Part", globals(), "Part") +# TechDraw = LazyLoader('TechDraw', globals(), 'TechDraw') +FeatureExtensions = LazyLoader( + "PathScripts.PathFeatureExtensions", globals(), "PathScripts.PathFeatureExtensions" +) +DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +translate = FreeCAD.Qt.translate def convertTo2d(pathArray): @@ -112,11 +113,11 @@ def discretize(edge, flipDirection=False): def CalcHelixConePoint(height, cur_z, radius, angle): - x = ((height - cur_z) / height) * radius * math.cos(math.radians(angle)*cur_z) - y = ((height - cur_z) / height) * radius * math.sin(math.radians(angle)*cur_z) + x = ((height - cur_z) / height) * radius * math.cos(math.radians(angle) * cur_z) + y = ((height - cur_z) / height) * radius * math.sin(math.radians(angle) * cur_z) z = cur_z - return {'X': x, 'Y': y, 'Z': z} + return {"X": x, "Y": y, "Z": z} def GenerateGCode(op, obj, adaptiveResults, helixDiameter): @@ -129,7 +130,9 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): for region in adaptiveResults: p1 = region["HelixCenterPoint"] p2 = region["StartPoint"] - r = math.sqrt((p1[0]-p2[0]) * (p1[0]-p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])) + r = math.sqrt( + (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]) + ) if r > helixRadius: helixRadius = r @@ -139,7 +142,7 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): if stepDown < 0.1: stepDown = 0.1 - length = 2*math.pi * helixRadius + length = 2 * math.pi * helixRadius if float(obj.HelixAngle) < 1: obj.HelixAngle = 1 @@ -162,13 +165,14 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): finish_step = stepDown depth_params = PathUtils.depth_params( - clearance_height=obj.ClearanceHeight.Value, - safe_height=obj.SafeHeight.Value, - start_depth=obj.StartDepth.Value, - step_down=stepDown, - z_finish_step=finish_step, - final_depth=obj.FinalDepth.Value, - user_depths=None) + clearance_height=obj.ClearanceHeight.Value, + safe_height=obj.SafeHeight.Value, + start_depth=obj.StartDepth.Value, + step_down=stepDown, + z_finish_step=finish_step, + final_depth=obj.FinalDepth.Value, + user_depths=None, + ) # ml: this is dangerous because it'll hide all unused variables hence forward # however, I don't know what lx and ly signify so I'll leave them for now @@ -182,16 +186,21 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): step = step + 1 for region in adaptiveResults: - startAngle = math.atan2(region["StartPoint"][1] - region["HelixCenterPoint"][1], region["StartPoint"][0] - region["HelixCenterPoint"][0]) + startAngle = math.atan2( + region["StartPoint"][1] - region["HelixCenterPoint"][1], + region["StartPoint"][0] - region["HelixCenterPoint"][0], + ) # lx = region["HelixCenterPoint"][0] # ly = region["HelixCenterPoint"][1] - passDepth = (passStartDepth - passEndDepth) + passDepth = passStartDepth - passEndDepth p1 = region["HelixCenterPoint"] p2 = region["StartPoint"] - helixRadius = math.sqrt((p1[0]-p2[0]) * (p1[0]-p2[0]) + (p1[1]-p2[1]) * (p1[1]-p2[1])) + helixRadius = math.sqrt( + (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]) + ) # Helix ramp if helixRadius > 0.01: @@ -199,43 +208,96 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): maxfi = passDepth / depthPerOneCircle * 2 * math.pi fi = 0 - offsetFi = -maxfi + startAngle-math.pi/16 + offsetFi = -maxfi + startAngle - math.pi / 16 - helixStart = [region["HelixCenterPoint"][0] + r * math.cos(offsetFi), region["HelixCenterPoint"][1] + r * math.sin(offsetFi)] + helixStart = [ + region["HelixCenterPoint"][0] + r * math.cos(offsetFi), + region["HelixCenterPoint"][1] + r * math.sin(offsetFi), + ] - op.commandlist.append(Path.Command("(Helix to depth: %f)" % passEndDepth)) + op.commandlist.append( + Path.Command("(Helix to depth: %f)" % passEndDepth) + ) if obj.UseHelixArcs is False: # rapid move to start point - op.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) - op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.ClearanceHeight.Value})) + op.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) + ) + op.commandlist.append( + Path.Command( + "G0", + { + "X": helixStart[0], + "Y": helixStart[1], + "Z": obj.ClearanceHeight.Value, + }, + ) + ) # rapid move to safe height - op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.SafeHeight.Value})) + op.commandlist.append( + Path.Command( + "G0", + { + "X": helixStart[0], + "Y": helixStart[1], + "Z": obj.SafeHeight.Value, + }, + ) + ) # move to start depth - op.commandlist.append(Path.Command("G1", {"X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed})) + op.commandlist.append( + Path.Command( + "G1", + { + "X": helixStart[0], + "Y": helixStart[1], + "Z": passStartDepth, + "F": op.vertFeed, + }, + ) + ) if obj.HelixConeAngle == 0: while fi < maxfi: - x = region["HelixCenterPoint"][0] + r * math.cos(fi+offsetFi) - y = region["HelixCenterPoint"][1] + r * math.sin(fi+offsetFi) - z = passStartDepth - fi / maxfi * (passStartDepth - passEndDepth) - op.commandlist.append(Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed})) + x = region["HelixCenterPoint"][0] + r * math.cos( + fi + offsetFi + ) + y = region["HelixCenterPoint"][1] + r * math.sin( + fi + offsetFi + ) + z = passStartDepth - fi / maxfi * ( + passStartDepth - passEndDepth + ) + op.commandlist.append( + Path.Command( + "G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed} + ) + ) # lx = x # ly = y fi = fi + math.pi / 16 # one more circle at target depth to make sure center is cleared - maxfi = maxfi + 2*math.pi + maxfi = maxfi + 2 * math.pi while fi < maxfi: - x = region["HelixCenterPoint"][0] + r * math.cos(fi+offsetFi) - y = region["HelixCenterPoint"][1] + r * math.sin(fi+offsetFi) + x = region["HelixCenterPoint"][0] + r * math.cos( + fi + offsetFi + ) + y = region["HelixCenterPoint"][1] + r * math.sin( + fi + offsetFi + ) z = passEndDepth - op.commandlist.append(Path.Command("G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed})) + op.commandlist.append( + Path.Command( + "G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed} + ) + ) # lx = x # ly = y - fi = fi + math.pi/16 + fi = fi + math.pi / 16 else: # Cone @@ -248,9 +310,14 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): # Calculate everything helix_height = passStartDepth - passEndDepth - r_extra = helix_height * math.tan(math.radians(obj.HelixConeAngle)) + r_extra = helix_height * math.tan( + math.radians(obj.HelixConeAngle) + ) HelixTopRadius = helixRadius + r_extra - helix_full_height = HelixTopRadius * (math.cos(math.radians(obj.HelixConeAngle)) / math.sin(math.radians(obj.HelixConeAngle))) + helix_full_height = HelixTopRadius * ( + math.cos(math.radians(obj.HelixConeAngle)) + / math.sin(math.radians(obj.HelixConeAngle)) + ) # Start height z = passStartDepth @@ -267,70 +334,250 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): p = None # Calculate conical helix - while(z >= passEndDepth): + while z >= passEndDepth: if z < passEndDepth: z = passEndDepth - p = CalcHelixConePoint(helix_full_height, i, HelixTopRadius, _HelixAngle) - op.commandlist.append(Path.Command("G1", {"X": p['X'] + region["HelixCenterPoint"][0], "Y": p['Y'] + region["HelixCenterPoint"][1], "Z": z, "F": op.vertFeed})) + p = CalcHelixConePoint( + helix_full_height, i, HelixTopRadius, _HelixAngle + ) + op.commandlist.append( + Path.Command( + "G1", + { + "X": p["X"] + region["HelixCenterPoint"][0], + "Y": p["Y"] + region["HelixCenterPoint"][1], + "Z": z, + "F": op.vertFeed, + }, + ) + ) z = z - z_step i = i + z_step # Calculate some stuff for arcs at bottom - p['X'] = p['X'] + region["HelixCenterPoint"][0] - p['Y'] = p['Y'] + region["HelixCenterPoint"][1] - x_m = region["HelixCenterPoint"][0] - p['X'] + region["HelixCenterPoint"][0] - y_m = region["HelixCenterPoint"][1] - p['Y'] + region["HelixCenterPoint"][1] - i_off = (x_m - p['X']) / 2 - j_off = (y_m - p['Y']) / 2 + p["X"] = p["X"] + region["HelixCenterPoint"][0] + p["Y"] = p["Y"] + region["HelixCenterPoint"][1] + x_m = ( + region["HelixCenterPoint"][0] + - p["X"] + + region["HelixCenterPoint"][0] + ) + y_m = ( + region["HelixCenterPoint"][1] + - p["Y"] + + region["HelixCenterPoint"][1] + ) + i_off = (x_m - p["X"]) / 2 + j_off = (y_m - p["Y"]) / 2 # One more circle at target depth to make sure center is cleared - op.commandlist.append(Path.Command("G3", {"X": x_m, "Y": y_m, "Z": passEndDepth, "I": i_off, "J": j_off, "F": op.horizFeed})) - op.commandlist.append(Path.Command("G3", {"X": p['X'], "Y": p['Y'], "Z": passEndDepth, "I": -i_off, "J": -j_off, "F": op.horizFeed})) + op.commandlist.append( + Path.Command( + "G3", + { + "X": x_m, + "Y": y_m, + "Z": passEndDepth, + "I": i_off, + "J": j_off, + "F": op.horizFeed, + }, + ) + ) + op.commandlist.append( + Path.Command( + "G3", + { + "X": p["X"], + "Y": p["Y"], + "Z": passEndDepth, + "I": -i_off, + "J": -j_off, + "F": op.horizFeed, + }, + ) + ) else: # Use arcs for helix - no conical shape support - helixStart = [region["HelixCenterPoint"][0] + r, region["HelixCenterPoint"][1]] + helixStart = [ + region["HelixCenterPoint"][0] + r, + region["HelixCenterPoint"][1], + ] # rapid move to start point - op.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) - op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.ClearanceHeight.Value})) + op.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) + ) + op.commandlist.append( + Path.Command( + "G0", + { + "X": helixStart[0], + "Y": helixStart[1], + "Z": obj.ClearanceHeight.Value, + }, + ) + ) # rapid move to safe height - op.commandlist.append(Path.Command("G0", {"X": helixStart[0], "Y": helixStart[1], "Z": obj.SafeHeight.Value})) + op.commandlist.append( + Path.Command( + "G0", + { + "X": helixStart[0], + "Y": helixStart[1], + "Z": obj.SafeHeight.Value, + }, + ) + ) # move to start depth - op.commandlist.append(Path.Command("G1", {"X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed})) + op.commandlist.append( + Path.Command( + "G1", + { + "X": helixStart[0], + "Y": helixStart[1], + "Z": passStartDepth, + "F": op.vertFeed, + }, + ) + ) x = region["HelixCenterPoint"][0] + r y = region["HelixCenterPoint"][1] curDep = passStartDepth while curDep > (passEndDepth + depthPerOneCircle): - op.commandlist.append(Path.Command("G2", {"X": x - (2*r), "Y": y, "Z": curDep - (depthPerOneCircle/2), "I": -r, "F": op.vertFeed})) - op.commandlist.append(Path.Command("G2", {"X": x, "Y": y, "Z": curDep - depthPerOneCircle, "I": r, "F": op.vertFeed})) + op.commandlist.append( + Path.Command( + "G2", + { + "X": x - (2 * r), + "Y": y, + "Z": curDep - (depthPerOneCircle / 2), + "I": -r, + "F": op.vertFeed, + }, + ) + ) + op.commandlist.append( + Path.Command( + "G2", + { + "X": x, + "Y": y, + "Z": curDep - depthPerOneCircle, + "I": r, + "F": op.vertFeed, + }, + ) + ) curDep = curDep - depthPerOneCircle lastStep = curDep - passEndDepth - if lastStep > (depthPerOneCircle/2): - op.commandlist.append(Path.Command("G2", {"X": x - (2*r), "Y": y, "Z": curDep - (lastStep/2), "I": -r, "F": op.vertFeed})) - op.commandlist.append(Path.Command("G2", {"X": x, "Y": y, "Z": passEndDepth, "I": r, "F": op.vertFeed})) + if lastStep > (depthPerOneCircle / 2): + op.commandlist.append( + Path.Command( + "G2", + { + "X": x - (2 * r), + "Y": y, + "Z": curDep - (lastStep / 2), + "I": -r, + "F": op.vertFeed, + }, + ) + ) + op.commandlist.append( + Path.Command( + "G2", + { + "X": x, + "Y": y, + "Z": passEndDepth, + "I": r, + "F": op.vertFeed, + }, + ) + ) else: - op.commandlist.append(Path.Command("G2", {"X": x - (2*r), "Y": y, "Z": passEndDepth, "I": -r, "F": op.vertFeed})) - op.commandlist.append(Path.Command("G1", {"X": x, "Y": y, "Z": passEndDepth, "F": op.vertFeed})) + op.commandlist.append( + Path.Command( + "G2", + { + "X": x - (2 * r), + "Y": y, + "Z": passEndDepth, + "I": -r, + "F": op.vertFeed, + }, + ) + ) + op.commandlist.append( + Path.Command( + "G1", + {"X": x, "Y": y, "Z": passEndDepth, "F": op.vertFeed}, + ) + ) # one more circle at target depth to make sure center is cleared - op.commandlist.append(Path.Command("G2", {"X": x - (2*r), "Y": y, "Z": passEndDepth, "I": -r, "F": op.horizFeed})) - op.commandlist.append(Path.Command("G2", {"X": x, "Y": y, "Z": passEndDepth, "I": r, "F": op.horizFeed})) + op.commandlist.append( + Path.Command( + "G2", + { + "X": x - (2 * r), + "Y": y, + "Z": passEndDepth, + "I": -r, + "F": op.horizFeed, + }, + ) + ) + op.commandlist.append( + Path.Command( + "G2", + { + "X": x, + "Y": y, + "Z": passEndDepth, + "I": r, + "F": op.horizFeed, + }, + ) + ) # lx = x # ly = y else: # no helix entry # rapid move to clearance height - op.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) - op.commandlist.append(Path.Command("G0", {"X": region["StartPoint"][0], "Y": region["StartPoint"][1], "Z": obj.ClearanceHeight.Value})) + op.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) + ) + op.commandlist.append( + Path.Command( + "G0", + { + "X": region["StartPoint"][0], + "Y": region["StartPoint"][1], + "Z": obj.ClearanceHeight.Value, + }, + ) + ) # straight plunge to target depth - op.commandlist.append(Path.Command("G1", {"X": region["StartPoint"][0], "Y": region["StartPoint"][1], "Z": passEndDepth, "F": op.vertFeed})) + op.commandlist.append( + Path.Command( + "G1", + { + "X": region["StartPoint"][0], + "Y": region["StartPoint"][1], + "Z": passEndDepth, + "F": op.vertFeed, + }, + ) + ) lz = passEndDepth z = obj.ClearanceHeight.Value @@ -349,9 +596,13 @@ def GenerateGCode(op, obj, adaptiveResults, helixDiameter): if motionType == area.AdaptiveMotionType.Cutting: z = passEndDepth if z != lz: - op.commandlist.append(Path.Command("G1", {"Z": z, "F": op.vertFeed})) + op.commandlist.append( + Path.Command("G1", {"Z": z, "F": op.vertFeed}) + ) - op.commandlist.append(Path.Command("G1", {"X": x, "Y": y, "F": op.horizFeed})) + op.commandlist.append( + Path.Command("G1", {"X": x, "Y": y, "F": op.horizFeed}) + ) elif motionType == area.AdaptiveMotionType.LinkClear: z = passEndDepth + stepUp @@ -411,7 +662,7 @@ def Execute(op, obj): obj.Path = Path.Path("(Calculating...)") if FreeCAD.GuiUp: - #store old visibility state + # store old visibility state job = op.getJob(obj) oldObjVisibility = obj.ViewObject.Visibility oldJobVisibility = job.ViewObject.Visibility @@ -469,7 +720,7 @@ def Execute(op, obj): opType = area.AdaptiveOperationType.ProfilingInside keepToolDownRatio = 3.0 - if hasattr(obj, 'KeepToolDownRatio'): + if hasattr(obj, "KeepToolDownRatio"): keepToolDownRatio = float(obj.KeepToolDownRatio) # put here all properties that influence calculation of adaptive base paths, @@ -486,7 +737,7 @@ def Execute(op, obj): "forceInsideOut": obj.ForceInsideOut, "finishingProfile": obj.FinishingProfile, "keepToolDownRatio": keepToolDownRatio, - "stockToLeave": float(obj.StockToLeave) + "stockToLeave": float(obj.StockToLeave), } inputStateChanged = False @@ -502,12 +753,16 @@ def Execute(op, obj): # progress callback fn, if return true it will stop processing def progressFn(tpaths): if FreeCAD.GuiUp: - for path in tpaths: #path[0] contains the MotionType, #path[1] contains list of points + for ( + path + ) in ( + tpaths + ): # path[0] contains the MotionType, #path[1] contains list of points if path[0] == area.AdaptiveMotionType.Cutting: - sceneDrawPath(path[1],(0,0,1)) + sceneDrawPath(path[1], (0, 0, 1)) else: - sceneDrawPath(path[1],(1,0,1)) + sceneDrawPath(path[1], (1, 0, 1)) FreeCADGui.updateGui() @@ -533,22 +788,27 @@ def Execute(op, obj): # need to convert results to python object to be JSON serializable adaptiveResults = [] for result in results: - adaptiveResults.append({ - "HelixCenterPoint": result.HelixCenterPoint, - "StartPoint": result.StartPoint, - "AdaptivePaths": result.AdaptivePaths, - "ReturnMotionType": result.ReturnMotionType}) + adaptiveResults.append( + { + "HelixCenterPoint": result.HelixCenterPoint, + "StartPoint": result.StartPoint, + "AdaptivePaths": result.AdaptivePaths, + "ReturnMotionType": result.ReturnMotionType, + } + ) # GENERATE GenerateGCode(op, obj, adaptiveResults, helixDiameter) if not obj.StopProcessing: - PathLog.info("*** Done. Elapsed time: %f sec\n\n" % (time.time()-start)) + PathLog.info("*** Done. Elapsed time: %f sec\n\n" % (time.time() - start)) obj.AdaptiveOutputState = adaptiveResults obj.AdaptiveInputState = inputStateObject else: - PathLog.info("*** Processing cancelled (after: %f sec).\n\n" % (time.time()-start)) + PathLog.info( + "*** Processing cancelled (after: %f sec).\n\n" % (time.time() - start) + ) finally: if FreeCAD.GuiUp: @@ -558,12 +818,12 @@ def Execute(op, obj): def _get_working_edges(op, obj): - '''_get_working_edges(op, obj)... + """_get_working_edges(op, obj)... Compile all working edges from the Base Geometry selection (obj.Base) for the current operation. Additional modifications to selected region(face), such as extensions, should be placed within this function. - ''' + """ all_regions = list() edge_list = list() avoidFeatures = list() @@ -603,7 +863,7 @@ def _get_working_edges(op, obj): edge_list.append([discretize(e)]) # Apply regular Extensions - op.exts = [] # pylint: disable=attribute-defined-outside-init + op.exts = [] # pylint: disable=attribute-defined-outside-init for ext in extensions: if not ext.avoid: wire = ext.getWire() @@ -626,55 +886,250 @@ def _get_working_edges(op, obj): class PathAdaptive(PathOp.ObjectOp): def opFeatures(self, obj): - '''opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation. + """opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation. The default implementation returns "FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint" - Should be overwritten by subclasses.''' - return PathOp.FeatureTool | PathOp.FeatureBaseEdges | PathOp.FeatureDepths \ - | PathOp.FeatureFinishDepth | PathOp.FeatureStepDown | PathOp.FeatureHeights \ - | PathOp.FeatureBaseGeometry | PathOp.FeatureCoolant | PathOp.FeatureLocations + Should be overwritten by subclasses.""" + return ( + PathOp.FeatureTool + | PathOp.FeatureBaseEdges + | PathOp.FeatureDepths + | PathOp.FeatureFinishDepth + | PathOp.FeatureStepDown + | PathOp.FeatureHeights + | PathOp.FeatureBaseGeometry + | PathOp.FeatureCoolant + | PathOp.FeatureLocations + ) + + @classmethod + def propertyEnumerations(self, dataType="data"): + """helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + + # Enumeration lists for App::PropertyEnumeration properties + enums = { + "Side": [ + (translate("Path_Adaptive", "Outside"), "Outside"), + (translate("Path_Adaptive", "Inside"), "Inside"), + ], # this is the direction that the profile runs + "OperationType": [ + (translate("Path_Adaptive", "Clearing"), "Clearing"), + (translate("Path_Adaptive", "Profiling"), "Profiling"), + ], # side of profile that cutter is on in relation to direction of profile + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + PathLog.debug(enums) + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + PathLog.debug(data) + + return data def initOperation(self, obj): - '''initOperation(obj) ... implement to create additional properties. - Should be overwritten by subclasses.''' - obj.addProperty("App::PropertyEnumeration", "Side", "Adaptive", "Side of selected faces that tool should cut") - obj.Side = ['Outside', 'Inside'] # side of profile that cutter is on in relation to direction of profile + """initOperation(obj) ... implement to create additional properties. + Should be overwritten by subclasses.""" + obj.addProperty( + "App::PropertyEnumeration", + "Side", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Side of selected faces that tool should cut", + ), + ) + # obj.Side = [ + # "Outside", + # "Inside", + # ] # side of profile that cutter is on in relation to direction of profile - obj.addProperty("App::PropertyEnumeration", "OperationType", "Adaptive", "Type of adaptive operation") - obj.OperationType = ['Clearing', 'Profiling'] # side of profile that cutter is on in relation to direction of profile + obj.addProperty( + "App::PropertyEnumeration", + "OperationType", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Type of adaptive operation", + ), + ) + # obj.OperationType = [ + # "Clearing", + # "Profiling", + # ] # side of profile that cutter is on in relation to direction of profile - obj.addProperty("App::PropertyFloat", "Tolerance", "Adaptive", "Influences accuracy and performance") - obj.addProperty("App::PropertyPercent", "StepOver", "Adaptive", "Percent of cutter diameter to step over on each pass") - obj.addProperty("App::PropertyDistance", "LiftDistance", "Adaptive", "Lift distance for rapid moves") - obj.addProperty("App::PropertyDistance", "KeepToolDownRatio", "Adaptive", "Max length of keep tool down path compared to direct distance between points") - obj.addProperty("App::PropertyDistance", "StockToLeave", "Adaptive", "How much stock to leave (i.e. for finishing operation)") - # obj.addProperty("App::PropertyBool", "ProcessHoles", "Adaptive","Process holes as well as the face outline") + obj.addProperty( + "App::PropertyFloat", + "Tolerance", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Influences accuracy and performance", + ), + ) + obj.addProperty( + "App::PropertyPercent", + "StepOver", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Percent of cutter diameter to step over on each pass", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "LiftDistance", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Lift distance for rapid moves", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "KeepToolDownRatio", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Max length of keep tool down path compared to direct distance between points", + ), + ) + obj.addProperty( + "App::PropertyDistance", + "StockToLeave", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "How much stock to leave (i.e. for finishing operation)", + ), + ) + obj.addProperty( + "App::PropertyBool", + "ForceInsideOut", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Force plunging into material inside and clearing towards the edges", + ), + ) + obj.addProperty( + "App::PropertyBool", + "FinishingProfile", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "To take a finishing profile path at the end", + ), + ) + obj.addProperty( + "App::PropertyBool", + "Stopped", + "Adaptive", + QT_TRANSLATE_NOOP("App::Property", "Stop processing"), + ) + obj.setEditorMode("Stopped", 2) # hide this property - obj.addProperty("App::PropertyBool", "ForceInsideOut", "Adaptive", "Force plunging into material inside and clearing towards the edges") - obj.addProperty("App::PropertyBool", "FinishingProfile", "Adaptive", "To take a finishing profile path at the end") - obj.addProperty("App::PropertyBool", "Stopped", - "Adaptive", "Stop processing") - obj.setEditorMode('Stopped', 2) # hide this property + obj.addProperty( + "App::PropertyBool", + "StopProcessing", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Stop processing", + ), + ) + obj.setEditorMode("StopProcessing", 2) # hide this property - obj.addProperty("App::PropertyBool", "StopProcessing", - "Adaptive", "Stop processing") - obj.setEditorMode('StopProcessing', 2) # hide this property + obj.addProperty( + "App::PropertyBool", + "UseHelixArcs", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Use Arcs (G2) for helix ramp", + ), + ) - obj.addProperty("App::PropertyBool", "UseHelixArcs", "Adaptive", "Use Arcs (G2) for helix ramp") + obj.addProperty( + "App::PropertyPythonObject", + "AdaptiveInputState", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Internal input state", + ), + ) + obj.addProperty( + "App::PropertyPythonObject", + "AdaptiveOutputState", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Internal output state", + ), + ) + obj.setEditorMode("AdaptiveInputState", 2) # hide this property + obj.setEditorMode("AdaptiveOutputState", 2) # hide this property + obj.addProperty( + "App::PropertyAngle", + "HelixAngle", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Helix ramp entry angle (degrees)", + ), + ) + obj.addProperty( + "App::PropertyAngle", + "HelixConeAngle", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Helix cone angle (degrees)", + ), + ) + obj.addProperty( + "App::PropertyLength", + "HelixDiameterLimit", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Limit helix entry diameter, if limit larger than tool diameter or 0, tool diameter is used", + ), + ) - obj.addProperty("App::PropertyPythonObject", "AdaptiveInputState", - "Adaptive", "Internal input state") - obj.addProperty("App::PropertyPythonObject", "AdaptiveOutputState", - "Adaptive", "Internal output state") - obj.setEditorMode('AdaptiveInputState', 2) # hide this property - obj.setEditorMode('AdaptiveOutputState', 2) # hide this property - obj.addProperty("App::PropertyAngle", "HelixAngle", "Adaptive", "Helix ramp entry angle (degrees)") - obj.addProperty("App::PropertyAngle", "HelixConeAngle", "Adaptive", "Helix cone angle (degrees)") - obj.addProperty("App::PropertyLength", "HelixDiameterLimit", "Adaptive", "Limit helix entry diameter, if limit larger than tool diameter or 0, tool diameter is used") + obj.addProperty( + "App::PropertyBool", + "UseOutline", + "Adaptive", + QT_TRANSLATE_NOOP( + "App::Property", + "Uses the outline of the base geometry.", + ), + ) - obj.addProperty("App::PropertyBool", "UseOutline", "Adaptive", "Uses the outline of the base geometry.") + obj.addProperty( + "Part::PropertyPartShape", + "removalshape", + "Path", + QT_TRANSLATE_NOOP("App::Property", ""), + ) - obj.addProperty("Part::PropertyPartShape", "removalshape", "Path", "") - obj.setEditorMode('removalshape', 2) # hide + for n in self.propertyEnumerations(): + setattr(obj, n[0], n[1]) + + obj.setEditorMode("removalshape", 2) # hide FeatureExtensions.initialize_properties(obj) @@ -701,43 +1156,66 @@ class PathAdaptive(PathOp.ObjectOp): FeatureExtensions.set_default_property_values(obj, job) def opExecute(self, obj): - '''opExecute(obj) ... called whenever the receiver needs to be recalculated. + """opExecute(obj) ... called whenever the receiver needs to be recalculated. See documentation of execute() for a list of base functionality provided. - Should be overwritten by subclasses.''' + Should be overwritten by subclasses.""" self.pathArray = _get_working_edges(self, obj) Execute(self, obj) def opOnDocumentRestored(self, obj): - if not hasattr(obj, 'HelixConeAngle'): - obj.addProperty("App::PropertyAngle", "HelixConeAngle", "Adaptive", "Helix cone angle (degrees)") + if not hasattr(obj, "HelixConeAngle"): + obj.addProperty( + "App::PropertyAngle", + "HelixConeAngle", + "Adaptive", + "Helix cone angle (degrees)", + ) if not hasattr(obj, "UseOutline"): - obj.addProperty("App::PropertyBool", - "UseOutline", - "Adaptive", - "Uses the outline of the base geometry.") + obj.addProperty( + "App::PropertyBool", + "UseOutline", + "Adaptive", + "Uses the outline of the base geometry.", + ) if not hasattr(obj, "removalshape"): obj.addProperty("Part::PropertyPartShape", "removalshape", "Path", "") - obj.setEditorMode('removalshape', 2) # hide + obj.setEditorMode("removalshape", 2) # hide FeatureExtensions.initialize_properties(obj) + + # Eclass def SetupProperties(): - setup = ["Side", "OperationType", "Tolerance", "StepOver", - "LiftDistance", "KeepToolDownRatio", "StockToLeave", - "ForceInsideOut", "FinishingProfile", "Stopped", - "StopProcessing", "UseHelixArcs", "AdaptiveInputState", - "AdaptiveOutputState", "HelixAngle", "HelixConeAngle", - "HelixDiameterLimit", "UseOutline"] + setup = [ + "Side", + "OperationType", + "Tolerance", + "StepOver", + "LiftDistance", + "KeepToolDownRatio", + "StockToLeave", + "ForceInsideOut", + "FinishingProfile", + "Stopped", + "StopProcessing", + "UseHelixArcs", + "AdaptiveInputState", + "AdaptiveOutputState", + "HelixAngle", + "HelixConeAngle", + "HelixDiameterLimit", + "UseOutline", + ] return setup def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a Adaptive operation.''' + """Create(name) ... Creates and returns a Adaptive operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = PathAdaptive(obj, name, parentJob) diff --git a/src/Mod/Path/PathScripts/PathAdaptiveGui.py b/src/Mod/Path/PathScripts/PathAdaptiveGui.py index daa2335926..113fc1d91d 100644 --- a/src/Mod/Path/PathScripts/PathAdaptiveGui.py +++ b/src/Mod/Path/PathScripts/PathAdaptiveGui.py @@ -22,142 +22,41 @@ # *************************************************************************** import PathScripts.PathOpGui as PathOpGui -from PySide import QtCore, QtGui +from PySide import QtCore import PathScripts.PathAdaptive as PathAdaptive import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui +import FreeCADGui class TaskPanelOpPage(PathOpGui.TaskPanelPage): - def initPage(self, obj): - self.setTitle("Adaptive path operation") - def getForm(self): - form = QtGui.QWidget() - layout = QtGui.QVBoxLayout() - formLayout = QtGui.QFormLayout() + """getForm() ... return UI""" - # tool controller - form.ToolController = QtGui.QComboBox() - formLayout.addRow(QtGui.QLabel("Tool Controller"), form.ToolController) + form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpAdaptiveEdit.ui") + comboToPropertyMap = [("Side", "Side"), ("OperationType", "OperationType")] - # Coolant controller - form.coolantController = QtGui.QComboBox() - formLayout.addRow(QtGui.QLabel("Coolant Mode"), form.coolantController) + enumTups = PathAdaptive.PathAdaptive.propertyEnumerations(dataType="raw") - # cut region - form.Side = QtGui.QComboBox() - form.Side.addItem("Inside") - form.Side.addItem("Outside") - form.Side.setToolTip("Cut inside or outside of the selected shapes") - formLayout.addRow(QtGui.QLabel("Cut Region"), form.Side) - - # operation type - form.OperationType = QtGui.QComboBox() - form.OperationType.addItem("Clearing") - form.OperationType.addItem("Profiling") - form.OperationType.setToolTip("Type of adaptive operation") - formLayout.addRow(QtGui.QLabel("Operation Type"), form.OperationType) - - # step over - form.StepOver = QtGui.QSpinBox() - form.StepOver.setMinimum(15) - form.StepOver.setMaximum(75) - form.StepOver.setSingleStep(1) - form.StepOver.setValue(25) - form.StepOver.setToolTip("Optimal value for tool stepover") - formLayout.addRow(QtGui.QLabel("Step Over Percent"), form.StepOver) - - # tolerance - form.Tolerance = QtGui.QSlider(QtCore.Qt.Horizontal) - form.Tolerance.setMinimum(5) - form.Tolerance.setMaximum(15) - form.Tolerance.setTickInterval(1) - form.Tolerance.setValue(10) - form.Tolerance.setTickPosition(QtGui.QSlider.TicksBelow) - form.Tolerance.setToolTip("Influences calculation performance vs stability and accuracy.") - formLayout.addRow(QtGui.QLabel("Accuracy vs Performance"), form.Tolerance) - - # helix angle - form.HelixAngle = QtGui.QDoubleSpinBox() - form.HelixAngle.setMinimum(1) - form.HelixAngle.setMaximum(89) - form.HelixAngle.setSingleStep(1) - form.HelixAngle.setValue(5) - form.HelixAngle.setToolTip("Angle of the helix ramp entry") - formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"), form.HelixAngle) - - # helix cone angle - form.HelixConeAngle = QtGui.QDoubleSpinBox() - form.HelixConeAngle.setMinimum(0) - form.HelixConeAngle.setMaximum(6) - form.HelixConeAngle.setSingleStep(1) - form.HelixConeAngle.setValue(0) - form.HelixConeAngle.setToolTip("Angle of the helix entry cone") - formLayout.addRow(QtGui.QLabel("Helix Cone Angle"), form.HelixConeAngle) - - # helix diam. limit - form.HelixDiameterLimit = QtGui.QDoubleSpinBox() - form.HelixDiameterLimit.setMinimum(0.0) - form.HelixDiameterLimit.setMaximum(90) - form.HelixDiameterLimit.setSingleStep(0.1) - form.HelixDiameterLimit.setValue(0) - form.HelixDiameterLimit.setToolTip("If >0 it limits the helix ramp diameter\notherwise the 75 percent of tool diameter is used as helix diameter") - formLayout.addRow(QtGui.QLabel("Helix Max Diameter"), form.HelixDiameterLimit) - - # lift distance - form.LiftDistance = QtGui.QDoubleSpinBox() - form.LiftDistance.setMinimum(0.0) - form.LiftDistance.setMaximum(1000) - form.LiftDistance.setSingleStep(0.1) - form.LiftDistance.setValue(1.0) - form.LiftDistance.setToolTip("How much to lift the tool up during the rapid linking moves over cleared regions.\nIf linking path is not clear tool is raised to clearence height.") - formLayout.addRow(QtGui.QLabel("Lift Distance"), form.LiftDistance) - - # KeepToolDownRatio - form.KeepToolDownRatio = QtGui.QDoubleSpinBox() - form.KeepToolDownRatio.setMinimum(1.0) - form.KeepToolDownRatio.setMaximum(10) - form.KeepToolDownRatio.setSingleStep(1) - form.KeepToolDownRatio.setValue(3.0) - form.KeepToolDownRatio.setToolTip("Max length of keep-tool-down linking path compared to direct distance between points.\nIf exceeded link will be done by raising the tool to clearence height.") - formLayout.addRow(QtGui.QLabel("Keep Tool Down Ratio"), form.KeepToolDownRatio) - - # stock to leave - form.StockToLeave = QtGui.QDoubleSpinBox() - form.StockToLeave.setMinimum(0.0) - form.StockToLeave.setMaximum(1000) - form.StockToLeave.setSingleStep(0.1) - form.StockToLeave.setValue(0) - form.StockToLeave.setToolTip("How much material to leave (i.e. for finishing operation)") - formLayout.addRow(QtGui.QLabel("Stock to Leave"), form.StockToLeave) - - # Force inside out - form.ForceInsideOut = QtGui.QCheckBox() - form.ForceInsideOut.setChecked(True) - formLayout.addRow(QtGui.QLabel("Force Clearing Inside-Out"), form.ForceInsideOut) - - # Finishing profile - form.FinishingProfile = QtGui.QCheckBox() - form.FinishingProfile.setChecked(True) - formLayout.addRow(QtGui.QLabel("Finishing Profile"), form.FinishingProfile) - - # Use outline checkbox - form.useOutline = QtGui.QCheckBox() - form.useOutline.setChecked(False) - formLayout.addRow(QtGui.QLabel("Use outline"), form.useOutline) - - layout.addLayout(formLayout) - - # stop button - form.StopButton = QtGui.QPushButton("Stop") - form.StopButton.setCheckable(True) - layout.addWidget(form.StopButton) - - form.setLayout(layout) + self.populateCombobox(form, enumTups, comboToPropertyMap) return form + def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): + """fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations + ** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol. + Args: + form = UI form + enumTups = list of (translated_text, data_string) tuples + comboBoxesPropertyMap = list of (translated_text, data_string) tuples + """ + # Load appropriate enumerations in each combobox + for cb, prop in comboBoxesPropertyMap: + box = getattr(form, cb) # Get the combobox + box.clear() # clear the combobox + for text, data in enumTups[prop]: # load enumerations + box.addItem(text, data) + def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] # signals.append(self.form.button.clicked) signals.append(self.form.Side.currentIndexChanged) @@ -189,10 +88,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.HelixConeAngle.setValue(obj.HelixConeAngle) self.form.HelixDiameterLimit.setValue(obj.HelixDiameterLimit) self.form.LiftDistance.setValue(obj.LiftDistance) - if hasattr(obj, 'KeepToolDownRatio'): + if hasattr(obj, "KeepToolDownRatio"): self.form.KeepToolDownRatio.setValue(obj.KeepToolDownRatio) - if hasattr(obj, 'StockToLeave'): + if hasattr(obj, "StockToLeave"): self.form.StockToLeave.setValue(obj.StockToLeave) # self.form.ProcessHoles.setChecked(obj.ProcessHoles) @@ -202,17 +101,17 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.setupToolController(obj, self.form.ToolController) self.setupCoolant(obj, self.form.coolantController) self.form.StopButton.setChecked(obj.Stopped) - obj.setEditorMode('AdaptiveInputState', 2) # hide this property - obj.setEditorMode('AdaptiveOutputState', 2) # hide this property - obj.setEditorMode('StopProcessing', 2) # hide this property - obj.setEditorMode('Stopped', 2) # hide this property + obj.setEditorMode("AdaptiveInputState", 2) # hide this property + obj.setEditorMode("AdaptiveOutputState", 2) # hide this property + obj.setEditorMode("StopProcessing", 2) # hide this property + obj.setEditorMode("Stopped", 2) # hide this property def getFields(self, obj): - if obj.Side != str(self.form.Side.currentText()): - obj.Side = str(self.form.Side.currentText()) + if obj.Side != str(self.form.Side.currentData()): + obj.Side = str(self.form.Side.currentData()) - if obj.OperationType != str(self.form.OperationType.currentText()): - obj.OperationType = str(self.form.OperationType.currentText()) + if obj.OperationType != str(self.form.OperationType.currentData()): + obj.OperationType = str(self.form.OperationType.currentData()) obj.StepOver = self.form.StepOver.value() obj.Tolerance = 1.0 * self.form.Tolerance.value() / 100.0 @@ -221,37 +120,41 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): obj.HelixDiameterLimit = self.form.HelixDiameterLimit.value() obj.LiftDistance = self.form.LiftDistance.value() - if hasattr(obj, 'KeepToolDownRatio'): + if hasattr(obj, "KeepToolDownRatio"): obj.KeepToolDownRatio = self.form.KeepToolDownRatio.value() - if hasattr(obj, 'StockToLeave'): + if hasattr(obj, "StockToLeave"): obj.StockToLeave = self.form.StockToLeave.value() obj.ForceInsideOut = self.form.ForceInsideOut.isChecked() obj.FinishingProfile = self.form.FinishingProfile.isChecked() obj.UseOutline = self.form.useOutline.isChecked() obj.Stopped = self.form.StopButton.isChecked() - if(obj.Stopped): + if obj.Stopped: self.form.StopButton.setChecked(False) # reset the button obj.StopProcessing = True self.updateToolController(obj, self.form.ToolController) self.updateCoolant(obj, self.form.coolantController) - obj.setEditorMode('AdaptiveInputState', 2) # hide this property - obj.setEditorMode('AdaptiveOutputState', 2) # hide this property - obj.setEditorMode('StopProcessing', 2) # hide this property - obj.setEditorMode('Stopped', 2) # hide this property + obj.setEditorMode("AdaptiveInputState", 2) # hide this property + obj.setEditorMode("AdaptiveOutputState", 2) # hide this property + obj.setEditorMode("StopProcessing", 2) # hide this property + obj.setEditorMode("Stopped", 2) # hide this property def taskPanelBaseLocationPage(self, obj, features): - if not hasattr(self, 'extensionsPanel'): - self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage(obj, features) # pylint: disable=attribute-defined-outside-init + if not hasattr(self, "extensionsPanel"): + self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage( + obj, features + ) # pylint: disable=attribute-defined-outside-init return self.extensionsPanel -Command = PathOpGui.SetupOperation('Adaptive', - PathAdaptive.Create, - TaskPanelOpPage, - 'Path_Adaptive', - QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive"), - QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive clearing and profiling"), - PathAdaptive.SetupProperties) +Command = PathOpGui.SetupOperation( + "Adaptive", + PathAdaptive.Create, + TaskPanelOpPage, + "Path_Adaptive", + QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive"), + QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive clearing and profiling"), + PathAdaptive.SetupProperties, +) diff --git a/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py b/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py index 5b9151587b..4a9b3903dc 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py +++ b/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py @@ -24,37 +24,46 @@ import FreeCADGui import PathScripts.PathPreferences as PathPreferences import PySide -# Qt translation handling -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) class AdvancedPreferencesPage: def __init__(self, parent=None): - self.form = FreeCADGui.PySideUic.loadUi(':preferences/Advanced.ui') + self.form = FreeCADGui.PySideUic.loadUi(":preferences/Advanced.ui") self.form.WarningSuppressAllSpeeds.stateChanged.connect(self.updateSelection) self.form.EnableAdvancedOCLFeatures.stateChanged.connect(self.updateSelection) def saveSettings(self): PathPreferences.setPreferencesAdvanced( - self.form.EnableAdvancedOCLFeatures.isChecked(), - self.form.WarningSuppressAllSpeeds.isChecked(), - self.form.WarningSuppressRapidSpeeds.isChecked(), - self.form.WarningSuppressSelectionMode.isChecked(), - self.form.WarningSuppressOpenCamLib.isChecked()) + self.form.EnableAdvancedOCLFeatures.isChecked(), + self.form.WarningSuppressAllSpeeds.isChecked(), + self.form.WarningSuppressRapidSpeeds.isChecked(), + self.form.WarningSuppressSelectionMode.isChecked(), + self.form.WarningSuppressOpenCamLib.isChecked(), + ) def loadSettings(self): - self.form.WarningSuppressAllSpeeds.setChecked(PathPreferences.suppressAllSpeedsWarning()) - self.form.WarningSuppressRapidSpeeds.setChecked(PathPreferences.suppressRapidSpeedsWarning(False)) - self.form.WarningSuppressSelectionMode.setChecked(PathPreferences.suppressSelectionModeWarning()) - self.form.EnableAdvancedOCLFeatures.setChecked(PathPreferences.advancedOCLFeaturesEnabled()) - self.form.WarningSuppressOpenCamLib.setChecked(PathPreferences.suppressOpenCamLibWarning()) + self.form.WarningSuppressAllSpeeds.setChecked( + PathPreferences.suppressAllSpeedsWarning() + ) + self.form.WarningSuppressRapidSpeeds.setChecked( + PathPreferences.suppressRapidSpeedsWarning(False) + ) + self.form.WarningSuppressSelectionMode.setChecked( + PathPreferences.suppressSelectionModeWarning() + ) + self.form.EnableAdvancedOCLFeatures.setChecked( + PathPreferences.advancedOCLFeaturesEnabled() + ) + self.form.WarningSuppressOpenCamLib.setChecked( + PathPreferences.suppressOpenCamLibWarning() + ) self.updateSelection() def updateSelection(self, state=None): - self.form.WarningSuppressOpenCamLib.setEnabled(self.form.EnableAdvancedOCLFeatures.isChecked()) + self.form.WarningSuppressOpenCamLib.setEnabled( + self.form.EnableAdvancedOCLFeatures.isChecked() + ) if self.form.WarningSuppressAllSpeeds.isChecked(): self.form.WarningSuppressRapidSpeeds.setChecked(True) self.form.WarningSuppressRapidSpeeds.setEnabled(False) else: self.form.WarningSuppressRapidSpeeds.setEnabled(True) - diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 2c640aaca2..ef561f6361 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -402,7 +402,7 @@ def select(op): opsel["Vcarve"] = vcarveselect opsel["Probe"] = probeselect opsel["Custom"] = customselect - opsel["Thread Milling"] = drillselect + opsel["ThreadMilling"] = drillselect opsel["TurnFace"] = turnselect opsel["TurnProfile"] = turnselect opsel["TurnPartoff"] = turnselect diff --git a/src/Mod/Path/PathScripts/PathThreadMilling.py b/src/Mod/Path/PathScripts/PathThreadMilling.py index 5d89c285b2..3dc5eb02c3 100644 --- a/src/Mod/Path/PathScripts/PathThreadMilling.py +++ b/src/Mod/Path/PathScripts/PathThreadMilling.py @@ -28,26 +28,25 @@ import PathScripts.PathCircularHoleBase as PathCircularHoleBase import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp -import PathScripts.PathUtils as PathUtils import math - -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Path Thread Milling Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Path thread milling operation." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate -def radiiInternal(majorDia, minorDia, toolDia, toolCrest = None): - '''internlThreadRadius(majorDia, minorDia, toolDia, toolCrest) ... returns the maximum radius for thread.''' +def radiiInternal(majorDia, minorDia, toolDia, toolCrest=None): + """internlThreadRadius(majorDia, minorDia, toolDia, toolCrest) ... returns the maximum radius for thread.""" PathLog.track(majorDia, minorDia, toolDia, toolCrest) if toolCrest is None: toolCrest = 0.0 @@ -57,20 +56,25 @@ def radiiInternal(majorDia, minorDia, toolDia, toolCrest = None): # - The major diameter is 3/8 * H bigger than the pitch diameter # Since we already have the outer diameter it's simpler to just add 1/8 * H # to get the outer tip of the thread. - H = ((majorDia - minorDia) / 2.0 ) * 1.6 # (D - d)/2 = 5/8 * H + H = ((majorDia - minorDia) / 2.0) * 1.6 # (D - d)/2 = 5/8 * H outerTip = majorDia / 2.0 + H / 8.0 # Compensate for the crest of the tool - toolTip = outerTip - toolCrest * 0.8660254037844386 # math.sqrt(3)/2 ... 60deg triangle height + toolTip = ( + outerTip - toolCrest * 0.8660254037844386 + ) # math.sqrt(3)/2 ... 60deg triangle height return ((minorDia - toolDia) / 2.0, toolTip - toolDia / 2.0) -def threadPasses(count, radii, majorDia, minorDia, toolDia, toolCrest = None): + +def threadPasses(count, radii, majorDia, minorDia, toolDia, toolCrest=None): PathLog.track(count, radii, majorDia, minorDia, toolDia, toolCrest) minor, major = radii(majorDia, minorDia, toolDia, toolCrest) - dr = float(major - minor) / count + dr = float(major - minor) / count return [major - dr * (count - (i + 1)) for i in range(count)] + class _InternalThread(object): - '''Helper class for dealing with different thread types''' + """Helper class for dealing with different thread types""" + def __init__(self, cmd, zStart, zFinal, pitch): self.cmd = cmd if zStart < zFinal: @@ -82,33 +86,34 @@ class _InternalThread(object): self.zFinal = zFinal def overshoots(self, z): - '''overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds''' + """overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds""" if self.pitch < 0: return z + self.hPitch < self.zFinal return z + self.hPitch > self.zFinal def adjustX(self, x, dx): - '''adjustX(x, dx) ... move x by dx, the direction depends on the thread settings''' + """adjustX(x, dx) ... move x by dx, the direction depends on the thread settings""" if self.isG3() == (self.pitch > 0): return x + dx return x - dx def adjustY(self, y, dy): - '''adjustY(y, dy) ... move y by dy, the direction depends on the thread settings''' + """adjustY(y, dy) ... move y by dy, the direction depends on the thread settings""" if self.isG3(): return y - dy return y - dy def isG3(self): - '''isG3() ... returns True if this is a G3 command''' - return self.cmd in ['G3', 'G03', 'g3', 'g03'] + """isG3() ... returns True if this is a G3 command""" + return self.cmd in ["G3", "G03", "g3", "g03"] def isUp(self): - '''isUp() ... returns True if the thread goes from the bottom up''' + """isUp() ... returns True if the thread goes from the bottom up""" return self.pitch > 0 + def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut): - '''internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread''' + """internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread""" thread = _InternalThread(cmd, zStart, zFinal, pitch) yMin = loc.y - radius @@ -118,30 +123,29 @@ def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut): # at this point the tool is at a safe height (depending on the previous thread), so we can move # into position first, and then drop to the start height. If there is any material in the way this # op hasn't been setup properly. - path.append(Path.Command('G0', {'X': loc.x, 'Y': loc.y})) - path.append(Path.Command('G0', {'Z': thread.zStart})) + path.append(Path.Command("G0", {"X": loc.x, "Y": loc.y})) + path.append(Path.Command("G0", {"Z": thread.zStart})) if leadInOut: - path.append(Path.Command(thread.cmd, {'Y': yMax, 'J': (yMax - loc.y) / 2})) + path.append(Path.Command(thread.cmd, {"Y": yMax, "J": (yMax - loc.y) / 2})) else: - path.append(Path.Command('G1', {'Y': yMax})) + path.append(Path.Command("G1", {"Y": yMax})) z = thread.zStart r = -radius i = 0 while True: - z = thread.zStart + i*thread.hPitch + z = thread.zStart + i * thread.hPitch if thread.overshoots(z): break if 0 == (i & 0x01): y = yMin else: y = yMax - path.append(Path.Command(thread.cmd, {'Y': y, 'Z': z + thread.hPitch, 'J': r})) + path.append(Path.Command(thread.cmd, {"Y": y, "Z": z + thread.hPitch, "J": r})) r = -r i = i + 1 - - z = thread.zStart + i*thread.hPitch + z = thread.zStart + i * thread.hPitch if PathGeom.isRoughly(z, thread.zFinal): x = loc.x else: @@ -151,48 +155,183 @@ def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut): dx = math.sin(k * math.pi) y = thread.adjustY(loc.y, r * dy) x = thread.adjustX(loc.x, r * dx) - path.append(Path.Command(thread.cmd, {'X': x, 'Y': y, 'Z': thread.zFinal, 'J': r})) + path.append( + Path.Command(thread.cmd, {"X": x, "Y": y, "Z": thread.zFinal, "J": r}) + ) if leadInOut: - path.append(Path.Command(thread.cmd, {'X': loc.x, 'Y': loc.y, 'I': (loc.x - x) / 2, 'J': (loc.y - y) / 2})) + path.append( + Path.Command( + thread.cmd, + {"X": loc.x, "Y": loc.y, "I": (loc.x - x) / 2, "J": (loc.y - y) / 2}, + ) + ) else: - path.append(Path.Command('G1', {'X': loc.x, 'Y': loc.y})) + path.append(Path.Command("G1", {"X": loc.x, "Y": loc.y})) return path -class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): - '''Proxy object for thread milling operation.''' - LeftHand = 'LeftHand' - RightHand = 'RightHand' - ThreadTypeCustom = 'Custom' - ThreadTypeMetricInternal = 'Metric - internal' - ThreadTypeImperialInternal = 'Imperial - internal' - DirectionClimb = 'Climb' - DirectionConventional = 'Conventional' +class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): + """Proxy object for thread milling operation.""" + + LeftHand = "LeftHand" + RightHand = "RightHand" + ThreadTypeCustom = "Custom" + ThreadTypeMetricInternal = "MetricInternal" + ThreadTypeImperialInternal = "ImperialInternal" + DirectionClimb = "Climb" + DirectionConventional = "Conventional" ThreadOrientations = [LeftHand, RightHand] - ThreadTypes = [ThreadTypeCustom, ThreadTypeMetricInternal, ThreadTypeImperialInternal] - Directions = [DirectionClimb, DirectionConventional] + ThreadTypes = [ + ThreadTypeCustom, + ThreadTypeMetricInternal, + ThreadTypeImperialInternal, + ] + Directions = [DirectionClimb, DirectionConventional] + + @classmethod + def propertyEnumerations(self, dataType="data"): + """helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + + # Enumeration lists for App::PropertyEnumeration properties + enums = { + "ThreadType": [ + (translate("Path_ThreadMilling", "Custom"), "Custom"), + (translate("Path_ThreadMilling", "Metric Internal"), "MetricInternal"), + ( + translate("Path_ThreadMilling", "Imperial Internal"), + "ImperialInternal", + ), + ], # this is the direction that the profile runs + "ThreadOrientation": [ + (translate("Path_ThreadMilling", "LeftHand"), "LeftHand"), + (translate("Path_ThreadMilling", "RightHand"), "RightHand"), + ], # side of profile that cutter is on in relation to direction of profile + "Direction": [ + (translate("Path_ThreadMilling", "Climb"), "Climb"), + (translate("Path_ThreadMilling", "Conventional"), "Conventional"), + ], # side of profile that cutter is on in relation to direction of profile + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + PathLog.debug(enums) + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + PathLog.debug(data) + + return data def circularHoleFeatures(self, obj): return PathOp.FeatureBaseGeometry def initCircularHoleOperation(self, obj): - obj.addProperty("App::PropertyEnumeration", "ThreadOrientation", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread orientation")) - obj.ThreadOrientation = self.ThreadOrientations - obj.addProperty("App::PropertyEnumeration", "ThreadType", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Currently only internal")) - obj.ThreadType = self.ThreadTypes - obj.addProperty("App::PropertyString", "ThreadName", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Defines which standard thread was chosen")) - obj.addProperty("App::PropertyLength", "MajorDiameter", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's major diameter")) - obj.addProperty("App::PropertyLength", "MinorDiameter", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's minor diameter")) - obj.addProperty("App::PropertyLength", "Pitch", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's pitch - used for metric threads")) - obj.addProperty("App::PropertyInteger", "TPI", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's TPI (turns per inch) - used for imperial threads")) - obj.addProperty("App::PropertyInteger", "ThreadFit", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set how many passes are used to cut the thread")) - obj.addProperty("App::PropertyInteger", "Passes", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set how many passes are used to cut the thread")) - obj.addProperty("App::PropertyEnumeration", "Direction", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Direction of thread cutting operation")) - obj.addProperty("App::PropertyBool", "LeadInOut", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set to True to get lead in and lead out arcs at the start and end of the thread cut")) - obj.addProperty("App::PropertyLink", "ClearanceOp", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Operation to clear the inside of the thread")) - obj.Direction = self.Directions + obj.addProperty( + "App::PropertyEnumeration", + "ThreadOrientation", + "Thread", + QT_TRANSLATE_NOOP("App::Property", "Set thread orientation"), + ) + # obj.ThreadOrientation = self.ThreadOrientations + obj.addProperty( + "App::PropertyEnumeration", + "ThreadType", + "Thread", + QT_TRANSLATE_NOOP("App::Property", "Currently only internal"), + ) + # obj.ThreadType = self.ThreadTypes + obj.addProperty( + "App::PropertyString", + "ThreadName", + "Thread", + QT_TRANSLATE_NOOP( + "App::Property", "Defines which standard thread was chosen" + ), + ) + obj.addProperty( + "App::PropertyLength", + "MajorDiameter", + "Thread", + QT_TRANSLATE_NOOP("App::Property", "Set thread's major diameter"), + ) + obj.addProperty( + "App::PropertyLength", + "MinorDiameter", + "Thread", + QT_TRANSLATE_NOOP("App::Property", "Set thread's minor diameter"), + ) + obj.addProperty( + "App::PropertyLength", + "Pitch", + "Thread", + QT_TRANSLATE_NOOP( + "App::Property", "Set thread's pitch - used for metric threads" + ), + ) + obj.addProperty( + "App::PropertyInteger", + "TPI", + "Thread", + QT_TRANSLATE_NOOP( + "App::Property", + "Set thread's TPI (turns per inch) - used for imperial threads", + ), + ) + obj.addProperty( + "App::PropertyInteger", + "ThreadFit", + "Thread", + QT_TRANSLATE_NOOP( + "App::Property", "Set how many passes are used to cut the thread" + ), + ) + obj.addProperty( + "App::PropertyInteger", + "Passes", + "Operation", + QT_TRANSLATE_NOOP( + "App::Property", "Set how many passes are used to cut the thread" + ), + ) + obj.addProperty( + "App::PropertyEnumeration", + "Direction", + "Operation", + QT_TRANSLATE_NOOP("App::Property", "Direction of thread cutting operation"), + ) + obj.addProperty( + "App::PropertyBool", + "LeadInOut", + "Operation", + QT_TRANSLATE_NOOP( + "App::Property", + "Set to True to get lead in and lead out arcs at the start and end of the thread cut", + ), + ) + obj.addProperty( + "App::PropertyLink", + "ClearanceOp", + "Operation", + QT_TRANSLATE_NOOP( + "App::Property", "Operation to clear the inside of the thread" + ), + ) + + for n in self.propertyEnumerations(): + setattr(obj, n[0], n[1]) def threadStartDepth(self, obj): if obj.ThreadOrientation == self.RightHand: @@ -225,25 +364,25 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): PathLog.track(obj.Label) if obj.ThreadOrientation == self.RightHand: if obj.Direction == self.DirectionClimb: - PathLog.track(obj.Label, 'G2') - return 'G2' - PathLog.track(obj.Label, 'G3') - return 'G3' + PathLog.track(obj.Label, "G2") + return "G2" + PathLog.track(obj.Label, "G3") + return "G3" if obj.Direction == self.DirectionClimb: - PathLog.track(obj.Label, 'G3') - return 'G3' - PathLog.track(obj.Label, 'G2') - return 'G2' + PathLog.track(obj.Label, "G3") + return "G3" + PathLog.track(obj.Label, "G2") + return "G2" def threadSetup(self, obj): # the thing to remember is that Climb, for an internal thread must always be G3 if obj.Direction == self.DirectionClimb: if obj.ThreadOrientation == self.RightHand: - return ('G3', obj.FinalDepth.Value, obj.StartDepth.Value) - return ('G3', obj.StartDepth.Value, obj.FinalDepth.Value) + return ("G3", obj.FinalDepth.Value, obj.StartDepth.Value) + return ("G3", obj.StartDepth.Value, obj.FinalDepth.Value) if obj.ThreadOrientation == self.RightHand: - return ('G2', obj.StartDepth.Value, obj.FinalDepth.Value) - return ('G2', obj.FinalDepth.Value, obj.StartDepth.Value) + return ("G2", obj.StartDepth.Value, obj.FinalDepth.Value) + return ("G2", obj.FinalDepth.Value, obj.StartDepth.Value) def threadPassRadii(self, obj): PathLog.track(obj.Label) @@ -251,7 +390,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): rMinor = (obj.MinorDiameter.Value - self.tool.Diameter) / 2.0 if obj.Passes < 1: obj.Passes = 1 - rPass = (rMajor - rMinor) / obj.Passes + rPass = (rMajor - rMinor) / obj.Passes passes = [rMajor] for i in range(1, obj.Passes): passes.append(rMajor - rPass * i) @@ -260,20 +399,33 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): def executeThreadMill(self, obj, loc, gcode, zStart, zFinal, pitch): PathLog.track(obj.Label, loc, gcode, zStart, zFinal, pitch) - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + self.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}) + ) - for radius in threadPasses(obj.Passes, radiiInternal, obj.MajorDiameter.Value, obj.MinorDiameter.Value, float(self.tool.Diameter), float(self.tool.Crest)): - commands = internalThreadCommands(loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut) + for radius in threadPasses( + obj.Passes, + radiiInternal, + obj.MajorDiameter.Value, + obj.MinorDiameter.Value, + float(self.tool.Diameter), + float(self.tool.Crest), + ): + commands = internalThreadCommands( + loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut + ) for cmd in commands: p = cmd.Parameters - if cmd.Name in ['G0']: - p.update({'F': self.vertRapid}) - if cmd.Name in ['G1', 'G2', 'G3']: - p.update({'F': self.horizFeed}) + if cmd.Name in ["G0"]: + p.update({"F": self.vertRapid}) + if cmd.Name in ["G1", "G2", "G3"]: + p.update({"F": self.horizFeed}) cmd.Parameters = p self.commandlist.extend(commands) - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + self.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}) + ) def circularHoleExecute(self, obj, holes): PathLog.track() @@ -290,11 +442,17 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): # rapid to clearance height for loc in holes: - self.executeThreadMill(obj, FreeCAD.Vector(loc['x'], loc['y'], 0), cmd, zStart, zFinal, pitch) + self.executeThreadMill( + obj, + FreeCAD.Vector(loc["x"], loc["y"], 0), + cmd, + zStart, + zFinal, + pitch, + ) else: PathLog.error("No suitable Tool found for thread milling operation") - def opSetDefaultValues(self, obj, job): obj.ThreadOrientation = self.RightHand obj.ThreadType = self.ThreadTypeMetricInternal @@ -306,8 +464,8 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): obj.LeadInOut = True def isToolSupported(self, obj, tool): - '''Thread milling only supports thread milling cutters.''' - return hasattr(tool, 'Diameter') and hasattr(tool, 'Crest') + """Thread milling only supports thread milling cutters.""" + return hasattr(tool, "Diameter") and hasattr(tool, "Crest") def SetupProperties(): @@ -327,11 +485,10 @@ def SetupProperties(): def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a thread milling operation.''' + """Create(name) ... Creates and returns a thread milling operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectThreadMilling(obj, name, parentJob) if obj.Proxy: obj.Proxy.findAllHoles(obj) return obj - diff --git a/src/Mod/Path/PathScripts/PathThreadMillingGui.py b/src/Mod/Path/PathScripts/PathThreadMillingGui.py index ff3bcd904f..1514f951e1 100644 --- a/src/Mod/Path/PathScripts/PathThreadMillingGui.py +++ b/src/Mod/Path/PathScripts/PathThreadMillingGui.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathThreadMilling as PathThreadMilling import PathScripts.PathGui as PathGui @@ -30,6 +30,9 @@ import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui import csv +from PySide.QtCore import QT_TRANSLATE_NOOP + + from PySide import QtCore __title__ = "Path Thread Milling Operation UI." @@ -37,52 +40,88 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "UI and Command for Path Thread Milling Operation." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +translate = FreeCAD.Qt.translate -def setupCombo(combo, selections): - combo.clear() - for item in selections: - combo.addItem(item) def fillThreads(combo, dataFile): combo.blockSignals(True) combo.clear() - with open("{}Mod/Path/Data/Threads/{}.csv".format(FreeCAD.getHomePath(), dataFile)) as fp: + with open( + "{}Mod/Path/Data/Threads/{}.csv".format(FreeCAD.getHomePath(), dataFile) + ) as fp: reader = csv.DictReader(fp) for row in reader: - combo.addItem(row['name'], row) + combo.addItem(row["name"], row) combo.setEnabled(True) combo.blockSignals(False) + class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): - '''Controller for the thread milling operation's page''' + """Controller for the thread milling operation's page""" def initPage(self, obj): - self.majorDia = PathGui.QuantitySpinBox(self.form.threadMajor, obj, 'MajorDiameter') # pylint: disable=attribute-defined-outside-init - self.minorDia = PathGui.QuantitySpinBox(self.form.threadMinor, obj, 'MinorDiameter') # pylint: disable=attribute-defined-outside-init - self.pitch = PathGui.QuantitySpinBox(self.form.threadPitch, obj, 'Pitch') # pylint: disable=attribute-defined-outside-init + self.majorDia = PathGui.QuantitySpinBox( + self.form.threadMajor, obj, "MajorDiameter" + ) # pylint: disable=attribute-defined-outside-init + self.minorDia = PathGui.QuantitySpinBox( + self.form.threadMinor, obj, "MinorDiameter" + ) # pylint: disable=attribute-defined-outside-init + self.pitch = PathGui.QuantitySpinBox( + self.form.threadPitch, obj, "Pitch" + ) # pylint: disable=attribute-defined-outside-init - setupCombo(self.form.threadOrientation, obj.Proxy.ThreadOrientations) - setupCombo(self.form.threadType, obj.Proxy.ThreadTypes) - setupCombo(self.form.opDirection, obj.Proxy.Directions) + # setupCombo(self.form.threadOrientation, obj.Proxy.ThreadOrientations) + # setupCombo(self.form.threadType, obj.Proxy.ThreadTypes) + # setupCombo(self.form.opDirection, obj.Proxy.Directions) def getForm(self): - '''getForm() ... return UI''' - return FreeCADGui.PySideUic.loadUi(":/panels/PageOpThreadMillingEdit.ui") + """getForm() ... return UI""" + form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpThreadMillingEdit.ui") + comboToPropertyMap = [ + ("threadOrientation", "ThreadOrientation"), + ("threadType", "ThreadType"), + ("opDirection", "Direction"), + ] + enumTups = PathThreadMilling.ObjectThreadMilling.propertyEnumerations( + dataType="raw" + ) + self.populateCombobox(form, enumTups, comboToPropertyMap) + + return form + + def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): + """fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations + ** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol. + Args: + form = UI form + enumTups = list of (translated_text, data_string) tuples + comboBoxesPropertyMap = list of (translated_text, data_string) tuples + """ + # Load appropriate enumerations in each combobox + for cb, prop in comboBoxesPropertyMap: + box = getattr(form, cb) # Get the combobox + box.clear() # clear the combobox + for text, data in enumTups[prop]: # load enumerations + box.addItem(text, data) def getFields(self, obj): - '''getFields(obj) ... update obj's properties with values from the UI''' + """getFields(obj) ... update obj's properties with values from the UI""" PathLog.track() self.majorDia.updateProperty() self.minorDia.updateProperty() self.pitch.updateProperty() - obj.ThreadOrientation = self.form.threadOrientation.currentText() - obj.ThreadType = self.form.threadType.currentText() + obj.ThreadOrientation = self.form.threadOrientation.currentData() + obj.ThreadType = self.form.threadType.currentData() obj.ThreadName = self.form.threadName.currentText() - obj.Direction = self.form.opDirection.currentText() + obj.Direction = self.form.opDirection.currentData() obj.Passes = self.form.opPasses.value() obj.LeadInOut = self.form.leadInOut.checkState() == QtCore.Qt.Checked obj.TPI = self.form.threadTPI.value() @@ -90,23 +129,22 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.updateToolController(obj, self.form.toolController) def setFields(self, obj): - '''setFields(obj) ... update UI with obj properties' values''' + """setFields(obj) ... update UI with obj properties' values""" PathLog.track() - self.form.threadOrientation.setCurrentText(obj.ThreadOrientation) + self.selectInComboBox(obj.ThreadOrientation, self.form.threadOrientation) + self.selectInComboBox(obj.ThreadType, self.form.threadType) + self.selectInComboBox(obj.Direction, self.form.opDirection) - self.form.threadType.blockSignals(True) self.form.threadName.blockSignals(True) - self.form.threadType.setCurrentText(obj.ThreadType) - self._updateFromThreadType() self.form.threadName.setCurrentText(obj.ThreadName) - self.form.threadType.blockSignals(False) self.form.threadName.blockSignals(False) self.form.threadTPI.setValue(obj.TPI) self.form.opPasses.setValue(obj.Passes) - self.form.opDirection.setCurrentText(obj.Direction) - self.form.leadInOut.setCheckState(QtCore.Qt.Checked if obj.LeadInOut else QtCore.Qt.Unchecked) + self.form.leadInOut.setCheckState( + QtCore.Qt.Checked if obj.LeadInOut else QtCore.Qt.Unchecked + ) self.majorDia.updateSpinBox() self.minorDia.updateSpinBox() @@ -114,16 +152,24 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setupToolController(obj, self.form.toolController) - def _isThreadMetric(self): - return self.form.threadType.currentText() == PathThreadMilling.ObjectThreadMilling.ThreadTypeMetricInternal + return ( + self.form.threadType.currentData() + == PathThreadMilling.ObjectThreadMilling.ThreadTypeMetricInternal + ) def _isThreadImperial(self): - return self.form.threadType.currentText() == PathThreadMilling.ObjectThreadMilling.ThreadTypeImperialInternal + return ( + self.form.threadType.currentData() + == PathThreadMilling.ObjectThreadMilling.ThreadTypeImperialInternal + ) def _updateFromThreadType(self): - if self.form.threadType.currentText() == PathThreadMilling.ObjectThreadMilling.ThreadTypeCustom: + if ( + self.form.threadType.currentData() + == PathThreadMilling.ObjectThreadMilling.ThreadTypeCustom + ): self.form.threadName.setEnabled(False) self.form.threadFit.setEnabled(False) self.form.threadFitLabel.setEnabled(False) @@ -140,7 +186,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.form.threadTPI.setEnabled(False) self.form.threadTPILabel.setEnabled(False) self.form.threadTPI.setValue(0) - fillThreads(self.form.threadName, 'metric-internal') + fillThreads(self.form.threadName, "metric-internal") if self._isThreadImperial(): self.form.threadFit.setEnabled(True) @@ -150,24 +196,24 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.form.threadTPI.setEnabled(True) self.form.threadTPILabel.setEnabled(True) self.pitch.updateSpinBox(0) - fillThreads(self.form.threadName, 'imperial-internal') + fillThreads(self.form.threadName, "imperial-internal") def _updateFromThreadName(self): thread = self.form.threadName.currentData() fit = float(self.form.threadFit.value()) / 100 - mamin = float(thread['dMajorMin']) - mamax = float(thread['dMajorMax']) + mamin = float(thread["dMajorMin"]) + mamax = float(thread["dMajorMax"]) major = mamin + (mamax - mamin) * fit - mimin = float(thread['dMinorMin']) - mimax = float(thread['dMinorMax']) + mimin = float(thread["dMinorMin"]) + mimax = float(thread["dMinorMax"]) minor = mimin + (mimax - mimin) * fit if self._isThreadMetric(): - pitch = float(thread['pitch']) + pitch = float(thread["pitch"]) self.pitch.updateSpinBox(pitch) if self._isThreadImperial(): - tpi = int(thread['tpi']) + tpi = int(thread["tpi"]) self.form.threadTPI.setValue(tpi) minor = minor * 25.4 major = major * 25.4 @@ -178,7 +224,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setDirty() def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals which cause the receiver to update the model''' + """getSignalsForUpdate(obj) ... return list of signals which cause the receiver to update the model""" signals = [] signals.append(self.form.threadMajor.editingFinished) @@ -200,12 +246,17 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.form.threadFit.valueChanged.connect(self._updateFromThreadName) -Command = PathOpGui.SetupOperation('Thread Milling', - PathThreadMilling.Create, - TaskPanelOpPage, - 'Path_ThreadMilling', - QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Thread Milling"), - QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Creates a Path Thread Milling operation from features of a base object"), - PathThreadMilling.SetupProperties) +Command = PathOpGui.SetupOperation( + "ThreadMilling", + PathThreadMilling.Create, + TaskPanelOpPage, + "Path_ThreadMilling", + QT_TRANSLATE_NOOP("Path_ThreadMilling", "Thread Milling"), + QT_TRANSLATE_NOOP( + "Path_ThreadMilling", + "Creates a Path Thread Milling operation from features of a base object", + ), + PathThreadMilling.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathThreadMillingGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 6d6032abfd..f4adde8fbb 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -25,30 +25,32 @@ import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathUtil as PathUtil -import PySide import json import os import zipfile +from PySide.QtCore import QT_TRANSLATE_NOOP # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") __title__ = "Tool bits." __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Class to deal with and represent a tool bit." -PropertyGroupShape = 'Shape' +PropertyGroupShape = "Shape" _DebugFindTool = False -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule() +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) def _findToolFile(name, containerFile, typ): PathLog.track(name) @@ -74,7 +76,6 @@ def _findToolFile(name, containerFile, typ): return (True, fullPath) return (False, None) - for p in paths: found, path = _findFile(p, name) if found: @@ -83,26 +84,26 @@ def _findToolFile(name, containerFile, typ): def findToolShape(name, path=None): - '''findToolShape(name, path) ... search for name, if relative path look in path''' + """findToolShape(name, path) ... search for name, if relative path look in path""" PathLog.track(name, path) - return _findToolFile(name, path, 'Shape') + return _findToolFile(name, path, "Shape") def findToolBit(name, path=None): - '''findToolBit(name, path) ... search for name, if relative path look in path''' + """findToolBit(name, path) ... search for name, if relative path look in path""" PathLog.track(name, path) - if name.endswith('.fctb'): - return _findToolFile(name, path, 'Bit') - return _findToolFile("{}.fctb".format(name), path, 'Bit') + if name.endswith(".fctb"): + return _findToolFile(name, path, "Bit") + return _findToolFile("{}.fctb".format(name), path, "Bit") # Only used in ToolBit unit test module: TestPathToolBit.py def findToolLibrary(name, path=None): - '''findToolLibrary(name, path) ... search for name, if relative path look in path''' + """findToolLibrary(name, path) ... search for name, if relative path look in path""" PathLog.track(name, path) - if name.endswith('.fctl'): - return _findToolFile(name, path, 'Library') - return _findToolFile("{}.fctl".format(name), path, 'Library') + if name.endswith(".fctl"): + return _findToolFile(name, path, "Library") + return _findToolFile("{}.fctl".format(name), path, "Library") def _findRelativePath(path, typ): @@ -110,7 +111,7 @@ def _findRelativePath(path, typ): relative = path for p in PathPreferences.searchPathsTool(typ): if path.startswith(p): - p = path[len(p):] + p = path[len(p) :] if os.path.sep == p[0]: p = p[1:] if len(p) < len(relative): @@ -130,23 +131,52 @@ def findRelativePathTool(path): def findRelativePathLibrary(path): - return _findRelativePath(path, 'Library') + return _findRelativePath(path, "Library") + class ToolBit(object): - def __init__(self, obj, shapeFile, path=None): PathLog.track(obj.Label, shapeFile, path) self.obj = obj - obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape')) - obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit')) - obj.addProperty('App::PropertyFile', 'File', 'Base', translate('PathToolBit', 'The file of the tool')) - obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file')) - obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) + obj.addProperty( + "App::PropertyFile", + "BitShape", + "Base", + QT_TRANSLATE_NOOP("App::Property", "Shape for bit shape"), + ) + obj.addProperty( + "App::PropertyLink", + "BitBody", + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "The parametrized body representing the tool bit" + ), + ) + obj.addProperty( + "App::PropertyFile", + "File", + "Base", + QT_TRANSLATE_NOOP("App::Property", "The file of the tool"), + ) + obj.addProperty( + "App::PropertyString", + "ShapeName", + "Base", + QT_TRANSLATE_NOOP("App::Property", "The name of the shape file"), + ) + obj.addProperty( + "App::PropertyStringList", + "BitPropertyNames", + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "List of all properties inherited from the bit" + ), + ) if path: obj.File = path if shapeFile is None: - obj.BitShape = 'endmill.fcstd' + obj.BitShape = "endmill.fcstd" self._setupBitShape(obj) self.unloadBitBody(obj) else: @@ -159,7 +189,7 @@ class ToolBit(object): def __setstate__(self, state): for obj in FreeCAD.ActiveDocument.Objects: - if hasattr(obj, 'Proxy') and obj.Proxy == self: + if hasattr(obj, "Proxy") and obj.Proxy == self: self.obj = obj break return None @@ -168,14 +198,21 @@ class ToolBit(object): # when files are shared it is essential to be able to change/set the shape file, # otherwise the file is hard to use # obj.setEditorMode('BitShape', 1) - obj.setEditorMode('BitBody', 2) - obj.setEditorMode('File', 1) - obj.setEditorMode('Shape', 2) - if not hasattr(obj, 'BitPropertyNames'): - obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit')) + obj.setEditorMode("BitBody", 2) + obj.setEditorMode("File", 1) + obj.setEditorMode("Shape", 2) + if not hasattr(obj, "BitPropertyNames"): + obj.addProperty( + "App::PropertyStringList", + "BitPropertyNames", + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "List of all properties inherited from the bit" + ), + ) propNames = [] for prop in obj.PropertiesList: - if obj.getGroupOfProperty(prop) == 'Bit': + if obj.getGroupOfProperty(prop) == "Bit": val = obj.getPropertyByName(prop) typ = obj.getTypeIdOfProperty(prop) dsc = obj.getDocumentationOfProperty(prop) @@ -185,10 +222,10 @@ class ToolBit(object): PathUtil.setProperty(obj, prop, val) propNames.append(prop) - elif obj.getGroupOfProperty(prop) == 'Attribute': + elif obj.getGroupOfProperty(prop) == "Attribute": propNames.append(prop) obj.BitPropertyNames = propNames - obj.setEditorMode('BitPropertyNames', 2) + obj.setEditorMode("BitPropertyNames", 2) for prop in obj.BitPropertyNames: if obj.getGroupOfProperty(prop) == PropertyGroupShape: @@ -202,7 +239,7 @@ class ToolBit(object): def onChanged(self, obj, prop): PathLog.track(obj.Label, prop) - if prop == 'BitShape' and 'Restore' not in obj.State: + if prop == "BitShape" and "Restore" not in obj.State: self._setupBitShape(obj) def onDelete(self, obj, arg2=None): @@ -212,7 +249,11 @@ class ToolBit(object): def _updateBitShape(self, obj, properties=None): if obj.BitBody is not None: - for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]: + for attributes in [ + o + for o in obj.BitBody.Group + if hasattr(o, "Proxy") and hasattr(o.Proxy, "getCustomProperties") + ]: for prop in attributes.Proxy.getCustomProperties(): # the property might not exist in our local object (new attribute in shape) # for such attributes we just keep the default @@ -291,7 +332,7 @@ class ToolBit(object): dsc = orig.getDocumentationOfProperty(prop) obj.addProperty(typ, prop, grp, dsc) - if 'App::PropertyEnumeration' == typ: + if "App::PropertyEnumeration" == typ: setattr(obj, prop, orig.getEnumerationsOfProperty(prop)) obj.setEditorMode(prop, 1) @@ -315,16 +356,21 @@ class ToolBit(object): if bitBody.ViewObject: bitBody.ViewObject.Visibility = False - PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody))) + PathLog.debug( + "bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody)) + ) propNames = [] - for attributes in [o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)]: + for attributes in [ + o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o) + ]: PathLog.debug("Process properties from {}".format(attributes.Label)) for prop in attributes.Proxy.getCustomProperties(): self._setupProperty(obj, prop, attributes) propNames.append(prop) if not propNames: - PathLog.error(translate('PathToolBit', 'Did not find a PropertyBag in {} - not a ToolBit shape?'.format(docName))) + PathLog.error("Did not find a PropertyBag in {} - not a ToolBit shape?".format( + docName)) # has to happen last because it could trigger op.execute evaluations obj.BitPropertyNames = propNames @@ -332,20 +378,32 @@ class ToolBit(object): self._copyBitShape(obj) def toolShapeProperties(self, obj): - '''toolShapeProperties(obj) ... return all properties defining it's shape''' - return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) == PropertyGroupShape]) + """toolShapeProperties(obj) ... return all properties defining it's shape""" + return sorted( + [ + prop + for prop in obj.BitPropertyNames + if obj.getGroupOfProperty(prop) == PropertyGroupShape + ] + ) def toolAdditionalProperties(self, obj): - '''toolShapeProperties(obj) ... return all properties unrelated to it's shape''' - return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) != PropertyGroupShape]) + """toolShapeProperties(obj) ... return all properties unrelated to it's shape""" + return sorted( + [ + prop + for prop in obj.BitPropertyNames + if obj.getGroupOfProperty(prop) != PropertyGroupShape + ] + ) def toolGroupsAndProperties(self, obj, includeShape=True): - '''toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.''' + """toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.""" category = {} for prop in obj.BitPropertyNames: group = obj.getGroupOfProperty(prop) if includeShape or group != PropertyGroupShape: - properties = category.get(group, []) + properties = category.get(group, []) properties.append(prop) category[group] = properties return category @@ -354,10 +412,10 @@ class ToolBit(object): if obj.BitShape: path = findToolShape(obj.BitShape) if path: - with open(path, 'rb') as fd: + with open(path, "rb") as fd: try: zf = zipfile.ZipFile(fd) - pf = zf.open('thumbnails/Thumbnail.png', 'r') + pf = zf.open("thumbnails/Thumbnail.png", "r") data = pf.read() pf.close() return data @@ -368,55 +426,58 @@ class ToolBit(object): def saveToFile(self, obj, path, setFile=True): PathLog.track(path) try: - with open(path, 'w') as fp: - json.dump(self.templateAttrs(obj), fp, indent=' ') + with open(path, "w") as fp: + json.dump(self.templateAttrs(obj), fp, indent=" ") if setFile: obj.File = path return True except (OSError, IOError) as e: - PathLog.error("Could not save tool {} to {} ({})".format(obj.Label, path, e)) + PathLog.error( + "Could not save tool {} to {} ({})".format(obj.Label, path, e) + ) raise def templateAttrs(self, obj): attrs = {} - attrs['version'] = 2 # Path.Tool is version 1 - attrs['name'] = obj.Label + attrs["version"] = 2 # Path.Tool is version 1 + attrs["name"] = obj.Label if PathPreferences.toolsStoreAbsolutePaths(): - attrs['shape'] = obj.BitShape + attrs["shape"] = obj.BitShape else: # attrs['shape'] = findRelativePathShape(obj.BitShape) # Extract the name of the shape file - __, filShp = os.path.split(obj.BitShape) # __ is an ignored placeholder acknowledged by LGTM - attrs['shape'] = str(filShp) + __, filShp = os.path.split( + obj.BitShape + ) # __ is an ignored placeholder acknowledged by LGTM + attrs["shape"] = str(filShp) params = {} for name in obj.BitPropertyNames: params[name] = PathUtil.getPropertyValueString(obj, name) - attrs['parameter'] = params + attrs["parameter"] = params params = {} - attrs['attribute'] = params + attrs["attribute"] = params return attrs def Declaration(path): PathLog.track(path) - with open(path, 'r') as fp: + with open(path, "r") as fp: return json.load(fp) class ToolBitFactory(object): - - def CreateFromAttrs(self, attrs, name='ToolBit', path=None): + def CreateFromAttrs(self, attrs, name="ToolBit", path=None): PathLog.track(attrs, path) - obj = Factory.Create(name, attrs['shape'], path) - obj.Label = attrs['name'] - params = attrs['parameter'] + obj = Factory.Create(name, attrs["shape"], path) + obj.Label = attrs["name"] + params = attrs["parameter"] for prop in params: PathUtil.setProperty(obj, prop, params[prop]) obj.Proxy._updateBitShape(obj) obj.Proxy.unloadBitBody(obj) return obj - def CreateFrom(self, path, name='ToolBit'): + def CreateFrom(self, path, name="ToolBit"): PathLog.track(name, path) try: data = Declaration(path) @@ -426,9 +487,9 @@ class ToolBitFactory(object): PathLog.error("%s not a valid tool file (%s)" % (path, e)) raise - def Create(self, name='ToolBit', shapeFile=None, path=None): + def Create(self, name="ToolBit", shapeFile=None, path=None): PathLog.track(name, shapeFile, path) - obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name) + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name) obj.Proxy = ToolBit(obj, shapeFile, path) return obj diff --git a/src/Mod/Path/PathScripts/PathToolBitCmd.py b/src/Mod/Path/PathScripts/PathToolBitCmd.py index 31df9d5ba2..8a388b3552 100644 --- a/src/Mod/Path/PathScripts/PathToolBitCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitCmd.py @@ -20,25 +20,37 @@ # * * # *************************************************************************** +from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import PathScripts +import PathScripts.PathLog as PathLog import os -from PySide import QtCore +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + class CommandToolBitCreate: - ''' + """ Command used to create a new Tool. - ''' + """ def __init__(self): pass def GetResources(self): - return {'Pixmap': 'Path_ToolBit', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Create Tool"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Creates a new ToolBit object")} + return { + "Pixmap": "Path_ToolBit", + "MenuText": QT_TRANSLATE_NOOP("Path_ToolBitCreate", "Create Tool"), + "ToolTip": QT_TRANSLATE_NOOP( + "Path_ToolBitCreate", "Creates a new ToolBit object" + ), + } def IsActive(self): return FreeCAD.ActiveDocument is not None @@ -47,26 +59,33 @@ class CommandToolBitCreate: obj = PathScripts.PathToolBit.Factory.Create() obj.ViewObject.Proxy.setCreate(obj.ViewObject) + class CommandToolBitSave: - ''' + """ Command used to save an existing Tool to a file. - ''' + """ def __init__(self, saveAs): self.saveAs = saveAs def GetResources(self): if self.saveAs: - menuTxt = QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Save Tool as...") + menuTxt = QT_TRANSLATE_NOOP("Path_ToolBitSaveAs", "Save Tool as...") else: - menuTxt = QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Save Tool") - return {'Pixmap': 'Path_ToolBit', - 'MenuText': menuTxt, - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Save an existing ToolBit object to a file")} + menuTxt = QT_TRANSLATE_NOOP("Path_ToolBitSave", "Save Tool") + return { + "Pixmap": "Path_ToolBit", + "MenuText": menuTxt, + "ToolTip": QT_TRANSLATE_NOOP( + "Path_ToolBitSave", "Save an existing ToolBit object to a file" + ), + } def selectedTool(self): sel = FreeCADGui.Selection.getSelectionEx() - if 1 == len(sel) and isinstance(sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit): + if 1 == len(sel) and isinstance( + sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit + ): return sel[0].Object return None @@ -80,6 +99,7 @@ class CommandToolBitSave: def Activated(self): from PySide import QtGui + tool = self.selectedTool() if tool: path = None @@ -87,35 +107,47 @@ class CommandToolBitSave: if tool.File: fname = tool.File else: - fname = os.path.join(PathScripts.PathPreferences.lastPathToolBit(), tool.Label + '.fctb') - foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Tool", fname, "*.fctb") + fname = os.path.join( + PathScripts.PathPreferences.lastPathToolBit(), + tool.Label + ".fctb", + ) + foo = QtGui.QFileDialog.getSaveFileName( + QtGui.QApplication.activeWindow(), "Tool", fname, "*.fctb" + ) if foo: path = foo[0] else: path = tool.File if path: - if not path.endswith('.fctb'): - path += '.fctb' + if not path.endswith(".fctb"): + path += ".fctb" tool.Proxy.saveToFile(tool, path) PathScripts.PathPreferences.setLastPathToolBit(os.path.dirname(path)) + class CommandToolBitLoad: - ''' + """ Command used to load an existing Tool from a file into the current document. - ''' + """ def __init__(self): pass def GetResources(self): - return {'Pixmap': 'Path_ToolBit', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Load Tool"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Load an existing ToolBit object from a file")} + return { + "Pixmap": "Path_ToolBit", + "MenuText": QT_TRANSLATE_NOOP("Path_ToolBitLoad", "Load Tool"), + "ToolTip": QT_TRANSLATE_NOOP( + "Path_ToolBitLoad", "Load an existing ToolBit object from a file" + ), + } def selectedTool(self): sel = FreeCADGui.Selection.getSelectionEx() - if 1 == len(sel) and isinstance(sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit): + if 1 == len(sel) and isinstance( + sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit + ): return sel[0].Object return None @@ -126,12 +158,18 @@ class CommandToolBitLoad: if PathScripts.PathToolBitGui.LoadTools(): FreeCAD.ActiveDocument.recompute() -if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_ToolBitCreate', CommandToolBitCreate()) - FreeCADGui.addCommand('Path_ToolBitLoad', CommandToolBitLoad()) - FreeCADGui.addCommand('Path_ToolBitSave', CommandToolBitSave(False)) - FreeCADGui.addCommand('Path_ToolBitSaveAs', CommandToolBitSave(True)) -CommandList = ['Path_ToolBitCreate', 'Path_ToolBitLoad', 'Path_ToolBitSave', 'Path_ToolBitSaveAs'] +if FreeCAD.GuiUp: + FreeCADGui.addCommand("Path_ToolBitCreate", CommandToolBitCreate()) + FreeCADGui.addCommand("Path_ToolBitLoad", CommandToolBitLoad()) + FreeCADGui.addCommand("Path_ToolBitSave", CommandToolBitSave(False)) + FreeCADGui.addCommand("Path_ToolBitSaveAs", CommandToolBitSave(True)) + +CommandList = [ + "Path_ToolBitCreate", + "Path_ToolBitLoad", + "Path_ToolBitSave", + "Path_ToolBitSaveAs", +] FreeCAD.Console.PrintLog("Loading PathToolBitCmd... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 3028bd7bb1..9746f0e3bb 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -20,32 +20,30 @@ # * * # *************************************************************************** -import FreeCAD +from PySide import QtCore, QtGui import FreeCADGui import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathPropertyEditor as PathPropertyEditor -import PathScripts.PathToolBit as PathToolBit import PathScripts.PathUtil as PathUtil import os import re -from PySide import QtCore, QtGui -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - class _Delegate(QtGui.QStyledItemDelegate): - '''Handles the creation of an appropriate editing widget for a given property.''' - ObjectRole = QtCore.Qt.UserRole + 1 + """Handles the creation of an appropriate editing widget for a given property.""" + + ObjectRole = QtCore.Qt.UserRole + 1 PropertyRole = QtCore.Qt.UserRole + 2 - EditorRole = QtCore.Qt.UserRole + 3 + EditorRole = QtCore.Qt.UserRole + 3 def createEditor(self, parent, option, index): editor = index.data(self.EditorRole) @@ -64,14 +62,18 @@ class _Delegate(QtGui.QStyledItemDelegate): # called to update the model with the data from the widget editor = index.data(self.EditorRole) editor.setModelData(widget) - index.model().setData(index, PathUtil.getPropertyValueString(editor.obj, editor.prop), QtCore.Qt.DisplayRole) + index.model().setData( + index, + PathUtil.getPropertyValueString(editor.obj, editor.prop), + QtCore.Qt.DisplayRole, + ) class ToolBitEditor(object): - '''UI and controller for editing a ToolBit. + """UI and controller for editing a ToolBit. The controller embeds the UI to the parentWidget which has to have a layout attached to it. - ''' + """ def __init__(self, tool, parentWidget=None, loadBitBody=True): PathLog.track() @@ -84,7 +86,7 @@ class ToolBitEditor(object): self.tool = tool self.loadbitbody = loadBitBody if not tool.BitShape: - self.tool.BitShape = 'endmill.fcstd' + self.tool.BitShape = "endmill.fcstd" if self.loadbitbody: self.tool.Proxy.loadBitBody(self.tool) @@ -107,7 +109,7 @@ class ToolBitEditor(object): # which aren't being needed anymore. def labelText(name): - return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name)) + return re.sub("([A-Z][a-z]+)", r" \1", re.sub("([A-Z]+)", r" \1", name)) layout = self.form.bitParams.layout() ui = FreeCADGui.UiLoader() @@ -125,12 +127,12 @@ class ToolBitEditor(object): label.show() qsb.show() else: - qsb = ui.createWidget('Gui::QuantitySpinBox') + qsb = ui.createWidget("Gui::QuantitySpinBox") editor = PathGui.QuantitySpinBox(qsb, tool, name) - label = QtGui.QLabel(labelText(name)) + label = QtGui.QLabel(labelText(name)) self.widgets.append((label, qsb, editor)) PathLog.debug("create row: {} [{}] {}".format(nr, name, type(qsb))) - if hasattr(qsb, 'editingFinished'): + if hasattr(qsb, "editingFinished"): qsb.editingFinished.connect(self.updateTool) if nr >= layout.rowCount(): @@ -156,10 +158,10 @@ class ToolBitEditor(object): PathLog.track() setup = True - if not hasattr(self, 'delegate'): + if not hasattr(self, "delegate"): self.delegate = _Delegate(self.form.attrTree) self.model = QtGui.QStandardItemModel(self.form.attrTree) - self.model.setHorizontalHeaderLabels(['Property', 'Value']) + self.model.setHorizontalHeaderLabels(["Property", "Value"]) else: self.model.removeRows(0, self.model.rowCount()) setup = False @@ -175,21 +177,22 @@ class ToolBitEditor(object): label.setEditable(False) value = QtGui.QStandardItem() - value.setData(PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole) + value.setData( + PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole + ) value.setData(tool, _Delegate.ObjectRole) value.setData(prop, _Delegate.PropertyRole) group.appendRow([label, value]) self.model.appendRow(group) - if setup: self.form.attrTree.setModel(self.model) self.form.attrTree.setItemDelegateForColumn(1, self.delegate) self.form.attrTree.expandAll() self.form.attrTree.resizeColumnToContents(0) self.form.attrTree.resizeColumnToContents(1) - #self.form.attrTree.collapseAll() + # self.form.attrTree.collapseAll() def accept(self): PathLog.track() @@ -215,7 +218,7 @@ class ToolBitEditor(object): # editors fires an event and tries to access its old property, which # might not exist anymore. for lbl, qsb, editor in self.widgets: - editor.attachTo(self.tool, 'File') + editor.attachTo(self.tool, "File") self.tool.BitShape = shapePath self.setupTool(self.tool) self.form.toolName.setText(self.tool.Label) @@ -260,7 +263,9 @@ class ToolBitEditor(object): path = self.tool.BitShape if not path: path = PathPreferences.lastPathToolShape() - foo = QtGui.QFileDialog.getOpenFileName(self.form, "Path - Tool Shape", path, "*.fcstd") + foo = QtGui.QFileDialog.getOpenFileName( + self.form, "Path - Tool Shape", path, "*.fcstd" + ) if foo and foo[0]: PathPreferences.setLastPathToolShape(os.path.dirname(foo[0])) self.form.shapePath.setText(foo[0]) diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index 62e8e0bfc1..bedb532070 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -20,6 +20,8 @@ # * * # *************************************************************************** +from PySide import QtCore, QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import PathScripts.PathIconViewProvider as PathIconViewProvider @@ -29,26 +31,24 @@ import PathScripts.PathToolBit as PathToolBit import PathScripts.PathToolBitEdit as PathToolBitEdit import os -from PySide import QtCore, QtGui - __title__ = "Tool Bit UI" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a ToolBit" -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +translate = FreeCAD.Qt.translate class ViewProvider(object): - '''ViewProvider for a ToolBit. - It's sole job is to provide an icon and invoke the TaskPanel on edit.''' + """ViewProvider for a ToolBit. + It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): PathLog.track(name, vobj.Object) @@ -67,9 +67,9 @@ class ViewProvider(object): png = self.obj.Proxy.getBitThumbnail(self.obj) if png: pixmap = QtGui.QPixmap() - pixmap.loadFromData(png, 'PNG') + pixmap.loadFromData(png, "PNG") return QtGui.QIcon(pixmap) - return ':/icons/Path_ToolBit.svg' + return ":/icons/Path_ToolBit.svg" def __getstate__(self): return None @@ -84,7 +84,7 @@ class ViewProvider(object): def getDisplayMode(self, mode): # pylint: disable=unused-argument - return 'Default' + return "Default" def _openTaskPanel(self, vobj, deleteOnReject): PathLog.track() @@ -117,15 +117,16 @@ class ViewProvider(object): if os.path.exists(vobj.Object.BitShape): self.setEdit(vobj) else: - msg = translate('PathToolBit', - 'Toolbit cannot be edited: Shapefile not found') - diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error', msg) + msg = translate( + "PathToolBit", "Toolbit cannot be edited: Shapefile not found" + ) + diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "Error", msg) diag.setWindowModality(QtCore.Qt.ApplicationModal) diag.exec_() class TaskPanel: - '''TaskPanel for the SetupSheet - if it is being edited directly.''' + """TaskPanel for the SetupSheet - if it is being edited directly.""" def __init__(self, vobj, deleteOnReject): PathLog.track(vobj.Object.Label) @@ -134,15 +135,14 @@ class TaskPanel: self.editor = PathToolBitEdit.ToolBitEditor(self.obj) self.form = self.editor.form self.deleteOnReject = deleteOnReject - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', - 'Edit ToolBit')) + FreeCAD.ActiveDocument.openTransaction("Edit ToolBit") def reject(self): FreeCAD.ActiveDocument.abortTransaction() self.editor.reject() FreeCADGui.Control.closeDialog() if self.deleteOnReject: - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Uncreate ToolBit')) + FreeCAD.ActiveDocument.openTransaction("Uncreate ToolBit") self.editor.reject() FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCAD.ActiveDocument.commitTransaction() @@ -169,18 +169,18 @@ class TaskPanel: class ToolBitGuiFactory(PathToolBit.ToolBitFactory): - - def Create(self, name='ToolBit', shapeFile=None, path=None): - '''Create(name = 'ToolBit') ... creates a new tool bit. - It is assumed the tool will be edited immediately so the internal bit body is still attached.''' + def Create(self, name="ToolBit", shapeFile=None, path=None): + """Create(name = 'ToolBit') ... creates a new tool bit. + It is assumed the tool will be edited immediately so the internal bit body is still attached.""" PathLog.track(name, shapeFile, path) - FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit')) + FreeCAD.ActiveDocument.openTransaction("Create ToolBit") tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile, path) PathIconViewProvider.Attach(tool.ViewObject, name) FreeCAD.ActiveDocument.commitTransaction() return tool + def isValidFileName(filename): print(filename) try: @@ -194,9 +194,9 @@ def GetNewToolFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() - foo = QtGui.QFileDialog.getSaveFileName(parent, 'Tool', - PathPreferences.lastPathToolBit(), - '*.fctb') + foo = QtGui.QFileDialog.getSaveFileName( + parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb" + ) if foo and foo[0]: if not isValidFileName(foo[0]): msgBox = QtGui.QMessageBox() @@ -212,9 +212,9 @@ def GetNewToolFile(parent=None): def GetToolFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() - foo = QtGui.QFileDialog.getOpenFileName(parent, 'Tool', - PathPreferences.lastPathToolBit(), - '*.fctb') + foo = QtGui.QFileDialog.getOpenFileName( + parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb" + ) if foo and foo[0]: PathPreferences.setLastPathToolBit(os.path.dirname(foo[0])) return foo[0] @@ -224,9 +224,9 @@ def GetToolFile(parent=None): def GetToolFiles(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() - foo = QtGui.QFileDialog.getOpenFileNames(parent, 'Tool', - PathPreferences.lastPathToolBit(), - '*.fctb') + foo = QtGui.QFileDialog.getOpenFileNames( + parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb" + ) if foo and foo[0]: PathPreferences.setLastPathToolBit(os.path.dirname(foo[0][0])) return foo[0] @@ -243,8 +243,9 @@ def GetToolShapeFile(parent=None): elif not os.path.isdir(location): location = PathPreferences.filePath() - fname = QtGui.QFileDialog.getOpenFileName(parent, 'Select Tool Shape', - location, '*.fcstd') + fname = QtGui.QFileDialog.getOpenFileName( + parent, "Select Tool Shape", location, "*.fcstd" + ) if fname and fname[0]: if fname != location: newloc = os.path.dirname(fname[0]) @@ -255,21 +256,21 @@ def GetToolShapeFile(parent=None): def LoadTool(parent=None): - ''' + """ LoadTool(parent=None) ... Open a file dialog to load a tool from a file. - ''' + """ foo = GetToolFile(parent) return PathToolBit.Factory.CreateFrom(foo) if foo else foo def LoadTools(parent=None): - ''' + """ LoadTool(parent=None) ... Open a file dialog to load a tool from a file. - ''' + """ return [PathToolBit.Factory.CreateFrom(foo) for foo in GetToolFiles(parent)] # Set the factory so all tools are created with UI PathToolBit.Factory = ToolBitGuiFactory() -PathIconViewProvider.RegisterViewProvider('ToolBit', ViewProvider) +PathIconViewProvider.RegisterViewProvider("ToolBit", ViewProvider) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py index 5d03f72fe0..309f77d5e9 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py @@ -20,65 +20,83 @@ # * * # *************************************************************************** +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import PySide.QtCore as QtCore -import PathScripts.PathPreferences as PathPreferences +import PathScripts.PathLog as PathLog + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +translate = FreeCAD.Qt.translate class CommandToolBitSelectorOpen: - ''' + """ Command to toggle the ToolBitSelector Dock - ''' + """ def __init__(self): pass def GetResources(self): - return {'Pixmap': 'Path_ToolTable', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Dock"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Toggle the Toolbit Dock"), - 'Accel': "P, T", - 'CmdType': "ForEdit"} + return { + "Pixmap": "Path_ToolTable", + "MenuText": QT_TRANSLATE_NOOP("Path_ToolBitDock", "ToolBit Dock"), + "ToolTip": QT_TRANSLATE_NOOP("Path_ToolBitDock", "Toggle the Toolbit Dock"), + "Accel": "P, T", + "CmdType": "ForEdit", + } def IsActive(self): return FreeCAD.ActiveDocument is not None def Activated(self): import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui + dock = PathToolBitLibraryGui.ToolBitSelector() dock.open() class CommandToolBitLibraryOpen: - ''' + """ Command to open ToolBitLibrary editor. - ''' + """ def __init__(self): pass def GetResources(self): - return {'Pixmap': 'Path_ToolTable', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Library editor"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Open an editor to manage ToolBit libraries"), - 'CmdType': "ForEdit"} + return { + "Pixmap": "Path_ToolTable", + "MenuText": QT_TRANSLATE_NOOP( + "Path_ToolBitLibraryOpen", "ToolBit Library editor" + ), + "ToolTip": QT_TRANSLATE_NOOP( + "Path_ToolBitLibraryOpen", "Open an editor to manage ToolBit libraries" + ), + "CmdType": "ForEdit", + } def IsActive(self): return FreeCAD.ActiveDocument is not None def Activated(self): import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui + library = PathToolBitLibraryGui.ToolBitLibrary() library.open() if FreeCAD.GuiUp: - FreeCADGui.addCommand('Path_ToolBitLibraryOpen', CommandToolBitLibraryOpen()) - FreeCADGui.addCommand('Path_ToolBitDock', CommandToolBitSelectorOpen()) + FreeCADGui.addCommand("Path_ToolBitLibraryOpen", CommandToolBitLibraryOpen()) + FreeCADGui.addCommand("Path_ToolBitDock", CommandToolBitSelectorOpen()) -BarList = ['Path_ToolBitDock'] -MenuList = ['Path_ToolBitLibraryOpen', 'Path_ToolBitDock'] +BarList = ["Path_ToolBitDock"] +MenuList = ["Path_ToolBitLibraryOpen", "Path_ToolBitDock"] FreeCAD.Console.PrintLog("Loading PathToolBitLibraryCmd... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index 693d954c0c..0ab4308b21 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -24,7 +24,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit @@ -42,15 +42,19 @@ import uuid as UUID from functools import partial -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + _UuidRole = PySide.QtCore.Qt.UserRole + 1 _PathRole = PySide.QtCore.Qt.UserRole + 2 -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate + def checkWorkingDir(): # users shouldn't use the example toolbits and libraries. @@ -60,30 +64,37 @@ def checkWorkingDir(): workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) - PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)) + PathLog.debug("workingdir: {} defaultdir: {}".format(workingdir, defaultdir)) - dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) + dirOK = lambda: workingdir != defaultdir and (os.access(workingdir, os.W_OK)) if dirOK(): return True qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No) + ret = qm.question( + None, "", "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No + ) if ret == qm.No: return False - msg = translate("Path", "Choose a writable location for your toolbits", None) + msg = translate( + "Path_ToolBit", "Choose a writable location for your toolbits", None + ) while not dirOK(): - workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg, - PathPreferences.filePath()) + workingdir = PySide.QtGui.QFileDialog.getExistingDirectory( + None, msg, PathPreferences.filePath() + ) - if workingdir[-8:] == os.path.sep + 'Library': + if workingdir[-8:] == os.path.sep + "Library": workingdir = workingdir[:-8] # trim off trailing /Library if user chose it - PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep)) + PathPreferences.setLastPathToolLibrary( + "{}{}Library".format(workingdir, os.path.sep) + ) PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) - PathLog.debug('setting workingdir to: {}'.format(workingdir)) + PathLog.debug("setting workingdir to: {}".format(workingdir)) # Copy only files of default Path\Tools folder to working directory (targeting the README.md help file) src_toolfiles = os.listdir(defaultdir) @@ -94,7 +105,7 @@ def checkWorkingDir(): shutil.copy(full_file_name, workingdir) # Determine which subdirectories are missing - subdirlist = ['Bit', 'Library', 'Shape'] + subdirlist = ["Bit", "Library", "Shape"] mode = 0o777 for dir in subdirlist.copy(): subdir = "{}{}{}".format(workingdir, os.path.sep, dir) @@ -103,9 +114,16 @@ def checkWorkingDir(): # Query user for creation permission of any missing subdirectories if len(subdirlist) >= 1: - needed = ', '.join([str(d) for d in subdirlist]) + needed = ", ".join([str(d) for d in subdirlist]) qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No) + ret = qm.question( + None, + "", + "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format( + workingdir, needed + ), + qm.Yes | qm.No, + ) if ret == qm.No: return False @@ -115,27 +133,37 @@ def checkWorkingDir(): subdir = "{}{}{}".format(workingdir, os.path.sep, dir) os.mkdir(subdir, mode) # Query user to copy example files into subdirectories created - if dir != 'Shape': + if dir != "Shape": qm = PySide.QtGui.QMessageBox - ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) + ret = qm.question( + None, + "", + "Copy example files to new {} directory?".format(dir), + qm.Yes | qm.No, + ) if ret == qm.Yes: - src="{}{}{}".format(defaultdir, os.path.sep, dir) + src = "{}{}{}".format(defaultdir, os.path.sep, dir) src_files = os.listdir(src) for file_name in src_files: full_file_name = os.path.join(src, file_name) if os.path.isfile(full_file_name): shutil.copy(full_file_name, subdir) - # if no library is set, choose the first one in the Library directory if PathPreferences.lastFileToolLibrary() is None: - libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')] + libFiles = [ + f + for f in glob.glob( + PathPreferences.lastPathToolLibrary() + os.path.sep + "*.fctl" + ) + ] PathPreferences.setLastFileToolLibrary(libFiles[0]) return True + class _TableView(PySide.QtGui.QTableView): - '''Subclass of QTableView to support rearrange and copying of ToolBits''' + """Subclass of QTableView to support rearrange and copying of ToolBits""" def __init__(self, parent): PySide.QtGui.QTableView.__init__(self, parent) @@ -169,9 +197,15 @@ class _TableView(PySide.QtGui.QTableView): for col in range(model.columnCount()): srcItem = model.item(srcRow, col) - model.setData(model.index(dstRow, col), srcItem.data(PySide.QtCore.Qt.EditRole), PySide.QtCore.Qt.EditRole) + model.setData( + model.index(dstRow, col), + srcItem.data(PySide.QtCore.Qt.EditRole), + PySide.QtCore.Qt.EditRole, + ) if col == 0: - model.setData(model.index(dstRow, col), srcItem.data(_PathRole), _PathRole) + model.setData( + model.index(dstRow, col), srcItem.data(_PathRole), _PathRole + ) # Even a clone of a tool gets its own uuid so it can be identified when # rearranging the order or inserting/deleting rows model.setData(model.index(dstRow, col), UUID.uuid4(), _UuidRole) @@ -185,7 +219,7 @@ class _TableView(PySide.QtGui.QTableView): def dropEvent(self, event): PathLog.track() mime = event.mimeData() - data = mime.data('application/x-qstandarditemmodeldatalist') + data = mime.data("application/x-qstandarditemmodeldatalist") stream = PySide.QtCore.QDataStream(data) srcRows = [] while not stream.atEnd(): @@ -205,8 +239,7 @@ class _TableView(PySide.QtGui.QTableView): class ModelFactory(object): - ''' Helper class to generate qtdata models for toolbit libraries - ''' + """Helper class to generate qtdata models for toolbit libraries""" def __init__(self, path=None): PathLog.track() @@ -221,35 +254,37 @@ class ModelFactory(object): with open(path) as fp: library = json.load(fp) - for toolBit in library['tools']: + for toolBit in library["tools"]: try: - nr = toolBit['nr'] - bit = PathToolBit.findToolBit(toolBit['path'], path) + nr = toolBit["nr"] + bit = PathToolBit.findToolBit(toolBit["path"], path) if bit: PathLog.track(bit) tool = PathToolBit.Declaration(bit) datamodel.appendRow(self._toolAdd(nr, tool, bit)) else: - PathLog.error("Could not find tool #{}: {}".format(nr, toolBit['path'])) + PathLog.error( + "Could not find tool #{}: {}".format(nr, toolBit["path"]) + ) except Exception as e: - msg = "Error loading tool: {} : {}".format(toolBit['path'], e) + msg = "Error loading tool: {} : {}".format(toolBit["path"], e) FreeCAD.Console.PrintError(msg) def _toolAdd(self, nr, tool, path): - strShape = os.path.splitext(os.path.basename(tool['shape']))[0] + strShape = os.path.splitext(os.path.basename(tool["shape"]))[0] # strDiam = tool['parameter']['Diameter'] tooltip = "{}".format(strShape) toolNr = PySide.QtGui.QStandardItem() toolNr.setData(nr, PySide.QtCore.Qt.EditRole) - toolNr.setToolTip(tool['shape']) + toolNr.setToolTip(tool["shape"]) toolNr.setData(path, _PathRole) toolNr.setData(UUID.uuid4(), _UuidRole) toolNr.setToolTip(tooltip) toolName = PySide.QtGui.QStandardItem() - toolName.setData(tool['name'], PySide.QtCore.Qt.EditRole) + toolName.setData(tool["name"], PySide.QtCore.Qt.EditRole) toolName.setEditable(False) toolName.setToolTip(tooltip) @@ -260,9 +295,9 @@ class ModelFactory(object): return [toolNr, toolName, toolShape] def newTool(self, datamodel, path): - ''' + """ Adds a toolbit item to a model - ''' + """ PathLog.track() try: @@ -278,15 +313,15 @@ class ModelFactory(object): datamodel.appendRow(self._toolAdd(nr, tool, path)) def findLibraries(self, model): - ''' + """ Finds all the fctl files in a location Returns a QStandardItemModel - ''' + """ PathLog.track() path = PathPreferences.lastPathToolLibrary() if os.path.isdir(path): # opening all tables in a directory - libFiles = [f for f in glob.glob(path + os.path.sep + '*.fctl')] + libFiles = [f for f in glob.glob(path + os.path.sep + "*.fctl")] libFiles.sort() for libFile in libFiles: loc, fnlong = os.path.split(libFile) @@ -294,17 +329,17 @@ class ModelFactory(object): libItem = PySide.QtGui.QStandardItem(fn) libItem.setToolTip(loc) libItem.setData(libFile, _PathRole) - libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg')) + libItem.setIcon(PySide.QtGui.QPixmap(":/icons/Path_ToolTable.svg")) model.appendRow(libItem) - PathLog.debug('model rows: {}'.format(model.rowCount())) + PathLog.debug("model rows: {}".format(model.rowCount())) return model def libraryOpen(self, model, lib=""): - ''' + """ opens the tools in library Returns a QStandardItemModel - ''' + """ PathLog.track(lib) if lib == "": @@ -316,23 +351,23 @@ class ModelFactory(object): if os.path.isfile(lib): # An individual library is wanted self.__libraryLoad(lib, model) - PathLog.debug('model rows: {}'.format(model.rowCount())) + PathLog.debug("model rows: {}".format(model.rowCount())) return model class ToolBitSelector(object): - '''Controller for displaying a library and creating ToolControllers''' + """Controller for displaying a library and creating ToolControllers""" def __init__(self): checkWorkingDir() - self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui') + self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitSelector.ui") self.factory = ModelFactory() self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) self.setupUI() self.title = self.form.windowTitle() def columnNames(self): - return ['#', 'Tool'] + return ["#", "Tool"] def currentLibrary(self, shortNameOnly): libfile = PathPreferences.lastFileToolLibrary() @@ -357,14 +392,19 @@ class ToolBitSelector(object): self.loadData() self.form.tools.setModel(self.toolModel) self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons) - self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers)) + self.form.tools.doubleClicked.connect( + partial(self.selectedOrAllToolControllers) + ) self.form.libraryEditorOpen.clicked.connect(self.libraryEditorOpen) self.form.addToolController.clicked.connect(self.selectedOrAllToolControllers) def enableButtons(self): - selected = (len(self.form.tools.selectedIndexes()) >= 1) + selected = len(self.form.tools.selectedIndexes()) >= 1 if selected: - jobs = len([1 for j in FreeCAD.ActiveDocument.Objects if j.Name[:3] == "Job"]) >= 1 + jobs = ( + len([1 for j in FreeCAD.ActiveDocument.Objects if j.Name[:3] == "Job"]) + >= 1 + ) self.form.addToolController.setEnabled(selected and jobs) def libraryEditorOpen(self): @@ -373,17 +413,17 @@ class ToolBitSelector(object): self.loadData() def selectedOrAllTools(self): - ''' + """ Iterate the selection and add individual tools If a group is selected, iterate and add children - ''' + """ itemsToProcess = [] for index in self.form.tools.selectedIndexes(): item = index.model().itemFromIndex(index) if item.hasChildren(): - for i in range(item.rowCount()-1): + for i in range(item.rowCount() - 1): if item.child(i).column() == 0: itemsToProcess.append(item.child(i)) @@ -398,10 +438,10 @@ class ToolBitSelector(object): return tools def selectedOrAllToolControllers(self, index=None): - ''' + """ if no jobs, don't do anything, otherwise all TCs for all selected toolbits - ''' + """ jobs = PathUtilsGui.PathUtils.GetJobs() if len(jobs) == 0: return @@ -417,12 +457,14 @@ class ToolBitSelector(object): tools = self.selectedOrAllTools() for tool in tools: - tc = PathToolControllerGui.Create("TC: {}".format(tool[1].Label), tool[1], tool[0]) + tc = PathToolControllerGui.Create( + "TC: {}".format(tool[1].Label), tool[1], tool[0] + ) job.Proxy.addToolController(tc) FreeCAD.ActiveDocument.recompute() def open(self, path=None): - ''' load library stored in path and bring up ui''' + """load library stored in path and bring up ui""" docs = FreeCADGui.getMainWindow().findChildren(PySide.QtGui.QDockWidget) for doc in docs: if doc.objectName() == "ToolSelector": @@ -434,13 +476,16 @@ class ToolBitSelector(object): return mw = FreeCADGui.getMainWindow() - mw.addDockWidget(PySide.QtCore.Qt.RightDockWidgetArea, self.form, - PySide.QtCore.Qt.Orientation.Vertical) + mw.addDockWidget( + PySide.QtCore.Qt.RightDockWidgetArea, + self.form, + PySide.QtCore.Qt.Orientation.Vertical, + ) class ToolBitLibrary(object): - '''ToolBitLibrary is the controller for - displaying/selecting/creating/editing a collection of ToolBits.''' + """ToolBitLibrary is the controller for + displaying/selecting/creating/editing a collection of ToolBits.""" def __init__(self): PathLog.track() @@ -449,9 +494,11 @@ class ToolBitLibrary(object): self.temptool = None self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames())) self.listModel = PySide.QtGui.QStandardItemModel() - self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitLibraryEdit.ui') + self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitLibraryEdit.ui") self.toolTableView = _TableView(self.form.toolTableGroup) - self.form.toolTableGroup.layout().replaceWidget(self.form.toolTable, self.toolTableView) + self.form.toolTableGroup.layout().replaceWidget( + self.form.toolTable, self.toolTableView + ) self.form.toolTable.hide() self.setupUI() self.title = self.form.windowTitle() @@ -503,7 +550,9 @@ class ToolBitLibrary(object): def toolDelete(self): PathLog.track() - selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()]) + selectedRows = set( + [index.row() for index in self.toolTableView.selectedIndexes()] + ) for row in sorted(list(selectedRows), key=lambda r: -r): self.toolModel.removeRows(row, 1) @@ -512,7 +561,7 @@ class ToolBitLibrary(object): self.form.toolDelete.setEnabled(sel) def tableSelected(self, index): - ''' loads the tools for the selected tool table ''' + """loads the tools for the selected tool table""" PathLog.track() item = index.model().itemFromIndex(index) libpath = item.data(_PathRole) @@ -525,7 +574,9 @@ class ToolBitLibrary(object): def libraryPath(self): PathLog.track() - path = PySide.QtGui.QFileDialog.getExistingDirectory(self.form, 'Tool Library Path', PathPreferences.lastPathToolLibrary()) + path = PySide.QtGui.QFileDialog.getExistingDirectory( + self.form, "Tool Library Path", PathPreferences.lastPathToolLibrary() + ) if len(path) == 0: return @@ -588,10 +639,14 @@ class ToolBitLibrary(object): pass else: tbpath = item.data(_PathRole) - self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool') - self.editor = PathToolBitEdit.ToolBitEditor(self.temptool, self.form.toolTableGroup, loadBitBody=False) + self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, "temptool") + self.editor = PathToolBitEdit.ToolBitEditor( + self.temptool, self.form.toolTableGroup, loadBitBody=False + ) - QBtn = PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel + QBtn = ( + PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel + ) buttonBox = PySide.QtGui.QDialogButtonBox(QBtn) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) @@ -603,24 +658,31 @@ class ToolBitLibrary(object): def toolEditDone(self, success=True): FreeCAD.ActiveDocument.removeObject("temptool") - print('all done') + print("all done") def libraryNew(self): - TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)") + TooltableTypeJSON = translate("Path_ToolBit", "Tooltable JSON (*.fctl)") - filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form, - translate("TooltableEditor", "Save toolbit library", None), - PathPreferences.lastPathToolLibrary(), "{}".format(TooltableTypeJSON)) + filename = PySide.QtGui.QFileDialog.getSaveFileName( + self.form, + translate("Path_ToolBit", "Save toolbit library", None), + PathPreferences.lastPathToolLibrary(), + "{}".format(TooltableTypeJSON), + ) if not (filename and filename[0]): self.loadData() - path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0]) + path = ( + filename[0] + if filename[0].endswith(".fctl") + else "{}.fctl".format(filename[0]) + ) library = {} tools = [] - library['version'] = 1 - library['tools'] = tools - with open(path, 'w') as fp: + library["version"] = 1 + library["tools"] = tools + with open(path, "w") as fp: json.dump(library, fp, sort_keys=True, indent=2) self.loadData() @@ -628,22 +690,26 @@ class ToolBitLibrary(object): def librarySave(self): library = {} tools = [] - library['version'] = 1 - library['tools'] = tools + library["version"] = 1 + library["tools"] = tools for row in range(self.toolModel.rowCount()): - toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole) + toolNr = self.toolModel.data( + self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole + ) toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) if PathPreferences.toolsStoreAbsolutePaths(): bitPath = toolPath else: # bitPath = PathToolBit.findRelativePathTool(toolPath) # Extract the name of the shape file - __, filShp = os.path.split(toolPath) # __ is an ignored placeholder acknowledged by LGTM + __, filShp = os.path.split( + toolPath + ) # __ is an ignored placeholder acknowledged by LGTM bitPath = str(filShp) - tools.append({'nr': toolNr, 'path': bitPath}) + tools.append({"nr": toolNr, "path": bitPath}) if self.path is not None: - with open(self.path, 'w') as fp: + with open(self.path, "w") as fp: json.dump(library, fp, sort_keys=True, indent=2) def libraryOk(self): @@ -658,7 +724,7 @@ class ToolBitLibrary(object): return lib, loc def columnNames(self): - return ['Nr', 'Tool', 'Shape'] + return ["Nr", "Tool", "Shape"] def loadData(self, path=None): PathLog.track(path) @@ -680,7 +746,7 @@ class ToolBitLibrary(object): self.path = path self.form.setWindowTitle("{}".format(PathPreferences.lastPathToolLibrary())) self.toolModel.setHorizontalHeaderLabels(self.columnNames()) - self.listModel.setHorizontalHeaderLabels(['Library']) + self.listModel.setHorizontalHeaderLabels(["Library"]) # Select the current library in the list of tables curIndex = None @@ -723,19 +789,29 @@ class ToolBitLibrary(object): def librarySaveAs(self, path): - TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)") - TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)") + TooltableTypeJSON = translate("Path_ToolBit", "Tooltable JSON (*.fctl)") + TooltableTypeLinuxCNC = translate("Path_ToolBit", "LinuxCNC tooltable (*.tbl)") - filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form, - translate("TooltableEditor", "Save toolbit library", None), - PathPreferences.lastPathToolLibrary(), "{};;{}".format(TooltableTypeJSON, - TooltableTypeLinuxCNC)) + filename = PySide.QtGui.QFileDialog.getSaveFileName( + self.form, + translate("Path_ToolBit", "Save toolbit library", None), + PathPreferences.lastPathToolLibrary(), + "{};;{}".format(TooltableTypeJSON, TooltableTypeLinuxCNC), + ) if filename and filename[0]: if filename[1] == TooltableTypeLinuxCNC: - path = filename[0] if filename[0].endswith('.tbl') else "{}.tbl".format(filename[0]) + path = ( + filename[0] + if filename[0].endswith(".tbl") + else "{}.tbl".format(filename[0]) + ) self.libararySaveLinuxCNC(path) else: - path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0]) + path = ( + filename[0] + if filename[0].endswith(".fctl") + else "{}.fctl".format(filename[0]) + ) self.path = path self.librarySave() self.updateToolbar() @@ -743,11 +819,13 @@ class ToolBitLibrary(object): def libararySaveLinuxCNC(self, path): # linuxcnc line template LIN = "T{} P{} X{} Y{} Z{} A{} B{} C{} U{} V{} W{} D{} I{} J{} Q{}; {}" - with open(path, 'w') as fp: + with open(path, "w") as fp: fp.write(";\n") for row in range(self.toolModel.rowCount()): - toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole) + toolNr = self.toolModel.data( + self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole + ) toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) bit = PathToolBit.Factory.CreateFrom(toolPath) @@ -765,16 +843,39 @@ class ToolBitLibrary(object): voffset = bit.Voffset if hasattr(bit, "Voffset") else "0" woffset = bit.Woffset if hasattr(bit, "Woffset") else "0" - diameter = bit.Diameter.getUserPreferred()[0].split()[0] if hasattr(bit, "Diameter") else "0" + diameter = ( + bit.Diameter.getUserPreferred()[0].split()[0] + if hasattr(bit, "Diameter") + else "0" + ) frontangle = bit.FrontAngle if hasattr(bit, "FrontAngle") else "0" backangle = bit.BackAngle if hasattr(bit, "BackAngle") else "0" - orientation = bit.Orientation if hasattr(bit, "Orientation") else "0" + orientation = ( + bit.Orientation if hasattr(bit, "Orientation") else "0" + ) remark = bit.Label - fp.write(LIN.format(toolNr, pocket, xoffset, yoffset, - zoffset, aoffset, boffset, coffset, uoffset, - voffset, woffset, diameter, frontangle, backangle, - orientation, remark) + "\n") + fp.write( + LIN.format( + toolNr, + pocket, + xoffset, + yoffset, + zoffset, + aoffset, + boffset, + coffset, + uoffset, + voffset, + woffset, + diameter, + frontangle, + backangle, + orientation, + remark, + ) + + "\n" + ) FreeCAD.ActiveDocument.removeObject(bit.Name) diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 0a7633208d..5704fcfb87 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -20,78 +20,149 @@ # * * # *************************************************************************** -'''Tool Controller defines tool, spindle speed and feed rates for Path Operations''' +"""Tool Controller defines tool, spindle speed and feed rates for Path Operations""" +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit -from PySide import QtCore -# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate class ToolControllerTemplate: - '''Attribute and sub element strings for template export/import.''' + """Attribute and sub element strings for template export/import.""" - Expressions = 'xengine' - ExprExpr = 'expr' - ExprProp = 'prop' - HorizFeed = 'hfeed' - HorizRapid = 'hrapid' - Label = 'label' - Name = 'name' - SpindleDir = 'dir' - SpindleSpeed = 'speed' - ToolNumber = 'nr' - Tool = 'tool' - Version = 'version' - VertFeed = 'vfeed' - VertRapid = 'vrapid' + Expressions = "xengine" + ExprExpr = "expr" + ExprProp = "prop" + HorizFeed = "hfeed" + HorizRapid = "hrapid" + Label = "label" + Name = "name" + SpindleDir = "dir" + SpindleSpeed = "speed" + ToolNumber = "nr" + Tool = "tool" + Version = "version" + VertFeed = "vfeed" + VertRapid = "vrapid" class ToolController: - def __init__(self, obj, legacyTool=False, createTool=True): - PathLog.track('tool: {}'.format(legacyTool)) + PathLog.track("tool: {}".format(legacyTool)) - obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The active tool")) + obj.addProperty( + "App::PropertyIntegerConstraint", + "ToolNumber", + "Tool", + QT_TRANSLATE_NOOP("App::Property", "The active tool"), + ) obj.ToolNumber = (0, 0, 10000, 1) - obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The speed of the cutting spindle in RPM")) - obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Direction of spindle rotation")) - obj.SpindleDir = ['Forward', 'Reverse'] - obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Feed rate for vertical moves in Z")) - obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Feed rate for horizontal moves")) - obj.addProperty("App::PropertySpeed", "VertRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Rapid rate for vertical moves in Z")) - obj.addProperty("App::PropertySpeed", "HorizRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Rapid rate for horizontal moves")) - obj.setEditorMode('Placement', 2) + obj.addProperty( + "App::PropertyFloat", + "SpindleSpeed", + "Tool", + QT_TRANSLATE_NOOP( + "App::Property", "The speed of the cutting spindle in RPM" + ), + ) + obj.addProperty( + "App::PropertyEnumeration", + "SpindleDir", + "Tool", + QT_TRANSLATE_NOOP("App::Property", "Direction of spindle rotation"), + ) + obj.addProperty( + "App::PropertySpeed", + "VertFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for vertical moves in Z"), + ) + obj.addProperty( + "App::PropertySpeed", + "HorizFeed", + "Feed", + QT_TRANSLATE_NOOP("App::Property", "Feed rate for horizontal moves"), + ) + obj.addProperty( + "App::PropertySpeed", + "VertRapid", + "Rapid", + QT_TRANSLATE_NOOP("App::Property", "Rapid rate for vertical moves in Z"), + ) + obj.addProperty( + "App::PropertySpeed", + "HorizRapid", + "Rapid", + QT_TRANSLATE_NOOP("App::Property", "Rapid rate for horizontal moves"), + ) + obj.setEditorMode("Placement", 2) + + for n in self.propertyEnumerations(): + setattr(obj, n[0], n[1]) if createTool: self.ensureUseLegacyTool(obj, legacyTool) + @classmethod + def propertyEnumerations(self, dataType="data"): + """helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + + # Enumeration lists for App::PropertyEnumeration properties + enums = { + "SpindleDir": [ + (translate("Path_ToolController", "Forward"), "Forward"), + (translate("Path_ToolController", "Reverse"), "Reverse"), + ], # this is the direction that the profile runs + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + PathLog.debug(enums) + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + PathLog.debug(data) + + return data + def onDocumentRestored(self, obj): - obj.setEditorMode('Placement', 2) + obj.setEditorMode("Placement", 2) def onDelete(self, obj, arg2=None): # pylint: disable=unused-argument if not self.usesLegacyTool(obj): - if hasattr(obj.Tool, 'InList') and len(obj.Tool.InList) == 1: - if hasattr(obj.Tool.Proxy, 'onDelete'): + if hasattr(obj.Tool, "InList") and len(obj.Tool.InList) == 1: + if hasattr(obj.Tool.Proxy, "onDelete"): obj.Tool.Proxy.onDelete(obj.Tool) def setFromTemplate(self, obj, template): - ''' + """ setFromTemplate(obj, xmlItem) ... extract properties from xmlItem and assign to receiver. - ''' + """ PathLog.track(obj.Name, template) version = 0 if template.get(ToolControllerTemplate.Version): @@ -108,51 +179,79 @@ class ToolController: if template.get(ToolControllerTemplate.HorizRapid): obj.HorizRapid = template.get(ToolControllerTemplate.HorizRapid) if template.get(ToolControllerTemplate.SpindleSpeed): - obj.SpindleSpeed = float(template.get(ToolControllerTemplate.SpindleSpeed)) + obj.SpindleSpeed = float( + template.get(ToolControllerTemplate.SpindleSpeed) + ) if template.get(ToolControllerTemplate.SpindleDir): obj.SpindleDir = template.get(ToolControllerTemplate.SpindleDir) if template.get(ToolControllerTemplate.ToolNumber): - obj.ToolNumber = int(template.get(ToolControllerTemplate.ToolNumber)) + obj.ToolNumber = int( + template.get(ToolControllerTemplate.ToolNumber) + ) if template.get(ToolControllerTemplate.Tool): - toolVersion = template.get(ToolControllerTemplate.Tool).get(ToolControllerTemplate.Version) + toolVersion = template.get(ToolControllerTemplate.Tool).get( + ToolControllerTemplate.Version + ) if toolVersion == 1: self.ensureUseLegacyTool(obj, True) - obj.Tool.setFromTemplate(template.get(ToolControllerTemplate.Tool)) + obj.Tool.setFromTemplate( + template.get(ToolControllerTemplate.Tool) + ) else: self.ensureUseLegacyTool(obj, False) - obj.Tool = PathToolBit.Factory.CreateFromAttrs(template.get(ToolControllerTemplate.Tool)) - if obj.Tool and obj.Tool.ViewObject and obj.Tool.ViewObject.Visibility: + obj.Tool = PathToolBit.Factory.CreateFromAttrs( + template.get(ToolControllerTemplate.Tool) + ) + if ( + obj.Tool + and obj.Tool.ViewObject + and obj.Tool.ViewObject.Visibility + ): obj.Tool.ViewObject.Visibility = False if template.get(ToolControllerTemplate.Expressions): for exprDef in template.get(ToolControllerTemplate.Expressions): if exprDef[ToolControllerTemplate.ExprExpr]: - obj.setExpression(exprDef[ToolControllerTemplate.ExprProp], exprDef[ToolControllerTemplate.ExprExpr]) + obj.setExpression( + exprDef[ToolControllerTemplate.ExprProp], + exprDef[ToolControllerTemplate.ExprExpr], + ) else: - PathLog.error(translate('PathToolController', "Unsupported PathToolController template version %s") % template.get(ToolControllerTemplate.Version)) + PathLog.error( + "Unsupported PathToolController template version {}".format( + template.get(ToolControllerTemplate.Version) + ) + ) else: - PathLog.error(translate('PathToolController', 'PathToolController template has no version - corrupted template file?')) + PathLog.error( + "PathToolController template has no version - corrupted template file?" + ) def templateAttrs(self, obj): - '''templateAttrs(obj) ... answer a dictionary with all properties that should be stored for a template.''' + """templateAttrs(obj) ... answer a dictionary with all properties that should be stored for a template.""" attrs = {} - attrs[ToolControllerTemplate.Version] = 1 - attrs[ToolControllerTemplate.Name] = obj.Name - attrs[ToolControllerTemplate.Label] = obj.Label - attrs[ToolControllerTemplate.ToolNumber] = obj.ToolNumber - attrs[ToolControllerTemplate.VertFeed] = ("%s" % (obj.VertFeed)) - attrs[ToolControllerTemplate.HorizFeed] = ("%s" % (obj.HorizFeed)) - attrs[ToolControllerTemplate.VertRapid] = ("%s" % (obj.VertRapid)) - attrs[ToolControllerTemplate.HorizRapid] = ("%s" % (obj.HorizRapid)) + attrs[ToolControllerTemplate.Version] = 1 + attrs[ToolControllerTemplate.Name] = obj.Name + attrs[ToolControllerTemplate.Label] = obj.Label + attrs[ToolControllerTemplate.ToolNumber] = obj.ToolNumber + attrs[ToolControllerTemplate.VertFeed] = "%s" % (obj.VertFeed) + attrs[ToolControllerTemplate.HorizFeed] = "%s" % (obj.HorizFeed) + attrs[ToolControllerTemplate.VertRapid] = "%s" % (obj.VertRapid) + attrs[ToolControllerTemplate.HorizRapid] = "%s" % (obj.HorizRapid) attrs[ToolControllerTemplate.SpindleSpeed] = obj.SpindleSpeed - attrs[ToolControllerTemplate.SpindleDir] = obj.SpindleDir + attrs[ToolControllerTemplate.SpindleDir] = obj.SpindleDir if self.usesLegacyTool(obj): - attrs[ToolControllerTemplate.Tool] = obj.Tool.templateAttrs() + attrs[ToolControllerTemplate.Tool] = obj.Tool.templateAttrs() else: - attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool) + attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool) expressions = [] for expr in obj.ExpressionEngine: - PathLog.debug('%s: %s' % (expr[0], expr[1])) - expressions.append({ToolControllerTemplate.ExprProp: expr[0], ToolControllerTemplate.ExprExpr: expr[1]}) + PathLog.debug("%s: %s" % (expr[0], expr[1])) + expressions.append( + { + ToolControllerTemplate.ExprProp: expr[0], + ToolControllerTemplate.ExprExpr: expr[1], + } + ) if expressions: attrs[ToolControllerTemplate.Expressions] = expressions return attrs @@ -161,24 +260,23 @@ class ToolController: PathLog.track() commands = "" - commands += "(" + obj.Label + ")"+'\n' - commands += 'M6 T'+str(obj.ToolNumber)+'\n' + commands += "(" + obj.Label + ")" + "\n" + commands += "M6 T" + str(obj.ToolNumber) + "\n" # If a toolbit is used, check to see if spindlepower is allowed. # This is to prevent accidentally spinning the spindle with an # unpowered tool like probe or dragknife allowSpindlePower = True - if (not isinstance(obj.Tool, Path.Tool) and - hasattr(obj.Tool, "SpindlePower")): - allowSpindlePower = obj.Tool.SpindlePower + if not isinstance(obj.Tool, Path.Tool) and hasattr(obj.Tool, "SpindlePower"): + allowSpindlePower = obj.Tool.SpindlePower if allowSpindlePower: - PathLog.debug('selected tool preventing spindle power') - if obj.SpindleDir == 'Forward': - commands += 'M3 S' + str(obj.SpindleSpeed) + '\n' + PathLog.debug("selected tool preventing spindle power") + if obj.SpindleDir == "Forward": + commands += "M3 S" + str(obj.SpindleSpeed) + "\n" else: - commands += 'M4 S' + str(obj.SpindleSpeed) + '\n' + commands += "M4 S" + str(obj.SpindleSpeed) + "\n" if commands == "": commands += "(No commands processed)" @@ -189,32 +287,56 @@ class ToolController: obj.ViewObject.Visibility = True def getTool(self, obj): - '''returns the tool associated with this tool controller''' + """returns the tool associated with this tool controller""" PathLog.track() return obj.Tool def usesLegacyTool(self, obj): - '''returns True if the tool being controlled is a legacy tool''' + """returns True if the tool being controlled is a legacy tool""" return isinstance(obj.Tool, Path.Tool) def ensureUseLegacyTool(self, obj, legacy): - if not hasattr(obj, 'Tool') or (legacy != self.usesLegacyTool(obj)): - if legacy and hasattr(obj, 'Tool') and len(obj.Tool.InList) == 1: - if hasattr(obj.Tool.Proxy, 'onDelete'): + if not hasattr(obj, "Tool") or (legacy != self.usesLegacyTool(obj)): + if legacy and hasattr(obj, "Tool") and len(obj.Tool.InList) == 1: + if hasattr(obj.Tool.Proxy, "onDelete"): obj.Tool.Proxy.onDelete(obj.Tool) obj.Document.removeObject(obj.Tool.Name) - if hasattr(obj, 'Tool'): - obj.removeProperty('Tool') + if hasattr(obj, "Tool"): + obj.removeProperty("Tool") if legacy: - obj.addProperty("Path::PropertyTool", "Tool", "Base", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The tool used by this controller")) + obj.addProperty( + "Path::PropertyTool", + "Tool", + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "The tool used by this controller" + ), + ) else: - obj.addProperty("App::PropertyLink", "Tool", "Base", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The tool used by this controller")) + obj.addProperty( + "App::PropertyLink", + "Tool", + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "The tool used by this controller" + ), + ) -def Create(name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider=True, assignTool=True): - legacyTool = PathPreferences.toolsUseLegacyTools() if tool is None else isinstance(tool, Path.Tool) +def Create( + name="TC: Default Tool", + tool=None, + toolNumber=1, + assignViewProvider=True, + assignTool=True, +): + legacyTool = ( + PathPreferences.toolsUseLegacyTools() + if tool is None + else isinstance(tool, Path.Tool) + ) PathLog.track(tool, toolNumber, legacyTool) diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/PathScripts/PathToolControllerGui.py index 905888026e..91595b9168 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/PathScripts/PathToolControllerGui.py @@ -20,45 +20,50 @@ # * * # *************************************************************************** +from PySide import QtCore, QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathGui as PathGui import PathScripts.PathLog as PathLog import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolEdit as PathToolEdit import PathScripts.PathUtil as PathUtil - -from PySide import QtCore, QtGui +import PathScripts.PathToolController as PathToolController # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader('Part', globals(), 'Part') + +Part = LazyLoader("Part", globals(), "Part") -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +translate = FreeCAD.Qt.translate class ViewProvider: - def __init__(self, vobj): vobj.Proxy = self self.vobj = vobj def attach(self, vobj): mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('Transparency', mode) - vobj.setEditorMode('Visibility', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) + vobj.setEditorMode("ShapeColor", mode) + vobj.setEditorMode("Transparency", mode) + vobj.setEditorMode("Visibility", mode) self.vobj = vobj def __getstate__(self): @@ -73,12 +78,12 @@ class ViewProvider: def onChanged(self, vobj, prop): # pylint: disable=unused-argument mode = 2 - vobj.setEditorMode('LineWidth', mode) - vobj.setEditorMode('MarkerColor', mode) - vobj.setEditorMode('NormalColor', mode) - vobj.setEditorMode('DisplayMode', mode) - vobj.setEditorMode('BoundingBox', mode) - vobj.setEditorMode('Selectable', mode) + vobj.setEditorMode("LineWidth", mode) + vobj.setEditorMode("MarkerColor", mode) + vobj.setEditorMode("NormalColor", mode) + vobj.setEditorMode("DisplayMode", mode) + vobj.setEditorMode("BoundingBox", mode) + vobj.setEditorMode("Selectable", mode) def onDelete(self, vobj, args=None): # pylint: disable=unused-argument @@ -115,7 +120,7 @@ class ViewProvider: PathLog.track() for action in menu.actions(): menu.removeAction(action) - action = QtGui.QAction(translate('Path', 'Edit'), menu) + action = QtGui.QAction(translate("Path", "Edit"), menu) action.triggered.connect(self.setEdit) menu.addAction(action) @@ -126,7 +131,7 @@ class ViewProvider: return [] -def Create(name='Default Tool', tool=None, toolNumber=1): +def Create(name="Default Tool", tool=None, toolNumber=1): PathLog.track(tool, toolNumber) obj = PathScripts.PathToolController.Create(name, tool, toolNumber) @@ -142,16 +147,20 @@ class CommandPathToolController(object): # pylint: disable=no-init def GetResources(self): - return {'Pixmap': 'Path_LengthOffset', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolController", "Add Tool Controller to the Job"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolController", "Add Tool Controller")} + return { + "Pixmap": "Path_LengthOffset", + "MenuText": QT_TRANSLATE_NOOP( + "Path_ToolController", "Add Tool Controller to the Job" + ), + "ToolTip": QT_TRANSLATE_NOOP("Path_ToolController", "Add Tool Controller"), + } def selectedJob(self): if FreeCAD.ActiveDocument: sel = FreeCADGui.Selection.getSelectionEx() - if sel and sel[0].Object.Name[:3] == 'Job': + if sel and sel[0].Object.Name[:3] == "Job": return sel[0].Object - jobs = [o for o in FreeCAD.ActiveDocument.Objects if o.Name[:3] == 'Job'] + jobs = [o for o in FreeCAD.ActiveDocument.Objects if o.Name[:3] == "Job"] if 1 == len(jobs): return jobs[0] return None @@ -178,30 +187,69 @@ class CommandPathToolController(object): class ToolControllerEditor(object): - def __init__(self, obj, asDialog): self.form = FreeCADGui.PySideUic.loadUi(":/panels/DlgToolControllerEdit.ui") if not asDialog: self.form.buttonBox.hide() self.obj = obj - self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj, - 'VertFeed') - self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj, - 'HorizFeed') - self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj, - 'VertRapid') - self.horizRapid = PathGui.QuantitySpinBox(self.form.horizRapid, obj, - 'HorizRapid') + comboToPropertyMap = [("spindleDirection", "SpindleDir")] + enumTups = PathToolController.ToolController.propertyEnumerations( + dataType="raw" + ) + + self.populateCombobox(self.form, enumTups, comboToPropertyMap) + self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj, "VertFeed") + self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj, "HorizFeed") + self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj, "VertRapid") + self.horizRapid = PathGui.QuantitySpinBox( + self.form.horizRapid, obj, "HorizRapid" + ) if obj.Proxy.usesLegacyTool(obj): - self.editor = PathToolEdit.ToolEditor(obj.Tool, - self.form.toolEditor) + self.editor = PathToolEdit.ToolEditor(obj.Tool, self.form.toolEditor) else: self.editor = None self.form.toolBox.widget(1).hide() self.form.toolBox.removeItem(1) + def selectInComboBox(self, name, combo): + """selectInComboBox(name, combo) ... + helper function to select a specific value in a combo box.""" + blocker = QtCore.QSignalBlocker(combo) + index = combo.currentIndex() # Save initial index + + # Search using currentData and return if found + newindex = combo.findData(name) + if newindex >= 0: + combo.setCurrentIndex(newindex) + return + + # if not found, search using current text + newindex = combo.findText(name, QtCore.Qt.MatchFixedString) + if newindex >= 0: + combo.setCurrentIndex(newindex) + return + + # not found, return unchanged + combo.setCurrentIndex(index) + return + + def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): + """fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations + ** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol. + Args: + form = UI form + enumTups = list of (translated_text, data_string) tuples + comboBoxesPropertyMap = list of (translated_text, data_string) tuples + """ + # Load appropriate enumerations in each combobox + for cb, prop in comboBoxesPropertyMap: + box = getattr(form, cb) # Get the combobox + box.clear() # clear the combobox + for text, data in enumTups[prop]: # load enumerations + box.addItem(text, data) + def updateUi(self): tc = self.obj self.form.tcName.setText(tc.Label) @@ -211,10 +259,14 @@ class ToolControllerEditor(object): self.vertFeed.updateSpinBox() self.vertRapid.updateSpinBox() self.form.spindleSpeed.setValue(tc.SpindleSpeed) - index = self.form.spindleDirection.findText(tc.SpindleDir, - QtCore.Qt.MatchFixedString) - if index >= 0: - self.form.spindleDirection.setCurrentIndex(index) + + self.selectInComboBox(tc.SpindleDir, self.form.spindleDirection) + + # index = self.form.spindleDirection.findText( + # tc.SpindleDir, QtCore.Qt.MatchFixedString + # ) + # if index >= 0: + # self.form.spindleDirection.setCurrentIndex(index) if self.editor: self.editor.updateUI() @@ -229,15 +281,14 @@ class ToolControllerEditor(object): self.horizRapid.updateProperty() self.vertRapid.updateProperty() tc.SpindleSpeed = self.form.spindleSpeed.value() - tc.SpindleDir = self.form.spindleDirection.currentText() + tc.SpindleDir = self.form.spindleDirection.currentData() if self.editor: self.editor.updateTool() tc.Tool = self.editor.tool except Exception as e: - PathLog.error(translate("PathToolController", - "Error updating TC: %s") % e) + PathLog.error("Error updating TC: {}".format(e)) def refresh(self): self.form.blockSignals(True) @@ -259,7 +310,6 @@ class ToolControllerEditor(object): class TaskPanel: - def __init__(self, obj): self.editor = ToolControllerEditor(obj, False) self.form = self.editor.form @@ -309,8 +359,7 @@ class TaskPanel: def setupUi(self): if self.editor.editor: t = Part.makeCylinder(1, 1) - self.toolrep = FreeCAD.ActiveDocument.addObject("Part::Feature", - "tool") + self.toolrep = FreeCAD.ActiveDocument.addObject("Part::Feature", "tool") self.toolrep.Shape = t self.setFields() @@ -337,6 +386,6 @@ class DlgToolControllerEdit: if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_ToolController', CommandPathToolController()) + FreeCADGui.addCommand("Path_ToolController", CommandPathToolController()) FreeCAD.Console.PrintLog("Loading PathToolControllerGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py index da2bae994e..5294b1d54e 100644 --- a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py +++ b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py @@ -21,7 +21,8 @@ # *************************************************************************** from __future__ import print_function - +from PySide import QtCore, QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path @@ -30,19 +31,20 @@ import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBitLibraryCmd as PathToolBitLibraryCmd import PathScripts.PathToolEdit as PathToolEdit -import PathScripts.PathUtils as PathUtils import PathScripts.PathToolLibraryManager as ToolLibraryManager +import PathScripts.PathUtils as PathUtils -from PySide import QtCore, QtGui -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate -class EditorPanel(): +class EditorPanel: def __init__(self, job, cb): self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolLibraryEditor.ui") self.TLM = ToolLibraryManager.ToolLibraryManager() @@ -88,7 +90,7 @@ class EditorPanel(): return toolslist[tooltype] def getMaterial(self, material): - '''gets a combobox index number for a given material or vice versa''' + """gets a combobox index number for a given material or vice versa""" matslist = Path.Tool.getToolMaterials(Path.Tool()) if isinstance(material, str): if material in matslist: @@ -99,7 +101,7 @@ class EditorPanel(): return matslist[material] def addTool(self): - '''adds new tool to the current tool table''' + """adds new tool to the current tool table""" tool = Path.Tool() editor = self.toolEditor(tool) @@ -111,19 +113,19 @@ class EditorPanel(): self.loadTable(listname) def delete(self): - '''deletes the selected tool''' + """deletes the selected tool""" listname = self.TLM.getCurrentTableName() model = self.form.ToolsList.model() for i in range(model.rowCount()): item = model.item(i, 0) if item.checkState(): t = model.index(i, 1) - self.TLM.delete(int(t.data()) ,listname) + self.TLM.delete(int(t.data()), listname) self.loadTable(listname) self.toolSelectionChanged() def editTool(self, currItem): - '''load the tool edit dialog''' + """load the tool edit dialog""" if not currItem: currItem = self.form.ToolsList.selectedIndexes()[1] @@ -141,7 +143,7 @@ class EditorPanel(): self.loadTable(listname) def moveUp(self): - '''moves a tool to a lower number, if possible''' + """moves a tool to a lower number, if possible""" item = self.form.ToolsList.selectedIndexes()[1].data() if item: number = int(item) @@ -152,7 +154,7 @@ class EditorPanel(): self.updateSelection(newNum) def moveDown(self): - '''moves a tool to a higher number, if possible''' + """moves a tool to a higher number, if possible""" item = self.form.ToolsList.selectedIndexes()[1].data() if item: number = int(item) @@ -163,7 +165,7 @@ class EditorPanel(): self.updateSelection(newNum) def duplicate(self): - '''duplicated the selected tool in the current tool table''' + """duplicated the selected tool in the current tool table""" item = self.form.ToolsList.selectedIndexes()[1].data() if item: number = int(item) @@ -174,7 +176,7 @@ class EditorPanel(): self.updateSelection(newNum) def updateSelection(self, number): - '''update the tool list selection to track moves''' + """update the tool list selection to track moves""" model = self.form.ToolsList.model() for i in range(model.rowCount()): if int(model.index(i, 1).data()) == number: @@ -182,24 +184,41 @@ class EditorPanel(): self.form.ToolsList.model().item(i, 0).setCheckState(QtCore.Qt.Checked) return - def importFile(self): - '''imports a tooltable from a file''' - filename = QtGui.QFileDialog.getOpenFileName(self.form, translate( "TooltableEditor", "Open tooltable", None), None, "{};;{};;{}".format(self.TLM.TooltableTypeJSON, self.TLM.TooltableTypeXML, self.TLM.TooltableTypeHeekscad)) + """imports a tooltable from a file""" + filename = QtGui.QFileDialog.getOpenFileName( + self.form, + translate("Path_ToolTable", "Open tooltable", None), + None, + "{};;{};;{}".format( + self.TLM.TooltableTypeJSON, + self.TLM.TooltableTypeXML, + self.TLM.TooltableTypeHeekscad, + ), + ) if filename[0]: listname = self.TLM.getNextToolTableName() if self.TLM.read(filename, listname): self.loadToolTables() def exportFile(self): - '''export a tooltable to a file''' - filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("TooltableEditor", "Save tooltable", None), None, "{};;{};;{}".format(self.TLM.TooltableTypeJSON, self.TLM.TooltableTypeXML, self.TLM.TooltableTypeLinuxCNC)) + """export a tooltable to a file""" + filename = QtGui.QFileDialog.getSaveFileName( + self.form, + translate("Path_ToolTable", "Save tooltable", None), + None, + "{};;{};;{}".format( + self.TLM.TooltableTypeJSON, + self.TLM.TooltableTypeXML, + self.TLM.TooltableTypeLinuxCNC, + ), + ) if filename[0]: listname = self.TLM.getCurrentTableName() self.TLM.write(filename, listname) def toolSelectionChanged(self, index=None): - ''' updates the ui when tools are selected''' + """updates the ui when tools are selected""" if index: self.form.ToolsList.selectRow(index.row()) @@ -222,7 +241,7 @@ class EditorPanel(): # only allow moving or deleting a single tool at a time. if checkCount == 1: - #make sure the row is highlighted when the check box gets ticked + # make sure the row is highlighted when the check box gets ticked self.form.ToolsList.selectRow(checkList[0]) self.form.ButtonDelete.setEnabled(True) self.form.ButtonUp.setEnabled(True) @@ -234,7 +253,7 @@ class EditorPanel(): self.form.btnCopyTools.setEnabled(False) def copyTools(self): - ''' copy selected tool ''' + """copy selected tool""" tools = [] model = self.form.ToolsList.model() for i in range(model.rowCount()): @@ -268,43 +287,54 @@ class EditorPanel(): for toolnum in tools: tool = self.TLM.getTool(currList, int(toolnum)) - PathLog.debug('tool: {}, toolnum: {}'.format(tool, toolnum)) + PathLog.debug("tool: {}, toolnum: {}".format(tool, toolnum)) if self.job: label = "T{}: {}".format(toolnum, tool.Name) - tc = PathScripts.PathToolController.Create(label, tool=tool, toolNumber=int(toolnum)) + tc = PathScripts.PathToolController.Create( + label, tool=tool, toolNumber=int(toolnum) + ) self.job.Proxy.addToolController(tc) else: for job in FreeCAD.ActiveDocument.findObjects("Path::Feature"): - if isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) and job.Label == targetlist: + if ( + isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) + and job.Label == targetlist + ): label = "T{}: {}".format(toolnum, tool.Name) - tc = PathScripts.PathToolController.Create(label, tool=tool, toolNumber=int(toolnum)) + tc = PathScripts.PathToolController.Create( + label, tool=tool, toolNumber=int(toolnum) + ) job.Proxy.addToolController(tc) if self.cb: self.cb() FreeCAD.ActiveDocument.recompute() def tableSelected(self, index): - ''' loads the tools for the selected tool table ''' - name = self.form.TableList.itemWidget(self.form.TableList.itemFromIndex(index)).getTableName() + """loads the tools for the selected tool table""" + name = self.form.TableList.itemWidget( + self.form.TableList.itemFromIndex(index) + ).getTableName() self.loadTable(name) def loadTable(self, name): - ''' loads the tools for the selected tool table ''' + """loads the tools for the selected tool table""" tooldata = self.TLM.getTools(name) if tooldata: self.form.ToolsList.setModel(tooldata) self.form.ToolsList.resizeColumnsToContents() - self.form.ToolsList.horizontalHeader().setResizeMode(self.form.ToolsList.model().columnCount() - 1, QtGui.QHeaderView.Stretch) + self.form.ToolsList.horizontalHeader().setResizeMode( + self.form.ToolsList.model().columnCount() - 1, QtGui.QHeaderView.Stretch + ) self.setCurrentToolTableByName(name) def addNewToolTable(self): - ''' adds new tool to selected tool table ''' + """adds new tool to selected tool table""" name = self.TLM.addNewToolTable() self.loadToolTables() self.loadTable(name) def loadToolTables(self): - ''' Load list of available tool tables ''' + """Load list of available tool tables""" self.form.TableList.clear() model = self.form.ToolsList.model() if model: @@ -314,44 +344,54 @@ class EditorPanel(): listWidgetItem = QtGui.QListWidgetItem() listItem = ToolTableListWidgetItem(self.TLM) listItem.setTableName(table.Name) - listItem.setIcon(QtGui.QPixmap(':/icons/Path_ToolTable.svg')) + listItem.setIcon(QtGui.QPixmap(":/icons/Path_ToolTable.svg")) listItem.toolMoved.connect(self.reloadReset) - listWidgetItem.setSizeHint(QtCore.QSize(0,40)) + listWidgetItem.setSizeHint(QtCore.QSize(0, 40)) self.form.TableList.addItem(listWidgetItem) self.form.TableList.setItemWidget(listWidgetItem, listItem) - #Load the first tooltable + # Load the first tooltable self.loadTable(self.TLM.getCurrentTableName()) def reloadReset(self): - ''' reloads the current tooltable''' + """reloads the current tooltable""" name = self.TLM.getCurrentTableName() self.loadTable(name) def setCurrentToolTableByName(self, name): - ''' get the current tool table ''' + """get the current tool table""" item = self.getToolTableByName(name) if item: self.form.TableList.setCurrentItem(item) def getToolTableByName(self, name): - ''' returns the listWidgetItem for the selected name''' + """returns the listWidgetItem for the selected name""" for i in range(self.form.TableList.count()): - tableName = self.form.TableList.itemWidget(self.form.TableList.item(i)).getTableName() + tableName = self.form.TableList.itemWidget( + self.form.TableList.item(i) + ).getTableName() if tableName == name: return self.form.TableList.item(i) return False def removeToolTable(self): - ''' delete the selected tool table ''' + """delete the selected tool table""" self.TLM.deleteToolTable() self.loadToolTables() def renameTable(self): - ''' provides dialog for new tablename and renames the selected tool table''' + """provides dialog for new tablename and renames the selected tool table""" name = self.TLM.getCurrentTableName() - newName, ok = QtGui.QInputDialog.getText(None, translate("TooltableEditor","Rename Tooltable"),translate("TooltableEditor","Enter Name:"),QtGui.QLineEdit.Normal,name) + newName, ok = QtGui.QInputDialog.getText( + None, + translate("Path_ToolTable", "Rename Tooltable"), + translate("Path_ToolTable", "Enter Name:"), + QtGui.QLineEdit.Normal, + name, + ) if ok and newName: - index = self.form.TableList.indexFromItem(self.getToolTableByName(name)).row() + index = self.form.TableList.indexFromItem( + self.getToolTableByName(name) + ).row() reloadTables = self.TLM.renameToolTable(newName, index) if reloadTables: self.loadToolTables() @@ -380,14 +420,21 @@ class EditorPanel(): self.form.TableList.itemChanged.connect(self.renameTable) self.form.ButtonAddToolTable.clicked.connect(self.addNewToolTable) - self.form.ButtonAddToolTable.setToolTip(translate("TooltableEditor","Add New Tool Table")) + self.form.ButtonAddToolTable.setToolTip( + translate("Path_ToolTable", "Add New Tool Table") + ) self.form.ButtonRemoveToolTable.clicked.connect(self.removeToolTable) - self.form.ButtonRemoveToolTable.setToolTip(translate("TooltableEditor","Delete Selected Tool Table")) + self.form.ButtonRemoveToolTable.setToolTip( + translate("Path_ToolTable", "Delete Selected Tool Table") + ) self.form.ButtonRenameToolTable.clicked.connect(self.renameTable) - self.form.ButtonRenameToolTable.setToolTip(translate("TooltableEditor","Rename Selected Tool Table")) + self.form.ButtonRenameToolTable.setToolTip( + translate("Path_ToolTable", "Rename Selected Tool Table") + ) self.setFields() + class ToolTableListWidgetItem(QtGui.QWidget): toolMoved = QtCore.Signal() @@ -405,13 +452,13 @@ class ToolTableListWidgetItem(QtGui.QWidget): self.mainLayout.addWidget(self.tableNameLabel, 1) self.setLayout(self.mainLayout) - def setTableName (self, text): + def setTableName(self, text): self.tableNameLabel.setText(text) def getTableName(self): return self.tableNameLabel.text() - def setIcon (self, icon): + def setIcon(self, icon): icon = icon.scaled(24, 24) self.iconQLabel.setPixmap(icon) @@ -434,7 +481,7 @@ class ToolTableListWidgetItem(QtGui.QWidget): self.toolMoved.emit() -class CommandToolLibraryEdit(): +class CommandToolLibraryEdit: def __init__(self): pass @@ -449,10 +496,12 @@ class CommandToolLibraryEdit(): cb() def GetResources(self): - return {'Pixmap' : 'Path_ToolTable', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolTable","Tool Manager"), - 'Accel': "P, T", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolTable","Tool Manager")} + return { + "Pixmap": "Path_ToolTable", + "MenuText": QT_TRANSLATE_NOOP("Path_ToolTable", "Tool Manager"), + "Accel": "P, T", + "ToolTip": QT_TRANSLATE_NOOP("Path_ToolTable", "Tool Manager"), + } def IsActive(self): return not FreeCAD.ActiveDocument is None @@ -460,6 +509,7 @@ class CommandToolLibraryEdit(): def Activated(self): self.edit() + if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_ToolLibraryEdit',CommandToolLibraryEdit()) + FreeCADGui.addCommand("Path_ToolLibraryEdit", CommandToolLibraryEdit()) diff --git a/src/Mod/Path/PathScripts/PathToolLibraryManager.py b/src/Mod/Path/PathScripts/PathToolLibraryManager.py index a352840597..cd5320a5c0 100644 --- a/src/Mod/Path/PathScripts/PathToolLibraryManager.py +++ b/src/Mod/Path/PathScripts/PathToolLibraryManager.py @@ -23,7 +23,6 @@ from __future__ import print_function import FreeCAD -import FreeCADGui import Path import PathScripts import PathScripts.PathLog as PathLog @@ -31,14 +30,15 @@ import PathScripts.PathUtil as PathUtil import json import os import xml.sax +from PySide import QtGui -from PySide import QtCore, QtGui +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) - -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +translate = FreeCAD.Qt.translate # Tooltable XML readers class FreeCADTooltableHandler(xml.sax.ContentHandler): @@ -62,7 +62,7 @@ class FreeCADTooltableHandler(xml.sax.ContentHandler): self.tool.ToolType = str(attrs["type"]) self.tool.Material = str(attrs["mat"]) # for some reason without the following line I get an error - #print attrs["diameter"] + # print attrs["diameter"] self.tool.Diameter = float(attrs["diameter"]) self.tool.LengthOffset = float(attrs["length"]) self.tool.FlatRadius = float(attrs["flat"]) @@ -80,7 +80,6 @@ class FreeCADTooltableHandler(xml.sax.ContentHandler): class HeeksTooltableHandler(xml.sax.ContentHandler): - def __init__(self): xml.sax.ContentHandler.__init__(self) self.tooltable = Path.Tooltable() @@ -115,15 +114,13 @@ class HeeksTooltableHandler(xml.sax.ContentHandler): elif m == "1": self.tool.Material = "Carbide" # for some reason without the following line I get an error - #print attrs["diameter"] + # print attrs["diameter"] self.tool.Diameter = float(attrs["diameter"]) self.tool.LengthOffset = float(attrs["tool_length_offset"]) self.tool.FlatRadius = float(attrs["flat_radius"]) self.tool.CornerRadius = float(attrs["corner_radius"]) - self.tool.CuttingEdgeAngle = float( - attrs["cutting_edge_angle"]) - self.tool.CuttingEdgeHeight = float( - attrs["cutting_edge_height"]) + self.tool.CuttingEdgeAngle = float(attrs["cutting_edge_angle"]) + self.tool.CuttingEdgeHeight = float(attrs["cutting_edge_height"]) # Call when an elements ends def endElement(self, name): @@ -134,17 +131,21 @@ class HeeksTooltableHandler(xml.sax.ContentHandler): self.tool = None -class ToolLibraryManager(): - ''' +class ToolLibraryManager: + """ The Tool Library is a list of individual tool tables. Each Tool Table can contain n tools. The tool library will be persisted to user preferences and all or part of the library can be exported to other formats - ''' + """ - TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.json)") - TooltableTypeXML = translate("PathToolLibraryManager", "Tooltable XML (*.xml)") - TooltableTypeHeekscad = translate("PathToolLibraryManager", "HeeksCAD tooltable (*.tooltable)") - TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)") + TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.json)") + TooltableTypeXML = translate("PathToolLibraryManager", "Tooltable XML (*.xml)") + TooltableTypeHeekscad = translate( + "PathToolLibraryManager", "HeeksCAD tooltable (*.tooltable)" + ) + TooltableTypeLinuxCNC = translate( + "PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)" + ) PreferenceMainLibraryXML = "ToolLibrary" PreferenceMainLibraryJSON = "ToolLibrary-Main" @@ -156,38 +157,40 @@ class ToolLibraryManager(): self.loadToolTables() def getToolTables(self): - ''' Return tool table list ''' + """Return tool table list""" return self.toolTables def getCurrentTableName(self): - ''' return the name of the currently loaded tool table ''' + """return the name of the currently loaded tool table""" return self.currentTableName def getCurrentTable(self): - ''' returns an object of the current tool table ''' + """returns an object of the current tool table""" return self.getTableFromName(self.currentTableName) def getTableFromName(self, name): - ''' get the tool table object from the name ''' + """get the tool table object from the name""" for table in self.toolTables: if table.Name == name: return table - def getNextToolTableName(self, tableName='Tool Table'): - ''' get a unique name for a new tool table ''' + def getNextToolTableName(self, tableName="Tool Table"): + """get a unique name for a new tool table""" iter = 1 tempName = tableName[-2:] - if tempName[0] == '-' and tempName[-1].isdigit(): + if tempName[0] == "-" and tempName[-1].isdigit(): tableName = tableName[:-2] - while any(table.Name == tableName + '-' + str(iter) for table in self.toolTables): + while any( + table.Name == tableName + "-" + str(iter) for table in self.toolTables + ): iter += 1 - return tableName + '-' + str(iter) + return tableName + "-" + str(iter) def addNewToolTable(self): - ''' creates a new tool table ''' + """creates a new tool table""" tt = Path.Tooltable() tt.Version = 1 name = self.getNextToolTableName() @@ -197,20 +200,27 @@ class ToolLibraryManager(): return name def deleteToolTable(self): - ''' deletes the selected tool table ''' + """deletes the selected tool table""" if len(self.toolTables): - index = next((index for (index, d) in enumerate(self.toolTables) if d.Name == self.currentTableName), None) + index = next( + ( + index + for (index, d) in enumerate(self.toolTables) + if d.Name == self.currentTableName + ), + None, + ) self.toolTables.pop(index) self.saveMainLibrary() def renameToolTable(self, newName, index): - ''' renames a tool table with the new name''' + """renames a tool table with the new name""" currentTableName = self.toolTables[index].Name if newName == currentTableName: - PathLog.error(translate('PathToolLibraryManager', "Tool Table Same Name")) + PathLog.error(translate("PathToolLibraryManager", "Tool Table Same Name")) return False if newName in self.toolTables: - PathLog.error(translate('PathToolLibraryManager', "Tool Table Name Exists")) + PathLog.error(translate("PathToolLibraryManager", "Tool Table Name Exists")) return False tt = self.getTableFromName(currentTableName) if tt: @@ -218,67 +228,74 @@ class ToolLibraryManager(): self.saveMainLibrary() return True - def templateAttrs(self): - ''' gets the tool table arributes ''' + """gets the tool table arributes""" toolTables = [] for tt in self.toolTables: tableData = {} - tableData['Version'] = 1 - tableData['TableName'] = tt.Name + tableData["Version"] = 1 + tableData["TableName"] = tt.Name toolData = {} for tool in tt.Tools: toolData[tool] = tt.Tools[tool].templateAttrs() - tableData['Tools'] = toolData + tableData["Tools"] = toolData toolTables.append(tableData) return toolTables def tooltableFromAttrs(self, stringattrs): - if stringattrs.get('Version') and 1 == int(stringattrs['Version']): + if stringattrs.get("Version") and 1 == int(stringattrs["Version"]): tt = Path.Tooltable() tt.Version = 1 tt.Name = self.getNextToolTableName() - if stringattrs.get('Version'): - tt.Version = stringattrs.get('Version') + if stringattrs.get("Version"): + tt.Version = stringattrs.get("Version") - if stringattrs.get('TableName'): - tt.Name = stringattrs.get('TableName') + if stringattrs.get("TableName"): + tt.Name = stringattrs.get("TableName") if any(table.Name == tt.Name for table in self.toolTables): tt.Name = self.getNextToolTableName(tt.Name) - for key, attrs in PathUtil.keyValueIter(stringattrs['Tools']): - tool = Path.Tool() - tool.Name = str(attrs["name"]) - tool.ToolType = str(attrs["tooltype"]) - tool.Material = str(attrs["material"]) - tool.Diameter = float(attrs["diameter"]) - tool.LengthOffset = float(attrs["lengthOffset"]) - tool.FlatRadius = float(attrs["flatRadius"]) - tool.CornerRadius = float(attrs["cornerRadius"]) - tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"]) - tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"]) - tt.setTool(int(key), tool) + for key, attrs in PathUtil.keyValueIter(stringattrs["Tools"]): + tool = Path.Tool() + tool.Name = str(attrs["name"]) + tool.ToolType = str(attrs["tooltype"]) + tool.Material = str(attrs["material"]) + tool.Diameter = float(attrs["diameter"]) + tool.LengthOffset = float(attrs["lengthOffset"]) + tool.FlatRadius = float(attrs["flatRadius"]) + tool.CornerRadius = float(attrs["cornerRadius"]) + tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"]) + tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"]) + tt.setTool(int(key), tool) return tt else: - PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable template version %s") % stringattrs.get('Version')) + PathLog.error( + translate( + "PathToolLibraryManager", + "Unsupported Path tooltable template version %s", + ) + % stringattrs.get("Version") + ) return None def loadToolTables(self): - ''' loads the tool tables from the stored data ''' + """loads the tool tables from the stored data""" self.toolTables = [] - self.currentTableName = '' + self.currentTableName = "" def addTable(tt): if tt: self.toolTables.append(tt) else: - PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable")) + PathLog.error( + translate("PathToolLibraryManager", "Unsupported Path tooltable") + ) prefString = self.prefs.GetString(self.PreferenceMainLibraryJSON, "") @@ -300,14 +317,14 @@ class ToolLibraryManager(): self.currentTableName = self.toolTables[0].Name def saveMainLibrary(self): - '''Persists the permanent library to FreeCAD user preferences''' + """Persists the permanent library to FreeCAD user preferences""" tmpstring = json.dumps(self.templateAttrs()) self.prefs.SetString(self.PreferenceMainLibraryJSON, tmpstring) self.loadToolTables() return True def getJobList(self): - '''Builds the list of all Tool Table lists''' + """Builds the list of all Tool Table lists""" tablelist = [] for o in FreeCAD.ActiveDocument.Objects: @@ -318,27 +335,29 @@ class ToolLibraryManager(): return tablelist def getTool(self, listname, toolnum): - ''' gets the tool object ''' + """gets the tool object""" tt = self.getTableFromName(listname) return tt.getTool(toolnum) def getTools(self, tablename): - '''returns the tool data for a given table''' + """returns the tool data for a given table""" tooldata = [] - tableExists = any(table.Name == tablename for table in self.toolTables) + tableExists = any(table.Name == tablename for table in self.toolTables) if tableExists: self.currentTableName = tablename else: return None tt = self.getTableFromName(tablename) - headers = ["","Tool Num.","Name","Tool Type","Diameter"] + headers = ["", "Tool Num.", "Name", "Tool Type", "Diameter"] model = QtGui.QStandardItemModel() model.setHorizontalHeaderLabels(headers) def unitconv(ivalue): val = FreeCAD.Units.Quantity(ivalue, FreeCAD.Units.Length) - displayed_val = val.UserString #just the displayed value-not the internal one + displayed_val = ( + val.UserString + ) # just the displayed value-not the internal one return displayed_val if tt: @@ -348,10 +367,10 @@ class ToolLibraryManager(): itemcheck = QtGui.QStandardItem() itemcheck.setCheckable(True) - itemNumber = QtGui.QStandardItem(str(number)) - itemName = QtGui.QStandardItem(t.Name) - itemToolType = QtGui.QStandardItem(t.ToolType) - itemDiameter = QtGui.QStandardItem(unitconv(t.Diameter)) + itemNumber = QtGui.QStandardItem(str(number)) + itemName = QtGui.QStandardItem(t.Name) + itemToolType = QtGui.QStandardItem(t.ToolType) + itemDiameter = QtGui.QStandardItem(unitconv(t.Diameter)) row = [itemcheck, itemNumber, itemName, itemToolType, itemDiameter] model.appendRow(row) @@ -367,9 +386,9 @@ class ToolLibraryManager(): try: fileExtension = os.path.splitext(filename[0])[1].lower() xmlHandler = None - if fileExtension == '.tooltable': + if fileExtension == ".tooltable": xmlHandler = HeeksTooltableHandler() - if fileExtension == '.xml': + if fileExtension == ".xml": xmlHandler = FreeCADTooltableHandler() if xmlHandler: @@ -405,41 +424,58 @@ class ToolLibraryManager(): else: return False - except Exception as e: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except print("could not parse file", e) - def write(self, filename, listname): "exports the tooltable to a file" tt = self.getTableFromName(listname) if tt: try: + def openFileWithExtension(name, ext): fext = os.path.splitext(name)[1].lower() if fext != ext: name = "{}{}".format(name, ext) - return (open(PathUtil.toUnicode(name), 'w'), name) + return (open(PathUtil.toUnicode(name), "w"), name) if filename[1] == self.TooltableTypeXML: - fp,fname = openFileWithExtension(filename[0], '.xml') + fp, fname = openFileWithExtension(filename[0], ".xml") fp.write('\n') fp.write(tt.Content) elif filename[1] == self.TooltableTypeLinuxCNC: - fp,fname = openFileWithExtension(filename[0], '.tbl') + fp, fname = openFileWithExtension(filename[0], ".tbl") for key in tt.Tools: t = tt.Tools[key] - fp.write("T{0} P{0} Y{1} Z{2} A{3} B{4} C{5} U{6} V{7} W{8} D{9} I{10} J{11} Q{12} ;{13}\n".format(key,0,t.LengthOffset,0,0,0,0,0,0,t.Diameter,0,0,0,t.Name)) + fp.write( + "T{0} P{0} Y{1} Z{2} A{3} B{4} C{5} U{6} V{7} W{8} D{9} I{10} J{11} Q{12} ;{13}\n".format( + key, + 0, + t.LengthOffset, + 0, + 0, + 0, + 0, + 0, + 0, + t.Diameter, + 0, + 0, + 0, + t.Name, + ) + ) else: - fp,fname = openFileWithExtension(filename[0], '.json') + fp, fname = openFileWithExtension(filename[0], ".json") json.dump(self.templateAttrs(), fp, sort_keys=True, indent=2) fp.close() print("Written ", PathUtil.toUnicode(fname)) - except Exception as e: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except print("Could not write file:", e) - def addnew(self, listname, tool, position = None): + def addnew(self, listname, tool, position=None): "adds a new tool at the end of the table" tt = self.getTableFromName(listname) if not tt: @@ -456,7 +492,7 @@ class ToolLibraryManager(): return newID def updateTool(self, listname, toolnum, tool): - '''updates tool data''' + """updates tool data""" tt = self.getTableFromName(listname) tt.deleteTool(toolnum) tt.setTool(toolnum, tool) @@ -498,7 +534,7 @@ class ToolLibraryManager(): return True, target def duplicate(self, number, listname): - ''' duplicates the selected tool in the selected tool table ''' + """duplicates the selected tool in the selected tool table""" tt = self.getTableFromName(listname) tool = tt.getTool(number).copy() tt.addTools(tool) @@ -510,7 +546,7 @@ class ToolLibraryManager(): return True, newID def moveToTable(self, number, listname): - ''' Moves the tool to selected tool table ''' + """Moves the tool to selected tool table""" fromTable = self.getTableFromName(self.getCurrentTableName()) toTable = self.getTableFromName(listname) tool = fromTable.getTool(number).copy() @@ -518,9 +554,9 @@ class ToolLibraryManager(): fromTable.deleteTool(number) def delete(self, number, listname): - '''deletes a tool from the current list''' + """deletes a tool from the current list""" tt = self.getTableFromName(listname) tt.deleteTool(number) if listname == self.getCurrentTableName(): self.saveMainLibrary() - return True \ No newline at end of file + return True