First move

Here's the place for discussion related to CAM/CNC and the development of the Path module.
jkv
Posts: 9
Joined: Wed Feb 28, 2018 10:38 am

First move

Postby jkv » Fri Jul 19, 2019 12:30 pm

Hi all

Why does the first move in the picture below start at the origin and then moves to the start point of the pocket?
I would expect that the path always starts in the pocket (indepedent of the coordinate origin).
plateManufacturingScreenshot.png
plateManufacturingScreenshot.png (9.9 KiB) Viewed 254 times

The following linuxcnc post output shows the NC code of the above path.
In my opinion the G0 Z0.000 is useless and even dangerous. Can we eliminate this command in the fixture.py modul?
And it would be much safer to output G00 X122.117 Y37.883 first and G0 Z5.000 afterwards. What's your opinion?

G17 G54 G40 G49 G80 G90
G21
G54
G0 Z0.000
M6 T3
M3 S600
G0 Z5.000
X122.117 Y37.883
Z3.000
G1 Z-4.000 F40.000
...
Attachments
plateManufacturing.FCStd
(33.71 KiB) Downloaded 5 times
chrisb
Posts: 16887
Joined: Tue Mar 17, 2015 9:14 am

Re: First move

Postby chrisb » Fri Jul 19, 2019 10:21 pm

jkv wrote:
Fri Jul 19, 2019 12:30 pm
Hi all

Why does the first move in the picture below start at the origin and then moves to the start point of the pocket?
I would expect that the path always starts in the pocket (indepedent of the coordinate origin).
That's what looks like if you use "Inspect G-code", there is no initial move to Z0.
And it would be much safer to output G00 X122.117 Y37.883 first and G0 Z5.000 afterwards. What's your opinion?
I think it is safer the way it is, as Z5 moves the mill up This is how it is implemented e.g. in the good old Philips control: rapid moves move first up, and then horizontal, if it goes down it is vice versa, horizontals are moved first and afterwards the mill goes down.
jkv
Posts: 9
Joined: Wed Feb 28, 2018 10:38 am

Re: First move

Postby jkv » Sat Jul 20, 2019 6:30 pm

chrisb wrote:
Fri Jul 19, 2019 10:21 pm
jkv wrote:
Fri Jul 19, 2019 12:30 pm
Hi all

Why does the first move in the picture below start at the origin and then moves to the start point of the pocket?
I would expect that the path always starts in the pocket (indepedent of the coordinate origin).
That's what looks like if you use "Inspect G-code", there is no initial move to Z0.
I don't no whether I'm stupid or not but the first move my standard linuxcnc post outputs is a real! G0 Z0.000 command as the above NC output shows. What I'm doing wrong? Am I really the only person struggling with this problem? Thanks a lot for any help.
mlampert
Posts: 1257
Joined: Fri Sep 16, 2016 9:28 pm

Re: First move

Postby mlampert » Sat Jul 20, 2019 7:00 pm

which version are you using, could you paste your system info?
jkv
Posts: 9
Joined: Wed Feb 28, 2018 10:38 am

Re: First move

Postby jkv » Sat Jul 20, 2019 7:27 pm

mlampert wrote:
Sat Jul 20, 2019 7:00 pm
which version are you using, could you paste your system info?
My system configuration is:
OS: Ubuntu 18.04.2 LTS (KDE/plasma)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.17104 (Git)
Build type: Unknown
Branch: master
Hash: 3810381d9207a64495ca3a4af552e5129491013c
Python version: 3.6.8
Qt version: 5.9.5
Coin version: 4.0.0a
OCC version: 7.3.0
Locale: English/UnitedStates (en_US)

Remark: I understand that this command is output by the module PathFixture.py line 52 but what I don't understand why this command is output. Thanks a lot for your help.
GeneFC
Posts: 1025
Joined: Sat Mar 19, 2016 3:36 pm
Location: Punta Gorda, FL

Re: First move

Postby GeneFC » Sat Jul 20, 2019 8:20 pm

I agree this looks like a bug (or at least an unintended consequence) in PathPost.py.

