Import LinuxCNC source tree

This commit is contained in:
Wang Dequan
2026-08-20 06:17:39 +08:00
commit 2c814634c3
9429 changed files with 10556013 additions and 0 deletions

471
lib/python/plasmac/block.py Normal file
View 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

View File

@@ -0,0 +1,225 @@
'''
bolt_circle.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength, shapeAng,
isCenter, xOffset, yOffset,
kerfWidth,
isOvercut, overCut,
smallHoleDia, smallHoleSpeed,
circleDia, holeDia, holeNum, circleAng,
invalidLeads):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, circleDia = Conv.conv_is_float(circleDia)
if not valid and circleDia:
msg0 = _('DIAMETER')
error += f'{msg0} {msg1}\n\n'
valid, holeDia = Conv.conv_is_float(holeDia)
if not valid and holeDia:
msg0 = _('HOLE DIAMETER')
error += f'{msg0} {msg1}\n\n'
valid, holeNum = Conv.conv_is_int(holeNum)
if not valid and holeNum:
msg0 = _('# OF HOLES')
error += f'{msg0} {msg1}\n\n'
valid, shapeAng = Conv.conv_is_float(shapeAng)
if not valid and shapeAng:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, circleAng = Conv.conv_is_float(circleAng)
if not valid and circleAng:
msg0 = _('CIRCLE ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, oclength = Conv.conv_is_float(overCut)
if not valid and oclength:
msg0 = _('OC LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg0 = _('Invalid Kerf Width entry in material')
error += f'{msg0} {msg1}\n\n'
if error:
return error
if circleDia == 0:
msg = _('DIAMETER cannot be zero')
error += f'{msg}\n\n'
if holeDia == 0:
msg = _('HOLE DIAMETER cannot be zero')
error += f'{msg}\n\n'
if holeNum == 0:
msg = _('# OF HOLES cannot be zero')
error += f'{msg}\n\n'
if circleAng == 0:
msg = _('CIRCLE ANG cannot be zero')
error += f'{msg}\n\n'
if error:
return error
cRadius = circleDia / 2
if circleAng == 360:
hAngle = math.radians(circleAng / holeNum)
else:
hAngle = math.radians(circleAng / (holeNum - 1))
kOffset = kerfWidth / 2
hRadius = (holeDia / 2) - kOffset
angle = math.radians(shapeAng)
leadinOffset = leadinLength * math.sin(math.radians(45))
leadoutOffset = leadoutLength * math.sin(math.radians(45))
if leadinOffset > hRadius - kOffset:
leadinOffset = hRadius - kOffset
if holeDia < smallHoleDia:
sHole = True
else:
sHole = False
if isCenter:
xC = xOffset
yC = yOffset
else:
xC = xOffset + cRadius
yC = yOffset + cRadius
right = math.radians(0)
up = math.radians(90)
down = math.radians(270)
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
for hole in range(holeNum):
outTmp.write(f'\n(conversational bolt circle, hole #{hole + 1})\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
xhC = xC + cRadius * math.cos(hAngle * hole + angle)
yhC = yC + cRadius * math.sin(hAngle * hole + angle)
xS = xhC - hRadius
yS = yhC
if sHole or invalidLeads == 2:
xlStart = xS + leadinOffset
ylStart = yhC
outTmp.write(f'G00 x{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
if sHole:
outTmp.write(f'M67 E3 Q{smallHoleSpeed}\n')
else:
xlCentre = xS + (leadinOffset * math.cos(angle + right))
ylCentre = yS + (leadinOffset * math.sin(angle + right))
xlStart = xlCentre + (leadinOffset * math.cos(angle + up))
ylStart = ylCentre + (leadinOffset * math.sin(angle + up))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
if leadinLength:
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{hRadius:.6f}\n')
torch = True
if sHole:
if isOvercut:
torch = False
outTmp.write('M62 P3 (disable torch)\n')
centerX = xS + hRadius
centerY = yS
cosA = math.cos(oclength / hRadius)
sinA = math.sin(oclength / hRadius)
cosB = (xS - centerX) / hRadius
sinB = (yS - centerY) / hRadius
endX = centerX + hRadius * ((cosB * cosA) - (sinB * sinA))
endY = centerY + hRadius * ((sinB * cosA) + (cosB * sinA))
outTmp.write(f'G03 X{endX:.6f} Y{endY:.6f} I{hRadius:.6f} J{0:.6f}\n')
else:
if leadoutLength and not invalidLeads:
xlCentre = xS + (leadoutOffset * math.cos(angle + right))
ylCentre = yS + (leadoutOffset * math.sin(angle + right))
xlStart = xlCentre + (leadoutOffset * math.cos(angle + down))
ylStart = ylCentre + (leadoutOffset * math.sin(angle + down))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
if leadinLength:
outTmp.write(f'G02 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
outTmp.write('M05 $0\n')
if sHole:
outTmp.write('M68 E3 Q0 (reset feed rate to 100%)\n')
if not torch:
torch = True
outTmp.write('M65 P3 (enable torch)\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False

View File

@@ -0,0 +1,211 @@
'''
circle.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
isCenter, xOffset, yOffset,
kerfWidth, isExternal,
isOvercut, overCut,
smallHoleDia, smallHoleSpeed,
diameter, invalidLeads):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, diameter = Conv.conv_is_float(diameter)
if not valid and diameter:
msg0 = _('DIAMETER')
error += f'{msg0} {msg1}\n\n'
valid, overCut = Conv.conv_is_float(overCut)
if not valid and overCut:
msg0 = _('OC LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if diameter == 0:
msg = _('DIAMETER cannot be zero')
error += f'{msg}\n\n'
if error:
return error
kOffset = kerfWidth / 2
radius = diameter / 2
angle = math.radians(45)
leadInOffset = math.sin(angle) * leadinLength
leadOutOffset = math.sin(math.radians(45)) * leadoutLength
if leadInOffset > radius - kOffset:
leadInOffset = radius - kOffset
if isExternal:
ijOffset = (radius + kOffset) * math.sin(angle)
else:
ijOffset = (radius - kOffset) * math.sin(angle)
if isCenter:
xC = xOffset
yC = yOffset
else:
if isExternal:
xC = xOffset + radius + kOffset
yC = yOffset + radius + kOffset
else:
xC = xOffset + radius - kOffset
yC = yOffset + radius - kOffset
xS = xC - ijOffset
yS = yC - ijOffset
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
if isExternal:
dir = [left, down]
else:
dir = [right, up]
if diameter < smallHoleDia:
sHole = True
else:
sHole = False
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational circle)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if leadInOffset:
if (sHole and not isExternal) or invalidLeads == 2:
xlStart = xS + leadInOffset * math.cos(angle)
ylStart = yS + leadInOffset * math.sin(angle)
else:
xlcenter = xS + (leadInOffset * math.cos(angle + dir[0]))
ylcenter = yS + (leadInOffset * math.sin(angle + dir[0]))
xlStart = xlcenter + (leadInOffset * math.cos(angle + dir[1]))
ylStart = ylcenter + (leadInOffset * math.sin(angle + dir[1]))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
if sHole:
outTmp.write(f'M67 E3 Q{smallHoleSpeed} (reduce feed rate to 60%)\n')
if (sHole and not isExternal) or invalidLeads == 2:
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
else:
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlcenter - xlStart:.6f} J{ylcenter - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write('M3 $0 S1\n')
if sHole:
outTmp.write(f'M67 E3 Q{smallHoleSpeed} (reduce feed rate to 60%)\n')
if isExternal:
outTmp.write(f'G02 X{xS:.6f} Y{yS:.6f} I{ijOffset:.6f} J{ijOffset:.6f}\n')
else:
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{ijOffset:.6f} J{ijOffset:.6f}\n')
if leadOutOffset and not isOvercut and not (not isExternal and sHole) and not invalidLeads:
if isExternal:
dir = [left, up]
else:
dir = [right, down]
xlcenter = xS + (leadOutOffset * math.cos(angle + dir[0]))
ylcenter = yS + (leadOutOffset * math.sin(angle + dir[0]))
xlEnd = xlcenter + (leadOutOffset * math.cos(angle + dir[1]))
ylEnd = ylcenter + (leadOutOffset * math.sin(angle + dir[1]))
outTmp.write(f'G03 X{xlEnd:.6f} Y{ylEnd:.6f} I{xlcenter - xS:.6f} J{ylcenter - yS:.6f}\n')
torch = True
if isOvercut and sHole and not isExternal:
torch = False
outTmp.write('M62 P3 (disable torch)\n')
centerX = xS + ijOffset
centerY = yS + ijOffset
cosA = math.cos(overCut / radius)
sinA = math.sin(overCut / radius)
cosB = ((xS - centerX) / radius)
sinB = ((yS - centerY) / radius)
endX = centerX + radius * ((cosB * cosA) - (sinB * sinA))
endY = centerY + radius * ((sinB * cosA) + (cosB * sinA))
outTmp.write(f'G03 X{endX:.6f} Y{endY:.6f} I{ijOffset:.6f} J{ijOffset:.6f}\n')
outTmp.write('M05 $0\n')
if sHole:
outTmp.write('M68 E3 Q0 (reset feed rate to 100%)\n')
if not torch:
torch = True
outTmp.write('M65 P3 (enable torch)\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False

View File

@@ -0,0 +1,213 @@
'''
ellipse.py
Copyright (C) 2021 - 2024 Phillip A Carter
Copyright (C) 2021 - 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 numpy
import gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
isCenter, xOffset, yOffset,
kerfWidth, isExternal,
width, height, angle, unitsPerMm):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, width = Conv.conv_is_float(width)
if not valid and width:
msg0 = _('WIDTH')
error += f'{msg0} {msg1}\n\n'
valid, height = Conv.conv_is_float(height)
if not valid and height:
msg0 = _('HEIGHT')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if width == 0:
msg = _('WIDTH cannot be zero')
error += f'{msg}\n\n'
if height == 0:
msg = _('HEIGHT cannot be zero')
error += f'{msg}\n\n'
if error:
return error
angle = numpy.radians(angle)
if isExternal:
width += kerfWidth
height += kerfWidth
else:
width -= kerfWidth
height -= kerfWidth
if isCenter:
centerX = xOffset
centerY = yOffset
else:
centerX = xOffset + width / 2
centerY = yOffset + height / 2
leadInOffset = numpy.sin(45) * leadinLength
leadOutOffset = numpy.sin(45) * leadoutLength
# approx perimeter in mm
perim = (numpy.pi * (3 * (width + height) - numpy.sqrt((3 * width + height) * (width + 3 * height)))) * unitsPerMm
# number of points is 360 unless perimeter is > 360mm then have a segment length of 1mm
points = 360 if perim <= 360 else int(perim)
mult = float(360) / points
# start/end point of cut
start = int(points * 0.625)
# create the ellipse points in an array
array = numpy.linspace(0, points, points)
X = centerX + width / 2 * numpy.cos(numpy.radians(array * mult))
Y = centerY + height / 2 * numpy.sin(numpy.radians(array * mult))
# rotate the ellipse if required
for point in range(points):
x = X[point]
y = Y[point]
X[point] = x * numpy.cos(angle) - y * numpy.sin(angle)
Y[point] = x * numpy.sin(angle) + y * numpy.cos(angle)
# initialize files
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
# begin writing the gcode
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational ellipse)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
# get the angle of the first segment
if isExternal:
dX = X[start - 1] - X[start]
dY = Y[start - 1] - Y[start]
else:
dX = X[start] - X[start + 1]
dY = Y[start] - Y[start + 1]
segAngle = numpy.arctan2(dY, dX)
# set direction constants
right = numpy.radians(0)
up = numpy.radians(90)
left = numpy.radians(180)
down = numpy.radians(270)
# leadin points if required
if leadInOffset > 0:
if isExternal:
dir = [up, left]
else:
dir = [down, right]
xlcenter = X[start] + (leadInOffset * numpy.cos(segAngle + dir[0]))
ylcenter = Y[start] + (leadInOffset * numpy.sin(segAngle + dir[0]))
xlStart = xlcenter + (leadInOffset * numpy.cos(segAngle + dir[1]))
ylStart = ylcenter + (leadInOffset * numpy.sin(segAngle + dir[1]))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{X[start]:.6f} Y{Y[start]:.6f} I{xlcenter - xlStart:.6f} J{ylcenter - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{X[start]:.6f} Y{Y[start]:.6f}\n')
outTmp.write('M03 $0 S01\n')
# write the ellipse points
if isExternal:
for point in range(start, -1, -1):
outTmp.write(f'G01 X{X[point]:.6f} Y{Y[point]:.6f}\n')
for point in range(points - 1, start - 1, -1):
outTmp.write(f'G01 X{X[point]:.6f} Y{Y[point]:.6f}\n')
else:
for point in range(start, points, 1):
outTmp.write(f'G01 X{X[point]:.6f} Y{Y[point]:.6f}\n')
for point in range(0, start + 1, 1):
outTmp.write(f'G01 X{X[point]:.6f} Y{Y[point]:.6f}\n')
# leadout points if required
if leadOutOffset:
if isExternal:
dir = [up, right]
else:
dir = [down, left]
xlcenter = X[start] + (leadOutOffset * numpy.cos(segAngle + dir[0]))
ylcenter = Y[start] + (leadOutOffset * numpy.sin(segAngle + dir[0]))
xlEnd = xlcenter + (leadOutOffset * numpy.cos(segAngle + dir[1]))
ylEnd = ylcenter + (leadOutOffset * numpy.sin(segAngle + dir[1]))
outTmp.write(f'G03 X{xlEnd:.6f} Y{ylEnd:.6f} I{xlcenter - X[start]:.6f} J{ylcenter - Y[start]:.6f}\n')
# finish off and close files
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False

View File

@@ -0,0 +1,280 @@
'''
gusset.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
xOffset, yOffset,
kerfWidth, isExternal,
width, height, angle, radius, rButton):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, width = Conv.conv_is_float(width)
if not valid and width:
msg0 = _('WIDTH')
error += f'{msg0} {msg1}\n\n'
valid, height = Conv.conv_is_float(height)
if not valid and height:
msg0 = _('HEIGHT')
error += f'{msg0} {msg1}\n\n'
valid, radius = Conv.conv_is_float(radius)
if not valid and radius:
error += f'{rButton} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _(' ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if width == 0:
msg = _('WIDTH cannot be zero')
error += f'{msg}\n\n'
if height == 0:
msg = _('HEIGHT cannot be zero')
error += f'{msg}\n\n'
if angle == 0:
msg = _('ANGLE cannot be zero')
error += f'{msg}\n\n'
if radius > width: # this needs a real calculation in the future *********
msg = _('must be less than WIDTH')
error += f'{rButton} {msg}\n\n'
if radius > height: # this needs a real calculation in the future *********
msg = _('must be less than HEIGHT')
error += f'{rButton} {msg}\n\n'
if error:
return error
angle = math.radians(angle)
leadInOffset = math.sin(math.radians(45)) * float(leadinLength)
leadOutOffset = math.sin(math.radians(45)) * float(leadoutLength)
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
# get original points
x0 = xOffset
y0 = yOffset
x1 = x0 + width * math.cos(right)
y1 = y0 + width * math.sin(right)
# get offset start point
x0n, y0n, = get_offset_coordinates([x1, y1], [x0, y0], angle, kerfWidth, isExternal)
# get new start point
x0 = x0n + (x0 - x0n) * 2
y0 = y0n + (y0 - y0n) * 2
# get new points
x1 = x0 + width * math.cos(right)
y1 = y0 + width * math.sin(right)
x2 = x0 + height * math.cos(angle)
y2 = y0 + height * math.sin(angle)
ang1 = get_angle([x2, y2], [x1, y1], [x0, y0])
ang2 = get_angle([x0, y0], [x2, y2], [x1, y1])
# get new offset points
x0, y0, = get_offset_coordinates([x1, y1], [x0, y0], angle, kerfWidth, isExternal)
x1, y1, = get_offset_coordinates([x2, y2], [x1, y1], ang1, kerfWidth, isExternal)
x2, y2, = get_offset_coordinates([x0, y0], [x2, y2], ang2, kerfWidth, isExternal)
# get leadin point
hypotLength = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
if x2 <= x1:
hypotAngle = left - math.atan((y2 - y1) / (x1 - x2))
else:
hypotAngle = right - math.atan((y2 - y1) / (x1 - x2))
xS = x1 + (hypotLength / 2) * math.cos(hypotAngle)
yS = y1 + (hypotLength / 2) * math.sin(hypotAngle)
if isExternal:
if y2 >= y0:
dir = [up, right]
else:
dir = [down, left]
else:
if y2 >= y0:
dir = [down, left]
else:
dir = [up, right]
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational gusset)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if leadInOffset > 0:
xlCentre = xS + (leadInOffset * math.cos(hypotAngle - dir[0]))
ylCentre = yS + (leadInOffset * math.sin(hypotAngle - dir[0]))
xlStart = xlCentre + (leadInOffset * math.cos(hypotAngle - dir[1]))
ylStart = ylCentre + (leadInOffset * math.sin(hypotAngle - dir[1]))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write('M03 $0 S1\n')
if isExternal:
outTmp.write(f'G01 X{x1:.6f} Y{y1:.6f}\n')
if radius > 0:
x3 = x0 + radius
y3 = y0
outTmp.write(f'G01 X{x3:.6f} Y{y3:.6f}\n')
x4 = x0 + radius * math.cos(angle)
y4 = y0 + radius * math.sin(angle)
if rButton.startswith(_('RADIUS')):
if y2 >= y0:
outTmp.write(f'G03 X{x4:.6f} Y{y4:.6f} I{x0 - x3:.6f} J{y0 - y3:.6f}\n')
else:
outTmp.write(f'G02 X{x4:.6f} Y{y4:.6f} I{x0 - x3:.6f} J{y0 - y3:.6f}\n')
else:
outTmp.write(f'G01 X{x4:.6f} Y{y4:.6f}\n')
else:
outTmp.write(f'G01 X{x0:.6f} Y{y0:.6f}\n')
outTmp.write(f'G01 X{x2:.6f} Y{y2:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
else:
outTmp.write(f'G01 X{x2:.6f} Y{y2:.6f}\n')
if radius > 0:
x3 = x0 + radius
y3 = y0
x4 = x0 + radius * math.cos(angle)
y4 = y0 + radius * math.sin(angle)
outTmp.write(f'G01 X{x4:.6f} Y{y4:.6f}\n')
if rButton.startswith(_('RADIUS')):
if y2 >= y0:
outTmp.write(f'G02 X{x3:.6f} Y{y3:.6f} I{x0 - x4:.6f} J{y0 - y4:.6f}\n')
else:
outTmp.write(f'G03 X{x3:.6f} Y{y3:.6f} I{x0 - x4:.6f} J{y0 - y4:.6f}\n')
else:
outTmp.write(f'G01 X{x3:.6f} Y{y3:.6f}\n')
else:
outTmp.write(f'G01 X{x0:.6f} Y{y0:.6f}\n')
outTmp.write(f'G01 X{x1:.6f} Y{y1:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
if leadOutOffset > 0:
if isExternal:
if y2 >= y0:
dir = [up, left]
else:
dir = [down, right]
else:
if y2 >= y0:
dir = [down, right]
else:
dir = [up, left]
xlCentre = xS + (leadOutOffset * math.cos(hypotAngle - dir[0]))
ylCentre = yS + (leadOutOffset * math.sin(hypotAngle - dir[0]))
xlEnd = xlCentre + (leadOutOffset * math.cos(hypotAngle - dir[1]))
ylEnd = ylCentre + (leadOutOffset * math.sin(hypotAngle - dir[1]))
outTmp.write(f'G03 X{xlEnd:.6f} Y{ylEnd:.6f} I{xlCentre - xS:.6f} J{ylCentre - yS:.6f}\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return None
def get_offset_coordinates(fromPoint, thisPoint, angle, kerfWidth, isExternal):
kOffset = kerfWidth / 2
inAng = math.atan2(thisPoint[1] - fromPoint[1], thisPoint[0] - fromPoint[0])
ang = math.radians(90) - (angle / 2)
offset = math.tan(ang) * kOffset
if isExternal:
x = thisPoint[0] + offset * math.cos(inAng)
y = thisPoint[1] + offset * math.sin(inAng)
x = x + kOffset * math.cos(inAng + math.radians(90))
y = y + kOffset * math.sin(inAng + math.radians(90))
else:
x = thisPoint[0] - offset * math.cos(inAng)
y = thisPoint[1] - offset * math.sin(inAng)
x = x + kOffset * math.cos(inAng + math.radians(-90))
y = y + kOffset * math.sin(inAng + math.radians(-90))
return x, y
def get_angle(fromPoint, thisPoint, nextPoint):
a = math.atan2(fromPoint[1] - thisPoint[1], fromPoint[0] - thisPoint[0])
c = math.atan2(nextPoint[1] - thisPoint[1], nextPoint[0] - thisPoint[0])
if a < 0:
a += math.pi * 2
if c < 0:
c += math.pi * 2
ang = (math.pi * 2 + c - a) if a > c else (c - a)
return ang

315
lib/python/plasmac/line.py Normal file
View File

@@ -0,0 +1,315 @@
'''
line.py
Copyright (C) 2020 - 2024 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 numpy
import gettext
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)
# Conv is the upstream calling module
def do_line_point_to_point(Conv, xStart, yStart, xEnd, yEnd):
error = ''
msg1 = _('entry is invalid')
valid, xStart = Conv.conv_is_float(xStart)
if not valid and xStart:
msg0 = _('X START')
error += f'{msg0} {msg1}\n\n'
valid, yStart = Conv.conv_is_float(yStart)
if not valid and yStart:
msg0 = _('Y START')
error += f'{msg0} {msg1}\n\n'
valid, xEnd = Conv.conv_is_float(xEnd)
if not valid and xEnd:
msg0 = _('X END')
error += f'{msg0} {msg1}\n\n'
valid, yEnd = Conv.conv_is_float(yEnd)
if not valid and yEnd:
msg0 = _('Y END')
error += f'{msg0} {msg1}\n\n'
if error:
return True, error
if xEnd == xStart and yEnd == yStart:
msg = _('Line length would be zero')
error += f'{msg}\n\n'
if error:
return True, error
code = f'G01 X{float(xEnd):.6f} Y{float(yEnd):.6f}\n'
return False, xEnd, yEnd, code
def do_line_by_angle(Conv, xStart, yStart, length, angle):
error = ''
msg1 = _('entry is invalid')
valid, xStart = Conv.conv_is_float(xStart)
if not valid and xStart:
msg0 = _('X START')
error += f'{msg0} {msg1}\n\n'
valid, yStart = Conv.conv_is_float(yStart)
if not valid and yStart:
msg0 = _('Y START')
error += f'{msg0} {msg1}\n\n'
valid, length = Conv.conv_is_float(length)
if not valid and length:
msg0 = _('LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
if error:
return True, error
if length == 0:
msg = _('LENGTH must be greater than zero')
error += f'{msg}\n\n'
if error:
return True, error
ang = math.radians(angle)
x = xStart + (length * math.cos(ang))
y = yStart + (length * math.sin(ang))
code = f'G01 X{x:.6f} Y{y:.6f}\n'
return False, x, y, code
def do_arc_3_points(Conv, xStart, yStart, xNext, yNext, xEnd, yEnd):
error = ''
msg1 = _('entry is invalid')
valid, xStart = Conv.conv_is_float(xStart)
if not valid and xStart:
msg0 = _('X START')
error += f'{msg0} {msg1}\n\n'
valid, yStart = Conv.conv_is_float(yStart)
if not valid and yStart:
msg0 = _('Y START')
error += f'{msg0} {msg1}\n\n'
valid, xNext = Conv.conv_is_float(xNext)
if not valid and xNext:
msg0 = _('X NEXT')
error += f'{msg0} {msg1}\n\n'
valid, yNext = Conv.conv_is_float(yNext)
if not valid and yNext:
msg0 = _('Y NEXT')
error += f'{msg0} {msg1}\n\n'
valid, xEnd = Conv.conv_is_float(xEnd)
if not valid and xEnd:
msg0 = _('X END')
error += f'{msg0} {msg1}\n\n'
valid, yEnd = Conv.conv_is_float(yEnd)
if not valid and yEnd:
msg0 = _('Y END')
error += f'{msg0} {msg1}\n\n'
if error:
return True, error
if xNext == xEnd == xStart:
msg = _('At least one X value must be different')
error += f'{msg}\n\n'
if yNext == yEnd == yStart:
msg = _('At least one Y value must be different')
error += f'{msg}\n\n'
if xNext == yNext and xEnd == yEnd and xStart == yStart:
msg = _('Cannot create a arc, check all entries')
error += f'{msg}\n\n'
if error:
return True, error
try:
A = numpy.array([xStart, yStart, 0.0])
B = numpy.array([xNext, yNext, 0.0])
C = numpy.array([xEnd, yEnd, 0.0])
a = numpy.linalg.norm(C - B)
b = numpy.linalg.norm(C - A)
c = numpy.linalg.norm(B - A)
b1 = a*a * (b*b + c*c - a*a)
b2 = b*b * (a*a + c*c - b*b)
b3 = c*c * (a*a + b*b - c*c)
p = numpy.column_stack((A, B, C)).dot(numpy.hstack((b1, b2, b3)))
p /= b1 + b2 + b3
G = '03' if (xNext - xStart) * (yEnd - yStart) - (yNext - yStart) * (xEnd - xStart) > 0 else '02'
if numpy.isnan(p[0] - xStart) or numpy.isnan(p[1] - yStart):
msg0 = _('Unknown calculation error')
msg1 = _('Ensure entries are correct')
error = f'{msg0},\n\n{msg1}'
return True, error
code = f'G{G} X{xEnd:.6f} Y{yEnd:.6f} I{p[0] - xStart:.6f} J{p[1] - yStart:.6f}\n'
return False, xEnd, yEnd, code
except Exception as e:
msg = _('SYSTEM ERROR')
error = f'{msg}:\n\n{e}'
return True, e
def do_arc_2_points_radius(Conv, xStart, yStart, xEnd, yEnd, radius, arcType):
error = ''
msg1 = _('entry is invalid')
valid, xStart = Conv.conv_is_float(xStart)
if not valid and xStart:
msg0 = _('X START')
error += f'{msg0} {msg1}\n\n'
valid, yStart = Conv.conv_is_float(yStart)
if not valid and yStart:
msg0 = _('Y START')
error += f'{msg0} {msg1}\n\n'
valid, xEnd = Conv.conv_is_float(xEnd)
if not valid and xEnd:
msg0 = _('X END')
error += f'{msg0} {msg1}\n\n'
valid, yEnd = Conv.conv_is_float(yEnd)
if not valid and yEnd:
msg0 = _('Y END')
error += f'{msg0} {msg1}\n\n'
valid, radius = Conv.conv_is_float(radius)
if not valid and radius:
msg0 = _('RADIUS')
error += f'{msg0} {msg1}\n\n'
if error:
return True, error
if radius == 0:
msg = _('RADIUS cannot be zero')
error += f'{msg}\n\n'
if xEnd == xStart and yEnd == yStart:
msg = _('Arc length would be zero')
error += f'{msg}\n\n'
if error:
return True, error
try:
dir = math.radians(270) if radius > 0 else math.radians(90)
height = math.sqrt((xEnd - xStart) ** 2 + (yEnd - yStart) ** 2) * 0.5
if radius < height:
return True, _('Radius must be at least half the length')
length = math.sqrt((radius ** 2) - (height ** 2))
angle = math.atan2((yEnd - yStart), (xEnd - xStart))
xLineCentre = (xStart + xEnd) / 2
yLineCentre = (yStart + yEnd) / 2
xArcCentre = xLineCentre + length * math.cos(angle + dir)
yArcCentre = yLineCentre + length * math.sin(angle + dir)
code = (f'G{arcType} X{xEnd:.6f} Y{yEnd:.6f} I{xArcCentre - xStart:.6f} J{yArcCentre - yStart:.6f}\n')
return False, xEnd, yEnd, code
except Exception as e:
msg = _('SYSTEM ERROR')
error = f'{msg}:\n\n{e}'
return True, e
def do_arc_by_angle_radius(Conv, xStart, yStart, length, angle, radius, arcType):
error = ''
msg1 = _('entry is invalid')
valid, xStart = Conv.conv_is_float(xStart)
if not valid and xStart:
msg0 = _('X START')
error += f'{msg0} {msg1}\n\n'
valid, yStart = Conv.conv_is_float(yStart)
if not valid and yStart:
msg0 = _('Y START')
error += f'{msg0} {msg1}\n\n'
valid, length = Conv.conv_is_float(length)
if not valid and length:
msg0 = _('LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, radius = Conv.conv_is_float(radius)
if not valid and radius:
msg0 = _('RADIUS')
error += f'{msg0} {msg1}\n\n'
if error:
return True, error
if radius == 0:
msg = _('RADIUS cannot be zero')
error += f'{msg}\n\n'
if length == 0:
msg = _('LENGTH cannot be zero')
error += f'{msg}\n\n'
if error:
return True, error
ang = math.radians(angle)
xEnd = xStart + (length * math.cos(ang))
yEnd = yStart + (length * math.sin(ang))
result = do_arc_2_points_radius(Conv, xStart, yStart, xEnd, yEnd, radius, arcType)
return result
def first_segment(fTmp, fNgc, fNgcBkp, preAmble, xStart, yStart, matNumber, matName):
with open(fTmp, 'w') as outTmp:
with open(fNgc, 'w') as outNgc:
with open(fNgcBkp, 'r') as inWiz:
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational line/arc)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
outTmp.write(f'G00 X{xStart:.6f} Y{yStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
def next_segment(fTmp, fNgc):
with open(fTmp, 'w') as outTmp:
with open(fNgc, 'r') as inNgc:
while(1):
line = inNgc.readline()
if not line or 'M05 $0' in line:
break
else:
outTmp.write(line)
open(fNgc, 'w')
def last_segment(fTmp, fNgc, code, postAmble):
with open(fTmp, 'a') as outTmp:
outTmp.write(code)
outTmp.write('M05 $0\n')
with open(fNgc, 'a') as outNgc:
with open(fTmp, 'r') as inTmp:
for line in inTmp:
outNgc.write(line)
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')

View File

@@ -0,0 +1,219 @@
'''
polygon.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
isCenter, xOffset, yOffset,
kerfWidth, isExternal,
sides, diameter, sAngle,
inStyle, diaOrLen):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, sides = Conv.conv_is_int(sides)
if not valid and sides:
msg0 = _('SIDES')
error += f'{msg0} {msg1}\n\n'
valid, diameter = Conv.conv_is_float(diameter)
if not valid and diameter:
error += f'{diaOrLen} {msg1}\n\n'
valid, sAngle = Conv.conv_is_float(sAngle)
if not valid and sAngle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if sides < 3:
msg = _('More than two SIDES required')
error += f'{msg}\n\n'
if diameter == 0:
msg = _('DIAMETER cannot be zero')
error += f'{msg}\n\n'
if error:
return error
if inStyle == 0: # circumscribed
radius = diameter / 2
elif inStyle == 1: # inscribed
radius = (diameter / 2) / math.cos(math.radians(180 / sides))
else: # side length
radius = diameter / (2 * math.sin(math.radians(180 / sides)))
sAngle = math.radians(sAngle)
# get start point
if isCenter:
xS = float(xOffset)
yS = float(yOffset)
else:
xS = float(xOffset) + radius # * math.cos(math.radians(0))
yS = float(yOffset) + radius # * math.sin(math.radians(90))
leadInOffset = float(leadinLength) / (2 * math.pi * (90.0 / 360))
leadOutOffset = math.sin(math.radians(45)) * float(leadoutLength)
# get all points
pList = get_points(sides, sAngle, xS, yS, radius)
# get offset required
offset = get_offset([pList[2][0], pList[2][1]], [pList[1][0], pList[1][1]], [pList[0][0], pList[0][1]], kerfWidth)
# get new points
move = 0 if isCenter else offset
if isExternal:
pList = get_points(sides, sAngle, xS + move, yS + move, radius + offset)
else:
pList = get_points(sides, sAngle, xS - move, yS - move, radius - offset)
# get center
xCentre = (float(pList[0][0]) + float(pList[sides - 1][0])) / 2
yCentre = (float(pList[0][1]) + float(pList[sides - 1][1])) / 2
angle = math.atan2(float(pList[0][1]) - yCentre, float(pList[0][0]) - xCentre)
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
if isExternal:
dir = [down, right]
else:
dir = [up, left]
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write(f'\n(conversational polygon {sides})\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if leadInOffset > 0:
xlCentre = xCentre + (leadInOffset * math.cos(angle + dir[0]))
ylCentre = yCentre + (leadInOffset * math.sin(angle + dir[0]))
xlStart = xlCentre + (leadInOffset * math.cos(angle + dir[1]))
ylStart = ylCentre + (leadInOffset * math.sin(angle + dir[1]))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{xCentre:.6f} Y{yCentre:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xCentre:.6f} Y{yCentre:.6f}\n')
outTmp.write('M03 $0 S1\n')
if isExternal:
for i in range(sides, 0, -1):
outTmp.write(f'G01 X{pList[i - 1][0]} Y{pList[i - 1][1]}\n')
else:
for i in range(sides):
outTmp.write(f'G01 X{pList[i][0]} Y{pList[i][1]}\n')
outTmp.write(f'G01 X{xCentre} Y{yCentre}\n')
if leadOutOffset > 0:
if isExternal:
dir = [down, left]
else:
dir = [up, right]
xlCentre = xCentre + (leadOutOffset * math.cos(angle + dir[0]))
ylCentre = yCentre + (leadOutOffset * math.sin(angle + dir[0]))
xlEnd = xlCentre + (leadOutOffset * math.cos(angle + dir[1]))
ylEnd = ylCentre + (leadOutOffset * math.sin(angle + dir[1]))
outTmp.write(f'G03 X{xlEnd:.6f} Y{ylEnd:.6f} I{xlCentre - xCentre:.6f} J{ylCentre - yCentre:.6f}\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False
def get_points(sides, sAngle, xS, yS, radius):
pList = []
for i in range(sides):
angle = sAngle + 2 * math.pi * i / sides
x = xS + radius * math.cos(angle)
y = yS + radius * math.sin(angle)
pList.append([round(x, 3), round(y, 3)])
return pList
def get_offset(A, B, C, kerfWidth):
Ax, Ay = A[0] - B[0], A[1] - B[1]
Cx, Cy = C[0] - B[0], C[1] - B[1]
a = math.atan2(Ay, Ax)
c = math.atan2(Cy, Cx)
if a < 0:
a += math.pi * 2
if c < 0:
c += math.pi * 2
ang = (math.pi * 2 + c - a) if a > c else (c - a)
ang = math.radians(90) - (ang / 2)
adj = (kerfWidth / 2) / math.sin(ang)
ofs = math.tan(ang) * adj
return ofs

View File

@@ -0,0 +1,457 @@
'''
rectangle.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
isCenter, xOffset, yOffset,
kerfWidth, isExternal,
width, height, angle,
styleR1, styleR2, styleR3, styleR4,
radiusR1, radiusR2, radiusR3, radiusR4,
textR1, textR2, textR3, textR4):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += 'f{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += 'f{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += 'f{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += 'f{msg0} {msg1}\n\n'
valid, width = Conv.conv_is_float(width)
if not valid and width:
msg0 = _('WIDTH')
error += 'f{msg0} {msg1}\n\n'
valid, height = Conv.conv_is_float(height)
if not valid and height:
msg0 = _('HEIGHT')
error += 'f{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += 'f{msg0} {msg1}\n\n'
valid, radius1 = Conv.conv_is_float(radiusR1)
if not valid and radiusR1:
error += f'{textR1} {msg1}\n\n'
valid, radius2 = Conv.conv_is_float(radiusR2)
if not valid and radiusR2:
error += f'{textR2} {msg1}\n\n'
valid, radius3 = Conv.conv_is_float(radiusR3)
if not valid and radiusR3:
error += f'{textR3} {msg1}\n\n'
valid, radius4 = Conv.conv_is_float(radiusR4)
if not valid and radiusR4:
error += f'{textR4} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Kerf Width entry in material is invalid')
error += f'{msg}\n\n'
if error:
return error
if width == 0:
msg = _('WIDTH cannot be zero')
error += f'{msg}\n\n'
if height == 0:
msg = _('HEIGHT cannot be zero')
error += f'{msg}\n\n'
if width and height:
msg0 = _('cannot be greater than')
if isExternal:
maxH = height
maxW = width
else:
maxH = height + kerfWidth * -1
maxW = width + kerfWidth * -1
max = min(maxW / 2, maxH)
if radius1 > max:
error += f'{textR1} {msg0} {max}\n\n'
if radius2 > max:
error += f'{textR2} {msg0} {max}\n\n'
if radius3 > maxH:
error += f'{textR3} {msg0} {maxH}\n\n'
if radius4 > maxH:
error += f'{textR4} {msg0} {maxH}\n\n'
if radius3 + radius4 > maxW:
error += f'{textR3} + {textR4} {msg0} {maxW}\n\n'
if radius1 + radius3 > maxH:
error += f'{textR1} + {textR3} {msg0} {maxH}\n\n'
if radius2 + radius4 > maxH:
error += f'{textR2} + {textR4} {msg0} {maxH}\n\n'
radii = [None, radius1, radius2, radius3, radius4]
texts = [None, textR1, textR2, textR3, textR4]
for r in range(1, 5):
if radii[r] and radii[r] < kerfWidth / 2:
msg0 = _('can not be less than kerf radius')
error += f'{texts[r]} {msg0}\n\n'
if error:
return error
angle = math.radians(angle)
leadInOffset = math.sin(math.radians(45)) * leadinLength
leadOutOffset = math.sin(math.radians(45)) * leadoutLength
kOffset = kerfWidth / 2
if isExternal:
width = width + kerfWidth
height = height + kerfWidth
else:
width = width - kerfWidth
height = height - kerfWidth
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
if isExternal:
if radius1:
if styleR1 == 'extRadius':
radius1 += kOffset
elif styleR1 == 'chamfer':
radius1 += kOffset * math.tan(math.radians(22.5))
if radius2:
if styleR2 == 'extRadius':
radius2 += kOffset
elif styleR2 == 'chamfer':
radius2 += kOffset * math.tan(math.radians(22.5))
if radius3:
if styleR3 == 'extRadius':
radius3 += kOffset
elif styleR3 == 'chamfer':
radius3 += kOffset * math.tan(math.radians(22.5))
if radius4:
if styleR4 == 'extRadius':
radius4 += kOffset
elif styleR4 == 'chamfer':
radius4 += kOffset * math.tan(math.radians(22.5))
else:
if radius1:
if styleR1 == 'extRadius':
radius1 -= kOffset
elif styleR1 == 'chamfer':
radius1 -= kOffset * math.tan(math.radians(22.5))
if radius2:
if styleR2 == 'extRadius':
radius2 -= kOffset
elif styleR2 == 'chamfer':
radius2 -= kOffset * math.tan(math.radians(22.5))
if radius3:
if styleR3 == 'extRadius':
radius3 -= kOffset
elif styleR3 == 'chamfer':
radius3 -= kOffset * math.tan(math.radians(22.5))
if radius4:
if styleR4 == 'extRadius':
radius4 -= kOffset
elif styleR4 == 'chamfer':
radius4 -= kOffset * math.tan(math.radians(22.5))
xLB = width - (radius3 + radius4)
xLT = width - (radius1 + radius2)
xC = width / 2
yLR = height - (radius2 + radius4)
yLL = height - (radius1 + radius3)
yC = height / 2
blLength = math.sqrt(xC ** 2 + (yC * 2) ** 2)
blAngle = math.atan((yC * 2) / xC)
if isCenter:
if isExternal:
xS = xOffset + ((yC - radius2) * math.cos(angle + up)) + (xC * math.cos(angle + right))
yS = yOffset + ((yC - radius2) * math.sin(angle + up)) + (xC * math.sin(angle + right))
else:
xS = xOffset + yC * math.cos(angle + up)
yS = yOffset + yC * math.sin(angle + up)
else:
if isExternal:
xS = xOffset + (width * math.cos(angle + up)) + (width * math.cos(angle + right))
yS = yOffset + ((height - radius2) * math.sin(angle + up)) + (width * math.sin(angle + right))
else:
xS = xOffset + (blLength * math.cos(angle + right + blAngle))
yS = yOffset + (blLength * math.sin(angle + right + blAngle))
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational rectangle)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if isExternal:
if leadInOffset > 0:
xlCentre = xS + (leadInOffset * math.cos(angle + right))
ylCentre = yS + (leadInOffset * math.sin(angle + right))
xlStart = xlCentre + (leadInOffset * math.cos(angle + up))
ylStart = ylCentre + (leadInOffset * math.sin(angle + up))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write('M03 $0 S1\n')
x1 = xS + yLR * math.cos(angle + down)
y1 = yS + yLR * math.sin(angle + down)
outTmp.write(f'G01 X{x1:.6f} Y{y1:.6f}\n')
if radius4:
if styleR4 == 'intRadius':
xrCentre = x1 + (radius4 * math.cos(angle + down))
yrCentre = y1 + (radius4 * math.sin(angle + down))
xrEnd = xrCentre + (radius4 * math.cos(angle + left))
yrEnd = yrCentre + (radius4 * math.sin(angle + left))
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x1:.6f} J{yrCentre - y1:.6f}\n')
else:
xrCentre = x1 + (radius4 * math.cos(angle + left))
yrCentre = y1 + (radius4 * math.sin(angle + left))
xrEnd = xrCentre + (radius4 * math.cos(angle + down))
yrEnd = yrCentre + (radius4 * math.sin(angle + down))
if styleR4 == 'extRadius':
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x1:.6f} J{yrCentre - y1:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
x2 = xrEnd + xLB * math.cos(angle + left)
y2 = yrEnd + xLB * math.sin(angle + left)
else:
x2 = x1 + xLB * math.cos(angle + left)
y2 = y1 + xLB * math.sin(angle + left)
outTmp.write(f'G01 X{x2:.6f} Y{y2:.6f}\n')
if radius3:
if styleR3 == 'intRadius':
xrCentre = x2 + (radius3 * math.cos(angle + left))
yrCentre = y2 + (radius3 * math.sin(angle + left))
xrEnd = xrCentre + (radius3 * math.cos(angle + up))
yrEnd = yrCentre + (radius3 * math.sin(angle + up))
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x2:.6f} J{yrCentre - y2:.6f}\n')
else:
xrCentre = x2 + (radius3 * math.cos(angle + up))
yrCentre = y2 + (radius3 * math.sin(angle + up))
xrEnd = xrCentre + (radius3 * math.cos(angle + left))
yrEnd = yrCentre + (radius3 * math.sin(angle + left))
if styleR3 == 'extRadius':
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x2:.6f} J{yrCentre - y2:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
x3 = xrEnd + yLL * math.cos(angle + up)
y3 = yrEnd + yLL * math.sin(angle + up)
else:
x3 = x2 + yLL * math.cos(angle + up)
y3 = y2 + yLL * math.sin(angle + up)
outTmp.write(f'G01 X{x3:.6f} Y{y3:.6f}\n')
if radius1:
if styleR1 == 'intRadius':
xrCentre = x3 + (radius1 * math.cos(angle + up))
yrCentre = y3 + (radius1 * math.sin(angle + up))
xrEnd = xrCentre + (radius1 * math.cos(angle + right))
yrEnd = yrCentre + (radius1 * math.sin(angle + right))
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x3:.6f} J{yrCentre - y3:.6f}\n')
else:
xrCentre = x3 + (radius1 * math.cos(angle + right))
yrCentre = y3 + (radius1 * math.sin(angle + right))
xrEnd = xrCentre + (radius1 * math.cos(angle + up))
yrEnd = yrCentre + (radius1 * math.sin(angle + up))
if styleR1 == 'extRadius':
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x3:.6f} J{yrCentre - y3:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
x4 = xrEnd + xLT * math.cos(angle + right)
y4 = yrEnd + xLT * math.sin(angle + right)
else:
x4 = x3 + xLT * math.cos(angle + right)
y4 = y3 + xLT * math.sin(angle + right)
outTmp.write(f'G01 X{x4:.6f} Y{y4:.6f}\n')
if radius2:
if styleR2 == 'intRadius':
xrCentre = x4 + (radius2 * math.cos(angle + right))
yrCentre = y4 + (radius2 * math.sin(angle + right))
xrEnd = xrCentre + (radius2 * math.cos(angle + down))
yrEnd = yrCentre + (radius2 * math.sin(angle + down))
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x4:.6f} J{yrCentre - y4:.6f}\n')
else:
xrCentre = x4 + (radius2 * math.cos(angle + down))
yrCentre = y4 + (radius2 * math.sin(angle + down))
xrEnd = xrCentre + (radius2 * math.cos(angle + right))
yrEnd = yrCentre + (radius2 * math.sin(angle + right))
if styleR2 == 'extRadius':
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x4:.6f} J{yrCentre - y4:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
else:
if leadInOffset > 0:
xlCentre = xS + (leadInOffset * math.cos(angle + down))
ylCentre = yS + (leadInOffset * math.sin(angle + down))
xlStart = xlCentre + (leadInOffset * math.cos(angle + right))
ylStart = ylCentre + (leadInOffset * math.sin(angle + right))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write('M03 $0 S1\n')
x1 = xS + ((xLT - (radius1 - radius2)) / 2) * math.cos(angle + left)
y1 = yS + ((xLT - (radius1 - radius2)) / 2) * math.sin(angle + left)
outTmp.write(f'G01 X{x1:.6f} Y{y1:.6f}\n')
if radius1:
if styleR1 == 'intRadius':
xrCentre = x1 + (radius1 * math.cos(angle + left))
yrCentre = y1 + (radius1 * math.sin(angle + left))
xrEnd = xrCentre + (radius1 * math.cos(angle + down))
yrEnd = yrCentre + (radius1 * math.sin(angle + down))
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x1:.6f} J{yrCentre - y1:.6f}\n')
else:
xrCentre = x1 + (radius1 * math.cos(angle + down))
yrCentre = y1 + (radius1 * math.sin(angle + down))
xrEnd = xrCentre + (radius1 * math.cos(angle + left))
yrEnd = yrCentre + (radius1 * math.sin(angle + left))
if styleR1 == 'extRadius':
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x1:.6f} J{yrCentre - y1:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
x2 = xrEnd + yLL * math.cos(angle + down)
y2 = yrEnd + yLL * math.sin(angle + down)
else:
x2 = x1 + yLL * math.cos(angle + down)
y2 = y1 + yLL * math.sin(angle + down)
outTmp.write(f'G01 X{x2:.6f} Y{y2:.6f}\n')
if radius3:
if styleR3 == 'intRadius':
xrCentre = x2 + (radius3 * math.cos(angle + down))
yrCentre = y2 + (radius3 * math.sin(angle + down))
xrEnd = xrCentre + (radius3 * math.cos(angle + right))
yrEnd = yrCentre + (radius3 * math.sin(angle + right))
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x2:.6f} J{yrCentre - y2:.6f}\n')
else:
xrCentre = x2 + (radius3 * math.cos(angle + right))
yrCentre = y2 + (radius3 * math.sin(angle + right))
xrEnd = xrCentre + (radius3 * math.cos(angle + down))
yrEnd = yrCentre + (radius3 * math.sin(angle + down))
if styleR3 == 'extRadius':
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x2:.6f} J{yrCentre - y2:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
x3 = xrEnd + xLB * math.cos(angle + right)
y3 = yrEnd + xLB * math.sin(angle + right)
else:
x3 = x2 + xLB * math.cos(angle + right)
y3 = y2 + xLB * math.sin(angle + right)
outTmp.write(f'G01 X{x3:.6f} Y{y3:.6f}\n')
if radius4:
if styleR4 == 'intRadius':
xrCentre = x3 + (radius4 * math.cos(angle + right))
yrCentre = y3 + (radius4 * math.sin(angle + right))
xrEnd = xrCentre + (radius4 * math.cos(angle + up))
yrEnd = yrCentre + (radius4 * math.sin(angle + up))
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x3:.6f} J{yrCentre - y3:.6f}\n')
else:
xrCentre = x3 + (radius4 * math.cos(angle + up))
yrCentre = y3 + (radius4 * math.sin(angle + up))
xrEnd = xrCentre + (radius4 * math.cos(angle + right))
yrEnd = yrCentre + (radius4 * math.sin(angle + right))
if styleR4 == 'extRadius':
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x3:.6f} J{yrCentre - y3:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
x4 = xrEnd + yLR * math.cos(angle + up)
y4 = yrEnd + yLR * math.sin(angle + up)
else:
x4 = x3 + yLR * math.cos(angle + up)
y4 = y3 + yLR * math.sin(angle + up)
outTmp.write(f'G01 X{x4:.6f} Y{y4:.6f}\n')
if radius2:
if styleR2 == 'intRadius':
xrCentre = x4 + (radius2 * math.cos(angle + up))
yrCentre = y4 + (radius2 * math.sin(angle + up))
xrEnd = xrCentre + (radius2 * math.cos(angle + left))
yrEnd = yrCentre + (radius2 * math.sin(angle + left))
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x4:.6f} J{yrCentre - y4:.6f}\n')
else:
xrCentre = x4 + (radius2 * math.cos(angle + left))
yrCentre = y4 + (radius2 * math.sin(angle + left))
xrEnd = xrCentre + (radius2 * math.cos(angle + up))
yrEnd = yrCentre + (radius2 * math.sin(angle + up))
if styleR2 == 'extRadius':
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x4:.6f} J{yrCentre - y4:.6f}\n')
else:
outTmp.write(f'G01 X{xrEnd:.6f} Y{yrEnd:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
if leadOutOffset > 0: # and not (isExternal and radius2):
if isExternal and not radius2:
dir = ['G02', down, right]
elif isExternal and radius2:
dir = ['G03', right, down]
else:
dir = ['G03', down, left]
xlCentre = xS + (leadOutOffset * math.cos(angle + dir[1]))
ylCentre = yS + (leadOutOffset * math.sin(angle + dir[1]))
xlEnd = xlCentre + (leadOutOffset * math.cos(angle + dir[2]))
ylEnd = ylCentre + (leadOutOffset * math.sin(angle + dir[2]))
outTmp.write(f'{dir[0]} X{xlEnd:.6f} Y{ylEnd:.6f} I{xlCentre - xS:.6f} J{ylCentre - yS:.6f}\n')
outTmp.write('G40\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False

View File

@@ -0,0 +1,361 @@
'''
run_from_line.py
Copyright (C) 2019 - 2024 Phillip A Carter
Copyright (C) 2020 - 2025 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 math
def run_from_line_get(file, startLine, lastLine=0):
preData, postData, newData, params, material = [], [], [], [], [None, None]
codes = {'g2_': '', 'g4_': '', 'g6_': '', 'g9_': '', 'g9arc': '', 'd3': '', 'd2': '', 'a3': '', 'x1': '', 'y1': '', 'x2': '', 'y2': ''}
codes['last'] = {'feed': '', 'code': ''}
codes['move'] = {'isSet': False, 'isG00': False}
codes['spindle'] = {'code': False, 'line': None}
oSub = []
cutComp = False
count = 0
with open(file, 'r') as inFile:
for line in inFile:
# code before selected line
if count < startLine:
preData.append(line)
# remaining code
else:
if count == startLine:
if 'G21' in line:
newData.append('G21')
elif 'G20' in line:
newData.append('G20')
# find the type of first move
if not codes['move']['isSet'] and 'G53G00' not in line.replace(' ', '') and 'G20' not in line and 'G21' not in line:
if 'G00' in line:
codes['move']['isSet'] = True
codes['move']['isG00'] = True
codes['x2'] = get_rfl_pos(line.strip(), codes['x2'], 'X')
codes['y2'] = get_rfl_pos(line.strip(), codes['y2'], 'Y')
if 'G01' in line or 'G02' in line or 'G03' in line:
codes['move']['isSet'] = True
codes['move']['isG00'] = False
codes['x2'] = get_rfl_pos(line.strip(), codes['x2'], 'X')
codes['y2'] = get_rfl_pos(line.strip(), codes['y2'], 'Y')
if 'M03' in line:
if not codes['spindle']['line']:
codes['spindle']['line'] = line.strip()
continue
codes['spindle']['line'] = line.strip()
postData.append(line)
if lastLine and count == lastLine:
postData.append('M02\n')
postData.append(';qtplasmac filtered G-code file\n')
break
count += 1
# read all lines before selected line to get last used codes
for line in preData:
if line.startswith('('):
if line.startswith('(o='):
material = [line.strip()]
continue
elif line.startswith('M190'):
material = [line.strip()]
continue
elif line.replace(' ', '').startswith('M66P3'):
material.append(line.strip())
continue
elif line.startswith('#'):
params.append(line.strip())
continue
for t1 in ['G20', 'G21', 'G40', 'G41.1', 'G42.1', 'G61', 'G61.1', 'G64', 'G90', 'G90.1', 'G91', 'G91.1']:
if t1 in line:
if t1[1] == '2':
codes['g2_'] = t1
elif t1[1] == '4':
codes['g4_'] = t1
if t1 != 'G40':
cutComp = True
else:
cutComp = False
elif t1[1] == '6':
codes['g6_'] = t1 + line.split(t1)[1]
elif t1 == 'G90' and 'G90.1' not in line:
codes['g9_'] = 'G90'
elif t1 == 'G91' and 'G91.1' not in line:
codes['g9_'] = 'G91'
elif 'G90.1' in line:
codes['g9arc'] = 'G90.1'
elif 'G91.1' in line:
codes['g9arc'] = 'G91.1'
if 'G00' in line and 'G53G00' not in line:
codes['last']['code'] = 'G00'
if 'G01' in line:
tmp = line.split('G01')[1]
if tmp[0] not in '0123456789':
codes['last']['code'] = 'G01'
if 'G02' in line:
tmp = line.split('G02')[1]
if tmp[0] not in '0123456789':
codes['last']['code'] = 'G02'
if 'G03' in line:
tmp = line.split('G03')[1]
if tmp[0] not in '0123456789':
codes['last']['code'] = 'G03'
if 'X' in line:
codes['x1'] = get_rfl_pos(line.strip(), codes['x1'], 'X')
if 'Y' in line:
codes['y1'] = get_rfl_pos(line.strip(), codes['y1'], 'Y')
# track spindle state: M03 starts, M05 stops
if 'M03' in line.replace(' ', ''):
codes['spindle']['line'] = line.strip()
if 'M05' in line.replace(' ', ''):
codes['spindle']['line'] = None
if 'M62P3' in line.replace(' ', ''):
codes['d3'] = 'M62 P3 (Disable Torch)'
elif 'M63P3' in line.replace(' ', ''):
codes['d3'] = 'M63 P3 (Enable Torch)'
elif 'M64P3' in line.replace(' ', ''):
codes['d3'] = 'M64 P3 (Disable Torch)'
elif 'M65P3' in line.replace(' ', ''):
codes['d3'] = 'M65 P3 (Enable Torch)'
if 'M62P2' in line.replace(' ', ''):
codes['d2'] = 'M62 P2 (Disable THC)'
elif 'M63P2' in line.replace(' ', ''):
codes['d2'] = 'M63 P2 (Enable THC)'
elif 'M64P2' in line.replace(' ', ''):
codes['d2'] = 'M64 P2 (Disable THC)'
elif 'M65P2' in line.replace(' ', ''):
codes['d2'] = 'M65 P2 (Enable THC)'
if 'M67E3Q' in line.replace(' ', ''):
codes['a3'] = 'M67 E3 Q'
tmp = line.replace(' ', '').split('M67E3Q')[1]
while 1:
if tmp[0] in '-.0123456789':
codes['a3'] += tmp[0]
tmp = tmp[1:]
else:
break
pc = float(codes['a3'].split('M67 E3 Q')[1])
pc = pc if pc > 0 else 100
codes['a3'] += f' (Velocity {pc}%)'
if 'M68E3Q' in line.replace(' ', ''):
codes['a3'] = 'M68 E3 Q'
tmp = line.replace(' ', '').split('M68E3Q')[1]
while 1:
if tmp[0] in '-.0123456789':
codes['a3'] += tmp[0]
tmp = tmp[1:]
else:
break
pc = float(codes['a3'].split('M68 E3 Q')[1])
pc = pc if pc > 0 else 100
codes['a3'] += f' (Velocity {pc}%)'
# test if inside a subroutine
if line.startswith('O'):
if 'END' in line:
oSub = False
else:
if line[1] == '<':
os = 'O<'
tmp = line.replace(' ', '').split('O<')[1]
while 1:
if tmp[0] != '>':
os += tmp[0]
tmp = tmp[1:]
else:
break
oSub.append(f'{os}>')
else:
os = 'O'
tmp = line.replace(' ', '').split('O')[1]
while 1:
if tmp[0] in '0123456789':
os += tmp[0]
tmp = tmp[1:]
else:
break
oSub.append(os)
if '#<_hal[plasmac.cut-feed-rate]>' in line:
codes['last']['feed'] = line.strip()
# return an error line within a subroutine or if using cutter compensation
if cutComp or oSub:
return {'error': True, 'compError': cutComp, 'subError': oSub}
# else return all data
return {'error': False, 'codes': codes, 'params': params, 'material': material, 'postData': postData, 'newData': newData}
def run_from_line_set(rflFile, data, leadin, unitsPerMm):
error = False
# add all the codes retrieved from before the start line
for param in data['params']:
if param:
data['newData'].append(param)
scale = 1
zMax = ''
if unitsPerMm == 1:
if data['codes']['g2_'] == 'G20':
scale = 0.03937
zMax = 'G53 G00 Z[[#<_ini[axis_z]max_limit> - 5] * 0.03937]'
else:
zMax = 'G53 G00 Z[#<_ini[axis_z]max_limit> - 5]'
else:
if data['codes']['g2_'] == 'G21':
scale = 25.4
zMax = 'G53 G00 Z[[#<_ini[axis_z]max_limit> * 25.4] - 5]'
else:
zMax = 'G53 G00 Z[#<_ini[axis_z]max_limit> - 0.02]'
if data['codes']['g2_']:
data['newData'].append(data['codes']['g2_'])
if data['codes']['g4_']:
data['newData'].append(data['codes']['g4_'])
if data['codes']['g6_']:
data['newData'].append(data['codes']['g6_'])
if data['codes']['g9_']:
data['newData'].append(data['codes']['g9_'])
if data['codes']['g9arc']:
data['newData'].append(data['codes']['g9arc'])
data['newData'].append('M52 P1')
if data['codes']['d3']:
data['newData'].append(data['codes']['d3'])
if data['codes']['d2']:
data['newData'].append(data['codes']['d2'])
if data['codes']['a3']:
data['newData'].append(data['codes']['a3'])
if zMax:
data['newData'].append(zMax)
if data['material'][0]:
data['newData'].append(data['material'][0])
if data['material'][1]:
data['newData'].append(data['material'][1])
if data['codes']['last']['feed']:
data['newData'].append(data['codes']['last']['feed'])
# if g00 is not the first motion command after selected line set x and y coordinates
if not data['codes']['move']['isG00']:
if leadin['do']:
error, xL, yL = set_leadin_coordinates(data['codes']['x1'], data['codes']['y1'], scale, leadin['length'], leadin['angle'])
else:
xL = data['codes']['x1']
yL = data['codes']['y1']
data['codes']['spindle']['code'] = set_spindle_start(xL, yL, data['codes']['x1'], data['codes']['y1'], data['codes']['spindle']['line'], data['newData'], False)
# if no spindle command yet then find the next one for the correct tool
if not data['codes']['spindle']['line']:
for line in data['postData']:
if 'M03' in line.replace(' ', ''):
data['codes']['spindle']['line'] = line.strip()
break
# add all the code from the selected line to the end
for line in data['postData']:
# if we have the first spindle code we don't need it again
if 'M03' in line.replace(' ', '') and data['codes']['spindle']['code']:
data['codes']['spindle']['code'] = False
continue
# if G00 is the first motion command after the selected line
if data['codes']['move']['isG00']:
# if G00 is the current motion command
if 'G00' in line:
# no need to process a G53G00 command]
if 'G53G00' in line or 'G20' in line or 'G21' in line:
data['newData'].append(line.strip())
continue
if leadin['do']:
error, xL, yL = set_leadin_coordinates(data['codes']['x2'], data['codes']['y2'], scale, leadin['length'], leadin['angle'])
else:
xL = data['codes']['x2']
yL = data['codes']['y2']
data['codes']['spindle']['code'] = set_spindle_start(xL, yL, data['codes']['x2'], data['codes']['y2'], data['codes']['spindle']['line'], data['newData'], True)
# no need to process any more G00 commands
data['codes']['move']['isG00'] = False
continue
data['newData'].append(line.strip())
# create the rfl file
with open(rflFile, 'w') as outFile:
for line in data['newData']:
outFile.write(f'{line}\n')
return {'error': error}
def set_leadin_coordinates(x, y, scale, length, angle):
xL = x
yL = y
try:
if x[-1] == ']':
xL = f'{x[:1]}[[{x[1:]}]+{(length * scale) * math.cos(math.radians(angle)):0.6f}]'
yL = f'{y[:1]}[[{y[1:]}]+{(length * scale) * math.sin(math.radians(angle)):0.6f}]'
else:
xL = float(x) + ((length * scale) * math.cos(math.radians(angle)))
yL = float(y) + ((length * scale) * math.sin(math.radians(angle)))
except:
return(True, x, y)
return(False, xL, yL)
def set_spindle_start(xL, yL, x, y, line, newData, reply):
leadIn = {}
if xL != x and yL != y:
newData.append(f'G00 X{xL} Y{yL}')
leadIn['x'] = x
leadIn['y'] = y
else:
if x and y:
newData.append(f'G00 X{x} Y{y}')
elif x:
newData.append(f'G00 X{x}')
elif y:
newData.append(f'G00 Y{y}')
if line:
newData.append(line)
if leadIn:
newData.append(f'G01 X{leadIn["x"]} Y{leadIn["y"]} (leadin)')
return reply
def get_rfl_pos(line, axisPos, axisLetter):
maths = 0
pos = ''
done = False
if line.startswith('(') or line.startswith(';'):
return pos if pos else axisPos
while len(line):
if line[0] == ('('):
break
if not line[0] == axisLetter:
line = line[1:]
else:
while 1:
line = line[1:]
if line[0] in '-.0123456789#':
pos += line[0]
elif line[0] == '[' or line[0] == '<':
pos += line[0]
maths += 1
elif (line[0] == ']' or line[0] == '>') and maths > 0:
pos += line[0]
maths -= 1
elif maths:
pos += line[0]
elif (pos and not maths) or line[0] == '(':
done = True
break
else:
if len(line) == 1:
break
break
if len(line) == 1:
break
if done:
break
return pos if pos else axisPos

View File

@@ -0,0 +1,211 @@
'''
sector.py
Copyright (C) 2020 - 2024 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 gettext
for f in sys.path:
if '/lib/python' in f:
if '/usr' in f:
localeDir = 'usr/share/locale'
else:
localeDir = os.path.join('{f.split("/lib")[0]}', 'share', 'locale')
break
gettext.install("linuxcnc", localedir=localeDir)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
xOffset, yOffset,
kerfWidth, isExternal,
radius, sAngle, angle):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, radius = Conv.conv_is_float(radius)
if not valid and radius:
msg0 = _('RADIUS')
error += f'{msg0} {msg1}\n\n'
valid, sAngle = Conv.conv_is_float(sAngle)
if not valid and sAngle:
msg0 = _('SEC ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if radius == 0:
msg = _('RADIUS cannot be zero')
error += f'{msg}\n\n'
if sAngle == 0:
msg = _('SEC ANGLE cannot be zero')
error += f'{msg}\n\n'
if error:
return error
sAngle = math.radians(sAngle)
angle = math.radians(angle)
leadInOffset = math.sin(math.radians(45)) * leadinLength
leadOutOffset = math.sin(math.radians(45)) * leadoutLength
# get origin
xO = xOffset
yO = yOffset
# get bottom point
xB = xO + radius * math.cos(angle)
yB = yO + radius * math.sin(angle)
# get offset origin
x0, y0, rOffset = get_offset_coordinates([xB, yB], [xO, yO], sAngle, kerfWidth, isExternal)
xO = x0 + (xO - x0) * 2
yO = y0 + (yO - y0) * 2
# get offset bottom point
xB = xO + radius * math.cos(angle)
yB = yO + radius * math.sin(angle)
xO, yO, rOffset = get_offset_coordinates([xB, yB], [xO, yO], sAngle, kerfWidth, isExternal)
# get new radius
if isExternal:
radius += rOffset + kerfWidth / 2
else:
radius -= rOffset + kerfWidth / 2
# get leadin/leadout point
xS = xO + (radius * 0.75) * math.cos(angle)
yS = yO + (radius * 0.75) * math.sin(angle)
# get new bottom point
xB = xO + radius * math.cos(angle)
yB = yO + radius * math.sin(angle)
# get new top point
xT = xO + radius * math.cos(angle + sAngle)
yT = yO + radius * math.sin(angle + sAngle)
# get directions
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
if isExternal:
dir = [down, right, left, up]
else:
dir = [up, left, right, down]
# get leadin and leadout points
xIC = xS + (leadInOffset * math.cos(angle + dir[0]))
yIC = yS + (leadInOffset * math.sin(angle + dir[0]))
xIS = xIC + (leadInOffset * math.cos(angle + dir[1]))
yIS = yIC + (leadInOffset * math.sin(angle + dir[1]))
xOC = xS + (leadOutOffset * math.cos(angle + dir[0]))
yOC = yS + (leadOutOffset * math.sin(angle + dir[0]))
xOE = xOC + (leadOutOffset * math.cos(angle + dir[2]))
yOE = yOC + (leadOutOffset * math.sin(angle + dir[2]))
# setup files and write G-code
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational sector)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
outTmp.write(f'G00 X{xIS:.6f} Y{yIS:.6f}\n')
outTmp.write('M03 $0 S1\n')
if leadInOffset:
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xIC - xIS:.6f} J{yIC - yIS:.6f}\n')
if isExternal:
outTmp.write(f'G01 X{xO:.6f} Y{yO:.6f}\n')
outTmp.write(f'G01 X{xT:.6f} Y{yT:.6f}\n')
outTmp.write(f'G02 X{xB:.6f} Y{yB:.6f} I{xO - xT:.6f} J{yO - yT:.6f}\n')
else:
outTmp.write(f'G01 X{xB:.6f} Y{yB:.6f}\n')
outTmp.write(f'G03 X{xT:.6f} Y{yT:.6f} I{xO - xB:.6f} J{yO - yB:.6f}\n')
outTmp.write(f'G01 X{xO:.6f} Y{yO:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
if leadOutOffset:
outTmp.write(f'G03 X{xOE:.6f} Y{yOE:.6f} I{xOC - xS:.6f} J{yOC - yS:.6f}\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False
def get_offset_coordinates(fromPoint, thisPoint, angle, kerfWidth, isExternal):
kOffset = kerfWidth / 2
inAng = math.atan2(thisPoint[1] - fromPoint[1], thisPoint[0] - fromPoint[0])
ang = math.radians(90) - (angle / 2)
offset = math.tan(ang) * kOffset
if isExternal:
x = round(thisPoint[0] + offset * math.cos(inAng), 3)
y = round(thisPoint[1] + offset * math.sin(inAng), 3)
x = round(x + kOffset * math.cos(inAng + math.radians(90)), 3)
y = round(y + kOffset * math.sin(inAng + math.radians(90)), 3)
else:
x = round(thisPoint[0] - offset * math.cos(inAng), 3)
y = round(thisPoint[1] - offset * math.sin(inAng), 3)
x = round(x + kOffset * math.cos(inAng + math.radians(-90)), 3)
y = round(y + kOffset * math.sin(inAng + math.radians(-90)), 3)
return x, y, offset

210
lib/python/plasmac/slot.py Normal file
View File

@@ -0,0 +1,210 @@
'''
slot.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
isCenter, xOffset, yOffset,
kerfWidth, isExternal,
length, width, angle):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, length = Conv.conv_is_float(length)
if not valid and length:
msg0 = _('LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, width = Conv.conv_is_float(width)
if not valid and width:
msg0 = _('WIDTH')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if length == 0:
msg = _('LENGTH cannot be zero')
error += f'{msg}\n\n'
if width == 0:
msg = _('WIDTH cannot be zero')
error += f'{msg}\n\n'
if width > length:
msg = _('WIDTH cannot be greater than LENGTH')
error += f'{msg}\n\n'
if error:
return error
if isExternal:
length += kerfWidth
width += kerfWidth
else:
length -= kerfWidth
width -= kerfWidth
angle = math.radians(angle)
leadInOffset = math.sin(math.radians(45)) * leadinLength
leadOutOffset = math.sin(math.radians(45)) * leadoutLength
radius = width / 2
blLength = math.sqrt((length / 2) ** 2 + width ** 2)
blAngle = math.atan(width / (length / 2))
length = length - width
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
if isCenter:
xS = xOffset + (width / 2) * math.cos(angle + up)
yS = yOffset + (width / 2) * math.sin(angle + up)
else:
xS = xOffset + blLength * math.cos(angle + right + blAngle)
yS = yOffset + blLength * math.sin(angle + right + blAngle)
if isExternal:
dir = [up, left, right]
else:
dir = [down, right, left]
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational slot)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if leadInOffset > 0:
xlCentre = xS + (leadInOffset * math.cos(angle + dir[0]))
ylCentre = yS + (leadInOffset * math.sin(angle + dir[0]))
xlStart = xlCentre + (leadInOffset * math.cos(angle + dir[1]))
ylStart = ylCentre + (leadInOffset * math.sin(angle + dir[1]))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write('M03 $0 S1\n')
x1 = xS + (length / 2) * math.cos(angle + dir[2])
y1 = yS + (length / 2) * math.sin(angle + dir[2])
outTmp.write(f'G01 X{x1:.6f} Y{y1:.6f}\n')
if isExternal:
xrCentre = x1 + (radius * math.cos(angle + down))
yrCentre = y1 + (radius * math.sin(angle + down))
xrEnd = xrCentre + (radius * math.cos(angle + down))
yrEnd = yrCentre + (radius * math.sin(angle + down))
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x1:.6f} J{yrCentre - y1:.6f}\n')
x2 = xrEnd + length * math.cos(angle + left)
y2 = yrEnd + length * math.sin(angle + left)
outTmp.write(f'G01 X{x2:.6f} Y{y2:.6f}\n')
xrCentre = x2 + (radius * math.cos(angle + up))
yrCentre = y2 + (radius * math.sin(angle + up))
xrEnd = xrCentre + (radius * math.cos(angle + up))
yrEnd = yrCentre + (radius * math.sin(angle + up))
outTmp.write(f'G02 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x2:.6f} J{yrCentre - y2:.6f}\n')
else:
xrCentre = x1 + (radius * math.cos(angle + down))
yrCentre = y1 + (radius * math.sin(angle + down))
xrEnd = xrCentre + (radius * math.cos(angle + down))
yrEnd = yrCentre + (radius * math.sin(angle + down))
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x1:.6f} J{yrCentre - y1:.6f}\n')
x2 = xrEnd + length * math.cos(angle + right)
y2 = yrEnd + length * math.sin(angle + right)
outTmp.write(f'G01 X{x2:.6f} Y{y2:.6f}\n')
xrCentre = x2 + (radius * math.cos(angle + up))
yrCentre = y2 + (radius * math.sin(angle + up))
xrEnd = xrCentre + (radius * math.cos(angle + up))
yrEnd = yrCentre + (radius * math.sin(angle + up))
outTmp.write(f'G03 X{xrEnd:.6f} Y{yrEnd:.6f} I{xrCentre - x2:.6f} J{yrCentre - y2:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
if leadOutOffset > 0:
if isExternal:
dir = [up, right]
else:
dir = [down, left]
xlCentre = xS + (leadOutOffset * math.cos(angle + dir[0]))
ylCentre = yS + (leadOutOffset * math.sin(angle + dir[0]))
xlEnd = xlCentre + (leadOutOffset * math.cos(angle + dir[1]))
ylEnd = ylCentre + (leadOutOffset * math.sin(angle + dir[1]))
outTmp.write(f'G03 X{xlEnd:.6f} Y{ylEnd:.6f} I{xlCentre - xS:.6f} J{ylCentre - yS:.6f}\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False

233
lib/python/plasmac/star.py Normal file
View File

@@ -0,0 +1,233 @@
'''
star.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
isCenter, xOffset, yOffset,
kerfWidth, isExternal,
points, extDia, intDia, angle):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, points = Conv.conv_is_int(points)
if not valid and points:
msg0 = _('POINTS')
error += f'{msg0} {msg1}\n\n'
valid, extDia = Conv.conv_is_float(extDia)
if not valid and extDia:
msg0 = _('OUTER DIA')
error += f'{msg0} {msg1}\n\n'
valid, intDia = Conv.conv_is_float(intDia)
if not valid and intDia:
msg0 = _('INNER DIA')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if error:
return error
if points < 2:
msg = _('More than two POINTS required')
error += f'{msg}\n\n'
if extDia == 0:
msg = _('OUTER DIA cannot be zero')
error += f'{msg}\n\n'
if intDia == 0:
msg = _('INNER DIA cannot be zero')
error += f'{msg}\n\n'
if intDia >= extDia:
msg = _('INNER DIA must be less than OUTER DIA')
error += f'{msg}\n\n'
if error:
return error
extRadius = extDia / 2
intRadius = intDia / 2
angle = math.radians(angle)
# get start pop
if isCenter:
xC = xOffset
yC = yOffset
else:
xC = xOffset + extRadius * math.cos(math.radians(0))
yC = yOffset + extRadius * math.sin(math.radians(90))
# get all points
pList = get_points(points, angle, xC, yC, extRadius, intRadius)
# get external offset required
extOffset = get_offset([pList[3][0], pList[3][1]], [pList[2][0], pList[2][1]], [pList[1][0], pList[1][1]], kerfWidth)
# get internal offset required
intOffset = get_offset([pList[0][0], pList[0][1]], [pList[1][0], pList[1][1]], [pList[2][0], pList[2][1]], kerfWidth)
# get new points
move = 0 if isCenter else extOffset
if isExternal:
pList = get_points(points, angle, xC + move, yC + move, extRadius + extOffset, intRadius + intOffset)
else:
pList = get_points(points, angle, xC - move, yC - move, extRadius - extOffset, intRadius - intOffset)
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write(f'\n(conversational star {points})\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if isExternal:
if leadinLength > 0:
lAngle = math.atan2(pList[0][1] - pList[-1][1],
pList[0][0] - pList[-1][0])
xlStart = pList[0][0] + leadinLength * math.cos(lAngle)
ylStart = pList[0][1] + leadinLength * math.sin(lAngle)
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G01 X{pList[0][0]} Y{pList[0][1]}\n')
else:
outTmp.write(f'G00 X{pList[0][0]} Y{pList[0][1]}\n')
outTmp.write('M03 $0 S1\n')
for i in range(points * 2, 0, -1):
outTmp.write(f'G01 X{pList[i - 1][0]} Y{pList[i - 1][1]}\n')
if leadoutLength > 0:
lAngle = math.atan2(pList[0][1] - pList[1][1],
pList[0][0] - pList[1][0])
xlEnd = pList[0][0] + leadoutLength * math.cos(lAngle)
ylEnd = pList[0][1] + leadoutLength * math.sin(lAngle)
outTmp.write(f'G01 X{xlEnd:.6f} Y{ylEnd:.6f}\n')
else:
if leadinLength > 0:
lAngle = math.atan2(pList[-1][1] - pList[0][1],
pList[-1][0] - pList[0][0])
xlStart = pList[points * 2 - 1][0] + leadinLength * math.cos(lAngle)
ylStart = pList[points * 2 - 1][1] + leadinLength * math.sin(lAngle)
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G01 X{pList[points * 2 - 1][0]} Y{pList[points * 2 - 1][1]}\n')
outTmp.write(f'G01 X{pList[0][0]} Y{pList[0][1]}\n')
else:
outTmp.write(f'G00 X{pList[points * 2 - 1][0]} Y{pList[points * 2 - 1][1]}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G01 X{pList[0][0]} Y{pList[0][1]}\n')
for i in range(1, points * 2):
outTmp.write(f'G01 X{pList[i][0]} Y{pList[i][1]}\n')
if leadoutLength > 0:
lAngle = math.atan2(pList[-1][1] - pList[-2][1],
pList[-1][0] - pList[-2][0])
xlEnd = pList[-1][0] + leadoutLength * math.cos(lAngle)
ylEnd = pList[-1][1] + leadoutLength * math.sin(lAngle)
outTmp.write(f'G01 X{xlEnd:.6f} Y{ylEnd:.6f}\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False
def get_points(points, angle, xC, yC, extRadius, intRadius):
pList = []
for i in range(points * 2):
pAngle = angle + 2 * math.pi * i / (points * 2)
if i % 2 == 0:
x = xC + extRadius * math.cos(pAngle)
y = yC + extRadius * math.sin(pAngle)
else:
x = xC + intRadius * math.cos(pAngle)
y = yC + intRadius * math.sin(pAngle)
pList.append([round(x, 3), round(y, 3)])
return pList
def get_offset(A, B, C, kerfWidth):
# Ax, Ay = A[0] - B[0], A[1] - B[1]
# Cx, Cy = C[0] - B[0], C[1] - B[1]
# a = math.atan2(Ay, Ax)
# c = math.atan2(Cy, Cx)
a = math.atan2(A[1] - B[1], A[0] - B[0])
c = math.atan2(C[1] - B[1], C[0] - B[0])
if a < 0:
a += math.pi * 2
if c < 0:
c += math.pi * 2
ang = (math.pi * 2 + c - a) if a > c else (c - a)
ang = math.radians(90) - (ang / 2)
adj = (kerfWidth / 2) / math.sin(ang)
ofs = math.tan(ang) * adj
return ofs

View File

@@ -0,0 +1,318 @@
'''
triangle.py
Copyright (C) 2020 - 2024 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 gettext
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)
# Conv is the upstream calling module
def preview(Conv, fTmp, fNgc, fNgcBkp,
matNumber, matName,
preAmble, postAmble,
leadinLength, leadoutLength,
xOffset, yOffset,
kerfWidth, isExternal,
angA, angB, angC, sideA, sideB, sideC, angle):
error = ''
msg1 = _('entry is invalid')
valid, xOffset = Conv.conv_is_float(xOffset)
if not valid and xOffset:
msg0 = _('X ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, yOffset = Conv.conv_is_float(yOffset)
if not valid and yOffset:
msg0 = _('Y ORIGIN')
error += f'{msg0} {msg1}\n\n'
valid, leadinLength = Conv.conv_is_float(leadinLength)
if not valid and leadinLength:
msg0 = _('LEAD IN')
error += f'{msg0} {msg1}\n\n'
valid, leadoutLength = Conv.conv_is_float(leadoutLength)
if not valid and leadoutLength:
msg0 = _('LEAD OUT')
error += f'{msg0} {msg1}\n\n'
valid, A = Conv.conv_is_float(angA)
if not valid and angA:
msg0 = _('A ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, B = Conv.conv_is_float(angB)
if not valid and angB:
msg0 = _('B ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, C = Conv.conv_is_float(angC)
if not valid and angC:
msg0 = _('C ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, a = Conv.conv_is_float(sideA)
if not valid and sideA:
msg0 = _('a LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, b = Conv.conv_is_float(sideB)
if not valid and sideB:
msg0 = _('b LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, c = Conv.conv_is_float(sideC)
if not valid and sideC:
msg0 = _('c LENGTH')
error += f'{msg0} {msg1}\n\n'
valid, angle = Conv.conv_is_float(angle)
if not valid and angle:
msg0 = _('ANGLE')
error += f'{msg0} {msg1}\n\n'
valid, kerfWidth = Conv.conv_is_float(kerfWidth)
if not valid:
msg = _('Invalid Kerf Width entry in material')
error += f'{msg}\n\n'
if a and b and c:
if a + b <= c:
msg = _('c must be less than a+b')
error += f'{msg}\n\n'
if a + c <= b:
msg = _('b must be less than a+c')
error += f'{msg}\n\n'
if b + c <= a:
msg = _('a must be less than b+c')
error += f'{msg}\n\n'
if A <= 0 and isinstance(A, float):
msg = _('A ANGLE cannot be zero or less')
error += f'{msg}\n\n'
if B <= 0 and isinstance(B, float):
msg = _('B ANGLE cannot be zero or less')
error += f'{msg}\n\n'
if C <= 0 and isinstance(C, float):
msg = _('C ANGLE cannot be zero or less')
error += f'{msg}\n\n'
if A >= 180 and isinstance(A, float):
msg = _('A ANGLE cannot be 180 or more')
error += f'{msg}\n\n'
if B >= 180 and isinstance(B, float):
msg = _('B ANGLE cannot be 180 or more')
error += f'{msg}\n\n'
if C >= 180 and isinstance(C, float):
msg = _('C ANGLE cannot be 180 or more')
error += f'{msg}\n\n'
if a <= 0 and isinstance(a, float):
msg = _('a LENGTH cannot be zero or less')
error += f'{msg}\n\n'
if b <= 0 and isinstance(b, float):
msg = _('b LENGTH cannot be zero or less')
error += f'{msg}\n\n'
if c <= 0 and isinstance(c, float):
msg = _('c LENGTH cannot be zero or less')
error += f'{msg}\n\n'
if A and B and C:
if not a and not b and not c:
msg = _('"a" or "b" or "c" are required')
error += f'{msg}\n\n'
if A + B + C != 180:
msg = _('"A" + "B" + "C" must equal 180')
error += f'{msg}\n\n'
if error:
return error
angle = math.radians(angle)
A = math.radians(A)
B = math.radians(B)
C = math.radians(C)
leadInOffset = math.sin(math.radians(45)) * leadinLength
leadOutOffset = math.sin(math.radians(45)) * leadoutLength
if A and B and C:
if a:
b = a / math.sin(A) * math.sin(B)
c = a / math.sin(A) * math.sin(C)
elif b:
a = b / math.sin(B) * math.sin(A)
c = b / math.sin(B) * math.sin(C)
elif c:
a = c / math.sin(C) * math.sin(A)
b = c / math.sin(C) * math.sin(B)
elif a and b and c:
A = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c))
B = math.acos((a ** 2 + c ** 2 - b ** 2) / (2 * a * c))
C = math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b))
elif a and b and C:
c = math.sqrt((a ** 2 + b ** 2) - 2 * a * b * math.cos(C))
A = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c))
B = math.acos((a ** 2 + c ** 2 - b ** 2) / (2 * a * c))
elif a and B and c:
b = math.sqrt((a ** 2 + c ** 2) - 2 * a * c * math.cos(B))
A = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c))
C = math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b))
elif A and b and c:
a = math.sqrt((b ** 2 + c ** 2) - 2 * b * c * math.cos(A))
B = math.acos((a ** 2 + c ** 2 - b ** 2) / (2 * a * c))
C = math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b))
else:
msg0 = 'MINIMUM REQUIREMENTS:\n'\
'In processing order are:\n\n'\
'1: "A" + "B" + "C" + ("a" or "b" or "c") \n\n'\
'2: "a" + "b" + "c"\n\n'\
'3: "a" + "b" + "C"\n\n'\
'4: "a" + "B" + "c"\n\n'\
'5: "A" + "b" + "c"\n'
error += f'{msg0}\n\n'
return error
right = math.radians(0)
up = math.radians(90)
left = math.radians(180)
down = math.radians(270)
# get start point
BX = xOffset
BY = yOffset
CX = round(BX + a * math.cos(angle), 3)
CY = round(BY + a * math.sin(angle), 3)
Bx, By = get_offset_coordinates([CX, CY], [BX, BY], B, kerfWidth, isExternal)
BX = Bx + (BX - Bx) * 2
BY = By + (BY - By) * 2
# get remaining points
CX = round(BX + a * math.cos(angle), 3)
CY = round(BY + a * math.sin(angle), 3)
AX = round(BX + c * math.cos(angle + B), 3)
AY = round(BY + c * math.sin(angle + B), 3)
# get offset points
Ax, Ay = get_offset_coordinates([BX, BY], [AX, AY], A, kerfWidth, isExternal)
Bx, By = get_offset_coordinates([CX, CY], [BX, BY], B, kerfWidth, isExternal)
Cx, Cy = get_offset_coordinates([AX, AY], [CX, CY], C, kerfWidth, isExternal)
# get leadin/leadout point
hypotLength = math.sqrt((Ax - Cx) ** 2 + (Ay - Cy) ** 2)
if Ax < Cx:
hypotAngle = left - math.atan((Ay - Cy) / (Cx - Ax))
elif Ax > Cx:
hypotAngle = right - math.atan((Ay - Cy) / (Cx - Ax))
else:
hypotAngle = up
xS = Cx + (hypotLength / 2) * math.cos(hypotAngle)
yS = Cy + (hypotLength / 2) * math.sin(hypotAngle)
# set leadin direction
if isExternal:
if Ay >= By:
dir = [up, right]
else:
dir = [down, left]
else:
if Ay >= By:
dir = [down, left]
else:
dir = [up, right]
outTmp = open(fTmp, 'w')
outNgc = open(fNgc, 'w')
inWiz = open(fNgcBkp, 'r')
for line in inWiz:
line = line.strip()
if line and line[0] not in ';':
if '(new conversational file)' in line:
if('\\n') in preAmble:
outNgc.write('(preamble)\n')
for l in preAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{preAmble} (preamble)\n')
break
elif '(postamble)' in line:
break
elif 'M2' in line.upper() or 'M02' in line.upper() or 'M30' in line.upper():
continue
outNgc.write(f"{line}\n")
outTmp.write('\n(conversational triangle)\n')
outTmp.write(f';using material #{matNumber}: {matName}\n')
outTmp.write(f'M190 P{matNumber}\n')
outTmp.write('M66 P3 L3 Q1\n')
outTmp.write('F#<_hal[plasmac.cut-feed-rate]>\n')
if leadInOffset > 0:
xlCentre = xS + (leadInOffset * math.cos(hypotAngle - dir[0]))
ylCentre = yS + (leadInOffset * math.sin(hypotAngle - dir[0]))
xlStart = xlCentre + (leadInOffset * math.cos(hypotAngle - dir[1]))
ylStart = ylCentre + (leadInOffset * math.sin(hypotAngle - dir[1]))
outTmp.write(f'G00 X{xlStart:.6f} Y{ylStart:.6f}\n')
outTmp.write('M03 $0 S1\n')
outTmp.write(f'G03 X{xS:.6f} Y{yS:.6f} I{xlCentre - xlStart:.6f} J{ylCentre - ylStart:.6f}\n')
else:
outTmp.write(f'G00 X{xS:.6f} Y{yS:.6f}\n')
outTmp.write('M03 $0 S1\n')
if isExternal:
outTmp.write(f'G01 X{Cx:.6f} Y{Cy:.6f}\n')
outTmp.write(f'G01 X{Bx:.6f} Y{By:.6f}\n')
outTmp.write(f'G01 X{Ax:.6f} Y{Ay:.6f}\n')
else:
outTmp.write(f'G01 X{Ax:.6f} Y{Ay:.6f}\n')
outTmp.write(f'G01 X{Bx:.6f} Y{By:.6f}\n')
outTmp.write(f'G01 X{Cx:.6f} Y{Cy:.6f}\n')
outTmp.write(f'G01 X{xS:.6f} Y{yS:.6f}\n')
# set leadout direction
if leadOutOffset > 0:
if isExternal:
if Ay >= By:
dir = [up, left]
else:
dir = [down, right]
else:
if Ay >= By:
dir = [down, right]
else:
dir = [up, left]
xlCentre = xS + (leadOutOffset * math.cos(hypotAngle - dir[0]))
ylCentre = yS + (leadOutOffset * math.sin(hypotAngle - dir[0]))
xlEnd = xlCentre + (leadOutOffset * math.cos(hypotAngle - dir[1]))
ylEnd = ylCentre + (leadOutOffset * math.sin(hypotAngle - dir[1]))
outTmp.write(f'G03 X{xlEnd:.6f} Y{ylEnd:.6f} I{xlCentre - xS:.6f} J{ylCentre - yS:.6f}\n')
outTmp.write('M05 $0\n')
outTmp.close()
outTmp = open(fTmp, 'r')
for line in outTmp:
outNgc.write(line)
outTmp.close()
if('\\n') in postAmble:
outNgc.write('\n(postamble)\n')
for l in postAmble.split('\\n'):
outNgc.write(f'{l}\n')
else:
outNgc.write(f'\n{postAmble} (postamble)\n')
outNgc.write('M02\n')
outNgc.close()
return False
def get_offset_coordinates(fromPoint, thisPoint, angle, kerfWidth, isExternal):
kOffset = kerfWidth / 2
inAng = math.atan2(thisPoint[1] - fromPoint[1], thisPoint[0] - fromPoint[0])
ang = math.radians(90) - (angle / 2)
offset = math.tan(ang) * kOffset
if isExternal:
x = round(thisPoint[0] + offset * math.cos(inAng), 3)
y = round(thisPoint[1] + offset * math.sin(inAng), 3)
x = round(x + kOffset * math.cos(inAng + math.radians(90)), 3)
y = round(y + kOffset * math.sin(inAng + math.radians(90)), 3)
else:
x = round(thisPoint[0] - offset * math.cos(inAng), 3)
y = round(thisPoint[1] - offset * math.sin(inAng), 3)
x = round(x + kOffset * math.cos(inAng + math.radians(-90)), 3)
y = round(y + kOffset * math.sin(inAng + math.radians(-90)), 3)
return x, y