5.
7 Using Python based move preprocessors
MachSim has the capability of processing each move with a custom Python script.
You can use it to:
a) change the axis values
b) synthesize new values. (Insert new moves in the simulation)
To enable it, add a line like this to the machine .xml file:
a) for chaning axis values:
<preprocessor fileName="preprocessor.py" instance="preprocessor" />
b) for inserting new moves into the simulation:
<preprocessor fileName="preprocessor.py" instance="preprocessor" type="inserter" />
fileName is the path to python script that needs to be processed. It can be relative to machine directory or full path.
instance is the name of the python object declared in script. It will be used to process each move.
type is the type of preprocessing. When missing it is assumed that preprocessing will change axis values. When declared as
"inserter" it will insert additional moves into the simulation.
Multiple <preprocessor ../> tags may be used in xml file.
One important thing to remember is that changing axis values is done via method ProcessMove and insertion of new moves is
done via GetNewMoves nethod. The discussion further will treat methods separatelly as there are differencies in the parameters
passed to them.
5.7.1 MachSim python interpreter
MachSim does not use installed python program on the computer where it runs. Instead he is compiled with a python interpreter. In
this case it is important to validate your script with a similar version. To find out which version requires MachSim one can use the
next script (setup).
a) xml tag
<preprocessor fileName="module1.py" instance="move_filter" />
b) module1.py
#-------------------------------------------------------
import sys
class Module1:
VERSION = 1
TYPE = "move"
AXIS = []
def __init__(self):
self.count = 0
self.f = open(__file__+".log", "w+")
def ProcessMove(self, environment, operation, move):
if self.count == 0:
(major,minor,micro,release,serial) = tuple(sys.version_info)
ver = "python version {0:d}.{1:d}.{2:d}".format(major,minor,micro)
self.f.write("%s\n" % ver)
self.f.close()
self.count += 1
move_filter = Module1()
#-------------------------------------------------------
Start machine simulation, load machine definition. It should be enough to get a call to move_filter.ProcessMove.
In the machine directory look for log file module1.py.log
It may contain a string similar with: python version 2.7.1
5.7.2 Minimum requirements to change axis values (ProcessMove)
The minimum required by the MachSim python implementation is:
a) xml tag
<preprocessor fileName="module1.py" instance="move_filter" />
b) module1.py
#-------------------------------------------------------
class Module1:
VERSION = 1
TYPE = "move"
AXIS = []
def ProcessMove(self, environment, operation, move):
pass
move_filter = Module1()
#-------------------------------------------------------
VERSION is mandatory class attribute with value 1.
TYPE is mandatory class attribute with value "move".
AXIS is mandatory class attribute.
ProcessMove is mandatory class method having 4 parameters.
move_filter is mandatory object having same name as xml's instance attribute.
Obviously the script above does nothing. It just declares the minimum necessary to change axis values.
5.7.3 ProcessMove method parameters
I will use a 5AxHeadHead machine with 5 axes : X, Y, Z, C, B and a CL file to load a simple simulation and log into file
module1.py.log the parameters passed by MachSim to method ProcessMove.
The setup for python is:
a) A snippet from 5AxHeadHead.xml file with <preprocessor .../> tag
<machine_definition>
<machine_data name="5AxHeadHead" version="1.7" units="metric" />
<preprocessor fileName="module1.py" instance="move_filter" />
<axis id="X" type="translation" ...
b) module1.py
#-------------------------------------------------------
class Module1:
VERSION = 1
TYPE = "move"
AXIS = ['X','Y','Z','C','B']
def __init__(self):
self.f = open(__file__+".log", "w+")
self.count = 0
return
def DebugMove(self, environment, operation, move):
self.f.write("\ncount: %d\n" % self.count)
self.f.write("environment: %s\n" % environment)
self.f.write("operation: %s\n" % operation)
self.f.write("move: %s\n" % move)
self.f.flush()
self.count += 1
def ProcessMove(self, environment, operation, move):
self.DebugMove(environment, operation, move)
move_filter = Module1()
#-------------------------------------------------------
c) module1.py.log - the logging file with the structure of ProcessMove parameters.
#-------------------------------------------------------
environment: {'globalMoveCount': 434}
operation: {
'comment': '"Sample 5 axis operation"',
'tool': {
'comment': ('"Sample tool"',),
'number': 1,
'name': 'tool'
},
'number': 1,
'transform': {
'workpiece_transform': (1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0),
'holder_transform': (1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, -12.0, 0.0, 0.0, 0.0, 1.0)
},
'size': 434}
move: {
'simulationIndex': 121,
'axisValue': {'Y': 1.5460000038146973, 'X': -48.779998779296875, 'C': 0.0, 'Z': 18.0, 'B': 0.0},
'globalIndex': 120
}
#-------------------------------------------------------
d) ProcessMove parameters:
environment [ 'globalMoveCount' ] => total number of moves in the simulation.
operation [ 'comment' ] => current operation comment
operation [ 'tool' ] [ 'comment' ] => comment for the first tool of the current operation
operation [ 'tool' ] [ 'number' ] => number of the first tool of the current operation
operation [ 'tool' ] [ 'name' ] => name of the first tool of the current operation
operation [ 'number' ] => current operation number
operation [ 'transform' ] [ 'workpiece_transform' ] => a tuple with 16 values representing the 4x4 matrix applied to machine's
workpiece_transform.
operation [ 'transform' ] [ 'holder_transform' ] => a tuple with 16 values representing the 4x4 matrix applied to machine's
holder_transform.
operation [ 'size' ] => total number of moves of the current operation
move [ 'simulationIndex' ] => 1 based index of current move acczoss entire simulation
move [ 'globalIndex' ] = 0 based index of current move across entire simulation.
move [ 'axisValue' ] [ 'Y' ] => provide access to axis value. Changed value will be visible to machine simulation.
5.7.4 Limit machine movement on Z axis using ProcessMove method
a) xml tag
<preprocessor fileName="module1.py" instance=" preserve_axes_limit" />
b) module1.py
#-------------------------------------------------------
class Module1:
VERSION = 1
TYPE = "move"
AXIS = ['X','Y','Z','C','B']
def ProcessMove(self, environment, operation, move):
if move[ 'axisValue' ] [ 'Z' ] > 20:
move [ 'axisValue' ] [ 'Z' ] = 20.
preserve_axes_limit = Module1()
#-------------------------------------------------------
5.7.5 Minimum requirements to insert new moves (GetNewMoves)
The minimum required by the MachSim python implementation is:
a) xml tag
<preprocessor fileName="module1.py" instance="move_insert" type="inserter" />
b) module1.py
#-------------------------------------------------------
class Module1:
VERSION = 1
TYPE = "move"
AXIS = []
def GetNewMoves(self, environment, operation, currMove, nextMove):
pass
move_insert = Module1()
#-------------------------------------------------------
VERSION is mandatory class attribute with value 1.
TYPE is mandatory class attribute with value "move".
AXIS is mandatory class attribute.
GetNewMoves is mandatory class method having 5 parameters.
move_filter is mandatory object having same name as xml's instance attribute.
Obviously the script above does nothing. It just declares the minimum necessary to use insert of new values functionality. In this
mode modifing axis values of existing moves is not possible. Only isertion is possible.
5.7.6 GetNewMoves method parameters
I will use a 5AxHeadHead machine with 5 axes : X, Y, Z, C, B and a CL file to load a simple simulation and log into file
module1.py.log the parameters passed by MachSim to method GetNewMoves.
The setup for python is:
a) xml tag
<preprocessor fileName="module1.py" instance="move_insert" type="inserter" />
b) module1.py
#-------------------------------------------------------
class Module1:
VERSION = 1
TYPE = "move"
AXIS = ['X','Y','Z','C','B']
def __init__(self):
self.f = open(__file__+".log", "w+")
self.count = 0
return
def DebugMove(self, environment, operation, move, nextmove):
self.f.write("\ncount: %d\n" % self.count)
self.f.write("environment: %s\n" % environment)
self.f.write("operation: %s\n" % operation)
self.f.write("move: %s\n" % move)
self.f.write("nextmove: %s\n" % nextmove)
self.f.flush()
self.count += 1
def GetNewMoves(self, environment, operation, move, nextmove):
self.DebugMove(environment, operation, move, nextmove)
# return value object to insert 1 move
insert_move_on_origin = {
'forwardInsertion' : 0 ,
'newMovesList' : [ { 'axisValue' : { 'X' : 0, 'Y' : 0, 'Z' : 0, 'B' : 0, 'C' : 0 } } ]
}
if move [ 'relativeIndex' ] == 0:
return insert_move_on_origin
move_insert = Module1()
#-------------------------------------------------------
c) module1.py.log - the logging file with the structure of GetNewMoves parameters.
#-------------------------------------------------------
environment: { }
operation: {
'comment': '"Sample 5 axis operation"',
'tool': {
'comment': ('"Sample tool"',),
'number': 1,
'name': 'tool'
},
'number': 1,
'transform': {
'workpiece_transform': (1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0),
'holder_transform': (1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, -12.0, 0.0, 0.0, 0.0, 1.0)},
'size': 434}
move: {
'relativeIndex': 431,
'axisValue': {'Y': -6.306000232696533, 'X': -39.33300018310547, 'C': 0.0, 'Z': 14.050000190734863, 'B': 0.0}, 'time':
0.004000000189989805
}
nextmove: {
'relativeIndex': 431,
'axisValue': {'Y': -6.482999801635742, 'X': -45.29499816894531, 'C': 0.0, 'Z': 24.049999237060547, 'B': 0.0}, 'time':
0.006000000052154064
}
#-------------------------------------------------------
d) GetNewMoves parameters:
environment => empty dictionary
operation [ 'comment' ] => current operation comment
operation [ 'tool' ] [ 'comment' ] => comment for the first tool of the current operation
operation [ 'tool' ] [ 'number' ] => number of the first tool of the current operation
operation [ 'tool' ] [ 'name' ] => name of the first tool of the current operation
operation [ 'number' ] => current operation number
operation [ 'transform' ] [ 'workpiece_transform' ] => a tuple with 16 values representing the 4x4 matrix applied to machine's
workpiece_transform.
operation [ 'transform' ] [ 'holder_transform' ] => a tuple with 16 values representing the 4x4 matrix applied to machine's
holder_transform.
operation [ 'size' ] => total number of moves of the current operation
move [ 'relativeIndex' ] => 0 based index of current move across entire simulation
move [ 'axisValue' ] [ 'Y' ] => provide access to axis value. Changed value will NOT be visible to machine simulation.
move [ 'time' ] => time in seconds needed to complete this move when MachSim runs in time-base mode.
nextmove [ 'relativeIndex' ] => 0 based index of next move across entire simulation
nextmove [ 'axisValue' ] [ 'Y' ] => provide access to axis value. Changed value will NOT be visible to machine simulation.
nextmove [ 'time' ] => time in seconds needed to complete next move when MachSim runs in time-base mode.
insert_move_on_origin [ 'forwardInsertion' ] => 1 to insert move after current move or 0 to insert move before current move
insert_move_on_origin [ 'newMovesList' ] => list with the new moves to be inserted
insert_move_on_origin [ 'newMovesList' ] [ 0 ] [ 'axisValue' ] [ 'X' ] => X value for a new move at position 0 in list.
5.7.7 Insert 2 moves at the beginning of simulation using GetNewMoves method
a) xml tag:
<preprocessor fileName="module1.py" instance="insert_new_moves" type="inserter" />
b) module1.py
#-------------------------------------------------------
class Module1:
VERSION = 1
TYPE = "move"
AXIS = ['X','Y','Z','C','B']
def __init__(self):
self.insert_moves_from_machine_origin = {
'forwardInsertion': 0,
'newMovesList': [{'axisValue': {'X':500, 'Y':500, 'Z':900, 'B':0, 'C':0} },
{'axisValue': {'X':500, 'Y':500, 'Z':850, 'B':0, 'C':0} }
]}
def GetNewMoves(self, environment, operation, move, nextmove):
if move[ 'relativeIndex' ] == 0:
return self.insert_moves_from_machine_origin
insert_new_moves = Module1()
#-------------------------------------------------------
5.7.8 Sample of a real-life preprocessing script
The following script automatically closes and opens the machine doors at the start/end of the simulation and spins a warning light.
Also, all of the axis values and operation properties are logged to a file with the same name but with the .log extension.
import time
from math import *
LOGGING_ENABLED = True
LOG_FILE = __file__.replace(".py", ".log")
def Log(text):
if LOGGING_ENABLED:
f = open(LOG_FILE, "a+t")
f.write(text)
f.close()
def LogMove(operation, move):
if LOGGING_ENABLED:
Log("operation=%r, move=%r\n" % (operation, move))
class DoorClosePreprocessorBase(object):
def __init__(self, axisName, closedValue, openedValue, stepsToClose):
self.__axisName = axisName
self.__closedValue = closedValue
self.__openedValue = openedValue
self.__stepsToClose = stepsToClose
self.__moveScale = (self.__closedValue - self.__openedValue) / self.__stepsToClose
def ProcessMove(self, environment, operation, move):
globalIndex = move["globalIndex"]
globalMoveCount = environment["globalMoveCount"]
if globalIndex <= self.__stepsToClose:
value = self.__openedValue + globalIndex * self.__moveScale
else:
threshold = globalMoveCount - self.__stepsToClose - 1
if globalIndex >= threshold:
value = self.__closedValue - (globalIndex - threshold) * self.__moveScale
else:
value = self.__closedValue
move["axisValue"][self.__axisName] = value
class DoorClosePreprocessor(object):
VERSION = 1
TYPE = "move"
AXIS = ["LH-Door", "RH-Door"]
def __init__(self):
stepsToClose = 50
self.__leftDoor = DoorClosePreprocessorBase("LH-Door", 0.0, -499.0, stepsToClose)
self.__rightDoor = DoorClosePreprocessorBase("RH-Door", 0.0, 499.0, stepsToClose)
def ProcessMove(self, environment, operation, move):
LogMove(operation, move)
self.__leftDoor.ProcessMove(environment, operation, move)
self.__rightDoor.ProcessMove(environment, operation, move)
class WarningLightPreprocessor(object):
VERSION = 1
TYPE = "move"
AXIS = ["Light"]
def ProcessMove(self, environment, operation, move):
LogMove(operation, move)
move["axisValue"]["Light"] = (move["globalIndex"] * 5.0) % 360.0
doorClosePreprocessor = DoorClosePreprocessor()
warningLightPreprocessor = WarningLightPreprocessor()
Log("Preprocessor module imported - %s\n" % time.strftime("%a, %d %B %Y, %H:%M:%S"))