Path Tooltable scriptability?

Here's the place for discussion related to CAM/CNC and the development of the Path module.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
onekk
Veteran
Posts: 6208
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Path Tooltable scriptability?

Post by onekk »

This is not very difficult, as you could retrieve a Job using his "name".

Providing you have in DOC you document maybe using:

Code: Select all

DOC = FreeCAD.getDocument("Your_Document_Name")

Code: Select all

for obj in DOC.Objects:
    print(obj.Name)
will list youe documentObjects

you will see that every job is starting with Job_, so maybe using:

Code: Select all

jobs = [x for x in DOC.Objects if x.Name.startswith("Job_")]
And then you would not that a Job is a <Path::FeaturePython object>, so maybe you could even use TypeId to sort DocumentObjects.

Another thing, is that scanning DOC.Objects you will se that there are even ToolControllers and Tools Listed, this is a portion of one of my "automatically generated boxes" DOc.Objects Names that have many jobs and operation on them:

Code: Select all

Job_B0_Bt
Operations001
SetupSheet001
Model001
Clone001
Tools001
TC__Default_Tool001
ToolBit001
Stock001
Job_B0_Ri
Operations002
SetupSheet002
Model002
Clone002
Tools002
TC__Default_Tool002
ToolBit002
Stock002
Job_B0_Re
Operations003
SetupSheet003
Model003
Clone003
Tools003
TC__Default_Tool003
ToolBit003
Stock003
Job_B0_Le
Operations004
SetupSheet004
Model004
Clone004
Tools004
TC__Default_Tool004
ToolBit004
Stock004

Hope it helps.

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
imm
Posts: 267
Joined: Wed Nov 03, 2021 1:00 pm

Re: Path Tooltable scriptability?

Post by imm »

Thanks for the hints onekk.

I have another simple question....

In most of the code examples I see the 'parameter' dictionary is populated with the shape 'attributes'
...but I have not seen any use of the 'attribute' dictionary....when is the 'attribute' dictionary used?

Thanks in advance.
User avatar
onekk
Veteran
Posts: 6208
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Path Tooltable scriptability?

Post by onekk »

imm wrote: Thu Dec 09, 2021 12:28 pm Thanks for the hints onekk.

I have another simple question....

In most of the code examples I see the 'parameter' dictionary is populated with the shape 'attributes'
...but I have not seen any use of the 'attribute' dictionary....when is the 'attribute' dictionary used?

Thanks in advance.
From what I could see from:

https://github.com/FreeCAD/FreeCAD/tree ... /Tools/Bit

attribute is always empty.

As I'm not a developer, I could only guess that is for "future development" or it has some relation to something "json" format.

Maybe poking @sliptonic would generated a better explanation:

sliptonic wrote: Tue Dec 07, 2021 5:00 pm
@sliptonic I apologize for the hassle but I know other way to obtain your attention! :D

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
sliptonic
Veteran
Posts: 3459
Joined: Tue Oct 25, 2011 10:46 pm
Location: Columbia, Missouri
Contact:

Re: Path Tooltable scriptability?

Post by sliptonic »

onekk wrote: Thu Dec 09, 2021 2:59 pm @sliptonic I apologize for the hassle but I know other way to obtain your attention! :D
Sorry. I'm spending less and less time on the forum. I find the discussions here are less focused on development and don't really result in progress. That's not a criticism of individuals so much as a symptom of our growth and changes in the community.

The intent of the attributes structure was to eventually hold any arbitrary information that the user wants to attach to the toolbit. Examples might include:
  • a vendor part number
  • web url for the tool.
  • wear data
  • toolroom storage location
  • etc.
User avatar
onekk
Veteran
Posts: 6208
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Path Tooltable scriptability?

Post by onekk »

sliptonic wrote: Fri Dec 10, 2021 5:34 pm Sorry. I'm spending less and less time on the forum. I find the discussions here are less focused on development and don't really result in progress. That's not a criticism of individuals so much as a symptom of our growth and changes in the community.
OK not a problem for me, I also think that things are "evolving" on this forum (and maybe no on the "better" side).

