Cutout generator implemented
This commit is contained in:
251
camlib.py
251
camlib.py
@@ -7,8 +7,10 @@ from shapely.geometry import MultiPoint, MultiPolygon
|
|||||||
from shapely.geometry import box as shply_box
|
from shapely.geometry import box as shply_box
|
||||||
from shapely.ops import cascaded_union
|
from shapely.ops import cascaded_union
|
||||||
|
|
||||||
|
# Used for solid polygons in Matplotlib
|
||||||
from descartes.patch import PolygonPatch
|
from descartes.patch import PolygonPatch
|
||||||
|
|
||||||
|
|
||||||
class Geometry:
|
class Geometry:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Units (in or mm)
|
# Units (in or mm)
|
||||||
@@ -18,20 +20,20 @@ class Geometry:
|
|||||||
self.solid_geometry = None
|
self.solid_geometry = None
|
||||||
|
|
||||||
def isolation_geometry(self, offset):
|
def isolation_geometry(self, offset):
|
||||||
'''
|
"""
|
||||||
Creates contours around geometry at a given
|
Creates contours around geometry at a given
|
||||||
offset distance.
|
offset distance.
|
||||||
'''
|
"""
|
||||||
return self.solid_geometry.buffer(offset)
|
return self.solid_geometry.buffer(offset)
|
||||||
|
|
||||||
def bounds(self):
|
def bounds(self):
|
||||||
'''
|
"""
|
||||||
Returns coordinates of rectangular bounds
|
Returns coordinates of rectangular bounds
|
||||||
of geometry: (xmin, ymin, xmax, ymax).
|
of geometry: (xmin, ymin, xmax, ymax).
|
||||||
'''
|
"""
|
||||||
if self.solid_geometry == None:
|
if self.solid_geometry is None:
|
||||||
print "Warning: solid_geometry not computed yet."
|
print "Warning: solid_geometry not computed yet."
|
||||||
return (0,0,0,0)
|
return (0, 0, 0, 0)
|
||||||
|
|
||||||
if type(self.solid_geometry) == list:
|
if type(self.solid_geometry) == list:
|
||||||
return cascaded_union(self.solid_geometry).bounds
|
return cascaded_union(self.solid_geometry).bounds
|
||||||
@@ -39,33 +41,33 @@ class Geometry:
|
|||||||
return self.solid_geometry.bounds
|
return self.solid_geometry.bounds
|
||||||
|
|
||||||
def size(self):
|
def size(self):
|
||||||
'''
|
"""
|
||||||
Returns (width, height) of rectangular
|
Returns (width, height) of rectangular
|
||||||
bounds of geometry.
|
bounds of geometry.
|
||||||
'''
|
"""
|
||||||
if self.solid_geometry == None:
|
if self.solid_geometry is None:
|
||||||
print "Warning: solid_geometry not computed yet."
|
print "Warning: solid_geometry not computed yet."
|
||||||
return 0
|
return 0
|
||||||
bounds = self.bounds()
|
bounds = self.bounds()
|
||||||
return (bounds[2]-bounds[0], bounds[3]-bounds[1])
|
return (bounds[2]-bounds[0], bounds[3]-bounds[1])
|
||||||
|
|
||||||
def get_empty_area(self, boundary=None):
|
def get_empty_area(self, boundary=None):
|
||||||
'''
|
"""
|
||||||
Returns the complement of self.solid_geometry within
|
Returns the complement of self.solid_geometry within
|
||||||
the given boundary polygon. If not specified, it defaults to
|
the given boundary polygon. If not specified, it defaults to
|
||||||
the rectangular bounding box of self.solid_geometry.
|
the rectangular bounding box of self.solid_geometry.
|
||||||
'''
|
"""
|
||||||
if boundary == None:
|
if boundary is None:
|
||||||
boundary = self.solid_geometry.envelope
|
boundary = self.solid_geometry.envelope
|
||||||
return boundary.difference(self.solid_geometry)
|
return boundary.difference(self.solid_geometry)
|
||||||
|
|
||||||
def clear_polygon(self, polygon, tooldia, overlap = 0.15):
|
def clear_polygon(self, polygon, tooldia, overlap=0.15):
|
||||||
'''
|
"""
|
||||||
Creates geometry inside a polygon for a tool to cover
|
Creates geometry inside a polygon for a tool to cover
|
||||||
the whole area.
|
the whole area.
|
||||||
'''
|
"""
|
||||||
poly_cuts = [polygon.buffer(-tooldia/2.0)]
|
poly_cuts = [polygon.buffer(-tooldia/2.0)]
|
||||||
while(1):
|
while True:
|
||||||
polygon = poly_cuts[-1].buffer(-tooldia*(1-overlap))
|
polygon = poly_cuts[-1].buffer(-tooldia*(1-overlap))
|
||||||
if polygon.area > 0:
|
if polygon.area > 0:
|
||||||
poly_cuts.append(polygon)
|
poly_cuts.append(polygon)
|
||||||
@@ -107,12 +109,12 @@ class Gerber (Geometry):
|
|||||||
self.flash_geometry = []
|
self.flash_geometry = []
|
||||||
|
|
||||||
def fix_regions(self):
|
def fix_regions(self):
|
||||||
'''
|
"""
|
||||||
Overwrites the region polygons with fixed
|
Overwrites the region polygons with fixed
|
||||||
versions if found to be invalid (according to Shapely).
|
versions if found to be invalid (according to Shapely).
|
||||||
'''
|
"""
|
||||||
for region in self.regions:
|
for region in self.regions:
|
||||||
if region['polygon'].is_valid == False:
|
if not region['polygon'].is_valid:
|
||||||
region['polygon'] = region['polygon'].buffer(0)
|
region['polygon'] = region['polygon'].buffer(0)
|
||||||
|
|
||||||
def buffer_paths(self):
|
def buffer_paths(self):
|
||||||
@@ -122,98 +124,98 @@ class Gerber (Geometry):
|
|||||||
self.buffered_paths.append(path["linestring"].buffer(width/2))
|
self.buffered_paths.append(path["linestring"].buffer(width/2))
|
||||||
|
|
||||||
def aperture_parse(self, gline):
|
def aperture_parse(self, gline):
|
||||||
'''
|
"""
|
||||||
Parse gerber aperture definition
|
Parse gerber aperture definition
|
||||||
into dictionary of apertures.
|
into dictionary of apertures.
|
||||||
'''
|
"""
|
||||||
indexstar = gline.find("*")
|
indexstar = gline.find("*")
|
||||||
indexC = gline.find("C,")
|
indexC = gline.find("C,")
|
||||||
if indexC != -1: # Circle, example: %ADD11C,0.1*%
|
if indexC != -1: # Circle, example: %ADD11C,0.1*%
|
||||||
apid = gline[4:indexC]
|
apid = gline[4:indexC]
|
||||||
self.apertures[apid] = {"type":"C",
|
self.apertures[apid] = {"type": "C",
|
||||||
"size":float(gline[indexC+2:indexstar])}
|
"size": float(gline[indexC+2:indexstar])}
|
||||||
return apid
|
return apid
|
||||||
indexR = gline.find("R,")
|
indexR = gline.find("R,")
|
||||||
if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
|
if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
|
||||||
apid = gline[4:indexR]
|
apid = gline[4:indexR]
|
||||||
indexX = gline.find("X")
|
indexX = gline.find("X")
|
||||||
self.apertures[apid] = {"type":"R",
|
self.apertures[apid] = {"type": "R",
|
||||||
"width":float(gline[indexR+2:indexX]),
|
"width": float(gline[indexR+2:indexX]),
|
||||||
"height":float(gline[indexX+1:indexstar])}
|
"height": float(gline[indexX+1:indexstar])}
|
||||||
return apid
|
return apid
|
||||||
indexO = gline.find("O,")
|
indexO = gline.find("O,")
|
||||||
if indexO != -1: # Obround
|
if indexO != -1: # Obround
|
||||||
apid = gline[4:indexO]
|
apid = gline[4:indexO]
|
||||||
indexX = gline.find("X")
|
indexX = gline.find("X")
|
||||||
self.apertures[apid] = {"type":"O",
|
self.apertures[apid] = {"type": "O",
|
||||||
"width":float(gline[indexO+2:indexX]),
|
"width": float(gline[indexO+2:indexX]),
|
||||||
"height":float(gline[indexX+1:indexstar])}
|
"height": float(gline[indexX+1:indexstar])}
|
||||||
return apid
|
return apid
|
||||||
print "WARNING: Aperture not implemented:", gline
|
print "WARNING: Aperture not implemented:", gline
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def parse_file(self, filename):
|
def parse_file(self, filename):
|
||||||
'''
|
"""
|
||||||
Calls Gerber.parse_lines() with array of lines
|
Calls Gerber.parse_lines() with array of lines
|
||||||
read from the given file.
|
read from the given file.
|
||||||
'''
|
"""
|
||||||
gfile = open(filename, 'r')
|
gfile = open(filename, 'r')
|
||||||
gstr = gfile.readlines()
|
gstr = gfile.readlines()
|
||||||
gfile.close()
|
gfile.close()
|
||||||
self.parse_lines(gstr)
|
self.parse_lines(gstr)
|
||||||
|
|
||||||
def parse_lines(self, glines):
|
def parse_lines(self, glines):
|
||||||
'''
|
"""
|
||||||
Main Gerber parser.
|
Main Gerber parser.
|
||||||
'''
|
"""
|
||||||
path = [] # Coordinates of the current path
|
path = [] # Coordinates of the current path
|
||||||
last_path_aperture = None
|
last_path_aperture = None
|
||||||
current_aperture = None
|
current_aperture = None
|
||||||
|
|
||||||
for gline in glines:
|
for gline in glines:
|
||||||
|
|
||||||
if gline.find("D01*") != -1: # pen down
|
if gline.find("D01*") != -1: # pen down
|
||||||
path.append(coord(gline, self.digits, self.fraction))
|
path.append(coord(gline, self.digits, self.fraction))
|
||||||
last_path_aperture = current_aperture
|
last_path_aperture = current_aperture
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if gline.find("D02*") != -1: # pen up
|
if gline.find("D02*") != -1: # pen up
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
# Path completed, create shapely LineString
|
# Path completed, create shapely LineString
|
||||||
self.paths.append({"linestring":LineString(path),
|
self.paths.append({"linestring": LineString(path),
|
||||||
"aperture":last_path_aperture})
|
"aperture": last_path_aperture})
|
||||||
path = [coord(gline, self.digits, self.fraction)]
|
path = [coord(gline, self.digits, self.fraction)]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
indexD3 = gline.find("D03*")
|
indexD3 = gline.find("D03*")
|
||||||
if indexD3 > 0: # Flash
|
if indexD3 > 0: # Flash
|
||||||
self.flashes.append({"loc":coord(gline, self.digits, self.fraction),
|
self.flashes.append({"loc": coord(gline, self.digits, self.fraction),
|
||||||
"aperture":current_aperture})
|
"aperture": current_aperture})
|
||||||
continue
|
continue
|
||||||
if indexD3 == 0: # Flash?
|
if indexD3 == 0: # Flash?
|
||||||
print "WARNING: Uninplemented flash style:", gline
|
print "WARNING: Uninplemented flash style:", gline
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if gline.find("G37*") != -1: # end region
|
if gline.find("G37*") != -1: # end region
|
||||||
# Only one path defines region?
|
# Only one path defines region?
|
||||||
self.regions.append({"polygon":Polygon(path),
|
self.regions.append({"polygon": Polygon(path),
|
||||||
"aperture":last_path_aperture})
|
"aperture": last_path_aperture})
|
||||||
path = []
|
path = []
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if gline.find("%ADD") != -1: # aperture definition
|
if gline.find("%ADD") != -1: # aperture definition
|
||||||
self.aperture_parse(gline) # adds element to apertures
|
self.aperture_parse(gline) # adds element to apertures
|
||||||
continue
|
continue
|
||||||
|
|
||||||
indexstar = gline.find("*")
|
indexstar = gline.find("*")
|
||||||
if gline.find("D") == 0: # Aperture change
|
if gline.find("D") == 0: # Aperture change
|
||||||
current_aperture = gline[1:indexstar]
|
current_aperture = gline[1:indexstar]
|
||||||
continue
|
continue
|
||||||
if gline.find("G54D") == 0: # Aperture change (deprecated)
|
if gline.find("G54D") == 0: # Aperture change (deprecated)
|
||||||
current_aperture = gline[4:indexstar]
|
current_aperture = gline[4:indexstar]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if gline.find("%FS") != -1: # Format statement
|
if gline.find("%FS") != -1: # Format statement
|
||||||
indexX = gline.find("X")
|
indexX = gline.find("X")
|
||||||
self.digits = int(gline[indexX + 1])
|
self.digits = int(gline[indexX + 1])
|
||||||
self.fraction = int(gline[indexX + 2])
|
self.fraction = int(gline[indexX + 2])
|
||||||
@@ -226,17 +228,17 @@ class Gerber (Geometry):
|
|||||||
"aperture":last_path_aperture})
|
"aperture":last_path_aperture})
|
||||||
|
|
||||||
def do_flashes(self):
|
def do_flashes(self):
|
||||||
'''
|
"""
|
||||||
Creates geometry for Gerber flashes (aperture on a single point).
|
Creates geometry for Gerber flashes (aperture on a single point).
|
||||||
'''
|
"""
|
||||||
self.flash_geometry = []
|
self.flash_geometry = []
|
||||||
for flash in self.flashes:
|
for flash in self.flashes:
|
||||||
aperture = self.apertures[flash['aperture']]
|
aperture = self.apertures[flash['aperture']]
|
||||||
if aperture['type'] == 'C': # Circles
|
if aperture['type'] == 'C': # Circles
|
||||||
circle = Point(flash['loc']).buffer(aperture['size']/2)
|
circle = Point(flash['loc']).buffer(aperture['size']/2)
|
||||||
self.flash_geometry.append(circle)
|
self.flash_geometry.append(circle)
|
||||||
continue
|
continue
|
||||||
if aperture['type'] == 'R': # Rectangles
|
if aperture['type'] == 'R': # Rectangles
|
||||||
loc = flash['loc']
|
loc = flash['loc']
|
||||||
width = aperture['width']
|
width = aperture['width']
|
||||||
height = aperture['height']
|
height = aperture['height']
|
||||||
@@ -260,6 +262,7 @@ class Gerber (Geometry):
|
|||||||
[poly['polygon'] for poly in self.regions] +
|
[poly['polygon'] for poly in self.regions] +
|
||||||
self.flash_geometry)
|
self.flash_geometry)
|
||||||
|
|
||||||
|
|
||||||
class Excellon(Geometry):
|
class Excellon(Geometry):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Geometry.__init__(self)
|
Geometry.__init__(self)
|
||||||
@@ -275,9 +278,9 @@ class Excellon(Geometry):
|
|||||||
self.parse_lines(estr)
|
self.parse_lines(estr)
|
||||||
|
|
||||||
def parse_lines(self, elines):
|
def parse_lines(self, elines):
|
||||||
'''
|
"""
|
||||||
Main Excellon parser.
|
Main Excellon parser.
|
||||||
'''
|
"""
|
||||||
current_tool = ""
|
current_tool = ""
|
||||||
|
|
||||||
for eline in elines:
|
for eline in elines:
|
||||||
@@ -320,7 +323,7 @@ class Excellon(Geometry):
|
|||||||
if indexX != -1 and indexY != -1:
|
if indexX != -1 and indexY != -1:
|
||||||
x = float(int(eline[indexX+1:indexY])/10000.0)
|
x = float(int(eline[indexX+1:indexY])/10000.0)
|
||||||
y = float(int(eline[indexY+1:-1])/10000.0)
|
y = float(int(eline[indexY+1:-1])/10000.0)
|
||||||
self.drills.append({'point':Point((x,y)), 'tool':current_tool})
|
self.drills.append({'point': Point((x, y)), 'tool': current_tool})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print "WARNING: Line ignored:", eline
|
print "WARNING: Line ignored:", eline
|
||||||
@@ -335,9 +338,10 @@ class Excellon(Geometry):
|
|||||||
self.solid_geometry.append(poly)
|
self.solid_geometry.append(poly)
|
||||||
self.solid_geometry = cascaded_union(self.solid_geometry)
|
self.solid_geometry = cascaded_union(self.solid_geometry)
|
||||||
|
|
||||||
|
|
||||||
class CNCjob(Geometry):
|
class CNCjob(Geometry):
|
||||||
def __init__(self, units="in", kind="generic", z_move = 0.1,
|
def __init__(self, units="in", kind="generic", z_move=0.1,
|
||||||
feedrate = 3.0, z_cut = -0.002, tooldia = 0.0):
|
feedrate=3.0, z_cut=-0.002, tooldia=0.0):
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
@@ -358,19 +362,17 @@ class CNCjob(Geometry):
|
|||||||
|
|
||||||
# Bounds of geometry given to CNCjob.generate_from_geometry()
|
# Bounds of geometry given to CNCjob.generate_from_geometry()
|
||||||
self.input_geometry_bounds = None
|
self.input_geometry_bounds = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Output generated by CNCjob.create_gcode_geometry()
|
# Output generated by CNCjob.create_gcode_geometry()
|
||||||
#self.G_geometry = None
|
#self.G_geometry = None
|
||||||
self.gcode_parsed = None
|
self.gcode_parsed = None
|
||||||
|
|
||||||
def generate_from_excellon(self, exobj):
|
def generate_from_excellon(self, exobj):
|
||||||
'''
|
"""
|
||||||
Generates G-code for drilling from excellon text.
|
Generates G-code for drilling from excellon text.
|
||||||
self.gcode becomes a list, each element is a
|
self.gcode becomes a list, each element is a
|
||||||
different job for each tool in the excellon code.
|
different job for each tool in the excellon code.
|
||||||
'''
|
"""
|
||||||
self.kind = "drill"
|
self.kind = "drill"
|
||||||
self.gcode = []
|
self.gcode = []
|
||||||
|
|
||||||
@@ -399,23 +401,23 @@ class CNCjob(Geometry):
|
|||||||
gcode += t%point
|
gcode += t%point
|
||||||
gcode += down + up
|
gcode += down + up
|
||||||
|
|
||||||
gcode += t%(0,0)
|
gcode += t%(0, 0)
|
||||||
gcode += "M05\n" # Spindle stop
|
gcode += "M05\n" # Spindle stop
|
||||||
|
|
||||||
self.gcode.append(gcode)
|
self.gcode.append(gcode)
|
||||||
|
|
||||||
def generate_from_geometry(self, geometry, append=True, tooldia=None):
|
def generate_from_geometry(self, geometry, append=True, tooldia=None):
|
||||||
'''
|
"""
|
||||||
Generates G-Code for geometry (Shapely collection).
|
Generates G-Code for geometry (Shapely collection).
|
||||||
'''
|
"""
|
||||||
if tooldia == None:
|
if tooldia is None:
|
||||||
tooldia = self.tooldia
|
tooldia = self.tooldia
|
||||||
else:
|
else:
|
||||||
self.tooldia = tooldia
|
self.tooldia = tooldia
|
||||||
|
|
||||||
self.input_geometry_bounds = geometry.bounds
|
self.input_geometry_bounds = geometry.bounds
|
||||||
|
|
||||||
if append == False:
|
if not append:
|
||||||
self.gcode = ""
|
self.gcode = ""
|
||||||
t = "G0%d X%.4fY%.4f\n"
|
t = "G0%d X%.4fY%.4f\n"
|
||||||
self.gcode = self.unitcode[self.units] + "\n"
|
self.gcode = self.unitcode[self.units] + "\n"
|
||||||
@@ -429,50 +431,50 @@ class CNCjob(Geometry):
|
|||||||
for geo in geometry:
|
for geo in geometry:
|
||||||
|
|
||||||
if type(geo) == Polygon:
|
if type(geo) == Polygon:
|
||||||
path = list(geo.exterior.coords) # Polygon exterior
|
path = list(geo.exterior.coords) # Polygon exterior
|
||||||
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
||||||
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
||||||
for pt in path[1:]:
|
for pt in path[1:]:
|
||||||
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
|
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
|
||||||
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
||||||
for ints in geo.interiors: # Polygon interiors
|
for ints in geo.interiors: # Polygon interiors
|
||||||
path = list(ints.coords)
|
path = list(ints.coords)
|
||||||
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
||||||
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
||||||
for pt in path[1:]:
|
for pt in path[1:]:
|
||||||
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
|
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
|
||||||
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if type(geo) == LineString or type(geo) == LinearRing:
|
if type(geo) == LineString or type(geo) == LinearRing:
|
||||||
path = list(geo.coords)
|
path = list(geo.coords)
|
||||||
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
||||||
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
||||||
for pt in path[1:]:
|
for pt in path[1:]:
|
||||||
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
|
self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
|
||||||
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if type(geo) == Point:
|
if type(geo) == Point:
|
||||||
path = list(geo.coords)
|
path = list(geo.coords)
|
||||||
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
|
||||||
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
|
||||||
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print "WARNING: G-code generation not implemented for %s"%(str(type(geo)))
|
print "WARNING: G-code generation not implemented for %s"%(str(type(geo)))
|
||||||
|
|
||||||
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
|
||||||
self.gcode += "G00 X0Y0\n"
|
self.gcode += "G00 X0Y0\n"
|
||||||
self.gcode += "M05\n" # Spindle stop
|
self.gcode += "M05\n" # Spindle stop
|
||||||
|
|
||||||
def gcode_parse(self):
|
def gcode_parse(self):
|
||||||
steps_per_circ = 20
|
steps_per_circ = 20
|
||||||
'''
|
"""
|
||||||
G-Code parser (from self.gcode). Generates dictionary with
|
G-Code parser (from self.gcode). Generates dictionary with
|
||||||
single-segment LineString's and "kind" indicating cut or travel,
|
single-segment LineString's and "kind" indicating cut or travel,
|
||||||
fast or feedrate speed.
|
fast or feedrate speed.
|
||||||
'''
|
"""
|
||||||
geometry = []
|
geometry = []
|
||||||
|
|
||||||
# TODO: ???? bring this into the class??
|
# TODO: ???? bring this into the class??
|
||||||
@@ -513,20 +515,19 @@ class CNCjob(Geometry):
|
|||||||
kind[1] = 'S'
|
kind[1] = 'S'
|
||||||
|
|
||||||
arcdir = [None, None, "cw", "ccw"]
|
arcdir = [None, None, "cw", "ccw"]
|
||||||
if current['G'] in [0,1]: # line
|
if current['G'] in [0, 1]: # line
|
||||||
geometry.append({'geom':LineString([(current['X'],current['Y']),
|
geometry.append({'geom': LineString([(current['X'], current['Y']),
|
||||||
(x,y)]), 'kind':kind})
|
(x, y)]), 'kind': kind})
|
||||||
if current['G'] in [2,3]: # arc
|
if current['G'] in [2, 3]: # arc
|
||||||
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
|
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
|
||||||
radius = sqrt(gobj['I']**2 + gobj['J']**2)
|
radius = sqrt(gobj['I']**2 + gobj['J']**2)
|
||||||
start = arctan2( -gobj['J'], -gobj['I'])
|
start = arctan2( -gobj['J'], -gobj['I'])
|
||||||
stop = arctan2(-center[1]+y, -center[0]+x)
|
stop = arctan2(-center[1]+y, -center[0]+x)
|
||||||
geometry.append({'geom':arc(center, radius, start, stop,
|
geometry.append({'geom': arc(center, radius, start, stop,
|
||||||
arcdir[current['G']],
|
arcdir[current['G']],
|
||||||
steps_per_circ),
|
steps_per_circ),
|
||||||
'kind':kind})
|
'kind': kind})
|
||||||
|
|
||||||
|
|
||||||
# Update current instruction
|
# Update current instruction
|
||||||
for code in gobj:
|
for code in gobj:
|
||||||
current[code] = gobj[code]
|
current[code] = gobj[code]
|
||||||
@@ -536,13 +537,13 @@ class CNCjob(Geometry):
|
|||||||
return geometry
|
return geometry
|
||||||
|
|
||||||
def plot(self, tooldia=None, dpi=75, margin=0.1,
|
def plot(self, tooldia=None, dpi=75, margin=0.1,
|
||||||
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
|
color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
|
||||||
alpha={"T":0.3, "C":1.0}):
|
alpha={"T": 0.3, "C": 1.0}):
|
||||||
'''
|
"""
|
||||||
Creates a Matplotlib figure with a plot of the
|
Creates a Matplotlib figure with a plot of the
|
||||||
G-code job.
|
G-code job.
|
||||||
'''
|
"""
|
||||||
if tooldia == None:
|
if tooldia is None:
|
||||||
tooldia = self.tooldia
|
tooldia = self.tooldia
|
||||||
|
|
||||||
fig = Figure(dpi=dpi)
|
fig = Figure(dpi=dpi)
|
||||||
@@ -571,12 +572,12 @@ class CNCjob(Geometry):
|
|||||||
return fig
|
return fig
|
||||||
|
|
||||||
def plot2(self, axes, tooldia=None, dpi=75, margin=0.1,
|
def plot2(self, axes, tooldia=None, dpi=75, margin=0.1,
|
||||||
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
|
color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
|
||||||
alpha={"T":0.3, "C":1.0}):
|
alpha={"T": 0.3, "C":1.0}):
|
||||||
'''
|
"""
|
||||||
Plots the G-code job onto the given axes.
|
Plots the G-code job onto the given axes.
|
||||||
'''
|
"""
|
||||||
if tooldia == None:
|
if tooldia is None:
|
||||||
tooldia = self.tooldia
|
tooldia = self.tooldia
|
||||||
|
|
||||||
if tooldia == 0:
|
if tooldia == 0:
|
||||||
@@ -598,14 +599,13 @@ class CNCjob(Geometry):
|
|||||||
def create_geometry(self):
|
def create_geometry(self):
|
||||||
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
|
self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def gparse1b(gtext):
|
def gparse1b(gtext):
|
||||||
'''
|
"""
|
||||||
gtext is a single string with g-code
|
gtext is a single string with g-code
|
||||||
'''
|
"""
|
||||||
gcmds = []
|
gcmds = []
|
||||||
lines = gtext.split("\n") # TODO: This is probably a lot of work!
|
lines = gtext.split("\n") # TODO: This is probably a lot of work!
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
@@ -635,8 +635,8 @@ def gparse1b(gtext):
|
|||||||
# Separate codes in line
|
# Separate codes in line
|
||||||
parts = []
|
parts = []
|
||||||
for p in range(n_codes-1):
|
for p in range(n_codes-1):
|
||||||
parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
|
parts.append(line[codes_idx[p]:codes_idx[p+1]].strip())
|
||||||
parts.append( line[codes_idx[-1]:].strip() )
|
parts.append(line[codes_idx[-1]:].strip())
|
||||||
|
|
||||||
# Separate codes from values
|
# Separate codes from values
|
||||||
cmds = {}
|
cmds = {}
|
||||||
@@ -645,6 +645,7 @@ def gparse1b(gtext):
|
|||||||
gcmds.append(cmds)
|
gcmds.append(cmds)
|
||||||
return gcmds
|
return gcmds
|
||||||
|
|
||||||
|
|
||||||
def get_bounds(geometry_set):
|
def get_bounds(geometry_set):
|
||||||
xmin = Inf
|
xmin = Inf
|
||||||
ymin = Inf
|
ymin = Inf
|
||||||
@@ -660,12 +661,13 @@ def get_bounds(geometry_set):
|
|||||||
|
|
||||||
return [xmin, ymin, xmax, ymax]
|
return [xmin, ymin, xmax, ymax]
|
||||||
|
|
||||||
|
|
||||||
def arc(center, radius, start, stop, direction, steps_per_circ):
|
def arc(center, radius, start, stop, direction, steps_per_circ):
|
||||||
da_sign = {"cw":-1.0, "ccw":1.0}
|
da_sign = {"cw": -1.0, "ccw": 1.0}
|
||||||
points = []
|
points = []
|
||||||
if direction=="ccw" and stop <= start:
|
if direction == "ccw" and stop <= start:
|
||||||
stop += 2*pi
|
stop += 2*pi
|
||||||
if direction=="cw" and stop >= start:
|
if direction == "cw" and stop >= start:
|
||||||
stop -= 2*pi
|
stop -= 2*pi
|
||||||
|
|
||||||
angle = abs(stop - start)
|
angle = abs(stop - start)
|
||||||
@@ -677,20 +679,21 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
|
|||||||
theta = start + delta_angle*i
|
theta = start + delta_angle*i
|
||||||
points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)])
|
points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)])
|
||||||
return LineString(points)
|
return LineString(points)
|
||||||
|
|
||||||
|
|
||||||
############### cam.py ####################
|
############### cam.py ####################
|
||||||
def coord(gstr,digits,fraction):
|
def coord(gstr,digits,fraction):
|
||||||
'''
|
"""
|
||||||
Parse Gerber coordinates
|
Parse Gerber coordinates
|
||||||
'''
|
"""
|
||||||
global gerbx, gerby
|
global gerbx, gerby
|
||||||
xindex = gstr.find("X")
|
xindex = gstr.find("X")
|
||||||
yindex = gstr.find("Y")
|
yindex = gstr.find("Y")
|
||||||
index = gstr.find("D")
|
index = gstr.find("D")
|
||||||
if (xindex == -1):
|
if xindex == -1:
|
||||||
x = gerbx
|
x = gerbx
|
||||||
y = int(gstr[(yindex+1):index])*(10**(-fraction))
|
y = int(gstr[(yindex+1):index])*(10**(-fraction))
|
||||||
elif (yindex == -1):
|
elif yindex == -1:
|
||||||
y = gerby
|
y = gerby
|
||||||
x = int(gstr[(xindex+1):index])*(10**(-fraction))
|
x = int(gstr[(xindex+1):index])*(10**(-fraction))
|
||||||
else:
|
else:
|
||||||
@@ -698,7 +701,5 @@ def coord(gstr,digits,fraction):
|
|||||||
y = int(gstr[(yindex+1):index])*(10**(-fraction))
|
y = int(gstr[(yindex+1):index])*(10**(-fraction))
|
||||||
gerbx = x
|
gerbx = x
|
||||||
gerby = y
|
gerby = y
|
||||||
return [x,y]
|
return [x, y]
|
||||||
|
|
||||||
|
|
||||||
################ end of cam.py #############
|
################ end of cam.py #############
|
||||||
|
|||||||
91
cirkuix.py
91
cirkuix.py
@@ -86,10 +86,10 @@ def get_entry_float(entry):
|
|||||||
def get_entry_eval(entry):
|
def get_entry_eval(entry):
|
||||||
return eval(entry.get_text)
|
return eval(entry.get_text)
|
||||||
|
|
||||||
getters = {"entry_text":get_entry_text,
|
getters = {"entry_text": get_entry_text,
|
||||||
"entry_int":get_entry_int,
|
"entry_int": get_entry_int,
|
||||||
"entry_float":get_entry_float,
|
"entry_float": get_entry_float,
|
||||||
"entry_eval":get_entry_eval}
|
"entry_eval": get_entry_eval}
|
||||||
|
|
||||||
setters = {"entry"}
|
setters = {"entry"}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class App:
|
|||||||
self.builder.add_from_file(self.gladefile)
|
self.builder.add_from_file(self.gladefile)
|
||||||
self.window = self.builder.get_object("window1")
|
self.window = self.builder.get_object("window1")
|
||||||
self.window.set_title("Cirkuix")
|
self.window.set_title("Cirkuix")
|
||||||
self.positionLabel = self.builder.get_object("label3")
|
self.position_label = self.builder.get_object("label3")
|
||||||
self.grid = self.builder.get_object("grid1")
|
self.grid = self.builder.get_object("grid1")
|
||||||
self.notebook = self.builder.get_object("notebook1")
|
self.notebook = self.builder.get_object("notebook1")
|
||||||
self.info_label = self.builder.get_object("label_status")
|
self.info_label = self.builder.get_object("label_status")
|
||||||
@@ -248,20 +248,27 @@ class App:
|
|||||||
|
|
||||||
def plot_geometry(self, geometry):
|
def plot_geometry(self, geometry):
|
||||||
for geo in geometry.solid_geometry:
|
for geo in geometry.solid_geometry:
|
||||||
x, y = geo.exterior.coords.xy
|
|
||||||
self.axes.plot(x, y, 'r-')
|
if type(geo) == Polygon:
|
||||||
for ints in geo.interiors:
|
x, y = geo.exterior.coords.xy
|
||||||
x, y = ints.coords.xy
|
|
||||||
self.axes.plot(x, y, 'r-')
|
self.axes.plot(x, y, 'r-')
|
||||||
|
for ints in geo.interiors:
|
||||||
|
x, y = ints.coords.xy
|
||||||
|
self.axes.plot(x, y, 'r-')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if type(geo) == LineString or type(geo) == LinearRing:
|
||||||
|
x, y = geo.coords.xy
|
||||||
|
self.axes.plot(x, y, 'r-')
|
||||||
|
continue
|
||||||
|
|
||||||
self.canvas.queue_draw()
|
self.canvas.queue_draw()
|
||||||
|
|
||||||
|
|
||||||
def setup_component_viewer(self):
|
def setup_component_viewer(self):
|
||||||
'''
|
"""
|
||||||
List or Tree where whatever has been loaded or created is
|
List or Tree where whatever has been loaded or created is
|
||||||
displayed.
|
displayed.
|
||||||
'''
|
"""
|
||||||
self.store = Gtk.ListStore(str)
|
self.store = Gtk.ListStore(str)
|
||||||
self.tree = Gtk.TreeView(self.store)
|
self.tree = Gtk.TreeView(self.store)
|
||||||
select = self.tree.get_selection()
|
select = self.tree.get_selection()
|
||||||
@@ -353,7 +360,59 @@ class App:
|
|||||||
########################################
|
########################################
|
||||||
## EVENT HANDLERS ##
|
## EVENT HANDLERS ##
|
||||||
########################################
|
########################################
|
||||||
|
def on_gerber_generate_boundary(self, widget):
|
||||||
|
margin = self.get_eval("entry_gerber_cutout_margin")
|
||||||
|
gap_size = self.get_eval("entry_gerber_cutout_gapsize")
|
||||||
|
gerber = self.stuff[self.selected_item_name]
|
||||||
|
minx, miny, maxx, maxy = gerber.bounds()
|
||||||
|
minx -= margin
|
||||||
|
maxx += margin
|
||||||
|
miny -= margin
|
||||||
|
maxy += margin
|
||||||
|
midx = 0.5 * (minx + maxx)
|
||||||
|
midy = 0.5 * (miny + maxy)
|
||||||
|
hgap = 0.5 * gap_size
|
||||||
|
pts = [[midx-hgap, maxy],
|
||||||
|
[minx, maxy],
|
||||||
|
[minx, midy+hgap],
|
||||||
|
[minx, midy-hgap],
|
||||||
|
[minx, miny],
|
||||||
|
[midx-hgap, miny],
|
||||||
|
[midx+hgap, miny],
|
||||||
|
[maxx, miny],
|
||||||
|
[maxx, midy-hgap],
|
||||||
|
[maxx, midy+hgap],
|
||||||
|
[maxx, maxy],
|
||||||
|
[midx+hgap, maxy]]
|
||||||
|
cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
|
||||||
|
[pts[6], pts[7], pts[10], pts[11]]],
|
||||||
|
"lr": [[pts[9], pts[10], pts[1], pts[2]],
|
||||||
|
[pts[3], pts[4], pts[7], pts[8]]],
|
||||||
|
"4": [[pts[0], pts[1], pts[2]],
|
||||||
|
[pts[3], pts[4], pts[5]],
|
||||||
|
[pts[6], pts[7], pts[8]],
|
||||||
|
[pts[9], pts[10], pts[11]]]}
|
||||||
|
name = self.selected_item_name + "_cutout"
|
||||||
|
geometry = CirkuixGeometry(name)
|
||||||
|
cuts = None
|
||||||
|
if self.builder.get_object("rb_2tb").get_active():
|
||||||
|
cuts = cases["tb"]
|
||||||
|
elif self.builder.get_object("rb_2lr").get_active():
|
||||||
|
cuts = cases["lr"]
|
||||||
|
else:
|
||||||
|
cuts = cases["4"]
|
||||||
|
geometry.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
|
||||||
|
|
||||||
|
# Add to App and update.
|
||||||
|
self.stuff[name] = geometry
|
||||||
|
self.build_list()
|
||||||
|
|
||||||
def on_eval_update(self, widget):
|
def on_eval_update(self, widget):
|
||||||
|
"""
|
||||||
|
Modifies the content of a Gtk.Entry by running
|
||||||
|
eval() on its contents and puting it back as a
|
||||||
|
string.
|
||||||
|
"""
|
||||||
# TODO: error handling here
|
# TODO: error handling here
|
||||||
widget.set_text(str(eval(widget.get_text())))
|
widget.set_text(str(eval(widget.get_text())))
|
||||||
|
|
||||||
@@ -384,7 +443,7 @@ class App:
|
|||||||
|
|
||||||
geometry = self.stuff[self.selected_item_name]
|
geometry = self.stuff[self.selected_item_name]
|
||||||
job_name = self.selected_item_name + "_cnc"
|
job_name = self.selected_item_name + "_cnc"
|
||||||
job = CirkuixCNCjob(job_name, z_move = travelz, z_cut = cutz, feedrate = feedrate)
|
job = CirkuixCNCjob(job_name, z_move=travelz, z_cut=cutz, feedrate=feedrate)
|
||||||
job.generate_from_geometry(geometry.solid_geometry)
|
job.generate_from_geometry(geometry.solid_geometry)
|
||||||
job.gcode_parse()
|
job.gcode_parse()
|
||||||
job.create_geometry()
|
job.create_geometry()
|
||||||
@@ -613,11 +672,11 @@ class App:
|
|||||||
|
|
||||||
def on_mouse_move_over_plot(self, event):
|
def on_mouse_move_over_plot(self, event):
|
||||||
try: # May fail in case mouse not within axes
|
try: # May fail in case mouse not within axes
|
||||||
self.positionLabel.set_label("X: %.4f Y: %.4f"%(
|
self.position_label.set_label("X: %.4f Y: %.4f"%(
|
||||||
event.xdata, event.ydata))
|
event.xdata, event.ydata))
|
||||||
self.mouse = [event.xdata, event.ydata]
|
self.mouse = [event.xdata, event.ydata]
|
||||||
except:
|
except:
|
||||||
self.positionLabel.set_label("")
|
self.position_label.set_label("")
|
||||||
self.mouse = None
|
self.mouse = None
|
||||||
|
|
||||||
def on_click_over_plot(self, event):
|
def on_click_over_plot(self, event):
|
||||||
|
|||||||
192
cirkuix.ui
192
cirkuix.ui
@@ -767,9 +767,6 @@
|
|||||||
<property name="position">6</property>
|
<property name="position">6</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="label13">
|
<object class="GtkLabel" id="label13">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
@@ -785,7 +782,7 @@
|
|||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">8</property>
|
<property name="position">7</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@@ -830,7 +827,7 @@
|
|||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">9</property>
|
<property name="position">8</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@@ -842,12 +839,195 @@
|
|||||||
<signal name="activate" handler="on_generate_isolation" swapped="no"/>
|
<signal name="activate" handler="on_generate_isolation" swapped="no"/>
|
||||||
<signal name="clicked" handler="on_generate_isolation" swapped="no"/>
|
<signal name="clicked" handler="on_generate_isolation" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">9</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_top">5</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="ypad">3</property>
|
||||||
|
<property name="label" translatable="yes">Board cutout:</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="semibold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">10</property>
|
<property name="position">10</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid" id="grid4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="row_spacing">3</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label27">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Margin: </property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">1</property>
|
||||||
|
<property name="height">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="entry_gerber_cutout_margin">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="invisible_char">●</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">1</property>
|
||||||
|
<property name="height">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="entry_gerber_cutout_gapsize">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="invisible_char">●</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="width">1</property>
|
||||||
|
<property name="height">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label28">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Gap size: </property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="width">1</property>
|
||||||
|
<property name="height">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label29">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Gaps: </property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="width">1</property>
|
||||||
|
<property name="height">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="box10">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="rb_2tb">
|
||||||
|
<property name="label" translatable="yes">2 (T/B)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="group">rb_2lr</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="rb_2lr">
|
||||||
|
<property name="label" translatable="yes">2 (L/R)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="rb_4">
|
||||||
|
<property name="label" translatable="yes">4</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="group">rb_2lr</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="width">1</property>
|
||||||
|
<property name="height">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">11</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="button4">
|
||||||
|
<property name="label" translatable="yes">Generate Geometry</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="activate" handler="on_gerber_generate_boundary" swapped="no"/>
|
||||||
|
<signal name="clicked" handler="on_gerber_generate_boundary" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">12</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<placeholder/>
|
<placeholder/>
|
||||||
</child>
|
</child>
|
||||||
@@ -997,7 +1177,7 @@
|
|||||||
<object class="GtkMenuItem" id="menuitem3">
|
<object class="GtkMenuItem" id="menuitem3">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label" translatable="yes">_View</property>
|
<property name="label" translatable="yes">_Tools</property>
|
||||||
<property name="use_underline">True</property>
|
<property name="use_underline">True</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|||||||
Reference in New Issue
Block a user