2
# ***************************************************************************
3
# * Copyright (c) 2021 Russell Johnson (russ4262) <russ4262@gmail.com> *
5
# * This file is part of the FreeCAD CAx development system. *
7
# * This program is free software; you can redistribute it and/or modify *
8
# * it under the terms of the GNU Lesser General Public License (LGPL) *
9
# * as published by the Free Software Foundation; either version 2 of *
10
# * the License, or (at your option) any later version. *
11
# * for detail see the LICENCE text file. *
13
# * This program is distributed in the hope that it will be useful, *
14
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16
# * GNU Library General Public License for more details. *
18
# * You should have received a copy of the GNU Library General Public *
19
# * License along with this program; if not, write to the Free Software *
20
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
23
# ***************************************************************************
27
import Path.Op.Adaptive as PathAdaptive
28
import Path.Main.Job as PathJob
29
from Tests.PathTestUtils import PathTestBase
32
import Path.Main.Gui.Job as PathJobGui
33
import Path.Op.Gui.Adaptive as PathAdaptiveGui
36
class TestPathAdaptive(PathTestBase):
37
"""Unit tests for the Adaptive operation."""
42
This method is called upon instantiation of this test class. Add code and objects here
43
that are needed for the duration of the test() methods in this class. In other words,
44
set up the 'global' test environment here; use the `setUp()` method to set up a 'local'
46
This method does not have access to the class `self` reference, but it
47
is able to call static methods within this same class.
53
# Open existing FreeCAD document with test geometry
55
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")
56
cls.doc = FreeCAD.open(FreeCAD.getHomePath() + "Mod/CAM/Tests/test_adaptive.fcstd")
57
FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "")
59
# Create Job object, adding geometry objects from file opened above
60
cls.job = PathJob.Create("Job", [cls.doc.Fusion], None)
61
cls.job.GeometryTolerance.Value = 0.001
63
cls.job.ViewObject.Proxy = PathJobGui.ViewProvider(cls.job.ViewObject)
65
# Instantiate an Adaptive operation for querying available properties
66
cls.prototype = PathAdaptive.Create("Adaptive")
67
cls.prototype.Base = [(cls.doc.Fusion, ["Face3"])]
68
cls.prototype.Label = "Prototype"
69
_addViewProvider(cls.prototype)
74
def tearDownClass(cls):
76
This method is called prior to destruction of this test class. Add code and objects here
77
that cleanup the test environment after the test() methods in this class have been executed.
78
This method does not have access to the class `self` reference. This method
79
is able to call static methods within this same class.
81
# FreeCAD.Console.PrintMessage("TestPathAdaptive.tearDownClass()\n")
83
# Close geometry document without saving
85
FreeCAD.closeDocument(cls.doc.Name)
87
# Setup and tear down methods called before and after each unit test
90
This method is called prior to each `test()` method. Add code and objects here
91
that are needed for multiple `test()` methods.
98
This method is called after each test() method. Add cleanup instructions here.
99
Such cleanup instructions will likely undo those in the setUp() method.
105
"""test00() Empty test."""
109
"""test01() Verify path generated on Face3."""
111
# Instantiate a Adaptive operation and set Base Geometry
112
adaptive = PathAdaptive.Create("Adaptive")
113
adaptive.Base = [(self.doc.Fusion, ["Face3"])] # (base, subs_list)
114
adaptive.Label = "test01+"
115
adaptive.Comment = "test01() Verify path generated on Face3."
117
# Set additional operation properties
118
# setDepthsAndHeights(adaptive)
119
adaptive.FinishingProfile = False
120
adaptive.HelixAngle = 75.0
121
adaptive.HelixDiameterLimit.Value = 1.0
122
adaptive.LiftDistance.Value = 1.0
123
adaptive.StepOver = 75
124
adaptive.UseOutline = False
125
adaptive.setExpression("StepDown", None)
126
adaptive.StepDown.Value = (
127
20.0 # Have to set expression to None before numerical value assignment
130
_addViewProvider(adaptive)
133
# moves = getGcodeMoves(adaptive.Path.Commands, includeRapids=False)
134
# operationMoves = "; ".join(moves)
135
# FreeCAD.Console.PrintMessage("test00_moves: " + operationMoves + "\n")
137
# self.assertTrue(expected_moves_test01 == operationMoves,
138
# "expected_moves_test01: {}\noperationMoves: {}".format(expected_moves_test01, operationMoves))
139
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
142
"""test02() Verify path generated on adjacent, combined Face3 and Face10. The Z heights are different."""
144
# Instantiate a Adaptive operation and set Base Geometry
145
adaptive = PathAdaptive.Create("Adaptive")
146
adaptive.Base = [(self.doc.Fusion, ["Face3", "Face10"])] # (base, subs_list)
147
adaptive.Label = "test02+"
148
adaptive.Comment = "test02() Verify path generated on adjacent, combined Face3 and Face10. The Z heights are different."
150
# Set additional operation properties
151
# setDepthsAndHeights(adaptive)
152
adaptive.FinishingProfile = False
153
adaptive.HelixAngle = 75.0
154
adaptive.HelixDiameterLimit.Value = 1.0
155
adaptive.LiftDistance.Value = 1.0
156
adaptive.StepOver = 75
157
adaptive.UseOutline = False
158
adaptive.setExpression("StepDown", None)
159
adaptive.StepDown.Value = (
160
20.0 # Have to set expression to None before numerical value assignment
163
_addViewProvider(adaptive)
166
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
169
"""test03() Verify path generated on adjacent, combined Face3 and Face10. The Z heights are different."""
171
# Instantiate a Adaptive operation and set Base Geometry
172
adaptive = PathAdaptive.Create("Adaptive")
173
adaptive.Base = [(self.doc.Fusion, ["Face3", "Face10"])] # (base, subs_list)
174
adaptive.Label = "test03+"
175
adaptive.Comment = "test03() Verify path generated on adjacent, combined Face3 and Face10. The Z heights are different."
177
# Set additional operation properties
178
# setDepthsAndHeights(adaptive)
179
adaptive.FinishingProfile = False
180
adaptive.HelixAngle = 75.0
181
adaptive.HelixDiameterLimit.Value = 1.0
182
adaptive.LiftDistance.Value = 1.0
183
adaptive.StepOver = 75
184
adaptive.UseOutline = True
185
adaptive.setExpression("StepDown", None)
186
adaptive.StepDown.Value = (
187
20.0 # Have to set expression to None before numerical value assignment
190
_addViewProvider(adaptive)
193
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
196
"""test04() Verify path generated non-closed edges with differing Z-heights that are closed with Z=1 projection: "Edge9", "Edge2", "Edge8", "Edge15", "Edge30", "Edge31", "Edge29", "Edge19"."""
198
# Instantiate a Adaptive operation and set Base Geometry
199
adaptive = PathAdaptive.Create("Adaptive")
214
] # (base, subs_list)
215
adaptive.Label = "test04+"
216
adaptive.Comment = 'test04() Verify path generated non-closed edges with differing Z-heights that are closed with Z=1 projection: "Edge9", "Edge2", "Edge8", "Edge15", "Edge30", "Edge31", "Edge29", "Edge19".'
218
# Set additional operation properties
219
# setDepthsAndHeights(adaptive)
220
adaptive.FinishingProfile = False
221
adaptive.HelixAngle = 75.0
222
adaptive.HelixDiameterLimit.Value = 1.0
223
adaptive.LiftDistance.Value = 1.0
224
adaptive.StepOver = 75
225
adaptive.UseOutline = False
226
adaptive.setExpression("StepDown", None)
227
adaptive.StepDown.Value = (
228
20.0 # Have to set expression to None before numerical value assignment
231
_addViewProvider(adaptive)
234
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
237
"""test05() Verify path generated closed wire with differing Z-heights: "Edge13", "Edge7", "Edge9", "Edge2", "Edge8", "Edge15", "Edge30", "Edge31", "Edge29", "Edge19"."""
239
# Instantiate a Adaptive operation and set Base Geometry
240
adaptive = PathAdaptive.Create("Adaptive")
257
] # (base, subs_list)
258
adaptive.Label = "test05+"
259
adaptive.Comment = 'test05() Verify path generated closed wire with differing Z-heights: "Edge13", "Edge7", "Edge9", "Edge2", "Edge8", "Edge15", "Edge30", "Edge31", "Edge29", "Edge19".'
261
# Set additional operation properties
262
# setDepthsAndHeights(adaptive)
263
adaptive.FinishingProfile = False
264
adaptive.HelixAngle = 75.0
265
adaptive.HelixDiameterLimit.Value = 1.0
266
adaptive.LiftDistance.Value = 1.0
267
adaptive.StepOver = 75
268
adaptive.UseOutline = False
269
adaptive.setExpression("StepDown", None)
270
adaptive.StepDown.Value = (
271
20.0 # Have to set expression to None before numerical value assignment
274
_addViewProvider(adaptive)
277
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
280
"""test06() Verify path generated with outer and inner edge loops at same Z height: "Edge15", "Edge30", "Edge31", "Edge29", "Edge19", "Edge18", "Edge35", "Edge32", "Edge34", "Edge33"."""
282
# Instantiate a Adaptive operation and set Base Geometry
283
adaptive = PathAdaptive.Create("Adaptive")
300
] # (base, subs_list)
301
adaptive.Label = "test06+"
302
adaptive.Comment = 'test06() Verify path generated with outer and inner edge loops at same Z height: "Edge15", "Edge30", "Edge31", "Edge29", "Edge19", "Edge18", "Edge35", "Edge32", "Edge34", "Edge33".'
304
# Set additional operation properties
305
# setDepthsAndHeights(adaptive)
306
adaptive.FinishingProfile = False
307
adaptive.HelixAngle = 75.0
308
adaptive.HelixDiameterLimit.Value = 1.0
309
adaptive.LiftDistance.Value = 1.0
310
adaptive.StepOver = 75
311
adaptive.UseOutline = False
312
adaptive.setExpression("StepDown", None)
313
adaptive.StepDown.Value = (
314
20.0 # Have to set expression to None before numerical value assignment
317
_addViewProvider(adaptive)
320
# Check command count
321
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
323
# Check if any paths originate inside inner hole of donut. They should not.
326
self.doc.Fusion.Shape.getElement(e) for e in ["Edge35", "Edge32", "Edge33", "Edge34"]
328
square = Part.Wire(edges)
329
sqrBB = square.BoundBox
330
minPoint = FreeCAD.Vector(sqrBB.XMin, sqrBB.YMin, 0.0)
331
maxPoint = FreeCAD.Vector(sqrBB.XMax, sqrBB.YMax, 0.0)
332
for c in adaptive.Path.Commands:
333
if pathOriginatesInBox(c, minPoint, maxPoint):
336
self.assertFalse(isInBox, "Paths originating within the inner hole.")
339
"""test07() Verify path generated on donut-shaped Face10."""
341
# Instantiate a Adaptive operation and set Base Geometry
342
adaptive = PathAdaptive.Create("Adaptive")
343
adaptive.Base = [(self.doc.Fusion, ["Face10"])] # (base, subs_list)
344
adaptive.Label = "test07+"
345
adaptive.Comment = "test07() Verify path generated on donut-shaped Face10."
347
# Set additional operation properties
348
# setDepthsAndHeights(adaptive)
349
adaptive.FinishingProfile = False
350
adaptive.HelixAngle = 75.0
351
adaptive.HelixDiameterLimit.Value = 1.0
352
adaptive.LiftDistance.Value = 1.0
353
adaptive.StepOver = 75
354
adaptive.UseOutline = False
355
adaptive.setExpression("StepDown", None)
356
adaptive.StepDown.Value = (
357
20.0 # Have to set expression to None before numerical value assignment
360
_addViewProvider(adaptive)
363
self.assertTrue(len(adaptive.Path.Commands) > 100, "Command count not greater than 100.")
365
# Check if any paths originate inside inner hole of donut. They should not.
368
self.doc.Fusion.Shape.getElement(e) for e in ["Edge35", "Edge32", "Edge33", "Edge34"]
370
square = Part.Wire(edges)
371
sqrBB = square.BoundBox
372
minPoint = FreeCAD.Vector(sqrBB.XMin, sqrBB.YMin, 0.0)
373
maxPoint = FreeCAD.Vector(sqrBB.XMax, sqrBB.YMax, 0.0)
374
for c in adaptive.Path.Commands:
375
if pathOriginatesInBox(c, minPoint, maxPoint):
378
self.assertFalse(isInBox, "Paths originating within the inner hole.")
380
# Set Adaptive op to only use the outline of the face.
381
adaptive.UseOutline = True
384
# Check if any paths originate inside inner hole of donut. They should not.
387
self.doc.Fusion.Shape.getElement(e) for e in ["Edge35", "Edge32", "Edge33", "Edge34"]
389
square = Part.Wire(edges)
390
sqrBB = square.BoundBox
391
minPoint = FreeCAD.Vector(sqrBB.XMin, sqrBB.YMin, 0.0)
392
maxPoint = FreeCAD.Vector(sqrBB.XMax, sqrBB.YMax, 0.0)
393
for c in adaptive.Path.Commands:
394
if pathOriginatesInBox(c, minPoint, maxPoint):
397
self.assertTrue(isInBox, "No paths originating within the inner hole.")
403
def setDepthsAndHeights(op, strDep=20.0, finDep=0.0):
404
"""setDepthsAndHeights(op, strDep=20.0, finDep=0.0)... Sets default depths and heights for `op` passed to it"""
406
# Set start and final depth in order to eliminate effects of stock (and its default values)
407
op.setExpression("StartDepth", None)
408
op.StartDepth.Value = strDep
409
op.setExpression("FinalDepth", None)
410
op.FinalDepth.Value = finDep
412
# Set step down so as to only produce one layer path
413
op.setExpression("StepDown", None)
414
op.StepDown.Value = 20.0
417
# default values used
420
def getGcodeMoves(cmdList, includeRapids=True, includeLines=True, includeArcs=True):
421
"""getGcodeMoves(cmdList, includeRapids=True, includeLines=True, includeArcs=True)...
422
Accepts command dict and returns point string coordinate.
425
last = FreeCAD.Vector(0.0, 0.0, 0.0)
429
if includeRapids and name in ["G0", "G00"]:
436
gcode += " X" + str(x)
439
gcode += " Y" + str(y)
442
gcode += " Z" + str(z)
446
gcode_list.append(gcode)
447
elif includeLines and name in ["G1", "G01"]:
454
gcode += " X" + str(x)
457
gcode += " Y" + str(y)
460
gcode += " Z" + str(z)
464
gcode_list.append(gcode)
465
elif includeArcs and name in ["G2", "G3", "G02", "G03"]:
475
gcode += " I" + str(i)
478
gcode += " J" + str(j)
481
gcode += " K" + str(k)
485
gcode += " X" + str(x)
488
gcode += " Y" + str(y)
491
gcode += " Z" + str(z)
493
gcode_list.append(gcode)
500
def pathOriginatesInBox(cmd, minPoint, maxPoint):
503
if name in ["G0", "G00", "G1", "G01"]:
504
if p.get("X") and p.get("Y"):
507
if x > minPoint.x and y > minPoint.y and x < maxPoint.x and y < maxPoint.y:
512
def _addViewProvider(adaptiveOp):
514
PathOpGui = PathAdaptiveGui.PathOpGui
515
cmdRes = PathAdaptiveGui.Command.res
516
adaptiveOp.ViewObject.Proxy = PathOpGui.ViewProvider(adaptiveOp.ViewObject, cmdRes)
519
# Example string literal of expected path moves from an operation
520
# Expected moves for unit test01
521
expected_moves_test01 = "G1 X32.5 Y32.5 Z5.0; \
522
G1 X17.5 Y32.5 Z5.0; \
523
G1 X17.5 Y30.0 Z5.0; \
524
G1 X32.5 Y30.0 Z5.0; \
525
G1 X32.5 Y27.5 Z5.0; \
526
G1 X17.5 Y27.5 Z5.0; \
527
G1 X17.5 Y25.0 Z5.0; \
528
G1 X32.5 Y25.0 Z5.0; \
529
G1 X32.5 Y22.5 Z5.0; \
530
G1 X17.5 Y22.5 Z5.0; \
531
G1 X17.5 Y20.0 Z5.0; \
532
G1 X32.5 Y20.0 Z5.0; \
533
G1 X32.5 Y17.5 Z5.0; \