Merge pull request #5436 from sliptonic/bug/translationJob

[PATH] More translation cleanup (job, profile, dressups)
This commit is contained in:
sliptonic
2022-01-26 09:36:11 -06:00
committed by GitHub
19 changed files with 1950 additions and 878 deletions

File diff suppressed because one or more lines are too long

View File

@@ -21,6 +21,9 @@
# ***************************************************************************
from __future__ import print_function
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import PathScripts.PathDressup as PathDressup
@@ -29,48 +32,49 @@ import PathScripts.PathLog as PathLog
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
import math
from PySide import QtCore
from PathScripts.PathGeom import CmdMoveCW, CmdMoveStraight, CmdMoveArc, CmdMoveRapid
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils')
Part = LazyLoader('Part', globals(), 'Part')
LOG_MODULE = PathLog.thisModule()
PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE)
#PathLog.trackModule(LOG_MODULE)
DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
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())
movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
movestraight = ['G1', 'G01']
movecw = ['G2', 'G02']
moveccw = ['G3', 'G03']
movearc = movecw + moveccw
translate = FreeCAD.Qt.translate
movecommands = CmdMoveStraight + CmdMoveRapid + CmdMoveArc
def debugMarker(vector, label, color=None, radius=0.5):
if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG:
if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label)
obj.Label = label
obj.Radius = radius
obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0))
obj.Placement = FreeCAD.Placement(
vector, FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
)
if color:
obj.ViewObject.ShapeColor = color
def debugCircle(vector, r, label, color=None):
if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG:
if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label)
obj.Label = label
obj.Radius = r
obj.Height = 1
obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0))
obj.Placement = FreeCAD.Placement(
vector, FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
)
obj.ViewObject.Transparency = 90
if color:
obj.ViewObject.ShapeColor = color
@@ -79,9 +83,9 @@ def debugCircle(vector, r, label, color=None):
def addAngle(a1, a2):
a = a1 + a2
while a <= -math.pi:
a += 2*math.pi
a += 2 * math.pi
while a > math.pi:
a -= 2*math.pi
a -= 2 * math.pi
return a
@@ -102,7 +106,7 @@ def getAngle(v):
return a
def pointFromCommand(cmd, pt, X='X', Y='Y', Z='Z'):
def pointFromCommand(cmd, pt, X="X", Y="Y", Z="Z"):
x = cmd.Parameters.get(X, pt.x)
y = cmd.Parameters.get(Y, pt.y)
z = cmd.Parameters.get(Z, pt.z)
@@ -115,18 +119,20 @@ def edgesForCommands(cmds, startPt):
for cmd in cmds:
if cmd.Name in movecommands:
pt = pointFromCommand(cmd, lastPt)
if cmd.Name in movestraight:
if cmd.Name in CmdMoveStraight:
edges.append(Part.Edge(Part.LineSegment(lastPt, pt)))
elif cmd.Name in movearc:
center = lastPt + pointFromCommand(cmd, FreeCAD.Vector(0, 0, 0), 'I', 'J', 'K')
elif cmd.Name in CmdMoveArc:
center = lastPt + pointFromCommand(
cmd, FreeCAD.Vector(0, 0, 0), "I", "J", "K"
)
A = lastPt - center
B = pt - center
d = -B.x * A.y + B.y * A.x
if d == 0:
# we're dealing with half a circle here
angle = getAngle(A) + math.pi/2
if cmd.Name in movecw:
angle = getAngle(A) + math.pi / 2
if cmd.Name in CmdMoveCW:
angle -= math.pi
else:
C = A + B
@@ -143,19 +149,19 @@ def edgesForCommands(cmds, startPt):
class Style(object):
# pylint: disable=no-init
Dogbone = 'Dogbone'
Tbone_H = 'T-bone horizontal'
Tbone_V = 'T-bone vertical'
Tbone_L = 'T-bone long edge'
Tbone_S = 'T-bone short edge'
Dogbone = "Dogbone"
Tbone_H = "T-bone horizontal"
Tbone_V = "T-bone vertical"
Tbone_L = "T-bone long edge"
Tbone_S = "T-bone short edge"
All = [Dogbone, Tbone_H, Tbone_V, Tbone_L, Tbone_S]
class Side(object):
# pylint: disable=no-init
Left = 'Left'
Right = 'Right'
Left = "Left"
Right = "Right"
All = [Left, Right]
@classmethod
@@ -170,9 +176,9 @@ class Side(object):
class Incision(object):
# pylint: disable=no-init
Fixed = 'fixed'
Adaptive = 'adaptive'
Custom = 'custom'
Fixed = "fixed"
Adaptive = "adaptive"
Custom = "custom"
All = [Adaptive, Fixed, Custom]
@@ -194,8 +200,7 @@ class Smooth(object):
# be constant in all calculated results.
# Instances of Chord are generally considered immutable and all movement member
# functions return new instances.
class Chord (object):
class Chord(object):
def __init__(self, start=None, end=None):
if not start:
start = FreeCAD.Vector()
@@ -205,15 +210,22 @@ class Chord (object):
self.End = end
def __str__(self):
return "Chord([%g, %g, %g] -> [%g, %g, %g])" % (self.Start.x, self.Start.y, self.Start.z, self.End.x, self.End.y, self.End.z)
return "Chord([%g, %g, %g] -> [%g, %g, %g])" % (
self.Start.x,
self.Start.y,
self.Start.z,
self.End.x,
self.End.y,
self.End.z,
)
def moveTo(self, newEnd):
return Chord(self.End, newEnd)
def moveToParameters(self, params):
x = params.get('X', self.End.x)
y = params.get('Y', self.End.y)
z = params.get('Z', self.End.z)
x = params.get("X", self.End.x)
y = params.get("Y", self.End.y)
z = params.get("Z", self.End.z)
return self.moveTo(FreeCAD.Vector(x, y, z))
def moveBy(self, x, y, z):
@@ -244,14 +256,14 @@ class Chord (object):
# if the 2 vectors are identical, they head in the same direction
PathLog.debug(" {}.getDirectionOfVector({})".format(A, B))
if PathGeom.pointsCoincide(A, B):
return 'Straight'
d = -A.x*B.y + A.y*B.x
return "Straight"
d = -A.x * B.y + A.y * B.x
if d < 0:
return Side.Left
if d > 0:
return Side.Right
# at this point the only direction left is backwards
return 'Back'
return "Back"
def getDirectionOf(self, chordOrVector):
if type(chordOrVector) is Chord:
@@ -278,7 +290,7 @@ class Chord (object):
def commandParams(self, f):
params = {"X": self.End.x, "Y": self.End.y, "Z": self.End.z}
if f:
params['F'] = f
params["F"] = f
return params
def g1Command(self, f):
@@ -287,9 +299,9 @@ class Chord (object):
def arcCommand(self, cmd, center, f):
params = self.commandParams(f)
d = center - self.Start
params['I'] = d.x
params['J'] = d.y
params['K'] = 0
params["I"] = d.x
params["J"] = d.y
params["K"] = 0
return Path.Command(cmd, params)
def g2Command(self, center, f):
@@ -302,13 +314,17 @@ class Chord (object):
return not PathGeom.isRoughly(self.End.z, self.Start.z)
def isANoopMove(self):
PathLog.debug("{}.isANoopMove(): {}".format(self, PathGeom.pointsCoincide(self.Start, self.End)))
PathLog.debug(
"{}.isANoopMove(): {}".format(
self, PathGeom.pointsCoincide(self.Start, self.End)
)
)
return PathGeom.pointsCoincide(self.Start, self.End)
def foldsBackOrTurns(self, chord, side):
direction = chord.getDirectionOf(self)
PathLog.info(" - direction = %s/%s" % (direction, side))
return direction == 'Back' or direction == side
return direction == "Back" or direction == side
def connectsTo(self, chord):
return PathGeom.pointsCoincide(self.End, chord.Start)
@@ -335,7 +351,7 @@ class Bone(object):
if self.cAngle is None:
baseAngle = self.inChord.getAngleXY()
turnAngle = self.outChord.getAngle(self.inChord)
theta = addAngle(baseAngle, (turnAngle - math.pi)/2)
theta = addAngle(baseAngle, (turnAngle - math.pi) / 2)
if self.obj.Side == Side.Left:
theta = addAngle(theta, math.pi)
self.tAngle = turnAngle
@@ -345,7 +361,7 @@ class Bone(object):
def distance(self, toolRadius):
if self.cDist is None:
self.angle() # make sure the angles are initialized
self.cDist = toolRadius / math.cos(self.tAngle/2)
self.cDist = toolRadius / math.cos(self.tAngle / 2)
return self.cDist
def corner(self, toolRadius):
@@ -368,7 +384,10 @@ class Bone(object):
# moving directly towards the corner
PathLog.debug("adaptive - on target: %.2f - %.2f" % (distance, toolRadius))
return distance - toolRadius
PathLog.debug("adaptive - angles: corner=%.2f bone=%.2f diff=%.12f" % (theta/math.pi, boneAngle/math.pi, theta - boneAngle))
PathLog.debug(
"adaptive - angles: corner=%.2f bone=%.2f diff=%.12f"
% (theta / math.pi, boneAngle / math.pi, theta - boneAngle)
)
# The bones root and end point form a triangle with the intersection of the tool path
# with the toolRadius circle around the bone end point.
@@ -376,7 +395,9 @@ class Bone(object):
# c = distance
# b = self.toolRadius
# beta = fabs(boneAngle - theta)
beta = math.fabs(addAngle(boneAngle, -theta)) # pylint: disable=invalid-unary-operand-type
beta = math.fabs(
addAngle(boneAngle, -theta)
) # pylint: disable=invalid-unary-operand-type
D = (distance / toolRadius) * math.sin(beta)
if D > 1: # no intersection
PathLog.debug("adaptive - no intersection - no bone")
@@ -395,28 +416,68 @@ class Bone(object):
length2 = toolRadius * math.sin(alpha2) / math.sin(beta2)
length = min(length, length2)
PathLog.debug("adaptive corner=%.2f * %.2f˚ -> bone=%.2f * %.2f˚" % (distance, theta, length, boneAngle))
PathLog.debug(
"adaptive corner=%.2f * %.2f˚ -> bone=%.2f * %.2f˚"
% (distance, theta, length, boneAngle)
)
return length
class ObjectDressup(object):
def __init__(self, obj, base):
# Tool Properties
obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "The base path to modify"))
obj.addProperty("App::PropertyEnumeration", "Side", "Dressup", QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "The side of path to insert bones"))
obj.addProperty(
"App::PropertyLink",
"Base",
"Base",
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
)
obj.addProperty(
"App::PropertyEnumeration",
"Side",
"Dressup",
QT_TRANSLATE_NOOP(
"App::Property", "The side of path to insert bones"
),
)
obj.Side = [Side.Left, Side.Right]
obj.Side = Side.Right
obj.addProperty("App::PropertyEnumeration", "Style", "Dressup", QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "The style of bones"))
obj.addProperty(
"App::PropertyEnumeration",
"Style",
"Dressup",
QT_TRANSLATE_NOOP("App::Property", "The style of bones"),
)
obj.Style = Style.All
obj.Style = Style.Dogbone
obj.addProperty("App::PropertyIntegerList", "BoneBlacklist", "Dressup", QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "Bones that aren't dressed up"))
obj.addProperty(
"App::PropertyIntegerList",
"BoneBlacklist",
"Dressup",
QT_TRANSLATE_NOOP(
"App::Property", "Bones that aren't dressed up"
),
)
obj.BoneBlacklist = []
obj.setEditorMode('BoneBlacklist', 2) # hide this one
obj.addProperty("App::PropertyEnumeration", "Incision", "Dressup", QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "The algorithm to determine the bone length"))
obj.setEditorMode("BoneBlacklist", 2) # hide this one
obj.addProperty(
"App::PropertyEnumeration",
"Incision",
"Dressup",
QT_TRANSLATE_NOOP(
"App::Property", "The algorithm to determine the bone length"
),
)
obj.Incision = Incision.All
obj.Incision = Incision.Adaptive
obj.addProperty("App::PropertyFloat", "Custom", "Dressup", QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "Dressup length if Incision == custom"))
obj.addProperty(
"App::PropertyFloat",
"Custom",
"Dressup",
QT_TRANSLATE_NOOP(
"App::Property", "Dressup length if Incision == custom"
),
)
obj.Custom = 0.0
obj.Proxy = self
obj.Base = base
@@ -431,7 +492,7 @@ class ObjectDressup(object):
self.bones = None
def onDocumentRestored(self, obj):
obj.setEditorMode('BoneBlacklist', 2) # hide this one
obj.setEditorMode("BoneBlacklist", 2) # hide this one
def __getstate__(self):
return None
@@ -446,14 +507,29 @@ class ObjectDressup(object):
# Answer true if a dogbone could be on either end of the chord, given its command
def canAttachDogbone(self, cmd, chord):
return cmd.Name in movestraight and not chord.isAPlungeMove() and not chord.isANoopMove()
return (
cmd.Name in CmdMoveStraight
and not chord.isAPlungeMove()
and not chord.isANoopMove()
)
def shouldInsertDogbone(self, obj, inChord, outChord):
return outChord.foldsBackOrTurns(inChord, self.theOtherSideOf(obj.Side))
def findPivotIntersection(self, pivot, pivotEdge, edge, refPt, d, color):
# pylint: disable=unused-argument
PathLog.track("(%.2f, %.2f)^%.2f - [(%.2f, %.2f), (%.2f, %.2f)]" % (pivotEdge.Curve.Center.x, pivotEdge.Curve.Center.y, pivotEdge.Curve.Radius, edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y))
PathLog.track(
"(%.2f, %.2f)^%.2f - [(%.2f, %.2f), (%.2f, %.2f)]"
% (
pivotEdge.Curve.Center.x,
pivotEdge.Curve.Center.y,
pivotEdge.Curve.Radius,
edge.Vertexes[0].Point.x,
edge.Vertexes[0].Point.y,
edge.Vertexes[1].Point.x,
edge.Vertexes[1].Point.y,
)
)
ppt = None
pptDistance = 0
for pt in DraftGeomUtils.findIntersection(edge, pivotEdge, dts=False):
@@ -469,7 +545,9 @@ class ObjectDressup(object):
PathLog.debug("Taking tangent as intersect %s" % tangent)
ppt = pivot + tangent
else:
PathLog.debug("Taking chord start as intersect %s" % edge.Vertexes[0].Point)
PathLog.debug(
"Taking chord start as intersect %s" % edge.Vertexes[0].Point
)
ppt = edge.Vertexes[0].Point
# debugMarker(ppt, "ptt.%d-%s.in" % (self.boneId, d), color, 0.2)
PathLog.debug(" --> (%.2f, %.2f)" % (ppt.x, ppt.y))
@@ -479,15 +557,17 @@ class ObjectDressup(object):
param = edge.Curve.parameter(point)
return edge.FirstParameter <= param <= edge.LastParameter
def smoothChordCommands(self, bone, inChord, outChord, edge, wire, corner, smooth, color=None):
def smoothChordCommands(
self, bone, inChord, outChord, edge, wire, corner, smooth, color=None
):
if smooth == 0:
PathLog.info(" No smoothing requested")
return [bone.lastCommand, outChord.g1Command(bone.F)]
d = 'in'
d = "in"
refPoint = inChord.Start
if smooth == Smooth.Out:
d = 'out'
d = "out"
refPoint = outChord.End
if DraftGeomUtils.areColinear(inChord.asEdge(), outChord.asEdge()):
@@ -497,15 +577,36 @@ class ObjectDressup(object):
pivot = None
pivotDistance = 0
PathLog.info("smooth: (%.2f, %.2f)-(%.2f, %.2f)" % (edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y))
PathLog.info(
"smooth: (%.2f, %.2f)-(%.2f, %.2f)"
% (
edge.Vertexes[0].Point.x,
edge.Vertexes[0].Point.y,
edge.Vertexes[1].Point.x,
edge.Vertexes[1].Point.y,
)
)
for e in wire.Edges:
self.dbg.append(e)
if type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line:
PathLog.debug(" (%.2f, %.2f)-(%.2f, %.2f)" % (e.Vertexes[0].Point.x, e.Vertexes[0].Point.y, e.Vertexes[1].Point.x, e.Vertexes[1].Point.y))
PathLog.debug(
" (%.2f, %.2f)-(%.2f, %.2f)"
% (
e.Vertexes[0].Point.x,
e.Vertexes[0].Point.y,
e.Vertexes[1].Point.x,
e.Vertexes[1].Point.y,
)
)
else:
PathLog.debug(" (%.2f, %.2f)^%.2f" % (e.Curve.Center.x, e.Curve.Center.y, e.Curve.Radius))
PathLog.debug(
" (%.2f, %.2f)^%.2f"
% (e.Curve.Center.x, e.Curve.Center.y, e.Curve.Radius)
)
for pt in DraftGeomUtils.findIntersection(edge, e, True, findAll=True):
if not PathGeom.pointsCoincide(pt, corner) and self.pointIsOnEdge(pt, e):
if not PathGeom.pointsCoincide(pt, corner) and self.pointIsOnEdge(
pt, e
):
# debugMarker(pt, "candidate-%d-%s" % (self.boneId, d), color, 0.05)
PathLog.debug(" -> candidate")
distance = (pt - refPoint).Length
@@ -518,9 +619,15 @@ class ObjectDressup(object):
if pivot:
# debugCircle(pivot, self.toolRadius, "pivot.%d-%s" % (self.boneId, d), color)
pivotEdge = Part.Edge(Part.Circle(pivot, FreeCAD.Vector(0, 0, 1), self.toolRadius))
t1 = self.findPivotIntersection(pivot, pivotEdge, inChord.asEdge(), inChord.End, d, color)
t2 = self.findPivotIntersection(pivot, pivotEdge, outChord.asEdge(), inChord.End, d, color)
pivotEdge = Part.Edge(
Part.Circle(pivot, FreeCAD.Vector(0, 0, 1), self.toolRadius)
)
t1 = self.findPivotIntersection(
pivot, pivotEdge, inChord.asEdge(), inChord.End, d, color
)
t2 = self.findPivotIntersection(
pivot, pivotEdge, outChord.asEdge(), inChord.End, d, color
)
commands = []
if not PathGeom.pointsCoincide(t1, inChord.Start):
@@ -530,7 +637,10 @@ class ObjectDressup(object):
PathLog.debug(" add g3 command")
commands.append(Chord(t1, t2).g3Command(pivot, bone.F))
else:
PathLog.debug(" add g2 command center=(%.2f, %.2f) -> from (%2f, %.2f) to (%.2f, %.2f" % (pivot.x, pivot.y, t1.x, t1.y, t2.x, t2.y))
PathLog.debug(
" add g2 command center=(%.2f, %.2f) -> from (%2f, %.2f) to (%.2f, %.2f"
% (pivot.x, pivot.y, t1.x, t1.y, t2.x, t2.y)
)
commands.append(Chord(t1, t2).g2Command(pivot, bone.F))
if not PathGeom.pointsCoincide(t2, outChord.End):
PathLog.debug(" add lead out")
@@ -575,7 +685,12 @@ class ObjectDressup(object):
bone.tip = boneInChord.End
if bone.smooth == 0:
return [bone.lastCommand, boneInChord.g1Command(bone.F), boneOutChord.g1Command(bone.F), bone.outChord.g1Command(bone.F)]
return [
bone.lastCommand,
boneInChord.g1Command(bone.F),
boneOutChord.g1Command(bone.F),
bone.outChord.g1Command(bone.F),
]
# reconstruct the corner and convert to an edge
offset = corner - bone.inChord.End
@@ -587,7 +702,7 @@ class ObjectDressup(object):
# construct a shape representing the cut made by the bone
vt0 = FreeCAD.Vector(0, self.toolRadius, 0)
vt1 = FreeCAD.Vector(length, self.toolRadius, 0)
vt1 = FreeCAD.Vector(length, self.toolRadius, 0)
vb0 = FreeCAD.Vector(0, -self.toolRadius, 0)
vb1 = FreeCAD.Vector(length, -self.toolRadius, 0)
vm2 = FreeCAD.Vector(length + self.toolRadius, 0, 0)
@@ -601,12 +716,32 @@ class ObjectDressup(object):
boneArc = Part.Arc(vt1, vm2, vb1)
# boneArc = Part.Circle(FreeCAD.Vector(length, 0, 0), FreeCAD.Vector(0,0,1), self.toolRadius)
boneWire = Part.Shape([boneTop, boneArc, boneBot, boneLid])
boneWire.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), boneAngle * 180 / math.pi)
boneWire.rotate(
FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), boneAngle * 180 / math.pi
)
boneWire.translate(bone.inChord.End)
self.boneShapes = [cornerShape, boneWire]
bone.inCommands = self.smoothChordCommands(bone, bone.inChord, boneInChord, Part.Edge(iLine), boneWire, corner, bone.smooth & Smooth.In, (1., 0., 0.))
bone.outCommands = self.smoothChordCommands(bone, boneOutChord, bone.outChord, Part.Edge(oLine), boneWire, corner, bone.smooth & Smooth.Out, (0., 1., 0.))
bone.inCommands = self.smoothChordCommands(
bone,
bone.inChord,
boneInChord,
Part.Edge(iLine),
boneWire,
corner,
bone.smooth & Smooth.In,
(1.0, 0.0, 0.0),
)
bone.outCommands = self.smoothChordCommands(
bone,
boneOutChord,
bone.outChord,
Part.Edge(oLine),
boneWire,
corner,
bone.smooth & Smooth.Out,
(0.0, 1.0, 0.0),
)
return bone.inCommands + bone.outCommands
def dogbone(self, bone):
@@ -617,13 +752,13 @@ class ObjectDressup(object):
def tboneHorizontal(self, bone):
angle = bone.angle()
boneAngle = 0
if math.fabs(angle) > math.pi/2:
if math.fabs(angle) > math.pi / 2:
boneAngle = math.pi
return self.inOutBoneCommands(bone, boneAngle, self.toolRadius)
def tboneVertical(self, bone):
angle = bone.angle()
boneAngle = math.pi/2
boneAngle = math.pi / 2
if PathGeom.isRoughly(angle, math.pi) or angle < 0:
boneAngle = -boneAngle
return self.inOutBoneCommands(bone, boneAngle, self.toolRadius)
@@ -635,14 +770,22 @@ class ObjectDressup(object):
boneAngle = bone.outChord.getAngleXY()
if Side.Right == bone.outChord.getDirectionOf(bone.inChord):
boneAngle = boneAngle - math.pi/2
boneAngle = boneAngle - math.pi / 2
else:
boneAngle = boneAngle + math.pi/2
boneAngle = boneAngle + math.pi / 2
onInString = 'out'
onInString = "out"
if onIn:
onInString = 'in'
PathLog.debug("tboneEdge boneAngle[%s]=%.2f (in=%.2f, out=%.2f)" % (onInString, boneAngle/math.pi, bone.inChord.getAngleXY()/math.pi, bone.outChord.getAngleXY()/math.pi))
onInString = "in"
PathLog.debug(
"tboneEdge boneAngle[%s]=%.2f (in=%.2f, out=%.2f)"
% (
onInString,
boneAngle / math.pi,
bone.inChord.getAngleXY() / math.pi,
bone.outChord.getAngleXY() / math.pi,
)
)
return self.inOutBoneCommands(bone, boneAngle, self.toolRadius)
def tboneLongEdge(self, bone):
@@ -661,7 +804,7 @@ class ObjectDressup(object):
elif bone.location() in self.locationBlacklist:
bone.obj.BoneBlacklist.append(bone.boneId)
blacklisted = True
elif hasattr(bone.obj.Base, 'BoneBlacklist'):
elif hasattr(bone.obj.Base, "BoneBlacklist"):
parentConsumed = bone.boneId not in bone.obj.Base.BoneBlacklist
blacklisted = parentConsumed
if blacklisted:
@@ -685,7 +828,10 @@ class ObjectDressup(object):
return [bone.lastCommand, bone.outChord.g1Command(bone.F)]
def insertBone(self, bone):
PathLog.debug(">----------------------------------- %d --------------------------------------" % bone.boneId)
PathLog.debug(
">----------------------------------- %d --------------------------------------"
% bone.boneId
)
self.boneShapes = []
blacklisted, inaccessible = self.boneIsBlacklisted(bone)
enabled = not blacklisted
@@ -701,7 +847,10 @@ class ObjectDressup(object):
bone.commands = commands
self.shapes[bone.boneId] = self.boneShapes
PathLog.debug("<----------------------------------- %d --------------------------------------" % bone.boneId)
PathLog.debug(
"<----------------------------------- %d --------------------------------------"
% bone.boneId
)
return commands
def removePathCrossing(self, commands, bone1, bone2):
@@ -709,36 +858,40 @@ class ObjectDressup(object):
bones = bone2.commands
if True and hasattr(bone1, "outCommands") and hasattr(bone2, "inCommands"):
inEdges = edgesForCommands(bone1.outCommands, bone1.tip)
outEdges = edgesForCommands(bone2.inCommands, bone2.inChord.Start)
outEdges = edgesForCommands(bone2.inCommands, bone2.inChord.Start)
for i in range(len(inEdges)):
e1 = inEdges[i]
for j in range(len(outEdges)-1, -1, -1):
for j in range(len(outEdges) - 1, -1, -1):
e2 = outEdges[j]
cutoff = DraftGeomUtils.findIntersection(e1, e2)
for pt in cutoff:
# debugCircle(e1.Curve.Center, e1.Curve.Radius, "bone.%d-1" % (self.boneId), (1.,0.,0.))
# debugCircle(e2.Curve.Center, e2.Curve.Radius, "bone.%d-2" % (self.boneId), (0.,1.,0.))
if PathGeom.pointsCoincide(pt, e1.valueAt(e1.LastParameter)) or PathGeom.pointsCoincide(pt, e2.valueAt(e2.FirstParameter)):
if PathGeom.pointsCoincide(
pt, e1.valueAt(e1.LastParameter)
) or PathGeom.pointsCoincide(pt, e2.valueAt(e2.FirstParameter)):
continue
# debugMarker(pt, "it", (0.0, 1.0, 1.0))
# 1. remove all redundant commands
commands = commands[:-(len(inEdges) - i)]
commands = commands[: -(len(inEdges) - i)]
# 2., correct where c1 ends
c1 = bone1.outCommands[i]
c1Params = c1.Parameters
c1Params.update({'X': pt.x, 'Y': pt.y, 'Z': pt.z})
c1Params.update({"X": pt.x, "Y": pt.y, "Z": pt.z})
c1 = Path.Command(c1.Name, c1Params)
commands.append(c1)
# 3. change where c2 starts, this depends on the command itself
c2 = bone2.inCommands[j]
if c2.Name in movearc:
if c2.Name in CmdMoveArc:
center = e2.Curve.Center
offset = center - pt
c2Params = c2.Parameters
c2Params.update({'I': offset.x, 'J': offset.y, 'K': offset.z})
c2Params.update(
{"I": offset.x, "J": offset.y, "K": offset.z}
)
c2 = Path.Command(c2.Name, c2Params)
bones = [c2]
bones.extend(bone2.commands[j+1:])
bones.extend(bone2.commands[j + 1 :])
else:
bones = bone2.commands[j:]
# there can only be the one ...
@@ -758,11 +911,13 @@ class ObjectDressup(object):
self.setup(obj, False)
commands = [] # the dressed commands
lastChord = Chord() # the last chord
lastCommand = None # the command that generated the last chord
lastBone = None # track last bone for optimizations
oddsAndEnds = [] # track chords that are connected to plunges - in case they form a loop
commands = [] # the dressed commands
lastChord = Chord() # the last chord
lastCommand = None # the command that generated the last chord
lastBone = None # track last bone for optimizations
oddsAndEnds = (
[]
) # track chords that are connected to plunges - in case they form a loop
boneId = 1
self.bones = []
@@ -782,32 +937,60 @@ class ObjectDressup(object):
thisChord = lastChord.moveToParameters(thisCommand.Parameters)
thisIsACandidate = self.canAttachDogbone(thisCommand, thisChord)
if thisIsACandidate and lastCommand and self.shouldInsertDogbone(obj, lastChord, thisChord):
if (
thisIsACandidate
and lastCommand
and self.shouldInsertDogbone(obj, lastChord, thisChord)
):
PathLog.info(" Found bone corner: {}".format(lastChord.End))
bone = Bone(boneId, obj, lastCommand, lastChord, thisChord, Smooth.InAndOut, thisCommand.Parameters.get('F'))
bone = Bone(
boneId,
obj,
lastCommand,
lastChord,
thisChord,
Smooth.InAndOut,
thisCommand.Parameters.get("F"),
)
bones = self.insertBone(bone)
boneId += 1
if lastBone:
PathLog.info(" removing potential path crossing")
# debugMarker(thisChord.Start, "it", (1.0, 0.0, 1.0))
commands, bones = self.removePathCrossing(commands, lastBone, bone)
commands, bones = self.removePathCrossing(
commands, lastBone, bone
)
commands.extend(bones[:-1])
lastCommand = bones[-1]
lastBone = bone
elif lastCommand and thisChord.isAPlungeMove():
PathLog.info(" Looking for connection in odds and ends")
haveNewLastCommand = False
for chord in (chord for chord in oddsAndEnds if lastChord.connectsTo(chord)):
for chord in (
chord for chord in oddsAndEnds if lastChord.connectsTo(chord)
):
if self.shouldInsertDogbone(obj, lastChord, chord):
PathLog.info(" and there is one")
PathLog.debug(" odd/end={} last={}".format(chord, lastChord))
bone = Bone(boneId, obj, lastCommand, lastChord, chord, Smooth.In, lastCommand.Parameters.get('F'))
PathLog.debug(
" odd/end={} last={}".format(chord, lastChord)
)
bone = Bone(
boneId,
obj,
lastCommand,
lastChord,
chord,
Smooth.In,
lastCommand.Parameters.get("F"),
)
bones = self.insertBone(bone)
boneId += 1
if lastBone:
PathLog.info(" removing potential path crossing")
# debugMarker(chord.Start, "it", (0.0, 1.0, 1.0))
commands, bones = self.removePathCrossing(commands, lastBone, bone)
commands, bones = self.removePathCrossing(
commands, lastBone, bone
)
commands.extend(bones[:-1])
lastCommand = bones[-1]
haveNewLastCommand = True
@@ -839,7 +1022,7 @@ class ObjectDressup(object):
lastChord = thisChord
else:
if thisCommand.Name[0] != '(':
if thisCommand.Name[0] != "(":
PathLog.info(" Clean slate")
if lastCommand:
commands.append(lastCommand)
@@ -861,12 +1044,12 @@ class ObjectDressup(object):
PathLog.info("Default side = right")
# otherwise dogbones are opposite of the base path's side
side = Side.Right
if hasattr(obj.Base, 'Side') and obj.Base.Side == 'Inside':
if hasattr(obj.Base, "Side") and obj.Base.Side == "Inside":
PathLog.info("inside -> side = left")
side = Side.Left
else:
PathLog.info("not inside -> side stays right")
if hasattr(obj.Base, 'Direction') and obj.Base.Direction == 'CCW':
if hasattr(obj.Base, "Direction") and obj.Base.Direction == "CCW":
PathLog.info("CCW -> switch sides")
side = Side.oppositeOf(side)
else:
@@ -890,7 +1073,7 @@ class ObjectDressup(object):
def boneStateList(self, obj):
state = {}
# If the receiver was loaded from file, then it never generated the bone list.
if not hasattr(self, 'bones'):
if not hasattr(self, "bones"):
self.execute(obj)
for (nr, loc, enabled, inaccessible) in self.bones:
item = state.get((loc[0], loc[1]))
@@ -901,14 +1084,14 @@ class ObjectDressup(object):
state[(loc[0], loc[1])] = (enabled, inaccessible, [nr], [loc[2]])
return state
class Marker(object):
class Marker(object):
def __init__(self, pt, r, h):
if PathGeom.isRoughly(h, 0):
h = 0.1
self.pt = pt
self.r = r
self.h = h
self.r = r
self.h = h
self.sep = coin.SoSeparator()
self.pos = coin.SoTranslation()
self.pos.translation = (pt.x, pt.y, pt.z + h / 2)
@@ -943,8 +1126,8 @@ class Marker(object):
def color(self, id):
if id == 1:
return coin.SbColor(.9, .9, .5)
return coin.SbColor(.9, .5, .9)
return coin.SbColor(0.9, 0.9, 0.5)
return coin.SbColor(0.9, 0.5, 0.9)
class TaskPanel(object):
@@ -957,7 +1140,7 @@ class TaskPanel(object):
self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(":/panels/DogboneEdit.ui")
self.s = None
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDogbone", "Edit Dogbone Dress-up"))
FreeCAD.ActiveDocument.openTransaction("Edit Dogbone Dress-up")
self.height = 10
self.markers = []
@@ -999,8 +1182,10 @@ class TaskPanel(object):
def updateBoneList(self):
itemList = []
for loc, (enabled, inaccessible, ids, zs) in PathUtil.keyValueIter(self.obj.Proxy.boneStateList(self.obj)):
lbl = '(%.2f, %.2f): %s' % (loc[0], loc[1], ','.join(str(id) for id in ids))
for loc, (enabled, inaccessible, ids, zs) in PathUtil.keyValueIter(
self.obj.Proxy.boneStateList(self.obj)
):
lbl = "(%.2f, %.2f): %s" % (loc[0], loc[1], ",".join(str(id) for id in ids))
item = QtGui.QListWidgetItem(lbl)
if enabled:
item.setCheckState(QtCore.Qt.CheckState.Checked)
@@ -1008,7 +1193,10 @@ class TaskPanel(object):
item.setCheckState(QtCore.Qt.CheckState.Unchecked)
flags = QtCore.Qt.ItemFlag.ItemIsSelectable
if not inaccessible:
flags |= QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable
flags |= (
QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsUserCheckable
)
item.setFlags(flags)
item.setData(self.DataIds, ids)
item.setData(self.DataKey, ids[0])
@@ -1020,7 +1208,13 @@ class TaskPanel(object):
self.form.bones.addItem(item)
loc = item.data(self.DataLoc)
r = max(self.obj.Proxy.length, 1)
markers.append(Marker(FreeCAD.Vector(loc[0], loc[1], min(zs)), r, max(1, max(zs) - min(zs))))
markers.append(
Marker(
FreeCAD.Vector(loc[0], loc[1], min(zs)),
r,
max(1, max(zs) - min(zs)),
)
)
for m in self.markers:
self.viewProvider.switch.removeChild(m.sep)
for m in markers:
@@ -1033,11 +1227,11 @@ class TaskPanel(object):
self.form.customLabel.setEnabled(customSelected)
self.updateBoneList()
if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG:
if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
for obj in FreeCAD.ActiveDocument.Objects:
if obj.Name.startswith('Shape'):
if obj.Name.startswith("Shape"):
FreeCAD.ActiveDocument.removeObject(obj.Name)
PathLog.info('object name %s' % self.obj.Name)
PathLog.info("object name %s" % self.obj.Name)
if hasattr(self.obj.Proxy, "shapes"):
PathLog.info("showing shapes attribute")
for shapes in self.obj.Proxy.shapes.values():
@@ -1091,23 +1285,27 @@ class TaskPanel(object):
for i, m in enumerate(self.markers):
m.setSelected(i == index)
class SelObserver(object):
def __init__(self):
import PathScripts.PathSelection as PST
PST.eselect()
def __del__(self):
import PathScripts.PathSelection as PST
PST.clear()
def addSelection(self, doc, obj, sub, pnt):
# pylint: disable=unused-argument
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')')
FreeCADGui.doCommand(
"Gui.Selection.addSelection(FreeCAD.ActiveDocument." + obj + ")"
)
FreeCADGui.updateGui()
class ViewProviderDressup(object):
def __init__(self, vobj):
self.vobj = vobj
self.obj = None
@@ -1148,7 +1346,7 @@ class ViewProviderDressup(object):
return None
def onDelete(self, arg1=None, arg2=None):
'''this makes sure that the base operation is added back to the project and visible'''
"""this makes sure that the base operation is added back to the project and visible"""
# pylint: disable=unused-argument
if arg1.Object and arg1.Object.Base:
FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True
@@ -1159,11 +1357,11 @@ class ViewProviderDressup(object):
return True
def Create(base, name='DogboneDressup'):
'''
def Create(base, name="DogboneDressup"):
"""
Create(obj, name='DogboneDressup') ... dresses the given PathProfile/PathContour object with dogbones.
'''
obj = FreeCAD.ActiveDocument.addObject('Path::FeaturePython', name)
"""
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
dbo = ObjectDressup(obj, base)
job = PathUtils.findParentJob(base)
job.Proxy.addOperation(obj, base)
@@ -1180,9 +1378,16 @@ class CommandDressupDogbone(object):
# pylint: disable=no-init
def GetResources(self):
return {'Pixmap': 'Path_Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "Dogbone Dress-up"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupDogbone", "Creates a Dogbone Dress-up object from a selected path")}
return {
"Pixmap": "Path_Dressup",
"MenuText": QT_TRANSLATE_NOOP(
"Path_DressupDogbone", "Dogbone Dress-up"
),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_DressupDogbone",
"Creates a Dogbone Dress-up object from a selected path",
),
}
def IsActive(self):
if FreeCAD.ActiveDocument is not None:
@@ -1196,17 +1401,25 @@ class CommandDressupDogbone(object):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
FreeCAD.Console.PrintError(translate("Path_DressupDogbone", "Please select one path object")+"\n")
FreeCAD.Console.PrintError(
translate("Path_DressupDogbone", "Please select one path object") + "\n"
)
return
baseObject = selection[0]
if not baseObject.isDerivedFrom("Path::Feature"):
FreeCAD.Console.PrintError(translate("Path_DressupDogbone", "The selected object is not a path")+"\n")
FreeCAD.Console.PrintError(
translate("Path_DressupDogbone", "The selected object is not a path")
+ "\n"
)
return
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDogbone", "Create Dogbone Dress-up"))
FreeCADGui.addModule('PathScripts.PathDressupDogbone')
FreeCADGui.doCommand("PathScripts.PathDressupDogbone.Create(FreeCAD.ActiveDocument.%s)" % baseObject.Name)
FreeCAD.ActiveDocument.openTransaction("Create Dogbone Dress-up")
FreeCADGui.addModule("PathScripts.PathDressupDogbone")
FreeCADGui.doCommand(
"PathScripts.PathDressupDogbone.Create(FreeCAD.ActiveDocument.%s)"
% baseObject.Name
)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
@@ -1215,6 +1428,7 @@ if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtGui
from pivy import coin
FreeCADGui.addCommand('Path_DressupDogbone', CommandDressupDogbone())
FreeCADGui.addCommand("Path_DressupDogbone", CommandDressupDogbone())
FreeCAD.Console.PrintLog("Loading DressupDogbone... done\n")

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import PathScripts.PathDressup as PathDressup
@@ -29,31 +30,50 @@ import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
from PySide import QtCore
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 _vstr(v):
if v:
return "(%.2f, %.2f, %.2f)" % (v.x, v.y, v.z)
return '-'
return "-"
class DressupPathBoundary(object):
def __init__(self, obj, base, job):
obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "The base path to modify"))
obj.addProperty(
"App::PropertyLink",
"Base",
"Base",
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
)
obj.Base = base
obj.addProperty("App::PropertyLink", "Stock", "Boundary", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "Solid object to be used to limit the generated Path."))
obj.addProperty(
"App::PropertyLink",
"Stock",
"Boundary",
QT_TRANSLATE_NOOP(
"App::Property",
"Solid object to be used to limit the generated Path.",
),
)
obj.Stock = PathStock.CreateFromBase(job)
obj.addProperty("App::PropertyBool", "Inside", "Boundary", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "Determines if Boundary describes an inclusion or exclusion mask."))
obj.addProperty(
"App::PropertyBool",
"Inside",
"Boundary",
QT_TRANSLATE_NOOP(
"App::Property",
"Determines if Boundary describes an inclusion or exclusion mask.",
),
)
obj.Inside = True
self.obj = obj
@@ -85,6 +105,8 @@ class DressupPathBoundary(object):
def execute(self, obj):
pb = PathBoundary(obj.Base, obj.Stock.Shape, obj.Inside)
obj.Path = pb.execute()
# Eclass
@@ -114,15 +136,19 @@ class PathBoundary:
if begin.z < self.clearanceHeight:
cmds.append(self.strG0ZclearanceHeight)
if end:
cmds.append(Path.Command('G0', {'X': end.x, 'Y': end.y}))
cmds.append(Path.Command("G0", {"X": end.x, "Y": end.y}))
if end.z < self.clearanceHeight:
cmds.append(Path.Command('G0', {'Z': max(self.safeHeight, end.z)}))
cmds.append(Path.Command("G0", {"Z": max(self.safeHeight, end.z)}))
if end.z < self.safeHeight:
cmds.append(Path.Command('G1', {'Z': end.z, 'F': verticalFeed}))
cmds.append(Path.Command("G1", {"Z": end.z, "F": verticalFeed}))
return cmds
def execute(self):
if not self.baseOp or not self.baseOp.isDerivedFrom('Path::Feature') or not self.baseOp.Path:
if (
not self.baseOp
or not self.baseOp.isDerivedFrom("Path::Feature")
or not self.baseOp.Path
):
return None
if len(self.baseOp.Path.Commands) == 0:
@@ -131,23 +157,27 @@ class PathBoundary:
tc = PathDressup.toolController(self.baseOp)
self.safeHeight = float(PathUtil.opProperty(self.baseOp, 'SafeHeight'))
self.clearanceHeight = float(PathUtil.opProperty(self.baseOp, 'ClearanceHeight'))
self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value})
self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight})
self.safeHeight = float(PathUtil.opProperty(self.baseOp, "SafeHeight"))
self.clearanceHeight = float(
PathUtil.opProperty(self.baseOp, "ClearanceHeight")
)
self.strG1ZsafeHeight = Path.Command(
"G1", {"Z": self.safeHeight, "F": tc.VertFeed.Value}
)
self.strG0ZclearanceHeight = Path.Command("G0", {"Z": self.clearanceHeight})
cmd = self.baseOp.Path.Commands[0]
pos = cmd.Placement.Base # bogus m/c position to create first edge
pos = cmd.Placement.Base # bogus m/c position to create first edge
bogusX = True
bogusY = True
commands = [cmd]
lastExit = None
for cmd in self.baseOp.Path.Commands[1:]:
if cmd.Name in PathGeom.CmdMoveAll:
if bogusX == True :
bogusX = ( 'X' not in cmd.Parameters )
if bogusY :
bogusY = ( 'Y' not in cmd.Parameters )
if bogusX == True:
bogusX = "X" not in cmd.Parameters
if bogusY:
bogusY = "Y" not in cmd.Parameters
edge = PathGeom.edgeForCmd(cmd, pos)
if edge:
inside = edge.common(self.boundary).Edges
@@ -159,22 +189,30 @@ class PathBoundary:
# it's really a shame that one cannot trust the sequence and/or
# orientation of edges
if 1 == len(inside) and 0 == len(outside):
PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd)
PathLog.track(_vstr(pos), _vstr(lastExit), " + ", cmd)
# cmd fully included by boundary
if lastExit:
if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position
commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value))
if not (
bogusX or bogusY
): # don't insert false paths based on bogus m/c position
commands.extend(
self.boundaryCommands(
lastExit, pos, tc.VertFeed.Value
)
)
lastExit = None
commands.append(cmd)
pos = PathGeom.commandEndPoint(cmd, pos)
elif 0 == len(inside) and 1 == len(outside):
PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd)
PathLog.track(_vstr(pos), _vstr(lastExit), " - ", cmd)
# cmd fully excluded by boundary
if not lastExit:
lastExit = pos
pos = PathGeom.commandEndPoint(cmd, pos)
else:
PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd)
PathLog.track(
_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd
)
# cmd pierces boundary
while inside or outside:
ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)]
@@ -187,35 +225,58 @@ class PathBoundary:
# inside edges are taken at this point (see swap of inside/outside
# above - so we can just connect the dots ...
if lastExit:
if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value))
if not (bogusX or bogusY):
commands.extend(
self.boundaryCommands(
lastExit, pos, tc.VertFeed.Value
)
)
lastExit = None
PathLog.track(e, flip)
if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position
commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value))
if not (
bogusX or bogusY
): # don't insert false paths based on bogus m/c position
commands.extend(
PathGeom.cmdsForEdge(
e,
flip,
False,
50,
tc.HorizFeed.Value,
tc.VertFeed.Value,
)
)
inside.remove(e)
pos = newPos
lastExit = newPos
else:
oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)]
oe = [
e
for e in outside
if PathGeom.edgeConnectsTo(e, pos)
]
PathLog.track(oe)
if oe:
e = oe[0]
ptL = e.valueAt(e.LastParameter)
flip = PathGeom.pointsCoincide(pos, ptL)
newPos = e.valueAt(e.FirstParameter) if flip else ptL
newPos = (
e.valueAt(e.FirstParameter) if flip else ptL
)
# outside edges are never taken at this point (see swap of
# inside/outside above) - so just move along ...
outside.remove(e)
pos = newPos
else:
PathLog.error('huh?')
PathLog.error("huh?")
import Part
Part.show(Part.Vertex(pos), 'pos')
Part.show(Part.Vertex(pos), "pos")
for e in inside:
Part.show(e, 'ei')
Part.show(e, "ei")
for e in outside:
Part.show(e, 'eo')
raise Exception('This is not supposed to happen')
Part.show(e, "eo")
raise Exception("This is not supposed to happen")
# Eif
# Eif
# Ewhile
@@ -223,7 +284,7 @@ class PathBoundary:
# pos = PathGeom.commandEndPoint(cmd, pos)
# Eif
else:
PathLog.track('no-move', cmd)
PathLog.track("no-move", cmd)
commands.append(cmd)
if lastExit:
commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value))
@@ -231,16 +292,22 @@ class PathBoundary:
PathLog.track(commands)
return Path.Path(commands)
# Eclass
def Create(base, name='DressupPathBoundary'):
'''Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary.'''
if not base.isDerivedFrom('Path::Feature'):
PathLog.error(translate('Path_DressupPathBoundary', 'The selected object is not a path')+'\n')
def Create(base, name="DressupPathBoundary"):
"""Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary."""
if not base.isDerivedFrom("Path::Feature"):
PathLog.error(
translate("Path_DressupPathBoundary", "The selected object is not a path")
+ "\n"
)
return None
obj = FreeCAD.ActiveDocument.addObject('Path::FeaturePython', name)
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
job = PathUtils.findParentJob(base)
obj.Proxy = DressupPathBoundary(obj, base, job)
job.Proxy.addOperation(obj, base, True)

View File

@@ -20,28 +20,29 @@
# * *
# ***************************************************************************
from PySide import 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.PathDressupPathBoundary as PathDressupPathBoundary
import PathScripts.PathLog as PathLog
from PySide import QtGui, QtCore
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())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
class TaskPanel(object):
def __init__(self, obj, viewProvider):
self.obj = obj
self.viewProvider = viewProvider
self.form = FreeCADGui.PySideUic.loadUi(':/panels/DressupPathBoundary.ui')
self.form = FreeCADGui.PySideUic.loadUi(":/panels/DressupPathBoundary.ui")
if obj.Stock:
self.visibilityBoundary = obj.Stock.ViewObject.Visibility
obj.Stock.ViewObject.Visibility = True
@@ -58,7 +59,11 @@ class TaskPanel(object):
self.stockEdit = None
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel)
return int(
QtGui.QDialogButtonBox.Ok
| QtGui.QDialogButtonBox.Apply
| QtGui.QDialogButtonBox.Cancel
)
def modifyStandardButtons(self, buttonBox):
self.buttonBox = buttonBox
@@ -113,32 +118,42 @@ class TaskPanel(object):
def setupFromBaseEdit():
PathLog.track(index, force)
if force or not self.stockFromBase:
self.stockFromBase = PathJobGui.StockFromBaseBoundBoxEdit(self.obj, self.form, force)
self.stockFromBase = PathJobGui.StockFromBaseBoundBoxEdit(
self.obj, self.form, force
)
self.stockEdit = self.stockFromBase
def setupCreateBoxEdit():
PathLog.track(index, force)
if force or not self.stockCreateBox:
self.stockCreateBox = PathJobGui.StockCreateBoxEdit(self.obj, self.form, force)
self.stockCreateBox = PathJobGui.StockCreateBoxEdit(
self.obj, self.form, force
)
self.stockEdit = self.stockCreateBox
def setupCreateCylinderEdit():
PathLog.track(index, force)
if force or not self.stockCreateCylinder:
self.stockCreateCylinder = PathJobGui.StockCreateCylinderEdit(self.obj, self.form, force)
self.stockCreateCylinder = PathJobGui.StockCreateCylinderEdit(
self.obj, self.form, force
)
self.stockEdit = self.stockCreateCylinder
def setupFromExisting():
PathLog.track(index, force)
if force or not self.stockFromExisting:
self.stockFromExisting = PathJobGui.StockFromExistingEdit(self.obj, self.form, force)
self.stockFromExisting = PathJobGui.StockFromExistingEdit(
self.obj, self.form, force
)
if self.stockFromExisting.candidates(self.obj):
self.stockEdit = self.stockFromExisting
return True
return False
if index == -1:
if self.obj.Stock is None or PathJobGui.StockFromBaseBoundBoxEdit.IsStock(self.obj):
if self.obj.Stock is None or PathJobGui.StockFromBaseBoundBoxEdit.IsStock(
self.obj
):
setupFromBaseEdit()
elif PathJobGui.StockCreateBoxEdit.IsStock(self.obj):
setupCreateBoxEdit()
@@ -147,7 +162,10 @@ class TaskPanel(object):
elif PathJobGui.StockFromExistingEdit.IsStock(self.obj):
setupFromExisting()
else:
PathLog.error(translate('PathJob', "Unsupported stock object %s") % self.obj.Stock.Label)
PathLog.error(
translate("PathJob", "Unsupported stock object %s")
% self.obj.Stock.Label
)
else:
if index == PathJobGui.StockFromBaseBoundBoxEdit.Index:
setupFromBaseEdit()
@@ -160,7 +178,10 @@ class TaskPanel(object):
setupFromBaseEdit()
index = -1
else:
PathLog.error(translate('PathJob', "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index))
PathLog.error(
translate("PathJob", "Unsupported stock type %s (%d)")
% (self.form.stock.currentText(), index)
)
self.stockEdit.activate(self.obj, index == -1)
def setupUi(self):
@@ -183,16 +204,15 @@ class TaskPanel(object):
class DressupPathBoundaryViewProvider(object):
def __init__(self, vobj):
self.attach(vobj)
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def attach(self, vobj):
self.vobj = vobj
self.obj = vobj.Object
@@ -225,8 +245,8 @@ class DressupPathBoundaryViewProvider(object):
self.panel = None
def Create(base, name='DressupPathBoundary'):
FreeCAD.ActiveDocument.openTransaction(translate('Path_DressupPathBoundary', 'Create a Boundary dressup'))
def Create(base, name="DressupPathBoundary"):
FreeCAD.ActiveDocument.openTransaction("Create a Boundary dressup")
obj = PathDressupPathBoundary.Create(base, name)
obj.ViewObject.Proxy = DressupPathBoundaryViewProvider(obj.ViewObject)
obj.Base.ViewObject.Visibility = False
@@ -235,18 +255,26 @@ def Create(base, name='DressupPathBoundary'):
obj.ViewObject.Document.setEdit(obj.ViewObject, 0)
return obj
class CommandPathDressupPathBoundary:
# pylint: disable=no-init
def GetResources(self):
return {'Pixmap': 'Path_Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP('Path_DressupPathBoundary', 'Boundary Dress-up'),
'ToolTip': QtCore.QT_TRANSLATE_NOOP('Path_DressupPathBoundary', 'Creates a Path Boundary Dress-up object from a selected path')}
return {
"Pixmap": "Path_Dressup",
"MenuText": QT_TRANSLATE_NOOP(
"Path_DressupPathBoundary", "Boundary Dress-up"
),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_DressupPathBoundary",
"Creates a Path Boundary Dress-up object from a selected path",
),
}
def IsActive(self):
if FreeCAD.ActiveDocument is not None:
for o in FreeCAD.ActiveDocument.Objects:
if o.Name[:3] == 'Job':
if o.Name[:3] == "Job":
return True
return False
@@ -254,19 +282,26 @@ class CommandPathDressupPathBoundary:
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
PathLog.error(translate('Path_DressupPathBoundary', 'Please select one path object')+'\n')
PathLog.error(
translate("Path_DressupPathBoundary", "Please select one path object")
+ "\n"
)
return
baseObject = selection[0]
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate('Path_DressupPathBoundary', 'Create Path Boundary Dress-up'))
FreeCADGui.addModule('PathScripts.PathDressupPathBoundaryGui')
FreeCADGui.doCommand("PathScripts.PathDressupPathBoundaryGui.Create(App.ActiveDocument.%s)" % baseObject.Name)
FreeCAD.ActiveDocument.openTransaction("Create Path Boundary Dress-up")
FreeCADGui.addModule("PathScripts.PathDressupPathBoundaryGui")
FreeCADGui.doCommand(
"PathScripts.PathDressupPathBoundaryGui.Create(App.ActiveDocument.%s)"
% baseObject.Name
)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_DressupPathBoundary', CommandPathDressupPathBoundary())
FreeCADGui.addCommand("Path_DressupPathBoundary", CommandPathDressupPathBoundary())
PathLog.notice('Loading PathDressupPathBoundaryGui... done\n')
PathLog.notice("Loading PathDressupPathBoundaryGui... done\n")

View File

@@ -20,6 +20,8 @@
# * *
# ***************************************************************************
from PathScripts import PathUtils
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import PathScripts.PathDressup as PathDressup
@@ -27,8 +29,6 @@ import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import math
from PathScripts import PathUtils
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -39,12 +39,14 @@ if FreeCAD.GuiUp:
import FreeCADGui
# Qt translation handling
def translate(text, context="Path_DressupRampEntry", disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class ObjectDressup:
@@ -54,41 +56,37 @@ class ObjectDressup:
"App::PropertyLink",
"Base",
"Path",
QtCore.QT_TRANSLATE_NOOP(
"Path_DressupRampEntry", "The base path to modify"
),
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
)
obj.addProperty(
"App::PropertyAngle",
"Angle",
"Path",
QtCore.QT_TRANSLATE_NOOP("Path_DressupRampEntry", "Angle of ramp."),
QT_TRANSLATE_NOOP("App::Property", "Angle of ramp."),
)
obj.addProperty(
"App::PropertyEnumeration",
"Method",
"Path",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Ramping Method"),
QT_TRANSLATE_NOOP("App::Property", "Ramping Method"),
)
obj.addProperty(
"App::PropertyEnumeration",
"RampFeedRate",
"FeedRate",
QtCore.QT_TRANSLATE_NOOP(
"App::Property", "Which feed rate to use for ramping"
),
QT_TRANSLATE_NOOP("App::Property", "Which feed rate to use for ramping"),
)
obj.addProperty(
"App::PropertySpeed",
"CustomFeedRate",
"FeedRate",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Custom feed rate"),
QT_TRANSLATE_NOOP("App::Property", "Custom feed rate"),
)
obj.addProperty(
"App::PropertyBool",
"UseStartDepth",
"StartDepth",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"Should the dressup ignore motion commands above DressupStartDepth",
),
@@ -97,18 +95,16 @@ class ObjectDressup:
"App::PropertyDistance",
"DressupStartDepth",
"StartDepth",
QtCore.QT_TRANSLATE_NOOP(
QT_TRANSLATE_NOOP(
"App::Property",
"The depth where the ramp dressup is enabled. Above this ramps are not generated, but motion commands are passed through as is.",
),
)
obj.Method = ["RampMethod1", "RampMethod2", "RampMethod3", "Helix"]
obj.RampFeedRate = [
"Horizontal Feed Rate",
"Vertical Feed Rate",
"Ramp Feed Rate",
"Custom",
]
# populate the enumerations
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
obj.Proxy = self
self.setEditorProperties(obj)
@@ -121,6 +117,55 @@ class ObjectDressup:
self.ignoreAboveEnabled = None
self.ignoreAbove = None
@classmethod
def propertyEnumerations(self, dataType="data"):
"""PropertyEnumerations(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
"""
enums = {
"Object": [
(translate("Path_DressupRampEntry", "RampMethod1"), "RampMethod1"),
(translate("Path_DressupRampEntry", "RampMethod2"), "RampMethod2"),
(translate("Path_DressupRampEntry", "RampMethod3"), "RampMethod3"),
(translate("Path_DressupRampEntry", "Helix"), "Helix"),
],
"RampFeedRate": [
(
translate("Path_DressupRampEntry", "Horizontal Feed Rate"),
"Horizontal Feed Rate",
),
(
translate("Path_DressupRampEntry", "Vertical Feed Rate"),
"Vertical Feed Rate",
),
(
translate("Path_DressupRampEntry", "Ramp Feed Rate"),
"Ramp Feed Rate",
),
(translate("Path_DressupRampEntry", "Custom"), "Custom"),
],
}
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 __getstate__(self):
return None
@@ -855,10 +900,10 @@ class CommandPathDressupRampEntry:
def GetResources(self):
return {
"Pixmap": "Path_Dressup",
"MenuText": QtCore.QT_TRANSLATE_NOOP(
"MenuText": QT_TRANSLATE_NOOP(
"Path_DressupRampEntry", "RampEntry Dress-up"
),
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
"ToolTip": QT_TRANSLATE_NOOP(
"Path_DressupRampEntry",
"Creates a Ramp Entry Dress-up object from a selected path",
),
@@ -875,18 +920,26 @@ class CommandPathDressupRampEntry:
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
PathLog.error(translate("Please select one path object") + "\n")
PathLog.error(
translate("Path_DressupRampEntry", "Please select one path object")
+ "\n"
)
return
baseObject = selection[0]
if not baseObject.isDerivedFrom("Path::Feature"):
PathLog.error(translate("The selected object is not a path") + "\n")
PathLog.error(
translate("Path_DressupRampEntry", "The selected object is not a path")
+ "\n"
)
return
if baseObject.isDerivedFrom("Path::FeatureCompoundPython"):
PathLog.error(translate("Please select a Profile object"))
PathLog.error(
translate("Path_DressupRampEntry", "Please select a Profile object")
)
return
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate("Create RampEntry Dress-up"))
FreeCAD.ActiveDocument.openTransaction("Create RampEntry Dress-up")
FreeCADGui.addModule("PathScripts.PathDressupRampEntry")
FreeCADGui.addModule("PathScripts.PathUtils")
FreeCADGui.doCommand(

View File

@@ -20,6 +20,8 @@
# * *
# ***************************************************************************
from PathScripts.PathDressupTagPreferences import HoldingTagPreferences
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
@@ -29,22 +31,23 @@ import math
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils')
Part = LazyLoader('Part', globals(), 'Part')
from PathScripts.PathDressupTagPreferences import HoldingTagPreferences
from PySide import QtCore
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule()
DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
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
MaxInt = 99999999999999
class TagSolid:
def __init__(self, proxy, z, R):
self.proxy = proxy
@@ -76,7 +79,7 @@ class TagSolid:
# with top
r2 = r1 - dr
s = height / math.sin(rad)
radius = min(r2, s) * math.tan((math.pi - rad)/2) * 0.95
radius = min(r2, s) * math.tan((math.pi - rad) / 2) * 0.95
else:
# triangular
r2 = 0
@@ -98,7 +101,9 @@ class TagSolid:
# lastly determine the center of the model, we want to make sure the seam of
# the tag solid points away (in the hopes it doesn't coincide with a path)
self.baseCenter = FreeCAD.Vector((proxy.ptMin.x+proxy.ptMax.x)/2, (proxy.ptMin.y+proxy.ptMax.y)/2, 0)
self.baseCenter = FreeCAD.Vector(
(proxy.ptMin.x + proxy.ptMax.x) / 2, (proxy.ptMin.y + proxy.ptMax.y) / 2, 0
)
def cloneAt(self, pos):
clone = self.solid.copy()
@@ -111,17 +116,59 @@ class TagSolid:
class ObjectDressup:
def __init__(self, obj, base):
obj.addProperty('App::PropertyLink', 'Base', 'Base', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'The base path to modify'))
obj.addProperty('App::PropertyLength', 'Width', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Width of tags.'))
obj.addProperty('App::PropertyLength', 'Height', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Height of tags.'))
obj.addProperty('App::PropertyAngle', 'Angle', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Angle of tag plunge and ascent.'))
obj.addProperty('App::PropertyLength', 'Radius', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Radius of the fillet for the tag.'))
obj.addProperty('App::PropertyVectorList', 'Positions', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Locations of inserted holding tags'))
obj.addProperty('App::PropertyIntegerList', 'Disabled', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'IDs of disabled holding tags'))
obj.addProperty('App::PropertyInteger', 'SegmentationFactor', 'Tag', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Factor determining the # of segments used to approximate rounded tags.'))
obj.addProperty(
"App::PropertyLink",
"Base",
"Base",
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
)
obj.addProperty(
"App::PropertyLength",
"Width",
"Tag",
QT_TRANSLATE_NOOP("App::Property", "Width of tags."),
)
obj.addProperty(
"App::PropertyLength",
"Height",
"Tag",
QT_TRANSLATE_NOOP("App::Property", "Height of tags."),
)
obj.addProperty(
"App::PropertyAngle",
"Angle",
"Tag",
QT_TRANSLATE_NOOP("App::Property", "Angle of tag plunge and ascent."),
)
obj.addProperty(
"App::PropertyLength",
"Radius",
"Tag",
QT_TRANSLATE_NOOP("App::Property", "Radius of the fillet for the tag."),
)
obj.addProperty(
"App::PropertyVectorList",
"Positions",
"Tag",
QT_TRANSLATE_NOOP("App::Property", "Locations of inserted holding tags"),
)
obj.addProperty(
"App::PropertyIntegerList",
"Disabled",
"Tag",
QT_TRANSLATE_NOOP("App::Property", "IDs of disabled holding tags"),
)
obj.addProperty(
"App::PropertyInteger",
"SegmentationFactor",
"Tag",
QT_TRANSLATE_NOOP(
"App::Property",
"Factor determining the # of segments used to approximate rounded tags.",
),
)
obj.Proxy = self
obj.Base = base
@@ -152,16 +199,20 @@ class ObjectDressup:
def execute(self, obj):
PathLog.track()
if not obj.Base:
PathLog.error(translate('Path_DressupTag', 'No Base object found.'))
PathLog.error(translate("Path_DressupTag", "No Base object found."))
return
if not obj.Base.isDerivedFrom('Path::Feature'):
PathLog.error(translate('Path_DressupTag', 'Base is not a Path::Feature object.'))
if not obj.Base.isDerivedFrom("Path::Feature"):
PathLog.error(
translate("Path_DressupTag", "Base is not a Path::Feature object.")
)
return
if not obj.Base.Path:
PathLog.error(translate('Path_DressupTag', 'Base doesn\'t have a Path to dress-up.'))
PathLog.error(
translate("Path_DressupTag", "Base doesn't have a Path to dress-up.")
)
return
if not obj.Base.Path.Commands:
PathLog.error(translate('Path_DressupTag', 'Base Path is empty.'))
PathLog.error(translate("Path_DressupTag", "Base Path is empty."))
return
self.obj = obj
@@ -190,14 +241,19 @@ class ObjectDressup:
maxZ = max(pt.z, maxZ)
minZ = min(pt.z, minZ)
lastPt = pt
PathLog.debug("bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)" % (minX, minY, minZ, maxX, maxY, maxZ))
PathLog.debug(
"bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)"
% (minX, minY, minZ, maxX, maxY, maxZ)
)
self.ptMin = FreeCAD.Vector(minX, minY, minZ)
self.ptMax = FreeCAD.Vector(maxX, maxY, maxZ)
self.masterSolid = TagSolid(self, minZ, self.toolRadius())
self.solids = [self.masterSolid.cloneAt(pos) for pos in self.obj.Positions]
self.tagSolid = Part.Compound(self.solids)
self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) # pylint: disable=unused-variable
self.wire, rapid = PathGeom.wireForPath(
obj.Base.Path
) # pylint: disable=unused-variable
self.edges = self.wire.Edges
maxTagZ = minZ + obj.Height.Value
@@ -210,7 +266,7 @@ class ObjectDressup:
for cmd in obj.Base.Path.Commands:
if cmd in PathGeom.CmdMove:
if lastZ <= maxTagZ or cmd.Parameters.get('Z', lastZ) <= maxTagZ:
if lastZ <= maxTagZ or cmd.Parameters.get("Z", lastZ) <= maxTagZ:
pass
else:
commands.append(cmd)
@@ -226,7 +282,7 @@ class ObjectDressup:
def addTagsToDocument(self):
for i, solid in enumerate(self.solids):
obj = FreeCAD.ActiveDocument.addObject('Part::Compound', "tag_%02d" % i)
obj = FreeCAD.ActiveDocument.addObject("Part::Compound", "tag_%02d" % i)
obj.Shape = solid
def supportsTagGeneration(self, obj):
@@ -241,16 +297,18 @@ class ObjectDressup:
return False
def Create(baseObject, name='DressupTag'):
'''
def Create(baseObject, name="DressupTag"):
"""
Create(basePath, name = 'DressupTag') ... create tag dressup object for the given base path.
'''
if not baseObject.isDerivedFrom('Path::Feature'):
PathLog.error(translate('Path_DressupTag', 'The selected object is not a path')+'\n')
"""
if not baseObject.isDerivedFrom("Path::Feature"):
PathLog.error(
translate("Path_DressupTag", "The selected object is not a path") + "\n"
)
return None
if baseObject.isDerivedFrom('Path::FeatureCompoundPython'):
PathLog.error(translate('Path_DressupTag', 'Please select a Profile object'))
if baseObject.isDerivedFrom("Path::FeatureCompoundPython"):
PathLog.error(translate("Path_DressupTag", "Please select a Profile object"))
return None
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
@@ -260,4 +318,5 @@ def Create(baseObject, name='DressupTag'):
dbo.assignDefaultValues()
return obj
PathLog.notice('Loading Path_DressupTag... done\n')
PathLog.notice("Loading Path_DressupTag... done\n")

View File

@@ -20,26 +20,27 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
from pivy import coin
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.PathDressupHoldingTags as PathDressupTag
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGetPoint as PathGetPoint
import PathScripts.PathDressupHoldingTags as PathDressupTag
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathUtils as PathUtils
from PySide import QtCore, QtGui
from pivy import coin
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())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
def addDebugDisplay():
@@ -60,7 +61,7 @@ class PathDressupTagTaskPanel:
self.getPoint = PathGetPoint.TaskPanel(self.form.removeEditAddGroup, True)
self.jvo = PathUtils.findParentJob(obj).ViewObject
if jvoVisibility is None:
FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up"))
FreeCAD.ActiveDocument.openTransaction("Edit HoldingTags Dress-up")
self.jvoVisible = self.jvo.isVisible()
if self.jvoVisible:
self.jvo.hide()
@@ -76,7 +77,11 @@ class PathDressupTagTaskPanel:
self.editItem = None
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel)
return int(
QtGui.QDialogButtonBox.Ok
| QtGui.QDialogButtonBox.Apply
| QtGui.QDialogButtonBox.Cancel
)
def clicked(self, button):
if button == QtGui.QDialogButtonBox.Apply:
@@ -201,7 +206,7 @@ class PathDressupTagTaskPanel:
self.Disabled = self.obj.Disabled
self.updateTagsView()
else:
PathLog.error(translate('Path_DressupTag', 'Cannot copy tags - internal error')+'\n')
PathLog.error("Cannot copy tags - internal error")
def updateModel(self):
self.getFields()
@@ -273,10 +278,16 @@ class PathDressupTagTaskPanel:
def setFields(self):
self.updateTagsView()
self.form.sbCount.setValue(len(self.Positions))
self.form.ifHeight.setText(FreeCAD.Units.Quantity(self.obj.Height, FreeCAD.Units.Length).UserString)
self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.obj.Width, FreeCAD.Units.Length).UserString)
self.form.ifHeight.setText(
FreeCAD.Units.Quantity(self.obj.Height, FreeCAD.Units.Length).UserString
)
self.form.ifWidth.setText(
FreeCAD.Units.Quantity(self.obj.Width, FreeCAD.Units.Length).UserString
)
self.form.dsbAngle.setValue(self.obj.Angle)
self.form.ifRadius.setText(FreeCAD.Units.Quantity(self.obj.Radius, FreeCAD.Units.Length).UserString)
self.form.ifRadius.setText(
FreeCAD.Units.Quantity(self.obj.Radius, FreeCAD.Units.Length).UserString
)
def setupUi(self):
self.Positions = self.obj.Positions
@@ -292,7 +303,9 @@ class PathDressupTagTaskPanel:
self.form.cbTagGeneration.setEnabled(False)
enableCopy = False
for tags in sorted([o.Label for o in FreeCAD.ActiveDocument.Objects if 'DressupTag' in o.Name]):
for tags in sorted(
[o.Label for o in FreeCAD.ActiveDocument.Objects if "DressupTag" in o.Name]
):
if tags != self.obj.Label:
enableCopy = True
self.form.cbSource.addItem(tags)
@@ -319,8 +332,8 @@ class HoldingTagMarker:
self.pos = coin.SoTranslation()
self.pos.translation = (point.x, point.y, point.z)
self.sphere = coin.SoSphere()
self.scale = coin.SoType.fromName('SoShapeScale').createInstance()
self.scale.setPart('shape', self.sphere)
self.scale = coin.SoType.fromName("SoShapeScale").createInstance()
self.scale.setPart("shape", self.sphere)
self.scale.scaleFactor.setValue(7)
self.material = coin.SoMaterial()
self.sep.addChild(self.pos)
@@ -337,15 +350,18 @@ class HoldingTagMarker:
def setEnabled(self, enabled):
self.enabled = enabled
if enabled:
self.material.diffuseColor = self.color[0] if not self.selected else self.color[2]
self.material.diffuseColor = (
self.color[0] if not self.selected else self.color[2]
)
self.material.transparency = 0.0
else:
self.material.diffuseColor = self.color[1] if not self.selected else self.color[2]
self.material.diffuseColor = (
self.color[1] if not self.selected else self.color[2]
)
self.material.transparency = 0.6
class PathDressupTagViewProvider:
def __init__(self, vobj):
PathLog.track()
self.vobj = vobj
@@ -362,7 +378,7 @@ class PathDressupTagViewProvider:
def debugDisplay(self):
# if False and addDebugDisplay():
# if not hasattr(self.vobj, 'Debug'):
# self.vobj.addProperty('App::PropertyLink', 'Debug', 'Debug', QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Some elements for debugging'))
# self.vobj.addProperty('App::PropertyLink', 'Debug', 'Debug', QT_TRANSLATE_NOOP('Path_DressupTag', 'Some elements for debugging'))
# dbg = self.vobj.Object.Document.addObject('App::DocumentObjectGroup', 'TagDebug')
# self.vobj.Debug = dbg
# return True
@@ -376,15 +392,25 @@ class PathDressupTagViewProvider:
def setupColors(self):
def colorForColorValue(val):
v = [((val >> n) & 0xff) / 255. for n in [24, 16, 8, 0]]
v = [((val >> n) & 0xFF) / 255.0 for n in [24, 16, 8, 0]]
return coin.SbColor(v[0], v[1], v[2])
pref = PathPreferences.preferences()
# R G B A
npc = pref.GetUnsigned('DefaultPathMarkerColor', ((85*256 + 255)*256 + 0) * 256 + 255)
hpc = pref.GetUnsigned('DefaultHighlightPathColor', ((255*256 + 125)*256 + 0)*256 + 255)
dpc = pref.GetUnsigned('DefaultDisabledPathColor', ((205*256 + 205)*256 + 205)*256 + 154)
self.colors = [colorForColorValue(npc), colorForColorValue(dpc), colorForColorValue(hpc)]
npc = pref.GetUnsigned(
"DefaultPathMarkerColor", ((85 * 256 + 255) * 256 + 0) * 256 + 255
)
hpc = pref.GetUnsigned(
"DefaultHighlightPathColor", ((255 * 256 + 125) * 256 + 0) * 256 + 255
)
dpc = pref.GetUnsigned(
"DefaultDisabledPathColor", ((205 * 256 + 205) * 256 + 205) * 256 + 154
)
self.colors = [
colorForColorValue(npc),
colorForColorValue(dpc),
colorForColorValue(hpc),
]
def attach(self, vobj):
PathLog.track()
@@ -398,7 +424,9 @@ class PathDressupTagViewProvider:
if self.obj and self.obj.Base:
for i in self.obj.Base.InList:
if hasattr(i, 'Group') and self.obj.Base.Name in [o.Name for o in i.Group]:
if hasattr(i, "Group") and self.obj.Base.Name in [
o.Name for o in i.Group
]:
i.Group = [o for o in i.Group if o.Name != self.obj.Base.Name]
if self.obj.Base.ViewObject:
self.obj.Base.ViewObject.Visibility = False
@@ -416,7 +444,7 @@ class PathDressupTagViewProvider:
return [self.obj.Base]
def onDelete(self, arg1=None, arg2=None):
'''this makes sure that the base operation is added back to the job and visible'''
"""this makes sure that the base operation is added back to the job and visible"""
# pylint: disable=unused-argument
PathLog.track()
if self.obj.Base and self.obj.Base.ViewObject:
@@ -436,7 +464,9 @@ class PathDressupTagViewProvider:
self.switch.removeChild(tag.sep)
tags = []
for i, p in enumerate(positions):
tag = HoldingTagMarker(self.obj.Proxy.pointAtBottom(self.obj, p), self.colors)
tag = HoldingTagMarker(
self.obj.Proxy.pointAtBottom(self.obj, p), self.colors
)
tag.setEnabled(not i in disabled)
tags.append(tag)
self.switch.addChild(tag.sep)
@@ -444,7 +474,7 @@ class PathDressupTagViewProvider:
def updateData(self, obj, propName):
PathLog.track(propName)
if 'Disabled' == propName:
if "Disabled" == propName:
self.updatePositions(obj.Positions, obj.Disabled)
def onModelChanged(self):
@@ -468,7 +498,7 @@ class PathDressupTagViewProvider:
def unsetEdit(self, vobj, mode):
# pylint: disable=unused-argument
if hasattr(self, 'panel') and self.panel:
if hasattr(self, "panel") and self.panel:
self.panel.abort()
def setupTaskPanel(self, panel):
@@ -500,7 +530,9 @@ class PathDressupTagViewProvider:
z = self.tags[0].point.z
p = FreeCAD.Vector(x, y, z)
for i, tag in enumerate(self.tags):
if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.3):
if PathGeom.pointsCoincide(
p, tag.point, tag.sphere.radius.getValue() * 1.3
):
return i
return -1
@@ -519,12 +551,12 @@ class PathDressupTagViewProvider:
FreeCADGui.updateGui()
def Create(baseObject, name='DressupTag'):
'''
def Create(baseObject, name="DressupTag"):
"""
Create(basePath, name = 'DressupTag') ... create tag dressup object for the given base path.
Use this command only iff the UI is up - for batch processing see PathDressupTag.Create
'''
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupTag", "Create a Tag dressup"))
"""
FreeCAD.ActiveDocument.openTransaction("Create a Tag dressup")
obj = PathDressupTag.Create(baseObject, name)
obj.ViewObject.Proxy = PathDressupTagViewProvider(obj.ViewObject)
FreeCAD.ActiveDocument.commitTransaction()
@@ -536,14 +568,18 @@ class CommandPathDressupTag:
# pylint: disable=no-init
def GetResources(self):
return {'Pixmap': 'Path_Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Tag Dress-up'),
'ToolTip': QtCore.QT_TRANSLATE_NOOP('Path_DressupTag', 'Creates a Tag Dress-up object from a selected path')}
return {
"Pixmap": "Path_Dressup",
"MenuText": QT_TRANSLATE_NOOP("Path_DressupTag", "Tag Dress-up"),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_DressupTag", "Creates a Tag Dress-up object from a selected path"
),
}
def IsActive(self):
if FreeCAD.ActiveDocument is not None:
for o in FreeCAD.ActiveDocument.Objects:
if o.Name[:3] == 'Job':
if o.Name[:3] == "Job":
return True
return False
@@ -551,19 +587,25 @@ class CommandPathDressupTag:
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
PathLog.error(translate('Path_DressupTag', 'Please select one path object')+'\n')
PathLog.error(
translate("Path_DressupTag", "Please select one path object") + "\n"
)
return
baseObject = selection[0]
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate('Path_DressupTag', 'Create Tag Dress-up'))
FreeCADGui.addModule('PathScripts.PathDressupTagGui')
FreeCADGui.doCommand("PathScripts.PathDressupTagGui.Create(App.ActiveDocument.%s)" % baseObject.Name)
FreeCAD.ActiveDocument.openTransaction("Create Tag Dress-up")
FreeCADGui.addModule("PathScripts.PathDressupTagGui")
FreeCADGui.doCommand(
"PathScripts.PathDressupTagGui.Create(App.ActiveDocument.%s)"
% baseObject.Name
)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_DressupTag', CommandPathDressupTag())
FreeCADGui.addCommand("Path_DressupTag", CommandPathDressupTag())
PathLog.notice('Loading PathDressupTagGui... done\n')
PathLog.notice("Loading PathDressupTagGui... done\n")