It appears that an automatic G0 Z0.00 is added as a result of processing coordinate systems (G54, for example)
and/or fixtures.

The internal g-code viewer does not show this, as Chrisb noted above. That fact alone is a bug. The core FreeCAD program should never output an actual move that is not shown in the internal viewer. Tool changes and machine controls, sure, but not actual moves.

OS: Windows 7 SP 1 (6.1)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.17352 (Git)
Build type: Release
Branch: master
Hash: ec0049921b813ee78c1fd4ea304e70904a3208f7
Python version: 3.6.8
Qt version: 5.12.1
Coin version: 4.0.0a
OCC version: 7.3.0
Locale: English/United States (en_US)

Gene
IMback!
Posts: 68
Joined: Sat Jul 13, 2019 9:40 pm

Re: First move

Postby IMback! » Sat Jul 20, 2019 8:50 pm

I also have this problem and have edited the PathPost.py on lines 294 and following to remove the offending cmmand.


Code: Select all

# -*- coding: utf-8 -*-

# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2015 Dan Falck <ddfalck@gmail.com>                      *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   This program is distributed in the hope that it will be useful,       *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************
''' Post Process command that will make use of the Output File and Post Processor entries in PathJob '''

from __future__ import print_function

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
import os

from PathScripts.PathPostProcessor import PostProcessor
from PySide import QtCore, QtGui


LOG_MODULE = PathLog.thisModule()

PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)


# Qt translation handling
def translate(context, text, disambig=None):
    return QtCore.QCoreApplication.translate(context, text, disambig)


class _TempObject:
    # pylint: disable=no-init
    Path = None
    Name = "Fixture"
    InList = []
    Label = "Fixture"


class DlgSelectPostProcessor:

    def __init__(self, parent=None):
        # pylint: disable=unused-argument
        self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgSelectPostProcessor.ui")
        firstItem = None
        for post in PathPreferences.allEnabledPostProcessors():
            item = QtGui.QListWidgetItem(post)
            item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
            self.dialog.lwPostProcessor.addItem(item)
            if not firstItem:
                firstItem = item
        if firstItem:
            self.dialog.lwPostProcessor.setCurrentItem(firstItem)
        else:
            self.dialog.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)
        self.tooltips = {}
        self.dialog.lwPostProcessor.itemDoubleClicked.connect(self.dialog.accept)
        self.dialog.lwPostProcessor.setMouseTracking(True)
        self.dialog.lwPostProcessor.itemEntered.connect(self.updateTooltip)

    def updateTooltip(self, item):
        if item.text() in self.tooltips.keys():
            tooltip = self.tooltips[item.text()]
        else:
            processor = PostProcessor.load(item.text())
            self.tooltips[item.text()] = processor.tooltip
            tooltip = processor.tooltip
        self.dialog.lwPostProcessor.setToolTip(tooltip)

    def exec_(self):
        if self.dialog.exec_() == 1:
            posts = self.dialog.lwPostProcessor.selectedItems()
            return posts[0].text()
        return None