Thanks for the reply and explications, infos are always a good thing.

Best Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
imm
Posts: 267
Joined: Wed Nov 03, 2021 1:00 pm

Re: Path Tooltable scriptability?

Post by imm »

Thanks for the update onekk, and for getting sliptonics input......I am new to the forum so not as familiar with the controls.

I don't have direct experience with cnc but I do have peripheral knowledge and I know 'tool management' is a big portion of the work for any serious enterprise beyond hobby. .

The concept of a central tool library is useful to create the standard set of tools and their parameters available for any specific job....
but usually once a job has been defined any tools associated with it....and the speeds and feeds for that specific process you want to remain static...so if you return to reproduce that Job at a later date (6 months .... 1 year later) that NONE of the operational parameters or tools have been modified by edits occurring in other processes.

That means that most of the 'tool library' functionality should be considered as a template system.
I am looking for a way to 'consolidate' and 'flatten' all that information into a single file that is under user control that allows for the
user to backup/restore/clone a collection of tools for both existing and new projects/jobs.

So far I have been able to create a simple csv file structure that consolidates the Toolbit info and Library data that can easily be parsed to
recreate all the key components from a single file. csv files can be more easily maintained by end-users.
myToolLib.csv
(1.86 KiB) Downloaded 26 times
The below code creates a list of dicts from the file above
and the first Tool (NR=0) is a dummy tool that actually contains information regarding this Library....default material, paths, and version info.

Code: Select all

import csv
import os.path 
import json
# ....
# -- required for gitem() function
boolTokens = ['True','False','T','F']
# -- test for multiple data types and return the most likely.  
# -- Handles most simple datatypes....integer,float,boolean,string.
def gitem(item):
    item.strip()
    try:
        item=int(item)
        return item
    except ValueError:
        pass
    try:
        item=float(item)
        return item
    except ValueError:
        pass
    for x in boolTokens:
        if any( x == item for x in boolTokens):
            if any (y == item for y in boolTokens[::2]):
                return True
            else:
                return False
    return item
    
# -- takes a header list and row list converts it into a dict. 
# -- Uses gitem() function to handle most simple datatypes....integer,float,boolean,string.
def rowConvert(h,a):
    b=[]
    for x in a:
        b.append(gitem(x))
    k = iter(h)
    it = iter(b)
    res_dct = dict(zip(k, it))
    return res_dct

def load_Params(filename= '../Mod/FeedsAndSpeeds/params.csv'):
    if not os.path.isfile(filename):
        print ('Error: [ ' + filename + ' ] does not exist!')
        return None
        
    mlist=[]
    with open(filename,'r') as csvin:
        alist=list(csv.reader(csvin))
        firstLine = True
        for a in alist:
            if firstLine:
                if len(a) == 0: continue
                if len(a) == 1: continue
                else:
                    h = a 
                    firstLine = False
            else:
                print(rowConvert(h,a))
                mlist.append(rowConvert(h,a))
    return mlist

# -- requires import csv and import os.path 

# -- ToolLib load
curDir=os.path.split(App.ActiveDocument.FileName)[0]
alist=load_Params( curDir + '/myToolLib.csv')

for row in alist:
    print (row)
    print (' row items ')
    for k,v in row.items():
        print ( k + ' : ' + str(v) )
    parm_dict=json.loads(row['parameter'])
    print (' -- parameter items ' )
    for k,v in parm_dict.items():
        print ( ' -- ' + k + ' : ' + v )


Below is the sample output...

Code: Select all

{'nr': 0, 'version': 1, 'name': 'My ToolLibrary', 'shape': '', 'parameter': '{"Shape_Path": "My/Shape/Path","Material":"Aluminum","Notes": "--"}', 'attribute': '{}'}
 row items 
nr : 0
version : 1
name : My ToolLibrary
shape : 
parameter : {"Shape_Path": "My/Shape/Path","Material":"Aluminum","Notes": "--"}
attribute : {}
 --  parameter items 
 -- Shape_Path : My/Shape/Path
 -- Material : Aluminum
 -- Notes : --
{'nr': 1, 'version': 2, 'name': '5mm Drill', 'shape': 'drill.fcstd', 'parameter': '{"Diameter": "5.0000 mm","Length": "50.0000 mm","TipAngle": "119.0000 \\u00b0"}', 'attribute': '{}'}
 row items 
