From 2a1fc03f826d7790b425d265121117e899fad832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=B6ftner?= Date: Tue, 27 Jun 2023 17:12:04 +0200 Subject: [PATCH 1/2] Path: fix g-code orientation Path.Op.Profile in case effective offset is 0.0 --- src/Mod/Path/CMakeLists.txt | 2 + src/Mod/Path/Path/Op/Profile.py | 5 +- src/Mod/Path/PathTests/TestPathProfile.py | 200 ++++++++++++++++++++++ src/Mod/Path/PathTests/test_profile.fcstd | Bin 0 -> 10086 bytes src/Mod/Path/TestPathApp.py | 4 + 5 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Path/PathTests/TestPathProfile.py create mode 100644 src/Mod/Path/PathTests/test_profile.fcstd diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 1468aeee3ba2..e29026adc6b1 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -279,6 +279,7 @@ SET(PathTests_SRCS PathTests/drill_test1.FCStd PathTests/PathTestUtils.py PathTests/test_adaptive.fcstd + PathTests/test_profile.fcstd PathTests/test_centroid_00.ngc PathTests/test_filenaming.fcstd PathTests/test_geomop.fcstd @@ -306,6 +307,7 @@ SET(PathTests_SRCS PathTests/TestPathOpUtil.py PathTests/TestPathPost.py PathTests/TestPathPreferences.py + PathTests/TestPathProfile.py PathTests/TestPathPropertyBag.py PathTests/TestPathRotationGenerator.py PathTests/TestPathSetupSheet.py diff --git a/src/Mod/Path/Path/Op/Profile.py b/src/Mod/Path/Path/Op/Profile.py index 3eff30c6e360..2d9adaf58834 100644 --- a/src/Mod/Path/Path/Op/Profile.py +++ b/src/Mod/Path/Path/Op/Profile.py @@ -352,7 +352,10 @@ def areaOpPathParams(self, obj, isHole): else: params["orientation"] = 1 - if not obj.UseComp: + offset = obj.OffsetExtra.Value + if obj.UseComp: + offset = self.radius + obj.OffsetExtra.Value + if offset == 0.0: if direction == "CCW": params["orientation"] = 1 else: diff --git a/src/Mod/Path/PathTests/TestPathProfile.py b/src/Mod/Path/PathTests/TestPathProfile.py new file mode 100644 index 000000000000..e6cdab9bea77 --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathProfile.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2023 Robert Schöftner * +# * Copyright (c) 2021 Russell Johnson (russ4262) * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * 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 Part +import Path.Op.Profile as PathProfile +import Path.Main.Job as PathJob +from PathTests.PathTestUtils import PathTestBase +from PathTests.TestPathAdaptive import getGcodeMoves + +if FreeCAD.GuiUp: + import Path.Main.Gui.Job as PathJobGui + import Path.Op.Gui.Profile as PathProfileGui + + +class TestPathProfile(PathTestBase): + """Unit tests for the Adaptive operation.""" + + @classmethod + def setUpClass(cls): + """setUpClass()... + This method is called upon instantiation of this test class. Add code and objects here + that are needed for the duration of the test() methods in this class. In other words, + set up the 'global' test environment here; use the `setUp()` method to set up a 'local' + test environment. + This method does not have access to the class `self` reference, but it + is able to call static methods within this same class. + """ + cls.needsInit = True + + @classmethod + def initClass(cls): + # Open existing FreeCAD document with test geometry + cls.needsInit = False + cls.doc = FreeCAD.open( + FreeCAD.getHomePath() + "Mod/Path/PathTests/test_profile.fcstd" + ) + + # Create Job object, adding geometry objects from file opened above + cls.job = PathJob.Create("Job", [cls.doc.Body], None) + cls.job.GeometryTolerance.Value = 0.001 + if FreeCAD.GuiUp: + cls.job.ViewObject.Proxy = PathJobGui.ViewProvider(cls.job.ViewObject) + + # Instantiate an Profile operation for querying available properties + cls.prototype = PathProfile.Create("Profile") + cls.prototype.Base = [(cls.doc.Body, ["Face4"])] + cls.prototype.Label = "Prototype" + _addViewProvider(cls.prototype) + + cls.doc.recompute() + + @classmethod + def tearDownClass(cls): + """tearDownClass()... + This method is called prior to destruction of this test class. Add code and objects here + that cleanup the test environment after the test() methods in this class have been executed. + This method does not have access to the class `self` reference. This method + is able to call static methods within this same class. + """ + # FreeCAD.Console.PrintMessage("TestPathAdaptive.tearDownClass()\n") + + # Close geometry document without saving + if not cls.needsInit: + FreeCAD.closeDocument(cls.doc.Name) + + # Setup and tear down methods called before and after each unit test + def setUp(self): + """setUp()... + This method is called prior to each `test()` method. Add code and objects here + that are needed for multiple `test()` methods. + """ + if self.needsInit: + self.initClass() + + def tearDown(self): + """tearDown()... + This method is called after each test() method. Add cleanup instructions here. + Such cleanup instructions will likely undo those in the setUp() method. + """ + pass + + # Unit tests + def test00(self): + """test00() Empty test.""" + return + + def test01(self): + """test01() Verify path generated on Face4.""" + + # Instantiate a Profile operation and set Base Geometry + profile = PathProfile.Create("Profile") + profile.Base = [(self.doc.Body, ["Face4"])] # (base, subs_list) + profile.Label = "test01+" + profile.Comment = "test01() Verify path generated on Face4." + + # Set additional operation properties + # setDepthsAndHeights(adaptive) + profile.processCircles = True + profile.processHoles = True + profile.UseComp = True + profile.Direction = "CW" + _addViewProvider(profile) + self.doc.recompute() + + moves = getGcodeMoves(profile.Path.Commands, includeRapids=False) + operationMoves = "; ".join(moves) + FreeCAD.Console.PrintMessage("test01_moves: " + operationMoves + "\n") + + expected_moves = "G1 X7.07 Y7.07 Z10.0; G3 I-7.07 J-7.07 K0.0 X-1.56 Y9.87 Z10.0; G3 I1.56 J-9.87 K0.0 X1.56 Y-9.87 Z10.0; G3 I-1.56 J9.87 K0.0 X7.07 Y7.07 Z10.0; G1 X19.44 Y19.44 Z10.0; G2 I-19.44 J-19.44 K0.0 X3.15 Y-27.32 Z10.0; G2 I-3.15 J27.32 K0.0 X-3.15 Y27.32 Z10.0; G2 I3.15 J-27.32 K0.0 X19.44 Y19.44 Z10.0" + + self.assertTrue(expected_moves == operationMoves, + "expected_moves: {}\noperationMoves: {}".format(expected_moves, operationMoves)) + + def test02(self): + """test02() Verify path generated on Face4.""" + + # Instantiate a Profile operation and set Base Geometry + profile = PathProfile.Create("Profile2") + profile.Base = [(self.doc.Body, ["Face4"])] # (base, subs_list) + profile.Label = "test02+" + profile.Comment = "test02() Verify path generated on Face4." + + # Set additional operation properties + # setDepthsAndHeights(adaptive) + profile.processCircles = True + profile.processHoles = True + profile.UseComp = False + profile.Direction = "CW" + _addViewProvider(profile) + self.doc.recompute() + + moves = getGcodeMoves(profile.Path.Commands, includeRapids=False) + operationMoves = "; ".join(moves) + FreeCAD.Console.PrintMessage("test02_moves: " + operationMoves + "\n") + + expected_moves = "G1 X8.84 Y8.84 Z10.0; G3 I-8.84 J-8.84 K0.0 X-0.0 Y12.5 Z10.0; G3 I0.0 J-12.5 K0.0 X0.0 Y-12.5 Z10.0; G3 I-0.0 J12.5 K0.0 X8.84 Y8.84 Z10.0; G1 X17.68 Y17.68 Z10.0; G2 I-17.68 J-17.68 K0.0 X0.0 Y-25.0 Z10.0; G2 I-0.0 J25.0 K0.0 X-0.0 Y25.0 Z10.0; G2 I0.0 J-25.0 K0.0 X17.68 Y17.68 Z10.0" + + self.assertTrue(expected_moves == operationMoves, + "expected_moves: {}\noperationMoves: {}".format(expected_moves, operationMoves)) + + def test03(self): + """test03() Verify path generated on Face4.""" + + # Instantiate a Profile operation and set Base Geometry + profile = PathProfile.Create("Profile3") + profile.Base = [(self.doc.Body, ["Face4"])] # (base, subs_list) + profile.Label = "test03+" + profile.Comment = "test03() Verify path generated on Face4." + + # Set additional operation properties + # setDepthsAndHeights(adaptive) + profile.processCircles = True + profile.processHoles = True + profile.UseComp = True + profile.Direction = "CW" + profile.OffsetExtra = -profile.OpToolDiameter / 2.0 + _addViewProvider(profile) + self.doc.recompute() + + moves = getGcodeMoves(profile.Path.Commands, includeRapids=False) + operationMoves = "; ".join(moves) + FreeCAD.Console.PrintMessage("test03_moves: " + operationMoves + "\n") + + expected_moves = "G1 X8.84 Y8.84 Z10.0; G3 I-8.84 J-8.84 K0.0 X-0.0 Y12.5 Z10.0; G3 I0.0 J-12.5 K0.0 X0.0 Y-12.5 Z10.0; G3 I-0.0 J12.5 K0.0 X8.84 Y8.84 Z10.0; G1 X17.68 Y17.68 Z10.0; G2 I-17.68 J-17.68 K0.0 X0.0 Y-25.0 Z10.0; G2 I-0.0 J25.0 K0.0 X-0.0 Y25.0 Z10.0; G2 I0.0 J-25.0 K0.0 X17.68 Y17.68 Z10.0" + + self.assertTrue(expected_moves == operationMoves, + "expected_moves: {}\noperationMoves: {}".format(expected_moves, operationMoves)) + + +def _addViewProvider(profileOp): + if FreeCAD.GuiUp: + PathOpGui = PathProfileGui.PathOpGui + cmdRes = PathProfileGui.Command.res + profileOp.ViewObject.Proxy = PathOpGui.ViewProvider( + profileOp.ViewObject, cmdRes + ) + diff --git a/src/Mod/Path/PathTests/test_profile.fcstd b/src/Mod/Path/PathTests/test_profile.fcstd new file mode 100644 index 0000000000000000000000000000000000000000..6e9c058b9d1370da5e141f7e2da2a77384f9ae9c GIT binary patch literal 10086 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 literal 0 HcmV?d00001 diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 88fb9307a8ae..3b905aab845f 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -22,6 +22,8 @@ import TestApp +from PathTests.TestPathProfile import TestPathProfile + from PathTests.TestPathAdaptive import TestPathAdaptive from PathTests.TestPathCore import TestPathCore from PathTests.TestPathDepthParams import depthTestCases @@ -47,6 +49,7 @@ from PathTests.TestPathPost import TestOutputNameSubstitution from PathTests.TestPathPreferences import TestPathPreferences +from PathTests.TestPathProfile import TestPathProfile from PathTests.TestPathPropertyBag import TestPathPropertyBag from PathTests.TestPathRotationGenerator import TestPathRotationGenerator from PathTests.TestPathSetupSheet import TestPathSetupSheet @@ -92,6 +95,7 @@ # False if TestPathPost.__name__ else True False if TestPathPostUtils.__name__ else True False if TestPathPreferences.__name__ else True +False if TestPathProfile.__name__ else True False if TestPathPropertyBag.__name__ else True False if TestPathRotationGenerator.__name__ else True False if TestPathSetupSheet.__name__ else True From c3b5dd60d0222f5316ec5651bbeaaade363c592c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=B6ftner?= Date: Wed, 28 Jun 2023 11:16:24 +0200 Subject: [PATCH 2/2] Hopefully more stable test pattern / path --- src/Mod/Path/PathTests/TestPathProfile.py | 72 +++++++++++++++------- src/Mod/Path/PathTests/test_profile.fcstd | Bin 10086 -> 17143 bytes 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/PathTests/TestPathProfile.py b/src/Mod/Path/PathTests/TestPathProfile.py index e6cdab9bea77..7de6c6832888 100644 --- a/src/Mod/Path/PathTests/TestPathProfile.py +++ b/src/Mod/Path/PathTests/TestPathProfile.py @@ -66,7 +66,7 @@ def initClass(cls): # Instantiate an Profile operation for querying available properties cls.prototype = PathProfile.Create("Profile") - cls.prototype.Base = [(cls.doc.Body, ["Face4"])] + cls.prototype.Base = [(cls.doc.Body, ["Face18"])] cls.prototype.Label = "Prototype" _addViewProvider(cls.prototype) @@ -108,13 +108,13 @@ def test00(self): return def test01(self): - """test01() Verify path generated on Face4.""" + """test01() Verify path generated on Face18, outside, with tool compensation.""" # Instantiate a Profile operation and set Base Geometry - profile = PathProfile.Create("Profile") - profile.Base = [(self.doc.Body, ["Face4"])] # (base, subs_list) + profile = PathProfile.Create("Profile1") + profile.Base = [(self.doc.Body, ["Face18"])] # (base, subs_list) profile.Label = "test01+" - profile.Comment = "test01() Verify path generated on Face4." + profile.Comment = "test01() Verify path generated on Face18, outside, with tool compensation." # Set additional operation properties # setDepthsAndHeights(adaptive) @@ -127,21 +127,31 @@ def test01(self): moves = getGcodeMoves(profile.Path.Commands, includeRapids=False) operationMoves = "; ".join(moves) - FreeCAD.Console.PrintMessage("test01_moves: " + operationMoves + "\n") - - expected_moves = "G1 X7.07 Y7.07 Z10.0; G3 I-7.07 J-7.07 K0.0 X-1.56 Y9.87 Z10.0; G3 I1.56 J-9.87 K0.0 X1.56 Y-9.87 Z10.0; G3 I-1.56 J9.87 K0.0 X7.07 Y7.07 Z10.0; G1 X19.44 Y19.44 Z10.0; G2 I-19.44 J-19.44 K0.0 X3.15 Y-27.32 Z10.0; G2 I-3.15 J27.32 K0.0 X-3.15 Y27.32 Z10.0; G2 I3.15 J-27.32 K0.0 X19.44 Y19.44 Z10.0" - + #FreeCAD.Console.PrintMessage("test01_moves: " + operationMoves + "\n") + + + expected_moves = \ + "G1 X16.47 Y16.47 Z10.0; G3 I-2.48 J-2.48 K0.0 X13.93 Y17.5 Z10.0; " \ + "G1 X-13.93 Y17.5 Z10.0; G3 I-0.06 J-3.51 K0.0 X-17.5 Y13.93 Z10.0; " \ + "G1 X-17.5 Y-13.93 Z10.0; G3 I3.51 J-0.06 K0.0 X-13.93 Y-17.5 Z10.0; " \ + "G1 X13.93 Y-17.5 Z10.0; G3 I0.06 J3.51 K0.0 X17.5 Y-13.93 Z10.0; " \ + "G1 X17.5 Y13.93 Z10.0; G3 I-3.51 J0.06 K0.0 X16.47 Y16.47 Z10.0; " \ + "G1 X23.55 Y23.54 Z10.0; G2 I-9.55 J-9.54 K0.0 X27.5 Y14.1 Z10.0; " \ + "G1 X27.5 Y-14.0 Z10.0; G2 I-13.5 J0.0 K0.0 X14.1 Y-27.5 Z10.0; " \ + "G1 X-14.0 Y-27.5 Z10.0; G2 I0.0 J13.5 K0.0 X-27.5 Y-14.1 Z10.0; " \ + "G1 X-27.5 Y14.0 Z10.0; G2 I13.5 J-0.0 K0.0 X-14.1 Y27.5 Z10.0; " \ + "G1 X14.0 Y27.5 Z10.0; G2 I-0.0 J-13.5 K0.0 X23.55 Y23.54 Z10.0" self.assertTrue(expected_moves == operationMoves, "expected_moves: {}\noperationMoves: {}".format(expected_moves, operationMoves)) def test02(self): - """test02() Verify path generated on Face4.""" + """test02() Verify path generated on Face18, outside, without compensation.""" # Instantiate a Profile operation and set Base Geometry profile = PathProfile.Create("Profile2") - profile.Base = [(self.doc.Body, ["Face4"])] # (base, subs_list) + profile.Base = [(self.doc.Body, ["Face18"])] # (base, subs_list) profile.Label = "test02+" - profile.Comment = "test02() Verify path generated on Face4." + profile.Comment = "test02() Verify path generated on Face18, outside, without compensation." # Set additional operation properties # setDepthsAndHeights(adaptive) @@ -154,21 +164,32 @@ def test02(self): moves = getGcodeMoves(profile.Path.Commands, includeRapids=False) operationMoves = "; ".join(moves) - FreeCAD.Console.PrintMessage("test02_moves: " + operationMoves + "\n") - - expected_moves = "G1 X8.84 Y8.84 Z10.0; G3 I-8.84 J-8.84 K0.0 X-0.0 Y12.5 Z10.0; G3 I0.0 J-12.5 K0.0 X0.0 Y-12.5 Z10.0; G3 I-0.0 J12.5 K0.0 X8.84 Y8.84 Z10.0; G1 X17.68 Y17.68 Z10.0; G2 I-17.68 J-17.68 K0.0 X0.0 Y-25.0 Z10.0; G2 I-0.0 J25.0 K0.0 X-0.0 Y25.0 Z10.0; G2 I0.0 J-25.0 K0.0 X17.68 Y17.68 Z10.0" + #FreeCAD.Console.PrintMessage("test02_moves: " + operationMoves + "\n") + + expected_moves = "G1 X18.24 Y18.24 Z10.0; G3 I-4.24 J-4.24 K0.0 X14.0 Y20.0 Z10.0; " \ + "G1 X-14.0 Y20.0 Z10.0; G3 I0.0 J-6.0 K0.0 X-20.0 Y14.0 Z10.0; " \ + "G1 X-20.0 Y-14.0 Z10.0; G3 I6.0 J0.0 K0.0 X-14.0 Y-20.0 Z10.0; " \ + "G1 X14.0 Y-20.0 Z10.0; G3 I-0.0 J6.0 K0.0 X20.0 Y-14.0 Z10.0; " \ + "G1 X20.0 Y14.0 Z10.0; G3 I-6.0 J-0.0 K0.0 X18.24 Y18.24 Z10.0; " \ + "G1 X21.78 Y21.78 Z10.0; G2 I-7.78 J-7.78 K0.0 X25.0 Y14.0 Z10.0; " \ + "G1 X25.0 Y-14.0 Z10.0; G2 I-11.0 J0.0 K0.0 X14.0 Y-25.0 Z10.0; " \ + "G1 X-14.0 Y-25.0 Z10.0; G2 I0.0 J11.0 K0.0 X-25.0 Y-14.0 Z10.0; " \ + "G1 X-25.0 Y14.0 Z10.0; G2 I11.0 J-0.0 K0.0 X-14.0 Y25.0 Z10.0; " \ + "G1 X14.0 Y25.0 Z10.0; G2 I-0.0 J-11.0 K0.0 X21.78 Y21.78 Z10.0" self.assertTrue(expected_moves == operationMoves, "expected_moves: {}\noperationMoves: {}".format(expected_moves, operationMoves)) def test03(self): - """test03() Verify path generated on Face4.""" + """test03() Verify path generated on Face18, outside, + with compensation and extra offset -radius.""" # Instantiate a Profile operation and set Base Geometry profile = PathProfile.Create("Profile3") - profile.Base = [(self.doc.Body, ["Face4"])] # (base, subs_list) + profile.Base = [(self.doc.Body, ["Face18"])] # (base, subs_list) profile.Label = "test03+" - profile.Comment = "test03() Verify path generated on Face4." + profile.Comment = "test03() Verify path generated on Face4, " \ + "with compensation and extra offset -radius" # Set additional operation properties # setDepthsAndHeights(adaptive) @@ -182,9 +203,18 @@ def test03(self): moves = getGcodeMoves(profile.Path.Commands, includeRapids=False) operationMoves = "; ".join(moves) - FreeCAD.Console.PrintMessage("test03_moves: " + operationMoves + "\n") - - expected_moves = "G1 X8.84 Y8.84 Z10.0; G3 I-8.84 J-8.84 K0.0 X-0.0 Y12.5 Z10.0; G3 I0.0 J-12.5 K0.0 X0.0 Y-12.5 Z10.0; G3 I-0.0 J12.5 K0.0 X8.84 Y8.84 Z10.0; G1 X17.68 Y17.68 Z10.0; G2 I-17.68 J-17.68 K0.0 X0.0 Y-25.0 Z10.0; G2 I-0.0 J25.0 K0.0 X-0.0 Y25.0 Z10.0; G2 I0.0 J-25.0 K0.0 X17.68 Y17.68 Z10.0" + #FreeCAD.Console.PrintMessage("test03_moves: " + operationMoves + "\n") + + expected_moves = "G1 X18.24 Y18.24 Z10.0; G3 I-4.24 J-4.24 K0.0 X14.0 Y20.0 Z10.0; " \ + "G1 X-14.0 Y20.0 Z10.0; G3 I0.0 J-6.0 K0.0 X-20.0 Y14.0 Z10.0; " \ + "G1 X-20.0 Y-14.0 Z10.0; G3 I6.0 J0.0 K0.0 X-14.0 Y-20.0 Z10.0; " \ + "G1 X14.0 Y-20.0 Z10.0; G3 I-0.0 J6.0 K0.0 X20.0 Y-14.0 Z10.0; " \ + "G1 X20.0 Y14.0 Z10.0; G3 I-6.0 J-0.0 K0.0 X18.24 Y18.24 Z10.0; " \ + "G1 X21.78 Y21.78 Z10.0; G2 I-7.78 J-7.78 K0.0 X25.0 Y14.0 Z10.0; " \ + "G1 X25.0 Y-14.0 Z10.0; G2 I-11.0 J0.0 K0.0 X14.0 Y-25.0 Z10.0; " \ + "G1 X-14.0 Y-25.0 Z10.0; G2 I0.0 J11.0 K0.0 X-25.0 Y-14.0 Z10.0; " \ + "G1 X-25.0 Y14.0 Z10.0; G2 I11.0 J-0.0 K0.0 X-14.0 Y25.0 Z10.0; " \ + "G1 X14.0 Y25.0 Z10.0; G2 I-0.0 J-11.0 K0.0 X21.78 Y21.78 Z10.0" self.assertTrue(expected_moves == operationMoves, "expected_moves: {}\noperationMoves: {}".format(expected_moves, operationMoves)) diff --git a/src/Mod/Path/PathTests/test_profile.fcstd b/src/Mod/Path/PathTests/test_profile.fcstd index 6e9c058b9d1370da5e141f7e2da2a77384f9ae9c..77b0fd72a09c7eecbe6615ed1ff2e4455cb504a8 100644 GIT binary patch literal 17143 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