├── README.md ├── find_nozzle.py ├── getCoordsDemo.py ├── installOpenCV.sh ├── toolAlignImage.py ├── toolAlignImageDuet.py └── videoOnly.py /README.md: -------------------------------------------------------------------------------- 1 | # DuetPython 2 | Python utilities for Duet RepRap based 3D printers, including D3+Pi 3 | 4 | ## find_nozzle.py 5 | * Uses OpenCV machine vision to find circles in a camera view. 6 | * Assumes USB camera. Could be changed for PiCam. 7 | * Requires: OpenCV. 8 | * Runs on: Whatever machine owns the camera 9 | * Must run in terminal on graphic console (or VNC) 10 | 11 | ## getCoordsDemo.py 12 | * Returns a JSON object with coordinates of axis of a Duet printer 13 | * Auto-senses RRF2 vs RRF3 printers. 14 | * Runs on: Any machine that can reach the printer via the network. 15 | * Does not support Duet passwords at this time. 16 | 17 | ## installOpenCV.sh 18 | * Installs openCV on the Pi system image provided by Duet 19 | * Invoke via: bash installOpenCV.sh 20 | * Runs for about an hour (depending on type of Pi and internet speed) 21 | * You MAY need to enlarge your swap file during the install. 22 | 23 | ## toolAlignImageDuet.py 24 | * Creates G10 commands for tool-to-tool alignment on a multi-tool printer. 25 | * Uses the human being for image alignment. 26 | * Interfaces to printer to fetch XY (using getCoord from above) 27 | * Requires: OpenCV. 28 | * Runs on: Whatever machine owns the camera and can reach the printer via the network. 29 | * Must run in terminal on graphic console (or VNC) 30 | 31 | ## toolAlignImage.py 32 | * Creates G10 commands for tool-to-tool alignment on a multi-tool printer. 33 | * Uses the human being for image alignment. 34 | * Uses the Human Bieng to enter XY coordinates. 35 | * Requires: OpenCV. 36 | * Runs on: Whatever machine owns the camera 37 | * Should work for ANY toolchanging printer. 38 | * Must run in terminal on graphic console (or VNC) 39 | 40 | 41 | ## videoOnly.py 42 | * Creates a video window with a green circle to assist in tool alignment 43 | * All other actions are up to the user 44 | * Should work for ANY toolchanging printer 45 | -------------------------------------------------------------------------------- /find_nozzle.py: -------------------------------------------------------------------------------- 1 | # Python Script to align multiple tools on Jubilee printer 2 | # Using images from USB camera and finding circles in those images 3 | # Circle find needs lots of tuning... 4 | # 5 | # Copyright (C) 2020 Danal Estes all rights reserved. 6 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 7 | # 8 | # Requires OpenCV to be installed on Pi 9 | # Requires running via the OpenCV installed python (that is why no shebang) 10 | import datetime 11 | import imutils 12 | import time 13 | import cv2 14 | import numpy as np 15 | 16 | # initialize the video stream and allow the cammera sensor to warmup 17 | vs = cv2.VideoCapture(0) 18 | time.sleep(2.0) 19 | avg=[0,0] 20 | count=0 21 | # loop over the frames from the video stream 22 | while True: 23 | (grabbed, frame) = vs.read() 24 | 25 | # draw the timestamp on the frame 26 | timestamp = datetime.datetime.now() 27 | ts = timestamp.strftime("%A %d %B %Y %I:%M:%S%p") 28 | cv2.putText(frame, ts, (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX,0.90, (255, 255, 255), 1) 29 | #Find circles 30 | img = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) 31 | img = cv2.medianBlur(img,17) 32 | circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,50,param1=50,param2=39,minRadius=15,maxRadius=20) 33 | if (circles is None): 34 | continue 35 | circles = np.uint16(np.around(circles)) 36 | for i in circles[0,:]: 37 | # Keep track of center of circle 38 | avg[0] += i[0] 39 | avg[1] += i[1] 40 | count += 1 41 | if (count > 15): 42 | avg[0] /= count 43 | avg[1] /= count 44 | avg = np.around(avg,2) 45 | print(avg[0],avg[1]) 46 | avg = [0,0] 47 | count = 0 48 | # draw the circle 49 | i = np.around(i) 50 | cv2.circle(frame,(i[0],i[1]),i[2],(0,0,255),3) 51 | 52 | # show the frame 53 | cv2.imshow("Nozzle", frame) 54 | key = cv2.waitKey(1) 55 | #print(ts) 56 | # if the `q` key was pressed, break from the loop 57 | if key == ord("q"): 58 | break 59 | 60 | # do a bit of cleanup 61 | cv2.destroyAllWindows() 62 | vs.stop() 63 | -------------------------------------------------------------------------------- /getCoordsDemo.py: -------------------------------------------------------------------------------- 1 | # Python Script that returns axis coordinates of a Duet RepRap based printer 2 | # Works with RRF2 or RRF3 3 | # 4 | # Copyright (C) 2020 Danal Estes all rights reserved. 5 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 6 | # 7 | # Requires Python3 8 | 9 | import requests 10 | import json 11 | import sys 12 | 13 | endpoint2='/rr_status?type=1' # RRF2 request 14 | endpoint3='/machine/status' # RRF3 request 15 | endpointA=endpoint2 # Active 16 | 17 | def getCoords(base_url): 18 | global endpointA 19 | if (endpointA == endpoint2): 20 | try: 21 | r = requests.get(f'{base_url}{endpointA}') 22 | j=json.loads(r.text) 23 | jc=j['coords']['xyz'] 24 | ret=json.loads('{}') 25 | for i in range(0,len(jc)): 26 | ret[ 'xyz'[i] ] = jc[i] 27 | return(ret) 28 | except: 29 | endpointA = endpoint3 30 | if (endpointA == endpoint3): 31 | try: 32 | r = requests.get(f'{base_url}{endpointA}') 33 | j=json.loads(r.text) 34 | ja=j['result']['move']['axes'] 35 | jd=j['result']['move']['drives'] 36 | ad=json.loads('{}') 37 | for i in range(0,len(ja)): 38 | ad[ ja[i]['letter'] ] = ja[i]['drives'][0] 39 | ret=json.loads('{}') 40 | for i in range(0,len(ja)): 41 | ret[ ja[i]['letter'] ] = jd[i]['position'] 42 | return(ret) 43 | except: 44 | print(base_url," does not appear to be a RRF2 or RRF3 printer", file=sys.stderr) 45 | raise 46 | # Test cases 47 | print(getCoords('http://192.168.7.100')) 48 | print(getCoords('http://127.0.0.1')) 49 | print(getCoords('http://192.168.7.101')) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /installOpenCV.sh: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # INSTALL OPENCV ON UBUNTU OR DEBIAN # 3 | ###################################### 4 | 5 | # -------------------------------------------------------------------- | 6 | # SCRIPT OPTIONS | 7 | # ---------------------------------------------------------------------| 8 | OPENCV_VERSION='4.2.0' # Version to be installed 9 | OPENCV_CONTRIB='NO' # Install OpenCV's extra modules (YES/NO) 10 | # -------------------------------------------------------------------- | 11 | 12 | # | THIS SCRIPT IS TESTED CORRECTLY ON | 13 | # |------------------------------------------------------| 14 | # | OS | OpenCV | Test | Last test | 15 | # |------------------|--------------|------|-------------| 16 | # | Debian 10.2 | OpenCV 4.2.0 | OK | 26 Dec 2019 | 17 | # |----------------------------------------------------- | 18 | # | Debian 10.1 | OpenCV 4.1.1 | OK | 28 Sep 2019 | 19 | # |----------------------------------------------------- | 20 | # | Ubuntu 18.04 LTS | OpenCV 4.1.0 | OK | 22 Jun 2019 | 21 | # | Debian 9.9 | OpenCV 4.1.0 | OK | 22 Jun 2019 | 22 | # |----------------------------------------------------- | 23 | # | Ubuntu 18.04 LTS | OpenCV 3.4.2 | OK | 18 Jul 2018 | 24 | # | Debian 9.5 | OpenCV 3.4.2 | OK | 18 Jul 2018 | 25 | 26 | 27 | 28 | # 1. KEEP UBUNTU OR DEBIAN UP TO DATE 29 | 30 | sudo apt-get -y update 31 | sudo apt-get -y upgrade # Uncomment to install new versions of packages currently installed 32 | sudo apt-get -y dist-upgrade # Uncomment to handle changing dependencies with new vers. of pack. 33 | sudo apt-get -y autoremove # Uncomment to remove packages that are now no longer needed 34 | 35 | 36 | # 2. INSTALL THE DEPENDENCIES 37 | 38 | # Build tools: 39 | sudo apt-get install -y build-essential cmake 40 | 41 | # GUI (if you want GTK, change 'qt5-default' to 'libgtkglext1-dev' and remove '-DWITH_QT=ON'): 42 | sudo apt-get install -y qt5-default libvtk6-dev 43 | 44 | # Media I/O: 45 | sudo apt-get install -y zlib1g-dev libjpeg-dev libwebp-dev libpng-dev libtiff5-dev libjasper-dev \ 46 | libopenexr-dev libgdal-dev 47 | 48 | # Video I/O: 49 | sudo apt-get install -y libdc1394-22-dev libavcodec-dev libavformat-dev libswscale-dev \ 50 | libtheora-dev libvorbis-dev libxvidcore-dev libx264-dev yasm \ 51 | libopencore-amrnb-dev libopencore-amrwb-dev libv4l-dev libxine2-dev 52 | 53 | # Parallelism and linear algebra libraries: 54 | sudo apt-get install -y libtbb-dev libeigen3-dev 55 | 56 | # Python: 57 | sudo apt-get install -y python-dev python-tk pylint python-numpy \ 58 | python3-dev python3-tk pylint3 python3-numpy flake8 59 | 60 | # Java: 61 | sudo apt-get install -y ant default-jdk 62 | 63 | # Documentation and other: 64 | sudo apt-get install -y doxygen unzip wget 65 | 66 | 67 | # 3. INSTALL THE LIBRARY 68 | 69 | wget https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.zip 70 | unzip ${OPENCV_VERSION}.zip && rm ${OPENCV_VERSION}.zip 71 | mv opencv-${OPENCV_VERSION} OpenCV 72 | 73 | if [ $OPENCV_CONTRIB = 'YES' ]; then 74 | wget https://github.com/opencv/opencv_contrib/archive/${OPENCV_VERSION}.zip 75 | unzip ${OPENCV_VERSION}.zip && rm ${OPENCV_VERSION}.zip 76 | mv opencv_contrib-${OPENCV_VERSION} opencv_contrib 77 | mv opencv_contrib OpenCV 78 | fi 79 | 80 | cd OpenCV && mkdir build && cd build 81 | 82 | if [ $OPENCV_CONTRIB = 'NO' ]; then 83 | cmake -DWITH_QT=ON -DWITH_OPENGL=ON -DFORCE_VTK=ON -DWITH_TBB=ON -DWITH_GDAL=ON \ 84 | -DWITH_XINE=ON -DENABLE_PRECOMPILED_HEADERS=OFF .. 85 | fi 86 | 87 | if [ $OPENCV_CONTRIB = 'YES' ]; then 88 | cmake -DWITH_QT=ON -DWITH_OPENGL=ON -DFORCE_VTK=ON -DWITH_TBB=ON -DWITH_GDAL=ON \ 89 | -DWITH_XINE=ON -DENABLE_PRECOMPILED_HEADERS=OFF \ 90 | -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules .. 91 | fi 92 | 93 | make -j8 94 | sudo make install 95 | sudo ldconfig 96 | 97 | 98 | -------------------------------------------------------------------------------- /toolAlignImage.py: -------------------------------------------------------------------------------- 1 | # Python Script to align multiple tools on Jubilee printer 2 | # Using images from USB camera and user input 3 | # 4 | # Copyright (C) 2020 Danal Estes all rights reserved. 5 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 6 | # 7 | # Requires OpenCV to be installed on Pi 8 | # Requires running via the OpenCV installed python (that is why no shebang) 9 | import datetime 10 | import time 11 | import cv2 12 | import numpy as np 13 | import threading 14 | 15 | # initialize the video stream and allow the cammera sensor to warmup 16 | vs = cv2.VideoCapture(0) 17 | time.sleep(2.0) 18 | avg=[0,0] 19 | count=0 20 | x = [] 21 | y = [] 22 | 23 | def imageStream(): 24 | # loop over the frames from the video stream 25 | while True: 26 | (grabbed, frame) = vs.read() 27 | 28 | # draw the timestamp on the frame 29 | timestamp = datetime.datetime.now() 30 | ts = timestamp.strftime("%A %d %B %Y %I:%M:%S%p") 31 | cv2.putText(frame, ts, (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX,0.90, (255, 255, 255), 1) 32 | cv2.circle(frame,(320,240),25,(0,255,),3) 33 | # show the frame 34 | cv2.imshow("Nozzle", frame) 35 | key = cv2.waitKey(10) 36 | 37 | 38 | 39 | def userInterface(): 40 | print("") 41 | tc=input("How many tools to align?\n") 42 | tc = int(tc) 43 | if (tc < 2): tc = 2 44 | print("IMPORTANT: Before proceeding, enter the commands below in Duet Web console.") 45 | for tool in range(0,tc): 46 | print("G10 P{} X0 Y0".format(tool)) 47 | print("It is OK to copy/paste, one line at a time.") 48 | print("") 49 | for tool in range(0,tc): 50 | print("Use Duet Web to align Tool {} with green circle.".format(tool)) 51 | string = input("After alignment, Input X coordinate from Duet Web:\n") 52 | x.append(float(string)) 53 | string = input("Input Y coordinate from Duet Web:\n") 54 | y.append(float(string)) 55 | 56 | def report(): 57 | print("") 58 | for tool in range(0,len(x)): 59 | xd = np.around(x[0] - x[tool],2) 60 | yd = np.around(y[0] - y[tool],2) 61 | print("G10 P{} X{} Y{}".format(tool,xd,yd)) 62 | print("") 63 | print("NOTE: Each G10 above will also have a Z component, not shown here.") 64 | 65 | 66 | ####################### 67 | # Main Code Starts Here 68 | ####################### 69 | th = threading.Thread(target=imageStream) 70 | th.daemon = True # Thread will be killed when main exits. 71 | th.start() 72 | 73 | userInterface() 74 | 75 | report() 76 | -------------------------------------------------------------------------------- /toolAlignImageDuet.py: -------------------------------------------------------------------------------- 1 | # Python Script to align multiple tools on Jubilee printer 2 | # Using images from USB camera and pulling positions from 3 | # the printer via web interface. 4 | # 5 | # Copyright (C) 2020 Danal Estes all rights reserved. 6 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 7 | # 8 | # Requires OpenCV to be installed on Pi 9 | # Requires running via the OpenCV installed python (that is why no shebang) 10 | import datetime 11 | import time 12 | import cv2 13 | import numpy as np 14 | import threading 15 | import requests 16 | import json 17 | import sys 18 | 19 | # initialize the video stream and allow the cammera sensor to warmup 20 | vs = cv2.VideoCapture(0) 21 | time.sleep(2.0) 22 | avg=[0,0] 23 | count=0 24 | x = [] 25 | y = [] 26 | 27 | # Globals to make getCoords work 28 | endpoint2='/rr_status?type=1' # RRF2 request 29 | endpoint3='/machine/status' # RRF3 request 30 | endpointA=endpoint2 # Active 31 | def getCoords(base_url): 32 | global endpointA 33 | if (endpointA == endpoint2): 34 | try: 35 | r = requests.get(f'{base_url}{endpointA}') 36 | j=json.loads(r.text) 37 | jc=j['coords']['xyz'] 38 | ret=json.loads('{}') 39 | for i in range(0,len(jc)): 40 | ret[ 'xyz'[i] ] = jc[i] 41 | return(ret) 42 | except: 43 | endpointA = endpoint3 44 | if (endpointA == endpoint3): 45 | try: 46 | r = requests.get(f'{base_url}{endpointA}') 47 | j=json.loads(r.text) 48 | ja=j['result']['move']['axes'] 49 | jd=j['result']['move']['drives'] 50 | ad=json.loads('{}') 51 | for i in range(0,len(ja)): 52 | ad[ ja[i]['letter'] ] = ja[i]['drives'][0] 53 | ret=json.loads('{}') 54 | for i in range(0,len(ja)): 55 | ret[ ja[i]['letter'] ] = jd[i]['position'] 56 | return(ret) 57 | except: 58 | print(base_url," does not appear to be a RRF2 or RRF3 printer", file=sys.stderr) 59 | raise 60 | 61 | def imageStream(): 62 | # loop over the frames from the video stream 63 | while True: 64 | (grabbed, frame) = vs.read() 65 | 66 | # draw the timestamp on the frame 67 | timestamp = datetime.datetime.now() 68 | ts = timestamp.strftime("%A %d %B %Y %I:%M:%S%p") 69 | cv2.putText(frame, ts, (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX,0.90, (255, 255, 255), 1) 70 | cv2.circle(frame,(320,240),25,(0,255,),3) 71 | # show the frame 72 | cv2.imshow("Nozzle", frame) 73 | key = cv2.waitKey(10) 74 | 75 | 76 | 77 | def userInterface(): 78 | print("") 79 | ip=input("Please enter IP or name of printer:\n") 80 | tc=input("How many tools to align?\n") 81 | tc = int(tc) 82 | if (tc < 2): tc = 2 83 | for tool in range(0,tc): 84 | print("G10 P{} X0 Y0".format(tool)) 85 | print("It is OK to copy/paste, one line at a time.") 86 | print("") 87 | for tool in range(0,tc): 88 | print("Use Duet Web to align Tool {} with green circle.".format(tool)) 89 | string = input("After alignment, press enter\n") 90 | j=getCoords('HTTP://'+ip) 91 | x.append(float(j['X'])) 92 | y.append(float(j['Y'])) 93 | print('') 94 | 95 | def report(): 96 | print("") 97 | for tool in range(0,len(x)): 98 | xd = np.around(x[0] - x[tool],2) 99 | yd = np.around(y[0] - y[tool],2) 100 | print("G10 P{} X{} Y{}".format(tool,xd,yd)) 101 | print("") 102 | print("NOTE: Each G10 above will also have a Z component, not shown here.") 103 | 104 | 105 | ####################### 106 | # Main Code Starts Here 107 | ####################### 108 | th = threading.Thread(target=imageStream) 109 | th.daemon = True # Thread will be killed when main exits. 110 | th.start() 111 | 112 | userInterface() 113 | 114 | report() 115 | -------------------------------------------------------------------------------- /videoOnly.py: -------------------------------------------------------------------------------- 1 | # Python Script to align multiple tools on Jubilee printer 2 | # Using images from USB camera and user input 3 | # 4 | # Copyright (C) 2020 Danal Estes all rights reserved. 5 | # Released under The MIT License. Full text available via https://opensource.org/licenses/MIT 6 | # 7 | # Requires OpenCV to be installed on Pi 8 | # Requires running via the OpenCV installed python (that is why no shebang) 9 | import datetime 10 | import time 11 | import cv2 12 | import numpy as np 13 | import threading 14 | 15 | # initialize the video stream and allow the cammera sensor to warmup 16 | vs = cv2.VideoCapture(0) 17 | time.sleep(2.0) 18 | avg=[0,0] 19 | count=0 20 | x = [] 21 | y = [] 22 | 23 | def imageStream(): 24 | # loop over the frames from the video stream 25 | while True: 26 | (grabbed, frame) = vs.read() 27 | 28 | # draw the timestamp on the frame 29 | timestamp = datetime.datetime.now() 30 | ts = timestamp.strftime("%A %d %B %Y %I:%M:%S%p") 31 | cv2.putText(frame, ts, (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX,0.90, (255, 255, 255), 1) 32 | cv2.circle(frame,(320,240),25,(0,255,),3) 33 | # show the frame 34 | cv2.imshow("Nozzle", frame) 35 | key = cv2.waitKey(10) 36 | 37 | 38 | 39 | def userInterface(): 40 | print("") 41 | tc=input("Press enter to close the window when done. \n") 42 | 43 | ####################### 44 | # Main Code Starts Here 45 | ####################### 46 | th = threading.Thread(target=imageStream) 47 | th.daemon = True # Thread will be killed when main exits. 48 | th.start() 49 | 50 | userInterface() 51 | --------------------------------------------------------------------------------