nr : 1
version : 2
name : 5mm Drill
shape : drill.fcstd
parameter : {"Diameter": "5.0000 mm","Length": "50.0000 mm","TipAngle": "119.0000 \u00b0"}
attribute : {}
 --  parameter items 
 -- Diameter : 5.0000 mm
 -- Length : 50.0000 mm
 -- TipAngle : 119.0000 °
{'nr': 2, 'version': 2, 'name': '5mm Endmill', 'shape': 'endmill.fcstd', 'parameter': '{"CuttingEdgeHeight": "30.0000 mm","Diameter": "5.0000 mm","Length": "50.0000 mm","ShankDiameter": "3.0000 mm"}', 'attribute': '{}'}
 row items 
nr : 2
version : 2
name : 5mm Endmill
shape : endmill.fcstd
parameter : {"CuttingEdgeHeight": "30.0000 mm","Diameter": "5.0000 mm","Length": "50.0000 mm","ShankDiameter": "3.0000 mm"}
attribute : {}
 --  parameter items 
 -- CuttingEdgeHeight : 30.0000 mm
 -- Diameter : 5.0000 mm
 -- Length : 50.0000 mm
 -- ShankDiameter : 3.0000 mm
{'nr': 3, 'version': 2, 'name': '5mm-thread-cutter', 'shape': 'thread-mill.fcstd', 'parameter': '{ "Crest": "0.10 mm","Diameter": "5.00 mm", "Length": "50.00 mm", "NeckDiameter": "3.00 mm", "NeckLength": "20.00 mm", "ShankDiameter": "5.00 mm" }', 'attribute': '{}'}
 row items 
nr : 3
version : 2
name : 5mm-thread-cutter
shape : thread-mill.fcstd
parameter : { "Crest": "0.10 mm","Diameter": "5.00 mm", "Length": "50.00 mm", "NeckDiameter": "3.00 mm", "NeckLength": "20.00 mm", "ShankDiameter": "5.00 mm" }
attribute : {}
 --  parameter items 
 -- Crest : 0.10 mm
 -- Diameter : 5.00 mm
 -- Length : 50.00 mm
 -- NeckDiameter : 3.00 mm
 -- NeckLength : 20.00 mm
 -- ShankDiameter : 5.00 mm
{'nr': 4, 'version': 2, 'name': '6mm Ball End', 'shape': 'ballend.fcstd', 'parameter': '{"CuttingEdgeHeight": "40.0000 mm","Diameter": "6.0000 mm","Length": "50.0000 mm","ShankDiameter": "3.0000 mm"}', 'attribute': '{}'}
 row items 
nr : 4
version : 2
name : 6mm Ball End
shape : ballend.fcstd
parameter : {"CuttingEdgeHeight": "40.0000 mm","Diameter": "6.0000 mm","Length": "50.0000 mm","ShankDiameter": "3.0000 mm"}
attribute : {}
 --  parameter items 
 -- CuttingEdgeHeight : 40.0000 mm
 -- Diameter : 6.0000 mm
 -- Length : 50.0000 mm
 -- ShankDiameter : 3.0000 mm
{'nr': 5, 'version': 2, 'name': '6 mm Bull Nose', 'shape': 'bullnose.fcstd', 'parameter': '{"CuttingEdgeHeight": "40.0000 mm","Diameter": "6.0000 mm","FlatRadius": "1.5000 mm","Length": "50.0000 mm","ShankDiameter": "3.0000 mm"}', 'attribute': '{}'}
 row items 
