// library for creating CuVoodoo Land Pattern (cvlp) JSON files and render them // create a set of elements with an line element function cvlp_line(x1, y1, x2, y2, thickness, round=true) { var line = {} line.type = "line" line.x1 = x1 line.y1 = y1 line.x2 = x2 line.y2 = y2 line.thickness = thickness line.round = round return [line] } // create a set of elements with an arc element // arc goes angle degrees clockwise from start function cvlp_arc(x, y, radius, start, angle, thickness, round=true) { var arc = {} arc.type = "arc" arc.x = x arc.y = y arc.radius = radius arc.start = start arc.angle = angle arc.thickness = thickness arc.round = round return [arc] } // create a set of elements with an pad element function cvlp_pad(number, x, y, width, height, clearance, soldermask, round=true) { var pad = {} pad.type = "pad" pad.number = number pad.x = x pad.y = y pad.width = width pad.height = height pad.clearance = clearance pad.soldermask = soldermask pad.round = round return [pad] } // create a set of elements with an pin element function cvlp_pin(number, x, y, thickness, drill, clearance, soldermask, round=true) { var pin = {} pin.type = "pin" pin.number = number pin.x = x pin.y = y pin.thickness = thickness pin.drill = drill pin.clearance = clearance pin.soldermask = soldermask pin.round = round return [pin] } // create a set of elements representing a chain of lines // provide a list of [x,y] coordinates function cvlp_polyline(points, thickness, closed=false) { var polyline = [] for (var i=0; i180 ? '1' : '0')+' 1 '+x2+' '+y2+' ') } arc.style.setProperty('stroke-width',element.thickness) if (element.round) { arc.style.setProperty('stroke-linecap','round') } else { arc.style.setProperty('stroke-linecap','square') } layers['silkscreen'].appendChild(arc) break case 'pad': var copper = document.createElementNS(svgNS,'rect') copper.style.setProperty('stroke-width',0) copper.setAttribute('x',element.x-element.width/2) copper.setAttribute('y',element.y-element.height/2) copper.setAttribute('width',element.width) copper.setAttribute('height',element.height) if (element.round) { copper.setAttribute('rx',Math.min(element.width,element.height)/2) copper.setAttribute('ry',Math.min(element.width,element.height)/2) } else { copper.setAttribute('rx',0) copper.setAttribute('ry',0) } layers['copper'].appendChild(copper) var clearance = document.createElementNS(svgNS,'rect') clearance.style.setProperty('stroke-width',0) clearance.setAttribute('x',element.x-element.width/2-element.clearance) clearance.setAttribute('y',element.y-element.height/2-element.clearance) clearance.setAttribute('width',element.width+element.clearance*2) clearance.setAttribute('height',element.height+element.clearance*2) if (element.round) { clearance.setAttribute('rx',Math.min(element.width,element.height)/2+element.clearance) clearance.setAttribute('ry',Math.min(element.width,element.height)/2+element.clearance) } else { clearance.setAttribute('rx',0) clearance.setAttribute('ry',0) } layers['clearance'].appendChild(clearance) var soldermask = document.createElementNS(svgNS,'rect') soldermask.style.setProperty('stroke-width',0) soldermask.setAttribute('x',element.x-element.width/2-element.soldermask/2) soldermask.setAttribute('y',element.y-element.height/2-element.soldermask/2) soldermask.setAttribute('width',element.width+element.soldermask*2) soldermask.setAttribute('height',element.height+element.soldermask*2) if (element.round) { soldermask.setAttribute('rx',Math.min(element.width,element.height)/2+element.soldermask) soldermask.setAttribute('ry',Math.min(element.width,element.height)/2+element.soldermask) } else { soldermask.setAttribute('rx',0) soldermask.setAttribute('ry',0) } layers['soldermask'].appendChild(soldermask) break case 'pin': break var copper = document.createElementNS(svgNS,'rect') copper.style.setProperty('stroke-width',0) copper.setAttribute('x',element.x-element.thickness/2) copper.setAttribute('y',element.y-element.thickness/2) copper.setAttribute('height',element.thickness) copper.setAttribute('width',element.thickness) if (element.round) { copper.setAttribute('rx',element.thickness/2) copper.setAttribute('ry',element.thickness/2) } else { copper.setAttribute('rx',0) copper.setAttribute('ry',0) } layers['copper'].appendChild(copper) var drill = document.createElementNS(svgNS,'circle') drill.style.setProperty('stroke-width',0) drill.setAttribute('cx',element.x) drill.setAttribute('cy',element.y) drill.setAttribute('r',element.drill/2) layers['drill'].appendChild(drill) var clearance = document.createElementNS(svgNS,'rect') clearance.style.setProperty('stroke-width',0) clearance.setAttribute('x',element.x-element.thickness/2-element.clearance) clearance.setAttribute('y',element.y-element.thickness/2-element.clearance) clearance.setAttribute('height',element.thickness+element.clearance*2) clearance.setAttribute('width',element.thickness+element.clearance*2) if (element.round) { clearance.setAttribute('rx',element.thickness/2+element.clearance) clearance.setAttribute('ry',element.thickness/2+element.clearance) } else { clearance.setAttribute('rx',0) clearance.setAttribute('ry',0) } layers['clearance'].appendChild(clearance) var soldermask = document.createElementNS(svgNS,'rect') soldermask.style.setProperty('stroke-width',0) soldermask.setAttribute('x',element.x-element.soldermask/2) soldermask.setAttribute('y',element.y-element.soldermask/2) soldermask.setAttribute('height',element.soldermask) soldermask.setAttribute('width',element.soldermask) if (element.round) { soldermask.setAttribute('rx',element.thickness/2+element.soldermask) soldermask.setAttribute('ry',element.thickness/2+element.soldermask) } else { soldermask.setAttribute('rx',0) soldermask.setAttribute('ry',0) } layers['soldermask'].appendChild(soldermask) break default: console.log('unknown element type: '+element.type) } } return svg } // convert CuVoodoo Land Pattern JSON to gEDA pcb footprint function json2pcb(json) { // create file and add info var pcb = '' pcb += '# footprint generated from CuVoodoo Land Pattern\n' pcb += '# author: '+json.author+'\n' pcb += '# version: '+json.version+'\n' pcb += '# date: '+json.date+'\n' pcb += 'Element["" "'+json.name+'" "" "" 0 0 0 0 0 100 ""]\n' pcb += '(\n' // add element parts for (var element_i in json.elements) { var element = json.elements[element_i] switch (element.type) { case 'line': pcb += 'ElementLine['+element.x1+''+json.unit+' '+element.y1+''+json.unit+' '+element.x2+''+json.unit+' '+element.y2+''+json.unit+' '+element.thickness+''+json.unit+']\n' break case 'arc': pcb += 'ElementArc['+element.x+''+json.unit+' '+element.y+''+json.unit+' '+element.radius+''+json.unit+' '+element.radius+''+json.unit+' '+((((180-element.start)%360)+360)%360)+' '+(-1*(element.angle))+' '+element.thickness+''+json.unit+']\n' break case 'pad': var x1, y1, x2, y2, thickness if (element.width>element.height) { x1 = element.x-element.width/2+element.height/2 y1 = element.y x2 = element.x+element.width/2-element.height/2 y2 = element.y thickness = element.height } else { x1 = element.x y1 = element.y-element.height/2+element.width/2 x2 = element.x y2 = element.y+element.height/2-element.width/2 thickness = element.width } pcb += 'Pad['+x1+''+json.unit+' '+y1+''+json.unit+' '+x2+''+json.unit+' '+y2+''+json.unit+' '+thickness+''+json.unit+' '+element.clearance*2+''+json.unit+' '+Math.max(thickness+element.soldermask,0)+''+json.unit+' "" "'+element.number+'" "' if (!element.round) { pcb += 'square' } pcb += '"]\n' break case 'pin': pcb += 'Pin['+element.x+''+json.unit+' '+element.y+''+json.unit+' '+element.thickness+''+json.unit+' '+element.clearance*2+''+json.unit+' '+Math.max(thickness+element.soldermask,0)+''+json.unit+' '+element.drill+''+json.unit+' "" "'+element.number+'" "' if (!element.round) { pcb += 'square' } pcb += '"]\n' break default: console.log('unknown element type: '+element.type) } } pcb += ')\n' return pcb } // convert CuVoodoo Land Pattern JSON to KiCad s-expression footprint module/file function json2kicad(json) { // create file and add info var kicad = '' kicad += '# footprint generated from CuVoodoo Land Pattern\n' kicad += '# author: '+json.author+'\n' kicad += '# version: '+json.version+'\n' kicad += '# date: '+json.date+'\n' kicad += '(module "'+json.name+'" (layer F.Cu)\n' // add element parts for (var element_i in json.elements) { var element = json.elements[element_i] switch (element.type) { case 'line': kicad += '(fp_line (start '+convert_unit(element.x1,json.unit,'mm')+' '+convert_unit(element.y1,json.unit,'mm')+') (end '+convert_unit(element.x2,json.unit,'mm')+' '+convert_unit(element.y2,json.unit,'mm')+') (layer F.SilkS) (width '+convert_unit(element.thickness,json.unit,'mm')+'))\n' break case 'arc': kicad += '(fp_arc (start '+convert_unit(element.x,json.unit,'mm')+' '+convert_unit(element.y,json.unit,'mm')+') (end '+convert_unit((element.x+element.radius*Math.cos(((-1*element.start)%360)/360.0*Math.PI*2)),json.unit,'mm')+' '+convert_unit((element.y-element.radius*Math.sin(((-1*element.start)%360)/360.0*Math.PI*2)),json.unit,'mm')+') (angle '+element.angle+') (layer F.SilkS) (width '+convert_unit(element.thickness,json.unit,'mm')+'))\n' break case 'pad': kicad += '(pad "'+element.number+'" smd ' if (element.round) { kicad += 'oval' } else { kicad += 'rect' } kicad += ' (at '+convert_unit(element.x,json.unit,'mm')+' '+convert_unit(element.y,json.unit,'mm')+')' kicad += ' (size '+convert_unit(element.width,json.unit,'mm')+' '+convert_unit(element.height,json.unit,'mm')+')' kicad += ' (layers F.Cu F.Paste F.Mask)' kicad += ' (clearance '+convert_unit(element.clearance,json.unit,'mm')+')' kicad += ' (solder_mask_margin '+convert_unit(element.soldermask,json.unit,'mm')+')' kicad += ')\n' break case 'pin': var x = (element.x1+element.x2)/2 var y = (element.y1+element.y2)/2 var length = Math.sqrt(Math.pow(element.x2-element.x1,2)+Math.pow(element.y2-element.y1,2)) var angle = Math.atan2(element.y2-element.y1,element.x2-element.x1)*180/Math.PI; kicad += '(pad "'+element.number+'" smd ' if (element.round) { kicad += 'oval' } else { kicad += 'rect' } kicad += ' (at '+convert_unit(x,json.unit,'mm')+' '+convert_unit(y,json.unit,'mm')+' '+angle+')' kicad += ' (size '+convert_unit(length,json.unit,'mm')+' '+convert_unit(element.thickness,json.unit,'mm')+')' kicad += ' (drill '+convert_unit(element.drill,json.unit,'mm') kicad += ' (layers F.Cu F.Paste F.Mask)' kicad += ' (clearance '+convert_unit(element.clearance,json.unit,'mm')+')' kicad += ' (solder_mask_margin '+convert_unit(element.soldermask,json.unit,'mm')+')' kicad += ')\n' break default: console.log('unknown element type: '+element.type) } } kicad += ')\n' return kicad } // convert JSON to eagle library function json2eagle(json) { // create eagle xml library var docType = document.implementation.createDocumentType('eagle', 'SYSTEM', 'eagle.dtd'); var xml = document.implementation.createDocument('', 'eagle', docType); xml.documentElement.setAttribute('version','6.0') var drawing = xml.createElement('drawing') xml.documentElement.appendChild(drawing) // settings and grid are skipped // add layers var layers = xml.createElement('layers') drawing.appendChild(layers) layers_desc = [ { number: 1, name: 'Top', color: 4, fill: 1, visible: 'yes', active: 'yes'}, { number: 17, name: 'Pads', color: 2, fill: 1, visible: 'yes', active: 'yes'}, { number: 21, name: 'tPLace', color: 7, fill: 1, visible: 'yes', active: 'yes'}, { number: 29, name: 'tStop', color: 7, fill: 3, visible: 'no', active: 'yes'}, { number: 39, name: 'tKeepout', color: 4, fill: 11, visible: 'no', active: 'yes'}, ] for (layer_i in layers_desc) { var layer_desc = layers_desc[layer_i] var layer = xml.createElement('layer') for (key in layer_desc) { layer.setAttribute(key,layer_desc[key]) } layers.appendChild(layer) } // create library var library = xml.createElement('library') drawing.appendChild(library) // add description var description = xml.createElement('description') library.appendChild(description) description.textContent = 'footprint generated from CuVoodoo Land Pattern<br>' description.textContent += 'author: '+json.author+'<br>' description.textContent += 'version: '+json.version+'<br>' description.textContent += 'date: '+json.date // add package var packages = xml.createElement('packages') library.appendChild(packages) var packag = xml.createElement('package') packages.appendChild(packag) packag.setAttribute('name','oshw_logo') var desc = xml.createElement('description') packag.appendChild(desc) desc.innerHtml = json.name // add element parts // note: in eagle the origin is the bottom left corner (negate y to be compatible with the json coordinate system) for (var element_i in json.elements) { var element = json.elements[element_i] switch (element.type) { case 'line': if (element.round) { var line = xml.createElement('wire') line.setAttribute('x1',convert_unit(element.x1,json.unit,'mm')) line.setAttribute('y1',convert_unit(-1*element.y1,json.unit,'mm')) line.setAttribute('x2',convert_unit(element.x2,json.unit,'mm')) line.setAttribute('y2',convert_unit(-1*element.y2,json.unit,'mm')) line.setAttribute('width',convert_unit(element.thickness,json.unit,'mm')) line.setAttribute('layer','21') packag.appendChild(line) } else { // note: in eagle only arcs support flat caps. use rectangles for wires var line = xml.createElement('rectangle') line.setAttribute('x1',convert_unit(element.x1-element.thickness/2,json.unit,'mm')) line.setAttribute('y1',convert_unit(-1*(element.y1-element.thickness/2),json.unit,'mm')) line.setAttribute('x2',convert_unit(element.x2+element.thickness/2,json.unit,'mm')) line.setAttribute('y2',convert_unit(-1*(element.y2+element.thickness/2),json.unit,'mm')) line.setAttribute('layer','21') packag.appendChild(line) } break case 'arc': var wire = xml.createElement('wire') wire.setAttribute('x1',convert_unit(element.x+element.radius*Math.cos(((-1*element.start)%360)/360.0*Math.PI*2),json.unit,'mm')) wire.setAttribute('y1',convert_unit(-1*element.y+element.radius*Math.sin(((-1*element.start)%360)/360.0*Math.PI*2),json.unit,'mm')) wire.setAttribute('x2',convert_unit(element.x+element.radius*Math.cos(((-1*(element.start+element.angle))%360)/360.0*Math.PI*2),json.unit,'mm')) wire.setAttribute('y2',convert_unit(-1*element.y+element.radius*Math.sin(((-1*(element.start+element.angle))%360)/360.0*Math.PI*2),json.unit,'mm')) wire.setAttribute('curve',-1*element.angle) wire.setAttribute('width',convert_unit(element.thickness,json.unit,'mm')) wire.setAttribute('layer','21') if (element.round) { wire.setAttribute('cap','round') } else { wire.setAttribute('cap','flat') } packag.appendChild(wire) break case 'pad': var pad = xml.createElement('smd') pad.setAttribute('name',element.number) pad.setAttribute('x',convert_unit(element.x,json.unit,'mm')) pad.setAttribute('y',convert_unit(-1*element.y,json.unit,'mm')) pad.setAttribute('dx',convert_unit(element.width,json.unit,'mm')) pad.setAttribute('dy',convert_unit(element.height,json.unit,'mm')) pad.setAttribute('layer','1') if (element.round) { pad.setAttribute('roundness',100) } else { pad.setAttribute('roundness',0) } packag.appendChild(pad) if (element.round) { var x1, y1, x2, y2, thickness if (element.width>element.height) { x1 = element.x-element.width/2+element.height/2 y1 = element.y x2 = element.x+element.width/2-element.height/2 y2 = element.y thickness = element.height } else { x1 = element.x y1 = element.y-element.height/2+element.width/2 x2 = element.x y2 = element.y+element.height/2-element.width/2 thickness = element.width } var clearance = xml.createElement('wire') clearance.setAttribute('x1',convert_unit(x1,json.unit,'mm')) clearance.setAttribute('y1',convert_unit(-1*y1,json.unit,'mm')) clearance.setAttribute('x2',convert_unit(x2,json.unit,'mm')) clearance.setAttribute('y2',convert_unit(-1*y2,json.unit,'mm')) clearance.setAttribute('width',convert_unit(Math.max(thickness+element.clearance*2,0),json.unit,'mm')) clearance.setAttribute('layer','39') packag.appendChild(clearance) var soldermask = xml.createElement('wire') soldermask.setAttribute('x1',convert_unit(x1,json.unit,'mm')) soldermask.setAttribute('y1',convert_unit(-1*y1,json.unit,'mm')) soldermask.setAttribute('x2',convert_unit(x2,json.unit,'mm')) soldermask.setAttribute('y2',convert_unit(-1*y2,json.unit,'mm')) soldermask.setAttribute('width',convert_unit(Math.max(thickness+element.soldermask*2,0),json.unit,'mm')) soldermask.setAttribute('layer','29') packag.appendChild(soldermask) } else { var clearance = xml.createElement('rectangle') clearance.setAttribute('x1',convert_unit(element.x-element.width/2-element.clearance,json.unit,'mm')) clearance.setAttribute('y1',convert_unit(-1*(element.y-element.height/2-element.clearance),json.unit,'mm')) clearance.setAttribute('x2',convert_unit(element.x+element.width/2+element.clearance,json.unit,'mm')) clearance.setAttribute('y2',convert_unit(-1*(element.y+element.height/2+element.clearance),json.unit,'mm')) clearance.setAttribute('layer','39') var soldermask = xml.createElement('rectangle') soldermask.setAttribute('x1',convert_unit(element.x-element.width/2-element.soldermask,json.unit,'mm')) soldermask.setAttribute('y1',convert_unit(-1*(element.y-element.height/2-element.soldermask),json.unit,'mm')) soldermask.setAttribute('x2',convert_unit(element.x+element.width/2+element.soldermask,json.unit,'mm')) soldermask.setAttribute('y2',convert_unit(-1*(element.y+element.height/2+element.soldermask),json.unit,'mm')) soldermask.setAttribute('layer','29') } break case 'pin': var pin = xml.createElement('pad') pin.setAttribute('name',element.number) pin.setAttribute('x',convert_unit(element.x,json.unit,'mm')) pin.setAttribute('y',convert_unit(-1*element.y,json.unit,'mm')) pin.setAttribute('drill',convert_unit(element.drill,json.unit,'mm')) pin.setAttribute('diameter',convert_unit(element.thickness,json.unit,'mm')) pin.setAttribute('layer','17') if (element.round) { pin.setAttribute('shape','round') } else { pin.setAttribute('shape','square') } packag.appendChild(pin) if (element.round) { var clearance = xml.createElement('wire') clearance.setAttribute('x1',convert_unit(x,json.unit,'mm')) clearance.setAttribute('y2',convert_unit(-1*y,json.unit,'mm')) clearance.setAttribute('x2',convert_unit(x,json.unit,'mm')) clearance.setAttribute('y2',convert_unit(-1*y,json.unit,'mm')) clearance.setAttribute('width',convert_unit(Math.max(element.thickness+element.clearance*2,0),json.unit,'mm')) clearance.setAttribute('layer','39') packag.appendChild(clearance) var soldermask = xml.createElement('wire') soldermask.setAttribute('x1',convert_unit(x,json.unit,'mm')) soldermask.setAttribute('y1',convert_unit(-1*y,json.unit,'mm')) soldermask.setAttribute('x2',convert_unit(x,json.unit,'mm')) soldermask.setAttribute('y2',convert_unit(-1*y,json.unit,'mm')) soldermask.setAttribute('width',convert_unit(Math.max(element.thickness+element.soldermask*2,0),json.unit,'mm')) soldermask.setAttribute('layer','29') packag.appendChild(soldermask) } else { var clearance = xml.createElement('rectangle') clearance.setAttribute('x1',convert_unit(element.x-element.thickness/2-element.clearance,json.unit,'mm')) clearance.setAttribute('y1',convert_unit(-1*(element.y-element.thickness/2-element.clearance),json.unit,'mm')) clearance.setAttribute('x2',convert_unit(element.x+element.thickness/2+element.clearance,json.unit,'mm')) clearance.setAttribute('y2',convert_unit(-1*(element.y+element.thickness/2+element.clearance),json.unit,'mm')) clearance.setAttribute('layer','39') var soldermask = xml.createElement('rectangle') soldermask.setAttribute('x1',convert_unit(element.x-element.thickness/2-element.soldermask,json.unit,'mm')) soldermask.setAttribute('y1',convert_unit(-1*(element.y-element.thickness/2-element.soldermask),json.unit,'mm')) soldermask.setAttribute('x2',convert_unit(element.x+element.thickness/2+element.soldermask,json.unit,'mm')) soldermask.setAttribute('y2',convert_unit(-1*(element.y+element.thickness/2+element.soldermask),json.unit,'mm')) soldermask.setAttribute('layer','29') } break break default: console.log('unknown element type: '+element.type) } } return xml }