PATH: Feature/dogbone ii (#7660)

* Start of new dogbone dressup

* Added Instruction and tangents support for G2/3 moves

* Added Maneuver class to represent a set of moves and process them coherently

* Created kinks and verify their creation.

* Added dogbone detection and verification

* Simplified gcode strings

* Added horizontal t-bones generation

* Added support for vertical t-bone

* Consolidated t-bone creation

* Added support for pathLength

* Added support for tbone on short edge

* Added support for long edges

* Added support for dogbones

* Fixed dogbone for non-horizontal lead-in

* Horizontal bone adaptive length tests

* Fixed dogbone angle and adaptive length

* Some code cleanup

* Added adaptive length tests for dogbones

* Split base data classes into their own PathLanguage module.

* Splitting dogboneII implementation into its constituents

* Moved adaptive length into DogbonII module

* Separate dogboneII generator test cases and changed interface to allow for dynamic length calculations

* Unit tests for length calculation

* Initial DogboneII unit test

* Unit tests and fixes for plunge move handling

* Unit tests for the remaining styles and incision strategies

* Basic DogboneII gui

* Added support for markers

* Better color and selection scheme for markers

* Cleaned up import statements

* Added DogboneII to Path WB init

* Support for dogbone on dogbone and fixed t-bone generation

* Fixed t-bone on short leg bones

* Fixed tbone on short edge when short edge is m1

* Fixed t-bone on long edge for m0/m1 and CW/CCW

* Removed redundant code

* Removed redundant 'Dress-up' from menu entries

* black code formatting

* added generator to cmake

* Fixed typos
This commit is contained in:
mlampert
2022-11-02 13:25:09 -07:00
committed by GitHub
parent fac648fff5
commit ebc1190d8b
64 changed files with 2708 additions and 252 deletions

View File

@@ -35,6 +35,7 @@ class MockToolBit(object):
self.FlatRadius = 0
self.CuttingEdgeAngle = 60
class TestPathDeburr(PathTestUtils.PathTestBase):
def test00(self):
"""Verify chamfer depth and offset for an end mill."""

View File

@@ -0,0 +1,640 @@
# -*- 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 *
# * *
# ***************************************************************************
import FreeCAD
import Path
import Path.Base.Generator.dogboneII as dogboneII
import Path.Base.Language as PathLanguage
import Path.Dressup.DogboneII
import PathTests.PathTestUtils as PathTestUtils
import math
PI = math.pi
class MockTB(object):
def __init__(self, dia):
self.Name = "ToolBit"
self.Label = "ToolBit"
self.Diameter = FreeCAD.Units.Quantity(dia, FreeCAD.Units.Length)
class MockTC(object):
def __init__(self, dia=2):
self.Name = "TC"
self.Label = "TC"
self.Tool = MockTB(dia)
class MockOp(object):
def __init__(self, path, dia=2):
self.Path = Path.Path(path)
self.ToolController = MockTC(dia)
class MockFeaturePython(object):
def __init__(self, name):
self.prop = {}
self.addProperty("App::PropertyString", "Name", val=name)
self.addProperty("App::PropertyString", "Label", val=name)
self.addProperty("App::PropertyLink", "Proxy")
self.addProperty("Path::Path", "Path", val=Path.Path())
def addProperty(self, typ, name, grp=None, desc=None, val=None):
self.prop[name] = (typ, val)
def setEditorMode(self, name, mode):
pass
def __setattr__(self, name, val):
if name == "prop":
return super().__setattr__(name, val)
self.prop[name] = (self.prop[name][0], val)
def __getattr__(self, name):
if name == "prop":
return super().__getattr__(name)
typ, val = self.prop.get(name, (None, None))
if typ is None and val is None:
raise AttributeError
if typ == "App::PropertyLength":
if type(val) == float or type(val) == int:
return FreeCAD.Units.Quantity(val, FreeCAD.Units.Length)
return FreeCAD.Units.Quantity(val)
return val
def CreateDressup(path):
op = MockOp(path)
obj = MockFeaturePython("DogboneII")
db = Path.Dressup.DogboneII.Proxy(obj, op)
obj.Proxy = db
return obj
def MNVR(gcode, begin=None):
# 'turns out the replace() isn't really necessary
# leave it here anyway for clarity
return PathLanguage.Maneuver.FromGCode(gcode.replace("/", "\n"), begin)
def INSTR(gcode, begin=None):
return MNVR(gcode, begin).instr[0]
def KINK(gcode, begin=None):
maneuver = MNVR(gcode, begin)
if len(maneuver.instr) != 2:
return None
return dogboneII.Kink(maneuver.instr[0], maneuver.instr[1])
class TestDressupDogboneII(PathTestUtils.PathTestBase):
"""Unit tests for the DogboneII dressup."""
def assertEqualPath(self, path, s):
def cmd2str(cmd):
param = [
f"{k}{v:g}" if Path.Geom.isRoughly(0, v - int(v)) else f"{k}{v:.2f}"
for k, v in cmd.Parameters.items()
]
return f"{cmd.Name}{''.join(param)}"
p = "/".join([cmd2str(cmd) for cmd in path.Commands])
self.assertEqual(p, s)
def test00(self):
"""Verify adaptive length"""
def adaptive(k, a, n):
return Path.Dressup.DogboneII.calc_length_adaptive(k, a, n, n)
if True:
# horizontal bones
self.assertRoughly(adaptive(KINK("G1X1/G1X2"), 0, 1), 0)
self.assertRoughly(adaptive(KINK("G1X1/G1Y1"), 0, 1), 1)
self.assertRoughly(adaptive(KINK("G1X1/G1X2Y1"), 0, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X1/G1X0Y1"), 0, 1), 2.414211)
self.assertRoughly(adaptive(KINK("G1X1/G1X0"), 0, 1), 1)
self.assertRoughly(adaptive(KINK("G1X1/G1X0Y-1"), 0, 1), 2.414211)
self.assertRoughly(adaptive(KINK("G1X1/G1X1Y-1"), 0, 1), 1)
self.assertRoughly(adaptive(KINK("G1X1/G1X2Y-1"), 0, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X1Y1/G1X0Y2"), 0, 1), 0.414214)
if True:
# more horizontal and some vertical bones
self.assertRoughly(adaptive(KINK("G1Y1/G1Y2"), 0, 1), 0)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y1X1"), PI, 1), 1)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y2X1"), PI, 1), 0.089820)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y2X1"), PI / 2, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y0X1"), PI / 2, 1), 2.414211)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y0"), 0, 1), 1)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y0X-1"), PI / 2, 1), 2.414211)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y1X-1"), 0, 1), 1)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y2X-1"), 0, 1), 0.089820)
self.assertRoughly(adaptive(KINK("G1Y1/G1Y2X-1"), PI / 2, 1), 0.414214)
if True:
# dogbones
self.assertRoughly(adaptive(KINK("G1X1/G1Y1"), -PI / 4, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X1/G1X0Y1"), -PI / 8, 1), 1.613126)
self.assertRoughly(adaptive(KINK("G1X1/G1Y-1"), PI / 4, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X1/G1X0Y-1"), PI / 8, 1), 1.613126)
self.assertRoughly(adaptive(KINK("G1Y1/G1X-1"), PI / 4, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1Y1/G1X1"), 3 * PI / 4, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1Y-1/G1X1"), -3 * PI / 4, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1Y-1/G1X-1"), -PI / 4, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X1Y1/G1X0Y2"), 0, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X-1Y1/G1X0Y2"), PI, 1), 0.414214)
self.assertRoughly(adaptive(KINK("G1X1Y1/G1X2Y0"), PI / 2, 2), 0.828428)
self.assertRoughly(adaptive(KINK("G1X-1Y-1/G1X-2Y0"), -PI / 2, 2), 0.828428)
self.assertRoughly(adaptive(KINK("G1X-1Y1/G1X-2Y0"), PI / 2, 2), 0.828428)
self.assertRoughly(adaptive(KINK("G1X1Y-1/G1X2Y0"), -PI / 2, 2), 0.828428)
def test01(self):
"""Verify nominal length"""
def nominal(k, a, n):
return Path.Dressup.DogboneII.calc_length_nominal(k, a, n, 0)
# neither angle nor kink matter
self.assertRoughly(nominal(KINK("G1X1/G1X2"), 0, 13), 13)
self.assertRoughly(nominal(KINK("G1X1/G1X2"), PI / 2, 13), 13)
self.assertRoughly(nominal(KINK("G1X1/G1X2"), PI, 13), 13)
self.assertRoughly(nominal(KINK("G1X1/G1X2"), -PI / 2, 13), 13)
self.assertRoughly(nominal(KINK("G1X8/G1X12"), 0, 13), 13)
self.assertRoughly(nominal(KINK("G1X9/G1X0"), 0, 13), 13)
self.assertRoughly(nominal(KINK("G1X7/G1X9"), 0, 13), 13)
self.assertRoughly(nominal(KINK("G1X5/G1X1"), 0, 13), 13)
def test02(self):
"""Verify custom length"""
def custom(k, a, c):
return Path.Dressup.DogboneII.calc_length_custom(k, a, 0, c)
# neither angle nor kink matter
self.assertRoughly(custom(KINK("G1X1/G1X2"), 0, 7), 7)
self.assertRoughly(custom(KINK("G1X1/G1X2"), PI / 2, 7), 7)
self.assertRoughly(custom(KINK("G1X1/G1X2"), PI, 7), 7)
self.assertRoughly(custom(KINK("G1X1/G1X2"), -PI / 2, 7), 7)
self.assertRoughly(custom(KINK("G1X8/G1X12"), 0, 7), 7)
self.assertRoughly(custom(KINK("G1X9/G1X0"), 0, 7), 7)
self.assertRoughly(custom(KINK("G1X7/G1X9"), 0, 7), 7)
self.assertRoughly(custom(KINK("G1X5/G1X1"), 0, 7), 7)
def test10(self):
"""Verify basic op dressup"""
obj = CreateDressup("G1X10/G1Y20")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj.Style = Path.Dressup.DogboneII.Style.Tbone_H
# bones on right side
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/G1X11/G1X10/G1Y20")
# no bones on left side
obj.Side = Path.Dressup.DogboneII.Side.Left
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/G1Y20")
def test11(self):
"""Verify retaining non-move instructions"""
obj = CreateDressup("G1X10/(some comment)/G1Y20")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj.Style = Path.Dressup.DogboneII.Style.Tbone_H
# bone on right side
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/(some comment)/G1X11/G1X10/G1Y20")
# no bone on left side
obj.Side = Path.Dressup.DogboneII.Side.Left
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/(some comment)/G1Y20")
def test20(self):
"""Verify bone on plunge moves"""
obj = CreateDressup("G0Z10/G1Z0/G1X10/G1Y10/G1X0/G1Y0/G0Z10")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj.Style = Path.Dressup.DogboneII.Style.Tbone_H
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Proxy.execute(obj)
self.assertEqualPath(
obj.Path,
"G0Z10/G1Z0/G1X10/G1X11/G1X10/G1Y10/G1X11/G1X10/G1X0/G1X-1/G1X0/G1Y0/G1X-1/G1X0/G0Z10",
)
def test21(self):
"""Verify ignoring plunge moves that don't connect"""
obj = CreateDressup("G0Z10/G1Z0/G1X10/G1Y10/G1X0/G1Y5/G0Z10")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj.Style = Path.Dressup.DogboneII.Style.Tbone_H
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Proxy.execute(obj)
self.assertEqualPath(
obj.Path,
"G0Z10/G1Z0/G1X10/G1X11/G1X10/G1Y10/G1X11/G1X10/G1X0/G1X-1/G1X0/G1Y5/G0Z10",
)
def test30(self):
"""Verify TBone_V style"""
def check_tbone(d, i, path, out, right):
obj = CreateDressup(f"({d}.{i:02})/{path}")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
if right:
obj.Side = Path.Dressup.DogboneII.Side.Right
else:
obj.Side = Path.Dressup.DogboneII.Side.Left
obj.Style = Path.Dressup.DogboneII.Style.Tbone_V
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, f"({d}.{i:02})/{out}")
# test data with a horizontal lead in
test_data_h = [
# top right quadrant
("G1X10Y0/G1X10Y10", "G1X10Y0/G1Y-1/G1Y0/G1X10Y10", True),
("G1X10Y0/G1X20Y10", "G1X10Y0/G1Y-1/G1Y0/G1X20Y10", True),
("G1X10Y0/G1X90Y10", "G1X10Y0/G1Y-1/G1Y0/G1X90Y10", True),
("G1X10Y0/G1X0Y10", "G1X10Y0/G1Y-1/G1Y0/G1X0Y10", True),
# bottom right quadrant
("G1X10Y0/G1X90Y-10", "G1X10Y0/G1Y1/G1Y0/G1X90Y-10", False),
("G1X10Y0/G1X20Y-10", "G1X10Y0/G1Y1/G1Y0/G1X20Y-10", False),
("G1X10Y0/G1X10Y-10", "G1X10Y0/G1Y1/G1Y0/G1X10Y-10", False),
("G1X10Y0/G1X0Y-10", "G1X10Y0/G1Y1/G1Y0/G1X0Y-10", False),
# top left quadrant
("G1X-10Y0/G1X-10Y10", "G1X-10Y0/G1Y-1/G1Y0/G1X-10Y10", False),
("G1X-10Y0/G1X-20Y10", "G1X-10Y0/G1Y-1/G1Y0/G1X-20Y10", False),
("G1X-10Y0/G1X-90Y10", "G1X-10Y0/G1Y-1/G1Y0/G1X-90Y10", False),
("G1X-10Y0/G1X-0Y10", "G1X-10Y0/G1Y-1/G1Y0/G1X-0Y10", False),
# bottom left quadrant
("G1X-10Y0/G1X-90Y-10", "G1X-10Y0/G1Y1/G1Y0/G1X-90Y-10", True),
("G1X-10Y0/G1X-20Y-10", "G1X-10Y0/G1Y1/G1Y0/G1X-20Y-10", True),
("G1X-10Y0/G1X-10Y-10", "G1X-10Y0/G1Y1/G1Y0/G1X-10Y-10", True),
("G1X-10Y0/G1X-0Y-10", "G1X-10Y0/G1Y1/G1Y0/G1X-0Y-10", True),
]
for i, (path, out, right) in enumerate(test_data_h):
check_tbone("h", i, path, out, right)
# test data with a vertical lead in
test_data_v = [
# top right quadrant
("G1X0Y10/G1X10Y10", "G1X0Y10/G1Y11/G1Y10/G1X10Y10", False),
("G1X0Y10/G1X10Y20", "G1X0Y10/G1Y11/G1Y10/G1X10Y20", False),
("G1X0Y10/G1X10Y90", "G1X0Y10/G1Y11/G1Y10/G1X10Y90", False),
("G1X0Y10/G1X10Y0", "G1X0Y10/G1Y11/G1Y10/G1X10Y0", False),
# bottom right quadrant
("G1X0Y-10/G1X10Y-90", "G1X0Y-10/G1Y-11/G1Y-10/G1X10Y-90", True),
("G1X0Y-10/G1X10Y-20", "G1X0Y-10/G1Y-11/G1Y-10/G1X10Y-20", True),
("G1X0Y-10/G1X10Y-10", "G1X0Y-10/G1Y-11/G1Y-10/G1X10Y-10", True),
("G1X0Y-10/G1X10Y-0", "G1X0Y-10/G1Y-11/G1Y-10/G1X10Y-0", True),
# top left quadrant
("G1X0Y10/G1X-10Y10", "G1X0Y10/G1Y11/G1Y10/G1X-10Y10", True),
("G1X0Y10/G1X-10Y20", "G1X0Y10/G1Y11/G1Y10/G1X-10Y20", True),
("G1X0Y10/G1X-10Y90", "G1X0Y10/G1Y11/G1Y10/G1X-10Y90", True),
("G1X0Y10/G1X-10Y0", "G1X0Y10/G1Y11/G1Y10/G1X-10Y0", True),
# bottom left quadrant
("G1X0Y-10/G1X-10Y-90", "G1X0Y-10/G1Y-11/G1Y-10/G1X-10Y-90", False),
("G1X0Y-10/G1X-10Y-20", "G1X0Y-10/G1Y-11/G1Y-10/G1X-10Y-20", False),
("G1X0Y-10/G1X-10Y-10", "G1X0Y-10/G1Y-11/G1Y-10/G1X-10Y-10", False),
("G1X0Y-10/G1X-10Y-0", "G1X0Y-10/G1Y-11/G1Y-10/G1X-10Y-0", False),
]
for i, (path, out, right) in enumerate(test_data_v):
check_tbone("v", i, path, out, right)
def test40(self):
"""Verify TBone_S style"""
def check_tbone_s(d, i, path, out, right):
obj = CreateDressup(f"(m{d}.{i:02})/{path}")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
if right:
obj.Side = Path.Dressup.DogboneII.Side.Right
else:
obj.Side = Path.Dressup.DogboneII.Side.Left
obj.Style = Path.Dressup.DogboneII.Style.Tbone_S
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, f"(m{d}.{i:02})/{out}")
# short edge m0
test_data_0 = [
# CCW
("G1X10/G1Y20", "G1X10/G1Y-1/G1Y0/G1Y20", True),
("G1X10Y10/G1X-10Y30", "G1X10Y10/G1X10.71Y9.29/G1X10Y10/G1X-10Y30", True),
("G1Y10/G1X-20", "G1Y10/G1X1/G1X0/G1X-20", True),
(
"G1X-10Y10/G1X-30Y-10",
"G1X-10Y10/G1X-9.29Y10.71/G1X-10Y10/G1X-30Y-10",
True,
),
("G1X-10/G1Y-20", "G1X-10/G1Y1/G1Y0/G1Y-20", True),
(
"G1X-10Y-10/G1X10Y-30",
"G1X-10Y-10/G1X-10.71Y-9.29/G1X-10Y-10/G1X10Y-30",
True,
),
("G1Y-10/G1X20", "G1Y-10/G1X-1/G1X0/G1X20", True),
("G1X10Y-10/G1X30Y10", "G1X10Y-10/G1X9.29Y-10.71/G1X10Y-10/G1X30Y10", True),
# CW
("G1X10/G1Y-20", "G1X10/G1Y1/G1Y0/G1Y-20", False),
("G1X10Y10/G1X30Y-10", "G1X10Y10/G1X9.29Y10.71/G1X10Y10/G1X30Y-10", False),
("G1Y10/G1X20", "G1Y10/G1X-1/G1X0/G1X20", False),
(
"G1X-10Y10/G1X10Y30",
"G1X-10Y10/G1X-10.71Y9.29/G1X-10Y10/G1X10Y30",
False,
),
("G1X-10/G1Y20", "G1X-10/G1Y-1/G1Y0/G1Y20", False),
(
"G1X-10Y-10/G1X-30Y10",
"G1X-10Y-10/G1X-9.29Y-10.71/G1X-10Y-10/G1X-30Y10",
False,
),
("G1Y-10/G1X-20", "G1Y-10/G1X1/G1X0/G1X-20", False),
(
"G1X10Y-10/G1X-10Y-30",
"G1X10Y-10/G1X10.71Y-9.29/G1X10Y-10/G1X-10Y-30",
False,
),
]
for i, (path, out, right) in enumerate(test_data_0):
check_tbone_s("0", i, path, out, right)
# short edge m1
test_data_1 = [
# CCW
("G1X20/G1Y10", "G1X20/G1X21/G1X20/G1Y10", True),
("G1X20Y20/G1X10Y30", "G1X20Y20/G1X20.71Y20.71/G1X20Y20/G1X10Y30", True),
("G1Y20/G1X-10", "G1Y20/G1Y21/G1Y20/G1X-10", True),
(
"G1X-20Y20/G1X-30Y10",
"G1X-20Y20/G1X-20.71Y20.71/G1X-20Y20/G1X-30Y10",
True,
),
("G1X-20/G1Y-10", "G1X-20/G1X-21/G1X-20/G1Y-10", True),
(
"G1X-20Y-20/G1X-10Y-30",
"G1X-20Y-20/G1X-20.71Y-20.71/G1X-20Y-20/G1X-10Y-30",
True,
),
("G1Y-20/G1X10", "G1Y-20/G1Y-21/G1Y-20/G1X10", True),
(
"G1X20Y-20/G1X30Y-10",
"G1X20Y-20/G1X20.71Y-20.71/G1X20Y-20/G1X30Y-10",
True,
),
# CW
("G1X20/G1Y-10", "G1X20/G1X21/G1X20/G1Y-10", False),
("G1X20Y20/G1X30Y10", "G1X20Y20/G1X20.71Y20.71/G1X20Y20/G1X30Y10", False),
("G1Y20/G1X10", "G1Y20/G1Y21/G1Y20/G1X10", False),
(
"G1X-20Y20/G1X-10Y30",
"G1X-20Y20/G1X-20.71Y20.71/G1X-20Y20/G1X-10Y30",
False,
),
("G1X-20/G1Y10", "G1X-20/G1X-21/G1X-20/G1Y10", False),
(
"G1X-20Y-20/G1X-30Y-10",
"G1X-20Y-20/G1X-20.71Y-20.71/G1X-20Y-20/G1X-30Y-10",
False,
),
("G1Y-20/G1X-10", "G1Y-20/G1Y-21/G1Y-20/G1X-10", False),
(
"G1X20Y-20/G1X10Y-30",
"G1X20Y-20/G1X20.71Y-20.71/G1X20Y-20/G1X10Y-30",
False,
),
]
for i, (path, out, right) in enumerate(test_data_1):
check_tbone_s("1", i, path, out, right)
def test50(self):
"""Verify TBone_L style"""
def check_tbone_l(d, i, path, out, right):
obj = CreateDressup(f"(m{d}.{i:02})/{path}")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
if right:
obj.Side = Path.Dressup.DogboneII.Side.Right
else:
obj.Side = Path.Dressup.DogboneII.Side.Left
obj.Style = Path.Dressup.DogboneII.Style.Tbone_L
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, f"(m{d}.{i:02})/{out}")
# long edge m1
test_data_1 = [
# CCW
("G1X10/G1Y20", "G1X10/G1X11/G1X10/G1Y20", True),
("G1X10Y10/G1X-10Y30", "G1X10Y10/G1X10.71Y10.71/G1X10Y10/G1X-10Y30", True),
("G1Y10/G1X-20", "G1Y10/G1Y11/G1Y10/G1X-20", True),
(
"G1X-10Y10/G1X-30Y-10",
"G1X-10Y10/G1X-10.71Y10.71/G1X-10Y10/G1X-30Y-10",
True,
),
("G1X-10/G1Y-20", "G1X-10/G1X-11/G1X-10/G1Y-20", True),
(
"G1X-10Y-10/G1X10Y-30",
"G1X-10Y-10/G1X-10.71Y-10.71/G1X-10Y-10/G1X10Y-30",
True,
),
("G1Y-10/G1X20", "G1Y-10/G1Y-11/G1Y-10/G1X20", True),
(
"G1X10Y-10/G1X30Y10",
"G1X10Y-10/G1X10.71Y-10.71/G1X10Y-10/G1X30Y10",
True,
),
# CW
("G1X10/G1Y-20", "G1X10/G1X11/G1X10/G1Y-20", False),
("G1X10Y10/G1X30Y-10", "G1X10Y10/G1X10.71Y10.71/G1X10Y10/G1X30Y-10", False),
("G1Y10/G1X20", "G1Y10/G1Y11/G1Y10/G1X20", False),
(
"G1X-10Y10/G1X10Y30",
"G1X-10Y10/G1X-10.71Y10.71/G1X-10Y10/G1X10Y30",
False,
),
("G1X-10/G1Y20", "G1X-10/G1X-11/G1X-10/G1Y20", False),
(
"G1X-10Y-10/G1X-30Y10",
"G1X-10Y-10/G1X-10.71Y-10.71/G1X-10Y-10/G1X-30Y10",
False,
),
("G1Y-10/G1X-20", "G1Y-10/G1Y-11/G1Y-10/G1X-20", False),
(
"G1X10Y-10/G1X-10Y-30",
"G1X10Y-10/G1X10.71Y-10.71/G1X10Y-10/G1X-10Y-30",
False,
),
]
for i, (path, out, right) in enumerate(test_data_1):
check_tbone_l("1", i, path, out, right)
# long edge m0
test_data_0 = [
# CCW
("G1X20/G1Y10", "G1X20/G1Y-1/G1Y0/G1Y10", True),
("G1X20Y20/G1X10Y30", "G1X20Y20/G1X20.71Y19.29/G1X20Y20/G1X10Y30", True),
("G1Y20/G1X-10", "G1Y20/G1X1/G1X0/G1X-10", True),
(
"G1X-20Y20/G1X-30Y10",
"G1X-20Y20/G1X-19.29Y20.71/G1X-20Y20/G1X-30Y10",
True,
),
("G1X-20/G1Y-10", "G1X-20/G1Y1/G1Y0/G1Y-10", True),
(
"G1X-20Y-20/G1X-10Y-30",
"G1X-20Y-20/G1X-20.71Y-19.29/G1X-20Y-20/G1X-10Y-30",
True,
),
("G1Y-20/G1X10", "G1Y-20/G1X-1/G1X0/G1X10", True),
(
"G1X20Y-20/G1X30Y-10",
"G1X20Y-20/G1X19.29Y-20.71/G1X20Y-20/G1X30Y-10",
True,
),
# CW
("G1X20/G1Y-10", "G1X20/G1Y1/G1Y0/G1Y-10", False),
("G1X20Y20/G1X30Y10", "G1X20Y20/G1X19.29Y20.71/G1X20Y20/G1X30Y10", False),
("G1Y20/G1X10", "G1Y20/G1X-1/G1X0/G1X10", False),
(
"G1X-20Y20/G1X-10Y30",
"G1X-20Y20/G1X-20.71Y19.29/G1X-20Y20/G1X-10Y30",
False,
),
("G1X-20/G1Y10", "G1X-20/G1Y-1/G1Y0/G1Y10", False),
(
"G1X-20Y-20/G1X-30Y-10",
"G1X-20Y-20/G1X-19.29Y-20.71/G1X-20Y-20/G1X-30Y-10",
False,
),
("G1Y-20/G1X-10", "G1Y-20/G1X1/G1X0/G1X-10", False),
(
"G1X20Y-20/G1X10Y-30",
"G1X20Y-20/G1X20.71Y-19.29/G1X20Y-20/G1X10Y-30",
False,
),
]
for i, (path, out, right) in enumerate(test_data_0):
check_tbone_l("0", i, path, out, right)
def test60(self):
"""Verify Dogbone style"""
obj = CreateDressup("G1X10/G1Y20")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Style = Path.Dressup.DogboneII.Style.Dogbone
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/G1X10.71Y-0.71/G1X10Y0/G1Y20")
def test70(self):
"""Verify custom length."""
obj = CreateDressup("G0Z10/G1Z0/G1X10/G1Y10/G1X0/G1Y0/G0Z10")
obj.Style = Path.Dressup.DogboneII.Style.Tbone_H
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Incision = Path.Dressup.DogboneII.Incision.Custom
obj.Custom = 3
obj.Proxy.execute(obj)
self.assertEqualPath(
obj.Path,
"G0Z10/G1Z0/G1X10/G1X13/G1X10/G1Y10/G1X13/G1X10/G1X0/G1X-3/G1X0/G1Y0/G1X-3/G1X0/G0Z10",
)
obj.Custom = 2
obj.Proxy.execute(obj)
self.assertEqualPath(
obj.Path,
"G0Z10/G1Z0/G1X10/G1X12/G1X10/G1Y10/G1X12/G1X10/G1X0/G1X-2/G1X0/G1Y0/G1X-2/G1X0/G0Z10",
)
def test80(self):
"""Verify adaptive length."""
obj = CreateDressup("G1X10/G1Y20")
obj.Incision = Path.Dressup.DogboneII.Incision.Adaptive
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Style = Path.Dressup.DogboneII.Style.Dogbone
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/G1X10.29Y-0.29/G1X10Y0/G1Y20")
def test81(self):
"""Verify adaptive length II."""
obj = CreateDressup("G1X10/G1X20Y20")
obj.Incision = Path.Dressup.DogboneII.Incision.Adaptive
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.Style = Path.Dressup.DogboneII.Style.Dogbone
obj.Proxy.execute(obj)
self.assertEqualPath(obj.Path, "G1X10/G1X10.09Y-0.15/G1X10Y0/G1X20Y20")
def test90(self):
"""Verify dogbone blacklist"""
obj = CreateDressup("G0Z10/G1Z0/G1X10/G1Y10/G1X0/G1Y0/G0Z10")
obj.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj.Style = Path.Dressup.DogboneII.Style.Tbone_H
obj.Side = Path.Dressup.DogboneII.Side.Right
obj.BoneBlacklist = [0, 2]
obj.Proxy.execute(obj)
self.assertEqualPath(
obj.Path, "G0Z10/G1Z0/G1X10/G1Y10/G1X11/G1X10/G1X0/G1Y0/G1X-1/G1X0/G0Z10"
)
return obj
def test91(self):
"""Verify dogbone on dogbone"""
obj = self.test90()
obj2 = MockFeaturePython("DogboneII_")
db2 = Path.Dressup.DogboneII.Proxy(obj2, obj)
obj2.Proxy = db2
obj2.Incision = Path.Dressup.DogboneII.Incision.Fixed
obj2.Style = Path.Dressup.DogboneII.Style.Tbone_H
obj2.Side = Path.Dressup.DogboneII.Side.Right
obj2.BoneBlacklist = [1]
obj2.Proxy.execute(obj2)
self.assertEqualPath(
obj2.Path,
"G0Z10/G1Z0/G1X10/G1X11/G1X10/G1Y10/G1X11/G1X10/G1X0/G1X-1/G1X0/G1Y0/G1X-1/G1X0/G0Z10",
)

View File

@@ -93,9 +93,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Passing explicit vector
self.assertTrue(
@@ -125,9 +123,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertFalse(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# raised cylinder
candidate = self.obj.getSubObject("Face32")
@@ -136,9 +132,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertFalse(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# cylinder on slope
candidate = self.obj.getSubObject("Face24")
@@ -146,9 +140,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Circular Faces
candidate = self.obj.getSubObject("Face54")
@@ -157,15 +149,11 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Passing explicit vector
self.assertTrue(
Drillable.isDrillable(
self.obj.Shape, candidate, vector=App.Vector(0, 0, 1)
)
Drillable.isDrillable(self.obj.Shape, candidate, vector=App.Vector(0, 0, 1))
)
# Drilling with smaller bit
@@ -185,9 +173,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Passing explicit vector
self.assertTrue(
@@ -202,9 +188,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# interrupted Face
candidate = self.obj.getSubObject("Face50")
@@ -212,9 +196,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertFalse(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# donut face
candidate = self.obj.getSubObject("Face48")
@@ -222,9 +204,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Test edges
# circular edge
@@ -234,15 +214,11 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Passing explicit vector
self.assertTrue(
Drillable.isDrillable(
self.obj.Shape, candidate, vector=App.Vector(0, 0, 1)
)
Drillable.isDrillable(self.obj.Shape, candidate, vector=App.Vector(0, 0, 1))
)
# Drilling with smaller bit
@@ -264,15 +240,11 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertTrue(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# Passing explicit vector
self.assertTrue(
Drillable.isDrillable(
self.obj.Shape, candidate, vector=App.Vector(0, 1, 0)
)
Drillable.isDrillable(self.obj.Shape, candidate, vector=App.Vector(0, 1, 0))
)
# incomplete circular edge
@@ -281,9 +253,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertFalse(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
# elliptical edge
candidate = self.obj.getSubObject("Edge56")
@@ -291,9 +261,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate))
# Passing None as vector
self.assertFalse(
Drillable.isDrillable(self.obj.Shape, candidate, vector=None)
)
self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate, vector=None))
def test20(self):
"""Test getDrillableTargets"""
@@ -303,7 +271,5 @@ class TestPathDrillable(PathTestUtils.PathTestBase):
results = Drillable.getDrillableTargets(self.obj, vector=None)
self.assertEqual(len(results), 20)
results = Drillable.getDrillableTargets(
self.obj, ToolDiameter=20, vector=None
)
results = Drillable.getDrillableTargets(self.obj, ToolDiameter=20, vector=None)
self.assertEqual(len(results), 5)

View File

@@ -0,0 +1,354 @@
# -*- 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 *
# * *
# ***************************************************************************
import FreeCAD
import Path
import Path.Base.Generator.dogboneII as dogboneII
import Path.Base.Language as PathLanguage
import PathTests.PathTestUtils as PathTestUtils
import math
# Path.Log.setLevel(Path.Log.Level.DEBUG)
Path.Log.setLevel(Path.Log.Level.NOTICE)
PI = math.pi
DebugMode = Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG
def createKinks(maneuver):
k = []
moves = maneuver.getMoves()
if moves:
move0 = moves[0]
prev = move0
for m in moves[1:]:
k.append(dogboneII.Kink(prev, m))
prev = m
if Path.Geom.pointsCoincide(move0.positionBegin(), prev.positionEnd()):
k.append(dogboneII.Kink(prev, move0))
return k
def findDogboneKinks(maneuver, threshold):
if threshold > 0:
return [k for k in createKinks(maneuver) if k.deflection() > threshold]
if threshold < 0:
return [k for k in createKinks(maneuver) if k.deflection() < threshold]
return createKinks(maneuver)
def MNVR(gcode, begin=None):
# 'turns out the replace() isn't really necessary
# leave it here anyway for clarity
return PathLanguage.Maneuver.FromGCode(gcode.replace("/", "\n"), begin)
def INSTR(gcode, begin=None):
return MNVR(gcode, begin).instr[0]
def KINK(gcode, begin=None):
maneuver = MNVR(gcode, begin)
if len(maneuver.instr) != 2:
return None
return dogboneII.Kink(maneuver.instr[0], maneuver.instr[1])
def GEN(generator, length):
return generator(lambda k, a, n, c: n, length, 1)
class TestGeneratorDogboneII(PathTestUtils.PathTestBase):
"""Unit tests for the dogboneII generator."""
def assertKinks(self, maneuver, s):
kinks = [f"{k.deflection():4.2f}" for k in createKinks(maneuver)]
self.assertEqual(f"[{', '.join(kinks)}]", s)
def assertBones(self, maneuver, threshold, s):
bones = [
f"({int(b.x())},{int(b.y())})"
for b in findDogboneKinks(maneuver, threshold)
]
self.assertEqual(f"[{', '.join(bones)}]", s)
def assertBone(self, bone, s, digits=0):
if DebugMode and FreeCAD.GuiUp:
Path.show(dogboneII.kink_to_path(bone.kink))
FreeCAD.ActiveDocument.Objects[-1].Visibility = False
Path.show(dogboneII.bone_to_path(bone))
FreeCAD.ActiveDocument.Objects[-1].Visibility = False
Path.Log.debug(f"{bone.kink} : {bone.angle / PI:.2f}")
b = [i.str(digits) for i in bone.instr]
self.assertEqual(f"[{', '.join(b)}]", s)
def test20(self):
"""Verify kinks of maneuvers"""
self.assertKinks(MNVR("G1X1/G1Y1"), "[1.57]")
self.assertKinks(MNVR("G1X1/G1Y-1"), "[-1.57]")
self.assertKinks(MNVR("G1X1/G1Y1/G1X0"), "[1.57, 1.57]")
self.assertKinks(MNVR("G1X1/G1Y1/G1X0/G1Y0"), "[1.57, 1.57, 1.57, 1.57]")
self.assertKinks(MNVR("G1Y1/G1X1"), "[-1.57]")
self.assertKinks(MNVR("G1Y1/G1X1/G1Y0"), "[-1.57, -1.57]")
self.assertKinks(MNVR("G1Y1/G1X1/G1Y0/G1X0"), "[-1.57, -1.57, -1.57, -1.57]")
# tangential arc moves
self.assertKinks(MNVR("G1X1/G3Y2J1"), "[0.00]")
self.assertKinks(MNVR("G1X1/G3Y2J1G1X0"), "[0.00, 0.00]")
# folding back arc moves
self.assertKinks(MNVR("G1X1/G2Y2J1"), "[-3.14]")
self.assertKinks(MNVR("G1X1/G2Y2J1G1X0"), "[-3.14, 3.14]")
def test30(self):
"""Verify dogbone detection"""
self.assertBones(
MNVR("G1X1/G1Y1/G1X0/G1Y0"), PI / 4, "[(1,0), (1,1), (0,1), (0,0)]"
)
self.assertBones(MNVR("G1X1/G1Y1/G1X0/G1Y0"), -PI / 4, "[]")
# no bones on flat angle
self.assertBones(MNVR("G1X1/G1X3Y1/G1X0/G1Y0"), PI / 4, "[(3,1), (0,1), (0,0)]")
self.assertBones(MNVR("G1X1/G1X3Y1/G1X0/G1Y0"), -PI / 4, "[]")
# no bones on tangential arc
self.assertBones(MNVR("G1X1/G3Y2J1/G1X0/G1Y0"), PI / 4, "[(0,2), (0,0)]")
self.assertBones(MNVR("G1X1/G3Y2J1/G1X0/G1Y0"), -PI / 4, "[]")
# a bone on perpendicular arc
self.assertBones(
MNVR("G1X1/G3X3I1/G1Y1/G1X0/G1Y0"), PI / 4, "[(3,1), (0,1), (0,0)]"
)
self.assertBones(MNVR("G1X1/G3X3I1/G1Y1/G1X0/G1Y0"), -PI / 4, "[(1,0)]")
def test40(self):
"""Verify horizontal t-bone creation"""
# Uses test data from test30, if that broke, this can't succeed
horizontal = GEN(dogboneII.GeneratorTBoneHorizontal, 1)
# single move right
maneuver = MNVR("G1X1/G1Y1")
kinks = findDogboneKinks(maneuver, PI / 4)
self.assertEqual(len(kinks), 1)
k = kinks[0]
p = k.position()
self.assertEqual(f"({int(p.x)}, {int(p.y)})", "(1, 0)")
bone = horizontal.generate(k)
self.assertBone(bone, "[G1{X: 2}, G1{X: 1}]")
# full loop CCW
kinks = findDogboneKinks(MNVR("G1X1/G1Y1/G1X0/G1Y0"), PI / 4)
bones = [horizontal.generate(k) for k in kinks]
self.assertEqual(len(bones), 4)
self.assertBone(bones[0], "[G1{X: 2}, G1{X: 1}]")
self.assertBone(bones[1], "[G1{X: 2}, G1{X: 1}]")
self.assertBone(bones[2], "[G1{X: -1}, G1{X: 0}]")
self.assertBone(bones[3], "[G1{X: -1}, G1{X: 0}]")
# single move left
maneuver = MNVR("G1X1/G1Y-1")
kinks = findDogboneKinks(maneuver, -PI / 4)
self.assertEqual(len(kinks), 1)
k = kinks[0]
p = k.position()
self.assertEqual(f"({int(p.x)}, {int(p.y)})", "(1, 0)")
bone = horizontal.generate(k)
self.assertBone(bone, "[G1{X: 2}, G1{X: 1}]")
# full loop CW
kinks = findDogboneKinks(MNVR("G1X1/G1Y-1/G1X0/G1Y0"), -PI / 4)
bones = [horizontal.generate(k) for k in kinks]
self.assertEqual(len(bones), 4)
self.assertBone(bones[0], "[G1{X: 2}, G1{X: 1}]")
self.assertBone(bones[1], "[G1{X: 2}, G1{X: 1}]")
self.assertBone(bones[2], "[G1{X: -1}, G1{X: 0}]")
self.assertBone(bones[3], "[G1{X: -1}, G1{X: 0}]")
# bones on arcs
kinks = findDogboneKinks(MNVR("G1X1/G3X3I1/G1Y1/G1X0/G1Y0"), PI / 4)
bones = [horizontal.generate(k) for k in kinks]
self.assertEqual(len(bones), 3)
self.assertBone(bones[0], "[G1{X: 4}, G1{X: 3}]")
self.assertBone(bones[1], "[G1{X: -1}, G1{X: 0}]")
self.assertBone(bones[2], "[G1{X: -1}, G1{X: 0}]")
# bones on arcs
kinks = findDogboneKinks(MNVR("G1X1/G3X3I1/G1Y1/G1X0/G1Y0"), -PI / 4)
bones = [horizontal.generate(k) for k in kinks]
self.assertEqual(len(bones), 1)
self.assertBone(bones[0], "[G1{X: 2}, G1{X: 1}]")
def test50(self):
"""Verify vertical t-bone creation"""
# Uses test data from test30, if that broke, this can't succeed
vertical = GEN(dogboneII.GeneratorTBoneVertical, 1)
# single move right
maneuver = MNVR("G1X1/G1Y1")
kinks = findDogboneKinks(maneuver, PI / 4)
self.assertEqual(len(kinks), 1)
k = kinks[0]
p = k.position()
self.assertEqual(f"({int(p.x)}, {int(p.y)})", "(1, 0)")
bone = vertical.generate(k)
self.assertBone(bone, "[G1{Y: -1}, G1{Y: 0}]")
# full loop CCW
kinks = findDogboneKinks(MNVR("G1X1/G1Y1/G1X0/G1Y0"), PI / 4)
bones = [vertical.generate(k) for k in kinks]
self.assertEqual(len(bones), 4)
self.assertBone(bones[0], "[G1{Y: -1}, G1{Y: 0}]")
self.assertBone(bones[1], "[G1{Y: 2}, G1{Y: 1}]")
self.assertBone(bones[2], "[G1{Y: 2}, G1{Y: 1}]")
self.assertBone(bones[3], "[G1{Y: -1}, G1{Y: 0}]")
# single move left
maneuver = MNVR("G1X1/G1Y-1")
kinks = findDogboneKinks(maneuver, -PI / 4)
self.assertEqual(len(kinks), 1)
k = kinks[0]
p = k.position()
self.assertEqual(f"({int(p.x)}, {int(p.y)})", "(1, 0)")
bone = vertical.generate(k)
self.assertBone(bone, "[G1{Y: 1}, G1{Y: 0}]")
# full loop CW
kinks = findDogboneKinks(MNVR("G1X1/G1Y-1/G1X0/G1Y0"), -PI / 4)
bones = [vertical.generate(k) for k in kinks]
self.assertEqual(len(bones), 4)
self.assertBone(bones[0], "[G1{Y: 1}, G1{Y: 0}]")
self.assertBone(bones[1], "[G1{Y: -2}, G1{Y: -1}]")
self.assertBone(bones[2], "[G1{Y: -2}, G1{Y: -1}]")
self.assertBone(bones[3], "[G1{Y: 1}, G1{Y: 0}]")
# bones on arcs
kinks = findDogboneKinks(MNVR("G1X1/G3X3I1/G1Y1/G1X0/G1Y0"), PI / 4)
bones = [vertical.generate(k) for k in kinks]
self.assertEqual(len(bones), 3)
self.assertBone(bones[0], "[G1{Y: 2}, G1{Y: 1}]")
self.assertBone(bones[1], "[G1{Y: 2}, G1{Y: 1}]")
self.assertBone(bones[2], "[G1{Y: -1}, G1{Y: 0}]")
# bones on arcs
kinks = findDogboneKinks(MNVR("G1X1/G3X3I1/G1Y1/G1X0/G1Y0"), -PI / 4)
bones = [vertical.generate(k) for k in kinks]
self.assertEqual(len(bones), 1)
self.assertBone(bones[0], "[G1{Y: 1}, G1{Y: 0}]")
def test60(self):
"""Verify t-bones on edges"""
on_short_1 = GEN(dogboneII.GeneratorTBoneOnShort, 1)
on_short_5 = GEN(dogboneII.GeneratorTBoneOnShort, 5)
# horizontal short edge
bone = on_short_1.generate(KINK("G1X1/G1Y2"))
self.assertBone(bone, "[G1{Y: -1}, G1{Y: 0}]")
bone = on_short_1.generate(KINK("G1X-1/G1Y2"))
self.assertBone(bone, "[G1{Y: -1}, G1{Y: 0}]")
# vertical short edge
bone = on_short_1.generate(KINK("G1Y1/G1X2"))
self.assertBone(bone, "[G1{X: -1}, G1{X: 0}]")
bone = on_short_1.generate(KINK("G1Y1/G1X-2"))
self.assertBone(bone, "[G1{X: 1}, G1{X: 0}]")
# some other angle
bone = on_short_5.generate(KINK("G1X1Y1/G1Y-1"))
self.assertBone(bone, "[G1{X: -2.5, Y: 4.5}, G1{X: 1.0, Y: 1.0}]", 2)
bone = on_short_5.generate(KINK("G1X-1Y-1/G1Y1"))
self.assertBone(bone, "[G1{X: 2.5, Y: -4.5}, G1{X: -1.0, Y: -1.0}]", 2)
# some other angle
bone = on_short_5.generate(KINK("G1X2Y1/G1Y-3"))
self.assertBone(bone, "[G1{X: -0.24, Y: 5.5}, G1{X: 2.0, Y: 1.0}]", 2)
bone = on_short_5.generate(KINK("G1X-2Y-1/G1Y3"))
self.assertBone(bone, "[G1{X: 0.24, Y: -5.5}, G1{X: -2.0, Y: -1.0}]", 2)
# short edge - the 2nd
bone = on_short_1.generate(KINK("G1Y2/G1X1"))
self.assertBone(bone, "[G1{Y: 3}, G1{Y: 2}]")
bone = on_short_1.generate(KINK("G1Y2/G1X-1"))
self.assertBone(bone, "[G1{Y: 3}, G1{Y: 2}]")
bone = on_short_5.generate(KINK("G1Y-3/G1X2Y-2"))
self.assertBone(bone, "[G1{X: 2.2, Y: -7.5}, G1{X: 0.0, Y: -3.0}]", 2)
bone = on_short_5.generate(KINK("G1Y3/G1X-2Y2"))
self.assertBone(bone, "[G1{X: -2.2, Y: 7.5}, G1{X: 0.0, Y: 3.0}]", 2)
# long edge
on_long_1 = GEN(dogboneII.GeneratorTBoneOnLong, 1)
on_long_5 = GEN(dogboneII.GeneratorTBoneOnLong, 5)
bone = on_long_1.generate(
KINK("G1X2/G1Y1"),
)
self.assertBone(bone, "[G1{Y: -1}, G1{Y: 0}]")
bone = on_long_1.generate(KINK("G1X-2/G1Y1"))
self.assertBone(bone, "[G1{Y: -1}, G1{Y: 0}]")
bone = on_long_5.generate(KINK("G1Y-1/G1X2Y0"))
self.assertBone(bone, "[G1{X: 2.2, Y: -5.5}, G1{X: 0.0, Y: -1.0}]", 2)
bone = on_long_5.generate(KINK("G1Y1/G1X-2Y0"))
self.assertBone(bone, "[G1{X: -2.2, Y: 5.5}, G1{X: 0.0, Y: 1.0}]", 2)
def test70(self):
"""Verify dogbone angles"""
self.assertRoughly(180 * KINK("G1X1/G1Y+1").normAngle() / PI, -45)
self.assertRoughly(180 * KINK("G1X1/G1Y-1").normAngle() / PI, 45)
self.assertRoughly(180 * KINK("G1X1/G1X2Y1").normAngle() / PI, -67.5)
self.assertRoughly(180 * KINK("G1X1/G1X2Y-1").normAngle() / PI, 67.5)
self.assertRoughly(180 * KINK("G1Y1/G1X+1").normAngle() / PI, 135)
self.assertRoughly(180 * KINK("G1Y1/G1X-1").normAngle() / PI, 45)
self.assertRoughly(180 * KINK("G1X-1/G1Y+1").normAngle() / PI, -135)
self.assertRoughly(180 * KINK("G1X-1/G1Y-1").normAngle() / PI, 135)
self.assertRoughly(180 * KINK("G1Y-1/G1X-1").normAngle() / PI, -45)
self.assertRoughly(180 * KINK("G1Y-1/G1X+1").normAngle() / PI, -135)
def test71(self):
"""Verify dogbones"""
dogbone = GEN(dogboneII.GeneratorDogbone, 1)
bone = dogbone.generate(KINK("G1X1/G1Y1"))
self.assertBone(bone, "[G1{X: 1.7, Y: -0.71}, G1{X: 1.0, Y: 0.0}]", 2)
bone = dogbone.generate(KINK("G1X1/G1X3Y-1"))
self.assertBone(bone, "[G1{X: 1.2, Y: 0.97}, G1{X: 1.0, Y: 0.0}]", 2)
bone = dogbone.generate(KINK("G1X1Y1/G1X2"))
self.assertBone(bone, "[G1{X: 0.62, Y: 1.9}, G1{X: 1.0, Y: 1.0}]", 2)

View File

@@ -89,22 +89,28 @@ class TestPathGeom(PathTestBase):
)
self.assertRoughly(
Path.Geom.diffAngle(+math.pi / 4, +0 * math.pi / 4, "CCW") / math.pi, 7 / 4.0
Path.Geom.diffAngle(+math.pi / 4, +0 * math.pi / 4, "CCW") / math.pi,
7 / 4.0,
)
self.assertRoughly(
Path.Geom.diffAngle(+math.pi / 4, +3 * math.pi / 4, "CCW") / math.pi, 2 / 4.0
Path.Geom.diffAngle(+math.pi / 4, +3 * math.pi / 4, "CCW") / math.pi,
2 / 4.0,
)
self.assertRoughly(
Path.Geom.diffAngle(+math.pi / 4, -1 * math.pi / 4, "CCW") / math.pi, 6 / 4.0
Path.Geom.diffAngle(+math.pi / 4, -1 * math.pi / 4, "CCW") / math.pi,
6 / 4.0,
)
self.assertRoughly(
Path.Geom.diffAngle(-math.pi / 4, +0 * math.pi / 4, "CCW") / math.pi, 1 / 4.0
Path.Geom.diffAngle(-math.pi / 4, +0 * math.pi / 4, "CCW") / math.pi,
1 / 4.0,
)
self.assertRoughly(
Path.Geom.diffAngle(-math.pi / 4, +3 * math.pi / 4, "CCW") / math.pi, 4 / 4.0
Path.Geom.diffAngle(-math.pi / 4, +3 * math.pi / 4, "CCW") / math.pi,
4 / 4.0,
)
self.assertRoughly(
Path.Geom.diffAngle(-math.pi / 4, -1 * math.pi / 4, "CCW") / math.pi, 0 / 4.0
Path.Geom.diffAngle(-math.pi / 4, -1 * math.pi / 4, "CCW") / math.pi,
0 / 4.0,
)
def test02(self):
@@ -607,7 +613,9 @@ class TestPathGeom(PathTestBase):
def cmds(center, radius, up=True):
norm = Vector(0, 0, 1) if up else Vector(0, 0, -1)
return Path.Geom.cmdsForEdge(Part.Edge(Part.Circle(center, norm, radius)))[0]
return Path.Geom.cmdsForEdge(Part.Edge(Part.Circle(center, norm, radius)))[
0
]
def cmd(g, end, off):
return Path.Command(

View File

@@ -41,6 +41,7 @@ def createTool(name="t1", diameter=1.75):
}
return PathToolBit.Factory.CreateFromAttrs(attrs, name)
class TestPathHelpers(PathTestBase):
def setUp(self):
self.doc = FreeCAD.newDocument("TestPathUtils")
@@ -133,7 +134,9 @@ class TestPathHelpers(PathTestBase):
self.assertTrue(len(results) == 2)
e1 = results[0]
self.assertTrue(isinstance(e1.Curve, Part.Circle))
self.assertTrue(Path.Geom.pointsCoincide(edge.Curve.Location, e1.Curve.Location))
self.assertTrue(
Path.Geom.pointsCoincide(edge.Curve.Location, e1.Curve.Location)
)
self.assertTrue(edge.Curve.Radius == e1.Curve.Radius)
# filter a 180 degree arc

View File

@@ -0,0 +1,124 @@
# -*- 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 *
# * *
# ***************************************************************************
import Path.Base.Language as PathLanguage
import PathTests.PathTestUtils as PathTestUtils
import math
PI = math.pi
def MNVR(gcode, begin=None):
# 'turns out the replace() isn't really necessary
# leave it here anyway for clarity
return PathLanguage.Maneuver.FromGCode(gcode.replace("/", "\n"), begin)
def INSTR(gcode, begin=None):
return MNVR(gcode, begin).instr[0]
class TestPathLanguage(PathTestUtils.PathTestBase):
"""Unit tests for the Language classes."""
def assertTangents(self, instr, t1):
"""Assert that the two tangent angles are identical"""
t0 = instr.anglesOfTangents()
self.assertRoughly(t0[0], t1[0])
self.assertRoughly(t0[1], t1[1])
def test00(self):
"""Verify G0 instruction construction"""
self.assertEqual(str(MNVR("")), "")
self.assertEqual(len(MNVR("").instr), 0)
self.assertEqual(str(MNVR("G0")), "G0{}")
self.assertEqual(str(MNVR("G0X3")), "G0{'X': 3.0}")
self.assertEqual(str(MNVR("G0X3Y7")), "G0{'X': 3.0, 'Y': 7.0}")
self.assertEqual(
str(MNVR("G0X3Y7/G0Z0")), "G0{'X': 3.0, 'Y': 7.0}\nG0{'Z': 0.0}"
)
self.assertEqual(len(MNVR("G0X3Y7").instr), 1)
self.assertEqual(len(MNVR("G0X3Y7/G0Z0").instr), 2)
self.assertEqual(type(MNVR("G0X3Y7").instr[0]), PathLanguage.MoveStraight)
def test10(self):
"""Verify G1 instruction construction"""
self.assertEqual(str(MNVR("G1")), "G1{}")
self.assertEqual(str(MNVR("G1X3")), "G1{'X': 3.0}")
self.assertEqual(str(MNVR("G1X3Y7")), "G1{'X': 3.0, 'Y': 7.0}")
self.assertEqual(
str(MNVR("G1X3Y7/G1Z0")), "G1{'X': 3.0, 'Y': 7.0}\nG1{'Z': 0.0}"
)
self.assertEqual(len(MNVR("G1X3Y7").instr), 1)
self.assertEqual(len(MNVR("G1X3Y7/G1Z0").instr), 2)
self.assertEqual(type(MNVR("G1X3Y7").instr[0]), PathLanguage.MoveStraight)
def test20(self):
"""Verify G2 instruction construction"""
self.assertEqual(str(MNVR("G2X2Y2I1")), "G2{'I': 1.0, 'X': 2.0, 'Y': 2.0}")
self.assertEqual(len(MNVR("G2X2Y2I1").instr), 1)
self.assertEqual(type(MNVR("G2X2Y2I1").instr[0]), PathLanguage.MoveArcCW)
def test30(self):
"""Verify G3 instruction construction"""
self.assertEqual(str(MNVR("G3X2Y2I1")), "G3{'I': 1.0, 'X': 2.0, 'Y': 2.0}")
self.assertEqual(len(MNVR("G3X2Y2I1").instr), 1)
self.assertEqual(type(MNVR("G3X2Y2I1").instr[0]), PathLanguage.MoveArcCCW)
def test40(self):
"""Verify pathLength correctness"""
self.assertRoughly(MNVR("G1X3").instr[0].pathLength(), 3)
self.assertRoughly(MNVR("G1X-7").instr[0].pathLength(), 7)
self.assertRoughly(MNVR("G1X3").instr[0].pathLength(), 3)
self.assertRoughly(MNVR("G1X3Y4").instr[0].pathLength(), 5)
self.assertRoughly(MNVR("G1X3Y-4").instr[0].pathLength(), 5)
self.assertRoughly(MNVR("G1X-3Y-4").instr[0].pathLength(), 5)
self.assertRoughly(MNVR("G1X-3Y4").instr[0].pathLength(), 5)
self.assertRoughly(MNVR("G2X2I1").instr[0].pathLength(), PI)
self.assertRoughly(MNVR("G2X1Y1I1").instr[0].pathLength(), PI / 2)
self.assertRoughly(MNVR("G3X2I1").instr[0].pathLength(), PI)
self.assertRoughly(MNVR("G3X1Y1I1").instr[0].pathLength(), 3 * PI / 2)
def test50(self):
"""Verify tangents of moves."""
self.assertTangents(INSTR("G1 X0 Y0"), (0, 0)) # by declaration
self.assertTangents(INSTR("G1 X1 Y0"), (0, 0))
self.assertTangents(INSTR("G1 X-1 Y0"), (PI, PI))
self.assertTangents(INSTR("G1 X0 Y1"), (PI / 2, PI / 2))
self.assertTangents(INSTR("G1 X0 Y-1"), (-PI / 2, -PI / 2))
self.assertTangents(INSTR("G1 X1 Y1"), (PI / 4, PI / 4))
self.assertTangents(INSTR("G1 X-1 Y1"), (3 * PI / 4, 3 * PI / 4))
self.assertTangents(INSTR("G1 X-1 Y -1"), (-3 * PI / 4, -3 * PI / 4))
self.assertTangents(INSTR("G1 X1 Y-1"), (-PI / 4, -PI / 4))
self.assertTangents(INSTR("G2 X2 Y0 I1 J0"), (PI / 2, -PI / 2))
self.assertTangents(INSTR("G2 X2 Y2 I1 J1"), (3 * PI / 4, -PI / 4))
self.assertTangents(INSTR("G2 X0 Y-2 I0 J-1"), (0, -PI))
self.assertTangents(INSTR("G3 X2 Y0 I1 J0"), (-PI / 2, PI / 2))
self.assertTangents(INSTR("G3 X2 Y2 I1 J1"), (-PI / 4, 3 * PI / 4))
self.assertTangents(INSTR("G3 X0 Y-2 I0 J-1"), (PI, 0))

View File

@@ -363,9 +363,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertRoughly(33, edge.Curve.Radius)
# the other way around everything's the same except the axis is negative
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getPositiveShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False)
self.assertEqual(1, len(wire.Edges))
edge = wire.Edges[0]
self.assertCoincide(Vector(), edge.Curve.Center)
@@ -394,9 +392,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertTrue(PathOpUtil.isWireClockwise(wire))
# change offset orientation
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getPositiveShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False)
self.assertEqual(8, len(wire.Edges))
self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
self.assertEqual(
@@ -432,9 +428,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
# change offset orientation
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getPositiveShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False)
self.assertEqual(6, len(wire.Edges))
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
self.assertEqual(
@@ -467,9 +461,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis)
# change offset orientation
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getPositiveShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False)
self.assertEqual(6, len(wire.Edges))
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
self.assertEqual(
@@ -494,9 +486,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertRoughly(27, edge.Curve.Radius)
# the other way around everything's the same except the axis is negative
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getNegativeShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
self.assertEqual(1, len(wire.Edges))
edge = wire.Edges[0]
self.assertCoincide(Vector(), edge.Curve.Center)
@@ -518,9 +508,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertFalse(PathOpUtil.isWireClockwise(wire))
# change offset orientation
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getNegativeShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
self.assertEqual(4, len(wire.Edges))
self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
for e in wire.Edges:
@@ -543,9 +531,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertFalse(PathOpUtil.isWireClockwise(wire))
# change offset orientation
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getNegativeShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
self.assertEqual(3, len(wire.Edges))
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
for e in wire.Edges:
@@ -572,9 +558,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase):
self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis)
# change offset orientation
wire = PathOpUtil.offsetWire(
getWire(obj.Tool), getNegativeShape(obj), 3, False
)
wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, False)
self.assertEqual(6, len(wire.Edges))
self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)]))
self.assertEqual(

View File

@@ -39,7 +39,9 @@ class TestPathPreferences(PathTestUtils.PathTestBase):
def test02(self):
"""Path/Post/scripts is part of the posts search path."""
paths = Path.Preferences.searchPathsPost()
self.assertEqual(len([p for p in paths if p.endswith("/Path/Post/scripts/")]), 1)
self.assertEqual(
len([p for p in paths if p.endswith("/Path/Post/scripts/")]), 1
)
def test03(self):
"""Available post processors include linuxcnc, grbl and opensbp."""
@@ -51,7 +53,9 @@ class TestPathPreferences(PathTestUtils.PathTestBase):
def test10(self):
"""Default paths for tools are resolved correctly"""
self.assertTrue(Path.Preferences.pathDefaultToolsPath().endswith("/Path/Tools/"))
self.assertTrue(
Path.Preferences.pathDefaultToolsPath().endswith("/Path/Tools/")
)
self.assertTrue(
Path.Preferences.pathDefaultToolsPath("Bit").endswith("/Path/Tools/Bit")
)

View File

@@ -26,7 +26,7 @@ import Path.Base.SetupSheet as PathSetupSheet
import json
import sys
#Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
# Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
from PathTests.PathTestUtils import PathTestBase
@@ -34,14 +34,15 @@ from PathTests.PathTestUtils import PathTestBase
def refstring(string):
return string.replace(" u'", " '")
class SomeOp (object):
class SomeOp(object):
def __init__(self, obj):
Path.Log.track(obj, type(obj))
obj.addProperty('App::PropertyPercent', 'StepOver', 'Base', 'Some help you are')
obj.addProperty("App::PropertyPercent", "StepOver", "Base", "Some help you are")
@classmethod
def SetupProperties(cls):
return ['StepOver']
return ["StepOver"]
@classmethod
def Create(cls, name, obj=None, parentJob=None):
@@ -51,6 +52,7 @@ class SomeOp (object):
obj.Proxy = SomeOp(obj)
return obj
class TestPathSetupSheet(PathTestBase):
def setUp(self):
self.doc = FreeCAD.newDocument("TestPathSetupSheet")
@@ -321,28 +323,32 @@ class TestPathSetupSheet(PathTestBase):
def test20(self):
"""Verify SetupSheet template op attributes roundtrip."""
opname = 'whoop'
opname = "whoop"
o1 = PathSetupSheet.Create()
PathSetupSheet.RegisterOperation(opname, SomeOp.Create, SomeOp.SetupProperties)
ptt = PathSetupSheet._RegisteredOps[opname].prototype('whoopsy')
pptt = ptt.getProperty('StepOver')
pptt.setupProperty(o1, PathSetupSheet.OpPropertyName(opname, pptt.name), PathSetupSheet.OpPropertyGroup(opname), 75)
ptt = PathSetupSheet._RegisteredOps[opname].prototype("whoopsy")
pptt = ptt.getProperty("StepOver")
pptt.setupProperty(
o1,
PathSetupSheet.OpPropertyName(opname, pptt.name),
PathSetupSheet.OpPropertyGroup(opname),
75,
)
# save setup sheet in json "file"
attrs = o1.Proxy.templateAttributes(False, False, False, False, [opname])
encdd = o1.Proxy.encodeTemplateAttributes(attrs)
j1 = json.dumps({'SetupSheet' : encdd}, sort_keys=True, indent=2)
j1 = json.dumps({"SetupSheet": encdd}, sort_keys=True, indent=2)
# restore setup sheet from json "file"
j2 = json.loads(j1)
o2 = PathSetupSheet.Create()
o2.Proxy.setFromTemplate(j2['SetupSheet'])
o2.Proxy.setFromTemplate(j2["SetupSheet"])
op = SomeOp.Create(opname)
self.assertEqual(op.StepOver, 0)
o2.Proxy.setOperationProperties(op, opname)
self.assertEqual(op.StepOver, 75)

View File

@@ -29,13 +29,13 @@ from PathTests.PathTestUtils import PathTestBase
class TestObject(object):
def __init__(self, orientation, direction, zTop, zBottom):
self.ThreadOrientation = orientation
self.Direction = direction
self.StartDepth = FreeCAD.Units.Quantity(zTop, FreeCAD.Units.Length)
self.FinalDepth = FreeCAD.Units.Quantity(zBottom, FreeCAD.Units.Length)
def radii(internal, major, minor, toolDia, toolCrest):
"""test radii function for simple testing"""
if internal:
@@ -56,13 +56,17 @@ class TestPathThreadMilling(PathTestBase):
self.assertRoughly(have[i], want[i])
def assertSetupInternal(self, obj, c, begin, end):
cmd, zBegin, zEnd = PathThreadMilling.threadSetupInternal(obj, obj.StartDepth.Value, obj.FinalDepth.Value)
cmd, zBegin, zEnd = PathThreadMilling.threadSetupInternal(
obj, obj.StartDepth.Value, obj.FinalDepth.Value
)
self.assertEqual(cmd, c)
self.assertEqual(zBegin, begin)
self.assertEqual(zEnd, end)
def assertSetupExternal(self, obj, c, begin, end):
cmd, zBegin, zEnd = PathThreadMilling.threadSetupExternal(obj, obj.StartDepth.Value, obj.FinalDepth.Value)
cmd, zBegin, zEnd = PathThreadMilling.threadSetupExternal(
obj, obj.StartDepth.Value, obj.FinalDepth.Value
)
self.assertEqual(cmd, c)
self.assertEqual(zBegin, begin)
self.assertEqual(zEnd, end)
@@ -120,30 +124,45 @@ class TestPathThreadMilling(PathTestBase):
hand = PathThreadMilling.RightHand
self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G2", 1, 0)
self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G3", 0, 1)
self.assertSetupInternal(
TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G2", 1, 0
)
self.assertSetupInternal(
TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G3", 0, 1
)
def test41(self):
"""Verify internal left hand thread setup."""
hand = PathThreadMilling.LeftHand
self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G2", 0, 1)
self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G3", 1, 0)
self.assertSetupInternal(
TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G2", 0, 1
)
self.assertSetupInternal(
TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G3", 1, 0
)
def test50(self):
"""Verify exteranl right hand thread setup."""
hand = PathThreadMilling.RightHand
self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G2", 1, 0)
self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G3", 0, 1)
self.assertSetupExternal(
TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G2", 1, 0
)
self.assertSetupExternal(
TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G3", 0, 1
)
def test51(self):
"""Verify exteranl left hand thread setup."""
hand = PathThreadMilling.LeftHand
self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G2", 0, 1)
self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G3", 1, 0)
self.assertSetupExternal(
TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G2", 0, 1
)
self.assertSetupExternal(
TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G3", 1, 0
)

View File

@@ -253,7 +253,9 @@ G21
"""
self.docobj.Path = Path.Path([])
postables = [self.docobj]
gcode: str = postprocessor.export(postables, "gcode.tmp", "--output_all_arguments")
gcode: str = postprocessor.export(
postables, "gcode.tmp", "--output_all_arguments"
)
# The argparse help routine turns out to be sensitive to the
# number of columns in the terminal window that the tests
# are run from. This affects the indenting in the output.
@@ -315,7 +317,9 @@ G21
def test00120(self):
"""Test axis-precision."""
self.compare_third_line("G0 X10 Y20 Z30", "G0 X10.00 Y20.00 Z30.00", "--axis-precision=2")
self.compare_third_line(
"G0 X10 Y20 Z30", "G0 X10.00 Y20.00 Z30.00", "--axis-precision=2"
)
def test00130(self):
"""Test comments."""
@@ -605,26 +609,42 @@ G21
"""Test G10 command Generation."""
self.compare_third_line("G10 L1 P2 Z1.23456", "G10 L1 Z1.235 P2", "")
self.compare_third_line(
"G10 L1 P2 R1.23456 I2.34567 J3.456789 Q3", "G10 L1 I2.346 J3.457 R1.235 P2 Q3", ""
"G10 L1 P2 R1.23456 I2.34567 J3.456789 Q3",
"G10 L1 I2.346 J3.457 R1.235 P2 Q3",
"",
)
self.compare_third_line(
"G10 L2 P3 X1.23456 Y2.34567 Z3.456789", "G10 L2 X1.235 Y2.346 Z3.457 P3", ""
)
self.compare_third_line("G10 L2 P0 X0 Y0 Z0", "G10 L2 X0.000 Y0.000 Z0.000 P0", "")
self.compare_third_line(
"G10 L10 P1 X1.23456 Y2.34567 Z3.456789", "G10 L10 X1.235 Y2.346 Z3.457 P1", ""
"G10 L2 P3 X1.23456 Y2.34567 Z3.456789",
"G10 L2 X1.235 Y2.346 Z3.457 P3",
"",
)
self.compare_third_line(
"G10 L10 P2 R1.23456 I2.34567 J3.456789 Q3", "G10 L10 I2.346 J3.457 R1.235 P2 Q3", ""
"G10 L2 P0 X0 Y0 Z0", "G10 L2 X0.000 Y0.000 Z0.000 P0", ""
)
self.compare_third_line(
"G10 L11 P1 X1.23456 Y2.34567 Z3.456789", "G10 L11 X1.235 Y2.346 Z3.457 P1", ""
"G10 L10 P1 X1.23456 Y2.34567 Z3.456789",
"G10 L10 X1.235 Y2.346 Z3.457 P1",
"",
)
self.compare_third_line(
"G10 L11 P2 R1.23456 I2.34567 J3.456789 Q3", "G10 L11 I2.346 J3.457 R1.235 P2 Q3", ""
"G10 L10 P2 R1.23456 I2.34567 J3.456789 Q3",
"G10 L10 I2.346 J3.457 R1.235 P2 Q3",
"",
)
self.compare_third_line(
"G10 L20 P9 X1.23456 Y2.34567 Z3.456789", "G10 L20 X1.235 Y2.346 Z3.457 P9", ""
"G10 L11 P1 X1.23456 Y2.34567 Z3.456789",
"G10 L11 X1.235 Y2.346 Z3.457 P1",
"",
)
self.compare_third_line(
"G10 L11 P2 R1.23456 I2.34567 J3.456789 Q3",
"G10 L11 I2.346 J3.457 R1.235 P2 Q3",
"",
)
self.compare_third_line(
"G10 L20 P9 X1.23456 Y2.34567 Z3.456789",
"G10 L20 X1.235 Y2.346 Z3.457 P9",
"",
)
def test01170(self):
@@ -779,7 +799,9 @@ G21
Path.Command(
"G52 X1.234567 Y2.345678 Z3.456789 A4.567891 B5.678912 C6.789123 U7.891234 V8.912345 W9.123456"
),
Path.Command("G52 X0 Y0.0 Z0.00 A0.000 B0.0000 C0.00000 U0.000000 V0 W0"),
Path.Command(
"G52 X0 Y0.0 Z0.00 A0.000 B0.0000 C0.00000 U0.000000 V0 W0"
),
],
"""G90
G21
@@ -793,7 +815,9 @@ G52 X0.000 Y0.000 Z0.000 A0.000 B0.000 C0.000 U0.000 V0.000 W0.000
Path.Command(
"G52 X1.234567 Y2.345678 Z3.456789 A4.567891 B5.678912 C6.789123 U7.891234 V8.912345 W9.123456"
),
Path.Command("G52 X0 Y0.0 Z0.00 A0.000 B0.0000 C0.00000 U0.000000 V0 W0"),
Path.Command(
"G52 X0 Y0.0 Z0.00 A0.000 B0.0000 C0.00000 U0.000000 V0 W0"
),
],
"""G90
G20
@@ -916,7 +940,9 @@ G52 X0.0000 Y0.0000 Z0.0000 A0.0000 B0.0000 C0.0000 U0.0000 V0.0000 W0.0000
self.compare_third_line("G64", "G64", "")
self.compare_third_line("G64 P3.456789", "G64 P3.457", "")
self.compare_third_line("G64 P3.456789 Q4.567891", "G64 P3.457 Q4.568", "")
self.compare_third_line("G64 P3.456789 Q4.567891", "G64 P0.1361 Q0.1798", "--inches")
self.compare_third_line(
"G64 P3.456789 Q4.567891", "G64 P0.1361 Q0.1798", "--inches"
)
def test01730(self):
"""Test G73 command Generation."""