nr : 5
version : 2
name : 6 mm Bull Nose
shape : bullnose.fcstd
parameter : {"CuttingEdgeHeight": "40.0000 mm","Diameter": "6.0000 mm","FlatRadius": "1.5000 mm","Length": "50.0000 mm","ShankDiameter": "3.0000 mm"}
attribute : {}
 --  parameter items 
 -- CuttingEdgeHeight : 40.0000 mm
 -- Diameter : 6.0000 mm
 -- FlatRadius : 1.5000 mm
 -- Length : 50.0000 mm
 -- ShankDiameter : 3.0000 mm
{'nr': 6, 'version': 2, 'name': '45 Deg. Chamfer', 'shape': 'chamfer.fcstd', 'parameter': '{"CuttingEdgeAngle": "45.0000 \\u00b0","CuttingEdgeHeight": "6.3500 mm","Diameter": "12.3323 mm","FlatRadius": "5.0000 mm","Length": "30.0000 mm","ShankDiameter": "6.3500 mm"}', 'attribute': '{}'}
 row items 
nr : 6
version : 2
name : 45 Deg. Chamfer
shape : chamfer.fcstd
parameter : {"CuttingEdgeAngle": "45.0000 \u00b0","CuttingEdgeHeight": "6.3500 mm","Diameter": "12.3323 mm","FlatRadius": "5.0000 mm","Length": "30.0000 mm","ShankDiameter": "6.3500 mm"}
attribute : {}
 --  parameter items 
 -- CuttingEdgeAngle : 45.0000 °
 -- CuttingEdgeHeight : 6.3500 mm
 -- Diameter : 12.3323 mm
 -- FlatRadius : 5.0000 mm
 -- Length : 30.0000 mm
 -- ShankDiameter : 6.3500 mm
{'nr': 7, 'version': 2, 'name': '60 Deg. V-Bit', 'shape': 'v-bit.fcstd', 'parameter': '{"CuttingEdgeAngle": "60.0000 \\u00b0","Diameter": "10.0000 mm","CuttingEdgeHeight": "1.0000 mm","TipDiameter": "1.0000 mm","Length": "20.0000 mm","ShankDiameter": "5.0000 mm"}', 'attribute': '{}'}
 row items 
nr : 7
version : 2
name : 60 Deg. V-Bit
shape : v-bit.fcstd
parameter : {"CuttingEdgeAngle": "60.0000 \u00b0","Diameter": "10.0000 mm","CuttingEdgeHeight": "1.0000 mm","TipDiameter": "1.0000 mm","Length": "20.0000 mm","ShankDiameter": "5.0000 mm"}
attribute : {}
 --  parameter items 
 -- CuttingEdgeAngle : 60.0000 °
 -- Diameter : 10.0000 mm
 -- CuttingEdgeHeight : 1.0000 mm
 -- TipDiameter : 1.0000 mm
 -- Length : 20.0000 mm
 -- ShankDiameter : 5.0000 mm
{'nr': 8, 'version': 2, 'name': 'Probe', 'shape': 'probe.fcstd', 'parameter': '{"Diameter": "6.0000 mm","Length": "50.0000 mm","ShaftDiameter": "4.0000 mm"}', 'attribute': '{}'}
 row items 
nr : 8
version : 2
name : Probe
shape : probe.fcstd
parameter : {"Diameter": "6.0000 mm","Length": "50.0000 mm","ShaftDiameter": "4.0000 mm"}
attribute : {}
 --  parameter items 
 -- Diameter : 6.0000 mm
 -- Length : 50.0000 mm
 -- ShaftDiameter : 4.0000 mm
{'nr': 9, 'version': 2, 'name': 'Slitting Saw', 'shape': 'slittingsaw.fcstd', 'parameter': '{"BladeThickness": "3.0000 mm","CapHeight": "3.0000 mm","CapDiameter": "8.0000 mm","Diameter": "76.2000 mm","Length": "50.0000 mm","ShankDiameter": "19.0500 mm"}', 'attribute': '{}'}
 row items 
nr : 9
version : 2
name : Slitting Saw
shape : slittingsaw.fcstd
parameter : {"BladeThickness": "3.0000 mm","CapHeight": "3.0000 mm","CapDiameter": "8.0000 mm","Diameter": "76.2000 mm","Length": "50.0000 mm","ShankDiameter": "19.0500 mm"}
attribute : {}
 --  parameter items 
 -- BladeThickness : 3.0000 mm
 -- CapHeight : 3.0000 mm
 -- CapDiameter : 8.0000 mm
 -- Diameter : 76.2000 mm
 -- Length : 50.0000 mm
 -- ShankDiameter : 19.0500 mm
