├── BLtZonly.py ├── README.md ├── ToolOffset.py ├── Zonly.py └── pythondcs.py /BLtZonly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Python script intended to determine tool-to-tool offsets on a tool changing 3D printer 3 | # running a Duet 3 with Pi. 4 | # 5 | # This variant measures Z only. 6 | # 7 | # Must run with root priviledge, such as via sudo. 8 | # 9 | # Copyright (C) 2020 Danal Estes all rights reserved. 10 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 11 | # 12 | 13 | 14 | # Edit these for your printer. 15 | tl = [0,1] # List of tools to be compared 16 | yc = 225 # Y line that will clear parked tools when moving in X 17 | xz = 288 # X coord of tool nozzle over flat plate to probe Z. 15x15mm area recommended. 18 | yz = 285 # Y coord of tool nozzle over flat plate to probe Z. 15x15mm area recommended. 19 | xp = 260 # X coord of BLtouch over flat plate to probe Z. 15x15mm area recommended. 20 | yp = 340 # Y coord of BLtouch over flat plate to probe Z. 15x15mm area recommended. 21 | # Normally, change nothing below this line. 22 | # 23 | # 24 | # 25 | toffs = [[0] * 3 for i in range(len(tl))] 26 | poffs = 0 27 | import pythondcs 28 | import numpy as np 29 | dcs = pythondcs.PythonDCS() 30 | 31 | def probePlate(): 32 | dcs.resetEndstops() 33 | dcs.resetAxisLimits() 34 | dcs.gCode('T-1') 35 | dcs.gCode('M280 P0 S160') 36 | dcs.gCode('G32 G28 Z') 37 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 38 | dcs.gCode('G0 Y'+str(yc)+' F10000') # Move carriage to avoid other tools 39 | dcs.gCode('G0 X'+str(xp)+' F10000') # Move BLt to axis of flat area 40 | dcs.gCode('G0 X'+str(xp)+' Y'+str(yp)+' F10000') # Move BLt to spot above flat part of plate 41 | dcs.gCode('G30 S-1') # Probe plate with BLt 42 | global poffs 43 | poffs = dcs.getPos()[2] # Capture the Z position at initial point of contact 44 | print("Plate Offset = "+str(poffs)) 45 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 46 | 47 | 48 | def probeTool(tn): 49 | dcs.resetEndstops() 50 | dcs.resetAxisLimits() 51 | dcs.gCode('M400') 52 | dcs.gCode('M280 P0 S160') 53 | dcs.gCode('G28 Z') 54 | dcs.gCode('G10 P'+str(tn)+' Z0') # Remove z offsets from Tool 55 | dcs.gCode('T'+str(tn)) # Pick up Tool 56 | # Z Axis 57 | dcs.gCode('M558 K0 P9 C"nil"') 58 | dcs.gCode('M574 Z1 S1 P"!io5.in"') 59 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 60 | dcs.gCode('G0 Y'+str(yc)+' F10000') # Move nozzle to avoid other tools 61 | dcs.gCode('G0 X'+str(xz)+' F10000') # Move nozzle to axis of flat area 62 | dcs.gCode('G0 X'+str(xz)+' Y'+str(yz)+' F10000') # Move nozzle to spot above flat part of plate 63 | dcs.gCode('G1 H3 Z-4 F10') 64 | print("Tool Offset for tool "+str(tn)+" first pass is "+str(dcs.getPos()[2])) 65 | dcs.resetAxisLimits() 66 | dcs.gCode('G0 Z4 F100') # Lower bed for second pass 67 | dcs.gCode('G1 H3 Z-4 F10') 68 | toffs[tn][2] = dcs.getPos()[2] # Capture the Z position at initial point of contact 69 | dcs.resetAxisLimits() 70 | print("Tool Offset for tool "+str(tn)+" is "+str(toffs[tn][2])) 71 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 72 | dcs.gCode('M574 Z1 S1 P"nil"') 73 | dcs.resetEndstops() 74 | dcs.resetAxisLimits() 75 | dcs.gCode('T-1') 76 | dcs.gCode('M400') 77 | # End of probeTool function 78 | 79 | # 80 | # Main 81 | # 82 | probePlate() 83 | for t in tl: 84 | probeTool(t) 85 | dcs.resetAxisLimits() 86 | dcs.resetEndstops() 87 | 88 | # Display Results 89 | # Actually set G10 offsets 90 | print("Plate Offset = "+str(poffs)) 91 | for i in range(len(toffs)): 92 | tn = tl[i] 93 | print("Tool Offset for tool "+str(tn)+" is "+str(toffs[tn][2])) 94 | print('G10 P'+str(tn)+' Z'+str(np.around((poffs-toffs[i][2]),2))) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SUMMARY 2 | 3 | This repository contains a Python module intended to interface with the "Duet Software Framework" found on a Duet 3 controller board for 3D Printers when that board is used with a ribbon cable attached Raspberry Pi. 4 | 5 | ## PythonDCS: 6 | 7 | Specifically, it provides a module called pythondcs.py that contains a class of named PythonDCS. At this time, it contains methods to connect to the "Duet Control Server". Since it connects to a priveleged socket on the Pi, it must run with root priveleges, such as "sudo". 8 | 9 | The methods include: 10 | 11 | #### Gcode('cmd') 12 | Passes the command argument to the printer, waits for execution, and returns the response (if any). 13 | #### getPos() 14 | Returns a Python list containing the current position of the machine, in MM. Normally list[0] is X, list[1] is Y, and so on. 15 | #### resetEndstops() 16 | Removes the endstops on XYZU and probe K0. Scans the 'config.g' file of the printer for all M574 and M558 commands, and executes them. This has the net effect of setting up all endstops as though the printer 17 | #### resetAxisLimits() 18 | Scans the 'config.g' file of the printer for all M208 commands, and executes them. This is particularly useful after G1 H3 command (which resets hi axis limit). 19 | 20 | ### ToolOffset.py 21 | 22 | This repository also contains a sample script that is designed to measure tool-to-tool offsets on a tool changing 3D printer. This script was specifically developed for the Jubilee printer. 23 | 24 | It assumes that a plate exists with known coordinates attached to the bed. This plate has a flat area, minimum 15x15mm for touch probing Z, and contains a round hole with crisp 90 deg edges between the wall of the hold and the surface. This hole should be about 15mm in diameter. This plate must be grounded. The nozzle of each tool must be connected via a wire to io4.in on the main 6HC board. 25 | 26 | You will then need to edit the coordinates of the Z touch area, and the center of the hole, into the begining of the ToolOffset.py script. 27 | 28 | The first several times you run this, be extra ready to E-Stop the printer. 29 | 30 | ### Zonly.py 31 | 32 | Like ToolOffset, but only does Z. Perhaps you will use a camera for XY. 33 | 34 | -------------------------------------------------------------------------------- /ToolOffset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Python script intended to determine tool-to-tool offsets on a tool changing 3D printer 3 | # running a Duet 3 with Pi. 4 | # 5 | # Must run with root priviledge, such as via sudo. 6 | # 7 | # Copyright (C) 2020 Danal Estes all rights reserved. 8 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 9 | # 10 | 11 | 12 | # Edit these for your printer. 13 | tl = [0,1] # List of tools to be compared 14 | yc = 225 # Y line that will clear parked tools when moving in X 15 | xz = 290 # X coord of flat plate to probe Z. 15x15mm area recommended. 16 | yz = 285 # Y coord of flat plate to probe Z. 15x15mm area recommended. 17 | xh = 290 # X coord of hole in which nozzle is inserted to probe XY. 15mm dia recommended 18 | yh = 272 # Y coord of hole in which nozzle is inserted to probe XY. 15mm dia recommended 19 | hd = 1.1 # Depth to lower nozzle into hole when probing. Depends on shape of nozzle. 20 | # About .9 to 1.1 works for most brass nozzles. 21 | zo = -3.1 # Offset from flat plate area probe Z to actual Z0 on print surface. 22 | # Normally, change nothing below this line. 23 | # 24 | # 25 | # 26 | toffs = [[0] * 3 for i in range(len(tl))] 27 | import pythondcs 28 | dcs = pythondcs.PythonDCS() 29 | 30 | def probeTool(tn): 31 | dcs.resetEndstops() 32 | dcs.gCode('M400') 33 | dcs.gCode('T'+str(tn)) # Pick up Tool 34 | dcs.gCode('G10 P'+str(tn)+' Z0 X0 Y0') # Remove all offsets from Tool 35 | # Z Axis 36 | dcs.gCode('M574 Z1 S1 P"!io5.in"') 37 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 38 | dcs.gCode('G0 Y'+str(yc)+' F10000') # Move nozzle to avoid other tools 39 | dcs.gCode('G0 X'+str(xz)+' F10000') # Move nozzle to axis of flat area 40 | dcs.gCode('G0 X'+str(xz)+' Y'+str(yz)+' F10000') # Move nozzle to spot above flat part of plate 41 | dcs.gCode('G1 H3 Z1 F100') 42 | toffs[tn][2] = dcs.getPos()[2] # Capture the Z position at initial point of contact 43 | dcs.resetAxisLimits() 44 | dcs.gCode('G0 Z'+str(toffs[tn][2]+1)+' F100') # Back off just slightly 45 | dcs.gCode('G1 H3 Z1 F10') 46 | toffs[tn][2] = dcs.getPos()[2] # Capture the Z position at point of contact 47 | dcs.resetAxisLimits() 48 | print(toffs[tn][2]) 49 | if (toffs[tn][2] < 1.1): 50 | print('Z less than 1.1, very likely miss on Z probe, stoping script to avoid damange to printer') 51 | dcs.resetEndstops() 52 | exit(8) 53 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 54 | dcs.gCode('M574 Z1 S1 P"nil"') 55 | 56 | # X Axis - First Pass for finding Y center 57 | dcs.gCode('M574 X1 S1 P"!io5.in"') 58 | dcs.gCode('G0 X'+str(xh)+' Y'+str(yh)+' F1000') # Place the nozzle tip in center of hole. 59 | dcs.gCode('G0 Z'+str(toffs[tn][2]-1.1)+' F100') # Place the nozzle tip just below surface. 60 | dcs.gCode('M675 X R1 F100') # Probe both ways, thus creating a chord and leaving nozzle at center X 61 | dcs.gCode('M574 X1 S1 P"nil"') 62 | 63 | # Y Axis 64 | dcs.gCode('M574 Y1 S1 P"!io5.in"') 65 | dcs.gCode('M675 Y R1 F100') # Probe both ways for Y while centered on X... now centered on Y 66 | toffs[tn][1] = dcs.getPos()[1] # Capture the Y position 67 | dcs.gCode('M574 Y1 S1 P"nil"') 68 | 69 | # X Axis - Second Pass now that Y is centered 70 | dcs.gCode('M574 X1 S1 P"!io5.in"') 71 | dcs.gCode('M675 X R1 F100') # Probe both ways on X while centered on Y... now centered on both X and Y 72 | toffs[tn][0] = dcs.getPos()[0] # Capture the X position 73 | dcs.gCode('M574 X1 S1 P"nil"') 74 | 75 | dcs.gCode('T-1') 76 | dcs.gCode('M400') 77 | # End of probeTool function 78 | 79 | # 80 | # Main 81 | # 82 | for t in tl: 83 | probeTool(t) 84 | dcs.resetEndstops() 85 | 86 | # Display Results 87 | for i in range(len(toffs)): 88 | for j in range(len(toffs[i])): 89 | print('Tool '+str(i)+' Axis '+str(j)+' = '+str(toffs[i][j])) 90 | 91 | for j in range(len(toffs[0])): 92 | print('Axis '+str(j)+' difference = '+str(toffs[0][j]-toffs[1][j])) 93 | 94 | # Actually set G10 offsets 95 | for i in range(len(toffs)): 96 | tn = tl[i] 97 | dcs.gCode('G10 P'+str(tn)+' Z'+str((-toffs[i][2])-zo)) 98 | dcs.gCode('G10 P'+str(tn)+' X'+str(toffs[0][0]-toffs[i][0])+' Y'+str(toffs[0][1]-toffs[i][1])) 99 | -------------------------------------------------------------------------------- /Zonly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Python script intended to determine tool-to-tool offsets on a tool changing 3D printer 3 | # running a Duet 3 with Pi. 4 | # 5 | # This variant measures Z only. 6 | # 7 | # Must run with root priviledge, such as via sudo. 8 | # 9 | # Copyright (C) 2020 Danal Estes all rights reserved. 10 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 11 | # 12 | 13 | 14 | # Edit these for your printer. 15 | tl = [0,1] # List of tools to be compared 16 | yc = 225 # Y line that will clear parked tools when moving in X 17 | xz = 288 # X coord of flat plate to probe Z. 15x15mm area recommended. 18 | yz = 285 # Y coord of flat plate to probe Z. 15x15mm area recommended. 19 | zo = -3.1 # Offset from flat plate area probe Z to actual Z0 on print surface. 20 | # Normally, change nothing below this line. 21 | # 22 | # 23 | # 24 | toffs = [[0] * 3 for i in range(len(tl))] 25 | import pythondcs 26 | import numpy as np 27 | dcs = pythondcs.PythonDCS() 28 | 29 | def probeTool(tn): 30 | dcs.resetEndstops() 31 | dcs.gCode('M400') 32 | dcs.gCode('T'+str(tn)) # Pick up Tool 33 | dcs.gCode('G10 P'+str(tn)+' Z0 X0 Y0') # Remove all offsets from Tool 34 | # Z Axis 35 | dcs.gCode('M574 Z1 S1 P"!io5.in"') 36 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 37 | dcs.gCode('G0 Y'+str(yc)+' F10000') # Move nozzle to avoid other tools 38 | dcs.gCode('G0 X'+str(xz)+' F10000') # Move nozzle to axis of flat area 39 | dcs.gCode('G0 X'+str(xz)+' Y'+str(yz)+' F10000') # Move nozzle to spot above flat part of plate 40 | dcs.gCode('G1 H3 Z1 F100') 41 | toffs[tn][2] = dcs.getPos()[2] # Capture the Z position at initial point of contact 42 | dcs.resetAxisLimits() 43 | dcs.gCode('G0 Z'+str(toffs[tn][2]+1)+' F100') # Back off just slightly 44 | dcs.gCode('G1 H3 Z1 F10') 45 | toffs[tn][2] = dcs.getPos()[2] # Capture the Z position at point of contact 46 | dcs.resetAxisLimits() 47 | dcs.gCode('G0 Z'+str(toffs[tn][2]+1)+' F100') # Back off just slightly 48 | dcs.gCode('G1 H3 Z1 F10') 49 | toffs[tn][2] = dcs.getPos()[2] # Capture the Z position at point of contact 50 | dcs.resetAxisLimits() 51 | print(toffs[tn][2]) 52 | if (toffs[tn][2] < 1.1): 53 | print('Z less than 1.1, very likely miss on Z probe, stoping script to avoid damange to printer') 54 | dcs.resetEndstops() 55 | exit(8) 56 | dcs.gCode('G0 Z10 F1000') # Lower bed to avoid collision with hole plate. 57 | dcs.gCode('M574 Z1 S1 P"nil"') 58 | 59 | dcs.gCode('T-1') 60 | dcs.gCode('M400') 61 | # End of probeTool function 62 | 63 | # 64 | # Main 65 | # 66 | for t in tl: 67 | probeTool(t) 68 | dcs.resetEndstops() 69 | 70 | # Display Results 71 | # Actually set G10 offsets 72 | for i in range(len(toffs)): 73 | tn = tl[i] 74 | print('G10 P'+str(tn)+' Z'+str(np.around((-toffs[i][2])-zo,3))) 75 | -------------------------------------------------------------------------------- /pythondcs.py: -------------------------------------------------------------------------------- 1 | # Python module intended for interface to the Duet Software Framework 2 | # Copyright (C) 2020 Danal Estes all rights reserved. 3 | # Released under The MIT 4 | # 5 | # As of Jan 2020, functions for interacting with Duet Control Server are implemented, 6 | # plus a few things specific to the virtual SD config.g 7 | # 8 | 9 | import socket 10 | import json 11 | 12 | class PythonDCS: 13 | 14 | def openDCS(self): 15 | self.DCSsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 16 | self.DCSsock.connect('/var/run/dsf/dcs.sock') 17 | self.DCSsock.setblocking(True) 18 | j=json.dumps({"mode":"command"}).encode() 19 | self.DCSsock.send(j) 20 | r=self.DCSsock.recv(128).decode() 21 | if (-1 == r.find('{"version":')): 22 | print("Failed to enter command mode - version not received") 23 | print(r) 24 | exit(8) 25 | if (-1 == r.find('{"success":true}')): #could be in same buffer as version 26 | r=self.DCSsock.recv(128).decode() 27 | if (-1 == r.find('{"success":true}')): 28 | print("Failed to enter command mode - success not received") 29 | print(r) 30 | exit(8) 31 | 32 | def closeDCS(self): 33 | self.DCSsock.close() 34 | 35 | def gCode(self,cmd=''): 36 | #print(cmd) 37 | j=json.dumps({"code": cmd,"channel": 0,"command": "SimpleCode"}).encode() 38 | self.DCSsock.send(j) 39 | r=self.DCSsock.recv(2048).decode() 40 | if ('Error' in r): 41 | print('Error detected, stopping script') 42 | print(j) 43 | print(r) 44 | exit(8) 45 | return(r) 46 | 47 | def getPos(self): 48 | result = json.loads(self.gCode('M408'))['result'] 49 | pos = json.loads(result)['pos'] 50 | #print('getPos = '+str(pos)) 51 | return pos 52 | 53 | def resetEndstops(self): 54 | self.gCode('M574 X1 S1 P"nil"') 55 | self.gCode('M574 Y1 S1 P"nil"') 56 | self.gCode('M574 Z1 S1 P"nil"') 57 | self.gCode('M574 U1 S1 P"nil"') 58 | self.gCode('M558 K0 P5 C"nil"') 59 | c = open('/opt/dsf/sd/sys/config.g','r') 60 | for each in [line for line in c if (('M574' in line) or ('M558' in line) or ('G31' in line))]: self.gCode(each) 61 | c.close() 62 | 63 | 64 | def resetAxisLimits(self): 65 | c = open('/opt/dsf/sd/sys/config.g','r') 66 | for each in [line for line in c if 'M208' in line]: self.gCode(each) 67 | c.close() 68 | 69 | def __init__(self): 70 | self.openDCS() 71 | 72 | def __enter__(self): 73 | return self 74 | 75 | def __exit__(self, *args): 76 | self.closeDCS() 77 | 78 | --------------------------------------------------------------------------------