Helpers and tests
Drillable lib and Tests
This commit is contained in:
@@ -10,6 +10,8 @@ set(Path_Scripts
|
||||
Init.py
|
||||
PathCommands.py
|
||||
TestPathApp.py
|
||||
PathMachineState.py
|
||||
PathFeedRate.py
|
||||
)
|
||||
|
||||
if(BUILD_GUI)
|
||||
@@ -144,6 +146,7 @@ SET(PathScripts_SRCS
|
||||
|
||||
SET(Generator_SRCS
|
||||
Generators/drill_generator.py
|
||||
Generators/rotation_generator.py
|
||||
)
|
||||
|
||||
SET(PathScripts_post_SRCS
|
||||
@@ -221,8 +224,10 @@ SET(PathTests_SRCS
|
||||
PathTests/TestPathDressupDogbone.py
|
||||
PathTests/TestPathDressupHoldingTags.py
|
||||
PathTests/TestPathDrillGenerator.py
|
||||
PathTests/TestPathRotationGenerator.py
|
||||
PathTests/TestPathGeom.py
|
||||
PathTests/TestPathHelix.py
|
||||
PathTests/TestPathHelpers.py
|
||||
PathTests/TestPathLog.py
|
||||
PathTests/TestPathOpTools.py
|
||||
PathTests/TestPathPost.py
|
||||
|
||||
@@ -73,16 +73,16 @@ def setFeedRate(commandlist, ToolController):
|
||||
if command.Name not in feedcommands + rapidcommands:
|
||||
continue
|
||||
|
||||
if _isVertical(machine.getPosition(), command):
|
||||
if _isVertical(FreeCAD.Vector(machine.X, machine.Y, machine.Z), command):
|
||||
rate = (
|
||||
ToolController.VertRapid.Value
|
||||
if command in rapidcommands
|
||||
if command.Name in rapidcommands
|
||||
else ToolController.VertFeed.Value
|
||||
)
|
||||
else:
|
||||
rate = (
|
||||
ToolController.HorizRapid.Value
|
||||
if command in rapidcommands
|
||||
if command.Name in rapidcommands
|
||||
else ToolController.HorizFeed.Value
|
||||
)
|
||||
|
||||
|
||||
@@ -58,48 +58,61 @@ class MachineState:
|
||||
"G59.9",
|
||||
]
|
||||
|
||||
X: float = field(default=None)
|
||||
Y: float = field(default=None)
|
||||
Z: float = field(default=None)
|
||||
A: float = field(default=None)
|
||||
B: float = field(default=None)
|
||||
C: float = field(default=None)
|
||||
X: float = field(default=0)
|
||||
Y: float = field(default=0)
|
||||
Z: float = field(default=0)
|
||||
A: float = field(default=0)
|
||||
B: float = field(default=0)
|
||||
C: float = field(default=0)
|
||||
F: float = field(default=None)
|
||||
Coolant: bool = field(default=False)
|
||||
WCS: str = field(default="G54")
|
||||
Spindle: str = field(default="off")
|
||||
S: int = field(default=0)
|
||||
T: str = field(default=None)
|
||||
T: int = field(default=None)
|
||||
|
||||
def addCommand(self, command):
|
||||
"""Processes a command and updates the internal state of the machine. Returns true if the command has alterned the machine state"""
|
||||
oldstate = self.getState()
|
||||
if command.Name == "M6":
|
||||
self.T = command.Parameters["T"]
|
||||
return
|
||||
self.T = int(command.Parameters["T"])
|
||||
return not oldstate == self.getState()
|
||||
|
||||
if command.Name in ["M3", "M4"]:
|
||||
self.S = command.Parameters["S"]
|
||||
self.SpindApple = "CW" if command.Name == "M3" else "CCW"
|
||||
return
|
||||
self.Spindle = "CW" if command.Name == "M3" else "CCW"
|
||||
return not oldstate == self.getState()
|
||||
|
||||
if command.Name in ["M2", "M5"]:
|
||||
self.S = 0
|
||||
self.Spindle = "off"
|
||||
return
|
||||
return not oldstate == self.getState()
|
||||
|
||||
if command.Name in self.WCSLIST:
|
||||
self.WCS = command.Name
|
||||
return
|
||||
return not oldstate == self.getState()
|
||||
|
||||
for p in command.Parameters:
|
||||
self.__setattr__(p, command.Parameters[p])
|
||||
|
||||
def getPosition(self):
|
||||
"""
|
||||
Returns an App.Vector of the current location
|
||||
"""
|
||||
x = 0 if self.X is None else self.X
|
||||
y = 0 if self.Y is None else self.Y
|
||||
z = 0 if self.Z is None else self.Z
|
||||
return not oldstate == self.getState()
|
||||
|
||||
return FreeCAD.Vector(x, y, z)
|
||||
def getState(self):
|
||||
"""
|
||||
Returns a dictionary of the current machine state
|
||||
"""
|
||||
state = {}
|
||||
state['X'] = self.X
|
||||
state['Y'] = self.Y
|
||||
state['Z'] = self.Z
|
||||
state['A'] = self.A
|
||||
state['B'] = self.B
|
||||
state['C'] = self.C
|
||||
state['F'] = self.F
|
||||
state['Coolant'] = self.Coolant
|
||||
state['WCS'] = self.WCS
|
||||
state['Spindle'] = self.Spindle
|
||||
state['S'] = self.S
|
||||
state['T'] = self.T
|
||||
|
||||
return state
|
||||
|
||||
235
src/Mod/Path/PathScripts/drillableLib.py
Normal file
235
src/Mod/Path/PathScripts/drillableLib.py
Normal file
@@ -0,0 +1,235 @@
|
||||
import PathScripts.PathLog as PathLog
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
import numpy
|
||||
import math
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
checks if a candidate cylindrical face is drillable
|
||||
"""
|
||||
|
||||
matchToolDiameter = tooldiameter is not None
|
||||
matchVector = vector is not None
|
||||
|
||||
PathLog.debug(
|
||||
"\n match tool diameter {} \n match vector {}".format(
|
||||
matchToolDiameter, matchVector
|
||||
)
|
||||
)
|
||||
|
||||
def raisedFeature(obj, candidate):
|
||||
# check if the cylindrical 'lids' are inside the base
|
||||
# object. This eliminates extruded circles but allows
|
||||
# actual holes.
|
||||
|
||||
startLidCenter = App.Vector(
|
||||
candidate.BoundBox.Center.x,
|
||||
candidate.BoundBox.Center.y,
|
||||
candidate.BoundBox.ZMax,
|
||||
)
|
||||
|
||||
endLidCenter = App.Vector(
|
||||
candidate.BoundBox.Center.x,
|
||||
candidate.BoundBox.Center.y,
|
||||
candidate.BoundBox.ZMin,
|
||||
)
|
||||
|
||||
return obj.isInside(startLidCenter, 1e-6, False) or obj.isInside(
|
||||
endLidCenter, 1e-6, False
|
||||
)
|
||||
|
||||
def getSeam(candidate):
|
||||
# Finds the vertical seam edge in a cylinder
|
||||
|
||||
for e in candidate.Edges:
|
||||
if isinstance(e.Curve, Part.Line): # found the seam
|
||||
return e
|
||||
|
||||
if not candidate.ShapeType == "Face":
|
||||
raise TypeError("expected a Face")
|
||||
|
||||
if not str(candidate.Surface) == "<Cylinder object>":
|
||||
raise TypeError("expected a cylinder")
|
||||
|
||||
if len(candidate.Edges) != 3:
|
||||
raise TypeError("cylinder does not have 3 edges. Not supported yet")
|
||||
|
||||
if raisedFeature(obj, candidate):
|
||||
PathLog.debug("The cylindrical face is a raised feature")
|
||||
return False
|
||||
|
||||
if not matchToolDiameter and not matchVector:
|
||||
return True
|
||||
|
||||
elif matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius:
|
||||
PathLog.debug("The tool is larger than the target")
|
||||
return False
|
||||
|
||||
elif matchVector and not (compareVecs(getSeam(candidate).Curve.Direction, vector)):
|
||||
PathLog.debug("The feature is not aligned with the given vector")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
checks if a flat face or edge is drillable
|
||||
"""
|
||||
|
||||
matchToolDiameter = tooldiameter is not None
|
||||
matchVector = vector is not None
|
||||
PathLog.debug(
|
||||
"\n match tool diameter {} \n match vector {}".format(
|
||||
matchToolDiameter, matchVector
|
||||
)
|
||||
)
|
||||
|
||||
if candidate.ShapeType == "Face":
|
||||
if not type(candidate.Surface) == Part.Plane:
|
||||
PathLog.debug("Drilling on non-planar faces not supported")
|
||||
return False
|
||||
|
||||
if (
|
||||
len(candidate.Edges) == 1 and type(candidate.Edges[0].Curve) == Part.Circle
|
||||
): # Regular circular face
|
||||
edge = candidate.Edges[0]
|
||||
elif (
|
||||
len(candidate.Edges) == 2
|
||||
and type(candidate.Edges[0].Curve) == Part.Circle
|
||||
and type(candidate.Edges[1].Curve) == Part.Circle
|
||||
): # process a donut
|
||||
e1 = candidate.Edges[0]
|
||||
e2 = candidate.Edges[1]
|
||||
edge = e1 if e1.Curve.Radius < e2.Curve.Radius else e2
|
||||
else:
|
||||
PathLog.debug(
|
||||
"expected a Face with one or two circular edges got a face with {} edges".format(
|
||||
len(candidate.Edges)
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
else: # edge
|
||||
edge = candidate
|
||||
if not (isinstance(edge.Curve, Part.Circle) and edge.isClosed()):
|
||||
PathLog.debug("expected a closed circular edge")
|
||||
return False
|
||||
|
||||
if not hasattr(edge.Curve, "Radius"):
|
||||
PathLog.debug("The Feature edge has no radius - Ellipse.")
|
||||
return False
|
||||
|
||||
if not matchToolDiameter and not matchVector:
|
||||
return True
|
||||
|
||||
elif matchToolDiameter and tooldiameter / 2 > edge.Curve.Radius:
|
||||
PathLog.debug("The tool is larger than the target")
|
||||
return False
|
||||
|
||||
elif matchVector and not (compareVecs(edge.Curve.Axis, vector)):
|
||||
PathLog.debug("The feature is not aligned with the given vector")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def isDrillable(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
Checks candidates to see if they can be drilled at the given vector.
|
||||
Candidates can be either faces - circular or cylindrical or circular edges.
|
||||
The tooldiameter can be optionally passed. if passed, the check will return
|
||||
False for any holes smaller than the tooldiameter.
|
||||
|
||||
vector defaults to (0,0,1) which aligns with the Z axis. By default will return False
|
||||
for any candidate not drillable in this orientation. Pass 'None' to vector to test whether
|
||||
the hole is drillable at any orientation.
|
||||
|
||||
obj=Shape
|
||||
candidate = Face or Edge
|
||||
tooldiameter=float
|
||||
vector=App.Vector or None
|
||||
|
||||
"""
|
||||
PathLog.debug(
|
||||
"obj: {} candidate: {} tooldiameter {} vector {}".format(
|
||||
obj, candidate, tooldiameter, vector
|
||||
)
|
||||
)
|
||||
|
||||
if list == type(obj):
|
||||
for shape in obj:
|
||||
if isDrillable(shape, candidate, tooldiameter, vector):
|
||||
return (True, shape)
|
||||
return (False, None)
|
||||
|
||||
if candidate.ShapeType not in ["Face", "Edge"]:
|
||||
raise TypeError("expected a Face or Edge. Got a {}".format(candidate.ShapeType))
|
||||
|
||||
try:
|
||||
if (
|
||||
candidate.ShapeType == "Face"
|
||||
and str(candidate.Surface) == "<Cylinder object>"
|
||||
):
|
||||
return isDrillableCylinder(obj, candidate, tooldiameter, vector)
|
||||
else:
|
||||
return isDrillableCircle(obj, candidate, tooldiameter, vector)
|
||||
except TypeError as e:
|
||||
PathLog.debug(e)
|
||||
return False
|
||||
# raise TypeError("{}".format(e))
|
||||
|
||||
|
||||
def compareVecs(vec1, vec2):
|
||||
"""
|
||||
compare the two vectors to see if they are aligned for drilling
|
||||
alignment can indicate the vectors are the same or exactly opposite
|
||||
"""
|
||||
|
||||
angle = vec1.getAngle(vec2)
|
||||
angle = 0 if math.isnan(angle) else math.degrees(angle)
|
||||
PathLog.debug("vector angle: {}".format(angle))
|
||||
return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) or numpy.isclose(
|
||||
angle, 180, rtol=1e-05, atol=1e-06
|
||||
)
|
||||
|
||||
|
||||
def getDrillableTargets(obj, ToolDiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
Returns a list of tuples for drillable subelements from the given object
|
||||
[(obj,'Face1'),(obj,'Face3')]
|
||||
|
||||
Finds cylindrical faces that are larger than the tool diameter (if provided) and
|
||||
oriented with the vector. If vector is None, all drillables are returned
|
||||
|
||||
"""
|
||||
|
||||
shp = obj.Shape
|
||||
|
||||
results = []
|
||||
for i in range(1, len(shp.Faces)):
|
||||
fname = 'Face{}'.format(i)
|
||||
PathLog.debug(fname)
|
||||
candidate = obj.getSubObject(fname)
|
||||
|
||||
if not str(candidate.Surface) == '<Cylinder object>':
|
||||
continue
|
||||
|
||||
try:
|
||||
drillable = isDrillable(shp, candidate, tooldiameter=ToolDiameter, vector=vector)
|
||||
PathLog.debug("fname: {} : drillable {}".format(fname, drillable))
|
||||
except Exception as e:
|
||||
PathLog.debug(e)
|
||||
continue
|
||||
|
||||
if drillable:
|
||||
results.append((obj,fname))
|
||||
|
||||
return results
|
||||
BIN
src/Mod/Path/PathTests/Drilling_1.FCStd
Normal file
BIN
src/Mod/Path/PathTests/Drilling_1.FCStd
Normal file
Binary file not shown.
310
src/Mod/Path/PathTests/TestPathDrillable.py
Normal file
310
src/Mod/Path/PathTests/TestPathDrillable.py
Normal file
@@ -0,0 +1,310 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2021 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 Path
|
||||
import FreeCAD as App
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
import PathScripts.drillableLib as drillableLib
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestPathDrillable(PathTestUtils.PathTestBase):
|
||||
def setUp(self):
|
||||
self.doc = App.open(App.getHomePath() + "/Mod/Path/PathTests/Drilling_1.FCStd")
|
||||
self.obj = self.doc.getObject("Pocket010")
|
||||
|
||||
def tearDown(self):
|
||||
App.closeDocument(self.doc.Name)
|
||||
|
||||
def test00(self):
|
||||
"""Test CompareVecs"""
|
||||
|
||||
# Vec and origin
|
||||
v1 = App.Vector(0, 0, 10)
|
||||
v2 = App.Vector(0, 0, 0)
|
||||
self.assertTrue(drillableLib.compareVecs(v1, v2))
|
||||
|
||||
# two valid vectors
|
||||
v1 = App.Vector(0, 10, 0)
|
||||
v2 = App.Vector(0, 20, 0)
|
||||
self.assertTrue(drillableLib.compareVecs(v1, v2))
|
||||
|
||||
# two valid vectors not aligned
|
||||
v1 = App.Vector(0, 10, 0)
|
||||
v2 = App.Vector(10, 0, 0)
|
||||
self.assertFalse(drillableLib.compareVecs(v1, v2))
|
||||
|
||||
def test10(self):
|
||||
"""Test isDrillable"""
|
||||
|
||||
# Invalid types
|
||||
candidate = self.obj.getSubObject("Vertex1")
|
||||
self.assertRaises(
|
||||
TypeError, lambda: drillableLib.isDrillable(self.obj.Shape, candidate)
|
||||
)
|
||||
|
||||
# # partial cylinder
|
||||
# candidate = self.obj.getSubObject("Face10")
|
||||
# self.assertRaises(
|
||||
# TypeError, lambda: drillableLib.isDrillable(self.obj.Shape, candidate)
|
||||
# )
|
||||
|
||||
# Test cylinder faces
|
||||
|
||||
# thru-hole
|
||||
candidate = self.obj.getSubObject("Face25")
|
||||
|
||||
# Typical drilling
|
||||
self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Drilling with smaller bit
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=20)
|
||||
)
|
||||
|
||||
# Drilling with bit too large
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30)
|
||||
)
|
||||
|
||||
# off-axis hole
|
||||
candidate = self.obj.getSubObject("Face42")
|
||||
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Passing explicit vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, vector=App.Vector(0, 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# Drilling with smaller bit
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, tooldiameter=10, vector=App.Vector(0, 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# Drilling with bit too large
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, tooldiameter=30, vector=App.Vector(0, 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# ellipse hole
|
||||
candidate = self.obj.getSubObject("Face20")
|
||||
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# raised cylinder
|
||||
candidate = self.obj.getSubObject("Face30")
|
||||
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
|
||||
# cylinder on slope
|
||||
candidate = self.obj.getSubObject("Face26")
|
||||
# Typical drilling
|
||||
self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Circular Faces
|
||||
candidate = self.obj.getSubObject("Face51")
|
||||
|
||||
# Typical drilling
|
||||
self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Passing explicit vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, vector=App.Vector(0, 0, 1)
|
||||
)
|
||||
)
|
||||
|
||||
# Drilling with smaller bit
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=10)
|
||||
)
|
||||
|
||||
# Drilling with bit too large
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30)
|
||||
)
|
||||
|
||||
# off-axis circular face hole
|
||||
candidate = self.obj.getSubObject("Face54")
|
||||
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Passing explicit vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, vector=App.Vector(0, 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# raised face
|
||||
candidate = self.obj.getSubObject("Face45")
|
||||
# Typical drilling
|
||||
self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# interrupted Face
|
||||
candidate = self.obj.getSubObject("Face46")
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# donut face
|
||||
candidate = self.obj.getSubObject("Face44")
|
||||
# Typical drilling
|
||||
self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Test edges
|
||||
# circular edge
|
||||
candidate = self.obj.getSubObject("Edge53")
|
||||
|
||||
# Typical drilling
|
||||
self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Passing explicit vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, vector=App.Vector(0, 0, 1)
|
||||
)
|
||||
)
|
||||
|
||||
# Drilling with smaller bit
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=10)
|
||||
)
|
||||
|
||||
# Drilling with bit too large
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30)
|
||||
)
|
||||
|
||||
# off-axis circular edge
|
||||
candidate = self.obj.getSubObject("Edge72")
|
||||
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# Passing explicit vector
|
||||
self.assertTrue(
|
||||
drillableLib.isDrillable(
|
||||
self.obj.Shape, candidate, vector=App.Vector(0, 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# incomplete circular edge
|
||||
candidate = self.obj.getSubObject("Edge108")
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
# elliptical edge
|
||||
candidate = self.obj.getSubObject("Edge54")
|
||||
# Typical drilling
|
||||
self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate))
|
||||
|
||||
# Passing None as vector
|
||||
self.assertFalse(
|
||||
drillableLib.isDrillable(self.obj.Shape, candidate, vector=None)
|
||||
)
|
||||
|
||||
|
||||
def test20(self):
|
||||
"""Test getDrillableTargets"""
|
||||
results = drillableLib.getDrillableTargets(self.obj)
|
||||
self.assertEqual(len(results), 15)
|
||||
|
||||
results = drillableLib.getDrillableTargets(self.obj, vector=None)
|
||||
self.assertEqual(len(results), 18)
|
||||
|
||||
results = drillableLib.getDrillableTargets(self.obj, ToolDiameter= 20, vector=None)
|
||||
self.assertEqual(len(results), 5)
|
||||
103
src/Mod/Path/PathTests/TestPathHelpers.py
Normal file
103
src/Mod/Path/PathTests/TestPathHelpers.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2021 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 PathFeedRate
|
||||
import PathMachineState
|
||||
import Path
|
||||
import PathScripts.PathToolController as PathToolController
|
||||
|
||||
from PathTests.PathTestUtils import PathTestBase
|
||||
|
||||
|
||||
class TestPathHelpers(PathTestBase):
|
||||
def setUp(self):
|
||||
self.doc = FreeCAD.newDocument("TestPathUtils")
|
||||
|
||||
c1 = Path.Command("G0 Z10")
|
||||
c2 = Path.Command("G0 X20 Y10")
|
||||
c3 = Path.Command("G1 X20 Y10 Z5")
|
||||
c4 = Path.Command("G1 X20 Y20")
|
||||
|
||||
self.commandlist = [c1, c2, c3, c4]
|
||||
|
||||
def tearDown(self):
|
||||
FreeCAD.closeDocument("TestPathUtils")
|
||||
|
||||
def test00(self):
|
||||
"""Test that FeedRate Helper populates horiz and vert feed rate based on TC"""
|
||||
t = Path.Tool("test", "5.0")
|
||||
tc = PathToolController.Create("TC0", t)
|
||||
tc.VertRapid = 5
|
||||
tc.HorizRapid = 10
|
||||
tc.VertFeed = 15
|
||||
tc.HorizFeed = 20
|
||||
|
||||
resultlist = PathFeedRate.setFeedRate(self.commandlist, tc)
|
||||
|
||||
self.assertTrue(resultlist[0].Parameters["F"] == 5)
|
||||
self.assertTrue(resultlist[1].Parameters["F"] == 10)
|
||||
self.assertTrue(resultlist[2].Parameters["F"] == 15)
|
||||
self.assertTrue(resultlist[3].Parameters["F"] == 20)
|
||||
|
||||
def test01(self):
|
||||
"""Test that Machine State initializes and stores position correctly"""
|
||||
|
||||
machine = PathMachineState.MachineState()
|
||||
state = machine.getState()
|
||||
self.assertTrue(state['X'] == 0 )
|
||||
self.assertTrue(state['Y'] == 0 )
|
||||
self.assertTrue(state['Z'] == 0 )
|
||||
self.assertTrue(machine.WCS == "G54")
|
||||
|
||||
for c in self.commandlist:
|
||||
result = machine.addCommand(c)
|
||||
|
||||
state = machine.getState()
|
||||
self.assertTrue(state['X'] == 20 )
|
||||
self.assertTrue(state['Y'] == 20 )
|
||||
self.assertTrue(state['Z'] == 5 )
|
||||
|
||||
machine.addCommand(Path.Command("M3 S200"))
|
||||
self.assertTrue(machine.S == 200)
|
||||
self.assertTrue(machine.Spindle == "CW")
|
||||
|
||||
machine.addCommand(Path.Command("M4 S200"))
|
||||
self.assertTrue(machine.Spindle == "CCW")
|
||||
|
||||
machine.addCommand(Path.Command("M2"))
|
||||
self.assertTrue(machine.Spindle == "off")
|
||||
self.assertTrue(machine.S == 0)
|
||||
|
||||
machine.addCommand(Path.Command("G57"))
|
||||
self.assertTrue(machine.WCS == "G57")
|
||||
|
||||
machine.addCommand(Path.Command("M6 T5"))
|
||||
self.assertTrue(machine.T == 5)
|
||||
|
||||
# Test that non-change commands return false
|
||||
result = machine.addCommand(Path.Command("G0 X20"))
|
||||
self.assertFalse(result)
|
||||
|
||||
result = machine.addCommand(Path.Command("G0 X30"))
|
||||
self.assertTrue(result)
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
import TestApp
|
||||
|
||||
# from PathTests.TestPathHelix import TestPathHelix
|
||||
# from PathTests.TestPathPost import PathPostTestCases
|
||||
from PathTests.TestPathAdaptive import TestPathAdaptive
|
||||
from PathTests.TestPathCore import TestPathCore
|
||||
from PathTests.TestPathDeburr import TestPathDeburr
|
||||
@@ -30,12 +32,12 @@ from PathTests.TestPathDressupDogbone import TestDressupDogbone
|
||||
from PathTests.TestPathDressupHoldingTags import TestHoldingTags
|
||||
from PathTests.TestPathDrillGenerator import TestPathDrillGenerator
|
||||
from PathTests.TestPathGeom import TestPathGeom
|
||||
# from PathTests.TestPathHelix import TestPathHelix
|
||||
from PathTests.TestPathHelpers import TestPathHelpers
|
||||
from PathTests.TestPathLog import TestPathLog
|
||||
from PathTests.TestPathOpTools import TestPathOpTools
|
||||
# from PathTests.TestPathPost import PathPostTestCases
|
||||
from PathTests.TestPathPreferences import TestPathPreferences
|
||||
from PathTests.TestPathPropertyBag import TestPathPropertyBag
|
||||
from PathTests.TestPathRotationGenerator import TestPathRotationGenerator
|
||||
from PathTests.TestPathSetupSheet import TestPathSetupSheet
|
||||
from PathTests.TestPathStock import TestPathStock
|
||||
from PathTests.TestPathThreadMilling import TestPathThreadMilling
|
||||
@@ -57,10 +59,12 @@ False if TestPathCore.__name__ else True
|
||||
False if TestPathDeburr.__name__ else True
|
||||
False if TestPathGeom.__name__ else True
|
||||
# False if TestPathHelix.__name__ else True
|
||||
False if TestPathHelpers.__name__ else True
|
||||
False if TestPathLog.__name__ else True
|
||||
False if TestPathOpTools.__name__ else True
|
||||
False if TestPathPreferences.__name__ else True
|
||||
False if TestPathPropertyBag.__name__ else True
|
||||
False if TestPathRotationGenerator.__name__ else True
|
||||
False if TestPathSetupSheet.__name__ else True
|
||||
False if TestPathStock.__name__ else True
|
||||
False if TestPathThreadMilling.__name__ else True
|
||||
|
||||
Reference in New Issue
Block a user