>>> 
Last edited by imm on Wed Dec 15, 2021 1:17 pm, edited 5 times in total.
User avatar
onekk
Veteran
Posts: 6208
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Path Tooltable scriptability?

Post by onekk »

From what I know, actual tool library is aiming to be a similar thing of what you describe.

As when you create a toolcontroller for a Path Operation you have to choose a tool from a toollibrary, "not necessarily default tool library", and apply to the operation, but feed and speeds are to be applied "per operation" or "per job" after having create the toolcontroller and put toolbit in it.

But you could use the attribute property to store user information, so it will be not difficult to create some filed that could be retrieved by a Macro to set correctly this data in the Path Operation or Job.

CSV files are in my opinion no the best to transfer "complex data" as they rely on the concept of "filed separator" that if it is a "comma" as in the name could easily be a character that is in a "filed" itself, vanishing his use as separator.

In fact in many practical application the filed separator character is modified and has to be supplied when importing the file.

Plus there is no concept of "header " so it is not easy to put some infos about what is contained in the file in the file itself.

Said so as it is used as a common "transfer format" by many programs, provided with the caveat above, it will be not difficult to put together the appropriate import mechanism.

I have to study better the mechanics of Python Json management, to make something that would be robust enough to be a "import mechanism" for ToolLibraries.

I'm pretty sure that someone is working on something similar, it could be a goo thing if some type of collaboration could be established to not duplicate efforts and make a decent tool, maybe asking some advice to Path developers, to avoid some "wrong assumptions" and have a "Long Term" (for what Long could mean in this state of development) stability and avoiding to waste time working on some "evolving features".

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
imm
Posts: 267
Joined: Wed Nov 03, 2021 1:00 pm

Re: Path Tooltable scriptability?

Post by imm »

Thanks onekk.

Most modern csv parsers will recognize 'embedded' separators as part of a string....ie if surround entire string with quotes ("").

If you check out the output above you will see that the embedded 'dictionary' contains many comma's....and also unicode symbols that are
translated correctly when I use json.loads() function.

The 'header' component is an option only....not required.....it was a hold over from a generic routine I was building to
store initialization parameters. Again built on the concept of separating 'data' from 'code'.

I updated the script loop at the end to show how I parse out the row dictionary and the parameter dictionary within it.
And I have added sample output so that people can see the result without running it themselves.

json complains if there is an embedded dictionary within a dictionary....which is why I have 2 separate processes.

I am trying to create a structure that won't scare a user away from the idea of maintaining their own data.

The goal is not to 'replace' the Toolbit interface. I would rather provide a 'helper' mechanism.
Let people build their Library...set material..set speeds and feeds..all through the existing interfaces....

Then set where they want their localized 'cache' to exist and hit a button and have it export into this csv format.

Then have another button on the path interface that allows you to import and initialize your tools and speeds and feeds from
that same file.

The same concept could be used to create a 'backup' of all the previous settings for a job/process.
If you find that suddenly all your settings are crap you can in one button click reload all the previous settings.

....or to take it further you could save multiple versions of the csv to create a revision system.
Last edited by imm on Tue Dec 14, 2021 8:30 pm, edited 1 time in total.
User avatar
onekk
Veteran
Posts: 6208
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Path Tooltable scriptability?

Post by onekk »

Not related, but some advancement:

Code: Select all

"""test_path.py

   This code was written as an sample code 
   for "FreeCAD Scripting Guide" 
     
   Author: Carlo Dormeletti
   Copyright: 2021
   Licence: CC BY-NC-ND 4.0 IT 
"""

import os
import os.path
import glob
import json

import FreeCAD
from FreeCAD import Placement, Rotation, Vector
import Part
import Path
import Draft

from PathScripts import PathJob
from PathScripts import PathJobGui

from PathScripts import PathProfile

from PathScripts import PathVcarve

