753 lines
25 KiB
Python
753 lines
25 KiB
Python
|
#! /usr/bin/env python
|
||
|
|
||
|
import inkex
|
||
|
import os
|
||
|
import subprocess
|
||
|
import tempfile
|
||
|
import shutil
|
||
|
import copy
|
||
|
import platform
|
||
|
import simplepath
|
||
|
import simpletransform
|
||
|
from simplestyle import *
|
||
|
import cubicsuperpath
|
||
|
import cspsubdiv
|
||
|
import webbrowser
|
||
|
import hashlib
|
||
|
import xml.etree.ElementTree as ET
|
||
|
import pickle
|
||
|
from copy import deepcopy
|
||
|
|
||
|
|
||
|
EXPORT_PNG_MAX_PROCESSES = 3
|
||
|
EXPORT_KICAD_MAX_PROCESSES = 2
|
||
|
|
||
|
PCB_HEADER = '''
|
||
|
(kicad_pcb (version 4) (host pcbnew 4.0.7)
|
||
|
|
||
|
(general
|
||
|
(links 0)
|
||
|
(no_connects 0)
|
||
|
(area 77.052499 41.877835 92.193313 53.630501)
|
||
|
(thickness 1.6)
|
||
|
(drawings 8)
|
||
|
(tracks 0)
|
||
|
(zones 0)
|
||
|
(modules 1)
|
||
|
(nets 1)
|
||
|
)
|
||
|
|
||
|
(page A4)
|
||
|
(layers
|
||
|
(0 F.Cu signal)
|
||
|
(31 B.Cu signal)
|
||
|
(32 B.Adhes user)
|
||
|
(33 F.Adhes user)
|
||
|
(34 B.Paste user)
|
||
|
(35 F.Paste user)
|
||
|
(36 B.SilkS user)
|
||
|
(37 F.SilkS user)
|
||
|
(38 B.Mask user)
|
||
|
(39 F.Mask user)
|
||
|
(40 Dwgs.User user)
|
||
|
(41 Cmts.User user)
|
||
|
(42 Eco1.User user)
|
||
|
(43 Eco2.User user)
|
||
|
(44 Edge.Cuts user)
|
||
|
(45 Margin user)
|
||
|
(46 B.CrtYd user)
|
||
|
(47 F.CrtYd user)
|
||
|
(48 B.Fab user)
|
||
|
(49 F.Fab user)
|
||
|
)
|
||
|
|
||
|
(setup
|
||
|
(last_trace_width 0.25)
|
||
|
(trace_clearance 0.2)
|
||
|
(zone_clearance 0.508)
|
||
|
(zone_45_only no)
|
||
|
(trace_min 0.2)
|
||
|
(segment_width 0.2)
|
||
|
(edge_width 0.15)
|
||
|
(via_size 0.6)
|
||
|
(via_drill 0.4)
|
||
|
(via_min_size 0.4)
|
||
|
(via_min_drill 0.3)
|
||
|
(uvia_size 0.3)
|
||
|
(uvia_drill 0.1)
|
||
|
(uvias_allowed no)
|
||
|
(uvia_min_size 0.2)
|
||
|
(uvia_min_drill 0.1)
|
||
|
(pcb_text_width 0.3)
|
||
|
(pcb_text_size 1.5 1.5)
|
||
|
(mod_edge_width 0.15)
|
||
|
(mod_text_size 1 1)
|
||
|
(mod_text_width 0.15)
|
||
|
(pad_size 1.524 1.524)
|
||
|
(pad_drill 0.762)
|
||
|
(pad_to_mask_clearance 0.2)
|
||
|
(aux_axis_origin 0 0)
|
||
|
(visible_elements FFFFFF7F)
|
||
|
(pcbplotparams
|
||
|
(layerselection 0x010f0_80000001)
|
||
|
(usegerberextensions false)
|
||
|
(excludeedgelayer true)
|
||
|
(linewidth 0.100000)
|
||
|
(plotframeref false)
|
||
|
(viasonmask false)
|
||
|
(mode 1)
|
||
|
(useauxorigin false)
|
||
|
(hpglpennumber 1)
|
||
|
(hpglpenspeed 20)
|
||
|
(hpglpendiameter 15)
|
||
|
(hpglpenoverlay 2)
|
||
|
(psnegative false)
|
||
|
(psa4output false)
|
||
|
(plotreference true)
|
||
|
(plotvalue true)
|
||
|
(plotinvisibletext false)
|
||
|
(padsonsilk false)
|
||
|
(subtractmaskfromsilk false)
|
||
|
(outputformat 1)
|
||
|
(mirror false)
|
||
|
(drillshape 1)
|
||
|
(scaleselection 1)
|
||
|
(outputdirectory gerbers/))
|
||
|
)
|
||
|
|
||
|
(net 0 "")
|
||
|
|
||
|
(net_class Default "This is the default net class."
|
||
|
(clearance 0.2)
|
||
|
(trace_width 0.25)
|
||
|
(via_dia 0.6)
|
||
|
(via_drill 0.4)
|
||
|
(uvia_dia 0.3)
|
||
|
(uvia_drill 0.1)
|
||
|
)
|
||
|
'''
|
||
|
|
||
|
PCB_FOOTER = '''
|
||
|
)
|
||
|
'''
|
||
|
|
||
|
PCB_LIB_TABLE = '''
|
||
|
(fp_lib_table
|
||
|
(lib (name "{name}")(type KiCad)(uri "$(KIPRJMOD)/{folder}")(options "")(descr ""))
|
||
|
)
|
||
|
'''
|
||
|
|
||
|
PCB_PROJECT_FILE = '''
|
||
|
update=2018 March 15, Thursday 14:41:19
|
||
|
version=1
|
||
|
last_client=kicad
|
||
|
[pcbnew]
|
||
|
version=1
|
||
|
LastNetListRead=
|
||
|
UseCmpFile=1
|
||
|
PadDrill=0.600000000000
|
||
|
PadDrillOvalY=0.600000000000
|
||
|
PadSizeH=1.500000000000
|
||
|
PadSizeV=1.500000000000
|
||
|
PcbTextSizeV=1.500000000000
|
||
|
PcbTextSizeH=1.500000000000
|
||
|
PcbTextThickness=0.300000000000
|
||
|
ModuleTextSizeV=1.000000000000
|
||
|
ModuleTextSizeH=1.000000000000
|
||
|
ModuleTextSizeThickness=0.150000000000
|
||
|
SolderMaskClearance=0.000000000000
|
||
|
SolderMaskMinWidth=0.000000000000
|
||
|
DrawSegmentWidth=0.200000000000
|
||
|
BoardOutlineThickness=0.100000000000
|
||
|
ModuleOutlineThickness=0.150000000000
|
||
|
[cvpcb]
|
||
|
version=1
|
||
|
NetIExt=net
|
||
|
[general]
|
||
|
version=1
|
||
|
[eeschema]
|
||
|
version=1
|
||
|
LibDir=
|
||
|
[eeschema/libraries]
|
||
|
LibName1=power
|
||
|
LibName2=device
|
||
|
LibName3=transistors
|
||
|
LibName4=conn
|
||
|
LibName5=linear
|
||
|
LibName6=regul
|
||
|
LibName7=74xx
|
||
|
LibName8=cmos4000
|
||
|
LibName9=adc-dac
|
||
|
LibName10=memory
|
||
|
LibName11=xilinx
|
||
|
LibName12=microcontrollers
|
||
|
LibName13=dsp
|
||
|
LibName14=microchip
|
||
|
LibName15=analog_switches
|
||
|
LibName16=motorola
|
||
|
LibName17=texas
|
||
|
LibName18=intel
|
||
|
LibName19=audio
|
||
|
LibName20=interface
|
||
|
LibName21=digital-audio
|
||
|
LibName22=philips
|
||
|
LibName23=display
|
||
|
LibName24=cypress
|
||
|
LibName25=siliconi
|
||
|
LibName26=opto
|
||
|
LibName27=atmel
|
||
|
LibName28=contrib
|
||
|
LibName29=valves
|
||
|
'''
|
||
|
|
||
|
MODULE_INVIS_REF_HEADER = '''
|
||
|
(fp_text reference Ref** (at 0 0) (layer F.SilkS) hide
|
||
|
(effects (font (size 1.27 1.27) (thickness 0.15)))
|
||
|
)
|
||
|
(fp_text value Val** (at 0 0) (layer F.SilkS) hide
|
||
|
(effects (font (size 1.27 1.27) (thickness 0.15)))
|
||
|
)
|
||
|
'''
|
||
|
|
||
|
IDENTITY_MATRIX = [[1.0,0.0,0.0],[0.0,1.0,0.0]]
|
||
|
|
||
|
|
||
|
LIBRARY_TABLE_FILE = "fp-lib-table"
|
||
|
EXPORT_IMAGE_FOLDER = "images"
|
||
|
EXPORT_CACHE_FOLDER = ".svg2shenzhen-cache"
|
||
|
|
||
|
|
||
|
class Svg2ShenzhenExport(inkex.Effect):
|
||
|
def __init__(self):
|
||
|
"""init the effect library and get options from gui"""
|
||
|
inkex.Effect.__init__(self)
|
||
|
self.OptionParser.add_option("--path", action="store", type="string", dest="path", default="~/", help="")
|
||
|
self.OptionParser.add_option('-f', '--filetype', action='store', type='string', dest='filetype', default='jpeg', help='Exported file type')
|
||
|
self.OptionParser.add_option("--crop", action="store", type="inkbool", dest="crop", default=False)
|
||
|
self.OptionParser.add_option("--dpi", action="store", type="float", dest="dpi", default=600)
|
||
|
self.OptionParser.add_option("--threshold", action="store", type="float", dest="threshold", default=128.0)
|
||
|
self.OptionParser.add_option("--openfactory", action="store", type="inkbool", dest="openfactory", default="true")
|
||
|
self.OptionParser.add_option("--openkicad", action="store", type="inkbool", dest="openkicad", default="true")
|
||
|
self.OptionParser.add_option("--autoflatten", action="store", type="inkbool", dest="autoflatten", default="true")
|
||
|
self.OptionParser.add_option("--debug", action="store", type="inkbool", dest="debug", default=False)
|
||
|
|
||
|
|
||
|
self.doc_width = 0
|
||
|
self.doc_height = 0
|
||
|
|
||
|
self.bb_width_center = 0
|
||
|
self.bb_height_center = 0
|
||
|
self.bb_scaling_w = 0
|
||
|
self.bb_scaling_h = 0
|
||
|
|
||
|
self.layer_map = {
|
||
|
#'inkscape-name' : 'kicad-name',
|
||
|
'F.Cu' : 'F.Cu',
|
||
|
'B.Cu' : 'B.Cu',
|
||
|
'B.Adhes' : 'B.Adhes',
|
||
|
'F.Adhes' : 'F.Adhes',
|
||
|
'B.Paste' : 'B.Paste',
|
||
|
'F.Paste' : 'F.Paste',
|
||
|
'B.SilkS' : 'B.SilkS',
|
||
|
'F.SilkS' : 'F.SilkS',
|
||
|
'B.Mask' : 'B.Mask',
|
||
|
'F.Mask' : 'F.Mask',
|
||
|
'Dwgs.User' : 'Dwgs.User',
|
||
|
'Cmts.User' : 'Cmts.User',
|
||
|
'Eco1.User' : 'Eco1.User',
|
||
|
'Eco2.User' : 'Eco2.User',
|
||
|
'Margin' : 'Margin',
|
||
|
'B.CrtYd' : 'B.CrtYd',
|
||
|
'F.CrtYd' : 'F.CrtYd',
|
||
|
'B.Fab' : 'B.Fab',
|
||
|
'F.Fab' : 'F.Fab',
|
||
|
# The following layers are here for backward compatibility:
|
||
|
'B.Silk' : 'B.SilkS',
|
||
|
'F.Silk' : 'F.SilkS',
|
||
|
# 'Edge.Cuts' : "Edge.Cuts"
|
||
|
}
|
||
|
|
||
|
|
||
|
def coordToKicad(self,XYCoord):
|
||
|
return [
|
||
|
(XYCoord[0]-self.bb_width_center)/self.bb_scaling_w,
|
||
|
(XYCoord[1]-self.bb_height_center)/self.bb_scaling_h,
|
||
|
]
|
||
|
|
||
|
def setInkscapeScaling(self):
|
||
|
|
||
|
root = self.document.getroot()
|
||
|
height = float(root.get('height').replace("mm", ""))
|
||
|
width = float(root.get('width').replace("mm", ""))
|
||
|
|
||
|
self.doc_width = width
|
||
|
self.doc_height = height
|
||
|
|
||
|
viewbox = root.attrib['viewBox'].split(' ')
|
||
|
viewbox_h = float(viewbox[-1])
|
||
|
viewbox_w = float(viewbox[-2])
|
||
|
|
||
|
self.bb_width_center = viewbox_w/2
|
||
|
self.bb_height_center = viewbox_h/2
|
||
|
self.bb_scaling_w = viewbox_w/width
|
||
|
self.bb_scaling_h = viewbox_h/height
|
||
|
|
||
|
def setDocumentSquare(self):
|
||
|
root = self.document.getroot()
|
||
|
height = float(root.attrib['height'].replace("mm", ""))
|
||
|
width = float(root.attrib['width'].replace("mm", ""))
|
||
|
|
||
|
if (width > height):
|
||
|
root.attrib['height'] = str(width) + "mm"
|
||
|
root.attrib['viewBox'] = "0 0 %f %f" % (width, width)
|
||
|
else:
|
||
|
root.attrib['width'] = str(height) + "mm"
|
||
|
root.attrib['viewBox'] = "0 0 %f %f" % (height, height)
|
||
|
|
||
|
def findLayer(self, layerName, contains=False):
|
||
|
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
|
||
|
for layer in svg_layers:
|
||
|
label_attrib_name = "{%s}label" % layer.nsmap['inkscape']
|
||
|
if label_attrib_name not in layer.attrib:
|
||
|
continue
|
||
|
if ((layer.attrib[label_attrib_name] == layerName) and (contains == False)):
|
||
|
return layer
|
||
|
elif ((layerName in layer.attrib[label_attrib_name]) and (contains == True)):
|
||
|
return layer
|
||
|
|
||
|
return False
|
||
|
|
||
|
def effect(self):
|
||
|
# self.setDocumentSquare()
|
||
|
self.setInkscapeScaling()
|
||
|
self.processAutoMasks()
|
||
|
self.processExportLayer()
|
||
|
if (self.options.openfactory):
|
||
|
webbrowser.open("https://www.pcbway.com/setinvite.aspx?inviteid=54747", new = 2)
|
||
|
|
||
|
def processAutoMasks(self):
|
||
|
self.processAutoMaskFromTo("F.Cu", "F.Mask-auto")
|
||
|
self.processAutoMaskFromTo("B.Cu", "B.Mask-auto")
|
||
|
|
||
|
def processAutoMaskFromTo(self, from_layer, to_layer):
|
||
|
copper_layer = self.findLayer(from_layer, False)
|
||
|
cpmask_layer = self.findLayer(to_layer, True)
|
||
|
# copper_layer = cpmask_layer
|
||
|
if (copper_layer != False and cpmask_layer != False):
|
||
|
for node in cpmask_layer.xpath("*", namespaces=inkex.NSS):
|
||
|
cpmask_layer.remove(node)
|
||
|
for node in copper_layer.xpath("*", namespaces=inkex.NSS):
|
||
|
cpmask_layer.append(deepcopy(node))
|
||
|
|
||
|
|
||
|
def processExportLayer(self):
|
||
|
options = self.options
|
||
|
|
||
|
output_path = os.path.expanduser(options.path)
|
||
|
curfile = self.args[-1]
|
||
|
layers = self.get_layers(curfile)
|
||
|
name = self.get_name()
|
||
|
kicad_pcb_file = "{}.kicad_pcb".format(name)
|
||
|
library_folder = "{}.pretty".format(name)
|
||
|
kicad_project_file = "{}.pro".format(name)
|
||
|
kicad_mod_file = "{}.kicad_mod".format(name)
|
||
|
kicad_mod_files = []
|
||
|
|
||
|
cache_folder_path = os.path.join(output_path, EXPORT_CACHE_FOLDER)
|
||
|
|
||
|
if options.filetype == "png":
|
||
|
image_folder_path = output_path
|
||
|
else:
|
||
|
image_folder_path = os.path.join(cache_folder_path, EXPORT_IMAGE_FOLDER)
|
||
|
|
||
|
if options.filetype == "kicad_pcb":
|
||
|
library_folder_path = os.path.join(output_path, library_folder)
|
||
|
else:
|
||
|
library_folder_path = os.path.join(cache_folder_path, library_folder)
|
||
|
|
||
|
if not os.path.exists(output_path):
|
||
|
os.makedirs(output_path)
|
||
|
if not os.path.exists(library_folder_path):
|
||
|
os.makedirs(library_folder_path)
|
||
|
if not os.path.exists(image_folder_path):
|
||
|
os.makedirs(image_folder_path)
|
||
|
if not os.path.exists(cache_folder_path):
|
||
|
os.makedirs(cache_folder_path)
|
||
|
|
||
|
kicad_pcb_path = os.path.join(output_path, kicad_pcb_file )
|
||
|
kicad_lib_path = os.path.join(output_path, LIBRARY_TABLE_FILE)
|
||
|
kicad_pro_path = os.path.join(output_path, kicad_project_file )
|
||
|
kicad_mod_path = os.path.join(output_path, kicad_mod_file)
|
||
|
|
||
|
options_path = os.path.join(cache_folder_path, 'options.pickle')
|
||
|
|
||
|
if os.path.exists(options_path):
|
||
|
with open(options_path, 'r') as f:
|
||
|
prev_options = pickle.load(f)
|
||
|
dpi_equal = prev_options.dpi == options.dpi
|
||
|
path_equal = prev_options.path == options.path
|
||
|
crop_equal = prev_options.crop == options.crop
|
||
|
filetype_equal = prev_options.filetype == options.filetype
|
||
|
threshold_equal = prev_options.threshold == options.threshold
|
||
|
ignore_hashes = not dpi_equal or not path_equal or not crop_equal or not filetype_equal or not threshold_equal
|
||
|
else:
|
||
|
ignore_hashes = True
|
||
|
|
||
|
with open(options_path, 'w') as f:
|
||
|
pickle.dump(options, f)
|
||
|
|
||
|
layer_arguments = []
|
||
|
temp_svg_paths = []
|
||
|
for (layer_id, layer_label, layer_type) in layers:
|
||
|
if layer_type == "fixed":
|
||
|
continue
|
||
|
show_layer_ids = [layer[0] for layer in layers if layer[2] == "fixed" or layer[0] == layer_id]
|
||
|
invert = "true"
|
||
|
|
||
|
if ("-auto" in layer_label):
|
||
|
layer_label = layer_label.replace("-auto", "")
|
||
|
|
||
|
if ("-invert" in layer_label):
|
||
|
layer_label = layer_label.replace("-invert", "")
|
||
|
invert = "false"
|
||
|
hash_sum_path = os.path.join(cache_folder_path, '{}-{}-{}-{}.hash'.format(layer_id, layer_label, layer_type, invert))
|
||
|
|
||
|
prev_hash_sum = None
|
||
|
if os.path.exists(hash_sum_path):
|
||
|
with open(hash_sum_path, 'r') as f:
|
||
|
prev_hash_sum = f.read()
|
||
|
|
||
|
# generate unique filename each layer
|
||
|
temp_name = next(tempfile._get_candidate_names())
|
||
|
layer_dest_svg_path = os.path.join(cache_folder_path, temp_name)
|
||
|
hash_sum = self.export_layers(layer_dest_svg_path, show_layer_ids)
|
||
|
temp_svg_paths.append(layer_dest_svg_path)
|
||
|
|
||
|
layer_dest_png_path = os.path.join(image_folder_path, "%s_%s.png" % (layer_label, layer_id))
|
||
|
layer_dest_kicad_path = os.path.join(library_folder_path, "%s_%s.kicad_mod" % (layer_label, layer_id))
|
||
|
kicad_mod_files.append(layer_dest_kicad_path)
|
||
|
|
||
|
|
||
|
if ignore_hashes or hash_sum != prev_hash_sum or not os.path.exists(layer_dest_kicad_path):
|
||
|
with open(hash_sum_path, 'w') as f:
|
||
|
f.write(hash_sum)
|
||
|
layer_arguments.append((layer_dest_svg_path, layer_dest_png_path, layer_dest_kicad_path, layer_label, invert))
|
||
|
|
||
|
|
||
|
for i in range(0, len(layer_arguments), EXPORT_PNG_MAX_PROCESSES):
|
||
|
processes = []
|
||
|
for layer_dest_svg_path, layer_dest_png_path, _, _, _ in layer_arguments[i:i+EXPORT_PNG_MAX_PROCESSES]:
|
||
|
#export layer to png
|
||
|
p = self.exportToPng(layer_dest_svg_path, layer_dest_png_path)
|
||
|
processes.append(p)
|
||
|
for p in processes:
|
||
|
p.wait()
|
||
|
|
||
|
for layer_dest_svg_path in temp_svg_paths:
|
||
|
os.remove(layer_dest_svg_path)
|
||
|
|
||
|
if options.filetype == "kicad_pcb" or options.filetype == "kicad_module":
|
||
|
for i in range(0, len(layer_arguments), EXPORT_KICAD_MAX_PROCESSES):
|
||
|
processes = []
|
||
|
for _, layer_dest_png_path, layer_dest_kicad_path, layer_label, invert in layer_arguments[i:i+EXPORT_KICAD_MAX_PROCESSES]:
|
||
|
#export layer png to kicad
|
||
|
p = self.exportToKicad(layer_dest_png_path, layer_dest_kicad_path, layer_label, invert)
|
||
|
processes.append(p)
|
||
|
for p in processes:
|
||
|
p.wait()
|
||
|
else:
|
||
|
return
|
||
|
|
||
|
kicad_edgecut_string = self.exportEdgeCut(kicad_mod = options.filetype == "kicad_module")
|
||
|
kicad_drill_string = self.exportDrill(kicad_mod = options.filetype == "kicad_module")
|
||
|
|
||
|
if options.filetype == "kicad_pcb":
|
||
|
kicad_modules_string = ""
|
||
|
for kicad_file in kicad_mod_files:
|
||
|
with open(kicad_file, 'r') as f:
|
||
|
kicad_modules_string += f.read()
|
||
|
|
||
|
with open(kicad_pcb_path, 'w') as f:
|
||
|
f.write(PCB_HEADER)
|
||
|
f.write(kicad_modules_string)
|
||
|
f.write(kicad_edgecut_string)
|
||
|
f.write(kicad_drill_string)
|
||
|
f.write(PCB_FOOTER)
|
||
|
|
||
|
with open(kicad_lib_path, 'w') as f:
|
||
|
f.write(PCB_LIB_TABLE.format(name=name, folder=library_folder))
|
||
|
|
||
|
with open(kicad_pro_path, 'w') as f:
|
||
|
f.write(PCB_PROJECT_FILE)
|
||
|
|
||
|
if (options.openkicad):
|
||
|
self.openKicad(kicad_pcb_path)
|
||
|
|
||
|
elif options.filetype == "kicad_module":
|
||
|
kicad_modules_string = '(module "{}" (layer F.Cu)'.format(name)
|
||
|
kicad_modules_string += MODULE_INVIS_REF_HEADER
|
||
|
for kicad_file in kicad_mod_files:
|
||
|
with open(kicad_file, 'r') as f:
|
||
|
mod = f.readlines()[8:-1]
|
||
|
kicad_modules_string += "".join(mod)
|
||
|
kicad_modules_string += kicad_edgecut_string
|
||
|
kicad_modules_string += kicad_drill_string
|
||
|
kicad_modules_string += ")"
|
||
|
with open(kicad_mod_path, 'w') as f:
|
||
|
f.write(kicad_modules_string)
|
||
|
|
||
|
|
||
|
def export_layers(self, dest, show):
|
||
|
"""
|
||
|
Export selected layers of SVG to the file `dest`.
|
||
|
:arg str dest: path to export SVG file.
|
||
|
:arg list hide: layers to hide. each element is a string.
|
||
|
:arg list show: layers to show. each element is a string.
|
||
|
"""
|
||
|
doc = copy.deepcopy(self.document)
|
||
|
root = doc.getroot()
|
||
|
for layer in doc.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS):
|
||
|
id = layer.attrib["id"]
|
||
|
if id in show:
|
||
|
layer.attrib['style'] = 'display:inline'
|
||
|
else:
|
||
|
root.remove(layer)
|
||
|
|
||
|
# remove the namedview for the hash as it changes based on user zoom/scroll
|
||
|
namedview = doc.find('sodipodi:namedview', namespaces=inkex.NSS)
|
||
|
root.remove(namedview)
|
||
|
|
||
|
doc.write(dest)
|
||
|
|
||
|
# returns a hash of the exported layer contents which can be used to
|
||
|
# detect changes
|
||
|
return hashlib.md5(ET.tostring(root)).hexdigest()
|
||
|
|
||
|
def get_name(self):
|
||
|
root = self.document.getroot()
|
||
|
docname = root.get('{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname')
|
||
|
if docname is None:
|
||
|
return 'drawing'
|
||
|
return os.path.splitext(docname)[0]
|
||
|
|
||
|
def get_layers(self, src):
|
||
|
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
|
||
|
layers = []
|
||
|
|
||
|
for layer in svg_layers:
|
||
|
label_attrib_name = "{%s}label" % layer.nsmap['inkscape']
|
||
|
if label_attrib_name not in layer.attrib:
|
||
|
continue
|
||
|
|
||
|
layer_id = layer.attrib["id"]
|
||
|
layer_label = layer.attrib[label_attrib_name]
|
||
|
|
||
|
layer_label_name = layer_label.replace("-invert", "")
|
||
|
layer_label_name = layer_label_name.replace("-auto", "")
|
||
|
|
||
|
if layer_label_name in self.layer_map.iterkeys():
|
||
|
layer_type = "export"
|
||
|
layer_label = layer_label
|
||
|
elif layer_label.lower().startswith("[fixed] "):
|
||
|
layer_type = "fixed"
|
||
|
layer_label = layer_label[8:]
|
||
|
else:
|
||
|
continue
|
||
|
|
||
|
layers.append([layer_id, layer_label, layer_type])
|
||
|
|
||
|
return layers
|
||
|
|
||
|
def openKicad(self, kicad_file_path):
|
||
|
platform_system = platform.system()
|
||
|
|
||
|
if (platform_system == 'Darwin'):
|
||
|
command = "open %s" % (kicad_file_path)
|
||
|
elif (platform_system == 'Linux'):
|
||
|
command = "xdg-open %s" % (kicad_file_path)
|
||
|
else:
|
||
|
command = "start %s" % (kicad_file_path)
|
||
|
|
||
|
return subprocess.Popen(command.encode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
|
||
|
|
||
|
|
||
|
def exportToKicad(self, png_path, output_path, layer_type, invert = "true"):
|
||
|
plugin_path = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
||
|
platform_system = platform.system()
|
||
|
|
||
|
if (platform_system == 'Darwin'):
|
||
|
bitmap2component_exe = os.path.join(plugin_path, 'bitmap2component_osx')
|
||
|
elif (platform_system == 'Linux'):
|
||
|
bitmap2component_exe = os.path.join(plugin_path, 'bitmap2component_linux64')
|
||
|
else:
|
||
|
bitmap2component_exe = os.path.join(plugin_path, 'bitmap2component.exe')
|
||
|
|
||
|
layer_name = self.layer_map[layer_type]
|
||
|
command = "\"%s\" \"%s\" \"%s\" %s %s %s %s" % (bitmap2component_exe, png_path, output_path, layer_name, invert , str(int(self.options.dpi)) , str(int(self.options.threshold)))
|
||
|
if (self.options.debug):
|
||
|
inkex.debug(command)
|
||
|
return subprocess.Popen(command.encode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
|
||
|
|
||
|
def exportToPng(self, svg_path, output_path):
|
||
|
area_param = '-D' if self.options.crop else 'C'
|
||
|
command = "inkscape %s -d %s -e \"%s\" \"%s\"" % (area_param, self.options.dpi, output_path, svg_path)
|
||
|
if (self.options.debug):
|
||
|
inkex.debug(command)
|
||
|
return subprocess.Popen(command.encode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
|
||
|
|
||
|
def exportEdgeCut(self, kicad_mod=False):
|
||
|
x0 = 0
|
||
|
y0 = 0
|
||
|
mirror = 1.0
|
||
|
|
||
|
line_type = "fp_line" if kicad_mod else "gr_line"
|
||
|
|
||
|
kicad_edgecut_string = ""
|
||
|
|
||
|
i = 0
|
||
|
layerPath = '//svg:g[@inkscape:groupmode="layer"]'
|
||
|
|
||
|
if (self.options.autoflatten):
|
||
|
self.flatten_bezier()
|
||
|
|
||
|
for layer in self.document.getroot().xpath(layerPath, namespaces=inkex.NSS):
|
||
|
i += 1
|
||
|
|
||
|
label_attrib_name = "{%s}label" % layer.nsmap['inkscape']
|
||
|
if label_attrib_name not in layer.attrib:
|
||
|
continue
|
||
|
|
||
|
layer_name = (layer.attrib[label_attrib_name])
|
||
|
|
||
|
if layer_name != "Edge.Cuts":
|
||
|
continue
|
||
|
|
||
|
layer_trans = layer.get('transform')
|
||
|
if layer_trans:
|
||
|
layer_m = simpletransform.parseTransform(layer_trans)
|
||
|
else:
|
||
|
layer_m = IDENTITY_MATRIX
|
||
|
|
||
|
nodePath = ('//svg:g[@inkscape:groupmode="layer"][%d]/descendant::svg:path') % i
|
||
|
for node in self.document.getroot().xpath(nodePath, namespaces=inkex.NSS):
|
||
|
d = node.get('d')
|
||
|
p = simplepath.parsePath(d)
|
||
|
|
||
|
points = []
|
||
|
if p:
|
||
|
#sanity check
|
||
|
if p[0][0] == 'M':
|
||
|
t = node.get('transform')
|
||
|
if t:
|
||
|
m = simpletransform.parseTransform(t)
|
||
|
trans = simpletransform.composeTransform(layer_m, m)
|
||
|
else:
|
||
|
trans = layer_m
|
||
|
|
||
|
for path in p:
|
||
|
if path[0] != "Z":
|
||
|
x = (path[1][0])
|
||
|
y = (path[1][1])
|
||
|
xy = [x,y]
|
||
|
simpletransform.applyTransformToPoint(trans,xy)
|
||
|
points.append(self.coordToKicad([(xy[0]-x0), xy[1]*mirror-y0]))
|
||
|
|
||
|
points_count = len(points)
|
||
|
points.append(points[0])
|
||
|
|
||
|
for x in range (0, points_count):
|
||
|
kicad_edgecut_string = kicad_edgecut_string + ("(%s (start %f %f) (end %f %f) (layer Edge.Cuts) (width 0.1))\n" % (line_type, points[x][0],points[x][1],points[x+1][0],points[x+1][1]))
|
||
|
|
||
|
return kicad_edgecut_string
|
||
|
|
||
|
def exportDrill(self, kicad_mod=False):
|
||
|
x0 = 0
|
||
|
y0 = 0
|
||
|
mirror = 1.0
|
||
|
|
||
|
self.setInkscapeScaling()
|
||
|
|
||
|
kicad_drill_string = ""
|
||
|
|
||
|
i = 0
|
||
|
|
||
|
if kicad_mod:
|
||
|
pad_template = "(pad {n} thru_hole circle (at {x} {y}) (size {d} {d}) (drill {d}) (layers *.Cu *.Mask))\n"
|
||
|
else:
|
||
|
pad_template = """
|
||
|
(module Wire_Pads:SolderWirePad_single_0-8mmDrill (layer F.Cu) (tedit 0) (tstamp 5ABD66D0)
|
||
|
(at {x} {y})
|
||
|
(pad {n} thru_hole circle (at 0 0) (size {d} {d}) (drill {d}) (layers *.Cu *.Mask))
|
||
|
)
|
||
|
"""
|
||
|
|
||
|
layerPath = '//svg:g[@inkscape:groupmode="layer"][@inkscape:label="Drill"]'
|
||
|
|
||
|
for layer in self.document.getroot().xpath(layerPath, namespaces=inkex.NSS):
|
||
|
|
||
|
layer_trans = layer.get('transform')
|
||
|
if layer_trans:
|
||
|
layer_m = simpletransform.parseTransform(layer_trans)
|
||
|
else:
|
||
|
layer_m = IDENTITY_MATRIX
|
||
|
|
||
|
nodePath = 'descendant::svg:circle'
|
||
|
|
||
|
count = 0
|
||
|
for node in layer.xpath(nodePath, namespaces=inkex.NSS):
|
||
|
count = count + 1
|
||
|
cx = float(node.get('cx'))
|
||
|
cy = float(node.get('cy'))
|
||
|
|
||
|
radius = float(node.get('r'))
|
||
|
drill_size = radius * 2
|
||
|
|
||
|
t = node.get('transform')
|
||
|
|
||
|
pt = [cx, cy]
|
||
|
|
||
|
if t:
|
||
|
m = simpletransform.parseTransform(t)
|
||
|
trans = simpletransform.composeTransform(layer_m, m)
|
||
|
else:
|
||
|
trans = layer_m
|
||
|
|
||
|
simpletransform.applyTransformToPoint(trans,pt)
|
||
|
padCoord = self.coordToKicad(pt)
|
||
|
|
||
|
kicad_drill_string += pad_template.format(x=padCoord[0], y=padCoord[1], n=count, d=drill_size)
|
||
|
|
||
|
return kicad_drill_string
|
||
|
|
||
|
def flatten_bezier(self):
|
||
|
layerPath = '//svg:g[@inkscape:groupmode="layer"][@inkscape:label="Edge.Cuts"]'
|
||
|
for layer in self.document.getroot().xpath(layerPath, namespaces=inkex.NSS):
|
||
|
nodePath = 'descendant::svg:path'
|
||
|
for node in layer.xpath(nodePath, namespaces=inkex.NSS):
|
||
|
if node.tag == inkex.addNS('path','svg'):
|
||
|
d = node.get('d')
|
||
|
p = cubicsuperpath.parsePath(d)
|
||
|
cspsubdiv.cspsubdiv(p, 0.01)
|
||
|
np = []
|
||
|
for sp in p:
|
||
|
first = True
|
||
|
for csp in sp:
|
||
|
cmd = 'L'
|
||
|
if first:
|
||
|
cmd = 'M'
|
||
|
first = False
|
||
|
np.append([cmd, [csp[1][0], csp[1][1]]])
|
||
|
node.set('d', simplepath.formatPath(np))
|
||
|
|
||
|
def _main():
|
||
|
e = Svg2ShenzhenExport()
|
||
|
e.affect()
|
||
|
exit()
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
_main()
|