From 7992f3ed00c96b82b04ae23f03a0961343bec9f2 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 4 Aug 2022 22:24:06 -0700 Subject: [PATCH 01/33] Renamed Path c++ python module to PathApp --- src/Mod/Path/App/AppPath.cpp | 6 +++--- src/Mod/Path/App/AppPathPy.cpp | 32 ++++++++++++++++---------------- src/Mod/Path/App/CMakeLists.txt | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index 1e7a055943..fa1a6af835 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -52,12 +52,12 @@ #include "VoronoiVertexPy.h" -namespace Path { +namespace PathApp { extern PyObject* initModule(); } /* Python entry */ -PyMOD_INIT_FUNC(Path) +PyMOD_INIT_FUNC(PathApp) { // load dependent module try { @@ -68,7 +68,7 @@ PyMOD_INIT_FUNC(Path) PyMOD_Return(nullptr); } - PyObject* pathModule = Path::initModule(); + PyObject* pathModule = PathApp::initModule(); Base::Console().Log("Loading Path module... done\n"); Py::Object module(pathModule); diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index ac33305e21..0ee2ca0a24 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -90,7 +90,7 @@ PyErr_SetString(Base::PyExc_FC_GeneralError,e); \ } throw Py::Exception(); -namespace Path { +namespace PathApp { class VoronoiModule : public Py::ExtensionModule { public: @@ -106,7 +106,7 @@ namespace Path { VoronoiModule voronoi; public: - Module() : Py::ExtensionModule("Path") + Module() : Py::ExtensionModule("PathApp") { add_varargs_method("write",&Module::write, "write(object,filename): Exports a given path object to a GCode file" @@ -161,8 +161,8 @@ namespace Path { if (PyObject_TypeCheck(pObj, &(App::DocumentObjectPy::Type))) { App::DocumentObject* obj = static_cast(pObj)->getDocumentObjectPtr(); - if (obj->getTypeId().isDerivedFrom(Base::Type::fromName("Path::Feature"))) { - const Toolpath& path = static_cast(obj)->Path.getValue(); + if (obj->getTypeId().isDerivedFrom(Base::Type::fromName("PathApp::Feature"))) { + const Path::Toolpath& path = static_cast(obj)->Path.getValue(); std::string gcode = path.toGCode(); Base::ofstream ofile(file); ofile << gcode; @@ -204,9 +204,9 @@ namespace Path { std::stringstream buffer; buffer << filestr.rdbuf(); std::string gcode = buffer.str(); - Toolpath path; + Path::Toolpath path; path.setFromGCode(gcode); - Path::Feature *object = static_cast(pcDoc->addObject("Path::Feature",file.fileNamePure().c_str())); + Path::Feature *object = static_cast(pcDoc->addObject("PathApp::Feature",file.fileNamePure().c_str())); object->Path.setValue(path); pcDoc->recompute(); } @@ -222,15 +222,15 @@ namespace Path { { PyObject *pcObj; char *name = "Path"; - if (!PyArg_ParseTuple(args.ptr(), "O!|s", &(PathPy::Type), &pcObj, &name)) + if (!PyArg_ParseTuple(args.ptr(), "O!|s", &(Path::PathPy::Type), &pcObj, &name)) throw Py::Exception(); try { App::Document *pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) pcDoc = App::GetApplication().newDocument(); - PathPy* pPath = static_cast(pcObj); - Path::Feature *pcFeature = static_cast(pcDoc->addObject("Path::Feature", name)); + Path::PathPy* pPath = static_cast(pcObj); + Path::Feature *pcFeature = static_cast(pcDoc->addObject("PathApp::Feature", name)); Path::Toolpath* pa = pPath->getToolpathPtr(); if (!pa) { throw Py::Exception(PyExc_ReferenceError, "object doesn't reference a valid path"); @@ -313,7 +313,7 @@ namespace Path { } ExpEdges.Next(); } - return Py::asObject(new PathPy(new Path::Toolpath(result))); + return Py::asObject(new Path::PathPy(new Path::Toolpath(result))); } else { throw Py::TypeError("the given shape must be a wire"); } @@ -365,13 +365,13 @@ namespace Path { try { gp_Pnt pend; - std::unique_ptr path(new Toolpath); - Area::toPath(*path,shapes,start?&pstart:nullptr, &pend, + std::unique_ptr path(new Path::Toolpath); + Path::Area::toPath(*path,shapes,start?&pstart:nullptr, &pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_PATH)); if (!Base::asBoolean(return_end)) - return Py::asObject(new PathPy(path.release())); + return Py::asObject(new Path::PathPy(path.release())); Py::Tuple tuple(2); - tuple.setItem(0, Py::asObject(new PathPy(path.release()))); + tuple.setItem(0, Py::asObject(new Path::PathPy(path.release()))); tuple.setItem(1, Py::asObject(new Base::VectorPy(Base::Vector3d(pend.X(),pend.Y(),pend.Z())))); return tuple; } PATH_CATCH @@ -419,8 +419,8 @@ namespace Path { } try { - bool need_arc_plane = arc_plane==Area::ArcPlaneAuto; - std::list wires = Area::sortWires(shapes, start != nullptr, &pstart, + bool need_arc_plane = arc_plane == Path::Area::ArcPlaneAuto; + std::list wires = Path::Area::sortWires(shapes, start != nullptr, &pstart, &pend, nullptr, &arc_plane, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); Py::List list; for(auto &wire : wires) { diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index fa9a52c302..5a0247de6e 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -163,7 +163,7 @@ if(FREECAD_USE_PCH) endif(FREECAD_USE_PCH) -SET_BIN_DIR(Path Path /Mod/Path) +SET_BIN_DIR(Path PathApp /Mod/Path) SET_PYTHON_PREFIX_SUFFIX(Path) INSTALL(TARGETS Path DESTINATION ${CMAKE_INSTALL_LIBDIR}) From 7e643c563bd07502065a56a0edaf6470f5256db8 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 4 Aug 2022 22:32:59 -0700 Subject: [PATCH 02/33] Added python based Path module and pull in PathApp namespace --- src/Mod/Path/CMakeLists.txt | 12 ++++++++++++ src/Mod/Path/Path/__init__.py | 1 + 2 files changed, 13 insertions(+) create mode 100644 src/Mod/Path/Path/__init__.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index c6899e5e54..4b738d373c 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -25,6 +25,10 @@ INSTALL( Mod/Path ) +SET(PathPython_SRCS + Path/__init__.py +) + SET(PathScripts_SRCS PathScripts/drillableLib.py PathScripts/PathAdaptive.py @@ -313,6 +317,7 @@ SET(Path_Data SET(all_files ${PathScripts_SRCS} + ${PathPython_SRCS} ${Generator_SRCS} ${PathScripts_post_SRCS} ${PathPythonGui_SRCS} @@ -347,6 +352,13 @@ INSTALL( Mod/Path/PathScripts ) +INSTALL( + FILES + ${PathPython_SRCS} + DESTINATION + Mod/Path/Path +) + INSTALL( FILES ${Generator_SRCS} diff --git a/src/Mod/Path/Path/__init__.py b/src/Mod/Path/Path/__init__.py new file mode 100644 index 0000000000..fc13dc215a --- /dev/null +++ b/src/Mod/Path/Path/__init__.py @@ -0,0 +1 @@ +from PathApp import * From 233c92b79ea83f97b06922cdbb6c9e2eb723ca5e Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 9 Aug 2022 20:06:40 -0700 Subject: [PATCH 03/33] Moved PathLog to Path.Log and import it by default --- src/Mod/Path/CMakeLists.txt | 2 +- src/Mod/Path/Generators/drill_generator.py | 17 +- src/Mod/Path/Generators/helix_generator.py | 15 +- src/Mod/Path/Generators/rotation_generator.py | 21 ++- .../Generators/threadmilling_generator.py | 13 +- .../Path/Generators/toolchange_generator.py | 11 +- .../{PathScripts/PathLog.py => Path/Log.py} | 0 src/Mod/Path/Path/__init__.py | 2 + src/Mod/Path/PathCommands.py | 4 +- src/Mod/Path/PathFeedRate.py | 8 +- src/Mod/Path/PathMachineState.py | 8 +- src/Mod/Path/PathScripts/PathAdaptive.py | 21 ++- src/Mod/Path/PathScripts/PathAreaOp.py | 55 +++--- src/Mod/Path/PathScripts/PathArray.py | 5 +- src/Mod/Path/PathScripts/PathCamoticsGui.py | 22 +-- .../Path/PathScripts/PathCircularHoleBase.py | 22 +-- .../PathScripts/PathCircularHoleBaseGui.py | 24 +-- src/Mod/Path/PathScripts/PathCollision.py | 6 +- src/Mod/Path/PathScripts/PathCustom.py | 7 +- src/Mod/Path/PathScripts/PathDeburr.py | 22 +-- src/Mod/Path/PathScripts/PathDeburrGui.py | 8 +- .../Path/PathScripts/PathDressupAxisMap.py | 7 +- .../Path/PathScripts/PathDressupDogbone.py | 117 +++++++------ .../PathScripts/PathDressupHoldingTags.py | 139 ++++++++------- .../Path/PathScripts/PathDressupLeadInOut.py | 39 ++--- .../PathScripts/PathDressupPathBoundary.py | 31 ++-- .../PathScripts/PathDressupPathBoundaryGui.py | 24 +-- .../Path/PathScripts/PathDressupRampEntry.py | 61 ++++--- src/Mod/Path/PathScripts/PathDressupTag.py | 36 ++-- src/Mod/Path/PathScripts/PathDressupTagGui.py | 44 ++--- .../PathScripts/PathDressupTagPreferences.py | 8 +- .../Path/PathScripts/PathDressupZCorrect.py | 25 ++- src/Mod/Path/PathScripts/PathDrilling.py | 17 +- src/Mod/Path/PathScripts/PathDrillingGui.py | 12 +- src/Mod/Path/PathScripts/PathEngrave.py | 21 ++- src/Mod/Path/PathScripts/PathEngraveBase.py | 21 ++- src/Mod/Path/PathScripts/PathEngraveGui.py | 18 +- .../Path/PathScripts/PathFeatureExtensions.py | 58 +++---- .../PathScripts/PathFeatureExtensionsGui.py | 76 ++++---- src/Mod/Path/PathScripts/PathGeom.py | 57 +++--- src/Mod/Path/PathScripts/PathGetPoint.py | 6 +- src/Mod/Path/PathScripts/PathGui.py | 30 ++-- src/Mod/Path/PathScripts/PathGuiInit.py | 12 +- src/Mod/Path/PathScripts/PathHelix.py | 13 +- src/Mod/Path/PathScripts/PathHelixGui.py | 12 +- .../Path/PathScripts/PathIconViewProvider.py | 16 +- src/Mod/Path/PathScripts/PathJob.py | 36 ++-- src/Mod/Path/PathScripts/PathJobCmd.py | 8 +- src/Mod/Path/PathScripts/PathJobDlg.py | 14 +- src/Mod/Path/PathScripts/PathJobGui.py | 108 ++++++------ src/Mod/Path/PathScripts/PathMillFace.py | 34 ++-- src/Mod/Path/PathScripts/PathMillFaceGui.py | 10 +- src/Mod/Path/PathScripts/PathOp.py | 51 +++--- src/Mod/Path/PathScripts/PathOpGui.py | 68 ++++---- src/Mod/Path/PathScripts/PathOpTools.py | 32 ++-- src/Mod/Path/PathScripts/PathPocket.py | 50 +++--- src/Mod/Path/PathScripts/PathPocketBase.py | 23 +-- src/Mod/Path/PathScripts/PathPocketBaseGui.py | 8 +- src/Mod/Path/PathScripts/PathPocketGui.py | 8 +- src/Mod/Path/PathScripts/PathPocketShape.py | 38 ++-- .../Path/PathScripts/PathPocketShapeGui.py | 8 +- src/Mod/Path/PathScripts/PathPost.py | 76 ++++---- src/Mod/Path/PathScripts/PathPostProcessor.py | 6 +- src/Mod/Path/PathScripts/PathPreferences.py | 20 +-- .../PathScripts/PathPreferencesAdvanced.py | 10 +- .../PathScripts/PathPreferencesPathJob.py | 3 +- src/Mod/Path/PathScripts/PathProbe.py | 9 +- src/Mod/Path/PathScripts/PathProbeGui.py | 8 +- src/Mod/Path/PathScripts/PathProfile.py | 135 ++++++++------- src/Mod/Path/PathScripts/PathProperty.py | 6 +- src/Mod/Path/PathScripts/PathPropertyBag.py | 8 +- .../Path/PathScripts/PathPropertyBagGui.py | 32 ++-- .../Path/PathScripts/PathPropertyEditor.py | 8 +- src/Mod/Path/PathScripts/PathSanity.py | 10 +- src/Mod/Path/PathScripts/PathSelection.py | 10 +- src/Mod/Path/PathScripts/PathSetupSheet.py | 22 +-- src/Mod/Path/PathScripts/PathSetupSheetGui.py | 30 ++-- .../PathScripts/PathSetupSheetOpPrototype.py | 6 +- .../PathSetupSheetOpPrototypeGui.py | 10 +- src/Mod/Path/PathScripts/PathSimulatorGui.py | 3 +- src/Mod/Path/PathScripts/PathSlot.py | 115 ++++++------- src/Mod/Path/PathScripts/PathStock.py | 38 ++-- src/Mod/Path/PathScripts/PathSurface.py | 93 +++++----- src/Mod/Path/PathScripts/PathSurfaceGui.py | 8 +- .../Path/PathScripts/PathSurfaceSupport.py | 148 ++++++++-------- src/Mod/Path/PathScripts/PathThreadMilling.py | 47 +++-- .../Path/PathScripts/PathThreadMillingGui.py | 14 +- src/Mod/Path/PathScripts/PathToolBit.py | 56 +++--- src/Mod/Path/PathScripts/PathToolBitCmd.py | 12 +- src/Mod/Path/PathScripts/PathToolBitEdit.py | 38 ++-- src/Mod/Path/PathScripts/PathToolBitGui.py | 24 +-- .../Path/PathScripts/PathToolBitLibraryCmd.py | 8 +- .../Path/PathScripts/PathToolBitLibraryGui.py | 70 ++++---- .../Path/PathScripts/PathToolController.py | 29 ++-- .../Path/PathScripts/PathToolControllerGui.py | 18 +- src/Mod/Path/PathScripts/PathToolEdit.py | 27 ++- .../Path/PathScripts/PathToolLibraryEditor.py | 9 +- .../PathScripts/PathToolLibraryManager.py | 15 +- src/Mod/Path/PathScripts/PathUtil.py | 20 +-- src/Mod/Path/PathScripts/PathUtils.py | 45 +++-- src/Mod/Path/PathScripts/PathUtilsGui.py | 8 +- src/Mod/Path/PathScripts/PathVcarve.py | 21 ++- src/Mod/Path/PathScripts/PathVcarveGui.py | 22 +-- src/Mod/Path/PathScripts/PathWaterline.py | 125 +++++++------- src/Mod/Path/PathScripts/PathWaterlineGui.py | 8 +- src/Mod/Path/PathScripts/drillableLib.py | 58 +++---- src/Mod/Path/PathScripts/post/dxf_post.py | 23 ++- src/Mod/Path/PathScripts/post/example_pre.py | 21 ++- src/Mod/Path/PathScripts/post/gcode_pre.py | 22 +-- src/Mod/Path/PathTests/TestCentroidPost.py | 6 +- src/Mod/Path/PathTests/TestGrblPost.py | 6 +- src/Mod/Path/PathTests/TestLinuxCNCPost.py | 7 +- src/Mod/Path/PathTests/TestMach3Mach4Post.py | 6 +- src/Mod/Path/PathTests/TestPathDeburr.py | 5 +- .../Path/PathTests/TestPathDrillGenerator.py | 9 +- src/Mod/Path/PathTests/TestPathDrillable.py | 9 +- src/Mod/Path/PathTests/TestPathHelix.py | 12 +- .../Path/PathTests/TestPathHelixGenerator.py | 5 +- src/Mod/Path/PathTests/TestPathLog.py | 162 +++++++++--------- src/Mod/Path/PathTests/TestPathOpTools.py | 6 +- src/Mod/Path/PathTests/TestPathPost.py | 5 +- .../PathTests/TestPathRotationGenerator.py | 21 ++- .../PathTests/TestPathToolChangeGenerator.py | 7 +- .../PathTests/TestRefactoredCentroidPost.py | 6 +- .../Path/PathTests/TestRefactoredGrblPost.py | 6 +- .../PathTests/TestRefactoredLinuxCNCPost.py | 6 +- .../PathTests/TestRefactoredMach3Mach4Post.py | 6 +- .../Path/PathTests/TestRefactoredTestPost.py | 6 +- 128 files changed, 1738 insertions(+), 1801 deletions(-) rename src/Mod/Path/{PathScripts/PathLog.py => Path/Log.py} (100%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 4b738d373c..fce6bdd8f9 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -27,6 +27,7 @@ INSTALL( SET(PathPython_SRCS Path/__init__.py + Path/Log.py ) SET(PathScripts_SRCS @@ -78,7 +79,6 @@ SET(PathScripts_SRCS PathScripts/PathJobCmd.py PathScripts/PathJobDlg.py PathScripts/PathJobGui.py - PathScripts/PathLog.py PathScripts/PathMillFace.py PathScripts/PathMillFaceGui.py PathScripts/PathOp.py diff --git a/src/Mod/Path/Generators/drill_generator.py b/src/Mod/Path/Generators/drill_generator.py index 9b82a4225a..72ebda1b3c 100644 --- a/src/Mod/Path/Generators/drill_generator.py +++ b/src/Mod/Path/Generators/drill_generator.py @@ -21,7 +21,6 @@ # *************************************************************************** -import PathScripts.PathLog as PathLog import Path import numpy @@ -32,10 +31,10 @@ __doc__ = "Generates the drilling toolpath for a single spotshape" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def generate(edge, dwelltime=0.0, peckdepth=0.0, repeat=1, retractheight=None, chipBreak=False): @@ -62,12 +61,12 @@ def generate(edge, dwelltime=0.0, peckdepth=0.0, repeat=1, retractheight=None, c startPoint = edge.Vertexes[0].Point endPoint = edge.Vertexes[1].Point - PathLog.debug(startPoint) - PathLog.debug(endPoint) + Path.Log.debug(startPoint) + Path.Log.debug(endPoint) - PathLog.debug(numpy.isclose(startPoint.sub(endPoint).x, 0, rtol=1e-05, atol=1e-06)) - PathLog.debug(numpy.isclose(startPoint.sub(endPoint).y, 0, rtol=1e-05, atol=1e-06)) - PathLog.debug(endPoint) + Path.Log.debug(numpy.isclose(startPoint.sub(endPoint).x, 0, rtol=1e-05, atol=1e-06)) + Path.Log.debug(numpy.isclose(startPoint.sub(endPoint).y, 0, rtol=1e-05, atol=1e-06)) + Path.Log.debug(endPoint) if dwelltime > 0.0 and peckdepth > 0.0: raise ValueError("Peck and Dwell cannot be used together") diff --git a/src/Mod/Path/Generators/helix_generator.py b/src/Mod/Path/Generators/helix_generator.py index ecd63d8457..260cedd6de 100644 --- a/src/Mod/Path/Generators/helix_generator.py +++ b/src/Mod/Path/Generators/helix_generator.py @@ -23,7 +23,6 @@ from numpy import ceil, linspace, isclose import Path -import PathScripts.PathLog as PathLog __title__ = "Helix Path Generator" __author__ = "sliptonic (Brad Collette)" @@ -33,10 +32,10 @@ __contributors__ = "russ4262 (Russell Johnson), Lorenz Hüdepohl" if True: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def generate( @@ -56,7 +55,7 @@ def generate( startPoint = edge.Vertexes[0].Point endPoint = edge.Vertexes[1].Point - PathLog.track( + Path.Log.track( "(helix: <{}, {}>\n hole radius {}\n inner radius {}\n step over {}\n start point {}\n end point {}\n step_down {}\n tool diameter {}\n direction {}\n startAt {})".format( startPoint.x, startPoint.y, @@ -121,7 +120,7 @@ def generate( raise ValueError("start point is below end point") if hole_radius <= tool_diameter: - PathLog.debug("(single helix mode)\n") + Path.Log.debug("(single helix mode)\n") radii = [hole_radius - tool_diameter / 2] if radii[0] <= 0: raise ValueError( @@ -132,7 +131,7 @@ def generate( outer_radius = hole_radius else: # inner_radius > 0: - PathLog.debug("(annulus mode / full hole)\n") + Path.Log.debug("(annulus mode / full hole)\n") outer_radius = hole_radius - tool_diameter / 2 step_radius = inner_radius + tool_diameter / 2 if abs((outer_radius - step_radius) / step_over_distance) < 1e-5: @@ -141,7 +140,7 @@ def generate( nr = max(int(ceil((outer_radius - inner_radius) / step_over_distance)), 2) radii = linspace(outer_radius, step_radius, nr) - PathLog.debug("Radii: {}".format(radii)) + Path.Log.debug("Radii: {}".format(radii)) # calculate the number of full and partial turns required # Each full turn is two 180 degree arcs. Zsteps is equally spaced step # down values diff --git a/src/Mod/Path/Generators/rotation_generator.py b/src/Mod/Path/Generators/rotation_generator.py index a2bc44f845..27518a7f87 100644 --- a/src/Mod/Path/Generators/rotation_generator.py +++ b/src/Mod/Path/Generators/rotation_generator.py @@ -25,7 +25,6 @@ # The main generator function should be extended to include other flavors of 3+2 -import PathScripts.PathLog as PathLog import math import Path import FreeCAD @@ -38,10 +37,10 @@ __doc__ = "Generates the rotation toolpath" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class refAxis(Enum): @@ -56,7 +55,7 @@ def relAngle(vec, ref): relative angle. The result is returned in degrees (plus or minus) """ - PathLog.debug("vec: {} ref: {}".format(vec, ref)) + Path.Log.debug("vec: {} ref: {}".format(vec, ref)) norm = vec * 1 # copy vec so we don't alter original if ref == refAxis.x: @@ -72,7 +71,7 @@ def relAngle(vec, ref): rot = FreeCAD.Rotation(norm, ref) ang = math.degrees(rot.Angle) angle = ang * plane.dot(rot.Axis) - PathLog.debug("relative ang: {}".format(angle)) + Path.Log.debug("relative ang: {}".format(angle)) return angle @@ -83,7 +82,7 @@ def __getCRotation(normalVector, cMin=-360, cMax=360): with either the +y or -y axis. multiple poses may be possible. Returns a list of all valid poses """ - PathLog.debug("normalVector: {} cMin: {} cMax: {}".format(normalVector, cMin, cMax)) + Path.Log.debug("normalVector: {} cMin: {} cMax: {}".format(normalVector, cMin, cMax)) angle = relAngle(normalVector, refAxis.y) @@ -152,7 +151,7 @@ def generate(normalVector, aMin=-360, aMax=360, cMin=-360, cMax=360, compound=Fa normalVector = rot.multVec(n """ - PathLog.track( + Path.Log.track( "\n=============\n normalVector: {}\n aMin: {}\n aMax: {}\n cMin: {}\n cMax: {}".format( normalVector, aMin, aMax, cMin, cMax ) @@ -160,7 +159,7 @@ def generate(normalVector, aMin=-360, aMax=360, cMin=-360, cMax=360, compound=Fa # Calculate C rotation cResults = __getCRotation(normalVector, cMin, cMax) - PathLog.debug("C Rotation results {}".format(cResults)) + Path.Log.debug("C Rotation results {}".format(cResults)) solutions = [] for result in cResults: @@ -172,7 +171,7 @@ def generate(normalVector, aMin=-360, aMax=360, cMin=-360, cMax=360, compound=Fa # Get the candidate A rotation for the new vector aResult = __getARotation(newvec, aMin, aMax) - PathLog.debug( + Path.Log.debug( "\n=====\nFor C Rotation: {}\n Calculated A {}\n".format(result, aResult) ) @@ -191,7 +190,7 @@ def generate(normalVector, aMin=-360, aMax=360, cMin=-360, cMax=360, compound=Fa best = solution curlen = testlen - PathLog.debug("best result: {}".format(best)) + Path.Log.debug("best result: {}".format(best)) # format and return rotation commands commands = [] diff --git a/src/Mod/Path/Generators/threadmilling_generator.py b/src/Mod/Path/Generators/threadmilling_generator.py index 3694acaac0..83aca0e762 100644 --- a/src/Mod/Path/Generators/threadmilling_generator.py +++ b/src/Mod/Path/Generators/threadmilling_generator.py @@ -25,7 +25,6 @@ from __future__ import print_function import FreeCAD import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import math from PySide.QtCore import QT_TRANSLATE_NOOP @@ -35,10 +34,10 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Path thread milling operation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -184,12 +183,12 @@ def generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, st a = math.atan2(y - center.y, x - center.x) dx = math.cos(a) * (radius - elevator) dy = math.sin(a) * (radius - elevator) - PathLog.debug("") - PathLog.debug("a={}: dx={:.2f}, dy={:.2f}".format(a / math.pi * 180, dx, dy)) + Path.Log.debug("") + Path.Log.debug("a={}: dx={:.2f}, dy={:.2f}".format(a / math.pi * 180, dx, dy)) elevatorX = x - dx elevatorY = y - dy - PathLog.debug( + Path.Log.debug( "({:.2f}, {:.2f}) -> ({:.2f}, {:.2f})".format(x, y, elevatorX, elevatorY) ) diff --git a/src/Mod/Path/Generators/toolchange_generator.py b/src/Mod/Path/Generators/toolchange_generator.py index e7a6f35b7f..eda14bcb34 100644 --- a/src/Mod/Path/Generators/toolchange_generator.py +++ b/src/Mod/Path/Generators/toolchange_generator.py @@ -21,7 +21,6 @@ # *************************************************************************** -import PathScripts.PathLog as PathLog import Path from enum import Enum @@ -32,10 +31,10 @@ __doc__ = "Generates the rotation toolpath" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class SpindleDirection(Enum): @@ -52,7 +51,7 @@ def generate( """ - PathLog.track( + Path.Log.track( f"toolnumber:{toolnumber} toollabel: {toollabel} spindlespeed:{spindlespeed} spindledirection: {spindledirection}" ) @@ -73,7 +72,7 @@ def generate( else: commands.append(Path.Command(spindledirection.value, {"S": spindlespeed})) - PathLog.track(commands) + Path.Log.track(commands) return commands diff --git a/src/Mod/Path/PathScripts/PathLog.py b/src/Mod/Path/Path/Log.py similarity index 100% rename from src/Mod/Path/PathScripts/PathLog.py rename to src/Mod/Path/Path/Log.py diff --git a/src/Mod/Path/Path/__init__.py b/src/Mod/Path/Path/__init__.py index fc13dc215a..e365888dbe 100644 --- a/src/Mod/Path/Path/__init__.py +++ b/src/Mod/Path/Path/__init__.py @@ -1 +1,3 @@ from PathApp import * + +import Path.Log diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index 725f27bf27..1409d930d6 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -21,8 +21,8 @@ # *************************************************************************** import FreeCAD +import Path import PathScripts -import PathScripts.PathLog as PathLog import traceback from PathScripts.PathUtils import loopdetect @@ -80,7 +80,7 @@ class _CommandSelectLoop: self.active = False return self.active except Exception as exc: - PathLog.error(exc) + Path.Log.error(exc) traceback.print_exc(exc) return False diff --git a/src/Mod/Path/PathFeedRate.py b/src/Mod/Path/PathFeedRate.py index ad151342db..87f16b2eba 100644 --- a/src/Mod/Path/PathFeedRate.py +++ b/src/Mod/Path/PathFeedRate.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathMachineState import PathScripts.PathGeom as PathGeom import Part @@ -37,10 +37,10 @@ TODO: This needs to be able to handle feedrates for axes other than X,Y,Z """ if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def setFeedRate(commandlist, ToolController): diff --git a/src/Mod/Path/PathMachineState.py b/src/Mod/Path/PathMachineState.py index 6c2ff960e6..933f9a3503 100644 --- a/src/Mod/Path/PathMachineState.py +++ b/src/Mod/Path/PathMachineState.py @@ -26,15 +26,15 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Dataclass to implement a machinestate tracker" __contributors__ = "" -import PathScripts.PathLog as PathLog +import Path import FreeCAD from PathScripts.PathGeom import CmdMoveRapid, CmdMoveAll, CmdMoveDrill if True: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class MachineState: diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index 3caf12e2ab..db0a66b5fb 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -22,11 +22,10 @@ # * * # *************************************************************************** +import Path import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils -import PathScripts.PathLog as PathLog import PathScripts.PathGeom as PathGeom -import Path import FreeCAD import time import json @@ -52,10 +51,10 @@ DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -653,7 +652,7 @@ def Execute(op, obj): if FreeCAD.GuiUp: sceneGraph = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() - PathLog.info("*** Adaptive toolpath processing started...\n") + Path.Log.info("*** Adaptive toolpath processing started...\n") # hide old toolpaths during recalculation obj.Path = Path.Path("(Calculating...)") @@ -680,7 +679,7 @@ def Execute(op, obj): # Get list of working edges for adaptive algorithm pathArray = op.pathArray if not pathArray: - PathLog.error("No wire data returned.") + Path.Log.error("No wire data returned.") return path2d = convertTo2d(pathArray) @@ -798,12 +797,12 @@ def Execute(op, obj): GenerateGCode(op, obj, adaptiveResults, helixDiameter) if not obj.StopProcessing: - PathLog.info("*** Done. Elapsed time: %f sec\n\n" % (time.time() - start)) + Path.Log.info("*** Done. Elapsed time: %f sec\n\n" % (time.time() - start)) obj.AdaptiveOutputState = adaptiveResults obj.AdaptiveInputState = inputStateObject else: - PathLog.info( + Path.Log.info( "*** Processing cancelled (after: %f sec).\n\n" % (time.time() - start) ) @@ -927,11 +926,11 @@ class PathAdaptive(PathOp.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index ee2323a753..7f9246416c 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -23,7 +23,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils @@ -44,10 +43,10 @@ __contributors__ = "russ4262 (Russell Johnson)" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -82,7 +81,7 @@ class ObjectOp(PathOp.ObjectOp): def initOperation(self, obj): """initOperation(obj) ... sets up standard Path.Area properties and calls initAreaOp(). Do not overwrite, overwrite initAreaOp(obj) instead.""" - PathLog.track() + Path.Log.track() # Debugging obj.addProperty("App::PropertyString", "AreaParams", "Path") @@ -113,16 +112,16 @@ class ObjectOp(PathOp.ObjectOp): The default implementation returns the job's Base.Shape""" if job: if job.Stock: - PathLog.debug( + Path.Log.debug( "job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape) ) return job.Stock.Shape else: - PathLog.warning( + Path.Log.warning( translate("PathAreaOp", "job %s has no Base.") % job.Label ) else: - PathLog.warning( + Path.Log.warning( translate("PathAreaOp", "no job for op %s found.") % obj.Label ) return None @@ -137,7 +136,7 @@ class ObjectOp(PathOp.ObjectOp): The base implementation takes a stab at determining Heights and Depths if the operations's Base changes. Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.""" - # PathLog.track(obj.Label, prop) + # Path.Log.track(obj.Label, prop) if prop in ["AreaParams", "PathParams", "removalshape"]: obj.setEditorMode(prop, 2) @@ -156,7 +155,7 @@ class ObjectOp(PathOp.ObjectOp): self.areaOpOnChanged(obj, prop) def opOnDocumentRestored(self, obj): - PathLog.track() + Path.Log.track() for prop in ["AreaParams", "PathParams", "removalshape"]: if hasattr(obj, prop): obj.setEditorMode(prop, 2) @@ -179,18 +178,18 @@ class ObjectOp(PathOp.ObjectOp): The base implementation sets the depths and heights based on the areaOpShapeForDepths() return value. Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.""" - PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label)) + Path.Log.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label)) if PathOp.FeatureDepths & self.opFeatures(obj): try: shape = self.areaOpShapeForDepths(obj, job) except Exception as ee: - PathLog.error(ee) + Path.Log.error(ee) shape = None # Set initial start and final depths if shape is None: - PathLog.debug("shape is None") + Path.Log.debug("shape is None") startDepth = 1.0 finalDepth = 0.0 else: @@ -203,12 +202,12 @@ class ObjectOp(PathOp.ObjectOp): obj.OpStartDepth.Value = startDepth obj.OpFinalDepth.Value = finalDepth - PathLog.debug( + Path.Log.debug( "Default OpDepths are Start: {}, and Final: {}".format( obj.OpStartDepth.Value, obj.OpFinalDepth.Value ) ) - PathLog.debug( + Path.Log.debug( "Default Depths are Start: {}, and Final: {}".format( startDepth, finalDepth ) @@ -223,7 +222,7 @@ class ObjectOp(PathOp.ObjectOp): def _buildPathArea(self, obj, baseobject, isHole, start, getsim): """_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.""" - PathLog.track() + Path.Log.track() area = Path.Area() area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) @@ -232,18 +231,18 @@ class ObjectOp(PathOp.ObjectOp): areaParams['SectionTolerance'] = 1e-07 heights = [i for i in self.depthparams] - PathLog.debug("depths: {}".format(heights)) + Path.Log.debug("depths: {}".format(heights)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) - PathLog.debug("Area with params: {}".format(area.getParams())) + Path.Log.debug("Area with params: {}".format(area.getParams())) sections = area.makeSections( mode=0, project=self.areaOpUseProjection(obj), heights=heights ) - PathLog.debug("sections = %s" % sections) + Path.Log.debug("sections = %s" % sections) shapelist = [sec.getShape() for sec in sections] - PathLog.debug("shapelist = %s" % shapelist) + Path.Log.debug("shapelist = %s" % shapelist) pathParams = self.areaOpPathParams(obj, isHole) pathParams["shapes"] = shapelist @@ -267,10 +266,10 @@ class ObjectOp(PathOp.ObjectOp): obj.PathParams = str( {key: value for key, value in pathParams.items() if key != "shapes"} ) - PathLog.debug("Path with params: {}".format(obj.PathParams)) + Path.Log.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) - PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) + Path.Log.debug("pp: {}, end vector: {}".format(pp, end_vector)) self.endVector = end_vector simobj = None @@ -287,11 +286,11 @@ class ObjectOp(PathOp.ObjectOp): def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim): """_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.""" - PathLog.track() + Path.Log.track() paths = [] heights = [i for i in self.depthparams] - PathLog.debug("depths: {}".format(heights)) + Path.Log.debug("depths: {}".format(heights)) for i in range(0, len(heights)): for baseShape in edgeList: hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges)) @@ -327,11 +326,11 @@ class ObjectOp(PathOp.ObjectOp): obj.PathParams = str( {key: value for key, value in pathParams.items() if key != "shapes"} ) - PathLog.debug("Path with params: {}".format(obj.PathParams)) + Path.Log.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) paths.extend(pp.Commands) - PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) + Path.Log.debug("pp: {}, end vector: {}".format(pp, end_vector)) self.endVector = end_vector simobj = None @@ -347,7 +346,7 @@ class ObjectOp(PathOp.ObjectOp): areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.""" - PathLog.track() + Path.Log.track() # Instantiate class variables for operation reference self.endVector = None @@ -445,7 +444,7 @@ class ObjectOp(PathOp.ObjectOp): ) ) - PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") + Path.Log.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims def areaOpRetractTool(self, obj): diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 295a34f72b..070900075c 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -24,7 +24,6 @@ import FreeCAD import FreeCADGui import Path import PathScripts -from PathScripts import PathLog from PathScripts.PathDressup import toolController from PySide import QtCore import math @@ -373,7 +372,7 @@ class PathArray: path data for the requested path array.""" if len(self.baseList) == 0: - PathLog.error(translate("PathArray", "No base objects for PathArray.")) + Path.Log.error(translate("PathArray", "No base objects for PathArray.")) return None base = self.baseList @@ -389,7 +388,7 @@ class PathArray: if b_tool_controller != toolController(base[0]): # this may be important if Job output is split by tool controller - PathLog.warning( + Path.Log.warning( translate( "PathArray", "Arrays of paths having different tool controllers are handled according to the tool controller of the first path.", diff --git a/src/Mod/Path/PathScripts/PathCamoticsGui.py b/src/Mod/Path/PathScripts/PathCamoticsGui.py index f68948a9d7..41d368ec0d 100644 --- a/src/Mod/Path/PathScripts/PathCamoticsGui.py +++ b/src/Mod/Path/PathScripts/PathCamoticsGui.py @@ -26,8 +26,8 @@ from threading import Thread, Lock import FreeCAD import FreeCADGui import Mesh +import Path import PathScripts -import PathScripts.PathLog as PathLog import PathScripts.PathPost as PathPost import camotics import io @@ -43,10 +43,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Task panel for Camotics Simulation" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -76,7 +76,7 @@ class CAMoticsUI: subprocess.Popen(["camotics", filename]) def makeCamoticsFile(self): - PathLog.track() + Path.Log.track() filename = QtGui.QFileDialog.getSaveFileName( self.form, translate("Path", "Save Project As"), @@ -139,7 +139,7 @@ class CamoticsSimulation(QtCore.QObject): def worker(self, lock): while True: item = self.q.get() - PathLog.debug("worker processing: {}".format(item)) + Path.Log.debug("worker processing: {}".format(item)) with lock: if item["TYPE"] == "STATUS": self.statusChange.emit(item["VALUE"]) @@ -199,7 +199,7 @@ class CamoticsSimulation(QtCore.QObject): ) postlist = PathPost.buildPostList(self.job) - PathLog.track(postlist) + Path.Log.track(postlist) # self.filenames = [PathPost.resolveFileName(self.job)] success = True @@ -217,7 +217,7 @@ class CamoticsSimulation(QtCore.QObject): extraargs="--no-show-editor", ) self.filenames.append(name) - PathLog.track(result, gcode, name) + Path.Log.track(result, gcode, name) if result is None: success = False @@ -231,11 +231,11 @@ class CamoticsSimulation(QtCore.QObject): self.SIM.wait() tot = sum([step["time"] for step in self.SIM.get_path()]) - PathLog.debug("sim time: {}".format(tot)) + Path.Log.debug("sim time: {}".format(tot)) self.taskForm.setRunTime(tot) def execute(self, timeIndex): - PathLog.track() + Path.Log.track() self.SIM.start(self.callback, time=timeIndex, done=self.isDone) def accept(self): @@ -245,7 +245,7 @@ class CamoticsSimulation(QtCore.QObject): pass def buildproject(self): # , files=[]): - PathLog.track() + Path.Log.track() job = self.job diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index d8b3fa0abe..1ca025d9cd 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -22,7 +22,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathOp as PathOp import PathScripts.drillableLib as drillableLib @@ -44,10 +44,10 @@ translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectOp(PathOp.ObjectOp): @@ -109,7 +109,7 @@ class ObjectOp(PathOp.ObjectOp): # for all other shapes the diameter is just the dimension in X. # This may be inaccurate as the BoundBox is calculated on the tessellated geometry - PathLog.warning( + Path.Log.warning( translate( "Path", "Hole diameter may be inaccurate due to tessellation on face. Consider selecting hole edge.", @@ -117,7 +117,7 @@ class ObjectOp(PathOp.ObjectOp): ) return shape.BoundBox.XLength except Part.OCCError as e: - PathLog.error(e) + Path.Log.error(e) return 0 @@ -141,9 +141,9 @@ class ObjectOp(PathOp.ObjectOp): if len(shape.Edges) == 1 and type(shape.Edges[0].Curve) == Part.Circle: return shape.Edges[0].Curve.Center except Part.OCCError as e: - PathLog.error(e) + Path.Log.error(e) - PathLog.error( + Path.Log.error( translate( "Path", "Feature %s.%s cannot be processed as a circular hole - please remove from Base geometry list.", @@ -164,7 +164,7 @@ class ObjectOp(PathOp.ObjectOp): drillable features are added to Base. In this case appropriate values for depths are also calculated and assigned. Do not overwrite, implement circularHoleExecute(obj, holes) instead.""" - PathLog.track() + Path.Log.track() def haveLocations(self, obj): if PathOp.FeatureLocations & self.opFeatures(obj): @@ -174,7 +174,7 @@ class ObjectOp(PathOp.ObjectOp): holes = [] for base, subs in obj.Base: for sub in subs: - PathLog.debug("processing {} in {}".format(sub, base.Name)) + Path.Log.debug("processing {} in {}".format(sub, base.Name)) if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) if pos: @@ -202,7 +202,7 @@ class ObjectOp(PathOp.ObjectOp): def findAllHoles(self, obj): """findAllHoles(obj) ... find all holes of all base models and assign as features.""" - PathLog.track() + Path.Log.track() job = self.getJob(obj) if not job: return diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py index 4f75193f31..7cde8bcc77 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py @@ -22,8 +22,8 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui @@ -36,10 +36,10 @@ __doc__ = "Implementation of circular hole specific base geometry page controlle LOGLEVEL = False if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.NOTICE, Path.Log.thisModule()) class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): @@ -63,7 +63,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): def setFields(self, obj): """setFields(obj) ... fill form with values from obj""" - PathLog.track() + Path.Log.track() self.form.baseList.blockSignals(True) self.form.baseList.clearContents() self.form.baseList.setRowCount(0) @@ -98,7 +98,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): def itemActivated(self): """itemActivated() ... callback when item in table is selected""" - PathLog.track() + Path.Log.track() if self.form.baseList.selectedItems(): self.form.deleteBase.setEnabled(True) FreeCADGui.Selection.clearSelection() @@ -109,7 +109,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): activatedRows.append(row) obj = item.data(self.DataObject) sub = str(item.data(self.DataObjectSub)) - PathLog.debug("itemActivated() -> %s.%s" % (obj.Label, sub)) + Path.Log.debug("itemActivated() -> %s.%s" % (obj.Label, sub)) if sub: FreeCADGui.Selection.addSelection(obj, sub) else: @@ -119,7 +119,7 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): def deleteBase(self): """deleteBase() ... callback for push button""" - PathLog.track() + Path.Log.track() selected = [ self.form.baseList.row(item) for item in self.form.baseList.selectedItems() ] @@ -135,23 +135,23 @@ class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): def updateBase(self): """updateBase() ... helper function to transfer current table to obj""" - PathLog.track() + Path.Log.track() newlist = [] for i in range(self.form.baseList.rowCount()): item = self.form.baseList.item(i, 0) obj = item.data(self.DataObject) sub = str(item.data(self.DataObjectSub)) base = (obj, sub) - PathLog.debug("keeping (%s.%s)" % (obj.Label, sub)) + Path.Log.debug("keeping (%s.%s)" % (obj.Label, sub)) newlist.append(base) - PathLog.debug("obj.Base=%s newlist=%s" % (self.obj.Base, newlist)) + Path.Log.debug("obj.Base=%s newlist=%s" % (self.obj.Base, newlist)) self.updating = True self.obj.Base = newlist self.updating = False def checkedChanged(self): """checkeChanged() ... callback when checked status of a base feature changed""" - PathLog.track() + Path.Log.track() disabled = [] for i in range(0, self.form.baseList.rowCount()): item = self.form.baseList.item(i, 0) diff --git a/src/Mod/Path/PathScripts/PathCollision.py b/src/Mod/Path/PathScripts/PathCollision.py index 645c16b96a..18c245d05b 100644 --- a/src/Mod/Path/PathScripts/PathCollision.py +++ b/src/Mod/Path/PathScripts/PathCollision.py @@ -21,14 +21,14 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path from PySide import QtCore from PathScripts.PathUtils import waiting_effects from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = "PathCollision" -PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) -PathLog.trackModule("PathCollision") +Path.Log.setLevel(Path.Log.Level.DEBUG, LOG_MODULE) +Path.Log.trackModule("PathCollision") FreeCAD.setLogLevel("Path.Area", 0) __title__ = "Path Collision Utility" diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index 17b03f830d..7b2459d175 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -24,7 +24,6 @@ import FreeCAD import Path import PathScripts.PathOp as PathOp -import PathScripts.PathLog as PathLog from PySide.QtCore import QT_TRANSLATE_NOOP @@ -35,10 +34,10 @@ __doc__ = "Path Custom object and FreeCAD command" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 98da2a488f..f590e7501f 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -22,9 +22,9 @@ # *************************************************************************** import FreeCAD +import Path import PathScripts.PathEngraveBase as PathEngraveBase import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathOpTools as PathOpTools import math @@ -43,10 +43,10 @@ __doc__ = "Deburr operation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -118,7 +118,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): ) def initOperation(self, obj): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) obj.addProperty( "App::PropertyDistance", "Width", @@ -197,12 +197,12 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): # data[k] = [tup[idx] for tup in v] data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -210,7 +210,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.setEditorMode("Join", 2) # hide for now def opExecute(self, obj): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) if not hasattr(self, "printInfo"): self.printInfo = True try: @@ -224,7 +224,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): # QtGui.QMessageBox.information(None, "Tool Error", msg) # return - PathLog.track(obj.Label, depth, offset) + Path.Log.track(obj.Label, depth, offset) self.basewires = [] self.adjusted_basewires = [] @@ -431,7 +431,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): zValues.append(z) zValues.append(depth) - PathLog.track(obj.Label, depth, zValues) + Path.Log.track(obj.Label, depth, zValues) if obj.EntryPoint < 0: obj.EntryPoint = 0 @@ -444,7 +444,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): return base not in self.model def opSetDefaultValues(self, obj, job): - PathLog.track(obj.Label, job.Label) + Path.Log.track(obj.Label, job.Label) obj.Width = "1 mm" obj.ExtraDepth = "0.5 mm" obj.Join = "Round" diff --git a/src/Mod/Path/PathScripts/PathDeburrGui.py b/src/Mod/Path/PathScripts/PathDeburrGui.py index 2222721d2a..77daa3fddd 100644 --- a/src/Mod/Path/PathScripts/PathDeburrGui.py +++ b/src/Mod/Path/PathScripts/PathDeburrGui.py @@ -22,9 +22,9 @@ import FreeCAD import FreeCADGui +import Path import PathScripts.PathDeburr as PathDeburr import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP @@ -36,10 +36,10 @@ __doc__ = "Deburr operation page controller and command implementation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathDressupAxisMap.py b/src/Mod/Path/PathScripts/PathDressupAxisMap.py index b5b6b7ab9f..365fc01d09 100644 --- a/src/Mod/Path/PathScripts/PathDressupAxisMap.py +++ b/src/Mod/Path/PathScripts/PathDressupAxisMap.py @@ -26,15 +26,14 @@ import math import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog from PySide.QtCore import QT_TRANSLATE_NOOP from PathScripts.PathGeom import CmdMoveArc if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) if FreeCAD.GuiUp: diff --git a/src/Mod/Path/PathScripts/PathDressupDogbone.py b/src/Mod/Path/PathScripts/PathDressupDogbone.py index 875e94a02a..9106bb196a 100644 --- a/src/Mod/Path/PathScripts/PathDressupDogbone.py +++ b/src/Mod/Path/PathScripts/PathDressupDogbone.py @@ -28,7 +28,6 @@ import FreeCAD import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import math @@ -42,10 +41,10 @@ Part = LazyLoader("Part", globals(), "Part") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -55,7 +54,7 @@ movecommands = CmdMoveStraight + CmdMoveRapid + CmdMoveArc def debugMarker(vector, label, color=None, radius=0.5): - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label) obj.Label = label obj.Radius = radius @@ -67,7 +66,7 @@ def debugMarker(vector, label, color=None, radius=0.5): def debugCircle(vector, r, label, color=None): - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label) obj.Label = label obj.Radius = r @@ -250,7 +249,7 @@ class Chord(object): def getDirectionOfVector(self, B): A = self.asDirection() # if the 2 vectors are identical, they head in the same direction - PathLog.debug(" {}.getDirectionOfVector({})".format(A, B)) + Path.Log.debug(" {}.getDirectionOfVector({})".format(A, B)) if PathGeom.pointsCoincide(A, B): return "Straight" d = -A.x * B.y + A.y * B.x @@ -310,7 +309,7 @@ class Chord(object): return not PathGeom.isRoughly(self.End.z, self.Start.z) def isANoopMove(self): - PathLog.debug( + Path.Log.debug( "{}.isANoopMove(): {}".format( self, PathGeom.pointsCoincide(self.Start, self.End) ) @@ -319,7 +318,7 @@ class Chord(object): def foldsBackOrTurns(self, chord, side): direction = chord.getDirectionOf(self) - PathLog.info(" - direction = %s/%s" % (direction, side)) + Path.Log.info(" - direction = %s/%s" % (direction, side)) return direction == "Back" or direction == side def connectsTo(self, chord): @@ -378,9 +377,9 @@ class Bone(object): # for some reason pi/2 is not equal to pi/2 if math.fabs(theta - boneAngle) < 0.00001: # moving directly towards the corner - PathLog.debug("adaptive - on target: %.2f - %.2f" % (distance, toolRadius)) + Path.Log.debug("adaptive - on target: %.2f - %.2f" % (distance, toolRadius)) return distance - toolRadius - PathLog.debug( + Path.Log.debug( "adaptive - angles: corner=%.2f bone=%.2f diff=%.12f" % (theta / math.pi, boneAngle / math.pi, theta - boneAngle) ) @@ -394,7 +393,7 @@ class Bone(object): beta = math.fabs(addAngle(boneAngle, -theta)) D = (distance / toolRadius) * math.sin(beta) if D > 1: # no intersection - PathLog.debug("adaptive - no intersection - no bone") + Path.Log.debug("adaptive - no intersection - no bone") return 0 gamma = math.asin(D) alpha = math.pi - beta - gamma @@ -410,7 +409,7 @@ class Bone(object): length2 = toolRadius * math.sin(alpha2) / math.sin(beta2) length = min(length, length2) - PathLog.debug( + Path.Log.debug( "adaptive corner=%.2f * %.2f˚ -> bone=%.2f * %.2f˚" % (distance, theta, length, boneAngle) ) @@ -505,7 +504,7 @@ class ObjectDressup(object): return outChord.foldsBackOrTurns(inChord, self.theOtherSideOf(obj.Side)) def findPivotIntersection(self, pivot, pivotEdge, edge, refPt, d, color): - PathLog.track( + Path.Log.track( "(%.2f, %.2f)^%.2f - [(%.2f, %.2f), (%.2f, %.2f)]" % ( pivotEdge.Curve.Center.x, @@ -522,22 +521,22 @@ class ObjectDressup(object): for pt in DraftGeomUtils.findIntersection(edge, pivotEdge, dts=False): # debugMarker(pt, "pti.%d-%s.in" % (self.boneId, d), color, 0.2) distance = (pt - refPt).Length - PathLog.debug(" --> (%.2f, %.2f): %.2f" % (pt.x, pt.y, distance)) + Path.Log.debug(" --> (%.2f, %.2f): %.2f" % (pt.x, pt.y, distance)) if not ppt or pptDistance < distance: ppt = pt pptDistance = distance if not ppt: tangent = DraftGeomUtils.findDistance(pivot, edge) if tangent: - PathLog.debug("Taking tangent as intersect %s" % tangent) + Path.Log.debug("Taking tangent as intersect %s" % tangent) ppt = pivot + tangent else: - PathLog.debug( + Path.Log.debug( "Taking chord start as intersect %s" % edge.Vertexes[0].Point ) ppt = edge.Vertexes[0].Point # debugMarker(ppt, "ptt.%d-%s.in" % (self.boneId, d), color, 0.2) - PathLog.debug(" --> (%.2f, %.2f)" % (ppt.x, ppt.y)) + Path.Log.debug(" --> (%.2f, %.2f)" % (ppt.x, ppt.y)) return ppt def pointIsOnEdge(self, point, edge): @@ -548,7 +547,7 @@ class ObjectDressup(object): self, bone, inChord, outChord, edge, wire, corner, smooth, color=None ): if smooth == 0: - PathLog.info(" No smoothing requested") + Path.Log.info(" No smoothing requested") return [bone.lastCommand, outChord.g1Command(bone.F)] d = "in" @@ -558,13 +557,13 @@ class ObjectDressup(object): refPoint = outChord.End if DraftGeomUtils.areColinear(inChord.asEdge(), outChord.asEdge()): - PathLog.info(" straight edge %s" % d) + Path.Log.info(" straight edge %s" % d) return [outChord.g1Command(bone.F)] pivot = None pivotDistance = 0 - PathLog.info( + Path.Log.info( "smooth: (%.2f, %.2f)-(%.2f, %.2f)" % ( edge.Vertexes[0].Point.x, @@ -576,7 +575,7 @@ class ObjectDressup(object): for e in wire.Edges: self.dbg.append(e) if type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line: - PathLog.debug( + Path.Log.debug( " (%.2f, %.2f)-(%.2f, %.2f)" % ( e.Vertexes[0].Point.x, @@ -586,7 +585,7 @@ class ObjectDressup(object): ) ) else: - PathLog.debug( + Path.Log.debug( " (%.2f, %.2f)^%.2f" % (e.Curve.Center.x, e.Curve.Center.y, e.Curve.Radius) ) @@ -595,13 +594,13 @@ class ObjectDressup(object): pt, e ): # debugMarker(pt, "candidate-%d-%s" % (self.boneId, d), color, 0.05) - PathLog.debug(" -> candidate") + Path.Log.debug(" -> candidate") distance = (pt - refPoint).Length if not pivot or pivotDistance > distance: pivot = pt pivotDistance = distance else: - PathLog.debug(" -> corner intersect") + Path.Log.debug(" -> corner intersect") if pivot: # debugCircle(pivot, self.toolRadius, "pivot.%d-%s" % (self.boneId, d), color) @@ -618,19 +617,19 @@ class ObjectDressup(object): commands = [] if not PathGeom.pointsCoincide(t1, inChord.Start): - PathLog.debug(" add lead in") + Path.Log.debug(" add lead in") commands.append(Chord(inChord.Start, t1).g1Command(bone.F)) if bone.obj.Side == Side.Left: - PathLog.debug(" add g3 command") + Path.Log.debug(" add g3 command") commands.append(Chord(t1, t2).g3Command(pivot, bone.F)) else: - PathLog.debug( + Path.Log.debug( " add g2 command center=(%.2f, %.2f) -> from (%2f, %.2f) to (%.2f, %.2f" % (pivot.x, pivot.y, t1.x, t1.y, t2.x, t2.y) ) commands.append(Chord(t1, t2).g2Command(pivot, bone.F)) if not PathGeom.pointsCoincide(t2, outChord.End): - PathLog.debug(" add lead out") + Path.Log.debug(" add lead out") commands.append(Chord(t2, outChord.End).g1Command(bone.F)) # debugMarker(pivot, "pivot.%d-%s" % (self.boneId, d), color, 0.2) @@ -639,7 +638,7 @@ class ObjectDressup(object): return commands - PathLog.info(" no pivot found - straight command") + Path.Log.info(" no pivot found - straight command") return [inChord.g1Command(bone.F), outChord.g1Command(bone.F)] def inOutBoneCommands(self, bone, boneAngle, fixedLength): @@ -647,7 +646,7 @@ class ObjectDressup(object): bone.tip = bone.inChord.End # in case there is no bone - PathLog.debug("corner = (%.2f, %.2f)" % (corner.x, corner.y)) + Path.Log.debug("corner = (%.2f, %.2f)" % (corner.x, corner.y)) # debugMarker(corner, 'corner', (1., 0., 1.), self.toolRadius) length = fixedLength @@ -657,7 +656,7 @@ class ObjectDressup(object): length = bone.adaptiveLength(boneAngle, self.toolRadius) if length == 0: - PathLog.info("no bone after all ..") + Path.Log.info("no bone after all ..") return [bone.lastCommand, bone.outChord.g1Command(bone.F)] # track length for marker visuals @@ -764,7 +763,7 @@ class ObjectDressup(object): onInString = "out" if onIn: onInString = "in" - PathLog.debug( + Path.Log.debug( "tboneEdge boneAngle[%s]=%.2f (in=%.2f, out=%.2f)" % ( onInString, @@ -815,7 +814,7 @@ class ObjectDressup(object): return [bone.lastCommand, bone.outChord.g1Command(bone.F)] def insertBone(self, bone): - PathLog.debug( + Path.Log.debug( ">----------------------------------- %d --------------------------------------" % bone.boneId ) @@ -826,7 +825,7 @@ class ObjectDressup(object): self.boneId = bone.boneId # Specific debugging `if` statement - # if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG and bone.boneId > 2: + # if Path.Log.getLevel(LOG_MODULE) == Path.Log.Level.DEBUG and bone.boneId > 2: # commands = self.boneCommands(bone, False) # else: # commands = self.boneCommands(bone, enabled) @@ -834,7 +833,7 @@ class ObjectDressup(object): bone.commands = commands self.shapes[bone.boneId] = self.boneShapes - PathLog.debug( + Path.Log.debug( "<----------------------------------- %d --------------------------------------" % bone.boneId ) @@ -919,7 +918,7 @@ class ObjectDressup(object): # lastCommand = None # commands.append(thisCommand) # continue - PathLog.info("%3d: %s" % (i, thisCommand)) + Path.Log.info("%3d: %s" % (i, thisCommand)) if thisCommand.Name in movecommands: thisChord = lastChord.moveToParameters(thisCommand.Parameters) thisIsACandidate = self.canAttachDogbone(thisCommand, thisChord) @@ -929,7 +928,7 @@ class ObjectDressup(object): and lastCommand and self.shouldInsertDogbone(obj, lastChord, thisChord) ): - PathLog.info(" Found bone corner: {}".format(lastChord.End)) + Path.Log.info(" Found bone corner: {}".format(lastChord.End)) bone = Bone( boneId, obj, @@ -942,7 +941,7 @@ class ObjectDressup(object): bones = self.insertBone(bone) boneId += 1 if lastBone: - PathLog.info(" removing potential path crossing") + Path.Log.info(" removing potential path crossing") # debugMarker(thisChord.Start, "it", (1.0, 0.0, 1.0)) commands, bones = self.removePathCrossing( commands, lastBone, bone @@ -951,14 +950,14 @@ class ObjectDressup(object): lastCommand = bones[-1] lastBone = bone elif lastCommand and thisChord.isAPlungeMove(): - PathLog.info(" Looking for connection in odds and ends") + Path.Log.info(" Looking for connection in odds and ends") haveNewLastCommand = False for chord in ( chord for chord in oddsAndEnds if lastChord.connectsTo(chord) ): if self.shouldInsertDogbone(obj, lastChord, chord): - PathLog.info(" and there is one") - PathLog.debug( + Path.Log.info(" and there is one") + Path.Log.debug( " odd/end={} last={}".format(chord, lastChord) ) bone = Bone( @@ -973,7 +972,7 @@ class ObjectDressup(object): bones = self.insertBone(bone) boneId += 1 if lastBone: - PathLog.info(" removing potential path crossing") + Path.Log.info(" removing potential path crossing") # debugMarker(chord.Start, "it", (0.0, 1.0, 1.0)) commands, bones = self.removePathCrossing( commands, lastBone, bone @@ -987,16 +986,16 @@ class ObjectDressup(object): commands.append(thisCommand) lastBone = None elif thisIsACandidate: - PathLog.info(" is a candidate, keeping for later") + Path.Log.info(" is a candidate, keeping for later") if lastCommand: commands.append(lastCommand) lastCommand = thisCommand lastBone = None elif thisChord.isANoopMove(): - PathLog.info(" ignoring and dropping noop move") + Path.Log.info(" ignoring and dropping noop move") continue else: - PathLog.info(" nope") + Path.Log.info(" nope") if lastCommand: commands.append(lastCommand) lastCommand = None @@ -1004,43 +1003,43 @@ class ObjectDressup(object): lastBone = None if lastChord.isAPlungeMove() and thisIsACandidate: - PathLog.info(" adding to odds and ends") + Path.Log.info(" adding to odds and ends") oddsAndEnds.append(thisChord) lastChord = thisChord else: if thisCommand.Name[0] != "(": - PathLog.info(" Clean slate") + Path.Log.info(" Clean slate") if lastCommand: commands.append(lastCommand) lastCommand = None lastBone = None commands.append(thisCommand) # for cmd in commands: - # PathLog.debug("cmd = '%s'" % cmd) + # Path.Log.debug("cmd = '%s'" % cmd) path = Path.Path(commands) obj.Path = path def setup(self, obj, initial): - PathLog.info("Here we go ... ") + Path.Log.info("Here we go ... ") if initial: if hasattr(obj.Base, "BoneBlacklist"): # dressing up a bone dressup obj.Side = obj.Base.Side else: - PathLog.info("Default side = right") + Path.Log.info("Default side = right") # otherwise dogbones are opposite of the base path's side side = Side.Right if hasattr(obj.Base, "Side") and obj.Base.Side == "Inside": - PathLog.info("inside -> side = left") + Path.Log.info("inside -> side = left") side = Side.Left else: - PathLog.info("not inside -> side stays right") + Path.Log.info("not inside -> side stays right") if hasattr(obj.Base, "Direction") and obj.Base.Direction == "CCW": - PathLog.info("CCW -> switch sides") + Path.Log.info("CCW -> switch sides") side = Side.oppositeOf(side) else: - PathLog.info("CW -> stay on side") + Path.Log.info("CW -> stay on side") obj.Side = side self.toolRadius = 5 @@ -1214,18 +1213,18 @@ class TaskPanel(object): self.form.customLabel.setEnabled(customSelected) self.updateBoneList() - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: for obj in FreeCAD.ActiveDocument.Objects: if obj.Name.startswith("Shape"): FreeCAD.ActiveDocument.removeObject(obj.Name) - PathLog.info("object name %s" % self.obj.Name) + Path.Log.info("object name %s" % self.obj.Name) if hasattr(self.obj.Proxy, "shapes"): - PathLog.info("showing shapes attribute") + Path.Log.info("showing shapes attribute") for shapes in self.obj.Proxy.shapes.values(): for shape in shapes: Part.show(shape) else: - PathLog.info("no shapes attribute found") + Path.Log.info("no shapes attribute found") def updateModel(self): self.getFields() diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 57a38c6285..864cd4d5a8 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -27,7 +27,6 @@ import FreeCAD import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import copy @@ -40,16 +39,16 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate def debugEdge(edge, prefix, force=False): - if force or PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if force or Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: @@ -78,7 +77,7 @@ def debugEdge(edge, prefix, force=False): def debugMarker(vector, label, color=None, radius=0.5): - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label) obj.Label = label obj.Radius = radius @@ -90,7 +89,7 @@ def debugMarker(vector, label, color=None, radius=0.5): def debugCylinder(vector, r, height, label, color=None): - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label) obj.Label = label obj.Radius = r @@ -104,7 +103,7 @@ def debugCylinder(vector, r, height, label, color=None): def debugCone(vector, r1, r2, height, label, color=None): - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Cone", label) obj.Label = label obj.Radius1 = r1 @@ -120,7 +119,7 @@ def debugCone(vector, r1, r2, height, label, color=None): class Tag: def __init__(self, nr, x, y, width, height, angle, radius, enabled=True): - PathLog.track( + Path.Log.track( "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d" % (x, y, width, height, angle, radius, enabled) ) @@ -170,7 +169,7 @@ class Tag: self.isSquare = True self.solid = Part.makeCylinder(r1, height) radius = min(min(self.radius, r1), self.height) - PathLog.debug("Part.makeCylinder(%f, %f)" % (r1, height)) + Path.Log.debug("Part.makeCylinder(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: # cone rad = math.radians(self.angle) @@ -187,25 +186,25 @@ class Tag: height = r1 * tangens * 1.01 self.actualHeight = height self.r2 = r2 - PathLog.debug("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) + Path.Log.debug("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag - PathLog.debug("Part.makeSphere(%f / 10000)" % (r1)) + Path.Log.debug("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) if not PathGeom.isRoughly( 0, R ): # testing is easier if the solid is not rotated angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi - PathLog.debug("solid.rotate(%f)" % angle) + Path.Log.debug("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), angle) orig = self.originAt(z - 0.01 * self.actualHeight) - PathLog.debug("solid.translate(%s)" % orig) + Path.Log.debug("solid.translate(%s)" % orig) self.solid.translate(orig) radius = min(self.radius, radius) self.realRadius = radius if not PathGeom.isRoughly(0, radius): - PathLog.debug("makeFillet(%.4f)" % radius) + Path.Log.debug("makeFillet(%.4f)" % radius) self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]]) def filterIntersections(self, pts, face): @@ -214,12 +213,12 @@ class Tag: or type(face.Surface) == Part.Cylinder or type(face.Surface) == Part.Toroid ): - PathLog.track("it's a cone/cylinder, checking z") + Path.Log.track("it's a cone/cylinder, checking z") return list( [pt for pt in pts if pt.z >= self.bottom() and pt.z <= self.top()] ) if type(face.Surface) == Part.Plane: - PathLog.track("it's a plane, checking R") + Path.Log.track("it's a plane, checking R") c = face.Edges[0].Curve if type(c) == Part.Circle: return list( @@ -230,7 +229,7 @@ class Tag: or PathGeom.isRoughly((pt - c.Center).Length, c.Radius) ] ) - PathLog.error("==== we got a %s" % face.Surface) + Path.Log.error("==== we got a %s" % face.Surface) def isPointOnEdge(self, pt, edge): param = edge.Curve.parameter(pt) @@ -320,11 +319,11 @@ class MapWireToTag: self.edges = [] self.entry = i if tail: - PathLog.debug( + Path.Log.debug( "MapWireToTag(%s - %s)" % (i, tail.valueAt(tail.FirstParameter)) ) else: - PathLog.debug("MapWireToTag(%s - )" % i) + Path.Log.debug("MapWireToTag(%s - )" % i) self.complete = False self.haveProblem = False @@ -364,13 +363,13 @@ class MapWireToTag: def cleanupEdges(self, edges): # want to remove all edges from the wire itself, and all internal struts - PathLog.track("+cleanupEdges") - PathLog.debug(" edges:") + Path.Log.track("+cleanupEdges") + Path.Log.debug(" edges:") if not edges: return edges for e in edges: debugEdge(e, " ") - PathLog.debug(":") + Path.Log.debug(":") self.edgesCleanup = [copy.copy(edges)] # remove any edge that has a point inside the tag solid @@ -402,7 +401,7 @@ class MapWireToTag: # if there are no edges connected to entry/exit, it means the plunge in/out is vertical # we need to add in the missing segment and collect the new entry/exit edges. if not self.entryEdges: - PathLog.debug("fill entryEdges ...") + Path.Log.debug("fill entryEdges ...") self.realEntry = sorted( self.edgePoints, key=lambda p: (p - self.entry).Length )[0] @@ -413,7 +412,7 @@ class MapWireToTag: else: self.realEntry = None if not self.exitEdges: - PathLog.debug("fill exitEdges ...") + Path.Log.debug("fill exitEdges ...") self.realExit = sorted( self.edgePoints, key=lambda p: (p - self.exit).Length )[0] @@ -451,7 +450,7 @@ class MapWireToTag: return edges def orderAndFlipEdges(self, inputEdges): - PathLog.track( + Path.Log.track( "entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % ( self.entry.x, @@ -493,7 +492,7 @@ class MapWireToTag: ) cnt = cnt + 1 p0 = p - PathLog.info("replaced edge with %d straight segments" % cnt) + Path.Log.info("replaced edge with %d straight segments" % cnt) edges.remove(e) lastP = None p0 = p1 @@ -505,25 +504,25 @@ class MapWireToTag: if lastP == p0: self.edgesOrder.append(outputEdges) self.edgesOrder.append(edges) - PathLog.debug("input edges:") + Path.Log.debug("input edges:") for e in inputEdges: debugEdge(e, " ", False) - PathLog.debug("ordered edges:") + Path.Log.debug("ordered edges:") for e, flip in outputEdges: debugEdge(e, " %c " % ("<" if flip else ">"), False) - PathLog.debug("remaining edges:") + Path.Log.debug("remaining edges:") for e in edges: debugEdge(e, " ", False) raise ValueError("No connection to %s" % (p0)) elif lastP: - PathLog.debug( + Path.Log.debug( "xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z) ) else: - PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) + Path.Log.debug("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) lastP = p0 - PathLog.track("-") + Path.Log.track("-") return outputEdges def isStrut(self, edge): @@ -605,7 +604,7 @@ class MapWireToTag: # rapid = None # commented out per LGTM suggestion return commands except Exception as e: - PathLog.error( + Path.Log.error( "Exception during processing tag @(%.2f, %.2f) (%s) - disabling the tag" % (self.tag.x, self.tag.y, e.args[0]) ) @@ -624,7 +623,7 @@ class MapWireToTag: if self.tag.solid.isInside( edge.valueAt(edge.LastParameter), PathGeom.Tolerance, True ): - PathLog.track("solid.isInside") + Path.Log.track("solid.isInside") self.addEdge(edge) else: i = self.tag.intersects(edge, edge.LastParameter) @@ -632,13 +631,13 @@ class MapWireToTag: self.offendingEdge = edge debugEdge(edge, "offending Edge:", False) o = self.tag.originAt(self.tag.z) - PathLog.debug("originAt: (%.2f, %.2f, %.2f)" % (o.x, o.y, o.z)) + Path.Log.debug("originAt: (%.2f, %.2f, %.2f)" % (o.x, o.y, o.z)) i = edge.valueAt(edge.FirstParameter) if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - PathLog.track("tail") + Path.Log.track("tail") self.tail = edge else: - PathLog.track("split") + Path.Log.track("split") e, tail = PathGeom.splitEdgeAt(edge, i) self.addEdge(e) self.tail = tail @@ -675,7 +674,7 @@ class _RapidEdges: class PathData: def __init__(self, obj): - PathLog.track(obj.Base.Name) + Path.Log.track(obj.Base.Name) self.obj = obj self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) self.rapid = _RapidEdges(rapid) @@ -727,7 +726,7 @@ class PathData: def generateTags( self, obj, count, width=None, height=None, angle=None, radius=None, spacing=None ): - PathLog.track(count, width, height, angle, spacing) + Path.Log.track(count, width, height, angle, spacing) # for e in self.baseWire.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -746,7 +745,7 @@ class PathData: startIndex = 0 for i in range(0, len(self.baseWire.Edges)): edge = self.baseWire.Edges[i] - PathLog.debug(" %d: %.2f" % (i, edge.Length)) + Path.Log.debug(" %d: %.2f" % (i, edge.Length)) if PathGeom.isRoughly(edge.Length, longestEdge.Length): startIndex = i break @@ -761,7 +760,7 @@ class PathData: minLength = min(2.0 * W, longestEdge.Length) - PathLog.debug( + Path.Log.debug( "length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % ( self.baseWire.Length, @@ -772,12 +771,12 @@ class PathData: minLength, ) ) - PathLog.debug( + Path.Log.debug( " start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance) ) - PathLog.debug(" -> lastTagLength=%.2f)" % lastTagLength) - PathLog.debug(" -> currentLength=%.2f)" % currentLength) + Path.Log.debug(" -> lastTagLength=%.2f)" % lastTagLength) + Path.Log.debug(" -> currentLength=%.2f)" % currentLength) edgeDict = {startIndex: startCount} @@ -796,7 +795,7 @@ class PathData: for (i, count) in PathUtil.keyValueIter(edgeDict): edge = self.baseWire.Edges[i] - PathLog.debug(" %d: %d" % (i, count)) + Path.Log.debug(" %d: %d" % (i, count)) # debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) # debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) if 0 != count: @@ -836,12 +835,12 @@ class PathData: tags.append(Tag(j, at.x, at.y, W, H, A, R, True)) j += 1 else: - PathLog.warning( + Path.Log.warning( "Tag[%d] (%.2f, %.2f, %.2f) is too far away to copy: %.2f (%.2f)" % (i, pos.x, pos.y, self.minZ, dist[0], W) ) else: - PathLog.info("tag[%d]: not enabled, skipping" % i) + Path.Log.info("tag[%d]: not enabled, skipping" % i) print("copied %d tags" % len(tags)) return tags @@ -862,10 +861,10 @@ class PathData: tagCount += 1 lastTagLength += tagDistance if tagCount > 0: - PathLog.debug(" index=%d -> count=%d" % (index, tagCount)) + Path.Log.debug(" index=%d -> count=%d" % (index, tagCount)) edgeDict[index] = tagCount else: - PathLog.debug(" skipping=%-2d (%.2f)" % (index, edge.Length)) + Path.Log.debug(" skipping=%-2d (%.2f)" % (index, edge.Length)) return (currentLength, lastTagLength) @@ -913,7 +912,7 @@ class PathData: ordered.append(t) # disable all tags that are not on the base wire. for tag in tags: - PathLog.info( + Path.Log.info( "Tag #%d (%.2f, %.2f, %.2f) not on base wire - disabling\n" % (len(ordered), tag.x, tag.y, self.minZ) ) @@ -923,7 +922,7 @@ class PathData: def pointIsOnPath(self, p): v = Part.Vertex(self.pointAtBottom(p)) - PathLog.debug("pt = (%f, %f, %f)" % (v.X, v.Y, v.Z)) + Path.Log.debug("pt = (%f, %f, %f)" % (v.X, v.Y, v.Z)) for e in self.bottomEdges: indent = "{} ".format(e.distToShape(v)[0]) debugEdge(e, indent, True) @@ -1071,7 +1070,7 @@ class ObjectTagDressup: return True def createPath(self, obj, pathData, tags): - PathLog.track() + Path.Log.track() commands = [] lastEdge = 0 lastTag = 0 @@ -1097,7 +1096,7 @@ class ObjectTagDressup: vertRapid = tc.VertRapid.Value while edge or lastEdge < len(pathData.edges): - PathLog.debug( + Path.Log.debug( "------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags)) ) if not edge: @@ -1194,10 +1193,10 @@ class ObjectTagDressup: if tag.enabled: if prev: if prev.solid.common(tag.solid).Faces: - PathLog.info( + Path.Log.info( "Tag #%d intersects with previous tag - disabling\n" % i ) - PathLog.debug("this tag = %d [%s]" % (i, tag.solid.BoundBox)) + Path.Log.debug("this tag = %d [%s]" % (i, tag.solid.BoundBox)) tag.enabled = False elif self.pathData.edges: e = self.pathData.edges[0] @@ -1206,14 +1205,14 @@ class ObjectTagDressup: if tag.solid.isInside( p0, PathGeom.Tolerance, True ) or tag.solid.isInside(p1, PathGeom.Tolerance, True): - PathLog.info( + Path.Log.info( "Tag #%d intersects with starting point - disabling\n" % i ) tag.enabled = False if tag.enabled: prev = tag - PathLog.debug("previousTag = %d [%s]" % (i, prev)) + Path.Log.debug("previousTag = %d [%s]" % (i, prev)) else: disabled.append(i) tag.nr = i # assign final nr @@ -1241,7 +1240,7 @@ class ObjectTagDressup: pathData = self.setup(obj) if not pathData: - PathLog.debug("execute - no pathData") + Path.Log.debug("execute - no pathData") return self.tags = [] @@ -1250,21 +1249,21 @@ class ObjectTagDressup: obj, obj.Positions, obj.Disabled ) if obj.Disabled != disabled: - PathLog.debug( + Path.Log.debug( "Updating properties.... %s vs. %s" % (obj.Disabled, disabled) ) obj.Positions = positions obj.Disabled = disabled if not self.tags: - PathLog.debug("execute - no tags") + Path.Log.debug("execute - no tags") obj.Path = obj.Base.Path return try: self.processTags(obj) except Exception as e: - PathLog.error( + Path.Log.error( "processing tags failed clearing all tags ... '%s'" % (e.args[0]) ) obj.Path = obj.Base.Path @@ -1283,11 +1282,11 @@ class ObjectTagDressup: @waiting_effects def processTags(self, obj): tagID = 0 - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: for tag in self.tags: tagID += 1 if tag.enabled: - PathLog.debug( + Path.Log.debug( "x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ) ) # debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) @@ -1299,12 +1298,12 @@ class ObjectTagDressup: obj.Path = self.createPath(obj, self.pathData, self.tags) def setup(self, obj, generate=False): - PathLog.debug("setup") + Path.Log.debug("setup") self.obj = obj try: pathData = PathData(obj) except ValueError: - PathLog.error( + Path.Log.error( translate( "Path_DressupTag", "Cannot insert holding tags for this path - please select a Profile path", @@ -1325,7 +1324,7 @@ class ObjectTagDressup: return self.pathData def setXyEnabled(self, triples): - PathLog.track() + Path.Log.track() if not self.pathData: self.setup(self.obj) positions = [] @@ -1358,13 +1357,13 @@ def Create(baseObject, name="DressupTag"): Create(basePath, name='DressupTag') ... create tag dressup object for the given base path. """ if not baseObject.isDerivedFrom("Path::Feature"): - PathLog.error( + Path.Log.error( translate("Path_DressupTag", "The selected object is not a path") + "\n" ) return None if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - PathLog.error(translate("Path_DressupTag", "Please select a Profile object")) + Path.Log.error(translate("Path_DressupTag", "Please select a Profile object")) return None obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) @@ -1375,4 +1374,4 @@ def Create(baseObject, name="DressupTag"): return obj -PathLog.notice("Loading Path_DressupTag... done\n") +Path.Log.notice("Loading Path_DressupTag... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index e4e70395cb..49b94593a2 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -28,7 +28,6 @@ import FreeCADGui import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import math import copy @@ -42,10 +41,10 @@ from PathPythonGui.simple_edit_panel import SimpleEditPanel translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) movecommands = ["G1", "G01", "G2", "G02", "G3", "G03"] @@ -186,7 +185,7 @@ class ObjectDressup: if not obj.Base.Path: return if obj.Length < 0: - PathLog.error( + Path.Log.error( translate("Path_DressupLeadInOut", "Length/Radius positive not Null") + "\n" ) @@ -268,12 +267,12 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) - PathLog.debug(" CURRENT_IN Line : P0 Z:{} p1 Z:{}".format(p0.z, p1.z)) + Path.Log.debug(" CURRENT_IN Line : P0 Z:{} p1 Z:{}".format(p0.z, p1.z)) else: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) - PathLog.debug( + Path.Log.debug( " CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format( p0.x, p0.y, p1.x, p1.y ) @@ -292,7 +291,7 @@ class ObjectDressup: vec_n = self.normalize(vec) vec_inv = self.invert(vec_n) vec_off = self.multiply(vec_inv, obj.ExtendLeadIn) - PathLog.debug( + Path.Log.debug( "LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format( queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y ) @@ -423,7 +422,7 @@ class ObjectDressup: extendcommand = Path.Command("G1", {"X": p0.x, "Y": p0.y, "F": horizFeed}) results.append(extendcommand) else: - PathLog.debug(" CURRENT_IN Perp") + Path.Log.debug(" CURRENT_IN Perp") currLocation.update(results[-1].Parameters) currLocation["Z"] = p1.z @@ -464,7 +463,7 @@ class ObjectDressup: vec_n = self.normalize(vec) vec_inv = self.invert(vec_n) vec_off = self.multiply(vec_inv, obj.ExtendLeadOut) - PathLog.debug( + Path.Log.debug( "LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format( queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y ) @@ -540,7 +539,7 @@ class ObjectDressup: ) results.append(extendcommand) else: - PathLog.debug(" CURRENT_IN Perp") + Path.Log.debug(" CURRENT_IN Perp") if obj.UseMachineCRC: # crc off results.append(Path.Command("G40", {})) @@ -560,7 +559,7 @@ class ObjectDressup: # Read in all commands for curCommand in obj.Base.Path.Commands: - PathLog.debug("CurCMD: {}".format(curCommand)) + Path.Log.debug("CurCMD: {}".format(curCommand)) if curCommand.Name not in movecommands + rapidcommands: # Don't worry about non-move commands, just add to output newpath.append(curCommand) @@ -587,7 +586,7 @@ class ObjectDressup: and prevCmd.Name in movecommands ): # Layer change within move cmds - PathLog.debug( + Path.Log.debug( "Layer change in move: {}->{}".format( currLocation["Z"], curCommand.z ) @@ -608,14 +607,14 @@ class ObjectDressup: # Go through each layer and add leadIn/Out idx = 0 for layer in layers: - PathLog.debug("Layer {}".format(idx)) + Path.Log.debug("Layer {}".format(idx)) if obj.LeadIn: temp = self.getLeadStart(obj, layer, action) newpath.extend(temp) for cmd in layer: - PathLog.debug("CurLoc: {}, NewCmd: {}!!".format(currLocation, cmd)) + Path.Log.debug("CurLoc: {}, NewCmd: {}!!".format(currLocation, cmd)) newpath.append(cmd) if obj.LeadOut: @@ -690,7 +689,7 @@ class ViewProviderDressup: def onDelete(self, arg1=None, arg2=None): """this makes sure that the base operation is added back to the project and visible""" - PathLog.debug("Deleting Dressup") + Path.Log.debug("Deleting Dressup") if arg1.Object and arg1.Object.Base: FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True job = PathUtils.findParentJob(self.obj) @@ -730,20 +729,20 @@ class CommandPathDressupLeadInOut: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - PathLog.error( + Path.Log.error( translate("Path_DressupLeadInOut", "Please select one path object") + "\n" ) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): - PathLog.error( + Path.Log.error( translate("Path_DressupLeadInOut", "The selected object is not a path") + "\n" ) return if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - PathLog.error( + Path.Log.error( translate("Path_DressupLeadInOut", "Please select a Profile object") ) return @@ -777,4 +776,4 @@ if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand("Path_DressupLeadInOut", CommandPathDressupLeadInOut()) -PathLog.notice("Loading Path_DressupLeadInOut... done\n") +Path.Log.notice("Loading Path_DressupLeadInOut... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py b/src/Mod/Path/PathScripts/PathDressupPathBoundary.py index 5a45f60965..f4730e30a6 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py +++ b/src/Mod/Path/PathScripts/PathDressupPathBoundary.py @@ -25,16 +25,15 @@ import FreeCAD import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathStock as PathStock import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -127,7 +126,7 @@ class PathBoundary: self.strG0ZclearanceHeight = None def boundaryCommands(self, begin, end, verticalFeed): - PathLog.track(_vstr(begin), _vstr(end)) + Path.Log.track(_vstr(begin), _vstr(end)) if end and PathGeom.pointsCoincide(begin, end): return [] cmds = [] @@ -152,7 +151,7 @@ class PathBoundary: return None if len(self.baseOp.Path.Commands) == 0: - PathLog.warning("No Path Commands for %s" % self.baseOp.Label) + Path.Log.warning("No Path Commands for %s" % self.baseOp.Label) return [] tc = PathDressup.toolController(self.baseOp) @@ -189,7 +188,7 @@ class PathBoundary: # it's really a shame that one cannot trust the sequence and/or # orientation of edges if 1 == len(inside) and 0 == len(outside): - PathLog.track(_vstr(pos), _vstr(lastExit), " + ", cmd) + Path.Log.track(_vstr(pos), _vstr(lastExit), " + ", cmd) # cmd fully included by boundary if lastExit: if not ( @@ -204,19 +203,19 @@ class PathBoundary: commands.append(cmd) pos = PathGeom.commandEndPoint(cmd, pos) elif 0 == len(inside) and 1 == len(outside): - PathLog.track(_vstr(pos), _vstr(lastExit), " - ", cmd) + Path.Log.track(_vstr(pos), _vstr(lastExit), " - ", cmd) # cmd fully excluded by boundary if not lastExit: lastExit = pos pos = PathGeom.commandEndPoint(cmd, pos) else: - PathLog.track( + Path.Log.track( _vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd ) # cmd pierces boundary while inside or outside: ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] - PathLog.track(ie) + Path.Log.track(ie) if ie: e = ie[0] LastPt = e.valueAt(e.LastParameter) @@ -232,7 +231,7 @@ class PathBoundary: ) ) lastExit = None - PathLog.track(e, flip) + Path.Log.track(e, flip) if not ( bogusX or bogusY ): # don't insert false paths based on bogus m/c position @@ -255,7 +254,7 @@ class PathBoundary: for e in outside if PathGeom.edgeConnectsTo(e, pos) ] - PathLog.track(oe) + Path.Log.track(oe) if oe: e = oe[0] ptL = e.valueAt(e.LastParameter) @@ -268,7 +267,7 @@ class PathBoundary: outside.remove(e) pos = newPos else: - PathLog.error("huh?") + Path.Log.error("huh?") import Part Part.show(Part.Vertex(pos), "pos") @@ -284,13 +283,13 @@ class PathBoundary: # pos = PathGeom.commandEndPoint(cmd, pos) # Eif else: - PathLog.track("no-move", cmd) + Path.Log.track("no-move", cmd) commands.append(cmd) if lastExit: commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value)) lastExit = None - PathLog.track(commands) + Path.Log.track(commands) return Path.Path(commands) @@ -301,7 +300,7 @@ def Create(base, name="DressupPathBoundary"): """Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary.""" if not base.isDerivedFrom("Path::Feature"): - PathLog.error( + Path.Log.error( translate("Path_DressupPathBoundary", "The selected object is not a path") + "\n" ) diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py b/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py index 9339ba8159..d99b677b6a 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py +++ b/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py @@ -24,15 +24,15 @@ from PySide import QtGui from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressupPathBoundary as PathDressupPathBoundary -import PathScripts.PathLog as PathLog if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -116,7 +116,7 @@ class TaskPanel(object): import PathScripts.PathStock as PathStock def setupFromBaseEdit(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockFromBase: self.stockFromBase = PathJobGui.StockFromBaseBoundBoxEdit( self.obj, self.form, force @@ -124,7 +124,7 @@ class TaskPanel(object): self.stockEdit = self.stockFromBase def setupCreateBoxEdit(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockCreateBox: self.stockCreateBox = PathJobGui.StockCreateBoxEdit( self.obj, self.form, force @@ -132,7 +132,7 @@ class TaskPanel(object): self.stockEdit = self.stockCreateBox def setupCreateCylinderEdit(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockCreateCylinder: self.stockCreateCylinder = PathJobGui.StockCreateCylinderEdit( self.obj, self.form, force @@ -140,7 +140,7 @@ class TaskPanel(object): self.stockEdit = self.stockCreateCylinder def setupFromExisting(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockFromExisting: self.stockFromExisting = PathJobGui.StockFromExistingEdit( self.obj, self.form, force @@ -162,7 +162,7 @@ class TaskPanel(object): elif PathJobGui.StockFromExistingEdit.IsStock(self.obj): setupFromExisting() else: - PathLog.error( + Path.Log.error( translate("PathJob", "Unsupported stock object %s") % self.obj.Stock.Label ) @@ -178,7 +178,7 @@ class TaskPanel(object): setupFromBaseEdit() index = -1 else: - PathLog.error( + Path.Log.error( translate("PathJob", "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index) ) @@ -280,7 +280,7 @@ class CommandPathDressupPathBoundary: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - PathLog.error( + Path.Log.error( translate("Path_DressupPathBoundary", "Please select one path object") + "\n" ) @@ -302,4 +302,4 @@ if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand("Path_DressupPathBoundary", CommandPathDressupPathBoundary()) -PathLog.notice("Loading PathDressupPathBoundaryGui... done\n") +Path.Log.notice("Loading PathDressupPathBoundaryGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressupRampEntry.py b/src/Mod/Path/PathScripts/PathDressupRampEntry.py index 603b977a02..e77783542a 100644 --- a/src/Mod/Path/PathScripts/PathDressupRampEntry.py +++ b/src/Mod/Path/PathScripts/PathDressupRampEntry.py @@ -26,7 +26,6 @@ import FreeCAD import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import math @@ -43,10 +42,10 @@ translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectDressup: @@ -158,11 +157,11 @@ class ObjectDressup: data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -263,7 +262,7 @@ class ObjectDressup: projectionlen = plungelen * math.tan( math.radians(rampangle) ) # length of the forthcoming ramp projected to XY plane - PathLog.debug( + Path.Log.debug( "Found plunge move at X:{} Y:{} From Z:{} to Z{}, length of ramp: {}".format( p0.x, p0.y, p0.z, p1.z, projectionlen ) @@ -284,7 +283,7 @@ class ObjectDressup: if abs(cp0.z - cp1.z) > 1e-6: # this edge is not parallel to XY plane, not qualified for ramping. break - # PathLog.debug("Next edge length {}".format(candidate.Length)) + # Path.Log.debug("Next edge length {}".format(candidate.Length)) rampedges.append(candidate) coveredlen = coveredlen + candidate.Length @@ -294,7 +293,7 @@ class ObjectDressup: if i >= len(edges): break if len(rampedges) == 0: - PathLog.debug( + Path.Log.debug( "No suitable edges for ramping, plunge will remain as such" ) outedges.append(edge) @@ -310,13 +309,13 @@ class ObjectDressup: ) else: rampangle = math.degrees(math.atan(l / plungelen)) - PathLog.warning( + Path.Log.warning( "Cannot cover with desired angle, tightening angle to: {}".format( rampangle ) ) - # PathLog.debug("Doing ramp to edges: {}".format(rampedges)) + # Path.Log.debug("Doing ramp to edges: {}".format(rampedges)) if self.method == "RampMethod1": outedges.extend( self.createRampMethod1( @@ -355,7 +354,7 @@ class ObjectDressup: def generateHelix(self): edges = self.wire.Edges minZ = self.findMinZ(edges) - PathLog.debug("Minimum Z in this path is {}".format(minZ)) + Path.Log.debug("Minimum Z in this path is {}".format(minZ)) outedges = [] i = 0 while i < len(edges): @@ -375,7 +374,7 @@ class ObjectDressup: and p0.z > p1.z ): # plungelen = abs(p0.z-p1.z) - PathLog.debug( + Path.Log.debug( "Found plunge move at X:{} Y:{} From Z:{} to Z{}, Searching for closed loop".format( p0.x, p0.y, p0.z, p1.z ) @@ -404,13 +403,13 @@ class ObjectDressup: if abs(cp0.z - cp1.z) > 1e-6: # this edge is not parallel to XY plane, not qualified for ramping. break - # PathLog.debug("Next edge length {}".format(candidate.Length)) + # Path.Log.debug("Next edge length {}".format(candidate.Length)) rampedges.append(candidate) j = j + 1 if j >= len(edges): break if len(rampedges) == 0 or not loopFound: - PathLog.debug("No suitable helix found") + Path.Log.debug("No suitable helix found") outedges.append(edge) else: outedges.extend(self.createHelix(rampedges, p0, p1)) @@ -434,12 +433,12 @@ class ObjectDressup: p1.z > self.ignoreAbove or PathGeom.isRoughly(p1.z, self.ignoreAbove.Value) ): - PathLog.debug("Whole plunge move above 'ignoreAbove', ignoring") + Path.Log.debug("Whole plunge move above 'ignoreAbove', ignoring") return (edge, True) elif p0.z > self.ignoreAbove and not PathGeom.isRoughly( p0.z, self.ignoreAbove.Value ): - PathLog.debug( + Path.Log.debug( "Plunge move partially above 'ignoreAbove', splitting into two" ) newPoint = FreeCAD.Base.Vector(p0.x, p0.y, self.ignoreAbove) @@ -474,7 +473,7 @@ class ObjectDressup: return outedges def createRampEdge(self, originalEdge, startPoint, endPoint): - # PathLog.debug("Create edge from [{},{},{}] to [{},{},{}]".format(startPoint.x,startPoint.y, startPoint.z, endPoint.x, endPoint.y, endPoint.z)) + # Path.Log.debug("Create edge from [{},{},{}] to [{},{},{}]".format(startPoint.x,startPoint.y, startPoint.z, endPoint.x, endPoint.y, endPoint.z)) if ( type(originalEdge.Curve) == Part.Line or type(originalEdge.Curve) == Part.LineSegment @@ -487,7 +486,7 @@ class ObjectDressup: arcMid.z = (startPoint.z + endPoint.z) / 2 return Part.Arc(startPoint, arcMid, endPoint).toShape() else: - PathLog.error("Edge should not be helix") + Path.Log.error("Edge should not be helix") def getreversed(self, edges): """ @@ -504,7 +503,7 @@ class ObjectDressup: arcMid = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2) outedges.append(Part.Arc(startPoint, arcMid, endPoint).toShape()) else: - PathLog.error("Edge should not be helix") + Path.Log.error("Edge should not be helix") return outedges def findMinZ(self, edges): @@ -545,8 +544,8 @@ class ObjectDressup: # will reach end of ramp within this edge, needs to be split p1 = self.getSplitPoint(redge, rampremaining) splitEdge = PathGeom.splitEdgeAt(redge, p1) - PathLog.debug("Ramp remaining: {}".format(rampremaining)) - PathLog.debug( + Path.Log.debug("Ramp remaining: {}".format(rampremaining)) + Path.Log.debug( "Got split edge (index: {}) (total len: {}) with lengths: {}, {}".format( i, redge.Length, splitEdge[0].Length, splitEdge[1].Length ) @@ -585,7 +584,7 @@ class ObjectDressup: if not done: # we did not reach the end of the ramp going this direction, lets reverse. rampedges = self.getreversed(rampedges) - PathLog.debug("Reversing") + Path.Log.debug("Reversing") if goingForward: goingForward = False else: @@ -628,7 +627,7 @@ class ObjectDressup: # will reach end of ramp within this edge, needs to be split p1 = self.getSplitPoint(redge, rampremaining) splitEdge = PathGeom.splitEdgeAt(redge, p1) - PathLog.debug( + Path.Log.debug( "Got split edge (index: {}) with lengths: {}, {}".format( i, splitEdge[0].Length, splitEdge[1].Length ) @@ -707,7 +706,7 @@ class ObjectDressup: PathGeom.xy(p0), PathGeom.xy(rampedges[-1].valueAt(rampedges[-1].LastParameter)), ): - PathLog.debug( + Path.Log.debug( "The ramp forms a closed wire, needless to move on original Z height" ) else: @@ -716,7 +715,7 @@ class ObjectDressup: # this edge needs to be split p1 = self.getSplitPoint(redge, rampremaining) splitEdge = PathGeom.splitEdgeAt(redge, p1) - PathLog.debug( + Path.Log.debug( "Got split edges with lengths: {}, {}".format( splitEdge[0].Length, splitEdge[1].Length ) @@ -877,7 +876,7 @@ class ViewProviderDressup: def onDelete(self, arg1=None, arg2=None): """this makes sure that the base operation is added back to the project and visible""" - PathLog.debug("Deleting Dressup") + Path.Log.debug("Deleting Dressup") if arg1.Object and arg1.Object.Base: FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True job = PathUtils.findParentJob(self.obj) @@ -917,20 +916,20 @@ class CommandPathDressupRampEntry: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - PathLog.error( + Path.Log.error( translate("Path_DressupRampEntry", "Please select one path object") + "\n" ) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): - PathLog.error( + Path.Log.error( translate("Path_DressupRampEntry", "The selected object is not a path") + "\n" ) return if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - PathLog.error( + Path.Log.error( translate("Path_DressupRampEntry", "Please select a Profile object") ) return @@ -964,4 +963,4 @@ if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand("Path_DressupRampEntry", CommandPathDressupRampEntry()) -PathLog.notice("Loading Path_DressupRampEntry... done\n") +Path.Log.notice("Loading Path_DressupRampEntry... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressupTag.py b/src/Mod/Path/PathScripts/PathDressupTag.py index 6a9487adea..89e97a5f2b 100644 --- a/src/Mod/Path/PathScripts/PathDressupTag.py +++ b/src/Mod/Path/PathScripts/PathDressupTag.py @@ -23,9 +23,9 @@ from PathScripts.PathDressupTagPreferences import HoldingTagPreferences from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD +import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import math @@ -37,10 +37,10 @@ Part = LazyLoader("Part", globals(), "Part") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -69,7 +69,7 @@ class TagSolid: # cylinder self.solid = Part.makeCylinder(r1, height) radius = min(min(self.radius, r1), self.height) - PathLog.debug("Part.makeCylinder(%f, %f)" % (r1, height)) + Path.Log.debug("Part.makeCylinder(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: # cone rad = math.radians(self.angle) @@ -86,17 +86,17 @@ class TagSolid: height = r1 * tangens * 1.01 self.actualHeight = height self.r2 = r2 - PathLog.debug("Part.makeCone(r1=%.2f, r2=%.2f, h=%.2f)" % (r1, r2, height)) + Path.Log.debug("Part.makeCone(r1=%.2f, r2=%.2f, h=%.2f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag - PathLog.debug("Part.makeSphere(%.2f)" % (r1 / 10000)) + Path.Log.debug("Part.makeSphere(%.2f)" % (r1 / 10000)) self.solid = Part.makeSphere(r1 / 10000) radius = min(self.radius, radius) self.realRadius = radius if radius != 0: - PathLog.debug("makeFillet(%.4f)" % radius) + Path.Log.debug("makeFillet(%.4f)" % radius) self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]]) # lastly determine the center of the model, we want to make sure the seam of @@ -197,22 +197,22 @@ class ObjectDressup: self.obj.Radius = HoldingTagPreferences.defaultRadius() def execute(self, obj): - PathLog.track() + Path.Log.track() if not obj.Base: - PathLog.error(translate("Path_DressupTag", "No Base object found.")) + Path.Log.error(translate("Path_DressupTag", "No Base object found.")) return if not obj.Base.isDerivedFrom("Path::Feature"): - PathLog.error( + Path.Log.error( translate("Path_DressupTag", "Base is not a Path::Feature object.") ) return if not obj.Base.Path: - PathLog.error( + Path.Log.error( translate("Path_DressupTag", "Base doesn't have a Path to dress-up.") ) return if not obj.Base.Path.Commands: - PathLog.error(translate("Path_DressupTag", "Base Path is empty.")) + Path.Log.error(translate("Path_DressupTag", "Base Path is empty.")) return self.obj = obj @@ -241,7 +241,7 @@ class ObjectDressup: maxZ = max(pt.z, maxZ) minZ = min(pt.z, minZ) lastPt = pt - PathLog.debug( + Path.Log.debug( "bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)" % (minX, minY, minZ, maxX, maxY, maxZ) ) @@ -273,7 +273,7 @@ class ObjectDressup: obj.Path = obj.Base.Path - PathLog.track() + Path.Log.track() def toolRadius(self): return float(PathDressup.toolController(self.obj.Base).Tool.Diameter) / 2.0 @@ -298,13 +298,13 @@ def Create(baseObject, name="DressupTag"): Create(basePath, name = 'DressupTag') ... create tag dressup object for the given base path. """ if not baseObject.isDerivedFrom("Path::Feature"): - PathLog.error( + Path.Log.error( translate("Path_DressupTag", "The selected object is not a path") + "\n" ) return None if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - PathLog.error(translate("Path_DressupTag", "Please select a Profile object")) + Path.Log.error(translate("Path_DressupTag", "Please select a Profile object")) return None obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) @@ -315,4 +315,4 @@ def Create(baseObject, name="DressupTag"): return obj -PathLog.notice("Loading Path_DressupTag... done\n") +Path.Log.notice("Loading Path_DressupTag... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressupTagGui.py b/src/Mod/Path/PathScripts/PathDressupTagGui.py index ff8e719bb2..13b0d5ced4 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagGui.py +++ b/src/Mod/Path/PathScripts/PathDressupTagGui.py @@ -25,26 +25,26 @@ from PySide.QtCore import QT_TRANSLATE_NOOP from pivy import coin import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressupHoldingTags as PathDressupTag import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtils as PathUtils if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate def addDebugDisplay(): - return PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG + return Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG class PathDressupTagTaskPanel: @@ -164,7 +164,7 @@ class PathDressupTagTaskPanel: self.isDirty = True def updateTagsView(self): - PathLog.track() + Path.Log.track() self.form.lwTags.blockSignals(True) self.form.lwTags.clear() for i, pos in enumerate(self.Positions): @@ -189,7 +189,7 @@ class PathDressupTagTaskPanel: def generateNewTags(self): count = self.form.sbCount.value() - PathLog.track(count) + Path.Log.track(count) if not self.obj.Proxy.generateTags(self.obj, count): self.obj.Proxy.execute(self.obj) self.Positions = self.obj.Positions @@ -206,7 +206,7 @@ class PathDressupTagTaskPanel: self.Disabled = self.obj.Disabled self.updateTagsView() else: - PathLog.error("Cannot copy tags - internal error") + Path.Log.error("Cannot copy tags - internal error") def updateModel(self): self.getFields() @@ -218,7 +218,7 @@ class PathDressupTagTaskPanel: self.form.pbGenerate.setEnabled(count) def selectTagWithId(self, index): - PathLog.track(index) + Path.Log.track(index) self.form.lwTags.setCurrentRow(index) def whenTagSelectionChanged(self): @@ -242,18 +242,18 @@ class PathDressupTagTaskPanel: def addNewTagAt(self, point, obj): if point and obj and self.obj.Proxy.pointIsOnPath(self.obj, point): - PathLog.info("addNewTagAt(%.2f, %.2f)" % (point.x, point.y)) + Path.Log.info("addNewTagAt(%.2f, %.2f)" % (point.x, point.y)) self.Positions.append(FreeCAD.Vector(point.x, point.y, 0)) self.updateTagsView() else: - PathLog.notice("ignore new tag at %s (obj=%s, on-path=%d" % (point, obj, 0)) + Path.Log.notice("ignore new tag at %s (obj=%s, on-path=%d" % (point, obj, 0)) def addNewTag(self): self.tags = self.getTags(True) self.getPoint.getPoint(self.addNewTagAt) def editTagAt(self, point, obj): - PathLog.track(point, obj) + Path.Log.track(point, obj) if point and self.obj.Proxy.pointIsOnPath(self.obj, point): tags = [] for i, (x, y, enabled) in enumerate(self.tags): @@ -363,7 +363,7 @@ class HoldingTagMarker: class PathDressupTagViewProvider: def __init__(self, vobj): - PathLog.track() + Path.Log.track() self.vobj = vobj self.panel = None @@ -413,7 +413,7 @@ class PathDressupTagViewProvider: ] def attach(self, vobj): - PathLog.track() + Path.Log.track() self.setupColors() self.vobj = vobj self.obj = vobj.Object @@ -438,14 +438,14 @@ class PathDressupTagViewProvider: self.switch.whichChild = sw def claimChildren(self): - PathLog.track() + Path.Log.track() # if self.debugDisplay(): # return [self.obj.Base, self.vobj.Debug] return [self.obj.Base] def onDelete(self, arg1=None, arg2=None): """this makes sure that the base operation is added back to the job and visible""" - PathLog.track() + Path.Log.track() if self.obj.Base and self.obj.Base.ViewObject: self.obj.Base.ViewObject.Visibility = True job = PathUtils.findParentJob(self.obj) @@ -472,12 +472,12 @@ class PathDressupTagViewProvider: self.tags = tags def updateData(self, obj, propName): - PathLog.track(propName) + Path.Log.track(propName) if "Disabled" == propName: self.updatePositions(obj.Positions, obj.Disabled) def onModelChanged(self): - PathLog.track() + Path.Log.track() # if self.debugDisplay(): # self.vobj.Debug.removeObjectsFromDocument() # for solid in self.obj.Proxy.solids: @@ -515,7 +515,7 @@ class PathDressupTagViewProvider: # SelectionObserver interface def selectTag(self, index): - PathLog.track(index) + Path.Log.track(index) for i, tag in enumerate(self.tags): tag.setSelected(i == index) @@ -539,7 +539,7 @@ class PathDressupTagViewProvider: return False def addSelection(self, doc, obj, sub, point): - PathLog.track(doc, obj, sub, point) + Path.Log.track(doc, obj, sub, point) if self.panel: i = self.tagAtPoint(point, sub is None) self.panel.selectTagWithId(i) @@ -580,7 +580,7 @@ class CommandPathDressupTag: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - PathLog.error( + Path.Log.error( translate("Path_DressupTag", "Please select one path object") + "\n" ) return @@ -601,4 +601,4 @@ if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand("Path_DressupTag", CommandPathDressupTag()) -PathLog.notice("Loading PathDressupTagGui... done\n") +Path.Log.notice("Loading PathDressupTagGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressupTagPreferences.py b/src/Mod/Path/PathScripts/PathDressupTagPreferences.py index c242e6c564..91032dd4ff 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagPreferences.py +++ b/src/Mod/Path/PathScripts/PathDressupTagPreferences.py @@ -21,15 +21,15 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathPreferences as PathPreferences import PathScripts.PathPreferencesPathDressup as PathPreferencesPathDressup if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index e9af62107a..16bbbdef01 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -27,7 +27,6 @@ import FreeCAD import FreeCADGui import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils from PySide import QtGui @@ -44,13 +43,13 @@ Part = LazyLoader("Part", globals(), "Part") LOGLEVEL = False -LOG_MODULE = PathLog.thisModule() +LOG_MODULE = Path.Log.thisModule() if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -130,12 +129,12 @@ class ObjectDressup: zval = round(float(w[2]), 2) pointlist.append([xval, yval, zval]) - PathLog.debug(pointlist) + Path.Log.debug(pointlist) cols = list(zip(*pointlist)) - PathLog.debug("cols: {}".format(cols)) + Path.Log.debug("cols: {}".format(cols)) yindex = list(sorted(set(cols[1]))) - PathLog.debug("yindex: {}".format(yindex)) + Path.Log.debug("yindex: {}".format(yindex)) array = [] for y in yindex: @@ -173,8 +172,8 @@ class ObjectDressup: currLocation = {"X": 0, "Y": 0, "Z": 0, "F": 0} for c in pathlist: - PathLog.debug(c) - PathLog.debug(" curLoc:{}".format(currLocation)) + Path.Log.debug(c) + Path.Log.debug(" curLoc:{}".format(currLocation)) newparams = dict(c.Parameters) zval = newparams.get("Z", currLocation["Z"]) if c.Name in CmdMoveStraight + CmdMoveArc: @@ -254,18 +253,18 @@ class TaskPanel: def updateUI(self): - if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG: + if Path.Log.getLevel(LOG_MODULE) == Path.Log.Level.DEBUG: for obj in FreeCAD.ActiveDocument.Objects: if obj.Name.startswith("Shape"): FreeCAD.ActiveDocument.removeObject(obj.Name) print("object name %s" % self.obj.Name) if hasattr(self.obj.Proxy, "shapes"): - PathLog.info("showing shapes attribute") + Path.Log.info("showing shapes attribute") for shapes in self.obj.Proxy.shapes.itervalues(): for shape in shapes: Part.show(shape) else: - PathLog.info("no shapes attribute found") + Path.Log.info("no shapes attribute found") def updateModel(self): self.getFields() diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index 8809b2dc05..348dd0695d 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -31,7 +31,6 @@ import Path import PathFeedRate import PathMachineState import PathScripts.PathCircularHoleBase as PathCircularHoleBase -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP @@ -43,10 +42,10 @@ __doc__ = "Path Drilling operation." __contributors__ = "IMBack!" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -84,11 +83,11 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -180,7 +179,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): def circularHoleExecute(self, obj, holes): """circularHoleExecute(obj, holes) ... generate drill operation for each hole in holes.""" - PathLog.track() + Path.Log.track() machine = PathMachineState.MachineState() self.commandlist.append(Path.Command("(Begin Drilling)")) @@ -220,7 +219,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): # iterate the edgelist and generate gcode for edge in edgelist: - PathLog.debug(edge) + Path.Log.debug(edge) # move to hole location @@ -259,7 +258,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): ) except ValueError as e: # any targets that fail the generator are ignored - PathLog.info(e) + Path.Log.info(e) continue for command in drillcommands: diff --git a/src/Mod/Path/PathScripts/PathDrillingGui.py b/src/Mod/Path/PathScripts/PathDrillingGui.py index 64c093c66e..e1b18f9b91 100644 --- a/src/Mod/Path/PathScripts/PathDrillingGui.py +++ b/src/Mod/Path/PathScripts/PathDrillingGui.py @@ -22,11 +22,11 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathDrilling as PathDrilling import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui from PySide import QtCore @@ -38,10 +38,10 @@ __doc__ = "UI and Command for Path Drilling Operation." __contributors__ = "IMBack!" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): @@ -105,7 +105,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def getFields(self, obj): """setFields(obj) ... update obj's properties with values from the UI""" - PathLog.track() + Path.Log.track() self.peckDepthSpinBox.updateProperty() self.peckRetractSpinBox.updateProperty() self.dwellTimeSpinBox.updateProperty() @@ -124,7 +124,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def setFields(self, obj): """setFields(obj) ... update UI with obj properties' values""" - PathLog.track() + Path.Log.track() self.updateQuantitySpinBoxes() if obj.DwellEnabled: diff --git a/src/Mod/Path/PathScripts/PathEngrave.py b/src/Mod/Path/PathScripts/PathEngrave.py index ca79b735d6..f0da66e0e8 100644 --- a/src/Mod/Path/PathScripts/PathEngrave.py +++ b/src/Mod/Path/PathScripts/PathEngrave.py @@ -23,7 +23,6 @@ import FreeCAD import Path import PathScripts.PathEngraveBase as PathEngraveBase -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils @@ -32,10 +31,10 @@ from PySide.QtCore import QT_TRANSLATE_NOOP __doc__ = "Class and implementation of Path Engrave operation" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -101,12 +100,12 @@ class ObjectEngrave(PathEngraveBase.ObjectOp): def opExecute(self, obj): """opExecute(obj) ... process engraving operation""" - PathLog.track() + Path.Log.track() jobshapes = [] if len(obj.Base) >= 1: # user has selected specific subelements - PathLog.track(len(obj.Base)) + Path.Log.track(len(obj.Base)) wires = [] for base, subs in obj.Base: edges = [] @@ -129,9 +128,9 @@ class ObjectEngrave(PathEngraveBase.ObjectOp): elif len(obj.BaseShapes) > 0: # user added specific shapes jobshapes.extend([base.Shape for base in obj.BaseShapes]) else: - PathLog.track(self.model) + Path.Log.track(self.model) for base in self.model: - PathLog.track(base.Label) + Path.Log.track(base.Label) if base.isDerivedFrom("Part::Part2DObject"): jobshapes.append(base.Shape) elif base.isDerivedFrom("Sketcher::SketchObject"): @@ -140,11 +139,11 @@ class ObjectEngrave(PathEngraveBase.ObjectOp): jobshapes.append(base.Shape) if len(jobshapes) > 0: - PathLog.debug("processing {} jobshapes".format(len(jobshapes))) + Path.Log.debug("processing {} jobshapes".format(len(jobshapes))) wires = [] for shape in jobshapes: shapeWires = shape.Wires - PathLog.debug("jobshape has {} edges".format(len(shape.Edges))) + Path.Log.debug("jobshape has {} edges".format(len(shape.Edges))) self.commandlist.append( Path.Command( "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} @@ -153,7 +152,7 @@ class ObjectEngrave(PathEngraveBase.ObjectOp): self.buildpathocc(obj, shapeWires, self.getZValues(obj)) wires.extend(shapeWires) self.wires = wires - PathLog.debug( + Path.Log.debug( "processing {} jobshapes -> {} wires".format(len(jobshapes), len(wires)) ) # the last command is a move to clearance, which is automatically added by PathOp diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/PathScripts/PathEngraveBase.py index 71810d1fb3..25c6e35cad 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/PathScripts/PathEngraveBase.py @@ -23,7 +23,6 @@ from lazy_loader.lazy_loader import LazyLoader import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathOpTools as PathOpTools import copy @@ -35,10 +34,10 @@ DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") Part = LazyLoader("Part", globals(), "Part") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectOp(PathOp.ObjectOp): @@ -61,7 +60,7 @@ class ObjectOp(PathOp.ObjectOp): def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True, start_idx=0): """buildpathocc(obj, wires, zValues, relZ=False) ... internal helper function to generate engraving commands.""" - PathLog.track(obj.Label, len(wires), zValues) + Path.Log.track(obj.Label, len(wires), zValues) decomposewires = [] for wire in wires: @@ -77,14 +76,14 @@ class ObjectOp(PathOp.ObjectOp): edges = wire.Edges # edges = copy.copy(PathOpTools.orientWire(offset, forward).Edges) - # PathLog.track("wire: {} offset: {}".format(len(wire.Edges), len(edges))) + # Path.Log.track("wire: {} offset: {}".format(len(wire.Edges), len(edges))) # edges = Part.sortEdges(edges)[0] - # PathLog.track("edges: {}".format(len(edges))) + # Path.Log.track("edges: {}".format(len(edges))) last = None for z in zValues: - PathLog.debug(z) + Path.Log.debug(z) if last: self.appendCommand( Path.Command("G1", {"X": last.x, "Y": last.y, "Z": last.z}), @@ -99,19 +98,19 @@ class ObjectOp(PathOp.ObjectOp): edges = edges[start_idx:] + edges[:start_idx] for edge in edges: - PathLog.debug( + Path.Log.debug( "points: {} -> {}".format( edge.Vertexes[0].Point, edge.Vertexes[-1].Point ) ) - PathLog.debug( + Path.Log.debug( "valueat {} -> {}".format( edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter), ) ) if first and (not last or not wire.isClosed()): - PathLog.debug("processing first edge entry") + Path.Log.debug("processing first edge entry") # we set the first move to our first point last = edge.Vertexes[0].Point diff --git a/src/Mod/Path/PathScripts/PathEngraveGui.py b/src/Mod/Path/PathScripts/PathEngraveGui.py index d90064e009..24dc13e30b 100644 --- a/src/Mod/Path/PathScripts/PathEngraveGui.py +++ b/src/Mod/Path/PathScripts/PathEngraveGui.py @@ -22,9 +22,9 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathEngrave as PathEngrave -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui import PathScripts.PathUtils as PathUtils @@ -37,10 +37,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Engrave operation page controller and command implementation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -70,7 +70,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): job = PathUtils.findParentJob(self.obj) base = job.Proxy.resourceClone(job, sel.Object) if not base: - PathLog.notice( + Path.Log.notice( ( translate("Path", "%s is not a Base Model object of the job %s") + "\n" @@ -79,7 +79,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): ) continue if base in shapes: - PathLog.notice( + Path.Log.notice( (translate("Path", "Base shape %s already in the list") + "\n") % (sel.Object.Label) ) @@ -89,7 +89,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): # selectively add some elements of the drawing to the Base for sub in sel.SubElementNames: if "Vertex" in sub: - PathLog.info("Ignoring vertex") + Path.Log.info("Ignoring vertex") else: self.obj.Proxy.addBase(self.obj, base, sub) else: @@ -116,7 +116,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): self.form.baseList.blockSignals(False) def updateBase(self): - PathLog.track() + Path.Log.track() shapes = [] for i in range(self.form.baseList.count()): item = self.form.baseList.item(i) @@ -124,7 +124,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): sub = item.data(self.super().DataObjectSub) if not sub: shapes.append(obj) - PathLog.debug( + Path.Log.debug( "Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes) ) self.obj.BaseShapes = shapes diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensions.py b/src/Mod/Path/PathScripts/PathFeatureExtensions.py index 9a734de260..da4e0c66fc 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensions.py +++ b/src/Mod/Path/PathScripts/PathFeatureExtensions.py @@ -23,8 +23,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Part +import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import math # lazily loaded modules @@ -40,10 +40,10 @@ __doc__ = "Class and implementation of face extensions features." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -95,7 +95,7 @@ def selectOffsetWire(feature, wires): def extendWire(feature, wire, length): """extendWire(wire, length) ... return a closed Wire which extends wire by length""" - PathLog.track(length) + Path.Log.track(length) if not length or length == 0: return None @@ -103,7 +103,7 @@ def extendWire(feature, wire, length): try: off2D = wire.makeOffset2D(length) except FreeCAD.Base.FreeCADError as ee: - PathLog.debug(ee) + Path.Log.debug(ee) return None endPts = endPoints(wire) # Assumes wire is NOT closed if endPts: @@ -157,7 +157,7 @@ def readObjExtensionFeature(obj): def getExtensions(obj): - PathLog.debug("getExtenstions()") + Path.Log.debug("getExtenstions()") extensions = [] i = 0 @@ -170,7 +170,7 @@ def getExtensions(obj): def setExtensions(obj, extensions): - PathLog.track(obj.Label, len(extensions)) + Path.Log.track(obj.Label, len(extensions)) obj.ExtensionFeature = [(ext.obj, ext.getSubLink()) for ext in extensions] @@ -218,7 +218,7 @@ class Extension(object): DirectionY = 2 def __init__(self, op, obj, feature, sub, length, direction): - PathLog.debug( + Path.Log.debug( "Extension(%s, %s, %s, %.2f, %s" % (obj.Label, feature, sub, length, direction) ) @@ -229,7 +229,7 @@ class Extension(object): self.length = length self.direction = direction self.extFaces = None - self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False + self.isDebug = True if Path.Log.getLevel(Path.Log.thisModule()) == 4 else False self.avoid = False if sub.startswith("Avoid_"): @@ -241,7 +241,7 @@ class Extension(object): return "%s:%s" % (self.feature, self.sub) def _extendEdge(self, feature, e0, direction): - PathLog.track(feature, e0, direction) + Path.Log.track(feature, e0, direction) if isinstance(e0.Curve, Part.Line) or isinstance(e0.Curve, Part.LineSegment): e2 = e0.copy() off = self.length.Value * direction @@ -269,7 +269,7 @@ class Extension(object): else: numbers = [self.sub[4:]] - PathLog.debug("_getEdgeNumbers() -> %s" % numbers) + Path.Log.debug("_getEdgeNumbers() -> %s" % numbers) return numbers def _getEdgeNames(self): @@ -293,7 +293,7 @@ class Extension(object): e0 = wire.Edges[0] midparam = e0.FirstParameter + 0.5 * (e0.LastParameter - e0.FirstParameter) tangent = e0.tangentAt(midparam) - PathLog.track("tangent", tangent, self.feature, self.sub) + Path.Log.track("tangent", tangent, self.feature, self.sub) normal = tangent.cross(FreeCAD.Vector(0, 0, 1)) if PathGeom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)): return None @@ -323,11 +323,11 @@ class Extension(object): """_getRegularWire()... Private method to retrieve the extension area, pertaining to the feature and sub element provided at class instantiation, as a closed wire. If no closed wire is possible, a `None` value is returned.""" - PathLog.track() + Path.Log.track() length = self.length.Value if PathGeom.isRoughly(0, length) or not self.sub: - PathLog.debug("no extension, length=%.2f, sub=%s" % (length, self.sub)) + Path.Log.debug("no extension, length=%.2f, sub=%s" % (length, self.sub)) return None feature = self.obj.Shape.getElement(self.feature) @@ -335,10 +335,10 @@ class Extension(object): sub = Part.Wire(Part.sortEdges(edges)[0]) if 1 == len(edges): - PathLog.debug("Extending single edge wire") + Path.Log.debug("Extending single edge wire") edge = edges[0] if Part.Circle == type(edge.Curve): - PathLog.debug("is Part.Circle") + Path.Log.debug("is Part.Circle") circle = edge.Curve # for a circle we have to figure out if it's a hole or a cylinder p0 = edge.valueAt(edge.FirstParameter) @@ -354,7 +354,7 @@ class Extension(object): # assuming the offset produces a valid circle - go for it if r > 0: - PathLog.debug("radius > 0 - extend outward") + Path.Log.debug("radius > 0 - extend outward") e3 = Part.makeCircle( r, circle.Center, @@ -373,7 +373,7 @@ class Extension(object): ) if endPoints(edge): - PathLog.debug("Make section of donut") + Path.Log.debug("Make section of donut") # need to construct the arc slice e0 = Part.makeLine( edge.valueAt(edge.FirstParameter), @@ -397,22 +397,22 @@ class Extension(object): self.extFaces = [self._makeCircularExtFace(edge, extWire)] return extWire - PathLog.debug("radius < 0 - extend inward") + Path.Log.debug("radius < 0 - extend inward") # the extension is bigger than the hole - so let's just cover the whole hole if endPoints(edge): # if the resulting arc is smaller than the radius, create a pie slice - PathLog.track() + Path.Log.track() center = circle.Center e0 = Part.makeLine(center, edge.valueAt(edge.FirstParameter)) e2 = Part.makeLine(edge.valueAt(edge.LastParameter), center) return Part.Wire([e0, edge, e2]) - PathLog.track() + Path.Log.track() return Part.Wire([edge]) else: - PathLog.debug("else is NOT Part.Circle") - PathLog.track(self.feature, self.sub, type(edge.Curve), endPoints(edge)) + Path.Log.debug("else is NOT Part.Circle") + Path.Log.track(self.feature, self.sub, type(edge.Curve), endPoints(edge)) direction = self._getDirection(sub) if direction is None: return None @@ -420,7 +420,7 @@ class Extension(object): return self._extendEdge(feature, edges[0], direction) elif sub.isClosed(): - PathLog.debug("Extending multi-edge closed wire") + Path.Log.debug("Extending multi-edge closed wire") subFace = Part.Face(sub) featFace = Part.Face(feature.Wires[0]) isOutside = True @@ -431,7 +431,7 @@ class Extension(object): try: off2D = sub.makeOffset2D(length) except FreeCAD.Base.FreeCADError as ee: - PathLog.debug(ee) + Path.Log.debug(ee) return None if isOutside: @@ -440,7 +440,7 @@ class Extension(object): self.extFaces = [subFace.cut(Part.Face(off2D))] return off2D - PathLog.debug("Extending multi-edge open wire") + Path.Log.debug("Extending multi-edge open wire") extendedWire = extendWire(feature, sub, length) if extendedWire is None: return extendedWire @@ -538,7 +538,7 @@ def getExtendOutlineFace( face, extension, removeHoles=remHoles, plane=face, tolerance=offset_tolerance ) if not offset_face: - PathLog.error("Failed to offset a selected face.") + Path.Log.error("Failed to offset a selected face.") return None # Apply collision detection by limiting extended face using base shape @@ -579,7 +579,7 @@ def getExtendOutlineFace( return extended - PathLog.error("No bottom face for extend outline.") + Path.Log.error("No bottom face for extend outline.") return None diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py index b133c80aff..8cb8daa639 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py +++ b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py @@ -24,10 +24,10 @@ from PySide import QtCore, QtGui from pivy import coin import FreeCAD import FreeCADGui +import Path import PathScripts.PathFeatureExtensions as FeatureExtensions import PathScripts.PathGeom as PathGeom import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui # lazily loaded modules @@ -44,10 +44,10 @@ __doc__ = "Extensions feature page controller." translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class _Extension(object): @@ -229,7 +229,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): try: self.obj.ViewObject.RootNode.removeChild(self.switch) except ReferenceError: - PathLog.debug("obj already destroyed - no cleanup required") + Path.Log.debug("obj already destroyed - no cleanup required") def getForm(self): form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpPocketExtEdit.ui") @@ -246,7 +246,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): cb(item, ext) def currentExtensions(self): - PathLog.debug("currentExtensions()") + Path.Log.debug("currentExtensions()") extensions = [] def extractExtension(item, ext): @@ -255,16 +255,16 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): if self.form.enableExtensions.isChecked(): self.forAllItemsCall(extractExtension) - PathLog.track("extensions", extensions) + Path.Log.track("extensions", extensions) return extensions def updateProxyExtensions(self, obj): - PathLog.debug("updateProxyExtensions()") + Path.Log.debug("updateProxyExtensions()") self.extensions = self.currentExtensions() FeatureExtensions.setExtensions(obj, self.extensions) def getFields(self, obj): - PathLog.track(obj.Label, self.model.rowCount(), self.model.columnCount()) + Path.Log.track(obj.Label, self.model.rowCount(), self.model.columnCount()) self.blockUpdateData = True if obj.ExtensionCorners != self.form.extendCorners.isChecked(): @@ -275,8 +275,8 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.blockUpdateData = False def setFields(self, obj): - PathLog.track(obj.Label) - # PathLog.debug("setFields()") + Path.Log.track(obj.Label) + # Path.Log.debug("setFields()") if obj.ExtensionCorners != self.form.extendCorners.isChecked(): self.form.extendCorners.toggle() @@ -312,10 +312,10 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self._enableExtensions() # Recalculate extensions def createItemForBaseModel(self, base, sub, edges, extensions): - PathLog.track( + Path.Log.track( base.Label, sub, "+", len(edges), len(base.Shape.getElement(sub).Edges) ) - # PathLog.debug("createItemForBaseModel() label: {}, sub: {}, {}, edgeCnt: {}, subEdges: {}".format(base.Label, sub, '+', len(edges), len(base.Shape.getElement(sub).Edges))) + # Path.Log.debug("createItemForBaseModel() label: {}, sub: {}, {}, edgeCnt: {}, subEdges: {}".format(base.Label, sub, '+', len(edges), len(base.Shape.getElement(sub).Edges))) extendCorners = self.form.extendCorners.isChecked() subShape = base.Shape.getElement(sub) @@ -370,7 +370,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): return PathGeom.edgesMatch(e0, e1) self.extensionEdges = extensionEdges - PathLog.debug("extensionEdges.values(): {}".format(extensionEdges.values())) + Path.Log.debug("extensionEdges.values(): {}".format(extensionEdges.values())) for edgeList in Part.sortEdges( list(extensionEdges.keys()) ): # Identify connected edges that form wires @@ -402,11 +402,11 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): return item def setExtensions(self, extensions): - PathLog.track(len(extensions)) - PathLog.debug("setExtensions()") + Path.Log.track(len(extensions)) + Path.Log.debug("setExtensions()") if self.extensionsReady: - PathLog.debug("setExtensions() returning per `extensionsReady` flag") + Path.Log.debug("setExtensions() returning per `extensionsReady` flag") return self.form.extensionTree.blockSignals(True) @@ -482,11 +482,11 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.form.extensionTree.blockSignals(False) self.extensionsReady = True - PathLog.debug(" setExtensions() finished and setting `extensionsReady=True`") + Path.Log.debug(" setExtensions() finished and setting `extensionsReady=True`") def updateData(self, obj, prop): - PathLog.track(obj.Label, prop, self.blockUpdateData) - # PathLog.debug("updateData({})".format(prop)) + Path.Log.track(obj.Label, prop, self.blockUpdateData) + # Path.Log.debug("updateData({})".format(prop)) if not self.blockUpdateData: if self.fieldsSet: @@ -503,10 +503,10 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.extensionsReady = False def restoreSelection(self, selection): - PathLog.debug("restoreSelection()") - PathLog.track() + Path.Log.debug("restoreSelection()") + Path.Log.track() if 0 == self.model.rowCount(): - PathLog.track("-") + Path.Log.track("-") self.form.buttonClear.setEnabled(False) self.form.buttonDisable.setEnabled(False) self.form.buttonEnable.setEnabled(False) @@ -549,11 +549,11 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.forAllItemsCall(setSelectionVisuals) def selectionChanged(self): - PathLog.debug("selectionChanged()") + Path.Log.debug("selectionChanged()") self.restoreSelection([]) def extensionsClear(self): - PathLog.debug("extensionsClear()") + Path.Log.debug("extensionsClear()") def disableItem(item, ext): item.setCheckState(QtCore.Qt.Unchecked) @@ -563,8 +563,8 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.setDirty() def _extensionsSetState(self, state): - PathLog.debug("_extensionsSetState()") - PathLog.track(state) + Path.Log.debug("_extensionsSetState()") + Path.Log.track(state) for index in self.selectionModel.selectedIndexes(): item = self.model.itemFromIndex(index) ext = item.data(self.DataObject) @@ -580,7 +580,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self._extensionsSetState(QtCore.Qt.Checked) def updateItemEnabled(self, item): - PathLog.track(item) + Path.Log.track(item) ext = item.data(self.DataObject) if item.checkState() == QtCore.Qt.Checked: ext.enable() @@ -606,8 +606,8 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): # self.setDirty() def toggleExtensionCorners(self): - PathLog.debug("toggleExtensionCorners()") - PathLog.track() + Path.Log.debug("toggleExtensionCorners()") + Path.Log.track() self.extensionsReady = False extensions = FeatureExtensions.getExtensions(self.obj) self.setExtensions(extensions) @@ -615,7 +615,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.setDirty() def getSignalsForUpdate(self, obj): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) signals = [] signals.append(self.form.defaultLength.editingFinished) signals.append(self.form.enableExtensions.toggled) @@ -654,7 +654,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): if page.panelTitle == "Operation" and hasattr( page.form, "useOutline" ): - PathLog.debug("Found useOutline checkbox") + Path.Log.debug("Found useOutline checkbox") self.useOutlineCheckbox = page.form.useOutline if page.form.useOutline.isChecked(): self.useOutline = 1 @@ -682,7 +682,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): if self.form.enableExtensions.isChecked(): enabled = True - PathLog.debug("_autoEnableExtensions() is {}".format(enabled)) + Path.Log.debug("_autoEnableExtensions() is {}".format(enabled)) self.enabled = enabled def _enableExtensions(self): @@ -691,7 +691,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): This method manages the enabled or disabled state of the extensionsEdit Task Panel input group. """ - PathLog.debug("_enableExtensions()") + Path.Log.debug("_enableExtensions()") if self.form.enableExtensions.isChecked(): self.enabled = True @@ -709,7 +709,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): This method manages the state of the button and the message thereof. """ self._getUseOutlineState() # Find `useOutline` checkbox and get its boolean value - PathLog.debug("_includeEdgesAndWires()") + Path.Log.debug("_includeEdgesAndWires()") self.extensionsReady = False self._enableExtensions() @@ -725,16 +725,16 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): cacheLabel = base.Name + "_" + sub + "_None" if cacheLabel in self.extensionsCache.keys(): - # PathLog.debug("return _cachedExtension({})".format(cacheLabel)) + # Path.Log.debug("return _cachedExtension({})".format(cacheLabel)) return self.extensionsCache[cacheLabel] else: - # PathLog.debug("_cachedExtension({}) created".format(cacheLabel)) + # Path.Log.debug("_cachedExtension({}) created".format(cacheLabel)) _ext = _Extension(obj, base, sub, label) self.extensionsCache[cacheLabel] = _ext # cache the extension return _ext def _resetCachedExtensions(self): - PathLog.debug("_resetCachedExtensions()") + Path.Log.debug("_resetCachedExtensions()") reset = dict() self.extensionsCache = reset self.extensionsReady = False diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 1486c73496..6566cf97f1 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -23,7 +23,6 @@ import FreeCAD import Path -import PathScripts.PathLog as PathLog import math from FreeCAD import Vector @@ -44,10 +43,10 @@ Tolerance = 0.000001 translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class Side: @@ -170,7 +169,7 @@ def isVertical(obj): if type(obj.Surface) == Part.SurfaceOfRevolution: return isHorizontal(obj.Surface.Direction) if type(obj.Surface) != Part.BSplineSurface: - PathLog.info( + Path.Log.info( translate("PathGeom", "face %s not handled, assuming not vertical") % type(obj.Surface) ) @@ -187,13 +186,13 @@ def isVertical(obj): # the current assumption is that a bezier curve is vertical if its end points are vertical return isVertical(obj.Curve.EndPoint - obj.Curve.StartPoint) if type(obj.Curve) != Part.BSplineCurve: - PathLog.info( + Path.Log.info( translate("PathGeom", "edge %s not handled, assuming not vertical") % type(obj.Curve) ) return None - PathLog.error(translate("PathGeom", "isVertical(%s) not supported") % obj) + Path.Log.error(translate("PathGeom", "isVertical(%s) not supported") % obj) return None @@ -224,7 +223,7 @@ def isHorizontal(obj): return isVertical(obj.Curve.Axis) return isRoughly(obj.BoundBox.ZLength, 0.0) - PathLog.error(translate("PathGeom", "isHorizontal(%s) not supported") % obj) + Path.Log.error(translate("PathGeom", "isHorizontal(%s) not supported") % obj) return None @@ -258,7 +257,7 @@ def speedBetweenPoints(p0, p1, hSpeed, vSpeed): pitch = pitch + 1 while pitch > 1: pitch = pitch - 1 - PathLog.debug( + Path.Log.debug( " pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f" % (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length) ) @@ -322,7 +321,7 @@ def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vS offset = edge.Curve.Center - pt else: pd = Part.Circle(xy(p1), xy(p2), xy(p3)).Center - PathLog.debug( + Path.Log.debug( "**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)" % ( cmd, @@ -347,15 +346,15 @@ def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vS pc = xy(p3) offset = Part.Circle(pa, pb, pc).Center - pa - PathLog.debug( + Path.Log.debug( "**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z) ) - PathLog.debug( + Path.Log.debug( "**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z) ) - PathLog.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + Path.Log.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) params.update({"I": offset.x, "J": offset.y, "K": (p3.z - p1.z) / 2}) # G2/G3 commands are always performed at hSpeed @@ -389,8 +388,8 @@ def edgeForCmd(cmd, startPoint): """edgeForCmd(cmd, startPoint). Returns an Edge representing the given command, assuming a given startPoint.""" - PathLog.debug("cmd: {}".format(cmd)) - PathLog.debug("startpoint {}".format(startPoint)) + Path.Log.debug("cmd: {}".format(cmd)) + Path.Log.debug("startpoint {}".format(startPoint)) endPoint = commandEndPoint(cmd, startPoint) if (cmd.Name in CmdMoveStraight) or (cmd.Name in CmdMoveRapid): @@ -405,7 +404,7 @@ def edgeForCmd(cmd, startPoint): d = -B.x * A.y + B.y * A.x if isRoughly(d, 0, 0.005): - PathLog.debug( + Path.Log.debug( "Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z) ) @@ -416,23 +415,23 @@ def edgeForCmd(cmd, startPoint): else: C = A + B angle = getAngle(C) - PathLog.debug( + Path.Log.debug( "Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f" % (d, center.x, center.y, center.z, angle / math.pi) ) R = A.Length - PathLog.debug( + Path.Log.debug( "arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)" % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y) ) - PathLog.debug( + Path.Log.debug( "arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d) ) - PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle / math.pi)) + Path.Log.debug("arc: R=%.2f angle=%.2f" % (R, angle / math.pi)) if isRoughly(startPoint.z, endPoint.z): midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R - PathLog.debug( + Path.Log.debug( "arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % ( startPoint.x, @@ -443,9 +442,9 @@ def edgeForCmd(cmd, startPoint): endPoint.y, ) ) - PathLog.debug("StartPoint:{}".format(startPoint)) - PathLog.debug("MidPoint:{}".format(midPoint)) - PathLog.debug("EndPoint:{}".format(endPoint)) + Path.Log.debug("StartPoint:{}".format(startPoint)) + Path.Log.debug("MidPoint:{}".format(midPoint)) + Path.Log.debug("EndPoint:{}".format(endPoint)) if pointsCoincide(startPoint, endPoint, 0.001): return Part.makeCircle(R, center, FreeCAD.Vector(0, 0, 1)) @@ -582,10 +581,10 @@ def combineConnectedShapes(shapes): while not done: done = True combined = [] - PathLog.debug("shapes: {}".format(shapes)) + Path.Log.debug("shapes: {}".format(shapes)) for shape in shapes: connected = [f for f in combined if isRoughly(shape.distToShape(f)[0], 0.0)] - PathLog.debug( + Path.Log.debug( " {}: connected: {} dist: {}".format( len(combined), connected, @@ -672,7 +671,7 @@ def flipEdge(edge): elif type(edge.Curve) == Part.OffsetCurve: return edge.reversed() - PathLog.warning( + Path.Log.warning( translate("PathGeom", "%s not supported for flipping") % type(edge.Curve) ) @@ -681,7 +680,7 @@ def flipWire(wire): """Flip the entire wire and all its edges so it is being processed the other way around.""" edges = [flipEdge(e) for e in wire.Edges] edges.reverse() - PathLog.debug(edges) + Path.Log.debug(edges) return Part.Wire(edges) @@ -729,7 +728,7 @@ def combineHorizontalFaces(faces): "PathGeom", "Zero working area to process. Check your selection and settings.", ) - PathLog.info(msg) + Path.Log.info(msg) return horizontal afbb = allFaces.BoundBox diff --git a/src/Mod/Path/PathScripts/PathGetPoint.py b/src/Mod/Path/PathScripts/PathGetPoint.py index 54e908eae0..1018ffa489 100644 --- a/src/Mod/Path/PathScripts/PathGetPoint.py +++ b/src/Mod/Path/PathScripts/PathGetPoint.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathScripts.PathLog as PathLog +import Path # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -37,8 +37,8 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Helper class to use FreeCADGUi.Snapper to let the user enter arbitrary points while the task panel is active." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.track(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.track(Path.Log.thisModule()) class TaskPanel: diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index bc825bc2ce..d216d3fa42 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -22,8 +22,8 @@ import FreeCAD import FreeCADGui +import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil from PySide import QtGui, QtCore @@ -36,10 +36,10 @@ __doc__ = "A collection of helper and utility functions for the Path GUI." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def populateCombobox(form, enumTups, comboBoxesPropertyMap): @@ -50,7 +50,7 @@ def populateCombobox(form, enumTups, comboBoxesPropertyMap): enumTups = list of (translated_text, data_string) tuples comboBoxesPropertyMap = list of (translated_text, data_string) tuples """ - PathLog.track(enumTups) + Path.Log.track(enumTups) # Load appropriate enumerations in each combobox for cb, prop in comboBoxesPropertyMap: @@ -70,7 +70,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): Returns True if a new value was assigned, False otherwise (new value is the same as the current). """ value = widget.property("rawValue") - PathLog.track("value: {}".format(value)) + Path.Log.track("value: {}".format(value)) attr = PathUtil.getProperty(obj, prop) attrValue = attr.Value if hasattr(attr, "Value") else attr @@ -83,7 +83,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): for (prp, expr) in obj.ExpressionEngine: if prp == prop: exprSet = True - PathLog.debug('prop = "expression": {} = "{}"'.format(prp, expr)) + Path.Log.debug('prop = "expression": {} = "{}"'.format(prp, expr)) value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value if not PathGeom.isRoughly(attrValue, value): isDiff = True @@ -97,7 +97,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): widget.update() if isDiff: - PathLog.debug( + Path.Log.debug( "updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value) ) if onBeforeChange: @@ -120,7 +120,7 @@ class QuantitySpinBox(QtCore.QObject): def __init__(self, widget, obj, prop, onBeforeChange=None): super().__init__() - PathLog.track(widget) + Path.Log.track(widget) self.widget = widget self.onBeforeChange = onBeforeChange self.prop = None @@ -150,7 +150,7 @@ class QuantitySpinBox(QtCore.QObject): def attachTo(self, obj, prop=None): """attachTo(obj, prop=None) ... use an existing editor for the given object and property""" - PathLog.track(self.prop, prop) + Path.Log.track(self.prop, prop) self.obj = obj self.prop = prop if obj and prop: @@ -161,21 +161,21 @@ class QuantitySpinBox(QtCore.QObject): self.widget.setProperty("binding", "%s.%s" % (obj.Name, prop)) self.valid = True else: - PathLog.warning("Cannot find property {} of {}".format(prop, obj.Label)) + Path.Log.warning("Cannot find property {} of {}".format(prop, obj.Label)) self.valid = False else: self.valid = False def expression(self): """expression() ... returns the expression if one is bound to the property""" - PathLog.track(self.prop, self.valid) + Path.Log.track(self.prop, self.valid) if self.valid: return self.widget.property("expression") return "" def setMinimum(self, quantity): """setMinimum(quantity) ... set the minimum""" - PathLog.track(self.prop, self.valid) + Path.Log.track(self.prop, self.valid) if self.valid: value = quantity.Value if hasattr(quantity, "Value") else quantity self.widget.setProperty("setMinimum", value) @@ -184,7 +184,7 @@ class QuantitySpinBox(QtCore.QObject): """updateSpinBox(quantity=None) ... update the display value of the spin box. If no value is provided the value of the bound property is used. quantity can be of type Quantity or Float.""" - PathLog.track(self.prop, self.valid, quantity) + Path.Log.track(self.prop, self.valid, quantity) if self.valid: expr = self._hasExpression() @@ -205,7 +205,7 @@ class QuantitySpinBox(QtCore.QObject): def updateProperty(self): """updateProperty() ... update the bound property with the value from the spin box""" - PathLog.track(self.prop, self.valid) + Path.Log.track(self.prop, self.valid) if self.valid: return updateInputField( self.obj, self.prop, self.widget, self.onBeforeChange diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 3ad0db2b42..59a448f58b 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -20,16 +20,16 @@ # * * # *************************************************************************** -import PathScripts.PathLog as PathLog +import Path import subprocess LOGLEVEL = False if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) Processed = False @@ -37,7 +37,7 @@ Processed = False def Startup(): global Processed if not Processed: - PathLog.debug("Initializing PathGui") + Path.Log.debug("Initializing PathGui") from PathScripts import PathAdaptiveGui from PathScripts import PathArray from PathScripts import PathComment @@ -98,4 +98,4 @@ def Startup(): Processed = True else: - PathLog.debug("Skipping PathGui initialisation") + Path.Log.debug("Skipping PathGui initialisation") diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 0b2eaf8a73..25c8937a69 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -28,7 +28,6 @@ import FreeCAD import Part import Path import PathScripts.PathCircularHoleBase as PathCircularHoleBase -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathFeedRate @@ -44,10 +43,10 @@ __lastModified__ = "2019-07-12 09:50 CST" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -84,11 +83,11 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -170,7 +169,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): def circularHoleExecute(self, obj, holes): """circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes""" - PathLog.track() + Path.Log.track() self.commandlist.append(Path.Command("(helix cut operation)")) self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index 4ea069ed1e..331fe1ca35 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -22,10 +22,10 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP @@ -39,10 +39,10 @@ __doc__ = "Helix operation page controller and command implementation." LOGLEVEL = False if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.NOTICE, Path.Log.thisModule()) class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): @@ -61,7 +61,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def getFields(self, obj): """getFields(obj) ... transfers values from UI to obj's proprties""" - PathLog.track() + Path.Log.track() if obj.Direction != str(self.form.direction.currentData()): obj.Direction = str(self.form.direction.currentData()) if obj.StartSide != str(self.form.startSide.currentData()): @@ -75,7 +75,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def setFields(self, obj): """setFields(obj) ... transfers obj's property values to UI""" - PathLog.track() + Path.Log.track() self.form.stepOverPercent.setValue(obj.StepOver) self.selectInComboBox(obj.Direction, self.form.direction) diff --git a/src/Mod/Path/PathScripts/PathIconViewProvider.py b/src/Mod/Path/PathScripts/PathIconViewProvider.py index 5b08e32edb..9238454a5d 100644 --- a/src/Mod/Path/PathScripts/PathIconViewProvider.py +++ b/src/Mod/Path/PathScripts/PathIconViewProvider.py @@ -21,8 +21,8 @@ # *************************************************************************** import FreeCAD +import Path import PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import importlib @@ -34,10 +34,10 @@ __doc__ = "ViewProvider who's main and only task is to assign an icon." translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ViewProvider(object): @@ -91,7 +91,7 @@ class ViewProvider(object): self._onEditCallback(False) def setupContextMenu(self, vobj, menu): - PathLog.track() + Path.Log.track() from PySide import QtGui edit = translate("Path", "Edit") @@ -107,12 +107,12 @@ def Attach(vobj, name): """Attach(vobj, name) ... attach the appropriate view provider to the view object. If no view provider was registered for the given name a default IconViewProvider is created.""" - PathLog.track(vobj.Object.Label, name) + Path.Log.track(vobj.Object.Label, name) global _factory for key, value in PathUtil.keyValueIter(_factory): if key == name: return value(vobj, name) - PathLog.track(vobj.Object.Label, name, "PathIconViewProvider") + Path.Log.track(vobj.Object.Label, name, "PathIconViewProvider") return ViewProvider(vobj, name) @@ -120,6 +120,6 @@ def RegisterViewProvider(name, provider): """RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name an instance of provider is used instead.""" - PathLog.track(name) + Path.Log.track(name) global _factory _factory[name] = provider diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index d2b64c1405..07846c4a27 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -24,7 +24,7 @@ from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock @@ -42,10 +42,10 @@ Draft = LazyLoader("Draft", globals(), "Draft") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -272,11 +272,11 @@ class ObjectJob: data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -316,7 +316,7 @@ class ObjectJob: self.setupSheet = obj.SetupSheet.Proxy def setupBaseModel(self, obj, models=None): - PathLog.track(obj.Label, models) + Path.Log.track(obj.Label, models) addModels = False if not hasattr(obj, "Model"): @@ -346,7 +346,7 @@ class ObjectJob: obj.Model.Label = "Model" if hasattr(obj, "Base"): - PathLog.info( + Path.Log.info( "Converting Job.Base to new Job.Model for {}".format(obj.Label) ) obj.Model.addObject(obj.Base) @@ -403,12 +403,12 @@ class ObjectJob: def onDelete(self, obj, arg2=None): """Called by the view provider, there doesn't seem to be a callback on the obj itself.""" - PathLog.track(obj.Label, arg2) + Path.Log.track(obj.Label, arg2) doc = obj.Document if getattr(obj, "Operations", None): # the first to tear down are the ops, they depend on other resources - PathLog.debug( + Path.Log.debug( "taking down ops: %s" % [o.Name for o in self.allOperations()] ) while obj.Operations.Group: @@ -426,7 +426,7 @@ class ObjectJob: # stock could depend on Model, so delete it first if getattr(obj, "Stock", None): - PathLog.debug("taking down stock") + Path.Log.debug("taking down stock") PathUtil.clearExpressionEngine(obj.Stock) doc.removeObject(obj.Stock.Name) obj.Stock = None @@ -434,7 +434,7 @@ class ObjectJob: # base doesn't depend on anything inside job if getattr(obj, "Model", None): for base in obj.Model.Group: - PathLog.debug("taking down base %s" % base.Label) + Path.Log.debug("taking down base %s" % base.Label) self.removeBase(obj, base, False) obj.Model.Group = [] doc.removeObject(obj.Model.Name) @@ -442,7 +442,7 @@ class ObjectJob: # Tool controllers might refer to either legacy tool or toolbit if getattr(obj, "Tools", None): - PathLog.debug("taking down tool controller") + Path.Log.debug("taking down tool controller") for tc in obj.Tools.Group: if hasattr(tc.Tool, "Proxy"): PathUtil.clearExpressionEngine(tc.Tool) @@ -631,10 +631,10 @@ class ObjectJob: if attrs.get(JobTemplate.SplitOutput): obj.SplitOutput = attrs.get(JobTemplate.SplitOutput) - PathLog.debug("setting tool controllers (%d)" % len(tcs)) + Path.Log.debug("setting tool controllers (%d)" % len(tcs)) obj.Tools.Group = tcs else: - PathLog.error( + Path.Log.error( "Unsupported PathJob template version {}".format( attrs.get(JobTemplate.Version) ) @@ -719,7 +719,7 @@ class ObjectJob: if removeBefore: group.remove(before) except Exception as e: - PathLog.error(e) + Path.Log.error(e) group.append(op) else: group.append(op) @@ -736,7 +736,7 @@ class ObjectJob: def addToolController(self, tc): group = self.obj.Tools.Group - PathLog.debug( + Path.Log.debug( "addToolController(%s): %s" % (tc.Label, [t.Label for t in group]) ) if tc.Name not in [str(t.Name) for t in group]: @@ -795,7 +795,7 @@ class ObjectJob: suffix = job.Name[3:] def errorMessage(grp, job): - PathLog.error("{} corrupt in {} job.".format(grp, job.Name)) + Path.Log.error("{} corrupt in {} job.".format(grp, job.Name)) if not job.Operations: self.setupOperations(job) diff --git a/src/Mod/Path/PathScripts/PathJobCmd.py b/src/Mod/Path/PathScripts/PathJobCmd.py index 830eb23aef..c1b15de56d 100644 --- a/src/Mod/Path/PathScripts/PathJobCmd.py +++ b/src/Mod/Path/PathScripts/PathJobCmd.py @@ -24,9 +24,9 @@ from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui +import Path import PathScripts.PathJob as PathJob import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import PathScripts.PathUtil as PathUtil @@ -37,10 +37,10 @@ import os translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class CommandJobCreate: diff --git a/src/Mod/Path/PathScripts/PathJobDlg.py b/src/Mod/Path/PathScripts/PathJobDlg.py index d0acdfda70..b3dd2b6721 100644 --- a/src/Mod/Path/PathScripts/PathJobDlg.py +++ b/src/Mod/Path/PathScripts/PathJobDlg.py @@ -24,8 +24,8 @@ from PySide import QtCore, QtGui from collections import Counter import FreeCAD import FreeCADGui +import Path import PathScripts.PathJob as PathJob -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import PathScripts.PathUtil as PathUtil @@ -36,10 +36,10 @@ translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class _ItemDelegate(QtGui.QStyledItemDelegate): @@ -238,7 +238,7 @@ class JobCreate: while name in template: i = i + 1 name = basename + " (%s)" % i - PathLog.track(name, tFile) + Path.Log.track(name, tFile) template[name] = tFile selectTemplate = PathPreferences.defaultJobTemplate() index = 0 @@ -253,7 +253,7 @@ class JobCreate: def templateFilesIn(self, path): """templateFilesIn(path) ... answer all file in the given directory which fit the job template naming convention. PathJob template files are name job_*.json""" - PathLog.track(path) + Path.Log.track(path) return glob.glob(path + "/job_*.json") def getModels(self): @@ -353,7 +353,7 @@ class JobTemplateExport: else: # Existing Solid seHint = "-" - PathLog.error(translate("Path_Job", "Unsupported stock type")) + Path.Log.error(translate("Path_Job", "Unsupported stock type")) self.dialog.stockExtentHint.setText(seHint) spHint = "%s" % job.Stock.Placement self.dialog.stockPlacementHint.setText(spHint) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index 8727f17ec4..c6490d5698 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -27,12 +27,12 @@ from contextlib import contextmanager from pivy import coin import FreeCAD import FreeCADGui +import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathStock as PathStock @@ -55,10 +55,10 @@ DraftVecUtils = LazyLoader("DraftVecUtils", globals(), "DraftVecUtils") translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def _OpenCloseResourceEditor(obj, vobj, edit): @@ -74,7 +74,7 @@ def _OpenCloseResourceEditor(obj, vobj, edit): missing = "ViewObject" if job.ViewObject: missing = "Proxy" - PathLog.warning("Cannot edit %s - no %s" % (obj.Label, missing)) + Path.Log.warning("Cannot edit %s - no %s" % (obj.Label, missing)) @contextmanager @@ -162,7 +162,7 @@ class ViewProvider: return hasattr(self, "deleteOnReject") and self.deleteOnReject def setEdit(self, vobj=None, mode=0): - PathLog.track(mode) + Path.Log.track(mode) if 0 == mode: job = self.vobj.Object if not job.Proxy.integrityCheck(job): @@ -192,7 +192,7 @@ class ViewProvider: return self.openTaskPanel("Model") if obj == self.obj.Stock: return self.openTaskPanel("Stock") - PathLog.info( + Path.Log.info( "Expected a specific object to edit - %s not recognized" % obj.Label ) return self.openTaskPanel() @@ -221,12 +221,12 @@ class ViewProvider: return children def onDelete(self, vobj, arg2=None): - PathLog.track(vobj.Object.Label, arg2) + Path.Log.track(vobj.Object.Label, arg2) self.obj.Proxy.onDelete(self.obj, arg2) return True def updateData(self, obj, prop): - PathLog.track(obj.Label, prop) + Path.Log.track(obj.Label, prop) # make sure the resource view providers are setup properly if prop == "Model" and self.obj.Model: for base in self.obj.Model.Group: @@ -241,7 +241,7 @@ class ViewProvider: self.obj.Stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) def rememberBaseVisibility(self, obj, base): - PathLog.track() + Path.Log.track() if base.ViewObject: orig = PathUtil.getPublicObject(obj.Proxy.baseObject(obj, base)) self.baseVisibility[base.Name] = ( @@ -254,7 +254,7 @@ class ViewProvider: base.ViewObject.Visibility = True def forgetBaseVisibility(self, obj, base): - PathLog.track() + Path.Log.track() if self.baseVisibility.get(base.Name): visibility = self.baseVisibility[base.Name] visibility[0].ViewObject.Visibility = visibility[1] @@ -262,7 +262,7 @@ class ViewProvider: del self.baseVisibility[base.Name] def setupEditVisibility(self, obj): - PathLog.track() + Path.Log.track() self.baseVisibility = {} for base in obj.Model.Group: self.rememberBaseVisibility(obj, base) @@ -273,14 +273,14 @@ class ViewProvider: self.obj.Stock.ViewObject.Visibility = True def resetEditVisibility(self, obj): - PathLog.track() + Path.Log.track() for base in obj.Model.Group: self.forgetBaseVisibility(obj, base) if obj.Stock and obj.Stock.ViewObject: obj.Stock.ViewObject.Visibility = self.stockVisibility def setupContextMenu(self, vobj, menu): - PathLog.track() + Path.Log.track() for action in menu.actions(): menu.removeAction(action) action = QtGui.QAction(translate("Path_Job", "Edit"), menu) @@ -293,7 +293,7 @@ class StockEdit(object): StockType = PathStock.StockType.Unknown def __init__(self, obj, form, force): - PathLog.track(obj.Label, force) + Path.Log.track(obj.Label, force) self.obj = obj self.form = form self.force = force @@ -304,7 +304,7 @@ class StockEdit(object): return PathStock.StockType.FromStock(obj.Stock) == cls.StockType def activate(self, obj, select=False): - PathLog.track(obj.Label, select) + Path.Log.track(obj.Label, select) def showHide(widget, activeWidget): if widget == activeWidget: @@ -322,11 +322,11 @@ class StockEdit(object): self.setFields(obj) def setStock(self, obj, stock): - PathLog.track(obj.Label, stock) + Path.Log.track(obj.Label, stock) if obj.Stock: - PathLog.track(obj.Stock.Name) + Path.Log.track(obj.Stock.Name) obj.Document.removeObject(obj.Stock.Name) - PathLog.track(stock.Name) + Path.Log.track(stock.Name) obj.Stock = stock if stock.ViewObject and stock.ViewObject.Proxy: stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) @@ -359,7 +359,7 @@ class StockFromBaseBoundBoxEdit(StockEdit): self.trackZpos = None def editorFrame(self): - PathLog.track() + Path.Log.track() return self.form.stockFromBase def getFieldsStock(self, stock, fields=None): @@ -384,16 +384,16 @@ class StockFromBaseBoundBoxEdit(StockEdit): def getFields(self, obj, fields=None): if fields is None: fields = ["xneg", "xpos", "yneg", "ypos", "zneg", "zpos"] - PathLog.track(obj.Label, fields) + Path.Log.track(obj.Label, fields) if self.IsStock(obj): self.getFieldsStock(obj.Stock, fields) else: - PathLog.error("Stock not from Base bound box!") + Path.Log.error("Stock not from Base bound box!") def setFields(self, obj): - PathLog.track() + Path.Log.track() if self.force or not self.IsStock(obj): - PathLog.track() + Path.Log.track() stock = PathStock.CreateFromBase(obj) if self.force and self.editorFrame().isVisible(): self.getFieldsStock(stock) @@ -407,7 +407,7 @@ class StockFromBaseBoundBoxEdit(StockEdit): self.setLengthField(self.form.stockExtZpos, obj.Stock.ExtZpos) def setupUi(self, obj): - PathLog.track() + Path.Log.track() self.setFields(obj) self.checkXpos() self.checkYpos() @@ -480,7 +480,7 @@ class StockCreateBoxEdit(StockEdit): self.form.stockBoxHeight.text() ) else: - PathLog.error("Stock not a box!") + Path.Log.error("Stock not a box!") except Exception: pass @@ -526,7 +526,7 @@ class StockCreateCylinderEdit(StockEdit): self.form.stockCylinderHeight.text() ) else: - PathLog.error(translate("Path_Job", "Stock not a cylinder!")) + Path.Log.error(translate("Path_Job", "Stock not a cylinder!")) except Exception: pass @@ -687,13 +687,13 @@ class TaskPanel: box.addItem(text, data) def preCleanup(self): - PathLog.track() + Path.Log.track() FreeCADGui.Selection.removeObserver(self) self.vproxy.resetEditVisibility(self.obj) self.vproxy.resetTaskPanel() def accept(self, resetEdit=True): - PathLog.track() + Path.Log.track() self._jobIntegrityCheck() # Check existence of Model and Tools self.preCleanup() self.getFields() @@ -703,19 +703,19 @@ class TaskPanel: self.cleanup(resetEdit) def reject(self, resetEdit=True): - PathLog.track() + Path.Log.track() self.preCleanup() self.setupGlobal.reject() self.setupOps.reject() FreeCAD.ActiveDocument.abortTransaction() if self.deleteOnReject and FreeCAD.ActiveDocument.getObject(self.name): - PathLog.info("Uncreate Job") + Path.Log.info("Uncreate Job") FreeCAD.ActiveDocument.openTransaction("Uncreate Job") if self.obj.ViewObject.Proxy.onDelete(self.obj.ViewObject, None): FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCAD.ActiveDocument.commitTransaction() else: - PathLog.track( + Path.Log.track( self.name, self.deleteOnReject, FreeCAD.ActiveDocument.getObject(self.name), @@ -724,7 +724,7 @@ class TaskPanel: return True def cleanup(self, resetEdit): - PathLog.track() + Path.Log.track() FreeCADGui.Control.closeDialog() if resetEdit: FreeCADGui.ActiveDocument.resetEdit() @@ -779,7 +779,7 @@ class TaskPanel: flist.append(self.form.wcslist.item(i).text()) self.obj.Fixtures = flist except Exception as e: - PathLog.debug(e) + Path.Log.debug(e) FreeCAD.Console.PrintWarning( "The Job was created without fixture support. Please delete and recreate the job\r\n" ) @@ -1083,10 +1083,10 @@ class TaskPanel: self.template.updateUI() def modelSetAxis(self, axis): - PathLog.track(axis) + Path.Log.track(axis) def alignSel(sel, normal, flip=False): - PathLog.track( + Path.Log.track( "Vector(%.2f, %.2f, %.2f)" % (normal.x, normal.y, normal.z), flip ) v = axis @@ -1104,7 +1104,7 @@ class TaskPanel: else: r = v.cross(normal) # rotation axis a = DraftVecUtils.angle(normal, v, r) * 180 / math.pi - PathLog.debug( + Path.Log.debug( "oh boy: (%.2f, %.2f, %.2f) x (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f) -> %.2f" % (v.x, v.y, v.z, normal.x, normal.y, normal.z, r.x, r.y, r.z, a) ) @@ -1117,19 +1117,19 @@ class TaskPanel: selObject = sel.Object for feature in sel.SubElementNames: selFeature = feature - PathLog.track(selObject.Label, feature) + Path.Log.track(selObject.Label, feature) sub = sel.Object.Shape.getElement(feature) if "Face" == sub.ShapeType: normal = sub.normalAt(0, 0) if sub.Orientation == "Reversed": normal = FreeCAD.Vector() - normal - PathLog.debug( + Path.Log.debug( "(%.2f, %.2f, %.2f) -> reversed (%s)" % (normal.x, normal.y, normal.z, sub.Orientation) ) else: - PathLog.debug( + Path.Log.debug( "(%.2f, %.2f, %.2f) -> forward (%s)" % (normal.x, normal.y, normal.z, sub.Orientation) ) @@ -1155,7 +1155,7 @@ class TaskPanel: alignSel(sel, normal) else: - PathLog.track(sub.ShapeType) + Path.Log.track(sub.ShapeType) if selObject and selFeature: FreeCADGui.Selection.clearSelection() @@ -1167,19 +1167,19 @@ class TaskPanel: FreeCADGui.Selection.addSelection(sel.Object, sel.SubElementNames) def modelSet0(self, axis): - PathLog.track(axis) + Path.Log.track(axis) with selectionEx() as selection: for sel in selection: selObject = sel.Object - PathLog.track(selObject.Label) + Path.Log.track(selObject.Label) for name in sel.SubElementNames: - PathLog.track(selObject.Label, name) + Path.Log.track(selObject.Label, name) feature = selObject.Shape.getElement(name) bb = feature.BoundBox offset = FreeCAD.Vector( axis.x * bb.XMax, axis.y * bb.YMax, axis.z * bb.ZMax ) - PathLog.track(feature.BoundBox.ZMax, offset) + Path.Log.track(feature.BoundBox.ZMax, offset) p = selObject.Placement p.move(offset) selObject.Placement = p @@ -1254,7 +1254,7 @@ class TaskPanel: def updateStockEditor(self, index, force=False): def setupFromBaseEdit(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockFromBase: self.stockFromBase = StockFromBaseBoundBoxEdit( self.obj, self.form, force @@ -1262,13 +1262,13 @@ class TaskPanel: self.stockEdit = self.stockFromBase def setupCreateBoxEdit(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockCreateBox: self.stockCreateBox = StockCreateBoxEdit(self.obj, self.form, force) self.stockEdit = self.stockCreateBox def setupCreateCylinderEdit(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockCreateCylinder: self.stockCreateCylinder = StockCreateCylinderEdit( self.obj, self.form, force @@ -1276,7 +1276,7 @@ class TaskPanel: self.stockEdit = self.stockCreateCylinder def setupFromExisting(): - PathLog.track(index, force) + Path.Log.track(index, force) if force or not self.stockFromExisting: self.stockFromExisting = StockFromExistingEdit( self.obj, self.form, force @@ -1296,7 +1296,7 @@ class TaskPanel: elif StockFromExistingEdit.IsStock(self.obj): setupFromExisting() else: - PathLog.error( + Path.Log.error( translate("Path_Job", "Unsupported stock object %s") % self.obj.Stock.Label ) @@ -1312,7 +1312,7 @@ class TaskPanel: setupFromBaseEdit() index = -1 else: - PathLog.error( + Path.Log.error( translate("Path_Job", "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index) ) @@ -1443,7 +1443,7 @@ class TaskPanel: if obsolete or additions: self.setFields() else: - PathLog.track("no changes to model") + Path.Log.track("no changes to model") def tabPageChanged(self, index): if index == 0: @@ -1459,7 +1459,7 @@ class TaskPanel: try: self.setupOps.setupUi() except Exception as ee: - PathLog.error(str(ee)) + Path.Log.error(str(ee)) self.updateStockEditor(-1, False) self.setFields() @@ -1649,7 +1649,7 @@ def Create(base, template=None, openTaskPanel=True): obj.ViewObject.Proxy.deleteOnReject = False return obj except Exception as exc: - PathLog.error(exc) + Path.Log.error(exc) traceback.print_exc() FreeCAD.ActiveDocument.abortTransaction() diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 59e42aace2..454074eb6e 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -23,7 +23,7 @@ from __future__ import print_function import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathPocketBase as PathPocketBase import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP @@ -42,10 +42,10 @@ __contributors__ = "russ4262 (Russell Johnson)" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -79,16 +79,16 @@ class ObjectFace(PathPocketBase.ObjectPocket): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data def initPocketOp(self, obj): - PathLog.track() + Path.Log.track() """initPocketOp(obj) ... create facing specific properties""" obj.addProperty( "App::PropertyEnumeration", @@ -122,7 +122,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): def areaOpOnChanged(self, obj, prop): """areaOpOnChanged(obj, prop) ... facing specific depths calculation.""" - PathLog.track(prop) + Path.Log.track(prop) if prop == "StepOver" and obj.StepOver == 0: obj.StepOver = 1 @@ -133,7 +133,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax if len(obj.Base) >= 1: - PathLog.debug("processing") + Path.Log.debug("processing") sublist = [] for i in obj.Base: o = i[0] @@ -154,10 +154,10 @@ class ObjectFace(PathPocketBase.ObjectPocket): self.removalshapes = [] holeShape = None - PathLog.debug("depthparams: {}".format([i for i in self.depthparams])) + Path.Log.debug("depthparams: {}".format([i for i in self.depthparams])) if obj.Base: - PathLog.debug("obj.Base: {}".format(obj.Base)) + Path.Log.debug("obj.Base: {}".format(obj.Base)) faces = [] holes = [] holeEnvs = [] @@ -187,7 +187,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): else: holes.append((b[0].Shape, wire)) else: - PathLog.warning( + Path.Log.warning( 'The base subobject, "{0}," is not a face. Ignoring "{0}."'.format( sub ) @@ -202,16 +202,16 @@ class ObjectFace(PathPocketBase.ObjectPocket): holeEnvs.append(env) holeShape = Part.makeCompound(holeEnvs) - PathLog.debug("Working on a collection of faces {}".format(faces)) + Path.Log.debug("Working on a collection of faces {}".format(faces)) planeshape = Part.makeCompound(faces) # If no base object, do planing of top surface of entire model else: planeshape = Part.makeCompound([base.Shape for base in self.model]) - PathLog.debug("Working on a shape {}".format(obj.Label)) + Path.Log.debug("Working on a shape {}".format(obj.Label)) # Find the correct shape depending on Boundary shape. - PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) + Path.Log.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox # Apply offset for clearing edges @@ -307,14 +307,14 @@ class ObjectFace(PathPocketBase.ObjectPocket): env = ofstShapeEnv if holeShape: - PathLog.debug("Processing holes and face ...") + Path.Log.debug("Processing holes and face ...") holeEnv = PathUtils.getEnvelope( partshape=holeShape, depthparams=self.depthparams ) newEnv = env.cut(holeEnv) tup = newEnv, False, "pathMillFace" else: - PathLog.debug("Processing solid face ...") + Path.Log.debug("Processing solid face ...") tup = env, False, "pathMillFace" self.removalshapes.append(tup) diff --git a/src/Mod/Path/PathScripts/PathMillFaceGui.py b/src/Mod/Path/PathScripts/PathMillFaceGui.py index 7ed8c37593..cdd7945b0c 100644 --- a/src/Mod/Path/PathScripts/PathMillFaceGui.py +++ b/src/Mod/Path/PathScripts/PathMillFaceGui.py @@ -22,7 +22,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathMillFace as PathMillFace import PathScripts.PathOpGui as PathOpGui import PathScripts.PathPocketBaseGui as PathPocketBaseGui @@ -35,17 +35,17 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Face Mill operation page controller and command implementation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class TaskPanelOpPage(PathPocketBaseGui.TaskPanelOpPage): """Page controller class for the face milling operation.""" def getForm(self): - PathLog.track() + Path.Log.track() """getForm() ... return UI""" form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpPocketFullEdit.ui") diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index dc8e61c5eb..f1e1e036a1 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -25,7 +25,6 @@ from PathScripts.PathUtils import waiting_effects from PySide.QtCore import QT_TRANSLATE_NOOP import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils @@ -44,10 +43,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Base class and properties implementation for all Path operations." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -164,7 +163,7 @@ class ObjectOp(object): obj.setEditorMode("OpStockZMin", 1) # read-only def __init__(self, obj, name, parentJob=None): - PathLog.track() + Path.Log.track() obj.addProperty( "App::PropertyBool", @@ -336,8 +335,8 @@ class ObjectOp(object): ) for n in self.opPropertyEnumerations(): - PathLog.debug("n: {}".format(n)) - PathLog.debug("n[0]: {} n[1]: {}".format(n[0], n[1])) + Path.Log.debug("n: {}".format(n)) + Path.Log.debug("n[0]: {} n[1]: {}".format(n[0], n[1])) if hasattr(obj, n[0]): setattr(obj, n[0], n[1]) @@ -390,11 +389,11 @@ class ObjectOp(object): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -410,13 +409,13 @@ class ObjectOp(object): obj.setEditorMode("OpFinalDepth", 2) def onDocumentRestored(self, obj): - PathLog.track() + Path.Log.track() features = self.opFeatures(obj) if ( FeatureBaseGeometry & features and "App::PropertyLinkSubList" == obj.getTypeIdOfProperty("Base") ): - PathLog.info("Replacing link property with global link (%s)." % obj.State) + Path.Log.info("Replacing link property with global link (%s)." % obj.State) base = obj.Base obj.removeProperty("Base") self.addBaseProperty(obj) @@ -580,8 +579,8 @@ class ObjectOp(object): obj.OpToolDiameter = obj.ToolController.Tool.Diameter if FeatureCoolant & features: - PathLog.track() - PathLog.debug(obj.getEnumerationsOfProperty("CoolantMode")) + Path.Log.track() + Path.Log.debug(obj.getEnumerationsOfProperty("CoolantMode")) obj.CoolantMode = job.SetupSheet.CoolantMode if FeatureDepths & features: @@ -635,11 +634,11 @@ class ObjectOp(object): if not job: if not ignoreErrors: - PathLog.error(translate("Path", "No parent job found for operation.")) + Path.Log.error(translate("Path", "No parent job found for operation.")) return False if not job.Model.Group: if not ignoreErrors: - PathLog.error( + Path.Log.error( translate("Path", "Parent job %s doesn't have a base object") % job.Label ) @@ -694,7 +693,7 @@ class ObjectOp(object): zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) except Part.OCCError as e: - PathLog.error(e) + Path.Log.error(e) else: # clearing with stock boundaries @@ -738,7 +737,7 @@ class ObjectOp(object): for sub in sublist: o.Shape.getElement(sub) except Part.OCCError: - PathLog.error( + Path.Log.error( "{} - stale base geometry detected - clearing.".format(obj.Label) ) obj.Base = [] @@ -766,7 +765,7 @@ class ObjectOp(object): Finally the base implementation adds a rapid move to clearance height and assigns the receiver's Path property from the command list. """ - PathLog.track() + Path.Log.track() if not obj.Active: path = Path.Path("(inactive operation)") @@ -782,7 +781,7 @@ class ObjectOp(object): if FeatureTool & self.opFeatures(obj): tc = obj.ToolController if tc is None or tc.ToolNumber == 0: - PathLog.error( + Path.Log.error( translate( "Path", "No Tool Controller is selected. We need a tool to build a Path.", @@ -796,7 +795,7 @@ class ObjectOp(object): self.horizRapid = tc.HorizRapid.Value tool = tc.Proxy.getTool(tc) if not tool or float(tool.Diameter) == 0: - PathLog.error( + Path.Log.error( translate( "Path", "No Tool found or diameter is zero. We need a tool to build a Path.", @@ -836,7 +835,7 @@ class ObjectOp(object): tc = obj.ToolController if tc is None or tc.ToolNumber == 0: - PathLog.error(translate("Path", "No Tool Controller selected.")) + Path.Log.error(translate("Path", "No Tool Controller selected.")) return translate("Path", "Tool Error") hFeedrate = tc.HorizFeed.Value @@ -847,7 +846,7 @@ class ObjectOp(object): if ( hFeedrate == 0 or vFeedrate == 0 ) and not PathPreferences.suppressAllSpeedsWarning(): - PathLog.warning( + Path.Log.warning( translate( "Path", "Tool Controller feedrates required to calculate the cycle time.", @@ -858,7 +857,7 @@ class ObjectOp(object): if ( hRapidrate == 0 or vRapidrate == 0 ) and not PathPreferences.suppressRapidSpeedsWarning(): - PathLog.warning( + Path.Log.warning( translate( "Path", "Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.", @@ -877,7 +876,7 @@ class ObjectOp(object): return cycleTime def addBase(self, obj, base, sub): - PathLog.track(obj, base, sub) + Path.Log.track(obj, base, sub) base = PathUtil.getPublicObject(base) if self._setBaseAndStock(obj): @@ -892,7 +891,7 @@ class ObjectOp(object): for p, el in baselist: if p == base and sub in el: - PathLog.notice( + Path.Log.notice( ( translate("Path", "Base object %s.%s already in the list") + "\n" @@ -905,7 +904,7 @@ class ObjectOp(object): baselist.append((base, sub)) obj.Base = baselist else: - PathLog.notice( + Path.Log.notice( ( translate("Path", "Base object %s.%s rejected by operation") + "\n" diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 62d9d80080..c60fb018b8 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -22,12 +22,12 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathGui as PathGui import PathScripts.PathJob as PathJob -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSelection as PathSelection @@ -47,10 +47,10 @@ __doc__ = "Base classes and framework for Path operation's UI" translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ViewProvider(object): @@ -62,7 +62,7 @@ class ViewProvider(object): """ def __init__(self, vobj, resources): - PathLog.track() + Path.Log.track() self.deleteOnReject = True self.OpIcon = ":/icons/%s.svg" % resources.pixmap self.OpName = resources.name @@ -75,7 +75,7 @@ class ViewProvider(object): self.panel = None def attach(self, vobj): - PathLog.track() + Path.Log.track() self.vobj = vobj self.Object = vobj.Object self.panel = None @@ -88,17 +88,17 @@ class ViewProvider(object): edit session, if the user does not press OK, it is assumed they've changed their mind about creating the operation. """ - PathLog.track() + Path.Log.track() return hasattr(self, "deleteOnReject") and self.deleteOnReject def setDeleteObjectsOnReject(self, state=False): - PathLog.track() + Path.Log.track() self.deleteOnReject = state return self.deleteOnReject def setEdit(self, vobj=None, mode=0): """setEdit(vobj, mode=0) ... initiate editing of receivers model.""" - PathLog.track() + Path.Log.track() if 0 == mode: if vobj is None: vobj = self.vobj @@ -124,7 +124,7 @@ class ViewProvider(object): if job: job.ViewObject.Proxy.setupEditVisibility(job) else: - PathLog.info("did not find no job") + Path.Log.info("did not find no job") def clearTaskPanel(self): """clearTaskPanel() ... internal callback function when editing has finished.""" @@ -140,7 +140,7 @@ class ViewProvider(object): def __getstate__(self): """__getstate__() ... callback before receiver is saved to a file. Returns a dictionary with the receiver's resources as strings.""" - PathLog.track() + Path.Log.track() state = {} state["OpName"] = self.OpName state["OpIcon"] = self.OpIcon @@ -176,7 +176,7 @@ class ViewProvider(object): def updateData(self, obj, prop): """updateData(obj, prop) ... callback whenever a property of the receiver's model is assigned. The callback is forwarded to the task panel - in case an editing session is ongoing.""" - # PathLog.track(obj.Label, prop) # Creates a lot of noise + # Path.Log.track(obj.Label, prop) # Creates a lot of noise if self.panel: self.panel.updateData(obj, prop) @@ -185,7 +185,7 @@ class ViewProvider(object): return True def setupContextMenu(self, vobj, menu): - PathLog.track() + Path.Log.track() for action in menu.actions(): menu.removeAction(action) action = QtGui.QAction(translate("PathOp", "Edit"), menu) @@ -548,7 +548,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): "Please select %s from a single solid" % self.featureName(), ) FreeCAD.Console.PrintError(msg + "\n") - PathLog.debug(msg) + Path.Log.debug(msg) return False sel = selection[0] if sel.HasSubObjects: @@ -557,26 +557,26 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): and selection[0].SubObjects[0].ShapeType == "Vertex" ): if not ignoreErrors: - PathLog.error(translate("PathOp", "Vertexes are not supported")) + Path.Log.error(translate("PathOp", "Vertexes are not supported")) return False if ( not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge" ): if not ignoreErrors: - PathLog.error(translate("PathOp", "Edges are not supported")) + Path.Log.error(translate("PathOp", "Edges are not supported")) return False if ( not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face" ): if not ignoreErrors: - PathLog.error(translate("PathOp", "Faces are not supported")) + Path.Log.error(translate("PathOp", "Faces are not supported")) return False else: if not self.supportsPanels() or "Panel" not in sel.Object.Name: if not ignoreErrors: - PathLog.error( + Path.Log.error( translate( "PathOp", "Please select %s of a solid" % self.featureName(), @@ -586,7 +586,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): return True def addBaseGeometry(self, selection): - PathLog.track(selection) + Path.Log.track(selection) if self.selectionSupportedAsBaseGeometry(selection, False): sel = selection[0] for sub in sel.SubElementNames: @@ -595,7 +595,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): return False def addBase(self): - PathLog.track() + Path.Log.track() if self.addBaseGeometry(FreeCADGui.Selection.getSelectionEx()): # self.obj.Proxy.execute(self.obj) self.setFields(self.obj) @@ -603,7 +603,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): self.updatePanelVisibility("Operation", self.obj) def deleteBase(self): - PathLog.track() + Path.Log.track() selected = self.form.baseList.selectedItems() for item in selected: self.form.baseList.takeItem(self.form.baseList.row(item)) @@ -621,7 +621,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): if sub: base = (obj, str(sub)) newlist.append(base) - PathLog.debug("Setting new base: %s -> %s" % (self.obj.Base, newlist)) + Path.Log.debug("Setting new base: %s -> %s" % (self.obj.Base, newlist)) self.obj.Base = newlist # self.obj.Proxy.execute(self.obj) @@ -677,7 +677,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): qList = self.form.baseList row = (qList.count() + qList.frameWidth()) * 15 # qList.setMinimumHeight(row) - PathLog.debug( + Path.Log.debug( "baseList({}, {}) {} * {}".format( qList.size(), row, qList.count(), qList.sizeHintForRow(0) ) @@ -748,7 +748,7 @@ class TaskPanelBaseLocationPage(TaskPanelPage): FreeCAD.ActiveDocument.recompute() def updateLocations(self): - PathLog.track() + Path.Log.track() locations = [] for i in range(self.formLoc.baseList.rowCount()): x = self.formLoc.baseList.item(i, 0).data(self.DataLocation) @@ -985,7 +985,7 @@ class TaskPanelDepthsPage(TaskPanelPage): def depthSet(self, obj, spinbox, prop): z = self.selectionZLevel(FreeCADGui.Selection.getSelectionEx()) if z is not None: - PathLog.debug("depthSet(%s, %s, %.2f)" % (obj.Label, prop, z)) + Path.Log.debug("depthSet(%s, %s, %.2f)" % (obj.Label, prop, z)) if spinbox.expression(): obj.setExpression(prop, None) self.setDirty() @@ -993,7 +993,7 @@ class TaskPanelDepthsPage(TaskPanelPage): if spinbox.updateProperty(): self.setDirty() else: - PathLog.info("depthSet(-)") + Path.Log.info("depthSet(-)") def selectionZLevel(self, sel): if len(sel) == 1 and len(sel[0].SubObjects) == 1: @@ -1071,7 +1071,7 @@ class TaskPanel(object): """ def __init__(self, obj, deleteOnReject, opPage, selectionFactory): - PathLog.track(obj.Label, deleteOnReject, opPage, selectionFactory) + Path.Log.track(obj.Label, deleteOnReject, opPage, selectionFactory) FreeCAD.ActiveDocument.openTransaction(translate("PathOp", "AreaOp Operation")) self.obj = obj self.deleteOnReject = deleteOnReject @@ -1209,7 +1209,7 @@ class TaskPanel(object): PathUtil.clearExpressionEngine(self.obj) FreeCAD.ActiveDocument.removeObject(self.obj.Name) except Exception as ee: - PathLog.debug("{}\n".format(ee)) + Path.Log.debug("{}\n".format(ee)) FreeCAD.ActiveDocument.commitTransaction() self.cleanup(resetEdit) return True @@ -1250,20 +1250,20 @@ class TaskPanel(object): def panelGetFields(self): """panelGetFields() ... invoked to trigger a complete transfer of UI data to the model.""" - PathLog.track() + Path.Log.track() for page in self.featurePages: page.pageGetFields() def panelSetFields(self): """panelSetFields() ... invoked to trigger a complete transfer of the model's properties to the UI.""" - PathLog.track() + Path.Log.track() self.obj.Proxy.sanitizeBase(self.obj) for page in self.featurePages: page.pageSetFields() def panelCleanup(self): """panelCleanup() ... invoked before the receiver is destroyed.""" - PathLog.track() + Path.Log.track() for page in self.featurePages: page.pageCleanup() @@ -1282,7 +1282,7 @@ class TaskPanel(object): def setupUi(self): """setupUi() ... internal function to initialise all pages.""" - PathLog.track(self.deleteOnReject) + Path.Log.track(self.deleteOnReject) if ( self.deleteOnReject @@ -1311,7 +1311,7 @@ class TaskPanel(object): def updateData(self, obj, prop): """updateDate(obj, prop) ... callback invoked whenever a model's property is assigned a value.""" - # PathLog.track(obj.Label, prop) # creates a lot of noise + # Path.Log.track(obj.Label, prop) # creates a lot of noise for page in self.featurePages: page.pageUpdateData(obj, prop) @@ -1391,7 +1391,7 @@ def Create(res): diag.setWindowModality(QtCore.Qt.ApplicationModal) diag.exec_() except PathOp.PathNoTCException: - PathLog.warning(translate("PathOp", "No tool controller, aborting op creation")) + Path.Log.warning(translate("PathOp", "No tool controller, aborting op creation")) FreeCAD.ActiveDocument.abortTransaction() FreeCAD.ActiveDocument.recompute() diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 3a5c530f2f..ea7e8cda0e 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.py @@ -23,8 +23,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD +import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import math # lazily loaded modules @@ -41,10 +41,10 @@ __doc__ = "Collection of functions used by various Path operations. The function PrintWireDebug = False if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -109,7 +109,7 @@ def debugWire(label, w): def _orientEdges(inEdges): """_orientEdges(inEdges) ... internal worker function to orient edges so the last vertex of one edge connects to the first vertex of the next edge. Assumes the edges are in an order so they can be connected.""" - PathLog.track() + Path.Log.track() # orient all edges of the wire so each edge's last value connects to the next edge's first value e0 = inEdges[0] # well, even the very first edge could be misoriented, so let's try and connect it to the second @@ -153,7 +153,7 @@ def _isWireClockwise(w): v0 = e.valueAt(e.FirstParameter) v1 = e.valueAt(e.LastParameter) area = area + (v0.x * v1.y - v1.x * v0.y) - PathLog.track(area) + Path.Log.track(area) return area < 0 @@ -167,13 +167,13 @@ def orientWire(w, forward=True): If forward = True (the default) the wire is oriented clockwise, looking down the negative Z axis. If forward = False the wire is oriented counter clockwise. If forward = None the orientation is determined by the order in which the edges appear in the wire.""" - PathLog.debug("orienting forward: {}: {} edges".format(forward, len(w.Edges))) + Path.Log.debug("orienting forward: {}: {} edges".format(forward, len(w.Edges))) wire = Part.Wire(_orientEdges(w.Edges)) if forward is not None: if forward != _isWireClockwise(wire): - PathLog.track("orientWire - needs flipping") + Path.Log.track("orientWire - needs flipping") return PathGeom.flipWire(wire) - PathLog.track("orientWire - ok") + Path.Log.track("orientWire - ok") return wire @@ -182,7 +182,7 @@ def offsetWire(wire, base, offset, forward, Side=None): The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. """ - PathLog.track("offsetWire") + Path.Log.track("offsetWire") if 1 == len(wire.Edges): edge = wire.Edges[0] @@ -297,11 +297,11 @@ def offsetWire(wire, base, offset, forward, Side=None): if wire.isClosed(): if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2, True): - PathLog.track("closed - outside") + Path.Log.track("closed - outside") if Side: Side[0] = "Outside" return orientWire(owire, forward) - PathLog.track("closed - inside") + Path.Log.track("closed - inside") if Side: Side[0] = "Inside" try: @@ -412,18 +412,18 @@ def offsetWire(wire, base, offset, forward, Side=None): for e0 in rightSideEdges: if PathGeom.edgesMatch(e, e0): edges = rightSideEdges - PathLog.debug("#use right side edges") + Path.Log.debug("#use right side edges") if not forward: - PathLog.debug("#reverse") + Path.Log.debug("#reverse") edges.reverse() return orientWire(Part.Wire(edges), None) # at this point we have the correct edges and they are in the order for forward # traversal (climb milling). If that's not what we want just reverse the order, # orientWire takes care of orienting the edges appropriately. - PathLog.debug("#use left side edges") + Path.Log.debug("#use left side edges") if not forward: - PathLog.debug("#reverse") + Path.Log.debug("#reverse") edges.reverse() return orientWire(Part.Wire(edges), None) diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 2db07d5b36..6c24d6ec82 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -23,7 +23,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Part -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathOp as PathOp import PathScripts.PathPocketBase as PathPocketBase import PathScripts.PathUtils as PathUtils @@ -40,10 +40,10 @@ __doc__ = "Class and implementation of the 3D Pocket operation." __created__ = "2014" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -127,11 +127,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -154,15 +154,15 @@ class ObjectPocket(PathPocketBase.ObjectPocket): def areaOpShapes(self, obj): """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" - PathLog.track() + Path.Log.track() subObjTups = [] removalshapes = [] if obj.Base: - PathLog.debug("base items exist. Processing... ") + Path.Log.debug("base items exist. Processing... ") for base in obj.Base: - PathLog.debug("obj.Base item: {}".format(base)) + Path.Log.debug("obj.Base item: {}".format(base)) # Check if all subs are faces allSubsFaceType = True @@ -185,7 +185,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): ): (fzmin, fzmax) = self.getMinMaxOfFaces(Faces) if obj.FinalDepth.Value < fzmin: - PathLog.warning( + Path.Log.warning( translate( "PathPocket", "Final depth set below ZMin of face(s) selected.", @@ -234,7 +234,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): removalshapes.append((obj.removalshape, False, "3DPocket")) else: # process the job base object as a whole - PathLog.debug("processing the whole job base object") + Path.Log.debug("processing the whole job base object") for base in self.model: if obj.ProcessStockArea is True: job = PathUtils.findParentJob(obj) @@ -326,8 +326,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): try: highFaceShape = Part.Face(Part.Wire(Part.__sortEdges__(allEdges))) except Exception as ee: - PathLog.warning(ee) - PathLog.error( + Path.Log.warning(ee) + Path.Log.error( translate( "Path", "A planar adaptive start is unavailable. The non-planar will be attempted.", @@ -343,8 +343,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): Part.__sortEdges__(allEdges) ) # NON-planar face method except Exception as eee: - PathLog.warning(eee) - PathLog.error( + Path.Log.warning(eee) + Path.Log.error( translate( "Path", "The non-planar adaptive start is also unavailable." ) @@ -368,13 +368,13 @@ class ObjectPocket(PathPocketBase.ObjectPocket): highFace.Shape.BoundBox.ZMax > mx or highFace.Shape.BoundBox.ZMin < mn ): - PathLog.warning( + Path.Log.warning( "ZMaxDiff: {}; ZMinDiff: {}".format( highFace.Shape.BoundBox.ZMax - mx, highFace.Shape.BoundBox.ZMin - mn, ) ) - PathLog.error( + Path.Log.error( translate( "Path", "The non-planar adaptive start is also unavailable." ) @@ -397,8 +397,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): lowFaceShape = Part.Face(Part.Wire(Part.__sortEdges__(allEdges))) # lowFaceShape = Part.makeFilledFace(Part.__sortEdges__(allEdges)) # NON-planar face method except Exception as ee: - PathLog.error(ee) - PathLog.error("An adaptive finish is unavailable.") + Path.Log.error(ee) + Path.Log.error("An adaptive finish is unavailable.") isLowFacePlanar = False else: FreeCAD.ActiveDocument.addObject("Part::Feature", "bottomEdgeFace") @@ -633,7 +633,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): for ei2 in range(0, len(face2.Edges)): edg2 = face2.Edges[ei2] if edg1.isSame(edg2) is True: - PathLog.debug( + Path.Log.debug( "{}.Edges[{}] connects at {}.Edges[{}]".format( sub1, ei1, sub2, ei2 ) @@ -704,7 +704,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): Compare vertexes of two edges to identify a common vertex. Returns the vertex index of edge1 to which edge2 is connected""" if show is True: - PathLog.info("New findCommonVertex()... ") + Path.Log.info("New findCommonVertex()... ") oIdx = 0 listOne = edge1.Vertexes @@ -713,15 +713,15 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # Find common vertexes for o in listOne: if show is True: - PathLog.info(" one ({}, {}, {})".format(o.X, o.Y, o.Z)) + Path.Log.info(" one ({}, {}, {})".format(o.X, o.Y, o.Z)) for t in listTwo: if show is True: - PathLog.error("two ({}, {}, {})".format(t.X, t.Y, t.Z)) + Path.Log.error("two ({}, {}, {})".format(t.X, t.Y, t.Z)) if o.X == t.X: if o.Y == t.Y: if o.Z == t.Z: if show is True: - PathLog.info("found") + Path.Log.info("found") return oIdx oIdx += 1 return -1 @@ -773,7 +773,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): while len(holds) > 0: if loops > 500: - PathLog.error("BREAK --- LOOPS LIMIT of 500 ---") + Path.Log.error("BREAK --- LOOPS LIMIT of 500 ---") break save = False diff --git a/src/Mod/Path/PathScripts/PathPocketBase.py b/src/Mod/Path/PathScripts/PathPocketBase.py index b8f78810fb..1952a03f9f 100644 --- a/src/Mod/Path/PathScripts/PathPocketBase.py +++ b/src/Mod/Path/PathScripts/PathPocketBase.py @@ -21,11 +21,12 @@ # *************************************************************************** import FreeCAD -from PySide.QtCore import QT_TRANSLATE_NOOP +import Path import PathScripts.PathAreaOp as PathAreaOp -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp +from PySide.QtCore import QT_TRANSLATE_NOOP + __title__ = "Base Path Pocket Operation" __author__ = "sliptonic (Brad Collette)" @@ -33,10 +34,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Base class and implementation for Path pocket operations." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -79,11 +80,11 @@ class ObjectPocket(PathAreaOp.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -114,7 +115,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): def initAreaOp(self, obj): """initAreaOp(obj) ... create pocket specific properties. Do not overwrite, implement initPocketOp(obj) instead.""" - PathLog.track() + Path.Log.track() # Pocket Properties obj.addProperty( @@ -191,7 +192,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): self.initPocketOp(obj) def areaOpRetractTool(self, obj): - PathLog.debug("retracting tool: %d" % (not obj.KeepToolDown)) + Path.Log.debug("retracting tool: %d" % (not obj.KeepToolDown)) return not obj.KeepToolDown def areaOpUseProjection(self, obj): @@ -200,7 +201,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): def areaOpAreaParams(self, obj, isHole): """areaOpAreaParams(obj, isHole) ... return dictionary with pocket's area parameters""" - PathLog.track() + Path.Log.track() params = {} params["Fill"] = 0 params["Coplanar"] = 0 @@ -245,7 +246,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): ), ) obj.PocketLastStepOver = 0 - PathLog.track() + Path.Log.track() def areaOpPathParams(self, obj, isHole): """areaOpAreaParams(obj, isHole) ... return dictionary with pocket's path parameters""" diff --git a/src/Mod/Path/PathScripts/PathPocketBaseGui.py b/src/Mod/Path/PathScripts/PathPocketBaseGui.py index 37d477a1de..b3c44d052e 100644 --- a/src/Mod/Path/PathScripts/PathPocketBaseGui.py +++ b/src/Mod/Path/PathScripts/PathPocketBaseGui.py @@ -22,11 +22,11 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui import PathScripts.PathPocket as PathPocket -import PathScripts.PathLog as PathLog __title__ = "Path Pocket Base Operation UI" __author__ = "sliptonic (Brad Collette)" @@ -34,10 +34,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Base page controller and command implementation for path pocket operations." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathPocketGui.py b/src/Mod/Path/PathScripts/PathPocketGui.py index 80b20ef9ab..008ab3807a 100644 --- a/src/Mod/Path/PathScripts/PathPocketGui.py +++ b/src/Mod/Path/PathScripts/PathPocketGui.py @@ -21,18 +21,18 @@ # *************************************************************************** import FreeCAD +import Path import PathScripts.PathOpGui as PathOpGui import PathScripts.PathPocket as PathPocket import PathScripts.PathPocketBaseGui as PathPocketBaseGui -import PathScripts.PathLog as PathLog from PySide.QtCore import QT_TRANSLATE_NOOP if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) __title__ = "Path Pocket Operation UI" diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index ba5d99c7a3..63a13e7271 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -22,8 +22,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD +import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathPocketBase as PathPocketBase @@ -47,10 +47,10 @@ __doc__ = "Class and implementation of shape based Pocket operation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectPocket(PathPocketBase.ObjectPocket): @@ -89,10 +89,10 @@ class ObjectPocket(PathPocketBase.ObjectPocket): def areaOpShapes(self, obj): """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" - PathLog.track() + Path.Log.track() self.removalshapes = [] - # self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False + # self.isDebug = True if Path.Log.getLevel(Path.Log.thisModule()) == 4 else False self.removalshapes = [] avoidFeatures = list() @@ -103,14 +103,14 @@ class ObjectPocket(PathPocketBase.ObjectPocket): avoidFeatures.append(e.feature) if obj.Base: - PathLog.debug("base items exist. Processing...") + Path.Log.debug("base items exist. Processing...") self.horiz = [] self.vert = [] for (base, subList) in obj.Base: for sub in subList: if "Face" in sub: if sub not in avoidFeatures and not self.clasifySub(base, sub): - PathLog.error( + Path.Log.error( "Pocket does not support shape {}.{}".format( base.Label, sub ) @@ -133,7 +133,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): - PathLog.error("Vertical faces do not form a loop - ignoring") + Path.Log.error("Vertical faces do not form a loop - ignoring") else: self.horiz.append(face) @@ -174,7 +174,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): ] else: # process the job base object as a whole - PathLog.debug("processing the whole job base object") + Path.Log.debug("processing the whole job base object") self.outlines = [ Part.Face( TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1)) @@ -218,15 +218,15 @@ class ObjectPocket(PathPocketBase.ObjectPocket): face = bs.Shape.getElement(sub) if type(face.Surface) == Part.Plane: - PathLog.debug("type() == Part.Plane") + Path.Log.debug("type() == Part.Plane") if PathGeom.isVertical(face.Surface.Axis): - PathLog.debug(" -isVertical()") + Path.Log.debug(" -isVertical()") # it's a flat horizontal face self.horiz.append(face) return True elif PathGeom.isHorizontal(face.Surface.Axis): - PathLog.debug(" -isHorizontal()") + Path.Log.debug(" -isHorizontal()") self.vert.append(face) return True @@ -236,10 +236,10 @@ class ObjectPocket(PathPocketBase.ObjectPocket): elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical( face.Surface.Axis ): - PathLog.debug("type() == Part.Cylinder") + Path.Log.debug("type() == Part.Cylinder") # vertical cylinder wall if any(e.isClosed() for e in face.Edges): - PathLog.debug(" -e.isClosed()") + Path.Log.debug(" -e.isClosed()") # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) @@ -250,23 +250,23 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return True else: - PathLog.debug(" -none isClosed()") + Path.Log.debug(" -none isClosed()") # partial cylinder wall self.vert.append(face) return True elif type(face.Surface) == Part.SurfaceOfExtrusion: # extrusion wall - PathLog.debug("type() == Part.SurfaceOfExtrusion") + Path.Log.debug("type() == Part.SurfaceOfExtrusion") # Save face to self.horiz for processing or display error if self.isVerticalExtrusionFace(face): self.vert.append(face) return True else: - PathLog.error("Failed to identify vertical face from {}".format(sub)) + Path.Log.error("Failed to identify vertical face from {}".format(sub)) else: - PathLog.debug(" -type(face.Surface): {}".format(type(face.Surface))) + Path.Log.debug(" -type(face.Surface): {}".format(type(face.Surface))) return False diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index dd88dadaf8..acd60454e2 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathOpGui as PathOpGui import PathScripts.PathPocketShape as PathPocketShape import PathScripts.PathPocketBaseGui as PathPocketBaseGui @@ -39,10 +39,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Pocket Shape operation page controller and command implementation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index e89ccc6224..033c1a3175 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -28,7 +28,6 @@ import FreeCAD import FreeCADGui import Path import PathScripts.PathJob as PathJob -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils @@ -43,10 +42,10 @@ from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = PathLog.thisModule() if True: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -70,7 +69,7 @@ def processFileNameSubstitutions( """Process any substitutions in the outputpath or filename.""" # The following section allows substitution within the path part - PathLog.track(f"path before substitution: {outputpath}") + Path.Log.track(f"path before substitution: {outputpath}") if "%D" in outputpath: # Directory of active document D = FreeCAD.ActiveDocument.FileName @@ -100,10 +99,10 @@ def processFileNameSubstitutions( j = job.Label outputpath = outputpath.replace("%j", j) - PathLog.track(f"path after substitution: {outputpath}") + Path.Log.track(f"path after substitution: {outputpath}") # The following section allows substitution within the filename part - PathLog.track(f"filename before substitution: {filename}") + Path.Log.track(f"filename before substitution: {filename}") # Use the file label if "%d" in filename: @@ -122,7 +121,7 @@ def processFileNameSubstitutions( # This section handles unique names for splitting output if job.SplitOutput: - PathLog.track() + Path.Log.track() if "%T" in filename and job.OrderOutputBy == "Tool": filename = filename.replace("%T", subpartname) @@ -142,20 +141,20 @@ def processFileNameSubstitutions( else: filename = f"{filename}-{sequencenumber}" - PathLog.track(f"filename after substitution: {filename}") + Path.Log.track(f"filename after substitution: {filename}") if not ext: ext = ".nc" - PathLog.track(f"file extension: {ext}") + Path.Log.track(f"file extension: {ext}") fullPath = f"{outputpath}{os.path.sep}{filename}{ext}" - PathLog.track(f"full filepath: {fullPath}") + Path.Log.track(f"full filepath: {fullPath}") return fullPath def resolveFileName(job, subpartname, sequencenumber): - PathLog.track(subpartname, sequencenumber) + Path.Log.track(subpartname, sequencenumber) validPathSubstitutions = ["D", "d", "M", "j"] validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"] @@ -198,7 +197,7 @@ def resolveFileName(job, subpartname, sequencenumber): ext = ".nc" # By now we should have a sanitized path, filename and extension to work with - PathLog.track(f"path: {outputpath} name: {filename} ext: {ext}") + Path.Log.track(f"path: {outputpath} name: {filename} ext: {ext}") fullPath = processFileNameSubstitutions( job, @@ -238,7 +237,7 @@ def resolveFileName(job, subpartname, sequencenumber): if openDialog: foo = QtGui.QFileDialog.getSaveFileName( - QtGui.QApplication.activeWindow(), "Output File", fullPath + QtGui.QApplication.activeWindow(), "Output File", filename ) if foo[0]: fullPath = foo[0] @@ -250,9 +249,8 @@ def resolveFileName(job, subpartname, sequencenumber): for s in validPathSubstitutions + validFilenameSubstitutions: fullPath = fullPath.replace(f"%{s}", "") - fullPath = os.path.normpath(fullPath) - PathLog.track(fullPath) - + fullPath = os.path.normpath(fullPath) + Path.Log.track(fullPath) return fullPath @@ -266,7 +264,7 @@ def buildPostList(job): postlist = [] if orderby == "Fixture": - PathLog.debug("Ordering by Fixture") + Path.Log.debug("Ordering by Fixture") # Order by fixture means all operations and tool changes will be # completed in one fixture before moving to the next. @@ -294,13 +292,13 @@ def buildPostList(job): if tc is not None and PathUtil.opProperty(obj, "Active"): if tc.ToolNumber != currTool: sublist.append(tc) - PathLog.debug("Appending TC: {}".format(tc.Name)) + Path.Log.debug("Appending TC: {}".format(tc.Name)) currTool = tc.ToolNumber sublist.append(obj) postlist.append((f, sublist)) elif orderby == "Tool": - PathLog.debug("Ordering by Tool") + Path.Log.debug("Ordering by Tool") # Order by tool means tool changes are minimized. # all operations with the current tool are processed in the current # fixture before moving to the next fixture. @@ -329,13 +327,13 @@ def buildPostList(job): curlist = [] # list of ops for tool, will repeat for each fixture sublist = [] # list of ops for output splitting - PathLog.track(job.PostProcessorOutputFile) + Path.Log.track(job.PostProcessorOutputFile) for idx, obj in enumerate(job.Operations.Group): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) # check if the operation is active if not getattr(obj, "Active", True): - PathLog.track() + Path.Log.track() continue # Determine the proper string for the Op's TC @@ -346,7 +344,7 @@ def buildPostList(job): tcstring = f"{tc.ToolNumber}" else: tcstring = re.sub(r"[^\w\d-]", "_", tc.Label) - PathLog.track(toolstring) + Path.Log.track(toolstring) if tc is None or tc.ToolNumber == currTool: curlist.append(obj) @@ -374,7 +372,7 @@ def buildPostList(job): postlist.append((toolstring, sublist)) elif orderby == "Operation": - PathLog.debug("Ordering by Operation") + Path.Log.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. currTool = None @@ -388,7 +386,7 @@ def buildPostList(job): continue sublist = [] - PathLog.debug("obj: {}".format(obj.Name)) + Path.Log.debug("obj: {}".format(obj.Name)) for f in wcslist: fobj = _TempObject() @@ -415,10 +413,10 @@ def buildPostList(job): postlist.append((obj.Label, sublist)) if job.SplitOutput: - PathLog.track() + Path.Log.track() return postlist else: - PathLog.track() + Path.Log.track() finalpostlist = [ ("allitems", [item for slist in postlist for item in slist[1]]) ] @@ -493,16 +491,16 @@ class CommandPathPost: return False def exportObjectsWith(self, objs, partname, job, sequence, extraargs=None): - PathLog.track(extraargs) + Path.Log.track(extraargs) # check if the user has a project and has set the default post and # output filename # extraargs can be passed in at this time - PathLog.track(partname, sequence) - PathLog.track(objs) + Path.Log.track(partname, sequence) + Path.Log.track(objs) # partname = objs[0] # slist = objs[1] - PathLog.track(objs, partname) + Path.Log.track(objs, partname) postArgs = PathPreferences.defaultPostProcessorArgs() if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs: @@ -513,7 +511,7 @@ class CommandPathPost: if extraargs is not None: postArgs += " {}".format(extraargs) - PathLog.track(postArgs) + Path.Log.track(postArgs) postname = self.resolvePostProcessor(job) # filename = "-" @@ -530,7 +528,7 @@ class CommandPathPost: return (True, "", filename) def Activated(self): - PathLog.track() + Path.Log.track() FreeCAD.ActiveDocument.openTransaction("Post Process the Selected path(s)") FreeCADGui.addModule("PathScripts.PathPost") @@ -564,7 +562,7 @@ class CommandPathPost: if hasattr(o, "Proxy"): if isinstance(o.Proxy, PathJob.ObjectJob): targetlist.append(o.Label) - PathLog.debug("Possible post objects: {}".format(targetlist)) + Path.Log.debug("Possible post objects: {}".format(targetlist)) if len(targetlist) > 1: jobname, result = QtGui.QInputDialog.getItem( None, translate("Path", "Choose a Path Job"), None, targetlist @@ -576,7 +574,7 @@ class CommandPathPost: jobname = targetlist[0] job = FreeCAD.ActiveDocument.getObject(jobname) - PathLog.debug("about to postprocess job: {}".format(job.Name)) + Path.Log.debug("about to postprocess job: {}".format(job.Name)) postlist = buildPostList(job) # filename = resolveFileName(job, "allitems", 0) @@ -591,7 +589,7 @@ class CommandPathPost: result, gcode, name = self.exportObjectsWith(sublist, partname, job, idx) filenames.append(name) - PathLog.track(result, gcode, name) + Path.Log.track(result, gcode, name) if name is None: success = False @@ -612,13 +610,13 @@ class CommandPathPost: # gcode = self.exportObjectsWith(finalpostlist, "allitems", job, 1) # success = gcode is not None - PathLog.track(success) + Path.Log.track(success) if success: if hasattr(job, "LastPostProcessDate"): job.LastPostProcessDate = str(datetime.now()) if hasattr(job, "LastPostProcessOutput"): job.LastPostProcessOutput = " \n".join(filenames) - PathLog.track(job.LastPostProcessOutput) + Path.Log.track(job.LastPostProcessOutput) FreeCAD.ActiveDocument.commitTransaction() else: FreeCAD.ActiveDocument.abortTransaction() diff --git a/src/Mod/Path/PathScripts/PathPostProcessor.py b/src/Mod/Path/PathScripts/PathPostProcessor.py index 85857c2394..e57a47107c 100644 --- a/src/Mod/Path/PathScripts/PathPostProcessor.py +++ b/src/Mod/Path/PathScripts/PathPostProcessor.py @@ -20,11 +20,11 @@ # * * # *************************************************************************** -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathPreferences as PathPreferences import sys -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class PostProcessor: @@ -34,7 +34,7 @@ class PostProcessor: @classmethod def load(cls, processor): - PathLog.track(processor) + Path.Log.track(processor) syspath = sys.path paths = PathPreferences.searchPathsPost() paths.extend(sys.path) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index 5148b7cbbd..16d340f98f 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -21,16 +21,16 @@ # *************************************************************************** import FreeCAD +import Path import glob import os -import PathScripts.PathLog as PathLog from PySide.QtGui import QMessageBox if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -208,7 +208,7 @@ def defaultJobTemplate(): def setJobDefaults(fileName, jobTemplate, geometryTolerance, curveAccuracy): - PathLog.track( + Path.Log.track( "(%s='%s', %s, %s, %s)" % (DefaultFilePath, fileName, jobTemplate, geometryTolerance, curveAccuracy) ) @@ -320,14 +320,14 @@ def lastFileToolLibrary(): if len(libFiles) >= 1: filename = libFiles[0] setLastFileToolLibrary(filename) - PathLog.track(filename) + Path.Log.track(filename) return filename else: return None def setLastFileToolLibrary(path): - PathLog.track(path) + Path.Log.track(path) if os.path.isfile(path): # keep the path and file in sync preferences().SetString(LastPathToolLibrary, os.path.split(path)[0]) return preferences().SetString(LastFileToolLibrary, path) @@ -342,14 +342,14 @@ def setLastPathToolBit(path): def lastPathToolLibrary(): - PathLog.track() + Path.Log.track() return preferences().GetString(LastPathToolLibrary, pathDefaultToolsPath("Library")) def setLastPathToolLibrary(path): - PathLog.track(path) + Path.Log.track(path) curLib = lastFileToolLibrary() - PathLog.debug("curLib: {}".format(curLib)) + Path.Log.debug("curLib: {}".format(curLib)) if curLib and os.path.split(curLib)[0] != path: setLastFileToolLibrary("") # a path is known but not specific file return preferences().SetString(LastPathToolLibrary, path) diff --git a/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py b/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py index cdac0a8a65..aa0bd6742f 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py +++ b/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py @@ -21,14 +21,14 @@ # *************************************************************************** import FreeCADGui +import Path import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathLog as PathLog if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class AdvancedPreferencesPage: @@ -48,7 +48,7 @@ class AdvancedPreferencesPage: ) def loadSettings(self): - PathLog.track() + Path.Log.track() self.form.WarningSuppressAllSpeeds.setChecked( PathPreferences.suppressAllSpeedsWarning() ) diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py index 3164438998..a72d77118b 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py @@ -22,7 +22,6 @@ import FreeCAD import Path -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import json @@ -32,7 +31,7 @@ from PySide import QtCore, QtGui from PathScripts.PathPostProcessor import PostProcessor -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class JobPreferencesPage: diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index dbb2b2c0e9..7120cea74c 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -24,7 +24,6 @@ from __future__ import print_function import FreeCAD import Path -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP @@ -36,10 +35,10 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Path Probing operation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectProbing(PathOp.ObjectOp): @@ -96,7 +95,7 @@ class ObjectProbing(PathOp.ObjectOp): def opExecute(self, obj): """opExecute(obj) ... generate probe locations.""" - PathLog.track() + Path.Log.track() self.commandlist.append(Path.Command("(Begin Probing)")) stock = PathUtils.findParentJob(obj).Stock diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index 96c80b50ae..f19429892f 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -22,11 +22,11 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathProbe as PathProbe import PathScripts.PathOpGui as PathOpGui import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog from PySide.QtCore import QT_TRANSLATE_NOOP from PySide import QtCore, QtGui @@ -38,10 +38,10 @@ __doc__ = "Probing operation page controller and command implementation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 7066966d79..b5dab94dc1 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -25,7 +25,6 @@ import FreeCAD import Path import PathScripts.PathAreaOp as PathAreaOp -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils import PathScripts.drillableLib as drillableLib @@ -50,10 +49,10 @@ __doc__ = ( __contributors__ = "Schildkroet" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectProfile(PathAreaOp.ObjectOp): @@ -218,12 +217,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): # data[k] = [tup[idx] for tup in v] data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -375,7 +374,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): shapes = [] remainingObjBaseFeatures = [] - self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False + self.isDebug = True if Path.Log.getLevel(Path.Log.thisModule()) == 4 else False self.inaccessibleMsg = translate( "PathProfile", "The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.", @@ -412,12 +411,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): obj.Base and len(obj.Base) > 0 ): # The user has selected subobjects from the base. Process each. shapes.extend(self._processEdges(obj, remainingObjBaseFeatures)) - PathLog.track("returned {} shapes".format(len(shapes))) + Path.Log.track("returned {} shapes".format(len(shapes))) - PathLog.track(remainingObjBaseFeatures) + Path.Log.track(remainingObjBaseFeatures) if obj.Base and len(obj.Base) > 0 and not remainingObjBaseFeatures: # Edges were already processed, or whole model targeted. - PathLog.track("remainingObjBaseFeatures is False") + Path.Log.track("remainingObjBaseFeatures is False") elif ( remainingObjBaseFeatures and len(remainingObjBaseFeatures) > 0 ): # Process remaining features after edges processed above. @@ -434,7 +433,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): if numpy.isclose( abs(shape.normalAt(0, 0).z), 1 ): # horizontal face - PathLog.debug(abs(shape.normalAt(0, 0).z)) + Path.Log.debug(abs(shape.normalAt(0, 0).z)) for wire in shape.Wires: if wire.hashCode() == shape.OuterWire.hashCode(): continue @@ -443,16 +442,16 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: - PathLog.track() + Path.Log.track() ignoreSub = base.Name + "." + sub msg = "Found a selected object which is not a face. Ignoring:" - PathLog.warning(msg + " {}".format(ignoreSub)) + Path.Log.warning(msg + " {}".format(ignoreSub)) for baseShape, wire in holes: cont = False f = Part.makeFace(wire, "Part::FaceMakerSimple") drillable = drillableLib.isDrillable(baseShape, f, vector=None) - PathLog.debug(drillable) + Path.Log.debug(drillable) if obj.processCircles: if drillable: @@ -486,7 +485,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): msg = translate( "PathProfile", "Unable to create path for face(s)." ) - PathLog.error(msg + "\n{}".format(ee)) + Path.Log.error(msg + "\n{}".format(ee)) cont = False if cont: @@ -508,17 +507,17 @@ class ObjectProfile(PathAreaOp.ObjectOp): else: # Try to build targets from the job models # No base geometry selected, so treating operation like a exterior contour operation - PathLog.track() + Path.Log.track() self.opUpdateDepths(obj) if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): - PathLog.debug("Single model processed.") + Path.Log.debug("Single model processed.") shapes.extend(self._processEachModel(obj)) else: shapes.extend(self._processEachModel(obj)) self.removalshapes = shapes - PathLog.debug("%d shapes" % len(shapes)) + Path.Log.debug("%d shapes" % len(shapes)) # Delete the temporary objects if self.isDebug: @@ -547,7 +546,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Edges pre-processing def _processEdges(self, obj, remainingObjBaseFeatures): - PathLog.track("remainingObjBaseFeatures: {}".format(remainingObjBaseFeatures)) + Path.Log.track("remainingObjBaseFeatures: {}".format(remainingObjBaseFeatures)) shapes = [] basewires = [] ezMin = None @@ -572,7 +571,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): if len(keepFaces) > 0: # save faces for returning and processing remainingObjBaseFeatures.append((base, keepFaces)) - PathLog.track(basewires) + Path.Log.track(basewires) for base, wires in basewires: for wire in wires: if wire.isClosed(): @@ -592,13 +591,13 @@ class ObjectProfile(PathAreaOp.ObjectOp): tup = shapeEnv, False, "pathProfile" shapes.append(tup) else: - PathLog.error(self.inaccessibleMsg) + Path.Log.error(self.inaccessibleMsg) else: # Attempt open-edges profile if self.JOB.GeometryTolerance.Value == 0.0: msg = self.JOB.Label + ".GeometryTolerance = 0.0. " msg += "Please set to an acceptable value greater than zero." - PathLog.error(msg) + Path.Log.error(msg) else: flattened = self._flattenWire(obj, wire, obj.FinalDepth.Value) zDiff = math.fabs(wire.BoundBox.ZMin - obj.FinalDepth.Value) @@ -624,7 +623,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): for cW in cutWireObjs: openEdges.append(cW) else: - PathLog.error(self.inaccessibleMsg) + Path.Log.error(self.inaccessibleMsg) if openEdges: tup = openEdges, False, "OpenEdge" @@ -635,9 +634,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): "PathProfile", "Check edge selection and Final Depth requirements for profiling open edge(s).", ) - PathLog.error(msg) + Path.Log.error(msg) else: - PathLog.error(self.inaccessibleMsg) + Path.Log.error(self.inaccessibleMsg) # Eif # Eif # Efor @@ -647,11 +646,11 @@ class ObjectProfile(PathAreaOp.ObjectOp): def _flattenWire(self, obj, wire, trgtDep): """_flattenWire(obj, wire)... Return a flattened version of the wire""" - PathLog.debug("_flattenWire()") + Path.Log.debug("_flattenWire()") wBB = wire.BoundBox if wBB.ZLength > 0.0: - PathLog.debug("Wire is not horizontally co-planar. Flattening it.") + Path.Log.debug("Wire is not horizontally co-planar. Flattening it.") # Extrude non-horizontal wire extFwdLen = (wBB.ZLength + 2.0) * 2.0 @@ -672,7 +671,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Open-edges methods def _getCutAreaCrossSection(self, obj, base, origWire, flatWire): - PathLog.debug("_getCutAreaCrossSection()") + Path.Log.debug("_getCutAreaCrossSection()") # FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value toolDiam = ( @@ -758,14 +757,14 @@ class ObjectProfile(PathAreaOp.ObjectOp): ): botFc.append(f) if len(topFc) == 0: - PathLog.error("Failed to identify top faces of cut area.") + Path.Log.error("Failed to identify top faces of cut area.") return False topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) topComp.translate( FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin) ) # Translate face to final depth if len(botFc) > 1: - # PathLog.debug('len(botFc) > 1') + # Path.Log.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Wires[0]) tmpFace = Part.Face(extBndbox.Wires[0]) for f in botFc: @@ -786,12 +785,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Determine with which set of intersection tags the model intersects (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, "QRY", comFC) if cmnExtArea > cmnIntArea: - PathLog.debug("Cutting on Ext side.") + Path.Log.debug("Cutting on Ext side.") self.cutSide = "E" self.cutSideTags = eTAG tagCOM = begExt.CenterOfMass else: - PathLog.debug("Cutting on Int side.") + Path.Log.debug("Cutting on Int side.") self.cutSide = "I" self.cutSideTags = iTAG tagCOM = begInt.CenterOfMass @@ -836,12 +835,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Efor if wi is None: - PathLog.error( + Path.Log.error( "The cut area cross-section wire does not coincide with selected edge. Wires[] index is None." ) return False else: - PathLog.debug("Cross-section Wires[] index is {}.".format(wi)) + Path.Log.debug("Cross-section Wires[] index is {}.".format(wi)) nWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi].Edges)) fcShp = Part.Face(nWire) @@ -850,22 +849,22 @@ class ObjectProfile(PathAreaOp.ObjectOp): # verify that wire chosen is not inside the physical model if wi > 0: # and isInterior is False: - PathLog.debug("Multiple wires in cut area. First choice is not 0. Testing.") + Path.Log.debug("Multiple wires in cut area. First choice is not 0. Testing.") testArea = fcShp.cut(base.Shape) isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, testArea) - PathLog.debug("isReady {}.".format(isReady)) + Path.Log.debug("isReady {}.".format(isReady)) if isReady is False: - PathLog.debug("Using wire index {}.".format(wi - 1)) + Path.Log.debug("Using wire index {}.".format(wi - 1)) pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) workShp = pfcShp.cut(fcShp) if testArea.Area < minArea: - PathLog.debug("offset area is less than minArea of {}.".format(minArea)) - PathLog.debug("Using wire index {}.".format(wi - 1)) + Path.Log.debug("offset area is less than minArea of {}.".format(minArea)) + Path.Log.debug("Using wire index {}.".format(wi - 1)) pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) @@ -879,7 +878,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return cutShp def _checkTagIntersection(self, iTAG, eTAG, cutSide, tstObj): - PathLog.debug("_checkTagIntersection()") + Path.Log.debug("_checkTagIntersection()") # Identify intersection of Common area and Interior Tags intCmn = tstObj.common(iTAG) @@ -893,17 +892,17 @@ class ObjectProfile(PathAreaOp.ObjectOp): return (cmnIntArea, cmnExtArea) if cmnExtArea > cmnIntArea: - PathLog.debug("Cutting on Ext side.") + Path.Log.debug("Cutting on Ext side.") if cutSide == "E": return True else: - PathLog.debug("Cutting on Int side.") + Path.Log.debug("Cutting on Int side.") if cutSide == "I": return True return False def _extractPathWire(self, obj, base, flatWire, cutShp): - PathLog.debug("_extractPathWire()") + Path.Log.debug("_extractPathWire()") subLoops = [] rtnWIRES = [] @@ -929,10 +928,10 @@ class ObjectProfile(PathAreaOp.ObjectOp): if osArea: # Make LGTM parser happy pass else: - PathLog.error("No area to offset shape returned.") + Path.Log.error("No area to offset shape returned.") return [] except Exception as ee: - PathLog.error("No area to offset shape returned.\n{}".format(ee)) + Path.Log.error("No area to offset shape returned.\n{}".format(ee)) return [] self._addDebugObject("OffsetShape", ofstShp) @@ -967,7 +966,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): self._addDebugObject("Near1", near1Shp) if w0 != w1: - PathLog.warning( + Path.Log.warning( "Offset wire endpoint indexes are not equal - w0, w1: {}, {}".format( w0, w1 ) @@ -976,12 +975,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Debugging """ if self.isDebug: - PathLog.debug('min0i is {}.'.format(min0i)) - PathLog.debug('min1i is {}.'.format(min1i)) - PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) - PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) - PathLog.debug('NEAR0 is {}.'.format(NEAR0)) - PathLog.debug('NEAR1 is {}.'.format(NEAR1)) + Path.Log.debug('min0i is {}.'.format(min0i)) + Path.Log.debug('min1i is {}.'.format(min1i)) + Path.Log.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) + Path.Log.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) + Path.Log.debug('NEAR0 is {}.'.format(NEAR0)) + Path.Log.debug('NEAR1 is {}.'.format(NEAR1)) """ mainWire = ofstShp.Wires[w0] @@ -1018,7 +1017,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1] ) except Exception as ee: - PathLog.error("Failed to identify offset edge.\n{}".format(ee)) + Path.Log.error("Failed to identify offset edge.\n{}".format(ee)) return False edgs0 = [] edgs1 = [] @@ -1043,7 +1042,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): def _getOffsetArea(self, obj, fcShape, isHole): """Get an offset area for a shape. Wrapper around PathUtils.getOffsetArea.""" - PathLog.debug("_getOffsetArea()") + Path.Log.debug("_getOffsetArea()") JOB = PathUtils.findParentJob(obj) tolerance = JOB.GeometryTolerance.Value @@ -1057,7 +1056,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): ) def _findNearestVertex(self, shape, point): - PathLog.debug("_findNearestVertex()") + Path.Log.debug("_findNearestVertex()") PT = FreeCAD.Vector(point.x, point.y, 0.0) def sortDist(tup): @@ -1086,7 +1085,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return PNTS def _separateWireAtVertexes(self, wire, VV1, VV2): - PathLog.debug("_separateWireAtVertexes()") + Path.Log.debug("_separateWireAtVertexes()") tolerance = self.JOB.GeometryTolerance.Value grps = [[], []] wireIdxs = [[], []] @@ -1129,7 +1128,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): FLGS[e] += v # Efor - # PathLog.debug('_separateWireAtVertexes() FLGS: {}'.format(FLGS)) + # Path.Log.debug('_separateWireAtVertexes() FLGS: {}'.format(FLGS)) PRE = [] POST = [] @@ -1209,12 +1208,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Debugging """ if self.isDebug: - PathLog.debug('grps[0]: {}'.format(grps[0])) - PathLog.debug('grps[1]: {}'.format(grps[1])) - PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) - PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) - PathLog.debug('PRE: {}'.format(PRE)) - PathLog.debug('IDXS: {}'.format(IDXS)) + Path.Log.debug('grps[0]: {}'.format(grps[0])) + Path.Log.debug('grps[1]: {}'.format(grps[1])) + Path.Log.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) + Path.Log.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) + Path.Log.debug('PRE: {}'.format(PRE)) + Path.Log.debug('IDXS: {}'.format(IDXS)) """ return (wireIdxs[0], wireIdxs[1]) @@ -1222,7 +1221,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): """_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... Creates cross-section objectc from shape. Translates cross-section to zHghtTrgt if available. Makes face shape from cross-section object. Returns face shape at zHghtTrgt.""" - PathLog.debug("_makeCrossSection()") + Path.Log.debug("_makeCrossSection()") # Create cross-section of shape and translate wires = [] slcs = shape.slice(FreeCAD.Vector(0, 0, 1), sliceZ) @@ -1237,7 +1236,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return False def _makeExtendedBoundBox(self, wBB, bbBfr, zDep): - PathLog.debug("_makeExtendedBoundBox()") + Path.Log.debug("_makeExtendedBoundBox()") p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) @@ -1251,7 +1250,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return Part.Face(Part.Wire([L1, L2, L3, L4])) def _makeIntersectionTags(self, useWire, numOrigEdges, fdv): - PathLog.debug("_makeIntersectionTags()") + Path.Log.debug("_makeIntersectionTags()") # Create circular probe tags around perimiter of wire extTags = [] intTags = [] @@ -1309,7 +1308,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return (begInt, begExt, iTAG, eTAG) def _makeOffsetCircleTag(self, p1, p2, cutterRad, depth, lbl, reverse=False): - # PathLog.debug('_makeOffsetCircleTag()') + # Path.Log.debug('_makeOffsetCircleTag()') pb = FreeCAD.Vector(p1.x, p1.y, 0.0) pe = FreeCAD.Vector(p2.x, p2.y, 0.0) @@ -1344,7 +1343,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return (intTag, extTag) def _makeStop(self, sType, pA, pB, lbl): - # PathLog.debug('_makeStop()') + # Path.Log.debug('_makeStop()') ofstRad = self.ofstRadius extra = self.radius / 5.0 lng = 0.05 diff --git a/src/Mod/Path/PathScripts/PathProperty.py b/src/Mod/Path/PathScripts/PathProperty.py index eea7c61745..e790a0d4df 100644 --- a/src/Mod/Path/PathScripts/PathProperty.py +++ b/src/Mod/Path/PathScripts/PathProperty.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -import PathScripts.PathLog as PathLog +import Path __title__ = "Property type abstraction for editing purposes" __author__ = "sliptonic (Brad Collette)" @@ -28,8 +28,8 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Prototype objects to allow extraction of setup sheet values and editing." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) class Property(object): diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py index 7238ec099b..e12122ae7d 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBag.py +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -22,8 +22,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD +import Path import re -import PathScripts.PathLog as PathLog __title__ = "Generic property container to store some values." __author__ = "sliptonic (Brad Collette)" @@ -31,10 +31,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "A generic container for typed properties in arbitrary categories." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index 07a1b31e68..bf7be075e7 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -23,8 +23,8 @@ from PySide import QtCore, QtGui import FreeCAD import FreeCADGui +import Path import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathLog as PathLog import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathUtil as PathUtil @@ -37,10 +37,10 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Task panel editor for a PropertyBag" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -50,7 +50,7 @@ class ViewProvider(object): It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): - PathLog.track(name) + Path.Log.track(name) vobj.Proxy = self self.icon = name # mode = 2 @@ -58,7 +58,7 @@ class ViewProvider(object): self.vobj = None def attach(self, vobj): - PathLog.track() + Path.Log.track() self.vobj = vobj self.obj = vobj.Object @@ -75,7 +75,7 @@ class ViewProvider(object): return "Default" def setEdit(self, vobj, mode=0): - PathLog.track() + Path.Log.track() taskPanel = TaskPanel(vobj) FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(taskPanel) @@ -99,7 +99,7 @@ class Delegate(QtGui.QStyledItemDelegate): RoleEditor = QtCore.Qt.UserRole + 3 # def paint(self, painter, option, index): - # #PathLog.track(index.column(), type(option)) + # #Path.Log.track(index.column(), type(option)) def createEditor(self, parent, option, index): editor = PathPropertyEditor.Editor( @@ -109,11 +109,11 @@ class Delegate(QtGui.QStyledItemDelegate): return editor.widget(parent) def setEditorData(self, widget, index): - PathLog.track(index.row(), index.column()) + Path.Log.track(index.row(), index.column()) index.data(self.RoleEditor).setEditorData(widget) def setModelData(self, widget, model, index): - PathLog.track(index.row(), index.column()) + Path.Log.track(index.row(), index.column()) editor = index.data(self.RoleEditor) editor.setModelData(widget) index.model().setData(index, editor.displayString(), QtCore.Qt.DisplayRole) @@ -273,7 +273,7 @@ class TaskPanel(object): # self.model.item(i, self.ColumnType).setEditable(False) def setupUi(self): - PathLog.track() + Path.Log.track() self.delegate = Delegate(self.form) self.model = QtGui.QStandardItemModel( @@ -308,7 +308,7 @@ class TaskPanel(object): FreeCAD.ActiveDocument.recompute() def propertySelected(self, selection): - PathLog.track() + Path.Log.track() if selection: self.form.modify.setEnabled(True) self.form.remove.setEnabled(True) @@ -327,7 +327,7 @@ class TaskPanel(object): return (propname, info) def propertyAdd(self): - PathLog.track() + Path.Log.track() more = False grp = None typ = None @@ -358,7 +358,7 @@ class TaskPanel(object): break def propertyModifyIndex(self, index): - PathLog.track(index.row(), index.column()) + Path.Log.track(index.row(), index.column()) row = index.row() obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject) @@ -387,7 +387,7 @@ class TaskPanel(object): ) def propertyModify(self): - PathLog.track() + Path.Log.track() rows = [] for index in self.form.table.selectionModel().selectedIndexes(): row = index.row() @@ -398,7 +398,7 @@ class TaskPanel(object): self.propertyModifyIndex(index) def propertyRemove(self): - PathLog.track() + Path.Log.track() # first find all rows which need to be removed rows = [] for index in self.form.table.selectionModel().selectedIndexes(): diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/PathScripts/PathPropertyEditor.py index 8a88862bd5..6e66ee2ac6 100644 --- a/src/Mod/Path/PathScripts/PathPropertyEditor.py +++ b/src/Mod/Path/PathScripts/PathPropertyEditor.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype from PySide import QtCore, QtGui @@ -33,10 +33,10 @@ __doc__ = "Task panel editor for Properties" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class _PropertyEditor(object): diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index b7478ea422..fd980f86e2 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -32,8 +32,8 @@ from __future__ import print_function from PySide import QtCore, QtGui import FreeCAD import FreeCADGui +import Path import PathScripts -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import PathScripts.PathPreferences as PathPreferences from collections import Counter @@ -47,8 +47,8 @@ translate = FreeCAD.Qt.translate LOG_MODULE = "PathSanity" -# PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) -# PathLog.trackModule('PathSanity') +# Path.Log.setLevel(Path.Log.Level.INFO, LOG_MODULE) +# Path.Log.trackModule('PathSanity') class CommandPathSanity: @@ -87,14 +87,14 @@ class CommandPathSanity: M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) filepath = filepath.replace("%M", M) - PathLog.debug("filepath: {}".format(filepath)) + Path.Log.debug("filepath: {}".format(filepath)) # starting at the derived filename, iterate up until we have a valid # directory to write to while not os.path.isdir(filepath): filepath = os.path.dirname(filepath) - PathLog.debug("filepath: {}".format(filepath)) + Path.Log.debug("filepath: {}".format(filepath)) return filepath + os.sep def GetResources(self): diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 8e89cf95a2..9bc21f6a9b 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -25,13 +25,13 @@ import FreeCAD import FreeCADGui -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathPreferences as PathPreferences import PathScripts.drillableLib as drillableLib import math -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) class PathBaseGate(object): @@ -123,7 +123,7 @@ class CHAMFERGate(PathBaseGate): class DRILLGate(PathBaseGate): def allow(self, doc, obj, sub): - PathLog.debug("obj: {} sub: {}".format(obj, sub)) + Path.Log.debug("obj: {} sub: {}".format(obj, sub)) if not hasattr(obj, "Shape"): return False shape = obj.Shape @@ -233,7 +233,7 @@ class PROBEGate: class TURNGate(PathBaseGate): def allow(self, doc, obj, sub): - PathLog.debug("obj: {} sub: {}".format(obj, sub)) + Path.Log.debug("obj: {} sub: {}".format(obj, sub)) if hasattr(obj, "Shape") and sub: shape = obj.Shape subobj = shape.getElement(sub) diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/PathScripts/PathSetupSheet.py index 027eea73da..656ebac45c 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/PathScripts/PathSetupSheet.py @@ -21,8 +21,8 @@ # *************************************************************************** import FreeCAD +import Path import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype import PathScripts.PathUtil as PathUtil from PySide.QtCore import QT_TRANSLATE_NOOP @@ -36,10 +36,10 @@ _RegisteredOps: dict = {} if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class Template: @@ -73,20 +73,20 @@ class Template: def _traverseTemplateAttributes(attrs, codec): - PathLog.debug(attrs) + Path.Log.debug(attrs) coded = {} for key, value in PathUtil.keyValueIter(attrs): if type(value) == dict: - PathLog.debug("%s is a dict" % key) + Path.Log.debug("%s is a dict" % key) coded[key] = _traverseTemplateAttributes(value, codec) elif type(value) == list: - PathLog.debug("%s is a list" % key) + Path.Log.debug("%s is a list" % key) coded[key] = [_traverseTemplateAttributes(attr, codec) for attr in value] elif PathUtil.isString(value): - PathLog.debug("%s is a string" % key) + Path.Log.debug("%s is a string" % key) coded[key] = codec(value) else: - PathLog.debug("%s is %s" % (key, type(value))) + Path.Log.debug("%s is %s" % (key, type(value))) coded[key] = value return coded @@ -394,7 +394,7 @@ class SetupSheet: return list(sorted(ops)) def setOperationProperties(self, obj, opName): - PathLog.track(obj.Label, opName) + Path.Log.track(obj.Label, opName) try: op = _RegisteredOps[opName] for prop in op.properties(): @@ -402,7 +402,7 @@ class SetupSheet: if hasattr(self.obj, propName): setattr(obj, prop, getattr(self.obj, propName)) except Exception: - PathLog.info("SetupSheet has no support for {}".format(opName)) + Path.Log.info("SetupSheet has no support for {}".format(opName)) def onDocumentRestored(self, obj): diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/PathScripts/PathSetupSheetGui.py index 7bc654af45..949dc3fc71 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetGui.py @@ -22,10 +22,10 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathLog as PathLog import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathSetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui import PathScripts.PathUtil as PathUtil @@ -41,10 +41,10 @@ __doc__ = "Task panel editor for a SetupSheet" LOGLEVEL = False if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ViewProvider: @@ -52,7 +52,7 @@ class ViewProvider: It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): - PathLog.track(name) + Path.Log.track(name) vobj.Proxy = self self.icon = name # mode = 2 @@ -60,7 +60,7 @@ class ViewProvider: self.vobj = None def attach(self, vobj): - PathLog.track() + Path.Log.track() self.vobj = vobj self.obj = vobj.Object @@ -77,7 +77,7 @@ class ViewProvider: return "Default" def setEdit(self, vobj, mode=0): - PathLog.track() + Path.Log.track() taskPanel = TaskPanel(vobj) FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(taskPanel) @@ -106,11 +106,11 @@ class Delegate(QtGui.QStyledItemDelegate): return index.data(self.EditorRole).widget(parent) def setEditorData(self, widget, index): - PathLog.track(index.row(), index.column()) + Path.Log.track(index.row(), index.column()) index.data(self.EditorRole).setEditorData(widget) def setModelData(self, widget, model, index): - PathLog.track(index.row(), index.column()) + Path.Log.track(index.row(), index.column()) editor = index.data(self.EditorRole) editor.setModelData(widget) index.model().setData(index, editor.prop.displayString(), QtCore.Qt.DisplayRole) @@ -150,7 +150,7 @@ class OpTaskPanel: self.model.item(topLeft.row(), 2).setEnabled(isset) def setupUi(self): - PathLog.track() + Path.Log.track() self.delegate = Delegate(self.form) self.model = QtGui.QStandardItemModel(len(self.props), 3, self.form) @@ -242,7 +242,7 @@ class OpsDefaultEditor: def accept(self): if any([op.accept() for op in self.ops]): - PathLog.track() + Path.Log.track() def getFields(self): pass @@ -259,7 +259,7 @@ class OpsDefaultEditor: self.currentOp.form.show() def updateModel(self, recomp=True): - PathLog.track() + Path.Log.track() self.getFields() self.updateUI() if recomp: @@ -324,7 +324,7 @@ class GlobalEditor(object): combo.blockSignals(False) def updateUI(self): - PathLog.track() + Path.Log.track() self.form.setupStartDepthExpr.setText(self.obj.StartDepthExpression) self.form.setupFinalDepthExpr.setText(self.obj.FinalDepthExpression) self.form.setupStepDownExpr.setText(self.obj.StepDownExpression) @@ -337,7 +337,7 @@ class GlobalEditor(object): self.selectInComboBox(self.obj.CoolantMode, self.form.setupCoolantMode) def updateModel(self, recomp=True): - PathLog.track() + Path.Log.track() self.getFields() self.updateUI() if recomp: @@ -369,7 +369,7 @@ class TaskPanel: def __init__(self, vobj): self.vobj = vobj self.obj = vobj.Object - PathLog.track(self.obj.Label) + Path.Log.track(self.obj.Label) self.globalForm = FreeCADGui.PySideUic.loadUi(":/panels/SetupGlobal.ui") self.globalEditor = GlobalEditor(self.obj, self.globalForm) self.opsEditor = OpsDefaultEditor(self.obj, None) diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py index 0070e9aa72..4e3f05c99b 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -import PathScripts.PathLog as PathLog +import Path __title__ = "Setup Sheet for a Job." __author__ = "sliptonic (Brad Collette)" @@ -28,8 +28,8 @@ __url__ = "https://www.freecadweb.org" __doc__ = "Prototype objects to allow extraction of setup sheet values and editing." -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) class Property(object): diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py index eefc4083eb..920b591732 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype from PySide import QtCore, QtGui @@ -35,10 +35,10 @@ __doc__ = "Task panel editor for a SetupSheet" LOGLEVEL = False if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class _PropertyEditor(object): @@ -68,7 +68,7 @@ class _PropertyEnumEditor(_PropertyEditor): """Editor for enumeration values - uses a combo box.""" def widget(self, parent): - PathLog.track(self.prop.name, self.prop.getEnumValues()) + Path.Log.track(self.prop.name, self.prop.getEnumValues()) return QtGui.QComboBox(parent) def setEditorData(self, widget): diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index ba712b6092..583cf5c4b5 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -25,7 +25,6 @@ import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import PathScripts.PathJob as PathJob import PathSimulator @@ -475,7 +474,7 @@ class PathSimulation: h = tool.CuttingEdgeHeight if h <= 0.0: # set default if user fails to avoid freeze h = 1.0 - PathLog.error("SET Tool Length") + Path.Log.error("SET Tool Length") # common to all tools vTR = Vector(xp + yf, yp - xf, zp + h) vTC = Vector(xp, yp, zp + h) diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index fac268d809..28eb47e51d 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -32,7 +32,6 @@ __contributors__ = "" import FreeCAD from PySide import QtCore import Path -import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import PathScripts.PathOp as PathOp import math @@ -51,10 +50,10 @@ translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectSlot(PathOp.ObjectOp): @@ -81,7 +80,7 @@ class ObjectSlot(PathOp.ObjectOp): self.initOpProperties(obj) # Initialize operation-specific properties # For debugging - if PathLog.getLevel(PathLog.thisModule()) != 4: + if Path.Log.getLevel(Path.Log.thisModule()) != 4: obj.setEditorMode("ShowTempObjects", 2) # hide if not hasattr(obj, "DoNotSetDefaultValues"): @@ -89,7 +88,7 @@ class ObjectSlot(PathOp.ObjectOp): def initOpProperties(self, obj, warn=False): """initOpProperties(obj) ... create operation specific properties""" - PathLog.track() + Path.Log.track() self.addNewProps = list() for (prtyp, nm, grp, tt) in self.opPropertyDefinitions(): @@ -251,7 +250,7 @@ class ObjectSlot(PathOp.ObjectOp): 'raw' is list of (translated_text, data_string) tuples 'translated' is list of translated string literals """ - PathLog.track() + Path.Log.track() enums = { "CutPattern": [ @@ -296,11 +295,11 @@ class ObjectSlot(PathOp.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -354,7 +353,7 @@ class ObjectSlot(PathOp.ObjectOp): in the operation. Returns the updated enumerations dictionary. Existing property values must be stored, and then restored after the assignment of updated enumerations.""" - PathLog.debug("updateEnumerations()") + Path.Log.debug("updateEnumerations()") # Save existing values pre_Ref1 = obj.Reference1 pre_Ref2 = obj.Reference2 @@ -410,7 +409,7 @@ class ObjectSlot(PathOp.ObjectOp): self.initOpProperties(obj, warn=True) self.opApplyPropertyDefaults(obj, job, self.addNewProps) - mode = 2 if PathLog.getLevel(PathLog.thisModule()) != 4 else 0 + mode = 2 if Path.Log.getLevel(Path.Log.thisModule()) != 4 else 0 obj.setEditorMode("ShowTempObjects", mode) # Repopulate enumerations in case of changes @@ -454,11 +453,11 @@ class ObjectSlot(PathOp.ObjectOp): if job: if job.Stock: d = PathUtils.guessDepths(job.Stock.Shape, None) - PathLog.debug("job.Stock exists") + Path.Log.debug("job.Stock exists") else: - PathLog.debug("job.Stock NOT exist") + Path.Log.debug("job.Stock NOT exist") else: - PathLog.debug("job NOT exist") + Path.Log.debug("job NOT exist") if d is not None: obj.OpFinalDepth.Value = d.final_depth @@ -467,8 +466,8 @@ class ObjectSlot(PathOp.ObjectOp): obj.OpFinalDepth.Value = -10 obj.OpStartDepth.Value = 10 - PathLog.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) - PathLog.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) + Path.Log.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) + Path.Log.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) def opApplyPropertyLimits(self, obj): """opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.""" @@ -485,12 +484,12 @@ class ObjectSlot(PathOp.ObjectOp): fbb = base.Shape.getElement(sub).BoundBox zmin = min(zmin, fbb.ZMin) except Part.OCCError as e: - PathLog.error(e) + Path.Log.error(e) obj.OpFinalDepth = zmin def opExecute(self, obj): """opExecute(obj) ... process surface operation""" - PathLog.track() + Path.Log.track() self.base = None self.shape1 = None @@ -509,7 +508,7 @@ class ObjectSlot(PathOp.ObjectOp): self.arcRadius = 0.0 self.newRadius = 0.0 self.featureDetails = ["", ""] - self.isDebug = False if PathLog.getLevel(PathLog.thisModule()) != 4 else True + self.isDebug = False if Path.Log.getLevel(Path.Log.thisModule()) != 4 else True self.showDebugObjects = False self.stockZMin = self.job.Stock.Shape.BoundBox.ZMin CMDS = list() @@ -630,14 +629,14 @@ class ObjectSlot(PathOp.ObjectOp): featureCount = len(subsList) if featureCount == 1: - PathLog.debug("Reference 1: {}".format(obj.Reference1)) + Path.Log.debug("Reference 1: {}".format(obj.Reference1)) sub1 = subsList[0] shape_1 = getattr(base.Shape, sub1) self.shape1 = shape_1 pnts = self._processSingle(obj, shape_1, sub1) else: - PathLog.debug("Reference 1: {}".format(obj.Reference1)) - PathLog.debug("Reference 2: {}".format(obj.Reference2)) + Path.Log.debug("Reference 1: {}".format(obj.Reference1)) + Path.Log.debug("Reference 2: {}".format(obj.Reference2)) sub1 = subsList[0] sub2 = subsList[1] shape_1 = getattr(base.Shape, sub1) @@ -662,16 +661,16 @@ class ObjectSlot(PathOp.ObjectOp): def _finishArc(self, obj, pnts, featureCnt): """This method finishes an Arc Slot operation. It returns the gcode for the slot operation.""" - PathLog.debug("arc center: {}".format(self.arcCenter)) + Path.Log.debug("arc center: {}".format(self.arcCenter)) self._addDebugObject( Part.makeLine(self.arcCenter, self.arcMidPnt), "CentToMidPnt" ) - # PathLog.debug('Pre-offset points are:\np1 = {}\np2 = {}'.format(p1, p2)) + # Path.Log.debug('Pre-offset points are:\np1 = {}\np2 = {}'.format(p1, p2)) if obj.ExtendRadius.Value != 0: # verify offset does not force radius < 0 newRadius = self.arcRadius + obj.ExtendRadius.Value - PathLog.debug( + Path.Log.debug( "arc radius: {}; offset radius: {}".format(self.arcRadius, newRadius) ) if newRadius <= 0: @@ -686,11 +685,11 @@ class ObjectSlot(PathOp.ObjectOp): pnts = self._makeOffsetArc(p1, p2, self.arcCenter, newRadius) self.newRadius = newRadius else: - PathLog.debug("arc radius: {}".format(self.arcRadius)) + Path.Log.debug("arc radius: {}".format(self.arcRadius)) self.newRadius = self.arcRadius # Apply path extension for arcs - # PathLog.debug('Pre-extension points are:\np1 = {}\np2 = {}'.format(p1, p2)) + # Path.Log.debug('Pre-extension points are:\np1 = {}\np2 = {}'.format(p1, p2)) if self.isArc == 1: # Complete circle if obj.ExtendPathStart.Value != 0 or obj.ExtendPathEnd.Value != 0: @@ -712,9 +711,9 @@ class ObjectSlot(PathOp.ObjectOp): return False (p1, p2) = pnts - # PathLog.error('Post-offset points are:\np1 = {}\np2 = {}'.format(p1, p2)) + # Path.Log.error('Post-offset points are:\np1 = {}\np2 = {}'.format(p1, p2)) if self.isDebug: - PathLog.debug("Path Points are:\np1 = {}\np2 = {}".format(p1, p2)) + Path.Log.debug("Path Points are:\np1 = {}\np2 = {}".format(p1, p2)) if p1.sub(p2).Length != 0: self._addDebugObject(Part.makeLine(p1, p2), "Path") @@ -727,7 +726,7 @@ class ObjectSlot(PathOp.ObjectOp): msg += translate("Path_Slot", "operation collides with model.") FreeCAD.Console.PrintError(msg + "\n") - # PathLog.warning('Unable to create G-code. _makeArcGCode() is incomplete.') + # Path.Log.warning('Unable to create G-code. _makeArcGCode() is incomplete.') cmds = self._makeArcGCode(obj, p1, p2) return cmds @@ -790,7 +789,7 @@ class ObjectSlot(PathOp.ObjectOp): ) if self.isDebug: - PathLog.debug("G-code arc command is: {}".format(PATHS[path_index][2])) + Path.Log.debug("G-code arc command is: {}".format(PATHS[path_index][2])) return CMDS @@ -808,7 +807,7 @@ class ObjectSlot(PathOp.ObjectOp): pnts = self._processSingleVertFace(obj, BE) perpZero = False elif self.shapeType1 == "Edge" and self.shapeType2 == "Edge": - PathLog.debug("_finishLine() Perp, featureCnt == 2") + Path.Log.debug("_finishLine() Perp, featureCnt == 2") if perpZero: (p1, p2) = pnts initPerpDist = p1.sub(p2).Length @@ -825,7 +824,7 @@ class ObjectSlot(PathOp.ObjectOp): if self.featureDetails[0] == "arc" and self.featureDetails[1] == "arc": perpZero = False elif self._isParallel(self.dYdX1, self.dYdX2): - PathLog.debug("_finishLine() StE, featureCnt == 2 // edges") + Path.Log.debug("_finishLine() StE, featureCnt == 2 // edges") (p1, p2) = pnts edg1_len = self.shape1.Length edg2_len = self.shape2.Length @@ -862,7 +861,7 @@ class ObjectSlot(PathOp.ObjectOp): (p1, p2) = pnts if self.isDebug: - PathLog.debug("Path Points are:\np1 = {}\np2 = {}".format(p1, p2)) + Path.Log.debug("Path Points are:\np1 = {}\np2 = {}".format(p1, p2)) if p1.sub(p2).Length != 0: self._addDebugObject(Part.makeLine(p1, p2), "Path") @@ -946,7 +945,7 @@ class ObjectSlot(PathOp.ObjectOp): if cat1 == "Face": pnts = False norm = shape_1.normalAt(0.0, 0.0) - PathLog.debug("{}.normalAt(): {}".format(sub1, norm)) + Path.Log.debug("{}.normalAt(): {}".format(sub1, norm)) if PathGeom.isRoughly(shape_1.BoundBox.ZMax, shape_1.BoundBox.ZMin): # Horizontal face @@ -978,7 +977,7 @@ class ObjectSlot(PathOp.ObjectOp): done = True elif cat1 == "Edge": - PathLog.debug("Single edge") + Path.Log.debug("Single edge") pnts = self._processSingleEdge(obj, shape_1) if pnts: (p1, p2) = pnts @@ -998,7 +997,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleHorizFace(self, obj, shape): """Determine slot path endpoints from a single horizontally oriented face.""" - PathLog.debug("_processSingleHorizFace()") + Path.Log.debug("_processSingleHorizFace()") lineTypes = ["Part::GeomLine"] def getRadians(self, E): @@ -1056,19 +1055,19 @@ class ObjectSlot(PathOp.ObjectOp): flag += 1 if debug: msg = "Erroneous Curve.TypeId: {}".format(debug) - PathLog.debug(msg) + Path.Log.debug(msg) pairCnt = len(parallel_edge_pairs) if pairCnt > 1: parallel_edge_pairs.sort(key=lambda tup: tup[0].Length, reverse=True) if self.isDebug: - PathLog.debug(" -pairCnt: {}".format(pairCnt)) + Path.Log.debug(" -pairCnt: {}".format(pairCnt)) for (a, b) in parallel_edge_pairs: - PathLog.debug( + Path.Log.debug( " -pair: {}, {}".format(round(a.Length, 4), round(b.Length, 4)) ) - PathLog.debug(" -parallel_edge_flags: {}".format(parallel_edge_flags)) + Path.Log.debug(" -parallel_edge_flags: {}".format(parallel_edge_flags)) if pairCnt == 0: msg = translate("Path_Slot", "No parallel edges identified.") @@ -1104,7 +1103,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleComplexFace(self, obj, shape): """Determine slot path endpoints from a single complex face.""" - PathLog.debug("_processSingleComplexFace()") + Path.Log.debug("_processSingleComplexFace()") pnts = list() def zVal(p): @@ -1119,7 +1118,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleVertFace(self, obj, shape): """Determine slot path endpoints from a single vertically oriented face with no single bottom edge.""" - PathLog.debug("_processSingleVertFace()") + Path.Log.debug("_processSingleVertFace()") eCnt = len(shape.Edges) V0 = shape.Edges[0].Vertexes[0] V1 = shape.Edges[eCnt - 1].Vertexes[1] @@ -1149,7 +1148,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processSingleEdge(self, obj, edge): """Determine slot path endpoints from a single horizontally oriented edge.""" - PathLog.debug("_processSingleEdge()") + Path.Log.debug("_processSingleEdge()") tolrnc = 0.0000001 lineTypes = ["Part::GeomLine"] curveTypes = ["Part::GeomCircle"] @@ -1180,7 +1179,7 @@ class ObjectSlot(PathOp.ObjectOp): # Circle radius (not used) # r = vP1P2.Length * vP2P3.Length * vP3P1.Length / 2 / l if round(L, 8) == 0.0: - PathLog.error("The three points are colinear, arc is a straight.") + Path.Log.error("The three points are colinear, arc is a straight.") return False # Sphere center. @@ -1205,7 +1204,7 @@ class ObjectSlot(PathOp.ObjectOp): elif edge.Curve.TypeId in curveTypes: if len(edge.Vertexes) == 1: # Circle edge - PathLog.debug("Arc with single vertex.") + Path.Log.debug("Arc with single vertex.") if oversizedTool(edge.BoundBox.XLength): return False @@ -1222,7 +1221,7 @@ class ObjectSlot(PathOp.ObjectOp): self.arcRadius = edge.BoundBox.XLength / 2.0 else: # Arc edge - PathLog.debug("Arc with multiple vertices.") + Path.Log.debug("Arc with multiple vertices.") self.isArc = 2 midPnt = edge.valueAt(edge.getParameterByLength(edge.Length / 2.0)) if not isHorizontal(V1.Z, V2.Z, midPnt.z): @@ -1253,7 +1252,7 @@ class ObjectSlot(PathOp.ObjectOp): def _processDouble(self, obj, shape_1, sub1, shape_2, sub2): """This is the control method for slots based on a two Base Geometry features.""" - PathLog.debug("_processDouble()") + Path.Log.debug("_processDouble()") p1 = None p2 = None @@ -1283,7 +1282,7 @@ class ObjectSlot(PathOp.ObjectOp): # Parallel check for twin face, and face-edge cases if dYdX1 and dYdX2: - PathLog.debug("dYdX1, dYdX2: {}, {}".format(dYdX1, dYdX2)) + Path.Log.debug("dYdX1, dYdX2: {}, {}".format(dYdX1, dYdX2)) if not self._isParallel(dYdX1, dYdX2): if self.shapeType1 != "Edge" or self.shapeType2 != "Edge": msg = translate("Path_Slot", "Selected geometry not parallel.") @@ -1389,7 +1388,7 @@ class ObjectSlot(PathOp.ObjectOp): p = None dYdX = None cat = sub[:4] - PathLog.debug("sub-feature is {}".format(cat)) + Path.Log.debug("sub-feature is {}".format(cat)) Ref = getattr(obj, "Reference" + str(pNum)) if cat == "Face": BE = self._getBottomEdge(shape) @@ -1488,7 +1487,7 @@ class ObjectSlot(PathOp.ObjectOp): 2 * ExtRadians ) # positive Ext lengthens slot so decrease start point angle - # PathLog.debug('begExt angles are: {}, {}'.format(beginRadians, math.degrees(beginRadians))) + # Path.Log.debug('begExt angles are: {}, {}'.format(beginRadians, math.degrees(beginRadians))) chord.rotate(origin, FreeCAD.Vector(0, 0, 1), math.degrees(beginRadians)) chord.translate(self.arcCenter) @@ -1509,7 +1508,7 @@ class ObjectSlot(PathOp.ObjectOp): 2 * ExtRadians ) # negative Ext shortens slot so decrease end point angle - # PathLog.debug('endExt angles are: {}, {}'.format(endRadians, math.degrees(endRadians))) + # Path.Log.debug('endExt angles are: {}, {}'.format(endRadians, math.degrees(endRadians))) chord.rotate(origin, FreeCAD.Vector(0, 0, 1), math.degrees(endRadians)) chord.translate(self.arcCenter) @@ -1773,7 +1772,7 @@ class ObjectSlot(PathOp.ObjectOp): def _makeReference1Enumerations(self, sub, single=False): """Customize Reference1 enumerations based on feature type.""" - PathLog.debug("_makeReference1Enumerations()") + Path.Log.debug("_makeReference1Enumerations()") cat = sub[:4] if single: if cat == "Face": @@ -1789,7 +1788,7 @@ class ObjectSlot(PathOp.ObjectOp): def _makeReference2Enumerations(self, sub): """Customize Reference2 enumerations based on feature type.""" - PathLog.debug("_makeReference2Enumerations()") + Path.Log.debug("_makeReference2Enumerations()") cat = sub[:4] if cat == "Vert": return ["Vertex"] @@ -1859,7 +1858,7 @@ class ObjectSlot(PathOp.ObjectOp): if cmn.Volume > 0.000001: return True except Exception: - PathLog.debug("Failed to complete path collision check.") + Path.Log.debug("Failed to complete path collision check.") return False @@ -1917,7 +1916,7 @@ class ObjectSlot(PathOp.ObjectOp): # verify offset does not force radius < 0 newRadius = arcRadius - rad - # PathLog.debug('arcRadius, newRadius: {}, {}'.format(arcRadius, newRadius)) + # Path.Log.debug('arcRadius, newRadius: {}, {}'.format(arcRadius, newRadius)) if newRadius <= 0: msg = translate( "Path_Slot", "Current offset value produces negative radius." @@ -1931,7 +1930,7 @@ class ObjectSlot(PathOp.ObjectOp): # Arc 2 - outside # verify offset does not force radius < 0 newRadius = arcRadius + rad - # PathLog.debug('arcRadius, newRadius: {}, {}'.format(arcRadius, newRadius)) + # Path.Log.debug('arcRadius, newRadius: {}, {}'.format(arcRadius, newRadius)) if newRadius <= 0: msg = translate( "Path_Slot", "Current offset value produces negative radius." @@ -1975,7 +1974,7 @@ class ObjectSlot(PathOp.ObjectOp): # print("volume=", cmn.Volume) return True except Exception: - PathLog.debug("Failed to complete path collision check.") + Path.Log.debug("Failed to complete path collision check.") return False diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py index 2338061477..9dfa41ecef 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -23,7 +23,7 @@ """Used to create material stock around a machined part - for visualization""" import FreeCAD -import PathScripts.PathLog as PathLog +import Path import math from PySide.QtCore import QT_TRANSLATE_NOOP from PySide import QtCore @@ -36,10 +36,10 @@ Part = LazyLoader("Part", globals(), "Part") translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class StockType: @@ -69,7 +69,7 @@ class StockType: def shapeBoundBox(obj): - PathLog.track(type(obj)) + Path.Log.track(type(obj)) if list == type(obj) and obj: bb = FreeCAD.BoundBox() for o in obj: @@ -86,7 +86,7 @@ def shapeBoundBox(obj): bb = bb.united(b) return bb if obj: - PathLog.error( + Path.Log.error( translate("PathStock", "Invalid base object %s - no shape found") % obj.Name ) return None @@ -185,7 +185,7 @@ class StockFromBase(Stock): FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation() ) else: - PathLog.track(obj.Label, base.Label) + Path.Log.track(obj.Label, base.Label) obj.Proxy = self # debugging aids @@ -206,7 +206,7 @@ class StockFromBase(Stock): if obj.Base and hasattr(obj.Base, "Group") else None ) - PathLog.track(obj.Label, bb) + Path.Log.track(obj.Label, bb) # Sometimes, when the Base changes it's temporarily not assigned when # Stock.execute is triggered - it'll be set correctly the next time around. @@ -327,7 +327,7 @@ class StockCreateCylinder(Stock): def SetupStockObject(obj, stockType): - PathLog.track(obj.Label, stockType) + Path.Log.track(obj.Label, stockType) if FreeCAD.GuiUp and obj.ViewObject: obj.addProperty( "App::PropertyString", @@ -362,7 +362,7 @@ def _getBase(job): def CreateFromBase(job, neg=None, pos=None, placement=None): - PathLog.track(job.Label, neg, pos, placement) + Path.Log.track(job.Label, neg, pos, placement) base = _getBase(job) obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock") obj.Proxy = StockFromBase(obj, base) @@ -512,7 +512,7 @@ def CreateFromTemplate(job, template): or rotZ is not None or rotW is not None ): - PathLog.warning( + Path.Log.warning( "Corrupted or incomplete placement information in template - ignoring" ) @@ -551,30 +551,30 @@ def CreateFromTemplate(job, template): or zneg is not None or zpos is not None ): - PathLog.error( + Path.Log.error( "Corrupted or incomplete specification for creating stock from base - ignoring extent" ) return CreateFromBase(job, neg, pos, placement) if stockType == StockType.CreateBox: - PathLog.track(" create box") + Path.Log.track(" create box") length = template.get("length") width = template.get("width") height = template.get("height") extent = None if length is not None and width is not None and height is not None: - PathLog.track(" have extent") + Path.Log.track(" have extent") extent = FreeCAD.Vector( FreeCAD.Units.Quantity(length).Value, FreeCAD.Units.Quantity(width).Value, FreeCAD.Units.Quantity(height).Value, ) elif length is not None or width is not None or height is not None: - PathLog.error( + Path.Log.error( "Corrupted or incomplete size for creating a stock box - ignoring size" ) else: - PathLog.track( + Path.Log.track( " take placement (%s) and extent (%s) from model" % (placement, extent) ) @@ -588,18 +588,18 @@ def CreateFromTemplate(job, template): elif radius is not None or height is not None: radius = None height = None - PathLog.error( + Path.Log.error( "Corrupted or incomplete size for creating a stock cylinder - ignoring size" ) return CreateCylinder(job, radius, height, placement) - PathLog.error( + Path.Log.error( translate("PathStock", "Unsupported stock type named {}").format( stockType ) ) else: - PathLog.error( + Path.Log.error( translate( "PathStock", "Unsupported PathStock template version {}" ).format(template.get("version")) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 33cf4d92f7..da67874b00 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -47,7 +47,6 @@ except ImportError: from PySide.QtCore import QT_TRANSLATE_NOOP import Path -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathSurfaceSupport as PathSurfaceSupport import PathScripts.PathUtils as PathUtils @@ -64,10 +63,10 @@ if FreeCAD.GuiUp: if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectSurface(PathOp.ObjectOp): @@ -92,7 +91,7 @@ class ObjectSurface(PathOp.ObjectOp): self.initOpProperties(obj) # Initialize operation-specific properties # For debugging - if PathLog.getLevel(PathLog.thisModule()) != 4: + if Path.Log.getLevel(Path.Log.thisModule()) != 4: obj.setEditorMode("ShowTempObjects", 2) # hide if not hasattr(obj, "DoNotSetDefaultValues"): @@ -603,7 +602,7 @@ class ObjectSurface(PathOp.ObjectOp): self.initOpProperties(obj, warn=True) self.opApplyPropertyDefaults(obj, job, self.addNewProps) - mode = 2 if PathLog.getLevel(PathLog.thisModule()) != 4 else 0 + mode = 2 if Path.Log.getLevel(Path.Log.thisModule()) != 4 else 0 obj.setEditorMode("ShowTempObjects", mode) # Repopulate enumerations in case of changes @@ -645,11 +644,11 @@ class ObjectSurface(PathOp.ObjectOp): if job: if job.Stock: d = PathUtils.guessDepths(job.Stock.Shape, None) - PathLog.debug("job.Stock exists") + Path.Log.debug("job.Stock exists") else: - PathLog.debug("job.Stock NOT exist") + Path.Log.debug("job.Stock NOT exist") else: - PathLog.debug("job NOT exist") + Path.Log.debug("job NOT exist") if d is not None: obj.OpFinalDepth.Value = d.final_depth @@ -658,8 +657,8 @@ class ObjectSurface(PathOp.ObjectOp): obj.OpFinalDepth.Value = -10 obj.OpStartDepth.Value = 10 - PathLog.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) - PathLog.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) + Path.Log.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) + Path.Log.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) def opApplyPropertyLimits(self, obj): """opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.""" @@ -684,20 +683,20 @@ class ObjectSurface(PathOp.ObjectOp): # Limit sample interval if obj.SampleInterval.Value < 0.0001: obj.SampleInterval.Value = 0.0001 - PathLog.error("Sample interval limits are 0.001 to 25.4 millimeters.") + Path.Log.error("Sample interval limits are 0.001 to 25.4 millimeters.") if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 - PathLog.error("Sample interval limits are 0.001 to 25.4 millimeters.") + Path.Log.error("Sample interval limits are 0.001 to 25.4 millimeters.") # Limit cut pattern angle if obj.CutPatternAngle < -360.0: obj.CutPatternAngle = 0.0 - PathLog.error("Cut pattern angle limits are +-360 degrees.") + Path.Log.error("Cut pattern angle limits are +-360 degrees.") if obj.CutPatternAngle >= 360.0: obj.CutPatternAngle = 0.0 - PathLog.error("Cut pattern angle limits are +- 360 degrees.") + Path.Log.error("Cut pattern angle limits are +- 360 degrees.") # Limit StepOver to natural number percentage if obj.StepOver > 100.0: @@ -708,11 +707,11 @@ class ObjectSurface(PathOp.ObjectOp): # Limit AvoidLastX_Faces to zero and positive values if obj.AvoidLastX_Faces < 0: obj.AvoidLastX_Faces = 0 - PathLog.error("AvoidLastX_Faces: Only zero or positive values permitted.") + Path.Log.error("AvoidLastX_Faces: Only zero or positive values permitted.") if obj.AvoidLastX_Faces > 100: obj.AvoidLastX_Faces = 100 - PathLog.error("AvoidLastX_Faces: Avoid last X faces count limited to 100.") + Path.Log.error("AvoidLastX_Faces: Avoid last X faces count limited to 100.") def opUpdateDepths(self, obj): if hasattr(obj, "Base") and obj.Base: @@ -725,7 +724,7 @@ class ObjectSurface(PathOp.ObjectOp): fbb = base.Shape.getElement(sub).BoundBox zmin = min(zmin, fbb.ZMin) except Part.OCCError as e: - PathLog.error(e) + Path.Log.error(e) obj.OpFinalDepth = zmin elif self.job: if hasattr(obj, "BoundBox"): @@ -741,7 +740,7 @@ class ObjectSurface(PathOp.ObjectOp): def opExecute(self, obj): """opExecute(obj) ... process surface operation""" - PathLog.track() + Path.Log.track() self.modelSTLs = [] self.safeSTLs = [] @@ -771,7 +770,7 @@ class ObjectSurface(PathOp.ObjectOp): self.showDebugObjects = False # Set to true if you want a visual DocObjects created for some path construction objects self.showDebugObjects = obj.ShowTempObjects deleteTempsFlag = True # Set to False for debugging - if PathLog.getLevel(PathLog.thisModule()) == 4: + if Path.Log.getLevel(Path.Log.thisModule()) == 4: deleteTempsFlag = False else: self.showDebugObjects = False @@ -783,7 +782,7 @@ class ObjectSurface(PathOp.ObjectOp): JOB = PathUtils.findParentJob(obj) self.JOB = JOB if JOB is None: - PathLog.error(translate("PathSurface", "No JOB")) + Path.Log.error(translate("PathSurface", "No JOB")) return self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin @@ -803,7 +802,7 @@ class ObjectSurface(PathOp.ObjectOp): oclTool = PathSurfaceSupport.OCL_Tool(ocl, obj) self.cutter = oclTool.getOclTool() if not self.cutter: - PathLog.error( + Path.Log.error( translate( "PathSurface", "Canceling 3D Surface operation. Error creating OCL cutter.", @@ -933,12 +932,12 @@ class ObjectSurface(PathOp.ObjectOp): self.profileShapes = PSF.profileShapes for idx, model in enumerate(JOB.Model.Group): - PathLog.debug(idx) + Path.Log.debug(idx) # Create OCL.stl model objects PathSurfaceSupport._prepareModelSTLs(self, JOB, obj, idx, ocl) if FACES[idx]: - PathLog.debug( + Path.Log.debug( "Working on Model.Group[{}]: {}".format(idx, model.Label) ) if idx > 0: @@ -963,12 +962,12 @@ class ObjectSurface(PathOp.ObjectOp): self._processCutAreas(JOB, obj, idx, FACES[idx], VOIDS[idx]) ) else: - PathLog.debug("No data for model base: {}".format(model.Label)) + Path.Log.debug("No data for model base: {}".format(model.Label)) # Save gcode produced self.commandlist.extend(CMDS) else: - PathLog.error("Failed to pre-process model and/or selected face(s).") + Path.Log.error("Failed to pre-process model and/or selected face(s).") # ###### CLOSING COMMANDS FOR OPERATION ###### @@ -1055,7 +1054,7 @@ class ObjectSurface(PathOp.ObjectOp): """_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. It then calls the correct scan method depending on the ScanType property.""" - PathLog.debug("_processCutAreas()") + Path.Log.debug("_processCutAreas()") final = [] @@ -1108,7 +1107,7 @@ class ObjectSurface(PathOp.ObjectOp): It makes the necessary facial geometries for the actual cut area. It calls the correct Single or Multi-pass method as needed. It returns the gcode for the operation.""" - PathLog.debug("_processPlanarOp()") + Path.Log.debug("_processPlanarOp()") final = [] SCANDATA = [] @@ -1144,14 +1143,14 @@ class ObjectSurface(PathOp.ObjectOp): prflShp = self.profileShapes[mdlIdx][fsi] if prflShp is False: msg = translate("PathSurface", "No profile geometry shape returned.") - PathLog.error(msg) + Path.Log.error(msg) return [] self.showDebugObject(prflShp, "NewProfileShape") # get offset path geometry and perform OCL scan with that geometry pathOffsetGeom = self._offsetFacesToPointData(obj, prflShp) if pathOffsetGeom is False: msg = translate("PathSurface", "No profile path geometry returned.") - PathLog.error(msg) + Path.Log.error(msg) return [] profScan = [self._planarPerformOclScan(obj, pdc, pathOffsetGeom, True)] @@ -1166,7 +1165,7 @@ class ObjectSurface(PathOp.ObjectOp): pathGeom = PGG.generatePathGeometry() if pathGeom is False: msg = translate("PathSurface", "No clearing shape returned.") - PathLog.error(msg) + Path.Log.error(msg) return [] if obj.CutPattern == "Offset": useGeom = self._offsetFacesToPointData(obj, pathGeom, profile=False) @@ -1174,7 +1173,7 @@ class ObjectSurface(PathOp.ObjectOp): msg = translate( "PathSurface", "No clearing path geometry returned." ) - PathLog.error(msg) + Path.Log.error(msg) return [] geoScan = [self._planarPerformOclScan(obj, pdc, useGeom, True)] else: @@ -1194,7 +1193,7 @@ class ObjectSurface(PathOp.ObjectOp): if len(SCANDATA) == 0: msg = translate("PathSurface", "No scan data to convert to Gcode.") - PathLog.error(msg) + Path.Log.error(msg) return [] # Apply depth offset @@ -1230,7 +1229,7 @@ class ObjectSurface(PathOp.ObjectOp): return final def _offsetFacesToPointData(self, obj, subShp, profile=True): - PathLog.debug("_offsetFacesToPointData()") + Path.Log.debug("_offsetFacesToPointData()") offsetLists = [] dist = obj.SampleInterval.Value / 5.0 @@ -1263,7 +1262,7 @@ class ObjectSurface(PathOp.ObjectOp): """_planarPerformOclScan(obj, pdc, pathGeom, offsetPoints=False)... Switching function for calling the appropriate path-geometry to OCL points conversion function for the various cut patterns.""" - PathLog.debug("_planarPerformOclScan()") + Path.Log.debug("_planarPerformOclScan()") SCANS = [] if offsetPoints or obj.CutPattern == "Offset": @@ -1375,7 +1374,7 @@ class ObjectSurface(PathOp.ObjectOp): # Main planar scan functions def _planarDropCutSingle(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): - PathLog.debug("_planarDropCutSingle()") + Path.Log.debug("_planarDropCutSingle()") GCODE = [Path.Command("N (Beginning of Single-pass layer.)", {})] tolrnc = JOB.GeometryTolerance.Value @@ -1513,7 +1512,7 @@ class ObjectSurface(PathOp.ObjectOp): # lastPrvStpLast = prvStpLast prvStpLast = None lyrDep = depthparams[lyr] - PathLog.debug("Multi-pass lyrDep: {}".format(round(lyrDep, 4))) + Path.Log.debug("Multi-pass lyrDep: {}".format(round(lyrDep, 4))) # Cycle through step-over sections (line segments or arcs) for so in range(0, len(SCANDATA)): @@ -1676,7 +1675,7 @@ class ObjectSurface(PathOp.ObjectOp): prevDepth = lyrDep # Efor - PathLog.debug("Multi-pass op has {} layers (step downs).".format(lyr + 1)) + Path.Log.debug("Multi-pass op has {} layers (step downs).".format(lyr + 1)) return GCODE @@ -1877,9 +1876,9 @@ class ObjectSurface(PathOp.ObjectOp): stepDown = obj.StepDown.Value if hasattr(obj, "StepDown") else 0 rtpd = min(height, p2.z + stepDown + 2) elif not p1: - PathLog.debug("_stepTransitionCmds() p1 is None") + Path.Log.debug("_stepTransitionCmds() p1 is None") elif not p2: - PathLog.debug("_stepTransitionCmds() p2 is None") + Path.Log.debug("_stepTransitionCmds() p2 is None") # Create raise, shift, and optional lower commands if height is not False: @@ -2025,7 +2024,7 @@ class ObjectSurface(PathOp.ObjectOp): return (coPlanar, cmds) def _planarApplyDepthOffset(self, SCANDATA, DepthOffset): - PathLog.debug("Applying DepthOffset value: {}".format(DepthOffset)) + Path.Log.debug("Applying DepthOffset value: {}".format(DepthOffset)) lenScans = len(SCANDATA) for s in range(0, lenScans): SO = SCANDATA[s] # StepOver @@ -2047,7 +2046,7 @@ class ObjectSurface(PathOp.ObjectOp): # Main rotational scan functions def _processRotationalOp(self, JOB, obj, mdlIdx, compoundFaces=None): - PathLog.debug( + Path.Log.debug( "_processRotationalOp(self, JOB, obj, mdlIdx, compoundFaces=None)" ) @@ -2138,7 +2137,7 @@ class ObjectSurface(PathOp.ObjectOp): line ) in scanLines: # extract circular set(ring) of points from scan lines if len(line) != numPnts: - PathLog.debug("Error: line lengths not equal") + Path.Log.debug("Error: line lengths not equal") return rngs for num in range(0, numPnts): @@ -2301,7 +2300,7 @@ class ObjectSurface(PathOp.ObjectOp): prevDepth = layDep lCnt += 1 # increment layer count - PathLog.debug( + Path.Log.debug( "--Layer " + str(lCnt) + ": " @@ -2330,7 +2329,7 @@ class ObjectSurface(PathOp.ObjectOp): # if self.useTiltCutter == True: if obj.CutterTilt != 0.0: cutterOfst = layDep * math.sin(math.radians(obj.CutterTilt)) - PathLog.debug("CutterTilt: cutterOfst is " + str(cutterOfst)) + Path.Log.debug("CutterTilt: cutterOfst is " + str(cutterOfst)) sumAdv = 0.0 for adv in advances: @@ -2686,7 +2685,7 @@ class ObjectSurface(PathOp.ObjectOp): if FR != 0.0: FR += 2.0 - PathLog.debug("ToolType: {}".format(obj.ToolController.Tool.ToolType)) + Path.Log.debug("ToolType: {}".format(obj.ToolController.Tool.ToolType)) if obj.ToolController.Tool.ToolType == "EndMill": # Standard End Mill return ocl.CylCutter(diam_1, (CEH + lenOfst)) @@ -2716,7 +2715,7 @@ class ObjectSurface(PathOp.ObjectOp): return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) else: # Default to standard end mill - PathLog.warning("Defaulting cutter to standard end mill.") + Path.Log.warning("Defaulting cutter to standard end mill.") return ocl.CylCutter(diam_1, (CEH + lenOfst)) def _getTransitionLine(self, pdc, p1, p2, obj): diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 6068b5292c..c44b130678 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -23,7 +23,7 @@ from PySide import QtCore import FreeCAD import FreeCADGui -import PathScripts.PathLog as PathLog +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui @@ -38,10 +38,10 @@ __doc__ = "Surface operation page controller and command implementation." translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class TaskPanelOpPage(PathOpGui.TaskPanelPage): diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/PathScripts/PathSurfaceSupport.py index b81b039f9d..142fa14191 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/PathScripts/PathSurfaceSupport.py @@ -29,7 +29,7 @@ __doc__ = "Support functions and classes for 3D Surface and Waterline operations __contributors__ = "" import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathUtils as PathUtils import PathScripts.PathOpTools as PathOpTools import math @@ -42,10 +42,10 @@ Part = LazyLoader("Part", globals(), "Part") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -225,7 +225,7 @@ class PathGeometryGenerator: if minRad < minRadSI: minRad = minRadSI - PathLog.debug(" -centerOfPattern: {}".format(self.centerOfPattern)) + Path.Log.debug(" -centerOfPattern: {}".format(self.centerOfPattern)) # Make small center circle to start pattern if self.obj.StepOver > 50: circle = Part.makeCircle(minRad, self.centerOfPattern) @@ -418,7 +418,7 @@ class PathGeometryGenerator: return FreeCAD.Vector(-1 * x, y, 0.0).add(move) def _extractOffsetFaces(self): - PathLog.debug("_extractOffsetFaces()") + Path.Log.debug("_extractOffsetFaces()") wires = [] shape = self.shape offset = 0.0 # Start right at the edge of cut area @@ -548,7 +548,7 @@ class ProcessSelectedFaces: self.showDebugObjects = val def preProcessModel(self, module): - PathLog.debug("preProcessModel()") + Path.Log.debug("preProcessModel()") if not self._isReady(module): return False @@ -570,7 +570,7 @@ class ProcessSelectedFaces: # The user has selected subobjects from the base. Pre-Process each. if self.checkBase: - PathLog.debug(" -obj.Base exists. Pre-processing for selected faces.") + Path.Log.debug(" -obj.Base exists. Pre-processing for selected faces.") (hasFace, hasVoid) = self._identifyFacesAndVoids( FACES, VOIDS @@ -591,14 +591,14 @@ class ProcessSelectedFaces: if hasGeometry and not proceed: return False else: - PathLog.debug(" -No obj.Base data.") + Path.Log.debug(" -No obj.Base data.") for m in range(0, lenGRP): self.modelSTLs[m] = True # Process each model base, as a whole, as needed for m in range(0, lenGRP): if self.modelSTLs[m] and not fShapes[m]: - PathLog.debug(" -Pre-processing {} as a whole.".format(GRP[m].Label)) + Path.Log.debug(" -Pre-processing {} as a whole.".format(GRP[m].Label)) if self.obj.BoundBox == "BaseBoundBox": base = GRP[m] elif self.obj.BoundBox == "Stock": @@ -618,24 +618,24 @@ class ProcessSelectedFaces: (fcShp, prflShp) = pPEB if fcShp: if fcShp is True: - PathLog.debug(" -fcShp is True.") + Path.Log.debug(" -fcShp is True.") fShapes[m] = True else: fShapes[m] = [fcShp] if prflShp: if fcShp: - PathLog.debug("vShapes[{}]: {}".format(m, vShapes[m])) + Path.Log.debug("vShapes[{}]: {}".format(m, vShapes[m])) if vShapes[m]: - PathLog.debug(" -Cutting void from base profile shape.") + Path.Log.debug(" -Cutting void from base profile shape.") adjPS = prflShp.cut(vShapes[m][0]) self.profileShapes[m] = [adjPS] else: - PathLog.debug(" -vShapes[m] is False.") + Path.Log.debug(" -vShapes[m] is False.") self.profileShapes[m] = [prflShp] else: - PathLog.debug(" -Saving base profile shape.") + Path.Log.debug(" -Saving base profile shape.") self.profileShapes[m] = [prflShp] - PathLog.debug( + Path.Log.debug( "self.profileShapes[{}]: {}".format( m, self.profileShapes[m] ) @@ -648,21 +648,21 @@ class ProcessSelectedFaces: def _isReady(self, module): """_isReady(module)... Internal method. Checks if required attributes are available for processing obj.Base (the Base Geometry).""" - PathLog.debug("ProcessSelectedFaces _isReady({})".format(module)) + Path.Log.debug("ProcessSelectedFaces _isReady({})".format(module)) if hasattr(self, module): self.module = module modMethod = getattr(self, module) # gets the attribute only modMethod() # executes as method else: - PathLog.error('PSF._isReady() no "{}" method.'.format(module)) + Path.Log.error('PSF._isReady() no "{}" method.'.format(module)) return False if not self.radius: - PathLog.error("PSF._isReady() no cutter radius available.") + Path.Log.error("PSF._isReady() no cutter radius available.") return False if not self.depthParams: - PathLog.error("PSF._isReady() no depth params available.") + Path.Log.error("PSF._isReady() no depth params available.") return False return True @@ -698,13 +698,13 @@ class ProcessSelectedFaces: if F[m] is False: F[m] = [] F[m].append((shape, faceIdx)) - PathLog.debug(".. Cutting {}".format(sub)) + Path.Log.debug(".. Cutting {}".format(sub)) hasFace = True else: if V[m] is False: V[m] = [] V[m].append((shape, faceIdx)) - PathLog.debug(".. Avoiding {}".format(sub)) + Path.Log.debug(".. Avoiding {}".format(sub)) hasVoid = True return (hasFace, hasVoid) @@ -718,7 +718,7 @@ class ProcessSelectedFaces: isHole = False if self.obj.HandleMultipleFeatures == "Collectively": cont = True - PathLog.debug("Attempting to get cross-section of collective faces.") + Path.Log.debug("Attempting to get cross-section of collective faces.") outFCS, ifL = self.findUnifiedRegions(FCS) if self.obj.InternalFeaturesCut and ifL: ifL = [] # clear avoid shape list @@ -732,7 +732,7 @@ class ProcessSelectedFaces: # Handle profile edges request if cont and self.profileEdges != "None": - PathLog.debug(".. include Profile Edge") + Path.Log.debug(".. include Profile Edge") ofstVal = self._calculateOffsetValue(isHole) psOfst = PathUtils.getOffsetArea(cfsL, ofstVal, plane=self.wpc) if psOfst: @@ -763,7 +763,7 @@ class ProcessSelectedFaces: lenIfL = len(ifL) if not self.obj.InternalFeaturesCut: if lenIfL == 0: - PathLog.debug(" -No internal features saved.") + Path.Log.debug(" -No internal features saved.") else: if lenIfL == 1: casL = ifL[0] @@ -798,7 +798,7 @@ class ProcessSelectedFaces: ifL = [] # avoid shape list if outerFace: - PathLog.debug( + Path.Log.debug( "Attempting to create offset face of Face{}".format(fNum) ) @@ -854,7 +854,7 @@ class ProcessSelectedFaces: mVS.append(ifs) if VDS: - PathLog.debug("Processing avoid faces.") + Path.Log.debug("Processing avoid faces.") cont = True isHole = False @@ -928,7 +928,7 @@ class ProcessSelectedFaces: partshape=base.Shape, subshape=None, depthparams=self.depthParams ) # Produces .Shape except Exception as ee: - PathLog.error(str(ee)) + Path.Log.error(str(ee)) shell = base.Shape.Shells[0] solid = Part.makeSolid(shell) try: @@ -936,7 +936,7 @@ class ProcessSelectedFaces: partshape=solid, subshape=None, depthparams=self.depthParams ) # Produces .Shape except Exception as eee: - PathLog.error(str(eee)) + Path.Log.error(str(eee)) cont = False if cont: @@ -946,11 +946,11 @@ class ProcessSelectedFaces: if csFaceShape is False: csFaceShape = getSliceFromEnvelope(baseEnv) if csFaceShape is False: - PathLog.debug("Failed to slice baseEnv shape.") + Path.Log.debug("Failed to slice baseEnv shape.") cont = False if cont and self.profileEdges != "None": - PathLog.debug(" -Attempting profile geometry for model base.") + Path.Log.debug(" -Attempting profile geometry for model base.") ofstVal = self._calculateOffsetValue(isHole) psOfst = PathUtils.getOffsetArea(csFaceShape, ofstVal, plane=self.wpc) if psOfst: @@ -966,7 +966,7 @@ class ProcessSelectedFaces: csFaceShape, ofstVal, plane=self.wpc ) if faceOffsetShape is False: - PathLog.debug("getOffsetArea() failed for entire base.") + Path.Log.debug("getOffsetArea() failed for entire base.") else: faceOffsetShape.translate( FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin) @@ -1005,7 +1005,7 @@ class ProcessSelectedFaces: def findUnifiedRegions(self, shapeAndIndexTuples, useAreaImplementation=True): """Wrapper around area and wire based region unification implementations.""" - PathLog.debug("findUnifiedRegions()") + Path.Log.debug("findUnifiedRegions()") # Allow merging of faces within the LinearDeflection tolerance. tolerance = self.obj.LinearDeflection.Value # Default: normal to Z=1 (XY plane), at Z=0 @@ -1038,7 +1038,7 @@ class ProcessSelectedFaces: internalFaces = Part.makeCompound(internalFaces) return ([outlineShape], [internalFaces]) except Exception as e: - PathLog.warning( + Path.Log.warning( "getOffsetArea failed: {}; Using FindUnifiedRegions.".format(e) ) # Use face-unifying class @@ -1053,14 +1053,14 @@ class ProcessSelectedFaces: # Functions for getting a shape envelope and cross-section def getExtrudedShape(wire): - PathLog.debug("getExtrudedShape()") + Path.Log.debug("getExtrudedShape()") wBB = wire.BoundBox extFwd = math.floor(2.0 * wBB.ZLength) + 10.0 try: shell = wire.extrude(FreeCAD.Vector(0.0, 0.0, extFwd)) except Exception as ee: - PathLog.error(" -extrude wire failed: \n{}".format(ee)) + Path.Log.error(" -extrude wire failed: \n{}".format(ee)) return False SHP = Part.makeSolid(shell) @@ -1068,7 +1068,7 @@ def getExtrudedShape(wire): def getShapeSlice(shape): - PathLog.debug("getShapeSlice()") + Path.Log.debug("getShapeSlice()") bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -1096,7 +1096,7 @@ def getShapeSlice(shape): if slcArea < midArea: for W in slcShp.Wires: if W.isClosed() is False: - PathLog.debug(" -wire.isClosed() is False") + Path.Log.debug(" -wire.isClosed() is False") return False if len(slcShp.Wires) == 1: wire = slcShp.Wires[0] @@ -1118,7 +1118,7 @@ def getShapeSlice(shape): def getProjectedFace(tempGroup, wire): import Draft - PathLog.debug("getProjectedFace()") + Path.Log.debug("getProjectedFace()") F = FreeCAD.ActiveDocument.addObject("Part::Feature", "tmpProjectionWire") F.Shape = wire F.purgeTouched() @@ -1129,7 +1129,7 @@ def getProjectedFace(tempGroup, wire): prj.purgeTouched() tempGroup.addObject(prj) except Exception as ee: - PathLog.error(str(ee)) + Path.Log.error(str(ee)) return False else: pWire = Part.Wire(prj.Shape.Edges) @@ -1141,7 +1141,7 @@ def getProjectedFace(tempGroup, wire): def getCrossSection(shape): - PathLog.debug("getCrossSection()") + Path.Log.debug("getCrossSection()") wires = [] bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -1154,19 +1154,19 @@ def getCrossSection(shape): comp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - comp.BoundBox.ZMin)) csWire = comp.Wires[0] if csWire.isClosed() is False: - PathLog.debug(" -comp.Wires[0] is not closed") + Path.Log.debug(" -comp.Wires[0] is not closed") return False CS = Part.Face(csWire) CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) return CS else: - PathLog.debug(" -No wires from .slice() method") + Path.Log.debug(" -No wires from .slice() method") return False def getShapeEnvelope(shape): - PathLog.debug("getShapeEnvelope()") + Path.Log.debug("getShapeEnvelope()") wBB = shape.BoundBox extFwd = wBB.ZLength + 10.0 @@ -1187,7 +1187,7 @@ def getShapeEnvelope(shape): def getSliceFromEnvelope(env): - PathLog.debug("getSliceFromEnvelope()") + Path.Log.debug("getSliceFromEnvelope()") eBB = env.BoundBox extFwd = eBB.ZLength + 10.0 maxz = eBB.ZMin + extFwd @@ -1217,7 +1217,7 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): Creates and OCL.stl object with combined data with waste stock, model, and avoided faces. Travel lines can be checked against this STL object to determine minimum travel height to clear stock and model.""" - PathLog.debug("_makeSafeSTL()") + Path.Log.debug("_makeSafeSTL()") fuseShapes = [] Mdl = JOB.Model.Group[mdlIdx] @@ -1243,7 +1243,7 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): ) # Produces .Shape cont = True except Exception as ee: - PathLog.error(str(ee)) + Path.Log.error(str(ee)) shell = Mdl.Shape.Shells[0] solid = Part.makeSolid(shell) try: @@ -1252,7 +1252,7 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): ) # Produces .Shape cont = True except Exception as eee: - PathLog.error(str(eee)) + Path.Log.error(str(eee)) if cont: stckWst = JOB.Stock.Shape.cut(envBB) @@ -1330,7 +1330,7 @@ def _makeSTL(model, obj, ocl, model_type=None): def pathGeomToLinesPointSet(self, obj, compGeoShp): """pathGeomToLinesPointSet(self, obj, compGeoShp)... Convert a compound set of sequential line segments to directionally-oriented collinear groupings.""" - PathLog.debug("pathGeomToLinesPointSet()") + Path.Log.debug("pathGeomToLinesPointSet()") # Extract intersection line segments for return value as [] LINES = [] inLine = [] @@ -1426,9 +1426,9 @@ def pathGeomToLinesPointSet(self, obj, compGeoShp): isEven = lnCnt % 2 if isEven == 0: - PathLog.debug("Line count is ODD: {}.".format(lnCnt)) + Path.Log.debug("Line count is ODD: {}.".format(lnCnt)) else: - PathLog.debug("Line count is even: {}.".format(lnCnt)) + Path.Log.debug("Line count is even: {}.".format(lnCnt)) return LINES @@ -1437,7 +1437,7 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): """_pathGeomToZigzagPointSet(self, obj, compGeoShp)... Convert a compound set of sequential line segments to directionally-oriented collinear groupings with a ZigZag directional indicator included for each collinear group.""" - PathLog.debug("_pathGeomToZigzagPointSet()") + Path.Log.debug("_pathGeomToZigzagPointSet()") # Extract intersection line segments for return value as [] LINES = [] inLine = [] @@ -1516,9 +1516,9 @@ def pathGeomToZigzagPointSet(self, obj, compGeoShp): # Fix directional issue with LAST line when line count is even isEven = lnCnt % 2 if isEven == 0: # Changed to != with 90 degree CutPatternAngle - PathLog.debug("Line count is even: {}.".format(lnCnt)) + Path.Log.debug("Line count is even: {}.".format(lnCnt)) else: - PathLog.debug("Line count is ODD: {}.".format(lnCnt)) + Path.Log.debug("Line count is ODD: {}.".format(lnCnt)) dirFlg = -1 * dirFlg if not obj.CutPatternReversed: if self.CutClimb: @@ -1561,7 +1561,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): Convert a compound set of arcs/circles to a set of directionally-oriented arc end points and the corresponding center point.""" # Extract intersection line segments for return value as [] - PathLog.debug("pathGeomToCircularPointSet()") + Path.Log.debug("pathGeomToCircularPointSet()") ARCS = [] stpOvrEI = [] segEI = [] @@ -1668,7 +1668,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): for so in range(0, len(stpOvrEI)): SO = stpOvrEI[so] if SO[0] == "L": # L = Loop/Ring/Circle - # PathLog.debug("SO[0] == 'Loop'") + # Path.Log.debug("SO[0] == 'Loop'") lei = SO[1] # loop Edges index v1 = compGeoShp.Edges[lei].Vertexes[0] @@ -1703,7 +1703,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): ) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) ARCS.append(("L", dirFlg, [arc])) elif SO[0] == "A": # A = Arc - # PathLog.debug("SO[0] == 'Arc'") + # Path.Log.debug("SO[0] == 'Arc'") PRTS = [] EI = SO[1] # list of corresponding Edges indexes CONN = SO[2] # list of corresponding connected edges tuples (iE, iS) @@ -1803,7 +1803,7 @@ def pathGeomToCircularPointSet(self, obj, compGeoShp): def pathGeomToSpiralPointSet(obj, compGeoShp): """_pathGeomToSpiralPointSet(obj, compGeoShp)... Convert a compound set of sequential line segments to directional, connected groupings.""" - PathLog.debug("_pathGeomToSpiralPointSet()") + Path.Log.debug("_pathGeomToSpiralPointSet()") # Extract intersection line segments for return value as [] LINES = [] inLine = [] @@ -1856,7 +1856,7 @@ def pathGeomToSpiralPointSet(obj, compGeoShp): def pathGeomToOffsetPointSet(obj, compGeoShp): """pathGeomToOffsetPointSet(obj, compGeoShp)... Convert a compound set of 3D profile segmented wires to 2D segments, applying linear optimization.""" - PathLog.debug("pathGeomToOffsetPointSet()") + Path.Log.debug("pathGeomToOffsetPointSet()") LINES = [] optimize = obj.OptimizeLinearPaths @@ -1952,7 +1952,7 @@ class FindUnifiedRegions: base = ef.cut(cutBox) if base.Volume == 0: - PathLog.debug( + Path.Log.debug( "Ignoring Face{}. It is likely vertical with no horizontal exposure.".format( fcIdx ) @@ -2026,7 +2026,7 @@ class FindUnifiedRegions: count[1] += 1 def _groupEdgesByLength(self): - PathLog.debug("_groupEdgesByLength()") + Path.Log.debug("_groupEdgesByLength()") threshold = self.geomToler grp = [] processLast = False @@ -2074,7 +2074,7 @@ class FindUnifiedRegions: self.idGroups.append(grp) def _identifySharedEdgesByLength(self, grp): - PathLog.debug("_identifySharedEdgesByLength()") + Path.Log.debug("_identifySharedEdgesByLength()") holds = [] specialIndexes = [] threshold = self.geomToler @@ -2132,7 +2132,7 @@ class FindUnifiedRegions: self.noSharedEdges = False def _extractWiresFromEdges(self): - PathLog.debug("_extractWiresFromEdges()") + Path.Log.debug("_extractWiresFromEdges()") DATA = self.edgeData holds = [] firstEdge = None @@ -2229,7 +2229,7 @@ class FindUnifiedRegions: # Ewhile numLoops = len(LOOPS) - PathLog.debug(" -numLoops: {}.".format(numLoops)) + Path.Log.debug(" -numLoops: {}.".format(numLoops)) if numLoops > 0: for li in range(0, numLoops): Edges = LOOPS[li] @@ -2258,7 +2258,7 @@ class FindUnifiedRegions: self.REGIONS.sort(key=faceArea, reverse=True) def _identifyInternalFeatures(self): - PathLog.debug("_identifyInternalFeatures()") + Path.Log.debug("_identifyInternalFeatures()") remList = [] for (top, fcIdx) in self.topFaces: @@ -2273,14 +2273,14 @@ class FindUnifiedRegions: remList.append(s) break else: - PathLog.debug(" - No common area.\n") + Path.Log.debug(" - No common area.\n") remList.sort(reverse=True) for ri in remList: self.REGIONS.pop(ri) def _processNestedRegions(self): - PathLog.debug("_processNestedRegions()") + Path.Log.debug("_processNestedRegions()") cont = True hold = [] Ids = [] @@ -2325,7 +2325,7 @@ class FindUnifiedRegions: # Accessory methods def _getCompleteCrossSection(self, shape): - PathLog.debug("_getCompleteCrossSection()") + Path.Log.debug("_getCompleteCrossSection()") wires = [] bb = shape.BoundBox mid = (bb.ZMin + bb.ZMax) / 2.0 @@ -2339,7 +2339,7 @@ class FindUnifiedRegions: CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) return CS - PathLog.debug(" -No wires from .slice() method") + Path.Log.debug(" -No wires from .slice() method") return False def _edgesAreConnected(self, e1, e2): @@ -2382,7 +2382,7 @@ class FindUnifiedRegions: def getUnifiedRegions(self): """getUnifiedRegions()... Returns a list of unified regions from list of tuples (faceShape, faceIndex) received at instantiation of the class object.""" - PathLog.debug("getUnifiedRegions()") + Path.Log.debug("getUnifiedRegions()") if len(self.FACES) == 0: msg = "No FACE data tuples received at instantiation of class.\n" FreeCAD.Console.PrintError(msg) @@ -2431,7 +2431,7 @@ class FindUnifiedRegions: self._identifySharedEdgesByLength(grp) if self.noSharedEdges: - PathLog.debug("No shared edges by length detected.") + Path.Log.debug("No shared edges by length detected.") allTopFaces = [] for (topFace, fcIdx) in self.topFaces: allTopFaces.append(topFace) @@ -2505,7 +2505,7 @@ class OCL_Tool: self.toolType = self.tool.ToolType # Indicates Legacy tool self.toolMode = "Legacy" if self.toolType: - PathLog.debug( + Path.Log.debug( "OCL_Tool tool mode, type: {}, {}".format(self.toolMode, self.toolType) ) @@ -2780,7 +2780,7 @@ class OCL_Tool: FreeCAD.Console.PrintError(err + "\n") return False else: - PathLog.debug("OCL_Tool tool method: {}".format(self.toolMethod)) + Path.Log.debug("OCL_Tool tool method: {}".format(self.toolMethod)) oclToolMethod = getattr(self, "_ocl" + self.toolMethod) oclToolMethod() @@ -2812,7 +2812,7 @@ class OCL_Tool: # Support functions def makeExtendedBoundBox(wBB, bbBfr, zDep): - PathLog.debug("makeExtendedBoundBox()") + Path.Log.debug("makeExtendedBoundBox()") p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) diff --git a/src/Mod/Path/PathScripts/PathThreadMilling.py b/src/Mod/Path/PathScripts/PathThreadMilling.py index 6aa1a67de7..a8ffa56e5f 100644 --- a/src/Mod/Path/PathScripts/PathThreadMilling.py +++ b/src/Mod/Path/PathScripts/PathThreadMilling.py @@ -26,7 +26,6 @@ import FreeCAD import Path import PathScripts.PathCircularHoleBase as PathCircularHoleBase import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import Generators.threadmilling_generator as threadmilling import math @@ -41,10 +40,10 @@ __doc__ = "Path thread milling operation." SQRT_3_DIVIDED_BY_2 = 0.8660254037844386 if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -106,7 +105,7 @@ def _isThreadInternal(obj): return obj.ThreadType in ThreadTypesInternal def threadSetupInternal(obj, zTop, zBottom): - PathLog.track() + Path.Log.track() if obj.ThreadOrientation == RightHand: # Right hand thread, G2, top down -> conventional milling if obj.Direction == DirectionConventional: @@ -121,7 +120,7 @@ def threadSetupInternal(obj, zTop, zBottom): return ("G2", zBottom, zTop) def threadSetupExternal(obj, zTop, zBottom): - PathLog.track() + Path.Log.track() if obj.ThreadOrientation == RightHand: # right hand thread, G2, top down -> climb milling if obj.Direction == DirectionClimb: @@ -136,7 +135,7 @@ def threadSetupExternal(obj, zTop, zBottom): def threadSetup(obj): """Return (cmd, zbegin, zend) of thread milling operation""" - PathLog.track() + Path.Log.track() zTop = obj.StartDepth.Value zBottom = obj.FinalDepth.Value @@ -149,7 +148,7 @@ def threadSetup(obj): def threadRadii(internal, majorDia, minorDia, toolDia, toolCrest): """threadRadii(majorDia, minorDia, toolDia, toolCrest) ... returns the minimum and maximum radius for thread.""" - PathLog.track(internal, majorDia, minorDia, toolDia, toolCrest) + Path.Log.track(internal, majorDia, minorDia, toolDia, toolCrest) if toolCrest is None: toolCrest = 0.0 # As it turns out metric and imperial standard threads follow the same rules. @@ -171,12 +170,12 @@ def threadRadii(internal, majorDia, minorDia, toolDia, toolCrest): # Compensate for the crest of the tool toolTip = innerTip - toolCrest * SQRT_3_DIVIDED_BY_2 radii = ((majorDia + toolDia) / 2.0, toolTip + toolDia / 2.0) - PathLog.track(radii) + Path.Log.track(radii) return radii def threadPasses(count, radii, internal, majorDia, minorDia, toolDia, toolCrest): - PathLog.track(count, radii, internal, majorDia, minorDia, toolDia, toolCrest) + Path.Log.track(count, radii, internal, majorDia, minorDia, toolDia, toolCrest) # the logic goes as follows, total area to be removed: # A = H * W ... where H is the depth and W is half the width of a thread # H = k * sin(30) = k * 1/2 -> k = 2 * H @@ -198,7 +197,7 @@ def threadPasses(count, radii, internal, majorDia, minorDia, toolDia, toolCrest) # the order in which they have to get milled. As a result H ends up being negative # and the math for internal and external threads is identical. passes = [minor + h for h in Hi] - PathLog.debug(f"threadPasses({minor}, {major}) -> H={H} : {Hi} --> {passes}") + Path.Log.debug(f"threadPasses({minor}, {major}) -> H={H} : {Hi} --> {passes}") return passes @@ -210,7 +209,7 @@ def elevatorRadius(obj, center, internal, tool): dy = float(obj.MinorDiameter - tool.Diameter) / 2 - 1 if dy < 0: if obj.MinorDiameter < tool.Diameter: - PathLog.error( + Path.Log.error( "The selected tool is too big (d={}) for milling a thread with minor diameter D={}".format( tool.Diameter, obj.MinorDiameter ) @@ -235,7 +234,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): 'raw' is list of (translated_text, data_string) tuples 'translated' is list of translated string literals """ - PathLog.track() + Path.Log.track() # Enumeration lists for App::PropertyEnumeration properties enums = { @@ -305,20 +304,20 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data def circularHoleFeatures(self, obj): - PathLog.track() + Path.Log.track() return PathOp.FeatureBaseGeometry def initCircularHoleOperation(self, obj): - PathLog.track() + Path.Log.track() obj.addProperty( "App::PropertyEnumeration", "ThreadOrientation", @@ -414,7 +413,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): setattr(obj, n[0], n[1]) def threadPassRadii(self, obj): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) rMajor = (obj.MajorDiameter.Value - self.tool.Diameter) / 2.0 rMinor = (obj.MinorDiameter.Value - self.tool.Diameter) / 2.0 if obj.Passes < 1: @@ -426,7 +425,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): return list(reversed(passes)) def executeThreadMill(self, obj, loc, gcode, zStart, zFinal, pitch): - PathLog.track(obj.Label, loc, gcode, zStart, zFinal, pitch) + Path.Log.track(obj.Label, loc, gcode, zStart, zFinal, pitch) elevator = elevatorRadius(obj, loc, _isThreadInternal(obj), self.tool) move2clearance = Path.Command( @@ -480,7 +479,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): ) def circularHoleExecute(self, obj, holes): - PathLog.track() + Path.Log.track() if self.isToolSupported(obj, self.tool): self.commandlist.append(Path.Command("(Begin Thread Milling)")) @@ -489,7 +488,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): if obj.TPI > 0: pitch = 25.4 / obj.TPI if pitch <= 0: - PathLog.error("Cannot create thread with pitch {}".format(pitch)) + Path.Log.error("Cannot create thread with pitch {}".format(pitch)) return # rapid to clearance height @@ -503,10 +502,10 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): pitch, ) else: - PathLog.error("No suitable Tool found for thread milling operation") + Path.Log.error("No suitable Tool found for thread milling operation") def opSetDefaultValues(self, obj, job): - PathLog.track() + Path.Log.track() obj.ThreadOrientation = RightHand obj.ThreadType = ThreadTypeMetricInternal6H obj.ThreadFit = 50 @@ -519,7 +518,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): def isToolSupported(self, obj, tool): """Thread milling only supports thread milling cutters.""" support = hasattr(tool, "Diameter") and hasattr(tool, "Crest") - PathLog.track(tool.Label, support) + Path.Log.track(tool.Label, support) return support diff --git a/src/Mod/Path/PathScripts/PathThreadMillingGui.py b/src/Mod/Path/PathScripts/PathThreadMillingGui.py index 54ae450a15..84857ee374 100644 --- a/src/Mod/Path/PathScripts/PathThreadMillingGui.py +++ b/src/Mod/Path/PathScripts/PathThreadMillingGui.py @@ -22,11 +22,11 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathThreadMilling as PathThreadMilling import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui import csv @@ -41,10 +41,10 @@ __url__ = "http://www.freecadweb.org" __doc__ = "UI and Command for Path Thread Milling Operation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -52,7 +52,7 @@ translate = FreeCAD.Qt.translate def fillThreads(form, dataFile, defaultSelect): form.threadName.blockSignals(True) select = form.threadName.currentText() - PathLog.debug("select = '{}'".format(select)) + Path.Log.debug("select = '{}'".format(select)) form.threadName.clear() with open( "{}Mod/Path/Data/Threads/{}".format(FreeCAD.getHomePath(), dataFile) @@ -97,7 +97,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def getFields(self, obj): """getFields(obj) ... update obj's properties with values from the UI""" - PathLog.track() + Path.Log.track() self.majorDia.updateProperty() self.minorDia.updateProperty() @@ -115,7 +115,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def setFields(self, obj): """setFields(obj) ... update UI with obj properties' values""" - PathLog.track() + Path.Log.track() self.selectInComboBox(obj.ThreadOrientation, self.form.threadOrientation) self.selectInComboBox(obj.ThreadType, self.form.threadType) diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/PathScripts/PathToolBit.py index 8067d9f9ca..dd3358d0c2 100644 --- a/src/Mod/Path/PathScripts/PathToolBit.py +++ b/src/Mod/Path/PathScripts/PathToolBit.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathPreferences as PathPreferences import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathUtil as PathUtil @@ -46,14 +46,14 @@ _DebugFindTool = False if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def _findToolFile(name, containerFile, typ): - PathLog.track(name) + Path.Log.track(name) if os.path.exists(name): # absolute reference return name @@ -65,7 +65,7 @@ def _findToolFile(name, containerFile, typ): paths.extend(PathPreferences.searchPathsTool(typ)) def _findFile(path, name): - PathLog.track(path, name) + Path.Log.track(path, name) fullPath = os.path.join(path, name) if os.path.exists(fullPath): return (True, fullPath) @@ -85,13 +85,13 @@ def _findToolFile(name, containerFile, typ): def findToolShape(name, path=None): """findToolShape(name, path) ... search for name, if relative path look in path""" - PathLog.track(name, path) + Path.Log.track(name, path) return _findToolFile(name, path, "Shape") def findToolBit(name, path=None): """findToolBit(name, path) ... search for name, if relative path look in path""" - PathLog.track(name, path) + Path.Log.track(name, path) if name.endswith(".fctb"): return _findToolFile(name, path, "Bit") return _findToolFile("{}.fctb".format(name), path, "Bit") @@ -100,14 +100,14 @@ def findToolBit(name, path=None): # Only used in ToolBit unit test module: TestPathToolBit.py def findToolLibrary(name, path=None): """findToolLibrary(name, path) ... search for name, if relative path look in path""" - PathLog.track(name, path) + Path.Log.track(name, path) if name.endswith(".fctl"): return _findToolFile(name, path, "Library") return _findToolFile("{}.fctl".format(name), path, "Library") def _findRelativePath(path, typ): - PathLog.track(path, typ) + Path.Log.track(path, typ) relative = path for p in PathPreferences.searchPathsTool(typ): if path.startswith(p): @@ -136,7 +136,7 @@ def findRelativePathLibrary(path): class ToolBit(object): def __init__(self, obj, shapeFile, path=None): - PathLog.track(obj.Label, shapeFile, path) + Path.Log.track(obj.Label, shapeFile, path) self.obj = obj obj.addProperty( "App::PropertyFile", @@ -238,12 +238,12 @@ class ToolBit(object): obj.setEditorMode(prop, 0) def onChanged(self, obj, prop): - PathLog.track(obj.Label, prop) + Path.Log.track(obj.Label, prop) if prop == "BitShape" and "Restore" not in obj.State: self._setupBitShape(obj) def onDelete(self, obj, arg2=None): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) self.unloadBitBody(obj) obj.Document.removeObject(obj.Name) @@ -276,7 +276,7 @@ class ToolBit(object): obj.Shape = Part.Shape() def _loadBitBody(self, obj, path=None): - PathLog.track(obj.Label, path) + Path.Log.track(obj.Label, path) p = path if path else obj.BitShape docOpened = False doc = None @@ -291,12 +291,12 @@ class ToolBit(object): if not path and p != obj.BitShape: obj.BitShape = p - PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) + Path.Log.debug("ToolBit {} using shape file: {}".format(obj.Label, p)) doc = FreeCAD.openDocument(p, True) obj.ShapeName = doc.Name docOpened = True else: - PathLog.debug("ToolBit {} already open: {}".format(obj.Label, doc)) + Path.Log.debug("ToolBit {} already open: {}".format(obj.Label, doc)) return (doc, docOpened) def _removeBitBody(self, obj): @@ -306,7 +306,7 @@ class ToolBit(object): obj.BitBody = None def _deleteBitSetup(self, obj): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) self._removeBitBody(obj) self._copyBitShape(obj) for prop in obj.BitPropertyNames: @@ -342,7 +342,7 @@ class ToolBit(object): PathUtil.setProperty(obj, prop, val) def _setupBitShape(self, obj, path=None): - PathLog.track(obj.Label) + Path.Log.track(obj.Label) activeDoc = FreeCAD.ActiveDocument (doc, docOpened) = self._loadBitBody(obj, path) @@ -359,7 +359,7 @@ class ToolBit(object): if bitBody.ViewObject: bitBody.ViewObject.Visibility = False - PathLog.debug( + Path.Log.debug( "bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody)) ) @@ -367,12 +367,12 @@ class ToolBit(object): for attributes in [ o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o) ]: - PathLog.debug("Process properties from {}".format(attributes.Label)) + Path.Log.debug("Process properties from {}".format(attributes.Label)) for prop in attributes.Proxy.getCustomProperties(): self._setupProperty(obj, prop, attributes) propNames.append(prop) if not propNames: - PathLog.error( + Path.Log.error( "Did not find a PropertyBag in {} - not a ToolBit shape?".format( docName ) @@ -430,7 +430,7 @@ class ToolBit(object): return None def saveToFile(self, obj, path, setFile=True): - PathLog.track(path) + Path.Log.track(path) try: with open(path, "w") as fp: json.dump(self.templateAttrs(obj), fp, indent=" ") @@ -438,7 +438,7 @@ class ToolBit(object): obj.File = path return True except (OSError, IOError) as e: - PathLog.error( + Path.Log.error( "Could not save tool {} to {} ({})".format(obj.Label, path, e) ) raise @@ -466,14 +466,14 @@ class ToolBit(object): def Declaration(path): - PathLog.track(path) + Path.Log.track(path) with open(path, "r") as fp: return json.load(fp) class ToolBitFactory(object): def CreateFromAttrs(self, attrs, name="ToolBit", path=None): - PathLog.track(attrs, path) + Path.Log.track(attrs, path) obj = Factory.Create(name, attrs["shape"], path) obj.Label = attrs["name"] params = attrs["parameter"] @@ -484,7 +484,7 @@ class ToolBitFactory(object): return obj def CreateFrom(self, path, name="ToolBit"): - PathLog.track(name, path) + Path.Log.track(name, path) if not os.path.isfile(path): raise FileNotFoundError(f"{path} not found") @@ -493,11 +493,11 @@ class ToolBitFactory(object): bit = Factory.CreateFromAttrs(data, name, path) return bit except (OSError, IOError) as e: - PathLog.error("%s not a valid tool file (%s)" % (path, e)) + Path.Log.error("%s not a valid tool file (%s)" % (path, e)) raise def Create(self, name="ToolBit", shapeFile=None, path=None): - PathLog.track(name, shapeFile, path) + Path.Log.track(name, shapeFile, path) obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name) obj.Proxy = ToolBit(obj, shapeFile, path) return obj diff --git a/src/Mod/Path/PathScripts/PathToolBitCmd.py b/src/Mod/Path/PathScripts/PathToolBitCmd.py index 8a388b3552..68f94397d6 100644 --- a/src/Mod/Path/PathScripts/PathToolBitCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitCmd.py @@ -20,19 +20,19 @@ # * * # *************************************************************************** -from PySide import QtCore -from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui +import Path import PathScripts -import PathScripts.PathLog as PathLog import os +from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class CommandToolBitCreate: diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/PathScripts/PathToolBitEdit.py index 9746f0e3bb..9b0a71087f 100644 --- a/src/Mod/Path/PathScripts/PathToolBitEdit.py +++ b/src/Mod/Path/PathScripts/PathToolBitEdit.py @@ -22,8 +22,8 @@ from PySide import QtCore, QtGui import FreeCADGui +import Path import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathPropertyEditor as PathPropertyEditor import PathScripts.PathUtil as PathUtil @@ -32,10 +32,10 @@ import re if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class _Delegate(QtGui.QStyledItemDelegate): @@ -76,7 +76,7 @@ class ToolBitEditor(object): """ def __init__(self, tool, parentWidget=None, loadBitBody=True): - PathLog.track() + Path.Log.track() self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitEditor.ui") if parentWidget: @@ -102,7 +102,7 @@ class ToolBitEditor(object): self.setupAttributes(self.tool) def setupTool(self, tool): - PathLog.track() + Path.Log.track() # Can't delete and add fields to the form because of dangling references in case of # a focus change. see https://forum.freecadweb.org/viewtopic.php?f=10&t=52246#p458583 # Instead we keep widgets once created and use them for new properties, and hide all @@ -120,7 +120,7 @@ class ToolBitEditor(object): usedRows = 0 for nr, name in enumerate(tool.Proxy.toolShapeProperties(tool)): if nr < len(self.widgets): - PathLog.debug("re-use row: {} [{}]".format(nr, name)) + Path.Log.debug("re-use row: {} [{}]".format(nr, name)) label, qsb, editor = self.widgets[nr] label.setText(labelText(name)) editor.attachTo(tool, name) @@ -131,7 +131,7 @@ class ToolBitEditor(object): editor = PathGui.QuantitySpinBox(qsb, tool, name) label = QtGui.QLabel(labelText(name)) self.widgets.append((label, qsb, editor)) - PathLog.debug("create row: {} [{}] {}".format(nr, name, type(qsb))) + Path.Log.debug("create row: {} [{}] {}".format(nr, name, type(qsb))) if hasattr(qsb, "editingFinished"): qsb.editingFinished.connect(self.updateTool) @@ -140,13 +140,13 @@ class ToolBitEditor(object): usedRows = usedRows + 1 # hide all rows which aren't being used - PathLog.track(usedRows, len(self.widgets)) + Path.Log.track(usedRows, len(self.widgets)) for i in range(usedRows, len(self.widgets)): label, qsb, editor = self.widgets[i] label.hide() qsb.hide() editor.attachTo(None) - PathLog.debug(" hide row: {}".format(i)) + Path.Log.debug(" hide row: {}".format(i)) img = tool.Proxy.getBitThumbnail(tool) if img: @@ -155,7 +155,7 @@ class ToolBitEditor(object): self.form.image.setPixmap(QtGui.QPixmap()) def setupAttributes(self, tool): - PathLog.track() + Path.Log.track() setup = True if not hasattr(self, "delegate"): @@ -195,16 +195,16 @@ class ToolBitEditor(object): # self.form.attrTree.collapseAll() def accept(self): - PathLog.track() + Path.Log.track() self.refresh() self.tool.Proxy.unloadBitBody(self.tool) def reject(self): - PathLog.track() + Path.Log.track() self.tool.Proxy.unloadBitBody(self.tool) def updateUI(self): - PathLog.track() + Path.Log.track() self.form.toolName.setText(self.tool.Label) self.form.shapePath.setText(self.tool.BitShape) @@ -230,7 +230,7 @@ class ToolBitEditor(object): return False def updateShape(self): - PathLog.track() + Path.Log.track() shapePath = str(self.form.shapePath.text()) # Only need to go through this exercise if the shape actually changed. if self._updateBitShape(shapePath): @@ -238,7 +238,7 @@ class ToolBitEditor(object): editor.updateSpinBox() def updateTool(self): - PathLog.track() + Path.Log.track() label = str(self.form.toolName.text()) shape = str(self.form.shapePath.text()) @@ -252,14 +252,14 @@ class ToolBitEditor(object): self.tool.Proxy._updateBitShape(self.tool) def refresh(self): - PathLog.track() + Path.Log.track() self.form.blockSignals(True) self.updateTool() self.updateUI() self.form.blockSignals(False) def selectShape(self): - PathLog.track() + Path.Log.track() path = self.tool.BitShape if not path: path = PathPreferences.lastPathToolShape() @@ -272,7 +272,7 @@ class ToolBitEditor(object): self.updateShape() def setupUI(self): - PathLog.track() + Path.Log.track() self.updateUI() self.form.toolName.editingFinished.connect(self.refresh) diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/PathScripts/PathToolBitGui.py index c5efb283b9..a258de7e91 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitGui.py @@ -24,8 +24,8 @@ from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui +import Path import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit import PathScripts.PathToolBitEdit as PathToolBitEdit @@ -38,10 +38,10 @@ __doc__ = "Task panel editor for a ToolBit" if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -51,7 +51,7 @@ class ViewProvider(object): It's sole job is to provide an icon and invoke the TaskPanel on edit.""" def __init__(self, vobj, name): - PathLog.track(name, vobj.Object) + Path.Log.track(name, vobj.Object) self.panel = None self.icon = name self.obj = vobj.Object @@ -59,7 +59,7 @@ class ViewProvider(object): vobj.Proxy = self def attach(self, vobj): - PathLog.track(vobj.Object) + Path.Log.track(vobj.Object) self.vobj = vobj self.obj = vobj.Object @@ -78,21 +78,21 @@ class ViewProvider(object): return None def onDelete(self, vobj, arg2=None): - PathLog.track(vobj.Object.Label) + Path.Log.track(vobj.Object.Label) vobj.Object.Proxy.onDelete(vobj.Object) def getDisplayMode(self, mode): return "Default" def _openTaskPanel(self, vobj, deleteOnReject): - PathLog.track() + Path.Log.track() self.panel = TaskPanel(vobj, deleteOnReject) FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(self.panel) self.panel.setupUi() def setCreate(self, vobj): - PathLog.track() + Path.Log.track() self._openTaskPanel(vobj, True) def setEdit(self, vobj, mode=0): @@ -125,7 +125,7 @@ class TaskPanel: """TaskPanel for the SetupSheet - if it is being edited directly.""" def __init__(self, vobj, deleteOnReject): - PathLog.track(vobj.Object.Label) + Path.Log.track(vobj.Object.Label) self.vobj = vobj self.obj = vobj.Object self.editor = PathToolBitEdit.ToolBitEditor(self.obj) @@ -153,7 +153,7 @@ class TaskPanel: FreeCAD.ActiveDocument.recompute() def updateUI(self): - PathLog.track() + Path.Log.track() self.editor.updateUI() def updateModel(self): @@ -169,7 +169,7 @@ class ToolBitGuiFactory(PathToolBit.ToolBitFactory): """Create(name = 'ToolBit') ... creates a new tool bit. It is assumed the tool will be edited immediately so the internal bit body is still attached.""" - PathLog.track(name, shapeFile, path) + Path.Log.track(name, shapeFile, path) FreeCAD.ActiveDocument.openTransaction("Create ToolBit") tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile, path) PathIconViewProvider.Attach(tool.ViewObject, name) diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py index 309f77d5e9..6b363b5942 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py @@ -23,13 +23,13 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import PathScripts.PathLog as PathLog +import Path if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py index a4db74a907..2d8764dac8 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py @@ -24,8 +24,8 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit import PathScripts.PathToolBitEdit as PathToolBitEdit @@ -43,10 +43,10 @@ from functools import partial if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) _UuidRole = PySide.QtCore.Qt.UserRole + 1 @@ -59,12 +59,12 @@ translate = FreeCAD.Qt.translate def checkWorkingDir(): # users shouldn't use the example toolbits and libraries. # working directory should be writable - PathLog.track() + Path.Log.track() workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) - PathLog.debug("workingdir: {} defaultdir: {}".format(workingdir, defaultdir)) + Path.Log.debug("workingdir: {} defaultdir: {}".format(workingdir, defaultdir)) dirOK = lambda: workingdir != defaultdir and (os.access(workingdir, os.W_OK)) @@ -95,7 +95,7 @@ def checkWorkingDir(): "{}{}Library".format(workingdir, os.path.sep) ) PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) - PathLog.debug("setting workingdir to: {}".format(workingdir)) + Path.Log.debug("setting workingdir to: {}".format(workingdir)) # Copy only files of default Path\Tools folder to working directory (targeting the README.md help file) src_toolfiles = os.listdir(defaultdir) @@ -221,7 +221,7 @@ class _TableView(PySide.QtGui.QTableView): self._copyTool(uuid, dst + i) def dropEvent(self, event): - PathLog.track() + Path.Log.track() mime = event.mimeData() data = mime.data("application/x-qstandarditemmodeldatalist") stream = PySide.QtCore.QDataStream(data) @@ -245,12 +245,12 @@ class ModelFactory(object): """Helper class to generate qtdata models for toolbit libraries""" def __init__(self, path=None): - PathLog.track() + Path.Log.track() self.path = "" # self.currentLib = "" def __libraryLoad(self, path, datamodel): - PathLog.track(path) + Path.Log.track(path) PathPreferences.setLastFileToolLibrary(path) # self.currenLib = path @@ -262,11 +262,11 @@ class ModelFactory(object): nr = toolBit["nr"] bit = PathToolBit.findToolBit(toolBit["path"], path) if bit: - PathLog.track(bit) + Path.Log.track(bit) tool = PathToolBit.Declaration(bit) datamodel.appendRow(self._toolAdd(nr, tool, bit)) else: - PathLog.error( + Path.Log.error( "Could not find tool #{}: {}".format(nr, toolBit["path"]) ) except Exception as e: @@ -301,7 +301,7 @@ class ModelFactory(object): """ Adds a toolbit item to a model """ - PathLog.track() + Path.Log.track() try: nr = 0 @@ -311,7 +311,7 @@ class ModelFactory(object): nr += 1 tool = PathToolBit.Declaration(path) except Exception as e: - PathLog.error(e) + Path.Log.error(e) datamodel.appendRow(self._toolAdd(nr, tool, path)) @@ -320,7 +320,7 @@ class ModelFactory(object): Finds all the fctl files in a location Returns a QStandardItemModel """ - PathLog.track() + Path.Log.track() path = PathPreferences.lastPathToolLibrary() if os.path.isdir(path): # opening all tables in a directory @@ -335,7 +335,7 @@ class ModelFactory(object): libItem.setIcon(PySide.QtGui.QPixmap(":/icons/Path_ToolTable.svg")) model.appendRow(libItem) - PathLog.debug("model rows: {}".format(model.rowCount())) + Path.Log.debug("model rows: {}".format(model.rowCount())) return model def libraryOpen(self, model, lib=""): @@ -343,7 +343,7 @@ class ModelFactory(object): opens the tools in library Returns a QStandardItemModel """ - PathLog.track(lib) + Path.Log.track(lib) if lib == "": lib = PathPreferences.lastFileToolLibrary() @@ -354,7 +354,7 @@ class ModelFactory(object): if os.path.isfile(lib): # An individual library is wanted self.__libraryLoad(lib, model) - PathLog.debug("model rows: {}".format(model.rowCount())) + Path.Log.debug("model rows: {}".format(model.rowCount())) return model @@ -381,7 +381,7 @@ class ToolBitSelector(object): return libfile def loadData(self): - PathLog.track() + Path.Log.track() self.toolModel.clear() self.toolModel.setHorizontalHeaderLabels(self.columnNames()) self.form.lblLibrary.setText(self.currentLibrary(True)) @@ -391,7 +391,7 @@ class ToolBitSelector(object): self.toolModel.takeColumn(2) def setupUI(self): - PathLog.track() + Path.Log.track() self.loadData() self.form.tools.setModel(self.toolModel) self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons) @@ -491,7 +491,7 @@ class ToolBitLibrary(object): displaying/selecting/creating/editing a collection of ToolBits.""" def __init__(self): - PathLog.track() + Path.Log.track() checkWorkingDir() self.factory = ModelFactory() self.temptool = None @@ -507,7 +507,7 @@ class ToolBitLibrary(object): self.title = self.form.windowTitle() def toolBitNew(self): - PathLog.track() + Path.Log.track() # select the shape file shapefile = PathToolBitGui.GetToolShapeFile() @@ -523,7 +523,7 @@ class ToolBitLibrary(object): loc, fil = os.path.split(filename) fname = os.path.splitext(fil)[0] fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname) - PathLog.debug("fullpath: {}".format(fullpath)) + Path.Log.debug("fullpath: {}".format(fullpath)) self.temptool = PathToolBit.ToolBitFactory().Create(name=fname) self.temptool.BitShape = shapefile @@ -552,7 +552,7 @@ class ToolBitLibrary(object): self.factory.newTool(self.toolModel, fullpath) def toolDelete(self): - PathLog.track() + Path.Log.track() selectedRows = set( [index.row() for index in self.toolTableView.selectedIndexes()] ) @@ -565,18 +565,18 @@ class ToolBitLibrary(object): def tableSelected(self, index): """loads the tools for the selected tool table""" - PathLog.track() + Path.Log.track() item = index.model().itemFromIndex(index) libpath = item.data(_PathRole) self.loadData(libpath) self.path = libpath def open(self): - PathLog.track() + Path.Log.track() return self.form.exec_() def libraryPath(self): - PathLog.track() + Path.Log.track() path = PySide.QtGui.QFileDialog.getExistingDirectory( self.form, "Tool Library Path", PathPreferences.lastPathToolLibrary() ) @@ -632,7 +632,7 @@ class ToolBitLibrary(object): self.form.librarySave.setEnabled(True) def toolEdit(self, selected): - PathLog.track() + Path.Log.track() item = self.toolModel.item(selected.row(), 0) if self.temptool is not None: @@ -723,14 +723,14 @@ class ToolBitLibrary(object): lib = PathPreferences.lastFileToolLibrary() loc = PathPreferences.lastPathToolLibrary() - PathLog.track("lib: {} loc: {}".format(lib, loc)) + Path.Log.track("lib: {} loc: {}".format(lib, loc)) return lib, loc def columnNames(self): return ["Nr", "Tool", "Shape"] def loadData(self, path=None): - PathLog.track(path) + Path.Log.track(path) self.toolTableView.setUpdatesEnabled(False) self.form.TableList.setUpdatesEnabled(False) @@ -766,7 +766,7 @@ class ToolBitLibrary(object): self.form.TableList.setUpdatesEnabled(True) def setupUI(self): - PathLog.track() + Path.Log.track() self.form.TableList.setModel(self.listModel) self.toolTableView.setModel(self.toolModel) @@ -843,7 +843,7 @@ class ToolBitLibrary(object): bit = PathToolBit.Factory.CreateFrom(toolPath) if bit: - PathLog.track(bit) + Path.Log.track(bit) pocket = bit.Pocket if hasattr(bit, "Pocket") else "0" xoffset = bit.Xoffset if hasattr(bit, "Xoffset") else "0" @@ -893,7 +893,7 @@ class ToolBitLibrary(object): FreeCAD.ActiveDocument.removeObject(bit.Name) else: - PathLog.error("Could not find tool #{} ".format(toolNr)) + Path.Log.error("Could not find tool #{} ".format(toolNr)) def libararySaveCamotics(self, path): @@ -923,7 +923,7 @@ class ToolBitLibrary(object): ) toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) - PathLog.debug(toolPath) + Path.Log.debug(toolPath) try: bit = PathToolBit.Factory.CreateFrom(toolPath) except FileNotFoundError as e: @@ -935,7 +935,7 @@ class ToolBitLibrary(object): if not bit: continue - PathLog.track(bit) + Path.Log.track(bit) toolitem = tooltemplate.copy() diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/PathScripts/PathToolController.py index 4e29a098af..1de3c6a2b3 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/PathScripts/PathToolController.py @@ -25,7 +25,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBit as PathToolBit from Generators import toolchange_generator as toolchange_generator @@ -33,10 +32,10 @@ from Generators.toolchange_generator import SpindleDirection if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -62,7 +61,7 @@ class ToolControllerTemplate: class ToolController: def __init__(self, obj, legacyTool=False, createTool=True): - PathLog.track("tool: {}".format(legacyTool)) + Path.Log.track("tool: {}".format(legacyTool)) obj.addProperty( "App::PropertyIntegerConstraint", @@ -143,11 +142,11 @@ class ToolController: data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -165,7 +164,7 @@ class ToolController: setFromTemplate(obj, xmlItem) ... extract properties from xmlItem and assign to receiver. """ - PathLog.track(obj.Name, template) + Path.Log.track(obj.Name, template) version = 0 if template.get(ToolControllerTemplate.Version): version = int(template.get(ToolControllerTemplate.Version)) @@ -218,13 +217,13 @@ class ToolController: exprDef[ToolControllerTemplate.ExprExpr], ) else: - PathLog.error( + Path.Log.error( "Unsupported PathToolController template version {}".format( template.get(ToolControllerTemplate.Version) ) ) else: - PathLog.error( + Path.Log.error( "PathToolController template has no version - corrupted template file?" ) @@ -247,7 +246,7 @@ class ToolController: attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool) expressions = [] for expr in obj.ExpressionEngine: - PathLog.debug("%s: %s" % (expr[0], expr[1])) + Path.Log.debug("%s: %s" % (expr[0], expr[1])) expressions.append( { ToolControllerTemplate.ExprProp: expr[0], @@ -259,7 +258,7 @@ class ToolController: return attrs def execute(self, obj): - PathLog.track() + Path.Log.track() args = { "toolnumber": obj.ToolNumber, @@ -294,7 +293,7 @@ class ToolController: def getTool(self, obj): """returns the tool associated with this tool controller""" - PathLog.track() + Path.Log.track() return obj.Tool def usesLegacyTool(self, obj): @@ -344,7 +343,7 @@ def Create( else isinstance(tool, Path.Tool) ) - PathLog.track(tool, toolNumber, legacyTool) + Path.Log.track(tool, toolNumber, legacyTool) obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Label = name @@ -376,7 +375,7 @@ def Create( def FromTemplate(template, assignViewProvider=True): - PathLog.track() + Path.Log.track() name = template.get(ToolControllerTemplate.Name, ToolControllerTemplate.Label) obj = Create(name, assignViewProvider=True, assignTool=False) diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/PathScripts/PathToolControllerGui.py index 72e4445c7b..4d6fcc3ce0 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/PathScripts/PathToolControllerGui.py @@ -24,10 +24,10 @@ from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathGui as PathGui -import PathScripts.PathLog as PathLog import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolEdit as PathToolEdit import PathScripts.PathUtil as PathUtil @@ -39,10 +39,10 @@ Part = LazyLoader("Part", globals(), "Part") if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -111,7 +111,7 @@ class ViewProvider: return False def setupContextMenu(self, vobj, menu): - PathLog.track() + Path.Log.track() for action in menu.actions(): menu.removeAction(action) action = QtGui.QAction(translate("Path", "Edit"), menu) @@ -126,7 +126,7 @@ class ViewProvider: def Create(name="Default Tool", tool=None, toolNumber=1): - PathLog.track(tool, toolNumber) + Path.Log.track(tool, toolNumber) obj = PathScripts.PathToolController.Create(name, tool, toolNumber) ViewProvider(obj.ViewObject) @@ -161,7 +161,7 @@ class CommandPathToolController(object): return self.selectedJob() is not None def Activated(self): - PathLog.track() + Path.Log.track() job = self.selectedJob() if job: tool = PathToolBitGui.ToolBitSelector().getTool() @@ -265,7 +265,7 @@ class ToolControllerEditor(object): tc.Tool = self.editor.tool except Exception as e: - PathLog.error("Error updating TC: {}".format(e)) + Path.Log.error("Error updating TC: {}".format(e)) def refresh(self): self.form.blockSignals(True) @@ -353,7 +353,7 @@ class DlgToolControllerEdit: rc = False if not self.editor.form.exec_(): - PathLog.info("revert") + Path.Log.info("revert") self.obj.Proxy.setFromTemplate(self.obj, restoreTC) rc = True return rc diff --git a/src/Mod/Path/PathScripts/PathToolEdit.py b/src/Mod/Path/PathScripts/PathToolEdit.py index 8cb5b1afc9..b55c3d272c 100644 --- a/src/Mod/Path/PathScripts/PathToolEdit.py +++ b/src/Mod/Path/PathScripts/PathToolEdit.py @@ -23,13 +23,12 @@ import FreeCAD import FreeCADGui import Path -import PathScripts.PathLog as PathLog import math from PySide import QtGui -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) class ToolEditorDefault: @@ -113,7 +112,7 @@ class ToolEditorImage(object): } def setupUI(self): - PathLog.track() + Path.Log.track() self.form.paramGeneric.hide() self.form.paramImage.show() @@ -129,14 +128,14 @@ class ToolEditorImage(object): self.form.image.setPixmap(self.image) def updateUI(self): - PathLog.track() + Path.Log.track() self.form.value_D.setText(self.quantityDiameter(True).UserString) self.form.value_d.setText(self.quantityFlatRadius(True).UserString) self.form.value_a.setText(self.quantityCuttingEdgeAngle(True).UserString) self.form.value_H.setText(self.quantityCuttingEdgeHeight(True).UserString) def updateTool(self): - PathLog.track() + Path.Log.track() toolDefault = Path.Tool() if "D" in self.hide: self.editor.tool.Diameter = toolDefault.Diameter @@ -227,7 +226,7 @@ class ToolEditorEngrave(ToolEditorImage): super(ToolEditorEngrave, self).__init__(editor, "v-bit.svg", "", "dS") def quantityCuttingEdgeHeight(self, propertyToDisplay): - PathLog.track() + Path.Log.track() dr = (self.quantityDiameter(False) - self.quantityFlatRadius(False)) / 2 da = self.quantityCuttingEdgeAngle(False).Value return dr / math.tan(math.radians(da) / 2) @@ -297,7 +296,7 @@ class ToolEditor: return matslist[material] def updateUI(self): - PathLog.track() + Path.Log.track() self.form.toolName.setText(self.tool.Name) self.form.toolType.setCurrentIndex(self.getType(self.tool.ToolType)) self.form.toolMaterial.setCurrentIndex(self.getMaterial(self.tool.Material)) @@ -310,7 +309,7 @@ class ToolEditor: self.editor.updateUI() def updateToolType(self): - PathLog.track() + Path.Log.track() self.form.blockSignals(True) self.tool.ToolType = self.getType(self.form.toolType.currentIndex()) self.setupToolType(self.tool.ToolType) @@ -318,19 +317,19 @@ class ToolEditor: self.form.blockSignals(False) def setupToolType(self, tt): - PathLog.track() + Path.Log.track() print("Tool type: %s" % (tt)) if "Undefined" == tt: tt = Path.Tool.getToolTypes(Path.Tool())[0] if tt in self.ToolTypeImage: self.editor = self.ToolTypeImage[tt](self) else: - PathLog.debug("weak supported ToolType = %s" % (tt)) + Path.Log.debug("weak supported ToolType = %s" % (tt)) self.editor = ToolEditorDefault(self) self.editor.setupUI() def updateTool(self): - PathLog.track() + Path.Log.track() self.tool.Name = str(self.form.toolName.text()) self.tool.Material = self.getMaterial(self.form.toolMaterial.currentIndex()) self.tool.LengthOffset = FreeCAD.Units.parseQuantity( @@ -339,14 +338,14 @@ class ToolEditor: self.editor.updateTool() def refresh(self): - PathLog.track() + Path.Log.track() self.form.blockSignals(True) self.updateTool() self.updateUI() self.form.blockSignals(False) def setupUI(self): - PathLog.track() + Path.Log.track() self.updateUI() self.form.toolName.editingFinished.connect(self.refresh) diff --git a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py index 77b1157829..2a2bf7cff9 100644 --- a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py +++ b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py @@ -27,7 +27,6 @@ import FreeCAD import FreeCADGui import Path import PathScripts -import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathToolBitLibraryCmd as PathToolBitLibraryCmd import PathScripts.PathToolEdit as PathToolEdit @@ -36,10 +35,10 @@ import PathScripts.PathUtils as PathUtils if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -287,7 +286,7 @@ class EditorPanel: for toolnum in tools: tool = self.TLM.getTool(currList, int(toolnum)) - PathLog.debug("tool: {}, toolnum: {}".format(tool, toolnum)) + Path.Log.debug("tool: {}, toolnum: {}".format(tool, toolnum)) if self.job: label = "T{}: {}".format(toolnum, tool.Name) tc = PathScripts.PathToolController.Create( diff --git a/src/Mod/Path/PathScripts/PathToolLibraryManager.py b/src/Mod/Path/PathScripts/PathToolLibraryManager.py index 36c298c821..c533f30dc9 100644 --- a/src/Mod/Path/PathScripts/PathToolLibraryManager.py +++ b/src/Mod/Path/PathScripts/PathToolLibraryManager.py @@ -25,7 +25,6 @@ from __future__ import print_function import FreeCAD import Path import PathScripts -import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import json import os @@ -33,10 +32,10 @@ import xml.sax from PySide import QtGui if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -217,10 +216,10 @@ class ToolLibraryManager: """renames a tool table with the new name""" currentTableName = self.toolTables[index].Name if newName == currentTableName: - PathLog.error(translate("PathToolLibraryManager", "Tool Table Same Name")) + Path.Log.error(translate("PathToolLibraryManager", "Tool Table Same Name")) return False if newName in self.toolTables: - PathLog.error(translate("PathToolLibraryManager", "Tool Table Name Exists")) + Path.Log.error(translate("PathToolLibraryManager", "Tool Table Name Exists")) return False tt = self.getTableFromName(currentTableName) if tt: @@ -275,7 +274,7 @@ class ToolLibraryManager: return tt else: - PathLog.error( + Path.Log.error( translate( "PathToolLibraryManager", "Unsupported Path tooltable template version %s", @@ -293,7 +292,7 @@ class ToolLibraryManager: if tt: self.toolTables.append(tt) else: - PathLog.error( + Path.Log.error( translate("PathToolLibraryManager", "Unsupported Path tooltable") ) diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py index 19657f6d83..27835f4c07 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/PathScripts/PathUtil.py @@ -27,20 +27,20 @@ PathUtils depends on PathJob. Which makes it impossible to use the functions and classes defined there in PathJob. So if you add to this file and think about importing anything from PathScripts -other than PathLog, then it probably doesn't belong here. +other than Path.Log, then it probably doesn't belong here. """ import FreeCAD import six -import PathScripts.PathLog as PathLog +import Path translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def _getProperty(obj, prop): @@ -54,13 +54,13 @@ def _getProperty(obj, prop): attr = getattr(o, name) if o == attr: - PathLog.warning( + Path.Log.warning( translate("PathGui", "%s has no property %s (%s))") % (obj.Label, prop, name) ) return (None, None, None) - # PathLog.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr)) + # Path.Log.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr)) return (o, attr, name) @@ -98,18 +98,18 @@ def isValidBaseObject(obj): """isValidBaseObject(obj) ... returns true if the object can be used as a base for a job.""" if hasattr(obj, "getParentGeoFeatureGroup") and obj.getParentGeoFeatureGroup(): # Can't link to anything inside a geo feature group anymore - PathLog.debug("%s is inside a geo feature group" % obj.Label) + Path.Log.debug("%s is inside a geo feature group" % obj.Label) return False if hasattr(obj, "BitBody") and hasattr(obj, "BitShape"): # ToolBit's are not valid base objects return False if obj.TypeId in NotValidBaseTypeIds: - PathLog.debug("%s is blacklisted (%s)" % (obj.Label, obj.TypeId)) + Path.Log.debug("%s is blacklisted (%s)" % (obj.Label, obj.TypeId)) return False if hasattr(obj, "Sheets") or hasattr( obj, "TagText" ): # Arch.Panels and Arch.PanelCut - PathLog.debug("%s is not an Arch.Panel" % (obj.Label)) + Path.Log.debug("%s is not an Arch.Panel" % (obj.Label)) return False import Part diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index fa46905689..71f191b154 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -23,7 +23,6 @@ import FreeCAD from FreeCAD import Vector -from PathScripts import PathLog from PySide import QtCore from PySide import QtGui import Path @@ -43,10 +42,10 @@ translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) UserInput = None @@ -99,7 +98,7 @@ def loopdetect(obj, edge1, edge2): edge2 = edge """ - PathLog.track() + Path.Log.track() candidates = [] for wire in obj.Shape.Wires: for e in wire.Edges: @@ -184,7 +183,7 @@ def horizontalFaceLoop(obj, face, faceList=None): def filterArcs(arcEdge): """filterArcs(Edge) -used to split an arc that is over 180 degrees. Returns list""" - PathLog.track() + Path.Log.track() splitlist = [] if isinstance(arcEdge.Curve, Part.Circle): angle = abs(arcEdge.LastParameter - arcEdge.FirstParameter) # Angle in radians @@ -225,7 +224,7 @@ def makeWorkplane(shape): """ Creates a workplane circle at the ZMin level. """ - PathLog.track() + Path.Log.track() loc = Vector(shape.BoundBox.Center.x, shape.BoundBox.Center.y, shape.BoundBox.ZMin) c = Part.makeCircle(10, loc) return c @@ -240,20 +239,20 @@ def getEnvelope(partshape, subshape=None, depthparams=None): partshape = solid object stockheight = float - Absolute Z height of the top of material before cutting. """ - PathLog.track(partshape, subshape, depthparams) + Path.Log.track(partshape, subshape, depthparams) zShift = 0 if subshape is not None: if isinstance(subshape, Part.Face): - PathLog.debug("processing a face") + Path.Log.debug("processing a face") sec = Part.makeCompound([subshape]) else: area = Path.Area(Fill=2, Coplanar=0).add(subshape) area.setPlane(makeWorkplane(partshape)) - PathLog.debug("About to section with params: {}".format(area.getParams())) + Path.Log.debug("About to section with params: {}".format(area.getParams())) sec = area.makeSections(heights=[0.0], project=True)[0].getShape() - PathLog.debug( + Path.Log.debug( "partshapeZmin: {}, subshapeZMin: {}, zShift: {}".format( partshape.BoundBox.ZMin, subshape.BoundBox.ZMin, zShift ) @@ -269,7 +268,7 @@ def getEnvelope(partshape, subshape=None, depthparams=None): if depthparams is not None: eLength = depthparams.safe_height - depthparams.final_depth zShift = depthparams.final_depth - sec.BoundBox.ZMin - PathLog.debug( + Path.Log.debug( "boundbox zMIN: {} elength: {} zShift {}".format( partshape.BoundBox.ZMin, eLength, zShift ) @@ -283,7 +282,7 @@ def getEnvelope(partshape, subshape=None, depthparams=None): # Extrude the section to top of Boundbox or desired height envelopeshape = sec.extrude(Vector(0, 0, eLength)) - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: + if Path.Log.getLevel(Path.Log.thisModule()) == Path.Log.Level.DEBUG: removalshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "Envelope") removalshape.Shape = envelopeshape return envelopeshape @@ -303,7 +302,7 @@ def getOffsetArea( Inspired by _buildPathArea() from PathAreaOp.py module. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.""" - PathLog.debug("getOffsetArea()") + Path.Log.debug("getOffsetArea()") areaParams = {} areaParams["Offset"] = offset @@ -363,7 +362,7 @@ def getToolControllers(obj, proxy=None): except Exception: job = None - PathLog.debug("op={} ({})".format(obj.Label, type(obj))) + Path.Log.debug("op={} ({})".format(obj.Label, type(obj))) if job: return [tc for tc in job.Tools.Group if proxy.isToolSupported(obj, tc.Tool)] return [] @@ -374,7 +373,7 @@ def findToolController(obj, proxy, name=None): If no name is specified, returns the first controller. if no controller is found, returns None""" - PathLog.track("name: {}".format(name)) + Path.Log.track("name: {}".format(name)) c = None if UserInput: c = UserInput.selectedToolController() @@ -401,7 +400,7 @@ def findToolController(obj, proxy, name=None): def findParentJob(obj): """retrieves a parent job object for an operation or other Path object""" - PathLog.track() + Path.Log.track() for i in obj.InList: if hasattr(i, "Proxy") and isinstance(i.Proxy, PathJob.ObjectJob): return i @@ -427,7 +426,7 @@ def addToJob(obj, jobname=None): """adds a path object to a job obj = obj jobname = None""" - PathLog.track(jobname) + Path.Log.track(jobname) job = None if jobname is not None: @@ -435,7 +434,7 @@ def addToJob(obj, jobname=None): if len(jobs) == 1: job = jobs[0] else: - PathLog.error(translate("Path", "Didn't find job {}".format(jobname))) + Path.Log.error(translate("Path", "Didn't find job {}".format(jobname))) return None else: jobs = GetJobs() @@ -543,17 +542,17 @@ def drillTipLength(tool): """returns the length of the drillbit tip.""" if isinstance(tool, Path.Tool): - PathLog.error(translate("Path", "Legacy Tools not supported")) + Path.Log.error(translate("Path", "Legacy Tools not supported")) return 0.0 if not hasattr(tool, "TipAngle"): - PathLog.error(translate("Path", "Selected tool is not a drill")) + Path.Log.error(translate("Path", "Selected tool is not a drill")) return 0.0 angle = tool.TipAngle if angle <= 0 or angle >= 180: - PathLog.error( + Path.Log.error( translate("Path", "Invalid Cutting Edge Angle %.2f, must be >0° and <=180°") % angle ) @@ -563,7 +562,7 @@ def drillTipLength(tool): length = (float(tool.Diameter) / 2) / math.tan(theta / 2) if length < 0: - PathLog.error( + Path.Log.error( translate( "Path", "Cutting Edge Angle (%.2f) results in negative tool tip length" ) diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index 98a70a47af..c9387d9948 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -22,18 +22,18 @@ import FreeCADGui import FreeCAD +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils from PySide import QtGui -import PathScripts.PathLog as PathLog if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 31b5377ced..70cdad768c 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -24,7 +24,6 @@ import FreeCAD import Part import Path import PathScripts.PathEngraveBase as PathEngraveBase -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils import PathScripts.PathGeom as PathGeom @@ -46,10 +45,10 @@ TWIN = 5 BORDERLINE = 6 if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -181,7 +180,7 @@ def _calculate_depth(MIC, geom): # given a maximum inscribed circle (MIC) and tool angle, # return depth of cut relative to zStart. depth = geom.start - round(MIC * geom.scale, 4) - PathLog.debug("zStart value: {} depth: {}".format(geom.start, depth)) + Path.Log.debug("zStart value: {} depth: {}".format(geom.start, depth)) return max(depth, geom.stop) @@ -264,7 +263,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): def insert_many_wires(vd, wires): for wire in wires: - PathLog.debug("discretize value: {}".format(obj.Discretize)) + Path.Log.debug("discretize value: {}".format(obj.Discretize)) pts = wire.discretize(QuasiDeflection=obj.Discretize) ptv = [FreeCAD.Vector(p.x, p.y) for p in pts] ptv.append(ptv[0]) @@ -336,10 +335,10 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): def opExecute(self, obj): """opExecute(obj) ... process engraving operation""" - PathLog.track() + Path.Log.track() if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): - PathLog.error( + Path.Log.error( translate( "Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle", @@ -347,7 +346,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): ) if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: - PathLog.error( + Path.Log.error( translate( "Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees." ) @@ -376,7 +375,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): if faces: self.buildPathMedial(obj, faces) else: - PathLog.error( + Path.Log.error( translate( "PathVcarve", "The Job Base Object has no engraveable element. Engraving operation will produce no output.", @@ -384,7 +383,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): ) except Exception as e: - PathLog.error( + Path.Log.error( "Error processing Base object. Engraving operation will produce no output." ) diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index c044763d71..c6eb6fc65c 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -22,9 +22,9 @@ import FreeCAD import FreeCADGui +import Path import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathVcarve as PathVcarve -import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui @@ -37,10 +37,10 @@ __doc__ = "Vcarve operation page controller and command implementation." if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate @@ -52,14 +52,14 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): return super(TaskPanelBaseGeometryPage, self) def addBaseGeometry(self, selection): - PathLog.track(selection) + Path.Log.track(selection) added = False shapes = self.obj.BaseShapes for sel in selection: job = PathUtils.findParentJob(self.obj) base = job.Proxy.resourceClone(job, sel.Object) if not base: - PathLog.notice( + Path.Log.notice( ( translate("Path", "%s is not a Base Model object of the job %s") + "\n" @@ -68,7 +68,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): ) continue if base in shapes: - PathLog.notice( + Path.Log.notice( "Base shape %s already in the list".format(sel.Object.Label) ) continue @@ -77,7 +77,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): # selectively add some elements of the drawing to the Base for sub in sel.SubElementNames: if "Vertex" in sub: - PathLog.info("Ignoring vertex") + Path.Log.info("Ignoring vertex") else: self.obj.Proxy.addBase(self.obj, base, sub) else: @@ -89,7 +89,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): if not added: # user wants us to engrave an edge of face of a base model - PathLog.info(" call default") + Path.Log.info(" call default") base = self.super().addBaseGeometry(selection) added = added or base @@ -106,7 +106,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): self.form.baseList.blockSignals(False) def updateBase(self): - PathLog.track() + Path.Log.track() shapes = [] for i in range(self.form.baseList.count()): item = self.form.baseList.item(i) @@ -114,7 +114,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): sub = item.data(self.super().DataObjectSub) if not sub: shapes.append(obj) - PathLog.debug( + Path.Log.debug( "Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes) ) self.obj.BaseShapes = shapes diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 83db68725c..ddce93d22a 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -43,7 +43,6 @@ except ImportError: raise ImportError import Path -import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathSurfaceSupport as PathSurfaceSupport import PathScripts.PathUtils as PathUtils @@ -60,10 +59,10 @@ if FreeCAD.GuiUp: import FreeCADGui if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class ObjectWaterline(PathOp.ObjectOp): @@ -145,11 +144,11 @@ class ObjectWaterline(PathOp.ObjectOp): data = list() idx = 0 if dataType == "translated" else 1 - PathLog.debug(enums) + Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) - PathLog.debug(data) + Path.Log.debug(data) return data @@ -161,7 +160,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.initOpProperties(obj) # Initialize operation-specific properties # For debugging - if PathLog.getLevel(PathLog.thisModule()) != 4: + if Path.Log.getLevel(Path.Log.thisModule()) != 4: obj.setEditorMode("ShowTempObjects", 2) # hide if not hasattr(obj, "DoNotSetDefaultValues"): @@ -575,7 +574,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.initOpProperties(obj, warn=True) self.opApplyPropertyDefaults(obj, job, self.addNewProps) - mode = 2 if PathLog.getLevel(PathLog.thisModule()) != 4 else 0 + mode = 2 if Path.Log.getLevel(Path.Log.thisModule()) != 4 else 0 obj.setEditorMode("ShowTempObjects", mode) # Repopulate enumerations in case of changes @@ -620,11 +619,11 @@ class ObjectWaterline(PathOp.ObjectOp): if job.Stock: d = PathUtils.guessDepths(job.Stock.Shape, None) obj.IgnoreOuterAbove = job.Stock.Shape.BoundBox.ZMax + 0.000001 - PathLog.debug("job.Stock exists") + Path.Log.debug("job.Stock exists") else: - PathLog.debug("job.Stock NOT exist") + Path.Log.debug("job.Stock NOT exist") else: - PathLog.debug("job NOT exist") + Path.Log.debug("job NOT exist") if d is not None: obj.OpFinalDepth.Value = d.final_depth @@ -633,15 +632,15 @@ class ObjectWaterline(PathOp.ObjectOp): obj.OpFinalDepth.Value = -10 obj.OpStartDepth.Value = 10 - PathLog.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) - PathLog.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) + Path.Log.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) + Path.Log.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) def opApplyPropertyLimits(self, obj): """opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.""" # Limit sample interval if obj.SampleInterval.Value < 0.0001: obj.SampleInterval.Value = 0.0001 - PathLog.error( + Path.Log.error( translate( "PathWaterline", "Sample interval limits are 0.0001 to 25.4 millimeters.", @@ -649,7 +648,7 @@ class ObjectWaterline(PathOp.ObjectOp): ) if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 - PathLog.error( + Path.Log.error( translate( "PathWaterline", "Sample interval limits are 0.0001 to 25.4 millimeters.", @@ -659,14 +658,14 @@ class ObjectWaterline(PathOp.ObjectOp): # Limit cut pattern angle if obj.CutPatternAngle < -360.0: obj.CutPatternAngle = 0.0 - PathLog.error( + Path.Log.error( translate( "PathWaterline", "Cut pattern angle limits are +-360 degrees." ) ) if obj.CutPatternAngle >= 360.0: obj.CutPatternAngle = 0.0 - PathLog.error( + Path.Log.error( translate( "PathWaterline", "Cut pattern angle limits are +- 360 degrees." ) @@ -681,7 +680,7 @@ class ObjectWaterline(PathOp.ObjectOp): # Limit AvoidLastX_Faces to zero and positive values if obj.AvoidLastX_Faces < 0: obj.AvoidLastX_Faces = 0 - PathLog.error( + Path.Log.error( translate( "PathWaterline", "AvoidLastX_Faces: Only zero or positive values permitted.", @@ -689,7 +688,7 @@ class ObjectWaterline(PathOp.ObjectOp): ) if obj.AvoidLastX_Faces > 100: obj.AvoidLastX_Faces = 100 - PathLog.error( + Path.Log.error( translate( "PathWaterline", "AvoidLastX_Faces: Avoid last X faces count limited to 100.", @@ -707,7 +706,7 @@ class ObjectWaterline(PathOp.ObjectOp): fbb = base.Shape.getElement(sub).BoundBox zmin = min(zmin, fbb.ZMin) except Part.OCCError as e: - PathLog.error(e) + Path.Log.error(e) obj.OpFinalDepth = zmin elif self.job: if hasattr(obj, "BoundBox"): @@ -723,7 +722,7 @@ class ObjectWaterline(PathOp.ObjectOp): def opExecute(self, obj): """opExecute(obj) ... process surface operation""" - PathLog.track() + Path.Log.track() self.modelSTLs = list() self.safeSTLs = list() @@ -756,19 +755,19 @@ class ObjectWaterline(PathOp.ObjectOp): self.showDebugObjects = False # Set to true if you want a visual DocObjects created for some path construction objects self.showDebugObjects = obj.ShowTempObjects deleteTempsFlag = True # Set to False for debugging - if PathLog.getLevel(PathLog.thisModule()) == 4: + if Path.Log.getLevel(Path.Log.thisModule()) == 4: deleteTempsFlag = False else: self.showDebugObjects = False # mark beginning of operation and identify parent Job - PathLog.info("\nBegin Waterline operation...") + Path.Log.info("\nBegin Waterline operation...") startTime = time.time() # Identify parent Job JOB = PathUtils.findParentJob(obj) if JOB is None: - PathLog.error(translate("PathWaterline", "No JOB")) + Path.Log.error(translate("PathWaterline", "No JOB")) return self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin @@ -788,7 +787,7 @@ class ObjectWaterline(PathOp.ObjectOp): oclTool = PathSurfaceSupport.OCL_Tool(ocl, obj) self.cutter = oclTool.getOclTool() if not self.cutter: - PathLog.error( + Path.Log.error( translate( "PathWaterline", "Canceling Waterline operation. Error creating OCL cutter.", @@ -871,7 +870,7 @@ class ObjectWaterline(PathOp.ObjectOp): if self.geoTlrnc == 0.0: useDGT = True except AttributeError as ee: - PathLog.warning( + Path.Log.warning( "{}\nPlease set Job.GeometryTolerance to an acceptable value. Using PathPreferences.defaultGeometryTolerance().".format( ee ) @@ -929,7 +928,7 @@ class ObjectWaterline(PathOp.ObjectOp): pPM = PSF.preProcessModel(self.module) # Process selected faces, if available if pPM is False: - PathLog.error("Unable to pre-process obj.Base.") + Path.Log.error("Unable to pre-process obj.Base.") else: (FACES, VOIDS) = pPM self.modelSTLs = PSF.modelSTLs @@ -942,7 +941,7 @@ class ObjectWaterline(PathOp.ObjectOp): Mdl = JOB.Model.Group[m] if FACES[m] is False: - PathLog.error( + Path.Log.error( "No data for model base: {}".format(JOB.Model.Group[m].Label) ) else: @@ -959,7 +958,7 @@ class ObjectWaterline(PathOp.ObjectOp): {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}, ) ) - PathLog.info( + Path.Log.info( "Working on Model.Group[{}]: {}".format(m, Mdl.Label) ) # make stock-model-voidShapes STL model for avoidance detection on transitions @@ -1038,7 +1037,7 @@ class ObjectWaterline(PathOp.ObjectOp): execTime = time.time() - startTime msg = translate("PathWaterline", "operation time is") - PathLog.info("Waterline " + msg + " {} sec.".format(execTime)) + Path.Log.info("Waterline " + msg + " {} sec.".format(execTime)) return True @@ -1047,7 +1046,7 @@ class ObjectWaterline(PathOp.ObjectOp): """_processWaterlineAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. It then calls the correct method.""" - PathLog.debug("_processWaterlineAreas()") + Path.Log.debug("_processWaterlineAreas()") final = list() @@ -1111,7 +1110,7 @@ class ObjectWaterline(PathOp.ObjectOp): """_getExperimentalWaterlinePaths(PNTSET, csHght, cutPattern)... Switching function for calling the appropriate path-geometry to OCL points conversion function for the various cut patterns.""" - PathLog.debug("_getExperimentalWaterlinePaths()") + Path.Log.debug("_getExperimentalWaterlinePaths()") SCANS = list() # PNTSET is list, by stepover. @@ -1300,7 +1299,7 @@ class ObjectWaterline(PathOp.ObjectOp): pntsPerLine = len(scanLines[0]) msg = "--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " msg += str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line" - PathLog.debug(msg) + Path.Log.debug(msg) # Extract Wl layers per depthparams lyr = 0 @@ -1311,7 +1310,7 @@ class ObjectWaterline(PathOp.ObjectOp): cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) commands.extend(cmds) lyr += 1 - PathLog.debug( + Path.Log.debug( "--All layer scans combined took " + str(time.time() - layTime) + " s" ) return commands @@ -1581,7 +1580,7 @@ class ObjectWaterline(PathOp.ObjectOp): while srch is True: srch = False if srchCnt > maxSrchs: - PathLog.debug( + Path.Log.debug( "Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!" @@ -1597,7 +1596,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.topoMap[L][P] = 0 # Mute the starting point loopList.append(loop) srchCnt += 1 - PathLog.debug( + Path.Log.debug( "Search count for layer " + str(lyr) + " is " @@ -1620,7 +1619,7 @@ class ObjectWaterline(PathOp.ObjectOp): while follow is True: ptc += 1 if ptc > ptLmt: - PathLog.debug( + Path.Log.debug( "Loop number " + str(loopNum) + " at [" @@ -1729,7 +1728,7 @@ class ObjectWaterline(PathOp.ObjectOp): def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): """_waterlineOp(JOB, obj, mdlIdx, subShp=None) ... Main waterline function to perform waterline extraction from model.""" - PathLog.debug("_experimentalWaterlineOp()") + Path.Log.debug("_experimentalWaterlineOp()") commands = [] base = JOB.Model.Group[mdlIdx] @@ -1751,7 +1750,7 @@ class ObjectWaterline(PathOp.ObjectOp): depthparams = [finDep] else: depthparams = [dp for dp in depthParams] - PathLog.debug("Experimental Waterline depthparams:\n{}".format(depthparams)) + Path.Log.debug("Experimental Waterline depthparams:\n{}".format(depthparams)) # Prepare PathDropCutter objects with STL data # safePDC = self._planarGetPDC(safeSTL, depthparams[lenDP - 1], obj.SampleInterval.Value, self.cutter) @@ -1777,7 +1776,7 @@ class ObjectWaterline(PathOp.ObjectOp): base.Shape, depthparams, bbFace, trimFace, borderFace ) if not CUTAREAS: - PathLog.error("No cross-section cut areas identified.") + Path.Log.error("No cross-section cut areas identified.") return commands caCnt = 0 @@ -1799,7 +1798,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.showDebugObject(area, "CutArea_{}".format(caCnt)) else: data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString - PathLog.debug("Cut area at {} is zero.".format(data)) + Path.Log.debug("Cut area at {} is zero.".format(data)) # get offset wire(s) based upon cross-section cut area if cont: @@ -1811,7 +1810,7 @@ class ObjectWaterline(PathOp.ObjectOp): data = FreeCAD.Units.Quantity( csHght, FreeCAD.Units.Length ).UserString - PathLog.debug( + Path.Log.debug( "No offset area returned for cut area depth at {}.".format(data) ) cont = False @@ -1828,7 +1827,7 @@ class ObjectWaterline(PathOp.ObjectOp): data = FreeCAD.Units.Quantity( csHght, FreeCAD.Units.Length ).UserString - PathLog.error( + Path.Log.error( "Could not determine solid faces at {}.".format(data) ) else: @@ -1836,7 +1835,7 @@ class ObjectWaterline(PathOp.ObjectOp): if cont: data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString - PathLog.debug("... Clearning area at {}.".format(data)) + Path.Log.debug("... Clearning area at {}.".format(data)) # Make waterline path for current CUTAREA depth (csHght) commands.extend(self._wiresToWaterlinePath(obj, clearArea, csHght)) clearArea.translate( @@ -1863,7 +1862,7 @@ class ObjectWaterline(PathOp.ObjectOp): # Efor if clearLastLayer and obj.ClearLastLayer != "Off": - PathLog.debug("... Clearning last layer") + Path.Log.debug("... Clearning last layer") (clrLyr, cLL) = self._clearLayer(obj, 1, 1, False) lastClearArea.translate( FreeCAD.Vector(0.0, 0.0, 0.0 - lastClearArea.BoundBox.ZMin) @@ -1885,7 +1884,7 @@ class ObjectWaterline(PathOp.ObjectOp): """_getCutAreas(JOB, shape, depthparams, bbFace, borderFace) ... Takes shape, depthparams and base-envelope-cross-section, and returns a list of cut areas - one for each depth.""" - PathLog.debug("_getCutAreas()") + Path.Log.debug("_getCutAreas()") CUTAREAS = list() isFirst = True @@ -1894,7 +1893,7 @@ class ObjectWaterline(PathOp.ObjectOp): # Cycle through layer depths for dp in range(0, lenDP): csHght = depthparams[dp] - # PathLog.debug('Depth {} is {}'.format(dp + 1, csHght)) + # Path.Log.debug('Depth {} is {}'.format(dp + 1, csHght)) # Get slice at depth of shape csFaces = self._getModelCrossSection(shape, csHght) # returned at Z=0.0 @@ -1922,7 +1921,7 @@ class ObjectWaterline(PathOp.ObjectOp): CUTAREAS.append(cutArea) isFirst = False else: - PathLog.error("No waterline at depth: {} mm.".format(csHght)) + Path.Log.error("No waterline at depth: {} mm.".format(csHght)) # Efor if len(CUTAREAS) > 0: @@ -1931,7 +1930,7 @@ class ObjectWaterline(PathOp.ObjectOp): return False def _wiresToWaterlinePath(self, obj, ofstPlnrShp, csHght): - PathLog.debug("_wiresToWaterlinePath()") + Path.Log.debug("_wiresToWaterlinePath()") commands = list() # Translate path geometry to layer height @@ -1965,7 +1964,7 @@ class ObjectWaterline(PathOp.ObjectOp): return commands def _makeCutPatternLayerPaths(self, JOB, obj, clrAreaShp, csHght, cutPattern): - PathLog.debug("_makeCutPatternLayerPaths()") + Path.Log.debug("_makeCutPatternLayerPaths()") commands = [] clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clrAreaShp.BoundBox.ZMin)) @@ -1981,7 +1980,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.tmpCOM = PGG.getCenterOfPattern() pathGeom = PGG.generatePathGeometry() if not pathGeom: - PathLog.warning("No path geometry generated.") + Path.Log.warning("No path geometry generated.") return commands pathGeom.translate( FreeCAD.Vector(0.0, 0.0, csHght - pathGeom.BoundBox.ZMin) @@ -2013,7 +2012,7 @@ class ObjectWaterline(PathOp.ObjectOp): return commands def _makeOffsetLayerPaths(self, obj, clrAreaShp, csHght): - PathLog.debug("_makeOffsetLayerPaths()") + Path.Log.debug("_makeOffsetLayerPaths()") cmds = list() ofst = 0.0 - self.cutOut shape = clrAreaShp @@ -2029,14 +2028,14 @@ class ObjectWaterline(PathOp.ObjectOp): if cnt == 0: ofst = 0.0 - self.cutOut cnt += 1 - PathLog.debug( + Path.Log.debug( " -Offset path count: {} at height: {}".format(cnt, round(csHght, 2)) ) return cmds def _clearGeomToPaths(self, JOB, obj, safePDC, stpOVRS, cutPattern): - PathLog.debug("_clearGeomToPaths()") + Path.Log.debug("_clearGeomToPaths()") GCODE = [Path.Command("N (Beginning of Single-pass layer.)", {})] tolrnc = JOB.GeometryTolerance.Value @@ -2083,7 +2082,7 @@ class ObjectWaterline(PathOp.ObjectOp): # Cycle through current step-over parts for i in range(0, lenPRTS): prt = PRTS[i] - # PathLog.debug('prt: {}'.format(prt)) + # Path.Log.debug('prt: {}'.format(prt)) if prt == "BRK": nxtStart = PRTS[i + 1][0] # minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart) # Check safe travel height against fullSTL @@ -2115,7 +2114,7 @@ class ObjectWaterline(PathOp.ObjectOp): elif cutPattern in ["Circular", "CircularZigZag"]: # isCircle = True if lenPRTS == 1 else False isZigZag = True if cutPattern == "CircularZigZag" else False - PathLog.debug( + Path.Log.debug( "so, isZigZag, odd, cMode: {}, {}, {}, {}".format( so, isZigZag, odd, prt[3] ) @@ -2135,11 +2134,11 @@ class ObjectWaterline(PathOp.ObjectOp): return GCODE def _getSolidAreasFromPlanarFaces(self, csFaces): - PathLog.debug("_getSolidAreasFromPlanarFaces()") + Path.Log.debug("_getSolidAreasFromPlanarFaces()") holds = list() useFaces = list() lenCsF = len(csFaces) - PathLog.debug("lenCsF: {}".format(lenCsF)) + Path.Log.debug("lenCsF: {}".format(lenCsF)) if lenCsF == 1: useFaces = csFaces @@ -2204,7 +2203,7 @@ class ObjectWaterline(PathOp.ObjectOp): return False def _getModelCrossSection(self, shape, csHght): - PathLog.debug("_getModelCrossSection()") + Path.Log.debug("_getModelCrossSection()") wires = list() def byArea(fc): @@ -2224,7 +2223,7 @@ class ObjectWaterline(PathOp.ObjectOp): FCS.sort(key=byArea, reverse=True) return FCS else: - PathLog.debug(" -No wires from .slice() method") + Path.Log.debug(" -No wires from .slice() method") return False @@ -2256,7 +2255,7 @@ class ObjectWaterline(PathOp.ObjectOp): def _wireToPath(self, obj, wire, startVect): """_wireToPath(obj, wire, startVect) ... wire to path.""" - PathLog.track() + Path.Log.track() paths = [] pathParams = {} @@ -2336,7 +2335,7 @@ class ObjectWaterline(PathOp.ObjectOp): return cmds def _clearLayer(self, obj, ca, lastCA, clearLastLayer): - PathLog.debug("_clearLayer()") + Path.Log.debug("_clearLayer()") clrLyr = False if obj.ClearLastLayer == "Off": @@ -2345,7 +2344,7 @@ class ObjectWaterline(PathOp.ObjectOp): else: obj.CutPattern = "None" if ca == lastCA: # if current iteration is last layer - PathLog.debug("... Clearing bottom layer.") + Path.Log.debug("... Clearing bottom layer.") clrLyr = obj.ClearLastLayer clearLastLayer = False diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index ed8548051b..381c980adf 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -25,7 +25,7 @@ from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import PathScripts.PathLog as PathLog +import Path import PathScripts.PathGui as PathGui import PathScripts.PathOpGui as PathOpGui import PathScripts.PathWaterline as PathWaterline @@ -38,10 +38,10 @@ __doc__ = "Waterline operation page controller and command implementation." translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class TaskPanelOpPage(PathOpGui.TaskPanelPage): diff --git a/src/Mod/Path/PathScripts/drillableLib.py b/src/Mod/Path/PathScripts/drillableLib.py index f9c1d8f92c..ea9d4b37fa 100644 --- a/src/Mod/Path/PathScripts/drillableLib.py +++ b/src/Mod/Path/PathScripts/drillableLib.py @@ -1,14 +1,14 @@ -import PathScripts.PathLog as PathLog import FreeCAD as App import Part +import Path import numpy import math if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) def checkForBlindHole(baseshape, selectedFace): @@ -47,7 +47,7 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, matchToolDiameter = tooldiameter is not None matchVector = vector is not None - PathLog.debug( + Path.Log.debug( "\n match tool diameter {} \n match vector {}".format( matchToolDiameter, matchVector ) @@ -91,28 +91,28 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, raise TypeError("cylinder does not have 3 edges. Not supported yet") if raisedFeature(obj, candidate): - PathLog.debug("The cylindrical face is a raised feature") + Path.Log.debug("The cylindrical face is a raised feature") return False if not matchToolDiameter and not matchVector: return True if matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius: - PathLog.debug("The tool is larger than the target") + Path.Log.debug("The tool is larger than the target") return False bottomface = checkForBlindHole(obj, candidate) - PathLog.track("candidate is a blind hole") + Path.Log.track("candidate is a blind hole") if ( bottomface is not None and matchVector ): # blind holes only drillable at exact vector result = compareVecs(bottomface.normalAt(0, 0), vector, exact=True) - PathLog.track(result) + Path.Log.track(result) return result elif matchVector and not (compareVecs(getSeam(candidate).Curve.Direction, vector)): - PathLog.debug("The feature is not aligned with the given vector") + Path.Log.debug("The feature is not aligned with the given vector") return False else: return True @@ -124,32 +124,32 @@ def isDrillableFace(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1 """ matchToolDiameter = tooldiameter is not None matchVector = vector is not None - PathLog.debug( + Path.Log.debug( "\n match tool diameter {} \n match vector {}".format( matchToolDiameter, matchVector ) ) if not type(candidate.Surface) == Part.Plane: - PathLog.debug("Drilling on non-planar faces not supported") + Path.Log.debug("Drilling on non-planar faces not supported") return False if ( len(candidate.Edges) == 1 and type(candidate.Edges[0].Curve) == Part.Circle ): # Regular circular face - PathLog.debug("Face is circular - 1 edge") + Path.Log.debug("Face is circular - 1 edge") edge = candidate.Edges[0] elif ( len(candidate.Edges) == 2 and type(candidate.Edges[0].Curve) == Part.Circle and type(candidate.Edges[1].Curve) == Part.Circle ): # process a donut - PathLog.debug("Face is a donut - 2 edges") + Path.Log.debug("Face is a donut - 2 edges") e1 = candidate.Edges[0] e2 = candidate.Edges[1] edge = e1 if e1.Curve.Radius < e2.Curve.Radius else e2 else: - PathLog.debug( + Path.Log.debug( "expected a Face with one or two circular edges got a face with {} edges".format( len(candidate.Edges) ) @@ -157,13 +157,13 @@ def isDrillableFace(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1 return False if vector is not None: # Check for blind hole alignment if not compareVecs(candidate.normalAt(0, 0), vector, exact=True): - PathLog.debug("Vector not aligned") + Path.Log.debug("Vector not aligned") return False if matchToolDiameter and edge.Curve.Radius < tooldiameter / 2: - PathLog.debug("Failed diameter check") + Path.Log.debug("Failed diameter check") return False else: - PathLog.debug("Face is drillable") + Path.Log.debug("Face is drillable") return True @@ -174,7 +174,7 @@ def isDrillableEdge(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1 matchToolDiameter = tooldiameter is not None matchVector = vector is not None - PathLog.debug( + Path.Log.debug( "\n match tool diameter {} \n match vector {}".format( matchToolDiameter, matchVector ) @@ -182,22 +182,22 @@ def isDrillableEdge(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1 edge = candidate if not (isinstance(edge.Curve, Part.Circle) and edge.isClosed()): - PathLog.debug("expected a closed circular edge") + Path.Log.debug("expected a closed circular edge") return False if not hasattr(edge.Curve, "Radius"): - PathLog.debug("The Feature edge has no radius - Ellipse.") + Path.Log.debug("The Feature edge has no radius - Ellipse.") return False if not matchToolDiameter and not matchVector: return True if matchToolDiameter and tooldiameter / 2 > edge.Curve.Radius: - PathLog.debug("The tool is larger than the target") + Path.Log.debug("The tool is larger than the target") return False if matchVector and not (compareVecs(edge.Curve.Axis, vector)): - PathLog.debug("The feature is not aligned with the given vector") + Path.Log.debug("The feature is not aligned with the given vector") return False else: return True @@ -220,7 +220,7 @@ def isDrillable(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): vector=App.Vector or None """ - PathLog.debug( + Path.Log.debug( "obj: {} candidate: {} tooldiameter {} vector {}".format( obj, candidate, tooldiameter, vector ) @@ -247,7 +247,7 @@ def isDrillable(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)): return False except TypeError as e: - PathLog.debug(e) + Path.Log.debug(e) return False # raise TypeError("{}".format(e)) @@ -261,7 +261,7 @@ def compareVecs(vec1, vec2, exact=False): angle = vec1.getAngle(vec2) angle = 0 if math.isnan(angle) else math.degrees(angle) - PathLog.debug("vector angle: {}".format(angle)) + Path.Log.debug("vector angle: {}".format(angle)) if exact: return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) else: @@ -285,7 +285,7 @@ def getDrillableTargets(obj, ToolDiameter=None, vector=App.Vector(0, 0, 1)): results = [] for i in range(1, len(shp.Faces)): fname = "Face{}".format(i) - PathLog.debug(fname) + Path.Log.debug(fname) candidate = obj.getSubObject(fname) if not isinstance(candidate.Surface, Part.Cylinder): @@ -295,9 +295,9 @@ def getDrillableTargets(obj, ToolDiameter=None, vector=App.Vector(0, 0, 1)): drillable = isDrillable( shp, candidate, tooldiameter=ToolDiameter, vector=vector ) - PathLog.debug("fname: {} : drillable {}".format(fname, drillable)) + Path.Log.debug("fname: {} : drillable {}".format(fname, drillable)) except Exception as e: - PathLog.debug(e) + Path.Log.debug(e) continue if drillable: diff --git a/src/Mod/Path/PathScripts/post/dxf_post.py b/src/Mod/Path/PathScripts/post/dxf_post.py index 91901e6be1..16e25ada60 100644 --- a/src/Mod/Path/PathScripts/post/dxf_post.py +++ b/src/Mod/Path/PathScripts/post/dxf_post.py @@ -22,12 +22,11 @@ # ***************************************************************************/ from __future__ import print_function import FreeCAD -import datetime -import PathScripts.PathGeom as PathGeom import Part -import importDXF import Path -import PathScripts.PathLog as PathLog +import PathScripts.PathGeom as PathGeom +import datetime +import importDXF TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to @@ -52,11 +51,11 @@ now = datetime.datetime.now() OUTPUT_HEADER = True if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) # to distinguish python built-in open function from the one declared below @@ -103,7 +102,7 @@ def parse(pathobj): # Gotta start somewhere. Assume 0,0,0 curPoint = FreeCAD.Vector(0, 0, 0) for c in pathobj.Path.Commands: - PathLog.debug("{} -> {}".format(curPoint, c)) + Path.Log.debug("{} -> {}".format(curPoint, c)) if "Z" in c.Parameters: newparams = c.Parameters newparams.pop("Z", None) @@ -114,7 +113,7 @@ def parse(pathobj): # ignore gcode that isn't moving if flatcommand.Name not in feedcommands + rapidcommands: - PathLog.debug("non move") + Path.Log.debug("non move") continue # ignore pure vertical feed and rapid @@ -122,13 +121,13 @@ def parse(pathobj): flatcommand.Parameters.get("X", curPoint.x) == curPoint.x and flatcommand.Parameters.get("Y", curPoint.y) == curPoint.y ): - PathLog.debug("vertical") + Path.Log.debug("vertical") continue # feeding move. Build an edge if flatcommand.Name in feedcommands: edges.append(PathGeom.edgeForCmd(flatcommand, curPoint)) - PathLog.debug("feeding move") + Path.Log.debug("feeding move") # update the curpoint curPoint.x = flatcommand.Parameters.get("X", curPoint.x) diff --git a/src/Mod/Path/PathScripts/post/example_pre.py b/src/Mod/Path/PathScripts/post/example_pre.py index 9ac769a519..8c2a0a599c 100644 --- a/src/Mod/Path/PathScripts/post/example_pre.py +++ b/src/Mod/Path/PathScripts/post/example_pre.py @@ -32,17 +32,16 @@ Read the Path Workbench documentation to know how to create Path objects from GCode. """ -import os -import Path import FreeCAD -import PathScripts.PathLog as PathLog +import Path +import os -# LEVEL = PathLog.Level.DEBUG -LEVEL = PathLog.Level.INFO -PathLog.setLevel(LEVEL, PathLog.thisModule()) +# LEVEL = Path.Log.Level.DEBUG +LEVEL = Path.Log.Level.INFO +Path.Log.setLevel(LEVEL, Path.Log.thisModule()) -if LEVEL == PathLog.Level.DEBUG: - PathLog.trackModule(PathLog.thisModule()) +if LEVEL == Path.Log.Level.DEBUG: + Path.Log.trackModule(Path.Log.thisModule()) # to distinguish python built-in open function from the one declared below @@ -52,7 +51,7 @@ if open.__module__ in ["__builtin__", "io"]: def open(filename): "called when freecad opens a file." - PathLog.track(filename) + Path.Log.track(filename) docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) insert(filename, doc.Name) @@ -60,7 +59,7 @@ def open(filename): def insert(filename, docname): "called when freecad imports a file" - PathLog.track(filename) + Path.Log.track(filename) gfile = pythonopen(filename) gcode = gfile.read() gfile.close() @@ -74,7 +73,7 @@ def insert(filename, docname): def parse(inputstring): "parse(inputstring): returns a parsed output string" print("preprocessing...") - PathLog.track(inputstring) + Path.Log.track(inputstring) # split the input by line lines = inputstring.split("\n") output = [] diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index 508b8aed12..6cac633bd3 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -42,10 +42,10 @@ Read the Path Workbench documentation to know how to create Path objects from GCode. """ -import os import FreeCAD +import Path import PathScripts.PathUtils as PathUtils -import PathScripts.PathLog as PathLog +import os import re from PySide.QtCore import QT_TRANSLATE_NOOP @@ -60,10 +60,10 @@ translate = FreeCAD.Qt.translate if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class PathNoActiveDocumentException(Exception): @@ -87,7 +87,7 @@ if open.__module__ in ["__builtin__", "io"]: def open(filename): """called when freecad opens a file.""" - PathLog.track(filename) + Path.Log.track(filename) docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) insert(filename, doc.Name) @@ -140,7 +140,7 @@ def parse(inputstring): axis = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"] FreeCAD.Console.PrintMessage("preprocessing...\n") - PathLog.track(inputstring) + Path.Log.track(inputstring) # split the input by line lines = inputstring.splitlines() output = [] @@ -183,7 +183,7 @@ def parse(inputstring): def _identifygcodeByToolNumberList(filename): """called when freecad imports a file""" - PathLog.track(filename) + Path.Log.track(filename) gcodeByToolNumberList = [] gfile = pythonopen(filename) @@ -217,16 +217,16 @@ def _identifygcodeByToolNumberList(filename): def insert(filename, docname=None): """called when freecad imports a file""" - PathLog.track(filename) + Path.Log.track(filename) try: if not _isImportEnvironmentReady(): return except PathNoActiveDocumentException: - PathLog.error(translate("Path_Gcode_pre", "No active document")) + Path.Log.error(translate("Path_Gcode_pre", "No active document")) return except PathNoJobException: - PathLog.error(translate("Path_Gcode_pre", "No job object")) + Path.Log.error(translate("Path_Gcode_pre", "No job object")) return # Create a Custom operation for each gcode-toolNumber pair diff --git a/src/Mod/Path/PathTests/TestCentroidPost.py b/src/Mod/Path/PathTests/TestCentroidPost.py index c77f133c88..5da421b0f9 100644 --- a/src/Mod/Path/PathTests/TestCentroidPost.py +++ b/src/Mod/Path/PathTests/TestCentroidPost.py @@ -25,15 +25,13 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import centroid_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestCentroidPost(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestGrblPost.py b/src/Mod/Path/PathTests/TestGrblPost.py index 24ff17a9af..a3d1211f0b 100644 --- a/src/Mod/Path/PathTests/TestGrblPost.py +++ b/src/Mod/Path/PathTests/TestGrblPost.py @@ -22,16 +22,14 @@ import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from importlib import reload from PathScripts.post import grbl_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestGrblPost(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestLinuxCNCPost.py b/src/Mod/Path/PathTests/TestLinuxCNCPost.py index 003865ba00..32a2c44988 100644 --- a/src/Mod/Path/PathTests/TestLinuxCNCPost.py +++ b/src/Mod/Path/PathTests/TestLinuxCNCPost.py @@ -22,16 +22,13 @@ import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from importlib import reload from PathScripts.post import linuxcnc_post as postprocessor - -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestLinuxCNCPost(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestMach3Mach4Post.py b/src/Mod/Path/PathTests/TestMach3Mach4Post.py index 3902734e06..d63b947a90 100644 --- a/src/Mod/Path/PathTests/TestMach3Mach4Post.py +++ b/src/Mod/Path/PathTests/TestMach3Mach4Post.py @@ -25,15 +25,13 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import mach3_mach4_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestMach3Mach4Post(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index f21bd569b3..06fb2d31f7 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -22,11 +22,10 @@ import Path import PathScripts.PathDeburr as PathDeburr -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) class TestPathDeburr(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestPathDrillGenerator.py b/src/Mod/Path/PathTests/TestPathDrillGenerator.py index f3c94bac61..0f71f05379 100644 --- a/src/Mod/Path/PathTests/TestPathDrillGenerator.py +++ b/src/Mod/Path/PathTests/TestPathDrillGenerator.py @@ -20,15 +20,14 @@ # * * # *************************************************************************** -import Path import FreeCAD import Generators.drill_generator as generator -import PathScripts.PathLog as PathLog -import PathTests.PathTestUtils as PathTestUtils import Part +import Path +import PathTests.PathTestUtils as PathTestUtils -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestPathDrillGenerator(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestPathDrillable.py b/src/Mod/Path/PathTests/TestPathDrillable.py index 24a6549841..67b5f86743 100644 --- a/src/Mod/Path/PathTests/TestPathDrillable.py +++ b/src/Mod/Path/PathTests/TestPathDrillable.py @@ -20,18 +20,17 @@ # * * # *************************************************************************** -# import Path import FreeCAD as App -import PathScripts.PathLog as PathLog +import Path import PathTests.PathTestUtils as PathTestUtils import PathScripts.drillableLib as drillableLib if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) + Path.Log.trackModule(Path.Log.thisModule()) else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class TestPathDrillable(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestPathHelix.py b/src/Mod/Path/PathTests/TestPathHelix.py index c3512571d7..c5e1fab693 100644 --- a/src/Mod/Path/PathTests/TestPathHelix.py +++ b/src/Mod/Path/PathTests/TestPathHelix.py @@ -22,13 +22,13 @@ import Draft import FreeCAD +import Path import PathScripts.PathHelix as PathHelix import PathScripts.PathJob as PathJob -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) class TestPathHelix(PathTestUtils.PathTestBase): @@ -78,7 +78,7 @@ class TestPathHelix(PathTestUtils.PathTestBase): model = base[0] for sub in base[1]: pos = proxy.holePosition(op, model, sub) - # PathLog.track(deg, pos, pos.Length) + # Path.Log.track(deg, pos, pos.Length) self.assertRoughly( round(pos.Length / 10, 0), proxy.holeDiameter(op, model, sub) ) @@ -104,7 +104,7 @@ class TestPathHelix(PathTestUtils.PathTestBase): model = base[0] for sub in base[1]: pos = proxy.holePosition(op, model, sub) - # PathLog.track(deg, pos, pos.Length) + # Path.Log.track(deg, pos, pos.Length) self.assertRoughly( round(pos.Length / 10, 0), proxy.holeDiameter(op, model, sub) ) @@ -130,7 +130,7 @@ class TestPathHelix(PathTestUtils.PathTestBase): model = base[0] for sub in base[1]: pos = proxy.holePosition(op, model, sub) - # PathLog.track(deg, pos, pos.Length) + # Path.Log.track(deg, pos, pos.Length) self.assertRoughly( round(pos.Length / 10, 0), proxy.holeDiameter(op, model, sub) ) diff --git a/src/Mod/Path/PathTests/TestPathHelixGenerator.py b/src/Mod/Path/PathTests/TestPathHelixGenerator.py index c8d6504ca5..170552ed7d 100644 --- a/src/Mod/Path/PathTests/TestPathHelixGenerator.py +++ b/src/Mod/Path/PathTests/TestPathHelixGenerator.py @@ -23,13 +23,12 @@ import FreeCAD import Part import Path -import PathScripts.PathLog as PathLog import Generators.helix_generator as generator import PathTests.PathTestUtils as PathTestUtils -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) def _resetArgs(): diff --git a/src/Mod/Path/PathTests/TestPathLog.py b/src/Mod/Path/PathTests/TestPathLog.py index a18fe4abc1..d334feccb9 100644 --- a/src/Mod/Path/PathTests/TestPathLog.py +++ b/src/Mod/Path/PathTests/TestPathLog.py @@ -20,27 +20,27 @@ # * * # *************************************************************************** -import PathScripts.PathLog as PathLog +import Path import unittest -class TestPathLog(unittest.TestCase): +class TestPath.Log(unittest.TestCase): """Some basic tests for the logging framework.""" - MODULE = "TestPathLog" # file name without extension + MODULE = "TestPath.Log" # file name without extension def setUp(self): - PathLog.setLevel(PathLog.Level.RESET) - PathLog.untrackAllModules() + Path.Log.setLevel(Path.Log.Level.RESET) + Path.Log.untrackAllModules() def callerFile(self): - return PathLog._caller()[0] + return Path.Log._caller()[0] def callerLine(self): - return PathLog._caller()[1] + return Path.Log._caller()[1] def callerFunc(self): - return PathLog._caller()[2] + return Path.Log._caller()[2] def test00(self): """Check for proper module extraction.""" @@ -52,145 +52,145 @@ class TestPathLog(unittest.TestCase): def test10(self): """Verify default log levels is NOTICE.""" - self.assertEqual(PathLog.getLevel(), PathLog.Level.NOTICE) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.NOTICE) + self.assertEqual(Path.Log.getLevel(), Path.Log.Level.NOTICE) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.NOTICE) def test11(self): """Verify setting global log level.""" - PathLog.setLevel(PathLog.Level.DEBUG) + Path.Log.setLevel(Path.Log.Level.DEBUG) - self.assertEqual(PathLog.getLevel(), PathLog.Level.DEBUG) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.DEBUG) + self.assertEqual(Path.Log.getLevel(), Path.Log.Level.DEBUG) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.DEBUG) def test12(self): """Verify setting module log level.""" - PathLog.setLevel(PathLog.Level.DEBUG, self.MODULE) + Path.Log.setLevel(Path.Log.Level.DEBUG, self.MODULE) - self.assertEqual(PathLog.getLevel(), PathLog.Level.NOTICE) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.DEBUG) + self.assertEqual(Path.Log.getLevel(), Path.Log.Level.NOTICE) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.DEBUG) def test13(self): """Verify setting other modul's log level doesn't change this one's.""" # if this test fails then most likely the global RESET is broken - PathLog.setLevel(PathLog.Level.DEBUG, "SomeOtherModule") + Path.Log.setLevel(Path.Log.Level.DEBUG, "SomeOtherModule") - self.assertEqual(PathLog.getLevel(), PathLog.Level.NOTICE) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.NOTICE) + self.assertEqual(Path.Log.getLevel(), Path.Log.Level.NOTICE) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.NOTICE) def test14(self): """Verify resetting log level for module falls back to global level.""" - PathLog.setLevel(PathLog.Level.DEBUG, self.MODULE) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.DEBUG) + Path.Log.setLevel(Path.Log.Level.DEBUG, self.MODULE) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.DEBUG) # changing global log level does not affect module - PathLog.setLevel(PathLog.Level.ERROR) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.DEBUG) + Path.Log.setLevel(Path.Log.Level.ERROR) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.DEBUG) # resetting module log level restores global log level for module - PathLog.setLevel(PathLog.Level.RESET, self.MODULE) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.ERROR) + Path.Log.setLevel(Path.Log.Level.RESET, self.MODULE) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.ERROR) # changing the global log level will also change the module log level - PathLog.setLevel(PathLog.Level.DEBUG) - self.assertEqual(PathLog.getLevel(self.MODULE), PathLog.Level.DEBUG) + Path.Log.setLevel(Path.Log.Level.DEBUG) + self.assertEqual(Path.Log.getLevel(self.MODULE), Path.Log.Level.DEBUG) def test20(self): """Verify debug logs aren't logged by default.""" - self.assertIsNone(PathLog.debug("this")) + self.assertIsNone(Path.Log.debug("this")) def test21(self): """Verify debug logs are logged if log level is set to DEBUG.""" - PathLog.setLevel(PathLog.Level.DEBUG) - self.assertIsNotNone(PathLog.debug("this")) + Path.Log.setLevel(Path.Log.Level.DEBUG) + self.assertIsNotNone(Path.Log.debug("this")) def test30(self): """Verify log level ERROR.""" - PathLog.setLevel(PathLog.Level.ERROR) - self.assertIsNone(PathLog.debug("something")) - self.assertIsNone(PathLog.info("something")) - self.assertIsNone(PathLog.notice("something")) - self.assertIsNone(PathLog.warning("something")) - self.assertIsNotNone(PathLog.error("something")) + Path.Log.setLevel(Path.Log.Level.ERROR) + self.assertIsNone(Path.Log.debug("something")) + self.assertIsNone(Path.Log.info("something")) + self.assertIsNone(Path.Log.notice("something")) + self.assertIsNone(Path.Log.warning("something")) + self.assertIsNotNone(Path.Log.error("something")) def test31(self): """Verify log level WARNING.""" - PathLog.setLevel(PathLog.Level.WARNING) - self.assertIsNone(PathLog.debug("something")) - self.assertIsNone(PathLog.info("something")) - self.assertIsNone(PathLog.notice("something")) - self.assertIsNotNone(PathLog.warning("something")) - self.assertIsNotNone(PathLog.error("something")) + Path.Log.setLevel(Path.Log.Level.WARNING) + self.assertIsNone(Path.Log.debug("something")) + self.assertIsNone(Path.Log.info("something")) + self.assertIsNone(Path.Log.notice("something")) + self.assertIsNotNone(Path.Log.warning("something")) + self.assertIsNotNone(Path.Log.error("something")) def test32(self): """Verify log level NOTICE.""" - PathLog.setLevel(PathLog.Level.NOTICE) - self.assertIsNone(PathLog.debug("something")) - self.assertIsNone(PathLog.info("something")) - self.assertIsNotNone(PathLog.notice("something")) - self.assertIsNotNone(PathLog.warning("something")) - self.assertIsNotNone(PathLog.error("something")) + Path.Log.setLevel(Path.Log.Level.NOTICE) + self.assertIsNone(Path.Log.debug("something")) + self.assertIsNone(Path.Log.info("something")) + self.assertIsNotNone(Path.Log.notice("something")) + self.assertIsNotNone(Path.Log.warning("something")) + self.assertIsNotNone(Path.Log.error("something")) def test33(self): """Verify log level INFO.""" - PathLog.setLevel(PathLog.Level.INFO) - self.assertIsNone(PathLog.debug("something")) - self.assertIsNotNone(PathLog.info("something")) - self.assertIsNotNone(PathLog.notice("something")) - self.assertIsNotNone(PathLog.warning("something")) - self.assertIsNotNone(PathLog.error("something")) + Path.Log.setLevel(Path.Log.Level.INFO) + self.assertIsNone(Path.Log.debug("something")) + self.assertIsNotNone(Path.Log.info("something")) + self.assertIsNotNone(Path.Log.notice("something")) + self.assertIsNotNone(Path.Log.warning("something")) + self.assertIsNotNone(Path.Log.error("something")) def test34(self): """Verify log level DEBUG.""" - PathLog.setLevel(PathLog.Level.DEBUG) - self.assertIsNotNone(PathLog.debug("something")) - self.assertIsNotNone(PathLog.info("something")) - self.assertIsNotNone(PathLog.notice("something")) - self.assertIsNotNone(PathLog.warning("something")) - self.assertIsNotNone(PathLog.error("something")) + Path.Log.setLevel(Path.Log.Level.DEBUG) + self.assertIsNotNone(Path.Log.debug("something")) + self.assertIsNotNone(Path.Log.info("something")) + self.assertIsNotNone(Path.Log.notice("something")) + self.assertIsNotNone(Path.Log.warning("something")) + self.assertIsNotNone(Path.Log.error("something")) def test50(self): """Verify no tracking by default.""" - self.assertIsNone(PathLog.track("this", "and", "that")) + self.assertIsNone(Path.Log.track("this", "and", "that")) def test51(self): """Verify enabling tracking for module results in tracking.""" - PathLog.trackModule() + Path.Log.trackModule() # Don't want to rely on the line number matching - still want some # indication that track does the right thing .... - msg = PathLog.track("this", "and", "that") + msg = Path.Log.track("this", "and", "that") self.assertTrue(msg.startswith(self.MODULE)) self.assertTrue(msg.endswith("test51(this, and, that)")) def test52(self): """Verify untracking stops tracking.""" - PathLog.trackModule() - self.assertIsNotNone(PathLog.track("this", "and", "that")) - PathLog.untrackModule() - self.assertIsNone(PathLog.track("this", "and", "that")) + Path.Log.trackModule() + self.assertIsNotNone(Path.Log.track("this", "and", "that")) + Path.Log.untrackModule() + self.assertIsNone(Path.Log.track("this", "and", "that")) def test53(self): """Verify trackAllModules works correctly.""" - PathLog.trackAllModules(True) - self.assertIsNotNone(PathLog.track("this", "and", "that")) - PathLog.trackAllModules(False) - self.assertIsNone(PathLog.track("this", "and", "that")) - PathLog.trackAllModules(True) - PathLog.trackModule() - self.assertIsNotNone(PathLog.track("this", "and", "that")) - PathLog.trackAllModules(False) - self.assertIsNotNone(PathLog.track("this", "and", "that")) + Path.Log.trackAllModules(True) + self.assertIsNotNone(Path.Log.track("this", "and", "that")) + Path.Log.trackAllModules(False) + self.assertIsNone(Path.Log.track("this", "and", "that")) + Path.Log.trackAllModules(True) + Path.Log.trackModule() + self.assertIsNotNone(Path.Log.track("this", "and", "that")) + Path.Log.trackAllModules(False) + self.assertIsNotNone(Path.Log.track("this", "and", "that")) def test60(self): """Verify track handles no argument.""" - PathLog.trackModule() - msg = PathLog.track() + Path.Log.trackModule() + msg = Path.Log.track() self.assertTrue(msg.startswith(self.MODULE)) self.assertTrue(msg.endswith("test60()")) def test61(self): """Verify track handles arbitrary argument types correctly.""" - PathLog.trackModule() - msg = PathLog.track("this", None, 1, 18.25) + Path.Log.trackModule() + msg = Path.Log.track("this", None, 1, 18.25) self.assertTrue(msg.startswith(self.MODULE)) self.assertTrue(msg.endswith("test61(this, None, 1, 18.25)")) def testzz(self): """Restoring environment after tests.""" - PathLog.setLevel(PathLog.Level.RESET) + Path.Log.setLevel(Path.Log.Level.RESET) diff --git a/src/Mod/Path/PathTests/TestPathOpTools.py b/src/Mod/Path/PathTests/TestPathOpTools.py index 34dfc09c5a..b3e6e92a9d 100644 --- a/src/Mod/Path/PathTests/TestPathOpTools.py +++ b/src/Mod/Path/PathTests/TestPathOpTools.py @@ -22,16 +22,16 @@ import FreeCAD import Part +import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathOpTools as PathOpTools -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils import math from FreeCAD import Vector -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +# Path.Log.trackModule(Path.Log.thisModule()) def getWire(obj, nr=0): diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index 3d980fc778..9bdd4634e3 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -28,7 +28,6 @@ import unittest import FreeCAD import Path -from PathScripts import PathLog from PathScripts import PathPost from PathScripts import PathPreferences from PathScripts import PostUtils @@ -40,8 +39,8 @@ from PathScripts.PathPostProcessor import PostProcessor # so it can be looked at easily. KEEP_DEBUG_OUTPUT = False -PathPost.LOG_MODULE = PathLog.thisModule() -PathLog.setLevel(PathLog.Level.INFO, PathPost.LOG_MODULE) +PathPost.LOG_MODULE = Path.Log.thisModule() +Path.Log.setLevel(Path.Log.Level.INFO, PathPost.LOG_MODULE) class TestPathPost(unittest.TestCase): diff --git a/src/Mod/Path/PathTests/TestPathRotationGenerator.py b/src/Mod/Path/PathTests/TestPathRotationGenerator.py index c9a3065d58..ff14f659df 100644 --- a/src/Mod/Path/PathTests/TestPathRotationGenerator.py +++ b/src/Mod/Path/PathTests/TestPathRotationGenerator.py @@ -20,15 +20,14 @@ # * * # *************************************************************************** -import Path import FreeCAD import Generators.rotation_generator as generator -import PathScripts.PathLog as PathLog +import Path import PathTests.PathTestUtils as PathTestUtils import numpy as np -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestPathRotationGenerator(PathTestUtils.PathTestBase): @@ -75,7 +74,7 @@ class TestPathRotationGenerator(PathTestUtils.PathTestBase): result = generator.generate(**args) self.assertTrue(len(result) == 2) - PathLog.debug(result) + Path.Log.debug(result) def test20(self): """Test non-zero rotation""" @@ -92,11 +91,11 @@ class TestPathRotationGenerator(PathTestUtils.PathTestBase): result = generator.generate(**args) command = result[0] - PathLog.debug(command.Parameters) + Path.Log.debug(command.Parameters) self.assertTrue(np.isclose(command.Parameters["A"], 54.736)) self.assertTrue(np.isclose(command.Parameters["C"], 45)) - PathLog.track(result) + Path.Log.track(result) def test30(self): """Test A limits""" @@ -109,7 +108,7 @@ class TestPathRotationGenerator(PathTestUtils.PathTestBase): args["aMax"] = 0 result = generator.generate(**args) - PathLog.debug(result) + Path.Log.debug(result) command = result[0] self.assertTrue(np.isclose(command.Parameters["A"], -54.736)) @@ -120,7 +119,7 @@ class TestPathRotationGenerator(PathTestUtils.PathTestBase): args["aMax"] = 90 result = generator.generate(**args) - PathLog.debug(result) + Path.Log.debug(result) command = result[0] self.assertTrue(np.isclose(command.Parameters["A"], 54.736)) @@ -137,7 +136,7 @@ class TestPathRotationGenerator(PathTestUtils.PathTestBase): args["cMax"] = 0 result = generator.generate(**args) - PathLog.debug(result) + Path.Log.debug(result) command = result[0] self.assertTrue(np.isclose(command.Parameters["A"], -54.736)) @@ -148,7 +147,7 @@ class TestPathRotationGenerator(PathTestUtils.PathTestBase): args["cMax"] = 180 result = generator.generate(**args) - PathLog.debug(result) + Path.Log.debug(result) command = result[0] self.assertTrue(np.isclose(command.Parameters["A"], 54.736)) diff --git a/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py index f4ec58b06a..17827dc523 100644 --- a/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py +++ b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py @@ -24,11 +24,10 @@ import Path import Generators.toolchange_generator as generator from Generators.toolchange_generator import SpindleDirection -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestPathToolChangeGenerator(PathTestUtils.PathTestBase): @@ -67,7 +66,7 @@ class TestPathToolChangeGenerator(PathTestUtils.PathTestBase): args["spindlespeed"] = 0 results = generator.generate(**args) self.assertTrue(len(results) == 2) - PathLog.track(results) + Path.Log.track(results) # negative spindlespeed args["spindlespeed"] = -10 diff --git a/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py b/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py index 4c49c03167..884a7e3d3b 100644 --- a/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py @@ -25,15 +25,13 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import refactored_centroid_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestRefactoredCentroidPost(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestRefactoredGrblPost.py b/src/Mod/Path/PathTests/TestRefactoredGrblPost.py index 9affd355ca..2cf0932966 100644 --- a/src/Mod/Path/PathTests/TestRefactoredGrblPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredGrblPost.py @@ -25,15 +25,13 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import refactored_grbl_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestRefactoredGrblPost(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py b/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py index 267dc10f84..8137bdb24f 100644 --- a/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py @@ -25,15 +25,13 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import refactored_linuxcnc_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestRefactoredLinuxCNCPost(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py b/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py index dab611262d..dd1f759f84 100644 --- a/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py +++ b/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py @@ -25,14 +25,12 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import refactored_mach3_mach4_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestRefactoredMach3Mach4Post(PathTestUtils.PathTestBase): diff --git a/src/Mod/Path/PathTests/TestRefactoredTestPost.py b/src/Mod/Path/PathTests/TestRefactoredTestPost.py index 0d42076929..317f4c4dac 100644 --- a/src/Mod/Path/PathTests/TestRefactoredTestPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredTestPost.py @@ -25,15 +25,13 @@ from importlib import reload import FreeCAD -# import Part import Path -import PathScripts.PathLog as PathLog import PathTests.PathTestUtils as PathTestUtils from PathScripts.post import refactored_test_post as postprocessor -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -PathLog.trackModule(PathLog.thisModule()) +Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) +Path.Log.trackModule(Path.Log.thisModule()) class TestRefactoredTestPost(PathTestUtils.PathTestBase): From eeaed7363aa2d15b4e819a68b635c2dcb0f27c90 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 10 Aug 2022 17:32:39 -0700 Subject: [PATCH 04/33] Fixed path log unit tests --- src/Mod/Path/Path/__init__.py | 2 +- src/Mod/Path/PathTests/TestPathLog.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/Path/__init__.py b/src/Mod/Path/Path/__init__.py index e365888dbe..e633bc4d34 100644 --- a/src/Mod/Path/Path/__init__.py +++ b/src/Mod/Path/Path/__init__.py @@ -1,3 +1,3 @@ from PathApp import * -import Path.Log +import Path.Log as Log diff --git a/src/Mod/Path/PathTests/TestPathLog.py b/src/Mod/Path/PathTests/TestPathLog.py index d334feccb9..50c8100efa 100644 --- a/src/Mod/Path/PathTests/TestPathLog.py +++ b/src/Mod/Path/PathTests/TestPathLog.py @@ -24,10 +24,10 @@ import Path import unittest -class TestPath.Log(unittest.TestCase): +class TestPathLog(unittest.TestCase): """Some basic tests for the logging framework.""" - MODULE = "TestPath.Log" # file name without extension + MODULE = "TestPathLog" # file name without extension def setUp(self): Path.Log.setLevel(Path.Log.Level.RESET) From 8c05a46174f3ba1bd08ada144ab7ec9780cbd14b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 10 Aug 2022 17:37:31 -0700 Subject: [PATCH 05/33] Moved PathAdaptive into new Path.Op module --- src/Mod/Path/CMakeLists.txt | 27 +++++++++++++++++-- .../PathAdaptive.py => Path/Op/Adaptive.py} | 0 .../Op/Gui/Adaptive.py} | 2 +- src/Mod/Path/Path/Op/Gui/__init__.py | 0 src/Mod/Path/Path/Op/__init__.py | 0 src/Mod/Path/PathScripts/PathGuiInit.py | 2 +- src/Mod/Path/PathTests/TestPathAdaptive.py | 4 +-- 7 files changed, 29 insertions(+), 6 deletions(-) rename src/Mod/Path/{PathScripts/PathAdaptive.py => Path/Op/Adaptive.py} (100%) rename src/Mod/Path/{PathScripts/PathAdaptiveGui.py => Path/Op/Gui/Adaptive.py} (99%) create mode 100644 src/Mod/Path/Path/Op/Gui/__init__.py create mode 100644 src/Mod/Path/Path/Op/__init__.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index fce6bdd8f9..0d739b5333 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -30,10 +30,18 @@ SET(PathPython_SRCS Path/Log.py ) +SET(PathPythonOp_SRCS + Path/Op/__init__.py + Path/Op/Adaptive.py +) + +SET(PathPythonOpGui_SRCS + Path/Op/Gui/__init__.py + Path/Op/Gui/Adaptive.py +) + SET(PathScripts_SRCS PathScripts/drillableLib.py - PathScripts/PathAdaptive.py - PathScripts/PathAdaptiveGui.py PathScripts/PathAreaOp.py PathScripts/PathArray.py PathScripts/PathCircularHoleBase.py @@ -318,6 +326,8 @@ SET(Path_Data SET(all_files ${PathScripts_SRCS} ${PathPython_SRCS} + ${PathPythonOp_SRCS} + ${PathPythonOpGui_SRCS} ${Generator_SRCS} ${PathScripts_post_SRCS} ${PathPythonGui_SRCS} @@ -359,6 +369,19 @@ INSTALL( Mod/Path/Path ) +INSTALL( + FILES + ${PathPythonOp_SRCS} + DESTINATION + Mod/Path/Path/Op +) +INSTALL( + FILES + ${PathPythonOpGui_SRCS} + DESTINATION + Mod/Path/Path/Op/Gui +) + INSTALL( FILES ${Generator_SRCS} diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/Path/Op/Adaptive.py similarity index 100% rename from src/Mod/Path/PathScripts/PathAdaptive.py rename to src/Mod/Path/Path/Op/Adaptive.py diff --git a/src/Mod/Path/PathScripts/PathAdaptiveGui.py b/src/Mod/Path/Path/Op/Gui/Adaptive.py similarity index 99% rename from src/Mod/Path/PathScripts/PathAdaptiveGui.py rename to src/Mod/Path/Path/Op/Gui/Adaptive.py index 4b42a2fd92..a4836b1dd0 100644 --- a/src/Mod/Path/PathScripts/PathAdaptiveGui.py +++ b/src/Mod/Path/Path/Op/Gui/Adaptive.py @@ -21,9 +21,9 @@ # * * # *************************************************************************** +import Path.Op.Adaptive as PathAdaptive import PathScripts.PathOpGui as PathOpGui from PySide import QtCore -import PathScripts.PathAdaptive as PathAdaptive import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui import FreeCADGui diff --git a/src/Mod/Path/Path/Op/Gui/__init__.py b/src/Mod/Path/Path/Op/Gui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Path/Op/__init__.py b/src/Mod/Path/Path/Op/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 59a448f58b..19bf366369 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -38,7 +38,7 @@ def Startup(): global Processed if not Processed: Path.Log.debug("Initializing PathGui") - from PathScripts import PathAdaptiveGui + from Path.Op.Gui import Adaptive from PathScripts import PathArray from PathScripts import PathComment from PathScripts import PathCustomGui diff --git a/src/Mod/Path/PathTests/TestPathAdaptive.py b/src/Mod/Path/PathTests/TestPathAdaptive.py index caac814be1..12fb52f546 100644 --- a/src/Mod/Path/PathTests/TestPathAdaptive.py +++ b/src/Mod/Path/PathTests/TestPathAdaptive.py @@ -24,13 +24,13 @@ import FreeCAD import Part +import Path.Op.Adaptive as PathAdaptive import PathScripts.PathJob as PathJob -import PathScripts.PathAdaptive as PathAdaptive import PathScripts.PathGeom as PathGeom from PathTests.PathTestUtils import PathTestBase if FreeCAD.GuiUp: - import PathScripts.PathAdaptiveGui as PathAdaptiveGui + import Path.Op.Gui.Adaptive as PathAdaptiveGui import PathScripts.PathJobGui as PathJobGui From 1d27fb00ecc454652c004d37cbbb54570684327f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 10 Aug 2022 19:03:14 -0700 Subject: [PATCH 06/33] Moved post processing files into new Path python module --- src/Mod/Path/CMakeLists.txt | 106 ++++++++++-------- src/Mod/Path/Gui/AppPathGuiPy.cpp | 12 +- .../PathPost.py => Path/Post/Command.py} | 4 +- .../Post/Processor.py} | 0 .../PostUtils.py => Path/Post/Utils.py} | 0 .../Post/UtilsArguments.py} | 0 .../Post/UtilsExport.py} | 4 +- .../Post/UtilsParse.py} | 3 +- .../Post/scripts}/KineticNCBeamicon2_post.py | 2 +- .../post => Path/Post/scripts}/__init__.py | 0 .../Post/scripts}/centroid_post.py | 2 +- .../Post/scripts}/comparams_post.py | 2 +- .../post => Path/Post/scripts}/dumper_post.py | 2 +- .../post => Path/Post/scripts}/dxf_post.py | 0 .../Post/scripts}/dynapath_post.py | 2 +- .../Post/scripts}/example_post.py | 0 .../post => Path/Post/scripts}/example_pre.py | 0 .../post => Path/Post/scripts}/fablin_post.py | 2 +- .../post => Path/Post/scripts}/fanuc_post.py | 2 +- .../post => Path/Post/scripts}/gcode_pre.py | 0 .../post => Path/Post/scripts}/grbl_post.py | 2 +- .../Post/scripts}/heidenhain_post.py | 4 +- .../post => Path/Post/scripts}/jtech_post.py | 2 +- .../Post/scripts}/linuxcnc_post.py | 2 +- .../Post/scripts}/mach3_mach4_post.py | 2 +- .../post => Path/Post/scripts}/marlin_post.py | 2 +- .../post => Path/Post/scripts}/nccad_post.py | 2 +- .../Post/scripts}/opensbp_post.py | 2 +- .../post => Path/Post/scripts}/opensbp_pre.py | 0 .../Post/scripts}/philips_post.py | 2 +- .../Post/scripts}/refactored_centroid_post.py | 4 +- .../Post/scripts}/refactored_grbl_post.py | 4 +- .../Post/scripts}/refactored_linuxcnc_post.py | 4 +- .../scripts}/refactored_mach3_mach4_post.py | 4 +- .../Post/scripts}/refactored_test_post.py | 4 +- .../post => Path/Post/scripts}/rml_post.py | 2 +- .../post => Path/Post/scripts}/rrf_post.py | 2 +- .../post => Path/Post/scripts}/slic3r_pre.py | 0 .../Post/scripts}/smoothie_post.py | 2 +- .../post => Path/Post/scripts}/uccnc_post.py | 2 +- src/Mod/Path/PathScripts/PathCamoticsGui.py | 2 +- src/Mod/Path/PathScripts/PathGuiInit.py | 2 +- src/Mod/Path/PathScripts/PathJob.py | 2 +- src/Mod/Path/PathScripts/PathPreferences.py | 8 +- .../PathScripts/PathPreferencesPathJob.py | 2 +- src/Mod/Path/PathTests/TestCentroidPost.py | 2 +- src/Mod/Path/PathTests/TestGrblPost.py | 2 +- src/Mod/Path/PathTests/TestLinuxCNCPost.py | 2 +- src/Mod/Path/PathTests/TestMach3Mach4Post.py | 2 +- src/Mod/Path/PathTests/TestPathPost.py | 6 +- src/Mod/Path/PathTests/TestPathPreferences.py | 8 +- .../PathTests/TestRefactoredCentroidPost.py | 2 +- .../Path/PathTests/TestRefactoredGrblPost.py | 2 +- .../PathTests/TestRefactoredLinuxCNCPost.py | 2 +- .../PathTests/TestRefactoredMach3Mach4Post.py | 2 +- .../Path/PathTests/TestRefactoredTestPost.py | 6 +- 56 files changed, 127 insertions(+), 116 deletions(-) rename src/Mod/Path/{PathScripts/PathPost.py => Path/Post/Command.py} (99%) rename src/Mod/Path/{PathScripts/PathPostProcessor.py => Path/Post/Processor.py} (100%) rename src/Mod/Path/{PathScripts/PostUtils.py => Path/Post/Utils.py} (100%) rename src/Mod/Path/{PathScripts/PostUtilsArguments.py => Path/Post/UtilsArguments.py} (100%) rename src/Mod/Path/{PathScripts/PostUtilsExport.py => Path/Post/UtilsExport.py} (99%) rename src/Mod/Path/{PathScripts/PostUtilsParse.py => Path/Post/UtilsParse.py} (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/KineticNCBeamicon2_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/__init__.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/centroid_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/comparams_post.py (98%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/dumper_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/dxf_post.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/dynapath_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/example_post.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/example_pre.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/fablin_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/fanuc_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/gcode_pre.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/grbl_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/heidenhain_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/jtech_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/linuxcnc_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/mach3_mach4_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/marlin_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/nccad_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/opensbp_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/opensbp_pre.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/philips_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/refactored_centroid_post.py (98%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/refactored_grbl_post.py (98%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/refactored_linuxcnc_post.py (98%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/refactored_mach3_mach4_post.py (98%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/refactored_test_post.py (98%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/rml_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/rrf_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/slic3r_pre.py (100%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/smoothie_post.py (99%) rename src/Mod/Path/{PathScripts/post => Path/Post/scripts}/uccnc_post.py (99%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 0d739b5333..aeef87b68b 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -40,6 +40,49 @@ SET(PathPythonOpGui_SRCS Path/Op/Gui/Adaptive.py ) +SET(PathPythonPost_SRCS + Path/Post/__init__.py + Path/Post/Command.py + Path/Post/Processor.py + Path/Post/Utils.py + Path/Post/UtilsArguments.py + Path/Post/UtilsExport.py + Path/Post/UtilsParse.py +) + +SET(PathPythonPostScripts_SRCS + Path/Post/scripts/__init__.py + Path/Post/scripts/centroid_post.py + Path/Post/scripts/comparams_post.py + Path/Post/scripts/dxf_post.py + Path/Post/scripts/dynapath_post.py + Path/Post/scripts/example_pre.py + Path/Post/scripts/fablin_post.py + Path/Post/scripts/fanuc_post.py + Path/Post/scripts/gcode_pre.py + Path/Post/scripts/grbl_post.py + Path/Post/scripts/heidenhain_post.py + Path/Post/scripts/jtech_post.py + Path/Post/scripts/KineticNCBeamicon2_post.py + Path/Post/scripts/linuxcnc_post.py + Path/Post/scripts/mach3_mach4_post.py + Path/Post/scripts/marlin_post.py + Path/Post/scripts/nccad_post.py + Path/Post/scripts/opensbp_post.py + Path/Post/scripts/opensbp_pre.py + Path/Post/scripts/philips_post.py + Path/Post/scripts/refactored_centroid_post.py + Path/Post/scripts/refactored_grbl_post.py + Path/Post/scripts/refactored_linuxcnc_post.py + Path/Post/scripts/refactored_mach3_mach4_post.py + Path/Post/scripts/refactored_test_post.py + Path/Post/scripts/rml_post.py + Path/Post/scripts/rrf_post.py + Path/Post/scripts/slic3r_pre.py + Path/Post/scripts/smoothie_post.py + Path/Post/scripts/uccnc_post.py +) + SET(PathScripts_SRCS PathScripts/drillableLib.py PathScripts/PathAreaOp.py @@ -98,8 +141,6 @@ SET(PathScripts_SRCS PathScripts/PathPocketGui.py PathScripts/PathPocketShape.py PathScripts/PathPocketShapeGui.py - PathScripts/PathPost.py - PathScripts/PathPostProcessor.py PathScripts/PathPreferences.py PathScripts/PathPreferencesAdvanced.py PathScripts/PathPreferencesPathDressup.py @@ -153,10 +194,6 @@ SET(PathScripts_SRCS PathScripts/PathVcarveGui.py PathScripts/PathWaterline.py PathScripts/PathWaterlineGui.py - PathScripts/PostUtils.py - PathScripts/PostUtilsArguments.py - PathScripts/PostUtilsExport.py - PathScripts/PostUtilsParse.py PathScripts/__init__.py ) @@ -168,39 +205,6 @@ SET(Generator_SRCS Generators/toolchange_generator.py ) -SET(PathScripts_post_SRCS - PathScripts/post/__init__.py - PathScripts/post/centroid_post.py - PathScripts/post/comparams_post.py - PathScripts/post/dxf_post.py - PathScripts/post/dynapath_post.py - PathScripts/post/example_pre.py - PathScripts/post/fablin_post.py - PathScripts/post/fanuc_post.py - PathScripts/post/gcode_pre.py - PathScripts/post/grbl_post.py - PathScripts/post/heidenhain_post.py - PathScripts/post/jtech_post.py - PathScripts/post/KineticNCBeamicon2_post.py - PathScripts/post/linuxcnc_post.py - PathScripts/post/mach3_mach4_post.py - PathScripts/post/marlin_post.py - PathScripts/post/nccad_post.py - PathScripts/post/opensbp_post.py - PathScripts/post/opensbp_pre.py - PathScripts/post/philips_post.py - PathScripts/post/refactored_centroid_post.py - PathScripts/post/refactored_grbl_post.py - PathScripts/post/refactored_linuxcnc_post.py - PathScripts/post/refactored_mach3_mach4_post.py - PathScripts/post/refactored_test_post.py - PathScripts/post/rml_post.py - PathScripts/post/rrf_post.py - PathScripts/post/slic3r_pre.py - PathScripts/post/smoothie_post.py - PathScripts/post/uccnc_post.py -) - SET(PathPythonGui_SRCS PathPythonGui/__init__.py PathPythonGui/simple_edit_panel.py @@ -328,8 +332,9 @@ SET(all_files ${PathPython_SRCS} ${PathPythonOp_SRCS} ${PathPythonOpGui_SRCS} + ${PathPythonPost_SRCS} + ${PathPythonPostScripts_SRCS} ${Generator_SRCS} - ${PathScripts_post_SRCS} ${PathPythonGui_SRCS} ${Tools_SRCS} ${Tools_Bit_SRCS} @@ -382,6 +387,20 @@ INSTALL( Mod/Path/Path/Op/Gui ) +INSTALL( + FILES + ${PathPythonPost_SRCS} + DESTINATION + Mod/Path/Path/Post +) + +INSTALL( + FILES + ${PathPythonPostScripts_SRCS} + DESTINATION + Mod/Path/Path/Post/scripts +) + INSTALL( FILES ${Generator_SRCS} @@ -404,13 +423,6 @@ INSTALL( ) -INSTALL( - FILES - ${PathScripts_post_SRCS} - DESTINATION - Mod/Path/PathScripts/post -) - INSTALL( FILES ${PathPythonGui_SRCS} diff --git a/src/Mod/Path/Gui/AppPathGuiPy.cpp b/src/Mod/Path/Gui/AppPathGuiPy.cpp index 54ee0e7bb5..5772dccbd5 100644 --- a/src/Mod/Path/Gui/AppPathGuiPy.cpp +++ b/src/Mod/Path/Gui/AppPathGuiPy.cpp @@ -82,7 +82,7 @@ private: try { std::string path = App::Application::getHomePath(); - path += "Mod/Path/PathScripts/post/"; + path += "Mod/Path/Path/Post/scripts/"; QDir dir1(QString::fromUtf8(path.c_str()), QString::fromLatin1("*_pre.py")); std::string cMacroPath = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") ->GetASCII("MacroPath",App::Application::getUserMacroDir().c_str()); @@ -113,7 +113,7 @@ private: QFileInfo fileInfo = list.at(i); if (fileInfo.baseName().toStdString() == processor) { if (fileInfo.absoluteFilePath().contains(QString::fromLatin1("PathScripts"))) { - pre << "from PathScripts.post import " << processor; + pre << "from Path.Post.scripts import " << processor; } else { pre << "import " << processor; } @@ -149,7 +149,7 @@ private: try { std::string path = App::Application::getHomePath(); - path += "Mod/Path/PathScripts/post/"; + path += "Mod/Path/Path/Post/scripts/"; QDir dir1(QString::fromUtf8(path.c_str()), QString::fromLatin1("*_pre.py")); std::string cMacroPath = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") ->GetASCII("MacroPath",App::Application::getUserMacroDir().c_str()); @@ -189,7 +189,7 @@ private: QFileInfo fileInfo = list.at(i); if (fileInfo.baseName().toStdString() == processor) { if (fileInfo.absoluteFilePath().contains(QString::fromLatin1("PathScripts"))) { - pre << "from PathScripts.post import " << processor; + pre << "from Path.Post.scripts import " << processor; } else { pre << "import " << processor; } @@ -225,7 +225,7 @@ private: throw Py::RuntimeError("No object to export"); std::string path = App::Application::getHomePath(); - path += "Mod/Path/PathScripts/post/"; + path += "Mod/Path/Path/Post/scripts/"; QDir dir1(QString::fromUtf8(path.c_str()), QString::fromLatin1("*_post.py")); std::string cMacroPath = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") ->GetASCII("MacroPath",App::Application::getUserMacroDir().c_str()); @@ -265,7 +265,7 @@ private: QFileInfo fileInfo = list.at(i); if (fileInfo.baseName().toStdString() == processor) { if (fileInfo.absoluteFilePath().contains(QString::fromLatin1("PathScripts"))) { - pre << "from PathScripts.post import " << processor; + pre << "from Path.Post.scripts import " << processor; } else { pre << "import " << processor; } diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/Path/Post/Command.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPost.py rename to src/Mod/Path/Path/Post/Command.py index 033c1a3175..9d17cb326b 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/Path/Post/Command.py @@ -34,7 +34,7 @@ import PathScripts.PathUtils as PathUtils import os import re -from PathScripts.PathPostProcessor import PostProcessor +from Path.Post.Processor import PostProcessor from PySide import QtCore, QtGui from datetime import datetime from PySide.QtCore import QT_TRANSLATE_NOOP @@ -530,7 +530,7 @@ class CommandPathPost: def Activated(self): Path.Log.track() FreeCAD.ActiveDocument.openTransaction("Post Process the Selected path(s)") - FreeCADGui.addModule("PathScripts.PathPost") + FreeCADGui.addModule("Path.Post.Command") # Attempt to figure out what the user wants to post-process # If a job is selected, post that. diff --git a/src/Mod/Path/PathScripts/PathPostProcessor.py b/src/Mod/Path/Path/Post/Processor.py similarity index 100% rename from src/Mod/Path/PathScripts/PathPostProcessor.py rename to src/Mod/Path/Path/Post/Processor.py diff --git a/src/Mod/Path/PathScripts/PostUtils.py b/src/Mod/Path/Path/Post/Utils.py similarity index 100% rename from src/Mod/Path/PathScripts/PostUtils.py rename to src/Mod/Path/Path/Post/Utils.py diff --git a/src/Mod/Path/PathScripts/PostUtilsArguments.py b/src/Mod/Path/Path/Post/UtilsArguments.py similarity index 100% rename from src/Mod/Path/PathScripts/PostUtilsArguments.py rename to src/Mod/Path/Path/Post/UtilsArguments.py diff --git a/src/Mod/Path/PathScripts/PostUtilsExport.py b/src/Mod/Path/Path/Post/UtilsExport.py similarity index 99% rename from src/Mod/Path/PathScripts/PostUtilsExport.py rename to src/Mod/Path/Path/Post/UtilsExport.py index 9c9263f8af..878068a58e 100644 --- a/src/Mod/Path/PathScripts/PostUtilsExport.py +++ b/src/Mod/Path/Path/Post/UtilsExport.py @@ -31,10 +31,10 @@ import datetime import os import FreeCAD +import Path.Post.Utils as PostUtils +import Path.Post.UtilsParse as PostUtilsParse from PathScripts import PathToolController -from PathScripts import PostUtils -from PathScripts import PostUtilsParse # to distinguish python built-in open function from the one declared below diff --git a/src/Mod/Path/PathScripts/PostUtilsParse.py b/src/Mod/Path/Path/Post/UtilsParse.py similarity index 99% rename from src/Mod/Path/PathScripts/PostUtilsParse.py rename to src/Mod/Path/Path/Post/UtilsParse.py index 9c559c9d21..3fb33334c5 100644 --- a/src/Mod/Path/PathScripts/PostUtilsParse.py +++ b/src/Mod/Path/Path/Post/UtilsParse.py @@ -33,8 +33,7 @@ import FreeCAD from FreeCAD import Units import Path - -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils def create_comment(comment_string, comment_symbol): diff --git a/src/Mod/Path/PathScripts/post/KineticNCBeamicon2_post.py b/src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/KineticNCBeamicon2_post.py rename to src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py index 1a0204d7fe..1b704aad63 100644 --- a/src/Mod/Path/PathScripts/post/KineticNCBeamicon2_post.py +++ b/src/Mod/Path/Path/Post/scripts/KineticNCBeamicon2_post.py @@ -35,10 +35,10 @@ from __future__ import print_function import FreeCAD from FreeCAD import Units import Path +import Path.Post.Utils as PostUtils import argparse import datetime import shlex -from PathScripts import PostUtils from PathScripts import PathUtils TOOLTIP = """ diff --git a/src/Mod/Path/PathScripts/post/__init__.py b/src/Mod/Path/Path/Post/scripts/__init__.py similarity index 100% rename from src/Mod/Path/PathScripts/post/__init__.py rename to src/Mod/Path/Path/Post/scripts/__init__.py diff --git a/src/Mod/Path/PathScripts/post/centroid_post.py b/src/Mod/Path/Path/Post/scripts/centroid_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/centroid_post.py rename to src/Mod/Path/Path/Post/scripts/centroid_post.py index 6fe06202bb..48477d5d7e 100644 --- a/src/Mod/Path/PathScripts/post/centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/centroid_post.py @@ -27,9 +27,9 @@ from __future__ import print_function import os import FreeCAD from FreeCAD import Units +import Path.Post.Utils as PostUtils import datetime import PathScripts -import PathScripts.PostUtils as PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to diff --git a/src/Mod/Path/PathScripts/post/comparams_post.py b/src/Mod/Path/Path/Post/scripts/comparams_post.py similarity index 98% rename from src/Mod/Path/PathScripts/post/comparams_post.py rename to src/Mod/Path/Path/Post/scripts/comparams_post.py index aa5c275e98..d4b1af4018 100644 --- a/src/Mod/Path/PathScripts/post/comparams_post.py +++ b/src/Mod/Path/Path/Post/scripts/comparams_post.py @@ -22,7 +22,7 @@ import FreeCAD import Path -import PathScripts.PostUtils as PostUtils +import Path.Post.Utils as PostUtils TOOLTIP = """Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output.""" diff --git a/src/Mod/Path/PathScripts/post/dumper_post.py b/src/Mod/Path/Path/Post/scripts/dumper_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/dumper_post.py rename to src/Mod/Path/Path/Post/scripts/dumper_post.py index a89d515d6b..753c367f42 100644 --- a/src/Mod/Path/PathScripts/post/dumper_post.py +++ b/src/Mod/Path/Path/Post/scripts/dumper_post.py @@ -30,7 +30,7 @@ doesn't do any manipulation of the path and doesn't write anything to disk. It shows the dialog so you can see it. Useful for debugging, but not much else. """ import datetime -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils now = datetime.datetime.now() SHOW_EDITOR = True diff --git a/src/Mod/Path/PathScripts/post/dxf_post.py b/src/Mod/Path/Path/Post/scripts/dxf_post.py similarity index 100% rename from src/Mod/Path/PathScripts/post/dxf_post.py rename to src/Mod/Path/Path/Post/scripts/dxf_post.py diff --git a/src/Mod/Path/PathScripts/post/dynapath_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/dynapath_post.py rename to src/Mod/Path/Path/Post/scripts/dynapath_post.py index 936d1f6b97..1db5e924e8 100644 --- a/src/Mod/Path/PathScripts/post/dynapath_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_post.py @@ -33,10 +33,10 @@ from __future__ import print_function import FreeCAD from FreeCAD import Units +import Path.Post.Utils import PostUtils import argparse import datetime import shlex -from PathScripts import PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to diff --git a/src/Mod/Path/PathScripts/post/example_post.py b/src/Mod/Path/Path/Post/scripts/example_post.py similarity index 100% rename from src/Mod/Path/PathScripts/post/example_post.py rename to src/Mod/Path/Path/Post/scripts/example_post.py diff --git a/src/Mod/Path/PathScripts/post/example_pre.py b/src/Mod/Path/Path/Post/scripts/example_pre.py similarity index 100% rename from src/Mod/Path/PathScripts/post/example_pre.py rename to src/Mod/Path/Path/Post/scripts/example_pre.py diff --git a/src/Mod/Path/PathScripts/post/fablin_post.py b/src/Mod/Path/Path/Post/scripts/fablin_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/fablin_post.py rename to src/Mod/Path/Path/Post/scripts/fablin_post.py index 74b01710fc..0c106f17f1 100644 --- a/src/Mod/Path/PathScripts/post/fablin_post.py +++ b/src/Mod/Path/Path/Post/scripts/fablin_post.py @@ -25,7 +25,7 @@ # *************************************************************************** import datetime -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils now = datetime.datetime.now() diff --git a/src/Mod/Path/PathScripts/post/fanuc_post.py b/src/Mod/Path/Path/Post/scripts/fanuc_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/fanuc_post.py rename to src/Mod/Path/Path/Post/scripts/fanuc_post.py index b5829a1ed1..752b5fb31b 100644 --- a/src/Mod/Path/PathScripts/post/fanuc_post.py +++ b/src/Mod/Path/Path/Post/scripts/fanuc_post.py @@ -29,7 +29,7 @@ import argparse import datetime import shlex import os.path -from PathScripts import PostUtils +import Path.Post.Utils import PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/Path/Post/scripts/gcode_pre.py similarity index 100% rename from src/Mod/Path/PathScripts/post/gcode_pre.py rename to src/Mod/Path/Path/Post/scripts/gcode_pre.py diff --git a/src/Mod/Path/PathScripts/post/grbl_post.py b/src/Mod/Path/Path/Post/scripts/grbl_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/grbl_post.py rename to src/Mod/Path/Path/Post/scripts/grbl_post.py index 4613683304..ab8482fa56 100755 --- a/src/Mod/Path/PathScripts/post/grbl_post.py +++ b/src/Mod/Path/Path/Post/scripts/grbl_post.py @@ -26,7 +26,7 @@ import FreeCAD from FreeCAD import Units -import PathScripts.PostUtils as PostUtils +import Path.Post.Utils as PostUtils import argparse import datetime import shlex diff --git a/src/Mod/Path/PathScripts/post/heidenhain_post.py b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/heidenhain_post.py rename to src/Mod/Path/Path/Post/scripts/heidenhain_post.py index a1880a0605..bba9f490cd 100644 --- a/src/Mod/Path/PathScripts/post/heidenhain_post.py +++ b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py @@ -21,9 +21,9 @@ # HEDENHAIN Post-Processor for FreeCAD import argparse -import shlex +import Path.Post.Utils as PostUtils import PathScripts -from PathScripts import PostUtils +import shlex import math # **************************************************************************# diff --git a/src/Mod/Path/PathScripts/post/jtech_post.py b/src/Mod/Path/Path/Post/scripts/jtech_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/jtech_post.py rename to src/Mod/Path/Path/Post/scripts/jtech_post.py index f753357584..36e5b1d498 100644 --- a/src/Mod/Path/PathScripts/post/jtech_post.py +++ b/src/Mod/Path/Path/Post/scripts/jtech_post.py @@ -28,7 +28,7 @@ import Path import argparse import datetime import shlex -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to diff --git a/src/Mod/Path/PathScripts/post/linuxcnc_post.py b/src/Mod/Path/Path/Post/scripts/linuxcnc_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/linuxcnc_post.py rename to src/Mod/Path/Path/Post/scripts/linuxcnc_post.py index 2d0f9e3d6d..d21eba1bad 100644 --- a/src/Mod/Path/PathScripts/post/linuxcnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/linuxcnc_post.py @@ -28,7 +28,7 @@ import Path import argparse import datetime import shlex -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to diff --git a/src/Mod/Path/PathScripts/post/mach3_mach4_post.py b/src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/mach3_mach4_post.py rename to src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py index 9fd054fb5a..d4cb8ecb66 100644 --- a/src/Mod/Path/PathScripts/post/mach3_mach4_post.py +++ b/src/Mod/Path/Path/Post/scripts/mach3_mach4_post.py @@ -27,7 +27,7 @@ import Path import argparse import datetime import shlex -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to diff --git a/src/Mod/Path/PathScripts/post/marlin_post.py b/src/Mod/Path/Path/Post/scripts/marlin_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/marlin_post.py rename to src/Mod/Path/Path/Post/scripts/marlin_post.py index ba990168c8..3a3d06e518 100644 --- a/src/Mod/Path/PathScripts/post/marlin_post.py +++ b/src/Mod/Path/Path/Post/scripts/marlin_post.py @@ -33,7 +33,7 @@ import shlex import FreeCAD from FreeCAD import Units import PathScripts.PathUtil as PathUtil -import PathScripts.PostUtils as PostUtils +import Path.Post.Utils as PostUtils Revised = "2020-11-03" # Revision date for this file. diff --git a/src/Mod/Path/PathScripts/post/nccad_post.py b/src/Mod/Path/Path/Post/scripts/nccad_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/nccad_post.py rename to src/Mod/Path/Path/Post/scripts/nccad_post.py index 2683870157..7661c888e1 100644 --- a/src/Mod/Path/PathScripts/post/nccad_post.py +++ b/src/Mod/Path/Path/Post/scripts/nccad_post.py @@ -22,7 +22,7 @@ # ****************************************************************************/ """Postprocessor to output real GCode for Max Computer GmbH nccad9.""" import FreeCAD -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils import datetime diff --git a/src/Mod/Path/PathScripts/post/opensbp_post.py b/src/Mod/Path/Path/Post/scripts/opensbp_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/opensbp_post.py rename to src/Mod/Path/Path/Post/scripts/opensbp_post.py index e76eeedc97..3d2e2af199 100644 --- a/src/Mod/Path/PathScripts/post/opensbp_post.py +++ b/src/Mod/Path/Path/Post/scripts/opensbp_post.py @@ -23,7 +23,7 @@ from __future__ import print_function import datetime -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils TOOLTIP = """ diff --git a/src/Mod/Path/PathScripts/post/opensbp_pre.py b/src/Mod/Path/Path/Post/scripts/opensbp_pre.py similarity index 100% rename from src/Mod/Path/PathScripts/post/opensbp_pre.py rename to src/Mod/Path/Path/Post/scripts/opensbp_pre.py diff --git a/src/Mod/Path/PathScripts/post/philips_post.py b/src/Mod/Path/Path/Post/scripts/philips_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/philips_post.py rename to src/Mod/Path/Path/Post/scripts/philips_post.py index 74bc2612e0..d6e57cee6d 100644 --- a/src/Mod/Path/PathScripts/post/philips_post.py +++ b/src/Mod/Path/Path/Post/scripts/philips_post.py @@ -27,7 +27,7 @@ import FreeCAD import argparse import time -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils import math TOOLTIP = """Post processor for Maho M 600E mill diff --git a/src/Mod/Path/PathScripts/post/refactored_centroid_post.py b/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py similarity index 98% rename from src/Mod/Path/PathScripts/post/refactored_centroid_post.py rename to src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py index a9b57a4287..825106a123 100644 --- a/src/Mod/Path/PathScripts/post/refactored_centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py @@ -26,8 +26,8 @@ from __future__ import print_function -from PathScripts import PostUtilsArguments -from PathScripts import PostUtilsExport +import Path.Post.UtilsArguments as PostUtilsArguments +import Path.Post.UtilsExport as PostUtilsExport # # The following variables need to be global variables diff --git a/src/Mod/Path/PathScripts/post/refactored_grbl_post.py b/src/Mod/Path/Path/Post/scripts/refactored_grbl_post.py similarity index 98% rename from src/Mod/Path/PathScripts/post/refactored_grbl_post.py rename to src/Mod/Path/Path/Post/scripts/refactored_grbl_post.py index 9e11a5ee76..f52952d8f4 100644 --- a/src/Mod/Path/PathScripts/post/refactored_grbl_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_grbl_post.py @@ -27,8 +27,8 @@ from __future__ import print_function -from PathScripts import PostUtilsArguments -from PathScripts import PostUtilsExport +import Path.Post.UtilsArguments as PostUtilsArguments +import Path.Post.UtilsExport as PostUtilsExport # # The following variables need to be global variables diff --git a/src/Mod/Path/PathScripts/post/refactored_linuxcnc_post.py b/src/Mod/Path/Path/Post/scripts/refactored_linuxcnc_post.py similarity index 98% rename from src/Mod/Path/PathScripts/post/refactored_linuxcnc_post.py rename to src/Mod/Path/Path/Post/scripts/refactored_linuxcnc_post.py index b6a250d835..23b33dbc07 100644 --- a/src/Mod/Path/PathScripts/post/refactored_linuxcnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_linuxcnc_post.py @@ -24,8 +24,8 @@ from __future__ import print_function -from PathScripts import PostUtilsArguments -from PathScripts import PostUtilsExport +import Path.Post.UtilsArguments as PostUtilsArguments +import Path.Post.UtilsExport as PostUtilsExport # # The following variables need to be global variables diff --git a/src/Mod/Path/PathScripts/post/refactored_mach3_mach4_post.py b/src/Mod/Path/Path/Post/scripts/refactored_mach3_mach4_post.py similarity index 98% rename from src/Mod/Path/PathScripts/post/refactored_mach3_mach4_post.py rename to src/Mod/Path/Path/Post/scripts/refactored_mach3_mach4_post.py index e0078f6a1d..d4b6c4ac17 100644 --- a/src/Mod/Path/PathScripts/post/refactored_mach3_mach4_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_mach3_mach4_post.py @@ -23,8 +23,8 @@ # ***************************************************************************/ from __future__ import print_function -from PathScripts import PostUtilsArguments -from PathScripts import PostUtilsExport +import Path.Post.UtilsArguments as PostUtilsArguments +import Path.Post.UtilsExport as PostUtilsExport # # The following variables need to be global variables diff --git a/src/Mod/Path/PathScripts/post/refactored_test_post.py b/src/Mod/Path/Path/Post/scripts/refactored_test_post.py similarity index 98% rename from src/Mod/Path/PathScripts/post/refactored_test_post.py rename to src/Mod/Path/Path/Post/scripts/refactored_test_post.py index eb019200af..17f0d58be0 100644 --- a/src/Mod/Path/PathScripts/post/refactored_test_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_test_post.py @@ -24,8 +24,8 @@ from __future__ import print_function -from PathScripts import PostUtilsArguments -from PathScripts import PostUtilsExport +import Path.Post.UtilsArguments as PostUtilsArguments +import Path.Post.UtilsExport as PostUtilsExport # # The following variables need to be global variables diff --git a/src/Mod/Path/PathScripts/post/rml_post.py b/src/Mod/Path/Path/Post/scripts/rml_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/rml_post.py rename to src/Mod/Path/Path/Post/scripts/rml_post.py index 62fa837737..c2e27e31f6 100644 --- a/src/Mod/Path/PathScripts/post/rml_post.py +++ b/src/Mod/Path/Path/Post/scripts/rml_post.py @@ -36,7 +36,7 @@ http://paulbourke.net/dataformats/hpgl/ import FreeCAD import Part -import PathScripts.PostUtils as PostUtils +import Path.Post.Utils as PostUtils # to distinguish python built-in open function from the one declared below if open.__module__ in ["__builtin__", "io"]: diff --git a/src/Mod/Path/PathScripts/post/rrf_post.py b/src/Mod/Path/Path/Post/scripts/rrf_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/rrf_post.py rename to src/Mod/Path/Path/Post/scripts/rrf_post.py index 176b2af0db..cdef35bb10 100644 --- a/src/Mod/Path/PathScripts/post/rrf_post.py +++ b/src/Mod/Path/Path/Post/scripts/rrf_post.py @@ -32,7 +32,7 @@ import shlex import FreeCAD from FreeCAD import Units import PathScripts.PathUtil as PathUtil -import PathScripts.PostUtils as PostUtils +import Path.Post.Utils as PostUtils Revised = "2021-10-21" # Revision date for this file. diff --git a/src/Mod/Path/PathScripts/post/slic3r_pre.py b/src/Mod/Path/Path/Post/scripts/slic3r_pre.py similarity index 100% rename from src/Mod/Path/PathScripts/post/slic3r_pre.py rename to src/Mod/Path/Path/Post/scripts/slic3r_pre.py diff --git a/src/Mod/Path/PathScripts/post/smoothie_post.py b/src/Mod/Path/Path/Post/scripts/smoothie_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/smoothie_post.py rename to src/Mod/Path/Path/Post/scripts/smoothie_post.py index 85f42a1506..0429997475 100644 --- a/src/Mod/Path/PathScripts/post/smoothie_post.py +++ b/src/Mod/Path/Path/Post/scripts/smoothie_post.py @@ -25,7 +25,7 @@ from __future__ import print_function import argparse import datetime -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils import FreeCAD from FreeCAD import Units import shlex diff --git a/src/Mod/Path/PathScripts/post/uccnc_post.py b/src/Mod/Path/Path/Post/scripts/uccnc_post.py similarity index 99% rename from src/Mod/Path/PathScripts/post/uccnc_post.py rename to src/Mod/Path/Path/Post/scripts/uccnc_post.py index ca41cdc2a7..f76c7f8c14 100644 --- a/src/Mod/Path/PathScripts/post/uccnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/uccnc_post.py @@ -37,7 +37,7 @@ import argparse import datetime # import shlex -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils VERSION = "0.0.4" diff --git a/src/Mod/Path/PathScripts/PathCamoticsGui.py b/src/Mod/Path/PathScripts/PathCamoticsGui.py index 41d368ec0d..5cfa941baf 100644 --- a/src/Mod/Path/PathScripts/PathCamoticsGui.py +++ b/src/Mod/Path/PathScripts/PathCamoticsGui.py @@ -28,7 +28,7 @@ import FreeCADGui import Mesh import Path import PathScripts -import PathScripts.PathPost as PathPost +import Path.Post.Command as PathPost import camotics import io import json diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 19bf366369..a770c223cb 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -60,7 +60,7 @@ def Startup(): from PathScripts import PathMillFaceGui from PathScripts import PathPocketGui from PathScripts import PathPocketShapeGui - from PathScripts import PathPost + from Path.Post import Command from PathScripts import PathProbeGui from PathScripts import PathProfileGui from PathScripts import PathPropertyBagGui diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 07846c4a27..7d33c423a2 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -20,11 +20,11 @@ # * * # *************************************************************************** -from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Post.Processor as PostProcessor import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index 16d340f98f..785f2cdc5d 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -75,8 +75,8 @@ def preferences(): return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") -def pathScriptsSourcePath(): - return os.path.join(FreeCAD.getHomePath(), "Mod/Path/PathScripts/") +def pathPostSourcePath(): + return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Path/Post/") def pathDefaultToolsPath(sub=None): @@ -160,8 +160,8 @@ def searchPathsPost(): if p: paths.append(p) paths.append(macroFilePath()) - paths.append(os.path.join(pathScriptsSourcePath(), "post/")) - paths.append(pathScriptsSourcePath()) + paths.append(os.path.join(pathPostSourcePath(), "scripts/")) + paths.append(pathPostSourcePath()) return paths diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py index a72d77118b..fd867c106a 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py @@ -22,13 +22,13 @@ import FreeCAD import Path +import Path.Post.Processor as PostProcessor import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import json from FreeCAD import Units from PySide import QtCore, QtGui -from PathScripts.PathPostProcessor import PostProcessor Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestCentroidPost.py b/src/Mod/Path/PathTests/TestCentroidPost.py index 5da421b0f9..ab617838f9 100644 --- a/src/Mod/Path/PathTests/TestCentroidPost.py +++ b/src/Mod/Path/PathTests/TestCentroidPost.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import centroid_post as postprocessor +from Path.Post.scripts import centroid_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestGrblPost.py b/src/Mod/Path/PathTests/TestGrblPost.py index a3d1211f0b..ebc9c6234f 100644 --- a/src/Mod/Path/PathTests/TestGrblPost.py +++ b/src/Mod/Path/PathTests/TestGrblPost.py @@ -25,7 +25,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils from importlib import reload -from PathScripts.post import grbl_post as postprocessor +from Path.Post.scripts import grbl_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestLinuxCNCPost.py b/src/Mod/Path/PathTests/TestLinuxCNCPost.py index 32a2c44988..0706ca8b75 100644 --- a/src/Mod/Path/PathTests/TestLinuxCNCPost.py +++ b/src/Mod/Path/PathTests/TestLinuxCNCPost.py @@ -25,7 +25,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils from importlib import reload -from PathScripts.post import linuxcnc_post as postprocessor +from Path.Post.scripts import linuxcnc_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestMach3Mach4Post.py b/src/Mod/Path/PathTests/TestMach3Mach4Post.py index d63b947a90..0eee4287f8 100644 --- a/src/Mod/Path/PathTests/TestMach3Mach4Post.py +++ b/src/Mod/Path/PathTests/TestMach3Mach4Post.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import mach3_mach4_post as postprocessor +from Path.Post.scripts import mach3_mach4_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index 9bdd4634e3..720f0eaa2f 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -28,11 +28,11 @@ import unittest import FreeCAD import Path -from PathScripts import PathPost +import Path.Post.Command as PathPost from PathScripts import PathPreferences -from PathScripts import PostUtils +import Path.Post.Utils as PostUtils -from PathScripts.PathPostProcessor import PostProcessor +import Path.Post.Processor as PostProcessor # If KEEP_DEBUG_OUTPUT is False, remove the gcode file after the test succeeds. # If KEEP_DEBUG_OUTPUT is True or the test fails leave the gcode file behind diff --git a/src/Mod/Path/PathTests/TestPathPreferences.py b/src/Mod/Path/PathTests/TestPathPreferences.py index 589c6ee64b..b59ce2950b 100644 --- a/src/Mod/Path/PathTests/TestPathPreferences.py +++ b/src/Mod/Path/PathTests/TestPathPreferences.py @@ -32,14 +32,14 @@ class TestPathPreferences(PathTestUtils.PathTestBase): self.assertGreater(len(paths), 0) def test01(self): - """PathScripts is part of the posts search path.""" + """Path/Post is part of the posts search path.""" paths = PathPreferences.searchPathsPost() - self.assertEqual(len([p for p in paths if p.endswith("/PathScripts/")]), 1) + self.assertEqual(len([p for p in paths if p.endswith("/Path/Post/")]), 1) def test02(self): - """PathScripts/post is part of the posts search path.""" + """Path/Post/scripts is part of the posts search path.""" paths = PathPreferences.searchPathsPost() - self.assertEqual(len([p for p in paths if p.endswith("/PathScripts/post/")]), 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.""" diff --git a/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py b/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py index 884a7e3d3b..59baeeadf2 100644 --- a/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredCentroidPost.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import refactored_centroid_post as postprocessor +from Path.Post.scripts import refactored_centroid_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestRefactoredGrblPost.py b/src/Mod/Path/PathTests/TestRefactoredGrblPost.py index 2cf0932966..4057a21c45 100644 --- a/src/Mod/Path/PathTests/TestRefactoredGrblPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredGrblPost.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import refactored_grbl_post as postprocessor +from Path.Post.scripts import refactored_grbl_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py b/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py index 8137bdb24f..6da0ee2fc5 100644 --- a/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import refactored_linuxcnc_post as postprocessor +from Path.Post.scripts import refactored_linuxcnc_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py b/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py index dd1f759f84..1873ee6a20 100644 --- a/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py +++ b/src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import refactored_mach3_mach4_post as postprocessor +from Path.Post.scripts import refactored_mach3_mach4_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestRefactoredTestPost.py b/src/Mod/Path/PathTests/TestRefactoredTestPost.py index 317f4c4dac..70614bc28d 100644 --- a/src/Mod/Path/PathTests/TestRefactoredTestPost.py +++ b/src/Mod/Path/PathTests/TestRefactoredTestPost.py @@ -27,7 +27,7 @@ import FreeCAD import Path import PathTests.PathTestUtils as PathTestUtils -from PathScripts.post import refactored_test_post as postprocessor +from Path.Post.scripts import refactored_test_post as postprocessor Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -136,7 +136,7 @@ class TestRefactoredTestPost(PathTestUtils.PathTestBase): self.assertEqual(gcode.splitlines()[0], "(Exported by FreeCAD)") self.assertEqual( gcode.splitlines()[1], - "(Post Processor: PathScripts.post.refactored_test_post)", + "(Post Processor: Path.Post.scripts.refactored_test_post)", ) self.assertEqual(gcode.splitlines()[2], "(Cam File: )") self.assertIn("(Output Time: ", gcode.splitlines()[3]) @@ -168,7 +168,7 @@ G21 self.assertEqual(gcode.splitlines()[0], "(Exported by FreeCAD)") self.assertEqual( gcode.splitlines()[1], - "(Post Processor: PathScripts.post.refactored_test_post)", + "(Post Processor: Path.Post.scripts.refactored_test_post)", ) self.assertEqual(gcode.splitlines()[2], "(Cam File: )") self.assertIn("(Output Time: ", gcode.splitlines()[3]) From 20d2d4c8d0e68256ed336bd847aa36fac60e846a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 10 Aug 2022 20:39:53 -0700 Subject: [PATCH 07/33] Moved Path ToolBit and Controller into Path.Tools module --- src/Mod/Path/CMakeLists.txt | 40 ++++-- src/Mod/Path/InitGui.py | 6 +- src/Mod/Path/Path/Post/UtilsExport.py | 2 +- src/Mod/Path/Path/Post/__init__.py | 0 .../Path/Path/Post/scripts/centroid_post.py | 6 +- .../Path/Path/Post/scripts/heidenhain_post.py | 5 +- src/Mod/Path/Path/Post/scripts/uccnc_post.py | 4 +- .../PathToolBit.py => Path/Tools/Bit.py} | 0 .../Tools/Controller.py} | 6 +- .../Tools/Gui/Bit.py} | 4 +- .../Tools/Gui/BitCmd.py} | 10 +- .../Tools/Gui/BitEdit.py} | 0 .../Tools/Gui/BitLibrary.py} | 8 +- .../Tools/Gui/BitLibraryCmd.py} | 4 +- .../Tools/Gui/Controller.py} | 8 +- src/Mod/Path/Path/Tools/Gui/__init__.py | 0 src/Mod/Path/Path/Tools/__init__.py | 0 .../Path/PathScripts/PathDressupDogboneII.py | 123 ++++++++++++++++++ src/Mod/Path/PathScripts/PathGuiInit.py | 6 +- src/Mod/Path/PathScripts/PathJob.py | 4 +- src/Mod/Path/PathScripts/PathJobGui.py | 4 +- .../Path/PathScripts/PathToolLibraryEditor.py | 7 +- src/Mod/Path/PathScripts/PathUtilsGui.py | 4 +- src/Mod/Path/PathTests/TestPathHelpers.py | 2 +- src/Mod/Path/PathTests/TestPathToolBit.py | 2 +- .../Path/PathTests/TestPathToolController.py | 4 +- src/Mod/Path/PathTests/TestPathVcarve.py | 2 +- 27 files changed, 205 insertions(+), 56 deletions(-) create mode 100644 src/Mod/Path/Path/Post/__init__.py rename src/Mod/Path/{PathScripts/PathToolBit.py => Path/Tools/Bit.py} (100%) rename src/Mod/Path/{PathScripts/PathToolController.py => Path/Tools/Controller.py} (98%) rename src/Mod/Path/{PathScripts/PathToolBitGui.py => Path/Tools/Gui/Bit.py} (98%) rename src/Mod/Path/{PathScripts/PathToolBitCmd.py => Path/Tools/Gui/BitCmd.py} (95%) rename src/Mod/Path/{PathScripts/PathToolBitEdit.py => Path/Tools/Gui/BitEdit.py} (100%) rename src/Mod/Path/{PathScripts/PathToolBitLibraryGui.py => Path/Tools/Gui/BitLibrary.py} (99%) rename src/Mod/Path/{PathScripts/PathToolBitLibraryCmd.py => Path/Tools/Gui/BitLibraryCmd.py} (96%) rename src/Mod/Path/{PathScripts/PathToolControllerGui.py => Path/Tools/Gui/Controller.py} (98%) create mode 100644 src/Mod/Path/Path/Tools/Gui/__init__.py create mode 100644 src/Mod/Path/Path/Tools/__init__.py create mode 100644 src/Mod/Path/PathScripts/PathDressupDogboneII.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index aeef87b68b..ac7cfbbcfd 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -40,6 +40,22 @@ SET(PathPythonOpGui_SRCS Path/Op/Gui/Adaptive.py ) +SET(PathPythonTools_SRCS + Path/Tools/__init__.py + Path/Tools/Bit.py + Path/Tools/Controller.py +) + +SET(PathPythonToolsGui_SRCS + Path/Tools/Gui/__init__.py + Path/Tools/Gui/Bit.py + Path/Tools/Gui/BitCmd.py + Path/Tools/Gui/BitEdit.py + Path/Tools/Gui/BitLibraryCmd.py + Path/Tools/Gui/BitLibrary.py + Path/Tools/Gui/Controller.py +) + SET(PathPythonPost_SRCS Path/Post/__init__.py Path/Post/Command.py @@ -176,14 +192,6 @@ SET(PathScripts_SRCS PathScripts/PathSurfaceSupport.py PathScripts/PathThreadMilling.py PathScripts/PathThreadMillingGui.py - PathScripts/PathToolBit.py - PathScripts/PathToolBitCmd.py - PathScripts/PathToolBitEdit.py - PathScripts/PathToolBitGui.py - PathScripts/PathToolBitLibraryCmd.py - PathScripts/PathToolBitLibraryGui.py - PathScripts/PathToolController.py - PathScripts/PathToolControllerGui.py PathScripts/PathToolEdit.py PathScripts/PathToolLibraryEditor.py PathScripts/PathToolLibraryManager.py @@ -334,6 +342,8 @@ SET(all_files ${PathPythonOpGui_SRCS} ${PathPythonPost_SRCS} ${PathPythonPostScripts_SRCS} + ${PathPythonTools_SRCS} + ${PathPythonToolsGui_SRCS} ${Generator_SRCS} ${PathPythonGui_SRCS} ${Tools_SRCS} @@ -394,6 +404,20 @@ INSTALL( Mod/Path/Path/Post ) +INSTALL( + FILES + ${PathPythonTools_SRCS} + DESTINATION + Mod/Path/Path/Tools +) + +INSTALL( + FILES + ${PathPythonToolsGui_SRCS} + DESTINATION + Mod/Path/Path/Tools/Gui +) + INSTALL( FILES ${PathPythonPostScripts_SRCS} diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 9fa9b32c52..d263aa1167 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -82,8 +82,8 @@ class PathWorkbench(Workbench): from PathScripts import PathGuiInit from PathScripts import PathJobCmd - from PathScripts import PathToolBitCmd - from PathScripts import PathToolBitLibraryCmd + from Path.Tools.Gui import BitCmd as PathToolBitCmd + from Path.Tools.Gui import BitLibraryCmd as PathToolBitLibraryCmd from PySide.QtCore import QT_TRANSLATE_NOOP @@ -341,7 +341,7 @@ class PathWorkbench(Workbench): for cmd in self.dressupcmds: self.appendContextMenu("", [cmd]) menuAppended = True - if isinstance(obj.Proxy, PathScripts.PathToolBit.ToolBit): + if isinstance(obj.Proxy, Path.Tools.Bit.ToolBit): self.appendContextMenu("", ["Path_ToolBitSave", "Path_ToolBitSaveAs"]) menuAppended = True if menuAppended: diff --git a/src/Mod/Path/Path/Post/UtilsExport.py b/src/Mod/Path/Path/Post/UtilsExport.py index 878068a58e..64abafbf2c 100644 --- a/src/Mod/Path/Path/Post/UtilsExport.py +++ b/src/Mod/Path/Path/Post/UtilsExport.py @@ -34,7 +34,7 @@ import FreeCAD import Path.Post.Utils as PostUtils import Path.Post.UtilsParse as PostUtilsParse -from PathScripts import PathToolController +import Path.Tools.Controller as PathToolController # to distinguish python built-in open function from the one declared below diff --git a/src/Mod/Path/Path/Post/__init__.py b/src/Mod/Path/Path/Post/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Path/Post/scripts/centroid_post.py b/src/Mod/Path/Path/Post/scripts/centroid_post.py index 48477d5d7e..6608dcf168 100644 --- a/src/Mod/Path/Path/Post/scripts/centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/centroid_post.py @@ -29,13 +29,13 @@ import FreeCAD from FreeCAD import Units import Path.Post.Utils as PostUtils import datetime -import PathScripts +import Path TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to take a pseudo-gcode fragment outputted by a Path object, and output real GCode suitable for a centroid 3 axis mill. This postprocessor, once placed -in the appropriate PathScripts folder, can be used directly from inside +in the appropriate Path/Tools folder, can be used directly from inside FreeCAD, via the GUI importer or via python scripts with: import centroid_post @@ -188,7 +188,7 @@ def export(objectslist, filename, argstring): if OUTPUT_COMMENTS: for item in objectslist: if hasattr(item, "Proxy") and isinstance( - item.Proxy, PathScripts.PathToolController.ToolController + item.Proxy, Path.Tools.Controller.ToolController ): gcode += ";T{}={}\n".format(item.ToolNumber, item.Name) gcode += linenumber() + ";begin preamble\n" diff --git a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py index bba9f490cd..1c4e9d113f 100644 --- a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py +++ b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py @@ -22,6 +22,7 @@ import argparse import Path.Post.Utils as PostUtils +import Path import PathScripts import shlex import math @@ -186,7 +187,7 @@ TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to take a pseudo-gcode fragment outputted by a Path object, and output real GCode suitable for a heidenhain 3 axis mill. This postprocessor, once placed -in the appropriate PathScripts folder, can be used directly from inside +in the appropriate Path/Tools folder, can be used directly from inside FreeCAD, via the GUI importer or via python scripts with: import heidenhain_post @@ -343,7 +344,7 @@ def export(objectslist, filename, argstring): LBLIZE_STAUS = False # useful to get idea of object kind - if isinstance(obj.Proxy, PathScripts.PathToolController.ToolController): + if isinstance(obj.Proxy, Path.Tools.Controller.ToolController): Object_Kind = "TOOL" # like we go to change tool position MACHINE_LAST_POSITION["X"] = 99999 diff --git a/src/Mod/Path/Path/Post/scripts/uccnc_post.py b/src/Mod/Path/Path/Post/scripts/uccnc_post.py index f76c7f8c14..1185a710f3 100644 --- a/src/Mod/Path/Path/Post/scripts/uccnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/uccnc_post.py @@ -46,7 +46,7 @@ TOOLTIP = """ Post processor for UC-CNC. This is a postprocessor file for the Path workbench. It is used to take a pseudo-gcode fragment outputted by a Path object, and output real GCode. This postprocessor, once placed in the appropriate -PathScripts folder, can be used directly from inside FreeCAD, +Path/Tools folder, can be used directly from inside FreeCAD, via the GUI importer or via python scripts with: import UCCNC_post @@ -453,7 +453,7 @@ def export(objectslist, filename, argstring): if OUTPUT_COMMENTS: gcode += append("(preamble: begin)\n") # for obj in objectslist: - # if isinstance(obj.Proxy, PathScripts.PathToolController.ToolController): + # if isinstance(obj.Proxy, Path.Tools.Controller.ToolController): # gcode += append("(T{}={})\n".format(obj.ToolNumber, item.Name)) # error: global name 'PathScripts' is not defined for line in PREAMBLE.splitlines(False): diff --git a/src/Mod/Path/PathScripts/PathToolBit.py b/src/Mod/Path/Path/Tools/Bit.py similarity index 100% rename from src/Mod/Path/PathScripts/PathToolBit.py rename to src/Mod/Path/Path/Tools/Bit.py diff --git a/src/Mod/Path/PathScripts/PathToolController.py b/src/Mod/Path/Path/Tools/Controller.py similarity index 98% rename from src/Mod/Path/PathScripts/PathToolController.py rename to src/Mod/Path/Path/Tools/Controller.py index 1de3c6a2b3..6de4f24a50 100644 --- a/src/Mod/Path/PathScripts/PathToolController.py +++ b/src/Mod/Path/Path/Tools/Controller.py @@ -25,8 +25,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Tools.Bit as PathToolBit import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathToolBit as PathToolBit from Generators import toolchange_generator as toolchange_generator from Generators.toolchange_generator import SpindleDirection @@ -386,6 +386,6 @@ def FromTemplate(template, assignViewProvider=True): if FreeCAD.GuiUp: # need ViewProvider class in this file to support loading of old files - from PathScripts.PathToolControllerGui import ViewProvider + from Path.Tools.Gui.Controller import ViewProvider -FreeCAD.Console.PrintLog("Loading PathToolController... done\n") +FreeCAD.Console.PrintLog("Loading Path.Tools.Gui.Controller... done\n") diff --git a/src/Mod/Path/PathScripts/PathToolBitGui.py b/src/Mod/Path/Path/Tools/Gui/Bit.py similarity index 98% rename from src/Mod/Path/PathScripts/PathToolBitGui.py rename to src/Mod/Path/Path/Tools/Gui/Bit.py index a258de7e91..3bb33f7dbb 100644 --- a/src/Mod/Path/PathScripts/PathToolBitGui.py +++ b/src/Mod/Path/Path/Tools/Gui/Bit.py @@ -25,10 +25,10 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Tools.Bit as PathToolBit +import Path.Tools.Gui.BitEdit as PathToolBitEdit import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathToolBit as PathToolBit -import PathScripts.PathToolBitEdit as PathToolBitEdit import os __title__ = "Tool Bit UI" diff --git a/src/Mod/Path/PathScripts/PathToolBitCmd.py b/src/Mod/Path/Path/Tools/Gui/BitCmd.py similarity index 95% rename from src/Mod/Path/PathScripts/PathToolBitCmd.py rename to src/Mod/Path/Path/Tools/Gui/BitCmd.py index 68f94397d6..9b6934a268 100644 --- a/src/Mod/Path/PathScripts/PathToolBitCmd.py +++ b/src/Mod/Path/Path/Tools/Gui/BitCmd.py @@ -23,7 +23,7 @@ import FreeCAD import FreeCADGui import Path -import PathScripts +import Path.Tools import os from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP @@ -56,7 +56,7 @@ class CommandToolBitCreate: return FreeCAD.ActiveDocument is not None def Activated(self): - obj = PathScripts.PathToolBit.Factory.Create() + obj = Path.Tools.Bit.Factory.Create() obj.ViewObject.Proxy.setCreate(obj.ViewObject) @@ -84,7 +84,7 @@ class CommandToolBitSave: def selectedTool(self): sel = FreeCADGui.Selection.getSelectionEx() if 1 == len(sel) and isinstance( - sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit + sel[0].Object.Proxy, Path.Tools.Bit.ToolBit ): return sel[0].Object return None @@ -146,7 +146,7 @@ class CommandToolBitLoad: def selectedTool(self): sel = FreeCADGui.Selection.getSelectionEx() if 1 == len(sel) and isinstance( - sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit + sel[0].Object.Proxy, Path.Tools.Bit.ToolBit ): return sel[0].Object return None @@ -155,7 +155,7 @@ class CommandToolBitLoad: return FreeCAD.ActiveDocument is not None def Activated(self): - if PathScripts.PathToolBitGui.LoadTools(): + if Path.Tools.Bit.Gui.LoadTools(): FreeCAD.ActiveDocument.recompute() diff --git a/src/Mod/Path/PathScripts/PathToolBitEdit.py b/src/Mod/Path/Path/Tools/Gui/BitEdit.py similarity index 100% rename from src/Mod/Path/PathScripts/PathToolBitEdit.py rename to src/Mod/Path/Path/Tools/Gui/BitEdit.py diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py b/src/Mod/Path/Path/Tools/Gui/BitLibrary.py similarity index 99% rename from src/Mod/Path/PathScripts/PathToolBitLibraryGui.py rename to src/Mod/Path/Path/Tools/Gui/BitLibrary.py index 2d8764dac8..1fc3a9eff6 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryGui.py +++ b/src/Mod/Path/Path/Tools/Gui/BitLibrary.py @@ -25,12 +25,12 @@ import FreeCAD import FreeCADGui import Path +import Path.Tools.Bit as PathToolBit +import Path.Tools.Gui.Bit as PathToolBitGui +import Path.Tools.Gui.BitEdit as PathToolBitEdit +import Path.Tools.Gui.Controller as PathToolControllerGui import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathToolBit as PathToolBit -import PathScripts.PathToolBitEdit as PathToolBitEdit -import PathScripts.PathToolBitGui as PathToolBitGui -import PathScripts.PathToolControllerGui as PathToolControllerGui import PathScripts.PathUtilsGui as PathUtilsGui import PySide import glob diff --git a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py b/src/Mod/Path/Path/Tools/Gui/BitLibraryCmd.py similarity index 96% rename from src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py rename to src/Mod/Path/Path/Tools/Gui/BitLibraryCmd.py index 6b363b5942..a27f388432 100644 --- a/src/Mod/Path/PathScripts/PathToolBitLibraryCmd.py +++ b/src/Mod/Path/Path/Tools/Gui/BitLibraryCmd.py @@ -55,7 +55,7 @@ class CommandToolBitSelectorOpen: return FreeCAD.ActiveDocument is not None def Activated(self): - import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui + import Path.Tools.Gui.BitLibrary as PathToolBitLibraryGui dock = PathToolBitLibraryGui.ToolBitSelector() dock.open() @@ -85,7 +85,7 @@ class CommandToolBitLibraryOpen: return FreeCAD.ActiveDocument is not None def Activated(self): - import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui + import Path.Tools.Gui.BitLibrary as PathToolBitLibraryGui library = PathToolBitLibraryGui.ToolBitLibrary() diff --git a/src/Mod/Path/PathScripts/PathToolControllerGui.py b/src/Mod/Path/Path/Tools/Gui/Controller.py similarity index 98% rename from src/Mod/Path/PathScripts/PathToolControllerGui.py rename to src/Mod/Path/Path/Tools/Gui/Controller.py index 4d6fcc3ce0..05c8c735d3 100644 --- a/src/Mod/Path/PathScripts/PathToolControllerGui.py +++ b/src/Mod/Path/Path/Tools/Gui/Controller.py @@ -25,10 +25,10 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Tools.Controller as PathToolController +import Path.Tools.Gui.Bit as PathToolBitGui import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts import PathScripts.PathGui as PathGui -import PathScripts.PathToolBitGui as PathToolBitGui import PathScripts.PathToolEdit as PathToolEdit import PathScripts.PathUtil as PathUtil @@ -128,7 +128,7 @@ class ViewProvider: def Create(name="Default Tool", tool=None, toolNumber=1): Path.Log.track(tool, toolNumber) - obj = PathScripts.PathToolController.Create(name, tool, toolNumber) + obj = PathToolController.Create(name, tool, toolNumber) ViewProvider(obj.ViewObject) if not obj.Proxy.usesLegacyTool(obj): # ToolBits are visible by default, which is typically not what the user wants @@ -186,7 +186,7 @@ class ToolControllerEditor(object): self.obj = obj comboToPropertyMap = [("spindleDirection", "SpindleDir")] - enumTups = PathScripts.PathToolController.ToolController.propertyEnumerations( + enumTups = PathToolController.ToolController.propertyEnumerations( dataType="raw" ) diff --git a/src/Mod/Path/Path/Tools/Gui/__init__.py b/src/Mod/Path/Path/Tools/Gui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Path/Tools/__init__.py b/src/Mod/Path/Path/Tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/PathScripts/PathDressupDogboneII.py b/src/Mod/Path/PathScripts/PathDressupDogboneII.py new file mode 100644 index 0000000000..564448c08c --- /dev/null +++ b/src/Mod/Path/PathScripts/PathDressupDogboneII.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2022 sliptonic * +# * * +# * 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 PathScripts.PathGeom as PathGeom +import PathScripts.PathLanguage as PathLanguage +import PathScripts.Path.Log as Path.Log +import math + +class Kink (object): + '''A Kink represents the angle at which two moves connect. +A positive kink angle represents a move to the left, and a negative angle represents a move to the right.''' + + def __init__(self, m0, m1): + if m1 is None: + m1 = m0[1] + m0 = m0[0] + self.m0 = m0 + self.m1 = m1 + self.t0 = m0.anglesOfTangents()[1] + self.t1 = m1.anglesOfTangents()[0] + + def deflection(self): + '''deflection() ... returns the tangential difference of the two edges at their intersection''' + return PathGeom.normalizeAngle(self.t1 - self.t0) + + def normAngle(self): + '''normAngle() ... returns the angle opposite between the two tangents''' + + # The normal angle is perpendicular to the "average tangent" of the kink. The question + # is into which direction to turn. One lies in the center between the two edges and the + # other is opposite to that. As it turns out, the magnitude of the tangents tell it all. + if self.t0 > self.t1: + return PathGeom.normalizeAngle((self.t0 + self.t1 + math.pi) / 2) + return PathGeom.normalizeAngle((self.t0 + self.t1 - math.pi) / 2) + + def position(self): + '''position() ... position of the edge's intersection''' + return self.m0.positionEnd() + + def x(self): + return self.position().x + + def y(self): + return self.position().y + + def __repr__(self): + return f"({self.x():.4f}, {self.y():.4f})[t0={180*self.t0/math.pi:.2f}, t1={180*self.t1/math.pi:.2f}, deflection={180*self.deflection()/math.pi:.2f}, normAngle={180*self.normAngle()/math.pi:.2f}]" + +def createKinks(maneuver): + k = [] + moves = maneuver.getMoves() + if moves: + move0 = moves[0] + prev = move0 + for m in moves[1:]: + k.append(Kink(prev, m)) + prev = m + if PathGeom.pointsCoincide(move0.positionBegin(), prev.positionEnd()): + k.append(Kink(prev, move0)) + return k + + +def findDogboneKinks(maneuver, threshold): + '''findDogboneKinks(maneuver, threshold) ... return all kinks fitting the criteria. +A positive threshold angle returns all kinks on the right side, and a negative all kinks on the left side''' + 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] + # you asked for it ... + return createKinks(maneuver) + + +class Bone (object): + '''A Bone holds all the information of a bone and the kink it is attached to''' + + def __init__(self, kink, angle, instr=None): + self.kink = kink + self.angle = angle + self.instr = [] if instr is None else instr + + def addInstruction(self, instr): + self.instr.append(instr) + +def kink_to_path(kink, g0=False): + return Path.Path([PathLanguage.instruction_to_command(instr) for instr in [kink.m0, kink.m1]]) + +def bone_to_path(bone, g0=False): + kink = bone.kink + cmds = [] + if g0 and not PathGeom.pointsCoincide(kink.m0.positionBegin(), FreeCAD.Vector(0, 0, 0)): + pos = kink.m0.positionBegin() + param = {} + if not PathGeom.isRoughly(pos.x, 0): + param['X'] = pos.x + if not PathGeom.isRoughly(pos.y, 0): + param['Y'] = pos.y + cmds.append(Path.Command('G0', param)) + for instr in [kink.m0, bone.instr[0], bone.instr[1], kink.m1]: + cmds.append(PathLanguage.instruction_to_command(instr)) + return Path.Path(cmds) + diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index a770c223cb..bd0a5bf420 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -39,6 +39,9 @@ def Startup(): if not Processed: Path.Log.debug("Initializing PathGui") from Path.Op.Gui import Adaptive + from Path.Post import Command + from Path.Tools import Controller + from Path.Tools.Gui import Controller from PathScripts import PathArray from PathScripts import PathComment from PathScripts import PathCustomGui @@ -60,7 +63,6 @@ def Startup(): from PathScripts import PathMillFaceGui from PathScripts import PathPocketGui from PathScripts import PathPocketShapeGui - from Path.Post import Command from PathScripts import PathProbeGui from PathScripts import PathProfileGui from PathScripts import PathPropertyBagGui @@ -71,8 +73,6 @@ def Startup(): from PathScripts import PathSlotGui from PathScripts import PathStop from PathScripts import PathThreadMillingGui - from PathScripts import PathToolController - from PathScripts import PathToolControllerGui from PathScripts import PathToolLibraryEditor from PathScripts import PathToolLibraryManager from PathScripts import PathUtilsGui diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 7d33c423a2..b764b8a825 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -24,11 +24,11 @@ from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import Path.Post.Processor as PostProcessor +from Path.Post.Processor import PostProcessor +import Path.Tools.Controller as PathToolController import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock -import PathScripts.PathToolController as PathToolController import PathScripts.PathUtil as PathUtil import json import time diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index c6490d5698..3a453f9d36 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -28,6 +28,8 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Tools.Gui.Bit as PathToolBitGui +import Path.Tools.Gui.Controller as PathToolControllerGui import PathScripts.PathGeom as PathGeom import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathJob as PathJob @@ -36,8 +38,6 @@ import PathScripts.PathJobDlg as PathJobDlg import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathStock as PathStock -import PathScripts.PathToolBitGui as PathToolBitGui -import PathScripts.PathToolControllerGui as PathToolControllerGui import PathScripts.PathToolLibraryEditor as PathToolLibraryEditor import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py index 2a2bf7cff9..f0eb0366bd 100644 --- a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py +++ b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py @@ -26,9 +26,10 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Tools.Controller as PathToolController +import Path.Tools.Gui.BitLibraryCmd as PathToolBitLibraryCmd import PathScripts import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathToolBitLibraryCmd as PathToolBitLibraryCmd import PathScripts.PathToolEdit as PathToolEdit import PathScripts.PathToolLibraryManager as ToolLibraryManager import PathScripts.PathUtils as PathUtils @@ -289,7 +290,7 @@ class EditorPanel: Path.Log.debug("tool: {}, toolnum: {}".format(tool, toolnum)) if self.job: label = "T{}: {}".format(toolnum, tool.Name) - tc = PathScripts.PathToolController.Create( + tc = PathToolController.Create( label, tool=tool, toolNumber=int(toolnum) ) self.job.Proxy.addToolController(tc) @@ -300,7 +301,7 @@ class EditorPanel: and job.Label == targetlist ): label = "T{}: {}".format(toolnum, tool.Name) - tc = PathScripts.PathToolController.Create( + tc = PathToolController.Create( label, tool=tool, toolNumber=int(toolnum) ) job.Proxy.addToolController(tc) diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index c9387d9948..feafdf1f45 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -23,8 +23,8 @@ import FreeCADGui import FreeCAD import Path +import Path.Tools.Controller as PathToolsController import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils from PySide import QtGui @@ -46,7 +46,7 @@ class PathUtilsUserInput(object): for sel in FreeCADGui.Selection.getSelectionEx(): if hasattr(sel.Object, "Proxy"): if isinstance( - sel.Object.Proxy, PathScripts.PathToolController.ToolController + sel.Object.Proxy, PathToolController.ToolController ): if tc is None: tc = sel.Object diff --git a/src/Mod/Path/PathTests/TestPathHelpers.py b/src/Mod/Path/PathTests/TestPathHelpers.py index 653fafabad..a0371ceb86 100644 --- a/src/Mod/Path/PathTests/TestPathHelpers.py +++ b/src/Mod/Path/PathTests/TestPathHelpers.py @@ -23,10 +23,10 @@ import FreeCAD import Part import Path +import Path.Tools.Controller as PathToolController import PathFeedRate import PathMachineState import PathScripts.PathGeom as PathGeom -import PathScripts.PathToolController as PathToolController import PathScripts.PathUtils as PathUtils from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index 71252c2a11..c58d6b04ea 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -import PathScripts.PathToolBit as PathToolBit +import Path.Tools.Bit as PathToolBit import PathTests.PathTestUtils as PathTestUtils import glob import os diff --git a/src/Mod/Path/PathTests/TestPathToolController.py b/src/Mod/Path/PathTests/TestPathToolController.py index afa2291e10..eb1dbab7f9 100644 --- a/src/Mod/Path/PathTests/TestPathToolController.py +++ b/src/Mod/Path/PathTests/TestPathToolController.py @@ -22,9 +22,9 @@ import FreeCAD import Path +import Path.Tools.Bit as PathToolBit +import Path.Tools.Controller as PathToolController import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathToolBit as PathToolBit -import PathScripts.PathToolController as PathToolController from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathVcarve.py b/src/Mod/Path/PathTests/TestPathVcarve.py index 51bb80678e..e90bd08f47 100644 --- a/src/Mod/Path/PathTests/TestPathVcarve.py +++ b/src/Mod/Path/PathTests/TestPathVcarve.py @@ -21,8 +21,8 @@ # *************************************************************************** import FreeCAD +import Path.Tools.Bit as PathToolBit import PathScripts.PathGeom as PathGeom -import PathScripts.PathToolBit as PathToolBit import PathScripts.PathVcarve as PathVcarve import math From 7fe03f2b7843bcab66ed108eee88d576e3f3226d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 10 Aug 2022 20:40:28 -0700 Subject: [PATCH 08/33] Fixed PathPost when post processing is cancelled --- src/Mod/Path/Path/Post/Command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/Path/Post/Command.py b/src/Mod/Path/Path/Post/Command.py index 9d17cb326b..796b63d46f 100644 --- a/src/Mod/Path/Path/Post/Command.py +++ b/src/Mod/Path/Path/Post/Command.py @@ -39,7 +39,7 @@ from PySide import QtCore, QtGui from datetime import datetime from PySide.QtCore import QT_TRANSLATE_NOOP -LOG_MODULE = PathLog.thisModule() +LOG_MODULE = Path.Log.thisModule() if True: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) From 289c1f8c87206e82ff0212eab517c64016c4056d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 10 Aug 2022 22:48:43 -0700 Subject: [PATCH 09/33] Moved dressups into Path.Dressup module --- src/Mod/Path/CMakeLists.txt | 51 ++- src/Mod/Path/Generators/helix_generator.py | 2 +- src/Mod/Path/InitGui.py | 3 +- .../Dressup/Gui/AxisMap.py} | 6 +- .../Dressup/Gui/Dogbone.py} | 6 +- .../Dressup/Gui/Dragknife.py} | 6 +- .../Dressup/Gui/LeadInOut.py} | 8 +- .../Dressup/Gui/PathBoundary.py} | 6 +- .../Dressup/Gui/Preferences.py} | 0 .../Dressup/Gui/RampEntry.py} | 8 +- .../Dressup/Gui/TagPreferences.py} | 2 +- .../Dressup/Gui/Tags.py} | 6 +- .../Dressup/Gui/ZCorrect.py} | 6 +- src/Mod/Path/Path/Dressup/Gui/__init__.py | 0 .../Dressup/PathBoundary.py} | 2 +- .../Dressup/Tags.py} | 4 +- .../PathDressup.py => Path/Dressup/Utils.py} | 0 src/Mod/Path/Path/Dressup/__init__.py | 0 src/Mod/Path/Path/Post/Command.py | 2 +- src/Mod/Path/PathCommands.py | 4 +- src/Mod/Path/PathMachineState.py | 2 +- src/Mod/Path/PathScripts/PathArray.py | 2 +- .../Path/PathScripts/PathDressupDogboneII.py | 123 ------- src/Mod/Path/PathScripts/PathDressupTag.py | 318 ------------------ src/Mod/Path/PathScripts/PathGuiInit.py | 16 +- src/Mod/Path/PathScripts/PathSimulatorGui.py | 2 +- .../Path/PathTests/TestPathDressupDogbone.py | 2 +- .../PathTests/TestPathDressupHoldingTags.py | 2 +- 28 files changed, 86 insertions(+), 503 deletions(-) rename src/Mod/Path/{PathScripts/PathDressupAxisMap.py => Path/Dressup/Gui/AxisMap.py} (98%) rename src/Mod/Path/{PathScripts/PathDressupDogbone.py => Path/Dressup/Gui/Dogbone.py} (99%) rename src/Mod/Path/{PathScripts/PathDressupDragknife.py => Path/Dressup/Gui/Dragknife.py} (98%) rename src/Mod/Path/{PathScripts/PathDressupLeadInOut.py => Path/Dressup/Gui/LeadInOut.py} (98%) rename src/Mod/Path/{PathScripts/PathDressupPathBoundaryGui.py => Path/Dressup/Gui/PathBoundary.py} (98%) rename src/Mod/Path/{PathScripts/PathPreferencesPathDressup.py => Path/Dressup/Gui/Preferences.py} (100%) rename src/Mod/Path/{PathScripts/PathDressupRampEntry.py => Path/Dressup/Gui/RampEntry.py} (99%) rename src/Mod/Path/{PathScripts/PathDressupTagPreferences.py => Path/Dressup/Gui/TagPreferences.py} (98%) rename src/Mod/Path/{PathScripts/PathDressupTagGui.py => Path/Dressup/Gui/Tags.py} (99%) rename src/Mod/Path/{PathScripts/PathDressupZCorrect.py => Path/Dressup/Gui/ZCorrect.py} (98%) create mode 100644 src/Mod/Path/Path/Dressup/Gui/__init__.py rename src/Mod/Path/{PathScripts/PathDressupPathBoundary.py => Path/Dressup/PathBoundary.py} (99%) rename src/Mod/Path/{PathScripts/PathDressupHoldingTags.py => Path/Dressup/Tags.py} (99%) rename src/Mod/Path/{PathScripts/PathDressup.py => Path/Dressup/Utils.py} (100%) create mode 100644 src/Mod/Path/Path/Dressup/__init__.py delete mode 100644 src/Mod/Path/PathScripts/PathDressupDogboneII.py delete mode 100644 src/Mod/Path/PathScripts/PathDressupTag.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index ac7cfbbcfd..9706526e26 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -30,6 +30,27 @@ SET(PathPython_SRCS Path/Log.py ) +SET(PathPythonDressup_SRCS + Path/Dressup/__init__.py + Path/Dressup/Utils.py + Path/Dressup/PathBoundary.py + Path/Dressup/Tags.py +) + +SET(PathPythonDressupGui_SRCS + Path/Dressup/Gui/__init__.py + Path/Dressup/Gui/AxisMap.py + Path/Dressup/Gui/Dogbone.py + Path/Dressup/Gui/Dragknife.py + Path/Dressup/Gui/LeadInOut.py + Path/Dressup/Gui/PathBoundary.py + Path/Dressup/Gui/Preferences.py + Path/Dressup/Gui/RampEntry.py + Path/Dressup/Gui/Tags.py + Path/Dressup/Gui/TagPreferences.py + Path/Dressup/Gui/ZCorrect.py +) + SET(PathPythonOp_SRCS Path/Op/__init__.py Path/Op/Adaptive.py @@ -112,19 +133,6 @@ SET(PathScripts_SRCS PathScripts/PathCustomGui.py PathScripts/PathDeburr.py PathScripts/PathDeburrGui.py - PathScripts/PathDressup.py - PathScripts/PathDressupAxisMap.py - PathScripts/PathDressupDogbone.py - PathScripts/PathDressupDragknife.py - PathScripts/PathDressupHoldingTags.py - PathScripts/PathDressupLeadInOut.py - PathScripts/PathDressupPathBoundary.py - PathScripts/PathDressupPathBoundaryGui.py - PathScripts/PathDressupRampEntry.py - PathScripts/PathDressupTag.py - PathScripts/PathDressupTagGui.py - PathScripts/PathDressupTagPreferences.py - PathScripts/PathDressupZCorrect.py PathScripts/PathDrilling.py PathScripts/PathDrillingGui.py PathScripts/PathEngrave.py @@ -159,7 +167,6 @@ SET(PathScripts_SRCS PathScripts/PathPocketShapeGui.py PathScripts/PathPreferences.py PathScripts/PathPreferencesAdvanced.py - PathScripts/PathPreferencesPathDressup.py PathScripts/PathPreferencesPathJob.py PathScripts/PathProbe.py PathScripts/PathProbeGui.py @@ -338,6 +345,8 @@ SET(Path_Data SET(all_files ${PathScripts_SRCS} ${PathPython_SRCS} + ${PathPythonDressup_SRCS} + ${PathPythonDressupGui_SRCS} ${PathPythonOp_SRCS} ${PathPythonOpGui_SRCS} ${PathPythonPost_SRCS} @@ -384,6 +393,20 @@ INSTALL( Mod/Path/Path ) +INSTALL( + FILES + ${PathPythonDressup_SRCS} + DESTINATION + Mod/Path/Path/Dressup +) + +INSTALL( + FILES + ${PathPythonDressupGui_SRCS} + DESTINATION + Mod/Path/Path/Dressup/Gui +) + INSTALL( FILES ${PathPythonOp_SRCS} diff --git a/src/Mod/Path/Generators/helix_generator.py b/src/Mod/Path/Generators/helix_generator.py index 260cedd6de..8002dd2760 100644 --- a/src/Mod/Path/Generators/helix_generator.py +++ b/src/Mod/Path/Generators/helix_generator.py @@ -31,7 +31,7 @@ __doc__ = "Generates the helix for a single spot targetshape" __contributors__ = "russ4262 (Russell Johnson), Lorenz Hüdepohl" -if True: +if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) else: diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index d263aa1167..cc112ee55a 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -59,7 +59,8 @@ class PathWorkbench(Workbench): global PathCommandGroup # Add preferences pages - before loading PathGui to properly order pages of Path group - from PathScripts import PathPreferencesPathJob, PathPreferencesPathDressup + from PathScripts import PathPreferencesPathJob + import Path.Dressup.Gui.Preferences as PathPreferencesPathDressup translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/PathScripts/PathDressupAxisMap.py b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDressupAxisMap.py rename to src/Mod/Path/Path/Dressup/Gui/AxisMap.py index 365fc01d09..0bded9c6e5 100644 --- a/src/Mod/Path/PathScripts/PathDressupAxisMap.py +++ b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py @@ -298,19 +298,19 @@ class CommandPathDressup: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupAxisMap") + FreeCADGui.addModule("Path.Dressup.Gui.AxisMap") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "AxisMapDressup")' ) - FreeCADGui.doCommand("PathScripts.PathDressupAxisMap.ObjectDressup(obj)") + FreeCADGui.doCommand("Path.Dressup.Gui.AxisMap.ObjectDressup(obj)") FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name) FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)") FreeCADGui.doCommand("obj.Base = base") FreeCADGui.doCommand("obj.Radius = 45") FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)") FreeCADGui.doCommand( - "obj.ViewObject.Proxy = PathScripts.PathDressupAxisMap.ViewProviderDressup(obj.ViewObject)" + "obj.ViewObject.Proxy = Path.Dressup.Gui.AxisMap.ViewProviderDressup(obj.ViewObject)" ) FreeCADGui.doCommand( "Gui.ActiveDocument.getObject(base.Name).Visibility = False" diff --git a/src/Mod/Path/PathScripts/PathDressupDogbone.py b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py similarity index 99% rename from src/Mod/Path/PathScripts/PathDressupDogbone.py rename to src/Mod/Path/Path/Dressup/Gui/Dogbone.py index 9106bb196a..c6f792e5cd 100644 --- a/src/Mod/Path/PathScripts/PathDressupDogbone.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py @@ -26,7 +26,7 @@ from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathDressup as PathDressup +import Path.Dressup.Utils as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils @@ -1394,9 +1394,9 @@ class CommandDressupDogbone(object): # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Dogbone Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupDogbone") + FreeCADGui.addModule("Path.Dressup.Gui.Dogbone") FreeCADGui.doCommand( - "PathScripts.PathDressupDogbone.Create(FreeCAD.ActiveDocument.%s)" + "Path.Dressup.Gui.Dogbone.Create(FreeCAD.ActiveDocument.%s)" % baseObject.Name ) # FreeCAD.ActiveDocument.commitTransaction() # Final `commitTransaction()` called via TaskPanel.accept() diff --git a/src/Mod/Path/PathScripts/PathDressupDragknife.py b/src/Mod/Path/Path/Dressup/Gui/Dragknife.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDressupDragknife.py rename to src/Mod/Path/Path/Dressup/Gui/Dragknife.py index 1f10cfc94d..41f15921c7 100644 --- a/src/Mod/Path/PathScripts/PathDressupDragknife.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dragknife.py @@ -639,18 +639,18 @@ class CommandDressupDragknife: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupDragknife") + FreeCADGui.addModule("Path.Dressup.Gui.Dragknife") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","DragknifeDressup")' ) - FreeCADGui.doCommand("PathScripts.PathDressupDragknife.ObjectDressup(obj)") + FreeCADGui.doCommand("Path.Dressup.Gui.Dragknife.ObjectDressup(obj)") FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name) FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)") FreeCADGui.doCommand("obj.Base = base") FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)") FreeCADGui.doCommand( - "obj.ViewObject.Proxy = PathScripts.PathDressupDragknife.ViewProviderDressup(obj.ViewObject)" + "obj.ViewObject.Proxy = Path.Dressup.Gui.Dragknife.ViewProviderDressup(obj.ViewObject)" ) FreeCADGui.doCommand( "Gui.ActiveDocument.getObject(base.Name).Visibility = False" diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDressupLeadInOut.py rename to src/Mod/Path/Path/Dressup/Gui/LeadInOut.py index 49b94593a2..5d1e181576 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py @@ -26,7 +26,7 @@ from __future__ import print_function import FreeCAD import FreeCADGui import Path -import PathScripts.PathDressup as PathDressup +import Path.Dressup.Utils as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils import math @@ -749,13 +749,13 @@ class CommandPathDressupLeadInOut: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create LeadInOut Dressup") - FreeCADGui.addModule("PathScripts.PathDressupLeadInOut") + FreeCADGui.addModule("Path.Dressup.Gui.LeadInOut") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "LeadInOutDressup")' ) FreeCADGui.doCommand( - "dbo = PathScripts.PathDressupLeadInOut.ObjectDressup(obj)" + "dbo = Path.Dressup.Gui.LeadInOut.ObjectDressup(obj)" ) FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name) FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)") @@ -763,7 +763,7 @@ class CommandPathDressupLeadInOut: FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)") FreeCADGui.doCommand("dbo.setup(obj)") FreeCADGui.doCommand( - "obj.ViewObject.Proxy = PathScripts.PathDressupLeadInOut.ViewProviderDressup(obj.ViewObject)" + "obj.ViewObject.Proxy = Path.Dressup.Gui.LeadInOut.ViewProviderDressup(obj.ViewObject)" ) FreeCADGui.doCommand( "Gui.ActiveDocument.getObject(base.Name).Visibility = False" diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py b/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py rename to src/Mod/Path/Path/Dressup/Gui/PathBoundary.py index d99b677b6a..50b4781f1c 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundaryGui.py +++ b/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py @@ -25,8 +25,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Dressup.PathBoundary as PathDressupPathBoundary import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathDressupPathBoundary as PathDressupPathBoundary if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -289,9 +289,9 @@ class CommandPathDressupPathBoundary: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Path Boundary Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupPathBoundaryGui") + FreeCADGui.addModule("Path.Dressup.Gui.PathBoundary") FreeCADGui.doCommand( - "PathScripts.PathDressupPathBoundaryGui.Create(App.ActiveDocument.%s)" + "Path.Dressup.Gui.PathBoundary.Create(App.ActiveDocument.%s)" % baseObject.Name ) # FreeCAD.ActiveDocument.commitTransaction() # Final `commitTransaction()` called via TaskPanel.accept() diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathDressup.py b/src/Mod/Path/Path/Dressup/Gui/Preferences.py similarity index 100% rename from src/Mod/Path/PathScripts/PathPreferencesPathDressup.py rename to src/Mod/Path/Path/Dressup/Gui/Preferences.py diff --git a/src/Mod/Path/PathScripts/PathDressupRampEntry.py b/src/Mod/Path/Path/Dressup/Gui/RampEntry.py similarity index 99% rename from src/Mod/Path/PathScripts/PathDressupRampEntry.py rename to src/Mod/Path/Path/Dressup/Gui/RampEntry.py index e77783542a..80dd8a89f0 100644 --- a/src/Mod/Path/PathScripts/PathDressupRampEntry.py +++ b/src/Mod/Path/Path/Dressup/Gui/RampEntry.py @@ -24,7 +24,7 @@ from PathScripts import PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathDressup as PathDressup +import Path.Dressup.Utils as PathDressup import PathScripts.PathGeom as PathGeom import math @@ -936,20 +936,20 @@ class CommandPathDressupRampEntry: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create RampEntry Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupRampEntry") + FreeCADGui.addModule("Path.Dressup.Gui.RampEntry") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "RampEntryDressup")' ) FreeCADGui.doCommand( - "dbo = PathScripts.PathDressupRampEntry.ObjectDressup(obj)" + "dbo = Path.Dressup.Gui.RampEntry.ObjectDressup(obj)" ) FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name) FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)") FreeCADGui.doCommand("obj.Base = base") FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)") FreeCADGui.doCommand( - "obj.ViewObject.Proxy = PathScripts.PathDressupRampEntry.ViewProviderDressup(obj.ViewObject)" + "obj.ViewObject.Proxy = Path.Dressup.Gui.RampEntry.ViewProviderDressup(obj.ViewObject)" ) FreeCADGui.doCommand( "Gui.ActiveDocument.getObject(base.Name).Visibility = False" diff --git a/src/Mod/Path/PathScripts/PathDressupTagPreferences.py b/src/Mod/Path/Path/Dressup/Gui/TagPreferences.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDressupTagPreferences.py rename to src/Mod/Path/Path/Dressup/Gui/TagPreferences.py index 91032dd4ff..248afda9c2 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagPreferences.py +++ b/src/Mod/Path/Path/Dressup/Gui/TagPreferences.py @@ -22,8 +22,8 @@ import FreeCAD import Path +import Path.Dressup.Gui.Preferences as PathPreferencesPathDressup import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathPreferencesPathDressup as PathPreferencesPathDressup if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathScripts/PathDressupTagGui.py b/src/Mod/Path/Path/Dressup/Gui/Tags.py similarity index 99% rename from src/Mod/Path/PathScripts/PathDressupTagGui.py rename to src/Mod/Path/Path/Dressup/Gui/Tags.py index 13b0d5ced4..284e166496 100644 --- a/src/Mod/Path/PathScripts/PathDressupTagGui.py +++ b/src/Mod/Path/Path/Dressup/Gui/Tags.py @@ -26,8 +26,8 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Dressup.Tags as PathDressupTag import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathDressupHoldingTags as PathDressupTag import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathPreferences as PathPreferences @@ -588,9 +588,9 @@ class CommandPathDressupTag: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Tag Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupTagGui") + FreeCADGui.addModule("Path.Dressup.Gui.Tags") FreeCADGui.doCommand( - "PathScripts.PathDressupTagGui.Create(App.ActiveDocument.%s)" + "Path.Dressup.Gui.Tags.Create(App.ActiveDocument.%s)" % baseObject.Name ) # FreeCAD.ActiveDocument.commitTransaction() # Final `commitTransaction()` called via TaskPanel.accept() diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDressupZCorrect.py rename to src/Mod/Path/Path/Dressup/Gui/ZCorrect.py index 16bbbdef01..a93417fe70 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py @@ -380,15 +380,15 @@ class CommandPathDressup: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Dress-up") - FreeCADGui.addModule("PathScripts.PathDressupZCorrect") + FreeCADGui.addModule("Path.Dressup.Gui.ZCorrect") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "ZCorrectDressup")' ) - FreeCADGui.doCommand("PathScripts.PathDressupZCorrect.ObjectDressup(obj)") + FreeCADGui.doCommand("Path.Dressup.Gui.ZCorrect.ObjectDressup(obj)") FreeCADGui.doCommand("obj.Base = FreeCAD.ActiveDocument." + selection[0].Name) FreeCADGui.doCommand( - "PathScripts.PathDressupZCorrect.ViewProviderDressup(obj.ViewObject)" + "Path.Dressup.Gui.ZCorrect.ViewProviderDressup(obj.ViewObject)" ) FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)") FreeCADGui.doCommand( diff --git a/src/Mod/Path/Path/Dressup/Gui/__init__.py b/src/Mod/Path/Path/Dressup/Gui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py b/src/Mod/Path/Path/Dressup/PathBoundary.py similarity index 99% rename from src/Mod/Path/PathScripts/PathDressupPathBoundary.py rename to src/Mod/Path/Path/Dressup/PathBoundary.py index f4730e30a6..00d7bb5a01 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py +++ b/src/Mod/Path/Path/Dressup/PathBoundary.py @@ -23,7 +23,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathDressup as PathDressup +import Path.Dressup.Utils as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathStock as PathStock import PathScripts.PathUtil as PathUtil diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/Path/Dressup/Tags.py similarity index 99% rename from src/Mod/Path/PathScripts/PathDressupHoldingTags.py rename to src/Mod/Path/Path/Dressup/Tags.py index 864cd4d5a8..fcd1b8b1c0 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/Path/Dressup/Tags.py @@ -20,12 +20,12 @@ # * * # *************************************************************************** -from PathScripts.PathDressupTagPreferences import HoldingTagPreferences +from Path.Dressup.Gui.TagPreferences import HoldingTagPreferences from PathScripts.PathUtils import waiting_effects from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathDressup as PathDressup +import Path.Dressup.Utils as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathScripts/PathDressup.py b/src/Mod/Path/Path/Dressup/Utils.py similarity index 100% rename from src/Mod/Path/PathScripts/PathDressup.py rename to src/Mod/Path/Path/Dressup/Utils.py diff --git a/src/Mod/Path/Path/Dressup/__init__.py b/src/Mod/Path/Path/Dressup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Path/Post/Command.py b/src/Mod/Path/Path/Post/Command.py index 796b63d46f..5a1eb5f812 100644 --- a/src/Mod/Path/Path/Post/Command.py +++ b/src/Mod/Path/Path/Post/Command.py @@ -41,7 +41,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = Path.Log.thisModule() -if True: +if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) else: diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index 1409d930d6..621133e86b 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -161,7 +161,7 @@ class _ToggleOperation: return False try: for sel in FreeCADGui.Selection.getSelectionEx(): - selProxy = PathScripts.PathDressup.baseOp(sel.Object).Proxy + selProxy = Path.Dressup.Utils.baseOp(sel.Object).Proxy if not isinstance( selProxy, PathScripts.PathOp.ObjectOp ) and not isinstance(selProxy, PathScripts.PathArray.ObjectArray): @@ -172,7 +172,7 @@ class _ToggleOperation: def Activated(self): for sel in FreeCADGui.Selection.getSelectionEx(): - op = PathScripts.PathDressup.baseOp(sel.Object) + op = Path.Dressup.Utils.baseOp(sel.Object) op.Active = not op.Active op.ViewObject.Visibility = op.Active diff --git a/src/Mod/Path/PathMachineState.py b/src/Mod/Path/PathMachineState.py index 933f9a3503..7334b1e4c7 100644 --- a/src/Mod/Path/PathMachineState.py +++ b/src/Mod/Path/PathMachineState.py @@ -30,7 +30,7 @@ import Path import FreeCAD from PathScripts.PathGeom import CmdMoveRapid, CmdMoveAll, CmdMoveDrill -if True: +if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) else: diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 070900075c..7e4c8fdf3c 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -24,7 +24,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts -from PathScripts.PathDressup import toolController +from Path.Dressup.Utils import toolController from PySide import QtCore import math import random diff --git a/src/Mod/Path/PathScripts/PathDressupDogboneII.py b/src/Mod/Path/PathScripts/PathDressupDogboneII.py deleted file mode 100644 index 564448c08c..0000000000 --- a/src/Mod/Path/PathScripts/PathDressupDogboneII.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2022 sliptonic * -# * * -# * 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 PathScripts.PathGeom as PathGeom -import PathScripts.PathLanguage as PathLanguage -import PathScripts.Path.Log as Path.Log -import math - -class Kink (object): - '''A Kink represents the angle at which two moves connect. -A positive kink angle represents a move to the left, and a negative angle represents a move to the right.''' - - def __init__(self, m0, m1): - if m1 is None: - m1 = m0[1] - m0 = m0[0] - self.m0 = m0 - self.m1 = m1 - self.t0 = m0.anglesOfTangents()[1] - self.t1 = m1.anglesOfTangents()[0] - - def deflection(self): - '''deflection() ... returns the tangential difference of the two edges at their intersection''' - return PathGeom.normalizeAngle(self.t1 - self.t0) - - def normAngle(self): - '''normAngle() ... returns the angle opposite between the two tangents''' - - # The normal angle is perpendicular to the "average tangent" of the kink. The question - # is into which direction to turn. One lies in the center between the two edges and the - # other is opposite to that. As it turns out, the magnitude of the tangents tell it all. - if self.t0 > self.t1: - return PathGeom.normalizeAngle((self.t0 + self.t1 + math.pi) / 2) - return PathGeom.normalizeAngle((self.t0 + self.t1 - math.pi) / 2) - - def position(self): - '''position() ... position of the edge's intersection''' - return self.m0.positionEnd() - - def x(self): - return self.position().x - - def y(self): - return self.position().y - - def __repr__(self): - return f"({self.x():.4f}, {self.y():.4f})[t0={180*self.t0/math.pi:.2f}, t1={180*self.t1/math.pi:.2f}, deflection={180*self.deflection()/math.pi:.2f}, normAngle={180*self.normAngle()/math.pi:.2f}]" - -def createKinks(maneuver): - k = [] - moves = maneuver.getMoves() - if moves: - move0 = moves[0] - prev = move0 - for m in moves[1:]: - k.append(Kink(prev, m)) - prev = m - if PathGeom.pointsCoincide(move0.positionBegin(), prev.positionEnd()): - k.append(Kink(prev, move0)) - return k - - -def findDogboneKinks(maneuver, threshold): - '''findDogboneKinks(maneuver, threshold) ... return all kinks fitting the criteria. -A positive threshold angle returns all kinks on the right side, and a negative all kinks on the left side''' - 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] - # you asked for it ... - return createKinks(maneuver) - - -class Bone (object): - '''A Bone holds all the information of a bone and the kink it is attached to''' - - def __init__(self, kink, angle, instr=None): - self.kink = kink - self.angle = angle - self.instr = [] if instr is None else instr - - def addInstruction(self, instr): - self.instr.append(instr) - -def kink_to_path(kink, g0=False): - return Path.Path([PathLanguage.instruction_to_command(instr) for instr in [kink.m0, kink.m1]]) - -def bone_to_path(bone, g0=False): - kink = bone.kink - cmds = [] - if g0 and not PathGeom.pointsCoincide(kink.m0.positionBegin(), FreeCAD.Vector(0, 0, 0)): - pos = kink.m0.positionBegin() - param = {} - if not PathGeom.isRoughly(pos.x, 0): - param['X'] = pos.x - if not PathGeom.isRoughly(pos.y, 0): - param['Y'] = pos.y - cmds.append(Path.Command('G0', param)) - for instr in [kink.m0, bone.instr[0], bone.instr[1], kink.m1]: - cmds.append(PathLanguage.instruction_to_command(instr)) - return Path.Path(cmds) - diff --git a/src/Mod/Path/PathScripts/PathDressupTag.py b/src/Mod/Path/PathScripts/PathDressupTag.py deleted file mode 100644 index 89e97a5f2b..0000000000 --- a/src/Mod/Path/PathScripts/PathDressupTag.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -from PathScripts.PathDressupTagPreferences import HoldingTagPreferences -from PySide.QtCore import QT_TRANSLATE_NOOP -import FreeCAD -import Path -import PathScripts.PathDressup as PathDressup -import PathScripts.PathGeom as PathGeom -import PathScripts.PathUtils as PathUtils -import math - -# lazily loaded modules -from lazy_loader.lazy_loader import LazyLoader - -DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") -Part = LazyLoader("Part", globals(), "Part") - - -if False: - Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) - Path.Log.trackModule(Path.Log.thisModule()) -else: - Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) - -translate = FreeCAD.Qt.translate - - -MaxInt = 99999999999999 - - -class TagSolid: - def __init__(self, proxy, z, R): - self.proxy = proxy - self.z = z - self.toolRadius = R - self.angle = math.fabs(proxy.obj.Angle) - self.width = math.fabs(proxy.obj.Width) - self.height = math.fabs(proxy.obj.Height) - self.radius = math.fabs(proxy.obj.Radius) - self.actualHeight = self.height - self.fullWidth = 2 * self.toolRadius + self.width - - r1 = self.fullWidth / 2 - self.r1 = r1 - self.r2 = r1 - height = self.actualHeight * 1.01 - radius = 0 - if self.angle == 90 and height > 0: - # cylinder - self.solid = Part.makeCylinder(r1, height) - radius = min(min(self.radius, r1), self.height) - Path.Log.debug("Part.makeCylinder(%f, %f)" % (r1, height)) - elif self.angle > 0.0 and height > 0.0: - # cone - rad = math.radians(self.angle) - tangens = math.tan(rad) - dr = height / tangens - if dr < r1: - # with top - r2 = r1 - dr - s = height / math.sin(rad) - radius = min(r2, s) * math.tan((math.pi - rad) / 2) * 0.95 - else: - # triangular - r2 = 0 - height = r1 * tangens * 1.01 - self.actualHeight = height - self.r2 = r2 - Path.Log.debug("Part.makeCone(r1=%.2f, r2=%.2f, h=%.2f)" % (r1, r2, height)) - self.solid = Part.makeCone(r1, r2, height) - else: - # degenerated case - no tag - Path.Log.debug("Part.makeSphere(%.2f)" % (r1 / 10000)) - self.solid = Part.makeSphere(r1 / 10000) - - radius = min(self.radius, radius) - self.realRadius = radius - if radius != 0: - Path.Log.debug("makeFillet(%.4f)" % radius) - self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]]) - - # lastly determine the center of the model, we want to make sure the seam of - # the tag solid points away (in the hopes it doesn't coincide with a path) - self.baseCenter = FreeCAD.Vector( - (proxy.ptMin.x + proxy.ptMax.x) / 2, (proxy.ptMin.y + proxy.ptMax.y) / 2, 0 - ) - - def cloneAt(self, pos): - clone = self.solid.copy() - pos.z = 0 - angle = -PathGeom.getAngle(pos - self.baseCenter) * 180 / math.pi - clone.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), angle) - pos.z = self.z - self.actualHeight * 0.01 - clone.translate(pos) - return clone - - -class ObjectDressup: - def __init__(self, obj, base): - - obj.addProperty( - "App::PropertyLink", - "Base", - "Base", - QT_TRANSLATE_NOOP("App::Property", "The base path to modify"), - ) - obj.addProperty( - "App::PropertyLength", - "Width", - "Tag", - QT_TRANSLATE_NOOP("App::Property", "Width of tags."), - ) - obj.addProperty( - "App::PropertyLength", - "Height", - "Tag", - QT_TRANSLATE_NOOP("App::Property", "Height of tags."), - ) - obj.addProperty( - "App::PropertyAngle", - "Angle", - "Tag", - QT_TRANSLATE_NOOP("App::Property", "Angle of tag plunge and ascent."), - ) - obj.addProperty( - "App::PropertyLength", - "Radius", - "Tag", - QT_TRANSLATE_NOOP("App::Property", "Radius of the fillet for the tag."), - ) - obj.addProperty( - "App::PropertyVectorList", - "Positions", - "Tag", - QT_TRANSLATE_NOOP("App::Property", "Locations of inserted holding tags"), - ) - obj.addProperty( - "App::PropertyIntegerList", - "Disabled", - "Tag", - QT_TRANSLATE_NOOP("App::Property", "IDs of disabled holding tags"), - ) - obj.addProperty( - "App::PropertyInteger", - "SegmentationFactor", - "Tag", - QT_TRANSLATE_NOOP( - "App::Property", - "Factor determining the # of segments used to approximate rounded tags.", - ), - ) - - obj.Proxy = self - obj.Base = base - - self.obj = obj - self.solids = [] - - # initialized later - self.edges = None - self.masterSolid = None - self.ptMax = None - self.ptMin = None - self.tagSolid = None - self.wire = None - - def __getstate__(self): - return None - - def __setstate__(self, state): - return None - - def assignDefaultValues(self): - self.obj.Width = HoldingTagPreferences.defaultWidth(self.toolRadius() * 2) - self.obj.Height = HoldingTagPreferences.defaultHeight(self.toolRadius()) - self.obj.Angle = HoldingTagPreferences.defaultAngle() - self.obj.Radius = HoldingTagPreferences.defaultRadius() - - def execute(self, obj): - Path.Log.track() - if not obj.Base: - Path.Log.error(translate("Path_DressupTag", "No Base object found.")) - return - if not obj.Base.isDerivedFrom("Path::Feature"): - Path.Log.error( - translate("Path_DressupTag", "Base is not a Path::Feature object.") - ) - return - if not obj.Base.Path: - Path.Log.error( - translate("Path_DressupTag", "Base doesn't have a Path to dress-up.") - ) - return - if not obj.Base.Path.Commands: - Path.Log.error(translate("Path_DressupTag", "Base Path is empty.")) - return - - self.obj = obj - - minZ = +MaxInt - minX = minZ - minY = minZ - - maxZ = -MaxInt - maxX = maxZ - maxY = maxZ - - # the assumption is that all helixes are in the xy-plane - in other words there is no - # intermittent point of a command that has a lower/higher Z-position than the start and - # and end positions of a command. - lastPt = FreeCAD.Vector(0, 0, 0) - for cmd in obj.Base.Path.Commands: - pt = PathGeom.commandEndPoint(cmd, lastPt) - if lastPt.x != pt.x: - maxX = max(pt.x, maxX) - minX = min(pt.x, minX) - if lastPt.y != pt.y: - maxY = max(pt.y, maxY) - minY = min(pt.y, minY) - if lastPt.z != pt.z: - maxZ = max(pt.z, maxZ) - minZ = min(pt.z, minZ) - lastPt = pt - Path.Log.debug( - "bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)" - % (minX, minY, minZ, maxX, maxY, maxZ) - ) - self.ptMin = FreeCAD.Vector(minX, minY, minZ) - self.ptMax = FreeCAD.Vector(maxX, maxY, maxZ) - self.masterSolid = TagSolid(self, minZ, self.toolRadius()) - self.solids = [self.masterSolid.cloneAt(pos) for pos in self.obj.Positions] - self.tagSolid = Part.Compound(self.solids) - - self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) - self.edges = self.wire.Edges - - maxTagZ = minZ + obj.Height.Value - - # lastX = 0 - # lastY = 0 - lastZ = 0 - - commands = [] - - for cmd in obj.Base.Path.Commands: - if cmd in PathGeom.CmdMove: - if lastZ <= maxTagZ or cmd.Parameters.get("Z", lastZ) <= maxTagZ: - pass - else: - commands.append(cmd) - else: - commands.append(cmd) - - obj.Path = obj.Base.Path - - Path.Log.track() - - def toolRadius(self): - return float(PathDressup.toolController(self.obj.Base).Tool.Diameter) / 2.0 - - def addTagsToDocument(self): - for i, solid in enumerate(self.solids): - obj = FreeCAD.ActiveDocument.addObject("Part::Compound", "tag_%02d" % i) - obj.Shape = solid - - def supportsTagGeneration(self, obj): - return False - - def pointIsOnPath(self, obj, p): - for e in self.edges: - if DraftGeomUtils.isPtOnEdge(p, e): - return True - return False - - -def Create(baseObject, name="DressupTag"): - """ - Create(basePath, name = 'DressupTag') ... create tag dressup object for the given base path. - """ - if not baseObject.isDerivedFrom("Path::Feature"): - Path.Log.error( - translate("Path_DressupTag", "The selected object is not a path") + "\n" - ) - return None - - if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): - Path.Log.error(translate("Path_DressupTag", "Please select a Profile object")) - return None - - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - dbo = ObjectDressup(obj, baseObject) - job = PathUtils.findParentJob(baseObject) - job.addOperation(obj) - dbo.assignDefaultValues() - return obj - - -Path.Log.notice("Loading Path_DressupTag... done\n") diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index bd0a5bf420..3b34efe08e 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -39,6 +39,14 @@ def Startup(): if not Processed: Path.Log.debug("Initializing PathGui") from Path.Op.Gui import Adaptive + from Path.Dressup.Gui import AxisMap + from Path.Dressup.Gui import Dogbone + from Path.Dressup.Gui import Dragknife + from Path.Dressup.Gui import LeadInOut + from Path.Dressup.Gui import PathBoundary + from Path.Dressup.Gui import RampEntry + from Path.Dressup.Gui import Tags + from Path.Dressup.Gui import ZCorrect from Path.Post import Command from Path.Tools import Controller from Path.Tools.Gui import Controller @@ -46,14 +54,6 @@ def Startup(): from PathScripts import PathComment from PathScripts import PathCustomGui from PathScripts import PathDeburrGui - from PathScripts import PathDressupAxisMap - from PathScripts import PathDressupDogbone - from PathScripts import PathDressupDragknife - from PathScripts import PathDressupLeadInOut - from PathScripts import PathDressupPathBoundaryGui - from PathScripts import PathDressupRampEntry - from PathScripts import PathDressupTagGui - from PathScripts import PathDressupZCorrect from PathScripts import PathDrillingGui from PathScripts import PathEngraveGui from PathScripts import PathFixture diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 583cf5c4b5..1403796632 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -22,8 +22,8 @@ import FreeCAD import Path +import Path.Dressup.Utils as PathDressup import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathUtil as PathUtil import PathScripts.PathJob as PathJob diff --git a/src/Mod/Path/PathTests/TestPathDressupDogbone.py b/src/Mod/Path/PathTests/TestPathDressupDogbone.py index 7f160c4c24..d8622c983c 100644 --- a/src/Mod/Path/PathTests/TestPathDressupDogbone.py +++ b/src/Mod/Path/PathTests/TestPathDressupDogbone.py @@ -22,7 +22,7 @@ import FreeCAD import Path -import PathScripts.PathDressupDogbone as PathDressupDogbone +import Path.Dressup.Gui.Dogbone as PathDressupDogbone import PathScripts.PathJob as PathJob import PathScripts.PathProfileFaces as PathProfileFaces diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 103e9f56fa..713a516b2e 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -24,7 +24,7 @@ import PathTests.PathTestUtils as PathTestUtils import math from FreeCAD import Vector -from PathScripts.PathDressupHoldingTags import Tag +from Path.Dressup.Tags import Tag class TestHoldingTags(PathTestUtils.PathTestBase): From 45f2765418de79d611023b50b63d07289b7ff198 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 11 Aug 2022 23:53:33 -0700 Subject: [PATCH 10/33] Moved all Path operations with model and gui into Path.Op module --- src/Mod/Path/CMakeLists.txt | 102 ++++++------ src/Mod/Path/InitGui.py | 6 +- src/Mod/Path/Path/Dressup/Gui/Dogbone.py | 2 +- src/Mod/Path/Path/Op/Adaptive.py | 2 +- .../PathAreaOp.py => Path/Op/Area.py} | 2 +- .../PathOp.py => Path/Op/Base.py} | 0 .../Op/CircularHoleBase.py} | 2 +- .../PathCustom.py => Path/Op/Custom.py} | 3 +- .../PathDeburr.py => Path/Op/Deburr.py} | 8 +- .../PathDrilling.py => Path/Op/Drilling.py} | 4 +- .../PathEngrave.py => Path/Op/Engrave.py} | 4 +- .../Op/EngraveBase.py} | 8 +- src/Mod/Path/Path/Op/Gui/Adaptive.py | 2 +- .../PathOpGui.py => Path/Op/Gui/Base.py} | 2 +- .../Op/Gui/CircularHoleBase.py} | 2 +- .../Op/Gui/Custom.py} | 4 +- .../Op/Gui/Deburr.py} | 4 +- .../Op/Gui/Drilling.py} | 6 +- .../Op/Gui/Engrave.py} | 4 +- .../PathHelixGui.py => Path/Op/Gui/Helix.py} | 6 +- .../Op/Gui/MillFace.py} | 8 +- .../Op/Gui/Pocket.py} | 6 +- .../Op/Gui/PocketBase.py} | 4 +- .../Op/Gui/PocketShape.py} | 6 +- .../PathProbeGui.py => Path/Op/Gui/Probe.py} | 4 +- .../Op/Gui/Profile.py} | 4 +- .../PathSlotGui.py => Path/Op/Gui/Slot.py} | 4 +- .../Op/Gui/Surface.py} | 4 +- .../Op/Gui/ThreadMilling.py} | 6 +- .../Op/Gui/Vcarve.py} | 4 +- .../Op/Gui/Waterline.py} | 4 +- .../PathHelix.py => Path/Op/Helix.py} | 4 +- .../PathMillFace.py => Path/Op/MillFace.py} | 2 +- .../PathPocket.py => Path/Op/Pocket.py} | 4 +- .../Op/PocketBase.py} | 4 +- .../Op/PocketShape.py} | 4 +- .../PathProbe.py => Path/Op/Probe.py} | 2 +- .../PathProfile.py => Path/Op/Profile.py} | 4 +- .../PathSlot.py => Path/Op/Slot.py} | 2 +- .../PathSurface.py => Path/Op/Surface.py} | 4 +- .../Op/SurfaceSupport.py} | 4 +- .../Op/ThreadMilling.py} | 4 +- .../PathOpTools.py => Path/Op/Util.py} | 2 +- .../PathVcarve.py => Path/Op/Vcarve.py} | 4 +- .../PathWaterline.py => Path/Op/Waterline.py} | 4 +- src/Mod/Path/Path/Post/scripts/gcode_pre.py | 4 +- .../Path/Path/Post/scripts/heidenhain_post.py | 6 +- src/Mod/Path/PathCommands.py | 4 +- .../PathScripts/PathFeatureExtensionsGui.py | 2 +- src/Mod/Path/PathScripts/PathGuiInit.py | 26 +-- .../Path/PathScripts/PathProfileContour.py | 2 +- .../Path/PathScripts/PathProfileContourGui.py | 7 +- src/Mod/Path/PathScripts/PathProfileEdges.py | 3 +- .../Path/PathScripts/PathProfileEdgesGui.py | 6 +- src/Mod/Path/PathScripts/PathProfileFaces.py | 2 +- .../Path/PathScripts/PathProfileFacesGui.py | 6 +- src/Mod/Path/PathScripts/PathSimpleCopy.py | 7 +- src/Mod/Path/PathScripts/PathUtils.py | 4 +- src/Mod/Path/PathScripts/PathUtilsGui.py | 2 +- src/Mod/Path/PathTests/TestPathDeburr.py | 2 +- src/Mod/Path/PathTests/TestPathHelix.py | 2 +- .../{TestPathOpTools.py => TestPathOpUtil.py} | 152 +++++++++--------- .../Path/PathTests/TestPathThreadMilling.py | 2 +- src/Mod/Path/PathTests/TestPathVcarve.py | 2 +- src/Mod/Path/TestPathApp.py | 4 +- 65 files changed, 261 insertions(+), 259 deletions(-) rename src/Mod/Path/{PathScripts/PathAreaOp.py => Path/Op/Area.py} (99%) rename src/Mod/Path/{PathScripts/PathOp.py => Path/Op/Base.py} (100%) rename src/Mod/Path/{PathScripts/PathCircularHoleBase.py => Path/Op/CircularHoleBase.py} (99%) rename src/Mod/Path/{PathScripts/PathCustom.py => Path/Op/Custom.py} (98%) rename src/Mod/Path/{PathScripts/PathDeburr.py => Path/Op/Deburr.py} (98%) rename src/Mod/Path/{PathScripts/PathDrilling.py => Path/Op/Drilling.py} (99%) rename src/Mod/Path/{PathScripts/PathEngrave.py => Path/Op/Engrave.py} (98%) rename src/Mod/Path/{PathScripts/PathEngraveBase.py => Path/Op/EngraveBase.py} (96%) rename src/Mod/Path/{PathScripts/PathOpGui.py => Path/Op/Gui/Base.py} (99%) rename src/Mod/Path/{PathScripts/PathCircularHoleBaseGui.py => Path/Op/Gui/CircularHoleBase.py} (99%) rename src/Mod/Path/{PathScripts/PathCustomGui.py => Path/Op/Gui/Custom.py} (97%) rename src/Mod/Path/{PathScripts/PathDeburrGui.py => Path/Op/Gui/Deburr.py} (98%) rename src/Mod/Path/{PathScripts/PathDrillingGui.py => Path/Op/Gui/Drilling.py} (98%) rename src/Mod/Path/{PathScripts/PathEngraveGui.py => Path/Op/Gui/Engrave.py} (98%) rename src/Mod/Path/{PathScripts/PathHelixGui.py => Path/Op/Gui/Helix.py} (96%) rename src/Mod/Path/{PathScripts/PathMillFaceGui.py => Path/Op/Gui/MillFace.py} (94%) rename src/Mod/Path/{PathScripts/PathPocketGui.py => Path/Op/Gui/Pocket.py} (94%) rename src/Mod/Path/{PathScripts/PathPocketBaseGui.py => Path/Op/Gui/PocketBase.py} (99%) rename src/Mod/Path/{PathScripts/PathPocketShapeGui.py => Path/Op/Gui/PocketShape.py} (95%) rename src/Mod/Path/{PathScripts/PathProbeGui.py => Path/Op/Gui/Probe.py} (98%) rename src/Mod/Path/{PathScripts/PathProfileGui.py => Path/Op/Gui/Profile.py} (98%) rename src/Mod/Path/{PathScripts/PathSlotGui.py => Path/Op/Gui/Slot.py} (99%) rename src/Mod/Path/{PathScripts/PathSurfaceGui.py => Path/Op/Gui/Surface.py} (99%) rename src/Mod/Path/{PathScripts/PathThreadMillingGui.py => Path/Op/Gui/ThreadMilling.py} (98%) rename src/Mod/Path/{PathScripts/PathVcarveGui.py => Path/Op/Gui/Vcarve.py} (98%) rename src/Mod/Path/{PathScripts/PathWaterlineGui.py => Path/Op/Gui/Waterline.py} (98%) rename src/Mod/Path/{PathScripts/PathHelix.py => Path/Op/Helix.py} (98%) rename src/Mod/Path/{PathScripts/PathMillFace.py => Path/Op/MillFace.py} (99%) rename src/Mod/Path/{PathScripts/PathPocket.py => Path/Op/Pocket.py} (99%) rename src/Mod/Path/{PathScripts/PathPocketBase.py => Path/Op/PocketBase.py} (99%) rename src/Mod/Path/{PathScripts/PathPocketShape.py => Path/Op/PocketShape.py} (99%) rename src/Mod/Path/{PathScripts/PathProbe.py => Path/Op/Probe.py} (99%) rename src/Mod/Path/{PathScripts/PathProfile.py => Path/Op/Profile.py} (99%) rename src/Mod/Path/{PathScripts/PathSlot.py => Path/Op/Slot.py} (99%) rename src/Mod/Path/{PathScripts/PathSurface.py => Path/Op/Surface.py} (99%) rename src/Mod/Path/{PathScripts/PathSurfaceSupport.py => Path/Op/SurfaceSupport.py} (99%) rename src/Mod/Path/{PathScripts/PathThreadMilling.py => Path/Op/ThreadMilling.py} (99%) rename src/Mod/Path/{PathScripts/PathOpTools.py => Path/Op/Util.py} (99%) rename src/Mod/Path/{PathScripts/PathVcarve.py => Path/Op/Vcarve.py} (99%) rename src/Mod/Path/{PathScripts/PathWaterline.py => Path/Op/Waterline.py} (99%) rename src/Mod/Path/PathTests/{TestPathOpTools.py => TestPathOpUtil.py} (86%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 9706526e26..0c5bebe456 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -51,16 +51,6 @@ SET(PathPythonDressupGui_SRCS Path/Dressup/Gui/ZCorrect.py ) -SET(PathPythonOp_SRCS - Path/Op/__init__.py - Path/Op/Adaptive.py -) - -SET(PathPythonOpGui_SRCS - Path/Op/Gui/__init__.py - Path/Op/Gui/Adaptive.py -) - SET(PathPythonTools_SRCS Path/Tools/__init__.py Path/Tools/Bit.py @@ -120,24 +110,62 @@ SET(PathPythonPostScripts_SRCS Path/Post/scripts/uccnc_post.py ) +SET(PathPythonOp_SRCS + Path/Op/__init__.py + Path/Op/Adaptive.py + Path/Op/Area.py + Path/Op/Base.py + Path/Op/CircularHoleBase.py + Path/Op/Custom.py + Path/Op/Deburr.py + Path/Op/Engrave.py + Path/Op/EngraveBase.py + Path/Op/Drilling.py + Path/Op/Helix.py + Path/Op/MillFace.py + Path/Op/Pocket.py + Path/Op/PocketBase.py + Path/Op/PocketShape.py + Path/Op/Probe.py + Path/Op/Profile.py + Path/Op/Slot.py + Path/Op/Surface.py + Path/Op/SurfaceSupport.py + Path/Op/ThreadMilling.py + Path/Op/Util.py + Path/Op/Vcarve.py + Path/Op/Waterline.py +) + +SET(PathPythonOpGui_SRCS + Path/Op/Gui/__init__.py + Path/Op/Gui/Adaptive.py + Path/Op/Gui/Base.py + Path/Op/Gui/CircularHoleBase.py + Path/Op/Gui/Custom.py + Path/Op/Gui/Deburr.py + Path/Op/Gui/Drilling.py + Path/Op/Gui/Engrave.py + Path/Op/Gui/Helix.py + Path/Op/Gui/MillFace.py + Path/Op/Gui/PocketBase.py + Path/Op/Gui/Pocket.py + Path/Op/Gui/PocketShape.py + Path/Op/Gui/Probe.py + Path/Op/Gui/Profile.py + Path/Op/Gui/Slot.py + Path/Op/Gui/Surface.py + Path/Op/Gui/ThreadMilling.py + Path/Op/Gui/Vcarve.py + Path/Op/Gui/Waterline.py +) + SET(PathScripts_SRCS PathScripts/drillableLib.py - PathScripts/PathAreaOp.py PathScripts/PathArray.py - PathScripts/PathCircularHoleBase.py - PathScripts/PathCircularHoleBaseGui.py PathScripts/PathCamoticsGui.py PathScripts/PathComment.py PathScripts/PathCopy.py - PathScripts/PathCustom.py - PathScripts/PathCustomGui.py - PathScripts/PathDeburr.py - PathScripts/PathDeburrGui.py - PathScripts/PathDrilling.py - PathScripts/PathDrillingGui.py - PathScripts/PathEngrave.py - PathScripts/PathEngraveBase.py - PathScripts/PathEngraveGui.py PathScripts/PathFeatureExtensions.py PathScripts/PathFeatureExtensionsGui.py PathScripts/PathFixture.py @@ -145,8 +173,6 @@ SET(PathScripts_SRCS PathScripts/PathGetPoint.py PathScripts/PathGui.py PathScripts/PathGuiInit.py - PathScripts/PathHelix.py - PathScripts/PathHelixGui.py PathScripts/PathHop.py PathScripts/PathIconViewProvider.py PathScripts/PathInspect.py @@ -154,30 +180,15 @@ SET(PathScripts_SRCS PathScripts/PathJobCmd.py PathScripts/PathJobDlg.py PathScripts/PathJobGui.py - PathScripts/PathMillFace.py - PathScripts/PathMillFaceGui.py - PathScripts/PathOp.py - PathScripts/PathOpGui.py - PathScripts/PathOpTools.py - PathScripts/PathPocket.py - PathScripts/PathPocketBase.py - PathScripts/PathPocketBaseGui.py - PathScripts/PathPocketGui.py - PathScripts/PathPocketShape.py - PathScripts/PathPocketShapeGui.py PathScripts/PathPreferences.py PathScripts/PathPreferencesAdvanced.py PathScripts/PathPreferencesPathJob.py - PathScripts/PathProbe.py - PathScripts/PathProbeGui.py - PathScripts/PathProfile.py PathScripts/PathProfileContour.py PathScripts/PathProfileContourGui.py PathScripts/PathProfileEdges.py PathScripts/PathProfileEdgesGui.py PathScripts/PathProfileFaces.py PathScripts/PathProfileFacesGui.py - PathScripts/PathProfileGui.py PathScripts/PathProperty.py PathScripts/PathPropertyBag.py PathScripts/PathPropertyBagGui.py @@ -190,25 +201,14 @@ SET(PathScripts_SRCS PathScripts/PathSetupSheetOpPrototypeGui.py PathScripts/PathSimpleCopy.py PathScripts/PathSimulatorGui.py - PathScripts/PathSlot.py - PathScripts/PathSlotGui.py PathScripts/PathStock.py PathScripts/PathStop.py - PathScripts/PathSurface.py - PathScripts/PathSurfaceGui.py - PathScripts/PathSurfaceSupport.py - PathScripts/PathThreadMilling.py - PathScripts/PathThreadMillingGui.py PathScripts/PathToolEdit.py PathScripts/PathToolLibraryEditor.py PathScripts/PathToolLibraryManager.py PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py - PathScripts/PathVcarve.py - PathScripts/PathVcarveGui.py - PathScripts/PathWaterline.py - PathScripts/PathWaterlineGui.py PathScripts/__init__.py ) @@ -287,7 +287,7 @@ SET(PathTests_SRCS PathTests/TestPathHelpers.py PathTests/TestPathHelixGenerator.py PathTests/TestPathLog.py - PathTests/TestPathOpTools.py + PathTests/TestPathOpUtil.py PathTests/TestPathPost.py PathTests/TestPathPreferences.py PathTests/TestPathPropertyBag.py diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cc112ee55a..952fa06b98 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -173,8 +173,8 @@ class PathWorkbench(Workbench): try: import ocl # pylint: disable=unused-variable - from PathScripts import PathSurfaceGui - from PathScripts import PathWaterlineGui + from Path.Op.Gui import Surface + from Path.Op.Gui import Waterline threedopcmdlist.extend(["Path_Surface", "Path_Waterline"]) threedcmdgroup = ["Path_3dTools"] @@ -325,7 +325,7 @@ class PathWorkbench(Workbench): "", ["Path_ExportTemplate"] + self.toolbitctxmenu ) menuAppended = True - if isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp): + if isinstance(obj.Proxy, Path.Op.Base.ObjectOp): self.appendContextMenu( "", ["Path_OperationCopy", "Path_OpActiveToggle"] ) diff --git a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py index c6f792e5cd..1d051a5a06 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py @@ -1342,7 +1342,7 @@ class ViewProviderDressup(object): def Create(base, name="DogboneDressup"): """ - Create(obj, name='DogboneDressup') ... dresses the given PathProfile/PathContour object with dogbones. + Create(obj, name='DogboneDressup') ... dresses the given Path.Op.Profile/PathContour object with dogbones. """ obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) dbo = ObjectDressup(obj, base) diff --git a/src/Mod/Path/Path/Op/Adaptive.py b/src/Mod/Path/Path/Op/Adaptive.py index db0a66b5fb..d456d423b3 100644 --- a/src/Mod/Path/Path/Op/Adaptive.py +++ b/src/Mod/Path/Path/Op/Adaptive.py @@ -23,7 +23,7 @@ # *************************************************************************** import Path -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils import PathScripts.PathGeom as PathGeom import FreeCAD diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/Path/Op/Area.py similarity index 99% rename from src/Mod/Path/PathScripts/PathAreaOp.py rename to src/Mod/Path/Path/Op/Area.py index 7f9246416c..5c35188b36 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -23,7 +23,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/Path/Op/Base.py similarity index 100% rename from src/Mod/Path/PathScripts/PathOp.py rename to src/Mod/Path/Path/Op/Base.py diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/Path/Op/CircularHoleBase.py similarity index 99% rename from src/Mod/Path/PathScripts/PathCircularHoleBase.py rename to src/Mod/Path/Path/Op/CircularHoleBase.py index 1ca025d9cd..b0e539d792 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/Path/Op/CircularHoleBase.py @@ -23,7 +23,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp import PathScripts.drillableLib as drillableLib # lazily loaded modules diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/Path/Op/Custom.py similarity index 98% rename from src/Mod/Path/PathScripts/PathCustom.py rename to src/Mod/Path/Path/Op/Custom.py index 7b2459d175..2d24957329 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/Path/Op/Custom.py @@ -22,8 +22,7 @@ import FreeCAD import Path - -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/Path/Op/Deburr.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDeburr.py rename to src/Mod/Path/Path/Op/Deburr.py index f590e7501f..d464441715 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/Path/Op/Deburr.py @@ -23,10 +23,10 @@ import FreeCAD import Path -import PathScripts.PathEngraveBase as PathEngraveBase +import Path.Op.Base as PathOp +import Path.Op.EngraveBase as PathEngraveBase +import Path.Op.Util as PathOpUtil import PathScripts.PathGeom as PathGeom -import PathScripts.PathOp as PathOp -import PathScripts.PathOpTools as PathOpTools import math from PySide.QtCore import QT_TRANSLATE_NOOP @@ -410,7 +410,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): for w in basewires: self.adjusted_basewires.append(w) - wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side) + wire = PathOpUtil.offsetWire(w, base.Shape, offset, True, side) if wire: wires.append(wire) diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/Path/Op/Drilling.py similarity index 99% rename from src/Mod/Path/PathScripts/PathDrilling.py rename to src/Mod/Path/Path/Op/Drilling.py index 348dd0695d..0434f5f6a3 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/Path/Op/Drilling.py @@ -28,10 +28,10 @@ from Generators import drill_generator as generator import FreeCAD import Part import Path +import Path.Op.Base as PathOp +import Path.Op.CircularHoleBase as PathCircularHoleBase import PathFeedRate import PathMachineState -import PathScripts.PathCircularHoleBase as PathCircularHoleBase -import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathEngrave.py b/src/Mod/Path/Path/Op/Engrave.py similarity index 98% rename from src/Mod/Path/PathScripts/PathEngrave.py rename to src/Mod/Path/Path/Op/Engrave.py index f0da66e0e8..022bea8409 100644 --- a/src/Mod/Path/PathScripts/PathEngrave.py +++ b/src/Mod/Path/Path/Op/Engrave.py @@ -22,8 +22,8 @@ import FreeCAD import Path -import PathScripts.PathEngraveBase as PathEngraveBase -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp +import Path.Op.EngraveBase as PathEngraveBase import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/Path/Op/EngraveBase.py similarity index 96% rename from src/Mod/Path/PathScripts/PathEngraveBase.py rename to src/Mod/Path/Path/Op/EngraveBase.py index 25c6e35cad..6169b12ffe 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/Path/Op/EngraveBase.py @@ -22,9 +22,9 @@ from lazy_loader.lazy_loader import LazyLoader import Path +import Path.Op.Base as PathOp import PathScripts.PathGeom as PathGeom -import PathScripts.PathOp as PathOp -import PathScripts.PathOpTools as PathOpTools +import Path.Op.Util as PathOpUtil import copy __doc__ = "Base class for all ops in the engrave family." @@ -64,7 +64,7 @@ class ObjectOp(PathOp.ObjectOp): decomposewires = [] for wire in wires: - decomposewires.extend(PathOpTools.makeWires(wire.Edges)) + decomposewires.extend(PathOpUtil.makeWires(wire.Edges)) wires = decomposewires for wire in wires: @@ -75,7 +75,7 @@ class ObjectOp(PathOp.ObjectOp): start_idx = obj.StartVertex edges = wire.Edges - # edges = copy.copy(PathOpTools.orientWire(offset, forward).Edges) + # edges = copy.copy(PathOpUtil.orientWire(offset, forward).Edges) # Path.Log.track("wire: {} offset: {}".format(len(wire.Edges), len(edges))) # edges = Part.sortEdges(edges)[0] # Path.Log.track("edges: {}".format(len(edges))) diff --git a/src/Mod/Path/Path/Op/Gui/Adaptive.py b/src/Mod/Path/Path/Op/Gui/Adaptive.py index a4836b1dd0..7879e633ca 100644 --- a/src/Mod/Path/Path/Op/Gui/Adaptive.py +++ b/src/Mod/Path/Path/Op/Gui/Adaptive.py @@ -22,7 +22,7 @@ # *************************************************************************** import Path.Op.Adaptive as PathAdaptive -import PathScripts.PathOpGui as PathOpGui +import Path.Op.Gui.Base as PathOpGui from PySide import QtCore import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui import FreeCADGui diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/Path/Op/Gui/Base.py similarity index 99% rename from src/Mod/Path/PathScripts/PathOpGui.py rename to src/Mod/Path/Path/Op/Gui/Base.py index c60fb018b8..c582bd119d 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/Path/Op/Gui/Base.py @@ -23,12 +23,12 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Base as PathOp import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathGui as PathGui import PathScripts.PathJob as PathJob -import PathScripts.PathOp as PathOp import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSelection as PathSelection import PathScripts.PathSetupSheet as PathSetupSheet diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py b/src/Mod/Path/Path/Op/Gui/CircularHoleBase.py similarity index 99% rename from src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py rename to src/Mod/Path/Path/Op/Gui/CircularHoleBase.py index 7cde8bcc77..95d10bd4b4 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py +++ b/src/Mod/Path/Path/Op/Gui/CircularHoleBase.py @@ -23,8 +23,8 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui diff --git a/src/Mod/Path/PathScripts/PathCustomGui.py b/src/Mod/Path/Path/Op/Gui/Custom.py similarity index 97% rename from src/Mod/Path/PathScripts/PathCustomGui.py rename to src/Mod/Path/Path/Op/Gui/Custom.py index 184229247d..4b00ad7d51 100644 --- a/src/Mod/Path/PathScripts/PathCustomGui.py +++ b/src/Mod/Path/Path/Op/Gui/Custom.py @@ -22,8 +22,8 @@ import FreeCAD import FreeCADGui -import PathScripts.PathCustom as PathCustom -import PathScripts.PathOpGui as PathOpGui +import Path.Op.Custom as PathCustom +import Path.Op.Gui.Base as PathOpGui from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathDeburrGui.py b/src/Mod/Path/Path/Op/Gui/Deburr.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDeburrGui.py rename to src/Mod/Path/Path/Op/Gui/Deburr.py index 77daa3fddd..be4c67799a 100644 --- a/src/Mod/Path/PathScripts/PathDeburrGui.py +++ b/src/Mod/Path/Path/Op/Gui/Deburr.py @@ -23,9 +23,9 @@ import FreeCAD import FreeCADGui import Path -import PathScripts.PathDeburr as PathDeburr +import Path.Op.Deburr as PathDeburr +import Path.Op.Gui.Base as PathOpGui import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathDrillingGui.py b/src/Mod/Path/Path/Op/Gui/Drilling.py similarity index 98% rename from src/Mod/Path/PathScripts/PathDrillingGui.py rename to src/Mod/Path/Path/Op/Gui/Drilling.py index e1b18f9b91..5075cc5c30 100644 --- a/src/Mod/Path/PathScripts/PathDrillingGui.py +++ b/src/Mod/Path/Path/Op/Gui/Drilling.py @@ -23,11 +23,11 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Drilling as PathDrilling +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui -import PathScripts.PathDrilling as PathDrilling import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui from PySide import QtCore diff --git a/src/Mod/Path/PathScripts/PathEngraveGui.py b/src/Mod/Path/Path/Op/Gui/Engrave.py similarity index 98% rename from src/Mod/Path/PathScripts/PathEngraveGui.py rename to src/Mod/Path/Path/Op/Gui/Engrave.py index 24dc13e30b..0283d9b6f9 100644 --- a/src/Mod/Path/PathScripts/PathEngraveGui.py +++ b/src/Mod/Path/Path/Op/Gui/Engrave.py @@ -23,9 +23,9 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Engrave as PathEngrave +import Path.Op.Gui.Base as PathOpGui import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathEngrave as PathEngrave -import PathScripts.PathOpGui as PathOpGui import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/Path/Op/Gui/Helix.py similarity index 96% rename from src/Mod/Path/PathScripts/PathHelixGui.py rename to src/Mod/Path/Path/Op/Gui/Helix.py index 331fe1ca35..b74af23fe6 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/Path/Op/Gui/Helix.py @@ -23,10 +23,10 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui +import Path.Op.Helix as PathHelix import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui -import PathScripts.PathHelix as PathHelix -import PathScripts.PathOpGui as PathOpGui import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathMillFaceGui.py b/src/Mod/Path/Path/Op/Gui/MillFace.py similarity index 94% rename from src/Mod/Path/PathScripts/PathMillFaceGui.py rename to src/Mod/Path/Path/Op/Gui/MillFace.py index cdd7945b0c..ae264e49d4 100644 --- a/src/Mod/Path/PathScripts/PathMillFaceGui.py +++ b/src/Mod/Path/Path/Op/Gui/MillFace.py @@ -23,10 +23,10 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathMillFace as PathMillFace -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathPocketBaseGui as PathPocketBaseGui -import PathScripts.PathPocketShape as PathPocketShape +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.PocketBase as PathPocketBaseGui +import Path.Op.MillFace as PathMillFace +import Path.Op.PocketShape as PathPocketShape import FreeCADGui __title__ = "Path Face Mill Operation UI" diff --git a/src/Mod/Path/PathScripts/PathPocketGui.py b/src/Mod/Path/Path/Op/Gui/Pocket.py similarity index 94% rename from src/Mod/Path/PathScripts/PathPocketGui.py rename to src/Mod/Path/Path/Op/Gui/Pocket.py index 008ab3807a..d733bcb3a7 100644 --- a/src/Mod/Path/PathScripts/PathPocketGui.py +++ b/src/Mod/Path/Path/Op/Gui/Pocket.py @@ -22,9 +22,9 @@ import FreeCAD import Path -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathPocket as PathPocket -import PathScripts.PathPocketBaseGui as PathPocketBaseGui +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.PocketBase as PathPocketBaseGui +import Path.Op.Pocket as PathPocket from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathPocketBaseGui.py b/src/Mod/Path/Path/Op/Gui/PocketBase.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPocketBaseGui.py rename to src/Mod/Path/Path/Op/Gui/PocketBase.py index b3c44d052e..0ba2f9647c 100644 --- a/src/Mod/Path/PathScripts/PathPocketBaseGui.py +++ b/src/Mod/Path/Path/Op/Gui/PocketBase.py @@ -23,10 +23,10 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Pocket as PathPocket import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathPocket as PathPocket __title__ = "Path Pocket Base Operation UI" __author__ = "sliptonic (Brad Collette)" diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/Path/Op/Gui/PocketShape.py similarity index 95% rename from src/Mod/Path/PathScripts/PathPocketShapeGui.py rename to src/Mod/Path/Path/Op/Gui/PocketShape.py index acd60454e2..70788fccce 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/Path/Op/Gui/PocketShape.py @@ -22,9 +22,9 @@ import FreeCAD import Path -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathPocketShape as PathPocketShape -import PathScripts.PathPocketBaseGui as PathPocketBaseGui +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.PocketBase as PathPocketBaseGui +import Path.Op.PocketShape as PathPocketShape import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/Path/Op/Gui/Probe.py similarity index 98% rename from src/Mod/Path/PathScripts/PathProbeGui.py rename to src/Mod/Path/Path/Op/Gui/Probe.py index f19429892f..071881038d 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/Path/Op/Gui/Probe.py @@ -23,9 +23,9 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Probe as PathProbe import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathProbe as PathProbe -import PathScripts.PathOpGui as PathOpGui import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathProfileGui.py b/src/Mod/Path/Path/Op/Gui/Profile.py similarity index 98% rename from src/Mod/Path/PathScripts/PathProfileGui.py rename to src/Mod/Path/Path/Op/Gui/Profile.py index 0a5513d61a..8cb09760e8 100644 --- a/src/Mod/Path/PathScripts/PathProfileGui.py +++ b/src/Mod/Path/Path/Op/Gui/Profile.py @@ -22,10 +22,10 @@ import FreeCAD import FreeCADGui +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Profile as PathProfile import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfile as PathProfile from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathSlotGui.py b/src/Mod/Path/Path/Op/Gui/Slot.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSlotGui.py rename to src/Mod/Path/Path/Op/Gui/Slot.py index 3162844f8b..68657be2be 100644 --- a/src/Mod/Path/PathScripts/PathSlotGui.py +++ b/src/Mod/Path/Path/Op/Gui/Slot.py @@ -22,10 +22,10 @@ import FreeCAD import FreeCADGui +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Slot as PathSlot import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathSlot as PathSlot import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui from PySide import QtCore diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/Path/Op/Gui/Surface.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSurfaceGui.py rename to src/Mod/Path/Path/Op/Gui/Surface.py index c44b130678..c428ec23ec 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/Path/Op/Gui/Surface.py @@ -24,10 +24,10 @@ from PySide import QtCore import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Surface as PathSurface import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathSurface as PathSurface __title__ = "Path Surface Operation UI" diff --git a/src/Mod/Path/PathScripts/PathThreadMillingGui.py b/src/Mod/Path/Path/Op/Gui/ThreadMilling.py similarity index 98% rename from src/Mod/Path/PathScripts/PathThreadMillingGui.py rename to src/Mod/Path/Path/Op/Gui/ThreadMilling.py index 84857ee374..422b9cfd1d 100644 --- a/src/Mod/Path/PathScripts/PathThreadMillingGui.py +++ b/src/Mod/Path/Path/Op/Gui/ThreadMilling.py @@ -23,11 +23,11 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui +import Path.Op.ThreadMilling as PathThreadMilling import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui -import PathScripts.PathThreadMilling as PathThreadMilling import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui import csv from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/Path/Op/Gui/Vcarve.py similarity index 98% rename from src/Mod/Path/PathScripts/PathVcarveGui.py rename to src/Mod/Path/Path/Op/Gui/Vcarve.py index c6eb6fc65c..dcff22fc07 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/Path/Op/Gui/Vcarve.py @@ -23,9 +23,9 @@ import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Vcarve as PathVcarve import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathVcarve as PathVcarve -import PathScripts.PathOpGui as PathOpGui import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/Path/Op/Gui/Waterline.py similarity index 98% rename from src/Mod/Path/PathScripts/PathWaterlineGui.py rename to src/Mod/Path/Path/Op/Gui/Waterline.py index 381c980adf..a593fe97d9 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/Path/Op/Gui/Waterline.py @@ -26,9 +26,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Waterline as PathWaterline import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathWaterline as PathWaterline __title__ = "Path Waterline Operation UI" __author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)" diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/Path/Op/Helix.py similarity index 98% rename from src/Mod/Path/PathScripts/PathHelix.py rename to src/Mod/Path/Path/Op/Helix.py index 25c8937a69..31f5436180 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/Path/Op/Helix.py @@ -27,8 +27,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Part import Path -import PathScripts.PathCircularHoleBase as PathCircularHoleBase -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp +import Path.Op.CircularHoleBase as PathCircularHoleBase import PathFeedRate diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/Path/Op/MillFace.py similarity index 99% rename from src/Mod/Path/PathScripts/PathMillFace.py rename to src/Mod/Path/Path/Op/MillFace.py index 454074eb6e..f633492ae5 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/Path/Op/MillFace.py @@ -24,7 +24,7 @@ from __future__ import print_function import FreeCAD import Path -import PathScripts.PathPocketBase as PathPocketBase +import Path.Op.PocketBase as PathPocketBase import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP import numpy diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/Path/Op/Pocket.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPocket.py rename to src/Mod/Path/Path/Op/Pocket.py index 6c24d6ec82..148ea4e0dc 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/Path/Op/Pocket.py @@ -24,8 +24,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Part import Path -import PathScripts.PathOp as PathOp -import PathScripts.PathPocketBase as PathPocketBase +import Path.Op.Base as PathOp +import Path.Op.PocketBase as PathPocketBase import PathScripts.PathUtils as PathUtils # lazily loaded modules diff --git a/src/Mod/Path/PathScripts/PathPocketBase.py b/src/Mod/Path/Path/Op/PocketBase.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPocketBase.py rename to src/Mod/Path/Path/Op/PocketBase.py index 1952a03f9f..e032b0a610 100644 --- a/src/Mod/Path/PathScripts/PathPocketBase.py +++ b/src/Mod/Path/Path/Op/PocketBase.py @@ -22,8 +22,8 @@ import FreeCAD import Path -import PathScripts.PathAreaOp as PathAreaOp -import PathScripts.PathOp as PathOp +import Path.Op.Area as PathAreaOp +import Path.Op.Base as PathOp from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/Path/Op/PocketShape.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPocketShape.py rename to src/Mod/Path/Path/Op/PocketShape.py index 63a13e7271..f5124908e7 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/Path/Op/PocketShape.py @@ -23,9 +23,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Op.Base as PathOp +import Path.Op.PocketBase as PathPocketBase import PathScripts.PathGeom as PathGeom -import PathScripts.PathOp as PathOp -import PathScripts.PathPocketBase as PathPocketBase # lazily loaded modules diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/Path/Op/Probe.py similarity index 99% rename from src/Mod/Path/PathScripts/PathProbe.py rename to src/Mod/Path/Path/Op/Probe.py index 7120cea74c..81ed2fdbb5 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/Path/Op/Probe.py @@ -24,7 +24,7 @@ from __future__ import print_function import FreeCAD import Path -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/Path/Op/Profile.py similarity index 99% rename from src/Mod/Path/PathScripts/PathProfile.py rename to src/Mod/Path/Path/Op/Profile.py index b5dab94dc1..7b79675ebf 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/Path/Op/Profile.py @@ -24,8 +24,8 @@ import FreeCAD import Path -import PathScripts.PathAreaOp as PathAreaOp -import PathScripts.PathOp as PathOp +import Path.Op.Area as PathAreaOp +import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils import PathScripts.drillableLib as drillableLib import math diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/Path/Op/Slot.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSlot.py rename to src/Mod/Path/Path/Op/Slot.py index 28eb47e51d..7d5ec7d81c 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/Path/Op/Slot.py @@ -32,8 +32,8 @@ __contributors__ = "" import FreeCAD from PySide import QtCore import Path +import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils -import PathScripts.PathOp as PathOp import math # lazily loaded modules diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/Path/Op/Surface.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSurface.py rename to src/Mod/Path/Path/Op/Surface.py index da67874b00..3093e65bd3 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/Path/Op/Surface.py @@ -47,8 +47,8 @@ except ImportError: from PySide.QtCore import QT_TRANSLATE_NOOP import Path -import PathScripts.PathOp as PathOp -import PathScripts.PathSurfaceSupport as PathSurfaceSupport +import Path.Op.Base as PathOp +import Path.Op.SurfaceSupport as PathSurfaceSupport import PathScripts.PathUtils as PathUtils import math import time diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/Path/Op/SurfaceSupport.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSurfaceSupport.py rename to src/Mod/Path/Path/Op/SurfaceSupport.py index 142fa14191..c262c45f33 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/Path/Op/SurfaceSupport.py @@ -30,8 +30,8 @@ __contributors__ = "" import FreeCAD import Path +import Path.Op.Util as PathOpUtil import PathScripts.PathUtils as PathUtils -import PathScripts.PathOpTools as PathOpTools import math # lazily loaded modules @@ -426,7 +426,7 @@ class PathGeometryGenerator: loop_cnt = 0 def _get_direction(w): - if PathOpTools._isWireClockwise(w): + if PathOpUtil._isWireClockwise(w): return 1 return -1 diff --git a/src/Mod/Path/PathScripts/PathThreadMilling.py b/src/Mod/Path/Path/Op/ThreadMilling.py similarity index 99% rename from src/Mod/Path/PathScripts/PathThreadMilling.py rename to src/Mod/Path/Path/Op/ThreadMilling.py index a8ffa56e5f..fa56a64541 100644 --- a/src/Mod/Path/PathScripts/PathThreadMilling.py +++ b/src/Mod/Path/Path/Op/ThreadMilling.py @@ -24,9 +24,9 @@ from __future__ import print_function import FreeCAD import Path -import PathScripts.PathCircularHoleBase as PathCircularHoleBase +import Path.Op.Base as PathOp +import Path.Op.CircularHoleBase as PathCircularHoleBase import PathScripts.PathGeom as PathGeom -import PathScripts.PathOp as PathOp import Generators.threadmilling_generator as threadmilling import math from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/Path/Op/Util.py similarity index 99% rename from src/Mod/Path/PathScripts/PathOpTools.py rename to src/Mod/Path/Path/Op/Util.py index ea7e8cda0e..2f547986a3 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/Path/Op/Util.py @@ -32,7 +32,7 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") -__title__ = "PathOpTools - Tools for Path operations." +__title__ = "Util - Utility functions for Path operations." __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Collection of functions used by various Path operations. The functions are specific to Path and the algorithms employed by Path's operations." diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/Path/Op/Vcarve.py similarity index 99% rename from src/Mod/Path/PathScripts/PathVcarve.py rename to src/Mod/Path/Path/Op/Vcarve.py index 70cdad768c..55f42a48f8 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/Path/Op/Vcarve.py @@ -23,8 +23,8 @@ import FreeCAD import Part import Path -import PathScripts.PathEngraveBase as PathEngraveBase -import PathScripts.PathOp as PathOp +import Path.Op.Base as PathOp +import Path.Op.EngraveBase as PathEngraveBase import PathScripts.PathUtils as PathUtils import PathScripts.PathGeom as PathGeom import PathScripts.PathPreferences as PathPreferences diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/Path/Op/Waterline.py similarity index 99% rename from src/Mod/Path/PathScripts/PathWaterline.py rename to src/Mod/Path/Path/Op/Waterline.py index ddce93d22a..6f7a0328f4 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/Path/Op/Waterline.py @@ -43,8 +43,8 @@ except ImportError: raise ImportError import Path -import PathScripts.PathOp as PathOp -import PathScripts.PathSurfaceSupport as PathSurfaceSupport +import Path.Op.Base as PathOp +import Path.Op.SurfaceSupport as PathSurfaceSupport import PathScripts.PathUtils as PathUtils import math import time diff --git a/src/Mod/Path/Path/Post/scripts/gcode_pre.py b/src/Mod/Path/Path/Post/scripts/gcode_pre.py index 6cac633bd3..8dc43ec50a 100644 --- a/src/Mod/Path/Path/Post/scripts/gcode_pre.py +++ b/src/Mod/Path/Path/Post/scripts/gcode_pre.py @@ -50,11 +50,11 @@ import re from PySide.QtCore import QT_TRANSLATE_NOOP if FreeCAD.GuiUp: - import PathScripts.PathCustomGui as PathCustomGui + import Path.Op.Gui.Custom as PathCustomGui PathCustom = PathCustomGui.PathCustom else: - import PathScripts.PathCustom as PathCustom + import Path.Op.Custom as PathCustom translate = FreeCAD.Qt.translate diff --git a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py index 1c4e9d113f..e42bda59f1 100644 --- a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py +++ b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py @@ -350,15 +350,15 @@ def export(objectslist, filename, argstring): MACHINE_LAST_POSITION["X"] = 99999 MACHINE_LAST_POSITION["Y"] = 99999 MACHINE_LAST_POSITION["Z"] = 99999 - elif isinstance(obj.Proxy, PathScripts.PathProfileEdges.ObjectProfile): + elif isinstance(obj.Proxy, Path.Op.ProfileEdges.ObjectProfile): Object_Kind = "PROFILE" if LBLIZE_ACTIVE: LBLIZE_STAUS = True - elif isinstance(obj.Proxy, PathScripts.PathMillFace.ObjectFace): + elif isinstance(obj.Proxy, Path.Op.MillFace.ObjectFace): Object_Kind = "FACE" if LBLIZE_ACTIVE: LBLIZE_STAUS = True - elif isinstance(obj.Proxy, PathScripts.PathHelix.ObjectHelix): + elif isinstance(obj.Proxy, Path.Op.Helix.ObjectHelix): Object_Kind = "HELIX" # If used compensated path, store, recompute and diff when asked diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index 621133e86b..a61e5ab25d 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -163,7 +163,7 @@ class _ToggleOperation: for sel in FreeCADGui.Selection.getSelectionEx(): selProxy = Path.Dressup.Utils.baseOp(sel.Object).Proxy if not isinstance( - selProxy, PathScripts.PathOp.ObjectOp + selProxy, Path.Op.Base.ObjectOp ) and not isinstance(selProxy, PathScripts.PathArray.ObjectArray): return False return True @@ -203,7 +203,7 @@ class _CopyOperation: return False try: for sel in FreeCADGui.Selection.getSelectionEx(): - if not isinstance(sel.Object.Proxy, PathScripts.PathOp.ObjectOp): + if not isinstance(sel.Object.Proxy, Path.Op.Base.ObjectOp): return False return True except (IndexError, AttributeError): diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py index 8cb8daa639..5d4f9d8e0c 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py +++ b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py @@ -25,10 +25,10 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Op.Gui.Base as PathOpGui import PathScripts.PathFeatureExtensions as FeatureExtensions import PathScripts.PathGeom as PathGeom import PathScripts.PathGui as PathGui -import PathScripts.PathOpGui as PathOpGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 3b34efe08e..36cbdd5e00 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -47,36 +47,36 @@ def Startup(): from Path.Dressup.Gui import RampEntry from Path.Dressup.Gui import Tags from Path.Dressup.Gui import ZCorrect + from Path.Op.Gui import Custom + from Path.Op.Gui import Deburr + from Path.Op.Gui import Drilling + from Path.Op.Gui import Engrave + from Path.Op.Gui import Helix + from Path.Op.Gui import MillFace + from Path.Op.Gui import Pocket + from Path.Op.Gui import PocketShape + from Path.Op.Gui import Probe + from Path.Op.Gui import Profile + from Path.Op.Gui import Slot + from Path.Op.Gui import ThreadMilling + from Path.Op.Gui import Vcarve from Path.Post import Command from Path.Tools import Controller from Path.Tools.Gui import Controller from PathScripts import PathArray from PathScripts import PathComment - from PathScripts import PathCustomGui - from PathScripts import PathDeburrGui - from PathScripts import PathDrillingGui - from PathScripts import PathEngraveGui from PathScripts import PathFixture - from PathScripts import PathHelixGui from PathScripts import PathHop from PathScripts import PathInspect - from PathScripts import PathMillFaceGui - from PathScripts import PathPocketGui - from PathScripts import PathPocketShapeGui - from PathScripts import PathProbeGui - from PathScripts import PathProfileGui from PathScripts import PathPropertyBagGui from PathScripts import PathSanity from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui - from PathScripts import PathSlotGui from PathScripts import PathStop - from PathScripts import PathThreadMillingGui from PathScripts import PathToolLibraryEditor from PathScripts import PathToolLibraryManager from PathScripts import PathUtilsGui - from PathScripts import PathVcarveGui from packaging.version import Version, parse diff --git a/src/Mod/Path/PathScripts/PathProfileContour.py b/src/Mod/Path/PathScripts/PathProfileContour.py index 3d2ee44d6d..56f6c59516 100644 --- a/src/Mod/Path/PathScripts/PathProfileContour.py +++ b/src/Mod/Path/PathScripts/PathProfileContour.py @@ -22,7 +22,7 @@ # * Major modifications: 2020 Russell Johnson * import FreeCAD -import PathScripts.PathProfile as PathProfile +import Path.Op.Profile as PathProfile __title__ = "Path Contour Operation (depreciated)" diff --git a/src/Mod/Path/PathScripts/PathProfileContourGui.py b/src/Mod/Path/PathScripts/PathProfileContourGui.py index 396d44b14a..79655c28bd 100644 --- a/src/Mod/Path/PathScripts/PathProfileContourGui.py +++ b/src/Mod/Path/PathScripts/PathProfileContourGui.py @@ -22,9 +22,10 @@ # * Major modifications: 2020 Russell Johnson * import FreeCAD -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfile as PathProfile -import PathScripts.PathProfileGui as PathProfileGui +import Path +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.Profile as PathProfileGui +import Path.Op.Profile as PathProfile from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Path Contour Operation UI (depreciated)" diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index 12f487942f..2bbd0a5020 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -22,7 +22,8 @@ # * Major modifications: 2020 Russell Johnson * import FreeCAD -import PathScripts.PathProfile as PathProfile +import Path +import Path.Op.Profile as PathProfile __title__ = "Path Profile Edges Operation (depreciated)" diff --git a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py index 8a647e6b41..d78bccb7ac 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py +++ b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py @@ -22,9 +22,9 @@ # * Major modifications: 2020 Russell Johnson * import FreeCAD -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfile as PathProfile -import PathScripts.PathProfileGui as PathProfileGui +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.Profile as PathProfileGui +import Path.Op.Profile as PathProfile from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Path Profile Edges Operation UI (depreciated)" diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 3de022ff57..14922b3951 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -23,7 +23,7 @@ # * Major modifications: 2020 Russell Johnson * import FreeCAD -import PathScripts.PathProfile as PathProfile +import Path.Op.Profile as PathProfile __title__ = "Path Profile Faces Operation (depreciated)" diff --git a/src/Mod/Path/PathScripts/PathProfileFacesGui.py b/src/Mod/Path/PathScripts/PathProfileFacesGui.py index 90acd3837a..93d487422b 100644 --- a/src/Mod/Path/PathScripts/PathProfileFacesGui.py +++ b/src/Mod/Path/PathScripts/PathProfileFacesGui.py @@ -22,9 +22,9 @@ # * Major modifications: 2020 Russell Johnson * import FreeCAD -import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathProfile as PathProfile -import PathScripts.PathProfileGui as PathProfileGui +import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.Profile as PathProfileGui +import Path.Op.Profile as PathProfile from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Path Profile Faces Operation UI (depreciated)" diff --git a/src/Mod/Path/PathScripts/PathSimpleCopy.py b/src/Mod/Path/PathScripts/PathSimpleCopy.py index 62e269408f..b8a21c3443 100644 --- a/src/Mod/Path/PathScripts/PathSimpleCopy.py +++ b/src/Mod/Path/PathScripts/PathSimpleCopy.py @@ -22,6 +22,7 @@ import FreeCAD import FreeCADGui +import Path import PathScripts from PySide.QtCore import QT_TRANSLATE_NOOP @@ -45,7 +46,7 @@ class CommandPathSimpleCopy: return False try: obj = FreeCADGui.Selection.getSelectionEx()[0].Object - return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) + return isinstance(obj.Proxy, Path.Op.Base.ObjectOp) except Exception: return False @@ -71,9 +72,9 @@ class CommandPathSimpleCopy: ) FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.addModule("PathScripts.PathCustom") + FreeCADGui.addModule("Path.Op.Custom") FreeCADGui.doCommand( - 'obj = PathScripts.PathCustom.Create("' + 'obj = Path.Op.Custom.Create("' + selection[0].Name + '_SimpleCopy")' ) diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 71f191b154..a6187166fe 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -24,7 +24,6 @@ import FreeCAD from FreeCAD import Vector from PySide import QtCore -from PySide import QtGui import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathJob as PathJob @@ -64,6 +63,7 @@ def waiting_effects(function): def new_function(*args, **kwargs): if not FreeCAD.GuiUp: return function(*args, **kwargs) + from PySide import QtGui QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) res = None try: @@ -299,7 +299,7 @@ def getOffsetArea( ): """Make an offset area of a shape, projected onto a plane. Positive offsets expand the area, negative offsets shrink it. - Inspired by _buildPathArea() from PathAreaOp.py module. Adjustments made + Inspired by _buildPathArea() from Path.Op.Area.py module. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.""" Path.Log.debug("getOffsetArea()") diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index feafdf1f45..5fedf7caee 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -23,7 +23,7 @@ import FreeCADGui import FreeCAD import Path -import Path.Tools.Controller as PathToolsController +import Path.Tools.Controller as PathToolController import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 06fb2d31f7..6da805f125 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -21,7 +21,7 @@ # *************************************************************************** import Path -import PathScripts.PathDeburr as PathDeburr +import Path.Op.Deburr as PathDeburr import PathTests.PathTestUtils as PathTestUtils Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestPathHelix.py b/src/Mod/Path/PathTests/TestPathHelix.py index c5e1fab693..fc630f28bd 100644 --- a/src/Mod/Path/PathTests/TestPathHelix.py +++ b/src/Mod/Path/PathTests/TestPathHelix.py @@ -23,7 +23,7 @@ import Draft import FreeCAD import Path -import PathScripts.PathHelix as PathHelix +import Path.Op.Helix as PathHelix import PathScripts.PathJob as PathJob import PathTests.PathTestUtils as PathTestUtils diff --git a/src/Mod/Path/PathTests/TestPathOpTools.py b/src/Mod/Path/PathTests/TestPathOpUtil.py similarity index 86% rename from src/Mod/Path/PathTests/TestPathOpTools.py rename to src/Mod/Path/PathTests/TestPathOpUtil.py index b3e6e92a9d..28f1e88b9b 100644 --- a/src/Mod/Path/PathTests/TestPathOpTools.py +++ b/src/Mod/Path/PathTests/TestPathOpUtil.py @@ -23,8 +23,8 @@ import FreeCAD import Part import Path +import Path.Op.Util as PathOpUtil import PathScripts.PathGeom as PathGeom -import PathScripts.PathOpTools as PathOpTools import PathTests.PathTestUtils as PathTestUtils import math @@ -85,7 +85,7 @@ def wireMarkers(wire): return pts -class TestPathOpTools(PathTestUtils.PathTestBase): +class TestPathOpUtil(PathTestUtils.PathTestBase): @classmethod def setUpClass(cls): global doc @@ -104,18 +104,18 @@ class TestPathOpTools(PathTestUtils.PathTestBase): pc = Vector(5, 5, 0) pd = Vector(5, 1, 0) - self.assertTrue(PathOpTools.isWireClockwise(makeWire([pa, pb, pc, pd]))) - self.assertFalse(PathOpTools.isWireClockwise(makeWire([pa, pd, pc, pb]))) + self.assertTrue(PathOpUtil.isWireClockwise(makeWire([pa, pb, pc, pd]))) + self.assertFalse(PathOpUtil.isWireClockwise(makeWire([pa, pd, pc, pb]))) def test01(self): """Verify isWireClockwise for single edge circle wires.""" self.assertTrue( - PathOpTools.isWireClockwise( + PathOpUtil.isWireClockwise( Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1)) ) ) self.assertFalse( - PathOpTools.isWireClockwise( + PathOpUtil.isWireClockwise( Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1)) ) ) @@ -124,31 +124,31 @@ class TestPathOpTools(PathTestUtils.PathTestBase): """Verify isWireClockwise for two half circle wires.""" e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 0, 180) e1 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 180, 360) - self.assertTrue(PathOpTools.isWireClockwise(Part.Wire([e0, e1]))) + self.assertTrue(PathOpUtil.isWireClockwise(Part.Wire([e0, e1]))) e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 0, 180) e1 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 180, 360) - self.assertFalse(PathOpTools.isWireClockwise(Part.Wire([e0, e1]))) + self.assertFalse(PathOpUtil.isWireClockwise(Part.Wire([e0, e1]))) def test03(self): """Verify isWireClockwise for two edge wires with an arc.""" e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 0, 180) e2 = Part.makeLine(e0.valueAt(e0.LastParameter), e0.valueAt(e0.FirstParameter)) - self.assertTrue(PathOpTools.isWireClockwise(Part.Wire([e0, e2]))) + self.assertTrue(PathOpUtil.isWireClockwise(Part.Wire([e0, e2]))) e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 0, 180) e2 = Part.makeLine(e0.valueAt(e0.LastParameter), e0.valueAt(e0.FirstParameter)) - self.assertFalse(PathOpTools.isWireClockwise(Part.Wire([e0, e2]))) + self.assertFalse(PathOpUtil.isWireClockwise(Part.Wire([e0, e2]))) def test04(self): """Verify isWireClockwise for unoriented wires.""" e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, -1), 0, 180) e3 = Part.makeLine(e0.valueAt(e0.FirstParameter), e0.valueAt(e0.LastParameter)) - self.assertTrue(PathOpTools.isWireClockwise(Part.Wire([e0, e3]))) + self.assertTrue(PathOpUtil.isWireClockwise(Part.Wire([e0, e3]))) e0 = Part.makeCircle(5, Vector(1, 2, 3), Vector(0, 0, +1), 0, 180) e3 = Part.makeLine(e0.valueAt(e0.FirstParameter), e0.valueAt(e0.LastParameter)) - self.assertFalse(PathOpTools.isWireClockwise(Part.Wire([e0, e3]))) + self.assertFalse(PathOpUtil.isWireClockwise(Part.Wire([e0, e3]))) def test11(self): """Check offsetting a circular hole.""" @@ -157,13 +157,13 @@ class TestPathOpTools(PathTestUtils.PathTestBase): small = getWireInside(obj) self.assertRoughly(10, small.Edges[0].Curve.Radius) - wire = PathOpTools.offsetWire(small, obj.Shape, 3, True) + wire = PathOpUtil.offsetWire(small, obj.Shape, 3, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(7, wire.Edges[0].Curve.Radius) self.assertCoincide(Vector(0, 0, 1), wire.Edges[0].Curve.Axis) - wire = PathOpTools.offsetWire(small, obj.Shape, 9.9, True) + wire = PathOpUtil.offsetWire(small, obj.Shape, 9.9, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(0.1, wire.Edges[0].Curve.Radius) @@ -175,10 +175,10 @@ class TestPathOpTools(PathTestUtils.PathTestBase): small = getWireInside(obj) self.assertRoughly(10, small.Edges[0].Curve.Radius) - wire = PathOpTools.offsetWire(small, obj.Shape, 10, True) + wire = PathOpUtil.offsetWire(small, obj.Shape, 10, True) self.assertIsNone(wire) - wire = PathOpTools.offsetWire(small, obj.Shape, 15, True) + wire = PathOpUtil.offsetWire(small, obj.Shape, 15, True) self.assertIsNone(wire) def test13(self): @@ -188,13 +188,13 @@ class TestPathOpTools(PathTestUtils.PathTestBase): big = getWireOutside(obj) self.assertRoughly(20, big.Edges[0].Curve.Radius) - wire = PathOpTools.offsetWire(big, obj.Shape, 10, True) + wire = PathOpUtil.offsetWire(big, obj.Shape, 10, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(30, wire.Edges[0].Curve.Radius) self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis) - wire = PathOpTools.offsetWire(big, obj.Shape, 20, True) + wire = PathOpUtil.offsetWire(big, obj.Shape, 20, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(40, wire.Edges[0].Curve.Radius) @@ -217,7 +217,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): # make sure there is a placement and I didn't mess up the model self.assertFalse(PathGeom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) - wire = PathOpTools.offsetWire(w, obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(8, wire.Edges[0].Curve.Radius) @@ -241,7 +241,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): # make sure there is a placement and I didn't mess up the model self.assertFalse(PathGeom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) - wire = PathOpTools.offsetWire(w, obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(22, wire.Edges[0].Curve.Radius) @@ -267,12 +267,12 @@ class TestPathOpTools(PathTestUtils.PathTestBase): ], ) - wire = PathOpTools.offsetWire(small, obj.Shape, 3, True) + wire = PathOpUtil.offsetWire(small, obj.Shape, 3, True) self.assertIsNotNone(wire) self.assertEqual(3, len(wire.Edges)) self.assertTrue(wire.isClosed()) # for holes processing "forward" means CCW - self.assertFalse(PathOpTools.isWireClockwise(wire)) + self.assertFalse(PathOpUtil.isWireClockwise(wire)) y = 4 # offset works in both directions x = 4 * math.cos(math.pi / 6) self.assertLines( @@ -299,7 +299,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): Vector(0, y, 0), ], ) - wire = PathOpTools.offsetWire(small, obj.Shape, 5, True) + wire = PathOpUtil.offsetWire(small, obj.Shape, 5, True) self.assertIsNone(wire) def test22(self): @@ -321,7 +321,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): ], ) - wire = PathOpTools.offsetWire(big, obj.Shape, 5, True) + wire = PathOpUtil.offsetWire(big, obj.Shape, 5, True) self.assertIsNotNone(wire) self.assertEqual(6, len(wire.Edges)) lastAngle = None @@ -350,13 +350,13 @@ class TestPathOpTools(PathTestUtils.PathTestBase): else: self.assertIsNone("%s: angle=%s" % (type(e.Curve), angle)) lastAngle = angle - self.assertTrue(PathOpTools.isWireClockwise(wire)) + self.assertTrue(PathOpUtil.isWireClockwise(wire)) def test31(self): """Check offsetting a cylinder.""" obj = doc.getObjectsByLabel("circle-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) self.assertEqual(1, len(wire.Edges)) edge = wire.Edges[0] self.assertCoincide(Vector(), edge.Curve.Center) @@ -364,7 +364,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertRoughly(33, edge.Curve.Radius) # the other way around everything's the same except the axis is negative - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getPositiveShape(obj), 3, False ) self.assertEqual(1, len(wire.Edges)) @@ -377,7 +377,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): """Check offsetting a box.""" obj = doc.getObjectsByLabel("square-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) self.assertEqual(8, len(wire.Edges)) self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) self.assertEqual( @@ -392,10 +392,10 @@ class TestPathOpTools(PathTestUtils.PathTestBase): if Part.Circle == type(e.Curve): self.assertRoughly(3, e.Curve.Radius) self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis) - self.assertTrue(PathOpTools.isWireClockwise(wire)) + self.assertTrue(PathOpUtil.isWireClockwise(wire)) # change offset orientation - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getPositiveShape(obj), 3, False ) self.assertEqual(8, len(wire.Edges)) @@ -412,13 +412,13 @@ class TestPathOpTools(PathTestUtils.PathTestBase): if Part.Circle == type(e.Curve): self.assertRoughly(3, e.Curve.Radius) self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis) - self.assertFalse(PathOpTools.isWireClockwise(wire)) + self.assertFalse(PathOpUtil.isWireClockwise(wire)) def test33(self): """Check offsetting a triangle.""" obj = doc.getObjectsByLabel("triangle-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) self.assertEqual(6, len(wire.Edges)) self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) self.assertEqual( @@ -433,7 +433,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis) # change offset orientation - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getPositiveShape(obj), 3, False ) self.assertEqual(6, len(wire.Edges)) @@ -452,7 +452,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): """Check offsetting a shape.""" obj = doc.getObjectsByLabel("shape-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) self.assertEqual(6, len(wire.Edges)) self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) self.assertEqual( @@ -468,7 +468,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis) # change offset orientation - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getPositiveShape(obj), 3, False ) self.assertEqual(6, len(wire.Edges)) @@ -487,7 +487,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): """Check offsetting a cylindrical hole.""" obj = doc.getObjectsByLabel("circle-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) self.assertEqual(1, len(wire.Edges)) edge = wire.Edges[0] self.assertCoincide(Vector(), edge.Curve.Center) @@ -495,7 +495,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertRoughly(27, edge.Curve.Radius) # the other way around everything's the same except the axis is negative - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getNegativeShape(obj), 3, False ) self.assertEqual(1, len(wire.Edges)) @@ -508,7 +508,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): """Check offsetting a square hole.""" obj = doc.getObjectsByLabel("square-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) 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: @@ -516,10 +516,10 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertRoughly(34, e.Length) if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertRoughly(54, e.Length) - self.assertFalse(PathOpTools.isWireClockwise(wire)) + self.assertFalse(PathOpUtil.isWireClockwise(wire)) # change offset orientation - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getNegativeShape(obj), 3, False ) self.assertEqual(4, len(wire.Edges)) @@ -529,35 +529,35 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertRoughly(34, e.Length) if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertRoughly(54, e.Length) - self.assertTrue(PathOpTools.isWireClockwise(wire)) + self.assertTrue(PathOpUtil.isWireClockwise(wire)) def test37(self): """Check offsetting a triangular holee.""" obj = doc.getObjectsByLabel("triangle-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) self.assertEqual(3, len(wire.Edges)) self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) length = 48 * math.sin(math.radians(60)) for e in wire.Edges: self.assertRoughly(length, e.Length) - self.assertFalse(PathOpTools.isWireClockwise(wire)) + self.assertFalse(PathOpUtil.isWireClockwise(wire)) # change offset orientation - wire = PathOpTools.offsetWire( + 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: self.assertRoughly(length, e.Length) - self.assertTrue(PathOpTools.isWireClockwise(wire)) + self.assertTrue(PathOpUtil.isWireClockwise(wire)) def test38(self): """Check offsetting a shape hole.""" obj = doc.getObjectsByLabel("shape-cut")[0] - wire = PathOpTools.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) + wire = PathOpUtil.offsetWire(getWire(obj.Tool), getNegativeShape(obj), 3, True) self.assertEqual(6, len(wire.Edges)) self.assertEqual(3, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) self.assertEqual( @@ -573,7 +573,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis) # change offset orientation - wire = PathOpTools.offsetWire( + wire = PathOpUtil.offsetWire( getWire(obj.Tool), getNegativeShape(obj), 3, False ) self.assertEqual(6, len(wire.Edges)) @@ -612,7 +612,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 5, True) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, True) self.assertEqual(1, len(wire.Edges)) y = y - 5 @@ -621,7 +621,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): # make sure we get the same result even if the edge is oriented the other way edge = PathGeom.flipEdge(edge) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 5, True) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, True) self.assertEqual(1, len(wire.Edges)) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) @@ -650,7 +650,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 5, False) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, False) self.assertEqual(1, len(wire.Edges)) y = y - 5 @@ -659,7 +659,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): # make sure we get the same result on a reversed edge edge = PathGeom.flipEdge(edge) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 5, False) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, False) self.assertEqual(1, len(wire.Edges)) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point) @@ -682,7 +682,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): ] self.assertEqual(2, len(lEdges)) - wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True) x = length / 2 + 2 * math.cos(math.pi / 6) y = -10 + 2 * math.sin(math.pi / 6) @@ -697,7 +697,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(0, 0, -1), rEdges[0].Curve.Axis) # offset the other way - wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) + wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point) @@ -726,7 +726,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertEqual(2, len(lEdges)) w = PathGeom.flipWire(Part.Wire(lEdges)) - wire = PathOpTools.offsetWire(w, obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) x = length / 2 + 2 * math.cos(math.pi / 6) y = -10 + 2 * math.sin(math.pi / 6) @@ -741,7 +741,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(0, 0, -1), rEdges[0].Curve.Axis) # offset the other way - wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) + wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point) @@ -776,7 +776,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, True) self.assertEqual(1, len(wire.Edges)) y = y + 2 @@ -785,7 +785,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): # make sure we get the same result even if the edge is oriented the other way edge = PathGeom.flipEdge(edge) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, True) self.assertEqual(1, len(wire.Edges)) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point) @@ -815,7 +815,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) self.assertEqual(1, len(wire.Edges)) y = y + 2 @@ -824,7 +824,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): # make sure we get the same result even if the edge is oriented the other way edge = PathGeom.flipEdge(edge) - wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) + wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) self.assertEqual(1, len(wire.Edges)) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) @@ -845,7 +845,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): ] self.assertEqual(2, len(lEdges)) - wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True) x = length / 2 - 2 * math.cos(math.pi / 6) y = -5 - 2 * math.sin(math.pi / 6) @@ -857,7 +857,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertEqual(0, len(rEdges)) # offset the other way - wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) + wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point) @@ -883,7 +883,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertEqual(2, len(lEdges)) w = PathGeom.flipWire(Part.Wire(lEdges)) - wire = PathOpTools.offsetWire(w, obj.Shape, 2, True) + wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) x = length / 2 - 2 * math.cos(math.pi / 6) y = -5 - 2 * math.sin(math.pi / 6) @@ -895,7 +895,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): self.assertEqual(0, len(rEdges)) # offset the other way - wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) + wire = PathOpUtil.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point) @@ -913,7 +913,7 @@ class TestPathOpTools(PathTestUtils.PathTestBase): e0 = Part.Edge(Part.LineSegment(p0, p1)) e1 = Part.Edge(Part.LineSegment(p1, p2)) - wire = PathOpTools.orientWire(Part.Wire([e0, e1])) + wire = PathOpUtil.orientWire(Part.Wire([e0, e1])) wirePts = wireMarkers(wire) self.assertPointsMatch(wirePts, pts) @@ -930,16 +930,16 @@ class TestPathOpTools(PathTestUtils.PathTestBase): e1p = Part.Edge(Part.LineSegment(p1, p2)) e1m = Part.Edge(Part.LineSegment(p2, p1)) - wire = PathOpTools.orientWire(Part.Wire([e0p, e1p])) + wire = PathOpUtil.orientWire(Part.Wire([e0p, e1p])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0p, e1m])) + wire = PathOpUtil.orientWire(Part.Wire([e0p, e1m])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0m, e1p])) + wire = PathOpUtil.orientWire(Part.Wire([e0m, e1p])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0m, e1m])) + wire = PathOpUtil.orientWire(Part.Wire([e0m, e1m])) self.assertPointsMatch(wireMarkers(wire), pts) def test52(self): @@ -957,26 +957,26 @@ class TestPathOpTools(PathTestUtils.PathTestBase): e2p = Part.Edge(Part.LineSegment(p2, p3)) e2m = Part.Edge(Part.LineSegment(p3, p2)) - wire = PathOpTools.orientWire(Part.Wire([e0p, e1p, e2p])) + wire = PathOpUtil.orientWire(Part.Wire([e0p, e1p, e2p])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0p, e1m, e2p])) + wire = PathOpUtil.orientWire(Part.Wire([e0p, e1m, e2p])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0m, e1p, e2p])) + wire = PathOpUtil.orientWire(Part.Wire([e0m, e1p, e2p])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0m, e1m, e2p])) + wire = PathOpUtil.orientWire(Part.Wire([e0m, e1m, e2p])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0p, e1p, e2m])) + wire = PathOpUtil.orientWire(Part.Wire([e0p, e1p, e2m])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0p, e1m, e2m])) + wire = PathOpUtil.orientWire(Part.Wire([e0p, e1m, e2m])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0m, e1p, e2m])) + wire = PathOpUtil.orientWire(Part.Wire([e0m, e1p, e2m])) self.assertPointsMatch(wireMarkers(wire), pts) - wire = PathOpTools.orientWire(Part.Wire([e0m, e1m, e2m])) + wire = PathOpUtil.orientWire(Part.Wire([e0m, e1m, e2m])) self.assertPointsMatch(wireMarkers(wire), pts) diff --git a/src/Mod/Path/PathTests/TestPathThreadMilling.py b/src/Mod/Path/PathTests/TestPathThreadMilling.py index dac113a77b..207cd93604 100644 --- a/src/Mod/Path/PathTests/TestPathThreadMilling.py +++ b/src/Mod/Path/PathTests/TestPathThreadMilling.py @@ -22,7 +22,7 @@ import FreeCAD import PathScripts.PathGeom as PathGeom -import PathScripts.PathThreadMilling as PathThreadMilling +import Path.Op.ThreadMilling as PathThreadMilling import math from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathVcarve.py b/src/Mod/Path/PathTests/TestPathVcarve.py index e90bd08f47..09d112590a 100644 --- a/src/Mod/Path/PathTests/TestPathVcarve.py +++ b/src/Mod/Path/PathTests/TestPathVcarve.py @@ -21,9 +21,9 @@ # *************************************************************************** import FreeCAD +import Path.Op.Vcarve as PathVcarve import Path.Tools.Bit as PathToolBit import PathScripts.PathGeom as PathGeom -import PathScripts.PathVcarve as PathVcarve import math from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 3d0a0606f9..84676268fa 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -36,7 +36,7 @@ from PathTests.TestPathGeom import TestPathGeom from PathTests.TestPathHelpers import TestPathHelpers from PathTests.TestPathHelixGenerator import TestPathHelixGenerator from PathTests.TestPathLog import TestPathLog -from PathTests.TestPathOpTools import TestPathOpTools +from PathTests.TestPathOpUtil import TestPathOpUtil # from PathTests.TestPathPost import TestPathPost from PathTests.TestPathPost import TestPathPostUtils @@ -84,7 +84,7 @@ False if TestPathGeom.__name__ else True False if TestPathHelpers.__name__ else True # False if TestPathHelix.__name__ else True False if TestPathLog.__name__ else True -False if TestPathOpTools.__name__ else True +False if TestPathOpUtil.__name__ else True # False if TestPathPost.__name__ else True False if TestPathPostUtils.__name__ else True False if TestPathPreferences.__name__ else True From 9b7e50ba0723e7ce0ca221c960165b8168044286 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 12 Aug 2022 17:58:06 -0700 Subject: [PATCH 11/33] Removed obsolete path profile implementations --- src/Mod/Path/CMakeLists.txt | 6 -- src/Mod/Path/Path/Op/Gui/Profile.py | 2 +- .../Path/PathScripts/PathProfileContour.py | 53 ----------------- .../Path/PathScripts/PathProfileContourGui.py | 56 ------------------ src/Mod/Path/PathScripts/PathProfileEdges.py | 55 ------------------ .../Path/PathScripts/PathProfileEdgesGui.py | 57 ------------------- src/Mod/Path/PathScripts/PathProfileFaces.py | 55 ------------------ .../Path/PathScripts/PathProfileFacesGui.py | 57 ------------------- .../Path/PathTests/TestPathDressupDogbone.py | 4 +- 9 files changed, 3 insertions(+), 342 deletions(-) delete mode 100644 src/Mod/Path/PathScripts/PathProfileContour.py delete mode 100644 src/Mod/Path/PathScripts/PathProfileContourGui.py delete mode 100644 src/Mod/Path/PathScripts/PathProfileEdges.py delete mode 100644 src/Mod/Path/PathScripts/PathProfileEdgesGui.py delete mode 100644 src/Mod/Path/PathScripts/PathProfileFaces.py delete mode 100644 src/Mod/Path/PathScripts/PathProfileFacesGui.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 0c5bebe456..420821d86c 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -183,12 +183,6 @@ SET(PathScripts_SRCS PathScripts/PathPreferences.py PathScripts/PathPreferencesAdvanced.py PathScripts/PathPreferencesPathJob.py - PathScripts/PathProfileContour.py - PathScripts/PathProfileContourGui.py - PathScripts/PathProfileEdges.py - PathScripts/PathProfileEdgesGui.py - PathScripts/PathProfileFaces.py - PathScripts/PathProfileFacesGui.py PathScripts/PathProperty.py PathScripts/PathPropertyBag.py PathScripts/PathPropertyBagGui.py diff --git a/src/Mod/Path/Path/Op/Gui/Profile.py b/src/Mod/Path/Path/Op/Gui/Profile.py index 8cb09760e8..da44a3fae2 100644 --- a/src/Mod/Path/Path/Op/Gui/Profile.py +++ b/src/Mod/Path/Path/Op/Gui/Profile.py @@ -169,4 +169,4 @@ Command = PathOpGui.SetupOperation( PathProfile.SetupProperties, ) -FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n") +FreeCAD.Console.PrintLog("Loading PathProfileGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathProfileContour.py b/src/Mod/Path/PathScripts/PathProfileContour.py deleted file mode 100644 index 56f6c59516..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileContour.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2016 sliptonic * -# * * -# * 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 * -# * * -# *************************************************************************** -# * Major modifications: 2020 Russell Johnson * - -import FreeCAD -import Path.Op.Profile as PathProfile - - -__title__ = "Path Contour Operation (depreciated)" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" -__doc__ = "Implementation of the Contour operation (depreciated)." - - -class ObjectContour(PathProfile.ObjectProfile): - """Pseudo class for Profile operation, - allowing for backward compatibility with pre-existing "Contour" operations.""" - - pass - - -# Eclass - - -def SetupProperties(): - return PathProfile.SetupProperties() - - -def Create(name, obj=None, parentJob=None): - """Create(name) ... Creates and returns a Profile operation.""" - if obj is None: - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectContour(obj, name, parentJob) - return obj diff --git a/src/Mod/Path/PathScripts/PathProfileContourGui.py b/src/Mod/Path/PathScripts/PathProfileContourGui.py deleted file mode 100644 index 79655c28bd..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileContourGui.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * 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 * -# * * -# *************************************************************************** -# * Major modifications: 2020 Russell Johnson * - -import FreeCAD -import Path -import Path.Op.Gui.Base as PathOpGui -import Path.Op.Gui.Profile as PathProfileGui -import Path.Op.Profile as PathProfile -from PySide.QtCore import QT_TRANSLATE_NOOP - -__title__ = "Path Contour Operation UI (depreciated)" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" -__doc__ = "Contour operation page controller and command implementation (deprecated)." - - -class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage): - """Pseudo page controller class for Profile operation, - allowing for backward compatibility with pre-existing "Contour" operations.""" - - pass - - -Command = PathOpGui.SetupOperation( - "Profile", - PathProfile.Create, - TaskPanelOpPage, - "Path_Contour", - QT_TRANSLATE_NOOP("Path_Profile", "Profile"), - QT_TRANSLATE_NOOP( - "Path_Profile", "Profile entire model, selected face(s) or selected edge(s)" - ), - PathProfile.SetupProperties, -) - -FreeCAD.Console.PrintLog("Loading PathProfileContourGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py deleted file mode 100644 index 2bbd0a5020..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2016 sliptonic * -# * * -# * 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 * -# * * -# *************************************************************************** -# * Major modifications: 2020 Russell Johnson * - -import FreeCAD -import Path -import Path.Op.Profile as PathProfile - - -__title__ = "Path Profile Edges Operation (depreciated)" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" -__doc__ = "Path Profile operation based on edges (depreciated)." -__contributors__ = "russ4262 (Russell Johnson)" - - -class ObjectProfile(PathProfile.ObjectProfile): - """Pseudo class for Profile operation, - allowing for backward compatibility with pre-existing "Profile Edges" operations.""" - - pass - - -# Eclass - - -def SetupProperties(): - return PathProfile.SetupProperties() - - -def Create(name, obj=None, parentJob=None): - """Create(name) ... Creates and returns a Profile operation.""" - if obj is None: - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name, parentJob) - return obj diff --git a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py b/src/Mod/Path/PathScripts/PathProfileEdgesGui.py deleted file mode 100644 index d78bccb7ac..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileEdgesGui.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * 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 * -# * * -# *************************************************************************** -# * Major modifications: 2020 Russell Johnson * - -import FreeCAD -import Path.Op.Gui.Base as PathOpGui -import Path.Op.Gui.Profile as PathProfileGui -import Path.Op.Profile as PathProfile -from PySide.QtCore import QT_TRANSLATE_NOOP - -__title__ = "Path Profile Edges Operation UI (depreciated)" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" -__doc__ = ( - "Profile Edges operation page controller and command implementation (deprecated)." -) - - -class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage): - """Pseudo page controller class for Profile operation, - allowing for backward compatibility with pre-existing "Profile Edges" operations.""" - - pass - - -Command = PathOpGui.SetupOperation( - "Profile", - PathProfile.Create, - TaskPanelOpPage, - "Path_Contour", - QT_TRANSLATE_NOOP("Path_Profile", "Profile"), - QT_TRANSLATE_NOOP( - "Path_Profile", "Profile entire model, selected face(s) or selected edge(s)" - ), - PathProfile.SetupProperties, -) - -FreeCAD.Console.PrintLog("Loading PathProfileEdgesGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py deleted file mode 100644 index 14922b3951..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2014 Yorik van Havre * -# * Copyright (c) 2020 Schildkroet * -# * * -# * 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 * -# * * -# *************************************************************************** -# * Major modifications: 2020 Russell Johnson * - -import FreeCAD -import Path.Op.Profile as PathProfile - - -__title__ = "Path Profile Faces Operation (depreciated)" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" -__doc__ = "Path Profile operation based on faces (depreciated)." -__contributors__ = "Schildkroet" - - -class ObjectProfile(PathProfile.ObjectProfile): - """Pseudo class for Profile operation, - allowing for backward compatibility with pre-existing "Profile Faces" operations.""" - - pass - - -# Eclass - - -def SetupProperties(): - return PathProfile.SetupProperties() - - -def Create(name, obj=None, parentJob=None): - """Create(name) ... Creates and returns a Profile operation.""" - if obj is None: - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name, parentJob) - return obj diff --git a/src/Mod/Path/PathScripts/PathProfileFacesGui.py b/src/Mod/Path/PathScripts/PathProfileFacesGui.py deleted file mode 100644 index 93d487422b..0000000000 --- a/src/Mod/Path/PathScripts/PathProfileFacesGui.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * 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 * -# * * -# *************************************************************************** -# * Major modifications: 2020 Russell Johnson * - -import FreeCAD -import Path.Op.Gui.Base as PathOpGui -import Path.Op.Gui.Profile as PathProfileGui -import Path.Op.Profile as PathProfile -from PySide.QtCore import QT_TRANSLATE_NOOP - -__title__ = "Path Profile Faces Operation UI (depreciated)" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" -__doc__ = ( - "Profile Faces operation page controller and command implementation (deprecated)." -) - - -class TaskPanelOpPage(PathProfileGui.TaskPanelOpPage): - """Pseudo page controller class for Profile operation, - allowing for backward compatibility with pre-existing "Profile Faces" operations.""" - - pass - - -Command = PathOpGui.SetupOperation( - "Profile", - PathProfile.Create, - TaskPanelOpPage, - "Path_Contour", - QT_TRANSLATE_NOOP("Path_Profile", "Profile"), - QT_TRANSLATE_NOOP( - "Path_Profile", "Profile entire model, selected face(s) or selected edge(s)" - ), - PathProfile.SetupProperties, -) - -FreeCAD.Console.PrintLog("Loading PathProfileFacesGui... done\n") diff --git a/src/Mod/Path/PathTests/TestPathDressupDogbone.py b/src/Mod/Path/PathTests/TestPathDressupDogbone.py index d8622c983c..f41260ace5 100644 --- a/src/Mod/Path/PathTests/TestPathDressupDogbone.py +++ b/src/Mod/Path/PathTests/TestPathDressupDogbone.py @@ -24,7 +24,7 @@ import FreeCAD import Path import Path.Dressup.Gui.Dogbone as PathDressupDogbone import PathScripts.PathJob as PathJob -import PathScripts.PathProfileFaces as PathProfileFaces +import Path.Op.Profile as PathProfile from PathTests.PathTestUtils import PathTestBase @@ -135,7 +135,7 @@ class TestDressupDogbone(PathTestBase): PathJob.Create("Job", [cut], None) - profile = PathProfileFaces.Create("Profile Faces") + profile = PathProfile.Create("Profile") profile.Base = (cut, face) profile.StepDown = 5 # set start and final depth in order to eliminate effects of stock (and its default values) From 29255f0ffd215f898a285f17aa7229cdaed8614d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 12 Aug 2022 23:34:05 -0700 Subject: [PATCH 12/33] Removed deprecated and obsolete Path.Tool and Path.Tooltable --- src/Mod/Path/App/AppPath.cpp | 12 - src/Mod/Path/App/CMakeLists.txt | 14 - src/Mod/Path/App/PropertyTool.cpp | 116 ---- src/Mod/Path/App/PropertyTool.h | 75 --- src/Mod/Path/App/PropertyTooltable.cpp | 116 ---- src/Mod/Path/App/PropertyTooltable.h | 75 --- src/Mod/Path/App/Tool.cpp | 258 -------- src/Mod/Path/App/Tool.h | 107 ---- src/Mod/Path/App/ToolPy.xml | 113 ---- src/Mod/Path/App/ToolPyImp.cpp | 295 --------- src/Mod/Path/App/Tooltable.cpp | 117 ---- src/Mod/Path/App/Tooltable.h | 68 --- src/Mod/Path/App/TooltablePy.xml | 78 --- src/Mod/Path/App/TooltablePyImp.cpp | 281 --------- src/Mod/Path/CMakeLists.txt | 5 - src/Mod/Path/Gui/Resources/Path.qrc | 2 - .../Gui/Resources/panels/ToolLibraryEditor.ui | 429 -------------- .../Path/Gui/Resources/preferences/PathJob.ui | 10 - src/Mod/Path/Gui/TaskDlgPathCompound.cpp | 3 - src/Mod/Path/InitGui.py | 8 +- src/Mod/Path/Path/Op/SurfaceSupport.py | 27 +- src/Mod/Path/Path/Tools/Bit.py | 2 +- src/Mod/Path/Path/Tools/Controller.py | 109 +--- src/Mod/Path/Path/Tools/Gui/Controller.py | 19 +- src/Mod/Path/PathScripts/PathGuiInit.py | 2 - src/Mod/Path/PathScripts/PathJob.py | 8 - src/Mod/Path/PathScripts/PathJobGui.py | 50 +- src/Mod/Path/PathScripts/PathPreferences.py | 17 +- .../PathScripts/PathPreferencesPathJob.py | 4 +- src/Mod/Path/PathScripts/PathSimulatorGui.py | 15 +- src/Mod/Path/PathScripts/PathToolEdit.py | 352 ----------- .../Path/PathScripts/PathToolLibraryEditor.py | 515 ---------------- .../PathScripts/PathToolLibraryManager.py | 561 ------------------ src/Mod/Path/PathScripts/PathUtils.py | 4 - .../Path/PathSimulator/App/PathSimPyImp.cpp | 1 - src/Mod/Path/PathTests/TestPathCore.py | 34 -- src/Mod/Path/PathTests/TestPathDeburr.py | 15 +- src/Mod/Path/PathTests/TestPathHelpers.py | 12 +- src/Mod/Path/PathTests/TestPathTool.py | 96 --- .../Path/PathTests/TestPathToolController.py | 7 +- src/Mod/Path/PathTests/TestPathTooltable.py | 68 --- src/Mod/Path/TestPathApp.py | 4 - 42 files changed, 93 insertions(+), 4011 deletions(-) delete mode 100644 src/Mod/Path/App/PropertyTool.cpp delete mode 100644 src/Mod/Path/App/PropertyTool.h delete mode 100644 src/Mod/Path/App/PropertyTooltable.cpp delete mode 100644 src/Mod/Path/App/PropertyTooltable.h delete mode 100644 src/Mod/Path/App/Tool.cpp delete mode 100644 src/Mod/Path/App/Tool.h delete mode 100644 src/Mod/Path/App/ToolPy.xml delete mode 100644 src/Mod/Path/App/ToolPyImp.cpp delete mode 100644 src/Mod/Path/App/Tooltable.cpp delete mode 100644 src/Mod/Path/App/Tooltable.h delete mode 100644 src/Mod/Path/App/TooltablePy.xml delete mode 100644 src/Mod/Path/App/TooltablePyImp.cpp delete mode 100644 src/Mod/Path/Gui/Resources/panels/ToolLibraryEditor.ui delete mode 100644 src/Mod/Path/PathScripts/PathToolEdit.py delete mode 100644 src/Mod/Path/PathScripts/PathToolLibraryEditor.py delete mode 100644 src/Mod/Path/PathScripts/PathToolLibraryManager.py delete mode 100644 src/Mod/Path/PathTests/TestPathTool.py delete mode 100644 src/Mod/Path/PathTests/TestPathTooltable.py diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index fa1a6af835..dce12ea957 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -30,14 +30,8 @@ #include "CommandPy.h" #include "Path.h" #include "PathPy.h" -#include "Tool.h" -#include "Tooltable.h" -#include "ToolPy.h" -#include "TooltablePy.h" #include "PropertyPath.h" #include "FeaturePath.h" -#include "PropertyTool.h" -#include "PropertyTooltable.h" #include "FeaturePathCompound.h" #include "FeaturePathShape.h" #include "AreaPy.h" @@ -76,8 +70,6 @@ PyMOD_INIT_FUNC(PathApp) // Add Types to module Base::Interpreter().addType(&Path::CommandPy ::Type, pathModule, "Command"); Base::Interpreter().addType(&Path::PathPy ::Type, pathModule, "Path"); - Base::Interpreter().addType(&Path::ToolPy ::Type, pathModule, "Tool"); - Base::Interpreter().addType(&Path::TooltablePy ::Type, pathModule, "Tooltable"); Base::Interpreter().addType(&Path::AreaPy ::Type, pathModule, "Area"); PyObject* voronoiModule(module.getAttr("Voronoi").ptr()); @@ -91,13 +83,9 @@ PyMOD_INIT_FUNC(PathApp) // This function is responsible for adding inherited slots from a type's base class. Path::Command ::init(); Path::Toolpath ::init(); - Path::Tool ::init(); - Path::Tooltable ::init(); Path::PropertyPath ::init(); Path::Feature ::init(); Path::FeaturePython ::init(); - Path::PropertyTool ::init(); - Path::PropertyTooltable ::init(); Path::FeatureCompound ::init(); Path::FeatureCompoundPython ::init(); Path::FeatureShape ::init(); diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 5a0247de6e..9fb6fc9bb1 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -31,8 +31,6 @@ set(Path_LIBS generate_from_xml(CommandPy) generate_from_xml(PathPy) -generate_from_xml(ToolPy) -generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) generate_from_xml(AreaPy) generate_from_xml(FeatureAreaPy) @@ -46,10 +44,6 @@ SET(Python_SRCS CommandPyImp.cpp PathPy.xml PathPyImp.cpp - ToolPy.xml - ToolPyImp.cpp - TooltablePy.xml - TooltablePyImp.cpp FeaturePathCompoundPy.xml FeaturePathCompoundPyImp.cpp AreaPy.xml @@ -78,18 +72,10 @@ SET(Path_SRCS Command.h Path.cpp Path.h - Tool.cpp - Tool.h - Tooltable.cpp - Tooltable.h PropertyPath.cpp PropertyPath.h FeaturePath.cpp FeaturePath.h - PropertyTool.cpp - PropertyTool.h - PropertyTooltable.cpp - PropertyTooltable.h FeaturePathCompound.cpp FeaturePathCompound.h FeaturePathShape.cpp diff --git a/src/Mod/Path/App/PropertyTool.cpp b/src/Mod/Path/App/PropertyTool.cpp deleted file mode 100644 index 43741c1d01..0000000000 --- a/src/Mod/Path/App/PropertyTool.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" - -#ifndef _PreComp_ -# include -#endif - - -#include -#include -#include -#include -#include -#include - -#include "PropertyTool.h" -#include "ToolPy.h" - -using namespace Path; - -TYPESYSTEM_SOURCE(Path::PropertyTool, App::Property) - -PropertyTool::PropertyTool() -{ -} - -PropertyTool::~PropertyTool() -{ -} - -void PropertyTool::setValue(const Tool& tt) -{ - aboutToSetValue(); - _Tool = tt; - hasSetValue(); -} - - -const Tool &PropertyTool::getValue()const -{ - return _Tool; -} - -PyObject *PropertyTool::getPyObject() -{ - return new ToolPy(new Tool(_Tool)); -} - -void PropertyTool::setPyObject(PyObject *value) -{ - if (PyObject_TypeCheck(value, &(ToolPy::Type))) { - ToolPy *pcObject = static_cast(value); - setValue(*pcObject->getToolPtr()); - } - else { - std::string error = std::string("type must be 'Tool', not "); - error += value->ob_type->tp_name; - throw Base::TypeError(error); - } -} - -App::Property *PropertyTool::Copy() const -{ - PropertyTool *prop = new PropertyTool(); - prop->_Tool = this->_Tool; - - return prop; -} - -void PropertyTool::Paste(const App::Property &from) -{ - aboutToSetValue(); - _Tool = dynamic_cast(from)._Tool; - hasSetValue(); -} - -unsigned int PropertyTool::getMemSize () const -{ - return _Tool.getMemSize(); -} - -void PropertyTool::Save (Base::Writer &writer) const -{ - _Tool.Save(writer); -} - -void PropertyTool::Restore(Base::XMLReader &reader) -{ - Path::Tool temp; - temp.Restore(reader); - setValue(temp); -} - - diff --git a/src/Mod/Path/App/PropertyTool.h b/src/Mod/Path/App/PropertyTool.h deleted file mode 100644 index d4fc146d65..0000000000 --- a/src/Mod/Path/App/PropertyTool.h +++ /dev/null @@ -1,75 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#ifndef PROPERTYTOOL_H -#define PROPERTYTOOL_H - -#include "Tool.h" -#include - -namespace Path -{ - - -/** The tool property class. */ -class PathExport PropertyTool : public App::Property -{ - TYPESYSTEM_HEADER(); - -public: - PropertyTool(); - ~PropertyTool(); - - /** @name Getter/setter */ - //@{ - /// set the part shape - void setValue(const Tool&); - /// get the part shape - const Tool &getValue(void) const; - //@} - - /** @name Python interface */ - //@{ - PyObject* getPyObject(void); - void setPyObject(PyObject *value); - //@} - - /** @name Save/restore */ - //@{ - void Save (Base::Writer &writer) const; - void Restore(Base::XMLReader &reader); - - App::Property *Copy(void) const; - void Paste(const App::Property &from); - unsigned int getMemSize (void) const; - //@} - -private: - Tool _Tool; -}; - - -} //namespace Path - - -#endif // PROPERTYTOOL_H diff --git a/src/Mod/Path/App/PropertyTooltable.cpp b/src/Mod/Path/App/PropertyTooltable.cpp deleted file mode 100644 index 7c4a3bc8f5..0000000000 --- a/src/Mod/Path/App/PropertyTooltable.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" - -#ifndef _PreComp_ -# include -#endif - - -#include -#include -#include -#include -#include -#include - -#include "PropertyTooltable.h" -#include "TooltablePy.h" - -using namespace Path; - -TYPESYSTEM_SOURCE(Path::PropertyTooltable, App::Property) - -PropertyTooltable::PropertyTooltable() -{ -} - -PropertyTooltable::~PropertyTooltable() -{ -} - -void PropertyTooltable::setValue(const Tooltable& tt) -{ - aboutToSetValue(); - _Tooltable = tt; - hasSetValue(); -} - - -const Tooltable &PropertyTooltable::getValue()const -{ - return _Tooltable; -} - -PyObject *PropertyTooltable::getPyObject() -{ - return new TooltablePy(new Tooltable(_Tooltable)); -} - -void PropertyTooltable::setPyObject(PyObject *value) -{ - if (PyObject_TypeCheck(value, &(TooltablePy::Type))) { - TooltablePy *pcObject = static_cast(value); - setValue(*pcObject->getTooltablePtr()); - } - else { - std::string error = std::string("type must be 'Tooltable', not "); - error += value->ob_type->tp_name; - throw Base::TypeError(error); - } -} - -App::Property *PropertyTooltable::Copy() const -{ - PropertyTooltable *prop = new PropertyTooltable(); - prop->_Tooltable = this->_Tooltable; - - return prop; -} - -void PropertyTooltable::Paste(const App::Property &from) -{ - aboutToSetValue(); - _Tooltable = dynamic_cast(from)._Tooltable; - hasSetValue(); -} - -unsigned int PropertyTooltable::getMemSize () const -{ - return _Tooltable.getMemSize(); -} - -void PropertyTooltable::Save (Base::Writer &writer) const -{ - _Tooltable.Save(writer); -} - -void PropertyTooltable::Restore(Base::XMLReader &reader) -{ - Path::Tooltable temp; - temp.Restore(reader); - setValue(temp); -} - - diff --git a/src/Mod/Path/App/PropertyTooltable.h b/src/Mod/Path/App/PropertyTooltable.h deleted file mode 100644 index 78267ce46f..0000000000 --- a/src/Mod/Path/App/PropertyTooltable.h +++ /dev/null @@ -1,75 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#ifndef PROPERTYTOOLTABLE_H -#define PROPERTYTOOLTABLE_H - -#include "Tooltable.h" -#include - -namespace Path -{ - - -/** The tooltable property class. */ -class PathExport PropertyTooltable : public App::Property -{ - TYPESYSTEM_HEADER(); - -public: - PropertyTooltable(); - ~PropertyTooltable(); - - /** @name Getter/setter */ - //@{ - /// set the part shape - void setValue(const Tooltable&); - /// get the part shape - const Tooltable &getValue(void) const; - //@} - - /** @name Python interface */ - //@{ - PyObject* getPyObject(void); - void setPyObject(PyObject *value); - //@} - - /** @name Save/restore */ - //@{ - void Save (Base::Writer &writer) const; - void Restore(Base::XMLReader &reader); - - App::Property *Copy(void) const; - void Paste(const App::Property &from); - unsigned int getMemSize (void) const; - //@} - -private: - Tooltable _Tooltable; -}; - - -} //namespace Path - - -#endif // PROPERTYTOOLTABLE_H diff --git a/src/Mod/Path/App/Tool.cpp b/src/Mod/Path/App/Tool.cpp deleted file mode 100644 index 0e0afda51e..0000000000 --- a/src/Mod/Path/App/Tool.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - -#include "PreCompiled.h" - -#ifndef _PreComp_ - -#endif -#include -#include -#include -#include "Tool.h" - -using namespace Base; -using namespace Path; - -TYPESYSTEM_SOURCE(Path::Tool , Base::Persistence) - -// Constructors & destructors - -Tool::Tool(const char* name, - ToolType type, - ToolMaterial /*material*/, - double diameter, - double lengthoffset, - double flatradius, - double cornerradius, - double cuttingedgeangle, - double cuttingedgeheight) -:Name(name),Type(type),Material(MATUNDEFINED),Diameter(diameter),LengthOffset(lengthoffset), -FlatRadius(flatradius),CornerRadius(cornerradius),CuttingEdgeAngle(cuttingedgeangle), -CuttingEdgeHeight(cuttingedgeheight) -{ -} - -Tool::Tool() -{ - Type = UNDEFINED; - Material = MATUNDEFINED; - Diameter = 0; - LengthOffset = 0; - FlatRadius = 0; - CornerRadius = 0; - CuttingEdgeAngle = 180; - CuttingEdgeHeight = 0; -} - -Tool::~Tool() -{ -} - -// Reimplemented from base class -unsigned int Tool::getMemSize () const -{ - return 0; -} - -void Tool::Save (Writer &writer) const -{ - writer.Stream() << writer.ind() << "" << std::endl; -} - -void Tool::Restore(XMLReader &reader) -{ - reader.readElement("Tool"); - Name = reader.getAttribute("name"); - Diameter = reader.hasAttribute("diameter") ? (double) reader.getAttributeAsFloat("diameter") : 0.0; - LengthOffset = reader.hasAttribute("length") ? (double) reader.getAttributeAsFloat("length") : 0.0; - FlatRadius = reader.hasAttribute("flat") ? (double) reader.getAttributeAsFloat("flat") : 0.0; - CornerRadius = reader.hasAttribute("corner") ? (double) reader.getAttributeAsFloat("corner") : 0.0; - CuttingEdgeAngle = reader.hasAttribute("angle") ? (double) reader.getAttributeAsFloat("angle") : 180.0; - CuttingEdgeHeight = reader.hasAttribute("height") ? (double) reader.getAttributeAsFloat("height") : 0.0; - std::string type = reader.hasAttribute("type") ? reader.getAttribute("type") : ""; - std::string mat = reader.hasAttribute("mat") ? reader.getAttribute("mat") : ""; - - Type = getToolType(type); - Material = getToolMaterial(mat); - - -} - -const std::vector Tool::ToolTypes() -{ - std::vector toolTypes(13); - toolTypes[0] ="EndMill"; - toolTypes[1] ="Drill"; - toolTypes[2] ="CenterDrill"; - toolTypes[3] ="CounterSink"; - toolTypes[4] ="CounterBore"; - toolTypes[5] ="FlyCutter"; - toolTypes[6] ="Reamer"; - toolTypes[7] ="Tap"; - toolTypes[8] ="SlotCutter"; - toolTypes[9] ="BallEndMill"; - toolTypes[10] ="ChamferMill"; - toolTypes[11] ="CornerRound"; - toolTypes[12] ="Engraver"; - return toolTypes; - -} - -const std::vector Tool::ToolMaterials() -{ - std::vector toolMat(7); - toolMat[0] ="Carbide"; - toolMat[1] ="HighSpeedSteel"; - toolMat[2] ="HighCarbonToolSteel"; - toolMat[3] ="CastAlloy"; - toolMat[4] ="Ceramics"; - toolMat[5] ="Diamond"; - toolMat[6] ="Sialon"; - return toolMat; - -} - -Tool::ToolType Tool::getToolType(std::string type) -{ - Tool::ToolType Type; - if(type=="EndMill") - Type = Tool::ENDMILL; - else if(type=="Drill") - Type = Tool::DRILL; - else if(type=="CenterDrill") - Type = Tool::CENTERDRILL; - else if(type=="CounterSink") - Type = Tool::COUNTERSINK; - else if(type=="CounterBore") - Type = Tool::COUNTERBORE; - else if(type=="FlyCutter") - Type = Tool::FLYCUTTER; - else if(type=="Reamer") - Type = Tool::REAMER; - else if(type=="Tap") - Type = Tool::TAP; - else if(type=="SlotCutter") - Type = Tool::SLOTCUTTER; - else if(type=="BallEndMill") - Type = Tool::BALLENDMILL; - else if(type=="ChamferMill") - Type = Tool::CHAMFERMILL; - else if(type=="CornerRound") - Type = Tool::CORNERROUND; - else if(type=="Engraver") - Type = Tool::ENGRAVER; - else - Type = Tool::UNDEFINED; - - return Type; -} - -Tool::ToolMaterial Tool::getToolMaterial(std::string mat) -{ - Tool::ToolMaterial Material; - if(mat=="Carbide") - Material = Tool::CARBIDE; - else if(mat=="HighSpeedSteel") - Material = Tool::HIGHSPEEDSTEEL; - else if(mat=="HighCarbonToolSteel") - Material = Tool::HIGHCARBONTOOLSTEEL; - else if(mat=="CastAlloy") - Material = Tool::CASTALLOY; - else if(mat=="Ceramics") - Material = Tool::CERAMICS; - else if(mat=="Diamond") - Material = Tool::DIAMOND; - else if(mat=="Sialon") - Material = Tool::SIALON; - else - Material = Tool::MATUNDEFINED; - - return Material; -} - -const char* Tool::TypeName(Tool::ToolType typ) { - switch (typ) { - case Tool::DRILL: - return "Drill"; - case Tool::CENTERDRILL: - return "CenterDrill"; - case Tool::COUNTERSINK: - return "CounterSink"; - case Tool::COUNTERBORE: - return "CounterBore"; - case Tool::FLYCUTTER: - return "FlyCutter"; - case Tool::REAMER: - return "Reamer"; - case Tool::TAP: - return "Tap"; - case Tool::ENDMILL: - return "EndMill"; - case Tool::SLOTCUTTER: - return "SlotCutter"; - case Tool::BALLENDMILL: - return "BallEndMill"; - case Tool::CHAMFERMILL: - return "ChamferMill"; - case Tool::CORNERROUND: - return "CornerRound"; - case Tool::ENGRAVER: - return "Engraver"; - case Tool::UNDEFINED: - return "Undefined"; - } - return "Undefined"; -} - -const char* Tool::MaterialName(Tool::ToolMaterial mat) -{ - switch (mat) { - case Tool::HIGHSPEEDSTEEL: - return "HighSpeedSteel"; - case Tool::CARBIDE: - return "Carbide"; - case Tool::HIGHCARBONTOOLSTEEL: - return "HighCarbonToolSteel"; - case Tool::CASTALLOY: - return "CastAlloy"; - case Tool::CERAMICS: - return "Ceramics"; - case Tool::DIAMOND: - return "Diamond"; - case Tool::SIALON: - return "Sialon"; - case Tool::MATUNDEFINED: - return "Undefined"; - } - return "Undefined"; -} \ No newline at end of file diff --git a/src/Mod/Path/App/Tool.h b/src/Mod/Path/App/Tool.h deleted file mode 100644 index 7be4d9d857..0000000000 --- a/src/Mod/Path/App/Tool.h +++ /dev/null @@ -1,107 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - -#ifndef PATH_TOOL_H -#define PATH_TOOL_H - -#include -#include -#include -#include -#include - -namespace Path -{ - - /** The representation of a single tool */ - class PathExport Tool : public Base::Persistence - { - TYPESYSTEM_HEADER(); - - public: - enum ToolType { - UNDEFINED, - DRILL, - CENTERDRILL, - COUNTERSINK, - COUNTERBORE, - FLYCUTTER, - REAMER, - TAP, - ENDMILL, - SLOTCUTTER, - BALLENDMILL, - CHAMFERMILL, - CORNERROUND, - ENGRAVER }; - - enum ToolMaterial { - MATUNDEFINED, - HIGHSPEEDSTEEL, - HIGHCARBONTOOLSTEEL, - CASTALLOY, - CARBIDE, - CERAMICS, - DIAMOND, - SIALON }; - - //constructors - Tool(); - Tool(const char* name, - ToolType type=Tool::UNDEFINED, - ToolMaterial material=Tool::MATUNDEFINED, - double diameter=10.0, - double lengthoffset=100, - double flatradius=0, - double cornerradius=0, - double cuttingedgeangle=0, - double cuttingedgeheight=0); - ~Tool(); - - // from base class - virtual unsigned int getMemSize (void) const; - virtual void Save (Base::Writer &/*writer*/) const; - virtual void Restore(Base::XMLReader &/*reader*/); - - // attributes - std::string Name; - ToolType Type; - ToolMaterial Material; - double Diameter; - double LengthOffset; - double FlatRadius; - double CornerRadius; - double CuttingEdgeAngle; - double CuttingEdgeHeight; - - static const std::vector ToolTypes(void); - static const std::vector ToolMaterials(void); - static const char* TypeName(ToolType typ); - static ToolType getToolType(std::string type); - static ToolMaterial getToolMaterial(std::string mat); - static const char* MaterialName(ToolMaterial mat); - }; - - using ToolPtr = std::shared_ptr; -} //namespace Path - -#endif // PATH_TOOL_H diff --git a/src/Mod/Path/App/ToolPy.xml b/src/Mod/Path/App/ToolPy.xml deleted file mode 100644 index c174b7a20b..0000000000 --- a/src/Mod/Path/App/ToolPy.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - The Tool objects holds the properties of a CNC tool. -optional attributes: - name: a user-defined name for this tool - tooltype: Drill, CenterDrill, CounterSink, CounterBore, Reamer, Tap, EndMill, SlotCutter, BallEndMill, ChamferMill, CornerRound, Engraver or Undefined - material: HighSpeedSteel, HighCarbonToolSteel, Carbide, CastAlloy, Ceramics, Diamond, Sialon or Undefined - diameter : the diameter of this tool - lengthOffset - flatRadius - cornerRadius - cuttingEdgeAngle - cuttingEdgeHeight - - - - the name of this tool in mm - - - - - - the type of this tool: Drill, CenterDrill, CounterSink, CounterBore, Reamer, Tap, -EndMill, SlotCutter, BallEndMill, ChamferMill, CornerRound, Engraver or Undefined - - - - - - the material of this tool: Steel, Carbide, HighSpeedSteel, -HighCarbonToolSteel CastAlloy, Ceramics, Diamond, Sialon or Undefined - - - - - - the diameter of this tool in mm - - - - - - the length offset of this tool in mm - - - - - - the flat radius of this tool in mm - - - - - - the corner radius of this tool in mm - - - - - - the cutting edge angle of this tool - - - - - - the cutting edge height of this tool in mm - - - - - - returns a copy of this tool - - - - - returns all available tool types - - - - - returns all available tool materials - - - - - setFromTemplate(xmlString|dictionary) ... fills receiver with values from the template string or dictionary - - - - - templateAttrs() ... returns a dictionary with all attributes - - - - - diff --git a/src/Mod/Path/App/ToolPyImp.cpp b/src/Mod/Path/App/ToolPyImp.cpp deleted file mode 100644 index 17cfe90d2c..0000000000 --- a/src/Mod/Path/App/ToolPyImp.cpp +++ /dev/null @@ -1,295 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" -#include "Base/Reader.h" -#include "Mod/Path/App/Tool.h" -#include "Mod/Path/App/Tooltable.h" - -// inclusion of the generated files (generated out of ToolPy.xml and TooltablePy.xml) -#include "ToolPy.h" -#include "ToolPy.cpp" - -using namespace Path; - -#define PYSTRING_FROMSTRING(str) PyUnicode_FromString(str) -#define PYINT_TYPE PyLong_Type -#define PYINT_FROMLONG(l) PyLong_FromLong(l) -#define PYINT_ASLONG(o) PyLong_AsLong(o) - - -// returns a string which represents the object e.g. when printed in python -std::string ToolPy::representation() const -{ - std::stringstream str; - str.precision(5); - str << "Tool "; - str << getToolPtr()->Name; - return str.str(); -} - -PyObject *ToolPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper -{ - // create a new instance of ToolPy and the Twin object - return new ToolPy(new Tool); -} - -// constructor method -int ToolPy::PyInit(PyObject* args, PyObject* kwd) -{ - char *name="Default tool"; - char *type = "Undefined"; - char *mat = "Undefined"; - PyObject *dia = nullptr; - PyObject *len = nullptr; - PyObject *fla = nullptr; - PyObject *cor = nullptr; - PyObject *ang = nullptr; - PyObject *hei = nullptr; - int version = 1; - - static char *kwlist[] = {"name", "tooltype", "material", "diameter", "lengthOffset", "flatRadius", "cornerRadius", "cuttingEdgeAngle", "cuttingEdgeHeight" , "version", nullptr}; - - PyObject *dict = nullptr; - if (!kwd && (PyObject_TypeCheck(args, &PyDict_Type) || PyArg_ParseTuple(args, "O!", &PyDict_Type, &dict))) { - static PyObject *arg = PyTuple_New(0); - if (PyObject_TypeCheck(args, &PyDict_Type)) { - dict = args; - } - if (!PyArg_ParseTupleAndKeywords(arg, dict, "|sssOOOOOOi", kwlist, &name, &type, &mat, &dia, &len, &fla, &cor, &ang, &hei, &version)) { - return -1; - } - } else { - PyErr_Clear(); - if (!PyArg_ParseTupleAndKeywords(args, kwd, "|sssOOOOOO", kwlist, &name, &type, &mat, &dia, &len, &fla, &cor, &ang, &hei)) { - return -1; - } - } - - if (1 != version) { - PyErr_SetString(PyExc_TypeError, "Unsupported Tool template version"); - return -1; - } - - getToolPtr()->Name = name; - - std::string typeStr(type); - getToolPtr()->Type = Tool::getToolType(typeStr); - - std::string matStr(mat); - getToolPtr()->Material = Tool::getToolMaterial(matStr); - - getToolPtr()->Diameter = dia ? PyFloat_AsDouble(dia) : 0.0; - getToolPtr()->LengthOffset = len ? PyFloat_AsDouble(len) : 0.0; - getToolPtr()->FlatRadius = fla ? PyFloat_AsDouble(fla) : 0.0; - getToolPtr()->CornerRadius = cor ? PyFloat_AsDouble(cor) : 0.0; - getToolPtr()->CuttingEdgeAngle = ang ? PyFloat_AsDouble(ang) : 180.0; - getToolPtr()->CuttingEdgeHeight = hei ? PyFloat_AsDouble(hei) : 0.0; - - return 0; -} - -// attributes get/setters - -Py::String ToolPy::getName() const -{ - return Py::String(getToolPtr()->Name.c_str()); -} - -void ToolPy::setName(Py::String arg) -{ - std::string name = arg.as_std_string(); - getToolPtr()->Name = name; -} - -Py::String ToolPy::getToolType() const -{ - return Py::String(Tool::TypeName(getToolPtr()->Type)); -} - -void ToolPy::setToolType(Py::String arg) -{ - std::string typeStr(arg.as_std_string()); - getToolPtr()->Type = Tool::getToolType(typeStr); - -} - -Py::String ToolPy::getMaterial() const -{ - return Py::String(Tool::MaterialName(getToolPtr()->Material)); -} - -void ToolPy::setMaterial(Py::String arg) -{ - std::string matStr(arg.as_std_string()); - getToolPtr()->Material = Tool::getToolMaterial(matStr); -} - -Py::Float ToolPy::getDiameter() const -{ - return Py::Float(getToolPtr()->Diameter); -} - -void ToolPy::setDiameter(Py::Float arg) -{ - getToolPtr()->Diameter = arg.operator double(); -} - -Py::Float ToolPy::getLengthOffset() const -{ - return Py::Float(getToolPtr()->LengthOffset); -} - -void ToolPy::setLengthOffset(Py::Float arg) -{ - getToolPtr()->LengthOffset = arg.operator double(); -} - -Py::Float ToolPy::getFlatRadius() const -{ - return Py::Float(getToolPtr()->FlatRadius); -} - -void ToolPy::setFlatRadius(Py::Float arg) -{ - getToolPtr()->FlatRadius = arg.operator double(); -} - -Py::Float ToolPy::getCornerRadius() const -{ - return Py::Float(getToolPtr()->CornerRadius); -} - -void ToolPy::setCornerRadius(Py::Float arg) -{ - getToolPtr()->CornerRadius = arg.operator double(); -} - -Py::Float ToolPy::getCuttingEdgeAngle() const -{ - return Py::Float(getToolPtr()->CuttingEdgeAngle); -} - -void ToolPy::setCuttingEdgeAngle(Py::Float arg) -{ - getToolPtr()->CuttingEdgeAngle = arg.operator double(); -} - -Py::Float ToolPy::getCuttingEdgeHeight() const -{ - return Py::Float(getToolPtr()->CuttingEdgeHeight); -} - -void ToolPy::setCuttingEdgeHeight(Py::Float arg) -{ - getToolPtr()->CuttingEdgeHeight = arg.operator double(); -} - -// custom attributes get/set - -PyObject *ToolPy::getCustomAttributes(const char* /*attr*/) const -{ - return nullptr; -} - -int ToolPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) -{ - return 0; -} - -PyObject* ToolPy::copy(PyObject * args) -{ - if (PyArg_ParseTuple(args, "")) { - return new ToolPy(new Path::Tool(*getToolPtr())); - } - throw Py::TypeError("This method accepts no argument"); -} - -PyObject* ToolPy::setFromTemplate(PyObject * args) -{ - char *pstr = nullptr; - if (PyArg_ParseTuple(args, "s", &pstr)) { - // embed actual string in dummy tag so XMLReader can consume that on construction - std::ostringstream os; - os << "" << pstr << ""; - std::istringstream is(os.str()); - Base::XMLReader reader("", is); - getToolPtr()->Restore(reader); - Py_Return ; - } - - PyErr_Clear(); - if (!PyInit(args, nullptr)) { - Py_Return ; - } - - PyErr_SetString(PyExc_TypeError, "argument must be a string or dictionary"); - return nullptr; -} - -PyObject* ToolPy::templateAttrs(PyObject * args) -{ - if (!args || PyArg_ParseTuple(args, "")) { - PyObject *dict = PyDict_New(); - PyDict_SetItemString(dict, "version", PYINT_FROMLONG(1)); - PyDict_SetItemString(dict, "name", PYSTRING_FROMSTRING(getToolPtr()->Name.c_str())); - PyDict_SetItemString(dict, "tooltype",PYSTRING_FROMSTRING(Tool::TypeName(getToolPtr()->Type))); - PyDict_SetItemString(dict, "material", PYSTRING_FROMSTRING(Tool::MaterialName(getToolPtr()->Material))); - PyDict_SetItemString(dict, "diameter", PyFloat_FromDouble(getToolPtr()->Diameter)); - PyDict_SetItemString(dict, "lengthOffset", PyFloat_FromDouble(getToolPtr()->LengthOffset)); - PyDict_SetItemString(dict, "flatRadius", PyFloat_FromDouble(getToolPtr()->FlatRadius)); - PyDict_SetItemString(dict, "cornerRadius", PyFloat_FromDouble(getToolPtr()->CornerRadius)); - PyDict_SetItemString(dict, "cuttingEdgeAngle", PyFloat_FromDouble(getToolPtr()->CuttingEdgeAngle)); - PyDict_SetItemString(dict, "cuttingEdgeHeight", PyFloat_FromDouble(getToolPtr()->CuttingEdgeHeight)); - return dict; - } - throw Py::TypeError("This method accepts no argument"); -} - -PyObject* ToolPy::getToolTypes(PyObject * args) -{ - if (PyArg_ParseTuple(args, "")) { - std::vector toolTypes = Tool::ToolTypes(); - Py::List list; - for(unsigned i = 0; i != toolTypes.size(); i++) { - - list.append(Py::asObject(PYSTRING_FROMSTRING(toolTypes[i].c_str()))); - } - return Py::new_reference_to(list); - } - throw Py::TypeError("This method accepts no argument"); -} - -PyObject* ToolPy::getToolMaterials(PyObject * args) -{ - if (PyArg_ParseTuple(args, "")) { - std::vector toolMaterials = Tool::ToolMaterials(); - Py::List list;; - for(unsigned i = 0; i != toolMaterials.size(); i++) { - - list.append(Py::asObject(PYSTRING_FROMSTRING(toolMaterials[i].c_str()))); - } - return Py::new_reference_to(list); - } - throw Py::TypeError("This method accepts no argument"); -} diff --git a/src/Mod/Path/App/Tooltable.cpp b/src/Mod/Path/App/Tooltable.cpp deleted file mode 100644 index a7d10877dc..0000000000 --- a/src/Mod/Path/App/Tooltable.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" - -#ifndef _PreComp_ - -#endif -#include -#include -#include -#include "Tooltable.h" - -using namespace Base; -using namespace Path; - -TYPESYSTEM_SOURCE(Path::Tooltable , Base::Persistence) - -Tooltable::Tooltable() -{ - Version = 0; -} - -Tooltable::~Tooltable() -{ -} - -void Tooltable::addTool(const Tool &tool) -{ - ToolPtr tmp = std::make_shared(tool); - if (!Tools.empty()) { - int max = 0; - for(std::map::const_iterator i = Tools.begin(); i != Tools.end(); ++i) { - int k = i->first; - if (k > max) - max = k; - } - Tools[max+1]= tmp; - } else - Tools[1] = tmp; -} - -void Tooltable::setTool(const Tool &tool, int pos) -{ - if (pos == -1) { - addTool(tool); - } else { - ToolPtr tmp = std::make_shared(tool); - Tools[pos] = tmp; - } -} - -void Tooltable::deleteTool(int pos) -{ - if (Tools.find(pos) != Tools.end()) { - Tools.erase(pos); - } else { - throw Base::IndexError("Index not found"); - } -} - -unsigned int Tooltable::getMemSize () const -{ - return 0; -} - -void Tooltable::Save (Writer &writer) const -{ - writer.Stream() << writer.ind() << "" << std::endl; - writer.incInd(); - for(std::map::const_iterator i = Tools.begin(); i != Tools.end(); ++i) { - int k = i->first; - ToolPtr v = i->second; - writer.Stream() << writer.ind() << "" << std::endl; - writer.incInd(); - v->Save(writer); - writer.decInd(); - writer.Stream() << writer.ind() << "" << std::endl; - } - writer.decInd(); - writer.Stream() << writer.ind() << "" << std::endl ; - -} - -void Tooltable::Restore (XMLReader &reader) -{ - Tools.clear(); - reader.readElement("Tooltable"); - int count = reader.getAttributeAsInteger("count"); - for (int i = 0; i < count; i++) { - reader.readElement("Toolslot"); - int id = reader.getAttributeAsInteger("number"); - ToolPtr tmp = std::make_shared(); - tmp->Restore(reader); - Tools[id] = tmp; - } -} diff --git a/src/Mod/Path/App/Tooltable.h b/src/Mod/Path/App/Tooltable.h deleted file mode 100644 index bdfbe27fba..0000000000 --- a/src/Mod/Path/App/Tooltable.h +++ /dev/null @@ -1,68 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#ifndef PATH_TOOLTABLE_H -#define PATH_TOOLTABLE_H - -#include -#include -#include -#include -#include "Tool.h" - -namespace Path -{ /** The representation of a table of tools */ - class PathExport Tooltable : public Base::Persistence - { - TYPESYSTEM_HEADER(); - - public: - //constructors - Tooltable(); - ~Tooltable(); - - // from base class - virtual unsigned int getMemSize (void) const; - virtual void Save (Base::Writer &/*writer*/) const; - virtual void Restore(Base::XMLReader &/*reader*/); - - // new functions - void addTool(const Tool &tool); // adds a tool at the end - void setTool(const Tool &tool, int); // inserts a tool - void deleteTool(int); // deletes a tool - - // auto - unsigned int getSize(void) const {return Tools.size();} - const Tool &getTool(int pos) {return *Tools.at(pos);} - const std::map &getTools(void) const {return Tools;} - bool hasTool(int pos) const {return (Tools.count(pos) != 0);} - - // attributes - std::map Tools; - int Version; - std::string Name; - }; - -} //namespace Path - -#endif // PATH_TOOLTABLE_H diff --git a/src/Mod/Path/App/TooltablePy.xml b/src/Mod/Path/App/TooltablePy.xml deleted file mode 100644 index 3549a508e0..0000000000 --- a/src/Mod/Path/App/TooltablePy.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - The Tooltable object holds a table of CNC tools - - - - the name of this tool table - - - - - - the version of this tooltable - - - - - - the dictionary of tools of this table - - - - - - returns a copy of this tooltable - - - - - adds a tool or a list of tools at the end of the table - - - - - getTool(int): -returns the tool found at the given position, or None - - - - - setTool(int,tool): -adds a tool at the given position - - - - - deleteTool(int): -deletes the tool found at the given position - - - - - - setFromTemplate(dict) ... restores receiver from given template attribute dictionary - - - - - templateAttrs() ... returns a dictionary representing the receivers attributes for a template - - - - diff --git a/src/Mod/Path/App/TooltablePyImp.cpp b/src/Mod/Path/App/TooltablePyImp.cpp deleted file mode 100644 index 42b319e8ba..0000000000 --- a/src/Mod/Path/App/TooltablePyImp.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2014 Yorik van Havre * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library 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 library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" -#include "Base/Reader.h" -#include "Mod/Path/App/Tool.h" -#include "Mod/Path/App/Tooltable.h" - -// inclusion of the generated files (generated out of ToolPy.xml and TooltablePy.xml) -#include "ToolPy.h" -#include "TooltablePy.h" -#include "TooltablePy.cpp" - -using namespace Path; - -#define PYSTRING_FROMSTRING(str) PyUnicode_FromString(str) -#define PYINT_TYPE PyLong_Type -#define PYINT_FROMLONG(l) PyLong_FromLong(l) -#define PYINT_ASLONG(o) PyLong_AsLong(o) - -// returns a string which represents the object e.g. when printed in python -std::string TooltablePy::representation() const -{ - std::stringstream str; - str.precision(5); - str << "Tooltable containing "; - str << getTooltablePtr()->getSize() << " tools"; - return str.str(); -} - -PyObject *TooltablePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper -{ - return new TooltablePy(new Tooltable); -} - -// constructor method -int TooltablePy::PyInit(PyObject* args, PyObject* /*kwd*/) -{ - //char *name="Tooltable"; - //int version = 1; - - if (PyArg_ParseTuple(args, "")) { - return 0; - } - PyErr_Clear(); // set by PyArg_ParseTuple() - - PyObject *pcObj; - if (PyArg_ParseTuple(args, "O!", &(PyDict_Type), &pcObj)) { - try { - Py::Dict dict(pcObj); - setTools(dict); - } catch(...) { - PyErr_SetString(PyExc_TypeError, "The dictionary can only contain int:tool pairs"); - return -1; - } - return 0; - } - PyErr_Clear(); // set by PyArg_ParseTuple() - - if (PyArg_ParseTuple(args, "O!", &(PyList_Type), &pcObj)) { - Py::List list(pcObj); - for (Py::List::iterator it = list.begin(); it != list.end(); ++it) { - if (PyObject_TypeCheck((*it).ptr(), &(Path::ToolPy::Type))) { - Path::Tool &tool = *static_cast((*it).ptr())->getToolPtr(); - getTooltablePtr()->addTool(tool); - } - } - return 0; - } - - PyErr_SetString(PyExc_TypeError, "Argument must be either empty or a list or a dictionary"); - return -1; -} - -// Commands get/set - -Py::Dict TooltablePy::getTools() const -{ - Py::Dict dict; - for(std::map::iterator i = getTooltablePtr()->Tools.begin(); i != getTooltablePtr()->Tools.end(); ++i) { - PyObject *tool = new Path::ToolPy(new Tool(*i->second)); - dict.setItem(Py::Long(i->first), Py::asObject(tool)); - } - return dict; -} - -void TooltablePy::setTools(Py::Dict arg) -{ - getTooltablePtr()->Tools.clear(); - PyObject* dict_copy = PyDict_Copy(arg.ptr()); - PyObject *key, *value; - Py_ssize_t pos = 0; - while (PyDict_Next(dict_copy, &pos, &key, &value)) { - if ( PyObject_TypeCheck(key,&(PYINT_TYPE)) && ((PyObject_TypeCheck(value, &(Path::ToolPy::Type))) || PyObject_TypeCheck(value, &PyDict_Type))) { - int ckey = (int)PYINT_ASLONG(key); - if (PyObject_TypeCheck(value, &(Path::ToolPy::Type))) { - Path::Tool &tool = *static_cast(value)->getToolPtr(); - getTooltablePtr()->setTool(tool, ckey); - } else { - PyErr_Clear(); - Path::Tool *tool = new Path::Tool; - // The 'pyTool' object must be created on the heap otherwise Python - // will fail to properly track the reference counts and aborts - // in debug mode. - Path::ToolPy* pyTool = new Path::ToolPy(tool); - PyObject* success = pyTool->setFromTemplate(value); - if (!success) { - Py_DECREF(pyTool); - throw Py::Exception(); - } - getTooltablePtr()->setTool(*tool, ckey); - Py_DECREF(pyTool); - Py_DECREF(success); - } - } else { - throw Py::TypeError("The dictionary can only contain int:tool pairs"); - } - } -} - -// specific methods - -PyObject* TooltablePy::copy(PyObject * args) -{ - if (PyArg_ParseTuple(args, "")) { - return new TooltablePy(new Path::Tooltable(*getTooltablePtr())); - } - throw Py::TypeError("This method accepts no argument"); -} - -PyObject* TooltablePy::addTools(PyObject * args) -{ - PyObject* o; - if (PyArg_ParseTuple(args, "O!", &(Path::ToolPy::Type), &o)) { - Path::Tool &tool = *static_cast(o)->getToolPtr(); - getTooltablePtr()->addTool(tool); - //return new TooltablePy(new Path::Tooltable(*getTooltablePtr())); - Py_INCREF(Py_None); - return Py_None; - } - PyErr_Clear(); - if (PyArg_ParseTuple(args, "O!", &(PyList_Type), &o)) { - Py::List list(o); - for (Py::List::iterator it = list.begin(); it != list.end(); ++it) { - if (PyObject_TypeCheck((*it).ptr(), &(Path::ToolPy::Type))) { - Path::Tool &tool = *static_cast((*it).ptr())->getToolPtr(); - getTooltablePtr()->addTool(tool); - } - } - //return new TooltablePy(new Path::Tooltable(*getTooltablePtr())); - Py_INCREF(Py_None); - return Py_None; - } - Py_Error(PyExc_TypeError, "Wrong parameters - tool or list of tools expected"); -} - -PyObject* TooltablePy::setTool(PyObject * args) -{ - PyObject* o; - int pos = -1; - if (PyArg_ParseTuple(args, "iO!", &pos, &(Path::ToolPy::Type), &o)) { - Path::Tool &tool = *static_cast(o)->getToolPtr(); - getTooltablePtr()->setTool(tool,pos); - //return new TooltablePy(new Path::Tooltable(*getTooltablePtr())); - Py_INCREF(Py_None); - return Py_None; - } - Py_Error(PyExc_TypeError, "Wrong parameters - expected tool and optional integer"); -} - -PyObject* TooltablePy::getTool(PyObject * args) -{ - int pos = -1; - if (PyArg_ParseTuple(args, "i", &pos)) { - if (getTooltablePtr()->hasTool(pos)) - { - Path::Tool tool = getTooltablePtr()->getTool(pos); - return new ToolPy(new Path::Tool(tool)); - } - else - { - Py_INCREF(Py_None); - return Py_None; - } - } - Py_Error(PyExc_TypeError, "Argument must be integer"); -} - -PyObject* TooltablePy::deleteTool(PyObject * args) -{ - int pos = -1; - if (PyArg_ParseTuple(args, "|i", &pos)) { - getTooltablePtr()->deleteTool(pos); - //return new TooltablePy(new Path::Tooltable(*getTooltablePtr())); - Py_INCREF(Py_None); - return Py_None; - } - Py_Error(PyExc_TypeError, "Wrong parameters - expected an integer (optional)"); -} - -// custom attributes get/set - -PyObject *TooltablePy::getCustomAttributes(const char* /*attr*/) const -{ - return nullptr; -} - -int TooltablePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) -{ - return 0; -} - -Py::Int TooltablePy::getVersion() const -{ - return Py::Int(getTooltablePtr()->Version); -} - -void TooltablePy::setVersion(Py::Int version) { - getTooltablePtr()->Version = version; -} - -Py::String TooltablePy::getName() const -{ - return Py::String(getTooltablePtr()->Name.c_str()); -} - -void TooltablePy::setName(Py::String arg) -{ - std::string name = arg.as_std_string(); - getTooltablePtr()->Name = name; -} - -PyObject* TooltablePy::setFromTemplate(PyObject * args) -{ - PyObject *dict = nullptr; - if (PyArg_ParseTuple(args, "O!", &PyDict_Type, &dict)) { - Py::Dict d(dict); - setTools(d); - Py_Return ; - } - - PyErr_SetString(PyExc_TypeError, "argument must be a dictionary returned from templateAttrs()"); - return nullptr; -} - -PyObject* TooltablePy::templateAttrs(PyObject * args) -{ - (void)args; - PyObject *dict = PyDict_New(); - for(std::map::iterator i = getTooltablePtr()->Tools.begin(); i != getTooltablePtr()->Tools.end(); ++i) { - // The 'tool' object must be created on the heap otherwise Python - // will fail to properly track the reference counts and aborts - // in debug mode. - Path::ToolPy* tool = new Path::ToolPy(new Path::Tool(*i->second)); - PyObject *attrs = tool->templateAttrs(nullptr); - PyDict_SetItem(dict, PYINT_FROMLONG(i->first), attrs); - Py_DECREF(tool); - } - return dict; -} - diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 420821d86c..6eed03090e 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -197,9 +197,6 @@ SET(PathScripts_SRCS PathScripts/PathSimulatorGui.py PathScripts/PathStock.py PathScripts/PathStop.py - PathScripts/PathToolEdit.py - PathScripts/PathToolLibraryEditor.py - PathScripts/PathToolLibraryManager.py PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py @@ -290,10 +287,8 @@ SET(PathTests_SRCS PathTests/TestPathToolChangeGenerator.py PathTests/TestPathThreadMilling.py PathTests/TestPathThreadMillingGenerator.py - PathTests/TestPathTool.py PathTests/TestPathToolBit.py PathTests/TestPathToolController.py - PathTests/TestPathTooltable.py PathTests/TestPathUtil.py PathTests/TestPathVcarve.py PathTests/TestPathVoronoi.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 9ef71d6643..18948c1080 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -129,8 +129,6 @@ panels/ToolBitEditor.ui panels/ToolBitLibraryEdit.ui panels/ToolBitSelector.ui - panels/ToolEditor.ui - panels/ToolLibraryEditor.ui panels/TaskPathCamoticsSim.ui panels/TaskPathSimulator.ui panels/ZCorrectEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/ToolLibraryEditor.ui b/src/Mod/Path/Gui/Resources/panels/ToolLibraryEditor.ui deleted file mode 100644 index a17c56e007..0000000000 --- a/src/Mod/Path/Gui/Resources/panels/ToolLibraryEditor.ui +++ /dev/null @@ -1,429 +0,0 @@ - - - ToolLibrary - - - - 0 - 0 - 946 - 614 - - - - - 0 - 0 - - - - Tool Library - - - - - - - - Import... - - - - - - - Export... - - - - - - - - - - - - - - 225 - 16777215 - - - - - 0 - - - 0 - - - 6 - - - 0 - - - - - - - Tool Tables - - - - - - - Qt::Horizontal - - - - 20 - 20 - - - - - - - - - - - 32 - 32 - - - - - - - - :/icons/edit-edit.svg:/icons/edit-edit.svg - - - - - - - - 32 - 32 - - - - - - - - :/icons/list-remove.svg - - - - - - - - - 32 - 32 - - - - - - - - :/icons/list-add.svg - - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - false - - - QAbstractItemView::NoDragDrop - - - Qt::IgnoreAction - - - true - - - - 20 - 20 - - - - - - - - - - - false - - - false - - - QAbstractItemView::DragOnly - - - Qt::IgnoreAction - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - false - - - false - - - false - - - - - - - - - - - New Tool - - - - :/icons/Path_ToolController.svg:/icons/Path_ToolController.svg - - - - - - - false - - - - 0 - 0 - - - - - - - Delete - - - - :/icons/delete.svg:/icons/delete.svg - - - - - - - false - - - false - - - Edit - - - - :/icons/edit-edit.svg:/icons/edit-edit.svg - - - - - - - false - - - false - - - Duplicate - - - - :/icons/Path_ToolDuplicate.svg:/icons/Path_ToolDuplicate.svg - - - - - - - false - - - Move up - - - - :/icons/button_up.svg:/icons/button_up.svg - - - false - - - false - - - false - - - - - - - false - - - false - - - Move down - - - - :/icons/button_down.svg:/icons/button_down.svg - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - false - - - - 0 - 0 - - - - Create Tool Controllers for the selected tools - - - Create Tool Controller(s) - - - - :/icons/Path_LengthOffset.svg:/icons/Path_LengthOffset.svg - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - - buttonBox - accepted() - ToolLibrary - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - ToolLibrary - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/Mod/Path/Gui/Resources/preferences/PathJob.ui b/src/Mod/Path/Gui/Resources/preferences/PathJob.ui index a930929b31..9f33af9868 100644 --- a/src/Mod/Path/Gui/Resources/preferences/PathJob.ui +++ b/src/Mod/Path/Gui/Resources/preferences/PathJob.ui @@ -633,16 +633,6 @@ Tools - - - - <html><head/><body><p>Legacy Tools have no accurate shape representation and are stored in the user preferences of FreeCAD.</p></body></html> - - - Use Legacy Tools - - - diff --git a/src/Mod/Path/Gui/TaskDlgPathCompound.cpp b/src/Mod/Path/Gui/TaskDlgPathCompound.cpp index a0b9f7aeaf..af741ba7f7 100644 --- a/src/Mod/Path/Gui/TaskDlgPathCompound.cpp +++ b/src/Mod/Path/Gui/TaskDlgPathCompound.cpp @@ -40,9 +40,6 @@ #include #include -#include - - using namespace PathGui; using namespace Gui; diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 952fa06b98..87f8ef2536 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -135,12 +135,8 @@ class PathWorkbench(Workbench): # remotecmdlist = ["Path_Remote"] specialcmdlist = [] - if PathPreferences.toolsUseLegacyTools(): - toolcmdlist.append("Path_ToolLibraryEdit") - toolbitcmdlist = [] - else: - toolcmdlist.extend(PathToolBitLibraryCmd.BarList) - toolbitcmdlist = PathToolBitLibraryCmd.MenuList + toolcmdlist.extend(PathToolBitLibraryCmd.BarList) + toolbitcmdlist = PathToolBitLibraryCmd.MenuList engravecmdgroup = ["Path_EngraveTools"] FreeCADGui.addCommand( diff --git a/src/Mod/Path/Path/Op/SurfaceSupport.py b/src/Mod/Path/Path/Op/SurfaceSupport.py index c262c45f33..90aabed6a2 100644 --- a/src/Mod/Path/Path/Op/SurfaceSupport.py +++ b/src/Mod/Path/Path/Op/SurfaceSupport.py @@ -2473,8 +2473,8 @@ class FindUnifiedRegions: class OCL_Tool: - """The OCL_Tool class is designed to translate a FreeCAD standard ToolBit shape, - or Legacy tool type, in the active Tool Controller, into an OCL tool type.""" + """The OCL_Tool class is designed to translate a FreeCAD standard ToolBit shape + in the active Tool Controller, into an OCL tool type.""" def __init__(self, ocl, obj, safe=False): self.ocl = ocl @@ -2501,9 +2501,6 @@ class OCL_Tool: if hasattr(self.tool, "ShapeName"): self.toolType = self.tool.ShapeName # Indicates ToolBit tool self.toolMode = "ToolBit" - elif hasattr(self.tool, "ToolType"): - self.toolType = self.tool.ToolType # Indicates Legacy tool - self.toolMode = "Legacy" if self.toolType: Path.Log.debug( "OCL_Tool tool mode, type: {}, {}".format(self.toolMode, self.toolType) @@ -2726,25 +2723,7 @@ class OCL_Tool: def _setToolMethod(self): toolMap = dict() - if self.toolMode == "Legacy": - # Set cutter details - # https://www.freecadweb.org/api/dd/dfe/classPath_1_1Tool.html#details - toolMap = { - "EndMill": "CylCutter", - "BallEndMill": "BallCutter", - "SlotCutter": "CylCutter", - "Engraver": "ConeCutter", - "Drill": "ConeCutter", - "CounterSink": "ConeCutter", - "FlyCutter": "CylCutter", - "CenterDrill": "None", - "CounterBore": "None", - "Reamer": "None", - "Tap": "None", - "ChamferMill": "None", - "CornerRound": "None", - } - elif self.toolMode == "ToolBit": + if self.toolMode == "ToolBit": toolMap = { "endmill": "CylCutter", "ballend": "BallCutter", diff --git a/src/Mod/Path/Path/Tools/Bit.py b/src/Mod/Path/Path/Tools/Bit.py index dd3358d0c2..e59734798f 100644 --- a/src/Mod/Path/Path/Tools/Bit.py +++ b/src/Mod/Path/Path/Tools/Bit.py @@ -445,7 +445,7 @@ class ToolBit(object): def templateAttrs(self, obj): attrs = {} - attrs["version"] = 2 # Path.Tool is version 1 + attrs["version"] = 2 attrs["name"] = obj.Label if PathPreferences.toolsStoreAbsolutePaths(): attrs["shape"] = obj.BitShape diff --git a/src/Mod/Path/Path/Tools/Controller.py b/src/Mod/Path/Path/Tools/Controller.py index 6de4f24a50..90fc86898f 100644 --- a/src/Mod/Path/Path/Tools/Controller.py +++ b/src/Mod/Path/Path/Tools/Controller.py @@ -60,8 +60,8 @@ class ToolControllerTemplate: class ToolController: - def __init__(self, obj, legacyTool=False, createTool=True): - Path.Log.track("tool: {}".format(legacyTool)) + def __init__(self, obj, createTool=True): + Path.Log.track("tool: ") obj.addProperty( "App::PropertyIntegerConstraint", @@ -114,7 +114,7 @@ class ToolController: setattr(obj, n[0], n[1]) if createTool: - self.ensureUseLegacyTool(obj, legacyTool) + self.ensureToolBit(obj) @classmethod def propertyEnumerations(self, dataType="data"): @@ -154,10 +154,9 @@ class ToolController: obj.setEditorMode("Placement", 2) def onDelete(self, obj, arg2=None): - if not self.usesLegacyTool(obj): - if hasattr(obj.Tool, "InList") and len(obj.Tool.InList) == 1: - if hasattr(obj.Tool.Proxy, "onDelete"): - obj.Tool.Proxy.onDelete(obj.Tool) + if hasattr(obj.Tool, "InList") and len(obj.Tool.InList) == 1: + if hasattr(obj.Tool.Proxy, "onDelete"): + obj.Tool.Proxy.onDelete(obj.Tool) def setFromTemplate(self, obj, template): """ @@ -193,22 +192,16 @@ class ToolController: toolVersion = template.get(ToolControllerTemplate.Tool).get( ToolControllerTemplate.Version ) - if toolVersion == 1: - self.ensureUseLegacyTool(obj, True) - obj.Tool.setFromTemplate( - template.get(ToolControllerTemplate.Tool) - ) - else: - self.ensureUseLegacyTool(obj, False) - obj.Tool = PathToolBit.Factory.CreateFromAttrs( - template.get(ToolControllerTemplate.Tool) - ) - if ( - obj.Tool - and obj.Tool.ViewObject - and obj.Tool.ViewObject.Visibility - ): - obj.Tool.ViewObject.Visibility = False + self.ensureToolBit(obj) + obj.Tool = PathToolBit.Factory.CreateFromAttrs( + template.get(ToolControllerTemplate.Tool) + ) + if ( + obj.Tool + and obj.Tool.ViewObject + and obj.Tool.ViewObject.Visibility + ): + obj.Tool.ViewObject.Visibility = False if template.get(ToolControllerTemplate.Expressions): for exprDef in template.get(ToolControllerTemplate.Expressions): if exprDef[ToolControllerTemplate.ExprExpr]: @@ -240,10 +233,7 @@ class ToolController: attrs[ToolControllerTemplate.HorizRapid] = "%s" % (obj.HorizRapid) attrs[ToolControllerTemplate.SpindleSpeed] = obj.SpindleSpeed attrs[ToolControllerTemplate.SpindleDir] = obj.SpindleDir - if self.usesLegacyTool(obj): - attrs[ToolControllerTemplate.Tool] = obj.Tool.templateAttrs() - else: - attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool) + attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool) expressions = [] for expr in obj.ExpressionEngine: Path.Log.debug("%s: %s" % (expr[0], expr[1])) @@ -296,38 +286,16 @@ class ToolController: Path.Log.track() return obj.Tool - def usesLegacyTool(self, obj): - """returns True if the tool being controlled is a legacy tool""" - return isinstance(obj.Tool, Path.Tool) - - def ensureUseLegacyTool(self, obj, legacy): - if not hasattr(obj, "Tool") or (legacy != self.usesLegacyTool(obj)): - if legacy and hasattr(obj, "Tool") and len(obj.Tool.InList) == 1: - if hasattr(obj.Tool.Proxy, "onDelete"): - obj.Tool.Proxy.onDelete(obj.Tool) - obj.Document.removeObject(obj.Tool.Name) - - if hasattr(obj, "Tool"): - obj.removeProperty("Tool") - - if legacy: - obj.addProperty( - "Path::PropertyTool", - "Tool", - "Base", - QT_TRANSLATE_NOOP( - "App::Property", "The tool used by this controller" - ), - ) - else: - obj.addProperty( - "App::PropertyLink", - "Tool", - "Base", - QT_TRANSLATE_NOOP( - "App::Property", "The tool used by this controller" - ), - ) + def ensureToolBit(self, obj): + if not hasattr(obj, "Tool"): + obj.addProperty( + "App::PropertyLink", + "Tool", + "Base", + QT_TRANSLATE_NOOP( + "App::Property", "The tool used by this controller" + ), + ) def Create( @@ -337,34 +305,21 @@ def Create( assignViewProvider=True, assignTool=True, ): - legacyTool = ( - PathPreferences.toolsUseLegacyTools() - if tool is None - else isinstance(tool, Path.Tool) - ) - Path.Log.track(tool, toolNumber, legacyTool) + Path.Log.track(tool, toolNumber) obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Label = name - obj.Proxy = ToolController(obj, legacyTool, assignTool) + obj.Proxy = ToolController(obj, assignTool) if FreeCAD.GuiUp and assignViewProvider: ViewProvider(obj.ViewObject) if assignTool: if not tool: - if legacyTool: - tool = Path.Tool() - tool.Diameter = 5.0 - tool.Name = "Default Tool" - tool.CuttingEdgeHeight = 15.0 - tool.ToolType = "EndMill" - tool.Material = "HighSpeedSteel" - else: - tool = PathToolBit.Factory.Create() - if tool.ViewObject: - tool.ViewObject.Visibility = False + tool = PathToolBit.Factory.Create() + if tool.ViewObject: + tool.ViewObject.Visibility = False obj.Tool = tool if hasattr(obj.Tool, "SpindleDirection"): diff --git a/src/Mod/Path/Path/Tools/Gui/Controller.py b/src/Mod/Path/Path/Tools/Gui/Controller.py index 05c8c735d3..0f023a1dfe 100644 --- a/src/Mod/Path/Path/Tools/Gui/Controller.py +++ b/src/Mod/Path/Path/Tools/Gui/Controller.py @@ -29,7 +29,6 @@ import Path.Tools.Controller as PathToolController import Path.Tools.Gui.Bit as PathToolBitGui import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui -import PathScripts.PathToolEdit as PathToolEdit import PathScripts.PathUtil as PathUtil # lazily loaded modules @@ -120,7 +119,7 @@ class ViewProvider: def claimChildren(self): obj = self.vobj.Object - if obj and obj.Proxy and not obj.Proxy.usesLegacyTool(obj): + if obj and obj.Proxy: return [obj.Tool] return [] @@ -130,10 +129,9 @@ def Create(name="Default Tool", tool=None, toolNumber=1): obj = PathToolController.Create(name, tool, toolNumber) ViewProvider(obj.ViewObject) - if not obj.Proxy.usesLegacyTool(obj): - # ToolBits are visible by default, which is typically not what the user wants - if tool and tool.ViewObject and tool.ViewObject.Visibility: - tool.ViewObject.Visibility = False + # ToolBits are visible by default, which is typically not what the user wants + if tool and tool.ViewObject and tool.ViewObject.Visibility: + tool.ViewObject.Visibility = False return obj @@ -198,12 +196,9 @@ class ToolControllerEditor(object): self.form.horizRapid, obj, "HorizRapid" ) - if obj.Proxy.usesLegacyTool(obj): - self.editor = PathToolEdit.ToolEditor(obj.Tool, self.form.toolEditor) - else: - self.editor = None - self.form.toolBox.widget(1).hide() - self.form.toolBox.removeItem(1) + self.editor = None + self.form.toolBox.widget(1).hide() + self.form.toolBox.removeItem(1) def selectInComboBox(self, name, combo): """selectInComboBox(name, combo) ... diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 36cbdd5e00..169754331e 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -74,8 +74,6 @@ def Startup(): from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui from PathScripts import PathStop - from PathScripts import PathToolLibraryEditor - from PathScripts import PathToolLibraryManager from PathScripts import PathUtilsGui from packaging.version import Version, parse diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index b764b8a825..5ef9c94ebe 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -549,14 +549,6 @@ class ObjectJob: for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) - if True in [isinstance(t.Tool, Path.Tool) for t in obj.Tools.Group]: - FreeCAD.Console.PrintWarning( - translate( - "Path", - "This job contains Legacy tools. Legacy tools are deprecated. They will be removed after version 0.20", - ) - ) - def onChanged(self, obj, prop): if prop == "PostProcessor" and obj.PostProcessor: processor = PostProcessor.load(obj.PostProcessor) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index 3a453f9d36..d9d74fdbda 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -38,7 +38,6 @@ import PathScripts.PathJobDlg as PathJobDlg import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathStock as PathStock -import PathScripts.PathToolLibraryEditor as PathToolLibraryEditor import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import json @@ -999,35 +998,30 @@ class TaskPanel: # Try to find a tool number from the currently selected lib. Otherwise # use next available number - if PathPreferences.toolsUseLegacyTools(): - PathToolLibraryEditor.CommandToolLibraryEdit().edit( - self.obj, self.updateToolController + tools = PathToolBitGui.LoadTools() + + curLib = PathPreferences.lastFileToolLibrary() + + library = None + if curLib is not None: + with open(curLib) as fp: + library = json.load(fp) + + for tool in tools: + toolNum = self.obj.Proxy.nextToolNumber() + if library is not None: + for toolBit in library["tools"]: + + if toolBit["path"] == tool.File: + toolNum = toolBit["nr"] + + tc = PathToolControllerGui.Create( + name=tool.Label, tool=tool, toolNumber=toolNum ) - else: - tools = PathToolBitGui.LoadTools() + self.obj.Proxy.addToolController(tc) - curLib = PathPreferences.lastFileToolLibrary() - - library = None - if curLib is not None: - with open(curLib) as fp: - library = json.load(fp) - - for tool in tools: - toolNum = self.obj.Proxy.nextToolNumber() - if library is not None: - for toolBit in library["tools"]: - - if toolBit["path"] == tool.File: - toolNum = toolBit["nr"] - - tc = PathToolControllerGui.Create( - name=tool.Label, tool=tool, toolNumber=toolNum - ) - self.obj.Proxy.addToolController(tc) - - FreeCAD.ActiveDocument.recompute() - self.updateToolController() + FreeCAD.ActiveDocument.recompute() + self.updateToolController() def toolControllerDelete(self): self.objectDelete(self.form.toolControllerList) diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/PathScripts/PathPreferences.py index 785f2cdc5d..23379446e5 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/PathScripts/PathPreferences.py @@ -54,7 +54,6 @@ LastFileToolBit = "LastFileToolBit" LastFileToolLibrary = "LastFileToolLibrary" LastFileToolShape = "LastFileToolShape" -UseLegacyTools = "UseLegacyTools" UseAbsoluteToolPaths = "UseAbsoluteToolPaths" # OpenLastLibrary = "OpenLastLibrary" @@ -171,9 +170,6 @@ def searchPathsTool(sub): return paths -def toolsUseLegacyTools(): - return preferences().GetBool(UseLegacyTools, False) - def toolsStoreAbsolutePaths(): return preferences().GetBool(UseAbsoluteToolPaths, False) @@ -183,19 +179,8 @@ def toolsStoreAbsolutePaths(): # return preferences().GetBool(OpenLastLibrary, False) -def setToolsSettings(legacy, relative): +def setToolsSettings(relative): pref = preferences() - if legacy: - msgBox = QMessageBox() - msgBox.setIcon(QMessageBox.Warning) - msgBox.setText( - translate( - "Path", - "Legacy tools are deprecated. They will be removed after version 0.20", - ) - ) - msgBox.exec_() - pref.SetBool(UseLegacyTools, legacy) pref.SetBool(UseAbsoluteToolPaths, relative) # pref.SetBool(OpenLastLibrary, lastlibrary) diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py index fd867c106a..7acb341e07 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py @@ -147,8 +147,7 @@ class JobPreferencesPage: def saveToolsSettings(self): PathPreferences.setToolsSettings( - self.form.toolsUseLegacy.isChecked(), - self.form.toolsAbsolutePaths.isChecked(), + self.form.toolsAbsolutePaths.isChecked() ) def selectComboEntry(self, widget, text): @@ -328,7 +327,6 @@ class JobPreferencesPage: self.form.stockCreateCylinder.hide() def loadToolSettings(self): - self.form.toolsUseLegacy.setChecked(PathPreferences.toolsUseLegacyTools()) self.form.toolsAbsolutePaths.setChecked( PathPreferences.toolsStoreAbsolutePaths() ) diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 1403796632..ab462c7d07 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -188,20 +188,7 @@ class PathSimulation: self.tool = None if self.tool is not None: - if isinstance(self.tool, Path.Tool): - # handle legacy tools - toolProf = self.CreateToolProfile( - self.tool, - Vector(0, 1, 0), - Vector(0, 0, 0), - float(self.tool.Diameter) / 2.0, - ) - self.cutTool.Shape = Part.makeSolid( - toolProf.revolve(Vector(0, 0, 0), Vector(0, 0, 1)) - ) - else: - # handle tool bits - self.cutTool.Shape = self.tool.Shape + self.cutTool.Shape = self.tool.Shape if not self.cutTool.Shape.isValid() or self.cutTool.Shape.isNull(): self.EndSimulation() diff --git a/src/Mod/Path/PathScripts/PathToolEdit.py b/src/Mod/Path/PathScripts/PathToolEdit.py deleted file mode 100644 index b55c3d272c..0000000000 --- a/src/Mod/Path/PathScripts/PathToolEdit.py +++ /dev/null @@ -1,352 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2018 sliptonic * -# * * -# * 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 FreeCADGui -import Path -import math - -from PySide import QtGui - -Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) -# Path.Log.trackModule(Path.Log.thisModule()) - - -class ToolEditorDefault: - """Generic Tool parameter editor for all Tools that don't have a customized edit function. - Let's the user enter the raw internal data. Not the best approach but this is the starting point.""" - - def __init__(self, editor): - self.editor = editor - self.form = editor.form - - def setupUI(self): - self.form.paramImage.hide() - self.form.paramGeneric.show() - - def updateUI(self): - self.form.toolDiameter.setText( - FreeCAD.Units.Quantity( - self.editor.tool.Diameter, FreeCAD.Units.Length - ).UserString - ) - self.form.toolFlatRadius.setText( - FreeCAD.Units.Quantity( - self.editor.tool.FlatRadius, FreeCAD.Units.Length - ).UserString - ) - self.form.toolCornerRadius.setText( - FreeCAD.Units.Quantity( - self.editor.tool.CornerRadius, FreeCAD.Units.Length - ).UserString - ) - self.form.toolCuttingEdgeHeight.setText( - FreeCAD.Units.Quantity( - self.editor.tool.CuttingEdgeHeight, FreeCAD.Units.Length - ).UserString - ) - self.form.toolCuttingEdgeAngle.setText( - FreeCAD.Units.Quantity( - self.editor.tool.CuttingEdgeAngle, FreeCAD.Units.Angle - ).UserString - ) - - def updateTool(self): - self.editor.tool.Diameter = FreeCAD.Units.parseQuantity( - self.form.toolDiameter.text() - ) - self.editor.tool.FlatRadius = FreeCAD.Units.parseQuantity( - self.form.toolFlatRadius.text() - ) - self.editor.tool.CornerRadius = FreeCAD.Units.parseQuantity( - self.form.toolCornerRadius.text() - ) - self.editor.tool.CuttingEdgeAngle = FreeCAD.Units.Quantity( - self.form.toolCuttingEdgeAngle.text() - ) - self.editor.tool.CuttingEdgeHeight = FreeCAD.Units.parseQuantity( - self.form.toolCuttingEdgeHeight.text() - ) - - -class ToolEditorImage(object): - """Base implementation for all customized Tool parameter editors. - While not required it is simplest to subclass specific editors.""" - - def __init__(self, editor, imageFile, hide="", disable=""): - self.editor = editor - self.form = editor.form - self.imagePath = "{}Mod/Path/Images/Tools/{}".format( - FreeCAD.getHomePath(), imageFile - ) - self.image = QtGui.QPixmap(self.imagePath) - self.hide = hide - self.disable = disable - - form = editor.form - self.widgets = { - "D": (form.label_D, form.value_D), - "d": (form.label_d, form.value_d), - "H": (form.label_H, form.value_H), - "a": (form.label_a, form.value_a), - "S": (form.label_S, form.value_S), - } - - def setupUI(self): - Path.Log.track() - self.form.paramGeneric.hide() - self.form.paramImage.show() - - for key, widgets in self.widgets.items(): - hide = key in self.hide - disable = key in self.disable - for w in widgets: - w.setHidden(hide) - w.setDisabled(disable) - if not hide and not disable: - widgets[1].editingFinished.connect(self.editor.refresh) - - self.form.image.setPixmap(self.image) - - def updateUI(self): - Path.Log.track() - self.form.value_D.setText(self.quantityDiameter(True).UserString) - self.form.value_d.setText(self.quantityFlatRadius(True).UserString) - self.form.value_a.setText(self.quantityCuttingEdgeAngle(True).UserString) - self.form.value_H.setText(self.quantityCuttingEdgeHeight(True).UserString) - - def updateTool(self): - Path.Log.track() - toolDefault = Path.Tool() - if "D" in self.hide: - self.editor.tool.Diameter = toolDefault.Diameter - else: - self.editor.tool.Diameter = self.quantityDiameter(False) - - if "d" in self.hide: - self.editor.tool.FlatRadius = toolDefault.FlatRadius - else: - self.editor.tool.FlatRadius = self.quantityFlatRadius(False) - - if "a" in self.hide: - self.editor.tool.CuttingEdgeAngle = toolDefault.CuttingEdgeAngle - else: - self.editor.tool.CuttingEdgeAngle = self.quantityCuttingEdgeAngle(False) - - if "H" in self.hide: - self.editor.tool.CuttingEdgeHeight = toolDefault.CuttingEdgeHeight - else: - self.editor.tool.CuttingEdgeHeight = self.quantityCuttingEdgeHeight(False) - - self.editor.tool.CornerRadius = toolDefault.CornerRadius - - def quantityDiameter(self, propertyToDisplay): - if propertyToDisplay: - return FreeCAD.Units.Quantity( - self.editor.tool.Diameter, FreeCAD.Units.Length - ) - return FreeCAD.Units.parseQuantity(self.form.value_D.text()) - - def quantityFlatRadius(self, propertyToDisplay): - if propertyToDisplay: - return ( - FreeCAD.Units.Quantity( - self.editor.tool.FlatRadius, FreeCAD.Units.Length - ) - * 2 - ) - return FreeCAD.Units.parseQuantity(self.form.value_d.text()) / 2 - - def quantityCuttingEdgeAngle(self, propertyToDisplay): - if propertyToDisplay: - return FreeCAD.Units.Quantity( - self.editor.tool.CuttingEdgeAngle, FreeCAD.Units.Angle - ) - return FreeCAD.Units.parseQuantity(self.form.value_a.text()) - - def quantityCuttingEdgeHeight(self, propertyToDisplay): - if propertyToDisplay: - return FreeCAD.Units.Quantity( - self.editor.tool.CuttingEdgeHeight, FreeCAD.Units.Length - ) - return FreeCAD.Units.parseQuantity(self.form.value_H.text()) - - -class ToolEditorEndmill(ToolEditorImage): - """Tool parameter editor for endmills.""" - - def __init__(self, editor): - super(ToolEditorEndmill, self).__init__(editor, "endmill.svg", "da", "S") - - -class ToolEditorReamer(ToolEditorImage): - """Tool parameter editor for reamers.""" - - def __init__(self, editor): - super(ToolEditorReamer, self).__init__(editor, "reamer.svg", "da", "S") - - -class ToolEditorDrill(ToolEditorImage): - """Tool parameter editor for drills.""" - - def __init__(self, editor): - super(ToolEditorDrill, self).__init__(editor, "drill.svg", "dS", "") - - def quantityCuttingEdgeAngle(self, propertyToDisplay): - if propertyToDisplay: - return FreeCAD.Units.Quantity( - self.editor.tool.CuttingEdgeAngle, FreeCAD.Units.Angle - ) - return FreeCAD.Units.parseQuantity(self.form.value_a.text()) - - -class ToolEditorEngrave(ToolEditorImage): - """Tool parameter editor for v-bits.""" - - def __init__(self, editor): - super(ToolEditorEngrave, self).__init__(editor, "v-bit.svg", "", "dS") - - def quantityCuttingEdgeHeight(self, propertyToDisplay): - Path.Log.track() - dr = (self.quantityDiameter(False) - self.quantityFlatRadius(False)) / 2 - da = self.quantityCuttingEdgeAngle(False).Value - return dr / math.tan(math.radians(da) / 2) - - -class ToolEditor: - """UI and controller for editing a Tool. - The controller embeds the UI to the parentWidget which has to have a layout attached to it. - The editor maintains two Tools, self.tool and self.Tool. The former is the one being edited - and always reflects the current state. self.Tool on the other hand is the "official" Tool - which should be used externally. The state is transferred between the two with accept and - reject. - - The editor uses instances of ToolEditorDefault and ToolEditorImage to deal with the changes - of the actual parameters. For any ToolType not mapped in ToolTypeImage the editor uses - an instance of ToolEditorDefault. - """ - - ToolTypeImage = { - "EndMill": ToolEditorEndmill, - "Drill": ToolEditorDrill, - "Engraver": ToolEditorEngrave, - "Reamer": ToolEditorReamer, - } - - def __init__(self, tool, parentWidget, parent=None): - self.Parent = parent - self.Tool = tool - self.tool = tool.copy() - self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolEditor.ui") - - self.form.setParent(parentWidget) - parentWidget.layout().addWidget(self.form) - - for tooltype in Path.Tool.getToolTypes(tool): - self.form.toolType.addItem(tooltype) - for material in Path.Tool.getToolMaterials(tool): - self.form.toolMaterial.addItem(material) - - self.setupToolType(self.tool.ToolType) - - def accept(self): - self.refresh() - self.Tool = self.tool - - def reject(self): - self.tool = self.Tool - - def getType(self, tooltype): - "gets a combobox index number for a given type or vice versa" - toolslist = Path.Tool.getToolTypes(Path.Tool()) - if isinstance(tooltype, str): - if tooltype in toolslist: - return toolslist.index(tooltype) - else: - return 0 - return toolslist[tooltype] - - def getMaterial(self, material): - "gets a combobox index number for a given material or vice versa" - matslist = Path.Tool.getToolMaterials(Path.Tool()) - if isinstance(material, str): - if material in matslist: - return matslist.index(material) - else: - return 0 - return matslist[material] - - def updateUI(self): - Path.Log.track() - self.form.toolName.setText(self.tool.Name) - self.form.toolType.setCurrentIndex(self.getType(self.tool.ToolType)) - self.form.toolMaterial.setCurrentIndex(self.getMaterial(self.tool.Material)) - self.form.toolLengthOffset.setText( - FreeCAD.Units.Quantity( - self.tool.LengthOffset, FreeCAD.Units.Length - ).UserString - ) - - self.editor.updateUI() - - def updateToolType(self): - Path.Log.track() - self.form.blockSignals(True) - self.tool.ToolType = self.getType(self.form.toolType.currentIndex()) - self.setupToolType(self.tool.ToolType) - self.updateUI() - self.form.blockSignals(False) - - def setupToolType(self, tt): - Path.Log.track() - print("Tool type: %s" % (tt)) - if "Undefined" == tt: - tt = Path.Tool.getToolTypes(Path.Tool())[0] - if tt in self.ToolTypeImage: - self.editor = self.ToolTypeImage[tt](self) - else: - Path.Log.debug("weak supported ToolType = %s" % (tt)) - self.editor = ToolEditorDefault(self) - self.editor.setupUI() - - def updateTool(self): - Path.Log.track() - self.tool.Name = str(self.form.toolName.text()) - self.tool.Material = self.getMaterial(self.form.toolMaterial.currentIndex()) - self.tool.LengthOffset = FreeCAD.Units.parseQuantity( - self.form.toolLengthOffset.text() - ) - self.editor.updateTool() - - def refresh(self): - Path.Log.track() - self.form.blockSignals(True) - self.updateTool() - self.updateUI() - self.form.blockSignals(False) - - def setupUI(self): - Path.Log.track() - self.updateUI() - - self.form.toolName.editingFinished.connect(self.refresh) - self.form.toolType.currentIndexChanged.connect(self.updateToolType) diff --git a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py b/src/Mod/Path/PathScripts/PathToolLibraryEditor.py deleted file mode 100644 index f0eb0366bd..0000000000 --- a/src/Mod/Path/PathScripts/PathToolLibraryEditor.py +++ /dev/null @@ -1,515 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2014 sliptonic * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -from __future__ import print_function -from PySide import QtCore, QtGui -from PySide.QtCore import QT_TRANSLATE_NOOP -import FreeCAD -import FreeCADGui -import Path -import Path.Tools.Controller as PathToolController -import Path.Tools.Gui.BitLibraryCmd as PathToolBitLibraryCmd -import PathScripts -import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathToolEdit as PathToolEdit -import PathScripts.PathToolLibraryManager as ToolLibraryManager -import PathScripts.PathUtils as PathUtils - - -if False: - Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) - Path.Log.trackModule(Path.Log.thisModule()) -else: - Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) - -translate = FreeCAD.Qt.translate - - -class EditorPanel: - def __init__(self, job, cb): - self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolLibraryEditor.ui") - self.TLM = ToolLibraryManager.ToolLibraryManager() - listname = self.TLM.getCurrentTableName() - - if listname: - self.loadToolTables() - - self.job = job - self.cb = cb - - def toolEditor(self, tool): - dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgToolEdit.ui") - editor = PathToolEdit.ToolEditor(tool, dialog.toolEditor, dialog) - editor.setupUI() - return editor - - def accept(self): - pass - - def reject(self): - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - - def getFields(self): - pass - - def setFields(self): - pass - - def open(self): - pass - - def getType(self, tooltype): - "gets a combobox index number for a given type or vice versa" - toolslist = Path.Tool.getToolTypes(Path.Tool()) - if isinstance(tooltype, str): - if tooltype in toolslist: - return toolslist.index(tooltype) - else: - return 0 - else: - return toolslist[tooltype] - - def getMaterial(self, material): - """gets a combobox index number for a given material or vice versa""" - matslist = Path.Tool.getToolMaterials(Path.Tool()) - if isinstance(material, str): - if material in matslist: - return matslist.index(material) - else: - return 0 - else: - return matslist[material] - - def addTool(self): - """adds new tool to the current tool table""" - tool = Path.Tool() - editor = self.toolEditor(tool) - - r = editor.Parent.exec_() - if r: - editor.accept() - listname = self.TLM.getCurrentTableName() - self.TLM.addnew(listname, editor.Tool) - self.loadTable(listname) - - def delete(self): - """deletes the selected tool""" - listname = self.TLM.getCurrentTableName() - model = self.form.ToolsList.model() - for i in range(model.rowCount()): - item = model.item(i, 0) - if item.checkState(): - t = model.index(i, 1) - self.TLM.delete(int(t.data()), listname) - self.loadTable(listname) - self.toolSelectionChanged() - - def editTool(self, currItem): - """load the tool edit dialog""" - if not currItem: - currItem = self.form.ToolsList.selectedIndexes()[1] - - row = currItem.row() - value = currItem.sibling(row, 1).data() - listname = self.TLM.getCurrentTableName() - toolnum = int(value) - tool = self.TLM.getTool(listname, toolnum) - editor = self.toolEditor(tool) - - r = editor.Parent.exec_() - if r: - editor.accept() - if self.TLM.updateTool(listname, toolnum, editor.Tool) is True: - self.loadTable(listname) - - def moveUp(self): - """moves a tool to a lower number, if possible""" - item = self.form.ToolsList.selectedIndexes()[1].data() - if item: - number = int(item) - listname = self.TLM.getCurrentTableName() - success, newNum = self.TLM.moveup(number, listname) - if success: - self.loadTable(listname) - self.updateSelection(newNum) - - def moveDown(self): - """moves a tool to a higher number, if possible""" - item = self.form.ToolsList.selectedIndexes()[1].data() - if item: - number = int(item) - listname = self.TLM.getCurrentTableName() - success, newNum = self.TLM.movedown(number, listname) - if success: - self.loadTable(listname) - self.updateSelection(newNum) - - def duplicate(self): - """duplicated the selected tool in the current tool table""" - item = self.form.ToolsList.selectedIndexes()[1].data() - if item: - number = int(item) - listname = self.TLM.getCurrentTableName() - success, newNum = self.TLM.duplicate(number, listname) - if success: - self.loadTable(listname) - self.updateSelection(newNum) - - def updateSelection(self, number): - """update the tool list selection to track moves""" - model = self.form.ToolsList.model() - for i in range(model.rowCount()): - if int(model.index(i, 1).data()) == number: - self.form.ToolsList.selectRow(i) - self.form.ToolsList.model().item(i, 0).setCheckState(QtCore.Qt.Checked) - return - - def importFile(self): - """imports a tooltable from a file""" - filename = QtGui.QFileDialog.getOpenFileName( - self.form, - translate("Path_ToolTable", "Open tooltable"), - None, - "{};;{};;{}".format( - self.TLM.TooltableTypeJSON, - self.TLM.TooltableTypeXML, - self.TLM.TooltableTypeHeekscad, - ), - ) - if filename[0]: - listname = self.TLM.getNextToolTableName() - if self.TLM.read(filename, listname): - self.loadToolTables() - - def exportFile(self): - """export a tooltable to a file""" - filename = QtGui.QFileDialog.getSaveFileName( - self.form, - translate("Path_ToolTable", "Save tooltable"), - None, - "{};;{};;{}".format( - self.TLM.TooltableTypeJSON, - self.TLM.TooltableTypeXML, - self.TLM.TooltableTypeLinuxCNC, - ), - ) - if filename[0]: - listname = self.TLM.getCurrentTableName() - self.TLM.write(filename, listname) - - def toolSelectionChanged(self, index=None): - """updates the ui when tools are selected""" - if index: - self.form.ToolsList.selectRow(index.row()) - - self.form.btnCopyTools.setEnabled(False) - self.form.ButtonDelete.setEnabled(False) - self.form.ButtonUp.setEnabled(False) - self.form.ButtonDown.setEnabled(False) - self.form.ButtonEdit.setEnabled(False) - self.form.ButtonDuplicate.setEnabled(False) - - model = self.form.ToolsList.model() - checkCount = 0 - checkList = [] - for i in range(model.rowCount()): - item = model.item(i, 0) - if item.checkState(): - checkCount += 1 - checkList.append(i) - self.form.btnCopyTools.setEnabled(True) - - # only allow moving or deleting a single tool at a time. - if checkCount == 1: - # make sure the row is highlighted when the check box gets ticked - self.form.ToolsList.selectRow(checkList[0]) - self.form.ButtonDelete.setEnabled(True) - self.form.ButtonUp.setEnabled(True) - self.form.ButtonDown.setEnabled(True) - self.form.ButtonEdit.setEnabled(True) - self.form.ButtonDuplicate.setEnabled(True) - - if len(PathUtils.GetJobs()) == 0: - self.form.btnCopyTools.setEnabled(False) - - def copyTools(self): - """copy selected tool""" - tools = [] - model = self.form.ToolsList.model() - for i in range(model.rowCount()): - item = model.item(i, 0) - if item.checkState(): - item = model.index(i, 1) - tools.append(item.data()) - if len(tools) == 0: - return - - targets = self.TLM.getJobList() - currList = self.TLM.getCurrentTableName() - - for target in targets: - if target == currList: - targets.remove(target) - - if len(targets) == 0: - FreeCAD.Console.PrintWarning("No Path Jobs in current document") - return - elif len(targets) == 1: - targetlist = targets[0] - else: - form = FreeCADGui.PySideUic.loadUi(":/panels/DlgToolCopy.ui") - form.cboTarget.addItems(targets) - r = form.exec_() - if r is False: - return None - else: - targetlist = form.cboTarget.currentText() - - for toolnum in tools: - tool = self.TLM.getTool(currList, int(toolnum)) - Path.Log.debug("tool: {}, toolnum: {}".format(tool, toolnum)) - if self.job: - label = "T{}: {}".format(toolnum, tool.Name) - tc = PathToolController.Create( - label, tool=tool, toolNumber=int(toolnum) - ) - self.job.Proxy.addToolController(tc) - else: - for job in FreeCAD.ActiveDocument.findObjects("Path::Feature"): - if ( - isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) - and job.Label == targetlist - ): - label = "T{}: {}".format(toolnum, tool.Name) - tc = PathToolController.Create( - label, tool=tool, toolNumber=int(toolnum) - ) - job.Proxy.addToolController(tc) - if self.cb: - self.cb() - FreeCAD.ActiveDocument.recompute() - - def tableSelected(self, index): - """loads the tools for the selected tool table""" - name = self.form.TableList.itemWidget( - self.form.TableList.itemFromIndex(index) - ).getTableName() - self.loadTable(name) - - def loadTable(self, name): - """loads the tools for the selected tool table""" - tooldata = self.TLM.getTools(name) - if tooldata: - self.form.ToolsList.setModel(tooldata) - self.form.ToolsList.resizeColumnsToContents() - self.form.ToolsList.horizontalHeader().setResizeMode( - self.form.ToolsList.model().columnCount() - 1, QtGui.QHeaderView.Stretch - ) - self.setCurrentToolTableByName(name) - - def addNewToolTable(self): - """adds new tool to selected tool table""" - name = self.TLM.addNewToolTable() - self.loadToolTables() - self.loadTable(name) - - def loadToolTables(self): - """Load list of available tool tables""" - self.form.TableList.clear() - model = self.form.ToolsList.model() - if model: - model.clear() - if len(self.TLM.getToolTables()) > 0: - for table in self.TLM.getToolTables(): - listWidgetItem = QtGui.QListWidgetItem() - listItem = ToolTableListWidgetItem(self.TLM) - listItem.setTableName(table.Name) - listItem.setIcon(QtGui.QPixmap(":/icons/Path_ToolTable.svg")) - listItem.toolMoved.connect(self.reloadReset) - listWidgetItem.setSizeHint(QtCore.QSize(0, 40)) - self.form.TableList.addItem(listWidgetItem) - self.form.TableList.setItemWidget(listWidgetItem, listItem) - # Load the first tooltable - self.loadTable(self.TLM.getCurrentTableName()) - - def reloadReset(self): - """reloads the current tooltable""" - name = self.TLM.getCurrentTableName() - self.loadTable(name) - - def setCurrentToolTableByName(self, name): - """get the current tool table""" - item = self.getToolTableByName(name) - if item: - self.form.TableList.setCurrentItem(item) - - def getToolTableByName(self, name): - """returns the listWidgetItem for the selected name""" - for i in range(self.form.TableList.count()): - tableName = self.form.TableList.itemWidget( - self.form.TableList.item(i) - ).getTableName() - if tableName == name: - return self.form.TableList.item(i) - return False - - def removeToolTable(self): - """delete the selected tool table""" - self.TLM.deleteToolTable() - self.loadToolTables() - - def renameTable(self): - """provides dialog for new tablename and renames the selected tool table""" - name = self.TLM.getCurrentTableName() - newName, ok = QtGui.QInputDialog.getText( - None, - translate("Path_ToolTable", "Rename Tooltable"), - translate("Path_ToolTable", "Enter Name:"), - QtGui.QLineEdit.Normal, - name, - ) - if ok and newName: - index = self.form.TableList.indexFromItem( - self.getToolTableByName(name) - ).row() - reloadTables = self.TLM.renameToolTable(newName, index) - if reloadTables: - self.loadToolTables() - self.loadTable(newName) - - def getStandardButtons(self): - return int(QtGui.QDialogButtonBox.Ok) - - def setupUi(self): - # Connect Signals and Slots - self.form.ButtonNewTool.clicked.connect(self.addTool) - self.form.ButtonImport.clicked.connect(self.importFile) - self.form.ButtonExport.clicked.connect(self.exportFile) - self.form.ButtonDown.clicked.connect(self.moveDown) - self.form.ButtonUp.clicked.connect(self.moveUp) - self.form.ButtonDelete.clicked.connect(self.delete) - self.form.ButtonEdit.clicked.connect(self.editTool) - self.form.ButtonDuplicate.clicked.connect(self.duplicate) - self.form.btnCopyTools.clicked.connect(self.copyTools) - - self.form.ToolsList.doubleClicked.connect(self.editTool) - self.form.ToolsList.clicked.connect(self.toolSelectionChanged) - - self.form.TableList.clicked.connect(self.tableSelected) - self.form.TableList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.form.TableList.itemChanged.connect(self.renameTable) - - self.form.ButtonAddToolTable.clicked.connect(self.addNewToolTable) - self.form.ButtonAddToolTable.setToolTip( - translate("Path_ToolTable", "Add New Tool Table") - ) - self.form.ButtonRemoveToolTable.clicked.connect(self.removeToolTable) - self.form.ButtonRemoveToolTable.setToolTip( - translate("Path_ToolTable", "Delete Selected Tool Table") - ) - self.form.ButtonRenameToolTable.clicked.connect(self.renameTable) - self.form.ButtonRenameToolTable.setToolTip( - translate("Path_ToolTable", "Rename Selected Tool Table") - ) - - self.setFields() - - -class ToolTableListWidgetItem(QtGui.QWidget): - - toolMoved = QtCore.Signal() - - def __init__(self, TLM): - super(ToolTableListWidgetItem, self).__init__() - - self.tlm = TLM - self.setAcceptDrops(True) - - self.mainLayout = QtGui.QHBoxLayout() - self.iconQLabel = QtGui.QLabel() - self.tableNameLabel = QtGui.QLabel() - self.mainLayout.addWidget(self.iconQLabel, 0) - self.mainLayout.addWidget(self.tableNameLabel, 1) - self.setLayout(self.mainLayout) - - def setTableName(self, text): - self.tableNameLabel.setText(text) - - def getTableName(self): - return self.tableNameLabel.text() - - def setIcon(self, icon): - icon = icon.scaled(24, 24) - self.iconQLabel.setPixmap(icon) - - def dragEnterEvent(self, e): - currentToolTable = self.tlm.getCurrentTableName() - thisToolTable = self.getTableName() - - if not currentToolTable == thisToolTable: - e.accept() - else: - e.ignore() - - def dropEvent(self, e): - selectedTools = e.source().selectedIndexes() - if selectedTools: - toolData = selectedTools[1].data() - - if toolData: - self.tlm.moveToTable(int(toolData), self.getTableName()) - self.toolMoved.emit() - - -class CommandToolLibraryEdit: - def __init__(self): - pass - - def edit(self, job=None, cb=None): - if PathPreferences.toolsUseLegacyTools(): - editor = EditorPanel(job, cb) - editor.setupUi() - editor.form.exec_() - else: - if PathToolBitLibraryCmd.CommandToolBitLibraryLoad.Execute(job): - if cb: - cb() - - def GetResources(self): - return { - "Pixmap": "Path_ToolTable", - "MenuText": QT_TRANSLATE_NOOP("Path_ToolTable", "Tool Manager"), - "Accel": "P, T", - "ToolTip": QT_TRANSLATE_NOOP("Path_ToolTable", "Tool Manager"), - } - - def IsActive(self): - return not FreeCAD.ActiveDocument is None - - def Activated(self): - self.edit() - - -if FreeCAD.GuiUp: - # register the FreeCAD command - FreeCADGui.addCommand("Path_ToolLibraryEdit", CommandToolLibraryEdit()) diff --git a/src/Mod/Path/PathScripts/PathToolLibraryManager.py b/src/Mod/Path/PathScripts/PathToolLibraryManager.py deleted file mode 100644 index c533f30dc9..0000000000 --- a/src/Mod/Path/PathScripts/PathToolLibraryManager.py +++ /dev/null @@ -1,561 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2014 sliptonic * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -from __future__ import print_function - -import FreeCAD -import Path -import PathScripts -import PathScripts.PathUtil as PathUtil -import json -import os -import xml.sax -from PySide import QtGui - -if False: - Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) - Path.Log.trackModule(Path.Log.thisModule()) -else: - Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) - -translate = FreeCAD.Qt.translate - -# Tooltable XML readers -class FreeCADTooltableHandler(xml.sax.ContentHandler): - # http://www.tutorialspoint.com/python/python_xml_processing.htm - - def __init__(self): - xml.sax.ContentHandler.__init__(self) - self.tooltable = None - self.tool = None - self.number = None - - # Call when an element is found - def startElement(self, name, attrs): - if name == "Tooltable": - self.tooltable = Path.Tooltable() - elif name == "Toolslot": - self.number = int(attrs["number"]) - elif name == "Tool": - self.tool = Path.Tool() - self.tool.Name = str(attrs["name"]) - self.tool.ToolType = str(attrs["type"]) - self.tool.Material = str(attrs["mat"]) - # for some reason without the following line I get an error - # print attrs["diameter"] - self.tool.Diameter = float(attrs["diameter"]) - self.tool.LengthOffset = float(attrs["length"]) - self.tool.FlatRadius = float(attrs["flat"]) - self.tool.CornerRadius = float(attrs["corner"]) - self.tool.CuttingEdgeAngle = float(attrs["angle"]) - self.tool.CuttingEdgeHeight = float(attrs["height"]) - - # Call when an elements ends - def endElement(self, name): - if name == "Toolslot": - if self.tooltable and self.tool and self.number: - self.tooltable.setTool(self.number, self.tool) - self.number = None - self.tool = None - - -class HeeksTooltableHandler(xml.sax.ContentHandler): - def __init__(self): - xml.sax.ContentHandler.__init__(self) - self.tooltable = Path.Tooltable() - self.tool = None - self.number = None - - # Call when an element is found - def startElement(self, name, attrs): - if name == "Tool": - self.tool = Path.Tool() - self.number = int(attrs["tool_number"]) - self.tool.Name = str(attrs["title"]) - elif name == "params": - t = str(attrs["type"]) - if t == "drill": - self.tool.ToolType = "Drill" - elif t == "center_drill_bit": - self.tool.ToolType = "CenterDrill" - elif t == "end_mill": - self.tool.ToolType = "EndMill" - elif t == "slot_cutter": - self.tool.ToolType = "SlotCutter" - elif t == "ball_end_mill": - self.tool.ToolType = "BallEndMill" - elif t == "chamfer": - self.tool.ToolType = "Chamfer" - elif t == "engraving_bit": - self.tool.ToolType = "Engraver" - m = str(attrs["material"]) - if m == "0": - self.tool.Material = "HighSpeedSteel" - elif m == "1": - self.tool.Material = "Carbide" - # for some reason without the following line I get an error - # print attrs["diameter"] - self.tool.Diameter = float(attrs["diameter"]) - self.tool.LengthOffset = float(attrs["tool_length_offset"]) - self.tool.FlatRadius = float(attrs["flat_radius"]) - self.tool.CornerRadius = float(attrs["corner_radius"]) - self.tool.CuttingEdgeAngle = float(attrs["cutting_edge_angle"]) - self.tool.CuttingEdgeHeight = float(attrs["cutting_edge_height"]) - - # Call when an elements ends - def endElement(self, name): - if name == "Tool": - if self.tooltable and self.tool and self.number: - self.tooltable.setTool(self.number, self.tool) - self.number = None - self.tool = None - - -class ToolLibraryManager: - """ - The Tool Library is a list of individual tool tables. Each - Tool Table can contain n tools. The tool library will be persisted to user - preferences and all or part of the library can be exported to other formats - """ - - TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.json)") - TooltableTypeXML = translate("PathToolLibraryManager", "Tooltable XML (*.xml)") - TooltableTypeHeekscad = translate( - "PathToolLibraryManager", "HeeksCAD tooltable (*.tooltable)" - ) - TooltableTypeLinuxCNC = translate( - "PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)" - ) - - PreferenceMainLibraryXML = "ToolLibrary" - PreferenceMainLibraryJSON = "ToolLibrary-Main" - - def __init__(self): - self.prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") - self.toolTables = [] - self.currentTableName = None - self.loadToolTables() - - def getToolTables(self): - """Return tool table list""" - return self.toolTables - - def getCurrentTableName(self): - """return the name of the currently loaded tool table""" - return self.currentTableName - - def getCurrentTable(self): - """returns an object of the current tool table""" - return self.getTableFromName(self.currentTableName) - - def getTableFromName(self, name): - """get the tool table object from the name""" - for table in self.toolTables: - if table.Name == name: - return table - - def getNextToolTableName(self, tableName="Tool Table"): - """get a unique name for a new tool table""" - iter = 1 - tempName = tableName[-2:] - - if tempName[0] == "-" and tempName[-1].isdigit(): - tableName = tableName[:-2] - - while any( - table.Name == tableName + "-" + str(iter) for table in self.toolTables - ): - iter += 1 - - return tableName + "-" + str(iter) - - def addNewToolTable(self): - """creates a new tool table""" - tt = Path.Tooltable() - tt.Version = 1 - name = self.getNextToolTableName() - tt.Name = name - self.toolTables.append(tt) - self.saveMainLibrary() - return name - - def deleteToolTable(self): - """deletes the selected tool table""" - if len(self.toolTables): - index = next( - ( - index - for (index, d) in enumerate(self.toolTables) - if d.Name == self.currentTableName - ), - None, - ) - self.toolTables.pop(index) - self.saveMainLibrary() - - def renameToolTable(self, newName, index): - """renames a tool table with the new name""" - currentTableName = self.toolTables[index].Name - if newName == currentTableName: - Path.Log.error(translate("PathToolLibraryManager", "Tool Table Same Name")) - return False - if newName in self.toolTables: - Path.Log.error(translate("PathToolLibraryManager", "Tool Table Name Exists")) - return False - tt = self.getTableFromName(currentTableName) - if tt: - tt.Name = newName - self.saveMainLibrary() - return True - - def templateAttrs(self): - """gets the tool table arributes""" - toolTables = [] - for tt in self.toolTables: - tableData = {} - tableData["Version"] = 1 - tableData["TableName"] = tt.Name - - toolData = {} - for tool in tt.Tools: - toolData[tool] = tt.Tools[tool].templateAttrs() - - tableData["Tools"] = toolData - toolTables.append(tableData) - - return toolTables - - def tooltableFromAttrs(self, stringattrs): - if stringattrs.get("Version") and 1 == int(stringattrs["Version"]): - - tt = Path.Tooltable() - tt.Version = 1 - tt.Name = self.getNextToolTableName() - - if stringattrs.get("Version"): - tt.Version = stringattrs.get("Version") - - if stringattrs.get("TableName"): - tt.Name = stringattrs.get("TableName") - if any(table.Name == tt.Name for table in self.toolTables): - tt.Name = self.getNextToolTableName(tt.Name) - - for key, attrs in PathUtil.keyValueIter(stringattrs["Tools"]): - tool = Path.Tool() - tool.Name = str(attrs["name"]) - tool.ToolType = str(attrs["tooltype"]) - tool.Material = str(attrs["material"]) - tool.Diameter = float(attrs["diameter"]) - tool.LengthOffset = float(attrs["lengthOffset"]) - tool.FlatRadius = float(attrs["flatRadius"]) - tool.CornerRadius = float(attrs["cornerRadius"]) - tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"]) - tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"]) - tt.setTool(int(key), tool) - - return tt - else: - Path.Log.error( - translate( - "PathToolLibraryManager", - "Unsupported Path tooltable template version %s", - ) - % stringattrs.get("Version") - ) - return None - - def loadToolTables(self): - """loads the tool tables from the stored data""" - self.toolTables = [] - self.currentTableName = "" - - def addTable(tt): - if tt: - self.toolTables.append(tt) - else: - Path.Log.error( - translate("PathToolLibraryManager", "Unsupported Path tooltable") - ) - - prefString = self.prefs.GetString(self.PreferenceMainLibraryJSON, "") - - if not prefString: - return - - prefsData = json.loads(prefString) - - if isinstance(prefsData, dict): - tt = self.tooltableFromAttrs(prefsData) - addTable(tt) - - if isinstance(prefsData, list): - for table in prefsData: - tt = self.tooltableFromAttrs(table) - addTable(tt) - - if len(self.toolTables): - self.currentTableName = self.toolTables[0].Name - - def saveMainLibrary(self): - """Persists the permanent library to FreeCAD user preferences""" - tmpstring = json.dumps(self.templateAttrs()) - self.prefs.SetString(self.PreferenceMainLibraryJSON, tmpstring) - self.loadToolTables() - return True - - def getJobList(self): - """Builds the list of all Tool Table lists""" - tablelist = [] - - for o in FreeCAD.ActiveDocument.Objects: - if hasattr(o, "Proxy"): - if isinstance(o.Proxy, PathScripts.PathJob.ObjectJob): - tablelist.append(o.Label) - - return tablelist - - def getTool(self, listname, toolnum): - """gets the tool object""" - tt = self.getTableFromName(listname) - return tt.getTool(toolnum) - - def getTools(self, tablename): - """returns the tool data for a given table""" - tooldata = [] - tableExists = any(table.Name == tablename for table in self.toolTables) - if tableExists: - self.currentTableName = tablename - else: - return None - - tt = self.getTableFromName(tablename) - headers = ["", "Tool Num.", "Name", "Tool Type", "Diameter"] - model = QtGui.QStandardItemModel() - model.setHorizontalHeaderLabels(headers) - - def unitconv(ivalue): - val = FreeCAD.Units.Quantity(ivalue, FreeCAD.Units.Length) - displayed_val = ( - val.UserString - ) # just the displayed value-not the internal one - return displayed_val - - if tt: - if len(tt.Tools) == 0: - tooldata.append([]) - for number, t in PathUtil.keyValueIter(tt.Tools): - - itemcheck = QtGui.QStandardItem() - itemcheck.setCheckable(True) - itemNumber = QtGui.QStandardItem(str(number)) - itemName = QtGui.QStandardItem(t.Name) - itemToolType = QtGui.QStandardItem(t.ToolType) - itemDiameter = QtGui.QStandardItem(unitconv(t.Diameter)) - - row = [itemcheck, itemNumber, itemName, itemToolType, itemDiameter] - model.appendRow(row) - - return model - - # methods for importing and exporting - def read(self, filename, listname): - "imports a tooltable from a file" - - importedTables = [] - - try: - fileExtension = os.path.splitext(filename[0])[1].lower() - xmlHandler = None - if fileExtension == ".tooltable": - xmlHandler = HeeksTooltableHandler() - if fileExtension == ".xml": - xmlHandler = FreeCADTooltableHandler() - - if xmlHandler: - parser = xml.sax.make_parser() - parser.setFeature(xml.sax.handler.feature_namespaces, 0) - parser.setContentHandler(xmlHandler) - parser.parse(PathUtil.toUnicode(filename[0])) - if not xmlHandler.tooltable: - return None - - ht = xmlHandler.tooltable - else: - with open(PathUtil.toUnicode(filename[0]), "rb") as fp: - tableData = json.load(fp) - - if isinstance(tableData, dict): - ht = self.tooltableFromAttrs(tableData) - if ht: - importedTables.append(ht) - - if isinstance(tableData, list): - for table in tableData: - ht = self.tooltableFromAttrs(table) - if ht: - importedTables.append(ht) - - if importedTables: - for tt in importedTables: - self.toolTables.append(tt) - - self.saveMainLibrary() - return True - else: - return False - - except Exception as e: - print("could not parse file", e) - - def write(self, filename, listname): - "exports the tooltable to a file" - tt = self.getTableFromName(listname) - if tt: - try: - - def openFileWithExtension(name, ext): - fext = os.path.splitext(name)[1].lower() - if fext != ext: - name = "{}{}".format(name, ext) - return (open(PathUtil.toUnicode(name), "w"), name) - - if filename[1] == self.TooltableTypeXML: - fp, fname = openFileWithExtension(filename[0], ".xml") - fp.write('\n') - fp.write(tt.Content) - elif filename[1] == self.TooltableTypeLinuxCNC: - fp, fname = openFileWithExtension(filename[0], ".tbl") - for key in tt.Tools: - t = tt.Tools[key] - fp.write( - "T{0} P{0} Y{1} Z{2} A{3} B{4} C{5} U{6} V{7} W{8} D{9} I{10} J{11} Q{12} ;{13}\n".format( - key, - 0, - t.LengthOffset, - 0, - 0, - 0, - 0, - 0, - 0, - t.Diameter, - 0, - 0, - 0, - t.Name, - ) - ) - else: - fp, fname = openFileWithExtension(filename[0], ".json") - json.dump(self.templateAttrs(), fp, sort_keys=True, indent=2) - - fp.close() - print("Written ", PathUtil.toUnicode(fname)) - - except Exception as e: - print("Could not write file:", e) - - def addnew(self, listname, tool, position=None): - "adds a new tool at the end of the table" - tt = self.getTableFromName(listname) - if not tt: - tt = Path.Tooltable() - if position is None: - tt.addTools(tool) - newID = list(tt.Tools)[-1] - else: - tt.setTool(position, tool) - newID = position - - if listname == self.getCurrentTableName(): - self.saveMainLibrary() - return newID - - def updateTool(self, listname, toolnum, tool): - """updates tool data""" - tt = self.getTableFromName(listname) - tt.deleteTool(toolnum) - tt.setTool(toolnum, tool) - if listname == self.getCurrentTableName(): - return self.saveMainLibrary() - return True - - def moveup(self, number, listname): - "moves a tool to a lower number, if possible" - target = number - 1 - if number < 2: - return False, target - target = number - 1 - tt = self.getTableFromName(listname) - t1 = tt.getTool(number).copy() - tt.deleteTool(number) - if target in tt.Tools.keys(): - t2 = tt.getTool(target).copy() - tt.deleteTool(target) - tt.setTool(number, t2) - tt.setTool(target, t1) - if listname == self.getCurrentTableName(): - self.saveMainLibrary() - return True, target - - def movedown(self, number, listname): - "moves a tool to a higher number, if possible" - tt = self.getTableFromName(listname) - target = number + 1 - t1 = tt.getTool(number).copy() - tt.deleteTool(number) - if target in tt.Tools.keys(): - t2 = tt.getTool(target).copy() - tt.deleteTool(target) - tt.setTool(number, t2) - tt.setTool(target, t1) - if listname == self.getCurrentTableName(): - self.saveMainLibrary() - return True, target - - def duplicate(self, number, listname): - """duplicates the selected tool in the selected tool table""" - tt = self.getTableFromName(listname) - tool = tt.getTool(number).copy() - tt.addTools(tool) - - newID = list(tt.Tools)[-1] - - if listname == self.getCurrentTableName(): - self.saveMainLibrary() - return True, newID - - def moveToTable(self, number, listname): - """Moves the tool to selected tool table""" - fromTable = self.getTableFromName(self.getCurrentTableName()) - toTable = self.getTableFromName(listname) - tool = fromTable.getTool(number).copy() - toTable.addTools(tool) - fromTable.deleteTool(number) - - def delete(self, number, listname): - """deletes a tool from the current list""" - tt = self.getTableFromName(listname) - tt.deleteTool(number) - if listname == self.getCurrentTableName(): - self.saveMainLibrary() - return True diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index a6187166fe..34c2f3b19e 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -541,10 +541,6 @@ def guessDepths(objshape, subs=None): def drillTipLength(tool): """returns the length of the drillbit tip.""" - if isinstance(tool, Path.Tool): - Path.Log.error(translate("Path", "Legacy Tools not supported")) - return 0.0 - if not hasattr(tool, "TipAngle"): Path.Log.error(translate("Path", "Selected tool is not a drill")) return 0.0 diff --git a/src/Mod/Path/PathSimulator/App/PathSimPyImp.cpp b/src/Mod/Path/PathSimulator/App/PathSimPyImp.cpp index 5809ffe250..9d1cc842f0 100644 --- a/src/Mod/Path/PathSimulator/App/PathSimPyImp.cpp +++ b/src/Mod/Path/PathSimulator/App/PathSimPyImp.cpp @@ -22,7 +22,6 @@ #include "PreCompiled.h" -#include #include #include #include diff --git a/src/Mod/Path/PathTests/TestPathCore.py b/src/Mod/Path/PathTests/TestPathCore.py index e27cb26a55..fdcc5d913e 100644 --- a/src/Mod/Path/PathTests/TestPathCore.py +++ b/src/Mod/Path/PathTests/TestPathCore.py @@ -130,40 +130,6 @@ G0 Z0.500000 p.setFromGCode(lines) self.assertEqual(p.toGCode(), output) - def test20(self): - """Test Path Tool and ToolTable object core functionality""" - - t1 = Path.Tool() - self.assertIsInstance(t1, Path.Tool) - - t1.Name = "12.7mm Drill Bit" - self.assertEqual(t1.Name, "12.7mm Drill Bit") - self.assertEqual(t1.ToolType, "Undefined") - t1.ToolType = "Drill" - self.assertEqual(t1.ToolType, "Drill") - t1.Diameter = 12.7 - t1.LengthOffset = 127 - t1.CuttingEdgeAngle = 59 - t1.CuttingEdgeHeight = 50.8 - - self.assertEqual(t1.Diameter, 12.7) - self.assertEqual(t1.LengthOffset, 127) - self.assertEqual(t1.CuttingEdgeAngle, 59) - self.assertEqual(t1.CuttingEdgeHeight, 50.8) - - t2 = Path.Tool("my other tool", tooltype="EndMill", diameter=10) - table = Path.Tooltable() - self.assertIsInstance(table, Path.Tooltable) - table.addTools(t1) - table.addTools(t2) - - self.assertEqual(len(table.Tools), 2) - # gcc7 build needs some special treatment (makes 1L out of a 1) ... - if str(table.Tools) != "{1L: Tool 12.7mm Drill Bit, 2L: Tool my other tool}": - self.assertEqual( - str(table.Tools), "{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}" - ) - def test50(self): """Test Path.Length calculation""" commands = [] diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 6da805f125..78dd2f776d 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -22,16 +22,23 @@ import Path import Path.Op.Deburr as PathDeburr +import Path.Tools.Bit as PathToolBit import PathTests.PathTestUtils as PathTestUtils Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) # Path.Log.trackModule(Path.Log.thisModule()) +class MockToolBit(object): + def __init__(self, name="t1", diameter=5.0): + self.Diameter = diameter + self.FlatRadius = 0 + self.CuttingEdgeAngle = 60 + class TestPathDeburr(PathTestUtils.PathTestBase): def test00(self): """Verify chamfer depth and offset for an end mill.""" - tool = Path.Tool() + tool = MockToolBit() tool.Diameter = 20 tool.FlatRadius = 0 tool.CuttingEdgeAngle = 180 @@ -51,7 +58,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): def test01(self): """Verify chamfer depth and offset for a 90 deg v-bit.""" - tool = Path.Tool() + tool = MockToolBit() tool.FlatRadius = 0 tool.CuttingEdgeAngle = 90 @@ -67,7 +74,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): def test02(self): """Verify chamfer depth and offset for a 90 deg v-bit with non 0 flat radius.""" - tool = Path.Tool() + tool = MockToolBit() tool.FlatRadius = 0.3 tool.CuttingEdgeAngle = 90 @@ -83,7 +90,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): def test03(self): """Verify chamfer depth and offset for a 60 deg v-bit with non 0 flat radius.""" - tool = Path.Tool() + tool = MockToolBit() tool.FlatRadius = 10 tool.CuttingEdgeAngle = 60 diff --git a/src/Mod/Path/PathTests/TestPathHelpers.py b/src/Mod/Path/PathTests/TestPathHelpers.py index a0371ceb86..3e2b62221d 100644 --- a/src/Mod/Path/PathTests/TestPathHelpers.py +++ b/src/Mod/Path/PathTests/TestPathHelpers.py @@ -23,6 +23,7 @@ import FreeCAD import Part import Path +import Path.Tools.Bit as PathToolBit import Path.Tools.Controller as PathToolController import PathFeedRate import PathMachineState @@ -32,6 +33,15 @@ import PathScripts.PathUtils as PathUtils from PathTests.PathTestUtils import PathTestBase +def createTool(name="t1", diameter=1.75): + attrs = { + "shape": None, + "name": name, + "parameter": {"Diameter": diameter}, + "attribute": [], + } + return PathToolBit.Factory.CreateFromAttrs(attrs, name) + class TestPathHelpers(PathTestBase): def setUp(self): self.doc = FreeCAD.newDocument("TestPathUtils") @@ -48,7 +58,7 @@ class TestPathHelpers(PathTestBase): def test00(self): """Test that FeedRate Helper populates horiz and vert feed rate based on TC""" - t = Path.Tool("test", "5.0") + t = createTool("test", 5.0) tc = PathToolController.Create("TC0", t) tc.VertRapid = 5 tc.HorizRapid = 10 diff --git a/src/Mod/Path/PathTests/TestPathTool.py b/src/Mod/Path/PathTests/TestPathTool.py deleted file mode 100644 index 130f16e473..0000000000 --- a/src/Mod/Path/PathTests/TestPathTool.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * 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 - -from PathTests.PathTestUtils import PathTestBase - - -class TestPathTool(PathTestBase): - def test00(self): - """Verify templateAttrs""" - - name = "tool 1" - mat = "Carbide" - typ = "EndMill" - dia = 1.7 - flat = 7.2 - offset = 3.2 - corner = 4 - height = 45.3 - angle = 118 - - tool = Path.Tool() - tool.Name = name - tool.ToolType = typ - tool.Material = mat - tool.Diameter = dia - tool.LengthOffset = offset - tool.FlatRadius = flat - tool.CornerRadius = corner - tool.CuttingEdgeAngle = angle - tool.CuttingEdgeHeight = height - - attrs = tool.templateAttrs() - self.assertEqual(attrs["name"], name) - self.assertEqual(attrs["diameter"], dia) - self.assertEqual(attrs["material"], mat) - self.assertEqual(attrs["tooltype"], typ) - self.assertEqual(attrs["lengthOffset"], offset) - self.assertEqual(attrs["flatRadius"], flat) - self.assertEqual(attrs["cornerRadius"], corner) - self.assertEqual(attrs["cuttingEdgeAngle"], angle) - self.assertEqual(attrs["cuttingEdgeHeight"], height) - return tool - - def test01(self): - """Verify template roundtrip""" - - t0 = self.test00() - t1 = Path.Tool() - t1.setFromTemplate(t0.templateAttrs()) - - self.assertEqual(t0.Name, t1.Name) - self.assertEqual(t0.ToolType, t1.ToolType) - self.assertEqual(t0.Material, t1.Material) - self.assertEqual(t0.Diameter, t1.Diameter) - self.assertEqual(t0.LengthOffset, t1.LengthOffset) - self.assertEqual(t0.FlatRadius, t1.FlatRadius) - self.assertEqual(t0.CornerRadius, t1.CornerRadius) - self.assertEqual(t0.CuttingEdgeAngle, t1.CuttingEdgeAngle) - self.assertEqual(t0.CuttingEdgeHeight, t1.CuttingEdgeHeight) - - def test02(self): - """Verify template dictionary construction""" - - t0 = self.test00() - t1 = Path.Tool(t0.templateAttrs()) - - self.assertEqual(t0.Name, t1.Name) - self.assertEqual(t0.ToolType, t1.ToolType) - self.assertEqual(t0.Material, t1.Material) - self.assertEqual(t0.Diameter, t1.Diameter) - self.assertEqual(t0.LengthOffset, t1.LengthOffset) - self.assertEqual(t0.FlatRadius, t1.FlatRadius) - self.assertEqual(t0.CornerRadius, t1.CornerRadius) - self.assertEqual(t0.CuttingEdgeAngle, t1.CuttingEdgeAngle) - self.assertEqual(t0.CuttingEdgeHeight, t1.CuttingEdgeHeight) diff --git a/src/Mod/Path/PathTests/TestPathToolController.py b/src/Mod/Path/PathTests/TestPathToolController.py index eb1dbab7f9..a7c4a90c12 100644 --- a/src/Mod/Path/PathTests/TestPathToolController.py +++ b/src/Mod/Path/PathTests/TestPathToolController.py @@ -37,8 +37,6 @@ class TestPathToolController(PathTestBase): FreeCAD.closeDocument(self.doc.Name) def createTool(self, name="t1", diameter=1.75): - if PathPreferences.toolsUseLegacyTools(): - return Path.Tool(name=name, diameter=diameter) attrs = { "shape": None, "name": name, @@ -73,10 +71,7 @@ class TestPathToolController(PathTestBase): self.assertEqual(attrs["hrapid"], "28.0 mm/s") self.assertEqual(attrs["dir"], "Reverse") self.assertEqual(attrs["speed"], 12000) - if PathPreferences.toolsUseLegacyTools(): - self.assertEqual(attrs["tool"], t.templateAttrs()) - else: - self.assertEqual(attrs["tool"], t.Proxy.templateAttrs(t)) + self.assertEqual(attrs["tool"], t.Proxy.templateAttrs(t)) return tc diff --git a/src/Mod/Path/PathTests/TestPathTooltable.py b/src/Mod/Path/PathTests/TestPathTooltable.py deleted file mode 100644 index 9752bc6263..0000000000 --- a/src/Mod/Path/PathTests/TestPathTooltable.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * 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 - -from PathTests.PathTestUtils import PathTestBase - - -class TestPathTooltable(PathTestBase): - def test00(self): - """Verify templateAttrs""" - - t = Path.Tool(name="t", diameter=1.2) - u = Path.Tool(name="u", diameter=3.4) - v = Path.Tool(name="v", diameter=5.6) - - tt = Path.Tooltable() - tt.setTool(3, t) - tt.setTool(1, u) - tt.addTools(v) - - attrs = tt.templateAttrs() - self.assertEqual(3, len(attrs)) - self.assertTrue(1 in attrs) - self.assertFalse(2 in attrs) - self.assertTrue(3 in attrs) - self.assertTrue(4 in attrs) - - self.assertEqual(attrs[1]["name"], "u") - self.assertEqual(attrs[1]["diameter"], 3.4) - self.assertEqual(attrs[3]["name"], "t") - self.assertEqual(attrs[3]["diameter"], 1.2) - self.assertEqual(attrs[4]["name"], "v") - self.assertEqual(attrs[4]["diameter"], 5.6) - return tt - - def test01(self): - """Verify setFromTemplate roundtrip.""" - tt = self.test00() - uu = Path.Tooltable() - uu.setFromTemplate(tt.templateAttrs()) - - self.assertEqual(tt.Content, uu.Content) - - def test02(self): - """Verify template constructor.""" - tt = self.test00() - uu = Path.Tooltable(tt.templateAttrs()) - self.assertEqual(tt.Content, uu.Content) diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 84676268fa..28e76ad1a0 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -50,11 +50,9 @@ from PathTests.TestPathSetupSheet import TestPathSetupSheet from PathTests.TestPathStock import TestPathStock from PathTests.TestPathThreadMilling import TestPathThreadMilling from PathTests.TestPathThreadMillingGenerator import TestPathThreadMillingGenerator -from PathTests.TestPathTool import TestPathTool from PathTests.TestPathToolBit import TestPathToolBit from PathTests.TestPathToolChangeGenerator import TestPathToolChangeGenerator from PathTests.TestPathToolController import TestPathToolController -from PathTests.TestPathTooltable import TestPathTooltable from PathTests.TestPathUtil import TestPathUtil from PathTests.TestPathVcarve import TestPathVcarve from PathTests.TestPathVoronoi import TestPathVoronoi @@ -94,11 +92,9 @@ False if TestPathSetupSheet.__name__ else True False if TestPathStock.__name__ else True False if TestPathThreadMilling.__name__ else True False if TestPathThreadMillingGenerator.__name__ else True -False if TestPathTool.__name__ else True False if TestPathToolBit.__name__ else True False if TestPathToolChangeGenerator.__name__ else True False if TestPathToolController.__name__ else True -False if TestPathTooltable.__name__ else True False if TestPathUtil.__name__ else True False if TestPathVcarve.__name__ else True False if TestPathVoronoi.__name__ else True From 9ab28b4ec22a95e93b66ef82c51111f84691e94f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 12 Aug 2022 23:47:40 -0700 Subject: [PATCH 13/33] Renamed Path.Tools module to Path.Tool --- src/Mod/Path/CMakeLists.txt | 24 +++++++++---------- src/Mod/Path/InitGui.py | 6 ++--- src/Mod/Path/Path/Post/UtilsExport.py | 3 +-- .../Path/Path/Post/scripts/centroid_post.py | 4 ++-- .../Path/Path/Post/scripts/heidenhain_post.py | 4 ++-- src/Mod/Path/Path/Post/scripts/uccnc_post.py | 4 ++-- src/Mod/Path/Path/{Tools => Tool}/Bit.py | 0 .../Path/Path/{Tools => Tool}/Controller.py | 6 ++--- src/Mod/Path/Path/{Tools => Tool}/Gui/Bit.py | 4 ++-- .../Path/Path/{Tools => Tool}/Gui/BitCmd.py | 10 ++++---- .../Path/Path/{Tools => Tool}/Gui/BitEdit.py | 0 .../Path/{Tools => Tool}/Gui/BitLibrary.py | 10 ++++---- .../Path/{Tools => Tool}/Gui/BitLibraryCmd.py | 4 ++-- .../Path/{Tools => Tool}/Gui/Controller.py | 4 ++-- .../Path/Path/{Tools => Tool}/Gui/__init__.py | 0 src/Mod/Path/Path/{Tools => Tool}/__init__.py | 0 src/Mod/Path/PathScripts/PathGuiInit.py | 4 ++-- src/Mod/Path/PathScripts/PathJob.py | 2 +- src/Mod/Path/PathScripts/PathJobGui.py | 4 ++-- src/Mod/Path/PathScripts/PathUtilsGui.py | 2 +- src/Mod/Path/PathTests/TestPathDeburr.py | 2 +- src/Mod/Path/PathTests/TestPathHelpers.py | 4 ++-- src/Mod/Path/PathTests/TestPathToolBit.py | 2 +- .../Path/PathTests/TestPathToolController.py | 4 ++-- src/Mod/Path/PathTests/TestPathVcarve.py | 2 +- 25 files changed, 54 insertions(+), 55 deletions(-) rename src/Mod/Path/Path/{Tools => Tool}/Bit.py (100%) rename src/Mod/Path/Path/{Tools => Tool}/Controller.py (98%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/Bit.py (99%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/BitCmd.py (96%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/BitEdit.py (100%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/BitLibrary.py (99%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/BitLibraryCmd.py (96%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/Controller.py (99%) rename src/Mod/Path/Path/{Tools => Tool}/Gui/__init__.py (100%) rename src/Mod/Path/Path/{Tools => Tool}/__init__.py (100%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 6eed03090e..6ac2ad6507 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -52,19 +52,19 @@ SET(PathPythonDressupGui_SRCS ) SET(PathPythonTools_SRCS - Path/Tools/__init__.py - Path/Tools/Bit.py - Path/Tools/Controller.py + Path/Tool/__init__.py + Path/Tool/Bit.py + Path/Tool/Controller.py ) SET(PathPythonToolsGui_SRCS - Path/Tools/Gui/__init__.py - Path/Tools/Gui/Bit.py - Path/Tools/Gui/BitCmd.py - Path/Tools/Gui/BitEdit.py - Path/Tools/Gui/BitLibraryCmd.py - Path/Tools/Gui/BitLibrary.py - Path/Tools/Gui/Controller.py + Path/Tool/Gui/__init__.py + Path/Tool/Gui/Bit.py + Path/Tool/Gui/BitCmd.py + Path/Tool/Gui/BitEdit.py + Path/Tool/Gui/BitLibraryCmd.py + Path/Tool/Gui/BitLibrary.py + Path/Tool/Gui/Controller.py ) SET(PathPythonPost_SRCS @@ -420,14 +420,14 @@ INSTALL( FILES ${PathPythonTools_SRCS} DESTINATION - Mod/Path/Path/Tools + Mod/Path/Path/Tool ) INSTALL( FILES ${PathPythonToolsGui_SRCS} DESTINATION - Mod/Path/Path/Tools/Gui + Mod/Path/Path/Tool/Gui ) INSTALL( diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 87f8ef2536..4d9fb22440 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -83,8 +83,8 @@ class PathWorkbench(Workbench): from PathScripts import PathGuiInit from PathScripts import PathJobCmd - from Path.Tools.Gui import BitCmd as PathToolBitCmd - from Path.Tools.Gui import BitLibraryCmd as PathToolBitLibraryCmd + from Path.Tool.Gui import BitCmd as PathToolBitCmd + from Path.Tool.Gui import BitLibraryCmd as PathToolBitLibraryCmd from PySide.QtCore import QT_TRANSLATE_NOOP @@ -338,7 +338,7 @@ class PathWorkbench(Workbench): for cmd in self.dressupcmds: self.appendContextMenu("", [cmd]) menuAppended = True - if isinstance(obj.Proxy, Path.Tools.Bit.ToolBit): + if isinstance(obj.Proxy, Path.Tool.Bit.ToolBit): self.appendContextMenu("", ["Path_ToolBitSave", "Path_ToolBitSaveAs"]) menuAppended = True if menuAppended: diff --git a/src/Mod/Path/Path/Post/UtilsExport.py b/src/Mod/Path/Path/Post/UtilsExport.py index 64abafbf2c..aa525aac18 100644 --- a/src/Mod/Path/Path/Post/UtilsExport.py +++ b/src/Mod/Path/Path/Post/UtilsExport.py @@ -33,8 +33,7 @@ import os import FreeCAD import Path.Post.Utils as PostUtils import Path.Post.UtilsParse as PostUtilsParse - -import Path.Tools.Controller as PathToolController +import Path.Tool.Controller as PathToolController # to distinguish python built-in open function from the one declared below diff --git a/src/Mod/Path/Path/Post/scripts/centroid_post.py b/src/Mod/Path/Path/Post/scripts/centroid_post.py index 6608dcf168..b61899da5f 100644 --- a/src/Mod/Path/Path/Post/scripts/centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/centroid_post.py @@ -35,7 +35,7 @@ TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to take a pseudo-gcode fragment outputted by a Path object, and output real GCode suitable for a centroid 3 axis mill. This postprocessor, once placed -in the appropriate Path/Tools folder, can be used directly from inside +in the appropriate Path/Tool folder, can be used directly from inside FreeCAD, via the GUI importer or via python scripts with: import centroid_post @@ -188,7 +188,7 @@ def export(objectslist, filename, argstring): if OUTPUT_COMMENTS: for item in objectslist: if hasattr(item, "Proxy") and isinstance( - item.Proxy, Path.Tools.Controller.ToolController + item.Proxy, Path.Tool.Controller.ToolController ): gcode += ";T{}={}\n".format(item.ToolNumber, item.Name) gcode += linenumber() + ";begin preamble\n" diff --git a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py index e42bda59f1..418db4ea64 100644 --- a/src/Mod/Path/Path/Post/scripts/heidenhain_post.py +++ b/src/Mod/Path/Path/Post/scripts/heidenhain_post.py @@ -187,7 +187,7 @@ TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to take a pseudo-gcode fragment outputted by a Path object, and output real GCode suitable for a heidenhain 3 axis mill. This postprocessor, once placed -in the appropriate Path/Tools folder, can be used directly from inside +in the appropriate Path/Tool folder, can be used directly from inside FreeCAD, via the GUI importer or via python scripts with: import heidenhain_post @@ -344,7 +344,7 @@ def export(objectslist, filename, argstring): LBLIZE_STAUS = False # useful to get idea of object kind - if isinstance(obj.Proxy, Path.Tools.Controller.ToolController): + if isinstance(obj.Proxy, Path.Tool.Controller.ToolController): Object_Kind = "TOOL" # like we go to change tool position MACHINE_LAST_POSITION["X"] = 99999 diff --git a/src/Mod/Path/Path/Post/scripts/uccnc_post.py b/src/Mod/Path/Path/Post/scripts/uccnc_post.py index 1185a710f3..3a70425294 100644 --- a/src/Mod/Path/Path/Post/scripts/uccnc_post.py +++ b/src/Mod/Path/Path/Post/scripts/uccnc_post.py @@ -46,7 +46,7 @@ TOOLTIP = """ Post processor for UC-CNC. This is a postprocessor file for the Path workbench. It is used to take a pseudo-gcode fragment outputted by a Path object, and output real GCode. This postprocessor, once placed in the appropriate -Path/Tools folder, can be used directly from inside FreeCAD, +Path/Tool folder, can be used directly from inside FreeCAD, via the GUI importer or via python scripts with: import UCCNC_post @@ -453,7 +453,7 @@ def export(objectslist, filename, argstring): if OUTPUT_COMMENTS: gcode += append("(preamble: begin)\n") # for obj in objectslist: - # if isinstance(obj.Proxy, Path.Tools.Controller.ToolController): + # if isinstance(obj.Proxy, Path.Tool.Controller.ToolController): # gcode += append("(T{}={})\n".format(obj.ToolNumber, item.Name)) # error: global name 'PathScripts' is not defined for line in PREAMBLE.splitlines(False): diff --git a/src/Mod/Path/Path/Tools/Bit.py b/src/Mod/Path/Path/Tool/Bit.py similarity index 100% rename from src/Mod/Path/Path/Tools/Bit.py rename to src/Mod/Path/Path/Tool/Bit.py diff --git a/src/Mod/Path/Path/Tools/Controller.py b/src/Mod/Path/Path/Tool/Controller.py similarity index 98% rename from src/Mod/Path/Path/Tools/Controller.py rename to src/Mod/Path/Path/Tool/Controller.py index 90fc86898f..27ea829d06 100644 --- a/src/Mod/Path/Path/Tools/Controller.py +++ b/src/Mod/Path/Path/Tool/Controller.py @@ -25,7 +25,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import Path.Tools.Bit as PathToolBit +import Path.Tool.Bit as PathToolBit import PathScripts.PathPreferences as PathPreferences from Generators import toolchange_generator as toolchange_generator from Generators.toolchange_generator import SpindleDirection @@ -341,6 +341,6 @@ def FromTemplate(template, assignViewProvider=True): if FreeCAD.GuiUp: # need ViewProvider class in this file to support loading of old files - from Path.Tools.Gui.Controller import ViewProvider + from Path.Tool.Gui.Controller import ViewProvider -FreeCAD.Console.PrintLog("Loading Path.Tools.Gui.Controller... done\n") +FreeCAD.Console.PrintLog("Loading Path.Tool.Gui.Controller... done\n") diff --git a/src/Mod/Path/Path/Tools/Gui/Bit.py b/src/Mod/Path/Path/Tool/Gui/Bit.py similarity index 99% rename from src/Mod/Path/Path/Tools/Gui/Bit.py rename to src/Mod/Path/Path/Tool/Gui/Bit.py index 3bb33f7dbb..dd19c85ae3 100644 --- a/src/Mod/Path/Path/Tools/Gui/Bit.py +++ b/src/Mod/Path/Path/Tool/Gui/Bit.py @@ -25,8 +25,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path -import Path.Tools.Bit as PathToolBit -import Path.Tools.Gui.BitEdit as PathToolBitEdit +import Path.Tool.Bit as PathToolBit +import Path.Tool.Gui.BitEdit as PathToolBitEdit import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathPreferences as PathPreferences import os diff --git a/src/Mod/Path/Path/Tools/Gui/BitCmd.py b/src/Mod/Path/Path/Tool/Gui/BitCmd.py similarity index 96% rename from src/Mod/Path/Path/Tools/Gui/BitCmd.py rename to src/Mod/Path/Path/Tool/Gui/BitCmd.py index 9b6934a268..61ceda5093 100644 --- a/src/Mod/Path/Path/Tools/Gui/BitCmd.py +++ b/src/Mod/Path/Path/Tool/Gui/BitCmd.py @@ -23,7 +23,7 @@ import FreeCAD import FreeCADGui import Path -import Path.Tools +import Path.Tool import os from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP @@ -56,7 +56,7 @@ class CommandToolBitCreate: return FreeCAD.ActiveDocument is not None def Activated(self): - obj = Path.Tools.Bit.Factory.Create() + obj = Path.Tool.Bit.Factory.Create() obj.ViewObject.Proxy.setCreate(obj.ViewObject) @@ -84,7 +84,7 @@ class CommandToolBitSave: def selectedTool(self): sel = FreeCADGui.Selection.getSelectionEx() if 1 == len(sel) and isinstance( - sel[0].Object.Proxy, Path.Tools.Bit.ToolBit + sel[0].Object.Proxy, Path.Tool.Bit.ToolBit ): return sel[0].Object return None @@ -146,7 +146,7 @@ class CommandToolBitLoad: def selectedTool(self): sel = FreeCADGui.Selection.getSelectionEx() if 1 == len(sel) and isinstance( - sel[0].Object.Proxy, Path.Tools.Bit.ToolBit + sel[0].Object.Proxy, Path.Tool.Bit.ToolBit ): return sel[0].Object return None @@ -155,7 +155,7 @@ class CommandToolBitLoad: return FreeCAD.ActiveDocument is not None def Activated(self): - if Path.Tools.Bit.Gui.LoadTools(): + if Path.Tool.Bit.Gui.LoadTools(): FreeCAD.ActiveDocument.recompute() diff --git a/src/Mod/Path/Path/Tools/Gui/BitEdit.py b/src/Mod/Path/Path/Tool/Gui/BitEdit.py similarity index 100% rename from src/Mod/Path/Path/Tools/Gui/BitEdit.py rename to src/Mod/Path/Path/Tool/Gui/BitEdit.py diff --git a/src/Mod/Path/Path/Tools/Gui/BitLibrary.py b/src/Mod/Path/Path/Tool/Gui/BitLibrary.py similarity index 99% rename from src/Mod/Path/Path/Tools/Gui/BitLibrary.py rename to src/Mod/Path/Path/Tool/Gui/BitLibrary.py index 1fc3a9eff6..2c0a4f1442 100644 --- a/src/Mod/Path/Path/Tools/Gui/BitLibrary.py +++ b/src/Mod/Path/Path/Tool/Gui/BitLibrary.py @@ -25,10 +25,10 @@ import FreeCAD import FreeCADGui import Path -import Path.Tools.Bit as PathToolBit -import Path.Tools.Gui.Bit as PathToolBitGui -import Path.Tools.Gui.BitEdit as PathToolBitEdit -import Path.Tools.Gui.Controller as PathToolControllerGui +import Path.Tool.Bit as PathToolBit +import Path.Tool.Gui.Bit as PathToolBitGui +import Path.Tool.Gui.BitEdit as PathToolBitEdit +import Path.Tool.Gui.Controller as PathToolControllerGui import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtilsGui as PathUtilsGui @@ -97,7 +97,7 @@ def checkWorkingDir(): PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) Path.Log.debug("setting workingdir to: {}".format(workingdir)) - # Copy only files of default Path\Tools folder to working directory (targeting the README.md help file) + # Copy only files of default Path/Tool folder to working directory (targeting the README.md help file) src_toolfiles = os.listdir(defaultdir) for file_name in src_toolfiles: if file_name in ["README.md"]: diff --git a/src/Mod/Path/Path/Tools/Gui/BitLibraryCmd.py b/src/Mod/Path/Path/Tool/Gui/BitLibraryCmd.py similarity index 96% rename from src/Mod/Path/Path/Tools/Gui/BitLibraryCmd.py rename to src/Mod/Path/Path/Tool/Gui/BitLibraryCmd.py index a27f388432..d141894220 100644 --- a/src/Mod/Path/Path/Tools/Gui/BitLibraryCmd.py +++ b/src/Mod/Path/Path/Tool/Gui/BitLibraryCmd.py @@ -55,7 +55,7 @@ class CommandToolBitSelectorOpen: return FreeCAD.ActiveDocument is not None def Activated(self): - import Path.Tools.Gui.BitLibrary as PathToolBitLibraryGui + import Path.Tool.Gui.BitLibrary as PathToolBitLibraryGui dock = PathToolBitLibraryGui.ToolBitSelector() dock.open() @@ -85,7 +85,7 @@ class CommandToolBitLibraryOpen: return FreeCAD.ActiveDocument is not None def Activated(self): - import Path.Tools.Gui.BitLibrary as PathToolBitLibraryGui + import Path.Tool.Gui.BitLibrary as PathToolBitLibraryGui library = PathToolBitLibraryGui.ToolBitLibrary() diff --git a/src/Mod/Path/Path/Tools/Gui/Controller.py b/src/Mod/Path/Path/Tool/Gui/Controller.py similarity index 99% rename from src/Mod/Path/Path/Tools/Gui/Controller.py rename to src/Mod/Path/Path/Tool/Gui/Controller.py index 0f023a1dfe..7f2e9f7bcc 100644 --- a/src/Mod/Path/Path/Tools/Gui/Controller.py +++ b/src/Mod/Path/Path/Tool/Gui/Controller.py @@ -25,8 +25,8 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path -import Path.Tools.Controller as PathToolController -import Path.Tools.Gui.Bit as PathToolBitGui +import Path.Tool.Controller as PathToolController +import Path.Tool.Gui.Bit as PathToolBitGui import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathUtil as PathUtil diff --git a/src/Mod/Path/Path/Tools/Gui/__init__.py b/src/Mod/Path/Path/Tool/Gui/__init__.py similarity index 100% rename from src/Mod/Path/Path/Tools/Gui/__init__.py rename to src/Mod/Path/Path/Tool/Gui/__init__.py diff --git a/src/Mod/Path/Path/Tools/__init__.py b/src/Mod/Path/Path/Tool/__init__.py similarity index 100% rename from src/Mod/Path/Path/Tools/__init__.py rename to src/Mod/Path/Path/Tool/__init__.py diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 169754331e..cf2711f110 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -61,8 +61,8 @@ def Startup(): from Path.Op.Gui import ThreadMilling from Path.Op.Gui import Vcarve from Path.Post import Command - from Path.Tools import Controller - from Path.Tools.Gui import Controller + from Path.Tool import Controller + from Path.Tool.Gui import Controller from PathScripts import PathArray from PathScripts import PathComment from PathScripts import PathFixture diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 5ef9c94ebe..3060247e23 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -25,7 +25,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path from Path.Post.Processor import PostProcessor -import Path.Tools.Controller as PathToolController +import Path.Tool.Controller as PathToolController import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index d9d74fdbda..e172646a7d 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -28,8 +28,8 @@ from pivy import coin import FreeCAD import FreeCADGui import Path -import Path.Tools.Gui.Bit as PathToolBitGui -import Path.Tools.Gui.Controller as PathToolControllerGui +import Path.Tool.Gui.Bit as PathToolBitGui +import Path.Tool.Gui.Controller as PathToolControllerGui import PathScripts.PathGeom as PathGeom import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathJob as PathJob diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index 5fedf7caee..dd758d1972 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -23,7 +23,7 @@ import FreeCADGui import FreeCAD import Path -import Path.Tools.Controller as PathToolController +import Path.Tool.Controller as PathToolController import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 78dd2f776d..716f1feb53 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -22,7 +22,7 @@ import Path import Path.Op.Deburr as PathDeburr -import Path.Tools.Bit as PathToolBit +import Path.Tool.Bit as PathToolBit import PathTests.PathTestUtils as PathTestUtils Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestPathHelpers.py b/src/Mod/Path/PathTests/TestPathHelpers.py index 3e2b62221d..50835bb47d 100644 --- a/src/Mod/Path/PathTests/TestPathHelpers.py +++ b/src/Mod/Path/PathTests/TestPathHelpers.py @@ -23,8 +23,8 @@ import FreeCAD import Part import Path -import Path.Tools.Bit as PathToolBit -import Path.Tools.Controller as PathToolController +import Path.Tool.Bit as PathToolBit +import Path.Tool.Controller as PathToolController import PathFeedRate import PathMachineState import PathScripts.PathGeom as PathGeom diff --git a/src/Mod/Path/PathTests/TestPathToolBit.py b/src/Mod/Path/PathTests/TestPathToolBit.py index c58d6b04ea..1b477d3f7e 100644 --- a/src/Mod/Path/PathTests/TestPathToolBit.py +++ b/src/Mod/Path/PathTests/TestPathToolBit.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -import Path.Tools.Bit as PathToolBit +import Path.Tool.Bit as PathToolBit import PathTests.PathTestUtils as PathTestUtils import glob import os diff --git a/src/Mod/Path/PathTests/TestPathToolController.py b/src/Mod/Path/PathTests/TestPathToolController.py index a7c4a90c12..de008da8dd 100644 --- a/src/Mod/Path/PathTests/TestPathToolController.py +++ b/src/Mod/Path/PathTests/TestPathToolController.py @@ -22,8 +22,8 @@ import FreeCAD import Path -import Path.Tools.Bit as PathToolBit -import Path.Tools.Controller as PathToolController +import Path.Tool.Bit as PathToolBit +import Path.Tool.Controller as PathToolController import PathScripts.PathPreferences as PathPreferences from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathVcarve.py b/src/Mod/Path/PathTests/TestPathVcarve.py index 09d112590a..0744fd32b8 100644 --- a/src/Mod/Path/PathTests/TestPathVcarve.py +++ b/src/Mod/Path/PathTests/TestPathVcarve.py @@ -22,7 +22,7 @@ import FreeCAD import Path.Op.Vcarve as PathVcarve -import Path.Tools.Bit as PathToolBit +import Path.Tool.Bit as PathToolBit import PathScripts.PathGeom as PathGeom import math From 9de105e59768498898212a0a31fa84e224b1acc4 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 13 Aug 2022 18:11:56 -0700 Subject: [PATCH 14/33] Started moving base classes into Path.Base module --- src/Mod/Path/CMakeLists.txt | 16 +- .../Generators/threadmilling_generator.py | 5 +- .../Base/FeedRate.py} | 12 +- .../Base/MachineState.py} | 4 +- .../PathProperty.py => Path/Base/Property.py} | 0 .../Base/PropertyBag.py} | 0 .../PathUtil.py => Path/Base/Util.py} | 2 +- src/Mod/Path/Path/Dressup/Gui/AxisMap.py | 8 +- src/Mod/Path/Path/Dressup/Gui/Dogbone.py | 42 +-- src/Mod/Path/Path/Dressup/Gui/LeadInOut.py | 3 +- src/Mod/Path/Path/Dressup/Gui/RampEntry.py | 33 +- src/Mod/Path/Path/Dressup/Gui/Tags.py | 3 +- src/Mod/Path/Path/Dressup/Gui/ZCorrect.py | 8 +- src/Mod/Path/Path/Dressup/PathBoundary.py | 25 +- src/Mod/Path/Path/Dressup/Tags.py | 125 ++++--- .../{PathScripts/PathGeom.py => Path/Geom.py} | 4 +- src/Mod/Path/Path/Op/Adaptive.py | 3 +- src/Mod/Path/Path/Op/Area.py | 5 +- src/Mod/Path/Path/Op/Base.py | 9 +- src/Mod/Path/Path/Op/Deburr.py | 5 +- src/Mod/Path/Path/Op/Drilling.py | 4 +- src/Mod/Path/Path/Op/EngraveBase.py | 9 +- src/Mod/Path/Path/Op/Gui/Base.py | 5 +- src/Mod/Path/Path/Op/Helix.py | 2 +- src/Mod/Path/Path/Op/Pocket.py | 8 +- src/Mod/Path/Path/Op/PocketShape.py | 19 +- src/Mod/Path/Path/Op/Slot.py | 3 +- src/Mod/Path/Path/Op/ThreadMilling.py | 1 - src/Mod/Path/Path/Op/Util.py | 29 +- src/Mod/Path/Path/Op/Vcarve.py | 3 +- src/Mod/Path/Path/Post/Command.py | 2 +- src/Mod/Path/Path/Post/Utils.py | 9 +- src/Mod/Path/Path/Post/scripts/dxf_post.py | 7 +- src/Mod/Path/Path/Post/scripts/grbl_post.py | 3 +- src/Mod/Path/Path/Post/scripts/marlin_post.py | 3 +- src/Mod/Path/Path/Post/scripts/opensbp_pre.py | 3 +- src/Mod/Path/Path/Post/scripts/rrf_post.py | 3 +- src/Mod/Path/Path/Tool/Bit.py | 4 +- src/Mod/Path/Path/Tool/Gui/BitEdit.py | 2 +- src/Mod/Path/Path/Tool/Gui/Controller.py | 2 +- src/Mod/Path/Path/__init__.py | 1 + .../Path/PathScripts/PathFeatureExtensions.py | 27 +- .../PathScripts/PathFeatureExtensionsGui.py | 7 +- src/Mod/Path/PathScripts/PathGui.py | 7 +- .../Path/PathScripts/PathIconViewProvider.py | 2 +- src/Mod/Path/PathScripts/PathJob.py | 2 +- src/Mod/Path/PathScripts/PathJobCmd.py | 2 +- src/Mod/Path/PathScripts/PathJobDlg.py | 2 +- src/Mod/Path/PathScripts/PathJobGui.py | 13 +- .../Path/PathScripts/PathPropertyBagGui.py | 4 +- src/Mod/Path/PathScripts/PathSanity.py | 2 +- src/Mod/Path/PathScripts/PathSetupSheet.py | 5 +- src/Mod/Path/PathScripts/PathSetupSheetGui.py | 2 +- src/Mod/Path/PathScripts/PathSimulatorGui.py | 7 +- src/Mod/Path/PathScripts/PathUtils.py | 21 +- src/Mod/Path/PathTests/PathTestUtils.py | 10 +- src/Mod/Path/PathTests/TestPathAdaptive.py | 1 - src/Mod/Path/PathTests/TestPathGeom.py | 341 +++++++++--------- src/Mod/Path/PathTests/TestPathHelpers.py | 7 +- src/Mod/Path/PathTests/TestPathOpUtil.py | 61 ++-- src/Mod/Path/PathTests/TestPathPropertyBag.py | 2 +- .../Path/PathTests/TestPathThreadMilling.py | 2 +- .../TestPathThreadMillingGenerator.py | 1 - src/Mod/Path/PathTests/TestPathUtil.py | 2 +- src/Mod/Path/PathTests/TestPathVcarve.py | 1 - src/Mod/Path/PathTests/TestPathVoronoi.py | 13 +- src/Mod/Path/Tools/toolbit-attributes.py | 4 +- 67 files changed, 474 insertions(+), 508 deletions(-) rename src/Mod/Path/{PathFeedRate.py => Path/Base/FeedRate.py} (91%) rename src/Mod/Path/{PathMachineState.py => Path/Base/MachineState.py} (97%) rename src/Mod/Path/{PathScripts/PathProperty.py => Path/Base/Property.py} (100%) rename src/Mod/Path/{PathScripts/PathPropertyBag.py => Path/Base/PropertyBag.py} (100%) rename src/Mod/Path/{PathScripts/PathUtil.py => Path/Base/Util.py} (98%) rename src/Mod/Path/{PathScripts/PathGeom.py => Path/Geom.py} (99%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 6ac2ad6507..8aab94bb94 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -10,8 +10,6 @@ set(Path_Scripts Init.py PathCommands.py TestPathApp.py - PathMachineState.py - PathFeedRate.py ) if(BUILD_GUI) @@ -28,6 +26,16 @@ INSTALL( SET(PathPython_SRCS Path/__init__.py Path/Log.py + Path/Geom.py +) + +SET(PathPythonBase_SRCS + Path/Base/__init__.py + Path/Base/MachineState.py + Path/Base/FeedRate.py + Path/Base/Property.py + Path/Base/PropertyBag.py + Path/Base/Util.py ) SET(PathPythonDressup_SRCS @@ -169,7 +177,6 @@ SET(PathScripts_SRCS PathScripts/PathFeatureExtensions.py PathScripts/PathFeatureExtensionsGui.py PathScripts/PathFixture.py - PathScripts/PathGeom.py PathScripts/PathGetPoint.py PathScripts/PathGui.py PathScripts/PathGuiInit.py @@ -183,8 +190,6 @@ SET(PathScripts_SRCS PathScripts/PathPreferences.py PathScripts/PathPreferencesAdvanced.py PathScripts/PathPreferencesPathJob.py - PathScripts/PathProperty.py - PathScripts/PathPropertyBag.py PathScripts/PathPropertyBagGui.py PathScripts/PathPropertyEditor.py PathScripts/PathSanity.py @@ -197,7 +202,6 @@ SET(PathScripts_SRCS PathScripts/PathSimulatorGui.py PathScripts/PathStock.py PathScripts/PathStop.py - PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py PathScripts/__init__.py diff --git a/src/Mod/Path/Generators/threadmilling_generator.py b/src/Mod/Path/Generators/threadmilling_generator.py index 83aca0e762..2fb0e7bd87 100644 --- a/src/Mod/Path/Generators/threadmilling_generator.py +++ b/src/Mod/Path/Generators/threadmilling_generator.py @@ -24,7 +24,6 @@ from __future__ import print_function import FreeCAD import Path -import PathScripts.PathGeom as PathGeom import math from PySide.QtCore import QT_TRANSLATE_NOOP @@ -152,7 +151,7 @@ def generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, st z = thread.zStart r = -radius i = 0 - while not PathGeom.isRoughly(z, thread.zFinal): + while not Path.Geom.isRoughly(z, thread.zFinal): if thread.overshoots(z): break if 0 == (i & 0x01): @@ -164,7 +163,7 @@ def generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, st i = i + 1 z = z + thread.hPitch - if PathGeom.isRoughly(z, thread.zFinal): + if Path.Geom.isRoughly(z, thread.zFinal): x = center.x y = yMin if (i & 0x01) else yMax else: diff --git a/src/Mod/Path/PathFeedRate.py b/src/Mod/Path/Path/Base/FeedRate.py similarity index 91% rename from src/Mod/Path/PathFeedRate.py rename to src/Mod/Path/Path/Base/FeedRate.py index 87f16b2eba..a95d367a37 100644 --- a/src/Mod/Path/PathFeedRate.py +++ b/src/Mod/Path/Path/Base/FeedRate.py @@ -22,10 +22,8 @@ import FreeCAD import Path -import PathMachineState -import PathScripts.PathGeom as PathGeom +import Path.Base.MachineState as PathMachineState import Part -from PathScripts.PathGeom import CmdMoveRapid, CmdMoveAll __title__ = "Feed Rate Helper Utility" __author__ = "sliptonic (Brad Collette)" @@ -69,24 +67,24 @@ def setFeedRate(commandlist, ToolController): endpoint = FreeCAD.Vector(x, y, z) if PathGeom.pointsCoincide(currentposition, endpoint): return True - return PathGeom.isVertical(Part.makeLine(currentposition, endpoint)) + return Path.Geom.isVertical(Part.makeLine(currentposition, endpoint)) machine = PathMachineState.MachineState() for command in commandlist: - if command.Name not in CmdMoveAll: + if command.Name not in Path.Geom.CmdMoveAll: continue if _isVertical(machine.getPosition(), command): rate = ( ToolController.VertRapid.Value - if command.Name in CmdMoveRapid + if command.Name in Path.Geom.CmdMoveRapid else ToolController.VertFeed.Value ) else: rate = ( ToolController.HorizRapid.Value - if command.Name in CmdMoveRapid + if command.Name in Path.Geom.CmdMoveRapid else ToolController.HorizFeed.Value ) diff --git a/src/Mod/Path/PathMachineState.py b/src/Mod/Path/Path/Base/MachineState.py similarity index 97% rename from src/Mod/Path/PathMachineState.py rename to src/Mod/Path/Path/Base/MachineState.py index 7334b1e4c7..313dce99d9 100644 --- a/src/Mod/Path/PathMachineState.py +++ b/src/Mod/Path/Path/Base/MachineState.py @@ -28,7 +28,7 @@ __contributors__ = "" import Path import FreeCAD -from PathScripts.PathGeom import CmdMoveRapid, CmdMoveAll, CmdMoveDrill +import Path if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -92,7 +92,7 @@ class MachineState: self.WCS = command.Name return not oldstate == self.getState() - if command.Name in CmdMoveDrill: + if command.Name in Path.Geom.CmdMoveDrill: oldZ = self.Z for p in command.Parameters: self.__setattr__(p, command.Parameters[p]) diff --git a/src/Mod/Path/PathScripts/PathProperty.py b/src/Mod/Path/Path/Base/Property.py similarity index 100% rename from src/Mod/Path/PathScripts/PathProperty.py rename to src/Mod/Path/Path/Base/Property.py diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/Path/Base/PropertyBag.py similarity index 100% rename from src/Mod/Path/PathScripts/PathPropertyBag.py rename to src/Mod/Path/Path/Base/PropertyBag.py diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/Path/Base/Util.py similarity index 98% rename from src/Mod/Path/PathScripts/PathUtil.py rename to src/Mod/Path/Path/Base/Util.py index 27835f4c07..291a47c340 100644 --- a/src/Mod/Path/PathScripts/PathUtil.py +++ b/src/Mod/Path/Path/Base/Util.py @@ -22,7 +22,7 @@ """ The purpose of this file is to collect some handy functions. The reason they -are not in PathUtils (and there is this confusing naming going on) is that +are not in Path.Base.Utils (and there is this confusing naming going on) is that PathUtils depends on PathJob. Which makes it impossible to use the functions and classes defined there in PathJob. diff --git a/src/Mod/Path/Path/Dressup/Gui/AxisMap.py b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py index 0bded9c6e5..d2d9929f06 100644 --- a/src/Mod/Path/Path/Dressup/Gui/AxisMap.py +++ b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py @@ -23,11 +23,9 @@ import FreeCAD import Path import math -import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP -from PathScripts.PathGeom import CmdMoveArc if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -88,11 +86,11 @@ class ObjectDressup: currLocation = {"X": 0, "Y": 0, "Z": 0, "F": 0} for p in path: - if p.Name in CmdMoveArc: + if p.Name in Path.Geom.CmdMoveArc: curVec = FreeCAD.Vector( currLocation["X"], currLocation["Y"], currLocation["Z"] ) - arcwire = PathGeom.edgeForCmd(p, curVec) + arcwire = Path.Geom.edgeForCmd(p, curVec) pointlist = arcwire.discretize(Deflection=d) for point in pointlist: newcommand = Path.Command( @@ -117,7 +115,7 @@ class ObjectDressup: if obj.Base.Path: if obj.Base.Path.Commands: pp = obj.Base.Path.Commands - if len([i for i in pp if i.Name in CmdMoveArc]) == 0: + if len([i for i in pp if i.Name in Path.Geom.CmdMoveArc]) == 0: pathlist = pp else: pathlist = self._stripArcs(pp, d) diff --git a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py index 1d051a5a06..8249ee6d16 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py @@ -26,12 +26,10 @@ from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup -import PathScripts.PathGeom as PathGeom -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import math -from PathScripts.PathGeom import CmdMoveCW, CmdMoveStraight, CmdMoveArc, CmdMoveRapid # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -50,7 +48,7 @@ else: translate = FreeCAD.Qt.translate -movecommands = CmdMoveStraight + CmdMoveRapid + CmdMoveArc +movecommands = Path.Geom.CmdMoveStraight + Path.Geom.CmdMoveRapid + Path.Geom.CmdMoveArc def debugMarker(vector, label, color=None, radius=0.5): @@ -118,9 +116,9 @@ def edgesForCommands(cmds, startPt): for cmd in cmds: if cmd.Name in movecommands: pt = pointFromCommand(cmd, lastPt) - if cmd.Name in CmdMoveStraight: + if cmd.Name in Path.Geom.CmdMoveStraight: edges.append(Part.Edge(Part.LineSegment(lastPt, pt))) - elif cmd.Name in CmdMoveArc: + elif cmd.Name in Path.Geom.CmdMoveArc: center = lastPt + pointFromCommand( cmd, FreeCAD.Vector(0, 0, 0), "I", "J", "K" ) @@ -131,7 +129,7 @@ def edgesForCommands(cmds, startPt): if d == 0: # we're dealing with half a circle here angle = getAngle(A) + math.pi / 2 - if cmd.Name in CmdMoveCW: + if cmd.Name in Path.Geom.CmdMoveCW: angle -= math.pi else: C = A + B @@ -250,7 +248,7 @@ class Chord(object): A = self.asDirection() # if the 2 vectors are identical, they head in the same direction Path.Log.debug(" {}.getDirectionOfVector({})".format(A, B)) - if PathGeom.pointsCoincide(A, B): + if Path.Geom.pointsCoincide(A, B): return "Straight" d = -A.x * B.y + A.y * B.x if d < 0: @@ -306,15 +304,15 @@ class Chord(object): return self.arcCommand("G3", center, f) def isAPlungeMove(self): - return not PathGeom.isRoughly(self.End.z, self.Start.z) + return not Path.Geom.isRoughly(self.End.z, self.Start.z) def isANoopMove(self): Path.Log.debug( "{}.isANoopMove(): {}".format( - self, PathGeom.pointsCoincide(self.Start, self.End) + self, Path.Geom.pointsCoincide(self.Start, self.End) ) ) - return PathGeom.pointsCoincide(self.Start, self.End) + return Path.Geom.pointsCoincide(self.Start, self.End) def foldsBackOrTurns(self, chord, side): direction = chord.getDirectionOf(self) @@ -322,7 +320,7 @@ class Chord(object): return direction == "Back" or direction == side def connectsTo(self, chord): - return PathGeom.pointsCoincide(self.End, chord.Start) + return Path.Geom.pointsCoincide(self.End, chord.Start) class Bone(object): @@ -397,7 +395,7 @@ class Bone(object): return 0 gamma = math.asin(D) alpha = math.pi - beta - gamma - if PathGeom.isRoughly(0.0, math.sin(beta)): + if Path.Geom.isRoughly(0.0, math.sin(beta)): # it is not a good idea to divide by 0 length = 0.0 else: @@ -495,7 +493,7 @@ class ObjectDressup(object): # Answer true if a dogbone could be on either end of the chord, given its command def canAttachDogbone(self, cmd, chord): return ( - cmd.Name in CmdMoveStraight + cmd.Name in Path.Geom.CmdMoveStraight and not chord.isAPlungeMove() and not chord.isANoopMove() ) @@ -590,7 +588,7 @@ class ObjectDressup(object): % (e.Curve.Center.x, e.Curve.Center.y, e.Curve.Radius) ) for pt in DraftGeomUtils.findIntersection(edge, e, True, findAll=True): - if not PathGeom.pointsCoincide(pt, corner) and self.pointIsOnEdge( + if not Path.Geom.pointsCoincide(pt, corner) and self.pointIsOnEdge( pt, e ): # debugMarker(pt, "candidate-%d-%s" % (self.boneId, d), color, 0.05) @@ -616,7 +614,7 @@ class ObjectDressup(object): ) commands = [] - if not PathGeom.pointsCoincide(t1, inChord.Start): + if not Path.Geom.pointsCoincide(t1, inChord.Start): Path.Log.debug(" add lead in") commands.append(Chord(inChord.Start, t1).g1Command(bone.F)) if bone.obj.Side == Side.Left: @@ -628,7 +626,7 @@ class ObjectDressup(object): % (pivot.x, pivot.y, t1.x, t1.y, t2.x, t2.y) ) commands.append(Chord(t1, t2).g2Command(pivot, bone.F)) - if not PathGeom.pointsCoincide(t2, outChord.End): + if not Path.Geom.pointsCoincide(t2, outChord.End): Path.Log.debug(" add lead out") commands.append(Chord(t2, outChord.End).g1Command(bone.F)) @@ -745,7 +743,7 @@ class ObjectDressup(object): def tboneVertical(self, bone): angle = bone.angle() boneAngle = math.pi / 2 - if PathGeom.isRoughly(angle, math.pi) or angle < 0: + if Path.Geom.isRoughly(angle, math.pi) or angle < 0: boneAngle = -boneAngle return self.inOutBoneCommands(bone, boneAngle, self.toolRadius) @@ -853,9 +851,9 @@ class ObjectDressup(object): for pt in cutoff: # debugCircle(e1.Curve.Center, e1.Curve.Radius, "bone.%d-1" % (self.boneId), (1.,0.,0.)) # debugCircle(e2.Curve.Center, e2.Curve.Radius, "bone.%d-2" % (self.boneId), (0.,1.,0.)) - if PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide( pt, e1.valueAt(e1.LastParameter) - ) or PathGeom.pointsCoincide(pt, e2.valueAt(e2.FirstParameter)): + ) or Path.Geom.pointsCoincide(pt, e2.valueAt(e2.FirstParameter)): continue # debugMarker(pt, "it", (0.0, 1.0, 1.0)) # 1. remove all redundant commands @@ -868,7 +866,7 @@ class ObjectDressup(object): commands.append(c1) # 3. change where c2 starts, this depends on the command itself c2 = bone2.inCommands[j] - if c2.Name in CmdMoveArc: + if c2.Name in Path.Geom.CmdMoveArc: center = e2.Curve.Center offset = center - pt c2Params = c2.Parameters @@ -1073,7 +1071,7 @@ class ObjectDressup(object): class Marker(object): def __init__(self, pt, r, h): - if PathGeom.isRoughly(h, 0): + if Path.Geom.isRoughly(h, 0): h = 0.1 self.pt = pt self.r = r diff --git a/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py b/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py index 5d1e181576..bdb4f2b16b 100644 --- a/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/Path/Path/Dressup/Gui/LeadInOut.py @@ -27,7 +27,6 @@ import FreeCAD import FreeCADGui import Path import Path.Dressup.Utils as PathDressup -import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils import math import copy @@ -190,7 +189,7 @@ class ObjectDressup: + "\n" ) obj.Length = 0.1 - self.wire, self.rapids = PathGeom.wireForPath(obj.Base.Path) + self.wire, self.rapids = Path.Geom.wireForPath(obj.Base.Path) obj.Path = self.generateLeadInOutCurve(obj) def getDirectionOfPath(self, obj): diff --git a/src/Mod/Path/Path/Dressup/Gui/RampEntry.py b/src/Mod/Path/Path/Dressup/Gui/RampEntry.py index 80dd8a89f0..5b740bef3f 100644 --- a/src/Mod/Path/Path/Dressup/Gui/RampEntry.py +++ b/src/Mod/Path/Path/Dressup/Gui/RampEntry.py @@ -25,7 +25,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import Path.Dressup.Utils as PathDressup -import PathScripts.PathGeom as PathGeom import math @@ -223,7 +222,7 @@ class ObjectDressup: self.angle = obj.Angle self.method = obj.Method - self.wire, self.rapids = PathGeom.wireForPath(obj.Base.Path) + self.wire, self.rapids = Path.Geom.wireForPath(obj.Base.Path) if self.method in ["RampMethod1", "RampMethod2", "RampMethod3"]: self.outedges = self.generateRamps() else: @@ -236,7 +235,7 @@ class ObjectDressup: for edge in edges: israpid = False for redge in self.rapids: - if PathGeom.edgesMatch(edge, redge): + if Path.Geom.edgesMatch(edge, redge): israpid = True if not israpid: bb = edge.BoundBox @@ -361,7 +360,7 @@ class ObjectDressup: edge = edges[i] israpid = False for redge in self.rapids: - if PathGeom.edgesMatch(edge, redge): + if Path.Geom.edgesMatch(edge, redge): israpid = True if not israpid: bb = edge.BoundBox @@ -395,7 +394,7 @@ class ObjectDressup: candidate = edges[j] cp0 = candidate.Vertexes[0].Point cp1 = candidate.Vertexes[1].Point - if PathGeom.pointsCoincide(p1, cp1): + if Path.Geom.pointsCoincide(p1, cp1): # found closed loop loopFound = True rampedges.append(candidate) @@ -413,7 +412,7 @@ class ObjectDressup: outedges.append(edge) else: outedges.extend(self.createHelix(rampedges, p0, p1)) - if not PathGeom.isRoughly(p1.z, minZ): + if not Path.Geom.isRoughly(p1.z, minZ): # the edges covered by the helix not handled again, # unless reached the bottom height i = j @@ -431,11 +430,11 @@ class ObjectDressup: p1 = edge.Vertexes[1].Point if p0.z > self.ignoreAbove and ( p1.z > self.ignoreAbove - or PathGeom.isRoughly(p1.z, self.ignoreAbove.Value) + or Path.Geom.isRoughly(p1.z, self.ignoreAbove.Value) ): Path.Log.debug("Whole plunge move above 'ignoreAbove', ignoring") return (edge, True) - elif p0.z > self.ignoreAbove and not PathGeom.isRoughly( + elif p0.z > self.ignoreAbove and not Path.Geom.isRoughly( p0.z, self.ignoreAbove.Value ): Path.Log.debug( @@ -543,7 +542,7 @@ class ObjectDressup: if redge.Length >= rampremaining: # will reach end of ramp within this edge, needs to be split p1 = self.getSplitPoint(redge, rampremaining) - splitEdge = PathGeom.splitEdgeAt(redge, p1) + splitEdge = Path.Geom.splitEdgeAt(redge, p1) Path.Log.debug("Ramp remaining: {}".format(rampremaining)) Path.Log.debug( "Got split edge (index: {}) (total len: {}) with lengths: {}, {}".format( @@ -626,7 +625,7 @@ class ObjectDressup: if redge.Length >= rampremaining: # will reach end of ramp within this edge, needs to be split p1 = self.getSplitPoint(redge, rampremaining) - splitEdge = PathGeom.splitEdgeAt(redge, p1) + splitEdge = Path.Geom.splitEdgeAt(redge, p1) Path.Log.debug( "Got split edge (index: {}) with lengths: {}, {}".format( i, splitEdge[0].Length, splitEdge[1].Length @@ -702,9 +701,9 @@ class ObjectDressup: outedges = [] rampremaining = projectionlen curPoint = p0 # start from the upper point of plunge - if PathGeom.pointsCoincide( - PathGeom.xy(p0), - PathGeom.xy(rampedges[-1].valueAt(rampedges[-1].LastParameter)), + if Path.Geom.pointsCoincide( + Path.Geom.xy(p0), + Path.Geom.xy(rampedges[-1].valueAt(rampedges[-1].LastParameter)), ): Path.Log.debug( "The ramp forms a closed wire, needless to move on original Z height" @@ -714,7 +713,7 @@ class ObjectDressup: if redge.Length >= rampremaining: # this edge needs to be split p1 = self.getSplitPoint(redge, rampremaining) - splitEdge = PathGeom.splitEdgeAt(redge, p1) + splitEdge = Path.Geom.splitEdgeAt(redge, p1) Path.Log.debug( "Got split edges with lengths: {}, {}".format( splitEdge[0].Length, splitEdge[1].Length @@ -785,13 +784,13 @@ class ObjectDressup: for edge in edges: israpid = False for redge in self.rapids: - if PathGeom.edgesMatch(edge, redge): + if Path.Geom.edgesMatch(edge, redge): israpid = True if israpid: v = edge.valueAt(edge.LastParameter) commands.append(Path.Command("G0", {"X": v.x, "Y": v.y, "Z": v.z})) else: - commands.extend(PathGeom.cmdsForEdge(edge)) + commands.extend(Path.Geom.cmdsForEdge(edge)) lastCmd = Path.Command("G0", {"X": 0.0, "Y": 0.0, "Z": 0.0}) @@ -830,7 +829,7 @@ class ObjectDressup: if cmd.Name in ["G1", "G2", "G3", "G01", "G02", "G03"]: if zVal is not None and zVal2 != zVal: - if PathGeom.isRoughly(xVal, xVal2) and PathGeom.isRoughly( + if Path.Geom.isRoughly(xVal, xVal2) and Path.Geom.isRoughly( yVal, yVal2 ): # this is a straight plunge diff --git a/src/Mod/Path/Path/Dressup/Gui/Tags.py b/src/Mod/Path/Path/Dressup/Gui/Tags.py index 284e166496..847aa0a038 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Tags.py +++ b/src/Mod/Path/Path/Dressup/Gui/Tags.py @@ -28,7 +28,6 @@ import FreeCADGui import Path import Path.Dressup.Tags as PathDressupTag import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtils as PathUtils @@ -527,7 +526,7 @@ class PathDressupTagViewProvider: z = self.tags[0].point.z p = FreeCAD.Vector(x, y, z) for i, tag in enumerate(self.tags): - if PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide( p, tag.point, tag.sphere.radius.getValue() * 1.3 ): return i diff --git a/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py b/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py index a93417fe70..15f884ecc7 100644 --- a/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py +++ b/src/Mod/Path/Path/Dressup/Gui/ZCorrect.py @@ -26,12 +26,10 @@ import FreeCAD import FreeCADGui import Path -import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils from PySide import QtGui from PySide.QtCore import QT_TRANSLATE_NOOP -from PathScripts.PathGeom import CmdMoveArc, CmdMoveStraight # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -176,16 +174,16 @@ class ObjectDressup: Path.Log.debug(" curLoc:{}".format(currLocation)) newparams = dict(c.Parameters) zval = newparams.get("Z", currLocation["Z"]) - if c.Name in CmdMoveStraight + CmdMoveArc: + if c.Name in Path.Geom.CmdMoveStraight + Path.Geom.CmdMoveArc: curVec = FreeCAD.Vector( currLocation["X"], currLocation["Y"], currLocation["Z"], ) - arcwire = PathGeom.edgeForCmd(c, curVec) + arcwire = Path.Geom.edgeForCmd(c, curVec) if arcwire is None: continue - if c.Name in CmdMoveArc: + if c.Name in Path.Geom.CmdMoveArc: pointlist = arcwire.discretize(Deflection=curveD) else: disc_number = int(arcwire.Length / sampleD) diff --git a/src/Mod/Path/Path/Dressup/PathBoundary.py b/src/Mod/Path/Path/Dressup/PathBoundary.py index 00d7bb5a01..896e4a164a 100644 --- a/src/Mod/Path/Path/Dressup/PathBoundary.py +++ b/src/Mod/Path/Path/Dressup/PathBoundary.py @@ -23,10 +23,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup -import PathScripts.PathGeom as PathGeom import PathScripts.PathStock as PathStock -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils if False: @@ -127,7 +126,7 @@ class PathBoundary: def boundaryCommands(self, begin, end, verticalFeed): Path.Log.track(_vstr(begin), _vstr(end)) - if end and PathGeom.pointsCoincide(begin, end): + if end and Path.Geom.pointsCoincide(begin, end): return [] cmds = [] if begin.z < self.safeHeight: @@ -172,12 +171,12 @@ class PathBoundary: commands = [cmd] lastExit = None for cmd in self.baseOp.Path.Commands[1:]: - if cmd.Name in PathGeom.CmdMoveAll: + if cmd.Name in Path.Geom.CmdMoveAll: if bogusX: bogusX = "X" not in cmd.Parameters if bogusY: bogusY = "Y" not in cmd.Parameters - edge = PathGeom.edgeForCmd(cmd, pos) + edge = Path.Geom.edgeForCmd(cmd, pos) if edge: inside = edge.common(self.boundary).Edges outside = edge.cut(self.boundary).Edges @@ -201,25 +200,25 @@ class PathBoundary: ) lastExit = None commands.append(cmd) - pos = PathGeom.commandEndPoint(cmd, pos) + pos = Path.Geom.commandEndPoint(cmd, pos) elif 0 == len(inside) and 1 == len(outside): Path.Log.track(_vstr(pos), _vstr(lastExit), " - ", cmd) # cmd fully excluded by boundary if not lastExit: lastExit = pos - pos = PathGeom.commandEndPoint(cmd, pos) + pos = Path.Geom.commandEndPoint(cmd, pos) else: Path.Log.track( _vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd ) # cmd pierces boundary while inside or outside: - ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] + ie = [e for e in inside if Path.Geom.edgeConnectsTo(e, pos)] Path.Log.track(ie) if ie: e = ie[0] LastPt = e.valueAt(e.LastParameter) - flip = PathGeom.pointsCoincide(pos, LastPt) + flip = Path.Geom.pointsCoincide(pos, LastPt) newPos = e.valueAt(e.FirstParameter) if flip else LastPt # inside edges are taken at this point (see swap of inside/outside # above - so we can just connect the dots ... @@ -236,7 +235,7 @@ class PathBoundary: bogusX or bogusY ): # don't insert false paths based on bogus m/c position commands.extend( - PathGeom.cmdsForEdge( + Path.Geom.cmdsForEdge( e, flip, False, @@ -252,13 +251,13 @@ class PathBoundary: oe = [ e for e in outside - if PathGeom.edgeConnectsTo(e, pos) + if Path.Geom.edgeConnectsTo(e, pos) ] Path.Log.track(oe) if oe: e = oe[0] ptL = e.valueAt(e.LastParameter) - flip = PathGeom.pointsCoincide(pos, ptL) + flip = Path.Geom.pointsCoincide(pos, ptL) newPos = ( e.valueAt(e.FirstParameter) if flip else ptL ) @@ -280,7 +279,7 @@ class PathBoundary: # Eif # Ewhile # Eif - # pos = PathGeom.commandEndPoint(cmd, pos) + # pos = Path.Geom.commandEndPoint(cmd, pos) # Eif else: Path.Log.track("no-move", cmd) diff --git a/src/Mod/Path/Path/Dressup/Tags.py b/src/Mod/Path/Path/Dressup/Tags.py index fcd1b8b1c0..bc4adec927 100644 --- a/src/Mod/Path/Path/Dressup/Tags.py +++ b/src/Mod/Path/Path/Dressup/Tags.py @@ -25,9 +25,8 @@ from PathScripts.PathUtils import waiting_effects from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup -import PathScripts.PathGeom as PathGeom -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import copy import math @@ -164,7 +163,7 @@ class Tag: self.r2 = r1 height = self.height * 1.01 radius = 0 - if PathGeom.isRoughly(90, self.angle) and height > 0: + if Path.Geom.isRoughly(90, self.angle) and height > 0: # cylinder self.isSquare = True self.solid = Part.makeCylinder(r1, height) @@ -192,10 +191,10 @@ class Tag: # degenerated case - no tag Path.Log.debug("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) - if not PathGeom.isRoughly( + if not Path.Geom.isRoughly( 0, R ): # testing is easier if the solid is not rotated - angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi + angle = -Path.Geom.getAngle(self.originAt(0)) * 180 / math.pi Path.Log.debug("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), angle) orig = self.originAt(z - 0.01 * self.actualHeight) @@ -203,7 +202,7 @@ class Tag: self.solid.translate(orig) radius = min(self.radius, radius) self.realRadius = radius - if not PathGeom.isRoughly(0, radius): + if not Path.Geom.isRoughly(0, radius): Path.Log.debug("makeFillet(%.4f)" % radius) self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]]) @@ -226,7 +225,7 @@ class Tag: pt for pt in pts if (pt - c.Center).Length <= c.Radius - or PathGeom.isRoughly((pt - c.Center).Length, c.Radius) + or Path.Geom.isRoughly((pt - c.Center).Length, c.Radius) ] ) Path.Log.error("==== we got a %s" % face.Surface) @@ -237,7 +236,7 @@ class Tag: return True if edge.LastParameter <= param <= edge.FirstParameter: return True - if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly( + if Path.Geom.isRoughly(edge.FirstParameter, param) or Path.Geom.isRoughly( edge.LastParameter, param ): return True @@ -265,7 +264,7 @@ class Tag: def intersects(self, edge, param): def isDefinitelySmaller(z, zRef): # Eliminate false positives of edges that just brush along the top of the tag - return z < zRef and not PathGeom.isRoughly(z, zRef, 0.01) + return z < zRef and not Path.Geom.isRoughly(z, zRef, 0.01) if self.enabled: zFirst = edge.valueAt(edge.FirstParameter).z @@ -297,20 +296,20 @@ class MapWireToTag: self.maxZ = maxZ self.hSpeed = hSpeed self.vSpeed = vSpeed - if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): + if Path.Geom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): tail = edge self.commands = [] debugEdge(tail, ".........=") - elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): + elif Path.Geom.pointsCoincide(edge.valueAt(edge.LastParameter), i): debugEdge(edge, "++++++++ .") - self.commands = PathGeom.cmdsForEdge( + self.commands = Path.Geom.cmdsForEdge( edge, segm=segm, hSpeed=self.hSpeed, vSpeed=self.vSpeed ) tail = None else: - e, tail = PathGeom.splitEdgeAt(edge, i) + e, tail = Path.Geom.splitEdgeAt(edge, i) debugEdge(e, "++++++++ .") - self.commands = PathGeom.cmdsForEdge( + self.commands = Path.Geom.cmdsForEdge( e, segm=segm, hSpeed=self.hSpeed, vSpeed=self.vSpeed ) debugEdge(tail, ".........-") @@ -344,20 +343,20 @@ class MapWireToTag: self.edges.append(edge) def needToFlipEdge(self, edge, p): - if PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), p): + if Path.Geom.pointsCoincide(edge.valueAt(edge.LastParameter), p): return True, edge.valueAt(edge.FirstParameter) return False, edge.valueAt(edge.LastParameter) def isEntryOrExitStrut(self, e): p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(p1, self.entry) and p2.z >= self.entry.z: + if Path.Geom.pointsCoincide(p1, self.entry) and p2.z >= self.entry.z: return 1 - if PathGeom.pointsCoincide(p2, self.entry) and p1.z >= self.entry.z: + if Path.Geom.pointsCoincide(p2, self.entry) and p1.z >= self.entry.z: return 1 - if PathGeom.pointsCoincide(p1, self.exit) and p2.z >= self.exit.z: + if Path.Geom.pointsCoincide(p1, self.exit) and p2.z >= self.exit.z: return 2 - if PathGeom.pointsCoincide(p2, self.exit) and p1.z >= self.exit.z: + if Path.Geom.pointsCoincide(p2, self.exit) and p1.z >= self.exit.z: return 2 return 0 @@ -383,16 +382,16 @@ class MapWireToTag: self.edgePoints.append(p1) self.edgePoints.append(p2) if self.tag.solid.isInside( - p1, PathGeom.Tolerance, False - ) or self.tag.solid.isInside(p2, PathGeom.Tolerance, False): + p1, Path.Geom.Tolerance, False + ) or self.tag.solid.isInside(p2, Path.Geom.Tolerance, False): edges.remove(e) debugEdge(e, "......... X0", False) else: - if PathGeom.pointsCoincide(p1, self.entry) or PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide(p1, self.entry) or Path.Geom.pointsCoincide( p2, self.entry ): self.entryEdges.append(e) - if PathGeom.pointsCoincide(p1, self.exit) or PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide(p1, self.exit) or Path.Geom.pointsCoincide( p2, self.exit ): self.exitEdges.append(e) @@ -406,7 +405,7 @@ class MapWireToTag: self.edgePoints, key=lambda p: (p - self.entry).Length )[0] self.entryEdges = list( - [e for e in edges if PathGeom.edgeConnectsTo(e, self.realEntry)] + [e for e in edges if Path.Geom.edgeConnectsTo(e, self.realEntry)] ) edges.append(Part.Edge(Part.LineSegment(self.entry, self.realEntry))) else: @@ -417,7 +416,7 @@ class MapWireToTag: self.edgePoints, key=lambda p: (p - self.exit).Length )[0] self.exitEdges = list( - [e for e in edges if PathGeom.edgeConnectsTo(e, self.realExit)] + [e for e in edges if Path.Geom.edgeConnectsTo(e, self.realExit)] ) edges.append(Part.Edge(Part.LineSegment(self.realExit, self.exit))) else: @@ -471,15 +470,15 @@ class MapWireToTag: for e in copy.copy(edges): p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(p1, p0): + if Path.Geom.pointsCoincide(p1, p0): outputEdges.append((e, False)) edges.remove(e) lastP = None p0 = p2 debugEdge(e, ">>>>> no flip") break - elif PathGeom.pointsCoincide(p2, p0): - flipped = PathGeom.flipEdge(e) + elif Path.Geom.pointsCoincide(p2, p0): + flipped = Path.Geom.flipEdge(e) if not flipped is None: outputEdges.append((flipped, True)) else: @@ -526,21 +525,21 @@ class MapWireToTag: return outputEdges def isStrut(self, edge): - p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) - p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) - return PathGeom.pointsCoincide(p1, p2) + p1 = Path.Geom.xy(edge.valueAt(edge.FirstParameter)) + p2 = Path.Geom.xy(edge.valueAt(edge.LastParameter)) + return Path.Geom.pointsCoincide(p1, p2) def shell(self): if len(self.edges) > 1: wire = Part.Wire(self.initialEdge) else: edge = self.edges[0] - if PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide( edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter), ): wire = Part.Wire(self.finalEdge) - elif hasattr(self, "initialEdge") and PathGeom.pointsCoincide( + elif hasattr(self, "initialEdge") and Path.Geom.pointsCoincide( edge.valueAt(edge.FirstParameter), self.initialEdge.valueAt(self.initialEdge.FirstParameter), ): @@ -549,7 +548,7 @@ class MapWireToTag: wire = Part.Wire(edge) for edge in self.edges[1:]: - if PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide( edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter), ): @@ -558,7 +557,7 @@ class MapWireToTag: wire.add(edge) shell = wire.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) - nullFaces = list([f for f in shell.Faces if PathGeom.isRoughly(f.Area, 0)]) + nullFaces = list([f for f in shell.Faces if Path.Geom.isRoughly(f.Area, 0)]) if nullFaces: return shell.removeShape(nullFaces) return shell @@ -575,8 +574,8 @@ class MapWireToTag: p2 = e.valueAt(e.LastParameter) if ( self.tag.isSquare - and (PathGeom.isRoughly(p1.z, self.maxZ) or p1.z > self.maxZ) - and (PathGeom.isRoughly(p2.z, self.maxZ) or p2.z > self.maxZ) + and (Path.Geom.isRoughly(p1.z, self.maxZ) or p1.z > self.maxZ) + and (Path.Geom.isRoughly(p2.z, self.maxZ) or p2.z > self.maxZ) ): rapid = p1 if flip else p2 else: @@ -588,7 +587,7 @@ class MapWireToTag: ) rapid = None commands.extend( - PathGeom.cmdsForEdge( + Path.Geom.cmdsForEdge( e, False, False, @@ -612,7 +611,7 @@ class MapWireToTag: commands = [] for e in self.edges: commands.extend( - PathGeom.cmdsForEdge(e, hSpeed=self.hSpeed, vSpeed=self.vSpeed) + Path.Geom.cmdsForEdge(e, hSpeed=self.hSpeed, vSpeed=self.vSpeed) ) return commands return [] @@ -621,7 +620,7 @@ class MapWireToTag: self.tail = None self.finalEdge = edge if self.tag.solid.isInside( - edge.valueAt(edge.LastParameter), PathGeom.Tolerance, True + edge.valueAt(edge.LastParameter), Path.Geom.Tolerance, True ): Path.Log.track("solid.isInside") self.addEdge(edge) @@ -633,12 +632,12 @@ class MapWireToTag: o = self.tag.originAt(self.tag.z) Path.Log.debug("originAt: (%.2f, %.2f, %.2f)" % (o.x, o.y, o.z)) i = edge.valueAt(edge.FirstParameter) - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + if Path.Geom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): Path.Log.track("tail") self.tail = edge else: Path.Log.track("split") - e, tail = PathGeom.splitEdgeAt(edge, i) + e, tail = Path.Geom.splitEdgeAt(edge, i) self.addEdge(e) self.tail = tail self.exit = i @@ -661,12 +660,12 @@ class _RapidEdges: r0 = r.Vertexes[0] r1 = r.Vertexes[1] if ( - PathGeom.isRoughly(r0.X, v0.X) - and PathGeom.isRoughly(r0.Y, v0.Y) - and PathGeom.isRoughly(r0.Z, v0.Z) - and PathGeom.isRoughly(r1.X, v1.X) - and PathGeom.isRoughly(r1.Y, v1.Y) - and PathGeom.isRoughly(r1.Z, v1.Z) + Path.Geom.isRoughly(r0.X, v0.X) + and Path.Geom.isRoughly(r0.Y, v0.Y) + and Path.Geom.isRoughly(r0.Z, v0.Z) + and Path.Geom.isRoughly(r1.X, v1.X) + and Path.Geom.isRoughly(r1.Y, v1.Y) + and Path.Geom.isRoughly(r1.Z, v1.Z) ): return True return False @@ -676,7 +675,7 @@ class PathData: def __init__(self, obj): Path.Log.track(obj.Base.Name) self.obj = obj - self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) + self.wire, rapid = Path.Geom.wireForPath(obj.Base.Path) self.rapid = _RapidEdges(rapid) if self.wire: self.edges = self.wire.Edges @@ -691,8 +690,8 @@ class PathData: bottom = [ e for e in edges - if PathGeom.isRoughly(e.Vertexes[0].Point.z, minZ) - and PathGeom.isRoughly(e.Vertexes[1].Point.z, minZ) + if Path.Geom.isRoughly(e.Vertexes[0].Point.z, minZ) + and Path.Geom.isRoughly(e.Vertexes[1].Point.z, minZ) ] self.bottomEdges = bottom try: @@ -746,7 +745,7 @@ class PathData: for i in range(0, len(self.baseWire.Edges)): edge = self.baseWire.Edges[i] Path.Log.debug(" %d: %.2f" % (i, edge.Length)) - if PathGeom.isRoughly(edge.Length, longestEdge.Length): + if Path.Geom.isRoughly(edge.Length, longestEdge.Length): startIndex = i break @@ -898,7 +897,7 @@ class PathData: ts = [ t for t in tags - if PathGeom.isRoughly( + if Path.Geom.isRoughly( 0, Part.Vertex(t.originAt(self.minZ)).distToShape(edge)[0], 0.1 ) ] @@ -926,7 +925,7 @@ class PathData: for e in self.bottomEdges: indent = "{} ".format(e.distToShape(v)[0]) debugEdge(e, indent, True) - if PathGeom.isRoughly(0.0, v.distToShape(e)[0], 0.1): + if Path.Geom.isRoughly(0.0, v.distToShape(e)[0], 0.1): return True return False @@ -1059,11 +1058,11 @@ class ObjectTagDressup: return False def isValidTagStartIntersection(self, edge, i): - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + if Path.Geom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): return False p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt(edge.LastParameter) - if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): + if Path.Geom.pointsCoincide(Path.Geom.xy(p1), Path.Geom.xy(p2)): # if this vertical goes up, it can't be the start of a tag intersection if p1.z < p2.z: return False @@ -1141,9 +1140,9 @@ class ObjectTagDressup: v = edge.Vertexes[1] if ( not commands - and PathGeom.isRoughly(0, v.X) - and PathGeom.isRoughly(0, v.Y) - and not PathGeom.isRoughly(0, v.Z) + and Path.Geom.isRoughly(0, v.X) + and Path.Geom.isRoughly(0, v.Y) + and not Path.Geom.isRoughly(0, v.Z) ): # The very first move is just to move to ClearanceHeight commands.append( @@ -1157,7 +1156,7 @@ class ObjectTagDressup: ) else: commands.extend( - PathGeom.cmdsForEdge( + Path.Geom.cmdsForEdge( edge, segm=segm, hSpeed=horizFeed, vSpeed=vertFeed ) ) @@ -1203,8 +1202,8 @@ class ObjectTagDressup: p0 = e.valueAt(e.FirstParameter) p1 = e.valueAt(e.LastParameter) if tag.solid.isInside( - p0, PathGeom.Tolerance, True - ) or tag.solid.isInside(p1, PathGeom.Tolerance, True): + p0, Path.Geom.Tolerance, True + ) or tag.solid.isInside(p1, Path.Geom.Tolerance, True): Path.Log.info( "Tag #%d intersects with starting point - disabling\n" % i ) @@ -1290,7 +1289,7 @@ class ObjectTagDressup: "x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ) ) # debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - # if not PathGeom.isRoughly(90, tag.angle): + # if not Path.Geom.isRoughly(90, tag.angle): # debugCone(tag.originAt(self.pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) # else: # debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/Path/Geom.py similarity index 99% rename from src/Mod/Path/PathScripts/PathGeom.py rename to src/Mod/Path/Path/Geom.py index 6566cf97f1..29955f6f25 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/Path/Geom.py @@ -33,7 +33,7 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") -__title__ = "PathGeom - geometry utilities for Path" +__title__ = "Geom - geometry utilities for Path" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "Functions to extract and convert between Path.Command and Part.Edge and utility functions to reason about them." @@ -709,7 +709,7 @@ def combineHorizontalFaces(faces): The Adaptive op is not concerned with which hole edges belong to which face. Attempts to do the same shape connecting failed with TechDraw.findShapeOutline() and - PathGeom.combineConnectedShapes(), so this algorithm was created. + Path.Geom.combineConnectedShapes(), so this algorithm was created. """ horizontal = list() offset = 10.0 diff --git a/src/Mod/Path/Path/Op/Adaptive.py b/src/Mod/Path/Path/Op/Adaptive.py index d456d423b3..b7a037440a 100644 --- a/src/Mod/Path/Path/Op/Adaptive.py +++ b/src/Mod/Path/Path/Op/Adaptive.py @@ -25,7 +25,6 @@ import Path import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils -import PathScripts.PathGeom as PathGeom import FreeCAD import time import json @@ -869,7 +868,7 @@ def _get_working_edges(op, obj): all_regions.append(f) # Second face-combining method attempted - horizontal = PathGeom.combineHorizontalFaces(all_regions) + horizontal = Path.Geom.combineHorizontalFaces(all_regions) if horizontal: obj.removalshape = Part.makeCompound(horizontal) for f in horizontal: diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 5c35188b36..094627c58e 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -32,7 +32,6 @@ from lazy_loader.lazy_loader import LazyLoader Draft = LazyLoader("Draft", globals(), "Draft") Part = LazyLoader("Part", globals(), "Part") -PathGeom = LazyLoader("PathScripts.PathGeom", globals(), "PathScripts.PathGeom") __title__ = "Base class for PathArea based operations." @@ -315,9 +314,9 @@ class ObjectOp(PathOp.ObjectOp): x = verts[idx].X y = verts[idx].Y # Zero start value adjustments for Path.fromShapes() bug - if PathGeom.isRoughly(x, 0.0): + if Path.Geom.isRoughly(x, 0.0): x = 0.00001 - if PathGeom.isRoughly(y, 0.0): + if Path.Geom.isRoughly(y, 0.0): y = 0.00001 pathParams["start"] = FreeCAD.Vector(x, y, verts[0].Z) else: diff --git a/src/Mod/Path/Path/Op/Base.py b/src/Mod/Path/Path/Op/Base.py index f1e1e036a1..04c3d25408 100644 --- a/src/Mod/Path/Path/Op/Base.py +++ b/src/Mod/Path/Path/Op/Base.py @@ -24,9 +24,8 @@ import FreeCAD from PathScripts.PathUtils import waiting_effects from PySide.QtCore import QT_TRANSLATE_NOOP import Path -import PathScripts.PathGeom as PathGeom +import Path.Base.Util as PathUtil import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import math import time @@ -703,12 +702,12 @@ class ObjectOp(object): if FeatureDepths & self.opFeatures(obj): # first set update final depth, it's value is not negotiable - if not PathGeom.isRoughly(obj.OpFinalDepth.Value, zmin): + if not Path.Geom.isRoughly(obj.OpFinalDepth.Value, zmin): obj.OpFinalDepth = zmin zmin = obj.OpFinalDepth.Value def minZmax(z): - if hasattr(obj, "StepDown") and not PathGeom.isRoughly( + if hasattr(obj, "StepDown") and not Path.Geom.isRoughly( obj.StepDown.Value, 0 ): return z + obj.StepDown.Value @@ -720,7 +719,7 @@ class ObjectOp(object): zmax = minZmax(zmin) # update start depth if requested and required - if not PathGeom.isRoughly(obj.OpStartDepth.Value, zmax): + if not Path.Geom.isRoughly(obj.OpStartDepth.Value, zmax): obj.OpStartDepth = zmax else: # every obj has a StartDepth diff --git a/src/Mod/Path/Path/Op/Deburr.py b/src/Mod/Path/Path/Op/Deburr.py index d464441715..9ad1893ac9 100644 --- a/src/Mod/Path/Path/Op/Deburr.py +++ b/src/Mod/Path/Path/Op/Deburr.py @@ -26,7 +26,6 @@ import Path import Path.Op.Base as PathOp import Path.Op.EngraveBase as PathEngraveBase import Path.Op.Util as PathOpUtil -import PathScripts.PathGeom as PathGeom import math from PySide.QtCore import QT_TRANSLATE_NOOP @@ -61,7 +60,7 @@ def toolDepthAndOffset(width, extraDepth, tool, printInfo): suppressInfo = False if hasattr(tool, "CuttingEdgeAngle"): angle = float(tool.CuttingEdgeAngle) - if PathGeom.isRoughly(angle, 180) or PathGeom.isRoughly(angle, 0): + if Path.Geom.isRoughly(angle, 180) or Path.Geom.isRoughly(angle, 0): angle = 180 toolOffset = float(tool.Diameter) / 2 else: @@ -95,7 +94,7 @@ def toolDepthAndOffset(width, extraDepth, tool, printInfo): tan = math.tan(math.radians(angle / 2)) - toolDepth = 0 if PathGeom.isRoughly(tan, 0) else width / tan + toolDepth = 0 if Path.Geom.isRoughly(tan, 0) else width / tan depth = toolDepth + extraDepth extraOffset = -width if angle == 180 else (extraDepth / tan) offset = toolOffset + extraOffset diff --git a/src/Mod/Path/Path/Op/Drilling.py b/src/Mod/Path/Path/Op/Drilling.py index 0434f5f6a3..bd60e7c922 100644 --- a/src/Mod/Path/Path/Op/Drilling.py +++ b/src/Mod/Path/Path/Op/Drilling.py @@ -28,10 +28,10 @@ from Generators import drill_generator as generator import FreeCAD import Part import Path +import Path.Base.FeedRate as PathFeedRate +import Path.Base.MachineState as PathMachineState import Path.Op.Base as PathOp import Path.Op.CircularHoleBase as PathCircularHoleBase -import PathFeedRate -import PathMachineState import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/Path/Op/EngraveBase.py b/src/Mod/Path/Path/Op/EngraveBase.py index 6169b12ffe..0a235bb356 100644 --- a/src/Mod/Path/Path/Op/EngraveBase.py +++ b/src/Mod/Path/Path/Op/EngraveBase.py @@ -23,7 +23,6 @@ from lazy_loader.lazy_loader import LazyLoader import Path import Path.Op.Base as PathOp -import PathScripts.PathGeom as PathGeom import Path.Op.Util as PathOpUtil import copy @@ -138,13 +137,13 @@ class ObjectOp(PathOp.ObjectOp): ) first = False - if PathGeom.pointsCoincide(last, edge.valueAt(edge.FirstParameter)): - # if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point): - for cmd in PathGeom.cmdsForEdge(edge): + if Path.Geom.pointsCoincide(last, edge.valueAt(edge.FirstParameter)): + # if Path.Geom.pointsCoincide(last, edge.Vertexes[0].Point): + for cmd in Path.Geom.cmdsForEdge(edge): self.appendCommand(cmd, z, relZ, self.horizFeed) last = edge.Vertexes[-1].Point else: - for cmd in PathGeom.cmdsForEdge(edge, True): + for cmd in Path.Geom.cmdsForEdge(edge, True): self.appendCommand(cmd, z, relZ, self.horizFeed) last = edge.Vertexes[0].Point self.commandlist.append( diff --git a/src/Mod/Path/Path/Op/Gui/Base.py b/src/Mod/Path/Path/Op/Gui/Base.py index c582bd119d..7efbccb3f2 100644 --- a/src/Mod/Path/Path/Op/Gui/Base.py +++ b/src/Mod/Path/Path/Op/Gui/Base.py @@ -23,16 +23,15 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import Path.Op.Base as PathOp import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGeom as PathGeom import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathGui as PathGui import PathScripts.PathJob as PathJob import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSelection as PathSelection import PathScripts.PathSetupSheet as PathSetupSheet -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import importlib from PySide.QtCore import QT_TRANSLATE_NOOP @@ -1000,7 +999,7 @@ class TaskPanelDepthsPage(TaskPanelPage): sub = sel[0].SubObjects[0] if "Vertex" == sub.ShapeType: return sub.Z - if PathGeom.isHorizontal(sub): + if Path.Geom.isHorizontal(sub): if "Edge" == sub.ShapeType: return sub.Vertexes[0].Z if "Face" == sub.ShapeType: diff --git a/src/Mod/Path/Path/Op/Helix.py b/src/Mod/Path/Path/Op/Helix.py index 31f5436180..c6af036821 100644 --- a/src/Mod/Path/Path/Op/Helix.py +++ b/src/Mod/Path/Path/Op/Helix.py @@ -27,9 +27,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Part import Path +import Path.Base.FeedRate as PathFeedRate import Path.Op.Base as PathOp import Path.Op.CircularHoleBase as PathCircularHoleBase -import PathFeedRate __title__ = "Path Helix Drill Operation" diff --git a/src/Mod/Path/Path/Op/Pocket.py b/src/Mod/Path/Path/Op/Pocket.py index 148ea4e0dc..2453f613d0 100644 --- a/src/Mod/Path/Path/Op/Pocket.py +++ b/src/Mod/Path/Path/Op/Pocket.py @@ -31,8 +31,6 @@ import PathScripts.PathUtils as PathUtils # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -PathGeom = LazyLoader("PathScripts.PathGeom", globals(), "PathScripts.PathGeom") - __title__ = "Path 3D Pocket Operation" __author__ = "Yorik van Havre " __url__ = "https://www.freecadweb.org" @@ -254,7 +252,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): removalSolids = [ s for s in rawRemovalShape.Solids - if PathGeom.isRoughly( + if Path.Geom.isRoughly( s.BoundBox.ZMax, rawRemovalShape.BoundBox.ZMax ) ] @@ -893,7 +891,7 @@ def _extrudeBaseDown(base): Extrudes and fuses all non-vertical faces downward to a level 1.0 mm below base ZMin.""" allExtrusions = list() zMin = base.Shape.BoundBox.ZMin - bbFace = PathGeom.makeBoundBoxFace(base.Shape.BoundBox, offset=5.0) + bbFace = Path.Geom.makeBoundBoxFace(base.Shape.BoundBox, offset=5.0) bbFace.translate( FreeCAD.Vector(0.0, 0.0, float(int(base.Shape.BoundBox.ZMin - 5.0))) ) @@ -902,7 +900,7 @@ def _extrudeBaseDown(base): # Make projections of each non-vertical face and extrude it for f in base.Shape.Faces: fbb = f.BoundBox - if not PathGeom.isRoughly(f.normalAt(0, 0).z, 0.0): + if not Path.Geom.isRoughly(f.normalAt(0, 0).z, 0.0): pp = bbFace.makeParallelProjection(f.Wires[0], direction) face = Part.Face(Part.Wire(pp.Edges)) face.translate(FreeCAD.Vector(0.0, 0.0, fbb.ZMin)) diff --git a/src/Mod/Path/Path/Op/PocketShape.py b/src/Mod/Path/Path/Op/PocketShape.py index f5124908e7..62b2c6bc92 100644 --- a/src/Mod/Path/Path/Op/PocketShape.py +++ b/src/Mod/Path/Path/Op/PocketShape.py @@ -25,7 +25,6 @@ import FreeCAD import Path import Path.Op.Base as PathOp import Path.Op.PocketBase as PathPocketBase -import PathScripts.PathGeom as PathGeom # lazily loaded modules @@ -123,16 +122,16 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # Check if selected vertical faces form a loop if len(self.vert) > 0: - self.vertical = PathGeom.combineConnectedShapes(self.vert) + self.vertical = Path.Geom.combineConnectedShapes(self.vert) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] for wire in self.vWires: - w = PathGeom.removeDuplicateEdges(wire) + w = Path.Geom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) - if PathGeom.isRoughly(face.Area, 0): + if Path.Geom.isRoughly(face.Area, 0): Path.Log.error("Vertical faces do not form a loop - ignoring") else: self.horiz.append(face) @@ -149,7 +148,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): self.exts.append(f) # check all faces and see if they are touching/overlapping and combine and simplify - self.horizontal = PathGeom.combineHorizontalFaces(self.horiz) + self.horizontal = Path.Geom.combineHorizontalFaces(self.horiz) # Move all faces to final depth less buffer before extrusion # Small negative buffer is applied to compensate for internal significant digits/rounding issue @@ -202,11 +201,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # Support methods def isVerticalExtrusionFace(self, face): fBB = face.BoundBox - if PathGeom.isRoughly(fBB.ZLength, 0.0): + if Path.Geom.isRoughly(fBB.ZLength, 0.0): return False extr = face.extrude(FreeCAD.Vector(0.0, 0.0, fBB.ZLength)) if hasattr(extr, "Volume"): - if PathGeom.isRoughly(extr.Volume, 0.0): + if Path.Geom.isRoughly(extr.Volume, 0.0): return True return False @@ -219,13 +218,13 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if type(face.Surface) == Part.Plane: Path.Log.debug("type() == Part.Plane") - if PathGeom.isVertical(face.Surface.Axis): + if Path.Geom.isVertical(face.Surface.Axis): Path.Log.debug(" -isVertical()") # it's a flat horizontal face self.horiz.append(face) return True - elif PathGeom.isHorizontal(face.Surface.Axis): + elif Path.Geom.isHorizontal(face.Surface.Axis): Path.Log.debug(" -isHorizontal()") self.vert.append(face) return True @@ -233,7 +232,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): else: return False - elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical( + elif type(face.Surface) == Part.Cylinder and Path.Geom.isVertical( face.Surface.Axis ): Path.Log.debug("type() == Part.Cylinder") diff --git a/src/Mod/Path/Path/Op/Slot.py b/src/Mod/Path/Path/Op/Slot.py index 7d5ec7d81c..0c726bdf02 100644 --- a/src/Mod/Path/Path/Op/Slot.py +++ b/src/Mod/Path/Path/Op/Slot.py @@ -41,7 +41,6 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") Arcs = LazyLoader("draftgeoutils.arcs", globals(), "draftgeoutils.arcs") -PathGeom = LazyLoader("PathScripts.PathGeom", globals(), "PathScripts.PathGeom") if FreeCAD.GuiUp: FreeCADGui = LazyLoader("FreeCADGui", globals(), "FreeCADGui") @@ -947,7 +946,7 @@ class ObjectSlot(PathOp.ObjectOp): norm = shape_1.normalAt(0.0, 0.0) Path.Log.debug("{}.normalAt(): {}".format(sub1, norm)) - if PathGeom.isRoughly(shape_1.BoundBox.ZMax, shape_1.BoundBox.ZMin): + if Path.Geom.isRoughly(shape_1.BoundBox.ZMax, shape_1.BoundBox.ZMin): # Horizontal face if norm.z == 1 or norm.z == -1: pnts = self._processSingleHorizFace(obj, shape_1) diff --git a/src/Mod/Path/Path/Op/ThreadMilling.py b/src/Mod/Path/Path/Op/ThreadMilling.py index fa56a64541..bd634d057d 100644 --- a/src/Mod/Path/Path/Op/ThreadMilling.py +++ b/src/Mod/Path/Path/Op/ThreadMilling.py @@ -26,7 +26,6 @@ import FreeCAD import Path import Path.Op.Base as PathOp import Path.Op.CircularHoleBase as PathCircularHoleBase -import PathScripts.PathGeom as PathGeom import Generators.threadmilling_generator as threadmilling import math from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/Path/Op/Util.py b/src/Mod/Path/Path/Op/Util.py index 2f547986a3..b201b0a447 100644 --- a/src/Mod/Path/Path/Op/Util.py +++ b/src/Mod/Path/Path/Op/Util.py @@ -24,7 +24,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path -import PathScripts.PathGeom as PathGeom import math # lazily loaded modules @@ -116,19 +115,19 @@ def _orientEdges(inEdges): if 1 < len(inEdges): last = e0.valueAt(e0.LastParameter) e1 = inEdges[1] - if not PathGeom.pointsCoincide( + if not Path.Geom.pointsCoincide( last, e1.valueAt(e1.FirstParameter) - ) and not PathGeom.pointsCoincide(last, e1.valueAt(e1.LastParameter)): + ) and not Path.Geom.pointsCoincide(last, e1.valueAt(e1.LastParameter)): debugEdge("# _orientEdges - flip first", e0) - e0 = PathGeom.flipEdge(e0) + e0 = Path.Geom.flipEdge(e0) edges = [e0] last = e0.valueAt(e0.LastParameter) for e in inEdges[1:]: edge = ( e - if PathGeom.pointsCoincide(last, e.valueAt(e.FirstParameter)) - else PathGeom.flipEdge(e) + if Path.Geom.pointsCoincide(last, e.valueAt(e.FirstParameter)) + else Path.Geom.flipEdge(e) ) edges.append(edge) last = edge.valueAt(edge.LastParameter) @@ -172,7 +171,7 @@ def orientWire(w, forward=True): if forward is not None: if forward != _isWireClockwise(wire): Path.Log.track("orientWire - needs flipping") - return PathGeom.flipWire(wire) + return Path.Geom.flipWire(wire) Path.Log.track("orientWire - ok") return wire @@ -196,7 +195,7 @@ def offsetWire(wire, base, offset, forward, Side=None): curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z) ) if base.isInside(new_edge.Vertexes[0].Point, offset / 2, True): - if offset > curve.Radius or PathGeom.isRoughly(offset, curve.Radius): + if offset > curve.Radius or Path.Geom.isRoughly(offset, curve.Radius): # offsetting a hole by its own radius (or more) makes the hole vanish return None if Side: @@ -285,9 +284,9 @@ def offsetWire(wire, base, offset, forward, Side=None): # flip the edge if it's not on the right side of the original edge if forward is not None: v1 = edge.Vertexes[1].Point - p0 - left = PathGeom.Side.Left == PathGeom.Side.of(v0, v1) + left = Path.Geom.Side.Left == Path.Geom.Side.of(v0, v1) if left != forward: - edge = PathGeom.flipEdge(edge) + edge = Path.Geom.flipEdge(edge) return Part.Wire([edge]) # if we get to this point the assumption is that makeOffset2D can deal with the edge @@ -342,7 +341,7 @@ def offsetWire(wire, base, offset, forward, Side=None): p0 = edge.firstVertex().Point p1 = edge.lastVertex().Point for p in insideEndpoints: - if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide(p, p0, 0.01) or Path.Geom.pointsCoincide( p, p1, 0.01 ): return True @@ -361,7 +360,7 @@ def offsetWire(wire, base, offset, forward, Side=None): def isCircleAt(edge, center): """isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.""" if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type(edge.Curve): - return PathGeom.pointsCoincide(edge.Curve.Center, center) + return Path.Geom.pointsCoincide(edge.Curve.Center, center) return False # split offset wire into edges to the left side and edges to the right side @@ -376,7 +375,7 @@ def offsetWire(wire, base, offset, forward, Side=None): # next side, we're done for e in owire.Edges + owire.Edges: if isCircleAt(e, start): - if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): + if Path.Geom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectLeft and leftSideEdges: break collectLeft = True @@ -387,7 +386,7 @@ def offsetWire(wire, base, offset, forward, Side=None): collectLeft = False collectRight = True elif isCircleAt(e, end): - if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): + if Path.Geom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectRight and rightSideEdges: break collectLeft = False @@ -410,7 +409,7 @@ def offsetWire(wire, base, offset, forward, Side=None): edges = leftSideEdges for e in longestWire.Edges: for e0 in rightSideEdges: - if PathGeom.edgesMatch(e, e0): + if Path.Geom.edgesMatch(e, e0): edges = rightSideEdges Path.Log.debug("#use right side edges") if not forward: diff --git a/src/Mod/Path/Path/Op/Vcarve.py b/src/Mod/Path/Path/Op/Vcarve.py index 55f42a48f8..03e5277596 100644 --- a/src/Mod/Path/Path/Op/Vcarve.py +++ b/src/Mod/Path/Path/Op/Vcarve.py @@ -26,7 +26,6 @@ import Path import Path.Op.Base as PathOp import Path.Op.EngraveBase as PathEngraveBase import PathScripts.PathUtils as PathUtils -import PathScripts.PathGeom as PathGeom import PathScripts.PathPreferences as PathPreferences import math from PySide.QtCore import QT_TRANSLATE_NOOP @@ -285,7 +284,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed)) ) for e in edges: - path.extend(PathGeom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed)) + path.extend(Path.Geom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed)) return path diff --git a/src/Mod/Path/Path/Post/Command.py b/src/Mod/Path/Path/Post/Command.py index 5a1eb5f812..2e36110f04 100644 --- a/src/Mod/Path/Path/Post/Command.py +++ b/src/Mod/Path/Path/Post/Command.py @@ -27,9 +27,9 @@ from __future__ import print_function import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import PathScripts.PathJob as PathJob import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import os import re diff --git a/src/Mod/Path/Path/Post/Utils.py b/src/Mod/Path/Path/Post/Utils.py index ec6b21c609..b4090afde3 100644 --- a/src/Mod/Path/Path/Post/Utils.py +++ b/src/Mod/Path/Path/Post/Utils.py @@ -34,8 +34,7 @@ import FreeCAD import Path import Part -from PathMachineState import MachineState -from PathScripts.PathGeom import CmdMoveArc, edgeForCmd, cmdsForEdge +from Path.Base.MachineState import MachineState translate = FreeCAD.Qt.translate FreeCADGui = None @@ -216,16 +215,16 @@ def splitArcs(path): machine = MachineState() for command in path.Commands: - if command.Name not in CmdMoveArc: + if command.Name not in Path.Geom.CmdMoveArc: machine.addCommand(command) results.append(command) continue - edge = edgeForCmd(command, machine.getPosition()) + edge = Path.Geom.edgeForCmd(command, machine.getPosition()) pts = edge.discretize(Deflection=deflection) edges = [Part.makeLine(v1, v2) for v1, v2 in zip(pts, pts[1:])] for edge in edges: - results.extend(cmdsForEdge(edge)) + results.extend(Path.Geom.cmdsForEdge(edge)) machine.addCommand(command) diff --git a/src/Mod/Path/Path/Post/scripts/dxf_post.py b/src/Mod/Path/Path/Post/scripts/dxf_post.py index 16e25ada60..e08d66d398 100644 --- a/src/Mod/Path/Path/Post/scripts/dxf_post.py +++ b/src/Mod/Path/Path/Post/scripts/dxf_post.py @@ -24,7 +24,6 @@ from __future__ import print_function import FreeCAD import Part import Path -import PathScripts.PathGeom as PathGeom import datetime import importDXF @@ -93,8 +92,8 @@ def dxfWrite(objlist, filename): def parse(pathobj): """accepts a Path object. Returns a list of wires""" - feedcommands = PathGeom.CmdMove - rapidcommands = PathGeom.CmdMoveRapid + feedcommands = Path.Geom.CmdMove + rapidcommands = Path.Geom.CmdMoveRapid edges = [] objlist = [] @@ -126,7 +125,7 @@ def parse(pathobj): # feeding move. Build an edge if flatcommand.Name in feedcommands: - edges.append(PathGeom.edgeForCmd(flatcommand, curPoint)) + edges.append(Path.Geom.edgeForCmd(flatcommand, curPoint)) Path.Log.debug("feeding move") # update the curpoint diff --git a/src/Mod/Path/Path/Post/scripts/grbl_post.py b/src/Mod/Path/Path/Post/scripts/grbl_post.py index ab8482fa56..5c27cd5fcf 100755 --- a/src/Mod/Path/Path/Post/scripts/grbl_post.py +++ b/src/Mod/Path/Path/Post/scripts/grbl_post.py @@ -26,11 +26,12 @@ import FreeCAD from FreeCAD import Units +import Path +import Path.Base.Util as PathUtil import Path.Post.Utils as PostUtils import argparse import datetime import shlex -import PathScripts.PathUtil as PathUtil import re diff --git a/src/Mod/Path/Path/Post/scripts/marlin_post.py b/src/Mod/Path/Path/Post/scripts/marlin_post.py index 3a3d06e518..a948ac9df5 100644 --- a/src/Mod/Path/Path/Post/scripts/marlin_post.py +++ b/src/Mod/Path/Path/Post/scripts/marlin_post.py @@ -32,7 +32,8 @@ import argparse import shlex import FreeCAD from FreeCAD import Units -import PathScripts.PathUtil as PathUtil +import Path +import Path.Base.Util as PathUtil import Path.Post.Utils as PostUtils Revised = "2020-11-03" # Revision date for this file. diff --git a/src/Mod/Path/Path/Post/scripts/opensbp_pre.py b/src/Mod/Path/Path/Post/scripts/opensbp_pre.py index 3bd4117d69..f0815d9442 100644 --- a/src/Mod/Path/Path/Post/scripts/opensbp_pre.py +++ b/src/Mod/Path/Path/Post/scripts/opensbp_pre.py @@ -49,7 +49,8 @@ Many other OpenSBP commands not handled """ from __future__ import print_function import FreeCAD -import PathScripts.PathUtil as PathUtil +import Path +import Path.Base.Util as PathUtil import os import Path diff --git a/src/Mod/Path/Path/Post/scripts/rrf_post.py b/src/Mod/Path/Path/Post/scripts/rrf_post.py index cdef35bb10..4d09d7b9ba 100644 --- a/src/Mod/Path/Path/Post/scripts/rrf_post.py +++ b/src/Mod/Path/Path/Post/scripts/rrf_post.py @@ -31,7 +31,8 @@ import argparse import shlex import FreeCAD from FreeCAD import Units -import PathScripts.PathUtil as PathUtil +import Path +import Path.Base.Util as PathUtil import Path.Post.Utils as PostUtils Revised = "2021-10-21" # Revision date for this file. diff --git a/src/Mod/Path/Path/Tool/Bit.py b/src/Mod/Path/Path/Tool/Bit.py index e59734798f..12f8469d7e 100644 --- a/src/Mod/Path/Path/Tool/Bit.py +++ b/src/Mod/Path/Path/Tool/Bit.py @@ -22,9 +22,9 @@ import FreeCAD import Path +import Path.Base.Util as PathUtil +import Path.Base.PropertyBag as PathPropertyBag import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathPropertyBag as PathPropertyBag -import PathScripts.PathUtil as PathUtil import json import os import zipfile diff --git a/src/Mod/Path/Path/Tool/Gui/BitEdit.py b/src/Mod/Path/Path/Tool/Gui/BitEdit.py index 9b0a71087f..30f1a4b1ab 100644 --- a/src/Mod/Path/Path/Tool/Gui/BitEdit.py +++ b/src/Mod/Path/Path/Tool/Gui/BitEdit.py @@ -23,10 +23,10 @@ from PySide import QtCore, QtGui import FreeCADGui import Path +import Path.Base.Util as PathUtil import PathScripts.PathGui as PathGui import PathScripts.PathPreferences as PathPreferences import PathScripts.PathPropertyEditor as PathPropertyEditor -import PathScripts.PathUtil as PathUtil import os import re diff --git a/src/Mod/Path/Path/Tool/Gui/Controller.py b/src/Mod/Path/Path/Tool/Gui/Controller.py index 7f2e9f7bcc..2908b06275 100644 --- a/src/Mod/Path/Path/Tool/Gui/Controller.py +++ b/src/Mod/Path/Path/Tool/Gui/Controller.py @@ -25,11 +25,11 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import Path.Tool.Controller as PathToolController import Path.Tool.Gui.Bit as PathToolBitGui import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui -import PathScripts.PathUtil as PathUtil # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader diff --git a/src/Mod/Path/Path/__init__.py b/src/Mod/Path/Path/__init__.py index e633bc4d34..a1bfe7366d 100644 --- a/src/Mod/Path/Path/__init__.py +++ b/src/Mod/Path/Path/__init__.py @@ -1,3 +1,4 @@ from PathApp import * import Path.Log as Log +import Path.Geom as Geom diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensions.py b/src/Mod/Path/PathScripts/PathFeatureExtensions.py index da4e0c66fc..fd14420453 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensions.py +++ b/src/Mod/Path/PathScripts/PathFeatureExtensions.py @@ -24,7 +24,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Part import Path -import PathScripts.PathGeom as PathGeom import math # lazily loaded modules @@ -56,7 +55,7 @@ def endPoints(edgeOrWire): pts.extend([e.valueAt(e.LastParameter) for e in edgeOrWire.Edges]) unique = [] for p in pts: - cnt = len([p2 for p2 in pts if PathGeom.pointsCoincide(p, p2)]) + cnt = len([p2 for p2 in pts if Path.Geom.pointsCoincide(p, p2)]) if 1 == cnt: unique.append(p) @@ -64,7 +63,7 @@ def endPoints(edgeOrWire): pfirst = edgeOrWire.valueAt(edgeOrWire.FirstParameter) plast = edgeOrWire.valueAt(edgeOrWire.LastParameter) - if PathGeom.pointsCoincide(pfirst, plast): + if Path.Geom.pointsCoincide(pfirst, plast): return None return [pfirst, plast] @@ -73,7 +72,7 @@ def endPoints(edgeOrWire): def includesPoint(p, pts): """includesPoint(p, pts) ... answer True if the collection of pts includes the point p""" for pt in pts: - if PathGeom.pointsCoincide(p, pt): + if Path.Geom.pointsCoincide(p, pt): return True return False @@ -246,7 +245,7 @@ class Extension(object): e2 = e0.copy() off = self.length.Value * direction e2.translate(off) - e2 = PathGeom.flipEdge(e2) + e2 = Path.Geom.flipEdge(e2) e1 = Part.Edge( Part.LineSegment( e0.valueAt(e0.LastParameter), e2.valueAt(e2.FirstParameter) @@ -295,7 +294,7 @@ class Extension(object): tangent = e0.tangentAt(midparam) Path.Log.track("tangent", tangent, self.feature, self.sub) normal = tangent.cross(FreeCAD.Vector(0, 0, 1)) - if PathGeom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)): + if Path.Geom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)): return None return self._getDirectedNormal(e0.valueAt(midparam), normal.normalize()) @@ -326,7 +325,7 @@ class Extension(object): Path.Log.track() length = self.length.Value - if PathGeom.isRoughly(0, length) or not self.sub: + if Path.Geom.isRoughly(0, length) or not self.sub: Path.Log.debug("no extension, length=%.2f, sub=%s" % (length, self.sub)) return None @@ -347,7 +346,7 @@ class Extension(object): if direction is None: return None - if PathGeom.pointsCoincide(normal, direction): + if Path.Geom.pointsCoincide(normal, direction): r = circle.Radius - length else: r = circle.Radius + length @@ -365,7 +364,7 @@ class Extension(object): # Determine if rotational alignment is necessary for new arc rotationAdjustment = arcAdjustmentAngle(edge, e3) - if not PathGeom.isRoughly(rotationAdjustment, 0.0): + if not Path.Geom.isRoughly(rotationAdjustment, 0.0): e3.rotate( edge.Curve.Center, FreeCAD.Vector(0.0, 0.0, 1.0), @@ -424,7 +423,7 @@ class Extension(object): subFace = Part.Face(sub) featFace = Part.Face(feature.Wires[0]) isOutside = True - if not PathGeom.isRoughly(featFace.Area, subFace.Area): + if not Path.Geom.isRoughly(featFace.Area, subFace.Area): length = -1.0 * length isOutside = False @@ -560,9 +559,9 @@ def getExtendOutlineFace( bbx = f.BoundBox zNorm = abs(f.normalAt(0.0, 0.0).z) if ( - PathGeom.isRoughly(zNorm, 1.0) - and PathGeom.isRoughly(bbx.ZMax - bbx.ZMin, 0.0) - and PathGeom.isRoughly(bbx.ZMin, face.BoundBox.ZMin) + Path.Geom.isRoughly(zNorm, 1.0) + and Path.Geom.isRoughly(bbx.ZMax - bbx.ZMin, 0.0) + and Path.Geom.isRoughly(bbx.ZMin, face.BoundBox.ZMin) ): if bbx.ZMin < zmin: bottom_faces.append(f) @@ -610,7 +609,7 @@ def getWaterlineFace(base_shape, face): # Get top face(s) of envelope at face height rawList = list() for f in env.Faces: - if PathGeom.isRoughly(f.BoundBox.ZMin, faceHeight): + if Path.Geom.isRoughly(f.BoundBox.ZMin, faceHeight): rawList.append(f) # make compound and extrude downward rawComp = Part.makeCompound(rawList) diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py index 5d4f9d8e0c..e43ab53242 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py +++ b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py @@ -27,7 +27,6 @@ import FreeCADGui import Path import Path.Op.Gui.Base as PathOpGui import PathScripts.PathFeatureExtensions as FeatureExtensions -import PathScripts.PathGeom as PathGeom import PathScripts.PathGui as PathGui # lazily loaded modules @@ -361,13 +360,13 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): if extendCorners: def edgesMatchShape(e0, e1): - flipped = PathGeom.flipEdge(e1) + flipped = Path.Geom.flipEdge(e1) if flipped: - return PathGeom.edgesMatch(e0, e1) or PathGeom.edgesMatch( + return Path.Geom.edgesMatch(e0, e1) or Path.Geom.edgesMatch( e0, flipped ) else: - return PathGeom.edgesMatch(e0, e1) + return Path.Geom.edgesMatch(e0, e1) self.extensionEdges = extensionEdges Path.Log.debug("extensionEdges.values(): {}".format(extensionEdges.values())) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index d216d3fa42..320ac6092f 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -23,8 +23,7 @@ import FreeCAD import FreeCADGui import Path -import PathScripts.PathGeom as PathGeom -import PathScripts.PathUtil as PathUtil +import Path.Base.Util as PathUtil from PySide import QtGui, QtCore from PySide import QtCore, QtGui @@ -75,7 +74,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): attrValue = attr.Value if hasattr(attr, "Value") else attr isDiff = False - if not PathGeom.isRoughly(attrValue, value): + if not Path.Geom.isRoughly(attrValue, value): isDiff = True else: if hasattr(obj, "ExpressionEngine"): @@ -85,7 +84,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): exprSet = True Path.Log.debug('prop = "expression": {} = "{}"'.format(prp, expr)) value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value - if not PathGeom.isRoughly(attrValue, value): + if not Path.Geom.isRoughly(attrValue, value): isDiff = True break if exprSet: diff --git a/src/Mod/Path/PathScripts/PathIconViewProvider.py b/src/Mod/Path/PathScripts/PathIconViewProvider.py index 9238454a5d..57ae10e895 100644 --- a/src/Mod/Path/PathScripts/PathIconViewProvider.py +++ b/src/Mod/Path/PathScripts/PathIconViewProvider.py @@ -22,8 +22,8 @@ import FreeCAD import Path +import Path.Base.Util as PathUtil import PathGui -import PathScripts.PathUtil as PathUtil import importlib __title__ = "Path Icon ViewProvider" diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 3060247e23..0cda5d0c47 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -24,12 +24,12 @@ from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Base.Util as PathUtil from Path.Post.Processor import PostProcessor import Path.Tool.Controller as PathToolController import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock -import PathScripts.PathUtil as PathUtil import json import time import Path diff --git a/src/Mod/Path/PathScripts/PathJobCmd.py b/src/Mod/Path/PathScripts/PathJobCmd.py index c1b15de56d..e3a1adddba 100644 --- a/src/Mod/Path/PathScripts/PathJobCmd.py +++ b/src/Mod/Path/PathScripts/PathJobCmd.py @@ -25,11 +25,11 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import PathScripts.PathJob as PathJob import PathScripts.PathJobDlg as PathJobDlg import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock -import PathScripts.PathUtil as PathUtil import json import os diff --git a/src/Mod/Path/PathScripts/PathJobDlg.py b/src/Mod/Path/PathScripts/PathJobDlg.py index b3dd2b6721..d21b1761fe 100644 --- a/src/Mod/Path/PathScripts/PathJobDlg.py +++ b/src/Mod/Path/PathScripts/PathJobDlg.py @@ -25,10 +25,10 @@ from collections import Counter import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import PathScripts.PathJob as PathJob import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock -import PathScripts.PathUtil as PathUtil import glob import os diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index e172646a7d..e17c30d375 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -28,9 +28,9 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import Path.Tool.Gui.Bit as PathToolBitGui import Path.Tool.Gui.Controller as PathToolControllerGui -import PathScripts.PathGeom as PathGeom import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd @@ -38,7 +38,6 @@ import PathScripts.PathJobDlg as PathJobDlg import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathStock as PathStock -import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import json import math @@ -1087,7 +1086,7 @@ class TaskPanel: if flip: v = axis.negative() - if PathGeom.pointsCoincide(abs(v), abs(normal)): + if Path.Geom.pointsCoincide(abs(v), abs(normal)): # Selection is already aligned with the axis of rotation leading # to a (0,0,0) cross product for rotation. # --> Need to flip the object around one of the "other" axis. @@ -1128,9 +1127,9 @@ class TaskPanel: % (normal.x, normal.y, normal.z, sub.Orientation) ) - if PathGeom.pointsCoincide(axis, normal): + if Path.Geom.pointsCoincide(axis, normal): alignSel(sel, normal, True) - elif PathGeom.pointsCoincide(axis, FreeCAD.Vector() - normal): + elif Path.Geom.pointsCoincide(axis, FreeCAD.Vector() - normal): alignSel(sel, FreeCAD.Vector() - normal, True) else: alignSel(sel, normal) @@ -1139,9 +1138,9 @@ class TaskPanel: normal = ( sub.Vertexes[1].Point - sub.Vertexes[0].Point ).normalize() - if PathGeom.pointsCoincide( + if Path.Geom.pointsCoincide( axis, normal - ) or PathGeom.pointsCoincide(axis, FreeCAD.Vector() - normal): + ) or Path.Geom.pointsCoincide(axis, FreeCAD.Vector() - normal): # Don't really know the orientation of an edge, so let's just flip the object # and if the user doesn't like it they can flip again alignSel(sel, normal, True) diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py index bf7be075e7..9d45778cdd 100644 --- a/src/Mod/Path/PathScripts/PathPropertyBagGui.py +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -24,10 +24,10 @@ from PySide import QtCore, QtGui import FreeCAD import FreeCADGui import Path +import Path.Base.PropertyBag as PathPropertyBag +import Path.Base.Util as PathUtil import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathPropertyBag as PathPropertyBag import PathScripts.PathPropertyEditor as PathPropertyEditor -import PathScripts.PathUtil as PathUtil import re diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index fd980f86e2..02c79bdaf3 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -33,8 +33,8 @@ from PySide import QtCore, QtGui import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import PathScripts -import PathScripts.PathUtil as PathUtil import PathScripts.PathPreferences as PathPreferences from collections import Counter from datetime import datetime diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/PathScripts/PathSetupSheet.py index 656ebac45c..483cd9dea6 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/PathScripts/PathSetupSheet.py @@ -22,9 +22,8 @@ import FreeCAD import Path -import PathScripts.PathGeom as PathGeom +import Path.Base.Util as PathUtil import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype -import PathScripts.PathUtil as PathUtil from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Setup Sheet for a Job." @@ -234,7 +233,7 @@ class SetupSheet: return None def hasDefaultToolRapids(self): - return PathGeom.isRoughly(self.obj.VertRapid.Value, 0) and PathGeom.isRoughly( + return Path.Geom.isRoughly(self.obj.VertRapid.Value, 0) and Path.Geom.isRoughly( self.obj.HorizRapid.Value, 0 ) diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/PathScripts/PathSetupSheetGui.py index 949dc3fc71..3aa4ea3a65 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetGui.py @@ -23,12 +23,12 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Util as PathUtil import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathGui as PathGui import PathScripts.PathIconViewProvider as PathIconViewProvider import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathSetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui -import PathScripts.PathUtil as PathUtil from PySide import QtCore, QtGui diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index ab462c7d07..52aec327a1 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -22,10 +22,9 @@ import FreeCAD import Path +import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGeom as PathGeom -import PathScripts.PathUtil as PathUtil import PathScripts.PathJob as PathJob import PathSimulator import math @@ -399,14 +398,14 @@ class PathSimulation: self.PerformCutBoolean() def RapidMove(self, cmd, curpos): - path = PathGeom.edgeForCmd(cmd, curpos) # hack to overcome occ bug + path = Path.Geom.edgeForCmd(cmd, curpos) # hack to overcome occ bug if path is None: return curpos return path.valueAt(path.LastParameter) # get a solid representation of a tool going along path def GetPathSolid(self, tool, cmd, pos): - toolPath = PathGeom.edgeForCmd(cmd, pos) + toolPath = Path.Geom.edgeForCmd(cmd, pos) startDir = toolPath.tangentAt(0) startDir[2] = 0.0 endPos = toolPath.valueAt(toolPath.LastParameter) diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 34c2f3b19e..dd06227c6a 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -25,7 +25,6 @@ import FreeCAD from FreeCAD import Vector from PySide import QtCore import Path -import PathScripts.PathGeom as PathGeom import PathScripts.PathJob as PathJob import math from numpy import linspace @@ -122,8 +121,8 @@ def horizontalEdgeLoop(obj, edge): loops = [ w for w in wires - if all(PathGeom.isHorizontal(e) for e in w.Edges) - and PathGeom.isHorizontal(Part.Face(w)) + if all(Path.Geom.isHorizontal(e) for e in w.Edges) + and Path.Geom.isHorizontal(Part.Face(w)) ] if len(loops) == 1: return loops[0] @@ -146,7 +145,7 @@ def horizontalFaceLoop(obj, face, faceList=None): faces = [ "Face%d" % (i + 1) for i, f in enumerate(obj.Shape.Faces) - if any(e.hashCode() in hashes for e in f.Edges) and PathGeom.isVertical(f) + if any(e.hashCode() in hashes for e in f.Edges) and Path.Geom.isVertical(f) ] if faceList and not all(f in faces for f in faceList): @@ -161,7 +160,7 @@ def horizontalFaceLoop(obj, face, faceList=None): # trace-backs single edge spikes don't contribute to the bound box uniqueEdges = [] for edge in outline.Edges: - if any(PathGeom.edgesMatch(edge, e) for e in uniqueEdges): + if any(Path.Geom.edgesMatch(edge, e) for e in uniqueEdges): continue uniqueEdges.append(edge) w = Part.Wire(uniqueEdges) @@ -172,10 +171,10 @@ def horizontalFaceLoop(obj, face, faceList=None): bb2 = w.BoundBox if ( w.isClosed() - and PathGeom.isRoughly(bb1.XMin, bb2.XMin) - and PathGeom.isRoughly(bb1.XMax, bb2.XMax) - and PathGeom.isRoughly(bb1.YMin, bb2.YMin) - and PathGeom.isRoughly(bb1.YMax, bb2.YMax) + and Path.Geom.isRoughly(bb1.XMin, bb2.XMin) + and Path.Geom.isRoughly(bb1.XMax, bb2.XMax) + and Path.Geom.isRoughly(bb1.YMin, bb2.YMin) + and Path.Geom.isRoughly(bb1.YMax, bb2.YMax) ): return faces return None @@ -721,7 +720,7 @@ class depth_params(object): def __filter_roughly_equal_depths(self, depths): """Depths arrive sorted from largest to smallest, positive to negative. - Return unique list of depths, using PathGeom.isRoughly() method to determine + Return unique list of depths, using Path.Geom.isRoughly() method to determine if the two values are equal. Only one of two consecutive equals are removed. The assumption is that there are not enough consecutively roughly-equal depths @@ -731,7 +730,7 @@ class depth_params(object): depthcopy = sorted(depths) # make a copy and sort low to high keep = [depthcopy[0]] for depth in depthcopy[1:]: - if not PathGeom.isRoughly(depth, keep[-1]): + if not Path.Geom.isRoughly(depth, keep[-1]): keep.append(depth) keep.reverse() # reverse results back high to low return keep diff --git a/src/Mod/Path/PathTests/PathTestUtils.py b/src/Mod/Path/PathTests/PathTestUtils.py index 492d44f77a..5df774fd71 100644 --- a/src/Mod/Path/PathTests/PathTestUtils.py +++ b/src/Mod/Path/PathTests/PathTestUtils.py @@ -22,7 +22,7 @@ import FreeCAD import Part -import PathScripts.PathGeom as PathGeom +import Path import math import unittest @@ -73,11 +73,11 @@ class PathTestBase(unittest.TestCase): self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1) self.assertCoincide(edge.valueAt(edge.LastParameter), pt2) ptm = edge.valueAt((edge.LastParameter + edge.FirstParameter) / 2) - side = PathGeom.Side.of(pt2 - pt1, ptm - pt1) + side = Path.Geom.Side.of(pt2 - pt1, ptm - pt1) if "CW" == direction: - self.assertEqual(side, PathGeom.Side.Left) + self.assertEqual(side, Path.Geom.Side.Left) else: - self.assertEqual(side, PathGeom.Side.Right) + self.assertEqual(side, Path.Geom.Side.Right) def assertCircle(self, edge, pt, r): """Verivy that edge is a circle at given location.""" @@ -162,7 +162,7 @@ class PathTestBase(unittest.TestCase): e.FirstParameter + (e.LastParameter - e.FirstParameter) * fraction ) - if PathGeom.pointsCoincide(e1.Vertexes[0].Point, e2.Vertexes[0].Point): + if Path.Geom.pointsCoincide(e1.Vertexes[0].Point, e2.Vertexes[0].Point): self.assertCoincide(e1.Vertexes[-1].Point, e2.Vertexes[-1].Point) self.assertCoincide(valueAt(e1, 0.10), valueAt(e2, 0.10)) self.assertCoincide(valueAt(e1, 0.20), valueAt(e2, 0.20)) diff --git a/src/Mod/Path/PathTests/TestPathAdaptive.py b/src/Mod/Path/PathTests/TestPathAdaptive.py index 12fb52f546..4805ba7856 100644 --- a/src/Mod/Path/PathTests/TestPathAdaptive.py +++ b/src/Mod/Path/PathTests/TestPathAdaptive.py @@ -26,7 +26,6 @@ import FreeCAD import Part import Path.Op.Adaptive as PathAdaptive import PathScripts.PathJob as PathJob -import PathScripts.PathGeom as PathGeom from PathTests.PathTestUtils import PathTestBase if FreeCAD.GuiUp: diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 87c93f5852..0a2fa38ef6 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -22,7 +22,6 @@ import Part import Path -import PathScripts.PathGeom as PathGeom import math from FreeCAD import Vector @@ -34,186 +33,186 @@ class TestPathGeom(PathTestBase): def test00(self): """Verify getAngle functionality.""" - self.assertRoughly(PathGeom.getAngle(Vector(1, 0, 0)), 0) - self.assertRoughly(PathGeom.getAngle(Vector(1, 1, 0)), math.pi / 4) - self.assertRoughly(PathGeom.getAngle(Vector(0, 1, 0)), math.pi / 2) - self.assertRoughly(PathGeom.getAngle(Vector(-1, 1, 0)), 3 * math.pi / 4) - self.assertRoughly(PathGeom.getAngle(Vector(-1, 0, 0)), math.pi) - self.assertRoughly(PathGeom.getAngle(Vector(-1, -1, 0)), -3 * math.pi / 4) - self.assertRoughly(PathGeom.getAngle(Vector(0, -1, 0)), -math.pi / 2) - self.assertRoughly(PathGeom.getAngle(Vector(1, -1, 0)), -math.pi / 4) + self.assertRoughly(Path.Geom.getAngle(Vector(1, 0, 0)), 0) + self.assertRoughly(Path.Geom.getAngle(Vector(1, 1, 0)), math.pi / 4) + self.assertRoughly(Path.Geom.getAngle(Vector(0, 1, 0)), math.pi / 2) + self.assertRoughly(Path.Geom.getAngle(Vector(-1, 1, 0)), 3 * math.pi / 4) + self.assertRoughly(Path.Geom.getAngle(Vector(-1, 0, 0)), math.pi) + self.assertRoughly(Path.Geom.getAngle(Vector(-1, -1, 0)), -3 * math.pi / 4) + self.assertRoughly(Path.Geom.getAngle(Vector(0, -1, 0)), -math.pi / 2) + self.assertRoughly(Path.Geom.getAngle(Vector(1, -1, 0)), -math.pi / 4) def test01(self): """Verify diffAngle functionality.""" self.assertRoughly( - PathGeom.diffAngle(0, +0 * math.pi / 4, "CW") / math.pi, 0 / 4.0 + Path.Geom.diffAngle(0, +0 * math.pi / 4, "CW") / math.pi, 0 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, +3 * math.pi / 4, "CW") / math.pi, 5 / 4.0 + Path.Geom.diffAngle(0, +3 * math.pi / 4, "CW") / math.pi, 5 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, -3 * math.pi / 4, "CW") / math.pi, 3 / 4.0 + Path.Geom.diffAngle(0, -3 * math.pi / 4, "CW") / math.pi, 3 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, +4 * math.pi / 4, "CW") / math.pi, 4 / 4.0 + Path.Geom.diffAngle(0, +4 * math.pi / 4, "CW") / math.pi, 4 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, +0 * math.pi / 4, "CCW") / math.pi, 0 / 4.0 + Path.Geom.diffAngle(0, +0 * math.pi / 4, "CCW") / math.pi, 0 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, +3 * math.pi / 4, "CCW") / math.pi, 3 / 4.0 + Path.Geom.diffAngle(0, +3 * math.pi / 4, "CCW") / math.pi, 3 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, -3 * math.pi / 4, "CCW") / math.pi, 5 / 4.0 + Path.Geom.diffAngle(0, -3 * math.pi / 4, "CCW") / math.pi, 5 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(0, +4 * math.pi / 4, "CCW") / math.pi, 4 / 4.0 + Path.Geom.diffAngle(0, +4 * math.pi / 4, "CCW") / math.pi, 4 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(+math.pi / 4, +0 * math.pi / 4, "CW") / math.pi, 1 / 4.0 + Path.Geom.diffAngle(+math.pi / 4, +0 * math.pi / 4, "CW") / math.pi, 1 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(+math.pi / 4, +3 * math.pi / 4, "CW") / math.pi, 6 / 4.0 + Path.Geom.diffAngle(+math.pi / 4, +3 * math.pi / 4, "CW") / math.pi, 6 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(+math.pi / 4, -1 * math.pi / 4, "CW") / math.pi, 2 / 4.0 + Path.Geom.diffAngle(+math.pi / 4, -1 * math.pi / 4, "CW") / math.pi, 2 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(-math.pi / 4, +0 * math.pi / 4, "CW") / math.pi, 7 / 4.0 + Path.Geom.diffAngle(-math.pi / 4, +0 * math.pi / 4, "CW") / math.pi, 7 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(-math.pi / 4, +3 * math.pi / 4, "CW") / math.pi, 4 / 4.0 + Path.Geom.diffAngle(-math.pi / 4, +3 * math.pi / 4, "CW") / math.pi, 4 / 4.0 ) self.assertRoughly( - PathGeom.diffAngle(-math.pi / 4, -1 * math.pi / 4, "CW") / math.pi, 0 / 4.0 + Path.Geom.diffAngle(-math.pi / 4, -1 * math.pi / 4, "CW") / math.pi, 0 / 4.0 ) self.assertRoughly( - PathGeom.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( - PathGeom.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( - PathGeom.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( - PathGeom.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( - PathGeom.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( - PathGeom.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): """Verify isVertical/isHorizontal for Vector""" - self.assertTrue(PathGeom.isVertical(Vector(0, 0, 1))) - self.assertTrue(PathGeom.isVertical(Vector(0, 0, -1))) - self.assertFalse(PathGeom.isVertical(Vector(1, 0, 1))) - self.assertFalse(PathGeom.isVertical(Vector(1, 0, -1))) + self.assertTrue(Path.Geom.isVertical(Vector(0, 0, 1))) + self.assertTrue(Path.Geom.isVertical(Vector(0, 0, -1))) + self.assertFalse(Path.Geom.isVertical(Vector(1, 0, 1))) + self.assertFalse(Path.Geom.isVertical(Vector(1, 0, -1))) - self.assertTrue(PathGeom.isHorizontal(Vector(1, 0, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(-1, 0, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(0, 1, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(0, -1, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(1, 1, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(-1, 1, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(1, -1, 0))) - self.assertTrue(PathGeom.isHorizontal(Vector(-1, -1, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(1, 0, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(-1, 0, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(0, 1, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(0, -1, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(1, 1, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(-1, 1, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(1, -1, 0))) + self.assertTrue(Path.Geom.isHorizontal(Vector(-1, -1, 0))) - self.assertFalse(PathGeom.isHorizontal(Vector(0, 1, 1))) - self.assertFalse(PathGeom.isHorizontal(Vector(0, -1, 1))) - self.assertFalse(PathGeom.isHorizontal(Vector(0, 1, -1))) - self.assertFalse(PathGeom.isHorizontal(Vector(0, -1, -1))) + self.assertFalse(Path.Geom.isHorizontal(Vector(0, 1, 1))) + self.assertFalse(Path.Geom.isHorizontal(Vector(0, -1, 1))) + self.assertFalse(Path.Geom.isHorizontal(Vector(0, 1, -1))) + self.assertFalse(Path.Geom.isHorizontal(Vector(0, -1, -1))) def test03(self): """Verify isVertical/isHorizontal for Edges""" # lines self.assertTrue( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.LineSegment(Vector(-1, -1, -1), Vector(-1, -1, 8))) ) ) self.assertFalse( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.LineSegment(Vector(-1, -1, -1), Vector(1, -1, 8))) ) ) self.assertFalse( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.LineSegment(Vector(-1, -1, -1), Vector(-1, 1, 8))) ) ) self.assertTrue( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.LineSegment(Vector(1, -1, -1), Vector(-1, -1, -1))) ) ) self.assertTrue( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.LineSegment(Vector(-1, 1, -1), Vector(-1, -1, -1))) ) ) self.assertTrue( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.LineSegment(Vector(1, 1, -1), Vector(-1, -1, -1))) ) ) self.assertFalse( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.LineSegment(Vector(1, -1, -1), Vector(1, -1, 8))) ) ) self.assertFalse( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.LineSegment(Vector(-1, 1, -1), Vector(-1, 1, 8))) ) ) # circles self.assertTrue( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(0, 1, 0))) ) ) self.assertTrue( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 0, 0))) ) ) self.assertTrue( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 1, 0))) ) ) self.assertFalse( - PathGeom.isVertical( + Path.Geom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 1, 1))) ) ) self.assertTrue( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(0, 0, 1))) ) ) self.assertFalse( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(0, 1, 1))) ) ) self.assertFalse( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 0, 1))) ) ) self.assertFalse( - PathGeom.isHorizontal( + Path.Geom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 1, 1))) ) ) @@ -223,21 +222,21 @@ class TestPathGeom(PathTestBase): # and now I disable the tests because they seem to fail on OCE # bezier = Part.BezierCurve() # bezier.setPoles([Vector(), Vector(1,1,0), Vector(2,1,0), Vector(2,2,0)]) - # self.assertTrue(PathGeom.isHorizontal(Part.Edge(bezier))) - # self.assertFalse(PathGeom.isVertical(Part.Edge(bezier))) + # self.assertTrue(Path.Geom.isHorizontal(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isVertical(Part.Edge(bezier))) # bezier.setPoles([Vector(), Vector(1,1,1), Vector(2,1,0), Vector(2,2,0)]) - # self.assertFalse(PathGeom.isHorizontal(Part.Edge(bezier))) - # self.assertFalse(PathGeom.isVertical(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isHorizontal(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isVertical(Part.Edge(bezier))) # bezier.setPoles([Vector(), Vector(1,1,0), Vector(2,1,1), Vector(2,2,0)]) - # self.assertFalse(PathGeom.isHorizontal(Part.Edge(bezier))) - # self.assertFalse(PathGeom.isVertical(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isHorizontal(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isVertical(Part.Edge(bezier))) # bezier.setPoles([Vector(), Vector(1,1,0), Vector(2,1,0), Vector(2,2,1)]) - # self.assertFalse(PathGeom.isHorizontal(Part.Edge(bezier))) - # self.assertFalse(PathGeom.isVertical(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isHorizontal(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isVertical(Part.Edge(bezier))) # # bezier.setPoles([Vector(), Vector(1,1,1), Vector(2,2,2), Vector(0,0,3)]) - # self.assertFalse(PathGeom.isHorizontal(Part.Edge(bezier))) - # self.assertTrue(PathGeom.isVertical(Part.Edge(bezier))) + # self.assertFalse(Path.Geom.isHorizontal(Part.Edge(bezier))) + # self.assertTrue(Path.Geom.isVertical(Part.Edge(bezier))) def test04(self): """Verify isVertical/isHorizontal for faces""" @@ -250,19 +249,19 @@ class TestPathGeom(PathTestBase): xzPlane = Part.makePlane(100, 100, Vector(), Vector(1, 0, 1)) yzPlane = Part.makePlane(100, 100, Vector(), Vector(0, 1, 1)) - self.assertTrue(PathGeom.isVertical(xPlane)) - self.assertTrue(PathGeom.isVertical(yPlane)) - self.assertFalse(PathGeom.isVertical(zPlane)) - self.assertTrue(PathGeom.isVertical(xyPlane)) - self.assertFalse(PathGeom.isVertical(xzPlane)) - self.assertFalse(PathGeom.isVertical(yzPlane)) + self.assertTrue(Path.Geom.isVertical(xPlane)) + self.assertTrue(Path.Geom.isVertical(yPlane)) + self.assertFalse(Path.Geom.isVertical(zPlane)) + self.assertTrue(Path.Geom.isVertical(xyPlane)) + self.assertFalse(Path.Geom.isVertical(xzPlane)) + self.assertFalse(Path.Geom.isVertical(yzPlane)) - self.assertFalse(PathGeom.isHorizontal(xPlane)) - self.assertFalse(PathGeom.isHorizontal(yPlane)) - self.assertTrue(PathGeom.isHorizontal(zPlane)) - self.assertFalse(PathGeom.isHorizontal(xyPlane)) - self.assertFalse(PathGeom.isHorizontal(xzPlane)) - self.assertFalse(PathGeom.isHorizontal(yzPlane)) + self.assertFalse(Path.Geom.isHorizontal(xPlane)) + self.assertFalse(Path.Geom.isHorizontal(yPlane)) + self.assertTrue(Path.Geom.isHorizontal(zPlane)) + self.assertFalse(Path.Geom.isHorizontal(xyPlane)) + self.assertFalse(Path.Geom.isHorizontal(xzPlane)) + self.assertFalse(Path.Geom.isHorizontal(yzPlane)) # cylinders xCylinder = [ @@ -296,92 +295,92 @@ class TestPathGeom(PathTestBase): if type(f.Surface) == Part.Cylinder ][0] - self.assertTrue(PathGeom.isHorizontal(xCylinder)) - self.assertTrue(PathGeom.isHorizontal(yCylinder)) - self.assertFalse(PathGeom.isHorizontal(zCylinder)) - self.assertTrue(PathGeom.isHorizontal(xyCylinder)) - self.assertFalse(PathGeom.isHorizontal(xzCylinder)) - self.assertFalse(PathGeom.isHorizontal(yzCylinder)) + self.assertTrue(Path.Geom.isHorizontal(xCylinder)) + self.assertTrue(Path.Geom.isHorizontal(yCylinder)) + self.assertFalse(Path.Geom.isHorizontal(zCylinder)) + self.assertTrue(Path.Geom.isHorizontal(xyCylinder)) + self.assertFalse(Path.Geom.isHorizontal(xzCylinder)) + self.assertFalse(Path.Geom.isHorizontal(yzCylinder)) def test07(self): """Verify speed interpolation works for different pitches""" # horizontal self.assertRoughly( - 100, PathGeom.speedBetweenPoints(Vector(), Vector(1, 1, 0), 100, 50) + 100, Path.Geom.speedBetweenPoints(Vector(), Vector(1, 1, 0), 100, 50) ) self.assertRoughly( - 100, PathGeom.speedBetweenPoints(Vector(1, 1, 0), Vector(), 100, 50) + 100, Path.Geom.speedBetweenPoints(Vector(1, 1, 0), Vector(), 100, 50) ) # vertical self.assertRoughly( - 50, PathGeom.speedBetweenPoints(Vector(), Vector(0, 0, 1), 100, 50) + 50, Path.Geom.speedBetweenPoints(Vector(), Vector(0, 0, 1), 100, 50) ) self.assertRoughly( - 50, PathGeom.speedBetweenPoints(Vector(0, 0, 1), Vector(), 100, 50) + 50, Path.Geom.speedBetweenPoints(Vector(0, 0, 1), Vector(), 100, 50) ) # 45° self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(), Vector(1, 0, 1), 100, 50) + 75, Path.Geom.speedBetweenPoints(Vector(), Vector(1, 0, 1), 100, 50) ) self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(), Vector(0, 1, 1), 100, 50) + 75, Path.Geom.speedBetweenPoints(Vector(), Vector(0, 1, 1), 100, 50) ) self.assertRoughly( 75, - PathGeom.speedBetweenPoints(Vector(), Vector(0.707, 0.707, 1), 100, 50), + Path.Geom.speedBetweenPoints(Vector(), Vector(0.707, 0.707, 1), 100, 50), 0.01, ) self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(1, 0, 1), Vector(), 100, 50) + 75, Path.Geom.speedBetweenPoints(Vector(1, 0, 1), Vector(), 100, 50) ) self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(0, 1, 1), Vector(), 100, 50) + 75, Path.Geom.speedBetweenPoints(Vector(0, 1, 1), Vector(), 100, 50) ) self.assertRoughly( 75, - PathGeom.speedBetweenPoints(Vector(0.707, 0.707, 1), Vector(), 100, 50), + Path.Geom.speedBetweenPoints(Vector(0.707, 0.707, 1), Vector(), 100, 50), 0.01, ) # 30° self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(), Vector(0.5774, 0, 1), 100, 50), + Path.Geom.speedBetweenPoints(Vector(), Vector(0.5774, 0, 1), 100, 50), 0.01, ) self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(), Vector(0, 0.5774, 1), 100, 50), + Path.Geom.speedBetweenPoints(Vector(), Vector(0, 0.5774, 1), 100, 50), 0.01, ) self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(0.5774, 0, 1), Vector(), 100, 50), + Path.Geom.speedBetweenPoints(Vector(0.5774, 0, 1), Vector(), 100, 50), 0.01, ) self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(0, 0.5774, 1), Vector(), 100, 50), + Path.Geom.speedBetweenPoints(Vector(0, 0.5774, 1), Vector(), 100, 50), 0.01, ) # 60° self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(), Vector(1, 0, 0.5774), 100, 50), + Path.Geom.speedBetweenPoints(Vector(), Vector(1, 0, 0.5774), 100, 50), 0.01, ) self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(), Vector(0, 1, 0.5774), 100, 50), + Path.Geom.speedBetweenPoints(Vector(), Vector(0, 1, 0.5774), 100, 50), 0.01, ) self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(1, 0, 0.5774), Vector(), 100, 50), + Path.Geom.speedBetweenPoints(Vector(1, 0, 0.5774), Vector(), 100, 50), 0.01, ) self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(0, 1, 0.5774), Vector(), 100, 50), + Path.Geom.speedBetweenPoints(Vector(0, 1, 0.5774), Vector(), 100, 50), 0.01, ) @@ -389,81 +388,81 @@ class TestPathGeom(PathTestBase): """Verify speed interpolation works for different pitches if vSpeed > hSpeed""" # horizontal self.assertRoughly( - 50, PathGeom.speedBetweenPoints(Vector(), Vector(1, 1, 0), 50, 100) + 50, Path.Geom.speedBetweenPoints(Vector(), Vector(1, 1, 0), 50, 100) ) self.assertRoughly( - 50, PathGeom.speedBetweenPoints(Vector(1, 1, 0), Vector(), 50, 100) + 50, Path.Geom.speedBetweenPoints(Vector(1, 1, 0), Vector(), 50, 100) ) # vertical self.assertRoughly( - 100, PathGeom.speedBetweenPoints(Vector(), Vector(0, 0, 1), 50, 100) + 100, Path.Geom.speedBetweenPoints(Vector(), Vector(0, 0, 1), 50, 100) ) self.assertRoughly( - 100, PathGeom.speedBetweenPoints(Vector(0, 0, 1), Vector(), 50, 100) + 100, Path.Geom.speedBetweenPoints(Vector(0, 0, 1), Vector(), 50, 100) ) # 45° self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(), Vector(1, 0, 1), 50, 100) + 75, Path.Geom.speedBetweenPoints(Vector(), Vector(1, 0, 1), 50, 100) ) self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(), Vector(0, 1, 1), 50, 100) + 75, Path.Geom.speedBetweenPoints(Vector(), Vector(0, 1, 1), 50, 100) ) self.assertRoughly( 75, - PathGeom.speedBetweenPoints(Vector(), Vector(0.707, 0.707, 1), 50, 100), + Path.Geom.speedBetweenPoints(Vector(), Vector(0.707, 0.707, 1), 50, 100), 0.01, ) self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(1, 0, 1), Vector(), 50, 100) + 75, Path.Geom.speedBetweenPoints(Vector(1, 0, 1), Vector(), 50, 100) ) self.assertRoughly( - 75, PathGeom.speedBetweenPoints(Vector(0, 1, 1), Vector(), 50, 100) + 75, Path.Geom.speedBetweenPoints(Vector(0, 1, 1), Vector(), 50, 100) ) self.assertRoughly( 75, - PathGeom.speedBetweenPoints(Vector(0.707, 0.707, 1), Vector(), 50, 100), + Path.Geom.speedBetweenPoints(Vector(0.707, 0.707, 1), Vector(), 50, 100), 0.01, ) # 30° self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(), Vector(0.5774, 0, 1), 50, 100), + Path.Geom.speedBetweenPoints(Vector(), Vector(0.5774, 0, 1), 50, 100), 0.01, ) self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(), Vector(0, 0.5774, 1), 50, 100), + Path.Geom.speedBetweenPoints(Vector(), Vector(0, 0.5774, 1), 50, 100), 0.01, ) self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(0.5774, 0, 1), Vector(), 50, 100), + Path.Geom.speedBetweenPoints(Vector(0.5774, 0, 1), Vector(), 50, 100), 0.01, ) self.assertRoughly( 83.33, - PathGeom.speedBetweenPoints(Vector(0, 0.5774, 1), Vector(), 50, 100), + Path.Geom.speedBetweenPoints(Vector(0, 0.5774, 1), Vector(), 50, 100), 0.01, ) # 60° self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(), Vector(1, 0, 0.5774), 50, 100), + Path.Geom.speedBetweenPoints(Vector(), Vector(1, 0, 0.5774), 50, 100), 0.01, ) self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(), Vector(0, 1, 0.5774), 50, 100), + Path.Geom.speedBetweenPoints(Vector(), Vector(0, 1, 0.5774), 50, 100), 0.01, ) self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(1, 0, 0.5774), Vector(), 50, 100), + Path.Geom.speedBetweenPoints(Vector(1, 0, 0.5774), Vector(), 50, 100), 0.01, ) self.assertRoughly( 66.66, - PathGeom.speedBetweenPoints(Vector(0, 1, 0.5774), Vector(), 50, 100), + Path.Geom.speedBetweenPoints(Vector(0, 1, 0.5774), Vector(), 50, 100), 0.01, ) @@ -471,12 +470,12 @@ class TestPathGeom(PathTestBase): """Verify proper geometry objects for G1 and G01 commands are created.""" spt = Vector(1, 2, 3) self.assertLine( - PathGeom.edgeForCmd(Path.Command("G1", {"X": 7, "Y": 2, "Z": 3}), spt), + Path.Geom.edgeForCmd(Path.Command("G1", {"X": 7, "Y": 2, "Z": 3}), spt), spt, Vector(7, 2, 3), ) self.assertLine( - PathGeom.edgeForCmd(Path.Command("G01", {"X": 1, "Y": 3, "Z": 5}), spt), + Path.Geom.edgeForCmd(Path.Command("G01", {"X": 1, "Y": 3, "Z": 5}), spt), spt, Vector(1, 3, 5), ) @@ -486,7 +485,7 @@ class TestPathGeom(PathTestBase): p1 = Vector(0, -1, 2) p2 = Vector(-1, 0, 2) self.assertArc( - PathGeom.edgeForCmd( + Path.Geom.edgeForCmd( Path.Command( "G2", {"X": p2.x, "Y": p2.y, "Z": p2.z, "I": 0, "J": 1, "K": 0} ), @@ -497,7 +496,7 @@ class TestPathGeom(PathTestBase): "CW", ) self.assertArc( - PathGeom.edgeForCmd( + Path.Geom.edgeForCmd( Path.Command( "G3", {"X": p1.x, "Y": p1.y, "z": p1.z, "I": -1, "J": 0, "K": 0} ), @@ -514,7 +513,7 @@ class TestPathGeom(PathTestBase): p1 = Vector(0, 1, 0) p2 = Vector(1, 0, 2) self.assertCurve( - PathGeom.edgeForCmd( + Path.Geom.edgeForCmd( Path.Command( "G2", {"X": p2.x, "Y": p2.y, "Z": p2.z, "I": 0, "J": -1, "K": 1} ), @@ -527,7 +526,7 @@ class TestPathGeom(PathTestBase): p1 = Vector(-1, 0, 0) p2 = Vector(0, -1, 2) self.assertCurve( - PathGeom.edgeForCmd( + Path.Geom.edgeForCmd( Path.Command( "G3", {"X": p2.x, "Y": p2.y, "Z": p2.z, "I": 1, "J": 0, "K": 1} ), @@ -542,7 +541,7 @@ class TestPathGeom(PathTestBase): p1 = Vector(0, -1, 2) p2 = Vector(-1, 0, 0) self.assertCurve( - PathGeom.edgeForCmd( + Path.Geom.edgeForCmd( Path.Command( "G2", {"X": p2.x, "Y": p2.y, "Z": p2.z, "I": 0, "J": 1, "K": -1} ), @@ -555,7 +554,7 @@ class TestPathGeom(PathTestBase): p1 = Vector(-1, 0, 2) p2 = Vector(0, -1, 0) self.assertCurve( - PathGeom.edgeForCmd( + Path.Geom.edgeForCmd( Path.Command( "G3", {"X": p2.x, "Y": p2.y, "Z": p2.z, "I": 1, "J": 0, "K": -1} ), @@ -574,7 +573,7 @@ class TestPathGeom(PathTestBase): p4 = Vector(+10, 0, 0) def cmds(pa, pb, pc, flip): - return PathGeom.cmdsForEdge(Part.Edge(Part.Arc(pa, pb, pc)), flip)[0] + return Path.Geom.cmdsForEdge(Part.Edge(Part.Arc(pa, pb, pc)), flip)[0] def cmd(g, end, off): return Path.Command( @@ -608,7 +607,7 @@ class TestPathGeom(PathTestBase): def cmds(center, radius, up=True): norm = Vector(0, 0, 1) if up else Vector(0, 0, -1) - return PathGeom.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( @@ -633,7 +632,7 @@ class TestPathGeom(PathTestBase): def test42(self): """Verify ellipsis results in a proper segmentation of G1 commands.""" ellipse = Part.Edge(Part.Ellipse()) - cmds = PathGeom.cmdsForEdge(ellipse) + cmds = Path.Geom.cmdsForEdge(ellipse) # let's make sure all commands are G1 and there are more than 20 of those self.assertGreater(len(cmds), 20) self.assertTrue(all([cmd.Name == "G1" for cmd in cmds])) @@ -646,16 +645,16 @@ class TestPathGeom(PathTestBase): commands.append(Path.Command("G0", {"X": 0})) commands.append(Path.Command("G1", {"Y": 0})) - wire, rapid = PathGeom.wireForPath(Path.Path(commands)) + wire, rapid = Path.Geom.wireForPath(Path.Path(commands)) self.assertEqual(len(wire.Edges), 4) self.assertLine(wire.Edges[0], Vector(0, 0, 0), Vector(1, 0, 0)) self.assertLine(wire.Edges[1], Vector(1, 0, 0), Vector(1, 1, 0)) self.assertLine(wire.Edges[2], Vector(1, 1, 0), Vector(0, 1, 0)) self.assertLine(wire.Edges[3], Vector(0, 1, 0), Vector(0, 0, 0)) self.assertEqual(len(rapid), 1) - self.assertTrue(PathGeom.edgesMatch(rapid[0], wire.Edges[2])) + self.assertTrue(Path.Geom.edgesMatch(rapid[0], wire.Edges[2])) - wires = PathGeom.wiresForPath(Path.Path(commands)) + wires = Path.Geom.wiresForPath(Path.Path(commands)) self.assertEqual(len(wires), 2) self.assertEqual(len(wires[0].Edges), 2) self.assertLine(wires[0].Edges[0], Vector(0, 0, 0), Vector(1, 0, 0)) @@ -669,15 +668,15 @@ class TestPathGeom(PathTestBase): p2 = Vector(0, 0, 0) p3 = Vector(10, 10, 0) - e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 2) + e = Path.Geom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 2) self.assertCurve(e, p1, p2 + Vector(0, 0, 1), p3 + Vector(0, 0, 2)) - e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 3, 7) + e = Path.Geom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 3, 7) self.assertCurve( e, p1 + Vector(0, 0, 3), p2 + Vector(0, 0, 5), p3 + Vector(0, 0, 7) ) - e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 9, 1) + e = Path.Geom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 9, 1) self.assertCurve( e, p1 + Vector(0, 0, 9), p2 + Vector(0, 0, 5), p3 + Vector(0, 0, 1) ) @@ -687,16 +686,16 @@ class TestPathGeom(PathTestBase): p12 = p2 + dz p13 = p3 + dz - e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 0, 8) + e = Path.Geom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 0, 8) self.assertCurve(e, p1, p2 + Vector(0, 0, 4), p3 + Vector(0, 0, 8)) - e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) + e = Path.Geom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) self.assertCurve(e, p1 + Vector(0, 0, 2), p2, p3 + Vector(0, 0, -2)) p1 = Vector(10, -10, 1) p2 = Vector(10 - 10 * math.sin(math.pi / 4), -10 * math.cos(math.pi / 4), 1) p3 = Vector(0, 0, 1) - e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 5) + e = Path.Geom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 5) self.assertCurve( e, Vector(10, -10, 0), Vector(p2.x, p2.y, 2.5), Vector(0, 0, 5) ) @@ -713,7 +712,7 @@ class TestPathGeom(PathTestBase): p12 = Vector(10 - o, -o, 0) p23 = Vector(10 - o, +o, 0) - e = PathGeom.splitArcAt(arc, p2) + e = Path.Geom.splitArcAt(arc, p2) self.assertCurve(e[0], p1, p12, p2) self.assertCurve(e[1], p2, p23, p3) @@ -724,7 +723,7 @@ class TestPathGeom(PathTestBase): 10 - 10 * math.sin(5 * math.pi / 8), -10 * math.cos(5 * math.pi / 8), 0 ) - e = PathGeom.splitArcAt(arc, p12) + e = Path.Geom.splitArcAt(arc, p12) self.assertCurve(e[0], p1, p34, p12) self.assertCurve(e[1], p12, p45, p3) @@ -732,7 +731,7 @@ class TestPathGeom(PathTestBase): """Verify splitEdgeAt.""" # split a line segment - e = PathGeom.splitEdgeAt( + e = Path.Geom.splitEdgeAt( Part.Edge(Part.LineSegment(Vector(), Vector(2, 4, 6))), Vector(1, 2, 3) ) self.assertLine(e[0], Vector(), Vector(1, 2, 3)) @@ -743,7 +742,7 @@ class TestPathGeom(PathTestBase): p2 = Vector(0, 0, 1) p3 = Vector(10, 10, 1) arc = Part.Edge(Part.Arc(p1, p2, p3)) - e = PathGeom.splitEdgeAt(arc, p2) + e = Path.Geom.splitEdgeAt(arc, p2) o = 10 * math.sin(math.pi / 4) p12 = Vector(10 - o, -o, 1) p23 = Vector(10 - o, +o, 1) @@ -754,10 +753,10 @@ class TestPathGeom(PathTestBase): p1 = Vector(10, -10, 0) p2 = Vector(0, 0, 5) p3 = Vector(10, 10, 10) - h = PathGeom.arcToHelix(arc, 0, 10) + h = Path.Geom.arcToHelix(arc, 0, 10) self.assertCurve(h, p1, p2, p3) - e = PathGeom.splitEdgeAt(h, p2) + e = Path.Geom.splitEdgeAt(h, p2) o = 10 * math.sin(math.pi / 4) p12 = Vector(10 - o, -o, 2.5) p23 = Vector(10 - o, +o, 7.5) @@ -774,7 +773,7 @@ class TestPathGeom(PathTestBase): ac = arc.Curve.Center s = Vector(434.54, 378.26, 1) - head, tail = PathGeom.splitEdgeAt(arc, s) + head, tail = Path.Geom.splitEdgeAt(arc, s) # make sure the arcs connect as they should self.assertCoincide( @@ -802,59 +801,59 @@ class TestPathGeom(PathTestBase): def test70(self): """Flip a line.""" edge = Part.Edge(Part.Line(Vector(0, 0, 0), Vector(3, 2, 1))) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.Edge(Part.Line(Vector(0, 0, 0), Vector(-3, -2, -1))) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) def test71(self): """Flip a line segment.""" edge = Part.Edge(Part.LineSegment(Vector(0, 0, 0), Vector(3, 2, 1))) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.Edge(Part.LineSegment(Vector(4, 2, 1), Vector(-3, -7, 9))) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeLine(Vector(1, 0, 3), Vector(3, 2, 1)) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) def test72(self): """Flip a circle""" edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1)) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1)) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) def test73(self): """Flip an arc""" # make sure all 4 quadrants work edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1), 45, 90) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1), 100, 170) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1), 200, 250) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1), 300, 340) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) # and the other way around too edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1), 45, 90) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1), 100, 170) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1), 200, 250) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1), 300, 340) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) def test74(self): """Flip a rotated arc""" # oh yes ... edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1), 45, 90) edge.rotate(edge.Curve.Center, Vector(0, 0, 1), -90) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) def test75(self): """Flip a B-spline""" @@ -863,7 +862,7 @@ class TestPathGeom(PathTestBase): [Vector(1, 2, 3), Vector(-3, 0, 7), Vector(-3, 1, 9), Vector(1, 3, 5)] ) edge = Part.Edge(spline) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) edge = Part.Edge( Part.BSplineCurve( @@ -876,7 +875,7 @@ class TestPathGeom(PathTestBase): weights=[2, 3, 5, 7], ) ) - self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) + self.assertEdgeShapesMatch(edge, Path.Geom.flipEdge(edge)) def test76(self): """Flip an offset wire""" @@ -896,7 +895,7 @@ class TestPathGeom(PathTestBase): e2 = Part.Edge(Part.LineSegment(Vector(0, -7, 0), Vector(-8, 4, 0))) w0 = Part.Wire([e0, e1, e2]) w1 = w0.makeOffset2D(1) - w2 = PathGeom.flipWire(w1) + w2 = Path.Geom.flipWire(w1) # do some sanity checks self.assertTrue(w2.isValid()) self.assertTrue(w2.isClosed()) diff --git a/src/Mod/Path/PathTests/TestPathHelpers.py b/src/Mod/Path/PathTests/TestPathHelpers.py index 50835bb47d..c70b3dabfc 100644 --- a/src/Mod/Path/PathTests/TestPathHelpers.py +++ b/src/Mod/Path/PathTests/TestPathHelpers.py @@ -23,11 +23,10 @@ import FreeCAD import Part import Path +import Path.Base.FeedRate as PathFeedRate +import Path.Base.MachineState as PathMachineState import Path.Tool.Bit as PathToolBit import Path.Tool.Controller as PathToolController -import PathFeedRate -import PathMachineState -import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils from PathTests.PathTestUtils import PathTestBase @@ -134,7 +133,7 @@ class TestPathHelpers(PathTestBase): self.assertTrue(len(results) == 2) e1 = results[0] self.assertTrue(isinstance(e1.Curve, Part.Circle)) - self.assertTrue(PathGeom.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 diff --git a/src/Mod/Path/PathTests/TestPathOpUtil.py b/src/Mod/Path/PathTests/TestPathOpUtil.py index 28f1e88b9b..c066352745 100644 --- a/src/Mod/Path/PathTests/TestPathOpUtil.py +++ b/src/Mod/Path/PathTests/TestPathOpUtil.py @@ -24,7 +24,6 @@ import FreeCAD import Part import Path import Path.Op.Util as PathOpUtil -import PathScripts.PathGeom as PathGeom import PathTests.PathTestUtils as PathTestUtils import math @@ -208,14 +207,14 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): w for w in obj.Shape.Wires if 1 == len(w.Edges) - and PathGeom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z) + and Path.Geom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z) ] self.assertEqual(2, len(wires)) w = wires[1] if wires[0].BoundBox.isInside(wires[1].BoundBox) else wires[0] self.assertRoughly(10, w.Edges[0].Curve.Radius) # make sure there is a placement and I didn't mess up the model - self.assertFalse(PathGeom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) + self.assertFalse(Path.Geom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) self.assertIsNotNone(wire) @@ -232,14 +231,14 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): w for w in obj.Shape.Wires if 1 == len(w.Edges) - and PathGeom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z) + and Path.Geom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z) ] self.assertEqual(2, len(wires)) w = wires[0] if wires[0].BoundBox.isInside(wires[1].BoundBox) else wires[1] self.assertRoughly(20, w.Edges[0].Curve.Radius) # make sure there is a placement and I didn't mess up the model - self.assertFalse(PathGeom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) + self.assertFalse(Path.Geom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) self.assertIsNotNone(wire) @@ -335,16 +334,16 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): begin = e.Vertexes[0].Point end = e.Vertexes[1].Point v = end - begin - angle = PathGeom.getAngle(v) - if PathGeom.isRoughly(0, angle) or PathGeom.isRoughly( + angle = Path.Geom.getAngle(v) + if Path.Geom.isRoughly(0, angle) or Path.Geom.isRoughly( math.pi, math.fabs(angle) ): if lastAngle: self.assertRoughly(-refAngle, lastAngle) - elif PathGeom.isRoughly(+refAngle, angle): + elif Path.Geom.isRoughly(+refAngle, angle): if lastAngle: self.assertRoughly(math.pi, math.fabs(lastAngle)) - elif PathGeom.isRoughly(-refAngle, angle): + elif Path.Geom.isRoughly(-refAngle, angle): if lastAngle: self.assertRoughly(+refAngle, lastAngle) else: @@ -385,9 +384,9 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): ) for e in wire.Edges: if Part.Line == type(e.Curve): - if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): + if Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): self.assertEqual(40, e.Length) - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertEqual(60, e.Length) if Part.Circle == type(e.Curve): self.assertRoughly(3, e.Curve.Radius) @@ -405,9 +404,9 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): ) for e in wire.Edges: if Part.Line == type(e.Curve): - if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): + if Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): self.assertEqual(40, e.Length) - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertEqual(60, e.Length) if Part.Circle == type(e.Curve): self.assertRoughly(3, e.Curve.Radius) @@ -512,9 +511,9 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): 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: - if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): + if Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): self.assertRoughly(34, e.Length) - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertRoughly(54, e.Length) self.assertFalse(PathOpUtil.isWireClockwise(wire)) @@ -525,9 +524,9 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): 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: - if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): + if Path.Geom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): self.assertRoughly(34, e.Length) - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertRoughly(54, e.Length) self.assertTrue(PathOpUtil.isWireClockwise(wire)) @@ -601,7 +600,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): hEdges = [ e for e in w.Edges - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] x = length / 2 @@ -620,7 +619,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point) # make sure we get the same result even if the edge is oriented the other way - edge = PathGeom.flipEdge(edge) + edge = Path.Geom.flipEdge(edge) wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, True) self.assertEqual(1, len(wire.Edges)) @@ -640,7 +639,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): hEdges = [ e for e in w.Edges - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] x = length / 2 @@ -658,7 +657,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point) # make sure we get the same result on a reversed edge - edge = PathGeom.flipEdge(edge) + edge = Path.Geom.flipEdge(edge) wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 5, False) self.assertEqual(1, len(wire.Edges)) @@ -678,7 +677,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): lEdges = [ e for e in w.Edges - if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] self.assertEqual(2, len(lEdges)) @@ -721,11 +720,11 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): lEdges = [ e for e in w.Edges - if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] self.assertEqual(2, len(lEdges)) - w = PathGeom.flipWire(Part.Wire(lEdges)) + w = Path.Geom.flipWire(Part.Wire(lEdges)) wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) x = length / 2 + 2 * math.cos(math.pi / 6) @@ -765,7 +764,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): hEdges = [ e for e in w.Edges - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] x = length / 2 @@ -784,7 +783,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[1].Point) # make sure we get the same result even if the edge is oriented the other way - edge = PathGeom.flipEdge(edge) + edge = Path.Geom.flipEdge(edge) wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, True) self.assertEqual(1, len(wire.Edges)) @@ -804,7 +803,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): hEdges = [ e for e in w.Edges - if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] x = length / 2 @@ -823,7 +822,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point) # make sure we get the same result even if the edge is oriented the other way - edge = PathGeom.flipEdge(edge) + edge = Path.Geom.flipEdge(edge) wire = PathOpUtil.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) self.assertEqual(1, len(wire.Edges)) @@ -841,7 +840,7 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): lEdges = [ e for e in w.Edges - if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] self.assertEqual(2, len(lEdges)) @@ -878,11 +877,11 @@ class TestPathOpUtil(PathTestUtils.PathTestBase): lEdges = [ e for e in w.Edges - if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) + if not Path.Geom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y) ] self.assertEqual(2, len(lEdges)) - w = PathGeom.flipWire(Part.Wire(lEdges)) + w = Path.Geom.flipWire(Part.Wire(lEdges)) wire = PathOpUtil.offsetWire(w, obj.Shape, 2, True) x = length / 2 - 2 * math.cos(math.pi / 6) diff --git a/src/Mod/Path/PathTests/TestPathPropertyBag.py b/src/Mod/Path/PathTests/TestPathPropertyBag.py index 71019dfb27..ae675b71ff 100644 --- a/src/Mod/Path/PathTests/TestPathPropertyBag.py +++ b/src/Mod/Path/PathTests/TestPathPropertyBag.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathPropertyBag as PathPropertyBag +import Path.Base.PropertyBag as PathPropertyBag import PathTests.PathTestUtils as PathTestUtils diff --git a/src/Mod/Path/PathTests/TestPathThreadMilling.py b/src/Mod/Path/PathTests/TestPathThreadMilling.py index 207cd93604..a1a29dde92 100644 --- a/src/Mod/Path/PathTests/TestPathThreadMilling.py +++ b/src/Mod/Path/PathTests/TestPathThreadMilling.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathGeom as PathGeom +import Path import Path.Op.ThreadMilling as PathThreadMilling import math diff --git a/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py b/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py index c73e5ea592..c33368cc7d 100644 --- a/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py +++ b/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py @@ -21,7 +21,6 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathGeom as PathGeom import Generators.threadmilling_generator as threadmilling import math diff --git a/src/Mod/Path/PathTests/TestPathUtil.py b/src/Mod/Path/PathTests/TestPathUtil.py index 04ae92ce58..a22c4f2876 100644 --- a/src/Mod/Path/PathTests/TestPathUtil.py +++ b/src/Mod/Path/PathTests/TestPathUtil.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathUtil as PathUtil +import Path.Base.Util as PathUtil import TestSketcherApp from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathVcarve.py b/src/Mod/Path/PathTests/TestPathVcarve.py index 0744fd32b8..469742b57f 100644 --- a/src/Mod/Path/PathTests/TestPathVcarve.py +++ b/src/Mod/Path/PathTests/TestPathVcarve.py @@ -23,7 +23,6 @@ import FreeCAD import Path.Op.Vcarve as PathVcarve import Path.Tool.Bit as PathToolBit -import PathScripts.PathGeom as PathGeom import math from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathVoronoi.py b/src/Mod/Path/PathTests/TestPathVoronoi.py index a6a91bf725..169ee5e1c1 100644 --- a/src/Mod/Path/PathTests/TestPathVoronoi.py +++ b/src/Mod/Path/PathTests/TestPathVoronoi.py @@ -23,7 +23,6 @@ import FreeCAD import Part import Path -import PathScripts.PathGeom as PathGeom import PathTests.PathTestUtils as PathTestUtils vd = None @@ -110,7 +109,7 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e = e0.toShape() self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) self.assertFalse( - PathGeom.pointsCoincide( + Path.Geom.pointsCoincide( e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter) ) ) @@ -127,7 +126,7 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e = e0.toShape(13.7) self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) self.assertFalse( - PathGeom.pointsCoincide( + Path.Geom.pointsCoincide( e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter) ) ) @@ -144,7 +143,7 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e = e0.toShape(2.37, 5.14) self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) self.assertFalse( - PathGeom.pointsCoincide( + Path.Geom.pointsCoincide( e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter) ) ) @@ -163,7 +162,7 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve ) self.assertFalse( - PathGeom.pointsCoincide( + Path.Geom.pointsCoincide( e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter) ) ) @@ -182,7 +181,7 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve ) self.assertFalse( - PathGeom.pointsCoincide( + Path.Geom.pointsCoincide( e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter) ) ) @@ -201,7 +200,7 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve ) self.assertFalse( - PathGeom.pointsCoincide( + Path.Geom.pointsCoincide( e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter) ) ) diff --git a/src/Mod/Path/Tools/toolbit-attributes.py b/src/Mod/Path/Tools/toolbit-attributes.py index 92f91d70fd..326eaa71dd 100755 --- a/src/Mod/Path/Tools/toolbit-attributes.py +++ b/src/Mod/Path/Tools/toolbit-attributes.py @@ -93,8 +93,8 @@ if args.freecad: import FreeCAD import Path -import PathScripts.PathPropertyBag as PathPropertyBag -import PathScripts.PathUtil as PathUtil +import Path.Base.PropertyBag as PathPropertyBag +import Path.Base.Util as PathUtil set_var = None set_val = None From f1596599eeae6cfb9245d3c110c5f0ae45d9d1a4 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 13 Aug 2022 18:12:52 -0700 Subject: [PATCH 15/33] Added PropertyBag compatibility layer to support existing ToolBits --- src/Mod/Path/PathScripts/PathPropertyBag.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/Mod/Path/PathScripts/PathPropertyBag.py diff --git a/src/Mod/Path/PathScripts/PathPropertyBag.py b/src/Mod/Path/PathScripts/PathPropertyBag.py new file mode 100644 index 0000000000..fbd22e3e2f --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyBag.py @@ -0,0 +1,4 @@ +# Do NOT remove! +# This establishes backwards compatibility with existing ToolBit files. + +from Path.Base.PropertyBag import * From 5ca5a94f5efa76112dbf06978c9dfd2fa962e9f2 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 13 Aug 2022 19:53:53 -0700 Subject: [PATCH 16/33] Move Path.Base and Path.Base.Gui refactoring --- src/Mod/Path/CMakeLists.txt | 28 ++++++------ src/Mod/Path/InitGui.py | 21 ++++----- .../Base/Gui/GetPoint.py} | 0 .../Base/Gui/IconViewProvider.py} | 0 .../Base/Gui/PreferencesAdvanced.py} | 15 +++---- .../Base/Gui/PropertyBag.py} | 0 .../Base/Gui/PropertyEditor.py} | 2 +- .../Base/Gui/SetupSheet.py} | 18 ++++---- .../Base/Gui/SetupSheetOpPrototype.py} | 2 +- .../PathGui.py => Path/Base/Gui/Util.py} | 0 src/Mod/Path/Path/Base/Gui/__init__.py | 0 .../Base/SetupSheet.py} | 2 +- .../Base/SetupSheetOpPrototype.py} | 0 src/Mod/Path/Path/Base/__init__.py | 0 src/Mod/Path/Path/Dressup/Gui/AxisMap.py | 4 +- src/Mod/Path/Path/Dressup/Gui/Dragknife.py | 8 ++-- src/Mod/Path/Path/Dressup/Gui/PathBoundary.py | 2 +- .../Path/Path/Dressup/Gui/TagPreferences.py | 13 +++--- src/Mod/Path/Path/Dressup/Gui/Tags.py | 7 ++- .../PathGuiInit.py => Path/GuiInit.py} | 6 +-- src/Mod/Path/Path/Op/Base.py | 5 +-- src/Mod/Path/Path/Op/Gui/Base.py | 31 +++++++------ src/Mod/Path/Path/Op/Gui/CircularHoleBase.py | 2 +- src/Mod/Path/Path/Op/Gui/Deburr.py | 10 ++--- src/Mod/Path/Path/Op/Gui/Drilling.py | 10 ++--- src/Mod/Path/Path/Op/Gui/Engrave.py | 2 +- src/Mod/Path/Path/Op/Gui/Helix.py | 6 +-- src/Mod/Path/Path/Op/Gui/PocketBase.py | 8 ++-- src/Mod/Path/Path/Op/Gui/Probe.py | 8 ++-- src/Mod/Path/Path/Op/Gui/Profile.py | 6 +-- src/Mod/Path/Path/Op/Gui/Slot.py | 8 ++-- src/Mod/Path/Path/Op/Gui/Surface.py | 10 ++--- src/Mod/Path/Path/Op/Gui/ThreadMilling.py | 10 ++--- src/Mod/Path/Path/Op/Gui/Vcarve.py | 2 +- src/Mod/Path/Path/Op/Gui/Waterline.py | 8 ++-- src/Mod/Path/Path/Op/Vcarve.py | 3 +- src/Mod/Path/Path/Op/Waterline.py | 6 +-- src/Mod/Path/Path/Post/Command.py | 11 +++-- src/Mod/Path/Path/Post/Processor.py | 5 +-- .../Preferences.py} | 1 - src/Mod/Path/Path/Tool/Bit.py | 7 ++- src/Mod/Path/Path/Tool/Controller.py | 1 - src/Mod/Path/Path/Tool/Gui/Bit.py | 21 +++++---- src/Mod/Path/Path/Tool/Gui/BitCmd.py | 4 +- src/Mod/Path/Path/Tool/Gui/BitEdit.py | 11 +++-- src/Mod/Path/Path/Tool/Gui/BitLibrary.py | 43 +++++++++---------- src/Mod/Path/Path/Tool/Gui/Controller.py | 14 +++--- src/Mod/Path/Path/__init__.py | 1 + .../PathScripts/PathFeatureExtensionsGui.py | 4 +- src/Mod/Path/PathScripts/PathJob.py | 23 +++++----- src/Mod/Path/PathScripts/PathJobCmd.py | 3 +- src/Mod/Path/PathScripts/PathJobDlg.py | 5 +-- src/Mod/Path/PathScripts/PathJobGui.py | 9 ++-- .../PathScripts/PathPreferencesPathJob.py | 39 ++++++++--------- src/Mod/Path/PathScripts/PathSanity.py | 7 ++- src/Mod/Path/PathScripts/PathSelection.py | 33 +++++++------- src/Mod/Path/PathScripts/PathSimulatorGui.py | 2 +- src/Mod/Path/PathScripts/PathStock.py | 4 +- src/Mod/Path/PathScripts/PathUtilsGui.py | 2 +- src/Mod/Path/PathTests/TestPathPost.py | 29 ++++++------- src/Mod/Path/PathTests/TestPathPreferences.py | 18 ++++---- src/Mod/Path/PathTests/TestPathSetupSheet.py | 11 +++-- .../Path/PathTests/TestPathToolController.py | 1 - 63 files changed, 274 insertions(+), 298 deletions(-) rename src/Mod/Path/{PathScripts/PathGetPoint.py => Path/Base/Gui/GetPoint.py} (100%) rename src/Mod/Path/{PathScripts/PathIconViewProvider.py => Path/Base/Gui/IconViewProvider.py} (100%) rename src/Mod/Path/{PathScripts/PathPreferencesAdvanced.py => Path/Base/Gui/PreferencesAdvanced.py} (87%) rename src/Mod/Path/{PathScripts/PathPropertyBagGui.py => Path/Base/Gui/PropertyBag.py} (100%) rename src/Mod/Path/{PathScripts/PathPropertyEditor.py => Path/Base/Gui/PropertyEditor.py} (99%) rename src/Mod/Path/{PathScripts/PathSetupSheetGui.py => Path/Base/Gui/SetupSheet.py} (96%) rename src/Mod/Path/{PathScripts/PathSetupSheetOpPrototypeGui.py => Path/Base/Gui/SetupSheetOpPrototype.py} (99%) rename src/Mod/Path/{PathScripts/PathGui.py => Path/Base/Gui/Util.py} (100%) create mode 100644 src/Mod/Path/Path/Base/Gui/__init__.py rename src/Mod/Path/{PathScripts/PathSetupSheet.py => Path/Base/SetupSheet.py} (99%) rename src/Mod/Path/{PathScripts/PathSetupSheetOpPrototype.py => Path/Base/SetupSheetOpPrototype.py} (100%) create mode 100644 src/Mod/Path/Path/Base/__init__.py rename src/Mod/Path/{PathScripts/PathGuiInit.py => Path/GuiInit.py} (97%) rename src/Mod/Path/{PathScripts/PathPreferences.py => Path/Preferences.py} (99%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 8aab94bb94..8ff9c14efb 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -27,6 +27,8 @@ SET(PathPython_SRCS Path/__init__.py Path/Log.py Path/Geom.py + Path/GuiInit.py + Path/Preferences.py ) SET(PathPythonBase_SRCS @@ -35,9 +37,23 @@ SET(PathPythonBase_SRCS Path/Base/FeedRate.py Path/Base/Property.py Path/Base/PropertyBag.py + Path/Base/SetupSheet.py + Path/Base/SetupSheetOpPrototype.py Path/Base/Util.py ) +SET(PathPythonBaseGui_SRCS + Path/Base/Gui/__init__.py + Path/Base/Gui/GetPoint.py + Path/Base/Gui/IconViewProvider.py + Path/Base/Gui/PreferencesAdvanced.py + Path/Base/Gui/PropertyBag.py + Path/Base/Gui/PropertyEditor.py + Path/Base/Gui/SetupSheet.py + Path/Base/Gui/SetupSheetOpPrototype.py + Path/Base/Gui/Util.py +) + SET(PathPythonDressup_SRCS Path/Dressup/__init__.py Path/Dressup/Utils.py @@ -177,27 +193,15 @@ SET(PathScripts_SRCS PathScripts/PathFeatureExtensions.py PathScripts/PathFeatureExtensionsGui.py PathScripts/PathFixture.py - PathScripts/PathGetPoint.py - PathScripts/PathGui.py - PathScripts/PathGuiInit.py PathScripts/PathHop.py - PathScripts/PathIconViewProvider.py PathScripts/PathInspect.py PathScripts/PathJob.py PathScripts/PathJobCmd.py PathScripts/PathJobDlg.py PathScripts/PathJobGui.py - PathScripts/PathPreferences.py - PathScripts/PathPreferencesAdvanced.py PathScripts/PathPreferencesPathJob.py - PathScripts/PathPropertyBagGui.py - PathScripts/PathPropertyEditor.py PathScripts/PathSanity.py PathScripts/PathSelection.py - PathScripts/PathSetupSheet.py - PathScripts/PathSetupSheetGui.py - PathScripts/PathSetupSheetOpPrototype.py - PathScripts/PathSetupSheetOpPrototypeGui.py PathScripts/PathSimpleCopy.py PathScripts/PathSimulatorGui.py PathScripts/PathStock.py diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 4d9fb22440..292c5d2082 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -69,9 +69,6 @@ class PathWorkbench(Workbench): PathPreferencesPathDressup.DressupPreferencesPage, "Path" ) - # Check enablement of experimental features - from PathScripts import PathPreferences - # load the builtin modules import Path import PathScripts @@ -80,7 +77,7 @@ class PathWorkbench(Workbench): FreeCADGui.addLanguagePath(":/translations") FreeCADGui.addIconPath(":/icons") - from PathScripts import PathGuiInit + import Path.GuiInit from PathScripts import PathJobCmd from Path.Tool.Gui import BitCmd as PathToolBitCmd @@ -92,7 +89,7 @@ class PathWorkbench(Workbench): import subprocess from packaging.version import Version, parse - PathGuiInit.Startup() + Path.GuiInit.Startup() # build commands list projcmdlist = ["Path_Job", "Path_Post"] @@ -148,14 +145,14 @@ class PathWorkbench(Workbench): ) threedcmdgroup = threedopcmdlist - if PathPreferences.experimentalFeaturesEnabled(): + if Path.Preferences.experimentalFeaturesEnabled(): projcmdlist.append("Path_Sanity") prepcmdlist.append("Path_Shape") extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) specialcmdlist.append("Path_ThreadMilling") twodopcmdlist.append("Path_Slot") - if PathPreferences.advancedOCLFeaturesEnabled(): + if Path.Preferences.advancedOCLFeaturesEnabled(): try: r = subprocess.run( ["camotics", "--version"], capture_output=True, text=True @@ -182,7 +179,7 @@ class PathWorkbench(Workbench): ), ) except ImportError: - if not PathPreferences.suppressOpenCamLibWarning(): + if not Path.Preferences.suppressOpenCamLibWarning(): FreeCAD.Console.PrintError("OpenCamLib is not working!\n") self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Project Setup"), projcmdlist) @@ -255,13 +252,13 @@ class PathWorkbench(Workbench): self.dressupcmds = dressupcmdlist - curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy() + curveAccuracy = Path.Preferences.defaultLibAreaCurveAccuracy() if curveAccuracy: Path.Area.setDefaultParams(Accuracy=curveAccuracy) # keep this one the last entry in the preferences - import PathScripts.PathPreferencesAdvanced as PathPreferencesAdvanced - from PathScripts.PathPreferences import preferences + import Path.Base.Gui.PreferencesAdvanced as PathPreferencesAdvanced + from Path.Preferences import preferences FreeCADGui.addPreferencePage( PathPreferencesAdvanced.AdvancedPreferencesPage, "Path" @@ -269,7 +266,7 @@ class PathWorkbench(Workbench): Log("Loading Path workbench... done\n") # Warn user if current schema doesn't use minute for time in velocity - if not PathPreferences.suppressVelocity(): + if not Path.Preferences.suppressVelocity(): velString = FreeCAD.Units.Quantity( 1, FreeCAD.Units.Velocity ).getUserPreferred()[2][3:] diff --git a/src/Mod/Path/PathScripts/PathGetPoint.py b/src/Mod/Path/Path/Base/Gui/GetPoint.py similarity index 100% rename from src/Mod/Path/PathScripts/PathGetPoint.py rename to src/Mod/Path/Path/Base/Gui/GetPoint.py diff --git a/src/Mod/Path/PathScripts/PathIconViewProvider.py b/src/Mod/Path/Path/Base/Gui/IconViewProvider.py similarity index 100% rename from src/Mod/Path/PathScripts/PathIconViewProvider.py rename to src/Mod/Path/Path/Base/Gui/IconViewProvider.py diff --git a/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py b/src/Mod/Path/Path/Base/Gui/PreferencesAdvanced.py similarity index 87% rename from src/Mod/Path/PathScripts/PathPreferencesAdvanced.py rename to src/Mod/Path/Path/Base/Gui/PreferencesAdvanced.py index aa0bd6742f..bb1725f483 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesAdvanced.py +++ b/src/Mod/Path/Path/Base/Gui/PreferencesAdvanced.py @@ -22,7 +22,6 @@ import FreeCADGui import Path -import PathScripts.PathPreferences as PathPreferences if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -38,7 +37,7 @@ class AdvancedPreferencesPage: self.form.EnableAdvancedOCLFeatures.stateChanged.connect(self.updateSelection) def saveSettings(self): - PathPreferences.setPreferencesAdvanced( + Path.Preferences.setPreferencesAdvanced( self.form.EnableAdvancedOCLFeatures.isChecked(), self.form.WarningSuppressAllSpeeds.isChecked(), self.form.WarningSuppressRapidSpeeds.isChecked(), @@ -50,21 +49,21 @@ class AdvancedPreferencesPage: def loadSettings(self): Path.Log.track() self.form.WarningSuppressAllSpeeds.setChecked( - PathPreferences.suppressAllSpeedsWarning() + Path.Preferences.suppressAllSpeedsWarning() ) self.form.WarningSuppressRapidSpeeds.setChecked( - PathPreferences.suppressRapidSpeedsWarning(False) + Path.Preferences.suppressRapidSpeedsWarning(False) ) self.form.WarningSuppressSelectionMode.setChecked( - PathPreferences.suppressSelectionModeWarning() + Path.Preferences.suppressSelectionModeWarning() ) self.form.EnableAdvancedOCLFeatures.setChecked( - PathPreferences.advancedOCLFeaturesEnabled() + Path.Preferences.advancedOCLFeaturesEnabled() ) self.form.WarningSuppressOpenCamLib.setChecked( - PathPreferences.suppressOpenCamLibWarning() + Path.Preferences.suppressOpenCamLibWarning() ) - self.form.WarningSuppressVelocity.setChecked(PathPreferences.suppressVelocity()) + self.form.WarningSuppressVelocity.setChecked(Path.Preferences.suppressVelocity()) self.updateSelection() def updateSelection(self, state=None): diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/Path/Base/Gui/PropertyBag.py similarity index 100% rename from src/Mod/Path/PathScripts/PathPropertyBagGui.py rename to src/Mod/Path/Path/Base/Gui/PropertyBag.py diff --git a/src/Mod/Path/PathScripts/PathPropertyEditor.py b/src/Mod/Path/Path/Base/Gui/PropertyEditor.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPropertyEditor.py rename to src/Mod/Path/Path/Base/Gui/PropertyEditor.py index 6e66ee2ac6..05830d9a19 100644 --- a/src/Mod/Path/PathScripts/PathPropertyEditor.py +++ b/src/Mod/Path/Path/Base/Gui/PropertyEditor.py @@ -22,7 +22,7 @@ import FreeCAD import Path -import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype +import Path.Base.SetupSheetOpPrototype as PathSetupSheetOpPrototype from PySide import QtCore, QtGui diff --git a/src/Mod/Path/PathScripts/PathSetupSheetGui.py b/src/Mod/Path/Path/Base/Gui/SetupSheet.py similarity index 96% rename from src/Mod/Path/PathScripts/PathSetupSheetGui.py rename to src/Mod/Path/Path/Base/Gui/SetupSheet.py index 3aa4ea3a65..124ae665f7 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetGui.py +++ b/src/Mod/Path/Path/Base/Gui/SetupSheet.py @@ -23,12 +23,12 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.IconViewProvider as PathIconViewProvider +import Path.Base.Gui.SetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui +import Path.Base.Gui.Util as PathGuiUtil +import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui -import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathSetupSheet as PathSetupSheet -import PathScripts.PathSetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui +import PathGui from PySide import QtCore, QtGui @@ -347,16 +347,16 @@ class GlobalEditor(object): self.updateUI() def setupUi(self): - self.clearanceHeightOffs = PathGui.QuantitySpinBox( + self.clearanceHeightOffs = PathGuiUtil.QuantitySpinBox( self.form.setupClearanceHeightOffs, self.obj, "ClearanceHeightOffset" ) - self.safeHeightOffs = PathGui.QuantitySpinBox( + self.safeHeightOffs = PathGuiUtil.QuantitySpinBox( self.form.setupSafeHeightOffs, self.obj, "SafeHeightOffset" ) - self.rapidHorizontal = PathGui.QuantitySpinBox( + self.rapidHorizontal = PathGuiUtil.QuantitySpinBox( self.form.setupRapidHorizontal, self.obj, "HorizRapid" ) - self.rapidVertical = PathGui.QuantitySpinBox( + self.rapidVertical = PathGuiUtil.QuantitySpinBox( self.form.setupRapidVertical, self.obj, "VertRapid" ) self.form.setupCoolantMode.addItems(self.obj.CoolantModes) diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py b/src/Mod/Path/Path/Base/Gui/SetupSheetOpPrototype.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py rename to src/Mod/Path/Path/Base/Gui/SetupSheetOpPrototype.py index 920b591732..81d7b8fcc3 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py +++ b/src/Mod/Path/Path/Base/Gui/SetupSheetOpPrototype.py @@ -22,7 +22,7 @@ import FreeCAD import Path -import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype +import Path.Base.SetupSheetOpPrototype as PathSetupSheetOpPrototype from PySide import QtCore, QtGui diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/Path/Base/Gui/Util.py similarity index 100% rename from src/Mod/Path/PathScripts/PathGui.py rename to src/Mod/Path/Path/Base/Gui/Util.py diff --git a/src/Mod/Path/Path/Base/Gui/__init__.py b/src/Mod/Path/Path/Base/Gui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/Path/Base/SetupSheet.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSetupSheet.py rename to src/Mod/Path/Path/Base/SetupSheet.py index 483cd9dea6..3667fd777e 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/Path/Base/SetupSheet.py @@ -23,7 +23,7 @@ import FreeCAD import Path import Path.Base.Util as PathUtil -import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype +import Path.Base.SetupSheetOpPrototype as PathSetupSheetOpPrototype from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Setup Sheet for a Job." diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py b/src/Mod/Path/Path/Base/SetupSheetOpPrototype.py similarity index 100% rename from src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py rename to src/Mod/Path/Path/Base/SetupSheetOpPrototype.py diff --git a/src/Mod/Path/Path/Base/__init__.py b/src/Mod/Path/Path/Base/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Path/Dressup/Gui/AxisMap.py b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py index d2d9929f06..df3271bea5 100644 --- a/src/Mod/Path/Path/Dressup/Gui/AxisMap.py +++ b/src/Mod/Path/Path/Dressup/Gui/AxisMap.py @@ -23,8 +23,8 @@ import FreeCAD import Path import math +import Path.Base.Gui.Util as PathGuiUtil import PathScripts.PathUtils as PathUtils -import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP if False: @@ -168,7 +168,7 @@ class TaskPanel: def __init__(self, obj): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/AxisMapEdit.ui") - self.radius = PathGui.QuantitySpinBox(self.form.radius, obj, "Radius") + self.radius = PathGuiUtil.QuantitySpinBox(self.form.radius, obj, "Radius") FreeCAD.ActiveDocument.openTransaction("Edit Dragknife Dress-up") def reject(self): diff --git a/src/Mod/Path/Path/Dressup/Gui/Dragknife.py b/src/Mod/Path/Path/Dressup/Gui/Dragknife.py index 41f15921c7..d1f6699cd5 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Dragknife.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dragknife.py @@ -23,10 +23,10 @@ from __future__ import print_function import FreeCAD import Path +import Path.Base.Gui.Util as PathGuiUtil from PySide import QtCore import math import PathScripts.PathUtils as PathUtils -import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP # lazily loaded modules @@ -498,13 +498,13 @@ class TaskPanel: def __init__(self, obj): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/DragKnifeEdit.ui") - self.filterAngle = PathGui.QuantitySpinBox( + self.filterAngle = PathGuiUtil.QuantitySpinBox( self.form.filterAngle, obj, "filterAngle" ) - self.offsetDistance = PathGui.QuantitySpinBox( + self.offsetDistance = PathGuiUtil.QuantitySpinBox( self.form.offsetDistance, obj, "offset" ) - self.pivotHeight = PathGui.QuantitySpinBox( + self.pivotHeight = PathGuiUtil.QuantitySpinBox( self.form.pivotHeight, obj, "pivotheight" ) diff --git a/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py b/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py index 50b4781f1c..3a4ae36e94 100644 --- a/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py +++ b/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py @@ -26,7 +26,7 @@ import FreeCAD import FreeCADGui import Path import Path.Dressup.PathBoundary as PathDressupPathBoundary -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/Path/Dressup/Gui/TagPreferences.py b/src/Mod/Path/Path/Dressup/Gui/TagPreferences.py index 248afda9c2..ad48f46dc8 100644 --- a/src/Mod/Path/Path/Dressup/Gui/TagPreferences.py +++ b/src/Mod/Path/Path/Dressup/Gui/TagPreferences.py @@ -23,7 +23,6 @@ import FreeCAD import Path import Path.Dressup.Gui.Preferences as PathPreferencesPathDressup -import PathScripts.PathPreferences as PathPreferences if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -43,7 +42,7 @@ class HoldingTagPreferences: @classmethod def defaultWidth(cls, ifNotSet): - value = PathPreferences.preferences().GetFloat( + value = Path.Preferences.preferences().GetFloat( cls.DefaultHoldingTagWidth, ifNotSet ) if value == 0.0: @@ -52,7 +51,7 @@ class HoldingTagPreferences: @classmethod def defaultHeight(cls, ifNotSet): - value = PathPreferences.preferences().GetFloat( + value = Path.Preferences.preferences().GetFloat( cls.DefaultHoldingTagHeight, ifNotSet ) if value == 0.0: @@ -61,7 +60,7 @@ class HoldingTagPreferences: @classmethod def defaultAngle(cls, ifNotSet=45.0): - value = PathPreferences.preferences().GetFloat( + value = Path.Preferences.preferences().GetFloat( cls.DefaultHoldingTagAngle, ifNotSet ) if value < 10.0: @@ -70,7 +69,7 @@ class HoldingTagPreferences: @classmethod def defaultCount(cls, ifNotSet=4): - value = PathPreferences.preferences().GetUnsigned( + value = Path.Preferences.preferences().GetUnsigned( cls.DefaultHoldingTagCount, ifNotSet ) if value < 2: @@ -79,7 +78,7 @@ class HoldingTagPreferences: @classmethod def defaultRadius(cls, ifNotSet=0.0): - return PathPreferences.preferences().GetFloat( + return Path.Preferences.preferences().GetFloat( cls.DefaultHoldingTagRadius, ifNotSet ) @@ -112,7 +111,7 @@ class HoldingTagPreferences: self.form.sbCount.setValue(self.defaultCount()) def saveSettings(self): - pref = PathPreferences.preferences() + pref = Path.Preferences.preferences() pref.SetFloat( self.DefaultHoldingTagWidth, FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value, diff --git a/src/Mod/Path/Path/Dressup/Gui/Tags.py b/src/Mod/Path/Path/Dressup/Gui/Tags.py index 847aa0a038..0ec1c9d5ad 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Tags.py +++ b/src/Mod/Path/Path/Dressup/Gui/Tags.py @@ -26,10 +26,9 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.GetPoint as PathGetPoint import Path.Dressup.Tags as PathDressupTag -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGetPoint as PathGetPoint -import PathScripts.PathPreferences as PathPreferences +import PathGui import PathScripts.PathUtils as PathUtils @@ -394,7 +393,7 @@ class PathDressupTagViewProvider: v = [((val >> n) & 0xFF) / 255.0 for n in [24, 16, 8, 0]] return coin.SbColor(v[0], v[1], v[2]) - pref = PathPreferences.preferences() + pref = Path.Preferences.preferences() # R G B A npc = pref.GetUnsigned( "DefaultPathMarkerColor", ((85 * 256 + 255) * 256 + 0) * 256 + 255 diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/Path/GuiInit.py similarity index 97% rename from src/Mod/Path/PathScripts/PathGuiInit.py rename to src/Mod/Path/Path/GuiInit.py index cf2711f110..ff978b1620 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/Path/GuiInit.py @@ -38,7 +38,8 @@ def Startup(): global Processed if not Processed: Path.Log.debug("Initializing PathGui") - from Path.Op.Gui import Adaptive + from Path.Base.Gui import PropertyBag + from Path.Base.Gui import PathSetupSheetGui from Path.Dressup.Gui import AxisMap from Path.Dressup.Gui import Dogbone from Path.Dressup.Gui import Dragknife @@ -47,6 +48,7 @@ def Startup(): from Path.Dressup.Gui import RampEntry from Path.Dressup.Gui import Tags from Path.Dressup.Gui import ZCorrect + from Path.Op.Gui import Adaptive from Path.Op.Gui import Custom from Path.Op.Gui import Deburr from Path.Op.Gui import Drilling @@ -68,9 +70,7 @@ def Startup(): from PathScripts import PathFixture from PathScripts import PathHop from PathScripts import PathInspect - from PathScripts import PathPropertyBagGui from PathScripts import PathSanity - from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui from PathScripts import PathStop diff --git a/src/Mod/Path/Path/Op/Base.py b/src/Mod/Path/Path/Op/Base.py index 04c3d25408..fce232ab41 100644 --- a/src/Mod/Path/Path/Op/Base.py +++ b/src/Mod/Path/Path/Op/Base.py @@ -25,7 +25,6 @@ from PathScripts.PathUtils import waiting_effects from PySide.QtCore import QT_TRANSLATE_NOOP import Path import Path.Base.Util as PathUtil -import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtils as PathUtils import math import time @@ -844,7 +843,7 @@ class ObjectOp(object): if ( hFeedrate == 0 or vFeedrate == 0 - ) and not PathPreferences.suppressAllSpeedsWarning(): + ) and not Path.Preferences.suppressAllSpeedsWarning(): Path.Log.warning( translate( "Path", @@ -855,7 +854,7 @@ class ObjectOp(object): if ( hRapidrate == 0 or vRapidrate == 0 - ) and not PathPreferences.suppressRapidSpeedsWarning(): + ) and not Path.Preferences.suppressRapidSpeedsWarning(): Path.Log.warning( translate( "Path", diff --git a/src/Mod/Path/Path/Op/Gui/Base.py b/src/Mod/Path/Path/Op/Gui/Base.py index 7efbccb3f2..0876702507 100644 --- a/src/Mod/Path/Path/Op/Gui/Base.py +++ b/src/Mod/Path/Path/Op/Gui/Base.py @@ -23,15 +23,14 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.GetPoint as PathGetPoint +import Path.Base.Gui.Util as PathGuiUtil +import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil import Path.Op.Base as PathOp -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGetPoint as PathGetPoint -import PathScripts.PathGui as PathGui +import PathGui import PathScripts.PathJob as PathJob -import PathScripts.PathPreferences as PathPreferences import PathScripts.PathSelection as PathSelection -import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathUtils as PathUtils import importlib from PySide.QtCore import QT_TRANSLATE_NOOP @@ -375,8 +374,8 @@ class TaskPanelPage(object): return def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): - """populateCombobox(form, enumTups, comboBoxesPropertyMap) ... proxy for PathGui.populateCombobox()""" - PathGui.populateCombobox(form, enumTups, comboBoxesPropertyMap) + """populateCombobox(form, enumTups, comboBoxesPropertyMap) ... proxy for PathGuiUtil.populateCombobox()""" + PathGuiUtil.populateCombobox(form, enumTups, comboBoxesPropertyMap) def resetToolController(self, job, tc): if self.obj is not None: @@ -823,10 +822,10 @@ class TaskPanelHeightsPage(TaskPanelPage): return FreeCADGui.PySideUic.loadUi(":/panels/PageHeightsEdit.ui") def initPage(self, obj): - self.safeHeight = PathGui.QuantitySpinBox( + self.safeHeight = PathGuiUtil.QuantitySpinBox( self.form.safeHeight, obj, "SafeHeight" ) - self.clearanceHeight = PathGui.QuantitySpinBox( + self.clearanceHeight = PathGuiUtil.QuantitySpinBox( self.form.clearanceHeight, obj, "ClearanceHeight" ) @@ -891,7 +890,7 @@ class TaskPanelDepthsPage(TaskPanelPage): def initPage(self, obj): if self.haveStartDepth(): - self.startDepth = PathGui.QuantitySpinBox( + self.startDepth = PathGuiUtil.QuantitySpinBox( self.form.startDepth, obj, "StartDepth" ) else: @@ -900,7 +899,7 @@ class TaskPanelDepthsPage(TaskPanelPage): self.form.startDepthSet.hide() if self.haveFinalDepth(): - self.finalDepth = PathGui.QuantitySpinBox( + self.finalDepth = PathGuiUtil.QuantitySpinBox( self.form.finalDepth, obj, "FinalDepth" ) else: @@ -918,13 +917,13 @@ class TaskPanelDepthsPage(TaskPanelPage): self.form.finalDepthSet.hide() if self.haveStepDown(): - self.stepDown = PathGui.QuantitySpinBox(self.form.stepDown, obj, "StepDown") + self.stepDown = PathGuiUtil.QuantitySpinBox(self.form.stepDown, obj, "StepDown") else: self.form.stepDown.hide() self.form.stepDownLabel.hide() if self.haveFinishDepth(): - self.finishDepth = PathGui.QuantitySpinBox( + self.finishDepth = PathGuiUtil.QuantitySpinBox( self.form.finishDepth, obj, "FinishDepth" ) else: @@ -1029,10 +1028,10 @@ class TaskPanelDiametersPage(TaskPanelPage): return FreeCADGui.PySideUic.loadUi(":/panels/PageDiametersEdit.ui") def initPage(self, obj): - self.minDiameter = PathGui.QuantitySpinBox( + self.minDiameter = PathGuiUtil.QuantitySpinBox( self.form.minDiameter, obj, "MinDiameter" ) - self.maxDiameter = PathGui.QuantitySpinBox( + self.maxDiameter = PathGuiUtil.QuantitySpinBox( self.form.maxDiameter, obj, "MaxDiameter" ) @@ -1132,7 +1131,7 @@ class TaskPanel(object): page.initPage(obj) page.onDirtyChanged(self.pageDirtyChanged) - taskPanelLayout = PathPreferences.defaultTaskPanelLayout() + taskPanelLayout = Path.Preferences.defaultTaskPanelLayout() if taskPanelLayout < 2: opTitle = opPage.getTitle(obj) diff --git a/src/Mod/Path/Path/Op/Gui/CircularHoleBase.py b/src/Mod/Path/Path/Op/Gui/CircularHoleBase.py index 95d10bd4b4..cbd4e5eb7e 100644 --- a/src/Mod/Path/Path/Op/Gui/CircularHoleBase.py +++ b/src/Mod/Path/Path/Op/Gui/CircularHoleBase.py @@ -24,7 +24,7 @@ import FreeCAD import FreeCADGui import Path import Path.Op.Gui.Base as PathOpGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui from PySide import QtCore, QtGui diff --git a/src/Mod/Path/Path/Op/Gui/Deburr.py b/src/Mod/Path/Path/Op/Gui/Deburr.py index be4c67799a..64ceb850dc 100644 --- a/src/Mod/Path/Path/Op/Gui/Deburr.py +++ b/src/Mod/Path/Path/Op/Gui/Deburr.py @@ -23,9 +23,9 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Deburr as PathDeburr import Path.Op.Gui.Base as PathOpGui -import PathScripts.PathGui as PathGui from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP @@ -83,8 +83,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.joinRound.setIcon(iconRound) def getFields(self, obj): - PathGui.updateInputField(obj, "Width", self.form.value_W) - PathGui.updateInputField(obj, "ExtraDepth", self.form.value_h) + PathGuiUtil.updateInputField(obj, "Width", self.form.value_W) + PathGuiUtil.updateInputField(obj, "ExtraDepth", self.form.value_h) if self.form.joinRound.isChecked(): obj.Join = "Round" elif self.form.joinMiter.isChecked(): @@ -113,10 +113,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.Direction, self.form.direction) def updateWidth(self): - PathGui.updateInputField(self.obj, "Width", self.form.value_W) + PathGuiUtil.updateInputField(self.obj, "Width", self.form.value_W) def updateExtraDepth(self): - PathGui.updateInputField(self.obj, "ExtraDepth", self.form.value_h) + PathGuiUtil.updateInputField(self.obj, "ExtraDepth", self.form.value_h) def getSignalsForUpdate(self, obj): signals = [] diff --git a/src/Mod/Path/Path/Op/Gui/Drilling.py b/src/Mod/Path/Path/Op/Gui/Drilling.py index 5075cc5c30..67b7a3b0d8 100644 --- a/src/Mod/Path/Path/Op/Gui/Drilling.py +++ b/src/Mod/Path/Path/Op/Gui/Drilling.py @@ -23,11 +23,11 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Drilling as PathDrilling import Path.Op.Gui.Base as PathOpGui import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui from PySide import QtCore @@ -48,13 +48,13 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): """Controller for the drilling operation's page""" def initPage(self, obj): - self.peckDepthSpinBox = PathGui.QuantitySpinBox( + self.peckDepthSpinBox = PathGuiUtil.QuantitySpinBox( self.form.peckDepth, obj, "PeckDepth" ) - self.peckRetractSpinBox = PathGui.QuantitySpinBox( + self.peckRetractSpinBox = PathGuiUtil.QuantitySpinBox( self.form.peckRetractHeight, obj, "RetractHeight" ) - self.dwellTimeSpinBox = PathGui.QuantitySpinBox( + self.dwellTimeSpinBox = PathGuiUtil.QuantitySpinBox( self.form.dwellTime, obj, "DwellTime" ) self.form.chipBreakEnabled.setEnabled(False) diff --git a/src/Mod/Path/Path/Op/Gui/Engrave.py b/src/Mod/Path/Path/Op/Gui/Engrave.py index 0283d9b6f9..3ea572574e 100644 --- a/src/Mod/Path/Path/Op/Gui/Engrave.py +++ b/src/Mod/Path/Path/Op/Gui/Engrave.py @@ -25,7 +25,7 @@ import FreeCADGui import Path import Path.Op.Engrave as PathEngrave import Path.Op.Gui.Base as PathOpGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui diff --git a/src/Mod/Path/Path/Op/Gui/Helix.py b/src/Mod/Path/Path/Op/Gui/Helix.py index b74af23fe6..2a86d1ae23 100644 --- a/src/Mod/Path/Path/Op/Gui/Helix.py +++ b/src/Mod/Path/Path/Op/Gui/Helix.py @@ -23,11 +23,11 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui import Path.Op.Helix as PathHelix -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui from PySide.QtCore import QT_TRANSLATE_NOOP translate = FreeCAD.Qt.translate @@ -68,7 +68,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): obj.StartSide = str(self.form.startSide.currentData()) if obj.StepOver != self.form.stepOverPercent.value(): obj.StepOver = self.form.stepOverPercent.value() - PathGui.updateInputField(obj, "OffsetExtra", self.form.extraOffset) + PathGuiUtil.updateInputField(obj, "OffsetExtra", self.form.extraOffset) self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) diff --git a/src/Mod/Path/Path/Op/Gui/PocketBase.py b/src/Mod/Path/Path/Op/Gui/PocketBase.py index 0ba2f9647c..f0552f0e1a 100644 --- a/src/Mod/Path/Path/Op/Gui/PocketBase.py +++ b/src/Mod/Path/Path/Op/Gui/PocketBase.py @@ -23,10 +23,10 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Pocket as PathPocket -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui __title__ = "Path Pocket Base Operation UI" __author__ = "sliptonic (Brad Collette)" @@ -112,7 +112,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.form.zigZagAngle.setEnabled(True) if setModel: - PathGui.updateInputField(obj, "ZigZagAngle", self.form.zigZagAngle) + PathGuiUtil.updateInputField(obj, "ZigZagAngle", self.form.zigZagAngle) def getFields(self, obj): """getFields(obj) ... transfers values from UI to obj's proprties""" @@ -123,7 +123,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.OffsetPattern != str(self.form.offsetPattern.currentData()): obj.OffsetPattern = str(self.form.offsetPattern.currentData()) - PathGui.updateInputField(obj, "ExtraOffset", self.form.extraOffset) + PathGuiUtil.updateInputField(obj, "ExtraOffset", self.form.extraOffset) self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) self.updateZigZagAngle(obj) diff --git a/src/Mod/Path/Path/Op/Gui/Probe.py b/src/Mod/Path/Path/Op/Gui/Probe.py index 071881038d..fc9fc0076f 100644 --- a/src/Mod/Path/Path/Op/Gui/Probe.py +++ b/src/Mod/Path/Path/Op/Gui/Probe.py @@ -23,10 +23,10 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Probe as PathProbe -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui from PySide.QtCore import QT_TRANSLATE_NOOP from PySide import QtCore, QtGui @@ -56,8 +56,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): """getFields(obj) ... transfers values from UI to obj's proprties""" self.updateToolController(obj, self.form.toolController) - PathGui.updateInputField(obj, "Xoffset", self.form.Xoffset) - PathGui.updateInputField(obj, "Yoffset", self.form.Yoffset) + PathGuiUtil.updateInputField(obj, "Xoffset", self.form.Xoffset) + PathGuiUtil.updateInputField(obj, "Yoffset", self.form.Yoffset) obj.PointCountX = self.form.PointCountX.value() obj.PointCountY = self.form.PointCountY.value() obj.OutputFileName = str(self.form.OutputFileName.text()) diff --git a/src/Mod/Path/Path/Op/Gui/Profile.py b/src/Mod/Path/Path/Op/Gui/Profile.py index da44a3fae2..0ffede83d8 100644 --- a/src/Mod/Path/Path/Op/Gui/Profile.py +++ b/src/Mod/Path/Path/Op/Gui/Profile.py @@ -22,10 +22,10 @@ import FreeCAD import FreeCADGui +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Profile as PathProfile -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui from PySide.QtCore import QT_TRANSLATE_NOOP @@ -76,7 +76,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): obj.Side = str(self.form.cutSide.currentData()) if obj.Direction != str(self.form.direction.currentData()): obj.Direction = str(self.form.direction.currentData()) - PathGui.updateInputField(obj, "OffsetExtra", self.form.extraOffset) + PathGuiUtil.updateInputField(obj, "OffsetExtra", self.form.extraOffset) if obj.UseComp != self.form.useCompensation.isChecked(): obj.UseComp = self.form.useCompensation.isChecked() diff --git a/src/Mod/Path/Path/Op/Gui/Slot.py b/src/Mod/Path/Path/Op/Gui/Slot.py index 68657be2be..b1c56be72f 100644 --- a/src/Mod/Path/Path/Op/Gui/Slot.py +++ b/src/Mod/Path/Path/Op/Gui/Slot.py @@ -22,10 +22,10 @@ import FreeCAD import FreeCADGui +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Slot as PathSlot -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui from PySide import QtCore @@ -62,10 +62,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.setTitle("Slot - " + obj.Label) # retrieve property enumerations # Requirements due to Gui::QuantitySpinBox class use in UI panel - self.geo1Extension = PathGui.QuantitySpinBox( + self.geo1Extension = PathGuiUtil.QuantitySpinBox( self.form.geo1Extension, obj, "ExtendPathStart" ) - self.geo2Extension = PathGui.QuantitySpinBox( + self.geo2Extension = PathGuiUtil.QuantitySpinBox( self.form.geo2Extension, obj, "ExtendPathEnd" ) diff --git a/src/Mod/Path/Path/Op/Gui/Surface.py b/src/Mod/Path/Path/Op/Gui/Surface.py index c428ec23ec..fd927a1204 100644 --- a/src/Mod/Path/Path/Op/Gui/Surface.py +++ b/src/Mod/Path/Path/Op/Gui/Surface.py @@ -24,10 +24,10 @@ from PySide import QtCore import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Surface as PathSurface -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui __title__ = "Path Surface Operation UI" @@ -66,7 +66,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): ("dropCutterDirSelect", "DropCutterDir"), ] enumTups = PathSurface.ObjectSurface.propertyEnumerations(dataType="raw") - PathGui.populateCombobox(form, enumTups, comboToPropertyMap) + PathGuiUtil.populateCombobox(form, enumTups, comboToPropertyMap) return form @@ -120,12 +120,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentData()): obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentData()) - PathGui.updateInputField(obj, "DepthOffset", self.form.depthOffset) + PathGuiUtil.updateInputField(obj, "DepthOffset", self.form.depthOffset) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval) + PathGuiUtil.updateInputField(obj, "SampleInterval", self.form.sampleInterval) if obj.UseStartPoint != self.form.useStartPoint.isChecked(): obj.UseStartPoint = self.form.useStartPoint.isChecked() diff --git a/src/Mod/Path/Path/Op/Gui/ThreadMilling.py b/src/Mod/Path/Path/Op/Gui/ThreadMilling.py index 422b9cfd1d..464f78150e 100644 --- a/src/Mod/Path/Path/Op/Gui/ThreadMilling.py +++ b/src/Mod/Path/Path/Op/Gui/ThreadMilling.py @@ -23,11 +23,11 @@ import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui import Path.Op.ThreadMilling as PathThreadMilling -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui import csv from PySide.QtCore import QT_TRANSLATE_NOOP @@ -72,13 +72,13 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): """Controller for the thread milling operation's page""" def initPage(self, obj): - self.majorDia = PathGui.QuantitySpinBox( + self.majorDia = PathGuiUtil.QuantitySpinBox( self.form.threadMajor, obj, "MajorDiameter" ) - self.minorDia = PathGui.QuantitySpinBox( + self.minorDia = PathGuiUtil.QuantitySpinBox( self.form.threadMinor, obj, "MinorDiameter" ) - self.pitch = PathGui.QuantitySpinBox(self.form.threadPitch, obj, "Pitch") + self.pitch = PathGuiUtil.QuantitySpinBox(self.form.threadPitch, obj, "Pitch") def getForm(self): """getForm() ... return UI""" diff --git a/src/Mod/Path/Path/Op/Gui/Vcarve.py b/src/Mod/Path/Path/Op/Gui/Vcarve.py index dcff22fc07..d29194d9c4 100644 --- a/src/Mod/Path/Path/Op/Gui/Vcarve.py +++ b/src/Mod/Path/Path/Op/Gui/Vcarve.py @@ -25,7 +25,7 @@ import FreeCADGui import Path import Path.Op.Gui.Base as PathOpGui import Path.Op.Vcarve as PathVcarve -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui diff --git a/src/Mod/Path/Path/Op/Gui/Waterline.py b/src/Mod/Path/Path/Op/Gui/Waterline.py index a593fe97d9..41253d4d44 100644 --- a/src/Mod/Path/Path/Op/Gui/Waterline.py +++ b/src/Mod/Path/Path/Op/Gui/Waterline.py @@ -26,9 +26,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Waterline as PathWaterline -import PathScripts.PathGui as PathGui __title__ = "Path Waterline Operation UI" __author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)" @@ -61,7 +61,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): ("cutPattern", "CutPattern"), ] enumTups = PathWaterline.ObjectWaterline.propertyEnumerations(dataType="raw") - PathGui.populateCombobox(form, enumTups, comboToPropertyMap) + PathGuiUtil.populateCombobox(form, enumTups, comboToPropertyMap) return form def getFields(self, obj): @@ -81,14 +81,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.CutPattern != str(self.form.cutPattern.currentData()): obj.CutPattern = str(self.form.cutPattern.currentData()) - PathGui.updateInputField( + PathGuiUtil.updateInputField( obj, "BoundaryAdjustment", self.form.boundaryAdjustment ) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - PathGui.updateInputField(obj, "SampleInterval", self.form.sampleInterval) + PathGuiUtil.updateInputField(obj, "SampleInterval", self.form.sampleInterval) if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() diff --git a/src/Mod/Path/Path/Op/Vcarve.py b/src/Mod/Path/Path/Op/Vcarve.py index 03e5277596..15d98d7017 100644 --- a/src/Mod/Path/Path/Op/Vcarve.py +++ b/src/Mod/Path/Path/Op/Vcarve.py @@ -26,7 +26,6 @@ import Path import Path.Op.Base as PathOp import Path.Op.EngraveBase as PathEngraveBase import PathScripts.PathUtils as PathUtils -import PathScripts.PathPreferences as PathPreferences import math from PySide.QtCore import QT_TRANSLATE_NOOP @@ -244,7 +243,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): ) obj.Colinear = 10.0 obj.Discretize = 0.01 - obj.Tolerance = PathPreferences.defaultGeometryTolerance() + obj.Tolerance = Path.Preferences.defaultGeometryTolerance() self.setupAdditionalProperties(obj) def opOnDocumentRestored(self, obj): diff --git a/src/Mod/Path/Path/Op/Waterline.py b/src/Mod/Path/Path/Op/Waterline.py index 6f7a0328f4..96f742c12e 100644 --- a/src/Mod/Path/Path/Op/Waterline.py +++ b/src/Mod/Path/Path/Op/Waterline.py @@ -871,15 +871,13 @@ class ObjectWaterline(PathOp.ObjectOp): useDGT = True except AttributeError as ee: Path.Log.warning( - "{}\nPlease set Job.GeometryTolerance to an acceptable value. Using PathPreferences.defaultGeometryTolerance().".format( + "{}\nPlease set Job.GeometryTolerance to an acceptable value. Using Path.Preferences.defaultGeometryTolerance().".format( ee ) ) useDGT = True if useDGT: - import PathScripts.PathPreferences as PathPreferences - - self.geoTlrnc = PathPreferences.defaultGeometryTolerance() + self.geoTlrnc = Path.Preferences.defaultGeometryTolerance() # Calculate default depthparams for operation self.depthParams = PathUtils.depth_params( diff --git a/src/Mod/Path/Path/Post/Command.py b/src/Mod/Path/Path/Post/Command.py index 2e36110f04..b0733580d3 100644 --- a/src/Mod/Path/Path/Post/Command.py +++ b/src/Mod/Path/Path/Post/Command.py @@ -29,7 +29,6 @@ import FreeCADGui import Path import Path.Base.Util as PathUtil import PathScripts.PathJob as PathJob -import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtils as PathUtils import os import re @@ -160,7 +159,7 @@ def resolveFileName(job, subpartname, sequencenumber): validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"] # Look for preference default - outputpath, filename = os.path.split(PathPreferences.defaultOutputFile()) + outputpath, filename = os.path.split(Path.Preferences.defaultOutputFile()) filename, ext = os.path.splitext(filename) # Override with document default if it exists @@ -209,7 +208,7 @@ def resolveFileName(job, subpartname, sequencenumber): ) # This section determines whether user interaction is necessary - policy = PathPreferences.defaultOutputPolicy() + policy = Path.Preferences.defaultOutputPolicy() openDialog = policy == "Open File Dialog" # if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): @@ -427,7 +426,7 @@ class DlgSelectPostProcessor: def __init__(self, parent=None): self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgSelectPostProcessor.ui") firstItem = None - for post in PathPreferences.allEnabledPostProcessors(): + for post in Path.Preferences.allEnabledPostProcessors(): item = QtGui.QListWidgetItem(post) item.setFlags( QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled @@ -465,7 +464,7 @@ class CommandPathPost: def resolvePostProcessor(self, job): if hasattr(job, "PostProcessor"): - post = PathPreferences.defaultPostProcessor() + post = Path.Preferences.defaultPostProcessor() if job.PostProcessor: post = job.PostProcessor if post and PostProcessor.exists(post): @@ -502,7 +501,7 @@ class CommandPathPost: # slist = objs[1] Path.Log.track(objs, partname) - postArgs = PathPreferences.defaultPostProcessorArgs() + postArgs = Path.Preferences.defaultPostProcessorArgs() if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs: postArgs = job.PostProcessorArgs elif hasattr(job, "PostProcessor") and job.PostProcessor: diff --git a/src/Mod/Path/Path/Post/Processor.py b/src/Mod/Path/Path/Post/Processor.py index e57a47107c..c80bebaa46 100644 --- a/src/Mod/Path/Path/Post/Processor.py +++ b/src/Mod/Path/Path/Post/Processor.py @@ -21,7 +21,6 @@ # *************************************************************************** import Path -import PathScripts.PathPreferences as PathPreferences import sys Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) @@ -30,13 +29,13 @@ Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) class PostProcessor: @classmethod def exists(cls, processor): - return processor in PathPreferences.allAvailablePostProcessors() + return processor in Path.Preferences.allAvailablePostProcessors() @classmethod def load(cls, processor): Path.Log.track(processor) syspath = sys.path - paths = PathPreferences.searchPathsPost() + paths = Path.Preferences.searchPathsPost() paths.extend(sys.path) sys.path = paths diff --git a/src/Mod/Path/PathScripts/PathPreferences.py b/src/Mod/Path/Path/Preferences.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPreferences.py rename to src/Mod/Path/Path/Preferences.py index 23379446e5..5b8eaafd28 100644 --- a/src/Mod/Path/PathScripts/PathPreferences.py +++ b/src/Mod/Path/Path/Preferences.py @@ -24,7 +24,6 @@ import FreeCAD import Path import glob import os -from PySide.QtGui import QMessageBox if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) diff --git a/src/Mod/Path/Path/Tool/Bit.py b/src/Mod/Path/Path/Tool/Bit.py index 12f8469d7e..e562517a35 100644 --- a/src/Mod/Path/Path/Tool/Bit.py +++ b/src/Mod/Path/Path/Tool/Bit.py @@ -24,7 +24,6 @@ import FreeCAD import Path import Path.Base.Util as PathUtil import Path.Base.PropertyBag as PathPropertyBag -import PathScripts.PathPreferences as PathPreferences import json import os import zipfile @@ -62,7 +61,7 @@ def _findToolFile(name, containerFile, typ): paths = [os.path.join(rootPath, typ)] else: paths = [] - paths.extend(PathPreferences.searchPathsTool(typ)) + paths.extend(Path.Preferences.searchPathsTool(typ)) def _findFile(path, name): Path.Log.track(path, name) @@ -109,7 +108,7 @@ def findToolLibrary(name, path=None): def _findRelativePath(path, typ): Path.Log.track(path, typ) relative = path - for p in PathPreferences.searchPathsTool(typ): + for p in Path.Preferences.searchPathsTool(typ): if path.startswith(p): p = path[len(p) :] if os.path.sep == p[0]: @@ -447,7 +446,7 @@ class ToolBit(object): attrs = {} attrs["version"] = 2 attrs["name"] = obj.Label - if PathPreferences.toolsStoreAbsolutePaths(): + if Path.Preferences.toolsStoreAbsolutePaths(): attrs["shape"] = obj.BitShape else: # attrs['shape'] = findRelativePathShape(obj.BitShape) diff --git a/src/Mod/Path/Path/Tool/Controller.py b/src/Mod/Path/Path/Tool/Controller.py index 27ea829d06..ee90b9925d 100644 --- a/src/Mod/Path/Path/Tool/Controller.py +++ b/src/Mod/Path/Path/Tool/Controller.py @@ -26,7 +26,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import Path.Tool.Bit as PathToolBit -import PathScripts.PathPreferences as PathPreferences from Generators import toolchange_generator as toolchange_generator from Generators.toolchange_generator import SpindleDirection diff --git a/src/Mod/Path/Path/Tool/Gui/Bit.py b/src/Mod/Path/Path/Tool/Gui/Bit.py index dd19c85ae3..53195f4cf1 100644 --- a/src/Mod/Path/Path/Tool/Gui/Bit.py +++ b/src/Mod/Path/Path/Tool/Gui/Bit.py @@ -25,10 +25,9 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.IconViewProvider as PathIconViewProvider import Path.Tool.Bit as PathToolBit import Path.Tool.Gui.BitEdit as PathToolBitEdit -import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathPreferences as PathPreferences import os __title__ = "Tool Bit UI" @@ -191,7 +190,7 @@ def GetNewToolFile(parent=None): parent = QtGui.QApplication.activeWindow() foo = QtGui.QFileDialog.getSaveFileName( - parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb" + parent, "Tool", Path.Preferences.lastPathToolBit(), "*.fctb" ) if foo and foo[0]: if not isValidFileName(foo[0]): @@ -200,7 +199,7 @@ def GetNewToolFile(parent=None): msgBox.setText(msg) msgBox.exec_() else: - PathPreferences.setLastPathToolBit(os.path.dirname(foo[0])) + Path.Preferences.setLastPathToolBit(os.path.dirname(foo[0])) return foo[0] return None @@ -209,10 +208,10 @@ def GetToolFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() foo = QtGui.QFileDialog.getOpenFileName( - parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb" + parent, "Tool", Path.Preferences.lastPathToolBit(), "*.fctb" ) if foo and foo[0]: - PathPreferences.setLastPathToolBit(os.path.dirname(foo[0])) + Path.Preferences.setLastPathToolBit(os.path.dirname(foo[0])) return foo[0] return None @@ -221,10 +220,10 @@ def GetToolFiles(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() foo = QtGui.QFileDialog.getOpenFileNames( - parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb" + parent, "Tool", Path.Preferences.lastPathToolBit(), "*.fctb" ) if foo and foo[0]: - PathPreferences.setLastPathToolBit(os.path.dirname(foo[0][0])) + Path.Preferences.setLastPathToolBit(os.path.dirname(foo[0][0])) return foo[0] return [] @@ -233,11 +232,11 @@ def GetToolShapeFile(parent=None): if parent is None: parent = QtGui.QApplication.activeWindow() - location = PathPreferences.lastPathToolShape() + location = Path.Preferences.lastPathToolShape() if os.path.isfile(location): location = os.path.split(location)[0] elif not os.path.isdir(location): - location = PathPreferences.filePath() + location = Path.Preferences.filePath() fname = QtGui.QFileDialog.getOpenFileName( parent, "Select Tool Shape", location, "*.fcstd" @@ -245,7 +244,7 @@ def GetToolShapeFile(parent=None): if fname and fname[0]: if fname != location: newloc = os.path.dirname(fname[0]) - PathPreferences.setLastPathToolShape(newloc) + Path.Preferences.setLastPathToolShape(newloc) return fname[0] else: return None diff --git a/src/Mod/Path/Path/Tool/Gui/BitCmd.py b/src/Mod/Path/Path/Tool/Gui/BitCmd.py index 61ceda5093..007717868e 100644 --- a/src/Mod/Path/Path/Tool/Gui/BitCmd.py +++ b/src/Mod/Path/Path/Tool/Gui/BitCmd.py @@ -108,7 +108,7 @@ class CommandToolBitSave: fname = tool.File else: fname = os.path.join( - PathScripts.PathPreferences.lastPathToolBit(), + Path.Preferences.lastPathToolBit(), tool.Label + ".fctb", ) foo = QtGui.QFileDialog.getSaveFileName( @@ -123,7 +123,7 @@ class CommandToolBitSave: if not path.endswith(".fctb"): path += ".fctb" tool.Proxy.saveToFile(tool, path) - PathScripts.PathPreferences.setLastPathToolBit(os.path.dirname(path)) + Path.Preferences.setLastPathToolBit(os.path.dirname(path)) class CommandToolBitLoad: diff --git a/src/Mod/Path/Path/Tool/Gui/BitEdit.py b/src/Mod/Path/Path/Tool/Gui/BitEdit.py index 30f1a4b1ab..1551f79209 100644 --- a/src/Mod/Path/Path/Tool/Gui/BitEdit.py +++ b/src/Mod/Path/Path/Tool/Gui/BitEdit.py @@ -23,10 +23,9 @@ from PySide import QtCore, QtGui import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil +import Path.Base.PropertyEditor as PathPropertyEditor import Path.Base.Util as PathUtil -import PathScripts.PathGui as PathGui -import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathPropertyEditor as PathPropertyEditor import os import re @@ -128,7 +127,7 @@ class ToolBitEditor(object): qsb.show() else: qsb = ui.createWidget("Gui::QuantitySpinBox") - editor = PathGui.QuantitySpinBox(qsb, tool, name) + editor = PathGuiUtil.QuantitySpinBox(qsb, tool, name) label = QtGui.QLabel(labelText(name)) self.widgets.append((label, qsb, editor)) Path.Log.debug("create row: {} [{}] {}".format(nr, name, type(qsb))) @@ -262,12 +261,12 @@ class ToolBitEditor(object): Path.Log.track() path = self.tool.BitShape if not path: - path = PathPreferences.lastPathToolShape() + path = Path.Preferences.lastPathToolShape() foo = QtGui.QFileDialog.getOpenFileName( self.form, "Path - Tool Shape", path, "*.fcstd" ) if foo and foo[0]: - PathPreferences.setLastPathToolShape(os.path.dirname(foo[0])) + Path.Preferences.setLastPathToolShape(os.path.dirname(foo[0])) self.form.shapePath.setText(foo[0]) self.updateShape() diff --git a/src/Mod/Path/Path/Tool/Gui/BitLibrary.py b/src/Mod/Path/Path/Tool/Gui/BitLibrary.py index 2c0a4f1442..85b791eeca 100644 --- a/src/Mod/Path/Path/Tool/Gui/BitLibrary.py +++ b/src/Mod/Path/Path/Tool/Gui/BitLibrary.py @@ -29,8 +29,7 @@ import Path.Tool.Bit as PathToolBit import Path.Tool.Gui.Bit as PathToolBitGui import Path.Tool.Gui.BitEdit as PathToolBitEdit import Path.Tool.Gui.Controller as PathToolControllerGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathPreferences as PathPreferences +import PathGui import PathScripts.PathUtilsGui as PathUtilsGui import PySide import glob @@ -61,8 +60,8 @@ def checkWorkingDir(): # working directory should be writable Path.Log.track() - workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) - defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) + workingdir = os.path.dirname(Path.Preferences.lastPathToolLibrary()) + defaultdir = os.path.dirname(Path.Preferences.pathDefaultToolsPath()) Path.Log.debug("workingdir: {} defaultdir: {}".format(workingdir, defaultdir)) @@ -85,16 +84,16 @@ def checkWorkingDir(): msg = translate("Path_ToolBit", "Choose a writable location for your toolbits") while not dirOK(): workingdir = PySide.QtGui.QFileDialog.getExistingDirectory( - None, msg, PathPreferences.filePath() + None, msg, Path.Preferences.filePath() ) if workingdir[-8:] == os.path.sep + "Library": workingdir = workingdir[:-8] # trim off trailing /Library if user chose it - PathPreferences.setLastPathToolLibrary( + Path.Preferences.setLastPathToolLibrary( "{}{}Library".format(workingdir, os.path.sep) ) - PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) + Path.Preferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) Path.Log.debug("setting workingdir to: {}".format(workingdir)) # Copy only files of default Path/Tool folder to working directory (targeting the README.md help file) @@ -154,14 +153,14 @@ def checkWorkingDir(): shutil.copy(full_file_name, subdir) # if no library is set, choose the first one in the Library directory - if PathPreferences.lastFileToolLibrary() is None: + if Path.Preferences.lastFileToolLibrary() is None: libFiles = [ f for f in glob.glob( - PathPreferences.lastPathToolLibrary() + os.path.sep + "*.fctl" + Path.Preferences.lastPathToolLibrary() + os.path.sep + "*.fctl" ) ] - PathPreferences.setLastFileToolLibrary(libFiles[0]) + Path.Preferences.setLastFileToolLibrary(libFiles[0]) return True @@ -251,7 +250,7 @@ class ModelFactory(object): def __libraryLoad(self, path, datamodel): Path.Log.track(path) - PathPreferences.setLastFileToolLibrary(path) + Path.Preferences.setLastFileToolLibrary(path) # self.currenLib = path with open(path) as fp: @@ -321,7 +320,7 @@ class ModelFactory(object): Returns a QStandardItemModel """ Path.Log.track() - path = PathPreferences.lastPathToolLibrary() + path = Path.Preferences.lastPathToolLibrary() if os.path.isdir(path): # opening all tables in a directory libFiles = [f for f in glob.glob(path + os.path.sep + "*.fctl")] @@ -346,7 +345,7 @@ class ModelFactory(object): Path.Log.track(lib) if lib == "": - lib = PathPreferences.lastFileToolLibrary() + lib = Path.Preferences.lastFileToolLibrary() if lib == "" or lib is None: return model @@ -373,7 +372,7 @@ class ToolBitSelector(object): return ["#", "Tool"] def currentLibrary(self, shortNameOnly): - libfile = PathPreferences.lastFileToolLibrary() + libfile = Path.Preferences.lastFileToolLibrary() if libfile is None or libfile == "": return "" elif shortNameOnly: @@ -578,12 +577,12 @@ class ToolBitLibrary(object): def libraryPath(self): Path.Log.track() path = PySide.QtGui.QFileDialog.getExistingDirectory( - self.form, "Tool Library Path", PathPreferences.lastPathToolLibrary() + self.form, "Tool Library Path", Path.Preferences.lastPathToolLibrary() ) if len(path) == 0: return - PathPreferences.setLastPathToolLibrary(path) + Path.Preferences.setLastPathToolLibrary(path) self.loadData() def cleanupDocument(self): @@ -669,7 +668,7 @@ class ToolBitLibrary(object): filename = PySide.QtGui.QFileDialog.getSaveFileName( self.form, translate("Path_ToolBit", "Save toolbit library"), - PathPreferences.lastPathToolLibrary(), + Path.Preferences.lastPathToolLibrary(), "{}".format(TooltableTypeJSON), ) @@ -700,7 +699,7 @@ class ToolBitLibrary(object): self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole ) toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) - if PathPreferences.toolsStoreAbsolutePaths(): + if Path.Preferences.toolsStoreAbsolutePaths(): bitPath = toolPath else: # bitPath = PathToolBit.findRelativePathTool(toolPath) @@ -720,8 +719,8 @@ class ToolBitLibrary(object): self.form.close() def libPaths(self): - lib = PathPreferences.lastFileToolLibrary() - loc = PathPreferences.lastPathToolLibrary() + lib = Path.Preferences.lastFileToolLibrary() + loc = Path.Preferences.lastPathToolLibrary() Path.Log.track("lib: {} loc: {}".format(lib, loc)) return lib, loc @@ -747,7 +746,7 @@ class ToolBitLibrary(object): self.factory.libraryOpen(self.toolModel, lib=path) self.path = path - self.form.setWindowTitle("{}".format(PathPreferences.lastPathToolLibrary())) + self.form.setWindowTitle("{}".format(Path.Preferences.lastPathToolLibrary())) self.toolModel.setHorizontalHeaderLabels(self.columnNames()) self.listModel.setHorizontalHeaderLabels(["Library"]) @@ -799,7 +798,7 @@ class ToolBitLibrary(object): filename = PySide.QtGui.QFileDialog.getSaveFileName( self.form, translate("Path_ToolBit", "Save toolbit library"), - PathPreferences.lastPathToolLibrary(), + Path.Preferences.lastPathToolLibrary(), "{};;{};;{}".format( TooltableTypeJSON, TooltableTypeLinuxCNC, TooltableTypeCamotics ), diff --git a/src/Mod/Path/Path/Tool/Gui/Controller.py b/src/Mod/Path/Path/Tool/Gui/Controller.py index 2908b06275..c19937f777 100644 --- a/src/Mod/Path/Path/Tool/Gui/Controller.py +++ b/src/Mod/Path/Path/Tool/Gui/Controller.py @@ -25,11 +25,11 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Base.Util as PathUtil import Path.Tool.Controller as PathToolController import Path.Tool.Gui.Bit as PathToolBitGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded -import PathScripts.PathGui as PathGui +import PathGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -188,11 +188,11 @@ class ToolControllerEditor(object): dataType="raw" ) - PathGui.populateCombobox(self.form, enumTups, comboToPropertyMap) - self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj, "VertFeed") - self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj, "HorizFeed") - self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj, "VertRapid") - self.horizRapid = PathGui.QuantitySpinBox( + PathGuiUtil.populateCombobox(self.form, enumTups, comboToPropertyMap) + self.vertFeed = PathGuiUtil.QuantitySpinBox(self.form.vertFeed, obj, "VertFeed") + self.horizFeed = PathGuiUtil.QuantitySpinBox(self.form.horizFeed, obj, "HorizFeed") + self.vertRapid = PathGuiUtil.QuantitySpinBox(self.form.vertRapid, obj, "VertRapid") + self.horizRapid = PathGuiUtil.QuantitySpinBox( self.form.horizRapid, obj, "HorizRapid" ) diff --git a/src/Mod/Path/Path/__init__.py b/src/Mod/Path/Path/__init__.py index a1bfe7366d..d3104b93f8 100644 --- a/src/Mod/Path/Path/__init__.py +++ b/src/Mod/Path/Path/__init__.py @@ -2,3 +2,4 @@ from PathApp import * import Path.Log as Log import Path.Geom as Geom +import Path.Preferences as Preferences diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py index e43ab53242..5df092ddb0 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py +++ b/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py @@ -25,9 +25,9 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import PathScripts.PathFeatureExtensions as FeatureExtensions -import PathScripts.PathGui as PathGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -197,7 +197,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): self.extensions = list() - self.defaultLength = PathGui.QuantitySpinBox( + self.defaultLength = PathGuiUtil.QuantitySpinBox( self.form.defaultLength, obj, "ExtensionLengthDefault" ) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 0cda5d0c47..155fee3416 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -24,11 +24,10 @@ from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path +import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil from Path.Post.Processor import PostProcessor import Path.Tool.Controller as PathToolController -import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock import json import time @@ -83,9 +82,9 @@ def createResourceClone(obj, orig, name, icon): clone.addProperty("App::PropertyString", "PathResource") clone.PathResource = name if clone.ViewObject: - import PathScripts.PathIconViewProvider + import Path.Base.Gui.IconViewProvider - PathScripts.PathIconViewProvider.Attach(clone.ViewObject, icon) + Path.Base.Gui.IconViewProvider.Attach(clone.ViewObject, icon) clone.ViewObject.Visibility = False clone.ViewObject.Transparency = 80 obj.Document.recompute() # necessary to create the clone shape @@ -223,16 +222,16 @@ class ObjectJob: for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) - obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() - obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors() - defaultPostProcessor = PathPreferences.defaultPostProcessor() + obj.PostProcessorOutputFile = Path.Preferences.defaultOutputFile() + obj.PostProcessor = postProcessors = Path.Preferences.allEnabledPostProcessors() + defaultPostProcessor = Path.Preferences.defaultPostProcessor() # Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed) if defaultPostProcessor in postProcessors: obj.PostProcessor = defaultPostProcessor else: obj.PostProcessor = postProcessors[0] - obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs() - obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance() + obj.PostProcessorArgs = Path.Preferences.defaultPostProcessorArgs() + obj.GeometryTolerance = Path.Preferences.defaultGeometryTolerance() self.setupOperations(obj) self.setupSetupSheet(obj) @@ -307,9 +306,9 @@ class ObjectJob: ) obj.SetupSheet = PathSetupSheet.Create() if obj.SetupSheet.ViewObject: - import PathScripts.PathIconViewProvider + import Path.Base.Gui.IconViewProvider - PathScripts.PathIconViewProvider.Attach( + Path.Base.Gui.IconViewProvider.Attach( obj.SetupSheet.ViewObject, "SetupSheet" ) obj.SetupSheet.Label = "SetupSheet" @@ -383,7 +382,7 @@ class ObjectJob: def setupStock(self, obj): """setupStock(obj)... setup the Stock for the Job object.""" if not obj.Stock: - stockTemplate = PathPreferences.defaultStockTemplate() + stockTemplate = Path.Preferences.defaultStockTemplate() if stockTemplate: obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate)) if not obj.Stock: diff --git a/src/Mod/Path/PathScripts/PathJobCmd.py b/src/Mod/Path/PathScripts/PathJobCmd.py index e3a1adddba..0c0c820d1c 100644 --- a/src/Mod/Path/PathScripts/PathJobCmd.py +++ b/src/Mod/Path/PathScripts/PathJobCmd.py @@ -28,7 +28,6 @@ import Path import Path.Base.Util as PathUtil import PathScripts.PathJob as PathJob import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import json import os @@ -136,7 +135,7 @@ class CommandJobTemplateExport: foo = QtGui.QFileDialog.getSaveFileName( QtGui.QApplication.activeWindow(), "Path - Job Template", - PathPreferences.filePath(), + Path.Preferences.filePath(), "job_*.json", )[0] if foo: diff --git a/src/Mod/Path/PathScripts/PathJobDlg.py b/src/Mod/Path/PathScripts/PathJobDlg.py index d21b1761fe..daf63c7fff 100644 --- a/src/Mod/Path/PathScripts/PathJobDlg.py +++ b/src/Mod/Path/PathScripts/PathJobDlg.py @@ -27,7 +27,6 @@ import FreeCADGui import Path import Path.Base.Util as PathUtil import PathScripts.PathJob as PathJob -import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import glob import os @@ -223,7 +222,7 @@ class JobCreate: def setupTemplate(self): templateFiles = [] - for path in PathPreferences.searchPaths(): + for path in Path.Preferences.searchPaths(): cleanPaths = [ f.replace("\\", "/") for f in self.templateFilesIn(path) ] # Standardize slashes used across os platforms @@ -240,7 +239,7 @@ class JobCreate: name = basename + " (%s)" % i Path.Log.track(name, tFile) template[name] = tFile - selectTemplate = PathPreferences.defaultJobTemplate() + selectTemplate = Path.Preferences.defaultJobTemplate() index = 0 self.dialog.jobTemplate.addItem("", "") for name in sorted(template.keys()): diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index e17c30d375..cfc73c9428 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -28,15 +28,14 @@ from pivy import coin import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.SetupSheetGui as PathSetupSheetGui import Path.Base.Util as PathUtil +import Path.GuiInit as PathGuiInit import Path.Tool.Gui.Bit as PathToolBitGui import Path.Tool.Gui.Controller as PathToolControllerGui -import PathScripts.PathGuiInit as PathGuiInit import PathScripts.PathJob as PathJob import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathPreferences as PathPreferences -import PathScripts.PathSetupSheetGui as PathSetupSheetGui import PathScripts.PathStock as PathStock import PathScripts.PathUtils as PathUtils import json @@ -635,7 +634,7 @@ class TaskPanel: self.form.toolControllerList.resizeColumnsToContents() currentPostProcessor = self.obj.PostProcessor - postProcessors = PathPreferences.allEnabledPostProcessors( + postProcessors = Path.Preferences.allEnabledPostProcessors( ["", currentPostProcessor] ) for post in postProcessors: @@ -999,7 +998,7 @@ class TaskPanel: tools = PathToolBitGui.LoadTools() - curLib = PathPreferences.lastFileToolLibrary() + curLib = Path.Preferences.lastFileToolLibrary() library = None if curLib is not None: diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py index 7acb341e07..400454c3ba 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py @@ -23,7 +23,6 @@ import FreeCAD import Path import Path.Post.Processor as PostProcessor -import PathScripts.PathPreferences as PathPreferences import PathScripts.PathStock as PathStock import json @@ -52,7 +51,7 @@ class JobPreferencesPage: jobTemplate = self.form.leDefaultJobTemplate.text() geometryTolerance = Units.Quantity(self.form.geometryTolerance.text()) curveAccuracy = Units.Quantity(self.form.curveAccuracy.text()) - PathPreferences.setJobDefaults( + Path.Preferences.setJobDefaults( filePath, jobTemplate, geometryTolerance, curveAccuracy ) @@ -66,11 +65,11 @@ class JobPreferencesPage: item = self.form.postProcessorList.item(i) if item.checkState() == QtCore.Qt.CheckState.Unchecked: blacklist.append(item.text()) - PathPreferences.setPostProcessorDefaults(processor, args, blacklist) + Path.Preferences.setPostProcessorDefaults(processor, args, blacklist) path = str(self.form.leOutputFile.text()) policy = str(self.form.cboOutputPolicy.currentText()) - PathPreferences.setOutputFileDefaults(path, policy) + Path.Preferences.setOutputFileDefaults(path, policy) self.saveStockSettings() self.saveToolsSettings() @@ -141,12 +140,12 @@ class JobPreferencesPage: attrs["posZ"] = FreeCAD.Units.Quantity( self.form.stockPositionZ.text() ).Value - PathPreferences.setDefaultStockTemplate(json.dumps(attrs)) + Path.Preferences.setDefaultStockTemplate(json.dumps(attrs)) else: - PathPreferences.setDefaultStockTemplate("") + Path.Preferences.setDefaultStockTemplate("") def saveToolsSettings(self): - PathPreferences.setToolsSettings( + Path.Preferences.setToolsSettings( self.form.toolsAbsolutePaths.isChecked() ) @@ -181,11 +180,11 @@ class JobPreferencesPage: ) def loadSettings(self): - self.form.leDefaultFilePath.setText(PathPreferences.defaultFilePath()) - self.form.leDefaultJobTemplate.setText(PathPreferences.defaultJobTemplate()) + self.form.leDefaultFilePath.setText(Path.Preferences.defaultFilePath()) + self.form.leDefaultJobTemplate.setText(Path.Preferences.defaultJobTemplate()) - blacklist = PathPreferences.postProcessorBlacklist() - for processor in PathPreferences.allAvailablePostProcessors(): + blacklist = Path.Preferences.postProcessorBlacklist() + for processor in Path.Preferences.allAvailablePostProcessors(): item = QtGui.QListWidgetItem(processor) if processor in blacklist: item.setCheckState(QtCore.Qt.CheckState.Unchecked) @@ -198,26 +197,26 @@ class JobPreferencesPage: ) self.form.postProcessorList.addItem(item) self.verifyAndUpdateDefaultPostProcessorWith( - PathPreferences.defaultPostProcessor() + Path.Preferences.defaultPostProcessor() ) self.form.defaultPostProcessorArgs.setText( - PathPreferences.defaultPostProcessorArgs() + Path.Preferences.defaultPostProcessorArgs() ) geomTol = Units.Quantity( - PathPreferences.defaultGeometryTolerance(), Units.Length + Path.Preferences.defaultGeometryTolerance(), Units.Length ) self.form.geometryTolerance.setText(geomTol.UserString) self.form.curveAccuracy.setText( Units.Quantity( - PathPreferences.defaultLibAreaCurveAccuracy(), Units.Length + Path.Preferences.defaultLibAreaCurveAccuracy(), Units.Length ).UserString ) - self.form.leOutputFile.setText(PathPreferences.defaultOutputFile()) + self.form.leOutputFile.setText(Path.Preferences.defaultOutputFile()) self.selectComboEntry( - self.form.cboOutputPolicy, PathPreferences.defaultOutputPolicy() + self.form.cboOutputPolicy, Path.Preferences.defaultOutputPolicy() ) self.form.tbDefaultFilePath.clicked.connect(self.browseDefaultFilePath) @@ -235,7 +234,7 @@ class JobPreferencesPage: self.loadToolSettings() def loadStockSettings(self): - stock = PathPreferences.defaultStockTemplate() + stock = Path.Preferences.defaultStockTemplate() index = -1 if stock: attrs = json.loads(stock) @@ -328,7 +327,7 @@ class JobPreferencesPage: def loadToolSettings(self): self.form.toolsAbsolutePaths.setChecked( - PathPreferences.toolsStoreAbsolutePaths() + Path.Preferences.toolsStoreAbsolutePaths() ) def getPostProcessor(self, name): @@ -370,7 +369,7 @@ class JobPreferencesPage: def bestGuessForFilePath(self): path = self.form.leDefaultFilePath.text() if not path: - path = PathPreferences.filePath() + path = Path.Preferences.filePath() return path def browseDefaultJobTemplate(self): diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 02c79bdaf3..fe9fc1472e 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -35,7 +35,6 @@ import FreeCADGui import Path import Path.Base.Util as PathUtil import PathScripts -import PathScripts.PathPreferences as PathPreferences from collections import Counter from datetime import datetime import os @@ -55,10 +54,10 @@ class CommandPathSanity: def resolveOutputPath(self, job): if job.PostProcessorOutputFile != "": filepath = job.PostProcessorOutputFile - elif PathPreferences.defaultOutputFile() != "": - filepath = PathPreferences.defaultOutputFile() + elif Path.Preferences.defaultOutputFile() != "": + filepath = Path.Preferences.defaultOutputFile() else: - filepath = PathPreferences.macroFilePath() + filepath = Path.Preferences.macroFilePath() if "%D" in filepath: D = FreeCAD.ActiveDocument.FileName diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 9bc21f6a9b..ecb7c0d0ee 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -26,7 +26,6 @@ import FreeCAD import FreeCADGui import Path -import PathScripts.PathPreferences as PathPreferences import PathScripts.drillableLib as drillableLib import math @@ -255,61 +254,61 @@ class ALLGate(PathBaseGate): def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Contour Select Mode\n") def eselect(): FreeCADGui.Selection.addSelectionGate(EGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Edge Select Mode\n") def drillselect(): FreeCADGui.Selection.addSelectionGate(DRILLGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Drilling Select Mode\n") def engraveselect(): FreeCADGui.Selection.addSelectionGate(ENGRAVEGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Engraving Select Mode\n") def fselect(): FreeCADGui.Selection.addSelectionGate(FACEGate()) # Was PROFILEGate() - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Profiling Select Mode\n") def chamferselect(): FreeCADGui.Selection.addSelectionGate(CHAMFERGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Deburr Select Mode\n") def profileselect(): FreeCADGui.Selection.addSelectionGate(PROFILEGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Profiling Select Mode\n") def pocketselect(): FreeCADGui.Selection.addSelectionGate(POCKETGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Pocketing Select Mode\n") def adaptiveselect(): FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Adaptive Select Mode\n") def slotselect(): FreeCADGui.Selection.addSelectionGate(ALLGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Slot Cutter Select Mode\n") @@ -318,30 +317,30 @@ def surfaceselect(): if MESHGate() or FACEGate(): gate = True FreeCADGui.Selection.addSelectionGate(gate) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Surfacing Select Mode\n") def vcarveselect(): FreeCADGui.Selection.addSelectionGate(VCARVEGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Vcarve Select Mode\n") def probeselect(): FreeCADGui.Selection.addSelectionGate(PROBEGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Probe Select Mode\n") def customselect(): - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Custom Select Mode\n") def turnselect(): FreeCADGui.Selection.addSelectionGate(TURNGate()) - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Turning Select Mode\n") @@ -377,5 +376,5 @@ def select(op): def clear(): FreeCADGui.Selection.removeSelectionGate() - if not PathPreferences.suppressSelectionModeWarning(): + if not Path.Preferences.suppressSelectionModeWarning(): FreeCAD.Console.PrintWarning("Free Select\n") diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 52aec327a1..c3731e4730 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -24,7 +24,7 @@ import FreeCAD import Path import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui import PathScripts.PathJob as PathJob import PathSimulator import math diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py index 9dfa41ecef..b8d768add1 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -338,9 +338,9 @@ def SetupStockObject(obj, stockType): obj.StockType = stockType obj.setEditorMode("StockType", 2) # hide - import PathScripts.PathIconViewProvider + import Path.Base.Gui.IconViewProvider - PathScripts.PathIconViewProvider.ViewProvider(obj.ViewObject, "Stock") + Path.Base.Gui.IconViewProvider.ViewProvider(obj.ViewObject, "Stock") obj.ViewObject.Transparency = 90 obj.ViewObject.DisplayMode = "Wireframe" diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index dd758d1972..06a8d6ec82 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -24,7 +24,7 @@ import FreeCADGui import FreeCAD import Path import Path.Tool.Controller as PathToolController -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils from PySide import QtGui diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index 720f0eaa2f..55240d00c1 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -29,7 +29,6 @@ import FreeCAD import Path import Path.Post.Command as PathPost -from PathScripts import PathPreferences import Path.Post.Utils as PostUtils import Path.Post.Processor as PostProcessor @@ -409,7 +408,7 @@ class TestOutputNameSubstitution(unittest.TestCase): FreeCAD.setActiveDocument(self.doc.Label) teststring = "" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) self.job.SplitOutput = False @@ -425,7 +424,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # Test basic string substitution without splitting teststring = "~/Desktop/%j.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) self.job.SplitOutput = False @@ -443,7 +442,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # Substitute current file path teststring = "%D/testfile.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -457,7 +456,7 @@ class TestOutputNameSubstitution(unittest.TestCase): def test020(self): teststring = "%d.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -468,7 +467,7 @@ class TestOutputNameSubstitution(unittest.TestCase): def test030(self): teststring = "%M/outfile.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -480,7 +479,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # unused substitution strings should be ignored teststring = "%d%T%t%W%O/testdoc.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -495,7 +494,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # explicitly using the sequence number should include it where indicated. teststring = "%S-%d.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -512,7 +511,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # substitute jobname and use default sequence numbers teststring = "%j.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) subpart, objs = outlist[0] @@ -525,7 +524,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # Use Toolnumbers and default sequence numbers teststring = "%T.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -539,7 +538,7 @@ class TestOutputNameSubstitution(unittest.TestCase): # Use Tooldescriptions and default sequence numbers teststring = "%t.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) outlist = PathPost.buildPostList(self.job) @@ -558,7 +557,7 @@ class TestOutputNameSubstitution(unittest.TestCase): teststring = "%j.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) subpart, objs = outlist[0] @@ -570,7 +569,7 @@ class TestOutputNameSubstitution(unittest.TestCase): teststring = "%W-%j.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) subpart, objs = outlist[0] @@ -588,7 +587,7 @@ class TestOutputNameSubstitution(unittest.TestCase): teststring = "%j.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) subpart, objs = outlist[0] @@ -600,7 +599,7 @@ class TestOutputNameSubstitution(unittest.TestCase): teststring = "%O-%j.nc" self.job.PostProcessorOutputFile = teststring - PathPreferences.setOutputFileDefaults( + Path.Preferences.setOutputFileDefaults( teststring, "Append Unique ID on conflict" ) subpart, objs = outlist[0] diff --git a/src/Mod/Path/PathTests/TestPathPreferences.py b/src/Mod/Path/PathTests/TestPathPreferences.py index b59ce2950b..0febdf5b35 100644 --- a/src/Mod/Path/PathTests/TestPathPreferences.py +++ b/src/Mod/Path/PathTests/TestPathPreferences.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -import PathScripts.PathPreferences as PathPreferences +import Path import PathTests.PathTestUtils as PathTestUtils @@ -28,22 +28,22 @@ class TestPathPreferences(PathTestUtils.PathTestBase): def test00(self): """There is at least one search path.""" - paths = PathPreferences.searchPaths() + paths = Path.Preferences.searchPaths() self.assertGreater(len(paths), 0) def test01(self): """Path/Post is part of the posts search path.""" - paths = PathPreferences.searchPathsPost() + paths = Path.Preferences.searchPathsPost() self.assertEqual(len([p for p in paths if p.endswith("/Path/Post/")]), 1) def test02(self): """Path/Post/scripts is part of the posts search path.""" - paths = PathPreferences.searchPathsPost() + paths = Path.Preferences.searchPathsPost() 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.""" - posts = PathPreferences.allAvailablePostProcessors() + posts = Path.Preferences.allAvailablePostProcessors() self.assertTrue("linuxcnc" in posts) self.assertTrue("grbl" in posts) self.assertTrue("opensbp" in posts) @@ -51,17 +51,17 @@ class TestPathPreferences(PathTestUtils.PathTestBase): def test10(self): """Default paths for tools are resolved correctly""" - self.assertTrue(PathPreferences.pathDefaultToolsPath().endswith("/Path/Tools/")) + self.assertTrue(Path.Preferences.pathDefaultToolsPath().endswith("/Path/Tools/")) self.assertTrue( - PathPreferences.pathDefaultToolsPath("Bit").endswith("/Path/Tools/Bit") + Path.Preferences.pathDefaultToolsPath("Bit").endswith("/Path/Tools/Bit") ) self.assertTrue( - PathPreferences.pathDefaultToolsPath("Library").endswith( + Path.Preferences.pathDefaultToolsPath("Library").endswith( "/Path/Tools/Library" ) ) self.assertTrue( - PathPreferences.pathDefaultToolsPath("Template").endswith( + Path.Preferences.pathDefaultToolsPath("Template").endswith( "/Path/Tools/Template" ) ) diff --git a/src/Mod/Path/PathTests/TestPathSetupSheet.py b/src/Mod/Path/PathTests/TestPathSetupSheet.py index 9b63124984..9bc25b0589 100644 --- a/src/Mod/Path/PathTests/TestPathSetupSheet.py +++ b/src/Mod/Path/PathTests/TestPathSetupSheet.py @@ -21,13 +21,12 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathSetupSheet as PathSetupSheet -import PathScripts.PathLog as PathLog +import Path +import Path.Base.SetupSheet as PathSetupSheet import json import sys -#PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +#Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) from PathTests.PathTestUtils import PathTestBase @@ -37,7 +36,7 @@ def refstring(string): class SomeOp (object): def __init__(self, obj): - PathLog.track(obj, type(obj)) + Path.Log.track(obj, type(obj)) obj.addProperty('App::PropertyPercent', 'StepOver', 'Base', 'Some help you are') @classmethod @@ -46,7 +45,7 @@ class SomeOp (object): @classmethod def Create(cls, name, obj=None, parentJob=None): - PathLog.track(name, obj) + Path.Log.track(name, obj) if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = SomeOp(obj) diff --git a/src/Mod/Path/PathTests/TestPathToolController.py b/src/Mod/Path/PathTests/TestPathToolController.py index de008da8dd..ab7437f351 100644 --- a/src/Mod/Path/PathTests/TestPathToolController.py +++ b/src/Mod/Path/PathTests/TestPathToolController.py @@ -24,7 +24,6 @@ import FreeCAD import Path import Path.Tool.Bit as PathToolBit import Path.Tool.Controller as PathToolController -import PathScripts.PathPreferences as PathPreferences from PathTests.PathTestUtils import PathTestBase From 080d708f21cb10614d460fd1377e16ccb642ab8e Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 13 Aug 2022 21:40:42 -0700 Subject: [PATCH 17/33] Fixed refactor issues breaking unit tests --- src/Mod/Path/Path/Base/Gui/PropertyBag.py | 4 ++-- src/Mod/Path/Path/Base/Gui/SetupSheet.py | 2 +- src/Mod/Path/Path/GuiInit.py | 2 +- src/Mod/Path/Path/Tool/Gui/BitEdit.py | 2 +- src/Mod/Path/PathScripts/PathJob.py | 1 - src/Mod/Path/PathScripts/PathJobGui.py | 2 +- src/Mod/Path/PathScripts/PathStock.py | 11 ++++++++--- 7 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/Path/Base/Gui/PropertyBag.py b/src/Mod/Path/Path/Base/Gui/PropertyBag.py index 9d45778cdd..aac5201fa3 100644 --- a/src/Mod/Path/Path/Base/Gui/PropertyBag.py +++ b/src/Mod/Path/Path/Base/Gui/PropertyBag.py @@ -24,10 +24,10 @@ from PySide import QtCore, QtGui import FreeCAD import FreeCADGui import Path +import Path.Base.Gui.IconViewProvider as PathIconViewProvider +import Path.Base.Gui.PropertyEditor as PathPropertyEditor import Path.Base.PropertyBag as PathPropertyBag import Path.Base.Util as PathUtil -import PathScripts.PathIconViewProvider as PathIconViewProvider -import PathScripts.PathPropertyEditor as PathPropertyEditor import re diff --git a/src/Mod/Path/Path/Base/Gui/SetupSheet.py b/src/Mod/Path/Path/Base/Gui/SetupSheet.py index 124ae665f7..fd77762490 100644 --- a/src/Mod/Path/Path/Base/Gui/SetupSheet.py +++ b/src/Mod/Path/Path/Base/Gui/SetupSheet.py @@ -24,7 +24,7 @@ import FreeCAD import FreeCADGui import Path import Path.Base.Gui.IconViewProvider as PathIconViewProvider -import Path.Base.Gui.SetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui +import Path.Base.Gui.SetupSheetOpPrototype as PathSetupSheetOpPrototypeGui import Path.Base.Gui.Util as PathGuiUtil import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil diff --git a/src/Mod/Path/Path/GuiInit.py b/src/Mod/Path/Path/GuiInit.py index ff978b1620..ff84d9ddc8 100644 --- a/src/Mod/Path/Path/GuiInit.py +++ b/src/Mod/Path/Path/GuiInit.py @@ -39,7 +39,7 @@ def Startup(): if not Processed: Path.Log.debug("Initializing PathGui") from Path.Base.Gui import PropertyBag - from Path.Base.Gui import PathSetupSheetGui + from Path.Base.Gui import SetupSheet from Path.Dressup.Gui import AxisMap from Path.Dressup.Gui import Dogbone from Path.Dressup.Gui import Dragknife diff --git a/src/Mod/Path/Path/Tool/Gui/BitEdit.py b/src/Mod/Path/Path/Tool/Gui/BitEdit.py index 1551f79209..cc7657fb09 100644 --- a/src/Mod/Path/Path/Tool/Gui/BitEdit.py +++ b/src/Mod/Path/Path/Tool/Gui/BitEdit.py @@ -23,8 +23,8 @@ from PySide import QtCore, QtGui import FreeCADGui import Path +import Path.Base.Gui.PropertyEditor as PathPropertyEditor import Path.Base.Gui.Util as PathGuiUtil -import Path.Base.PropertyEditor as PathPropertyEditor import Path.Base.Util as PathUtil import os import re diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 155fee3416..ff99602856 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -31,7 +31,6 @@ import Path.Tool.Controller as PathToolController import PathScripts.PathStock as PathStock import json import time -import Path # lazily loaded modules diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index cfc73c9428..6a03526b9c 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -28,7 +28,7 @@ from pivy import coin import FreeCAD import FreeCADGui import Path -import Path.Base.Gui.SetupSheetGui as PathSetupSheetGui +import Path.Base.Gui.SetupSheet as PathSetupSheetGui import Path.Base.Util as PathUtil import Path.GuiInit as PathGuiInit import Path.Tool.Gui.Bit as PathToolBitGui diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py index b8d768add1..f467da5011 100644 --- a/src/Mod/Path/PathScripts/PathStock.py +++ b/src/Mod/Path/PathScripts/PathStock.py @@ -325,7 +325,6 @@ class StockCreateCylinder(Stock): if prop in ["Radius", "Height"] and not "Restore" in obj.State: self.execute(obj) - def SetupStockObject(obj, stockType): Path.Log.track(obj.Label, stockType) if FreeCAD.GuiUp and obj.ViewObject: @@ -338,9 +337,15 @@ def SetupStockObject(obj, stockType): obj.StockType = stockType obj.setEditorMode("StockType", 2) # hide - import Path.Base.Gui.IconViewProvider + # If I don't rename the module then usage as Path.Base.Gui.IconViewProvider below + # 'causes above Path.Log.track(...) to fail with - claiming that Path is accessed + # before it's assigned. + # Alternative _another_ `import Path` statement in front of `Path.Log.track(...)` + # also prevents the issue from happening. + # Go figure. + import Path.Base.Gui.IconViewProvider as PathIconViewProvider - Path.Base.Gui.IconViewProvider.ViewProvider(obj.ViewObject, "Stock") + PathIconViewProvider.ViewProvider(obj.ViewObject, "Stock") obj.ViewObject.Transparency = 90 obj.ViewObject.DisplayMode = "Wireframe" From a5fad7e0520a0592d96f04f298b2b50c6931546d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 14 Aug 2022 11:14:51 -0700 Subject: [PATCH 18/33] Moved the rest of the operations into Path.Op (.Gui) module --- src/Mod/Path/CMakeLists.txt | 22 ++--- .../Base/Drillable.py} | 0 src/Mod/Path/Path/Dressup/Gui/Dogbone.py | 4 +- src/Mod/Path/Path/GuiInit.py | 11 ++- src/Mod/Path/Path/Op/Adaptive.py | 2 +- src/Mod/Path/Path/Op/CircularHoleBase.py | 4 +- .../Op/FeatureExtension.py} | 0 src/Mod/Path/Path/Op/Gui/Adaptive.py | 4 +- .../PathArray.py => Path/Op/Gui/Array.py} | 4 +- src/Mod/Path/Path/Op/Gui/Base.py | 2 +- .../PathComment.py => Path/Op/Gui/Comment.py} | 6 +- .../PathCopy.py => Path/Op/Gui/Copy.py} | 9 +- .../Op/Gui/FeatureExtension.py} | 2 +- .../PathHop.py => Path/Op/Gui/Hop.py} | 6 +- src/Mod/Path/Path/Op/Gui/PocketShape.py | 2 +- .../Op/Gui/Selection.py} | 6 +- .../Op/Gui/SimpleCopy.py} | 0 .../PathStop.py => Path/Op/Gui/Stop.py} | 0 src/Mod/Path/Path/Op/PocketShape.py | 2 +- src/Mod/Path/Path/Op/Profile.py | 4 +- src/Mod/Path/PathCommands.py | 2 +- src/Mod/Path/PathTests/TestPathDrillable.py | 96 +++++++++---------- 22 files changed, 94 insertions(+), 94 deletions(-) rename src/Mod/Path/{PathScripts/drillableLib.py => Path/Base/Drillable.py} (100%) rename src/Mod/Path/{PathScripts/PathFeatureExtensions.py => Path/Op/FeatureExtension.py} (100%) rename src/Mod/Path/{PathScripts/PathArray.py => Path/Op/Gui/Array.py} (99%) rename src/Mod/Path/{PathScripts/PathComment.py => Path/Op/Gui/Comment.py} (96%) rename src/Mod/Path/{PathScripts/PathCopy.py => Path/Op/Gui/Copy.py} (95%) rename src/Mod/Path/{PathScripts/PathFeatureExtensionsGui.py => Path/Op/Gui/FeatureExtension.py} (99%) rename src/Mod/Path/{PathScripts/PathHop.py => Path/Op/Gui/Hop.py} (96%) rename src/Mod/Path/{PathScripts/PathSelection.py => Path/Op/Gui/Selection.py} (98%) rename src/Mod/Path/{PathScripts/PathSimpleCopy.py => Path/Op/Gui/SimpleCopy.py} (100%) rename src/Mod/Path/{PathScripts/PathStop.py => Path/Op/Gui/Stop.py} (100%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 8ff9c14efb..9537899f98 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -33,6 +33,7 @@ SET(PathPython_SRCS SET(PathPythonBase_SRCS Path/Base/__init__.py + Path/Base//Drillable.py Path/Base/MachineState.py Path/Base/FeedRate.py Path/Base/Property.py @@ -144,6 +145,7 @@ SET(PathPythonOp_SRCS Path/Op/Deburr.py Path/Op/Engrave.py Path/Op/EngraveBase.py + Path/Op/FeatureExtensions.py Path/Op/Drilling.py Path/Op/Helix.py Path/Op/MillFace.py @@ -164,20 +166,28 @@ SET(PathPythonOp_SRCS SET(PathPythonOpGui_SRCS Path/Op/Gui/__init__.py Path/Op/Gui/Adaptive.py + Path/Op/Gui/Array.py Path/Op/Gui/Base.py Path/Op/Gui/CircularHoleBase.py + Path/Op/Gui/Comment.py + Path/Op/Gui/Copy.py Path/Op/Gui/Custom.py Path/Op/Gui/Deburr.py Path/Op/Gui/Drilling.py Path/Op/Gui/Engrave.py + Path/Op/Gui/FeatureExtensions.py Path/Op/Gui/Helix.py + Path/Op/Gui/Hop.py Path/Op/Gui/MillFace.py - Path/Op/Gui/PocketBase.py Path/Op/Gui/Pocket.py + Path/Op/Gui/PocketBase.py Path/Op/Gui/PocketShape.py Path/Op/Gui/Probe.py Path/Op/Gui/Profile.py + Path/Op/Gui/Selection.py + Path/Op/Gui/SimpleCopy.py Path/Op/Gui/Slot.py + Path/Op/Gui/Stop.py Path/Op/Gui/Surface.py Path/Op/Gui/ThreadMilling.py Path/Op/Gui/Vcarve.py @@ -185,15 +195,8 @@ SET(PathPythonOpGui_SRCS ) SET(PathScripts_SRCS - PathScripts/drillableLib.py - PathScripts/PathArray.py PathScripts/PathCamoticsGui.py - PathScripts/PathComment.py - PathScripts/PathCopy.py - PathScripts/PathFeatureExtensions.py - PathScripts/PathFeatureExtensionsGui.py PathScripts/PathFixture.py - PathScripts/PathHop.py PathScripts/PathInspect.py PathScripts/PathJob.py PathScripts/PathJobCmd.py @@ -201,11 +204,8 @@ SET(PathScripts_SRCS PathScripts/PathJobGui.py PathScripts/PathPreferencesPathJob.py PathScripts/PathSanity.py - PathScripts/PathSelection.py - PathScripts/PathSimpleCopy.py PathScripts/PathSimulatorGui.py PathScripts/PathStock.py - PathScripts/PathStop.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py PathScripts/__init__.py diff --git a/src/Mod/Path/PathScripts/drillableLib.py b/src/Mod/Path/Path/Base/Drillable.py similarity index 100% rename from src/Mod/Path/PathScripts/drillableLib.py rename to src/Mod/Path/Path/Base/Drillable.py diff --git a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py index 8249ee6d16..1ffd6436fd 100644 --- a/src/Mod/Path/Path/Dressup/Gui/Dogbone.py +++ b/src/Mod/Path/Path/Dressup/Gui/Dogbone.py @@ -1272,12 +1272,12 @@ class TaskPanel(object): class SelObserver(object): def __init__(self): - import PathScripts.PathSelection as PST + import Path.Op.Gui.Selection as PST PST.eselect() def __del__(self): - import PathScripts.PathSelection as PST + import Path.Op.Gui.Selection as PST PST.clear() diff --git a/src/Mod/Path/Path/GuiInit.py b/src/Mod/Path/Path/GuiInit.py index ff84d9ddc8..265f6b9710 100644 --- a/src/Mod/Path/Path/GuiInit.py +++ b/src/Mod/Path/Path/GuiInit.py @@ -49,31 +49,32 @@ def Startup(): from Path.Dressup.Gui import Tags from Path.Dressup.Gui import ZCorrect from Path.Op.Gui import Adaptive + from Path.Op.Gui import Array + from Path.Op.Gui import Comment from Path.Op.Gui import Custom from Path.Op.Gui import Deburr from Path.Op.Gui import Drilling from Path.Op.Gui import Engrave from Path.Op.Gui import Helix + from Path.Op.Gui import Hop from Path.Op.Gui import MillFace from Path.Op.Gui import Pocket from Path.Op.Gui import PocketShape from Path.Op.Gui import Probe from Path.Op.Gui import Profile + from Path.Op.Gui import SimpleCopy from Path.Op.Gui import Slot + from Path.Op.Gui import Stop from Path.Op.Gui import ThreadMilling from Path.Op.Gui import Vcarve from Path.Post import Command from Path.Tool import Controller from Path.Tool.Gui import Controller - from PathScripts import PathArray - from PathScripts import PathComment + from PathScripts import PathFixture - from PathScripts import PathHop from PathScripts import PathInspect from PathScripts import PathSanity - from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui - from PathScripts import PathStop from PathScripts import PathUtilsGui from packaging.version import Version, parse diff --git a/src/Mod/Path/Path/Op/Adaptive.py b/src/Mod/Path/Path/Op/Adaptive.py index b7a037440a..6c93c3e2c7 100644 --- a/src/Mod/Path/Path/Op/Adaptive.py +++ b/src/Mod/Path/Path/Op/Adaptive.py @@ -44,7 +44,7 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") # TechDraw = LazyLoader('TechDraw', globals(), 'TechDraw') FeatureExtensions = LazyLoader( - "PathScripts.PathFeatureExtensions", globals(), "PathScripts.PathFeatureExtensions" + "Path.Op.FeatureExtension", globals(), "Path.Op.FeatureExtension" ) DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") diff --git a/src/Mod/Path/Path/Op/CircularHoleBase.py b/src/Mod/Path/Path/Op/CircularHoleBase.py index b0e539d792..885b5ca031 100644 --- a/src/Mod/Path/Path/Op/CircularHoleBase.py +++ b/src/Mod/Path/Path/Op/CircularHoleBase.py @@ -24,7 +24,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import Path.Op.Base as PathOp -import PathScripts.drillableLib as drillableLib +import Path.Base.Drillable as Drillable # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -213,7 +213,7 @@ class ObjectOp(PathOp.ObjectOp): features = [] for base in self.model: features.extend( - drillableLib.getDrillableTargets( + Drillable.getDrillableTargets( base, ToolDiameter=tooldiameter, vector=matchvector ) ) diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensions.py b/src/Mod/Path/Path/Op/FeatureExtension.py similarity index 100% rename from src/Mod/Path/PathScripts/PathFeatureExtensions.py rename to src/Mod/Path/Path/Op/FeatureExtension.py diff --git a/src/Mod/Path/Path/Op/Gui/Adaptive.py b/src/Mod/Path/Path/Op/Gui/Adaptive.py index 7879e633ca..4bc069470b 100644 --- a/src/Mod/Path/Path/Op/Gui/Adaptive.py +++ b/src/Mod/Path/Path/Op/Gui/Adaptive.py @@ -21,11 +21,11 @@ # * * # *************************************************************************** +import FreeCADGui import Path.Op.Adaptive as PathAdaptive import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.FeatureExtension as PathFeatureExtensionsGui from PySide import QtCore -import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui -import FreeCADGui class TaskPanelOpPage(PathOpGui.TaskPanelPage): diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/Path/Op/Gui/Array.py similarity index 99% rename from src/Mod/Path/PathScripts/PathArray.py rename to src/Mod/Path/Path/Op/Gui/Array.py index 7e4c8fdf3c..b7daea3400 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/Path/Op/Gui/Array.py @@ -539,14 +539,14 @@ class CommandPathArray: # if everything is ok, execute and register the transaction in the # undo/redo stack FreeCAD.ActiveDocument.openTransaction("Create Array") - FreeCADGui.addModule("PathScripts.PathArray") + FreeCADGui.addModule("Path.Op.Gui.Array") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")' ) - FreeCADGui.doCommand("PathScripts.PathArray.ObjectArray(obj)") + FreeCADGui.doCommand("Path.Op.Gui.Array.ObjectArray(obj)") baseString = "[%s]" % ",".join( ["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection] diff --git a/src/Mod/Path/Path/Op/Gui/Base.py b/src/Mod/Path/Path/Op/Gui/Base.py index 0876702507..cc240ed37f 100644 --- a/src/Mod/Path/Path/Op/Gui/Base.py +++ b/src/Mod/Path/Path/Op/Gui/Base.py @@ -28,9 +28,9 @@ import Path.Base.Gui.Util as PathGuiUtil import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil import Path.Op.Base as PathOp +import Path.Op.Gui.Selection as PathSelection import PathGui import PathScripts.PathJob as PathJob -import PathScripts.PathSelection as PathSelection import PathScripts.PathUtils as PathUtils import importlib from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/PathScripts/PathComment.py b/src/Mod/Path/Path/Op/Gui/Comment.py similarity index 96% rename from src/Mod/Path/PathScripts/PathComment.py rename to src/Mod/Path/Path/Op/Gui/Comment.py index 1f5e7c06ce..a359347892 100644 --- a/src/Mod/Path/PathScripts/PathComment.py +++ b/src/Mod/Path/Path/Op/Gui/Comment.py @@ -115,14 +115,14 @@ class CommandPathComment: def Activated(self): FreeCAD.ActiveDocument.openTransaction("Create a Comment in your CNC program") - FreeCADGui.addModule("PathScripts.PathComment") + FreeCADGui.addModule("Path.Op.Gui.Comment") snippet = """ import Path import PathScripts from PathScripts import PathUtils obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Comment") -PathScripts.PathComment.Comment(obj) -PathScripts.PathComment._ViewProviderComment(obj.ViewObject) +Path.Op.Gui.Comment.Comment(obj) +Path.Op.Gui.Comment._ViewProviderComment(obj.ViewObject) PathUtils.addToJob(obj) """ diff --git a/src/Mod/Path/PathScripts/PathCopy.py b/src/Mod/Path/Path/Op/Gui/Copy.py similarity index 95% rename from src/Mod/Path/PathScripts/PathCopy.py rename to src/Mod/Path/Path/Op/Gui/Copy.py index 0a622a3331..939da3c488 100644 --- a/src/Mod/Path/PathScripts/PathCopy.py +++ b/src/Mod/Path/Path/Op/Gui/Copy.py @@ -104,12 +104,11 @@ class CommandPathCopy: def Activated(self): FreeCAD.ActiveDocument.openTransaction("Create Copy") - FreeCADGui.addModule("PathScripts.PathCopy") + FreeCADGui.addModule("Path.Op.Gui.Copy") consolecode = """ import Path -import PathScripts -from PathScripts import PathCopy +import Path.Op.Gui.Copy selGood = True # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() @@ -125,8 +124,8 @@ if not selection[0].isDerivedFrom("Path::Feature"): if selGood: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", str(selection[0].Name)+'_Copy') - PathScripts.PathCopy.ObjectPathCopy(obj) - PathScripts.PathCopy.ViewProviderPathCopy(obj.ViewObject) + Path.Op.Gui.Copy.ObjectPathCopy(obj) + Path.Op.Gui.Copy.ViewProviderPathCopy(obj.ViewObject) obj.Base = FreeCAD.ActiveDocument.getObject(selection[0].Name) if hasattr(obj.Base, 'ToolController'): obj.ToolController = obj.Base.ToolController diff --git a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py b/src/Mod/Path/Path/Op/Gui/FeatureExtension.py similarity index 99% rename from src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py rename to src/Mod/Path/Path/Op/Gui/FeatureExtension.py index 5df092ddb0..8ce59b0260 100644 --- a/src/Mod/Path/PathScripts/PathFeatureExtensionsGui.py +++ b/src/Mod/Path/Path/Op/Gui/FeatureExtension.py @@ -26,8 +26,8 @@ import FreeCAD import FreeCADGui import Path import Path.Base.Gui.Util as PathGuiUtil +import Path.Op.FeatureExtension as FeatureExtensions import Path.Op.Gui.Base as PathOpGui -import PathScripts.PathFeatureExtensions as FeatureExtensions # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader diff --git a/src/Mod/Path/PathScripts/PathHop.py b/src/Mod/Path/Path/Op/Gui/Hop.py similarity index 96% rename from src/Mod/Path/PathScripts/PathHop.py rename to src/Mod/Path/Path/Op/Gui/Hop.py index 1447f98fa3..d3b2a04f32 100644 --- a/src/Mod/Path/PathScripts/PathHop.py +++ b/src/Mod/Path/Path/Op/Gui/Hop.py @@ -125,13 +125,13 @@ class CommandPathHop: return FreeCAD.ActiveDocument.openTransaction("Create Hop") - FreeCADGui.addModule("PathScripts.PathHop") + FreeCADGui.addModule("Path.Op.Gui.Hop") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Hop")' ) - FreeCADGui.doCommand("PathScripts.PathHop.ObjectHop(obj)") - FreeCADGui.doCommand("PathScripts.PathHop.ViewProviderPathHop(obj.ViewObject)") + FreeCADGui.doCommand("Path.Op.Gui.Hop.ObjectHop(obj)") + FreeCADGui.doCommand("Path.Op.Gui.Hop.ViewProviderPathHop(obj.ViewObject)") FreeCADGui.doCommand( "obj.NextObject = FreeCAD.ActiveDocument." + selection[0].Name ) diff --git a/src/Mod/Path/Path/Op/Gui/PocketShape.py b/src/Mod/Path/Path/Op/Gui/PocketShape.py index 70788fccce..00f699b61d 100644 --- a/src/Mod/Path/Path/Op/Gui/PocketShape.py +++ b/src/Mod/Path/Path/Op/Gui/PocketShape.py @@ -23,9 +23,9 @@ import FreeCAD import Path import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.FeatureExtension as PathFeatureExtensionsGui import Path.Op.Gui.PocketBase as PathPocketBaseGui import Path.Op.PocketShape as PathPocketShape -import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui from PySide.QtCore import QT_TRANSLATE_NOOP # lazily loaded modules diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/Path/Op/Gui/Selection.py similarity index 98% rename from src/Mod/Path/PathScripts/PathSelection.py rename to src/Mod/Path/Path/Op/Gui/Selection.py index ecb7c0d0ee..9044e54a55 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/Path/Op/Gui/Selection.py @@ -26,7 +26,7 @@ import FreeCAD import FreeCADGui import Path -import PathScripts.drillableLib as drillableLib +import Path.Base.Drillable as Drillable import math Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) @@ -129,7 +129,7 @@ class DRILLGate(PathBaseGate): subobj = shape.getElement(sub) if subobj.ShapeType not in ["Edge", "Face"]: return False - return drillableLib.isDrillable(shape, subobj, vector=None) + return Drillable.isDrillable(shape, subobj, vector=None) class FACEGate(PathBaseGate): @@ -236,7 +236,7 @@ class TURNGate(PathBaseGate): if hasattr(obj, "Shape") and sub: shape = obj.Shape subobj = shape.getElement(sub) - return drillableLib.isDrillable(shape, subobj, vector=None) + return Drillable.isDrillable(shape, subobj, vector=None) else: return False diff --git a/src/Mod/Path/PathScripts/PathSimpleCopy.py b/src/Mod/Path/Path/Op/Gui/SimpleCopy.py similarity index 100% rename from src/Mod/Path/PathScripts/PathSimpleCopy.py rename to src/Mod/Path/Path/Op/Gui/SimpleCopy.py diff --git a/src/Mod/Path/PathScripts/PathStop.py b/src/Mod/Path/Path/Op/Gui/Stop.py similarity index 100% rename from src/Mod/Path/PathScripts/PathStop.py rename to src/Mod/Path/Path/Op/Gui/Stop.py diff --git a/src/Mod/Path/Path/Op/PocketShape.py b/src/Mod/Path/Path/Op/PocketShape.py index 62b2c6bc92..1a1817f1e2 100644 --- a/src/Mod/Path/Path/Op/PocketShape.py +++ b/src/Mod/Path/Path/Op/PocketShape.py @@ -35,7 +35,7 @@ TechDraw = LazyLoader("TechDraw", globals(), "TechDraw") math = LazyLoader("math", globals(), "math") PathUtils = LazyLoader("PathScripts.PathUtils", globals(), "PathScripts.PathUtils") FeatureExtensions = LazyLoader( - "PathScripts.PathFeatureExtensions", globals(), "PathScripts.PathFeatureExtensions" + "Path.Op.FeatureExtension", globals(), "Path.Op.FeatureExtension" ) diff --git a/src/Mod/Path/Path/Op/Profile.py b/src/Mod/Path/Path/Op/Profile.py index 7b79675ebf..92e17606a3 100644 --- a/src/Mod/Path/Path/Op/Profile.py +++ b/src/Mod/Path/Path/Op/Profile.py @@ -24,10 +24,10 @@ import FreeCAD import Path +import Path.Base.Drillable as Drillable import Path.Op.Area as PathAreaOp import Path.Op.Base as PathOp import PathScripts.PathUtils as PathUtils -import PathScripts.drillableLib as drillableLib import math import numpy from PySide.QtCore import QT_TRANSLATE_NOOP @@ -450,7 +450,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): for baseShape, wire in holes: cont = False f = Part.makeFace(wire, "Part::FaceMakerSimple") - drillable = drillableLib.isDrillable(baseShape, f, vector=None) + drillable = Drillable.isDrillable(baseShape, f, vector=None) Path.Log.debug(drillable) if obj.processCircles: diff --git a/src/Mod/Path/PathCommands.py b/src/Mod/Path/PathCommands.py index a61e5ab25d..e42c995038 100644 --- a/src/Mod/Path/PathCommands.py +++ b/src/Mod/Path/PathCommands.py @@ -164,7 +164,7 @@ class _ToggleOperation: selProxy = Path.Dressup.Utils.baseOp(sel.Object).Proxy if not isinstance( selProxy, Path.Op.Base.ObjectOp - ) and not isinstance(selProxy, PathScripts.PathArray.ObjectArray): + ) and not isinstance(selProxy, Path.Op.Gui.Array.ObjectArray): return False return True except (IndexError, AttributeError): diff --git a/src/Mod/Path/PathTests/TestPathDrillable.py b/src/Mod/Path/PathTests/TestPathDrillable.py index 67b5f86743..f333585e2e 100644 --- a/src/Mod/Path/PathTests/TestPathDrillable.py +++ b/src/Mod/Path/PathTests/TestPathDrillable.py @@ -22,8 +22,8 @@ import FreeCAD as App import Path +import Path.Base.Drillable as Drillable import PathTests.PathTestUtils as PathTestUtils -import PathScripts.drillableLib as drillableLib if False: @@ -47,17 +47,17 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # Vec and origin v1 = App.Vector(0, 0, 10) v2 = App.Vector(0, 0, 0) - self.assertTrue(drillableLib.compareVecs(v1, v2)) + self.assertTrue(Drillable.compareVecs(v1, v2)) # two valid vectors v1 = App.Vector(0, 10, 0) v2 = App.Vector(0, 20, 0) - self.assertTrue(drillableLib.compareVecs(v1, v2)) + self.assertTrue(Drillable.compareVecs(v1, v2)) # two valid vectors not aligned v1 = App.Vector(0, 10, 0) v2 = App.Vector(10, 0, 0) - self.assertFalse(drillableLib.compareVecs(v1, v2)) + self.assertFalse(Drillable.compareVecs(v1, v2)) def test10(self): """Test isDrillable""" @@ -65,7 +65,7 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # Invalid types candidate = self.obj.getSubObject("Vertex1") self.assertRaises( - TypeError, lambda: drillableLib.isDrillable(self.obj.Shape, candidate) + TypeError, lambda: Drillable.isDrillable(self.obj.Shape, candidate) ) # Test cylinder faces @@ -74,46 +74,46 @@ class TestPathDrillable(PathTestUtils.PathTestBase): candidate = self.obj.getSubObject("Face30") # Typical drilling - self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate)) # Drilling with smaller bit self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=20) + Drillable.isDrillable(self.obj.Shape, candidate, tooldiameter=20) ) # Drilling with bit too large self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30) + Drillable.isDrillable(self.obj.Shape, candidate, tooldiameter=30) ) # off-axis hole candidate = self.obj.getSubObject("Face44") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Passing explicit vector self.assertTrue( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, vector=App.Vector(0, -1, 0) ) ) # Drilling with smaller bit self.assertTrue( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, tooldiameter=10, vector=App.Vector(0, -1, 0) ) ) # Drilling with bit too large self.assertFalse( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, tooldiameter=30, vector=App.Vector(0, -1, 0) ) ) @@ -122,76 +122,76 @@ class TestPathDrillable(PathTestUtils.PathTestBase): candidate = self.obj.getSubObject("Face29") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # raised cylinder candidate = self.obj.getSubObject("Face32") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # cylinder on slope candidate = self.obj.getSubObject("Face24") # Typical drilling - self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Circular Faces candidate = self.obj.getSubObject("Face54") # Typical drilling - self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Passing explicit vector self.assertTrue( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, vector=App.Vector(0, 0, 1) ) ) # Drilling with smaller bit self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=10) + Drillable.isDrillable(self.obj.Shape, candidate, tooldiameter=10) ) # Drilling with bit too large self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=30) + Drillable.isDrillable(self.obj.Shape, candidate, tooldiameter=30) ) # off-axis circular face hole candidate = self.obj.getSubObject("Face58") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Passing explicit vector self.assertTrue( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, vector=App.Vector(0, -1, 0) ) ) @@ -199,31 +199,31 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # raised face candidate = self.obj.getSubObject("Face49") # Typical drilling - self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # interrupted Face candidate = self.obj.getSubObject("Face50") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # donut face candidate = self.obj.getSubObject("Face48") # Typical drilling - self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Test edges @@ -231,28 +231,28 @@ class TestPathDrillable(PathTestUtils.PathTestBase): candidate = self.obj.getSubObject("Edge55") # Typical drilling - self.assertTrue(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertTrue(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Passing explicit vector self.assertTrue( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, vector=App.Vector(0, 0, 1) ) ) # Drilling with smaller bit self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, tooldiameter=10) + Drillable.isDrillable(self.obj.Shape, candidate, tooldiameter=10) ) # Drilling with bit too large self.assertFalse( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, tooldiameter=30, vector=None ) ) @@ -261,16 +261,16 @@ class TestPathDrillable(PathTestUtils.PathTestBase): candidate = self.obj.getSubObject("Edge74") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertTrue( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # Passing explicit vector self.assertTrue( - drillableLib.isDrillable( + Drillable.isDrillable( self.obj.Shape, candidate, vector=App.Vector(0, 1, 0) ) ) @@ -278,32 +278,32 @@ class TestPathDrillable(PathTestUtils.PathTestBase): # incomplete circular edge candidate = self.obj.getSubObject("Edge39") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) # elliptical edge candidate = self.obj.getSubObject("Edge56") # Typical drilling - self.assertFalse(drillableLib.isDrillable(self.obj.Shape, candidate)) + self.assertFalse(Drillable.isDrillable(self.obj.Shape, candidate)) # Passing None as vector self.assertFalse( - drillableLib.isDrillable(self.obj.Shape, candidate, vector=None) + Drillable.isDrillable(self.obj.Shape, candidate, vector=None) ) def test20(self): """Test getDrillableTargets""" - results = drillableLib.getDrillableTargets(self.obj) + results = Drillable.getDrillableTargets(self.obj) self.assertEqual(len(results), 15) - results = drillableLib.getDrillableTargets(self.obj, vector=None) + results = Drillable.getDrillableTargets(self.obj, vector=None) self.assertEqual(len(results), 20) - results = drillableLib.getDrillableTargets( + results = Drillable.getDrillableTargets( self.obj, ToolDiameter=20, vector=None ) self.assertEqual(len(results), 5) From b7afb189877f0bcf7c9fc4778897b993a2b0272f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 14 Aug 2022 11:56:28 -0700 Subject: [PATCH 19/33] Moved generators into Path.Base.Generator module --- src/Mod/Path/CMakeLists.txt | 62 ++++++++++++------- src/Mod/Path/Path/Base/Generator/__init__.py | 0 .../Base/Generator/drill.py} | 0 .../Base/Generator/helix.py} | 0 .../Base/Generator/rotation.py} | 0 .../Base/Generator/threadmilling.py} | 0 .../Base/Generator/toolchange.py} | 0 src/Mod/Path/Path/Op/Drilling.py | 4 +- src/Mod/Path/Path/Op/Helix.py | 4 +- src/Mod/Path/Path/Op/ThreadMilling.py | 2 +- src/Mod/Path/Path/Tool/Controller.py | 19 +++--- .../Path/PathTests/TestPathDrillGenerator.py | 2 +- .../Path/PathTests/TestPathHelixGenerator.py | 2 +- .../PathTests/TestPathRotationGenerator.py | 2 +- .../TestPathThreadMillingGenerator.py | 2 +- .../PathTests/TestPathToolChangeGenerator.py | 8 +-- 16 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 src/Mod/Path/Path/Base/Generator/__init__.py rename src/Mod/Path/{Generators/drill_generator.py => Path/Base/Generator/drill.py} (100%) rename src/Mod/Path/{Generators/helix_generator.py => Path/Base/Generator/helix.py} (100%) rename src/Mod/Path/{Generators/rotation_generator.py => Path/Base/Generator/rotation.py} (100%) rename src/Mod/Path/{Generators/threadmilling_generator.py => Path/Base/Generator/threadmilling.py} (100%) rename src/Mod/Path/{Generators/toolchange_generator.py => Path/Base/Generator/toolchange.py} (100%) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 9537899f98..06076ec361 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -145,7 +145,7 @@ SET(PathPythonOp_SRCS Path/Op/Deburr.py Path/Op/Engrave.py Path/Op/EngraveBase.py - Path/Op/FeatureExtensions.py + Path/Op/FeatureExtension.py Path/Op/Drilling.py Path/Op/Helix.py Path/Op/MillFace.py @@ -175,7 +175,7 @@ SET(PathPythonOpGui_SRCS Path/Op/Gui/Deburr.py Path/Op/Gui/Drilling.py Path/Op/Gui/Engrave.py - Path/Op/Gui/FeatureExtensions.py + Path/Op/Gui/FeatureExtension.py Path/Op/Gui/Helix.py Path/Op/Gui/Hop.py Path/Op/Gui/MillFace.py @@ -211,12 +211,12 @@ SET(PathScripts_SRCS PathScripts/__init__.py ) -SET(Generator_SRCS - Generators/drill_generator.py - Generators/helix_generator.py - Generators/rotation_generator.py - Generators/threadmilling_generator.py - Generators/toolchange_generator.py +SET(PathPythonBaseGenerator_SRCS + Path/Base/Generator/drill.py + Path/Base/Generator/helix.py + Path/Base/Generator/rotation.py + Path/Base/Generator/threadmilling.py + Path/Base/Generator/toolchange.py ) SET(PathPythonGui_SRCS @@ -342,6 +342,9 @@ SET(Path_Data SET(all_files ${PathScripts_SRCS} ${PathPython_SRCS} + ${PathPythonBase_SRCS} + ${PathPythonBaseGui_SRCS} + ${PathPythonBaseGenerator_SRCS} ${PathPythonDressup_SRCS} ${PathPythonDressupGui_SRCS} ${PathPythonOp_SRCS} @@ -350,7 +353,6 @@ SET(all_files ${PathPythonPostScripts_SRCS} ${PathPythonTools_SRCS} ${PathPythonToolsGui_SRCS} - ${Generator_SRCS} ${PathPythonGui_SRCS} ${Tools_SRCS} ${Tools_Bit_SRCS} @@ -390,6 +392,27 @@ INSTALL( Mod/Path/Path ) +INSTALL( + FILES + ${PathPythonBase_SRCS} + DESTINATION + Mod/Path/Path/Base +) + +INSTALL( + FILES + ${PathPythonBaseGenerator_SRCS} + DESTINATION + Mod/Path/Path/Base/Generator +) + +INSTALL( + FILES + ${PathPythonBaseGui_SRCS} + DESTINATION + Mod/Path/Path/Base/Gui +) + INSTALL( FILES ${PathPythonDressup_SRCS} @@ -424,6 +447,13 @@ INSTALL( Mod/Path/Path/Post ) +INSTALL( + FILES + ${PathPythonPostScripts_SRCS} + DESTINATION + Mod/Path/Path/Post/scripts +) + INSTALL( FILES ${PathPythonTools_SRCS} @@ -438,20 +468,6 @@ INSTALL( Mod/Path/Path/Tool/Gui ) -INSTALL( - FILES - ${PathPythonPostScripts_SRCS} - DESTINATION - Mod/Path/Path/Post/scripts -) - -INSTALL( - FILES - ${Generator_SRCS} - DESTINATION - Mod/Path/Generators -) - INSTALL( FILES ${PathTests_SRCS} diff --git a/src/Mod/Path/Path/Base/Generator/__init__.py b/src/Mod/Path/Path/Base/Generator/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Generators/drill_generator.py b/src/Mod/Path/Path/Base/Generator/drill.py similarity index 100% rename from src/Mod/Path/Generators/drill_generator.py rename to src/Mod/Path/Path/Base/Generator/drill.py diff --git a/src/Mod/Path/Generators/helix_generator.py b/src/Mod/Path/Path/Base/Generator/helix.py similarity index 100% rename from src/Mod/Path/Generators/helix_generator.py rename to src/Mod/Path/Path/Base/Generator/helix.py diff --git a/src/Mod/Path/Generators/rotation_generator.py b/src/Mod/Path/Path/Base/Generator/rotation.py similarity index 100% rename from src/Mod/Path/Generators/rotation_generator.py rename to src/Mod/Path/Path/Base/Generator/rotation.py diff --git a/src/Mod/Path/Generators/threadmilling_generator.py b/src/Mod/Path/Path/Base/Generator/threadmilling.py similarity index 100% rename from src/Mod/Path/Generators/threadmilling_generator.py rename to src/Mod/Path/Path/Base/Generator/threadmilling.py diff --git a/src/Mod/Path/Generators/toolchange_generator.py b/src/Mod/Path/Path/Base/Generator/toolchange.py similarity index 100% rename from src/Mod/Path/Generators/toolchange_generator.py rename to src/Mod/Path/Path/Base/Generator/toolchange.py diff --git a/src/Mod/Path/Path/Op/Drilling.py b/src/Mod/Path/Path/Op/Drilling.py index bd60e7c922..b21a03dfb4 100644 --- a/src/Mod/Path/Path/Op/Drilling.py +++ b/src/Mod/Path/Path/Op/Drilling.py @@ -24,11 +24,11 @@ from __future__ import print_function -from Generators import drill_generator as generator import FreeCAD import Part import Path import Path.Base.FeedRate as PathFeedRate +import Path.Base.Generator.drill as drill import Path.Base.MachineState as PathMachineState import Path.Op.Base as PathOp import Path.Op.CircularHoleBase as PathCircularHoleBase @@ -248,7 +248,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): chipBreak = (obj.chipBreakEnabled and obj.PeckEnabled) try: - drillcommands = generator.generate( + drillcommands = drill.generate( edge, dwelltime, peckdepth, diff --git a/src/Mod/Path/Path/Op/Helix.py b/src/Mod/Path/Path/Op/Helix.py index c6af036821..efc94c4f2c 100644 --- a/src/Mod/Path/Path/Op/Helix.py +++ b/src/Mod/Path/Path/Op/Helix.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -from Generators import helix_generator +import Path.Base.Generator.helix as helix from PathScripts.PathUtils import fmt from PathScripts.PathUtils import sort_locations from PySide.QtCore import QT_TRANSLATE_NOOP @@ -218,7 +218,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): ) ) - results = helix_generator.generate(**args) + results = helix.generate(**args) for command in results: self.commandlist.append(command) diff --git a/src/Mod/Path/Path/Op/ThreadMilling.py b/src/Mod/Path/Path/Op/ThreadMilling.py index bd634d057d..144098273a 100644 --- a/src/Mod/Path/Path/Op/ThreadMilling.py +++ b/src/Mod/Path/Path/Op/ThreadMilling.py @@ -24,9 +24,9 @@ from __future__ import print_function import FreeCAD import Path +import Path.Base.Generator.threadmilling as threadmilling import Path.Op.Base as PathOp import Path.Op.CircularHoleBase as PathCircularHoleBase -import Generators.threadmilling_generator as threadmilling import math from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/Path/Tool/Controller.py b/src/Mod/Path/Path/Tool/Controller.py index ee90b9925d..1426adf058 100644 --- a/src/Mod/Path/Path/Tool/Controller.py +++ b/src/Mod/Path/Path/Tool/Controller.py @@ -26,8 +26,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import Path.Tool.Bit as PathToolBit -from Generators import toolchange_generator as toolchange_generator -from Generators.toolchange_generator import SpindleDirection +import Path.Base.Generator.toolchange as toolchange if False: @@ -253,27 +252,27 @@ class ToolController: "toolnumber": obj.ToolNumber, "toollabel": obj.Label, "spindlespeed": obj.SpindleSpeed, - "spindledirection": SpindleDirection.OFF, + "spindledirection": toolchange.SpindleDirection.OFF, } if hasattr(obj.Tool, "SpindlePower"): if not obj.Tool.SpindlePower: - args["spindledirection"] = SpindleDirection.OFF + args["spindledirection"] = toolchange.SpindleDirection.OFF else: if obj.SpindleDir == "Forward": - args["spindledirection"] = SpindleDirection.CW + args["spindledirection"] = toolchange.SpindleDirection.CW else: - args["spindledirection"] = SpindleDirection.CCW + args["spindledirection"] = toolchange.SpindleDirection.CCW elif obj.SpindleDir == "None": - args["spindledirection"] = SpindleDirection.OFF + args["spindledirection"] = toolchange.SpindleDirection.OFF else: if obj.SpindleDir == "Forward": - args["spindledirection"] = SpindleDirection.CW + args["spindledirection"] = toolchange.SpindleDirection.CW else: - args["spindledirection"] = SpindleDirection.CCW + args["spindledirection"] = toolchange.SpindleDirection.CCW - commands = toolchange_generator.generate(**args) + commands = toolchange.generate(**args) path = Path.Path(commands) obj.Path = path diff --git a/src/Mod/Path/PathTests/TestPathDrillGenerator.py b/src/Mod/Path/PathTests/TestPathDrillGenerator.py index 0f71f05379..15ae13bd8b 100644 --- a/src/Mod/Path/PathTests/TestPathDrillGenerator.py +++ b/src/Mod/Path/PathTests/TestPathDrillGenerator.py @@ -21,9 +21,9 @@ # *************************************************************************** import FreeCAD -import Generators.drill_generator as generator import Part import Path +import Path.Base.Generator.drill as generator import PathTests.PathTestUtils as PathTestUtils Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestPathHelixGenerator.py b/src/Mod/Path/PathTests/TestPathHelixGenerator.py index 170552ed7d..274ea84c89 100644 --- a/src/Mod/Path/PathTests/TestPathHelixGenerator.py +++ b/src/Mod/Path/PathTests/TestPathHelixGenerator.py @@ -23,7 +23,7 @@ import FreeCAD import Part import Path -import Generators.helix_generator as generator +import Path.Base.Generator.helix as generator import PathTests.PathTestUtils as PathTestUtils diff --git a/src/Mod/Path/PathTests/TestPathRotationGenerator.py b/src/Mod/Path/PathTests/TestPathRotationGenerator.py index ff14f659df..78f6dbb0ad 100644 --- a/src/Mod/Path/PathTests/TestPathRotationGenerator.py +++ b/src/Mod/Path/PathTests/TestPathRotationGenerator.py @@ -21,8 +21,8 @@ # *************************************************************************** import FreeCAD -import Generators.rotation_generator as generator import Path +import Path.Base.Generator.rotation as generator import PathTests.PathTestUtils as PathTestUtils import numpy as np diff --git a/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py b/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py index c33368cc7d..cfc3ad7ca9 100644 --- a/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py +++ b/src/Mod/Path/PathTests/TestPathThreadMillingGenerator.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import Generators.threadmilling_generator as threadmilling +import Path.Base.Generator.threadmilling as threadmilling import math from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py index 17827dc523..72b0cf80ee 100644 --- a/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py +++ b/src/Mod/Path/PathTests/TestPathToolChangeGenerator.py @@ -21,9 +21,7 @@ # *************************************************************************** import Path -import Generators.toolchange_generator as generator -from Generators.toolchange_generator import SpindleDirection - +import Path.Base.Generator.toolchange as generator import PathTests.PathTestUtils as PathTestUtils Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) @@ -38,7 +36,7 @@ class TestPathToolChangeGenerator(PathTestUtils.PathTestBase): "toolnumber": 1, "toollabel": "My Label", "spindlespeed": 500, - "spindledirection": SpindleDirection.OFF, + "spindledirection": generator.SpindleDirection.OFF, } results = generator.generate(**args) @@ -54,7 +52,7 @@ class TestPathToolChangeGenerator(PathTestUtils.PathTestBase): self.assertTrue(toolcommand.Name == "M6") # Turn on the spindle - args["spindledirection"] = SpindleDirection.CW + args["spindledirection"] = generator.SpindleDirection.CW results = generator.generate(**args) self.assertTrue(len(results) == 3) From c6c708363b4af57d8376ebd2bcc51a4a615aea0f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 19 Aug 2022 19:25:23 -0700 Subject: [PATCH 20/33] Added PropertyBagGui again for backwards compatibility --- src/Mod/Path/PathScripts/PathPropertyBagGui.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/Mod/Path/PathScripts/PathPropertyBagGui.py diff --git a/src/Mod/Path/PathScripts/PathPropertyBagGui.py b/src/Mod/Path/PathScripts/PathPropertyBagGui.py new file mode 100644 index 0000000000..5ed353dc6a --- /dev/null +++ b/src/Mod/Path/PathScripts/PathPropertyBagGui.py @@ -0,0 +1,4 @@ +# Do NOT remove! +# This establishes backwards compatibility with existing ToolBit files. + +from Path.Base.Gui.PropertyBag import * From 6ecba4f9ee19f1a8ad755e34b3f8751b84d34dca Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 1 Sep 2022 18:57:49 -0700 Subject: [PATCH 21/33] Moved remaing files out of PathScripts --- src/Mod/Path/CMakeLists.txt | 49 ++++++++++++++----- src/Mod/Path/InitGui.py | 4 +- .../Dressup/{PathBoundary.py => Boundary.py} | 2 +- .../Gui/{PathBoundary.py => Boundary.py} | 10 ++-- src/Mod/Path/Path/Dressup/Utils.py | 2 +- src/Mod/Path/Path/GuiInit.py | 14 +++--- .../Main/Gui/Camotics.py} | 2 +- .../Main/Gui/Fixture.py} | 6 +-- .../Main/Gui/Inspect.py} | 4 +- .../PathJobGui.py => Path/Main/Gui/Job.py} | 10 ++-- .../PathJobCmd.py => Path/Main/Gui/JobCmd.py} | 10 ++-- .../PathJobDlg.py => Path/Main/Gui/JobDlg.py} | 4 +- .../Main/Gui/PreferencesJob.py} | 2 +- .../PathSanity.py => Path/Main/Gui/Sanity.py} | 4 +- .../Main/Gui/Simulator.py} | 2 +- src/Mod/Path/Path/Main/Gui/__init__.py | 0 .../PathJob.py => Path/Main/Job.py} | 4 +- .../PathStock.py => Path/Main/Stock.py} | 0 src/Mod/Path/Path/Main/__init__.py | 0 src/Mod/Path/Path/Op/Gui/Base.py | 2 +- src/Mod/Path/Path/Post/Command.py | 2 +- src/Mod/Path/PathScripts/PathUtils.py | 2 +- src/Mod/Path/PathScripts/PathUtilsGui.py | 2 +- src/Mod/Path/PathScripts/README.md | 6 +++ src/Mod/Path/PathTests/TestPathAdaptive.py | 4 +- .../Path/PathTests/TestPathDressupDogbone.py | 2 +- src/Mod/Path/PathTests/TestPathHelix.py | 2 +- src/Mod/Path/PathTests/TestPathStock.py | 2 +- 28 files changed, 92 insertions(+), 61 deletions(-) rename src/Mod/Path/Path/Dressup/{PathBoundary.py => Boundary.py} (99%) rename src/Mod/Path/Path/Dressup/Gui/{PathBoundary.py => Boundary.py} (97%) rename src/Mod/Path/{PathScripts/PathCamoticsGui.py => Path/Main/Gui/Camotics.py} (99%) rename src/Mod/Path/{PathScripts/PathFixture.py => Path/Main/Gui/Fixture.py} (97%) rename src/Mod/Path/{PathScripts/PathInspect.py => Path/Main/Gui/Inspect.py} (98%) rename src/Mod/Path/{PathScripts/PathJobGui.py => Path/Main/Gui/Job.py} (99%) rename src/Mod/Path/{PathScripts/PathJobCmd.py => Path/Main/Gui/JobCmd.py} (96%) rename src/Mod/Path/{PathScripts/PathJobDlg.py => Path/Main/Gui/JobDlg.py} (99%) rename src/Mod/Path/{PathScripts/PathPreferencesPathJob.py => Path/Main/Gui/PreferencesJob.py} (99%) rename src/Mod/Path/{PathScripts/PathSanity.py => Path/Main/Gui/Sanity.py} (99%) rename src/Mod/Path/{PathScripts/PathSimulatorGui.py => Path/Main/Gui/Simulator.py} (99%) create mode 100644 src/Mod/Path/Path/Main/Gui/__init__.py rename src/Mod/Path/{PathScripts/PathJob.py => Path/Main/Job.py} (99%) rename src/Mod/Path/{PathScripts/PathStock.py => Path/Main/Stock.py} (100%) create mode 100644 src/Mod/Path/Path/Main/__init__.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 06076ec361..40c5558f27 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -58,7 +58,7 @@ SET(PathPythonBaseGui_SRCS SET(PathPythonDressup_SRCS Path/Dressup/__init__.py Path/Dressup/Utils.py - Path/Dressup/PathBoundary.py + Path/Dressup/Boundary.py Path/Dressup/Tags.py ) @@ -68,7 +68,7 @@ SET(PathPythonDressupGui_SRCS Path/Dressup/Gui/Dogbone.py Path/Dressup/Gui/Dragknife.py Path/Dressup/Gui/LeadInOut.py - Path/Dressup/Gui/PathBoundary.py + Path/Dressup/Gui/Boundary.py Path/Dressup/Gui/Preferences.py Path/Dressup/Gui/RampEntry.py Path/Dressup/Gui/Tags.py @@ -76,6 +76,25 @@ SET(PathPythonDressupGui_SRCS Path/Dressup/Gui/ZCorrect.py ) +SET(PathPythonMain_SRCS + Path/Main/__init__.py + Path/Main/Job.py + Path/Main/Stock.py +) + +SET(PathPythonMainGui_SRCS + Path/Main/Gui/__init__.py + Path/Main/Gui/Camotics.py + Path/Main/Gui/Fixture.py + Path/Main/Gui/Inspect.py + Path/Main/Gui/Job.py + Path/Main/Gui/JobCmd.py + Path/Main/Gui/JobDlg.py + Path/Main/Gui/PreferencesJob.py + Path/Main/Gui/Sanity.py + Path/Main/Gui/Simulator.py +) + SET(PathPythonTools_SRCS Path/Tool/__init__.py Path/Tool/Bit.py @@ -195,17 +214,6 @@ SET(PathPythonOpGui_SRCS ) SET(PathScripts_SRCS - PathScripts/PathCamoticsGui.py - PathScripts/PathFixture.py - PathScripts/PathInspect.py - PathScripts/PathJob.py - PathScripts/PathJobCmd.py - PathScripts/PathJobDlg.py - PathScripts/PathJobGui.py - PathScripts/PathPreferencesPathJob.py - PathScripts/PathSanity.py - PathScripts/PathSimulatorGui.py - PathScripts/PathStock.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py PathScripts/__init__.py @@ -347,6 +355,8 @@ SET(all_files ${PathPythonBaseGenerator_SRCS} ${PathPythonDressup_SRCS} ${PathPythonDressupGui_SRCS} + ${PathPythonMain_SRCS} + ${PathPythonMainGui_SRCS} ${PathPythonOp_SRCS} ${PathPythonOpGui_SRCS} ${PathPythonPost_SRCS} @@ -427,6 +437,19 @@ INSTALL( Mod/Path/Path/Dressup/Gui ) +INSTALL( + FILES + ${PathPythonMain_SRCS} + DESTINATION + Mod/Path/Path/Main +) +INSTALL( + FILES + ${PathPythonMainGui_SRCS} + DESTINATION + Mod/Path/Path/Main/Gui +) + INSTALL( FILES ${PathPythonOp_SRCS} diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 292c5d2082..8e0e6770fc 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -59,8 +59,8 @@ class PathWorkbench(Workbench): global PathCommandGroup # Add preferences pages - before loading PathGui to properly order pages of Path group - from PathScripts import PathPreferencesPathJob import Path.Dressup.Gui.Preferences as PathPreferencesPathDressup + import Path.Main.Gui.PreferencesJob as PathPreferencesPathJob translate = FreeCAD.Qt.translate @@ -78,8 +78,8 @@ class PathWorkbench(Workbench): FreeCADGui.addLanguagePath(":/translations") FreeCADGui.addIconPath(":/icons") import Path.GuiInit - from PathScripts import PathJobCmd + from Path.Main.Gui import JobCmd as PathJobCmd from Path.Tool.Gui import BitCmd as PathToolBitCmd from Path.Tool.Gui import BitLibraryCmd as PathToolBitLibraryCmd diff --git a/src/Mod/Path/Path/Dressup/PathBoundary.py b/src/Mod/Path/Path/Dressup/Boundary.py similarity index 99% rename from src/Mod/Path/Path/Dressup/PathBoundary.py rename to src/Mod/Path/Path/Dressup/Boundary.py index 896e4a164a..9194b0d6d7 100644 --- a/src/Mod/Path/Path/Dressup/PathBoundary.py +++ b/src/Mod/Path/Path/Dressup/Boundary.py @@ -25,7 +25,7 @@ import FreeCAD import Path import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup -import PathScripts.PathStock as PathStock +import Path.Main.Stock as PathStock import PathScripts.PathUtils as PathUtils if False: diff --git a/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py b/src/Mod/Path/Path/Dressup/Gui/Boundary.py similarity index 97% rename from src/Mod/Path/Path/Dressup/Gui/PathBoundary.py rename to src/Mod/Path/Path/Dressup/Gui/Boundary.py index 3a4ae36e94..0947f271b9 100644 --- a/src/Mod/Path/Path/Dressup/Gui/PathBoundary.py +++ b/src/Mod/Path/Path/Dressup/Gui/Boundary.py @@ -25,7 +25,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui import Path -import Path.Dressup.PathBoundary as PathDressupPathBoundary +import Path.Dressup.Boundary as PathDressupPathBoundary import PathGui if False: @@ -112,8 +112,8 @@ class TaskPanel(object): self.setClean() def updateStockEditor(self, index, force=False): - import PathScripts.PathJobGui as PathJobGui - import PathScripts.PathStock as PathStock + import Path.Main.Gui.Job as PathJobGui + import Path.Main.Stock as PathStock def setupFromBaseEdit(): Path.Log.track(index, force) @@ -289,9 +289,9 @@ class CommandPathDressupPathBoundary: # everything ok! FreeCAD.ActiveDocument.openTransaction("Create Path Boundary Dress-up") - FreeCADGui.addModule("Path.Dressup.Gui.PathBoundary") + FreeCADGui.addModule("Path.Dressup.Gui.Boundary") FreeCADGui.doCommand( - "Path.Dressup.Gui.PathBoundary.Create(App.ActiveDocument.%s)" + "Path.Dressup.Gui.Boundary.Create(App.ActiveDocument.%s)" % baseObject.Name ) # FreeCAD.ActiveDocument.commitTransaction() # Final `commitTransaction()` called via TaskPanel.accept() diff --git a/src/Mod/Path/Path/Dressup/Utils.py b/src/Mod/Path/Path/Dressup/Utils.py index cec69a65fc..3927f77bdf 100644 --- a/src/Mod/Path/Path/Dressup/Utils.py +++ b/src/Mod/Path/Path/Dressup/Utils.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathJob as PathJob +import Path.Main.Job as PathJob def selection(): diff --git a/src/Mod/Path/Path/GuiInit.py b/src/Mod/Path/Path/GuiInit.py index 265f6b9710..b57207240b 100644 --- a/src/Mod/Path/Path/GuiInit.py +++ b/src/Mod/Path/Path/GuiInit.py @@ -44,10 +44,16 @@ def Startup(): from Path.Dressup.Gui import Dogbone from Path.Dressup.Gui import Dragknife from Path.Dressup.Gui import LeadInOut - from Path.Dressup.Gui import PathBoundary + from Path.Dressup.Gui import Boundary from Path.Dressup.Gui import RampEntry from Path.Dressup.Gui import Tags from Path.Dressup.Gui import ZCorrect + + from Path.Main.Gui import Fixture + from Path.Main.Gui import Inspect + from Path.Main.Gui import Sanity + from Path.Main.Gui import Simulator + from Path.Op.Gui import Adaptive from Path.Op.Gui import Array from Path.Op.Gui import Comment @@ -71,10 +77,6 @@ def Startup(): from Path.Tool import Controller from Path.Tool.Gui import Controller - from PathScripts import PathFixture - from PathScripts import PathInspect - from PathScripts import PathSanity - from PathScripts import PathSimulatorGui from PathScripts import PathUtilsGui from packaging.version import Version, parse @@ -91,7 +93,7 @@ def Startup(): v = parse(r.decode("utf-8")) if v >= Version("1.2.2"): - from PathScripts import PathCamoticsGui + from Path.Main.Gui import Camotics except (FileNotFoundError, ModuleNotFoundError): pass diff --git a/src/Mod/Path/PathScripts/PathCamoticsGui.py b/src/Mod/Path/Path/Main/Gui/Camotics.py similarity index 99% rename from src/Mod/Path/PathScripts/PathCamoticsGui.py rename to src/Mod/Path/Path/Main/Gui/Camotics.py index 5cfa941baf..04416d9fc3 100644 --- a/src/Mod/Path/PathScripts/PathCamoticsGui.py +++ b/src/Mod/Path/Path/Main/Gui/Camotics.py @@ -322,7 +322,7 @@ class CommandCamoticsSimulate: return False try: job = FreeCADGui.Selection.getSelectionEx()[0].Object - return isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) + return isinstance(job.Proxy, Path.Main.Job.ObjectJob) except: return False diff --git a/src/Mod/Path/PathScripts/PathFixture.py b/src/Mod/Path/Path/Main/Gui/Fixture.py similarity index 97% rename from src/Mod/Path/PathScripts/PathFixture.py rename to src/Mod/Path/Path/Main/Gui/Fixture.py index 4a847306ff..ed98c73d29 100644 --- a/src/Mod/Path/PathScripts/PathFixture.py +++ b/src/Mod/Path/Path/Main/Gui/Fixture.py @@ -165,16 +165,16 @@ class CommandPathFixture: def Activated(self): FreeCAD.ActiveDocument.openTransaction("Create a Fixture Offset") - FreeCADGui.addModule("PathScripts.PathFixture") + FreeCADGui.addModule("Path.Main.Gui.Fixture") snippet = """ import Path import PathScripts from PathScripts import PathUtils prjexists = False obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Fixture") -PathScripts.PathFixture.Fixture(obj) +Path.Main.Gui.Fixture.Fixture(obj) obj.Active = True -PathScripts.PathFixture._ViewProviderFixture(obj.ViewObject) +Path.Main.Gui.Fixture._ViewProviderFixture(obj.ViewObject) PathUtils.addToJob(obj) diff --git a/src/Mod/Path/PathScripts/PathInspect.py b/src/Mod/Path/Path/Main/Gui/Inspect.py similarity index 98% rename from src/Mod/Path/PathScripts/PathInspect.py rename to src/Mod/Path/Path/Main/Gui/Inspect.py index be63084b7c..63d4e6f7d6 100644 --- a/src/Mod/Path/PathScripts/PathInspect.py +++ b/src/Mod/Path/Path/Main/Gui/Inspect.py @@ -285,9 +285,9 @@ class CommandPathInspect: return # if everything is ok, execute - FreeCADGui.addModule("PathScripts.PathInspect") + FreeCADGui.addModule("Path.Main.Gui.Inspect") FreeCADGui.doCommand( - "PathScripts.PathInspect.show(FreeCAD.ActiveDocument." + "Path.Main.Gui.Inspect.show(FreeCAD.ActiveDocument." + selection[0].Name + ")" ) diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/Path/Main/Gui/Job.py similarity index 99% rename from src/Mod/Path/PathScripts/PathJobGui.py rename to src/Mod/Path/Path/Main/Gui/Job.py index 6a03526b9c..c8cd3285d4 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/Path/Main/Gui/Job.py @@ -31,12 +31,12 @@ import Path import Path.Base.Gui.SetupSheet as PathSetupSheetGui import Path.Base.Util as PathUtil import Path.GuiInit as PathGuiInit +import Path.Main.Gui.JobCmd as PathJobCmd +import Path.Main.Gui.JobDlg as PathJobDlg +import Path.Main.Job as PathJob +import Path.Main.Stock as PathStock import Path.Tool.Gui.Bit as PathToolBitGui import Path.Tool.Gui.Controller as PathToolControllerGui -import PathScripts.PathJob as PathJob -import PathScripts.PathJobCmd as PathJobCmd -import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathStock as PathStock import PathScripts.PathUtils as PathUtils import json import math @@ -1627,7 +1627,7 @@ class TaskPanel: def Create(base, template=None, openTaskPanel=True): """Create(base, template) ... creates a job instance for the given base object using template to configure it.""" - FreeCADGui.addModule("PathScripts.PathJob") + FreeCADGui.addModule("Path.Main.Job") FreeCAD.ActiveDocument.openTransaction("Create Job") try: obj = PathJob.Create("Job", base, template) diff --git a/src/Mod/Path/PathScripts/PathJobCmd.py b/src/Mod/Path/Path/Main/Gui/JobCmd.py similarity index 96% rename from src/Mod/Path/PathScripts/PathJobCmd.py rename to src/Mod/Path/Path/Main/Gui/JobCmd.py index 0c0c820d1c..33ca911867 100644 --- a/src/Mod/Path/PathScripts/PathJobCmd.py +++ b/src/Mod/Path/Path/Main/Gui/JobCmd.py @@ -26,9 +26,9 @@ import FreeCAD import FreeCADGui import Path import Path.Base.Util as PathUtil -import PathScripts.PathJob as PathJob -import PathScripts.PathJobDlg as PathJobDlg -import PathScripts.PathStock as PathStock +import Path.Main.Gui.JobDlg as PathJobDlg +import Path.Main.Job as PathJob +import Path.Main.Stock as PathStock import json import os @@ -75,13 +75,13 @@ class CommandJobCreate: @classmethod def Execute(cls, base, template): - FreeCADGui.addModule("PathScripts.PathJobGui") + FreeCADGui.addModule("Path.Main.Gui.Job") if template: template = "'%s'" % template else: template = "None" FreeCADGui.doCommand( - "PathScripts.PathJobGui.Create(%s, %s)" % ([o.Name for o in base], template) + "Path.Main.Gui.Job.Create(%s, %s)" % ([o.Name for o in base], template) ) diff --git a/src/Mod/Path/PathScripts/PathJobDlg.py b/src/Mod/Path/Path/Main/Gui/JobDlg.py similarity index 99% rename from src/Mod/Path/PathScripts/PathJobDlg.py rename to src/Mod/Path/Path/Main/Gui/JobDlg.py index daf63c7fff..4476dc0a00 100644 --- a/src/Mod/Path/PathScripts/PathJobDlg.py +++ b/src/Mod/Path/Path/Main/Gui/JobDlg.py @@ -26,8 +26,8 @@ import FreeCAD import FreeCADGui import Path import Path.Base.Util as PathUtil -import PathScripts.PathJob as PathJob -import PathScripts.PathStock as PathStock +import Path.Main.Job as PathJob +import Path.Main.Stock as PathStock import glob import os diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/Path/Main/Gui/PreferencesJob.py similarity index 99% rename from src/Mod/Path/PathScripts/PathPreferencesPathJob.py rename to src/Mod/Path/Path/Main/Gui/PreferencesJob.py index 400454c3ba..7d3d3c0b8a 100644 --- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py +++ b/src/Mod/Path/Path/Main/Gui/PreferencesJob.py @@ -22,8 +22,8 @@ import FreeCAD import Path +import Path.Main.Stock as PathStock import Path.Post.Processor as PostProcessor -import PathScripts.PathStock as PathStock import json from FreeCAD import Units diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/Path/Main/Gui/Sanity.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSanity.py rename to src/Mod/Path/Path/Main/Gui/Sanity.py index fe9fc1472e..d3cc8f8935 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/Path/Main/Gui/Sanity.py @@ -110,7 +110,7 @@ class CommandPathSanity: def IsActive(self): obj = FreeCADGui.Selection.getSelectionEx()[0].Object - return isinstance(obj.Proxy, PathScripts.PathJob.ObjectJob) + return isinstance(obj.Proxy, Path.Main.Job.ObjectJob) def Activated(self): # if everything is ok, execute @@ -551,7 +551,7 @@ class CommandPathSanity: m = 0 for i in obj.Document.Objects: if hasattr(i, "Proxy"): - if isinstance(i.Proxy, PathScripts.PathJob.ObjectJob): + if isinstance(i.Proxy, Path.Main.Job.ObjectJob): m += 1 if i is obj: n = m diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/Path/Main/Gui/Simulator.py similarity index 99% rename from src/Mod/Path/PathScripts/PathSimulatorGui.py rename to src/Mod/Path/Path/Main/Gui/Simulator.py index c3731e4730..430f12b559 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/Path/Main/Gui/Simulator.py @@ -24,8 +24,8 @@ import FreeCAD import Path import Path.Base.Util as PathUtil import Path.Dressup.Utils as PathDressup +import Path.Main.Job as PathJob import PathGui -import PathScripts.PathJob as PathJob import PathSimulator import math import os diff --git a/src/Mod/Path/Path/Main/Gui/__init__.py b/src/Mod/Path/Path/Main/Gui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/Path/Main/Job.py similarity index 99% rename from src/Mod/Path/PathScripts/PathJob.py rename to src/Mod/Path/Path/Main/Job.py index ff99602856..a758232cc4 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/Path/Main/Job.py @@ -20,15 +20,15 @@ # * * # *************************************************************************** +from Path.Post.Processor import PostProcessor from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import Path import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil -from Path.Post.Processor import PostProcessor +import Path.Main.Stock as PathStock import Path.Tool.Controller as PathToolController -import PathScripts.PathStock as PathStock import json import time diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/Path/Main/Stock.py similarity index 100% rename from src/Mod/Path/PathScripts/PathStock.py rename to src/Mod/Path/Path/Main/Stock.py diff --git a/src/Mod/Path/Path/Main/__init__.py b/src/Mod/Path/Path/Main/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Path/Path/Op/Gui/Base.py b/src/Mod/Path/Path/Op/Gui/Base.py index cc240ed37f..9f61f5d5cf 100644 --- a/src/Mod/Path/Path/Op/Gui/Base.py +++ b/src/Mod/Path/Path/Op/Gui/Base.py @@ -27,10 +27,10 @@ import Path.Base.Gui.GetPoint as PathGetPoint import Path.Base.Gui.Util as PathGuiUtil import Path.Base.SetupSheet as PathSetupSheet import Path.Base.Util as PathUtil +import Path.Main.Job as PathJob import Path.Op.Base as PathOp import Path.Op.Gui.Selection as PathSelection import PathGui -import PathScripts.PathJob as PathJob import PathScripts.PathUtils as PathUtils import importlib from PySide.QtCore import QT_TRANSLATE_NOOP diff --git a/src/Mod/Path/Path/Post/Command.py b/src/Mod/Path/Path/Post/Command.py index b0733580d3..324412c415 100644 --- a/src/Mod/Path/Path/Post/Command.py +++ b/src/Mod/Path/Path/Post/Command.py @@ -28,7 +28,7 @@ import FreeCAD import FreeCADGui import Path import Path.Base.Util as PathUtil -import PathScripts.PathJob as PathJob +import Path.Main.Job as PathJob import PathScripts.PathUtils as PathUtils import os import re diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index dd06227c6a..11ce84ad09 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -25,7 +25,7 @@ import FreeCAD from FreeCAD import Vector from PySide import QtCore import Path -import PathScripts.PathJob as PathJob +import Path.Main.Job as PathJob import math from numpy import linspace diff --git a/src/Mod/Path/PathScripts/PathUtilsGui.py b/src/Mod/Path/PathScripts/PathUtilsGui.py index 06a8d6ec82..8e27ecc2c1 100644 --- a/src/Mod/Path/PathScripts/PathUtilsGui.py +++ b/src/Mod/Path/PathScripts/PathUtilsGui.py @@ -23,9 +23,9 @@ import FreeCADGui import FreeCAD import Path +import Path.Main.Gui.JobCmd as PathJobCmd import Path.Tool.Controller as PathToolController import PathGui -import PathScripts.PathJobCmd as PathJobCmd import PathScripts.PathUtils as PathUtils from PySide import QtGui diff --git a/src/Mod/Path/PathScripts/README.md b/src/Mod/Path/PathScripts/README.md index b1ada3c2d6..7ce3416f40 100644 --- a/src/Mod/Path/PathScripts/README.md +++ b/src/Mod/Path/PathScripts/README.md @@ -1,3 +1,9 @@ +This is a legacy directory. The remaining files in here should either get refactored so +they can move into the new structure or removed. + +The PropertyBag diversion files are here in order to keep existing ToolBit files working. +They might get removed in future versions. + ### Assigned Accel Keys: * `P,I` ... Launch Path Inspector * `P,J` ... Create Job diff --git a/src/Mod/Path/PathTests/TestPathAdaptive.py b/src/Mod/Path/PathTests/TestPathAdaptive.py index 4805ba7856..62b0322ca3 100644 --- a/src/Mod/Path/PathTests/TestPathAdaptive.py +++ b/src/Mod/Path/PathTests/TestPathAdaptive.py @@ -25,12 +25,12 @@ import FreeCAD import Part import Path.Op.Adaptive as PathAdaptive -import PathScripts.PathJob as PathJob +import Path.Main.Job as PathJob from PathTests.PathTestUtils import PathTestBase if FreeCAD.GuiUp: + import Path.Main.gui.Job as PathJobGui import Path.Op.Gui.Adaptive as PathAdaptiveGui - import PathScripts.PathJobGui as PathJobGui class TestPathAdaptive(PathTestBase): diff --git a/src/Mod/Path/PathTests/TestPathDressupDogbone.py b/src/Mod/Path/PathTests/TestPathDressupDogbone.py index f41260ace5..aea96e434c 100644 --- a/src/Mod/Path/PathTests/TestPathDressupDogbone.py +++ b/src/Mod/Path/PathTests/TestPathDressupDogbone.py @@ -23,7 +23,7 @@ import FreeCAD import Path import Path.Dressup.Gui.Dogbone as PathDressupDogbone -import PathScripts.PathJob as PathJob +import Path.Main.Job as PathJob import Path.Op.Profile as PathProfile from PathTests.PathTestUtils import PathTestBase diff --git a/src/Mod/Path/PathTests/TestPathHelix.py b/src/Mod/Path/PathTests/TestPathHelix.py index fc630f28bd..69ce117a0d 100644 --- a/src/Mod/Path/PathTests/TestPathHelix.py +++ b/src/Mod/Path/PathTests/TestPathHelix.py @@ -23,8 +23,8 @@ import Draft import FreeCAD import Path +import Path.Main.Job as PathJob import Path.Op.Helix as PathHelix -import PathScripts.PathJob as PathJob import PathTests.PathTestUtils as PathTestUtils Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) diff --git a/src/Mod/Path/PathTests/TestPathStock.py b/src/Mod/Path/PathTests/TestPathStock.py index e7293e9092..bc2ab0060c 100644 --- a/src/Mod/Path/PathTests/TestPathStock.py +++ b/src/Mod/Path/PathTests/TestPathStock.py @@ -21,7 +21,7 @@ # *************************************************************************** import FreeCAD -import PathScripts.PathStock as PathStock +import Path.Main.Stock as PathStock from PathTests.PathTestUtils import PathTestBase From dba0106ce0ccb0b161b3def0e232e694f809b72d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 2 Sep 2022 18:48:57 -0700 Subject: [PATCH 22/33] Removing obsolete and unused files --- src/Mod/Path/PathScripts/PathCollision.py | 127 ------------------ src/Mod/Path/PathScripts/PathPlane.py | 156 ---------------------- 2 files changed, 283 deletions(-) delete mode 100644 src/Mod/Path/PathScripts/PathCollision.py delete mode 100644 src/Mod/Path/PathScripts/PathPlane.py diff --git a/src/Mod/Path/PathScripts/PathCollision.py b/src/Mod/Path/PathScripts/PathCollision.py deleted file mode 100644 index 18c245d05b..0000000000 --- a/src/Mod/Path/PathScripts/PathCollision.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2017 sliptonic * -# * * -# * 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 -from PySide import QtCore -from PathScripts.PathUtils import waiting_effects -from PySide.QtCore import QT_TRANSLATE_NOOP - -LOG_MODULE = "PathCollision" -Path.Log.setLevel(Path.Log.Level.DEBUG, LOG_MODULE) -Path.Log.trackModule("PathCollision") -FreeCAD.setLogLevel("Path.Area", 0) - -__title__ = "Path Collision Utility" -__author__ = "sliptonic (Brad Collette)" -__url__ = "https://www.freecadweb.org" - -"""Path Collision object and FreeCAD command""" - - -class _CollisionSim: - def __init__(self, obj): - obj.Proxy = self - - def execute(self, fp): - """Do something when doing a recomputation, this method is mandatory""" - print("_CollisionSim", fp) - - -class _ViewProviderCollisionSim: - def __init__(self, vobj): - self.Object = vobj.Object - vobj.Proxy = self - vobj.addProperty( - "App::PropertyLink", - "Original", - "reference", - QT_TRANSLATE_NOOP( - "App::Property", "The base object this collision refers to" - ), - ) - - def attach(self, vobj): - self.Object = vobj.Object - - def setEdit(self, vobj, mode=0): - return True - - def getIcon(self): - return ":/icons/Path_Contour.svg" - - def __getstate__(self): - return None - - def __setstate__(self, state): - return None - - def onDelete(self, feature, subelements): - feature.Original.ViewObject.Visibility = True - return True - - -def __compareBBSpace(bb1, bb2): - if ( - bb1.XMin == bb2.XMin - and bb1.XMax == bb2.XMax - and bb1.YMin == bb2.YMin - and bb1.YMax == bb2.YMax - and bb1.ZMin == bb2.ZMin - and bb1.ZMax == bb2.ZMax - ): - return True - return False - - -@waiting_effects -def getCollisionObject(baseobject, simobject): - result = None - cVol = baseobject.Shape.common(simobject) - if cVol.Volume > 1e-12: - baseColor = (0.800000011920929, 0.800000011920929, 0.800000011920929, 00.0) - intersecColor = (1.0, 0.0, 0.0, 0.0) - colorassignment = [] - gougedShape = baseobject.Shape.cut(simobject) - - for i in gougedShape.Faces: - match = False - for j in cVol.Faces: - if __compareBBSpace(i.BoundBox, j.BoundBox): - match = True - if match is True: - # print ("Need to highlight Face{}".format(idx+1)) - colorassignment.append(intersecColor) - else: - colorassignment.append(baseColor) - - obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Collision") - obj.Shape = gougedShape - _CollisionSim(obj) - _ViewProviderCollisionSim(obj.ViewObject) - - obj.ViewObject.DiffuseColor = colorassignment - FreeCAD.ActiveDocument.recompute() - baseobject.ViewObject.Visibility = False - obj.ViewObject.Original = baseobject - - return result diff --git a/src/Mod/Path/PathScripts/PathPlane.py b/src/Mod/Path/PathScripts/PathPlane.py deleted file mode 100644 index 9db418952d..0000000000 --- a/src/Mod/Path/PathScripts/PathPlane.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -# *************************************************************************** -# * Copyright (c) 2015 Dan Falck * -# * * -# * 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 * -# * * -# *************************************************************************** - -""" Used for CNC machine plane selection G17,G18,G19 """ - -import FreeCAD -import FreeCADGui -import Path -from PySide import QtCore -from PySide.QtCore import QT_TRANSLATE_NOOP - - -class Plane: - def __init__(self, obj): - obj.addProperty( - "App::PropertyEnumeration", - "SelectionPlane", - "Path", - QT_TRANSLATE_NOOP("App::Property", "Orientation plane of CNC path"), - ) - obj.SelectionPlane = ["XY", "XZ", "YZ"] - obj.addProperty( - "App::PropertyBool", - "Active", - "Path", - QT_TRANSLATE_NOOP( - "App::Property", "Make False, to prevent operation from generating code" - ), - ) - obj.Proxy = self - - def execute(self, obj): - clonelist = ["XY", "XZ", "YZ"] - cindx = clonelist.index(str(obj.SelectionPlane)) - pathlist = ["G17", "G18", "G19"] - labelindx = clonelist.index(obj.SelectionPlane) + 1 - obj.Label = "Plane" + str(labelindx) - if obj.Active: - obj.Path = Path.Path(pathlist[cindx]) - obj.ViewObject.Visibility = True - else: - obj.Path = Path.Path("(inactive operation)") - obj.ViewObject.Visibility = False - - -class _ViewProviderPlane: - def __init__(self, vobj): # mandatory - vobj.Proxy = self - mode = 2 - vobj.setEditorMode("LineWidth", mode) - vobj.setEditorMode("MarkerColor", mode) - vobj.setEditorMode("NormalColor", mode) - vobj.setEditorMode("DisplayMode", mode) - vobj.setEditorMode("BoundingBox", mode) - vobj.setEditorMode("Selectable", mode) - vobj.setEditorMode("ShapeColor", mode) - vobj.setEditorMode("Transparency", mode) - vobj.setEditorMode("Visibility", mode) - - def __getstate__(self): # mandatory - return None - - def __setstate__(self, state): # mandatory - return None - - def getIcon(self): # optional - return ":/icons/Path_Plane.svg" - - def onChanged(self, vobj, prop): # optional - mode = 2 - vobj.setEditorMode("LineWidth", mode) - vobj.setEditorMode("MarkerColor", mode) - vobj.setEditorMode("NormalColor", mode) - vobj.setEditorMode("DisplayMode", mode) - vobj.setEditorMode("BoundingBox", mode) - vobj.setEditorMode("Selectable", mode) - vobj.setEditorMode("ShapeColor", mode) - vobj.setEditorMode("Transparency", mode) - vobj.setEditorMode("Visibility", mode) - - def updateData(self, vobj, prop): # optional - # this is executed when a property of the APP OBJECT changes - pass - - def setEdit(self, vobj, mode): # optional - # this is executed when the object is double-clicked in the tree - pass - - def unsetEdit(self, vobj, mode): # optional - # this is executed when the user cancels or terminates edit mode - pass - - -class CommandPathPlane: - def GetResources(self): - return { - "Pixmap": "PathPlane", - "MenuText": QT_TRANSLATE_NOOP("PathPlane", "Selection Plane"), - "ToolTip": QT_TRANSLATE_NOOP( - "PathPlane", "Create a Selection Plane object" - ), - } - - def IsActive(self): - if FreeCAD.ActiveDocument is not None: - for o in FreeCAD.ActiveDocument.Objects: - if o.Name[:3] == "Job": - return True - return False - - def Activated(self): - FreeCAD.ActiveDocument.openTransaction("Create a Selection Plane object") - FreeCADGui.addModule("PathScripts.PathPlane") - snippet = """ -import Path -import PathScripts -from PathScripts import PathUtils -prjexists = False -obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Plane") -PathScripts.PathPlane.Plane(obj) -obj.Active = True -PathScripts.PathPlane._ViewProviderPlane(obj.ViewObject) -PathUtils.addToJob(obj) - -""" - - FreeCADGui.doCommand(snippet) - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() - - -if FreeCAD.GuiUp: - # register the FreeCAD command - FreeCADGui.addCommand("Path_Plane", CommandPathPlane()) - - -FreeCAD.Console.PrintLog("Loading PathPlane... done\n") From 134a1c35ba7ff45bc99a36f7f3cf606c0a549e0d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 5 Sep 2022 12:19:42 -0700 Subject: [PATCH 23/33] Added missing diversion entries to cmake --- src/Mod/Path/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 40c5558f27..6383353c35 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -216,6 +216,8 @@ SET(PathPythonOpGui_SRCS SET(PathScripts_SRCS PathScripts/PathUtils.py PathScripts/PathUtilsGui.py + PathScripts/PathPropertyBag.py + PathScripts/PathPropertyBagGui.py PathScripts/__init__.py ) From 93af3a13e5a478a8f38540512da157afa2ed3863 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 8 Sep 2022 20:54:14 -0700 Subject: [PATCH 24/33] Fix SurfaceSupport for new Path source structure --- src/Mod/Path/Path/Op/SurfaceSupport.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/Path/Op/SurfaceSupport.py b/src/Mod/Path/Path/Op/SurfaceSupport.py index 90aabed6a2..88656ef260 100644 --- a/src/Mod/Path/Path/Op/SurfaceSupport.py +++ b/src/Mod/Path/Path/Op/SurfaceSupport.py @@ -649,9 +649,10 @@ class ProcessSelectedFaces: """_isReady(module)... Internal method. Checks if required attributes are available for processing obj.Base (the Base Geometry).""" Path.Log.debug("ProcessSelectedFaces _isReady({})".format(module)) - if hasattr(self, module): + modMethodName = module.replace("Op.", "Path") + if hasattr(self, modMethodName): self.module = module - modMethod = getattr(self, module) # gets the attribute only + modMethod = getattr(self, modMethodName) # gets the attribute only modMethod() # executes as method else: Path.Log.error('PSF._isReady() no "{}" method.'.format(module)) From 38a7826ddeed64deec2b6b3830bf9267fc74d9ea Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 9 Sep 2022 20:22:35 -0700 Subject: [PATCH 25/33] Fixed merge issues --- src/Mod/Path/Path/Base/FeedRate.py | 2 +- src/Mod/Path/Path/Base/SetupSheetOpPrototype.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/Path/Base/FeedRate.py b/src/Mod/Path/Path/Base/FeedRate.py index a95d367a37..b3c150fb85 100644 --- a/src/Mod/Path/Path/Base/FeedRate.py +++ b/src/Mod/Path/Path/Base/FeedRate.py @@ -65,7 +65,7 @@ def setFeedRate(commandlist, ToolController): else currentposition.z ) endpoint = FreeCAD.Vector(x, y, z) - if PathGeom.pointsCoincide(currentposition, endpoint): + if Path.Geom.pointsCoincide(currentposition, endpoint): return True return Path.Geom.isVertical(Part.makeLine(currentposition, endpoint)) diff --git a/src/Mod/Path/Path/Base/SetupSheetOpPrototype.py b/src/Mod/Path/Path/Base/SetupSheetOpPrototype.py index 4e3f05c99b..92d17fc99f 100644 --- a/src/Mod/Path/Path/Base/SetupSheetOpPrototype.py +++ b/src/Mod/Path/Path/Base/SetupSheetOpPrototype.py @@ -65,11 +65,11 @@ class Property(object): def setupProperty(self, obj, name, category, value): created = False if not hasattr(obj, name): - PathLog.track('add', obj.Name, name, self.propType) + Path.Log.track('add', obj.Name, name, self.propType) obj.addProperty(self.propType, name, category, self.info) self.initProperty(obj, name) created = True - PathLog.track('set', obj.Name, name, value, type(value)) + Path.Log.track('set', obj.Name, name, value, type(value)) setattr(obj, name, value) return created From 1105403c6781c44eab32791cefef3f9d33784c0f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 11 Sep 2022 20:44:02 -0700 Subject: [PATCH 26/33] Fixed unit test files by re-creating the appropriate jobs --- src/Mod/Path/PathTests/boxtest.fcstd | Bin 37622 -> 39419 bytes src/Mod/Path/PathTests/boxtest1.fcstd | Bin 50965 -> 50190 bytes src/Mod/Path/PathTests/test_filenaming.fcstd | Bin 300854 -> 298893 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Mod/Path/PathTests/boxtest.fcstd b/src/Mod/Path/PathTests/boxtest.fcstd index 2a6e76e292557c82cc37f043bfa0606ac5425335..185ca0bea1b0333eb74628582453b2808b11f45d 100644 GIT binary patch delta 31395 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 delta 29117 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

-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 diff --git a/src/Mod/Path/PathTests/boxtest1.fcstd b/src/Mod/Path/PathTests/boxtest1.fcstd index 210307fd72906bf897168c83e657b337edf1b7d1..11c624ef32e812a7ebae74d6a53ca7ad16920b18 100644 GIT binary patch literal 50190 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 literal 50965 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

  • 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 U1rJhSSO>6J7KjYANhX<0a&n$0 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 delta 62059 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%0Dwp8&{DNG>|}qj@aETh}qm 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 From 8448b69cf352a89e85ba67bc1a31ad50b2886283 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Sep 2022 19:50:47 -0700 Subject: [PATCH 27/33] Fix Path::Feature object check --- src/Mod/Path/App/AppPathPy.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 0ee2ca0a24..e3305aff80 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -161,7 +161,7 @@ namespace PathApp { if (PyObject_TypeCheck(pObj, &(App::DocumentObjectPy::Type))) { App::DocumentObject* obj = static_cast(pObj)->getDocumentObjectPtr(); - if (obj->getTypeId().isDerivedFrom(Base::Type::fromName("PathApp::Feature"))) { + if (obj->getTypeId().isDerivedFrom(Base::Type::fromName("Path::Feature"))) { const Path::Toolpath& path = static_cast(obj)->Path.getValue(); std::string gcode = path.toGCode(); Base::ofstream ofile(file); @@ -206,7 +206,7 @@ namespace PathApp { std::string gcode = buffer.str(); Path::Toolpath path; path.setFromGCode(gcode); - Path::Feature *object = static_cast(pcDoc->addObject("PathApp::Feature",file.fileNamePure().c_str())); + Path::Feature *object = static_cast(pcDoc->addObject("Path::Feature",file.fileNamePure().c_str())); object->Path.setValue(path); pcDoc->recompute(); } @@ -230,7 +230,7 @@ namespace PathApp { if (!pcDoc) pcDoc = App::GetApplication().newDocument(); Path::PathPy* pPath = static_cast(pcObj); - Path::Feature *pcFeature = static_cast(pcDoc->addObject("PathApp::Feature", name)); + Path::Feature *pcFeature = static_cast(pcDoc->addObject("Path::Feature", name)); Path::Toolpath* pa = pPath->getToolpathPtr(); if (!pa) { throw Py::Exception(PyExc_ReferenceError, "object doesn't reference a valid path"); From 83177edcbf42b7ecbbd956908457b0fd18cb9108 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Sep 2022 19:51:17 -0700 Subject: [PATCH 28/33] Convert usage of PathStop to Path.Op.Gui.Stop --- src/Mod/Path/Path/Main/Gui/Sanity.py | 2 +- src/Mod/Path/Path/Op/Gui/Stop.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/Path/Main/Gui/Sanity.py b/src/Mod/Path/Path/Main/Gui/Sanity.py index d3cc8f8935..0ba6da50e4 100644 --- a/src/Mod/Path/Path/Main/Gui/Sanity.py +++ b/src/Mod/Path/Path/Main/Gui/Sanity.py @@ -819,7 +819,7 @@ class CommandPathSanity: fname = obj.PostProcessorOutputFile data["outputfilename"] = os.path.splitext(os.path.basename(fname))[0] for op in obj.Operations.Group: - if isinstance(op.Proxy, PathScripts.PathStop.Stop) and op.Stop is True: + if isinstance(op.Proxy, Path.Op.Gui.Stop.Stop) and op.Stop is True: data["optionalstops"] = "True" if obj.LastPostProcessOutput == "": diff --git a/src/Mod/Path/Path/Op/Gui/Stop.py b/src/Mod/Path/Path/Op/Gui/Stop.py index 196c9ce74a..b6dbba9a4f 100644 --- a/src/Mod/Path/Path/Op/Gui/Stop.py +++ b/src/Mod/Path/Path/Op/Gui/Stop.py @@ -124,16 +124,16 @@ class CommandPathStop: FreeCAD.ActiveDocument.openTransaction( "Add Optional or Mandatory Stop to the program" ) - FreeCADGui.addModule("PathScripts.PathStop") + FreeCADGui.addModule("Path.Op.Gui.Stop") snippet = """ import Path import PathScripts from PathScripts import PathUtils prjexists = False obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Stop") -PathScripts.PathStop.Stop(obj) +Path.Op.Gui.Stop.Stop(obj) -PathScripts.PathStop._ViewProviderStop(obj.ViewObject) +Path.Op.Gui.Stop._ViewProviderStop(obj.ViewObject) PathUtils.addToJob(obj) """ FreeCADGui.doCommand(snippet) From 72f85f6873b77664e94eb2d6ef9774d485ac79f6 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 23 Sep 2022 21:53:42 -0700 Subject: [PATCH 29/33] Fixed refactor import issue --- src/Mod/Path/Path/Post/scripts/dynapath_post.py | 2 +- src/Mod/Path/Path/Post/scripts/fanuc_post.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/Path/Post/scripts/dynapath_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_post.py index 1db5e924e8..5f1798d50a 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_post.py @@ -33,7 +33,7 @@ from __future__ import print_function import FreeCAD from FreeCAD import Units -import Path.Post.Utils import PostUtils +import Path.Post.Utils as PostUtils import argparse import datetime import shlex diff --git a/src/Mod/Path/Path/Post/scripts/fanuc_post.py b/src/Mod/Path/Path/Post/scripts/fanuc_post.py index 752b5fb31b..afb7b8244f 100644 --- a/src/Mod/Path/Path/Post/scripts/fanuc_post.py +++ b/src/Mod/Path/Path/Post/scripts/fanuc_post.py @@ -29,7 +29,7 @@ import argparse import datetime import shlex import os.path -import Path.Post.Utils import PostUtils +import Path.Post.Utils as PostUtils TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to From b2d23a70012d3e45dda7c13432bf53e0588bb43a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 23 Sep 2022 23:53:06 -0700 Subject: [PATCH 30/33] Fixed property bag editor to cancel if no name is given and OK is pressed --- src/Mod/Path/Path/Base/Gui/PropertyBag.py | 43 +++++++++++++---------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/Path/Base/Gui/PropertyBag.py b/src/Mod/Path/Path/Base/Gui/PropertyBag.py index aac5201fa3..83e52290e0 100644 --- a/src/Mod/Path/Path/Base/Gui/PropertyBag.py +++ b/src/Mod/Path/Path/Base/Gui/PropertyBag.py @@ -321,10 +321,12 @@ class TaskPanel(object): typ = dialog.propertyType() grp = dialog.propertyGroup() info = dialog.propertyInfo() - propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info) - if dialog.propertyIsEnumeration(): - setattr(self.obj, name, dialog.propertyEnumerations()) - return (propname, info) + if name: + propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info) + if dialog.propertyIsEnumeration(): + setattr(self.obj, name, dialog.propertyEnumerations()) + return (propname, info) + return (None, None) def propertyAdd(self): Path.Log.track() @@ -337,21 +339,24 @@ class TaskPanel(object): # if we block signals the view doesn't get updated, surprise, surprise # self.model.blockSignals(True) name, info = self.addCustomProperty(self.obj, dialog) - index = 0 - for i in range(self.model.rowCount()): - index = i - if ( - self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) - > dialog.propertyName() - ): - break - self.model.insertRows(index, 1) - self._setupProperty(index, name) - self.form.table.selectionModel().setCurrentIndex( - self.model.index(index, 0), QtCore.QItemSelectionModel.Rows - ) - # self.model.blockSignals(False) - more = dialog.createAnother() + if name: + index = 0 + for i in range(self.model.rowCount()): + index = i + if ( + self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) + > dialog.propertyName() + ): + break + self.model.insertRows(index, 1) + self._setupProperty(index, name) + self.form.table.selectionModel().setCurrentIndex( + self.model.index(index, 0), QtCore.QItemSelectionModel.Rows + ) + # self.model.blockSignals(False) + more = dialog.createAnother() + else: + more = False else: more = False if not more: From 3c446479726bd1f07070fdb6c281a5ba41982127 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 2 Oct 2022 20:56:17 -0700 Subject: [PATCH 31/33] Full recompute on test file --- src/Mod/Path/PathTests/test_filenaming.fcstd | Bin 298893 -> 278960 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Mod/Path/PathTests/test_filenaming.fcstd b/src/Mod/Path/PathTests/test_filenaming.fcstd index 6d0f8b6eea415823ed0f1cb0712370b1431e2cfa..3e418e34d0ad77ee28bfcd3b775ec33c59731f44 100644 GIT binary patch literal 278960 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%0DQwdAs9-7;-D-? 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 R^3G;vGSaeif8VY7{{Z0c*`ELa literal 298893 zcmYIvV~{3Iu=VWNwr$&<*|BZgwr$(yGds3z+qTWU``#O0e1AG3>vTm`baYkb$;_i5 z4FZY^1Ox;L#8pb8TK*N^sv-jfw2TM}1p9AP)ZWS!O&kXlP(x?Kd)%lr(1ufJLfQSb3aswpRG1XjK#O<#AQBaI}&?tgt^lr7K-*< z{s%74ll2TQ`JZf(g}eB->#>M@^S4hQb{@Xt*R3I>k(05EQN6B=UapI*IOFH7{duU% zfIq{V2IMwZB8=ZG))S5$x~r}^7iDkRI^gpkc(mR3VsE14O;z1j*Jn5lhKxNP$zb9Y zbB8E99(IChz4j-v`K_yJ?fTOHF~|ft+jr3hS1;}?6p7jmU!CR z+Rh$U2y&e#_3J!r(;X~#eqGJ|fA_ZEQJR@5{NC5{NAqv%x$0%d@uzM%L&aYEvquSt z)^CDq0U=+jEj`#apKG}I+Ap=|IUOsOs~_Dj6`&;k-0C{T%a6`NZkl()6bqy|GqI1D z!UuLjB|ll+AMcCd?C9`ZMGnHTNk6d~f5(tqnGNd%1lqm%9eGY9x8wYVe&T@TyLtq? z5I?^P_9#@mB=)M9jDDwR;;*6TOhw9vE`QI*^X+TcHllFE@2?UB-LK_CcRuK18Wog) z2-82J9PeZsD6VYnbDE{jUI(&ZK27y6jB)U+`N0o4J)h?9>YwcB5MTQ1fg1Azk<7Y; zoy_s{wBFbCC(w$(^Y zH)!;r5&_lQye4Bb6+THG)mwNxlY1Aw`!cY5>MhS@ZO-)*vt^)rx$3K^s$U1!D)g_S zS1NFb_T^5)(L<~2lH>@^5RD2F(1TUmu&Ns>$D% zw>0+evJTM~38W0;;l&nJv~UIwBT?=Yb#)%@+~`%WkF9E50(fM@pUIDOt|}_DsQt86 zaROYV@9Jw0bsC;)3y-}8iIujISD0!Z^9zxHued8r`Q~KRy7mKDfC|kDUU(6Cf9D1J z)dlspX=za7&E=hC^P-}Dg9Edxe3jU8wgIy(+ezESlH&5^3SQnNWBKTEhD~a+Q?syk zch9OX1^;o}L<$DD0bG5eMIBpNrxurjQgwD5Y|iS|rH&y1-TCoEX8>=eZCe+Hc1>R< zjq0bWxE4wz0*JCBD|EGMbF`{KGdos7-aUc|S{H1*0PlZN;Dsy=yBi0gD+JJanXK+Jo6(cP*xMS*?B{%Kh zzqi+JT*JvW0nmd*M!V`z`C)^=y#YZ~j-Ck!YI@qdwdhTaVfCMdc zocn0X92+Bf>Fva+l7In;DvK~xBinz*7ccZ7u?y}pUYZOvC2Vw*k}law7JPKkz*N_u z7^P4*2t5TDYM5Amw%V0!=S4zk8C~?27n{1YT`!=eb)K#rTw^p)<2XXcUF_T3BRwiF z3ULaJkrCBS`B+Dk5jXHX%KiCp`08Z7E+&3!C?h{g+zx4p#zolm!l|yn00h`TIN=u> zUVBW?cnK&DWvw}Uu%^gu&-jScJ*8oB9U(mfx#7He9e2=`P^i6zJ5xiussfK41HbPkfbf3UrIhJi<{=IhmmndZ6jQ zO<*{{bn^QRZ>zhHr{+e)z9>Ue%lZTmw`UT>oYwso!tW$S5N=iVPY7ok555~#5f~e2 zABQw0l6|1XMR(1U9n3z`)VN~Hn-@4Gz^d**Ep=I|@E=44?qI52@VX(}sf@Ofs zRzRm(JE>})9;>)aOxKbWA9SBM;V;gk+HAM1hX5j~U8Obrz?Rajv6yN@d@$hEuwx}h zF=5wJz$B^i!lpT=>0h?PdM%cT*_}-FT zXIR6xL2%4Qizrw&Zr=SaH3D7st*AFX@I%^o!>TtcIK&Twx_yhl=a-zP4TUT*EFN z8RJgf9JkDOpxi5b96=y2zf06?g!H~Tdr#6pg^VPc8|ZQmG}o)DpTq{@JI&L$iDnrAPL|v@%ykBEl>f2@%s=uS8OJCUbCZ1Xs(=K$>fq zr^K#5sLHImAinb|S_^C9r%E-wP}+EuR8(85qNDCggM@EaIsnbsZ6lVR^SeozX2#r7 zyHX&$aca!=!g-? z{BL&grp11Gx4^n-={%k`dZTL2QZ+zeGIS*BUtY1I{eSFVxV(7Def$Gu98-Y#<@ylPx*>8_feQd(=8 zYxh@(g(mGO)TAB*7Xb8p%>+>Z^s%k!WHIc`b;p>}=HOf-3z-Q&i=)e>lMqePtB#dP za;F3Fgr>V2d8?yATiWjpZ5ug2mL@OZkWt;uhDuMD4ae1sOg5zI2lzQKiCnT4cjIHX zJ|so+9q;0hS{9)7jwkG>wN|EsE`?I3<~NGmWpx~-1#DaU1^+ysN^Vp86-O2l_XOq1 zyjrEMKQp7VUP~4-HF2w;--U}(!cVl2@<;Wy9+pG<9seR)J?k?IFZN5q%8hizKsA8b zI8eI?7fr@nMaJ_4`W87Q2H%s-1$jdR6BL@1TIDY${N%c15qDsvZF{wjx3to_DK(;c;{rwOrF{YJSw<ih*PC=UJUMRQBc-SeKrB;Y*ub}>$)gjXHU6?x99T<8UBd+ z3I@$qB1qhE`3RboXJbTyU|jb>n7B_BUcz>!S@MWv`^+cy`v`9P?Ve}3*;~Mc@pVRT zX8t$9)sW6tZ!q_dSmx%;!H#}r_8Pg?$xYf+YIcm)$;q_Vi?@}ot<)^t*aUqaPUIAt zkDMuB&v|U?u!s?#lf33Pq;9J*9Qb#kWwxFFT<8?pz;i;V9Z$8lf2duSWXbT2U8F8n zu=#ddksIS#fcbkym9vBWM8CqmCSb9fFRg3qv6k#MPI4zIK{U6tlFVK@sns5sa_JI* z)_U#|UZ^>@iN>u?x$lYym zn(*)&P_1!QcO=`m)_k;0H%@bqt&gUR#&y-YCBc(U*4S=8;(Z85O0(rDm<&{Y*^h4mMm z{`T%Hlw=SPPbye5E3;sb%?_l}J8)*9;}O^*=Wvs$`A>6N_S{R>k> zgY^@GUnDhfdceNtB2v#hvM*UT1OjLXr}9?yt8>Y3AEIGLD3ayR_s??%AykI0@oCrs z-;Kf7$48{fAdQn6EvN3W5ir3m6P-9Q@<|(OW-k}4V2mF&rUsy$ zHOsCsYK4VMT@cHhA#B~zzSx@E2jKzi97L0-5``Pt81YBIJ0 zt-0Y{Ehm|ao%ybZSq}&Kz4%E{w|gf)LY<97jwX2)NAQ&U9Dlo9kpTJBkd{G37CQ)z z6Z44*s16$l!?NMZR1$jo21A85>>Er>eWlA{{$YIB{CcjPI$>@suz{^3iXW zR22p4dzeFy+;~&vZlfxm?Mq@oI?0#VM<8}eMXepnXLM=?$jOhFhm^jK_!p@7JAHxr z(ro1!-{XfyQ~g0A{b4;nmTS{vZjYHu0(eAbxXbjB4k?g zHje+X$M=b3DIO3`e+Qh4y5l~)b`&<>7WwPzBQ@hnt&Rm3{v(dh<#U^8jI47gSBCy? zn3rmB+;XEM!mJiW$ko%qtp79D9K&mnQ5xFx{FSuZIh9W)1ZZpLG%Zb<}Z-yMV4G53y6*u z5J|I+If(V+V>ZDyXo+)kFU^JqKiFal`=By0co{b&xJia0O(NHmC&PokA1u?ix-)Ta zN3p?REr7*qP8a_G-Ts?Qi3hSk^ag~P@yE78<90Q7i`Nc0cZrR_hcL2S9oc!b@19K( z@5(IP_VtF91t+f29I~uctrcO*Ou&U+t2AC~2>=NrZM562{4!&(zpp_TLljpeJfu-w zYLebvI4cGLhSf4FRxEn(Grf^4dGeo!#Ag~1?*h#f~-7AsSVUk zT}jIg!h7%{7{0$BY8KR6Pbn*RXYLvfT(>&fcA#ODS&}NDI9nO0spQ}JZ2HXIFMw!9 zX2r^|nPLtcA=5f`Y~6Ld-tP6!uLoL{nByt4R6L9T&NU zjDjGAZ(`@UN!-yk1{ zhY+4n+yQ8*uSrqoUX~xq{vb`gLxlS#q`uq<{ICR|aPql%f5XEw<&12Rt;!-C>$)IB z?FDDEXz$9A_uBw`S3=n|XWP?Z4gL7I@fxi~?4{IjiBa31&p2~?Vl%*ad#^@S-PB>e zCoVs$1-$%2kldxU?M7X^7)x{gFm%4ACVntwoZU4TGm&ImynRurDV|as=#}bNpFOo1 z{XW(#_ddm52%JZ3?DlVRZD9+QdE$P{G5pM-AA0;T4yV*lH&36zrDr$7(df1bJ4Xn) zw(`C8SQB}hJ@3?PjDTbIoehNW;1mSkaxfS`E%9aDVo;k5pEnPv)W)g8dNECygn%!Rwpv_ z{7OO|<7@$ujsn%OD8wp3>tSJ7NeBcBWwOa$WX1=nHip=_iN(o>Ea8m!PTYaNW41{d zz?Wrx)w7*%-OA$7c~h_%{#l9OCW!N+;-m+J03~*b$af`>=CZE+@ z5OL2wD6t5H6n-Q{DxU}&Ev2k$~eN*g zmv6-!BktJO}) zvtwmplicFhdqDH-S6q+ZL+{&s7j?JzK-q^brkuyD)_AFABV}Pl)R!8x2~-tujUt_3 z1R#B`b>Tx}QeRt@PH@gggv;Ua%(Sb2*oWc)qm$NnZy0XGSFqskdAHgreHPoc3rF-i zZOl+t-=%4rs;NM3B6;lHO+3eM%V$#Ko;Gi1QfWS;<>6$~4ztlAa*1~9+udjA7=7=< zg^4}YD`0;+oj*-1I9k6mSF~8M%=4w6hXwpG;*PoE1V9h#1*RMBt^lFf1ndUZMc)cm zA3nSzTg}}M*X(LP?fP~`|RhX(svp6LrQ66&Sy zu9r;F3v9iQCqF#9(HMyyC1d2%WL6U$Vy*r~46La-=|V@rb66jd5sP0KIxvO80UF%o zSgH$&2QX=Teca)ML1!+7s}&-q@05kBLtOfB@v$JY%{v(nawcYjg}a*5-@yXD5d@(y z0_~*pB+yn&$j0gDPK!vzcqr;OfO!_s5AEBHQ3Kv=k43zMC$v&bs~E*klBPU7!^ws# z4n2$4)D0xg`Uo<@oo^XU0JpzmCP9DKn)HA+VXg!qm}9Y5;QiI_sCa7dy_Jy8!;wEb zH}8RkQ}G$i+rMP0a=vYsFvPGZt64wTqaa`7*K&5eGRixV||c( zFZM}JW2UkTLV1KxsT}`!x^7F!HsYqt`NjLnfd0toa{wM*(6gX6J;wLq^ye_bQ8OUW zk~`cHV)2??D~01mGtPzL-Vc%w@7xwFjzHIsHco*$()w4&mxg4bPMz2F&U-P3o?p!Z z3T<_R*jZLGv5-h$zUkocXc%6nbRP%$TO}@r0-fk}t3*<*=7(Gx9pT)%bdTxug1WGJ zUh_@z4!E>_X-Yz4yV_s;K{eF71fD2W%U{w3pz4W*e~S?7W8XB5Rd(bE5Tmv6 z-e`i&vc!Nm!W9jf9~73K-3+KhWQ(o}ZMluG%NjO%%M7W9Rn={IBYbe{Hry<5%7hPt zuq%}+Min0-Kn#l$sby59ViH;ssk_-#JYrQOF&yz+AjhBVsnlgjREO4Gu`37EPS&BE zO~-t2LMHp76KW-@m}=b|H6n;_wajL~nYoeU`E#s8-Io#|8BQK4p}u zl4F$9Qr2eb7fW%ZJP;;HEnspImf!70n3Zkc*nn0L`G+-8>fvRJrU=Ru?U+M3=0TQ# zL?h_-#U&IcG_X4aPZX{$AJukor(A8UY8K68754T$<8UY>t!Ybm7ccO}QK9JONz0uL zNNE&*c9W3jO~wIuAPU(rM>e?)N(PQN^R$gIA`%5?9&sO4tU54n4K zGCq-C6tN57(k#V}3m0=0ARL3Na$1KaufaM#9G4jk%SrXu@YHc`cuKY@Kd=iIxFL9; zumCfJql`)!@#~ZAVM!SH_|>4~mh283w-wit|5l(Y(mUl=ypUoBkS%#&Q|MB#uDfM; z&V_{^4m&J}z50Y@pfiV<;V-DmKm6_w06kHPq3*V->NUjS5i%~D3>R8AZwa(;s21Xm z&C_#Jak^AF_Gj|ThNsAfP2S{5?~!|bmwe7p4HEb&LxCA zh_bdk_EBc?G7a^9g&zzg{$(Xee6o;JD3gy0Od40u51>kS4 zK;9*26h%R???d?k^-Oz{V;m+nXCI6v4&ZO?dJ2c#SgR7m`>?@4C}M&*6h*c_+B0H> z1)Hdc@LOqj1X6-_Z3@Dcam?KX@#^~kW2aPo4gw?x>z!t-pDGq17 zbQfI|rcH%gqBw}nR--uh8zNv!kW!3x-bz3yFHPcM~Y0&owHZ#KR|?p zV?)HwU&d8gkx@df9}d$FeR_1>X*poW71`k zR>=ADNyrI~H0gf3^Z<0N>(yGW>%CPFhwNP$)Q%|hr&(8(ElQVW?Gmsb*BLlWf#sYy z-kdm={d1M)c8wbK`9B*jV~<>^OdG1ix^3j@DWy}7brvTk!Kj@9+LK48!3=J7PYh#O zn%xI>q%o2MZ53qM0S<0eqEMPgkFLhI4cy2W8&J59?YgB?tKC|wVzKxiJm(NA5@7AZ zrX!MaR{?|OBvDVkQ4ng2Bka#1*F;2n!A!dM{QJuX6+(Y39T!X0^DcG-)OK;c@HQPM`Ds<1lib67 zKLeL>vryu@}8E%2;W`zg=;T{Vy8bde9AO z@zTVKJGf{jll?K1>yhN((V{v##57`q(g?tM)HDcU zrs2K+mNtk;KQkY)7*vfWUyoOt_!_ z@mnSYwJ#F{-@Cl;2v+67)p9{eQ@1}`jMyAZ;;*Y5zqbv`Z((&8_kA0~=hnu=RFL>v z{+;H6)m8}0*+@E3zz=Y@;-b3Oi>-kyRQ#gs zSS4+s5dt!42wLW)Xq;vo zEToR7v!`E>^<}B5(Sm@g#zf9>^_=)4bkoq@_QmgQBW6#z`;s4xHE*kcax`-rYlF<0 zzLev|oMwFSLrkt6F7UfUdSSm*c)N|sK(>^$h)IF|Q7yMeXjV*`oXY;tYa|W)SFhZ> z?Z#aE*7wd}+~Cq_BkSv&wAH15%#(>2n4ns>_q{Ys3(FvZAY?zU06-o?fM ziR*Hg%7FGVm#PX{>*s4gCY`v4wOcD9U=!;lJdI0cwP+41A2=H!Qbtt*M|-rqs9qSG z#-&fA@KCRH(RFIyz+fG4$*nsHiZ;s@#XH_F?AvIc+5OYk0NxGQ3_trEPujAV=64@E zEp^+|<)mmX?Rd+{$)()Bh7aTO@p+cjaIR_)SN()n0p zR+TC6fkH3${0Aj~!%28fRSZ_!_ukymDG?- z*qE5cgqgx~$FX5PEfCkkJFP0mi1%)uZ>Ak~P$^>R0Cz^u0~M8Ax`c1*A!HCO_26-1 zGd2w_c^nUOV!C(%0e+bze?2`~*F&%-ZeMOt^=<8dvbo3^#isxFd6n7{HdDGB&Dq}E_JnoeFcut$ z;4rAu)jV7sh=$DAbR-5tFV1vp2SdGVGn%mJpZ?+1l@q1!p;^vxw@k6jrx?OL{^)Q% z&Mr7~6R#m1E!^U(%yzHH|3fNJp^fPE&hL)Pg@nT(s=OV+=<`fNgrG1jdzY4u^ zJQqC!I#eWY#m1si)G35qE~6$p4xIFKZK-WJ;4v=Lgh;VfF~lMDHP~JRY3MJh3l=?b zJ^Pp55~Zxh}n`CcD3m|<^EP_50!UvMH(sGKF<{N zeNnQaXPo;(9HZ81+`dwft!w0J;##}57QPNOaMoQXK%3*+QSE_QoQ9nJkj~X=kep9E$Ny=_6Tlb zaj*P=*^a$?+JE`4-k~r{`{#B2$$7)K|1`zS;ruboMWFUK)u*i}l|HUVbWAd;2aSnX zY34*njd@&o-PNeycHgF&)uz?nH4LyljtB|cof?RJX`i+s`)@pt`I)M_Yan~WH@ZN0 zoGR`b<2#8VR$4XMNwGRAGzO4Z-%H(3%ouYNdun>DG8*rTiM+bj1_}c?Y|rY%R@bRz>|yQQy&RdIbZ>$I?{3X@2)m8_nkHxdF&svBt84^I;)uL1t?=R&v8xIgRTf)nQccj*J7O znaGSA${|ecHE`^b`yj&Dk&a++ON)ds)Dp+G=ef;amKxnFiHQHOE^d&=iyyz6!3;}q z6U*JU_LXH+eRDQ3(IHj|kziImtiyR-Cv3<0m?Rl_h8_q&cH9B`D zawA$9ie^49)}z>+fAtk^KWCSmA3waA#2|S5+EGGc3PjAb6K^431$T?Wzo1*Q#1Oy0%`!U4N$Rt7y z?DlN<`3sbZokqAt1scclQOILjQ7MU8IbTT-N!n&{Oc7r;S zLwYhA{A{=Yw$Gn0g5YlS)MJ@Vyeeotcx%!0&||(4Sy`gW$z57j$jC-Z6&SHzo{CG- zG+;e!7ssWF3>}mOW#s#rypc|5Z;v|cvr%7p(wsy2K(>TJ8^K}Sgs0N#V7z>~!i^?E zNR{>FNQ^i_eY8BtpnrU>Z8fAC#v^N1FYP2mG9#-;5=WRVf4C7$Uuxo(-F0DxgKV7F zT&MMn!HTxe%nm8<`j4N=S?E${hcX8!-gdHC8JxQGxZ~RQK}r}L^05Tw1$?nG|6S@nB2WvEakXmhql|`u~KfAzUlKl3`zGZxekL&dj-dK$alEmO?sdh8Efg z*j^+HApC6p#f&3^>^wghkLLx(pOSk|1#Baaiyy+Rd$G2pI=x)~Ha;#f0Bv<|Y_pE5 z-hzS_#U`a^di!R<3eVhrvgvpggH95RHDig4$rjQP?$A_<1fVb5d*eyJCnqeZN_Rs zVzD@k1j?#aY-F=q;|{K!tP%C9ux=3-h|3f3nDrAyHYcrQ2Ak-Y5ke%u)*Mhoj3w`J z3}hLL_Y1u@cs6WC%no{FdP#(trB6ogR4d$v26F~~@ghR?0qpR_WY_$^x}u7GT3%?1yF z=MEIFAdUy^?%9DEZ`;a3%2uM=qsG+T*@bO5wpbN$ev?=x&NM`W;e$NP(7m%2w228y z!=iM|q0uVbM`p>PHog&|uo*Q|?#1#wUx^Xa!@mn{w5)oHtbea7hiB16R&(gkP~ITY%b zL{O(faVV>8MH!r_5Qj%Sn-=Bb+(f`Q$n5tTp~wlkK}*&VGZTP%U4l)^!ut)ju$O(HtTK=R$N1C!~(7-@UX3#b;JKHK+^8wUfW`g6Ao_uV9gjun=n^zl57E|4 zRoCi4^FN;ebTnM@>IGhs-dmW`RCwNL9jdX~l{T=`D0m|HwTbB=DN@ToYbWY&iOof^ zT&?Mjx-v(l_@P8)cLUc#?sK`)e&#-VTF*s+*ILX)Ng&&U_iT4W-!e)y0s|_AXqp9S zDhgGnZoQU=h;IO@X0{;24^@IRv4&vSO+`X9%yZBo$At~RErT>h?YRy6TOLDS=OSMb zlx+nS@(Y%dK~9AR(WWi>?7}F~7DkB&P7tMqB>R8SoWYYnD1@u`H6Px^lY};Z*Aj|W zDAHgzD~+};cSu&#G~$C=mDbOc{x|kup#>1Hjj(X{XeZPS8*59WG+sqEy~3WzAg`oJ zW!&Vn5=$pqFpY4*BGwJ7KrXDe4G7~q(@>igrrS%=B$cNXs2hDpz&Ycag23D-PYlpMWh)vLA5*+2$~Acf5kH?B$WDMtt7a-QLd9 z_=cB4M(8-7gbgLi6J}$In&J*17PT`zZFfJFAL@C`=7cUo!1DI5v(H?+TXVOBKkdA35N6Kj`%BmNWq|B9=OR|vVmwL&2%C;#dXXu#L2@O4ub z1*Q@ModP?!WMUGCGt+y*!kUv48+dBl_s>qfZwaZt-&_YnwWx+&`7=ZG1?b5g2}^r- z$cts*+7z^6cT&k5uH?mvPS)Vbj^-|i4u4ojsAf>doxqK^AC z-8s~BSW$}LC=ARWfi?n>T;0*cmi$Ip!z7F&Si>UFUnwytf)=d=m_)l$#9n|?-6U_8 z{l9#E=rZbRB08=%sz93Fk9HxH9W&8X`5ymyl~Gim|0u;40mp%YO({jKNW501yw~OQ z*O`x$ATRM9O=nS%uPwoQmaFWQj?a7#-|P^GT5nlm7(SkbY;lJr+vU5=4@=i3=E>QA zeeB==+VUDxqE0`!{9wy}k1Ubs$PQQ+1(W#pq(HbOIeyj5Ac}B4vOknP_T`UY(6ZBU zKhE%{&k^Taa@Y?JpVeOwO4OS5y?OJE;=hFSj54Dhd$Qi!2UFB@?)^%DSO9qg?S8u- z!wfIx_?^LV7{qIx5FXq5$m#KL5D-&~F^VK8@LL>p;V&<^aSYo%$p~dKtQ3l9z^7U( zMe^h@Wt3&!!e0*861|T{Mbs$`3@}gR2X(%DY$D&8Y|O(Nv_t&7@HeIm9RCEinzt40 zC_KEj*L~f)Sk$dN^(oRq&R+w8bX)#1a!!Ic=FBh!z(FZn0q zAs^tWlGjt_d8uOT&r5-NI#0Xqr2x16ZQXY6DQr8rw*l$9tuPb8gL$sDV=*4e@j$vHMKC% zw5vh4x^-M|`1cvTre>Gb!`Cg$(e;?Kc)J_lY{ly_l07^bL=DqaUEOXNG>PVbsG8#TY$ z|NKj79b}=4+LS}+%ZVfB9>NnRgRT(KW)=mKAXt&bDHUl7Cn{#|0G=3&6TPu7y_|}p$gys=M zZb`G^!7muLc=URs+ue8FnrPnp_}lX$W%37)gZ@)-tV3cF*`}@|>+`YhxX?BEd2l1k znx5aupgJEL->}1m!mx!>pTuvvFduK&Wt$T>x~(rz^ycR|ZD#5vp4lB`ITFa+A(DEU zb8|CCz+hzKhS@JOGP_g~jn^fXtVT3ilM0%6Sj@GXMGZWI9*^eFsqUkX=^l^#^kKnz zr7f+=ho`hwv&2|P9)>MYyR%AL0isJ(_ibh@UrN|W<{c!Q=_V;i2RKx2YA142(}^aM z&i@kspgUv}d85BD;cFoV=DxQ^ZLYs<`kcfEeHxey5iL(=6hG-_ z-#!+NJNod##S{3(Q~HgLI=t`>1g@6l7}tHno7der7-1mysu)H@1pOR>tqzXu=W@9h zW+In%U-w&l9VouZ)`T@6LR61Xsw$oFZRxaS|1kIR_&=VE-IbGMz#nq_o*ioaK#ju? zjVIC)3CDcEyTHPe?c(ZsyTZ}>b8Y|S!PM#bf||UOS~ZQPs5b^_{NB7#Q}WQ2B8}5a1oJ^;Zc4Bwm-znZm@)mv3E-mGWQfnakav3nkWifSHQ#`*-o{^=?D|RSLJ0tAD&5JKx=KteQeX znOBVmVkA;ahBpFhn2hiKYoGuYh!aTo=s&#?OVUiPqsmDqi$K(PCYh)Yma0x3L625CHyU(%QijIv4qR#QISFT%;8>=DwFV%#)JUXk=W zQ)V}k73})&FZ7-7V`3zMus-{0ZhuCx!VdK-l0Z8Ngg*9efp$-&5pKF!+lNS@Gzl1T z!!XK&c8JsqNk!q>oV0`MYKk%s|L3Kq&`@rN?q@s$M-*!6RbIUF#-Kqaw2pNSn`4Ek z9>96ws|Y68-|zA4{&0HG#2FvpWeJ~{AprQYwoxBN2QWsHLkBqp;y^GYE07L$q8pKC z@7p7~s6E!UT9L5@H!{xR+~yvKCNN%m4s?g~mCOR|CTsm{#VTHO7pZBE(3H~EWs zI*^XNoq@)u_WH;XZVzn2J&2F&V&FSixa;KUUy^yM0`jYD@ISqNWuOCnGcotByyILT4#3d-{G z7)AZ0G@72a$}XMiUOhs?PO)FOhDC~aQZOs}$X(J`=!v;Rz;{wYqDc2iqqFBM*d3eT zF0XUB(GI{9>oRSZSIiGLo?fWT6crL#$)NM&!2C9%>wB!hJvpRHU*Gyat)@T0Av<;? zA-)nCr+z|&uM!Q6J4pEGA6SySWR+CY^*){|Dz=rhRg4Ym$}bt=@3mzovom?E*GXg? zBfG^splp$Y^h`Wr18H6sELtJ`Fi6VALS40;j?{r?xk{GzFaP3e)%D=u3tQ|*q;jIS*T zyQ-Q0m$}zUD{Ep(VKru`Oe|GB;51Z2pPnVA)G6@+*N5Dqg3iy{#Hdxy#cf*}Li)N~ z`i)1^??aE&LI&jp1>lS072ZSkBa86l_T6)hG&+n^>e>q3jS!1%={I^D=I~=15gKY( z%bIoi)l05mfOArvDD-Q1VFVn^7UGkWve(wM^s7am=D4-CCWQ|tWw*6K$#1tV_2E-Z zZGs#$d#zTstr zs}x7lSrKlHTur3Af>5R7g2K1;=I?bj?R&)}^%SN1v;#Iz+1#Ubv5*4Z2lKt*Fvfze zq@m}j7)D}ImDXPBa0_kc`cX5^`*I+DJ+m9khF36T@H@w-PA@h?Z z(3l(2_m!$0@+2X} z(RB4mrBSH)i&~OY^%;g@J3pjXeWjt`p+S5fjT1oZjQjYwC;iEpmfsQU@pts|d|