from PathScripts import PathGeom
from PathScripts import PathPostProcessor
from PathScripts import PathUtil

import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathToolBit as PathToolBit
import PathScripts.PathToolController as PathToolController

import numpy as np

DOC_NAME = "Pippo"
gcodePath = os.path.expanduser('~/engrave.ngc')

DOC = FreeCAD.activeDocument()

def clear_doc():
    """
    Clear the active document deleting all the objects
    """
    for obj in DOC.Objects:
        DOC.removeObject(obj.Name)

if DOC is None:
    FreeCAD.newDocument(DOC_NAME)
    FreeCAD.setActiveDocument(DOC_NAME)
    DOC = FreeCAD.activeDocument()
    VIEW = FreeCAD.Gui.ActiveDocument.ActiveView
else:
    clear_doc()
    VIEW = FreeCAD.Gui.ActiveDocument.ActiveView

def setview():
    """Rearrange View"""
    DOC.recompute()
    VIEW.viewAxometric()
    VIEW.setAxisCross(True)
    VIEW.fitAll()

EPS = 0.10
EPS_C = EPS * -0.5

VZOR = Vector(0,0,0)
ROT0 = Rotation(0, 0, 0)


def checkWorkingDir():
    # Check existence of a working directory.
    # and check that working directory is writable
    PathLog.track()

    workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary())
    defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath())

    msg = 'workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)
    PathLog.debug(msg)

    print(msg)
    dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK))

    if dirOK():
        return True
    else:
        return False

class ToolLibrary(object):
    '''Helper class to access and manage ToolLibraries.'''

    def __init__(self, path=None):
        PathLog.track()
        self.path = ""
        # self.currentLib = ""

    def __libraryLoad(self, path, datamodel):
        PathLog.track(path)
        PathPreferences.setLastFileToolLibrary(path)
        # self.currenLib = path

        with open(path) as fp:
            library = json.load(fp)

        for toolBit in library['tools']:
            try:
                nr = toolBit['nr']
                bit = PathToolBit.findToolBit(toolBit['path'], path)
                if bit:
                    PathLog.track(bit)
                    tool = PathToolBit.Declaration(bit)
                    datamodel.append((nr, tool, bit))
                else:
                    PathLog.error("Could not find tool #{}: {}".format(nr, toolBit['path']))
            except Exception as e:
                msg = "Error loading tool: {} : {}".format(toolBit['path'], e)
                FreeCAD.Console.PrintError(msg)

    def _toolAdd(self, nr, tool, path):

        strShape = os.path.splitext(os.path.basename(tool['shape']))[0]
        # strDiam = tool['parameter']['Diameter']
        tooltip = "{}".format(strShape)

        return [toolNr, toolName, toolShape]

    def newTool(self, datamodel, path):
        """Add a toolbit item to a library.
        """
        PathLog.track()

        try:
            nr = 0

            tool = PathToolBit.Declaration(path)
        except Exception as e:
            PathLog.error(e)

    def findLibraries(self):
        """Find all the fctl files in a location.

        Return a list of filenames with path
        """
        libfiles = []
        PathLog.track()
        path = PathPreferences.lastPathToolLibrary()

        if os.path.isdir(path):
            libFiles = [f for f in glob.glob(path + os.path.sep + '*.fctl')]
            libFiles.sort()
            for libFile in libFiles:
                libfiles.append(libFile)

        PathLog.debug('model rows: {}'.format(len(libfiles)))
        return libfiles

    def libraryOpen(self, lib=""):
        '''
        opens the tools in library
        '''
        model = []
        PathLog.track(lib)

        if lib == "":
            lib = PathPreferences.lastFileToolLibrary()

        if lib == "" or lib is None:
            return model

        print("lib: {}".format(lib))

        if os.path.isfile(lib):  # An individual library is wanted
            self.__libraryLoad(lib, model)

        PathLog.debug('model rows: {}'.format(len(model)))
        return model

    def libraryDump(self, lib=""):
        tools = self.libraryOpen(lib)

        for idx, tool in enumerate(tools):
            msg = ["Index: {}".format(idx)]
            msg.append("ToolNumber: {}".format(tool[0]))

            for mk, mv in tool[1].items():
                if mk not in ('parameter', 'attribute'):
                    msg.append("{}: {}".format(mk, mv))
                else:
                    msg.append("{}:".format(mk))

                    for pk, pv in mv.items():
                     msg.append("-- {}: {}".format(pk, pv))

            out_msg = "\n".join(msg)
            out_msg += "\n-----------------------\n\n"
            FreeCAD.Console.PrintMessage(out_msg)


