| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | import math |
| | import Path |
| | import FreeCAD |
| | from enum import Enum |
| |
|
| | __title__ = "Rotation toolpath Generator" |
| | __author__ = "sliptonic (Brad Collette)" |
| | __url__ = "https://www.freecad.org" |
| | __doc__ = "Generates the rotation toolpath" |
| |
|
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | class refAxis(Enum): |
| | x = FreeCAD.Vector(1, 0, 0) |
| | y = FreeCAD.Vector(0, 1, 0) |
| | z = FreeCAD.Vector(0, 0, 1) |
| |
|
| |
|
| | def relAngle(vec, ref): |
| | """ |
| | Takes a vector and a reference axis (refAxis) vector. Calculates the |
| | relative angle. The result is returned in degrees (plus or minus) |
| | """ |
| |
|
| | Path.Log.debug("vec: {} ref: {}".format(vec, ref)) |
| | norm = vec * 1 |
| |
|
| | if ref == refAxis.x: |
| | plane = refAxis.y.value |
| | elif ref == refAxis.y: |
| | plane = refAxis.z.value |
| | else: |
| | plane = refAxis.x.value |
| |
|
| | norm.projectToPlane(FreeCAD.Vector(0, 0, 0), plane) |
| |
|
| | ref = ref.value |
| | rot = FreeCAD.Rotation(norm, ref) |
| | ang = math.degrees(rot.Angle) |
| | angle = ang * plane.dot(rot.Axis) |
| | Path.Log.debug("relative ang: {}".format(angle)) |
| |
|
| | return angle |
| |
|
| |
|
| | def __getCRotation(normalVector, cMin=-360, cMax=360): |
| | """ |
| | Calculate the valid C axis rotations component to align the normalVector |
| | with either the +y or -y axis. |
| | multiple poses may be possible. Returns a list of all valid poses |
| | """ |
| | Path.Log.debug("normalVector: {} cMin: {} cMax: {}".format(normalVector, cMin, cMax)) |
| |
|
| | angle = relAngle(normalVector, refAxis.y) |
| |
|
| | |
| | |
| | candidates = [angle] |
| | if angle == 0: |
| | candidates.append(180) |
| | elif angle == 180: |
| | candidates.append(0) |
| | elif angle >= 0: |
| | candidates.append(angle - 180) |
| | candidates.append(180 + angle) |
| | candidates.append(angle - 360) |
| | else: |
| | candidates.append(angle + 180) |
| | candidates.append(-180 + angle) |
| | candidates.append(angle + 360) |
| |
|
| | |
| | results = [c for c in candidates if c >= cMin and c <= cMax] |
| |
|
| | return results |
| |
|
| |
|
| | def __getARotation(normalVector, aMin=-360, aMax=360): |
| | """ |
| | Calculate the A axis rotation component. |
| | Final rotation is always assumed to be around +X. The sign of the returned |
| | value indicates direction of rotation. |
| | |
| | Returns None if rotation violates min/max constraints |
| | """ |
| |
|
| | angle = relAngle(normalVector, refAxis.z) |
| |
|
| | |
| | if angle > aMin and angle <= aMax: |
| | return angle |
| | else: |
| | return None |
| |
|
| |
|
| | def generate(normalVector, aMin=-360, aMax=360, cMin=-360, cMax=360, compound=False): |
| | """ |
| | Generates Gcode rotation to align a vector (alignVector) with the positive Z axis. |
| | |
| | It first rotates around the Z axis (C rotation) |
| | to align the vector the positive Y axis. Then around the X axis |
| | (A rotation). |
| | |
| | The min and max arguments dictate the range of motion allowed rotation in |
| | the respective axis. |
| | Default assumes continuous rotation. |
| | |
| | Returns a list of path commands for the shortest valid solution |
| | |
| | If compound is False, axis moves will be broken out to individual commands |
| | |
| | The normalVector input from a typical face (f) can be obtained like this: |
| | |
| | u, v = f.ParameterRange[:2] |
| | n = f.normalAt(u,v) |
| | plm = obj.getGlobalPlacement() |
| | rot = plm.Rotation |
| | normalVector = rot.multVec(n |
| | """ |
| |
|
| | Path.Log.track( |
| | "\n=============\n normalVector: {}\n aMin: {}\n aMax: {}\n cMin: {}\n cMax: {}".format( |
| | normalVector, aMin, aMax, cMin, cMax |
| | ) |
| | ) |
| |
|
| | |
| | cResults = __getCRotation(normalVector, cMin, cMax) |
| | Path.Log.debug("C Rotation results {}".format(cResults)) |
| |
|
| | solutions = [] |
| | for result in cResults: |
| |
|
| | |
| | rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), result) |
| | newvec = rot.multVec(normalVector) |
| |
|
| | |
| | aResult = __getARotation(newvec, aMin, aMax) |
| |
|
| | Path.Log.debug("\n=====\nFor C Rotation: {}\n Calculated A {}\n".format(result, aResult)) |
| |
|
| | if aResult is not None: |
| | solutions.append({"A": aResult, "C": result}) |
| |
|
| | if len(solutions) == 0: |
| | raise ValueError("No valid rotation solution found") |
| |
|
| | |
| | best = solutions[0] |
| | curlen = math.fabs(best["A"]) + math.fabs(best["C"]) |
| | for solution in solutions[1:]: |
| | testlen = math.fabs(solution["A"]) + math.fabs(solution["C"]) |
| | if testlen < curlen: |
| | best = solution |
| | curlen = testlen |
| |
|
| | Path.Log.debug("best result: {}".format(best)) |
| |
|
| | |
| | commands = [] |
| | if compound: |
| | commands.append(Path.Command("G0", best)) |
| | else: |
| | for key, val in best.items(): |
| | print(key, val) |
| | commands.append(Path.Command("G0", {key: val})) |
| |
|
| | return commands |
| |
|