| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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) |
| |
|