Import LinuxCNC source tree
This commit is contained in:
471
lib/python/plasmac/block.py
Normal file
471
lib/python/plasmac/block.py
Normal file
@@ -0,0 +1,471 @@
|
||||
'''
|
||||
block.py
|
||||
|
||||
Copyright (C) 2020 - 2025 Phillip A Carter
|
||||
Copyright (C) 2020 - 2024 Gregory D Carl
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import re
|
||||
import gettext
|
||||
import linuxcnc
|
||||
from rs274.glcanon import GlCanonDraw as DRAW
|
||||
|
||||
for f in sys.path:
|
||||
if '/lib/python' in f:
|
||||
if '/usr' in f:
|
||||
localeDir = 'usr/share/locale'
|
||||
else:
|
||||
localeDir = os.path.join(f'{f.split("/lib")[0]}', 'share', 'locale')
|
||||
break
|
||||
gettext.install("linuxcnc", localedir=localeDir)
|
||||
|
||||
STAT = linuxcnc.stat()
|
||||
|
||||
gcodePattern = re.compile(
|
||||
r"(;row(?P<OP>.[^X]+) *)?"
|
||||
r"([Gg] *(?P<G1>\d{1,2}) *)?"
|
||||
r"([Gg] *(?P<G2>\d{1,2}) *)?"
|
||||
r"([Xx] *(?P<X>-*\d+\.?\d*) *)?"
|
||||
r"([Yy] *(?P<Y>-*\d+\.?\d*) *)?"
|
||||
r"([Zz] *(?P<Z>(-*\d+\.?\d*)|([^;^(]*)) *)?"
|
||||
r"([Ii] *(?P<I>-*\d+\.?\d*) *)?"
|
||||
r"([Jj] *(?P<J>-*\d+\.?\d*) *)?"
|
||||
r"([Mm] *(?P<M>\d{1,3}) *)?"
|
||||
r"([Pp] *(?P<P>-*\d+\.?\d*) *)?"
|
||||
r"([Qq] *(?P<Q>-*\d+\.?\d*) *)?"
|
||||
r"((?P<C>[;(].+) *)?"
|
||||
)
|
||||
|
||||
# Conv is the upstream calling module
|
||||
def preview(Conv, fNgc, isConvBlock, window, columns, rows, cSpacing, rSpacing,
|
||||
xOffset, yOffset, angle, scale, rotation, doMirror, doFlip):
|
||||
''' preview the new block '''
|
||||
# validate inputs
|
||||
error = ''
|
||||
msg1 = _('entry is invalid')
|
||||
valid, columns = Conv.conv_is_int(columns)
|
||||
if not valid:
|
||||
msg0 = _('COLUMNS NUMBER')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, cSpacing = Conv.conv_is_float(cSpacing)
|
||||
if not valid and cSpacing:
|
||||
msg0 = _('COLUMNS SPACING')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, rows = Conv.conv_is_int(rows)
|
||||
if not valid:
|
||||
msg0 = _('ROWS NUMBER')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, rSpacing = Conv.conv_is_float(rSpacing)
|
||||
if not valid and rSpacing:
|
||||
msg0 = _('ROWS SPACING')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, xOffset = Conv.conv_is_float(xOffset)
|
||||
if not valid and xOffset:
|
||||
msg0 = _('X OFFSET')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, yOffset = Conv.conv_is_float(yOffset)
|
||||
if not valid and yOffset:
|
||||
msg0 = _('Y OFFSET')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, angle = Conv.conv_is_float(angle)
|
||||
if not valid and angle:
|
||||
msg0 = _('PATTERN ANGLE')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, scale = Conv.conv_is_float(scale)
|
||||
if not valid:
|
||||
msg0 = _('SHAPE SCALE')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
valid, rotation = Conv.conv_is_float(rotation)
|
||||
if not valid and rotation:
|
||||
msg0 = _('SHAPE ROTATION')
|
||||
error += f'{msg0} {msg1}\n\n'
|
||||
if error:
|
||||
return error
|
||||
# validate some entry values
|
||||
if columns <= 0:
|
||||
msg = _('COLUMNS NUMBER cannot be zero')
|
||||
error += f'{msg}\n\n'
|
||||
if rows <= 0:
|
||||
msg = _('ROWS NUMBER cannot be zero')
|
||||
error += f'{msg}\n\n'
|
||||
if columns > 1 and not cSpacing:
|
||||
msg = _('COLUMNS SPACING is required')
|
||||
error += f'{msg}\n\n'
|
||||
if rows > 1 and not rSpacing:
|
||||
msg = _('ROWS SPACING is required')
|
||||
error += f'{msg}\n\n'
|
||||
if scale <= 0:
|
||||
msg = _('SCALE cannot be zero')
|
||||
error += f'{msg}\n\n'
|
||||
if error:
|
||||
return error
|
||||
if isConvBlock:
|
||||
# this is a valid conversational block version:
|
||||
# get the inputs from the conversational entries
|
||||
# get current mirror and flip state from the file
|
||||
# get the gcode from the file
|
||||
inputsOld, preCode, gCode, postCode = get_v2_code(fNgc)
|
||||
inputs = [cSpacing, rSpacing, columns, rows, xOffset, yOffset, angle, scale, rotation,
|
||||
inputsOld[9], inputsOld[10], inputsOld[11], inputsOld[12]]
|
||||
# files converted from v1 will not have the mid points set yet
|
||||
# get the midpoints
|
||||
if inputsOld[11].strip() == '0' and inputsOld[12].strip() == '0':
|
||||
xMid, yMid = get_midpoints(window)
|
||||
# need the midpoints of the first shape only
|
||||
if columns > 1:
|
||||
xMid -= (columns - 1) * cSpacing / 2
|
||||
if rows > 1:
|
||||
yMid -= (rows - 1) * rSpacing / 2
|
||||
inputs[11] = f"{xMid:0.4f}"
|
||||
inputs[12] = f"{yMid:0.4f}"
|
||||
else:
|
||||
# this is not a conversational block:
|
||||
# get the inputs from the conversational entries
|
||||
# set the mirror and flip state to off
|
||||
# get the gcode from the file
|
||||
inputs = [cSpacing, rSpacing, columns, rows, xOffset, yOffset, angle, scale, rotation, 1, 1]
|
||||
preCode, gCode, postCode = get_raw_code(fNgc)
|
||||
# get the midpoints
|
||||
xMid, yMid = get_midpoints(window)
|
||||
inputs.extend([f"{xMid:0.4f}", f"{yMid:0.4f}"])
|
||||
write_block(fNgc, inputs, preCode, gCode, postCode, doMirror, doFlip)
|
||||
|
||||
def write_block(file, inputs, preCode, gCode, postCode, doMirror=False, doFlip=False):
|
||||
''' write the gcode for the block '''
|
||||
# write the file
|
||||
with open(file, 'w') as ngc:
|
||||
cSpacing = float(inputs[0])
|
||||
rSpacing = float(inputs[1])
|
||||
columns = int(inputs[2])
|
||||
rows = int(inputs[3])
|
||||
xOffset = float(inputs[4])
|
||||
yOffset = float(inputs[5])
|
||||
angle = float(inputs[6]) # pattern angle
|
||||
scale = float(inputs[7])
|
||||
rotation = float(inputs[8]) # shape rotation
|
||||
mirrored = int(inputs[9])
|
||||
flipped = int(inputs[10])
|
||||
xMid = float(inputs[11])
|
||||
yMid = float(inputs[12])
|
||||
outCode = [] # temporary storage for output gcode
|
||||
templateWritten = False # write all subsequent output gcode to temporary storage
|
||||
# set mirror
|
||||
mirror = -1 if (doMirror and mirrored != -1) or (not doMirror and mirrored == -1) else 1
|
||||
# set flip
|
||||
flip = -1 if (doFlip and flipped != -1) or (not doFlip and flipped == -1) else 1
|
||||
outCode.append('\n;CONV_BLOCK GCODE')
|
||||
# iterate through the column and row combination
|
||||
for row in range(rows):
|
||||
for col in range(columns):
|
||||
# set start coordinates
|
||||
xStart, yStart = rotate_point(angle, (col * cSpacing * scale, row * rSpacing * scale))
|
||||
xStart += xOffset
|
||||
yStart += yOffset
|
||||
if templateWritten:
|
||||
ngc.write(f'\n;row{row} col{col} - origin: X{xStart:0.5f} Y{yStart:0.5f}\n')
|
||||
else:
|
||||
outCode.append(f'\n;row{row} col{col} - origin: X{xStart:0.4f} Y{yStart:0.4f}')
|
||||
gCurrent = None # the current active gcode
|
||||
xCurrent = 0.0 # the current original x coordinate
|
||||
yCurrent = 0.0 # the current original y coordinate
|
||||
previous = {} # the previous original and rotated x and y coordinates
|
||||
# iterate through the gcode
|
||||
for line in gCode:
|
||||
line = line.strip()
|
||||
# create the code dictionary
|
||||
code = gcodePattern.match(line).groupdict()
|
||||
# set the current gcode
|
||||
if code['G1']:
|
||||
gCurrent = code['G1'] if code['G1'] in ['00', '01', '02', '03'] else None
|
||||
elif code['M']:
|
||||
gCurrent = None
|
||||
# if rapid, line, or arc
|
||||
if gCurrent in ['00', '01', '02', '03']:
|
||||
# reverse arc direction only if one of mirror or flip
|
||||
if mirror != flip:
|
||||
if 'G02' in line:
|
||||
line = line.replace('G02', 'G03')
|
||||
elif 'G03' in line:
|
||||
line = line.replace('G03', 'G02')
|
||||
# set the current x and y coordinates
|
||||
xCurrent = xCurrent if not code['X'] else float(code['X'])
|
||||
yCurrent = yCurrent if not code['Y'] else float(code['Y'])
|
||||
# rotate end point for pattern angle
|
||||
x, y = rotate_point(angle, (xCurrent * scale * mirror, yCurrent * scale * flip))
|
||||
# rotate end point for shape rotation
|
||||
x, y = rotate_point(rotation, (x, y), (xMid, yMid))
|
||||
# save the calculated x and y points
|
||||
line=line.replace(f"x{code['X']}", f' X{xStart + x:0.5f}').replace(f"X{code['X']}", f' X{xStart + x:0.5f}')
|
||||
line=line.replace(f"y{code['Y']}", f' Y{yStart + y:0.5f}').replace(f"Y{code['Y']}", f' Y{yStart + y:0.5f}')
|
||||
# if this is an arc
|
||||
if code['I'] or code['J']:
|
||||
# set new i and j relative coordinates
|
||||
ir = 0.0 if not code['I'] else float(code['I'])
|
||||
yr = 0.0 if not code['J'] else float(code['J'])
|
||||
# set arc absolute center points
|
||||
xCenter = previous['xOriginal'] + ir
|
||||
yCenter = previous['yOriginal'] + yr
|
||||
# rotate arc center point for pattern angle
|
||||
xCenter, yCenter = rotate_point(angle, (xCenter * scale * mirror, yCenter * scale * flip))
|
||||
# rotate arc center point for shape rotation
|
||||
xCenter, yCenter = rotate_point(rotation, (xCenter, yCenter), (xMid, yMid))
|
||||
# set the relative i and j cordinates
|
||||
i = xCenter - previous['xRotated']
|
||||
j = yCenter - previous['yRotated']
|
||||
# save the calculated i and j coordinates
|
||||
line=line.replace(f"i{code['I']}", f' I{i:0.5f}').replace(f"I{code['I']}", f' I{i:0.5f}')
|
||||
line=line.replace(f"j{code['J']}", f' J{j:0.5f}').replace(f"J{code['J']}", f' J{j:0.5f}')
|
||||
previous['xOriginal'] = xCurrent
|
||||
previous['yOriginal'] = yCurrent
|
||||
previous['xRotated'] = x
|
||||
previous['yRotated'] = y
|
||||
# write output gcode to file
|
||||
if templateWritten:
|
||||
ngc.write(f'{line}\n')
|
||||
# write output gcode to temporary storage and test if template is ready to be written
|
||||
else:
|
||||
if mirror or flip:
|
||||
outCode.append(line)
|
||||
inputs[9] = mirror
|
||||
inputs[10] = flip
|
||||
# write the header to the file
|
||||
ngc.write(';conversational block V2\n')
|
||||
# write the version 2 template to the file
|
||||
ngc.write(f'\n;CONV_BLOCK TEMPLATE START *****************************\n')
|
||||
ngc.write(f'\n;CONV_BLOCK INPUTS\n;')
|
||||
ins = f'{float(inputs[0]):0.4f}'
|
||||
for i in range(1, len(inputs)):
|
||||
if i in [2, 3, 9, 10]:
|
||||
ins += f', {inputs[i]}'
|
||||
else:
|
||||
ins += f', {float(inputs[i]):0.4f}'
|
||||
#ins += f', {inputs[i]}'
|
||||
ngc.write(f'{ins}')
|
||||
ngc.write(f'\n\n;CONV_BLOCK PRECODE\n')
|
||||
for line in preCode:
|
||||
ngc.write(f';{line.replace(";;", ";")}\n')
|
||||
ngc.write('\n;CONV_BLOCK GCODE\n')
|
||||
for line in gCode:
|
||||
ngc.write(f';{line.replace(";;", ";")}\n')
|
||||
ngc.write('\n;CONV_BLOCK POSTCODE\n')
|
||||
for line in postCode:
|
||||
ngc.write(f';{line.replace(";;", ";")}\n')
|
||||
ngc.write(f';CONV_BLOCK TEMPLATE END *******************************\n\n')
|
||||
# write the precode to the file
|
||||
ngc.write(f'\n;CONV_BLOCK PRECODE\n')
|
||||
for line in preCode:
|
||||
ngc.write(f'{line}\n')
|
||||
for line in outCode:
|
||||
ngc.write(f'{line}\n')
|
||||
# write all subsequent output gcode directly to file
|
||||
templateWritten = True
|
||||
else:
|
||||
outCode.append(line)
|
||||
# write the postcode to the file
|
||||
ngc.write('\n;CONV_BLOCK POSTCODE\n')
|
||||
for line in postCode:
|
||||
ngc.write(f'{line}\n')
|
||||
ngc.write(';qtplasmac filtered G-code file\n')
|
||||
return False
|
||||
|
||||
def rotate_point(rotation, point, origin=(0, 0)):
|
||||
''' rotate a point counterclockwise by rotation degrees '''
|
||||
angle = math.radians(rotation)
|
||||
xP, yP = point
|
||||
xO, yO = origin
|
||||
x = xO + math.cos(angle) * (xP - xO) - math.sin(angle) * (yP - yO)
|
||||
y = yO + math.sin(angle) * (xP - xO) + math.cos(angle) * (yP - yO)
|
||||
return (x, y)
|
||||
|
||||
def get_midpoints(window):
|
||||
''' retrive the midpoints of the shape '''
|
||||
STAT.poll()
|
||||
mult = 1
|
||||
if STAT.linear_units == 1:
|
||||
mult = 25.4
|
||||
xMid = DRAW.extents_info(window)[0][0] * mult - STAT.g5x_offset[0]
|
||||
yMid = DRAW.extents_info(window)[0][1] * mult - STAT.g5x_offset[1]
|
||||
return xMid, yMid
|
||||
|
||||
def set_to_upper_case(data):
|
||||
''' set gcode words to upper case '''
|
||||
tmp = ''
|
||||
keep = False
|
||||
for d in data:
|
||||
if d in '#(':
|
||||
keep = True
|
||||
tmp += d
|
||||
elif d in '>)':
|
||||
keep = False
|
||||
tmp += d
|
||||
else:
|
||||
if keep:
|
||||
tmp += d
|
||||
else:
|
||||
tmp += d.upper()
|
||||
return tmp
|
||||
|
||||
def get_raw_code(file):
|
||||
''' read qtplasmac format file to allow block manipulation
|
||||
file has been filtered so no preprocessing is required '''
|
||||
preCode, gCode, postCode, outCode = [], [], [], 'precode'
|
||||
with open(file, 'r') as inCode:
|
||||
raw = inCode.read()
|
||||
pierce = raw.count('M03')
|
||||
m03 = False
|
||||
with open(file, 'r') as inCode:
|
||||
for line in inCode:
|
||||
line = line.strip()
|
||||
if 'M03' in line:
|
||||
m03 = True
|
||||
if outCode == 'precode':
|
||||
preCode.append(line)
|
||||
if '#<_hal[plasmac.cut-feed-rate]>' in line:
|
||||
outCode = 'gcode'
|
||||
elif outCode == 'gcode':
|
||||
# no need to process comments or empty lines
|
||||
if line and line[0] in ';(':
|
||||
gCode.append(line)
|
||||
continue
|
||||
elif 'M05' in line:
|
||||
if m03: # or 'postamble' in line:
|
||||
m03 = False
|
||||
pierce -= 1
|
||||
if not pierce:
|
||||
outCode = 'postcode'
|
||||
gCode.append(line)
|
||||
else:
|
||||
gCode.append(line)
|
||||
else:
|
||||
if 'M68' in line:
|
||||
gCode.append(line)
|
||||
else:
|
||||
postCode.append(line)
|
||||
return(preCode, gCode, postCode)
|
||||
|
||||
def get_v2_code(file):
|
||||
''' read new format conversational block files '''
|
||||
inputs, preCode, gCode, postCode, outCode = [], [], [], [], None
|
||||
with open(file, 'r') as inCode:
|
||||
for line in inCode:
|
||||
line = line.strip()
|
||||
if outCode is None:
|
||||
if ';CONV_BLOCK INPUTS' in line:
|
||||
outCode = 'inputs'
|
||||
elif outCode == 'inputs':
|
||||
if ';CONV_BLOCK PRECODE' in line:
|
||||
outCode = 'precode'
|
||||
elif line:
|
||||
inputs = line[1:].split(', ')
|
||||
elif outCode == 'precode':
|
||||
if ';CONV_BLOCK GCODE' in line:
|
||||
outCode = 'gcode'
|
||||
elif line:
|
||||
preCode.append(line[1:])
|
||||
elif outCode == 'gcode':
|
||||
if ';CONV_BLOCK POSTCODE' in line:
|
||||
outCode = 'postCode'
|
||||
elif line:
|
||||
gCode.append(line[1:])
|
||||
elif outCode == 'postCode':
|
||||
if ';CONV_BLOCK TEMPLATE END' in line:
|
||||
outCode = 'completed'
|
||||
else:
|
||||
postCode.append(line[1:])
|
||||
return(inputs, preCode, gCode, postCode)
|
||||
|
||||
def convert_v1_code(file):
|
||||
''' convert old format conversational block files '''
|
||||
inputs, preCode, gCode, postCode, outCode = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [], [], [], None
|
||||
mirror = '*#<blk_scale>*#<shape_mirror>]'
|
||||
flip = '*#<blk_scale>*#<shape_flip>]'
|
||||
with open(file, 'r') as raw:
|
||||
check = raw.read().upper()
|
||||
pierces = sum(check.count(x) for x in ('M3', 'M03')) - check.count('M30')
|
||||
with open(file, 'r') as inCode:
|
||||
for line in inCode:
|
||||
# strip leading and trailing whitespace
|
||||
line = line.strip()
|
||||
# set to uppercase
|
||||
line = set_to_upper_case(line)
|
||||
# add leading 0's to short G & M codes
|
||||
if len(line) == 2 and line[0] in 'gmGM':
|
||||
line = f'{line[0].upper()}0{line[1]}'
|
||||
elif len(line) > 3 and ('g' in line or 'G' in line or 'm' in line or 'M' in line):
|
||||
tmp = ''
|
||||
while len(line) > 1:
|
||||
if line[0] in 'gmGM' and line[1].isdigit() and not line[2].isdigit():
|
||||
tmp += f'{line[0].upper()}0{line[1]}'
|
||||
line = line[2:]
|
||||
else:
|
||||
tmp += line[0]
|
||||
line = line[1:]
|
||||
tmp += line
|
||||
line = tmp
|
||||
# get old inputs
|
||||
if outCode is None:
|
||||
if line[:6] in ['g10 l2', 'G10 L2']:
|
||||
outCode = 'precode'
|
||||
if line.startswith('#<array_x_offset>'):
|
||||
inputs[0] = line.split("=")[1].strip()
|
||||
elif line.startswith('#<array_y_offset>'):
|
||||
inputs[1] = line.split("=")[1].strip()
|
||||
elif line.startswith('#<array_columns>'):
|
||||
inputs[2] = line.split('=')[1].strip()
|
||||
elif line.startswith('#<array_rows>'):
|
||||
inputs[3] = line.split('=')[1].strip()
|
||||
elif line.startswith('#<origin_x_offset>'):
|
||||
inputs[4] = line.split("=")[1].strip()
|
||||
elif line.startswith('#<origin_y_offset>'):
|
||||
inputs[5] = line.split("=")[1].strip()
|
||||
elif line.startswith('#<array_angle>'):
|
||||
inputs[6] = line.split('=')[1].strip()
|
||||
elif line.startswith('#<blk_scale>'):
|
||||
inputs[7] = line.split('=')[1].strip()
|
||||
elif line.startswith('#<shape_angle>'):
|
||||
inputs[8] = line.split('=')[1].strip()
|
||||
elif line.startswith('#<shape_mirror>'):
|
||||
inputs[9] = line.split('=')[1].strip()
|
||||
elif line.startswith('#<shape_flip>'):
|
||||
inputs[10] = line.split('=')[1].strip()
|
||||
elif outCode == 'precode':
|
||||
if line[:3] in ['g00', 'G00']:
|
||||
outCode = 'gcode'
|
||||
gCode.append(line.replace('[', '').replace(mirror, '').replace(flip, ''))
|
||||
elif line:
|
||||
preCode.append(line)
|
||||
elif outCode == 'gcode':
|
||||
if line[:3] in ['m05', 'M05']:
|
||||
pierces -= 1
|
||||
if not pierces:
|
||||
outCode = 'postcode'
|
||||
gCode.append(line)
|
||||
elif line:
|
||||
gCode.append(line.replace('[', '').replace(mirror, '').replace(flip, ''))
|
||||
elif outCode == 'postcode':
|
||||
if '#<this_col>' in line:
|
||||
outCode = 'invalid'
|
||||
postCode.append('M02')
|
||||
break
|
||||
elif line[:3] in ['m68', 'M68']:
|
||||
gCode.append(line)
|
||||
elif line:
|
||||
postCode.append(line)
|
||||
return inputs, preCode, gCode, postCode
|
||||
Reference in New Issue
Block a user