Split out thread milling generator into its own file
This commit is contained in:
@@ -146,8 +146,9 @@ SET(PathScripts_SRCS
|
||||
|
||||
SET(Generator_SRCS
|
||||
Generators/drill_generator.py
|
||||
Generators/rotation_generator.py
|
||||
Generators/helix_generator.py
|
||||
Generators/rotation_generator.py
|
||||
Generators/threadmilling_generator.py
|
||||
Generators/toolchange_generator.py
|
||||
)
|
||||
|
||||
@@ -242,6 +243,7 @@ SET(PathTests_SRCS
|
||||
PathTests/TestPathStock.py
|
||||
PathTests/TestPathToolChangeGenerator.py
|
||||
PathTests/TestPathThreadMilling.py
|
||||
PathTests/TestPathThreadMillingGenerator.py
|
||||
PathTests/TestPathTool.py
|
||||
PathTests/TestPathToolBit.py
|
||||
PathTests/TestPathToolController.py
|
||||
|
||||
208
src/Mod/Path/Generators/threadmilling_generator.py
Normal file
208
src/Mod/Path/Generators/threadmilling_generator.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import math
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__title__ = "Path Thread Milling generator"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Path thread milling operation."
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
def _comment(path, msg):
|
||||
"""Nice for debugging to insert markers into generated g-code"""
|
||||
if False:
|
||||
path.append(Path.Command("(------- {} -------)".format(msg)))
|
||||
|
||||
|
||||
class _Thread(object):
|
||||
"""Helper class for dealing with different thread types"""
|
||||
|
||||
def __init__(self, cmd, zStart, zFinal, pitch, internal):
|
||||
self.cmd = cmd
|
||||
if zStart < zFinal:
|
||||
self.pitch = pitch
|
||||
else:
|
||||
self.pitch = -pitch
|
||||
self.hPitch = self.pitch / 2
|
||||
self.zStart = zStart
|
||||
self.zFinal = zFinal
|
||||
self.internal = internal
|
||||
|
||||
def overshoots(self, z):
|
||||
"""overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds"""
|
||||
if self.pitch < 0:
|
||||
return z + self.hPitch < self.zFinal
|
||||
return z + self.hPitch > self.zFinal
|
||||
|
||||
def adjustX(self, x, dx):
|
||||
"""adjustX(x, dx) ... move x by dx, the direction depends on the thread settings"""
|
||||
if self.isG3() == (self.pitch > 0):
|
||||
return x + dx
|
||||
return x - dx
|
||||
|
||||
def adjustY(self, y, dy):
|
||||
"""adjustY(y, dy) ... move y by dy, the direction depends on the thread settings"""
|
||||
if self.isG3():
|
||||
return y - dy
|
||||
return y - dy
|
||||
|
||||
def isG3(self):
|
||||
"""isG3() ... returns True if this is a G3 command"""
|
||||
return self.cmd in ["G3", "G03", "g3", "g03"]
|
||||
|
||||
def isUp(self):
|
||||
"""isUp() ... returns True if the thread goes from the bottom up"""
|
||||
return self.pitch > 0
|
||||
|
||||
def g4Opposite(self):
|
||||
return "G2" if self.isG3() else "G3"
|
||||
|
||||
def g4LeadInOut(self):
|
||||
if self.internal:
|
||||
return self.cmd
|
||||
return self.g4Opposite()
|
||||
|
||||
def g4Start2Elevator(self):
|
||||
return self.g4Opposite()
|
||||
|
||||
|
||||
def generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, start):
|
||||
"""generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, start) ... returns the g-code to mill the given internal thread"""
|
||||
thread = _Thread(cmd, zStart, zFinal, pitch, radius > elevator)
|
||||
|
||||
yMin = center.y - radius
|
||||
yMax = center.y + radius
|
||||
|
||||
path = []
|
||||
# at this point the tool is at a safe heiht (depending on the previous thread), so we can move
|
||||
# into position first, and then drop to the start height. If there is any material in the way this
|
||||
# op hasn't been setup properly.
|
||||
if leadInOut:
|
||||
_comment(path, "lead-in")
|
||||
if start is None:
|
||||
path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator}))
|
||||
path.append(Path.Command("G0", {"Z": thread.zStart}))
|
||||
else:
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.g4Start2Elevator(),
|
||||
{
|
||||
"X": center.x,
|
||||
"Y": center.y + elevator,
|
||||
"Z": thread.zStart,
|
||||
"I": (center.x - start.x) / 2,
|
||||
"J": (center.y + elevator - start.y) / 2,
|
||||
"K": (thread.zStart - start.z) / 2,
|
||||
},
|
||||
)
|
||||
)
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.g4LeadInOut(),
|
||||
{"Y": yMax, "J": (yMax - (center.y + elevator)) / 2},
|
||||
)
|
||||
)
|
||||
_comment(path, "lead-in")
|
||||
else:
|
||||
if start is None:
|
||||
path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator}))
|
||||
path.append(Path.Command("G0", {"Z": thread.zStart}))
|
||||
else:
|
||||
path.append(
|
||||
Path.Command(
|
||||
"G0", {"X": center.x, "Y": center.y + elevator, "Z": thread.zStart}
|
||||
)
|
||||
)
|
||||
path.append(Path.Command("G1", {"Y": yMax}))
|
||||
|
||||
z = thread.zStart
|
||||
r = -radius
|
||||
i = 0
|
||||
while not PathGeom.isRoughly(z, thread.zFinal):
|
||||
if thread.overshoots(z):
|
||||
break
|
||||
if 0 == (i & 0x01):
|
||||
y = yMin
|
||||
else:
|
||||
y = yMax
|
||||
path.append(Path.Command(thread.cmd, {"Y": y, "Z": z + thread.hPitch, "J": r}))
|
||||
r = -r
|
||||
i = i + 1
|
||||
z = z + thread.hPitch
|
||||
|
||||
if PathGeom.isRoughly(z, thread.zFinal):
|
||||
x = center.x
|
||||
y = yMin if (i & 0x01) else yMax
|
||||
else:
|
||||
n = math.fabs(thread.zFinal - thread.zStart) / thread.hPitch
|
||||
k = n - int(n)
|
||||
dy = math.cos(k * math.pi)
|
||||
dx = math.sin(k * math.pi)
|
||||
y = thread.adjustY(center.y, r * dy)
|
||||
x = thread.adjustX(center.x, r * dx)
|
||||
_comment(path, "finish-thread")
|
||||
path.append(
|
||||
Path.Command(thread.cmd, {"X": x, "Y": y, "Z": thread.zFinal, "J": r})
|
||||
)
|
||||
_comment(path, "finish-thread")
|
||||
|
||||
a = math.atan2(y - center.y, x - center.x)
|
||||
dx = math.cos(a) * (radius - elevator)
|
||||
dy = math.sin(a) * (radius - elevator)
|
||||
PathLog.debug("")
|
||||
PathLog.debug("a={}: dx={:.2f}, dy={:.2f}".format(a / math.pi * 180, dx, dy))
|
||||
|
||||
elevatorX = x - dx
|
||||
elevatorY = y - dy
|
||||
PathLog.debug(
|
||||
"({:.2f}, {:.2f}) -> ({:.2f}, {:.2f})".format(x, y, elevatorX, elevatorY)
|
||||
)
|
||||
|
||||
if leadInOut:
|
||||
_comment(path, "lead-out")
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.g4LeadInOut(),
|
||||
{"X": elevatorX, "Y": elevatorY, "I": -dx / 2, "J": -dy / 2},
|
||||
)
|
||||
)
|
||||
_comment(path, "lead-out")
|
||||
else:
|
||||
path.append(Path.Command("G1", {"X": elevatorX, "Y": elevatorY}))
|
||||
|
||||
return (path, FreeCAD.Vector(elevatorX, elevatorY, thread.zFinal))
|
||||
@@ -28,6 +28,7 @@ import PathScripts.PathCircularHoleBase as PathCircularHoleBase
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import Generators.threadmilling_generator as threadmilling
|
||||
import math
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
@@ -117,172 +118,6 @@ def elevatorRadius(obj, center, internal, tool):
|
||||
return dy
|
||||
|
||||
|
||||
def comment(path, msg):
|
||||
if False:
|
||||
path.append(Path.Command("(------- {} -------)".format(msg)))
|
||||
|
||||
|
||||
class _Thread(object):
|
||||
"""Helper class for dealing with different thread types"""
|
||||
|
||||
def __init__(self, cmd, zStart, zFinal, pitch, internal):
|
||||
self.cmd = cmd
|
||||
if zStart < zFinal:
|
||||
self.pitch = pitch
|
||||
else:
|
||||
self.pitch = -pitch
|
||||
self.hPitch = self.pitch / 2
|
||||
self.zStart = zStart
|
||||
self.zFinal = zFinal
|
||||
self.internal = internal
|
||||
|
||||
def overshoots(self, z):
|
||||
"""overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds"""
|
||||
if self.pitch < 0:
|
||||
return z + self.hPitch < self.zFinal
|
||||
return z + self.hPitch > self.zFinal
|
||||
|
||||
def adjustX(self, x, dx):
|
||||
"""adjustX(x, dx) ... move x by dx, the direction depends on the thread settings"""
|
||||
if self.isG3() == (self.pitch > 0):
|
||||
return x + dx
|
||||
return x - dx
|
||||
|
||||
def adjustY(self, y, dy):
|
||||
"""adjustY(y, dy) ... move y by dy, the direction depends on the thread settings"""
|
||||
if self.isG3():
|
||||
return y - dy
|
||||
return y - dy
|
||||
|
||||
def isG3(self):
|
||||
"""isG3() ... returns True if this is a G3 command"""
|
||||
return self.cmd in ["G3", "G03", "g3", "g03"]
|
||||
|
||||
def isUp(self):
|
||||
"""isUp() ... returns True if the thread goes from the bottom up"""
|
||||
return self.pitch > 0
|
||||
|
||||
def g4Opposite(self):
|
||||
return "G2" if self.isG3() else "G3"
|
||||
|
||||
def g4LeadInOut(self):
|
||||
if self.internal:
|
||||
return self.cmd
|
||||
return self.g4Opposite()
|
||||
|
||||
def g4Start2Elevator(self):
|
||||
return self.g4Opposite()
|
||||
|
||||
|
||||
def threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, start
|
||||
):
|
||||
"""threadCommands(center, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread"""
|
||||
thread = _Thread(cmd, zStart, zFinal, pitch, radius > elevator)
|
||||
|
||||
yMin = center.y - radius
|
||||
yMax = center.y + radius
|
||||
|
||||
path = []
|
||||
# at this point the tool is at a safe heiht (depending on the previous thread), so we can move
|
||||
# into position first, and then drop to the start height. If there is any material in the way this
|
||||
# op hasn't been setup properly.
|
||||
if leadInOut:
|
||||
comment(path, "lead-in")
|
||||
if start is None:
|
||||
path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator}))
|
||||
path.append(Path.Command("G0", {"Z": thread.zStart}))
|
||||
else:
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.g4Start2Elevator(),
|
||||
{
|
||||
"X": center.x,
|
||||
"Y": center.y + elevator,
|
||||
"Z": thread.zStart,
|
||||
"I": (center.x - start.x) / 2,
|
||||
"J": (center.y + elevator - start.y) / 2,
|
||||
"K": (thread.zStart - start.z) / 2,
|
||||
},
|
||||
)
|
||||
)
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.g4LeadInOut(),
|
||||
{"Y": yMax, "J": (yMax - (center.y + elevator)) / 2},
|
||||
)
|
||||
)
|
||||
comment(path, "lead-in")
|
||||
else:
|
||||
if start is None:
|
||||
path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator}))
|
||||
path.append(Path.Command("G0", {"Z": thread.zStart}))
|
||||
else:
|
||||
path.append(
|
||||
Path.Command(
|
||||
"G0", {"X": center.x, "Y": center.y + elevator, "Z": thread.zStart}
|
||||
)
|
||||
)
|
||||
path.append(Path.Command("G1", {"Y": yMax}))
|
||||
|
||||
z = thread.zStart
|
||||
r = -radius
|
||||
i = 0
|
||||
while not PathGeom.isRoughly(z, thread.zFinal):
|
||||
if thread.overshoots(z):
|
||||
break
|
||||
if 0 == (i & 0x01):
|
||||
y = yMin
|
||||
else:
|
||||
y = yMax
|
||||
path.append(Path.Command(thread.cmd, {"Y": y, "Z": z + thread.hPitch, "J": r}))
|
||||
r = -r
|
||||
i = i + 1
|
||||
z = z + thread.hPitch
|
||||
|
||||
if PathGeom.isRoughly(z, thread.zFinal):
|
||||
x = center.x
|
||||
y = yMin if (i & 0x01) else yMax
|
||||
else:
|
||||
n = math.fabs(thread.zFinal - thread.zStart) / thread.hPitch
|
||||
k = n - int(n)
|
||||
dy = math.cos(k * math.pi)
|
||||
dx = math.sin(k * math.pi)
|
||||
y = thread.adjustY(center.y, r * dy)
|
||||
x = thread.adjustX(center.x, r * dx)
|
||||
comment(path, "finish-thread")
|
||||
path.append(
|
||||
Path.Command(thread.cmd, {"X": x, "Y": y, "Z": thread.zFinal, "J": r})
|
||||
)
|
||||
comment(path, "finish-thread")
|
||||
|
||||
a = math.atan2(y - center.y, x - center.x)
|
||||
dx = math.cos(a) * (radius - elevator)
|
||||
dy = math.sin(a) * (radius - elevator)
|
||||
PathLog.debug("")
|
||||
PathLog.debug("a={}: dx={:.2f}, dy={:.2f}".format(a / math.pi * 180, dx, dy))
|
||||
|
||||
elevatorX = x - dx
|
||||
elevatorY = y - dy
|
||||
PathLog.debug(
|
||||
"({:.2f}, {:.2f}) -> ({:.2f}, {:.2f})".format(x, y, elevatorX, elevatorY)
|
||||
)
|
||||
|
||||
if leadInOut:
|
||||
comment(path, "lead-out")
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.g4LeadInOut(),
|
||||
{"X": elevatorX, "Y": elevatorY, "I": -dx / 2, "J": -dy / 2},
|
||||
)
|
||||
)
|
||||
comment(path, "lead-out")
|
||||
else:
|
||||
path.append(Path.Command("G1", {"X": elevatorX, "Y": elevatorY}))
|
||||
|
||||
return (path, FreeCAD.Vector(elevatorX, elevatorY, thread.zFinal))
|
||||
|
||||
|
||||
class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
"""Proxy object for thread milling operation."""
|
||||
|
||||
@@ -594,7 +429,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
# shortcuts when moving to the elevator position
|
||||
self.commandlist.append(move2clearance)
|
||||
start = None
|
||||
commands, start = threadCommands(
|
||||
commands, start = threadmilling.generate(
|
||||
loc,
|
||||
gcode,
|
||||
zStart,
|
||||
|
||||
@@ -92,213 +92,3 @@ class TestPathThreadMilling(PathTestBase):
|
||||
PathThreadMilling.threadPasses(5, radii, False, 10, 9, 0, 0),
|
||||
[9.552786, 9.367544, 9.225403, 9.105573, 9],
|
||||
)
|
||||
|
||||
def test40(self):
|
||||
"""Verify thread commands for a single thread"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G1 X0.000000 Y2.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, 2, zFinal))
|
||||
|
||||
def test41(self):
|
||||
"""Verify thread commands for a thwo threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 2
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
"G2 J3.000000 Y3.000000 Z2.000000",
|
||||
"G1 X0.000000 Y2.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, 2, zFinal))
|
||||
|
||||
def test42(self):
|
||||
"""Verify thread commands for a one and a half threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1.5
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
"G1 X0.000000 Y-2.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, -2, zFinal))
|
||||
|
||||
def test43(self):
|
||||
"""Verify thread commands for a one and 3 quarter threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1.75
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G2 J3.000000 X-3.000000 Y0.000000 Z1.750000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G1 X-2.000000 Y0.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(-2, 0, zFinal))
|
||||
|
||||
def test44(self):
|
||||
"""Verify thread commands for a one and 3 quarter threads - CCW"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G3"
|
||||
zStart = 0
|
||||
zFinal = 1.75
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G3 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G3 J3.000000 Y3.000000 Z1.000000",
|
||||
"G3 J-3.000000 Y-3.000000 Z1.500000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G3 J3.000000 X3.000000 Y0.000000 Z1.750000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G1 X2.000000 Y0.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(2, 0, zFinal))
|
||||
|
||||
def test50(self):
|
||||
"""Verify lead in/out commands for a single thread"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = True
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J0.500000 Y3.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
#'(------- lead-out -------)',
|
||||
"G2 I0.000000 J-0.500000 X0.000000 Y2.000000",
|
||||
#'(------- lead-out -------)',
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, 2, zFinal))
|
||||
|
||||
def test51(self):
|
||||
"""Verify lead in/out commands for one and a half threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1.5
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = True
|
||||
elevator = 2
|
||||
|
||||
path, start = PathThreadMilling.threadCommands(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J0.500000 Y3.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
#'(------- lead-out -------)',
|
||||
"G2 I0.000000 J0.500000 X0.000000 Y-2.000000",
|
||||
#'(------- lead-out -------)',
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, -2, zFinal))
|
||||
|
||||
|
||||
246
src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py
Normal file
246
src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py
Normal file
@@ -0,0 +1,246 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2019 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import Generators.threadmilling_generator as threadmilling
|
||||
import math
|
||||
|
||||
from PathTests.PathTestUtils import PathTestBase
|
||||
|
||||
|
||||
def radii(internal, major, minor, toolDia, toolCrest):
|
||||
"""test radii function for simple testing"""
|
||||
return (minor, major)
|
||||
|
||||
|
||||
class TestPathThreadMillingGenerator(PathTestBase):
|
||||
"""Test thread milling generator."""
|
||||
|
||||
def test00(self):
|
||||
"""Verify thread commands for a single thread"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G1 X0.000000 Y2.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, 2, zFinal))
|
||||
|
||||
def test01(self):
|
||||
"""Verify thread commands for a thwo threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 2
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
"G2 J3.000000 Y3.000000 Z2.000000",
|
||||
"G1 X0.000000 Y2.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, 2, zFinal))
|
||||
|
||||
def test02(self):
|
||||
"""Verify thread commands for a one and a half threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1.5
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
"G1 X0.000000 Y-2.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, -2, zFinal))
|
||||
|
||||
def test03(self):
|
||||
"""Verify thread commands for a one and 3 quarter threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1.75
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G2 J3.000000 X-3.000000 Y0.000000 Z1.750000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G1 X-2.000000 Y0.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(-2, 0, zFinal))
|
||||
|
||||
def test04(self):
|
||||
"""Verify thread commands for a one and 3 quarter threads - CCW"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G3"
|
||||
zStart = 0
|
||||
zFinal = 1.75
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = False
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
"G1 Y3.000000",
|
||||
"G3 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G3 J3.000000 Y3.000000 Z1.000000",
|
||||
"G3 J-3.000000 Y-3.000000 Z1.500000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G3 J3.000000 X3.000000 Y0.000000 Z1.750000",
|
||||
#'(------- finish-thread -------)',
|
||||
"G1 X2.000000 Y0.000000",
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(2, 0, zFinal))
|
||||
|
||||
def test10(self):
|
||||
"""Verify lead in/out commands for a single thread"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = True
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J0.500000 Y3.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
#'(------- lead-out -------)',
|
||||
"G2 I0.000000 J-0.500000 X0.000000 Y2.000000",
|
||||
#'(------- lead-out -------)',
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, 2, zFinal))
|
||||
|
||||
def test11(self):
|
||||
"""Verify lead in/out commands for one and a half threads"""
|
||||
|
||||
center = FreeCAD.Vector()
|
||||
cmd = "G2"
|
||||
zStart = 0
|
||||
zFinal = 1.5
|
||||
pitch = 1
|
||||
radius = 3
|
||||
leadInOut = True
|
||||
elevator = 2
|
||||
|
||||
path, start = threadmilling.generate(
|
||||
center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None
|
||||
)
|
||||
|
||||
gcode = [
|
||||
"G0 X0.000000 Y2.000000",
|
||||
"G0 Z0.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J0.500000 Y3.000000",
|
||||
#'(------- lead-in -------)',
|
||||
"G2 J-3.000000 Y-3.000000 Z0.500000",
|
||||
"G2 J3.000000 Y3.000000 Z1.000000",
|
||||
"G2 J-3.000000 Y-3.000000 Z1.500000",
|
||||
#'(------- lead-out -------)',
|
||||
"G2 I0.000000 J0.500000 X0.000000 Y-2.000000",
|
||||
#'(------- lead-out -------)',
|
||||
]
|
||||
self.assertEqual([p.toGCode() for p in path], gcode)
|
||||
self.assertCoincide(start, FreeCAD.Vector(0, -2, zFinal))
|
||||
@@ -46,6 +46,7 @@ from PathTests.TestPathRotationGenerator import TestPathRotationGenerator
|
||||
from PathTests.TestPathSetupSheet import TestPathSetupSheet
|
||||
from PathTests.TestPathStock import TestPathStock
|
||||
from PathTests.TestPathThreadMilling import TestPathThreadMilling
|
||||
from PathTests.TestPathThreadMillingGenerator import TestPathThreadMillingGenerator
|
||||
from PathTests.TestPathTool import TestPathTool
|
||||
from PathTests.TestPathToolBit import TestPathToolBit
|
||||
from PathTests.TestPathToolChangeGenerator import TestPathToolChangeGenerator
|
||||
@@ -77,6 +78,7 @@ False if TestPathRotationGenerator.__name__ else True
|
||||
False if TestPathSetupSheet.__name__ else True
|
||||
False if TestPathStock.__name__ else True
|
||||
False if TestPathThreadMilling.__name__ else True
|
||||
False if TestPathThreadMillingGenerator.__name__ else True
|
||||
False if TestPathTool.__name__ else True
|
||||
False if TestPathToolBit.__name__ else True
|
||||
False if TestPathToolChangeGenerator.__name__ else True
|
||||
|
||||
Reference in New Issue
Block a user