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