View File

@@ -21,79 +21,116 @@
# ***************************************************************************
import FreeCAD
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathPreferencesPathDressup as PathPreferencesPathDressup
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())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
class HoldingTagPreferences:
DefaultHoldingTagWidth = 'DefaultHoldingTagWidth'
DefaultHoldingTagHeight = 'DefaultHoldingTagHeight'
DefaultHoldingTagAngle = 'DefaultHoldingTagAngle'
DefaultHoldingTagRadius = 'DefaultHoldingTagRadius'
DefaultHoldingTagCount = 'DefaultHoldingTagCount'
DefaultHoldingTagWidth = "DefaultHoldingTagWidth"
DefaultHoldingTagHeight = "DefaultHoldingTagHeight"
DefaultHoldingTagAngle = "DefaultHoldingTagAngle"
DefaultHoldingTagRadius = "DefaultHoldingTagRadius"
DefaultHoldingTagCount = "DefaultHoldingTagCount"
@classmethod
def defaultWidth(cls, ifNotSet):
value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagWidth, ifNotSet)
value = PathPreferences.preferences().GetFloat(
cls.DefaultHoldingTagWidth, ifNotSet
)
if value == 0.0:
return ifNotSet
return value
@classmethod
def defaultHeight(cls, ifNotSet):
value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagHeight, ifNotSet)
value = PathPreferences.preferences().GetFloat(
cls.DefaultHoldingTagHeight, ifNotSet
)
if value == 0.0:
return ifNotSet
return value
@classmethod
def defaultAngle(cls, ifNotSet=45.0):
value = PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagAngle, ifNotSet)
value = PathPreferences.preferences().GetFloat(
cls.DefaultHoldingTagAngle, ifNotSet
)
if value < 10.0:
return ifNotSet
return value
@classmethod
def defaultCount(cls, ifNotSet=4):
value = PathPreferences.preferences().GetUnsigned(cls.DefaultHoldingTagCount, ifNotSet)
value = PathPreferences.preferences().GetUnsigned(
cls.DefaultHoldingTagCount, ifNotSet
)
if value < 2:
return float(ifNotSet)
return float(value)
@classmethod
def defaultRadius(cls, ifNotSet=0.0):
return PathPreferences.preferences().GetFloat(cls.DefaultHoldingTagRadius, ifNotSet)
return PathPreferences.preferences().GetFloat(
cls.DefaultHoldingTagRadius, ifNotSet
)
def __init__(self):
if FreeCAD.GuiUp:
import FreeCADGui
self.form = FreeCADGui.PySideUic.loadUi(":/preferences/PathDressupHoldingTags.ui")
self.label = translate("Path_DressupTag", 'Holding Tag')
self.form = FreeCADGui.PySideUic.loadUi(
":/preferences/PathDressupHoldingTags.ui"
)
self.label = translate("Path_DressupTag", "Holding Tag")
def loadSettings(self):
self.form.ifWidth.setText(FreeCAD.Units.Quantity(self.defaultWidth(0), FreeCAD.Units.Length).UserString)
self.form.ifHeight.setText(FreeCAD.Units.Quantity(self.defaultHeight(0), FreeCAD.Units.Length).UserString)
self.form.ifWidth.setText(
FreeCAD.Units.Quantity(
self.defaultWidth(0), FreeCAD.Units.Length
).UserString
)
self.form.ifHeight.setText(
FreeCAD.Units.Quantity(
self.defaultHeight(0), FreeCAD.Units.Length
).UserString
)
self.form.dsbAngle.setValue(self.defaultAngle())
self.form.ifRadius.setText(FreeCAD.Units.Quantity(self.defaultRadius(), FreeCAD.Units.Length).UserString)
self.form.ifRadius.setText(
FreeCAD.Units.Quantity(
self.defaultRadius(), FreeCAD.Units.Length
).UserString
)
self.form.sbCount.setValue(self.defaultCount())
def saveSettings(self):
pref = PathPreferences.preferences()
pref.SetFloat(self.DefaultHoldingTagWidth, FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value)
pref.SetFloat(self.DefaultHoldingTagHeight, FreeCAD.Units.Quantity(self.form.ifHeight.text()).Value)
pref.SetFloat(
self.DefaultHoldingTagWidth,
FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value,
)
pref.SetFloat(
self.DefaultHoldingTagHeight,
FreeCAD.Units.Quantity(self.form.ifHeight.text()).Value,
)
pref.SetFloat(self.DefaultHoldingTagAngle, self.form.dsbAngle.value())
pref.SetFloat(self.DefaultHoldingTagRadius, FreeCAD.Units.Quantity(self.form.ifRadius.text()))
pref.SetFloat(
self.DefaultHoldingTagRadius,
FreeCAD.Units.Quantity(self.form.ifRadius.text()),
)
pref.SetUnsigned(self.DefaultHoldingTagCount, self.form.sbCount.value())
@classmethod
def preferencesPage(cls):
return HoldingTagPreferences()
PathPreferencesPathDressup.RegisterDressup(HoldingTagPreferences)