box0 = DOC.addObject('Part::Box', 'Box')
box0.Width = 100
box0.Length = 100
box0.Height = 10

box1 = DOC.addObject('Part::Box', 'Box')
box1.Width = 50
box1.Length = 50
box1.Height = 20
box1.Placement = FreeCAD.Placement(FreeCAD.Vector(25,25,-5), FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0))

DOC.recompute()

cut = DOC.addObject('Part::Cut', 'Cut')
cut.Base = box0
cut.Tool = box1


# some path to follow
pol_r = (
    (000.00, 000.00, 0.0),
    (038.60, 000.00, 0.0),
    (057.55, 013.00, 0.0),
    (076.50, 000.00, 0.0),
    (115.10, 000.00, 0.0),
    (115.10, 029.50, 0.0),
    (088.90, 029.50, 0.0),
    (070.10, 045.70, 0.0),
    (044.90, 045.70, 0.0),
    (026.20, 029.50, 0.0),
    (000.00, 029.50, 0.0),
    (000.00, 000.00, 0.0),
)

wire = Part.makePolygon(pol_r)
spess = 1.0
wire1 = wire.makeOffset2D(spess)
wire2 = wire.makeOffset2D(spess * -1)

eng_face = Part.Face([wire1, wire2])

circuit = DOC.addObject('Part::Feature', 'circuit')
circuit.Shape = eng_face
circuit.Placement = Placement(Vector(150,0,0), ROT0)

# get the upper face of the circuit shape
for i in range(len(circuit.Shape.Faces)):
    c_face = "Face%d" % (i+1)
    cuf = circuit.Shape.getElement(c_face)
    if cuf.Surface.Axis == Vector(0,0,1) and cuf.Orientation == 'Forward':
        break

DOC.recompute()

# get the upper face?
for i in range(11):
    face = "Face%d" % (i+1)
    f = cut.Shape.getElement(face)
    if f.Surface.Axis == FreeCAD.Vector(0,0,1) and f.Orientation == 'Forward':
        break


workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary())

checkWorkingDir()

libraries = []
tools = []

library = ToolLibrary()

libraries = library.findLibraries()

tools = library.libraryOpen(libraries[0])

for tool in tools:
    #print(tool[1]['name'])
    if tool[1]['name'] == "60 Deg. V-Bit":
        eng_tool = tool


job = PathJob.Create('Job', [cut], None)
job.ViewObject.Proxy = PathJobGui.ViewProvider(job.ViewObject)

job.PostProcessorOutputFile = gcodePath
job.PostProcessor = 'grbl'
job.PostProcessorArgs = '--no-show-editor'

# set some common data
job.SetupSheet.Label = "Custom SS"
job.SetupSheet.HorizRapid = '10 mm/s'
job.SetupSheet.VertRapid = '1 mm/s'

job.recompute()

# Assume we are using default tool controller 0
tc0 = job.Tools.Group[0]
tc0.HorizFeed = '25.0000 mm/s'
tc0.VertFeed = '16.0000 mm/s'
# different way if there is Expressions in fields
tc0.setExpression('HorizRapid', None)
tc0.HorizRapid = "15 mm/s"
tc0.setExpression('VertRapid', None)
tc0.VertRapid = "2 mm/s"
tc0.recompute()

# Modify default Tool data
tct1 = tc0.Tool
tct1.Label = "Tool 1 Endmill"
#tct1.CuttingEdgeHeight =
tct1.Diameter = "3.25 mm"
tct1.Length =  "45.00 mm"
tct1.ShankDiameter = "3.20 mm"

tct1.recompute()

