| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import FreeCAD |
| import Path |
| import math |
|
|
| __title__ = "PathLanguage - classes for an internal language/representation for Path" |
| __author__ = "sliptonic (Brad Collette)" |
| __url__ = "https://www.freecad.org" |
| __doc__ = "Functions to extract and convert between Path.Command and Part.Edge and utility functions to reason about them." |
|
|
| CmdMoveStraight = Path.Geom.CmdMoveStraight + Path.Geom.CmdMoveRapid |
|
|
|
|
| class Instruction(object): |
| """An Instruction is a pure python replacement of Path.Command which also tracks its begin position.""" |
|
|
| def __init__(self, begin, cmd, param=None): |
| self.begin = begin |
| if isinstance(cmd, Path.Command): |
| self.cmd = Path.Name |
| self.param = Path.Parameters |
| else: |
| self.cmd = cmd |
| if param is None: |
| self.param = {} |
| else: |
| self.param = param |
|
|
| def anglesOfTangents(self): |
| return (0, 0) |
|
|
| def setPositionBegin(self, begin): |
| self.begin = begin |
|
|
| def positionBegin(self): |
| """positionBegin() ... returns a Vector of the begin position""" |
| return self.begin |
|
|
| def positionEnd(self): |
| """positionEnd() ... returns a Vector of the end position""" |
| return FreeCAD.Vector(self.x(self.begin.x), self.y(self.begin.y), self.z(self.begin.z)) |
|
|
| def pathLength(self): |
| """pathLength() ... returns the length in mm""" |
| return 0 |
|
|
| def isMove(self): |
| return False |
|
|
| def isRapid(self): |
| return False |
|
|
| def isPlunge(self): |
| """isPlunge() ... return true if this moves is vertical""" |
| if self.isMove(): |
| if ( |
| Path.Geom.isRoughly(self.begin.x, self.x(self.begin.x)) |
| and Path.Geom.isRoughly(self.begin.y, self.y(self.begin.y)) |
| and not Path.Geom.isRoughly(self.begin.z, self.z(self.begin.z)) |
| ): |
| return True |
| return False |
|
|
| def leadsInto(self, instr): |
| """leadsInto(instr) ... return true if instr is a continuation of self""" |
| return Path.Geom.pointsCoincide(self.positionEnd(), instr.positionBegin()) |
|
|
| def x(self, default=0): |
| return self.param.get("X", default) |
|
|
| def y(self, default=0): |
| return self.param.get("Y", default) |
|
|
| def z(self, default=0): |
| return self.param.get("Z", default) |
|
|
| def i(self, default=0): |
| return self.param.get("I", default) |
|
|
| def j(self, default=0): |
| return self.param.get("J", default) |
|
|
| def k(self, default=0): |
| return self.param.get("K", default) |
|
|
| def xyBegin(self): |
| """xyBegin() ... internal convenience function""" |
| return FreeCAD.Vector(self.begin.x, self.begin.y, 0) |
|
|
| def xyEnd(self): |
| """xyEnd() ... internal convenience function""" |
| return FreeCAD.Vector(self.x(self.begin.x), self.y(self.begin.y), 0) |
|
|
| def __repr__(self): |
| return f"{self.cmd}{self.param}" |
|
|
| def str(self, digits=2): |
| if digits == 0: |
| s = [f"{k}: {int(v)}" for k, v in self.param.items()] |
| else: |
| fmt = f"{{}}: {{:.{digits}}}" |
| s = [fmt.format(k, v) for k, v in self.param.items()] |
| return f"{self.cmd}{{{', '.join(s)}}}" |
|
|
|
|
| class MoveStraight(Instruction): |
| def anglesOfTangents(self): |
| """anglesOfTangents() ... return a tuple with the tangent angles at begin and end position""" |
| begin = self.xyBegin() |
| end = self.xyEnd() |
| if end == begin: |
| return (0, 0) |
| a = Path.Geom.getAngle(end - begin) |
| return (a, a) |
|
|
| def isMove(self): |
| return True |
|
|
| def isStraight(self): |
| return True |
|
|
| def isArc(self): |
| return False |
|
|
| def isRapid(self): |
| return self.cmd in Path.Geom.CmdMoveRapid |
|
|
| def pathLength(self): |
| return (self.positionEnd() - self.positionBegin()).Length |
|
|
|
|
| class MoveArc(Instruction): |
| def anglesOfTangents(self): |
| """anglesOfTangents() ... return a tuple with the tangent angles at begin and end position""" |
| begin = self.xyBegin() |
| end = self.xyEnd() |
| center = self.xyCenter() |
| |
| s0 = Path.Geom.getAngle(begin - center) |
| s1 = Path.Geom.getAngle(end - center) |
| |
| |
| return ( |
| Path.Geom.normalizeAngle(s0 + self.arcDirection()), |
| Path.Geom.normalizeAngle(s1 + self.arcDirection()), |
| ) |
|
|
| def isMove(self): |
| return True |
|
|
| def isArc(self): |
| return True |
|
|
| def isStraight(self): |
| return False |
|
|
| def isCW(self): |
| return self.arcDirection() < 0 |
|
|
| def isCCW(self): |
| return self.arcDirection() > 0 |
|
|
| def arcAngle(self): |
| """arcAngle() ... return the angle of the arc opening""" |
| begin = self.xyBegin() |
| end = self.xyEnd() |
| center = self.xyCenter() |
| s0 = Path.Geom.getAngle(begin - center) |
| s1 = Path.Geom.getAngle(end - center) |
|
|
| if self.isCW(): |
| while s0 < s1: |
| s0 = s0 + 2 * math.pi |
| return s0 - s1 |
|
|
| |
| while s1 < s0: |
| s1 = s1 + 2 * math.pi |
| return s1 - s0 |
|
|
| def arcRadius(self): |
| """arcRadius() ... return the radius""" |
| return (self.xyBegin() - self.xyCenter()).Length |
|
|
| def pathLength(self): |
| return self.arcAngle() * self.arcRadius() |
|
|
| def xyCenter(self): |
| return FreeCAD.Vector(self.begin.x + self.i(), self.begin.y + self.j(), 0) |
|
|
|
|
| class MoveArcCW(MoveArc): |
| def arcDirection(self): |
| return -math.pi / 2 |
|
|
|
|
| class MoveArcCCW(MoveArc): |
| def arcDirection(self): |
| return math.pi / 2 |
|
|
|
|
| class Maneuver(object): |
| """A series of instructions and moves""" |
|
|
| def __init__(self, begin=None, instr=None): |
| self.instr = instr if instr else [] |
| self.setPositionBegin(begin if begin else FreeCAD.Vector(0, 0, 0)) |
|
|
| def setPositionBegin(self, begin): |
| self.begin = begin |
| for i in self.instr: |
| i.setPositionBegin(begin) |
| begin = i.positionEnd() |
|
|
| def positionBegin(self): |
| return self.begin |
|
|
| def getMoves(self): |
| return [instr for instr in self.instr if instr.isMove()] |
|
|
| def addInstruction(self, instr): |
| self.instr.append(instr) |
|
|
| def addInstructions(self, coll): |
| self.instr.extend(coll) |
|
|
| def toPath(self): |
| return Path.Path([instruction_to_command(instr) for instr in self.instr]) |
|
|
| def __repr__(self): |
| if self.instr: |
| return "\n".join([str(i) for i in self.instr]) |
| return "" |
|
|
| @classmethod |
| def InstructionFromCommand(cls, cmd, begin=None): |
| if not begin: |
| begin = FreeCAD.Vector(0, 0, 0) |
|
|
| if cmd.Name in CmdMoveStraight: |
| return MoveStraight(begin, cmd.Name, cmd.Parameters) |
| if cmd.Name in Path.Geom.CmdMoveCW: |
| return MoveArcCW(begin, cmd.Name, cmd.Parameters) |
| if cmd.Name in Path.Geom.CmdMoveCCW: |
| return MoveArcCCW(begin, cmd.Name, cmd.Parameters) |
| return Instruction(begin, cmd.Name, cmd.Parameters) |
|
|
| @classmethod |
| def FromPath(cls, path, begin=None): |
| maneuver = Maneuver(begin) |
| instr = [] |
| begin = maneuver.positionBegin() |
| for cmd in path.Commands: |
| i = cls.InstructionFromCommand(cmd, begin) |
| instr.append(i) |
| begin = i.positionEnd() |
| maneuver.instr = instr |
| return maneuver |
|
|
| @classmethod |
| def FromGCode(cls, gcode, begin=None): |
| return cls.FromPath(Path.Path(gcode), begin) |
|
|
|
|
| def instruction_to_command(instr): |
| return Path.Command(instr.cmd, instr.param) |
|
|