View File

@@ -20,34 +20,36 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Part
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import Part
import math
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
PathUtils = LazyLoader('PathScripts.PathUtils', globals(), 'PathScripts.PathUtils')
from PySide import QtCore
PathUtils = LazyLoader("PathScripts.PathUtils", globals(), "PathScripts.PathUtils")
__title__ = "Path Features Extensions"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Class and implementation of face extensions features."
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 endPoints(edgeOrWire):
'''endPoints(edgeOrWire) ... return the first and last point of the wire or the edge, assuming the argument is not a closed wire.'''
"""endPoints(edgeOrWire) ... return the first and last point of the wire or the edge, assuming the argument is not a closed wire."""
if Part.Wire == type(edgeOrWire):
# edges = edgeOrWire.Edges
pts = [e.valueAt(e.FirstParameter) for e in edgeOrWire.Edges]
@@ -69,7 +71,7 @@ def endPoints(edgeOrWire):
def includesPoint(p, pts):
'''includesPoint(p, pts) ... answer True if the collection of pts includes the point p'''
"""includesPoint(p, pts) ... answer True if the collection of pts includes the point p"""
for pt in pts:
if PathGeom.pointsCoincide(p, pt):
return True
@@ -78,11 +80,13 @@ def includesPoint(p, pts):
def selectOffsetWire(feature, wires):
'''selectOffsetWire(feature, wires) ... returns the Wire in wires which is does not intersect with feature'''
"""selectOffsetWire(feature, wires) ... returns the Wire in wires which is does not intersect with feature"""
closest = None
for w in wires:
dist = feature.distToShape(w)[0]
if closest is None or dist > closest[0]: # pylint: disable=unsubscriptable-object
if (
closest is None or dist > closest[0]
): # pylint: disable=unsubscriptable-object
closest = (dist, w)
if closest is not None:
@@ -92,19 +96,24 @@ def selectOffsetWire(feature, wires):
def extendWire(feature, wire, length):
'''extendWire(wire, length) ... return a closed Wire which extends wire by length'''
"""extendWire(wire, length) ... return a closed Wire which extends wire by length"""
PathLog.track(length)
if not length or length == 0:
return None
try:
off2D = wire.makeOffset2D(length)
except FreeCAD.Base.FreeCADError as ee:
PathLog.debug(ee)
return None
endPts = endPoints(wire) # Assumes wire is NOT closed
if endPts:
edges = [e for e in off2D.Edges if Part.Circle != type(e.Curve) or not includesPoint(e.Curve.Center, endPts)]
edges = [
e
for e in off2D.Edges
if Part.Circle != type(e.Curve) or not includesPoint(e.Curve.Center, endPts)
]
wires = [Part.Wire(e) for e in Part.sortEdges(edges)]
offset = selectOffsetWire(feature, wires)
ePts = endPoints(offset)
@@ -127,12 +136,14 @@ def extendWire(feature, wire, length):
def createExtension(obj, extObj, extFeature, extSub):
return Extension(obj,
extObj,
extFeature,
extSub,
obj.ExtensionLengthDefault,
Extension.DirectionNormal)
return Extension(
obj,
extObj,
extFeature,
extSub,
obj.ExtensionLengthDefault,
Extension.DirectionNormal,
)
def readObjExtensionFeature(obj):
@@ -142,7 +153,7 @@ def readObjExtensionFeature(obj):
for extObj, features in obj.ExtensionFeature:
for sub in features:
extFeature, extSub = sub.split(':')
extFeature, extSub = sub.split(":")
extensions.append((extObj.Name, extFeature, extSub))
return extensions
@@ -154,7 +165,7 @@ def getExtensions(obj):
for extObj, features in obj.ExtensionFeature:
for sub in features:
extFeature, extSub = sub.split(':')
extFeature, extSub = sub.split(":")
extensions.append(createExtension(obj, extObj, extFeature, extSub))
i = i + 1
return extensions
@@ -167,11 +178,14 @@ def setExtensions(obj, extensions):
class Extension(object):
DirectionNormal = 0
DirectionX = 1
DirectionY = 2
DirectionX = 1
DirectionY = 2
def __init__(self, op, obj, feature, sub, length, direction):
PathLog.debug("Extension(%s, %s, %s, %.2f, %s" % (obj.Label, feature, sub, length, direction))
PathLog.debug(
"Extension(%s, %s, %s, %.2f, %s"
% (obj.Label, feature, sub, length, direction)
)
self.op = op
self.obj = obj
self.feature = feature
@@ -197,8 +211,16 @@ class Extension(object):
off = self.length.Value * direction
e2.translate(off)
e2 = PathGeom.flipEdge(e2)
e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), e2.valueAt(e2.FirstParameter)))
e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), e0.valueAt(e0.FirstParameter)))
e1 = Part.Edge(
Part.LineSegment(
e0.valueAt(e0.LastParameter), e2.valueAt(e2.FirstParameter)
)
)
e3 = Part.Edge(
Part.LineSegment(
e2.valueAt(e2.LastParameter), e0.valueAt(e0.FirstParameter)
)
)
wire = Part.Wire([e0, e1, e2, e3])
self.wire = wire
return wire
@@ -206,8 +228,8 @@ class Extension(object):
return extendWire(feature, Part.Wire([e0]), self.length.Value)
def _getEdgeNumbers(self):
if 'Wire' in self.sub:
numbers = [nr for nr in self.sub[5:-1].split(',')]
if "Wire" in self.sub:
numbers = [nr for nr in self.sub[5:-1].split(",")]
else:
numbers = [self.sub[4:]]
@@ -235,7 +257,7 @@ class Extension(object):
e0 = wire.Edges[0]
midparam = e0.FirstParameter + 0.5 * (e0.LastParameter - e0.FirstParameter)
tangent = e0.tangentAt(midparam)
PathLog.track('tangent', tangent, self.feature, self.sub)
PathLog.track("tangent", tangent, self.feature, self.sub)
normal = tangent.cross(FreeCAD.Vector(0, 0, 1))
if PathGeom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)):
return None
@@ -243,21 +265,21 @@ class Extension(object):
return self._getDirectedNormal(e0.valueAt(midparam), normal.normalize())
def getExtensionFaces(self, extensionWire):
'''getExtensionFace(extensionWire)...
"""getExtensionFace(extensionWire)...
A public helper method to retrieve the requested extension as a face,
rather than a wire because some extensions require a face shape
for definition that allows for two wires for boundary definition.
'''
"""
if self.extFaces:
return self.extFaces
return [Part.Face(extensionWire)]
def getWire(self):
'''getWire()... Public method to retrieve the extension area, pertaining to the feature
"""getWire()... Public method to retrieve the extension area, pertaining to the feature
and sub element provided at class instantiation, as a closed wire. If no closed wire
is possible, a `None` value is returned.'''
is possible, a `None` value is returned."""
if self.sub[:6] == "Avoid_":
feature = self.obj.Shape.getElement(self.feature)
@@ -281,9 +303,9 @@ class Extension(object):
return self._getRegularWire()
def _getRegularWire(self):
'''_getRegularWire()... Private method to retrieve the extension area, pertaining to the feature
"""_getRegularWire()... Private method to retrieve the extension area, pertaining to the feature
and sub element provided at class instantiation, as a closed wire. If no closed wire
is possible, a `None` value is returned.'''
is possible, a `None` value is returned."""
PathLog.track()
length = self.length.Value
@@ -314,11 +336,23 @@ class Extension(object):
# assuming the offset produces a valid circle - go for it
if r > 0:
e3 = Part.makeCircle(r, circle.Center, circle.Axis, edge.FirstParameter * 180 / math.pi, edge.LastParameter * 180 / math.pi)
e3 = Part.makeCircle(
r,
circle.Center,
circle.Axis,
edge.FirstParameter * 180 / math.pi,
edge.LastParameter * 180 / math.pi,
)
if endPoints(edge):
# need to construct the arc slice
e0 = Part.makeLine(edge.valueAt(edge.FirstParameter), e3.valueAt(e3.FirstParameter))
e2 = Part.makeLine(edge.valueAt(edge.LastParameter), e3.valueAt(e3.LastParameter))
e0 = Part.makeLine(
edge.valueAt(edge.FirstParameter),
e3.valueAt(e3.FirstParameter),
)
e2 = Part.makeLine(
edge.valueAt(edge.LastParameter),
e3.valueAt(e3.LastParameter),
)
return Part.Wire([e0, edge, e2, e3])
extWire = Part.Wire([e3])
@@ -357,8 +391,9 @@ class Extension(object):
try:
off2D = sub.makeOffset2D(length)
except FreeCAD.Base.FreeCADError as ee:
PathLog.debug(ee)
return None
if isOutside:
self.extFaces = [Part.Face(off2D).cut(featFace)]
else:
@@ -369,9 +404,9 @@ class Extension(object):
return extendWire(feature, sub, length)
def _getOutlineWire(self):
'''_getOutlineWire()... Private method to retrieve an extended outline extension area,
"""_getOutlineWire()... Private method to retrieve an extended outline extension area,
pertaining to the feature and sub element provided at class instantiation, as a closed wire.
If no closed wire is possible, a `None` value is returned.'''
If no closed wire is possible, a `None` value is returned."""
PathLog.track()
baseShape = self.obj.Shape
@@ -411,10 +446,10 @@ class Extension(object):
return None
def _getWaterlineWire(self):
'''_getWaterlineWire()... Private method to retrieve a waterline extension area,
"""_getWaterlineWire()... Private method to retrieve a waterline extension area,
pertaining to the feature and sub element provided at class instantiation, as a closed wire.
Only waterline faces touching source face are returned as part of the waterline extension area.
If no closed wire is possible, a `None` value is returned.'''
If no closed wire is possible, a `None` value is returned."""
PathLog.track()
msg = translate("PathFeatureExtensions", "Waterline error")
@@ -451,9 +486,9 @@ class Extension(object):
return None
def _makeCircularExtFace(self, edge, extWire):
'''_makeCircularExtensionFace(edge, extWire)...
"""_makeCircularExtensionFace(edge, extWire)...
Create proper circular extension face shape. Incoming edge is expected to be a circle.
'''
"""
# Add original outer wire to cut faces if necessary
edgeFace = Part.Face(Part.Wire([edge]))
edgeFace.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - edgeFace.BoundBox.ZMin))
@@ -467,37 +502,58 @@ class Extension(object):
extensionFace.translate(FreeCAD.Vector(0.0, 0.0, edge.BoundBox.ZMin))
return extensionFace
# Eclass
def initialize_properties(obj):
"""initialize_properties(obj)... Adds feature properties to object argument"""
if not hasattr(obj, 'ExtensionLengthDefault'):
obj.addProperty('App::PropertyDistance', 'ExtensionLengthDefault', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Default length of extensions.'))
if not hasattr(obj, 'ExtensionFeature'):
obj.addProperty('App::PropertyLinkSubListGlobal', 'ExtensionFeature', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'List of features to extend.'))
if not hasattr(obj, 'ExtensionCorners'):
obj.addProperty('App::PropertyBool', 'ExtensionCorners', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'When enabled connected extension edges are combined to wires.'))
if not hasattr(obj, "ExtensionLengthDefault"):
obj.addProperty(
"App::PropertyDistance",
"ExtensionLengthDefault",
"Extension",
QT_TRANSLATE_NOOP("App::Property", "Default length of extensions."),
)
if not hasattr(obj, "ExtensionFeature"):
obj.addProperty(
"App::PropertyLinkSubListGlobal",
"ExtensionFeature",
"Extension",
QT_TRANSLATE_NOOP("App::Property", "List of features to extend."),
)
if not hasattr(obj, "ExtensionCorners"):
obj.addProperty(
"App::PropertyBool",
"ExtensionCorners",
"Extension",
QT_TRANSLATE_NOOP(
"App::Property",
"When enabled connected extension edges are combined to wires.",
),
)
obj.ExtensionCorners = True
obj.setEditorMode('ExtensionFeature', 2)
obj.setEditorMode("ExtensionFeature", 2)
def set_default_property_values(obj, job):
"""set_default_property_values(obj, job) ... set default values for feature properties"""
obj.ExtensionCorners = True
obj.setExpression('ExtensionLengthDefault', 'OpToolDiameter / 2.0')
obj.setExpression("ExtensionLengthDefault", "OpToolDiameter / 2.0")
def SetupProperties():
"""SetupProperties()... Returns list of feature property names"""
setup = ['ExtensionLengthDefault', 'ExtensionFeature',
'ExtensionCorners']
setup = ["ExtensionLengthDefault", "ExtensionFeature", "ExtensionCorners"]
return setup
# Extend outline face generation function
def getExtendOutlineFace(base_shape, face, extension, remHoles=False, offset_tolerance=1e-4):
def getExtendOutlineFace(
base_shape, face, extension, remHoles=False, offset_tolerance=1e-4
):
"""getExtendOutlineFace(obj, base_shape, face, extension, remHoles) ...
Creates an extended face for the pocket, taking into consideration lateral
collision with the greater base shape.
@@ -513,11 +569,9 @@ def getExtendOutlineFace(base_shape, face, extension, remHoles=False, offset_tol
"""
# Make offset face per user-specified extension distance so as to allow full clearing of face where possible.
offset_face = PathUtils.getOffsetArea(face,
extension,
removeHoles=remHoles,
plane=face,
tolerance=offset_tolerance)
offset_face = PathUtils.getOffsetArea(
face, extension, removeHoles=remHoles, plane=face, tolerance=offset_tolerance
)
if not offset_face:
PathLog.error("Failed to offset a selected face.")
return None
@@ -540,9 +594,11 @@ def getExtendOutlineFace(base_shape, face, extension, remHoles=False, offset_tol
for f in available.Faces:
bbx = f.BoundBox
zNorm = abs(f.normalAt(0.0, 0.0).z)
if (PathGeom.isRoughly(zNorm, 1.0) and
PathGeom.isRoughly(bbx.ZMax - bbx.ZMin, 0.0) and
PathGeom.isRoughly(bbx.ZMin, face.BoundBox.ZMin)):
if (
PathGeom.isRoughly(zNorm, 1.0)
and PathGeom.isRoughly(bbx.ZMax - bbx.ZMin, 0.0)
and PathGeom.isRoughly(bbx.ZMin, face.BoundBox.ZMin)
):
if bbx.ZMin < zmin:
bottom_faces.append(f)
@@ -561,6 +617,7 @@ def getExtendOutlineFace(base_shape, face, extension, remHoles=False, offset_tol
PathLog.error("No bottom face for extend outline.")
return None
# Waterline extension face generation function
def getWaterlineFace(base_shape, face):
"""getWaterlineFace(base_shape, face) ...
@@ -580,8 +637,11 @@ def getWaterlineFace(base_shape, face):
step_down=math.floor(faceHeight - baseBB.ZMin + 2.0),
z_finish_step=0.0,
final_depth=baseBB.ZMin,
user_depths=None)
env = PathUtils.getEnvelope(partshape=base_shape, subshape=None, depthparams=depthparams)
user_depths=None,
)
env = PathUtils.getEnvelope(
partshape=base_shape, subshape=None, depthparams=depthparams
)
# Get top face(s) of envelope at face height
rawList = list()
for f in env.Faces:
@@ -589,7 +649,9 @@ def getWaterlineFace(base_shape, face):
rawList.append(f)
# make compound and extrude downward
rawComp = Part.makeCompound(rawList)
rawCompExtNeg = rawComp.extrude(FreeCAD.Vector(0.0, 0.0, baseBB.ZMin - faceHeight - 1.0))
rawCompExtNeg = rawComp.extrude(
FreeCAD.Vector(0.0, 0.0, baseBB.ZMin - faceHeight - 1.0)
)
# Cut off bottom of base shape at face height
topSolid = base_shape.cut(rawCompExtNeg)

View File

@@ -20,35 +20,39 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
from pivy import coin
import FreeCAD
import FreeCADGui
import PathScripts.PathFeatureExtensions as FeatureExtensions
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathFeatureExtensions as FeatureExtensions
from PySide import QtCore, QtGui
from pivy import coin
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader('Part', globals(), 'Part')
Part = LazyLoader("Part", globals(), "Part")
__title__ = "Path Feature Extensions UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Extensions feature page controller."
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
translate = FreeCAD.Qt.translate
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class _Extension(object):
ColourEnabled = (1.0, .5, 1.0)
ColourDisabled = (1.0, 1.0, .5)
ColourEnabled = (1.0, 0.5, 1.0)
ColourDisabled = (1.0, 1.0, 0.5)
TransparencySelected = 0.0
TransparencyDeselected = 0.7
@@ -152,7 +156,7 @@ class _Extension(object):
if self.switch:
self.switch.whichChild = coin.SO_SWITCH_NONE
def enable(self, ena = True):
def enable(self, ena=True):
if ena:
self.material.diffuseColor = self.ColourEnabled
else:
@@ -173,10 +177,10 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
DataSwitch = QtCore.Qt.ItemDataRole.UserRole + 2
Direction = {
FeatureExtensions.Extension.DirectionNormal: translate('PathPocket', 'Normal'),
FeatureExtensions.Extension.DirectionX: translate('PathPocket', 'X'),
FeatureExtensions.Extension.DirectionY: translate('PathPocket', 'Y')
}
FeatureExtensions.Extension.DirectionNormal: translate("PathPocket", "Normal"),
FeatureExtensions.Extension.DirectionX: translate("PathPocket", "X"),
FeatureExtensions.Extension.DirectionY: translate("PathPocket", "Y"),
}
def initPage(self, obj):
self.setTitle("Extensions")
@@ -193,17 +197,21 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.extensions = list()
self.defaultLength = PathGui.QuantitySpinBox(self.form.defaultLength, obj, 'ExtensionLengthDefault') # pylint: disable=attribute-defined-outside-init
self.defaultLength = PathGui.QuantitySpinBox(
self.form.defaultLength, obj, "ExtensionLengthDefault"
) # pylint: disable=attribute-defined-outside-init
self.form.extensionTree.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.form.extensionTree.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.switch = coin.SoSwitch() # pylint: disable=attribute-defined-outside-init
self.switch = coin.SoSwitch() # pylint: disable=attribute-defined-outside-init
self.obj.ViewObject.RootNode.addChild(self.switch)
self.switch.whichChild = coin.SO_SWITCH_ALL
self.model = QtGui.QStandardItemModel(self.form.extensionTree) # pylint: disable=attribute-defined-outside-init
self.model.setHorizontalHeaderLabels(['Base', 'Extension'])
self.model = QtGui.QStandardItemModel(
self.form.extensionTree
) # pylint: disable=attribute-defined-outside-init
self.model.setHorizontalHeaderLabels(["Base", "Extension"])
"""
# russ4262: This `if` block shows all available extensions upon edit of operation with any extension enabled.
@@ -216,7 +224,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
"""
self.form.showExtensions.setCheckState(QtCore.Qt.Unchecked)
self.blockUpdateData = False # pylint: disable=attribute-defined-outside-init
self.blockUpdateData = False # pylint: disable=attribute-defined-outside-init
def cleanupPage(self, obj):
try:
@@ -243,29 +251,33 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
def currentExtensions(self):
PathLog.debug("currentExtensions()")
extensions = []
def extractExtension(item, ext):
if ext and ext.edge and item.checkState() == QtCore.Qt.Checked:
extensions.append(ext.ext)
if self.form.enableExtensions.isChecked():
self.forAllItemsCall(extractExtension)
PathLog.track('extensions', extensions)
PathLog.track("extensions", extensions)
return extensions
def updateProxyExtensions(self, obj):
PathLog.debug("updateProxyExtensions()")
self.extensions = self.currentExtensions() # pylint: disable=attribute-defined-outside-init
self.extensions = (
self.currentExtensions()
) # pylint: disable=attribute-defined-outside-init
FeatureExtensions.setExtensions(obj, self.extensions)
def getFields(self, obj):
PathLog.track(obj.Label, self.model.rowCount(), self.model.columnCount())
self.blockUpdateData = True # pylint: disable=attribute-defined-outside-init
self.blockUpdateData = True # pylint: disable=attribute-defined-outside-init
if obj.ExtensionCorners != self.form.extendCorners.isChecked():
obj.ExtensionCorners = self.form.extendCorners.isChecked()
self.defaultLength.updateProperty()
self.updateProxyExtensions(obj)
self.blockUpdateData = False # pylint: disable=attribute-defined-outside-init
self.blockUpdateData = False # pylint: disable=attribute-defined-outside-init
def setFields(self, obj):
PathLog.track(obj.Label)
@@ -285,7 +297,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
Subroutine called inside `setFields()` to initialize Extensions efficiently."""
if self.enabled:
self.extensions = FeatureExtensions.getExtensions(obj)
elif len(obj.ExtensionFeature) > 0: # latter test loads pre-existing extensions (editing of existing operation)
elif (
len(obj.ExtensionFeature) > 0
): # latter test loads pre-existing extensions (editing of existing operation)
noEdges = True
for (__, __, subFeat) in FeatureExtensions.readObjExtensionFeature(obj):
if subFeat.startswith("Edge") or subFeat.startswith("Wire"):
@@ -311,7 +325,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self._enableExtensions() # Recalculate extensions
def createItemForBaseModel(self, base, sub, edges, extensions):
PathLog.track(base.Label, sub, '+', len(edges), len(base.Shape.getElement(sub).Edges))
PathLog.track(
base.Label, sub, "+", len(edges), len(base.Shape.getElement(sub).Edges)
)
# PathLog.debug("createItemForBaseModel() label: {}, sub: {}, {}, edgeCnt: {}, subEdges: {}".format(base.Label, sub, '+', len(edges), len(base.Shape.getElement(sub).Edges)))
extendCorners = self.form.extendCorners.isChecked()
@@ -341,7 +357,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
extensionEdges = {}
if includeEdges:
if self.useOutline == 1 and sub.startswith('Face'):
if self.useOutline == 1 and sub.startswith("Face"):
# Only show exterior extensions if `Use Outline` is True
subEdges = subShape.Wires[0].Edges
else:
@@ -358,26 +374,48 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
createSubItem(label, ext1)
if extendCorners and includeEdges:
def edgesMatchShape(e0, e1):
flipped = PathGeom.flipEdge(e1)
if flipped:
return PathGeom.edgesMatch(e0, e1) or PathGeom.edgesMatch(e0, flipped)
return PathGeom.edgesMatch(e0, e1) or PathGeom.edgesMatch(
e0, flipped
)
else:
return PathGeom.edgesMatch(e0, e1)
self.extensionEdges = extensionEdges
PathLog.debug("extensionEdges.values(): {}".format(extensionEdges.values()))
for edgeList in Part.sortEdges(list(extensionEdges.keys())): # Identify connected edges that form wires
for edgeList in Part.sortEdges(
list(extensionEdges.keys())
): # Identify connected edges that form wires
self.edgeList = edgeList
if len(edgeList) == 1:
label = "Edge%s" % [extensionEdges[keyEdge] for keyEdge in extensionEdges.keys() if edgesMatchShape(keyEdge, edgeList[0])][0]
label = (
"Edge%s"
% [
extensionEdges[keyEdge]
for keyEdge in extensionEdges.keys()
if edgesMatchShape(keyEdge, edgeList[0])
][0]
)
else:
label = "Wire(%s)" % ','.join(sorted([extensionEdges[keyEdge] for e in edgeList for keyEdge in extensionEdges.keys() if edgesMatchShape(e, keyEdge)], key=lambda s: int(s))) # pylint: disable=unnecessary-lambda
label = "Wire(%s)" % ",".join(
sorted(
[
extensionEdges[keyEdge]
for e in edgeList
for keyEdge in extensionEdges.keys()
if edgesMatchShape(e, keyEdge)
],
key=lambda s: int(s),
)
) # pylint: disable=unnecessary-lambda
ext2 = self._cachedExtension(self.obj, base, sub, label)
createSubItem(label, ext2)
# Only add these subItems for horizontally oriented faces, not edges or vertical faces (from vertical face loops)
if sub.startswith('Face') and PathGeom.isHorizontal(subShape):
if sub.startswith("Face") and PathGeom.isHorizontal(subShape):
# Add entry to extend outline of face
label = "Extend_" + sub
ext3 = self._cachedExtension(self.obj, base, sub, label)
@@ -406,8 +444,11 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.form.extensionTree.blockSignals(True)
# remember current visual state
if hasattr(self, 'selectionModel'):
selectedExtensions = [self.model.itemFromIndex(index).data(self.DataObject).ext for index in self.selectionModel.selectedIndexes()]
if hasattr(self, "selectionModel"):
selectedExtensions = [
self.model.itemFromIndex(index).data(self.DataObject).ext
for index in self.selectionModel.selectedIndexes()
]
else:
selectedExtensions = []
collapsedModels = []
@@ -420,7 +461,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
for featureRow in range(model.rowCount()):
feature = model.child(featureRow, 0)
if not self.form.extensionTree.isExpanded(feature.index()):
collapsedFeatures.append("%s.%s" % (modelName, feature.data(QtCore.Qt.EditRole)))
collapsedFeatures.append(
"%s.%s" % (modelName, feature.data(QtCore.Qt.EditRole))
)
# remove current extensions and all their visuals
def removeItemSwitch(item, ext):
@@ -428,6 +471,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
ext.hide()
if ext.root:
self.switch.removeChild(ext.root)
self.forAllItemsCall(removeItemSwitch)
self.model.clear()
@@ -435,14 +479,19 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
if self.enabled:
for base in self.obj.Base:
show = False
edges = [(edge, "Edge%d" % (i + 1)) for i, edge in enumerate(base[0].Shape.Edges)]
edges = [
(edge, "Edge%d" % (i + 1))
for i, edge in enumerate(base[0].Shape.Edges)
]
baseItem = QtGui.QStandardItem()
baseItem.setData(base[0].Label, QtCore.Qt.EditRole)
baseItem.setSelectable(False)
for sub in sorted(base[1]):
if sub.startswith('Face'):
if sub.startswith("Face"):
show = True
baseItem.appendRow(self.createItemForBaseModel(base[0], sub, edges, extensions))
baseItem.appendRow(
self.createItemForBaseModel(base[0], sub, edges, extensions)
)
if show:
self.model.appendRow(baseItem)
@@ -458,10 +507,10 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.form.extensionTree.setExpanded(model.index(), False)
for featureRow in range(model.rowCount()):
feature = model.child(featureRow, 0)
featureName = "%s.%s" % (modelName, feature.data(QtCore.Qt.EditRole))
featureName = "%s.%s" % (modelName, feature.data(QtCore.Qt.EditRole))
if featureName in collapsedFeatures:
self.form.extensionTree.setExpanded(feature.index(), False)
if hasattr(self, 'selectionModel') and selectedExtensions:
if hasattr(self, "selectionModel") and selectedExtensions:
self.restoreSelection(selectedExtensions)
self.form.extensionTree.blockSignals(False)
@@ -475,22 +524,22 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
if not self.blockUpdateData:
if self.fieldsSet:
if self.form.enableExtensions.isChecked():
if prop == 'ExtensionLengthDefault':
self.updateQuantitySpinBoxes()
elif prop == 'Base':
self.extensionsReady = False
self.setExtensions(FeatureExtensions.getExtensions(obj))
elif prop == 'UseOutline':
self._getUseOutlineState() # Find `useOutline` checkbox and get its boolean value
self._includeEdgesAndWires()
elif prop == 'Base':
if prop == "ExtensionLengthDefault":
self.updateQuantitySpinBoxes()
elif prop == "Base":
self.extensionsReady = False
self.setExtensions(FeatureExtensions.getExtensions(obj))
elif prop == "UseOutline":
self._getUseOutlineState() # Find `useOutline` checkbox and get its boolean value
self._includeEdgesAndWires()
elif prop == "Base":
self.extensionsReady = False
def restoreSelection(self, selection):
PathLog.debug("restoreSelection()")
PathLog.track()
if 0 == self.model.rowCount():
PathLog.track('-')
PathLog.track("-")
self.form.buttonClear.setEnabled(False)
self.form.buttonDisable.setEnabled(False)
self.form.buttonEnable.setEnabled(False)
@@ -515,7 +564,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
def setSelectionVisuals(item, ext):
if selectItem(item, ext):
self.selectionModel.select(item.index(), QtCore.QItemSelectionModel.Select)
self.selectionModel.select(
item.index(), QtCore.QItemSelectionModel.Select
)
selected = self.selectionModel.isSelected(item.index())
if selected:
@@ -528,6 +579,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
ext.show()
else:
ext.hide()
self.forAllItemsCall(setSelectionVisuals)
def selectionChanged(self):
@@ -536,6 +588,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
def extensionsClear(self):
PathLog.debug("extensionsClear()")
def disableItem(item, ext):
item.setCheckState(QtCore.Qt.Unchecked)
ext.disable()
@@ -572,16 +625,20 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
def showHideExtension(self):
if self.form.showExtensions.isChecked():
def enableExtensionEdit(item, ext):
# pylint: disable=unused-argument
ext.show()
self.forAllItemsCall(enableExtensionEdit)
else:
def disableExtensionEdit(item, ext):
if not self.selectionModel.isSelected(item.index()):
ext.hide()
self.forAllItemsCall(disableExtensionEdit)
#self.setDirty()
# self.setDirty()
def toggleExtensionCorners(self):
PathLog.debug("toggleExtensionCorners()")
@@ -612,7 +669,9 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.model.itemChanged.connect(self.updateItemEnabled)
self.selectionModel = self.form.extensionTree.selectionModel() # pylint: disable=attribute-defined-outside-init
self.selectionModel = (
self.form.extensionTree.selectionModel()
) # pylint: disable=attribute-defined-outside-init
self.selectionModel.selectionChanged.connect(self.selectionChanged)
self.selectionChanged()
@@ -626,12 +685,14 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
if self.useOutlineCheckbox:
self.useOutline = self.useOutlineCheckbox.isChecked()
if hasattr(self, 'parent'):
parent = getattr(self, 'parent')
if parent and hasattr(parent, 'featurePages'):
if hasattr(self, "parent"):
parent = getattr(self, "parent")
if parent and hasattr(parent, "featurePages"):
for page in parent.featurePages:
if hasattr(page, 'panelTitle'):
if page.panelTitle == 'Operation' and hasattr(page.form, 'useOutline'):
if hasattr(page, "panelTitle"):
if page.panelTitle == "Operation" and hasattr(
page.form, "useOutline"
):
PathLog.debug("Found useOutline checkbox")
self.useOutlineCheckbox = page.form.useOutline
if page.form.useOutline.isChecked():
@@ -669,10 +730,14 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.form.includeEdges.blockSignals(True)
# Make changes to form
msg = translate("PathPocketShape",
"Edge count greater than threshold of" + " " +
str(self.edgeCountThreshold) + ": " +
str(self.initialEdgeCount))
msg = translate(
"PathPocketShape",
"Edge count greater than threshold of"
+ " "
+ str(self.edgeCountThreshold)
+ ": "
+ str(self.initialEdgeCount),
)
self.form.enableExtensionsWarning.setText(msg)
self.form.enableExtensions.setChecked(False)
self.form.enableExtensionsWarning.show()
@@ -763,6 +828,8 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
reset[k] = self.extensionsCache[k]
self.extensionsCache = reset
self.extensionsReady = False
# Eclass
FreeCAD.Console.PrintLog("Loading PathFeatureExtensionsGui... done\n")

View File

@@ -20,6 +20,9 @@
# * *
# ***************************************************************************
from PathScripts.PathPostProcessor import PostProcessor
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
@@ -29,8 +32,7 @@ import PathScripts.PathToolController as PathToolController
import PathScripts.PathUtil as PathUtil
import json
import time
from PathScripts.PathPostProcessor import PostProcessor
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -38,12 +40,13 @@ from lazy_loader.lazy_loader import LazyLoader
Draft = LazyLoader("Draft", globals(), "Draft")
PathLog.setLevel(PathLog.Level.INFO, 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 JobTemplate:
@@ -111,34 +114,35 @@ class ObjectJob:
"App::PropertyFile",
"PostProcessorOutputFile",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "The NC output file for this project"),
QT_TRANSLATE_NOOP("App::Property", "The NC output file for this project"),
)
obj.addProperty(
"App::PropertyEnumeration",
"PostProcessor",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Post Processor"),
QT_TRANSLATE_NOOP("App::Property", "Select the Post Processor"),
)
obj.addProperty(
"App::PropertyString",
"PostProcessorArgs",
"Output",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Arguments for the Post Processor (specific to the script)"
QT_TRANSLATE_NOOP(
"App::Property",
"Arguments for the Post Processor (specific to the script)",
),
)
obj.addProperty(
"App::PropertyString",
"LastPostProcessDate",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"),
QT_TRANSLATE_NOOP("App::Property", "Last Time the Job was post-processed"),
)
obj.setEditorMode("LastPostProcessDate", 2) # Hide
obj.addProperty(
"App::PropertyString",
"LastPostProcessOutput",
"Output",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Last Time the Job was post-processed"),
QT_TRANSLATE_NOOP("App::Property", "Last Time the Job was post-processed"),
)
obj.setEditorMode("LastPostProcessOutput", 2) # Hide
@@ -146,21 +150,21 @@ class ObjectJob:
"App::PropertyString",
"Description",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathJob", "An optional description for this job"),
QT_TRANSLATE_NOOP("App::Property", "An optional description for this job"),
)
obj.addProperty(
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Job Cycle Time Estimation"),
QT_TRANSLATE_NOOP("App::Property", "Job Cycle Time Estimation"),
)
obj.setEditorMode("CycleTime", 1) # read-only
obj.addProperty(
"App::PropertyDistance",
"GeometryTolerance",
"Geometry",
QtCore.QT_TRANSLATE_NOOP(
"PathJob",
QT_TRANSLATE_NOOP(
"App::Property",
"For computing Paths; smaller increases accuracy, but slows down computation",
),
)
@@ -169,14 +173,14 @@ class ObjectJob:
"App::PropertyLink",
"Stock",
"Base",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock."),
QT_TRANSLATE_NOOP("App::Property", "Solid object to be used as stock."),
)
obj.addProperty(
"App::PropertyLink",
"Operations",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob",
QT_TRANSLATE_NOOP(
"App::Property",
"Compound path of all operations in the order they are processed.",
),
)
@@ -185,7 +189,7 @@ class ObjectJob:
"App::PropertyEnumeration",
"JobType",
"Base",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Type of Job"),
QT_TRANSLATE_NOOP("App::Property", "Select the Type of Job"),
)
obj.setEditorMode("JobType", 2) # Hide
@@ -193,30 +197,31 @@ class ObjectJob:
"App::PropertyBool",
"SplitOutput",
"Output",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Split output into multiple gcode files"
QT_TRANSLATE_NOOP(
"App::Property", "Split output into multiple gcode files"
),
)
obj.addProperty(
"App::PropertyEnumeration",
"OrderOutputBy",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "If multiple WCS, order the output this way"
QT_TRANSLATE_NOOP(
"App::Property", "If multiple WCS, order the output this way"
),
)
obj.addProperty(
"App::PropertyStringList",
"Fixtures",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "The Work Coordinate Systems for the Job"
QT_TRANSLATE_NOOP(
"App::Property", "The Work Coordinate Systems for the Job"
),
)
obj.OrderOutputBy = ["Fixture", "Tool", "Operation"]
obj.Fixtures = ["G54"]
obj.JobType = ["2D", "2.5D", "Lathe", "Multiaxis"]
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile()
obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors()
@@ -236,6 +241,45 @@ class ObjectJob:
self.setFromTemplateFile(obj, templateFile)
self.setupStock(obj)
@classmethod
def propertyEnumerations(self, dataType="data"):
"""propertyEnumerations(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
"""
enums = {
"OrderOutputBy": [
(translate("Path_Job", "Fixture"), "Fixture"),
(translate("Path_Job", "Tool"), "Tool"),
(translate("Path_Job", "Operation"), "Operation"),
],
"JobType": [
(translate("Path_Job", "2D"), "2D"),
(translate("Path_Job", "2.5D"), "2.5D"),
(translate("Path_Job", "Lathe"), "Lathe"),
(translate("Path_Job", "Multiaxis"), "Multiaxis"),
],
}
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 setupOperations(self, obj):
"""setupOperations(obj)... setup the Operations group for the Job object."""
ops = FreeCAD.ActiveDocument.addObject(
@@ -255,8 +299,8 @@ class ObjectJob:
"App::PropertyLink",
"SetupSheet",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "SetupSheet holding the settings for this job"
QT_TRANSLATE_NOOP(
"App::Property", "SetupSheet holding the settings for this job"
),
)
obj.SetupSheet = PathSetupSheet.Create()
@@ -278,8 +322,8 @@ class ObjectJob:
"App::PropertyLink",
"Model",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "The base objects for all operations"
QT_TRANSLATE_NOOP(
"App::Property", "The base objects for all operations"
),
)
addModels = True
@@ -314,8 +358,8 @@ class ObjectJob:
"App::PropertyLink",
"Tools",
"Base",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Collection of all tool controllers for the job"
QT_TRANSLATE_NOOP(
"App::Property", "Collection of all tool controllers for the job"
),
)
addTable = True
@@ -451,7 +495,7 @@ class ObjectJob:
"App::PropertyString",
"CycleTime",
"Path",
QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation"),
QT_TRANSLATE_NOOP("App::Property", "Operations Cycle Time Estimation"),
)
obj.setEditorMode("CycleTime", 1) # read-only
@@ -460,8 +504,8 @@ class ObjectJob:
"App::PropertyStringList",
"Fixtures",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "The Work Coordinate Systems for the Job"
QT_TRANSLATE_NOOP(
"App::Property", "The Work Coordinate Systems for the Job"
),
)
obj.Fixtures = ["G54"]
@@ -471,8 +515,8 @@ class ObjectJob:
"App::PropertyEnumeration",
"OrderOutputBy",
"WCS",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "If multiple WCS, order the output this way"
QT_TRANSLATE_NOOP(
"App::Property", "If multiple WCS, order the output this way"
),
)
obj.OrderOutputBy = ["Fixture", "Tool", "Operation"]
@@ -482,8 +526,8 @@ class ObjectJob:
"App::PropertyBool",
"SplitOutput",
"Output",
QtCore.QT_TRANSLATE_NOOP(
"PathJob", "Split output into multiple gcode files"
QT_TRANSLATE_NOOP(
"App::Property", "Split output into multiple gcode files"
),
)
obj.SplitOutput = False
@@ -493,11 +537,12 @@ class ObjectJob:
"App::PropertyEnumeration",
"JobType",
"Base",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Type of Job"),
QT_TRANSLATE_NOOP("App::Property", "Select the Type of Job"),
)
obj.setEditorMode("JobType", 2) # Hide
obj.JobType = ["2D", "2.5D", "Lathe", "Multiaxis"]
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
def onChanged(self, obj, prop):
if prop == "PostProcessor" and obj.PostProcessor:
@@ -577,9 +622,11 @@ class ObjectJob:
obj.Tools.Group = tcs
else:
PathLog.error(
translate("PathJob", "Unsupported PathJob template version %s")
% attrs.get(JobTemplate.Version)
"Unsupported PathJob template version {}".format(
attrs.get(JobTemplate.Version)
)
)
if not tcs:
self.addToolController(PathToolController.Create())
@@ -733,9 +780,7 @@ class ObjectJob:
suffix = job.Name[3:]
def errorMessage(grp, job):
PathLog.error(
translate("PathJobGui", "{} corrupt in {} job.".format(grp, job.Name))
)
PathLog.error("{} corrupt in {} job.".format(grp, job.Name))
if not job.Operations:
self.setupOperations(job)