# name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider=True, assignTool=True):
tc1 = PathToolController.Create(name='TC: TC1')

tc_group = job.Tools.Group
tc_group.append(tc1)

job.Tools.Group = tc_group

tct2 = tc1.Tool
tct2.BitShape = eng_tool[1]['shape']
tct2.CuttingEdgeAngle = eng_tool[1]['parameter']['CuttingEdgeAngle']
tct2.CuttingEdgeHeight = eng_tool[1]['parameter']['CuttingEdgeHeight']
tct2.Diameter = eng_tool[1]['parameter']['Diameter']

tct2.recompute()

tc1.recompute()

job.recompute()

DOC.recompute()

profile = PathProfile.Create('Profile')

profile.Base = (cut, face)
profile.setExpression('StepDown', None)
profile.StepDown = 3.00
profile.setExpression('StartDepth', None)
profile.StartDepth = 10
profile.setExpression('FinalDepth', None)
profile.FinalDepth = 0
profile.processHoles = True
profile.processPerimeter = True

profile.recompute()

DOC.recompute()

vcarve = PathVcarve.Create('VCarve')

vcarve.Base = (circuit, c_face)

vcarve.recompute()

DOC.recompute()


postlist = []
currTool = None

for obj in job.Operations.Group:
    print( obj.Name)
    tc = PathUtil.toolControllerForOp(obj)
    if tc is not None:
        if tc.ToolNumber != currTool:
            postlist.append(tc)
            currTool = tc.ToolNumber
    postlist.append(obj)

post = PathPostProcessor.PostProcessor.load(job.PostProcessor)
gcode = post.export(postlist, gcodePath , job.PostProcessorArgs)

#ops = DOC.getObject("Operations")
#ops.Visibility = True

DOC.recompute()

print("--- done ---")

For now I consider this and advancement, some background:
  1. I have copied in my FreeCAD user dir the skeleton in /Mod/Tools
  2. the code to retrieve the library is the version that you will find here, but there where a system to retrieve using the stock PathScript code, but I haven't find a proper way to use it
  3. VCarve Scriptability is decent, only when adding a new ToolController the GUI is raising a window that ask for a toolcontroller for the "second operation" in this case vcarve
  4. There were some errors aroound and strangely V-Bit have some "artifacts" like orientation and so on.

That said the operation is created, and seems to be correct.

RESULT:

If some developers will gave some hints how to make things more smooth without having the GUI asking for the TC as it was done for Job, it will be a good thing.

Obviously, I could have made some big mistakes, so maybe I'm totally wrong.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
sliptonic
Veteran
Posts: 3459
Joined: Tue Oct 25, 2011 10:46 pm
Location: Columbia, Missouri
Contact:

Re: Path Tooltable scriptability?

Post by sliptonic »

imm wrote: Tue Dec 14, 2021 1:20 pm I don't have direct experience with cnc but I do have peripheral knowledge and I know 'tool management' is a big portion of the work for any serious enterprise beyond hobby. .
Exactly right!
The concept of a central tool library is useful to create the standard set of tools and their parameters available for any specific job....
but usually once a job has been defined any tools associated with it....and the speeds and feeds for that specific process you want to remain static...so if you return to reproduce that Job at a later date (6 months .... 1 year later) that NONE of the operational parameters or tools have been modified by edits occurring in other processes.

That means that most of the 'tool library' functionality should be considered as a template system.
Yes. This is quite central to the Path model. Nothing that is set in the job will override the configuration in the library and vice versa. The Tool an Tool Controller in the job are independent after creation.

The ONLY thing that is not stored in the job file and could feasibly change is the tool shape file. The individual parameters that drive the shape like diameter, cutting edge height are stored in the document but the shape file is not. This is an intentional choice and should not cause a problem with standard shapes.
I am looking for a way to 'consolidate' and 'flatten' all that information into a single file that is under user control that allows for the
user to backup/restore/clone a collection of tools for both existing and new projects/jobs.
Our expectation is that you do this by configuring a job with the tools the way you want it. Then you export the job template. The template is used when you create a new job and all the corresponding tools and tool controllers will be pre-populated.
Post Reply