diff options
Diffstat (limited to 'pcbnew/eagle_plugin.cpp')
-rw-r--r-- | pcbnew/eagle_plugin.cpp | 2989 |
1 files changed, 2989 insertions, 0 deletions
diff --git a/pcbnew/eagle_plugin.cpp b/pcbnew/eagle_plugin.cpp new file mode 100644 index 0000000..cc3f198 --- /dev/null +++ b/pcbnew/eagle_plugin.cpp @@ -0,0 +1,2989 @@ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> + * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +/* + +Pcbnew PLUGIN for Eagle 6.x XML *.brd and footprint format. + +XML parsing and converting: +Getting line numbers and byte offsets from the source XML file is not +possible using currently available XML libraries within KiCad project: +wxXmlDocument and boost::property_tree. + +property_tree will give line numbers but no byte offsets, and only during +document loading. This means that if we have a problem after the document is +successfully loaded, there is no way to correlate back to line number and byte +offset of the problem. So a different approach is taken, one which relies on the +XML elements themselves using an XPATH type of reporting mechanism. The path to +the problem is reported in the error messages. This means keeping track of that +path as we traverse the XML document for the sole purpose of accurate error +reporting. + +User can load the source XML file into firefox or other xml browser and follow +our error message. + +Load() TODO's + +*) verify zone fill clearances are correct + +*/ + +#include <errno.h> + +#include <wx/string.h> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> + +#include <eagle_plugin.h> + +#include <common.h> +#include <macros.h> +#include <fctsys.h> +#include <trigo.h> +#include <macros.h> +#include <kicad_string.h> +#include <wx/filename.h> + +#include <class_board.h> +#include <class_module.h> +#include <class_track.h> +#include <class_edge_mod.h> +#include <class_zone.h> +#include <class_pcb_text.h> + +using namespace boost::property_tree; +using namespace std; + +typedef EAGLE_PLUGIN::BIU BIU; +typedef PTREE::const_assoc_iterator CA_ITER; +typedef PTREE::const_iterator CITER; +typedef std::pair<CA_ITER, CA_ITER> CA_ITER_RANGE; + +typedef MODULE_MAP::iterator MODULE_ITER; +typedef MODULE_MAP::const_iterator MODULE_CITER; + +typedef boost::optional<string> opt_string; +typedef boost::optional<int> opt_int; +typedef boost::optional<double> opt_double; +typedef boost::optional<bool> opt_bool; + + +/// segment (element) of our XPATH into the Eagle XML document tree in PTREE form. +struct TRIPLET +{ + const char* element; + const char* attribute; + const char* value; + + TRIPLET( const char* aElement, const char* aAttribute = "", const char* aValue = "" ) : + element( aElement ), + attribute( aAttribute ), + value( aValue ) + {} +}; + + +/** + * Class XPATH + * keeps track of what we are working on within a PTREE. + * Then if an exception is thrown, the place within the tree that gave us + * grief can be reported almost accurately. To minimally impact + * speed, merely assign const char* pointers during the tree walking + * expedition. The const char* pointers must be to C strings residing either in + * the data or code segment (i.e. "compiled in") or within the XML document, but + * not on the stack, since the stack is unwound during the throwing of the + * exception. The XML document will not immediately vanish since we capture + * the xpath (using function Contents()) before the XML document tree (PTREE) + * is destroyed. + */ +class XPATH +{ + std::vector<TRIPLET> p; + +public: + void push( const char* aPathSegment, const char* aAttribute="" ) + { + p.push_back( TRIPLET( aPathSegment, aAttribute ) ); + } + + void clear() { p.clear(); } + + void pop() { p.pop_back(); } + + /// modify the last path node's value + void Value( const char* aValue ) + { + p.back().value = aValue; + } + + /// modify the last path node's attribute + void Attribute( const char* aAttribute ) + { + p.back().attribute = aAttribute; + } + + /// return the contents of the XPATH as a single string + string Contents() + { + typedef std::vector<TRIPLET>::const_iterator CITER; + + string ret; + + for( CITER it = p.begin(); it != p.end(); ++it ) + { + if( it != p.begin() ) + ret += '.'; + + ret += it->element; + + if( it->attribute[0] && it->value[0] ) + { + ret += '['; + ret += it->attribute; + ret += '='; + ret += it->value; + ret += ']'; + } + } + + return ret; + } +}; + + +/** + * Function parseOptionalBool + * returns an opt_bool and sets it true or false according to the presence + * and value of an attribute within the CPTREE element. + */ +static opt_bool parseOptionalBool( CPTREE& attribs, const char* aName ) +{ + opt_bool ret; + opt_string stemp = attribs.get_optional<string>( aName ); + + if( stemp ) + ret = !stemp->compare( "yes" ); + + return ret; +} + + +// All of the 'E'STRUCTS below merely hold Eagle XML information verbatim, in binary. +// For maintenance and troubleshooting purposes, it was thought that we'd need to +// separate the conversion process into distinct steps. There is no intent to have KiCad +// forms of information in these 'E'STRUCTS. They are only binary forms +// of the Eagle information in the corresponding Eagle XML nodes. + + +/// Eagle rotation +struct EROT +{ + bool mirror; + bool spin; + double degrees; + + EROT() : + mirror( false ), + spin( false ), + degrees( 0 ) + {} + + EROT( double aDegrees ) : + mirror( false ), + spin( false ), + degrees( aDegrees ) + {} +}; + +typedef boost::optional<EROT> opt_erot; + +/// parse an Eagle XML "rot" field. Unfortunately the DTD seems not to explain +/// this format very well. [S][M]R<degrees>. Examples: "R90", "MR180", "SR180" +static EROT erot( const string& aRot ) +{ + EROT rot; + + rot.spin = aRot.find( 'S' ) != aRot.npos; + rot.mirror = aRot.find( 'M' ) != aRot.npos; + rot.degrees = strtod( aRot.c_str() + + 1 // skip leading 'R' + + int( rot.spin ) // skip optional leading 'S' + + int( rot.mirror ), // skip optional leading 'M' + NULL ); + return rot; +} + +/// Eagle "rot" fields are optional, handle that by returning opt_erot. +static opt_erot parseOptionalEROT( CPTREE& attribs ) +{ + opt_erot ret; + opt_string stemp = attribs.get_optional<string>( "rot" ); + if( stemp ) + ret = erot( *stemp ); + return ret; +} + +/// Eagle wire +struct EWIRE +{ + double x1; + double y1; + double x2; + double y2; + double width; + LAYER_NUM layer; + + // for style: (continuous | longdash | shortdash | dashdot) + enum { + CONTINUOUS, + LONGDASH, + SHORTDASH, + DASHDOT, + }; + opt_int style; + opt_double curve; ///< range is -359.9..359.9 + + // for cap: (flat | round) + enum { + FLAT, + ROUND, + }; + opt_int cap; + + EWIRE( CPTREE& aWire ); +}; + +/** + * Constructor EWIRE + * converts a "wire"'s xml attributes ( <wire> ) + * to binary without additional conversion. + * This result is an EWIRE with the <wire> textual data merely converted to binary. + */ +EWIRE::EWIRE( CPTREE& aWire ) +{ + CPTREE& attribs = aWire.get_child( "<xmlattr>" ); + + /* + <!ELEMENT wire EMPTY> + <!ATTLIST wire + x1 %Coord; #REQUIRED + y1 %Coord; #REQUIRED + x2 %Coord; #REQUIRED + y2 %Coord; #REQUIRED + width %Dimension; #REQUIRED + layer %Layer; #REQUIRED + extent %Extent; #IMPLIED -- only applicable for airwires -- + style %WireStyle; "continuous" + curve %WireCurve; "0" + cap %WireCap; "round" -- only applicable if 'curve' is not zero -- + > + */ + + x1 = attribs.get<double>( "x1" ); + y1 = attribs.get<double>( "y1" ); + x2 = attribs.get<double>( "x2" ); + y2 = attribs.get<double>( "y2" ); + width = attribs.get<double>( "width" ); + layer = attribs.get<int>( "layer" ); + + curve = attribs.get_optional<double>( "curve" ); + + opt_string s = attribs.get_optional<string>( "style" ); + if( s ) + { + if( !s->compare( "continuous" ) ) + style = EWIRE::CONTINUOUS; + else if( !s->compare( "longdash" ) ) + style = EWIRE::LONGDASH; + else if( !s->compare( "shortdash" ) ) + style = EWIRE::SHORTDASH; + else if( !s->compare( "dashdot" ) ) + style = EWIRE::DASHDOT; + } + + s = attribs.get_optional<string>( "cap" ); + if( s ) + { + if( !s->compare( "round" ) ) + cap = EWIRE::ROUND; + else if( !s->compare( "flat" ) ) + cap = EWIRE::FLAT; + } + // ignoring extent +} + + +/// Eagle via +struct EVIA +{ + double x; + double y; + int layer_front_most; /// < extent + int layer_back_most; /// < inclusive + double drill; + opt_double diam; + opt_string shape; + EVIA( CPTREE& aVia ); +}; + +EVIA::EVIA( CPTREE& aVia ) +{ + CPTREE& attribs = aVia.get_child( "<xmlattr>" ); + + /* + <!ELEMENT via EMPTY> + <!ATTLIST via + x %Coord; #REQUIRED + y %Coord; #REQUIRED + extent %Extent; #REQUIRED + drill %Dimension; #REQUIRED + diameter %Dimension; "0" + shape %ViaShape; "round" + alwaysstop %Bool; "no" + > + */ + + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + + string ext = attribs.get<string>( "extent" ); + + sscanf( ext.c_str(), "%d-%d", &layer_front_most, &layer_back_most ); + + drill = attribs.get<double>( "drill" ); + diam = attribs.get_optional<double>( "diameter" ); + shape = attribs.get_optional<string>( "shape" ); +} + + +/// Eagle circle +struct ECIRCLE +{ + double x; + double y; + double radius; + double width; + LAYER_NUM layer; + + ECIRCLE( CPTREE& aCircle ); +}; + +ECIRCLE::ECIRCLE( CPTREE& aCircle ) +{ + CPTREE& attribs = aCircle.get_child( "<xmlattr>" ); + + /* + <!ELEMENT circle EMPTY> + <!ATTLIST circle + x %Coord; #REQUIRED + y %Coord; #REQUIRED + radius %Coord; #REQUIRED + width %Dimension; #REQUIRED + layer %Layer; #REQUIRED + > + */ + + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + radius = attribs.get<double>( "radius" ); + width = attribs.get<double>( "width" ); + layer = attribs.get<int>( "layer" ); +} + + +/// Eagle XML rectangle in binary +struct ERECT +{ + double x1; + double y1; + double x2; + double y2; + int layer; + opt_erot rot; + + ERECT( CPTREE& aRect ); +}; + +ERECT::ERECT( CPTREE& aRect ) +{ + CPTREE& attribs = aRect.get_child( "<xmlattr>" ); + + /* + <!ELEMENT rectangle EMPTY> + <!ATTLIST rectangle + x1 %Coord; #REQUIRED + y1 %Coord; #REQUIRED + x2 %Coord; #REQUIRED + y2 %Coord; #REQUIRED + layer %Layer; #REQUIRED + rot %Rotation; "R0" + > + */ + + x1 = attribs.get<double>( "x1" ); + y1 = attribs.get<double>( "y1" ); + x2 = attribs.get<double>( "x2" ); + y2 = attribs.get<double>( "y2" ); + layer = attribs.get<int>( "layer" ); + rot = parseOptionalEROT( attribs ); +} + + +/// Eagle "attribute" XML element, no foolin'. +struct EATTR +{ + string name; + opt_string value; + opt_double x; + opt_double y; + opt_double size; + opt_int layer; + opt_double ratio; + opt_erot rot; + + enum { // for 'display' + Off, + VALUE, + NAME, + BOTH, + }; + opt_int display; + + EATTR( CPTREE& aTree ); + EATTR() {} +}; + +/** + * Constructor EATTR + * parses an Eagle "attribute" XML element. Note that an attribute element + * is different than an XML element attribute. The attribute element is a + * full XML node in and of itself, and has attributes of its own. Blame Eagle. + */ +EATTR::EATTR( CPTREE& aAttribute ) +{ + CPTREE& attribs = aAttribute.get_child( "<xmlattr>" ); + + /* + <!ELEMENT attribute EMPTY> + <!ATTLIST attribute + name %String; #REQUIRED + value %String; #IMPLIED + x %Coord; #IMPLIED + y %Coord; #IMPLIED + size %Dimension; #IMPLIED + layer %Layer; #IMPLIED + font %TextFont; #IMPLIED + ratio %Int; #IMPLIED + rot %Rotation; "R0" + display %AttributeDisplay; "value" -- only in <element> or <instance> context -- + constant %Bool; "no" -- only in <device> context -- + > + */ + + name = attribs.get<string>( "name" ); // #REQUIRED + value = attribs.get_optional<string>( "value" ); + + x = attribs.get_optional<double>( "x" ); + y = attribs.get_optional<double>( "y" ); + size = attribs.get_optional<double>( "size" ); + + // KiCad cannot currently put a TEXTE_MODULE on a different layer than the MODULE + // Eagle can it seems. + layer = attribs.get_optional<int>( "layer" ); + + ratio = attribs.get_optional<double>( "ratio" ); + rot = parseOptionalEROT( attribs ); + + opt_string stemp = attribs.get_optional<string>( "display" ); + if( stemp ) + { + // (off | value | name | both) + if( !stemp->compare( "off" ) ) + display = EATTR::Off; + else if( !stemp->compare( "value" ) ) + display = EATTR::VALUE; + else if( !stemp->compare( "name" ) ) + display = EATTR::NAME; + else if( !stemp->compare( "both" ) ) + display = EATTR::BOTH; + } +} + + +/// Eagle text element +struct ETEXT +{ + string text; + double x; + double y; + double size; + int layer; + opt_string font; + opt_double ratio; + opt_erot rot; + + enum { // for align + CENTER, + CENTER_LEFT, + TOP_CENTER, + TOP_LEFT, + TOP_RIGHT, + + // opposites are -1 x above, used by code tricks in here + CENTER_RIGHT = -CENTER_LEFT, + BOTTOM_CENTER = -TOP_CENTER, + BOTTOM_LEFT = -TOP_RIGHT, + BOTTOM_RIGHT = -TOP_LEFT, + }; + + opt_int align; + + ETEXT( CPTREE& aText ); +}; + +ETEXT::ETEXT( CPTREE& aText ) +{ + CPTREE& attribs = aText.get_child( "<xmlattr>" ); + + /* + <!ELEMENT text (#PCDATA)> + <!ATTLIST text + x %Coord; #REQUIRED + y %Coord; #REQUIRED + size %Dimension; #REQUIRED + layer %Layer; #REQUIRED + font %TextFont; "proportional" + ratio %Int; "8" + rot %Rotation; "R0" + align %Align; "bottom-left" + > + */ + + text = aText.data(); + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + size = attribs.get<double>( "size" ); + layer = attribs.get<int>( "layer" ); + + font = attribs.get_optional<string>( "font" ); + ratio = attribs.get_optional<double>( "ratio" ); + rot = parseOptionalEROT( attribs ); + + opt_string stemp = attribs.get_optional<string>( "align" ); + if( stemp ) + { + // (bottom-left | bottom-center | bottom-right | center-left | + // center | center-right | top-left | top-center | top-right) + if( !stemp->compare( "center" ) ) + align = ETEXT::CENTER; + else if( !stemp->compare( "center-right" ) ) + align = ETEXT::CENTER_RIGHT; + else if( !stemp->compare( "top-left" ) ) + align = ETEXT::TOP_LEFT; + else if( !stemp->compare( "top-center" ) ) + align = ETEXT::TOP_CENTER; + else if( !stemp->compare( "top-right" ) ) + align = ETEXT::TOP_RIGHT; + else if( !stemp->compare( "bottom-left" ) ) + align = ETEXT::BOTTOM_LEFT; + else if( !stemp->compare( "bottom-center" ) ) + align = ETEXT::BOTTOM_CENTER; + else if( !stemp->compare( "bottom-right" ) ) + align = ETEXT::BOTTOM_RIGHT; + else if( !stemp->compare( "center-left" ) ) + align = ETEXT::CENTER_LEFT; + } +} + + +/// Eagle thru hol pad +struct EPAD +{ + string name; + double x; + double y; + double drill; + opt_double diameter; + + // for shape: (square | round | octagon | long | offset) + enum { + SQUARE, + ROUND, + OCTAGON, + LONG, + OFFSET, + }; + opt_int shape; + opt_erot rot; + opt_bool stop; + opt_bool thermals; + opt_bool first; + + EPAD( CPTREE& aPad ); +}; + +EPAD::EPAD( CPTREE& aPad ) +{ + CPTREE& attribs = aPad.get_child( "<xmlattr>" ); + + /* + <!ELEMENT pad EMPTY> + <!ATTLIST pad + name %String; #REQUIRED + x %Coord; #REQUIRED + y %Coord; #REQUIRED + drill %Dimension; #REQUIRED + diameter %Dimension; "0" + shape %PadShape; "round" + rot %Rotation; "R0" + stop %Bool; "yes" + thermals %Bool; "yes" + first %Bool; "no" + > + */ + + // #REQUIRED says DTD, throw exception if not found + name = attribs.get<string>( "name" ); + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + drill = attribs.get<double>( "drill" ); + + diameter = attribs.get_optional<double>( "diameter" ); + + opt_string s = attribs.get_optional<string>( "shape" ); + if( s ) + { + // (square | round | octagon | long | offset) + if( !s->compare( "square" ) ) + shape = EPAD::SQUARE; + else if( !s->compare( "round" ) ) + shape = EPAD::ROUND; + else if( !s->compare( "octagon" ) ) + shape = EPAD::OCTAGON; + else if( !s->compare( "long" ) ) + shape = EPAD::LONG; + else if( !s->compare( "offset" ) ) + shape = EPAD::OFFSET; + } + + rot = parseOptionalEROT( attribs ); + stop = parseOptionalBool( attribs, "stop" ); + thermals = parseOptionalBool( attribs, "thermals" ); + first = parseOptionalBool( attribs, "first" ); +} + + +/// Eagle SMD pad +struct ESMD +{ + string name; + double x; + double y; + double dx; + double dy; + int layer; + opt_int roundness; + opt_erot rot; + opt_bool stop; + opt_bool thermals; + opt_bool cream; + + ESMD( CPTREE& aSMD ); +}; + +ESMD::ESMD( CPTREE& aSMD ) +{ + CPTREE& attribs = aSMD.get_child( "<xmlattr>" ); + + /* + <!ATTLIST smd + name %String; #REQUIRED + x %Coord; #REQUIRED + y %Coord; #REQUIRED + dx %Dimension; #REQUIRED + dy %Dimension; #REQUIRED + layer %Layer; #REQUIRED + roundness %Int; "0" + rot %Rotation; "R0" + stop %Bool; "yes" + thermals %Bool; "yes" + cream %Bool; "yes" + > + */ + + // DTD #REQUIRED, throw exception if not found + name = attribs.get<string>( "name" ); + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + dx = attribs.get<double>( "dx" ); + dy = attribs.get<double>( "dy" ); + layer = attribs.get<int>( "layer" ); + rot = parseOptionalEROT( attribs ); + + roundness = attribs.get_optional<int>( "roundness" ); + thermals = parseOptionalBool( attribs, "thermals" ); + stop = parseOptionalBool( attribs, "stop" ); + thermals = parseOptionalBool( attribs, "thermals" ); + cream = parseOptionalBool( attribs, "cream" ); +} + + +struct EVERTEX +{ + double x; + double y; + + EVERTEX( CPTREE& aVertex ); +}; + +EVERTEX::EVERTEX( CPTREE& aVertex ) +{ + CPTREE& attribs = aVertex.get_child( "<xmlattr>" ); + + /* + <!ELEMENT vertex EMPTY> + <!ATTLIST vertex + x %Coord; #REQUIRED + y %Coord; #REQUIRED + curve %WireCurve; "0" -- the curvature from this vertex to the next one -- + > + */ + + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); +} + + +/// Eagle polygon, without vertices which are parsed as needed +struct EPOLYGON +{ + double width; + int layer; + opt_double spacing; + + // KiCad priority is opposite of Eagle rank, that is: + // - Eagle Low rank drawn first + // - KiCad high priority drawn first + // So since Eagle has an upper limit we define this, used for the cases + // where no rank is specified. + static const int max_priority = 6; + + enum { // for pour + SOLID, + HATCH, + CUTOUT, + }; + int pour; + opt_double isolate; + opt_bool orphans; + opt_bool thermals; + opt_int rank; + + EPOLYGON( CPTREE& aPolygon ); +}; + +EPOLYGON::EPOLYGON( CPTREE& aPolygon ) +{ + CPTREE& attribs = aPolygon.get_child( "<xmlattr>" ); + + /* + <!ATTLIST polygon + width %Dimension; #REQUIRED + layer %Layer; #REQUIRED + spacing %Dimension; #IMPLIED + pour %PolygonPour; "solid" + isolate %Dimension; #IMPLIED -- only in <signal> or <package> context -- + orphans %Bool; "no" -- only in <signal> context -- + thermals %Bool; "yes" -- only in <signal> context -- + rank %Int; "0" -- 1..6 in <signal> context, 0 or 7 in <package> context -- + > + */ + + width = attribs.get<double>( "width" ); + layer = attribs.get<int>( "layer" ); + spacing = attribs.get_optional<double>( "spacing" ); + isolate = attribs.get_optional<double>( "isolate" ); + // default pour to solid fill + pour = EPOLYGON::SOLID; + opt_string s = attribs.get_optional<string>( "pour" ); + + if( s ) + { + // (solid | hatch | cutout) + if( !s->compare( "hatch" ) ) + pour = EPOLYGON::HATCH; + else if( !s->compare( "cutout" ) ) + pour = EPOLYGON::CUTOUT; + } + + orphans = parseOptionalBool( attribs, "orphans" ); + thermals = parseOptionalBool( attribs, "thermals" ); + rank = attribs.get_optional<int>( "rank" ); +} + +/// Eagle hole element +struct EHOLE +{ + double x; + double y; + double drill; + + EHOLE( CPTREE& aHole ); +}; + +EHOLE::EHOLE( CPTREE& aHole ) +{ + CPTREE& attribs = aHole.get_child( "<xmlattr>" ); + + /* + <!ELEMENT hole EMPTY> + <!ATTLIST hole + x %Coord; #REQUIRED + y %Coord; #REQUIRED + drill %Dimension; #REQUIRED + > + */ + + // #REQUIRED: + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + drill = attribs.get<double>( "drill" ); +} + + +/// Eagle element element +struct EELEMENT +{ + string name; + string library; + string package; + string value; + double x; + double y; + opt_bool locked; + // opt_bool smashed; + opt_erot rot; + + EELEMENT( CPTREE& aElement ); +}; + +EELEMENT::EELEMENT( CPTREE& aElement ) +{ + CPTREE& attribs = aElement.get_child( "<xmlattr>" ); + + /* + <!ELEMENT element (attribute*, variant*)> + <!ATTLIST element + name %String; #REQUIRED + library %String; #REQUIRED + package %String; #REQUIRED + value %String; #REQUIRED + x %Coord; #REQUIRED + y %Coord; #REQUIRED + locked %Bool; "no" + smashed %Bool; "no" + rot %Rotation; "R0" + > + */ + + // #REQUIRED + name = attribs.get<string>( "name" ); + library = attribs.get<string>( "library" ); + value = attribs.get<string>( "value" ); + + package = attribs.get<string>( "package" ); + ReplaceIllegalFileNameChars( &package ); + + x = attribs.get<double>( "x" ); + y = attribs.get<double>( "y" ); + + // optional + locked = parseOptionalBool( attribs, "locked" ); + // smashed = pasreOptionalBool( attribs, "smashed" ); + rot = parseOptionalEROT( attribs ); +} + + +struct ELAYER +{ + int number; + string name; + int color; + int fill; + opt_bool visible; + opt_bool active; + + ELAYER( CPTREE& aLayer ); +}; + +ELAYER::ELAYER( CPTREE& aLayer ) +{ + CPTREE& attribs = aLayer.get_child( "<xmlattr>" ); + + /* + <!ELEMENT layer EMPTY> + <!ATTLIST layer + number %Layer; #REQUIRED + name %String; #REQUIRED + color %Int; #REQUIRED + fill %Int; #REQUIRED + visible %Bool; "yes" + active %Bool; "yes" + > + */ + + number = attribs.get<int>( "number" ); + name = attribs.get<string>( "name" ); + color = attribs.get<int>( "color" ); + fill = 1; // Temporary value. + visible = parseOptionalBool( attribs, "visible" ); + active = parseOptionalBool( attribs, "active" ); +} + + +/// Parse an eagle distance which is either mm, or mils if there is "mil" suffix. +/// Return is in BIU. +static double parseEagle( const string& aDistance ) +{ + double ret = strtod( aDistance.c_str(), NULL ); + if( aDistance.npos != aDistance.find( "mil" ) ) + ret = IU_PER_MILS * ret; + else + ret = IU_PER_MM * ret; + + return ret; +} + + +/// subset of eagle.drawing.board.designrules in the XML document +struct ERULES +{ + int psElongationLong; ///< percent over 100%. 0-> not elongated, 100->twice as wide as is tall + ///< Goes into making a scaling factor for "long" pads. + + int psElongationOffset; ///< the offset of the hole within the "long" pad. + + double rvPadTop; ///< top pad size as percent of drill size + // double rvPadBottom; ///< bottom pad size as percent of drill size + + double rlMinPadTop; ///< minimum copper annulus on through hole pads + double rlMaxPadTop; ///< maximum copper annulus on through hole pads + + double rvViaOuter; ///< copper annulus is this percent of via hole + double rlMinViaOuter; ///< minimum copper annulus on via + double rlMaxViaOuter; ///< maximum copper annulus on via + double mdWireWire; ///< wire to wire spacing I presume. + + + ERULES() : + psElongationLong ( 100 ), + psElongationOffset ( 0 ), + rvPadTop ( 0.25 ), + // rvPadBottom ( 0.25 ), + rlMinPadTop ( Mils2iu( 10 ) ), + rlMaxPadTop ( Mils2iu( 20 ) ), + + rvViaOuter ( 0.25 ), + rlMinViaOuter ( Mils2iu( 10 ) ), + rlMaxViaOuter ( Mils2iu( 20 ) ), + mdWireWire ( 0 ) + {} + + void parse( CPTREE& aRules ); +}; + +void ERULES::parse( CPTREE& aRules ) +{ + for( CITER it = aRules.begin(); it != aRules.end(); ++it ) + { + if( it->first != "param" ) + continue; + + CPTREE& attribs = it->second.get_child( "<xmlattr>" ); + + const string& name = attribs.get<string>( "name" ); + + if( name == "psElongationLong" ) + psElongationLong = attribs.get<int>( "value" ); + else if( name == "psElongationOffset" ) + psElongationOffset = attribs.get<int>( "value" ); + else if( name == "rvPadTop" ) + rvPadTop = attribs.get<double>( "value" ); + else if( name == "rlMinPadTop" ) + rlMinPadTop = parseEagle( attribs.get<string>( "value" ) ); + else if( name == "rlMaxPadTop" ) + rlMaxPadTop = parseEagle( attribs.get<string>( "value" ) ); + else if( name == "rvViaOuter" ) + rvViaOuter = attribs.get<double>( "value" ); + else if( name == "rlMinViaOuter" ) + rlMinViaOuter = parseEagle( attribs.get<string>( "value" ) ); + else if( name == "rlMaxViaOuter" ) + rlMaxViaOuter = parseEagle( attribs.get<string>( "value" ) ); + else if( name == "mdWireWire" ) + mdWireWire = parseEagle( attribs.get<string>( "value" ) ); + } +} + + +/// Assemble a two part key as a simple concatonation of aFirst and aSecond parts, +/// using a separator. +static inline string makeKey( const string& aFirst, const string& aSecond ) +{ + string key = aFirst + '\x02' + aSecond; + return key; +} + + +/// Make a unique time stamp +static inline unsigned long timeStamp( CPTREE& aTree ) +{ + // in this case from a unique tree memory location + return (unsigned long)(void*) &aTree; +} + + +/// Convert an Eagle curve end to a KiCad center for S_ARC +wxPoint kicad_arc_center( wxPoint start, wxPoint end, double angle ) +{ + // Eagle give us start and end. + // S_ARC wants start to give the center, and end to give the start. + double dx = end.x - start.x, dy = end.y - start.y; + wxPoint mid = (start + end) / 2; + + double dlen = sqrt( dx*dx + dy*dy ); + double dist = dlen / ( 2 * tan( DEG2RAD( angle ) / 2 ) ); + + wxPoint center( + mid.x + dist * ( dy / dlen ), + mid.y - dist * ( dx / dlen ) + ); + + return center; +} + + +EAGLE_PLUGIN::EAGLE_PLUGIN() : + m_rules( new ERULES() ), + m_xpath( new XPATH() ), + m_mod_time( wxDateTime::Now() ) +{ + init( NULL ); + + clear_cu_map(); +} + + +EAGLE_PLUGIN::~EAGLE_PLUGIN() +{ + delete m_rules; + delete m_xpath; +} + + +const wxString EAGLE_PLUGIN::PluginName() const +{ + return wxT( "Eagle" ); +} + + +const wxString EAGLE_PLUGIN::GetFileExtension() const +{ + return wxT( "brd" ); +} + + +int inline EAGLE_PLUGIN::kicad( double d ) const +{ + return KiROUND( biu_per_mm * d ); +} + + +wxSize inline EAGLE_PLUGIN::kicad_fontz( double d ) const +{ + // texts seem to better match eagle when scaled down by 0.95 + int kz = kicad( d ) * 95 / 100; + return wxSize( kz, kz ); +} + + +BOARD* EAGLE_PLUGIN::Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties ) +{ + LOCALE_IO toggle; // toggles on, then off, the C locale. + PTREE doc; + + init( aProperties ); + + m_board = aAppendToMe ? aAppendToMe : new BOARD(); + + // Give the filename to the board if it's new + if( !aAppendToMe ) + m_board->SetFileName( aFileName ); + + // delete on exception, iff I own m_board, according to aAppendToMe + auto_ptr<BOARD> deleter( aAppendToMe ? NULL : m_board ); + + try + { + // 8 bit "filename" should be encoded according to disk filename encoding, + // (maybe this is current locale, maybe not, its a filesystem issue), + // and is not necessarily utf8. + string filename = (const char*) aFileName.char_str( wxConvFile ); + + read_xml( filename, doc, xml_parser::no_comments ); + + m_min_trace = INT_MAX; + m_min_via = INT_MAX; + m_min_via_hole = INT_MAX; + + loadAllSections( doc ); + + BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings(); + + if( m_min_trace < designSettings.m_TrackMinWidth ) + designSettings.m_TrackMinWidth = m_min_trace; + + if( m_min_via < designSettings.m_ViasMinSize ) + designSettings.m_ViasMinSize = m_min_via; + + if( m_min_via_hole < designSettings.m_ViasMinDrill ) + designSettings.m_ViasMinDrill = m_min_via_hole; + + if( m_rules->mdWireWire ) + { + NETCLASSPTR defaultNetclass = designSettings.GetDefault(); + int clearance = KiROUND( m_rules->mdWireWire ); + + if( clearance < defaultNetclass->GetClearance() ) + defaultNetclass->SetClearance( clearance ); + } + + // should be empty, else missing m_xpath->pop() + wxASSERT( m_xpath->Contents().size() == 0 ); + } + + catch( file_parser_error fpe ) + { + // for xml_parser_error, what() has the line number in it, + // but no byte offset. That should be an adequate error message. + THROW_IO_ERROR( fpe.what() ); + } + + // Class ptree_error is a base class for xml_parser_error & file_parser_error, + // so one catch should be OK for all errors. + catch( ptree_error pte ) + { + string errmsg = pte.what(); + + errmsg += " @\n"; + errmsg += m_xpath->Contents(); + + THROW_IO_ERROR( errmsg ); + } + + // IO_ERROR exceptions are left uncaught, they pass upwards from here. + + // Ensure the copper layers count is a multiple of 2 + // Pcbnew does not like boards with odd layers count + // (these boards cannot exist. they actually have a even layers count) + int lyrcnt = m_board->GetCopperLayerCount(); + + if( (lyrcnt % 2) != 0 ) + { + lyrcnt++; + m_board->SetCopperLayerCount( lyrcnt ); + } + + centerBoard(); + + deleter.release(); + return m_board; +} + + +void EAGLE_PLUGIN::init( const PROPERTIES* aProperties ) +{ + m_hole_count = 0; + m_min_trace = 0; + m_min_via = 0; + m_min_via_hole = 0; + m_xpath->clear(); + m_pads_to_nets.clear(); + + // m_templates.clear(); this is the FOOTPRINT cache too + + m_board = NULL; + m_props = aProperties; + + mm_per_biu = 1/IU_PER_MM; + biu_per_mm = IU_PER_MM; + + delete m_rules; + m_rules = new ERULES(); +} + + +void EAGLE_PLUGIN::clear_cu_map() +{ + // All cu layers are invalid until we see them in the <layers> section while + // loading either a board or library. See loadLayerDefs(). + for( unsigned i = 0; i < DIM(m_cu_map); ++i ) + m_cu_map[i] = -1; +} + + +void EAGLE_PLUGIN::loadAllSections( CPTREE& aDoc ) +{ + CPTREE& drawing = aDoc.get_child( "eagle.drawing" ); + CPTREE& board = drawing.get_child( "board" ); + + m_xpath->push( "eagle.drawing" ); + + { + m_xpath->push( "board" ); + + CPTREE& designrules = board.get_child( "designrules" ); + loadDesignRules( designrules ); + + m_xpath->pop(); + } + + { + m_xpath->push( "layers" ); + + CPTREE& layers = drawing.get_child( "layers" ); + loadLayerDefs( layers ); + + m_xpath->pop(); + } + + { + m_xpath->push( "board" ); + + CPTREE& plain = board.get_child( "plain" ); + loadPlain( plain ); + + CPTREE& signals = board.get_child( "signals" ); + loadSignals( signals ); + + CPTREE& libs = board.get_child( "libraries" ); + loadLibraries( libs ); + + CPTREE& elems = board.get_child( "elements" ); + loadElements( elems ); + + m_xpath->pop(); // "board" + } + + m_xpath->pop(); // "eagle.drawing" +} + + +void EAGLE_PLUGIN::loadDesignRules( CPTREE& aDesignRules ) +{ + m_xpath->push( "designrules" ); + m_rules->parse( aDesignRules ); + m_xpath->pop(); // "designrules" +} + + +void EAGLE_PLUGIN::loadLayerDefs( CPTREE& aLayers ) +{ + typedef std::vector<ELAYER> ELAYERS; + typedef ELAYERS::const_iterator EITER; + + ELAYERS cu; // copper layers + + // find the subset of layers that are copper, and active + for( CITER layer = aLayers.begin(); layer != aLayers.end(); ++layer ) + { + ELAYER elayer( layer->second ); + + if( elayer.number >= 1 && elayer.number <= 16 && ( !elayer.active || *elayer.active ) ) + { + cu.push_back( elayer ); + } + } + + // establish cu layer map: + int ki_layer_count = 0; + + for( EITER it = cu.begin(); it != cu.end(); ++it, ++ki_layer_count ) + { + if( ki_layer_count == 0 ) + m_cu_map[it->number] = F_Cu; + else if( ki_layer_count == int( cu.size()-1 ) ) + m_cu_map[it->number] = B_Cu; + else + { + // some eagle boards do not have contiguous layer number sequences. + +#if 0 // pre LAYER_ID & LSET: + m_cu_map[it->number] = cu.size() - 1 - ki_layer_count; +#else + m_cu_map[it->number] = ki_layer_count; +#endif + } + } + +#if 0 && defined(DEBUG) + printf( "m_cu_map:\n" ); + for( unsigned i=0; i<DIM(m_cu_map); ++i ) + { + printf( "\t[%d]:%d\n", i, m_cu_map[i] ); + } +#endif + + // Set the layer names and cu count iff we're loading a board. + if( m_board ) + { + m_board->SetCopperLayerCount( cu.size() ); + + for( EITER it = cu.begin(); it != cu.end(); ++it ) + { + LAYER_ID layer = kicad_layer( it->number ); + + // these function provide their own protection against UNDEFINED_LAYER: + m_board->SetLayerName( layer, FROM_UTF8( it->name.c_str() ) ); + m_board->SetLayerType( layer, LT_SIGNAL ); + + // could map the colors here + } + } +} + + +void EAGLE_PLUGIN::loadPlain( CPTREE& aGraphics ) +{ + m_xpath->push( "plain" ); + + // (polygon | wire | text | circle | rectangle | frame | hole)* + for( CITER gr = aGraphics.begin(); gr != aGraphics.end(); ++gr ) + { + if( gr->first == "wire" ) + { + m_xpath->push( "wire" ); + + EWIRE w( gr->second ); + LAYER_ID layer = kicad_layer( w.layer ); + + wxPoint start( kicad_x( w.x1 ), kicad_y( w.y1 ) ); + wxPoint end( kicad_x( w.x2 ), kicad_y( w.y2 ) ); + + if( layer != UNDEFINED_LAYER ) + { + DRAWSEGMENT* dseg = new DRAWSEGMENT( m_board ); + m_board->Add( dseg, ADD_APPEND ); + + if( !w.curve ) + { + dseg->SetStart( start ); + dseg->SetEnd( end ); + } + else + { + wxPoint center = kicad_arc_center( start, end, *w.curve); + + dseg->SetShape( S_ARC ); + dseg->SetStart( center ); + dseg->SetEnd( start ); + dseg->SetAngle( *w.curve * -10.0 ); // KiCad rotates the other way + } + + dseg->SetTimeStamp( timeStamp( gr->second ) ); + dseg->SetLayer( layer ); + dseg->SetWidth( Millimeter2iu( DEFAULT_PCB_EDGE_THICKNESS ) ); + } + m_xpath->pop(); + } + else if( gr->first == "text" ) + { +#if defined(DEBUG) + if( gr->second.data() == "ATMEGA328" ) + { + int breakhere = 1; + (void) breakhere; + } +#endif + m_xpath->push( "text" ); + + ETEXT t( gr->second ); + LAYER_ID layer = kicad_layer( t.layer ); + + if( layer != UNDEFINED_LAYER ) + { + TEXTE_PCB* pcbtxt = new TEXTE_PCB( m_board ); + m_board->Add( pcbtxt, ADD_APPEND ); + + pcbtxt->SetLayer( layer ); + pcbtxt->SetTimeStamp( timeStamp( gr->second ) ); + pcbtxt->SetText( FROM_UTF8( t.text.c_str() ) ); + pcbtxt->SetTextPosition( wxPoint( kicad_x( t.x ), kicad_y( t.y ) ) ); + + pcbtxt->SetSize( kicad_fontz( t.size ) ); + + double ratio = t.ratio ? *t.ratio : 8; // DTD says 8 is default + + pcbtxt->SetThickness( kicad( t.size * ratio / 100 ) ); + + int align = t.align ? *t.align : ETEXT::BOTTOM_LEFT; + + if( t.rot ) + { + int sign = t.rot->mirror ? -1 : 1; + pcbtxt->SetMirrored( t.rot->mirror ); + + double degrees = t.rot->degrees; + + if( degrees == 90 || t.rot->spin ) + pcbtxt->SetOrientation( sign * t.rot->degrees * 10 ); + else if( degrees == 180 ) + align = ETEXT::TOP_RIGHT; + else if( degrees == 270 ) + { + pcbtxt->SetOrientation( sign * 90 * 10 ); + align = ETEXT::TOP_RIGHT; + } + } + + switch( align ) + { + case ETEXT::CENTER: + // this was the default in pcbtxt's constructor + break; + + case ETEXT::CENTER_LEFT: + pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + break; + + case ETEXT::CENTER_RIGHT: + pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + break; + + case ETEXT::TOP_CENTER: + pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::TOP_LEFT: + pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::TOP_RIGHT: + pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::BOTTOM_CENTER: + pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + + case ETEXT::BOTTOM_LEFT: + pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + + case ETEXT::BOTTOM_RIGHT: + pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + } + } + m_xpath->pop(); + } + else if( gr->first == "circle" ) + { + m_xpath->push( "circle" ); + + ECIRCLE c( gr->second ); + LAYER_ID layer = kicad_layer( c.layer ); + + if( layer != UNDEFINED_LAYER ) // unsupported layer + { + DRAWSEGMENT* dseg = new DRAWSEGMENT( m_board ); + m_board->Add( dseg, ADD_APPEND ); + + dseg->SetShape( S_CIRCLE ); + dseg->SetTimeStamp( timeStamp( gr->second ) ); + dseg->SetLayer( layer ); + dseg->SetStart( wxPoint( kicad_x( c.x ), kicad_y( c.y ) ) ); + dseg->SetEnd( wxPoint( kicad_x( c.x + c.radius ), kicad_y( c.y ) ) ); + dseg->SetWidth( kicad( c.width ) ); + } + m_xpath->pop(); + } + else if( gr->first == "rectangle" ) + { + // This seems to be a simplified rectangular [copper] zone, cannot find any + // net related info on it from the DTD. + m_xpath->push( "rectangle" ); + + ERECT r( gr->second ); + LAYER_ID layer = kicad_layer( r.layer ); + + if( IsCopperLayer( layer ) ) + { + // use a "netcode = 0" type ZONE: + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_APPEND ); + + zone->SetTimeStamp( timeStamp( gr->second ) ); + zone->SetLayer( layer ); + zone->SetNetCode( NETINFO_LIST::UNCONNECTED ); + + CPolyLine::HATCH_STYLE outline_hatch = CPolyLine::DIAGONAL_EDGE; + + zone->Outline()->Start( layer, kicad_x( r.x1 ), kicad_y( r.y1 ), outline_hatch ); + zone->AppendCorner( wxPoint( kicad_x( r.x2 ), kicad_y( r.y1 ) ) ); + zone->AppendCorner( wxPoint( kicad_x( r.x2 ), kicad_y( r.y2 ) ) ); + zone->AppendCorner( wxPoint( kicad_x( r.x1 ), kicad_y( r.y2 ) ) ); + zone->Outline()->CloseLastContour(); + + // this is not my fault: + zone->Outline()->SetHatch( + outline_hatch, Mils2iu( zone->Outline()->GetDefaultHatchPitchMils() ), true ); + } + + m_xpath->pop(); + } + else if( gr->first == "hole" ) + { + m_xpath->push( "hole" ); + EHOLE e( gr->second ); + + // Fabricate a MODULE with a single PAD_ATTRIB_HOLE_NOT_PLATED pad. + // Use m_hole_count to gen up a unique name. + + MODULE* module = new MODULE( m_board ); + m_board->Add( module, ADD_APPEND ); + + char temp[40]; + sprintf( temp, "@HOLE%d", m_hole_count++ ); + module->SetReference( FROM_UTF8( temp ) ); + module->Reference().SetVisible( false ); + + wxPoint pos( kicad_x( e.x ), kicad_y( e.y ) ); + + module->SetPosition( pos ); + + // Add a PAD_ATTRIB_HOLE_NOT_PLATED pad to this module. + D_PAD* pad = new D_PAD( module ); + module->Pads().PushBack( pad ); + + pad->SetShape( PAD_SHAPE_CIRCLE ); + pad->SetAttribute( PAD_ATTRIB_HOLE_NOT_PLATED ); + + /* pad's position is already centered on module at relative (0, 0) + wxPoint padpos( kicad_x( e.x ), kicad_y( e.y ) ); + + pad->SetPos0( padpos ); + pad->SetPosition( padpos + module->GetPosition() ); + */ + + wxSize sz( kicad( e.drill ), kicad( e.drill ) ); + + pad->SetDrillSize( sz ); + pad->SetSize( sz ); + + pad->SetLayerSet( LSET::AllCuMask() ); + m_xpath->pop(); + } + else if( gr->first == "frame" ) + { + // picture this + } + else if( gr->first == "polygon" ) + { + // could be on a copper layer, could be on another layer. + // copper layer would be done using netCode=0 type of ZONE_CONTAINER. + } + } + m_xpath->pop(); +} + + +void EAGLE_PLUGIN::loadLibrary( CPTREE& aLib, const string* aLibName ) +{ + m_xpath->push( "packages" ); + + // library will have <xmlattr> node, skip that and get the single packages node + CPTREE& packages = aLib.get_child( "packages" ); + + // Create a MODULE for all the eagle packages, for use later via a copy constructor + // to instantiate needed MODULES in our BOARD. Save the MODULE templates in + // a MODULE_MAP using a single lookup key consisting of libname+pkgname. + + for( CITER package = packages.begin(); package != packages.end(); ++package ) + { + m_xpath->push( "package", "name" ); + + const string& pack_ref = package->second.get<string>( "<xmlattr>.name" ); + + string pack_name( pack_ref ); + + ReplaceIllegalFileNameChars( &pack_name ); + +#if 0 && defined(DEBUG) + if( pack_name == "TO220H" ) + { + int breakhere = 1; + (void) breakhere; + } +#endif + m_xpath->Value( pack_name.c_str() ); + + string key = aLibName ? makeKey( *aLibName, pack_name ) : pack_name; + + MODULE* m = makeModule( package->second, pack_name ); + + // add the templating MODULE to the MODULE template factory "m_templates" + std::pair<MODULE_ITER, bool> r = m_templates.insert( key, m ); + + if( !r.second + // && !( m_props && m_props->Value( "ignore_duplicates" ) ) + ) + { + wxString lib = aLibName ? FROM_UTF8( aLibName->c_str() ) : m_lib_path; + wxString pkg = FROM_UTF8( pack_name.c_str() ); + + wxString emsg = wxString::Format( + _( "<package> name: '%s' duplicated in eagle <library>: '%s'" ), + GetChars( pkg ), + GetChars( lib ) + ); + THROW_IO_ERROR( emsg ); + } + + m_xpath->pop(); + } + + m_xpath->pop(); // "packages" +} + + +void EAGLE_PLUGIN::loadLibraries( CPTREE& aLibs ) +{ + m_xpath->push( "libraries.library", "name" ); + + for( CITER library = aLibs.begin(); library != aLibs.end(); ++library ) + { + const string& lib_name = library->second.get<string>( "<xmlattr>.name" ); + + m_xpath->Value( lib_name.c_str() ); + + loadLibrary( library->second, &lib_name ); + } + + m_xpath->pop(); +} + + +void EAGLE_PLUGIN::loadElements( CPTREE& aElements ) +{ + m_xpath->push( "elements.element", "name" ); + + EATTR name; + EATTR value; + + for( CITER it = aElements.begin(); it != aElements.end(); ++it ) + { + if( it->first != "element" ) + continue; + + EELEMENT e( it->second ); + + // use "NULL-ness" as an indication of presence of the attribute: + EATTR* nameAttr = 0; + EATTR* valueAttr = 0; + + m_xpath->Value( e.name.c_str() ); + + string key = makeKey( e.library, e.package ); + + MODULE_CITER mi = m_templates.find( key ); + + if( mi == m_templates.end() ) + { + wxString emsg = wxString::Format( _( "No '%s' package in library '%s'" ), + GetChars( FROM_UTF8( e.package.c_str() ) ), + GetChars( FROM_UTF8( e.library.c_str() ) ) ); + THROW_IO_ERROR( emsg ); + } + +#if defined(DEBUG) + if( e.name == "ARM_C8" ) + { + int breakhere = 1; + (void) breakhere; + } +#endif + // copy constructor to clone the template + MODULE* m = new MODULE( *mi->second ); + m_board->Add( m, ADD_APPEND ); + + // update the nets within the pads of the clone + for( D_PAD* pad = m->Pads(); pad; pad = pad->Next() ) + { + string key = makeKey( e.name, TO_UTF8( pad->GetPadName() ) ); + + NET_MAP_CITER ni = m_pads_to_nets.find( key ); + if( ni != m_pads_to_nets.end() ) + { + const ENET* enet = &ni->second; + pad->SetNetCode( enet->netcode ); + } + } + + m->SetPosition( wxPoint( kicad_x( e.x ), kicad_y( e.y ) ) ); + m->SetReference( FROM_UTF8( e.name.c_str() ) ); + m->SetValue( FROM_UTF8( e.value.c_str() ) ); + // m->Value().SetVisible( false ); + + // initalize these to default values incase the <attribute> elements are not present. + m_xpath->push( "attribute", "name" ); + + // VALUE and NAME can have something like our text "effects" overrides + // in SWEET and new schematic. Eagle calls these XML elements "attribute". + // There can be one for NAME and/or VALUE both. Features present in the + // EATTR override the ones established in the package only if they are + // present here (except for rot, which if not present means angle zero). + // So the logic is a bit different than in packageText() and in plain text. + for( CITER ait = it->second.begin(); ait != it->second.end(); ++ait ) + { + if( ait->first != "attribute" ) + continue; + + EATTR a( ait->second ); + + if( a.name == "NAME" ) + { + name = a; + nameAttr = &name; + } + else if( a.name == "VALUE" ) + { + value = a; + valueAttr = &value; + } + } + + m_xpath->pop(); // "attribute" + + orientModuleAndText( m, e, nameAttr, valueAttr ); + } + + m_xpath->pop(); // "elements.element" +} + + +void EAGLE_PLUGIN::orientModuleAndText( MODULE* m, const EELEMENT& e, + const EATTR* nameAttr, const EATTR* valueAttr ) +{ + if( e.rot ) + { + if( e.rot->mirror ) + { + double orientation = e.rot->degrees + 180.0; + m->SetOrientation( orientation * 10 ); + m->Flip( m->GetPosition() ); + } + else + m->SetOrientation( e.rot->degrees * 10 ); + } + + orientModuleText( m, e, &m->Reference(), nameAttr ); + orientModuleText( m, e, &m->Value(), valueAttr ); +} + + +void EAGLE_PLUGIN::orientModuleText( MODULE* m, const EELEMENT& e, + TEXTE_MODULE* txt, const EATTR* aAttr ) +{ + if( aAttr ) + { + const EATTR& a = *aAttr; + + if( a.value ) + { + txt->SetText( FROM_UTF8( a.value->c_str() ) ); + } + + if( a.x && a.y ) // boost::optional + { + wxPoint pos( kicad_x( *a.x ), kicad_y( *a.y ) ); + txt->SetTextPosition( pos ); + } + + // Even though size and ratio are both optional, I am not seeing + // a case where ratio is present but size is not. + double ratio = 8; + wxSize fontz = txt->GetSize(); + + if( a.size ) + { + fontz = kicad_fontz( *a.size ); + txt->SetSize( fontz ); + + if( a.ratio ) + ratio = *a.ratio; + } + + int lw = int( fontz.y * ratio / 100.0 ); + txt->SetThickness( lw ); + + int align = ETEXT::BOTTOM_LEFT; // bottom-left is eagle default + + // The "rot" in a EATTR seems to be assumed to be zero if it is not + // present, and this zero rotation becomes an override to the + // package's text field. If they did not want zero, they specify + // what they want explicitly. + double degrees = a.rot ? a.rot->degrees : 0; + double orient; // relative to parent + + int sign = 1; + bool spin = false; + + if( a.rot ) + { + spin = a.rot->spin; + sign = a.rot->mirror ? -1 : 1; + txt->SetMirrored( a.rot->mirror ); + } + + if( degrees == 90 || degrees == 0 || spin ) + { + orient = degrees - m->GetOrientation() / 10; + txt->SetOrientation( sign * orient * 10 ); + } + else if( degrees == 180 ) + { + orient = 0 - m->GetOrientation() / 10; + txt->SetOrientation( sign * orient * 10 ); + align = ETEXT::TOP_RIGHT; + } + else if( degrees == 270 ) + { + orient = 90 - m->GetOrientation() / 10; + align = ETEXT::TOP_RIGHT; + txt->SetOrientation( sign * orient * 10 ); + } + else + { + orient = 90 + degrees - m->GetOrientation() / 10; + txt->SetOrientation( sign * orient * 10 ); + } + + switch( align ) + { + case ETEXT::TOP_RIGHT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::BOTTOM_LEFT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + + default: + ; + } + } + else // the text is per the original package, sans <attribute> + { + double degrees = ( txt->GetOrientation() + m->GetOrientation() ) / 10; + + // @todo there are a few more cases than these to contend with: + if( (!txt->IsMirrored() && ( abs( degrees ) == 180 || abs( degrees ) == 270 )) + || ( txt->IsMirrored() && ( degrees == 360 ) ) ) + { + // ETEXT::TOP_RIGHT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + } + } +} + +MODULE* EAGLE_PLUGIN::makeModule( CPTREE& aPackage, const string& aPkgName ) const +{ + std::auto_ptr<MODULE> m( new MODULE( m_board ) ); + + m->SetFPID( FPID( aPkgName ) ); + + opt_string description = aPackage.get_optional<string>( "description" ); + if( description ) + m->SetDescription( FROM_UTF8( description->c_str() ) ); + + for( CITER it = aPackage.begin(); it != aPackage.end(); ++it ) + { + CPTREE& t = it->second; + + if( it->first == "wire" ) + packageWire( m.get(), t ); + + else if( it->first == "pad" ) + packagePad( m.get(), t ); + + else if( it->first == "text" ) + packageText( m.get(), t ); + + else if( it->first == "rectangle" ) + packageRectangle( m.get(), t ); + + else if( it->first == "polygon" ) + packagePolygon( m.get(), t ); + + else if( it->first == "circle" ) + packageCircle( m.get(), t ); + + else if( it->first == "hole" ) + packageHole( m.get(), t ); + + else if( it->first == "smd" ) + packageSMD( m.get(), t ); + } + + return m.release(); +} + + +void EAGLE_PLUGIN::packageWire( MODULE* aModule, CPTREE& aTree ) const +{ + EWIRE w( aTree ); + LAYER_ID layer = kicad_layer( w.layer ); + + if( IsNonCopperLayer( layer ) ) // only valid non-copper wires, skip copper package wires + { + wxPoint start( kicad_x( w.x1 ), kicad_y( w.y1 ) ); + wxPoint end( kicad_x( w.x2 ), kicad_y( w.y2 ) ); + int width = kicad( w.width ); + + // FIXME: the cap attribute is ignored because kicad can't create lines + // with flat ends. + EDGE_MODULE* dwg; + if( !w.curve ) + { + dwg = new EDGE_MODULE( aModule, S_SEGMENT ); + + dwg->SetStart0( start ); + dwg->SetEnd0( end ); + } + else + { + dwg = new EDGE_MODULE( aModule, S_ARC ); + wxPoint center = kicad_arc_center( start, end, *w.curve); + + dwg->SetStart0( center ); + dwg->SetEnd0( start ); + dwg->SetAngle( *w.curve * -10.0 ); // KiCad rotates the other way + } + + dwg->SetLayer( layer ); + dwg->SetWidth( width ); + + aModule->GraphicalItems().PushBack( dwg ); + } +} + + +void EAGLE_PLUGIN::packagePad( MODULE* aModule, CPTREE& aTree ) const +{ + // this is thru hole technology here, no SMDs + EPAD e( aTree ); + + D_PAD* pad = new D_PAD( aModule ); + aModule->Pads().PushBack( pad ); + + pad->SetPadName( FROM_UTF8( e.name.c_str() ) ); + + // pad's "Position" is not relative to the module's, + // whereas Pos0 is relative to the module's but is the unrotated coordinate. + + wxPoint padpos( kicad_x( e.x ), kicad_y( e.y ) ); + + pad->SetPos0( padpos ); + + RotatePoint( &padpos, aModule->GetOrientation() ); + + pad->SetPosition( padpos + aModule->GetPosition() ); + + pad->SetDrillSize( wxSize( kicad( e.drill ), kicad( e.drill ) ) ); + + pad->SetLayerSet( LSET::AllCuMask().set( B_Mask ).set( F_Mask ) ); + + if( e.shape ) + { + switch( *e.shape ) + { + case EPAD::ROUND: + wxASSERT( pad->GetShape()==PAD_SHAPE_CIRCLE ); // verify set in D_PAD constructor + break; + case EPAD::OCTAGON: + // no KiCad octagonal pad shape, use PAD_CIRCLE for now. + // pad->SetShape( PAD_OCTAGON ); + wxASSERT( pad->GetShape()==PAD_SHAPE_CIRCLE ); // verify set in D_PAD constructor + break; + case EPAD::LONG: + pad->SetShape( PAD_SHAPE_OVAL ); + break; + case EPAD::SQUARE: + pad->SetShape( PAD_SHAPE_RECT ); + break; + case EPAD::OFFSET: + ; // don't know what to do here. + } + } + else + { + // if shape is not present, our default is circle and that matches their default "round" + } + + if( e.diameter ) + { + int diameter = kicad( *e.diameter ); + pad->SetSize( wxSize( diameter, diameter ) ); + } + else + { + double drillz = pad->GetDrillSize().x; + double annulus = drillz * m_rules->rvPadTop; // copper annulus, eagle "restring" + annulus = Clamp( m_rules->rlMinPadTop, annulus, m_rules->rlMaxPadTop ); + int diameter = KiROUND( drillz + 2 * annulus ); + pad->SetSize( wxSize( KiROUND( diameter ), KiROUND( diameter ) ) ); + } + + if( pad->GetShape() == PAD_SHAPE_OVAL ) + { + // The Eagle "long" pad is wider than it is tall, + // m_elongation is percent elongation + wxSize sz = pad->GetSize(); + sz.x = ( sz.x * ( 100 + m_rules->psElongationLong ) ) / 100; + pad->SetSize( sz ); + } + + if( e.rot ) + { + pad->SetOrientation( e.rot->degrees * 10 ); + } + + // @todo: handle stop and thermal +} + + +void EAGLE_PLUGIN::packageText( MODULE* aModule, CPTREE& aTree ) const +{ + ETEXT t( aTree ); + LAYER_ID layer = kicad_layer( t.layer ); + + if( layer == UNDEFINED_LAYER ) + { + layer = Cmts_User; + } + + TEXTE_MODULE* txt; + + if( t.text == ">NAME" || t.text == ">name" ) + txt = &aModule->Reference(); + else if( t.text == ">VALUE" || t.text == ">value" ) + txt = &aModule->Value(); + else + { + // FIXME: graphical text items are rotated for some reason. + txt = new TEXTE_MODULE( aModule ); + aModule->GraphicalItems().PushBack( txt ); + } + + txt->SetTimeStamp( timeStamp( aTree ) ); + txt->SetText( FROM_UTF8( t.text.c_str() ) ); + + wxPoint pos( kicad_x( t.x ), kicad_y( t.y ) ); + + txt->SetTextPosition( pos ); + txt->SetPos0( pos - aModule->GetPosition() ); + + txt->SetLayer( layer ); + + txt->SetSize( kicad_fontz( t.size ) ); + + double ratio = t.ratio ? *t.ratio : 8; // DTD says 8 is default + + txt->SetThickness( kicad( t.size * ratio / 100 ) ); + + int align = t.align ? *t.align : ETEXT::BOTTOM_LEFT; // bottom-left is eagle default + + // An eagle package is never rotated, the DTD does not allow it. + // angle -= aModule->GetOrienation(); + + if( t.rot ) + { + int sign = t.rot->mirror ? -1 : 1; + txt->SetMirrored( t.rot->mirror ); + + double degrees = t.rot->degrees; + + if( degrees == 90 || t.rot->spin ) + txt->SetOrientation( sign * degrees * 10 ); + + else if( degrees == 180 ) + align = ETEXT::TOP_RIGHT; + + else if( degrees == 270 ) + { + align = ETEXT::TOP_RIGHT; + txt->SetOrientation( sign * 90 * 10 ); + } + } + + switch( align ) + { + case ETEXT::CENTER: + // this was the default in pcbtxt's constructor + break; + + case ETEXT::CENTER_LEFT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + break; + + case ETEXT::CENTER_RIGHT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + break; + + case ETEXT::TOP_CENTER: + txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::TOP_LEFT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::TOP_RIGHT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case ETEXT::BOTTOM_CENTER: + txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + + case ETEXT::BOTTOM_LEFT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + + case ETEXT::BOTTOM_RIGHT: + txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + } +} + + +void EAGLE_PLUGIN::packageRectangle( MODULE* aModule, CPTREE& aTree ) const +{ + ERECT r( aTree ); + LAYER_ID layer = kicad_layer( r.layer ); + + if( IsNonCopperLayer( layer ) ) // skip copper "package.rectangle"s + { + EDGE_MODULE* dwg = new EDGE_MODULE( aModule, S_POLYGON ); + aModule->GraphicalItems().PushBack( dwg ); + + dwg->SetLayer( layer ); + dwg->SetWidth( 0 ); + + dwg->SetTimeStamp( timeStamp( aTree ) ); + + std::vector<wxPoint> pts; + + wxPoint start( wxPoint( kicad_x( r.x1 ), kicad_y( r.y1 ) ) ); + wxPoint end( wxPoint( kicad_x( r.x1 ), kicad_y( r.y2 ) ) ); + + pts.push_back( start ); + pts.push_back( wxPoint( kicad_x( r.x2 ), kicad_y( r.y1 ) ) ); + pts.push_back( wxPoint( kicad_x( r.x2 ), kicad_y( r.y2 ) ) ); + pts.push_back( end ); + + dwg->SetPolyPoints( pts ); + + dwg->SetStart0( start ); + dwg->SetEnd0( end ); + } +} + + +void EAGLE_PLUGIN::packagePolygon( MODULE* aModule, CPTREE& aTree ) const +{ + EPOLYGON p( aTree ); + LAYER_ID layer = kicad_layer( p.layer ); + + if( IsNonCopperLayer( layer ) ) // skip copper "package.rectangle"s + { + EDGE_MODULE* dwg = new EDGE_MODULE( aModule, S_POLYGON ); + aModule->GraphicalItems().PushBack( dwg ); + + dwg->SetWidth( 0 ); // it's filled, no need for boundary width + + /* + switch( layer ) + { + case Eco1_User: layer = F_SilkS; break; + case Eco2_User: layer = B_SilkS; break; + + // all MODULE templates (created from eagle packages) are on front layer + // until cloned. + case Cmts_User: layer = F_SilkS; break; + } + */ + + dwg->SetLayer( layer ); + + dwg->SetTimeStamp( timeStamp( aTree ) ); + + std::vector<wxPoint> pts; + pts.reserve( aTree.size() ); + + for( CITER vi = aTree.begin(); vi != aTree.end(); ++vi ) + { + if( vi->first != "vertex" ) // skip <xmlattr> node + continue; + + EVERTEX v( vi->second ); + + pts.push_back( wxPoint( kicad_x( v.x ), kicad_y( v.y ) ) ); + } + + dwg->SetPolyPoints( pts ); + + dwg->SetStart0( *pts.begin() ); + dwg->SetEnd0( pts.back() ); + } +} + + +void EAGLE_PLUGIN::packageCircle( MODULE* aModule, CPTREE& aTree ) const +{ + ECIRCLE e( aTree ); + LAYER_ID layer = kicad_layer( e.layer ); + EDGE_MODULE* gr = new EDGE_MODULE( aModule, S_CIRCLE ); + + aModule->GraphicalItems().PushBack( gr ); + + gr->SetWidth( kicad( e.width ) ); + + switch( (int) layer ) + { + case UNDEFINED_LAYER: layer = Cmts_User; break; + /* + case Eco1_User: layer = F_SilkS; break; + case Eco2_User: layer = B_SilkS; break; + */ + default: + break; + } + + gr->SetLayer( layer ); + gr->SetTimeStamp( timeStamp( aTree ) ); + + gr->SetStart0( wxPoint( kicad_x( e.x ), kicad_y( e.y ) ) ); + gr->SetEnd0( wxPoint( kicad_x( e.x + e.radius ), kicad_y( e.y ) ) ); +} + + +void EAGLE_PLUGIN::packageHole( MODULE* aModule, CPTREE& aTree ) const +{ + EHOLE e( aTree ); + + // we add a PAD_ATTRIB_HOLE_NOT_PLATED pad to this module. + D_PAD* pad = new D_PAD( aModule ); + aModule->Pads().PushBack( pad ); + + pad->SetShape( PAD_SHAPE_CIRCLE ); + pad->SetAttribute( PAD_ATTRIB_HOLE_NOT_PLATED ); + + // Mechanical purpose only: + // no offset, no net name, no pad name allowed + // pad->SetOffset( wxPoint( 0, 0 ) ); + // pad->SetPadName( wxEmptyString ); + + wxPoint padpos( kicad_x( e.x ), kicad_y( e.y ) ); + + pad->SetPos0( padpos ); + pad->SetPosition( padpos + aModule->GetPosition() ); + + wxSize sz( kicad( e.drill ), kicad( e.drill ) ); + + pad->SetDrillSize( sz ); + pad->SetSize( sz ); + + pad->SetLayerSet( LSET::AllCuMask() /* | SOLDERMASK_LAYER_BACK | SOLDERMASK_LAYER_FRONT */ ); +} + + +void EAGLE_PLUGIN::packageSMD( MODULE* aModule, CPTREE& aTree ) const +{ + ESMD e( aTree ); + LAYER_ID layer = kicad_layer( e.layer ); + + if( !IsCopperLayer( layer ) ) + { + return; + } + + D_PAD* pad = new D_PAD( aModule ); + aModule->Pads().PushBack( pad ); + + pad->SetPadName( FROM_UTF8( e.name.c_str() ) ); + pad->SetShape( PAD_SHAPE_RECT ); + pad->SetAttribute( PAD_ATTRIB_SMD ); + + // pad's "Position" is not relative to the module's, + // whereas Pos0 is relative to the module's but is the unrotated coordinate. + + wxPoint padpos( kicad_x( e.x ), kicad_y( e.y ) ); + + pad->SetPos0( padpos ); + + RotatePoint( &padpos, aModule->GetOrientation() ); + + pad->SetPosition( padpos + aModule->GetPosition() ); + + pad->SetSize( wxSize( kicad( e.dx ), kicad( e.dy ) ) ); + + pad->SetLayer( layer ); + + static const LSET front( 3, F_Cu, F_Paste, F_Mask ); + static const LSET back( 3, B_Cu, B_Paste, B_Mask ); + + if( layer == F_Cu ) + pad->SetLayerSet( front ); + else if( layer == B_Cu ) + pad->SetLayerSet( back ); + + // Optional according to DTD + if( e.roundness ) // set set shape to PAD_SHAPE_RECT above, in case roundness is not present + { + if( *e.roundness >= 75 ) // roundness goes from 0-100% as integer + { + if( e.dy == e.dx ) + pad->SetShape( PAD_SHAPE_CIRCLE ); + else + pad->SetShape( PAD_SHAPE_OVAL ); + } + } + + if( e.rot ) + { + pad->SetOrientation( e.rot->degrees * 10 ); + } + + // don't know what stop, thermals, and cream should look like now. +} + +/// non-owning container +typedef std::vector<ZONE_CONTAINER*> ZONES; + + +void EAGLE_PLUGIN::loadSignals( CPTREE& aSignals ) +{ + ZONES zones; // per net + + m_xpath->push( "signals.signal", "name" ); + + int netCode = 1; + + for( CITER net = aSignals.begin(); net != aSignals.end(); ++net ) + { + bool sawPad = false; + + zones.clear(); + + const string& nname = net->second.get<string>( "<xmlattr>.name" ); + wxString netName = FROM_UTF8( nname.c_str() ); + m_board->AppendNet( new NETINFO_ITEM( m_board, netName, netCode ) ); + + m_xpath->Value( nname.c_str() ); + +#if defined(DEBUG) + if( netName == wxT( "N$8" ) ) + { + int breakhere = 1; + (void) breakhere; + } +#endif + // (contactref | polygon | wire | via)* + for( CITER it = net->second.begin(); it != net->second.end(); ++it ) + { + if( it->first == "wire" ) + { + m_xpath->push( "wire" ); + EWIRE w( it->second ); + LAYER_ID layer = kicad_layer( w.layer ); + + if( IsCopperLayer( layer ) ) + { + TRACK* t = new TRACK( m_board ); + + t->SetTimeStamp( timeStamp( it->second ) ); + + t->SetPosition( wxPoint( kicad_x( w.x1 ), kicad_y( w.y1 ) ) ); + t->SetEnd( wxPoint( kicad_x( w.x2 ), kicad_y( w.y2 ) ) ); + + int width = kicad( w.width ); + if( width < m_min_trace ) + m_min_trace = width; + + t->SetWidth( width ); + t->SetLayer( layer ); + t->SetNetCode( netCode ); + + m_board->m_Track.Insert( t, NULL ); + } + else + { + // put non copper wires where the sun don't shine. + } + + m_xpath->pop(); + } + + else if( it->first == "via" ) + { + m_xpath->push( "via" ); + EVIA v( it->second ); + + LAYER_ID layer_front_most = kicad_layer( v.layer_front_most ); + LAYER_ID layer_back_most = kicad_layer( v.layer_back_most ); + + if( IsCopperLayer( layer_front_most ) && + IsCopperLayer( layer_back_most ) ) + { + int kidiam; + int drillz = kicad( v.drill ); + VIA* via = new VIA( m_board ); + m_board->m_Track.Insert( via, NULL ); + + via->SetLayerPair( layer_front_most, layer_back_most ); + + if( v.diam ) + { + kidiam = kicad( *v.diam ); + via->SetWidth( kidiam ); + } + else + { + double annulus = drillz * m_rules->rvViaOuter; // eagle "restring" + annulus = Clamp( m_rules->rlMinViaOuter, annulus, m_rules->rlMaxViaOuter ); + kidiam = KiROUND( drillz + 2 * annulus ); + via->SetWidth( kidiam ); + } + + via->SetDrill( drillz ); + + if( kidiam < m_min_via ) + m_min_via = kidiam; + + if( drillz < m_min_via_hole ) + m_min_via_hole = drillz; + + if( layer_front_most == F_Cu && layer_back_most == B_Cu ) + via->SetViaType( VIA_THROUGH ); + else if( layer_front_most == F_Cu || layer_back_most == B_Cu ) + via->SetViaType( VIA_MICROVIA ); + else + via->SetViaType( VIA_BLIND_BURIED ); + + via->SetTimeStamp( timeStamp( it->second ) ); + + wxPoint pos( kicad_x( v.x ), kicad_y( v.y ) ); + + via->SetPosition( pos ); + via->SetEnd( pos ); + + via->SetNetCode( netCode ); + } + m_xpath->pop(); + } + + else if( it->first == "contactref" ) + { + m_xpath->push( "contactref" ); + // <contactref element="RN1" pad="7"/> + CPTREE& attribs = it->second.get_child( "<xmlattr>" ); + + const string& reference = attribs.get<string>( "element" ); + const string& pad = attribs.get<string>( "pad" ); + + string key = makeKey( reference, pad ) ; + + // D(printf( "adding refname:'%s' pad:'%s' netcode:%d netname:'%s'\n", reference.c_str(), pad.c_str(), netCode, nname.c_str() );) + + m_pads_to_nets[ key ] = ENET( netCode, nname ); + + m_xpath->pop(); + + sawPad = true; + } + + else if( it->first == "polygon" ) + { + m_xpath->push( "polygon" ); + + EPOLYGON p( it->second ); + LAYER_ID layer = kicad_layer( p.layer ); + + if( IsCopperLayer( layer ) ) + { + // use a "netcode = 0" type ZONE: + ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board ); + m_board->Add( zone, ADD_APPEND ); + zones.push_back( zone ); + + zone->SetTimeStamp( timeStamp( it->second ) ); + zone->SetLayer( layer ); + zone->SetNetCode( netCode ); + + bool first = true; + for( CITER vi = it->second.begin(); vi != it->second.end(); ++vi ) + { + if( vi->first != "vertex" ) // skip <xmlattr> node + continue; + + EVERTEX v( vi->second ); + + // the ZONE_CONTAINER API needs work, as you can see: + if( first ) + { + zone->Outline()->Start( layer, kicad_x( v.x ), kicad_y( v.y ), + CPolyLine::NO_HATCH); + first = false; + } + else + zone->AppendCorner( wxPoint( kicad_x( v.x ), kicad_y( v.y ) ) ); + } + + zone->Outline()->CloseLastContour(); + + // If the pour is a cutout it needs to be set to a keepout + if( p.pour == EPOLYGON::CUTOUT ) + { + zone->SetIsKeepout( true ); + zone->SetDoNotAllowCopperPour( true ); + zone->Outline()->SetHatchStyle( CPolyLine::NO_HATCH ); + } + + // if spacing is set the zone should be hatched + if( p.spacing ) + zone->Outline()->SetHatch( CPolyLine::DIAGONAL_EDGE, + *p.spacing, + true ); + + // clearances, etc. + zone->SetArcSegmentCount( 32 ); // @todo: should be a constructor default? + zone->SetMinThickness( kicad( p.width ) ); + + // FIXME: KiCad zones have very rounded corners compared to eagle. + // This means that isolation amounts that work well in eagle + // tend to make copper intrude in soldermask free areas around pads. + if( p.isolate ) + { + zone->SetZoneClearance( kicad( *p.isolate ) ); + } + + // missing == yes per DTD. + bool thermals = !p.thermals || *p.thermals; + zone->SetPadConnection( thermals ? PAD_ZONE_CONN_THERMAL : PAD_ZONE_CONN_FULL ); + if( thermals ) + { + // FIXME: eagle calculates dimensions for thermal spokes + // based on what the zone is connecting to. + // (i.e. width of spoke is half of the smaller side of an smd pad) + // This is a basic workaround + zone->SetThermalReliefGap( kicad( p.width + 0.05 ) ); + zone->SetThermalReliefCopperBridge( kicad( p.width + 0.05 ) ); + } + + int rank = p.rank ? *p.rank : p.max_priority; + zone->SetPriority( rank ); + } + + m_xpath->pop(); // "polygon" + } + } + + if( zones.size() && !sawPad ) + { + // KiCad does not support an unconnected zone with its own non-zero netcode, + // but only when assigned netcode = 0 w/o a name... + for( ZONES::iterator it = zones.begin(); it != zones.end(); ++it ) + (*it)->SetNetCode( NETINFO_LIST::UNCONNECTED ); + + // therefore omit this signal/net. + } + else + netCode++; + } + + m_xpath->pop(); // "signals.signal" +} + + +LAYER_ID EAGLE_PLUGIN::kicad_layer( int aEagleLayer ) const +{ + /* will assume this is a valid mapping for all eagle boards until I get paid more: + + <layers> + <layer number="1" name="Top" color="4" fill="1" visible="yes" active="yes"/> + <layer number="2" name="Route2" color="1" fill="3" visible="no" active="no"/> + <layer number="3" name="Route3" color="4" fill="3" visible="no" active="no"/> + <layer number="4" name="Route4" color="1" fill="4" visible="no" active="no"/> + <layer number="5" name="Route5" color="4" fill="4" visible="no" active="no"/> + <layer number="6" name="Route6" color="1" fill="8" visible="no" active="no"/> + <layer number="7" name="Route7" color="4" fill="8" visible="no" active="no"/> + <layer number="8" name="Route8" color="1" fill="2" visible="no" active="no"/> + <layer number="9" name="Route9" color="4" fill="2" visible="no" active="no"/> + <layer number="10" name="Route10" color="1" fill="7" visible="no" active="no"/> + <layer number="11" name="Route11" color="4" fill="7" visible="no" active="no"/> + <layer number="12" name="Route12" color="1" fill="5" visible="no" active="no"/> + <layer number="13" name="Route13" color="4" fill="5" visible="no" active="no"/> + <layer number="14" name="Route14" color="1" fill="6" visible="no" active="no"/> + <layer number="15" name="Route15" color="4" fill="6" visible="no" active="no"/> + <layer number="16" name="Bottom" color="1" fill="1" visible="yes" active="yes"/> + <layer number="17" name="Pads" color="2" fill="1" visible="yes" active="yes"/> + <layer number="18" name="Vias" color="14" fill="1" visible="yes" active="yes"/> + <layer number="19" name="Unrouted" color="6" fill="1" visible="yes" active="yes"/> + <layer number="20" name="Dimension" color="15" fill="1" visible="yes" active="yes"/> + <layer number="21" name="tPlace" color="7" fill="1" visible="yes" active="yes"/> + <layer number="22" name="bPlace" color="7" fill="1" visible="yes" active="yes"/> + <layer number="23" name="tOrigins" color="15" fill="1" visible="yes" active="yes"/> + <layer number="24" name="bOrigins" color="15" fill="1" visible="yes" active="yes"/> + <layer number="25" name="tNames" color="7" fill="1" visible="yes" active="yes"/> + <layer number="26" name="bNames" color="7" fill="1" visible="yes" active="yes"/> + <layer number="27" name="tValues" color="7" fill="1" visible="no" active="yes"/> + <layer number="28" name="bValues" color="7" fill="1" visible="no" active="yes"/> + <layer number="29" name="tStop" color="2" fill="3" visible="no" active="yes"/> + <layer number="30" name="bStop" color="5" fill="6" visible="no" active="yes"/> + <layer number="31" name="tCream" color="7" fill="4" visible="no" active="yes"/> + <layer number="32" name="bCream" color="7" fill="5" visible="no" active="yes"/> + <layer number="33" name="tFinish" color="6" fill="3" visible="no" active="yes"/> + <layer number="34" name="bFinish" color="6" fill="6" visible="no" active="yes"/> + <layer number="35" name="tGlue" color="7" fill="4" visible="no" active="yes"/> + <layer number="36" name="bGlue" color="7" fill="5" visible="no" active="yes"/> + <layer number="37" name="tTest" color="7" fill="1" visible="no" active="yes"/> + <layer number="38" name="bTest" color="7" fill="1" visible="no" active="yes"/> + <layer number="39" name="tKeepout" color="4" fill="11" visible="no" active="yes"/> + <layer number="40" name="bKeepout" color="1" fill="11" visible="no" active="yes"/> + <layer number="41" name="tRestrict" color="4" fill="10" visible="no" active="yes"/> + <layer number="42" name="bRestrict" color="1" fill="10" visible="no" active="yes"/> + <layer number="43" name="vRestrict" color="2" fill="10" visible="no" active="yes"/> + <layer number="44" name="Drills" color="7" fill="1" visible="no" active="yes"/> + <layer number="45" name="Holes" color="7" fill="1" visible="no" active="yes"/> + <layer number="46" name="Milling" color="3" fill="1" visible="no" active="yes"/> + <layer number="47" name="Measures" color="7" fill="1" visible="no" active="yes"/> + <layer number="48" name="Document" color="7" fill="1" visible="no" active="yes"/> + <layer number="49" name="ReferenceLC" color="13" fill="1" visible="yes" active="yes"/> + <layer number="50" name="ReferenceLS" color="12" fill="1" visible="yes" active="yes"/> + <layer number="51" name="tDocu" color="7" fill="1" visible="yes" active="yes"/> + <layer number="52" name="bDocu" color="7" fill="1" visible="yes" active="yes"/> + + * These layers are used only in eagle schematic. + * They should not be found in board files. + * They are listed for info only. + <layer number="91" name="Nets" color="2" fill="1" visible="no" active="no"/> + <layer number="92" name="Busses" color="1" fill="1" visible="no" active="no"/> + <layer number="93" name="Pins" color="2" fill="1" visible="no" active="no"/> + <layer number="94" name="Symbols" color="4" fill="1" visible="no" active="no"/> + <layer number="95" name="Names" color="7" fill="1" visible="no" active="no"/> + <layer number="96" name="Values" color="7" fill="1" visible="no" active="no"/> + <layer number="97" name="Info" color="7" fill="1" visible="no" active="no"/> + <layer number="98" name="Guide" color="6" fill="1" visible="no" active="no"/> + + * These layers are user layers + <layer number="160" name="???" color="7" fill="1" visible="yes" active="yes"/> + <layer number="161" name="???" color="7" fill="1" visible="yes" active="yes"/> + + </layers> + + */ + + int kiLayer; + + // eagle copper layer: + if( aEagleLayer >= 1 && aEagleLayer < int( DIM( m_cu_map ) ) ) + { + kiLayer = m_cu_map[aEagleLayer]; + } + + else + { + // translate non-copper eagle layer to pcbnew layer + switch( aEagleLayer ) + { + case 20: kiLayer = Edge_Cuts; break; // eagle says "Dimension" layer, but it's for board perimeter + case 21: kiLayer = F_SilkS; break; + case 22: kiLayer = B_SilkS; break; + case 25: kiLayer = F_SilkS; break; + case 26: kiLayer = B_SilkS; break; + case 27: kiLayer = F_SilkS; break; + case 28: kiLayer = B_SilkS; break; + case 29: kiLayer = F_Mask; break; + case 30: kiLayer = B_Mask; break; + case 31: kiLayer = F_Paste; break; + case 32: kiLayer = B_Paste; break; + case 33: kiLayer = F_Mask; break; + case 34: kiLayer = B_Mask; break; + case 35: kiLayer = F_Adhes; break; + case 36: kiLayer = B_Adhes; break; + case 49: kiLayer = Cmts_User; break; + case 50: kiLayer = Cmts_User; break; + + // Packages show the future chip pins on SMD parts using layer 51. + // This is an area slightly smaller than the PAD/SMD copper area. + // Carry those visual aids into the MODULE on the drawing layer, not silkscreen. + case 51: kiLayer = Dwgs_User; break; + case 52: kiLayer = Dwgs_User; break; + + // thes layers are defined as user layers. put them on ECO layers + case 160: kiLayer = Eco1_User; break; + case 161: kiLayer = Eco2_User; break; + default: + // some layers do not map to KiCad + // DBG( printf( "unsupported eagle layer: %d\n", aEagleLayer );) + kiLayer = UNDEFINED_LAYER; break; + } + } + + return LAYER_ID( kiLayer ); +} + + +void EAGLE_PLUGIN::centerBoard() +{ + if( m_props ) + { + UTF8 page_width; + UTF8 page_height; + + if( m_props->Value( "page_width", &page_width ) && + m_props->Value( "page_height", &page_height ) ) + { + EDA_RECT bbbox = m_board->ComputeBoundingBox( true ); + + int w = atoi( page_width.c_str() ); + int h = atoi( page_height.c_str() ); + + int desired_x = ( w - bbbox.GetWidth() ) / 2; + int desired_y = ( h - bbbox.GetHeight() ) / 2; + + DBG(printf( "bbox.width:%d bbox.height:%d w:%d h:%d desired_x:%d desired_y:%d\n", + bbbox.GetWidth(), bbbox.GetHeight(), w, h, desired_x, desired_y );) + + m_board->Move( wxPoint( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() ) ); + } + } +} + + +wxDateTime EAGLE_PLUGIN::getModificationTime( const wxString& aPath ) +{ + wxFileName fn( aPath ); + + // Do not call wxFileName::GetModificationTime() on a non-existent file, because + // if it fails, wx's implementation calls the crap wxLogSysError() which + // eventually infects our UI with an unwanted popup window, so don't let it fail. + if( !fn.IsFileReadable() ) + { + wxString msg = wxString::Format( + _( "File '%s' is not readable." ), + GetChars( aPath ) ); + + THROW_IO_ERROR( msg ); + } + + /* + // update the writable flag while we have a wxFileName, in a network this + // is possibly quite dynamic anyway. + m_writable = fn.IsFileWritable(); + */ + + wxDateTime modTime = fn.GetModificationTime(); + + if( !modTime.IsValid() ) + modTime.Now(); + + return modTime; +} + + +void EAGLE_PLUGIN::cacheLib( const wxString& aLibPath ) +{ + try + { + wxDateTime modtime = getModificationTime( aLibPath ); + + // Fixes assertions in wxWidgets debug builds for the wxDateTime object. Refresh the + // cache if either of the wxDateTime objects are invalid or the last file modification + // time differs from the current file modification time. + bool load = !m_mod_time.IsValid() || !modtime.IsValid() || + m_mod_time != modtime; + + if( aLibPath != m_lib_path || load ) + { + PTREE doc; + LOCALE_IO toggle; // toggles on, then off, the C locale. + + m_templates.clear(); + + // Set this before completion of loading, since we rely on it for + // text of an exception. Delay setting m_mod_time until after successful load + // however. + m_lib_path = aLibPath; + + // 8 bit "filename" should be encoded according to disk filename encoding, + // (maybe this is current locale, maybe not, its a filesystem issue), + // and is not necessarily utf8. + string filename = (const char*) aLibPath.char_str( wxConvFile ); + + read_xml( filename, doc, xml_parser::no_comments ); + + // clear the cu map and then rebuild it. + clear_cu_map(); + + m_xpath->push( "eagle.drawing.layers" ); + CPTREE& layers = doc.get_child( "eagle.drawing.layers" ); + loadLayerDefs( layers ); + m_xpath->pop(); + + m_xpath->push( "eagle.drawing.library" ); + CPTREE& library = doc.get_child( "eagle.drawing.library" ); + loadLibrary( library, NULL ); + m_xpath->pop(); + + m_mod_time = modtime; + } + } + catch( file_parser_error fpe ) + { + // for xml_parser_error, what() has the line number in it, + // but no byte offset. That should be an adequate error message. + THROW_IO_ERROR( fpe.what() ); + } + + // Class ptree_error is a base class for xml_parser_error & file_parser_error, + // so one catch should be OK for all errors. + catch( ptree_error pte ) + { + string errmsg = pte.what(); + + errmsg += " @\n"; + errmsg += m_xpath->Contents(); + + THROW_IO_ERROR( errmsg ); + } +} + + +wxArrayString EAGLE_PLUGIN::FootprintEnumerate( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ + init( aProperties ); + + cacheLib( aLibraryPath ); + + wxArrayString ret; + + for( MODULE_CITER it = m_templates.begin(); it != m_templates.end(); ++it ) + ret.Add( FROM_UTF8( it->first.c_str() ) ); + + return ret; +} + + +MODULE* EAGLE_PLUGIN::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, + const PROPERTIES* aProperties ) +{ + init( aProperties ); + + cacheLib( aLibraryPath ); + + string key = TO_UTF8( aFootprintName ); + + MODULE_CITER mi = m_templates.find( key ); + + if( mi == m_templates.end() ) + return NULL; + + // copy constructor to clone the template + MODULE* ret = new MODULE( *mi->second ); + + return ret; +} + + +void EAGLE_PLUGIN::FootprintLibOptions( PROPERTIES* aListToAppendTo ) const +{ + PLUGIN::FootprintLibOptions( aListToAppendTo ); + + /* + (*aListToAppendTo)["ignore_duplicates"] = UTF8( _( + "Ignore duplicately named footprints within the same Eagle library. " + "Only the first similarly named footprint will be loaded." + )); + */ +} + + +/* +void EAGLE_PLUGIN::Save( const wxString& aFileName, BOARD* aBoard, const PROPERTIES* aProperties ) +{ + // Eagle lovers apply here. +} + + +void EAGLE_PLUGIN::FootprintSave( const wxString& aLibraryPath, const MODULE* aFootprint, const PROPERTIES* aProperties ) +{ +} + + +void EAGLE_PLUGIN::FootprintDelete( const wxString& aLibraryPath, const wxString& aFootprintName ) +{ +} + + +void EAGLE_PLUGIN::FootprintLibCreate( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ +} + + +bool EAGLE_PLUGIN::FootprintLibDelete( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ +} + + +bool EAGLE_PLUGIN::IsFootprintLibWritable( const wxString& aLibraryPath ) +{ + return true; +} + +*/ |