Merged in sopak/flatcam/kamil_combo1 (pull request #29)
PCB panelizing, aligning and gap geocutout shell commands
This commit is contained in:
commit
c23450a68e
465
FlatCAMApp.py
465
FlatCAMApp.py
|
@ -2134,6 +2134,58 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
return 'Ok'
|
return 'Ok'
|
||||||
|
|
||||||
|
|
||||||
|
def geocutout(name, *args):
|
||||||
|
"""
|
||||||
|
subtract gaps from geometry, this will not create new object
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
:param args:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
a, kwa = h(*args)
|
||||||
|
types = {'dia': float,
|
||||||
|
'gapsize': float,
|
||||||
|
'gaps': str}
|
||||||
|
|
||||||
|
#way gaps wil be rendered:
|
||||||
|
# lr - left + right
|
||||||
|
# tb - top + bottom
|
||||||
|
# 4 - left + right +top + bottom
|
||||||
|
# 2lr - 2*left + 2*right
|
||||||
|
# 2tb - 2*top + 2*bottom
|
||||||
|
# 8 - 2*left + 2*right +2*top + 2*bottom
|
||||||
|
|
||||||
|
for key in kwa:
|
||||||
|
if key not in types:
|
||||||
|
return 'Unknown parameter: %s' % key
|
||||||
|
kwa[key] = types[key](kwa[key])
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = self.collection.get_by_name(str(name))
|
||||||
|
except:
|
||||||
|
return "Could not retrieve object: %s" % name
|
||||||
|
|
||||||
|
|
||||||
|
#get min and max data for each object as we just cut rectangles across X or Y
|
||||||
|
xmin, ymin, xmax, ymax = obj.bounds()
|
||||||
|
px = 0.5 * (xmin + xmax)
|
||||||
|
py = 0.5 * (ymin + ymax)
|
||||||
|
lenghtx = (xmax - xmin)
|
||||||
|
lenghty = (ymax - ymin)
|
||||||
|
gapsize = kwa['gapsize']+kwa['dia']/2
|
||||||
|
if kwa['gaps'] == '8' or kwa['gaps']=='2lr':
|
||||||
|
subtract_rectangle(name,xmin-gapsize,py-gapsize+lenghty/4,xmax+gapsize,py+gapsize+lenghty/4)
|
||||||
|
subtract_rectangle(name,xmin-gapsize,py-gapsize-lenghty/4,xmax+gapsize,py+gapsize-lenghty/4)
|
||||||
|
if kwa['gaps'] == '8' or kwa['gaps']=='2tb':
|
||||||
|
subtract_rectangle(name,px-gapsize+lenghtx/4,ymin-gapsize,px+gapsize+lenghtx/4,ymax+gapsize)
|
||||||
|
subtract_rectangle(name,px-gapsize-lenghtx/4,ymin-gapsize,px+gapsize-lenghtx/4,ymax+gapsize)
|
||||||
|
if kwa['gaps'] == '4' or kwa['gaps']=='lr':
|
||||||
|
subtract_rectangle(name,xmin-gapsize,py-gapsize,xmax+gapsize,py+gapsize)
|
||||||
|
if kwa['gaps'] == '4' or kwa['gaps']=='tb':
|
||||||
|
subtract_rectangle(name,px-gapsize,ymin-gapsize,px+gapsize,ymax+gapsize)
|
||||||
|
return 'Ok'
|
||||||
|
|
||||||
def mirror(name, *args):
|
def mirror(name, *args):
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'box': str,
|
types = {'box': str,
|
||||||
|
@ -2202,6 +2254,194 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
return 'Ok'
|
return 'Ok'
|
||||||
|
|
||||||
|
def aligndrillgrid(outname, *args):
|
||||||
|
a, kwa = h(*args)
|
||||||
|
types = {'gridx': float,
|
||||||
|
'gridy': float,
|
||||||
|
'gridoffsetx': float,
|
||||||
|
'gridoffsety': float,
|
||||||
|
'columns':int,
|
||||||
|
'rows':int,
|
||||||
|
'dia': float
|
||||||
|
}
|
||||||
|
for key in kwa:
|
||||||
|
if key not in types:
|
||||||
|
return 'Unknown parameter: %s' % key
|
||||||
|
kwa[key] = types[key](kwa[key])
|
||||||
|
|
||||||
|
|
||||||
|
if 'columns' not in kwa or 'rows' not in kwa:
|
||||||
|
return "ERROR: Specify -columns and -rows"
|
||||||
|
|
||||||
|
if 'gridx' not in kwa or 'gridy' not in kwa:
|
||||||
|
return "ERROR: Specify -gridx and -gridy"
|
||||||
|
|
||||||
|
if 'dia' not in kwa:
|
||||||
|
return "ERROR: Specify -dia"
|
||||||
|
|
||||||
|
if 'gridoffsetx' not in kwa:
|
||||||
|
gridoffsetx=0
|
||||||
|
else:
|
||||||
|
gridoffsetx=kwa['gridoffsetx']
|
||||||
|
|
||||||
|
if 'gridoffsety' not in kwa:
|
||||||
|
gridoffsety=0
|
||||||
|
else:
|
||||||
|
gridoffsety=kwa['gridoffsety']
|
||||||
|
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
tools = {"1": {"C": kwa['dia']}}
|
||||||
|
|
||||||
|
def aligndrillgrid_init_me(init_obj, app_obj):
|
||||||
|
drills = []
|
||||||
|
currenty=0
|
||||||
|
for row in range(kwa['rows']):
|
||||||
|
currentx=0
|
||||||
|
for col in range(kwa['columns']):
|
||||||
|
point = Point(currentx+gridoffsetx,currenty+gridoffsety)
|
||||||
|
drills.append({"point": point, "tool": "1"})
|
||||||
|
currentx=currentx+kwa['gridx']
|
||||||
|
currenty=currenty+kwa['gridy']
|
||||||
|
init_obj.tools = tools
|
||||||
|
init_obj.drills = drills
|
||||||
|
init_obj.create_geometry()
|
||||||
|
|
||||||
|
self.new_object("excellon", outname , aligndrillgrid_init_me)
|
||||||
|
|
||||||
|
def aligndrill(name, *args):
|
||||||
|
a, kwa = h(*args)
|
||||||
|
types = {'box': str,
|
||||||
|
'axis': str,
|
||||||
|
'holes': str,
|
||||||
|
'grid': float,
|
||||||
|
'minoffset': float,
|
||||||
|
'gridoffset': float,
|
||||||
|
'axisoffset': float,
|
||||||
|
'dia': float,
|
||||||
|
'dist': float}
|
||||||
|
|
||||||
|
for key in kwa:
|
||||||
|
if key not in types:
|
||||||
|
return 'Unknown parameter: %s' % key
|
||||||
|
kwa[key] = types[key](kwa[key])
|
||||||
|
|
||||||
|
# Get source object.
|
||||||
|
try:
|
||||||
|
obj = self.collection.get_by_name(str(name))
|
||||||
|
except:
|
||||||
|
return "Could not retrieve object: %s" % name
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
return "Object not found: %s" % name
|
||||||
|
|
||||||
|
if not isinstance(obj, FlatCAMGeometry) and not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMExcellon):
|
||||||
|
return "ERROR: Only Gerber, Geometry and Excellon objects can be used."
|
||||||
|
|
||||||
|
# Axis
|
||||||
|
try:
|
||||||
|
axis = kwa['axis'].upper()
|
||||||
|
except KeyError:
|
||||||
|
return "ERROR: Specify -axis X or -axis Y"
|
||||||
|
|
||||||
|
if not ('holes' in kwa or ('grid' in kwa and 'gridoffset' in kwa)):
|
||||||
|
return "ERROR: Specify -holes or -grid with -gridoffset "
|
||||||
|
|
||||||
|
if 'holes' in kwa:
|
||||||
|
try:
|
||||||
|
holes = eval("[" + kwa['holes'] + "]")
|
||||||
|
except KeyError:
|
||||||
|
return "ERROR: Wrong -holes format (X1,Y1),(X2,Y2)"
|
||||||
|
|
||||||
|
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
tools = {"1": {"C": kwa['dia']}}
|
||||||
|
|
||||||
|
def alligndrill_init_me(init_obj, app_obj):
|
||||||
|
|
||||||
|
drills = []
|
||||||
|
if 'holes' in kwa:
|
||||||
|
for hole in holes:
|
||||||
|
point = Point(hole)
|
||||||
|
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
||||||
|
drills.append({"point": point, "tool": "1"})
|
||||||
|
drills.append({"point": point_mirror, "tool": "1"})
|
||||||
|
else:
|
||||||
|
if not 'box' in kwa:
|
||||||
|
return "ERROR: -grid can be used only for -box"
|
||||||
|
|
||||||
|
if 'axisoffset' in kwa:
|
||||||
|
axisoffset=kwa['axisoffset']
|
||||||
|
else:
|
||||||
|
axisoffset=0
|
||||||
|
|
||||||
|
#this will align hole to given aligngridoffset and minimal offset from pcb, based on selected axis
|
||||||
|
if axis == "X":
|
||||||
|
firstpoint=kwa['gridoffset']
|
||||||
|
while (xmin-kwa['minoffset'])<firstpoint:
|
||||||
|
firstpoint=firstpoint-kwa['grid']
|
||||||
|
lastpoint=kwa['gridoffset']
|
||||||
|
while (xmax+kwa['minoffset'])>lastpoint:
|
||||||
|
lastpoint=lastpoint+kwa['grid']
|
||||||
|
localHoles=(firstpoint,axisoffset),(lastpoint,axisoffset)
|
||||||
|
else:
|
||||||
|
firstpoint=kwa['gridoffset']
|
||||||
|
while (ymin-kwa['minoffset'])<firstpoint:
|
||||||
|
firstpoint=firstpoint-kwa['grid']
|
||||||
|
lastpoint=kwa['gridoffset']
|
||||||
|
while (ymax+kwa['minoffset'])>lastpoint:
|
||||||
|
lastpoint=lastpoint+kwa['grid']
|
||||||
|
localHoles=(axisoffset,firstpoint),(axisoffset,lastpoint)
|
||||||
|
|
||||||
|
for hole in localHoles:
|
||||||
|
point = Point(hole)
|
||||||
|
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
||||||
|
drills.append({"point": point, "tool": "1"})
|
||||||
|
drills.append({"point": point_mirror, "tool": "1"})
|
||||||
|
|
||||||
|
init_obj.tools = tools
|
||||||
|
init_obj.drills = drills
|
||||||
|
init_obj.create_geometry()
|
||||||
|
|
||||||
|
# Box
|
||||||
|
if 'box' in kwa:
|
||||||
|
try:
|
||||||
|
box = self.collection.get_by_name(kwa['box'])
|
||||||
|
except:
|
||||||
|
return "Could not retrieve object box: %s" % kwa['box']
|
||||||
|
|
||||||
|
if box is None:
|
||||||
|
return "Object box not found: %s" % kwa['box']
|
||||||
|
|
||||||
|
try:
|
||||||
|
xmin, ymin, xmax, ymax = box.bounds()
|
||||||
|
px = 0.5 * (xmin + xmax)
|
||||||
|
py = 0.5 * (ymin + ymax)
|
||||||
|
|
||||||
|
obj.app.new_object("excellon", name + "_aligndrill", alligndrill_init_me)
|
||||||
|
|
||||||
|
except Exception, e:
|
||||||
|
return "Operation failed: %s" % str(e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
dist = float(kwa['dist'])
|
||||||
|
except KeyError:
|
||||||
|
dist = 0.0
|
||||||
|
except ValueError:
|
||||||
|
return "Invalid distance: %s" % kwa['dist']
|
||||||
|
|
||||||
|
try:
|
||||||
|
px=dist
|
||||||
|
py=dist
|
||||||
|
obj.app.new_object("excellon", name + "_alligndrill", alligndrill_init_me)
|
||||||
|
except Exception, e:
|
||||||
|
return "Operation failed: %s" % str(e)
|
||||||
|
|
||||||
|
return 'Ok'
|
||||||
|
|
||||||
|
|
||||||
def drillcncjob(name, *args):
|
def drillcncjob(name, *args):
|
||||||
a, kwa = h(*args)
|
a, kwa = h(*args)
|
||||||
types = {'tools': str,
|
types = {'tools': str,
|
||||||
|
@ -2492,6 +2732,28 @@ class App(QtCore.QObject):
|
||||||
return add_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y,
|
return add_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y,
|
||||||
topright_x, topright_y, topright_x, botleft_y)
|
topright_x, topright_y, topright_x, botleft_y)
|
||||||
|
|
||||||
|
def subtract_poly(obj_name, *args):
|
||||||
|
if len(args) % 2 != 0:
|
||||||
|
return "Incomplete coordinate."
|
||||||
|
|
||||||
|
points = [[float(args[2*i]), float(args[2*i+1])] for i in range(len(args)/2)]
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = self.collection.get_by_name(str(obj_name))
|
||||||
|
except:
|
||||||
|
return "Could not retrieve object: %s" % obj_name
|
||||||
|
if obj is None:
|
||||||
|
return "Object not found: %s" % obj_name
|
||||||
|
|
||||||
|
obj.subtract_polygon(points)
|
||||||
|
obj.plot()
|
||||||
|
|
||||||
|
return "OK."
|
||||||
|
|
||||||
|
def subtract_rectangle(obj_name, botleft_x, botleft_y, topright_x, topright_y):
|
||||||
|
return subtract_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y,
|
||||||
|
topright_x, topright_y, topright_x, botleft_y)
|
||||||
|
|
||||||
def add_circle(obj_name, center_x, center_y, radius):
|
def add_circle(obj_name, center_x, center_y, radius):
|
||||||
try:
|
try:
|
||||||
obj = self.collection.get_by_name(str(obj_name))
|
obj = self.collection.get_by_name(str(obj_name))
|
||||||
|
@ -2510,6 +2772,8 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
def delete(obj_name):
|
def delete(obj_name):
|
||||||
try:
|
try:
|
||||||
|
#deselect all to avoid delete selected object when run delete from shell
|
||||||
|
self.collection.set_all_inactive()
|
||||||
self.collection.set_active(str(obj_name))
|
self.collection.set_active(str(obj_name))
|
||||||
self.on_delete()
|
self.on_delete()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
@ -2541,6 +2805,121 @@ class App(QtCore.QObject):
|
||||||
if objs is not None:
|
if objs is not None:
|
||||||
self.new_object("geometry", obj_name, initialize)
|
self.new_object("geometry", obj_name, initialize)
|
||||||
|
|
||||||
|
def join_excellons(obj_name, *obj_names):
|
||||||
|
objs = []
|
||||||
|
for obj_n in obj_names:
|
||||||
|
obj = self.collection.get_by_name(str(obj_n))
|
||||||
|
if obj is None:
|
||||||
|
return "Object not found: %s" % obj_n
|
||||||
|
else:
|
||||||
|
objs.append(obj)
|
||||||
|
|
||||||
|
def initialize(obj, app):
|
||||||
|
FlatCAMExcellon.merge(objs, obj)
|
||||||
|
|
||||||
|
if objs is not None:
|
||||||
|
self.new_object("excellon", obj_name, initialize)
|
||||||
|
|
||||||
|
def panelize(name, *args):
|
||||||
|
a, kwa = h(*args)
|
||||||
|
types = {'box': str,
|
||||||
|
'spacing_columns': float,
|
||||||
|
'spacing_rows': float,
|
||||||
|
'columns': int,
|
||||||
|
'rows': int,
|
||||||
|
'outname': str}
|
||||||
|
|
||||||
|
for key in kwa:
|
||||||
|
if key not in types:
|
||||||
|
return 'Unknown parameter: %s' % key
|
||||||
|
kwa[key] = types[key](kwa[key])
|
||||||
|
|
||||||
|
# Get source object.
|
||||||
|
try:
|
||||||
|
obj = self.collection.get_by_name(str(name))
|
||||||
|
except:
|
||||||
|
return "Could not retrieve object: %s" % name
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
return "Object not found: %s" % name
|
||||||
|
|
||||||
|
if 'box' in kwa:
|
||||||
|
boxname=kwa['box']
|
||||||
|
try:
|
||||||
|
box = self.collection.get_by_name(boxname)
|
||||||
|
except:
|
||||||
|
return "Could not retrieve object: %s" % name
|
||||||
|
else:
|
||||||
|
box=obj
|
||||||
|
|
||||||
|
if 'columns' not in kwa or 'rows' not in kwa:
|
||||||
|
return "ERROR: Specify -columns and -rows"
|
||||||
|
|
||||||
|
if 'outname' in kwa:
|
||||||
|
outname=kwa['outname']
|
||||||
|
else:
|
||||||
|
outname=name+'_panelized'
|
||||||
|
|
||||||
|
if 'spacing_columns' in kwa:
|
||||||
|
spacing_columns=kwa['spacing_columns']
|
||||||
|
else:
|
||||||
|
spacing_columns=5
|
||||||
|
|
||||||
|
if 'spacing_rows' in kwa:
|
||||||
|
spacing_rows=kwa['spacing_rows']
|
||||||
|
else:
|
||||||
|
spacing_rows=5
|
||||||
|
|
||||||
|
xmin, ymin, xmax, ymax = box.bounds()
|
||||||
|
lenghtx = xmax-xmin+spacing_columns
|
||||||
|
lenghty = ymax-ymin+spacing_rows
|
||||||
|
|
||||||
|
currenty=0
|
||||||
|
def initialize_local(obj_init, app):
|
||||||
|
obj_init.solid_geometry = obj.solid_geometry
|
||||||
|
obj_init.offset([float(currentx), float(currenty)]),
|
||||||
|
|
||||||
|
def initialize_local_excellon(obj_init, app):
|
||||||
|
FlatCAMExcellon.merge(obj, obj_init)
|
||||||
|
obj_init.offset([float(currentx), float(currenty)]),
|
||||||
|
|
||||||
|
def initialize_geometry(obj_init, app):
|
||||||
|
FlatCAMGeometry.merge(objs, obj_init)
|
||||||
|
|
||||||
|
def initialize_excellon(obj_init, app):
|
||||||
|
FlatCAMExcellon.merge(objs, obj_init)
|
||||||
|
|
||||||
|
objs=[]
|
||||||
|
if obj is not None:
|
||||||
|
|
||||||
|
for row in range(kwa['rows']):
|
||||||
|
currentx=0
|
||||||
|
for col in range(kwa['columns']):
|
||||||
|
local_outname=outname+".tmp."+str(col)+"."+str(row)
|
||||||
|
if isinstance(obj, FlatCAMExcellon):
|
||||||
|
new_obj=self.new_object("excellon", local_outname, initialize_local_excellon)
|
||||||
|
else:
|
||||||
|
new_obj=self.new_object("geometry", local_outname, initialize_local)
|
||||||
|
objs.append(new_obj)
|
||||||
|
currentx=currentx+lenghtx
|
||||||
|
currenty=currenty+lenghty
|
||||||
|
|
||||||
|
if isinstance(obj, FlatCAMExcellon):
|
||||||
|
self.new_object("excellon", outname, initialize_excellon)
|
||||||
|
else:
|
||||||
|
self.new_object("geometry", outname, initialize_geometry)
|
||||||
|
|
||||||
|
#deselect all to avoid delete selected object when run delete from shell
|
||||||
|
self.collection.set_all_inactive()
|
||||||
|
for delobj in objs:
|
||||||
|
self.collection.set_active(delobj.options['name'])
|
||||||
|
self.on_delete()
|
||||||
|
|
||||||
|
else:
|
||||||
|
return "ERROR: obj is None"
|
||||||
|
|
||||||
|
return "Ok"
|
||||||
|
|
||||||
def make_docs():
|
def make_docs():
|
||||||
output = ''
|
output = ''
|
||||||
import collections
|
import collections
|
||||||
|
@ -2721,6 +3100,30 @@ class App(QtCore.QObject):
|
||||||
" gapsize: size of gap\n" +
|
" gapsize: size of gap\n" +
|
||||||
" gaps: type of gaps"
|
" gaps: type of gaps"
|
||||||
},
|
},
|
||||||
|
'geocutout': {
|
||||||
|
'fcn': geocutout,
|
||||||
|
'help': "Cut holding gaps from geometry.\n" +
|
||||||
|
"> geocutout <name> [-dia <3.0 (float)>] [-margin <0.0 (float)>] [-gapsize <0.5 (float)>] [-gaps <lr (8|4|tb|lr|2tb|2lr)>]\n" +
|
||||||
|
" name: Name of the geometry object\n" +
|
||||||
|
" dia: Tool diameter\n" +
|
||||||
|
" margin: Margin over bounds\n" +
|
||||||
|
" gapsize: size of gap\n" +
|
||||||
|
" gaps: type of gaps\n" +
|
||||||
|
"\n" +
|
||||||
|
" example:\n" +
|
||||||
|
"\n" +
|
||||||
|
" #isolate margin for example from fritzing arduino shield or any svg etc\n" +
|
||||||
|
" isolate BCu_margin -dia 3 -overlap 1\n" +
|
||||||
|
"\n" +
|
||||||
|
" #create exteriors from isolated object\n" +
|
||||||
|
" exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" +
|
||||||
|
"\n" +
|
||||||
|
" #delete isolated object if you dond need id anymore\n" +
|
||||||
|
" delete BCu_margin_iso\n" +
|
||||||
|
"\n" +
|
||||||
|
" #finally cut holding gaps\n" +
|
||||||
|
" geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"
|
||||||
|
},
|
||||||
'mirror': {
|
'mirror': {
|
||||||
'fcn': mirror,
|
'fcn': mirror,
|
||||||
'help': "Mirror a layer.\n" +
|
'help': "Mirror a layer.\n" +
|
||||||
|
@ -2730,6 +3133,33 @@ class App(QtCore.QObject):
|
||||||
" axis: Mirror axis parallel to the X or Y axis.\n" +
|
" axis: Mirror axis parallel to the X or Y axis.\n" +
|
||||||
" dist: Distance of the mirror axis to the X or Y axis."
|
" dist: Distance of the mirror axis to the X or Y axis."
|
||||||
},
|
},
|
||||||
|
'aligndrillgrid': {
|
||||||
|
'fcn': aligndrillgrid,
|
||||||
|
'help': "Create excellon with drills for aligment grid.\n" +
|
||||||
|
"> aligndrillgrid <outname> [-dia <3.0 (float)>] -gridx <float> [-gridoffsetx <0 (float)>] -gridy <float> [-gridoffsety <0 (float)>] -columns <int> -rows <int>\n" +
|
||||||
|
" outname: Name of the object to create.\n" +
|
||||||
|
" dia: Tool diameter\n" +
|
||||||
|
" gridx: grid size in X axis\n" +
|
||||||
|
" gridoffsetx: move grid from origin\n" +
|
||||||
|
" gridy: grid size in Y axis\n" +
|
||||||
|
" gridoffsety: move grid from origin\n" +
|
||||||
|
" colums: grid holes on X axis\n" +
|
||||||
|
" rows: grid holes on Y axis\n"
|
||||||
|
},
|
||||||
|
'aligndrill': {
|
||||||
|
'fcn': aligndrill,
|
||||||
|
'help': "Create excellon with drills for aligment.\n" +
|
||||||
|
"> aligndrill <name> [-dia <3.0 (float)>] -axis <X|Y> [-box <nameOfBox> -minoffset <float> [-grid <10 (float)> -gridoffset <5 (float)> [-axisoffset <0 (float)>]] | -dist <number>]\n" +
|
||||||
|
" name: Name of the object (Gerber or Excellon) to mirror.\n" +
|
||||||
|
" dia: Tool diameter\n" +
|
||||||
|
" box: Name of object which act as box (cutout for example.)\n" +
|
||||||
|
" grid: aligning to grid, for thouse, who have aligning pins inside table in grid (-5,0),(5,0),(15,0)..." +
|
||||||
|
" gridoffset: offset of grid from 0 position" +
|
||||||
|
" minoffset: min and max distance between align hole and pcb" +
|
||||||
|
" axisoffset: offset on second axis before aligment holes" +
|
||||||
|
" axis: Mirror axis parallel to the X or Y axis.\n" +
|
||||||
|
" dist: Distance of the mirror axis to the X or Y axis."
|
||||||
|
},
|
||||||
'exteriors': {
|
'exteriors': {
|
||||||
'fcn': exteriors,
|
'fcn': exteriors,
|
||||||
'help': "Get exteriors of polygons.\n" +
|
'help': "Get exteriors of polygons.\n" +
|
||||||
|
@ -2827,6 +3257,13 @@ class App(QtCore.QObject):
|
||||||
' name: Name of the geometry object to which to append the polygon.\n' +
|
' name: Name of the geometry object to which to append the polygon.\n' +
|
||||||
' xi, yi: Coordinates of points in the polygon.'
|
' xi, yi: Coordinates of points in the polygon.'
|
||||||
},
|
},
|
||||||
|
'subtract_poly': {
|
||||||
|
'fcn': subtract_poly,
|
||||||
|
'help': 'Subtract polygon from the given Geometry object.\n' +
|
||||||
|
'> subtract_poly <name> <x0> <y0> <x1> <y1> <x2> <y2> [x3 y3 [...]]\n' +
|
||||||
|
' name: Name of the geometry object, which will be sutracted.\n' +
|
||||||
|
' xi, yi: Coordinates of points in the polygon.'
|
||||||
|
},
|
||||||
'delete': {
|
'delete': {
|
||||||
'fcn': delete,
|
'fcn': delete,
|
||||||
'help': 'Deletes the give object.\n' +
|
'help': 'Deletes the give object.\n' +
|
||||||
|
@ -2850,6 +3287,34 @@ class App(QtCore.QObject):
|
||||||
' out_name: Name of the new geometry object.' +
|
' out_name: Name of the new geometry object.' +
|
||||||
' obj_name_0... names of the objects to join'
|
' obj_name_0... names of the objects to join'
|
||||||
},
|
},
|
||||||
|
'join_excellons': {
|
||||||
|
'fcn': join_excellons,
|
||||||
|
'help': 'Runs a merge operation (join) on the excellon ' +
|
||||||
|
'objects.' +
|
||||||
|
'> join_excellons <out_name> <obj_name_0>....\n' +
|
||||||
|
' out_name: Name of the new excellon object.' +
|
||||||
|
' obj_name_0... names of the objects to join'
|
||||||
|
},
|
||||||
|
'panelize': {
|
||||||
|
'fcn': panelize,
|
||||||
|
'help': "Simple panelize geometries.\n" +
|
||||||
|
"> panelize <name> [-box <nameOfBox>] [-spacing_columns <5 (float)>] [-spacing_rows <5 (float)>] -columns <int> -rows <int> [-outname <n>]\n" +
|
||||||
|
" name: Name of the object to panelize.\n" +
|
||||||
|
" box: Name of object which act as box (cutout for example.) for cutout boundary. Object from name is used if not specified.\n" +
|
||||||
|
" spacing_columns: spacing between columns\n"+
|
||||||
|
" spacing_rows: spacing between rows\n"+
|
||||||
|
" columns: number of columns\n"+
|
||||||
|
" rows: number of rows\n"+
|
||||||
|
" outname: Name of the new geometry object."
|
||||||
|
},
|
||||||
|
'subtract_rect': {
|
||||||
|
'fcn': subtract_rectangle,
|
||||||
|
'help': 'Subtract rectange from the given Geometry object.\n' +
|
||||||
|
'> subtract_rect <name> <botleft_x> <botleft_y> <topright_x> <topright_y>\n' +
|
||||||
|
' name: Name of the geometry object, which will be subtracted.\n' +
|
||||||
|
' botleft_x, botleft_y: Coordinates of the bottom left corner.\n' +
|
||||||
|
' topright_x, topright_y Coordinates of the top right corner.'
|
||||||
|
},
|
||||||
'add_rect': {
|
'add_rect': {
|
||||||
'fcn': add_rectangle,
|
'fcn': add_rectangle,
|
||||||
'help': 'Creates a rectange in the given Geometry object.\n' +
|
'help': 'Creates a rectange in the given Geometry object.\n' +
|
||||||
|
|
112
FlatCAMObj.py
112
FlatCAMObj.py
|
@ -123,8 +123,12 @@ class FlatCAMObj(QtCore.QObject):
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.to_form()")
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
self.set_form_item(option)
|
try:
|
||||||
|
self.set_form_item(option)
|
||||||
|
except:
|
||||||
|
self.app.log.warning("Unexpected error:", sys.exc_info())
|
||||||
|
|
||||||
def read_form(self):
|
def read_form(self):
|
||||||
"""
|
"""
|
||||||
|
@ -135,7 +139,11 @@ class FlatCAMObj(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
|
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()")
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
self.read_form_item(option)
|
try:
|
||||||
|
self.read_form_item(option)
|
||||||
|
except:
|
||||||
|
self.app.log.warning("Unexpected error:", sys.exc_info())
|
||||||
|
|
||||||
|
|
||||||
def build_ui(self):
|
def build_ui(self):
|
||||||
"""
|
"""
|
||||||
|
@ -191,11 +199,16 @@ class FlatCAMObj(QtCore.QObject):
|
||||||
:type option: str
|
:type option: str
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
#try read field only when option have equivalent in form_fields
|
||||||
try:
|
if option in self.form_fields:
|
||||||
self.options[option] = self.form_fields[option].get_value()
|
option_type=type(self.options[option])
|
||||||
except KeyError:
|
try:
|
||||||
self.app.log.warning("Failed to read option from field: %s" % option)
|
value=self.form_fields[option].get_value()
|
||||||
|
#catch per option as it was ignored anyway, also when syntax error (probably uninitialized field),don't read either.
|
||||||
|
except (KeyError,SyntaxError):
|
||||||
|
self.app.log.warning("Failed to read option from field: %s" % option)
|
||||||
|
else:
|
||||||
|
self.app.log.warning("Form fied does not exists: %s" % option)
|
||||||
|
|
||||||
def plot(self):
|
def plot(self):
|
||||||
"""
|
"""
|
||||||
|
@ -630,6 +643,76 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
||||||
# from predecessors.
|
# from predecessors.
|
||||||
self.ser_attrs += ['options', 'kind']
|
self.ser_attrs += ['options', 'kind']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def merge(exc_list, exc_final):
|
||||||
|
"""
|
||||||
|
Merge excellons in exc_list into exc_final.
|
||||||
|
Options are allways copied from source .
|
||||||
|
|
||||||
|
Tools are also merged, if name for tool is same and size differs, then as name is used next available number from both lists
|
||||||
|
|
||||||
|
if only one object is specified in exc_list then this acts as copy only
|
||||||
|
|
||||||
|
:param exc_list: List or one object of FlatCAMExcellon Objects to join.
|
||||||
|
:param exc_final: Destination FlatCAMExcellon object.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if type(exc_list) is not list:
|
||||||
|
exc_list_real= list()
|
||||||
|
exc_list_real.append(exc_list)
|
||||||
|
else:
|
||||||
|
exc_list_real=exc_list
|
||||||
|
|
||||||
|
for exc in exc_list_real:
|
||||||
|
# Expand lists
|
||||||
|
if type(exc) is list:
|
||||||
|
FlatCAMExcellon.merge(exc, exc_final)
|
||||||
|
# If not list, merge excellons
|
||||||
|
else:
|
||||||
|
|
||||||
|
# TODO: I realize forms does not save values into options , when object is deselected
|
||||||
|
# leave this here for future use
|
||||||
|
# this reinitialize options based on forms, all steps may not be necessary
|
||||||
|
# exc.app.collection.set_active(exc.options['name'])
|
||||||
|
# exc.to_form()
|
||||||
|
# exc.read_form()
|
||||||
|
for option in exc.options:
|
||||||
|
if option is not 'name':
|
||||||
|
try:
|
||||||
|
exc_final.options[option] = exc.options[option]
|
||||||
|
except:
|
||||||
|
exc.app.log.warning("Failed to copy option.",option)
|
||||||
|
|
||||||
|
#deep copy of all drills,to avoid any references
|
||||||
|
for drill in exc.drills:
|
||||||
|
point = Point(drill['point'].x,drill['point'].y)
|
||||||
|
exc_final.drills.append({"point": point, "tool": drill['tool']})
|
||||||
|
toolsrework=dict()
|
||||||
|
max_numeric_tool=0
|
||||||
|
for toolname in exc.tools.iterkeys():
|
||||||
|
numeric_tool=int(toolname)
|
||||||
|
if numeric_tool>max_numeric_tool:
|
||||||
|
max_numeric_tool=numeric_tool
|
||||||
|
toolsrework[exc.tools[toolname]['C']]=toolname
|
||||||
|
|
||||||
|
#exc_final as last because names from final tools will be used
|
||||||
|
for toolname in exc_final.tools.iterkeys():
|
||||||
|
numeric_tool=int(toolname)
|
||||||
|
if numeric_tool>max_numeric_tool:
|
||||||
|
max_numeric_tool=numeric_tool
|
||||||
|
toolsrework[exc_final.tools[toolname]['C']]=toolname
|
||||||
|
|
||||||
|
for toolvalues in toolsrework.iterkeys():
|
||||||
|
if toolsrework[toolvalues] in exc_final.tools:
|
||||||
|
if exc_final.tools[toolsrework[toolvalues]]!={"C": toolvalues}:
|
||||||
|
exc_final.tools[str(max_numeric_tool+1)]={"C": toolvalues}
|
||||||
|
else:
|
||||||
|
exc_final.tools[toolsrework[toolvalues]]={"C": toolvalues}
|
||||||
|
#this value was not co
|
||||||
|
exc_final.zeros=exc.zeros
|
||||||
|
exc_final.create_geometry()
|
||||||
|
|
||||||
def build_ui(self):
|
def build_ui(self):
|
||||||
FlatCAMObj.build_ui(self)
|
FlatCAMObj.build_ui(self)
|
||||||
|
|
||||||
|
@ -1264,11 +1347,16 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
||||||
|
|
||||||
dx, dy = vect
|
dx, dy = vect
|
||||||
|
|
||||||
if type(self.solid_geometry) == list:
|
def translate_recursion(geom):
|
||||||
self.solid_geometry = [affinity.translate(g, xoff=dx, yoff=dy)
|
if type(geom) == list:
|
||||||
for g in self.solid_geometry]
|
geoms=list()
|
||||||
else:
|
for local_geom in geom:
|
||||||
self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
|
geoms.append(translate_recursion(local_geom))
|
||||||
|
return geoms
|
||||||
|
else:
|
||||||
|
return affinity.translate(geom, xoff=dx, yoff=dy)
|
||||||
|
|
||||||
|
self.solid_geometry=translate_recursion(self.solid_geometry)
|
||||||
|
|
||||||
def convert_units(self, units):
|
def convert_units(self, units):
|
||||||
factor = Geometry.convert_units(self, units)
|
factor = Geometry.convert_units(self, units)
|
||||||
|
|
|
@ -244,6 +244,27 @@ class ObjectCollection(QtCore.QAbstractListModel):
|
||||||
iobj = self.createIndex(self.get_names().index(name), 0) # Column 0
|
iobj = self.createIndex(self.get_names().index(name), 0) # Column 0
|
||||||
self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Select)
|
self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Select)
|
||||||
|
|
||||||
|
def set_inactive(self, name):
|
||||||
|
"""
|
||||||
|
Unselect object by name from the project list. This triggers the
|
||||||
|
list_selection_changed event and call on_list_selection_changed.
|
||||||
|
|
||||||
|
:param name: Name of the FlatCAM Object
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
iobj = self.createIndex(self.get_names().index(name), 0) # Column 0
|
||||||
|
self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Deselect)
|
||||||
|
|
||||||
|
def set_all_inactive(self):
|
||||||
|
"""
|
||||||
|
Unselect all objects from the project list. This triggers the
|
||||||
|
list_selection_changed event and call on_list_selection_changed.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
for name in self.get_names():
|
||||||
|
self.set_inactive(name)
|
||||||
|
|
||||||
def on_list_selection_change(self, current, previous):
|
def on_list_selection_change(self, current, previous):
|
||||||
FlatCAMApp.App.log.debug("on_list_selection_change()")
|
FlatCAMApp.App.log.debug("on_list_selection_change()")
|
||||||
FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
|
FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
|
||||||
|
|
23
camlib.py
23
camlib.py
|
@ -136,6 +136,29 @@ class Geometry(object):
|
||||||
log.error("Failed to run union on polygons.")
|
log.error("Failed to run union on polygons.")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def subtract_polygon(self, points):
|
||||||
|
"""
|
||||||
|
Subtract polygon from the given object. This only operates on the paths in the original geometry, i.e. it converts polygons into paths.
|
||||||
|
|
||||||
|
:param points: The vertices of the polygon.
|
||||||
|
:return: none
|
||||||
|
"""
|
||||||
|
if self.solid_geometry is None:
|
||||||
|
self.solid_geometry = []
|
||||||
|
|
||||||
|
#pathonly should be allways True, otherwise polygons are not subtracted
|
||||||
|
flat_geometry = self.flatten(pathonly=True)
|
||||||
|
log.debug("%d paths" % len(flat_geometry))
|
||||||
|
polygon=Polygon(points)
|
||||||
|
toolgeo=cascaded_union(polygon)
|
||||||
|
diffs=[]
|
||||||
|
for target in flat_geometry:
|
||||||
|
if type(target) == LineString or type(target) == LinearRing:
|
||||||
|
diffs.append(target.difference(toolgeo))
|
||||||
|
else:
|
||||||
|
log.warning("Not implemented.")
|
||||||
|
self.solid_geometry=cascaded_union(diffs)
|
||||||
|
|
||||||
def bounds(self):
|
def bounds(self):
|
||||||
"""
|
"""
|
||||||
Returns coordinates of rectangular bounds
|
Returns coordinates of rectangular bounds
|
||||||
|
|
Loading…
Reference in New Issue