main
unknown 2022-01-13 18:48:35 -06:00
rodzic 3007a25b6f
commit 1c7a8854d4
8 zmienionych plików z 874 dodań i 17 usunięć

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -618,18 +618,18 @@ def display_4_lines(pixels, frame, flip=False):
class GCodeSender:
def __init__(self, gCodeFile):
self.grbl = Gerbil(self.gerbil_callback)
self.grbl.setup_logging()
self.event = threading.Event()
self.gerbil = Gerbil(self.gerbil_callback)
self.gerbil.setup_logging()
ports = serial.tools.list_ports.comports()
for p in ports:
print(p.device)
self.grbl.cnect("/dev/ttyUSB0", 57600)
self.grbl.poll_start()
self.gerbil.cnect("COM4", 115200)
self.gerbil.poll_start()
self.gCodeFile = gCodeFile
self.event = threading.Event()
@ -639,12 +639,14 @@ class GCodeSender:
args.append(str(d))
print("GERBIL CALLBACK: event={} data={}".format(eventstring.ljust(30), ", ".join(args)))
self.curData = data
self.curEvent = eventstring
print(eventstring)
#indicate callback is done
self.event.set()
def home_machine(self):
self.gerbil.send_imediately("$H\n")
self.gerbil.send_immediately("$H\n")
pass
def zero_on_workpice(self, refPoints):
@ -652,28 +654,33 @@ class GCodeSender:
avgY = (refPoints[0][1] + refPoints[1][1] + refPoints[2][1] + refPoints[3][1]) / 4.0
# Set absolute positioning
self.gerbil.send_imediately("G53\n") # Absolute positioning
self.gerbil.send_imediately("G20\n") # Inches
self.gerbil.send_imediately("G0 Z-0.25\n") # Move close to Z limit
self.gerbil.send_immediately("G53\n") # Absolute positioning
time.sleep(1)
self.gerbil.send_immediately("G20\n") # Inches
time.sleep(1)
self.gerbil.send_immediately("G0 Z-0.25\n") # Move close to Z limit
time.sleep(1)
#Rapid traverse to above reference plate
self.gerbil.send_imediately("G0 X" + str(avgX) + " Y" + str(avgY) + "\n")
print("avgXY: " + str(avgX) + " " + str(avgY))
self.gerbil.send_immediately("G0 X" + str(avgX) + " Y" + str(avgY) + "\n")
time.sleep(1)
#Move down medium speed to reference plate
self.gerbil.send_imediately("G38.2 Z-3.75 F5.9\n")
self.gerbil.send_immediately("G38.2 Z-3.75 F5.9\n")
M114Resp = self.waitOnGCodeComplete("G38")
self.gerbil.send_imediately("M114")
self.gerbil.send_immediately("M114")
M114Resp = self.waitOnGCodeComplete("M114")
resultZ = re.search('Z[+-]?([0-9]*[.])?[0-9]+', M114Resp)
z = float(resultY.group()[1:])
#Move up, then slowly to reference plate
self.gerbil.send_imediately("G0 Z" + str(z + 0.25) + "\n") # Move just above reference plate
self.gerbil.send_imediately("G38.2 Z" + str(z - 0.125) + " F1.5\n") #Move down slowly
self.gerbil.send_immediately("G0 Z" + str(z + 0.25) + "\n") # Move just above reference plate
self.gerbil.send_immediately("G38.2 Z" + str(z - 0.125) + " F1.5\n") #Move down slowly
M114Resp = self.waitOnGCodeComplete("G38")
self.gerbil.send_imediately("G92 Z0")
self.gerbil.send_immediately("G92 Z0")
#Move up, then move to side of reference plate
#Move down, then over to side of reference plate
@ -689,7 +696,7 @@ class GCodeSender:
pass
def waitOnGCodeComplete(self, gCode):
resp = None
while Resp == None:
while resp == None:
self.event.wait()
if gCode in self.curData:
resp = self.curData["M114"]
@ -820,7 +827,7 @@ cv2.waitKey()
gCodeFile = 'test.nc'
cv2Overhead = cv2.warpPerspective(frame, bedPixelToPhysicalLoc, (frame.shape[1], frame.shape[0]))
cv2Overhead = cv2.resize(cv2Overhead, (bedViewSizePixels, bedViewSizePixels))
GCodeOverlay = OverlayGcode(cv2Overhead, gCodeFile)
GCodeOverlay = OverlayGcode(cv2Overhead, gCodeFile, False)
########################################
# Detect box location in overhead image
@ -832,6 +839,7 @@ bedPercent = refPhysicalLoc / [frame.shape[1], frame.shape[0]]
bedLoc = []
for a in bedPercent:
bedLoc.append(a[0] * [bedSize.X, bedSize.Y])
print("Bed Loc: " + str(bedLoc))
GCodeOverlay.set_ref_loc(bedLoc)
######################################################################