View File

@@ -20,6 +20,8 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import PathScripts.PathJob as PathJob
@@ -31,33 +33,35 @@ import PathScripts.PathUtil as PathUtil
import json
import os
from PySide import QtCore, QtGui
translate = FreeCAD.Qt.translate
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
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())
class CommandJobCreate:
'''
"""
Command used to create a command.
When activated the command opens a dialog allowing the user to select a base object (has to be a solid)
and a template to be used for the initial creation.
'''
"""
def __init__(self):
pass
def GetResources(self):
return {'Pixmap': 'Path_Job',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Job", "Job"),
'Accel': "P, J",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Job", "Creates a Path Job object")}
return {
"Pixmap": "Path_Job",
"MenuText": QT_TRANSLATE_NOOP("Path_Job", "Job"),
"Accel": "P, J",
"ToolTip": QT_TRANSLATE_NOOP(
"Path_Job", "Creates a Path Job object"
),
}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
@@ -74,29 +78,35 @@ class CommandJobCreate:
@classmethod
def Execute(cls, base, template):
FreeCADGui.addModule('PathScripts.PathJobGui')
FreeCADGui.addModule("PathScripts.PathJobGui")
if template:
template = "'%s'" % template
else:
template = 'None'
FreeCADGui.doCommand('PathScripts.PathJobGui.Create(%s, %s)' % ([o.Name for o in base], template))
template = "None"
FreeCADGui.doCommand(
"PathScripts.PathJobGui.Create(%s, %s)" % ([o.Name for o in base], template)
)
class CommandJobTemplateExport:
'''
"""
Command to export a template of a given job.
Opens a dialog to select the file to store the template in. If the template is stored in Path's
file path (see preferences) and named in accordance with job_*.json it will automatically be found
on Job creation and be available for selection.
'''
"""
def __init__(self):
pass
def GetResources(self):
return {'Pixmap': 'Path_ExportTemplate',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Job", "Export Template"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Job", "Exports Path Job as a template to be used for other jobs")}
return {
"Pixmap": "Path_ExportTemplate",
"MenuText": QT_TRANSLATE_NOOP("Path_ExportTemplate", "Export Template"),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_ExportTemplate", "Exports Path Job as a template to be used for other jobs"
),
}
def GetJob(self):
# if there's only one Job in the document ...
@@ -109,7 +119,7 @@ class CommandJobTemplateExport:
sel = FreeCADGui.Selection.getSelection()
if len(sel) == 1:
job = sel[0]
if hasattr(job, 'Proxy') and isinstance(job.Proxy, PathJob.ObjectJob):
if hasattr(job, "Proxy") and isinstance(job.Proxy, PathJob.ObjectJob):
return job
return None
@@ -124,15 +134,17 @@ class CommandJobTemplateExport:
@classmethod
def SaveDialog(cls, job, dialog):
foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(),
"Path - Job Template",
PathPreferences.filePath(),
"job_*.json")[0]
foo = QtGui.QFileDialog.getSaveFileName(
QtGui.QApplication.activeWindow(),
"Path - Job Template",
PathPreferences.filePath(),
"job_*.json",
)[0]
if foo:
if not os.path.basename(foo).startswith('job_'):
foo = os.path.join(os.path.dirname(foo), 'job_' + os.path.basename(foo))
if not foo.endswith('.json'):
foo = foo + '.json'
if not os.path.basename(foo).startswith("job_"):
foo = os.path.join(os.path.dirname(foo), "job_" + os.path.basename(foo))
if not foo.endswith(".json"):
foo = foo + ".json"
cls.Execute(job, foo, dialog)
@classmethod
@@ -155,7 +167,11 @@ class CommandJobTemplateExport:
stockAttrs = None
if dialog:
if dialog.includeStock():
stockAttrs = PathStock.TemplateAttributes(job.Stock, dialog.includeStockExtent(), dialog.includeStockPlacement())
stockAttrs = PathStock.TemplateAttributes(
job.Stock,
dialog.includeStockExtent(),
dialog.includeStockPlacement(),
)
else:
stockAttrs = PathStock.TemplateAttributes(job.Stock)
if stockAttrs:
@@ -169,7 +185,8 @@ class CommandJobTemplateExport:
dialog.includeSettingCoolant(),
dialog.includeSettingOperationHeights(),
dialog.includeSettingOperationDepths(),
dialog.includeSettingOpsSettings())
dialog.includeSettingOpsSettings(),
)
else:
setupSheetAttrs = job.Proxy.setupSheet.templateAttributes(True, True, True)
if setupSheetAttrs:
@@ -177,13 +194,13 @@ class CommandJobTemplateExport:
encoded = job.Proxy.setupSheet.encodeTemplateAttributes(attrs)
# write template
with open(PathUtil.toUnicode(path), 'w') as fp:
with open(PathUtil.toUnicode(path), "w") as fp:
json.dump(encoded, fp, sort_keys=True, indent=2)
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Job', CommandJobCreate())
FreeCADGui.addCommand('Path_ExportTemplate', CommandJobTemplateExport())
FreeCADGui.addCommand("Path_Job", CommandJobCreate())
FreeCADGui.addCommand("Path_ExportTemplate", CommandJobTemplateExport())
FreeCAD.Console.PrintLog("Loading PathJobCmd... done\n")

View File

@@ -20,6 +20,9 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
from collections import Counter
import FreeCAD
import FreeCADGui
import PathScripts.PathJob as PathJob
import PathScripts.PathLog as PathLog
@@ -29,21 +32,17 @@ import PathScripts.PathUtil as PathUtil
import glob
import os
from PySide import QtCore, QtGui
from collections import Counter
translate = FreeCAD.Qt.translate
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
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())
class _ItemDelegate(QtGui.QStyledItemDelegate):
def __init__(self, controller, parent):
self.controller = controller
QtGui.QStyledItemDelegate.__init__(self, parent)
@@ -54,15 +53,16 @@ class _ItemDelegate(QtGui.QStyledItemDelegate):
self.controller.setupColumnEditor(index, editor)
return editor
class JobCreate:
DataObject = QtCore.Qt.ItemDataRole.UserRole
def __init__(self, parent=None, sel=None):
# pylint: disable=unused-argument
self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobCreate.ui")
self.itemsSolid = QtGui.QStandardItem(translate('PathJob', 'Solids'))
self.items2D = QtGui.QStandardItem(translate('PathJob', '2D'))
self.itemsJob = QtGui.QStandardItem(translate('PathJob', 'Jobs'))
self.itemsSolid = QtGui.QStandardItem(translate("Path_Job", "Solids"))
self.items2D = QtGui.QStandardItem(translate("Path_Job", "2D"))
self.itemsJob = QtGui.QStandardItem(translate("Path_Job", "Jobs"))
self.dialog.templateGroup.hide()
self.dialog.modelGroup.hide()
# debugging support
@@ -74,27 +74,40 @@ class JobCreate:
def setupTitle(self, title):
self.dialog.setWindowTitle(title)
def setupModel(self, job = None):
def setupModel(self, job=None):
if job:
preSelected = Counter([PathUtil.getPublicObject(job.Proxy.baseObject(job, obj)).Label for obj in job.Model.Group])
preSelected = Counter(
[
PathUtil.getPublicObject(job.Proxy.baseObject(job, obj)).Label
for obj in job.Model.Group
]
)
jobResources = job.Model.Group + [job.Stock]
else:
preSelected = Counter([obj.Label for obj in FreeCADGui.Selection.getSelection()])
preSelected = Counter(
[obj.Label for obj in FreeCADGui.Selection.getSelection()]
)
jobResources = []
self.candidates = sorted(PathJob.ObjectJob.baseCandidates(), key=lambda o: o.Label)
self.candidates = sorted(
PathJob.ObjectJob.baseCandidates(), key=lambda o: o.Label
)
# If there is only one possibility we might as well make sure it's selected
if not preSelected and 1 == len(self.candidates):
preSelected = Counter([self.candidates[0].Label])
expandSolids = False
expand2Ds = False
expandJobs = False
expand2Ds = False
expandJobs = False
for base in self.candidates:
if not base in jobResources and not PathJob.isResourceClone(job, base, None) and not hasattr(base, 'StockType'):
if (
not base in jobResources
and not PathJob.isResourceClone(job, base, None)
and not hasattr(base, "StockType")
):
item0 = QtGui.QStandardItem()
item1 = QtGui.QStandardItem()
@@ -149,7 +162,7 @@ class JobCreate:
self.delegate = _ItemDelegate(self, self.dialog.modelTree)
self.model = QtGui.QStandardItemModel(self.dialog)
self.model.setHorizontalHeaderLabels(['Model', 'Count'])
self.model.setHorizontalHeaderLabels(["Model", "Count"])
if self.itemsSolid.hasChildren():
self.model.appendRow(self.itemsSolid)
@@ -213,7 +226,9 @@ class JobCreate:
def setupTemplate(self):
templateFiles = []
for path in PathPreferences.searchPaths():
cleanPaths = [f.replace("\\", "/") for f in self.templateFilesIn(path)] # Standardize slashes used across os platforms
cleanPaths = [
f.replace("\\", "/") for f in self.templateFilesIn(path)
] # Standardize slashes used across os platforms
templateFiles.extend(cleanPaths)
template = {}
@@ -229,7 +244,7 @@ class JobCreate:
template[name] = tFile
selectTemplate = PathPreferences.defaultJobTemplate()
index = 0
self.dialog.jobTemplate.addItem('<none>', '')
self.dialog.jobTemplate.addItem("<none>", "")
for name in sorted(template.keys()):
if template[name] == selectTemplate:
index = self.dialog.jobTemplate.count()
@@ -238,16 +253,18 @@ class JobCreate:
self.dialog.templateGroup.show()
def templateFilesIn(self, path):
'''templateFilesIn(path) ... answer all file in the given directory which fit the job template naming convention.
PathJob template files are name job_*.json'''
"""templateFilesIn(path) ... answer all file in the given directory which fit the job template naming convention.
PathJob template files are name job_*.json"""
PathLog.track(path)
return glob.glob(path + '/job_*.json')
return glob.glob(path + "/job_*.json")
def getModels(self):
models = []
for i in range(self.itemsSolid.rowCount()):
for j in range(self.itemsSolid.child(i, 1).data(QtCore.Qt.EditRole)): # pylint: disable=unused-variable
for j in range(
self.itemsSolid.child(i, 1).data(QtCore.Qt.EditRole)
): # pylint: disable=unused-variable
models.append(self.itemsSolid.child(i).data(self.DataObject))
for i in range(self.items2D.rowCount()):
@@ -259,12 +276,14 @@ class JobCreate:
# Note that we do want to use the models (resource clones) of the
# source job as base objects for the new job in order to get the
# identical placement, and anything else that's been customized.
models.extend(self.itemsJob.child(i, 0).data(self.DataObject).Model.Group)
models.extend(
self.itemsJob.child(i, 0).data(self.DataObject).Model.Group
)
return models
def getTemplate(self):
'''answer the file name of the template to be assigned'''
"""answer the file name of the template to be assigned"""
return self.dialog.jobTemplate.itemData(self.dialog.jobTemplate.currentIndex())
def exec_(self):
@@ -298,24 +317,44 @@ class JobTemplateExport:
def updateUI(self):
job = self.job
if job.PostProcessor:
ppHint = "%s %s %s" % (job.PostProcessor, job.PostProcessorArgs, job.PostProcessorOutputFile)
ppHint = "%s %s %s" % (
job.PostProcessor,
job.PostProcessorArgs,
job.PostProcessorOutputFile,
)
self.dialog.postProcessingHint.setText(ppHint)
else:
self.dialog.postProcessingGroup.setEnabled(False)
self.dialog.postProcessingGroup.setChecked(False)
if job.Stock and not PathJob.isResourceClone(job, 'Stock', 'Stock'):
if job.Stock and not PathJob.isResourceClone(job, "Stock", "Stock"):
stockType = PathStock.StockType.FromStock(job.Stock)
if stockType == PathStock.StockType.FromBase:
seHint = translate('PathJob', "Base -/+ %.2f/%.2f %.2f/%.2f %.2f/%.2f") % (job.Stock.ExtXneg, job.Stock.ExtXpos, job.Stock.ExtYneg, job.Stock.ExtYpos, job.Stock.ExtZneg, job.Stock.ExtZpos)
seHint = translate(
"Path_Job", "Base -/+ %.2f/%.2f %.2f/%.2f %.2f/%.2f"
) % (
job.Stock.ExtXneg,
job.Stock.ExtXpos,
job.Stock.ExtYneg,
job.Stock.ExtYpos,
job.Stock.ExtZneg,
job.Stock.ExtZpos,
)
self.dialog.stockPlacement.setChecked(False)
elif stockType == PathStock.StockType.CreateBox:
seHint = translate('PathJob', "Box: %.2f x %.2f x %.2f") % (job.Stock.Length, job.Stock.Width, job.Stock.Height)
seHint = translate("Path_Job", "Box: %.2f x %.2f x %.2f") % (
job.Stock.Length,
job.Stock.Width,
job.Stock.Height,
)
elif stockType == PathStock.StockType.CreateCylinder:
seHint = translate('PathJob', "Cylinder: %.2f x %.2f") % (job.Stock.Radius, job.Stock.Height)
seHint = translate("Path_Job", "Cylinder: %.2f x %.2f") % (
job.Stock.Radius,
job.Stock.Height,
)
else:
seHint = '-'
PathLog.error(translate('PathJob', 'Unsupported stock type'))
seHint = "-"
PathLog.error(translate("Path_Job", "Unsupported stock type"))
self.dialog.stockExtentHint.setText(seHint)
spHint = "%s" % job.Stock.Placement
self.dialog.stockPlacementHint.setText(spHint)
@@ -325,7 +364,13 @@ class JobTemplateExport:
heightsChanged = not job.SetupSheet.Proxy.hasDefaultOperationHeights()
coolantChanged = not job.SetupSheet.Proxy.hasDefaultCoolantMode()
opsWithSettings = job.SetupSheet.Proxy.operationsWithSettings()
settingsChanged = rapidChanged or depthsChanged or heightsChanged or coolantChanged or 0 != len(opsWithSettings)
settingsChanged = (
rapidChanged
or depthsChanged
or heightsChanged
or coolantChanged
or 0 != len(opsWithSettings)
)
self.dialog.settingsGroup.setChecked(settingsChanged)
self.dialog.settingToolRapid.setChecked(rapidChanged)
self.dialog.settingOperationDepths.setChecked(depthsChanged)
@@ -346,7 +391,11 @@ class JobTemplateExport:
self.dialog.toolsList.addItem(item)
def checkUncheckTools(self):
state = QtCore.Qt.CheckState.Checked if self.dialog.toolsGroup.isChecked() else QtCore.Qt.CheckState.Unchecked
state = (
QtCore.Qt.CheckState.Checked
if self.dialog.toolsGroup.isChecked()
else QtCore.Qt.CheckState.Unchecked
)
for i in range(self.dialog.toolsList.count()):
self.dialog.toolsList.item(i).setCheckState(state)

View File

@@ -25,27 +25,25 @@ from PySide import QtCore, QtGui
from collections import Counter
from contextlib import contextmanager
from pivy import coin
import json
import math
import traceback
import FreeCAD
import FreeCADGui
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGuiInit as PathGuiInit
import PathScripts.PathJob as PathJob
import PathScripts.PathJobCmd as PathJobCmd
import PathScripts.PathJobDlg as PathJobDlg
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGuiInit as PathGuiInit
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathSetupSheetGui as PathSetupSheetGui
import PathScripts.PathStock as PathStock
import PathScripts.PathToolBitGui as PathToolBitGui
import PathScripts.PathToolControllerGui as PathToolControllerGui
import PathScripts.PathToolLibraryEditor as PathToolLibraryEditor
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
import PathScripts.PathToolBitGui as PathToolBitGui
import json
import math
import traceback
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -54,14 +52,13 @@ Draft = LazyLoader("Draft", globals(), "Draft")
Part = LazyLoader("Part", globals(), "Part")
DraftVecUtils = LazyLoader("DraftVecUtils", globals(), "DraftVecUtils")
translate = FreeCAD.Qt.translate
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
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 _OpenCloseResourceEditor(obj, vobj, edit):
@@ -282,7 +279,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_Job", "Edit"), menu)
action.triggered.connect(self.setEdit)
menu.addAction(action)
@@ -387,7 +384,7 @@ class StockFromBaseBoundBoxEdit(StockEdit):
if self.IsStock(obj):
self.getFieldsStock(obj.Stock, fields)
else:
PathLog.error(translate("PathJob", "Stock not from Base bound box!"))
PathLog.error("Stock not from Base bound box!")
def setFields(self, obj):
PathLog.track()
@@ -479,7 +476,7 @@ class StockCreateBoxEdit(StockEdit):
self.form.stockBoxHeight.text()
)
else:
PathLog.error(translate("PathJob", "Stock not a box!"))
PathLog.error("Stock not a box!")
except Exception:
pass
@@ -525,7 +522,7 @@ class StockCreateCylinderEdit(StockEdit):
self.form.stockCylinderHeight.text()
)
else:
PathLog.error(translate("PathJob", "Stock not a cylinder!"))
PathLog.error(translate("Path_Job", "Stock not a cylinder!"))
except Exception:
pass
@@ -609,7 +606,7 @@ class TaskPanel:
DataProperty = QtCore.Qt.ItemDataRole.UserRole + 1
def __init__(self, vobj, deleteOnReject):
FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Edit Job"))
FreeCAD.ActiveDocument.openTransaction("Edit Job")
self.vobj = vobj
self.vproxy = vobj.Proxy
self.obj = vobj.Object
@@ -644,6 +641,11 @@ class TaskPanel:
self.form.postProcessorArguments.toolTip()
)
# Populate the other comboboxes with enums from the job class
comboToPropertyMap = [("orderBy", "OrderOutputBy")]
enumTups = PathJob.ObjectJob.propertyEnumerations(dataType="raw")
self.populateCombobox(self.form, enumTups, comboToPropertyMap)
self.vproxy.setupEditVisibility(self.obj)
self.stockFromBase = None
@@ -659,6 +661,21 @@ class TaskPanel:
self.obj.SetupSheet, self.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 preCleanup(self):
PathLog.track()
FreeCADGui.Selection.removeObserver(self)
@@ -682,9 +699,7 @@ class TaskPanel:
FreeCAD.ActiveDocument.abortTransaction()
if self.deleteOnReject and FreeCAD.ActiveDocument.getObject(self.name):
PathLog.info("Uncreate Job")
FreeCAD.ActiveDocument.openTransaction(
translate("Path_Job", "Uncreate Job")
)
FreeCAD.ActiveDocument.openTransaction("Uncreate Job")
if self.obj.ViewObject.Proxy.onDelete(self.obj.ViewObject, None):
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
FreeCAD.ActiveDocument.commitTransaction()
@@ -1257,7 +1272,7 @@ class TaskPanel:
setupFromExisting()
else:
PathLog.error(
translate("PathJob", "Unsupported stock object %s")
translate("Path_Job", "Unsupported stock object %s")
% self.obj.Stock.Label
)
else:
@@ -1273,7 +1288,7 @@ class TaskPanel:
index = -1
else:
PathLog.error(
translate("PathJob", "Unsupported stock type %s (%d)")
translate("Path_Job", "Unsupported stock type %s (%d)")
% (self.form.stock.currentText(), index)
)
self.stockEdit.activate(self.obj, index == -1)
@@ -1562,7 +1577,7 @@ def Create(base, template=None):
"""Create(base, template) ... creates a job instance for the given base object
using template to configure it."""
FreeCADGui.addModule("PathScripts.PathJob")
FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Create Job"))
FreeCAD.ActiveDocument.openTransaction("Create Job")
try:
obj = PathJob.Create("Job", base, template)
obj.ViewObject.Proxy = ViewProvider(obj.ViewObject)

View File

@@ -20,26 +20,28 @@
# * *
# ***************************************************************************
import FreeCAD
from PySide import QtCore, QtGui
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
_dressups = []
def RegisterDressup(dressup):
_dressups.append(dressup)
class DressupPreferencesPage:
def __init__(self, parent=None):
# pylint: disable=unused-argument
self.form = QtGui.QToolBox()
self.form.setWindowTitle(translate("Path_PreferencesPathDressup", 'Dressups'))
self.form.setWindowTitle(translate("Path_PreferencesPathDressup", "Dressups"))
pages = []
for dressup in _dressups:
page = dressup.preferencesPage()
if hasattr(page, 'icon') and page.icon:
if hasattr(page, "icon") and page.icon:
self.form.addItem(page.form, page.icon, page.label)
else:
self.form.addItem(page.form, page.label)
@@ -53,4 +55,3 @@ class DressupPreferencesPage:
def loadSettings(self):
for page in self.pages:
page.loadSettings()

View File

@@ -25,28 +25,31 @@ import FreeCAD
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathProfile as PathProfile
import PathScripts.PathProfileGui as PathProfileGui
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Path Contour Operation UI (depreciated)"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Contour operation page controller and command implementation (depreciated)."
__doc__ = "Contour operation page controller and command implementation (deprecated)."
class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage):
'''Psuedo page controller class for Profile operation,
allowing for backward compatibility with pre-existing "Contour" operations.'''
"""Psuedo page controller class for Profile operation,
allowing for backward compatibility with pre-existing "Contour" operations."""
pass
# Eclass
Command = PathOpGui.SetupOperation('Profile',
PathProfile.Create,
TaskPanelOpPage,
'Path_Contour',
QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Profile"),
QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Profile entire model, selected face(s) or selected edge(s)"),
PathProfile.SetupProperties)
Command = PathOpGui.SetupOperation(
"Profile",
PathProfile.Create,
TaskPanelOpPage,
"Path_Contour",
QT_TRANSLATE_NOOP("Path_Profile", "Profile"),
QT_TRANSLATE_NOOP(
"Path_Profile", "Profile entire model, selected face(s) or selected edge(s)"
),
PathProfile.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathProfileContourGui... done\n")

View File

@@ -25,28 +25,33 @@ import FreeCAD
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathProfile as PathProfile
import PathScripts.PathProfileGui as PathProfileGui
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Path Profile Edges Operation UI (depreciated)"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Profile Edges operation page controller and command implementation (depreciated)."
__doc__ = (
"Profile Edges operation page controller and command implementation (deprecated)."
)
class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage):
'''Psuedo page controller class for Profile operation,
allowing for backward compatibility with pre-existing "Profile Edges" operations.'''
"""Psuedo page controller class for Profile operation,
allowing for backward compatibility with pre-existing "Profile Edges" operations."""
pass
# Eclass
Command = PathOpGui.SetupOperation('Profile',
PathProfile.Create,
TaskPanelOpPage,
'Path_Contour',
QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Profile"),
QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Profile entire model, selected face(s) or selected edge(s)"),
PathProfile.SetupProperties)
Command = PathOpGui.SetupOperation(
"Profile",
PathProfile.Create,
TaskPanelOpPage,
"Path_Contour",
QT_TRANSLATE_NOOP("Path_Profile", "Profile"),
QT_TRANSLATE_NOOP(
"Path_Profile", "Profile entire model, selected face(s) or selected edge(s)"
),
PathProfile.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathProfileEdgesGui... done\n")

View File

@@ -25,28 +25,33 @@ import FreeCAD
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathProfile as PathProfile
import PathScripts.PathProfileGui as PathProfileGui
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Path Profile Faces Operation UI (depreciated)"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Profile Faces operation page controller and command implementation (depreciated)."
__doc__ = (
"Profile Faces operation page controller and command implementation (deprecated)."
)
class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage):
'''Psuedo page controller class for Profile operation,
allowing for backward compatibility with pre-existing "Profile Faces" operations.'''
"""Psuedo page controller class for Profile operation,
allowing for backward compatibility with pre-existing "Profile Faces" operations."""
pass
# Eclass
Command = PathOpGui.SetupOperation('Profile',
PathProfile.Create,
TaskPanelOpPage,
'Path_Contour',
QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Profile"),
QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Profile entire model, selected face(s) or selected edge(s)"),
PathProfile.SetupProperties)
Command = PathOpGui.SetupOperation(
"Profile",
PathProfile.Create,
TaskPanelOpPage,
"Path_Contour",
QT_TRANSLATE_NOOP("Path_Profile", "Profile"),
QT_TRANSLATE_NOOP(
"Path_Profile", "Profile entire model, selected face(s) or selected edge(s)"
),
PathProfile.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n")