class CommandPathPost:
    # pylint: disable=no-init
    subpart = 1

    def resolveFileName(self, job):
        path = PathPreferences.defaultOutputFile()
        if job.PostProcessorOutputFile:
            path = job.PostProcessorOutputFile
        filename = path

        if '%D' in filename:
            D = FreeCAD.ActiveDocument.FileName
            if D:
                D = os.path.dirname(D)
                # in case the document is in the current working directory
                if not D:
                    D = '.'
            else:
                FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n")
                return None
            filename = filename.replace('%D', D)

        if '%d' in filename:
            d = FreeCAD.ActiveDocument.Label
            filename = filename.replace('%d', d)

        if '%j' in filename:
            j = job.Label
            filename = filename.replace('%j', j)

        if '%M' in filename:
            pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
            M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir())
            filename = filename.replace('%M', M)

        if '%s' in filename:
            if job.SplitOutput:
                filename = filename.replace('%s', '_'+str(self.subpart))
                self.subpart += 1
            else:
                filename = filename.replace('%s', '')

        policy = PathPreferences.defaultOutputPolicy()

        openDialog = policy == 'Open File Dialog'
        if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
            # Either the entire filename resolves into a directory or the parent directory doesn't exist.
            # Either way I don't know what to do - ask for help
            openDialog = True

        if os.path.isfile(filename) and not openDialog:
            if policy == 'Open File Dialog on conflict':
                openDialog = True
            elif policy == 'Append Unique ID on conflict':
                fn, ext = os.path.splitext(filename)
                nr = fn[-3:]
                n = 1
                if nr.isdigit():
                    n = int(nr)
                while os.path.isfile("%s%03d%s" % (fn, n, ext)):
                    n = n + 1
                filename = "%s%03d%s" % (fn, n, ext)

        if openDialog:
            foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename)
            if foo:
                filename = foo[0]
            else:
                filename = None

        return filename

    def resolvePostProcessor(self, job):
        if hasattr(job, "PostProcessor"):
            post = PathPreferences.defaultPostProcessor()
            if job.PostProcessor:
                post = job.PostProcessor
            if post and PostProcessor.exists(post):
                return post
        dlg = DlgSelectPostProcessor()
        return dlg.exec_()

    def GetResources(self):
        return {'Pixmap': 'Path-Post',
                'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process"),
                'Accel': "P, P",
                'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process the selected Job")}

    def IsActive(self):
        if FreeCAD.ActiveDocument is not None:
            if FreeCADGui.Selection.getCompleteSelection():
                for o in FreeCAD.ActiveDocument.Objects:
                    if o.Name[:3] == "Job":
                        return True

        return False

    def exportObjectsWith(self, objs, job, needFilename=True):
        PathLog.track()
        # check if the user has a project and has set the default post and
        # output filename
        postArgs = PathPreferences.defaultPostProcessorArgs()
        if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs:
            postArgs = job.PostProcessorArgs
        elif hasattr(job, "PostProcessor") and job.PostProcessor:
            postArgs = ''

        postname = self.resolvePostProcessor(job)
        filename = '-'
        if postname and needFilename:
            filename = self.resolveFileName(job)

        if postname and filename:
            print("post: %s(%s, %s)" % (postname, filename, postArgs))
            processor = PostProcessor.load(postname)
            gcode = processor.export(objs, filename, postArgs)
            return (False, gcode)
        else:
            return (True, '')

    def Activated(self):
        PathLog.track()
        FreeCAD.ActiveDocument.openTransaction(
            translate("Path_Post", "Post Process the Selected path(s)"))
        FreeCADGui.addModule("PathScripts.PathPost")

        # Attempt to figure out what the user wants to post-process
        # If a job is selected, post that.
        # If there's only one job in a document, post it.
        # If a user has selected a subobject of a job, post the job.
        # If multiple jobs and can't guess, ask them.

        selected = FreeCADGui.Selection.getSelectionEx()
        if len(selected) > 1:
            FreeCAD.Console.PrintError("Please select a single job or other path object\n")
            return
        elif len(selected) == 1:
            sel = selected[0].Object
            if sel.Name[:3] == "Job":
                job = sel
            elif hasattr(sel, "Path"):
                try:
                    job = PathUtils.findParentJob(sel)
                except Exception: # pylint: disable=broad-except
                    job = None
            else:
                job = None
        if job is None:
            targetlist = []
            for o in FreeCAD.ActiveDocument.Objects:
                if hasattr(o, "Proxy"):
                    if isinstance(o.Proxy, PathJob.ObjectJob):
                        targetlist.append(o.Label)
            PathLog.debug("Possible post objects: {}".format(targetlist))
            if len(targetlist) > 1:
                form = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobChooser.ui")
                form.cboProject.addItems(targetlist)
                r = form.exec_()
                if r is False:
                    return
                else:
                    jobname = form.cboProject.currentText()
            else:
                jobname = targetlist[0]
            job = FreeCAD.ActiveDocument.getObject(jobname)

        PathLog.debug("about to postprocess job: {}".format(job.Name))

        # Build up an ordered list of operations and tool changes.
        # Then post-the ordered list
        if hasattr(job, "Fixtures"):
            wcslist = job.Fixtures
        else:
            wcslist = ['G54']

        if hasattr(job, "OrderOutputBy"):
            orderby = job.OrderOutputBy
        else:
            orderby = "Operation"

        if hasattr(job, "SplitOutput"):
            split = job.SplitOutput
        else:
            split = False

        postlist = []

        if orderby == 'Fixture':
            PathLog.debug("Ordering by Fixture")
            # Order by fixture means all operations and tool changes will be completed in one
            # fixture before moving to the next.

            currTool = None
            for f in wcslist:
                # create an object to serve as the fixture path
                fobj = _TempObject()
                c1 = Path.Command(f)
                #c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax))
                fobj.Path = Path.Path([c1])
                fobj.InList.append(job)
                sublist = [fobj]

                # Now generate the gcode
                for obj in job.Operations.Group:
                    tc = PathUtil.toolControllerForOp(obj)
                    if tc is not None:
                        if tc.ToolNumber != currTool:
                            sublist.append(tc)
                            PathLog.debug("Appending TC: {}".format(tc.Name))
                            currTool = tc.ToolNumber
                    sublist.append(obj)
                postlist.append(sublist)

        elif orderby == 'Tool':
            PathLog.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.

            currTool = None
            fixturelist = []
            for f in wcslist:
                # create an object to serve as the fixture path
                fobj = _TempObject()
                c1 = Path.Command(f)
                #c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax))
                fobj.Path = Path.Path([c1])
                fobj.InList.append(job)
                fixturelist.append(fobj)

            # Now generate the gcode
            curlist = []  # list of ops for tool, will repeat for each fixture
            sublist = []  # list of ops for output splitting

            for idx, obj in enumerate(job.Operations.Group):
                tc = PathUtil.toolControllerForOp(obj)
                if tc is None or tc.ToolNumber == currTool:
                    curlist.append(obj)
                elif tc.ToolNumber != currTool and currTool is None:  # first TC
                    sublist.append(tc)
                    curlist.append(obj)
                    currTool = tc.ToolNumber
                elif tc.ToolNumber != currTool and currTool is not None:  # TC
                    for fixture in fixturelist:
                        sublist.append(fixture)
                        sublist.extend(curlist)
                    postlist.append(sublist)
                    sublist = [tc]
                    curlist = [obj]
                    currTool = tc.ToolNumber

                if idx == len(job.Operations.Group) - 1:  # Last operation.
                    for fixture in fixturelist:
                        sublist.append(fixture)
                        sublist.extend(curlist)
                    postlist.append(sublist)

        elif orderby == 'Operation':
            PathLog.debug("Ordering by Operation")
            # Order by operation means ops are done in each fixture in
            # sequence.
            currTool = None
            fixturelist = []
            for f in wcslist:
                # create an object to serve as the fixture path
                fobj = _TempObject()
                c1 = Path.Command(f)
                #c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax))
                fobj.Path = Path.Path([c1])
                fobj.InList.append(job)
                fixturelist.append(fobj)

            # Now generate the gcode
            for obj in job.Operations.Group:
                sublist = []
                PathLog.debug("obj: {}".format(obj.Name))
                for fixture in fixturelist:
                    sublist.append(fixture)
                    tc = PathUtil.toolControllerForOp(obj)
                    if tc is not None:
                        if tc.ToolNumber != currTool:
                            sublist.append(tc)
                            currTool = tc.ToolNumber
                    sublist.append(obj)
                postlist.append(sublist)

        fail = True
        rc = '' # pylint: disable=unused-variable
        if split:
            for slist in postlist:
                (fail, rc) = self.exportObjectsWith(slist, job)
        else:
            finalpostlist = [item for slist in postlist for item in slist]
            (fail, rc) = self.exportObjectsWith(finalpostlist, job)

        self.subpart = 1

        if fail:
            FreeCAD.ActiveDocument.abortTransaction()
        else:
            FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()


if FreeCAD.GuiUp:
    # register the FreeCAD command
    FreeCADGui.addCommand('Path_Post', CommandPathPost())

FreeCAD.Console.PrintLog("Loading PathPost... done\n")