Wyświetl plik

@ -0,0 +1,849 @@
#TODO: use interpolateCornersCharuco to get better accuracy on corner detection
# import the necessary packages
import numpy as np
import argparse
import cv2
import time
import math
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
from svgpathtools import svg2paths, wsvg, svg2paths2, polyline
#import matplotlib
#matplotlib.use('GTK3Agg')
from matplotlib import pyplot as plt
from matplotlib.widgets import TextBox
from matplotlib.backend_bases import MouseButton
from copy import deepcopy
from pygcode import Machine, GCodeRapidMove, GCodeFeedRate, GCodeLinearMove, GCodeUseMillimeters
import pygcode
from pygcode.gcodes import MODAL_GROUP_MAP
import re
import sys
#sys.path.insert(1, 'C:\\Git\\gerbil\\')
#sys.path.insert(1, 'C:\\Git\\gcode_machine\\')
from gerbil import Gerbil
import serial.tools.list_ports
import threading
import functools
class Point3D:
def __init__(self, X, Y, Z = None):
self.X = X
self.Y = Y
self.Z = Z
def to2DComplex(self):
return self.X + self.Y * 1j
def distanceXY(self, point):
return distanceXY(self, point)
def __str__(self):
return str("(" + str(self.X) + "," + str(self.Y) + "," + str(self.Z) + ")")
def __repr__(self):
return str("(" + str(self.X) + "," + str(self.Y) + "," + str(self.Z) + ")")
global boxWidth
global rightBoxRef
global leftBoxRef
global bedSize
global bedViewSizePixels
boxWidth = 1.25
#These are distances from machine origin (0,0,0), right, back, upper corner.
rightBoxRef = Point3D(2.0, -35.0, 1.0)
leftBoxRef = Point3D(-37.0, -35.0, 1.0)
bedSize = Point3D(-35.0, -35.0, -3.75)
bedViewSizePixels = 1400
#First ID is upper right, which is most positive Z and most positice Y
# Z, Y
global idToLocDict
idToLocDict = {0 :[2,21],
1 :[2,19],
2 :[2,17],
3 :[2,16],
4 :[2,13],
5 :[2,11],
6 :[2, 9],
7 :[2, 7],
8 :[2, 5],
9 :[2, 3],
10:[2, 1],
11:[1, 20],
12:[1, 18],
13:[1, 16],
14:[1, 14],
15:[1, 12],
16:[1, 10],
17:[1, 8],
18:[1, 6],
19:[1, 4],
20:[1, 2],
21:[1, 0],
22:[0, 21],
23:[0, 19],
24:[0, 17],
25:[0, 15],
26:[0, 13],
27:[0, 11],
28:[0, 9],
29:[0, 7],
30:[0, 5],
31:[0, 3],
32:[0, 1],
33:[0, 20],
34:[0, 18],
35:[0, 16],
36:[0, 14],
37:[0, 12],
38:[0, 10],
39:[0, 8],
40:[0, 6],
41:[0, 4],
42:[0, 2],
43:[0, 0],
44:[1, 21],
45:[1, 19],
46:[1, 17],
47:[1, 15],
48:[1, 13],
49:[1, 11],
50:[1, 9],
51:[1, 7],
52:[1, 5],
53:[1, 3],
54:[1, 1],
55:[2, 20],
56:[2, 18],
57:[2, 16],
58:[2, 14],
59:[2, 12],
60:[2, 10],
61:[2, 8],
62:[2, 6],
63:[2, 4],
64:[2, 2],
65:[2, 0]}
####################################################################################
# Should put these in a shared libary
####################################################################################
def distanceXY(p1, p2):
return ((p1.X - p2.X)**2 + (p1.Y - p2.Y)**2)**0.5
def lineOrCurveToPoints3D(lineOrCurve, pointsPerCurve):
if isinstance(lineOrCurve,Line):
#print(lineOrCurve)
return [Point3D(lineOrCurve.bpoints()[0].real, lineOrCurve.bpoints()[0].imag), \
Point3D(lineOrCurve.bpoints()[1].real, lineOrCurve.bpoints()[1].imag)]
elif isinstance(lineOrCurve, CubicBezier):
points3D = []
for i in range(int(pointsPerCurve)):
complexPoint = lineOrCurve.point(i / (pointsPerCurve - 1.0))
points3D.append(Point3D(complexPoint.real, complexPoint.imag, None))
return points3D
elif isinstance(lineOrCurve, Arc):
points3D = []
for i in range(int(pointsPerCurve) * 10):
complexPoint = lineOrCurve.point(i / (pointsPerCurve * 10 - 1.0))
points3D.append(Point3D(complexPoint.real, complexPoint.imag, None))
return points3D
else:
print("unsuported type: " + str(lineOrCurve))
quit()
def pathToPoints3D(path, pointsPerCurve):
prevEnd = None
points3D = []
for lineOrCurve in path:
curPoints3D = lineOrCurveToPoints3D(lineOrCurve, pointsPerCurve)
#check that the last line ending starts the beginning of the next line.
#print(curPoints3D)
if prevEnd != None and distanceXY(curPoints3D[0], prevEnd) > 0.01:
print(curPoints3D[0])
print(prevEnd)
print("A SVG path must be contiguous, one line ending and beginning on the next line. Make a seperate path out of non contiguous lines")
quit()
elif prevEnd == None:
#first line, store both beginning point and end point
points3D.extend(curPoints3D)
else:
#add to point list except first one as it was verified to be same as ending of last
points3D.extend(curPoints3D[1:])
prevEnd = curPoints3D[-1]
return points3D
def arcToPoints(startX, startY, endX, endY, i, j, clockWise, curZ):
points = []
centerX = startX + i
centerY = startY + j
radius = math.dist([centerX, centerY], [startX, startY])
startAngle = math.atan2(startY - centerY, startX - centerX)
endAngle = math.atan2(endY - centerY, endX - centerX)
for angle in np.arange(startAngle, endAngle, (clockWise * -2 + 1) * 0.1):
x = math.cos(angle) * radius + centerX
y = math.sin(angle) * radius + centerY
points.append(Point3D(x, y, curZ))
x = math.cos(endAngle) * radius + centerX
y = math.sin(endAngle) * radius + centerY
points.append(Point3D(x, y, curZ))
return points
def rotate(origin, point, angle):
"""
Rotate a point counterclockwise by a given angle around a given origin.
The angle should be given in radians.
"""
ox, oy = origin
px, py = point
qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
return qx, qy
####################################################################################
# OverlayGcode class
####################################################################################
class OverlayGcode:
def __init__(self, cv2Overhead, gCodeFile, disableSender = True):
global bedViewSizePixels
global bedSize
self.bedViewSizePixels = bedViewSizePixels
self.bedSize = bedSize
self.xOffset = 0
self.yOffset = 0
self.rotation = 0
self.cv2Overhead = cv2.cvtColor(cv2Overhead, cv2.COLOR_BGR2RGB)
self.move = False
fig, ax = plt.subplots()
fig.tight_layout()
plt.subplots_adjust(bottom=0.01, right = 0.99)
plt.axis([self.bedViewSizePixels,0, self.bedViewSizePixels, 0])
#Generate matplotlib plot from opencv image
self.matPlotImage = plt.imshow(self.cv2Overhead)
###############################################
# Generate controls for plot
###############################################
xAxes = plt.axes([0.01, 0.8, 0.2, 0.04])
self.xBox = TextBox(xAxes, "xOffset (in)", initial="0")
label = self.xBox.ax.get_children()[1] # label is a child of the TextBox axis
label.set_position([0.5,1]) # [x,y] - change here to set the position
label.set_horizontalalignment('center')
label.set_verticalalignment('bottom')
self.xBox.on_submit(self.onUpdateXOffset)
yAxes = plt.axes([0.01, 0.7, 0.2, 0.04])
self.yBox = TextBox(yAxes, "yOffset (in)", initial="0")
label = self.yBox.ax.get_children()[1] # label is a child of the TextBox axis
label.set_position([0.5,1]) # [x,y] - change here to set the position
label.set_horizontalalignment('center')
label.set_verticalalignment('bottom')
self.yBox.on_submit(self.onUpdateYOffset)
rAxes = plt.axes([0.01, 0.6, 0.2, 0.04])
self.rBox = TextBox(rAxes, "rotation (deg)", initial="0")
label = self.rBox.ax.get_children()[1] # label is a child of the TextBox axis
label.set_position([0.5,1]) # [x,y] - change here to set the position
label.set_horizontalalignment('center')
label.set_verticalalignment('bottom')
self.rBox.on_submit(self.onUpdateRotation)
cid = fig.canvas.mpl_connect('button_press_event', self.onclick)
cid = fig.canvas.mpl_connect('button_release_event', self.onrelease)
cid = fig.canvas.mpl_connect('motion_notify_event', self.onmousemove)
cid = fig.canvas.mpl_connect('key_press_event', self.onkeypress)
#Create object to handle controlling the CNC machine and sending the g codes to it
if disableSender == False:
self.sender = GCodeSender(gCodeFile)
self.points = []
self.laserPowers = []
self.machine = Machine()
with open(gCodeFile, 'r') as fh:
for line_text in fh.readlines():
line = pygcode.Line(line_text)
prevPos = self.machine.pos
self.machine.process_block(line.block)
######################################
# First determine machine motion mode and power
######################################
motion = str(self.machine.mode.modal_groups[MODAL_GROUP_MAP['motion']])
sCode = str(self.machine.mode.modal_groups[MODAL_GROUP_MAP['spindle_speed']])
power = sCode.split('S')[1]
#Make rapid movements 0 laser power
if motion == "G00" or motion == "G0":
self.laserPowers.append(0.0)
else:
self.laserPowers.append(float(power) / 100.0)
######################################
# Determine machine current unit, convert to inches
######################################
unit = str(self.machine.mode.modal_groups[MODAL_GROUP_MAP['units']])
x = None
if unit == "G20":
if motion == "G02" or motion == "G2" or motion == "G03" or motion == "G3":
beforeComment = line_text.split("(")[0]
resultX = re.search('X[+-]?([0-9]*[.])?[0-9]+', beforeComment)
resultY = re.search('Y[+-]?([0-9]*[.])?[0-9]+', beforeComment)
resultI = re.search('I[+-]?([0-9]*[.])?[0-9]+', beforeComment)
resultJ = re.search('J[+-]?([0-9]*[.])?[0-9]+', beforeComment)
if resultX != None:
x = float(resultX.group()[1:])
y = float(resultY.group()[1:])
i = float(resultI.group()[1:])
j = float(resultJ.group()[1:])
clockWise = "G02" in motion or "G2" in motion
else:
self.points.append(Point3D(self.machine.pos.X, self.machine.pos.Y, self.machine.pos.Z))
else:
if motion == "G02" or motion == "G2" or motion == "G03" or motion == "G3":
resultX = re.search('X[+-]?([0-9]*[.])?[0-9]+', beforeComment)
resultY = re.search('Y[+-]?([0-9]*[.])?[0-9]+', beforeComment)
resultI = re.search('I[+-]?([0-9]*[.])?[0-9]+', beforeComment)
resultJ = re.search('J[+-]?([0-9]*[.])?[0-9]+', beforeComment)
if resultX != None:
x = float(resultX.group()[1:]) / 25.4
y = float(resultY.group()[1:]) / 25.4
i = float(resultI.group()[1:]) / 25.4
j = float(resultJ.group()[1:]) / 25.4
clockWise = "G02" in motion or "G2" in motion
else:
self.points.append(Point3D(self.machine.pos.X / 25.4, self.machine.pos.Y / 25.4, self.machine.pos.Z / 25.4))
if x != None:
self.points.extend(arcToPoints(prevPos.X, prevPos.Y, self.machine.pos.X, self.machine.pos.Y, i, j, clockWise, self.machine.pos.Z))
self.laserPowers.extend([self.laserPowers[-1]] * (len(self.points) - len(self.laserPowers)))
#scale mm to inches
#self.scalePoints(self.points, 1 / 25.4, 1 / 25.4)
#self.paths, attributes, svg_attributes = svg2paths2("C:\\Git\\svgToGCode\\project_StorageBox\\0p5in_BoxBacks_x4_35by32.svg")
#Generate a list of all points up front, non transformed
#self.points = []
#for path in self.paths:
# newPoints = pathToPoints3D(path, 10)
# self.points.extend(newPoints)
#scale mm to inches
#self.scalePoints(self.points, 1 / 25.4, 1 / 25.4)
self.updateOverlay()
def set_ref_loc(self, refPoints):
self.refPoints = refPoints
def scalePoints(self, points, scaleX, scaleY):
for point in points:
point.X = point.X * scaleX
point.Y = point.Y * scaleY
def offsetPoints(self, points, X, Y):
for point in points:
point.X = point.X + X
point.Y = point.Y + Y
def rotatePoints(self, points, origin, angle):
for point in points:
point.X, point.Y = rotate(origin, [point.X, point.Y], angle)
def overlaySvg(self, image, xOff = 0, yOff = 0, rotation = 0):
"""
image is opencv image
xOff is in inches
yOff is in inches
rotation is in degrees
"""
#convert to radians
rotation = rotation * math.pi / 180
overlay = image.copy()
#Make copy of points before transforming them
transformedPoints = deepcopy(self.points)
#Apply an offset in inches
self.offsetPoints(transformedPoints, xOff, yOff)
#Then rotate
self.rotatePoints(transformedPoints, [xOff, yOff], rotation)
#Then convert to pixel location
self.scalePoints(transformedPoints, self.bedViewSizePixels / self.bedSize.X, self.bedViewSizePixels / self.bedSize.Y)
prevPoint = None
for point, laserPower in zip(transformedPoints, self.laserPowers):
newPoint = (int(point.X), int(point.Y))
if prevPoint is not None:
cv2.line(overlay, prevPoint, newPoint, (int(laserPower * 255), 0, 0), 2)
prevPoint = newPoint
return overlay
def updateOverlay(self):
overlay = self.overlaySvg(self.cv2Overhead, self.xOffset, self.yOffset, self.rotation)
self.matPlotImage.set_data(overlay)
self.matPlotImage.figure.canvas.draw()
def onUpdateXOffset(self, text):
if self.xOffset == float(text):
return
self.xOffset = float(text)
self.updateOverlay()
def onUpdateYOffset(self, text):
if self.yOffset == float(text):
return
self.yOffset = float(text)
self.updateOverlay()
def onUpdateRotation(self, text):
if self.rotation == float(text):
return
self.rotation = float(text)
self.updateOverlay()
def onmousemove(self, event):
self.move = True
self.mouseX = event.xdata
self.mouseY = event.ydata
def onkeypress(self, event):
if event.key == 's':
self.sender.send_file(self.xOffset, self.yOffset, self.rotation)
elif event.key == 'h':
self.sender.home_machine()
elif event.key == 'z':
#Find X, Y, and Z position of the aluminum reference block on the work pice
#sepcify the X and Y estimated position of the reference block
self.sender.zero_on_workpice(self.refPoints)
elif event.key == 'm':
self.sender.move_to(self.mouseX / self.bedViewSizePixels * self.bedSize.X \
, self.mouseY / self.bedViewSizePixels * self.bedSize.Y)
def onclick(self, event):
self.move = False
def onrelease(self, event):
global matPlotImage
#If clicking outside region, or mouse moved since released then return
if event.x < 260 or self.move == True:
return
pixelsToOrigin = np.array([event.xdata, event.ydata])
if event.button == MouseButton.RIGHT:
xIn = pixelsToOrigin[0] / self.bedViewSizePixels * self.bedSize.X
yIn = pixelsToOrigin[1] / self.bedViewSizePixels * self.bedSize.Y
self.rotation = math.atan2(yIn - self.yOffset, xIn - self.xOffset)
self.rotation = self.rotation * 180 / math.pi
else:
self.xOffset = pixelsToOrigin[0] / self.bedViewSizePixels * self.bedSize.X
self.yOffset = pixelsToOrigin[1] / self.bedViewSizePixels * self.bedSize.Y
self.updateOverlay()
self.xBox.set_val(str(self.xOffset))
self.yBox.set_val(str(self.yOffset))
self.rBox.set_val(str(self.rotation))
def crop_half_vertically(img):
#cropped_img = image[,int(image.shape[1]/2):int(image.shape[1])]
#height = img.shape[0]
width = img.shape[1]
# Cut the image in half
width_cutoff = int(width // 2)
left = img[:, :width_cutoff]
right = img[:, width_cutoff:]
return left, right
def sortBoxPoints(points, rightSide = True):
#First sort by X
sortedX = sorted(points , key=lambda k: [k[0]])
#Then sorty by Y left and right most two X set of points
rightTwoPoints = sorted(sortedX[2:], key=lambda k: [k[1]])
leftTwoPoints = sorted(sortedX[0:2], key=lambda k: [k[1]])
if rightSide:
minZminY = leftTwoPoints[1]
minZmaxY = leftTwoPoints[0]
maxZmaxY = rightTwoPoints[0]
maxZminY = rightTwoPoints[1]
else:
minZminY = rightTwoPoints[1]
minZmaxY = rightTwoPoints[0]
maxZmaxY = leftTwoPoints[0]
maxZminY = leftTwoPoints[1]
return [minZminY, minZmaxY, maxZmaxY, maxZminY]
def get_id_loc(image, boxes, ids, ID):
for box, curID in zip(boxes, ids):
if curID != ID:
continue
boxPoints = box[0]
boxPointsSorted = np.array(sortBoxPoints(boxPoints))
return boxPointsSorted
return None
def boxes_to_point_and_location_list(boxes, ids, image, rightSide = False):
global boxWidth
global idToLocDict
pointList = []
locations = []
for box, ID in zip(boxes, ids):
#IDs below 33 are on right side, skip those if looking for left side points
if (rightSide == False and ID < 33) or \
(rightSide == True and ID >= 33) or \
(ID > 65):
continue
boxLoc = idToLocDict[ID[0]]
for boxPoints in box:
prevX = int(boxPoints[0][0])
prevY = int(boxPoints[0][1])
i = 0
font = cv2.FONT_HERSHEY_SIMPLEX
bottomLeftCornerOfText = prevX + 100,prevY
fontScale = 1
fontColor = (0,255,255)
thickness = 3
lineType = 2
cv2.putText(image,str(ID[0]),
bottomLeftCornerOfText,
font,
fontScale,
fontColor,
thickness,
lineType)
boxPointsSorted = sortBoxPoints(boxPoints, rightSide)
for point in boxPointsSorted:
############################################
# Generate list of points
############################################
pointList.append(point)
############################################
# Generate point location based on boxWidth and index within box
############################################
curLoc = [0,0]
if i == 0:
curLoc[0] = boxLoc[0] + 0
curLoc[1] = boxLoc[1] + 0
elif i ==1:
curLoc[0] = boxLoc[0] + 0
curLoc[1] = boxLoc[1] + 1
elif i == 2:
curLoc[0] = boxLoc[0] + 1
curLoc[1] = boxLoc[1] + 1
else:
curLoc[0] = boxLoc[0] + 1
curLoc[1] = boxLoc[1] + 0
if rightSide:
curLoc[0] = curLoc[0] * boxWidth + rightBoxRef.Z
curLoc[1] = curLoc[1] * boxWidth + rightBoxRef.Y
else:
curLoc[0] = curLoc[0] * boxWidth + leftBoxRef.Z
curLoc[1] = curLoc[1] * boxWidth + leftBoxRef.Y
locations.append(curLoc)
############################################
# Display points on image
############################################
x= int(point[0])
y= int(point[1])
image = cv2.arrowedLine(image, (prevX,prevY), (x,y),
(0,255,255), 5)
prevX = x
prevY = y
i = i + 1
return np.array(pointList), locations, image
def generate_dest_locations(corners, image):
global boxWidth
prevX=2000
prevY=2000
locations = []
yIndex = 0
xIndex = 0
for corner in corners:
x,y= corner
x= int(x)
y= int(y)
#cv2.rectangle(gray, (prevX,prevY),(x,y),(i*3,0,0),-1)
image = cv2.arrowedLine(image, (prevX,prevY), (x,y),
(200,0,0), 5)
locations.append([xIndex * boxWidth, yIndex * boxWidth])
if xIndex == 2:
xIndex = 0
yIndex = yIndex + 1
else:
xIndex = xIndex + 1
prevX = x
prevY = y
return locations, image
def pixel_loc_at_cnc_bed(phyToPixel):
global bedSize
points = np.array([[0,0],[bedSize.Z,0],[bedSize.Z,bedSize.Y],[0,bedSize.Y]])
return points, \
cv2.perspectiveTransform(points.reshape(-1,1,2), phyToPixel)
def display_4_lines(pixels, frame, flip=False):
line1 = tuple(pixels[0][0].astype(np.int))
line2 = tuple(pixels[1][0].astype(np.int))
if flip:
line3 = tuple(pixels[3][0].astype(np.int))
line4 = tuple(pixels[2][0].astype(np.int))
else:
line3 = tuple(pixels[2][0].astype(np.int))
line4 = tuple(pixels[3][0].astype(np.int))
cv2.line(frame, line1,line2,(0,255,255),3)
cv2.line(frame, line2,line3,(0,255,255),3)
cv2.line(frame, line3,line4,(0,255,255),3)
cv2.line(frame, line4,line1,(0,255,255),3)
class GCodeSender:
def __init__(self, gCodeFile):
self.event = threading.Event()
self.gerbil = Gerbil(self.gerbil_callback)
self.gerbil.setup_logging()
ports = serial.tools.list_ports.comports()
for p in ports:
print(p.device)
self.gerbil.cnect("COM4", 115200)
self.gerbil.poll_start()
self.gCodeFile = gCodeFile
def gerbil_callback(self, eventstring, *data):
args = []
for d in data:
args.append(str(d))
print("GERBIL CALLBACK: event={} data={}".format(eventstring.ljust(30), ", ".join(args)))
self.curData = data
self.curEvent = eventstring
print(eventstring)
#indicate callback is done
self.event.set()
def home_machine(self):
self.gerbil.send_immediately("$H\n")
pass
def zero_on_workpice(self, refPoints):
avgX = (refPoints[0][0] + refPoints[1][0] + refPoints[2][0] + refPoints[3][0]) / 4.0
avgY = (refPoints[0][1] + refPoints[1][1] + refPoints[2][1] + refPoints[3][1]) / 4.0
# Set absolute positioning
self.gerbil.send_immediately("G53\n") # Absolute positioning
self.gerbil.send_immediately("G20\n") # Inches
self.gerbil.send_immediately("G0 Z-0.25\n") # Move close to Z limit
#Rapid traverse to above reference plate
print("avgXY: " + str(avgX) + " " + str(avgY))
self.gerbil.send_immediately("G0 X" + str(avgX) + " Y" + str(avgY) + "\n")
#Move down medium speed to reference plate
self.gerbil.send_immediately("G38.2 Z-3.75 F5.9\n")
M114Resp = self.waitOnGCodeComplete("G38")
self.gerbil.send_immediately("M114")
M114Resp = self.waitOnGCodeComplete("M114")
resultZ = re.search('Z[+-]?([0-9]*[.])?[0-9]+', M114Resp)
z = float(resultY.group()[1:])
#Move up, then slowly to reference plate
self.gerbil.send_immediately("G0 Z" + str(z + 0.25) + "\n") # Move just above reference plate
self.gerbil.send_immediately("G38.2 Z" + str(z - 0.125) + " F1.5\n") #Move down slowly
M114Resp = self.waitOnGCodeComplete("G38")
self.gerbil.send_immediately("G92 Z0")
#Move up, then move to side of reference plate
#Move down, then over to side of reference plate
#Move back, then slowly to side of reference plate
#Move up, then over to other side
#Move up, then move to side of reference plate
#Move down, then over to side of reference plate
#Move back, then slowly to side of reference plate
#Move up, then to center of reference plate
pass
def waitOnGCodeComplete(self, gCode):
resp = None
while resp == None:
self.event.wait()
if gCode in self.curData:
resp = self.curData["M114"]
return
def move_to(self, x, y , z = None, feedRate = 100):
if z == None:
zStr = ""
else:
zStr = " Z" + str(z)
xStr = " X" + str(X)
yStr = " Y" + str(Y)
fStr = " F" + str(feedRate)
self.gerbil.send_immediately("G1" + xStr + yStr + zStr + fStr + "\n")
def send_file(self, xOffset, yOffset, rotation):
#Set to inches
self.gerbil.send_immediately("G20\n")
##########################################
#Offset work to desired offset
##########################################
self.gerbil.send_immediately("G54 X" + str(xOffset) + " Y" + str(yOffset) + "\n")
##########################################
#Rotate work to desired rotation
##########################################
deg = -rotation * 180 / math.pi
self.gerbil.send_immediately("G68 X0 Y0 R" + str(deg) + "\n")
#Set back to mm, typically the units g code assumes
self.gerbil.send_immediately("G21\n")
with open(self.gCodeFile, 'r') as fh:
for line_text in fh.readlines():
self.gerbil.stream(line_text)
# Turn off rotated coordinate system
self.gerbil.send_immediately("G69\n")
#############################################################################
# Main
#############################################################################
cap = cv2.VideoCapture(1, cv2.CAP_DSHOW) # Set Capture Device, in case of a USB Webcam try 1, or give -1 to get a list of available devices
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 800)
#Set Width and Height
# cap.set(3,1280)
# cap.set(4,720)
# Capture frame-by-frame
#ret, frame = cap.read()
frame = cv2.imread('cnc4.jpeg')
img = cv2.imread('cnc4.jpeg')
#######################################################################
# Get grayscale image above threshold
#######################################################################
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
########################################
# Get aruco box information
########################################
boxes, ids, rejectedImgPoints = cv2.aruco.detectMarkers(gray, cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_100))
print("boxes")
print(boxes)
print("ids")
print(ids)
pixelLoc = [None]*2
locations = [None]*2
pixelToPhysicalLoc = [None]*2
physicalToPixelLoc = [None]*2
pixelsAtBed = [None]*2
refPointsAtBed = [None]*2
########################################
# Determine vertical homography at left (i=0) and right (i=1) side of CNC machine
########################################
for i in range(0, 2):
pixelLoc[i], locations[i], frame = boxes_to_point_and_location_list(boxes, ids, frame, i == 1)
print(ids)
for location in locations[i]:
print(location)
########################################
#Determine forward and backward transformation through homography
########################################
pixelToPhysicalLoc[i], status = cv2.findHomography(np.array(pixelLoc[i]), np.array(locations[i]))
physicalToPixelLoc[i], status = cv2.findHomography(np.array(locations[i]), np.array(pixelLoc[i]))
#############################################################
# Draw vertical box on left and right vertical region of CNC
#############################################################
refPointsAtBed[i], pixelsAtBed[i] = pixel_loc_at_cnc_bed(physicalToPixelLoc[i])
display_4_lines(pixelsAtBed[i], frame)
#out = cv2.perspectiveTransform(np.array([[32.8125*7,32.8125*1],[32.8125*8,32.8125*1],[32.8125*8,32.8125*2],[32.8125*7,32.8125*2]]).reshape(-1,1,2), backward)
##########################################################################
# Get forward and backward homography from bed location to pixel location
##########################################################################
bedPixelCorners = np.array([[float(frame.shape[1]),0.0],[float(frame.shape[1]),float(frame.shape[0])],[0.0,0.0],[0.0,float(frame.shape[0])]])
refPixels = np.array([pixelsAtBed[0][1],pixelsAtBed[0][2],pixelsAtBed[1][1],pixelsAtBed[1][2]])
bedPhysicalToPixelLoc, status = cv2.findHomography(bedPixelCorners, refPixels)
bedPixelToPhysicalLoc, status = cv2.findHomography(refPixels, bedPixelCorners)
#############################################################
# Draw box on CNC bed
#############################################################
pixels = cv2.perspectiveTransform(bedPixelCorners.reshape(-1,1,2), bedPhysicalToPixelLoc)
display_4_lines(pixels, frame, flip=True)
#############################################################
# Display bed on original image
#############################################################
gray = cv2.resize(frame, (1280, 700))
cv2.imshow('image',gray)
cv2.waitKey()
######################################################################
# Warp perspective to perpendicular to bed view, create overlay calss
######################################################################
gCodeFile = 'test.nc'
cv2Overhead = cv2.warpPerspective(frame, bedPixelToPhysicalLoc, (frame.shape[1], frame.shape[0]))
cv2Overhead = cv2.resize(cv2Overhead, (bedViewSizePixels, bedViewSizePixels))
GCodeOverlay = OverlayGcode(cv2Overhead, gCodeFile, False)
########################################
# Detect box location in overhead image
########################################
#Change overhead image to gray for box detection
refPixelLoc = get_id_loc(frame, boxes, ids, 66)
refPhysicalLoc = cv2.perspectiveTransform(refPixelLoc.reshape(-1,1,2), bedPixelToPhysicalLoc)
bedPercent = refPhysicalLoc / [frame.shape[1], frame.shape[0]]
bedLoc = []
for a in bedPercent:
bedLoc.append(a[0] * [bedSize.X, bedSize.Y])
print("Bed Loc: " + str(bedLoc))
GCodeOverlay.set_ref_loc(bedLoc)
######################################################################
# Create a G Code sender now that overlay is created
######################################################################
plt.show()
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()