Initial Revision
DXF to Gerber converter
This commit is contained in:
22
.gitattributes
vendored
Normal file
22
.gitattributes
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
215
.gitignore
vendored
Normal file
215
.gitignore
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
build/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
*.pubxml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
#packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
#############
|
||||
## Windows detritus
|
||||
#############
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac crap
|
||||
.DS_Store
|
||||
|
||||
|
||||
#############
|
||||
## Python
|
||||
#############
|
||||
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
eggs/
|
||||
parts/
|
||||
var/
|
||||
sdist/
|
||||
develop-eggs/
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
||||
650
dxf_to_gerber.py
Normal file
650
dxf_to_gerber.py
Normal file
@ -0,0 +1,650 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Convert a DXF file to a series of Gerber and Excellon files suitable for e.g. PCB Train
|
||||
#
|
||||
# Layer names on the DXF file are used to determine how the objects in the DXF file map onto the circuit board
|
||||
#
|
||||
# A two-layer PCB can be represented as follows:
|
||||
#
|
||||
# (G) Top Overlay -> .gto
|
||||
# (G) Top Soldermask -> .gts
|
||||
# (G) Top Copper -> .gtl
|
||||
# (E) Drill -> .gdd
|
||||
# (G) Bottom Copper -> .gbl
|
||||
# (G) Bottom Overlay -> .gbo
|
||||
# (G) Bottom Soldermask -> .gbs
|
||||
# (E) Mechanical -> .gm1
|
||||
#
|
||||
# where 'E' will become an Excellon file (CNC drill or routing), and 'G' will become a Gerber file (PCB exposure)
|
||||
#
|
||||
# The converter knows how to handle two types of objects, polylines and circles
|
||||
#
|
||||
# For Gerber files
|
||||
#
|
||||
# Closed polylines -> translate into filled areas of copper, open solder masks, and filled overlay
|
||||
# Open polylines -> tracks on the copper layer, lines on the overlay/silkscreen
|
||||
# Circles -> circular aperture flashes on copper, silkscreen and soldermask layers
|
||||
#
|
||||
# For Excellon files
|
||||
#
|
||||
# Circles -> drilled holes on 'Drill' layer
|
||||
# Open Polylines -> Cut-out slots on Mechanical layer
|
||||
# Closed Polylines -> Cut-out pieces, or boundary of whole PCB
|
||||
#
|
||||
# Open polylines need the "Global Linewidth" property set in AutoCAD to define how wide
|
||||
#
|
||||
# Deficiencies / to be implemented:
|
||||
#
|
||||
# Could process drills sensibly: we currently output tool codes for unused holes
|
||||
# Cut-outs are not implemented on the mechanical layer
|
||||
#
|
||||
|
||||
|
||||
import math;
|
||||
import re;
|
||||
import os;
|
||||
import glob;
|
||||
|
||||
class DXFFile:
|
||||
|
||||
X = 10;
|
||||
Y = 20;
|
||||
Z = 30;
|
||||
DIAMETER = 40;
|
||||
LINEWIDTH = 41;
|
||||
POLYLINE_FLAGS = 70;
|
||||
BULGE = 42;
|
||||
LAYER = 8;
|
||||
|
||||
POLYLINE_FLAG_CLOSED = 1;
|
||||
|
||||
prec = 8;
|
||||
|
||||
def __init__(self,fname):
|
||||
self.polylines = list();
|
||||
self.circles = list();
|
||||
self.layers = set();
|
||||
self.filename = fname;
|
||||
|
||||
# An entry in a DXF file consists of
|
||||
# integer
|
||||
# text
|
||||
#
|
||||
# e.g.
|
||||
# 10
|
||||
# x-coordinate
|
||||
# 20
|
||||
# y-coordinate
|
||||
#
|
||||
# We build a dictionary that allows look-up of a parser for the next line of input, based on the integer code
|
||||
#
|
||||
rev_parse = { (lambda a: round(float(a)*self.prec)/self.prec):(self.X,self.Y,self.Z,self.LINEWIDTH,self.BULGE,self.DIAMETER), (lambda a: str(a)):(self.LAYER,), (lambda a: int(a)):(self.POLYLINE_FLAGS,) };
|
||||
self.parse = dict();
|
||||
for k in rev_parse:
|
||||
for j in rev_parse[k]:
|
||||
self.parse[j]=k;
|
||||
|
||||
with open(fname) as f:
|
||||
self.read_dxf_file(f);
|
||||
|
||||
def read_entity(self,f):
|
||||
results = {};
|
||||
|
||||
while True:
|
||||
try:
|
||||
t = f.readline().strip();
|
||||
l1=int(t);
|
||||
except ValueError:
|
||||
try:
|
||||
l1=int(t,16);
|
||||
except ValueError:
|
||||
l1 = f.readline();
|
||||
continue;
|
||||
|
||||
if l1==0:
|
||||
break;
|
||||
|
||||
if l1 in self.parse:
|
||||
l2=f.readline().strip();
|
||||
results[l1]=self.parse[l1](l2);
|
||||
|
||||
return results;
|
||||
|
||||
def read_circle(self,f):
|
||||
c = self.read_entity(f);
|
||||
# return diameter, not radius
|
||||
if 40 in c:
|
||||
c[40] = c[40] * 2.0;
|
||||
self.circles.append(c);
|
||||
|
||||
def read_polyline(self,f):
|
||||
result = list();
|
||||
this_entity = self.read_entity(f);
|
||||
|
||||
while True:
|
||||
l = f.readline().strip();
|
||||
|
||||
if l=='SEQEND':
|
||||
break;
|
||||
|
||||
if l=='VERTEX':
|
||||
result.append(self.read_entity(f));
|
||||
|
||||
this_entity['VERTICES'] = result;
|
||||
|
||||
self.polylines.append(this_entity);
|
||||
|
||||
def read_dxf_file(self,f):
|
||||
while True:
|
||||
l=f.readline().strip();
|
||||
if l=='EOF':
|
||||
break;
|
||||
|
||||
if l=='CIRCLE':
|
||||
self.read_circle(f);
|
||||
|
||||
if l=='POLYLINE':
|
||||
self.read_polyline(f);
|
||||
|
||||
@staticmethod
|
||||
def matches(a,b):
|
||||
return re.sub(' ','_',a.strip().lower())==re.sub(' ','_',b.strip().lower());
|
||||
|
||||
def circles_on_layer(self,n):
|
||||
for c in self.circles:
|
||||
if self.matches(c[self.LAYER],n):
|
||||
yield c;
|
||||
|
||||
def polylines_on_layer(self,n):
|
||||
for p in self.polylines:
|
||||
if self.matches(p[self.LAYER],n):
|
||||
yield p;
|
||||
|
||||
def open_polylines_on_layer(self,n):
|
||||
for p in self.polylines:
|
||||
if self.matches(p[self.LAYER],n):
|
||||
if self.POLYLINE_FLAGS not in p:
|
||||
yield p;
|
||||
else:
|
||||
if (p[self.POLYLINE_FLAGS] & self.POLYLINE_FLAG_CLOSED)==0:
|
||||
yield p;
|
||||
|
||||
def closed_polylines_on_layer(self,n):
|
||||
for p in self.polylines:
|
||||
if self.matches(p[self.LAYER],n):
|
||||
if self.POLYLINE_FLAGS in p:
|
||||
if (p[self.POLYLINE_FLAGS] & self.POLYLINE_FLAG_CLOSED)!=0:
|
||||
yield p;
|
||||
|
||||
def diameters(self,circles,layer='ALL'):
|
||||
result = set();
|
||||
for c in circles:
|
||||
if (self.matches(c[self.LAYER],layer)) | (layer=='ALL'):
|
||||
result.add(c[self.DIAMETER]);
|
||||
return result;
|
||||
|
||||
def linewidths(self,polylines,layer='ALL'):
|
||||
result = set();
|
||||
for p in polylines:
|
||||
if (self.matches(p[self.LAYER],layer)) | layer=='ALL':
|
||||
result.add(p[self.LINEWIDTH]);
|
||||
return result;
|
||||
|
||||
def layer_names(self):
|
||||
result = set();
|
||||
for c in self.circles:
|
||||
result.add(c[self.LAYER]);
|
||||
for p in self.polylines:
|
||||
result.add(p[self.LAYER]);
|
||||
return result;
|
||||
|
||||
class GerberWriter:
|
||||
|
||||
precision = (2,6);
|
||||
scale = 1.0;
|
||||
default_diameter = 0.01;
|
||||
|
||||
gerber_layers = { \
|
||||
'.gbl':('Bottom Copper','Bottom'), \
|
||||
'.gbo':('Bottom Outlines','Bottom Overlay'), \
|
||||
'.gbs':('Bottom Soldermask',), \
|
||||
'.gtl':('Top Copper','Top',), \
|
||||
'.gto':('Top Outlines','Top Overlay'), \
|
||||
'.gts':('Top Soldermask',)};
|
||||
|
||||
mechanical_layers = {'.gm1':('Mechanical','Cutout','Cut Out')};
|
||||
|
||||
excellon_layers = { \
|
||||
'.gdd':('Drill',) };
|
||||
|
||||
@staticmethod
|
||||
def emit_coord(d):
|
||||
s = d * GerberWriter.scale;
|
||||
result = ('%%%dd' % (GerberWriter.precision[0]+GerberWriter.precision[1])) % int(s*pow(10.0,GerberWriter.precision[1]));
|
||||
return result.strip();
|
||||
|
||||
@staticmethod
|
||||
def exc_emit_coord(d):
|
||||
s = d * GerberWriter.scale;
|
||||
return re.sub('^0+','','%06.2f' % (s));
|
||||
|
||||
@staticmethod
|
||||
def exc_emit_point(p):
|
||||
result ='X%s' % GerberWriter.exc_emit_coord(p[0]);
|
||||
result +='Y%s' % GerberWriter.exc_emit_coord(p[1]);
|
||||
return result;
|
||||
|
||||
def flash_command(self,f,p,c):
|
||||
self.emit_command(f,self.emit_point(p)+c);
|
||||
|
||||
@staticmethod
|
||||
def emit_command(f,symbol,value=""):
|
||||
if symbol=='G04':
|
||||
print >> f, "G04 %s*" % (value.strip());
|
||||
else:
|
||||
print >> f, "%s%s*" % (symbol,value);
|
||||
|
||||
@staticmethod
|
||||
def emit_parameter(f,p,value):
|
||||
print >> f,"%%%s%s*%%" % (p,value);
|
||||
|
||||
@staticmethod
|
||||
def emit_precision(f):
|
||||
GerberWriter.emit_parameter(f,"FS","LAX%d%d" % (GerberWriter.precision[0],GerberWriter.precision[1]));
|
||||
|
||||
@staticmethod
|
||||
def XthenY(a,b):
|
||||
if a[DXFFile.X] < b[DXFFile.X]:
|
||||
return -1;
|
||||
else:
|
||||
if a[DXFFile.X] > b[DXFFile.X]:
|
||||
return 1;
|
||||
else:
|
||||
if a[DXFFile.Y] < b[DXFFile.Y]:
|
||||
return -1;
|
||||
else:
|
||||
if a[DXFFile.Y] > b[DXFFile.Y]:
|
||||
return 1;
|
||||
else:
|
||||
return a[DXFFile.DIAMETER] < b[DXFFile.DIAMETER];
|
||||
|
||||
@staticmethod
|
||||
def no_duplicates(k):
|
||||
First = True;
|
||||
for j in k:
|
||||
if First:
|
||||
i = j;
|
||||
yield i;
|
||||
First = False;
|
||||
else:
|
||||
if GerberWriter.XthenY(i,j)==0:
|
||||
continue;
|
||||
else:
|
||||
i = j;
|
||||
yield i;
|
||||
|
||||
def emit_point(self,p):
|
||||
result='';
|
||||
if self.X!=p[0]:
|
||||
result +='X%s' % GerberWriter.emit_coord(p[0]);
|
||||
self.X=p[0];
|
||||
|
||||
if self.Y!=p[1]:
|
||||
result +='Y%s' % GerberWriter.emit_coord(p[1]);
|
||||
self.Y=p[1];
|
||||
|
||||
return result;
|
||||
|
||||
def draw_to(self,f,p):
|
||||
self.emit_command(f,self.emit_point(p)+"D01");
|
||||
|
||||
def move_to(self,f,p):
|
||||
self.emit_command(f,self.emit_point(p)+"D02");
|
||||
|
||||
def emit_level(self,f,dark=True):
|
||||
if dark:
|
||||
self.emit_parameter(f,"LP","D");
|
||||
self.level_dark = True;
|
||||
else:
|
||||
self.emit_parameter(f,"LP","C");
|
||||
self.level_dark = False;
|
||||
|
||||
def clear_aperture_cache(self):
|
||||
self.circular_apertures = set();
|
||||
self.aperture_codes = dict();
|
||||
self.aperture_diameters = dict();
|
||||
|
||||
self.excellon_drill_diameters = dict();
|
||||
self.excellon_drill_codes = dict();
|
||||
|
||||
self.current_aperture_code = -1;
|
||||
|
||||
self.excellon_drill_counter = -1;
|
||||
self.current_excellon_drill_code = -1;
|
||||
|
||||
|
||||
## INIT METHOD
|
||||
|
||||
def __init__(self):
|
||||
self.clear_aperture_cache();
|
||||
|
||||
'''Measure the DXF file, record circular apertures'''
|
||||
def measure_dxf_file(self,dxf):
|
||||
|
||||
for c in dxf.circles:
|
||||
self.circular_apertures.add(c[DXFFile.DIAMETER]);
|
||||
|
||||
for p in dxf.polylines:
|
||||
if DXFFile.LINEWIDTH in p:
|
||||
self.circular_apertures.add(p[DXFFile.LINEWIDTH]);
|
||||
else:
|
||||
self.circular_apertures.add(0.0);
|
||||
|
||||
def process_dxf_for_writing(self,dxf,layernames):
|
||||
|
||||
regions = list();
|
||||
tracks = list();
|
||||
circles = list();
|
||||
|
||||
# Process tracks
|
||||
for layer in layernames:
|
||||
for p in dxf.open_polylines_on_layer(layer):
|
||||
tracks.append(p);
|
||||
|
||||
# Process regions
|
||||
for layer in layernames:
|
||||
for p in dxf.closed_polylines_on_layer(layer):
|
||||
regions.append(p);
|
||||
|
||||
# Process circles
|
||||
for layer in layernames:
|
||||
for p in dxf.circles_on_layer(layer):
|
||||
circles.append(p);
|
||||
|
||||
return {'Tracks':tracks,'Regions':regions,'Circles':circles};
|
||||
|
||||
def emit_gerber_aperture_definition(self,f,n,s):
|
||||
self.emit_parameter(f,"ADD%d" % n,s);
|
||||
|
||||
def define_gerber_circular_aperture(self,f,n,d):
|
||||
|
||||
dia = self.default_diameter if d==0.0 else d;
|
||||
|
||||
self.emit_gerber_aperture_definition(f,n,"C,%f" % (dia*self.scale));
|
||||
|
||||
self.aperture_diameters[d]=n;
|
||||
self.aperture_codes[n]=d;
|
||||
|
||||
def ensure_region(self,f,state=True):
|
||||
if self.region!=state:
|
||||
if state:
|
||||
self.emit_command(f,"G36");
|
||||
self.region=True;
|
||||
else:
|
||||
self.emit_command(f,"G37");
|
||||
self.region=False;
|
||||
|
||||
def emit_region(self,f,poly):
|
||||
self.ensure_region(f,True);
|
||||
points = iter(poly['VERTICIES']);
|
||||
first_point = points.next();
|
||||
self.move_to(f,first_point);
|
||||
for point in points:
|
||||
self.draw_to(f,point);
|
||||
self.draw_to(f,first_point);
|
||||
|
||||
def write_gerber_select_aperture(self,f,c):
|
||||
req_aperture_code = self.aperture_diameters[c];
|
||||
if self.current_aperture_code!=req_aperture_code:
|
||||
self.emit_command(f,'D%d' % req_aperture_code);
|
||||
self.current_aperture_code = req_aperture_code;
|
||||
|
||||
def reset_gerber_state(self,f):
|
||||
self.emit_level(f,dark=True);
|
||||
self.region=False;
|
||||
self.X = -1.0;
|
||||
self.Y = -1.0;
|
||||
self.current_aperture_diameter = -1;
|
||||
self.current_aperture_code = -1;
|
||||
self.current_drill_diameter = -1;
|
||||
|
||||
def write_gerber_header(self,f):
|
||||
self.emit_parameter(f,"G04","Lancaster University RF PCB");
|
||||
self.emit_precision(f);
|
||||
self.emit_parameter(f,"MO","MM");
|
||||
self.emit_parameter(f,"SR","X1Y1I0J0");
|
||||
|
||||
self.reset_gerber_state(f);
|
||||
|
||||
def write_gerber_apertures(self,f):
|
||||
self.aperture_counter = 10;
|
||||
for c in self.circular_apertures:
|
||||
self.define_gerber_circular_aperture(f,self.aperture_counter,c);
|
||||
self.aperture_counter += 1;
|
||||
|
||||
def write_gerber_track(self,f,poly):
|
||||
self.ensure_region(f,False);
|
||||
if DXFFile.LINEWIDTH in poly:
|
||||
self.write_gerber_select_aperture(f,poly[DXFFile.LINEWIDTH]);
|
||||
else:
|
||||
self.write_gerber_select_aperture(f,0.0);
|
||||
print "Bug! writing a zero-width open line";
|
||||
|
||||
points = iter(poly['VERTICES']);
|
||||
first_point = points.next();
|
||||
self.move_to(f,(first_point[DXFFile.X],first_point[DXFFile.Y]));
|
||||
for point in points:
|
||||
self.draw_to(f,(point[DXFFile.X],point[DXFFile.Y]));
|
||||
|
||||
def write_gerber_region(self,f,poly):
|
||||
self.ensure_region(f,True);
|
||||
points = iter(poly['VERTICES']);
|
||||
first_point = points.next();
|
||||
self.move_to(f,(first_point[DXFFile.X],first_point[DXFFile.Y]));
|
||||
for point in points:
|
||||
self.draw_to(f,(point[DXFFile.X],point[DXFFile.Y]));
|
||||
self.draw_to(f,(first_point[DXFFile.X],first_point[DXFFile.Y]));
|
||||
|
||||
def write_gerber_flash(self,f,c):
|
||||
if c[DXFFile.X]==0.0:
|
||||
if c[DXFFile.Y]==0.0:
|
||||
return
|
||||
self.write_gerber_select_aperture(f,c[DXFFile.DIAMETER]);
|
||||
self.flash_command(f,(c[DXFFile.X],c[DXFFile.Y]),'D03');
|
||||
|
||||
def write_gerber_trailer(self,f):
|
||||
self.ensure_region(f,False);
|
||||
self.emit_command(f,"M02");
|
||||
|
||||
def write_gerber_file(self,fname,dxf,layernames):
|
||||
print 'Writing Gerber file %s' % fname;
|
||||
|
||||
entities = self.process_dxf_for_writing(dxf,layernames);
|
||||
|
||||
print 'File will contain %d regions, %d tracks and %d circles' % (len(entities['Regions']),len(entities['Tracks']),len(entities['Circles']));
|
||||
|
||||
if len(entities['Regions'])==0:
|
||||
if len(entities['Tracks'])==0:
|
||||
if len(entities['Circles'])==0:
|
||||
print "File will be empty: Skipping file %s" % fname;
|
||||
try:
|
||||
os.unlink(fname);
|
||||
except:
|
||||
pass;
|
||||
return
|
||||
|
||||
with open(fname,'w') as f:
|
||||
self.write_gerber_header(f);
|
||||
self.write_gerber_apertures(f);
|
||||
|
||||
print "Writing %d Tracks" % (len(entities['Tracks']));
|
||||
|
||||
for c in self.circular_apertures:
|
||||
for p in entities['Tracks']:
|
||||
if DXFFile.LINEWIDTH in p:
|
||||
if p[DXFFile.LINEWIDTH]==c:
|
||||
self.write_gerber_track(f,p);
|
||||
else:
|
||||
if c==0.0:
|
||||
self.write_gerber_track(f,p);
|
||||
|
||||
print "Flashing %d Apertures" % (len(self.circular_apertures));
|
||||
for d in self.circular_apertures:
|
||||
for c in self.no_duplicates(sorted(list(entities['Circles']),cmp=self.XthenY)):
|
||||
if d==c[DXFFile.DIAMETER]:
|
||||
self.write_gerber_flash(f,c);
|
||||
|
||||
print "Writing %d Regions" % (len(entities['Regions']));
|
||||
for r in entities['Regions']:
|
||||
self.write_gerber_region(f,r);
|
||||
|
||||
self.write_gerber_trailer(f);
|
||||
|
||||
# To do with excellon
|
||||
|
||||
def write_excellon_header(self,f):
|
||||
print >> f, "%";
|
||||
print >> f, "M48";
|
||||
print >> f, "METRIC,TZ";
|
||||
print >> f, "M71";
|
||||
|
||||
def define_excellon_drill_diameter(self,f,n,d):
|
||||
dia = self.default_diameter if d==0.0 else d;
|
||||
print >> f, "T%02dC%4.3f" % (n,math.ceil(dia*10.0)/10.0);
|
||||
self.excellon_drill_diameters[d]=n;
|
||||
self.excellon_drill_codes[n]=d;
|
||||
|
||||
def write_excellon_drills(self,f):
|
||||
self.excellon_drill_counter = 1;
|
||||
for c in self.circular_apertures:
|
||||
if c==0.0:
|
||||
continue;
|
||||
self.define_excellon_drill_diameter(f,self.excellon_drill_counter,c);
|
||||
self.excellon_drill_counter += 1;
|
||||
|
||||
def write_excellon_select_drill(self,f,d):
|
||||
req_drill_code = self.excellon_drill_diameters[d];
|
||||
if self.current_excellon_drill_code!=req_drill_code:
|
||||
print >> f, "T%02d" % req_drill_code;
|
||||
self.current_excellon_drill_code = req_drill_code;
|
||||
|
||||
def write_excellon_cut(self,f,p):
|
||||
pass;
|
||||
|
||||
def write_excellon_cutout(self,f,p):
|
||||
pass;
|
||||
|
||||
def write_excellon_drill_point(self,f,c):
|
||||
self.write_excellon_select_drill(f,c[DXFFile.DIAMETER]);
|
||||
print >> f, GerberWriter.exc_emit_point((c[DXFFile.X],c[DXFFile.Y]));
|
||||
|
||||
def write_excellon_trailer(self,f):
|
||||
print >> f, "M30";
|
||||
|
||||
def write_excellon_file(self,fname,dxf,layernames):
|
||||
print 'Writing Excellon file %s' % fname;
|
||||
|
||||
killfile = True;
|
||||
entities = self.process_dxf_for_writing(dxf,layernames);
|
||||
diameters = sorted(list(self.circular_apertures));
|
||||
|
||||
with open(fname,'w') as f:
|
||||
|
||||
self.write_excellon_header(f);
|
||||
self.write_excellon_drills(f);
|
||||
|
||||
print >> f, "%";
|
||||
print >> f, "G05";
|
||||
|
||||
for dia in diameters:
|
||||
print "Diameter = %g" % (dia);
|
||||
|
||||
if dia==0.0:
|
||||
print "Skipping diameter 0 holes";
|
||||
continue;
|
||||
|
||||
print "Processing entries for drill diameter %g" % (dia);
|
||||
|
||||
holes = list(self.no_duplicates(sorted(entities['Circles'],cmp=self.XthenY)));
|
||||
|
||||
print "Drilling %d holes\n" % len(holes);
|
||||
|
||||
for circle in holes:
|
||||
if circle[DXFFile.DIAMETER]==dia:
|
||||
self.write_excellon_drill_point(f,circle);
|
||||
|
||||
# print "Making %d cuts\n" % len(entities['Tracks']);
|
||||
#
|
||||
# print >> f, "G01";
|
||||
#
|
||||
# for p in entities['Tracks']:
|
||||
# if DXFFile.LINEWIDTH in p:
|
||||
# if p[DXFFile.LINEWIDTH]==dia:
|
||||
# self.write_excellon_cut(f,p);
|
||||
# else:
|
||||
# raise Exception("Error: trying to cut a slot with zero cutter width");
|
||||
#
|
||||
# print "Making %d cut-outs\n" % (len(entities['Regions']));
|
||||
#
|
||||
# print >> f, "G01";
|
||||
#
|
||||
# for r in entities['Regions']:
|
||||
# print r;
|
||||
# if DXFFile.LINEWIDTH in r:
|
||||
# print "Cut-out has width %g" % (r[DXFFile.LINEWIDTH]);
|
||||
# if r[DXFFile.LINEWIDTH]==dia:
|
||||
# self.write_excellon_cutout(f,r);
|
||||
# else:
|
||||
# raise Exception("Error: trying to cut a cut-out with zero cutter width");
|
||||
|
||||
self.write_excellon_trailer(f);
|
||||
|
||||
def process_cam(self,dxf,camname=None):
|
||||
|
||||
self.clear_aperture_cache();
|
||||
self.measure_dxf_file(dxf);
|
||||
|
||||
# Pick a sensible output filename
|
||||
|
||||
if camname == None:
|
||||
camname = dxf.filename;
|
||||
|
||||
self.cam_base = os.path.splitext(camname)[0];
|
||||
|
||||
# For each layer, produce a Gerber or Excellon file
|
||||
|
||||
print "\n\nProcessing Gerber files\n";
|
||||
|
||||
for extension in self.gerber_layers:
|
||||
ofname = self.cam_base+extension;
|
||||
print "Writing data of type %s to file %s" % (self.gerber_layers[extension][0],ofname);
|
||||
self.write_gerber_file(ofname,dxf,self.gerber_layers[extension]);
|
||||
print "";
|
||||
|
||||
print "\n\nProcessing Excellon files\n";
|
||||
|
||||
# For each layer, produce a Gerber or Excellon file
|
||||
|
||||
for extension in self.excellon_layers:
|
||||
ofname = self.cam_base+extension;
|
||||
print "Writing data of type %s to file %s" % (self.excellon_layers[extension][0],ofname);
|
||||
self.write_excellon_file(ofname,dxf,self.excellon_layers[extension]);
|
||||
print "";
|
||||
|
||||
print "\n\nDone\n";
|
||||
|
||||
# The main program
|
||||
|
||||
if __name__=="__main__":
|
||||
|
||||
for f in glob.glob('G:\\resonator_board\\*.dxf'):
|
||||
|
||||
print 'Processing file %s' % f;
|
||||
|
||||
d = DXFFile(f);
|
||||
g = GerberWriter();
|
||||
|
||||
g.process_cam(d);
|
||||
|
||||
del g;
|
||||
del d;
|
||||
Reference in New Issue
Block a user