kicad-developers team mailing list archive
-
kicad-developers team
-
Mailing list archive
-
Message #40034
[PATCH] [RFC] Exporter for Mentor Hyperlynx
-
To:
Kicad Developers <kicad-developers@xxxxxxxxxxxxxxxxxxx>
-
From:
Tomasz Wlostowski <tomasz.wlostowski@xxxxxxx>
-
Date:
Fri, 5 Apr 2019 02:16:20 +0200
-
Authentication-results:
spf=pass (sender IP is 188.184.36.48) smtp.mailfrom=cern.ch; lists.launchpad.net; dkim=none (message not signed) header.d=none;lists.launchpad.net; dmarc=bestguesspass action=none header.from=cern.ch;
-
Autocrypt:
addr=tomasz.wlostowski@xxxxxxx; prefer-encrypt=mutual; keydata= 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 DhAcLoHO+yCvkKJabrfOZoa2
-
Openpgp:
preference=signencrypt
-
User-agent:
Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.6.1
Hi,
We needed to do some signal/power integrity simulations on one of our
Kicad designs and in order to do that, we needed to convert a Kicad PCB
to Hyperlynx format. Luckily, the format is simple, all text and well
documented in [1], so here comes a patch that adds a Hyperlynx exporter.
Notes:
- since Kicad doesn't have a concept of board stackup (permittivities,
loss tangent, dielectric types, etc.), the exporter writes a dummy
stackup. Edit it to match the PCB spec in Hyperlynx.
- no support for offset pad holes, slotted pad holes,
trapezoid/polygonal pads (it seems HL format doesn't support such
features or I need to figure out how to emulate them).
- no support for thermal pads (yet)
- no error reporting.
Looking forward to your feedback & wish you happy testing,
Tom
[1] http://www.ibis.org/birds/bird33.txt
>From e966c63a6f00959e359be36bfc0b8206e01ed4bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomasz=20W=C5=82ostowski?= <tomasz.wlostowski@xxxxxxx>
Date: Thu, 4 Apr 2019 18:07:12 +0200
Subject: [PATCH] pcbnew: Initial support for Mentor Hyperlynx export
---
pcbnew/CMakeLists.txt | 1 +
pcbnew/dialogs/dialog_export_idf.cpp | 1 +
pcbnew/exporters/export_hyperlynx.cpp | 547 ++++++++++++++++++
pcbnew/menubar_pcb_editor.cpp | 5 +
pcbnew/pcb_edit_frame.cpp | 29 +
pcbnew/pcb_edit_frame.h | 7 +
pcbnew/pcbnew_id.h | 1 +
qa/CMakeLists.txt | 1 +
qa/hyperlynx_export/CMakeLists.txt | 64 ++
qa/hyperlynx_export/test_hyperlynx_export.cpp | 61 ++
10 files changed, 717 insertions(+)
create mode 100644 pcbnew/exporters/export_hyperlynx.cpp
create mode 100644 qa/hyperlynx_export/CMakeLists.txt
create mode 100644 qa/hyperlynx_export/test_hyperlynx_export.cpp
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 3bbdd8723..9f0f5eac1 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -193,6 +193,7 @@ set( PCBNEW_IMPORT_GFX
)
set( PCBNEW_EXPORTERS
+ exporters/export_hyperlynx.cpp
exporters/export_d356.cpp
exporters/export_footprint_associations.cpp
exporters/export_gencad.cpp
diff --git a/pcbnew/dialogs/dialog_export_idf.cpp b/pcbnew/dialogs/dialog_export_idf.cpp
index e9e16bfbc..882224b7c 100644
--- a/pcbnew/dialogs/dialog_export_idf.cpp
+++ b/pcbnew/dialogs/dialog_export_idf.cpp
@@ -229,3 +229,4 @@ void PCB_EDIT_FRAME::OnExportIDF3( wxCommandEvent& event )
return;
}
}
+
diff --git a/pcbnew/exporters/export_hyperlynx.cpp b/pcbnew/exporters/export_hyperlynx.cpp
new file mode 100644
index 000000000..5f5c678dd
--- /dev/null
+++ b/pcbnew/exporters/export_hyperlynx.cpp
@@ -0,0 +1,547 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2019 CERN
+ *
+ * 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
+ */
+
+#include <kiface_i.h>
+#include <pcb_edit_frame.h>
+#include <pcbnew.h>
+
+#include <class_board.h>
+#include <class_board_item.h>
+#include <class_drawsegment.h>
+#include <class_edge_mod.h>
+#include <class_module.h>
+#include <class_track.h>
+#include <class_zone.h>
+#include <cstdio>
+#include <vector>
+
+static double iu2hyp( double iu )
+{
+ return iu / 1e9 / 0.0254;
+}
+
+class PAD_STACK
+{
+public:
+ PAD_STACK( BOARD* aBoard, const D_PAD* aPad );
+ PAD_STACK( BOARD* aBoard, const VIA* aVia );
+ ~PAD_STACK(){};
+
+
+ bool isThrough() const
+ {
+ return m_type == PAD_ATTRIB_HOLE_NOT_PLATED || m_type == PAD_ATTRIB_STANDARD;
+ }
+
+ bool operator==( const PAD_STACK& other ) const
+ {
+ if( m_shape != other.m_shape )
+ return false;
+
+ if( m_type != other.m_type )
+ return false;
+
+ if( isThrough() && other.isThrough() && m_drill != other.m_drill )
+ return false;
+
+ if( m_sx != other.m_sx )
+ return false;
+
+ if( m_sy != other.m_sy )
+ return false;
+
+ if( m_layers != other.m_layers )
+ return false;
+
+ if( m_angle != other.m_angle )
+ return false;
+
+ return true;
+ }
+
+ bool isSMD() const
+ {
+ return m_type == PAD_ATTRIB_SMD;
+ }
+
+ PCB_LAYER_ID getSMDLayer() const
+ {
+ for( auto l : LSET::AllCuMask().Seq() )
+ if( m_layers[l] )
+ return l;
+ return F_Cu;
+ }
+
+ void SetId( int id )
+ {
+ m_id = id;
+ }
+
+ int GetId() const
+ {
+ return m_id;
+ }
+
+ void FormatPadShape(
+ int indentLevel, std::shared_ptr<FILE_OUTPUTFORMATTER> fmt, const wxString& layerName )
+ {
+ int shapeId;
+
+ switch( m_shape )
+ {
+ case PAD_SHAPE_CIRCLE:
+ case PAD_SHAPE_OVAL: shapeId = 0; break;
+ case PAD_SHAPE_ROUNDRECT: shapeId = 2; break;
+ case PAD_SHAPE_RECT: shapeId = 1; break;
+ default: wxLogDebug( wxT( "Unsupported pad shape %d\n" ), m_shape ); return;
+ }
+
+ fmt->Print( indentLevel, "(\"%s\", %d, %.9f, %.9f, %.1f, M)\n",
+ (const char*) layerName.c_str(), shapeId, iu2hyp( m_sx ), iu2hyp( m_sy ), m_angle );
+ }
+
+ bool isEmpty() const
+ {
+ LSET layerMask = LSET::AllCuMask() & m_board->GetEnabledLayers();
+ LSET outLayers = m_layers & layerMask;
+
+ return outLayers.none();
+ }
+
+ void Format( int indentLevel, std::shared_ptr<FILE_OUTPUTFORMATTER> fmt )
+ {
+ LSET layerMask = LSET::AllCuMask() & m_board->GetEnabledLayers();
+ LSET outLayers = m_layers & layerMask;
+
+ if( outLayers.none() )
+ return;
+
+ fmt->Print( 0, "{PADSTACK=%d, %.9f\n", m_id, iu2hyp( m_drill ) );
+
+ if( outLayers == layerMask )
+ {
+ FormatPadShape( indentLevel + 1, fmt, wxT( "MDEF" ) );
+ }
+ else
+ {
+ for( auto l : outLayers.Seq() )
+ {
+ FormatPadShape( indentLevel + 1, fmt, m_board->GetLayerName( l ) );
+ }
+ }
+
+
+ fmt->Print( 0, "}\n\n" );
+ }
+
+
+private:
+ BOARD* m_board;
+ int m_id;
+ int m_drill;
+ PAD_SHAPE_T m_shape;
+ int m_sx, m_sy;
+ double m_angle;
+ LSET m_layers;
+ PAD_ATTR_T m_type;
+};
+
+class HYPERLYNX_EXPORTER
+{
+public:
+ HYPERLYNX_EXPORTER(){};
+ ~HYPERLYNX_EXPORTER(){};
+
+ void SetOutputFilename( const wxFileName& aPath )
+ {
+ m_outputFilePath = aPath;
+ }
+
+ void SetBoard( BOARD* aBoard )
+ {
+ m_board = aBoard;
+ }
+
+ bool Run();
+
+ PAD_STACK* addPadStack( PAD_STACK stack )
+ {
+ for( auto p : m_padStacks )
+ {
+ if( *p == stack )
+ return p;
+ }
+
+ stack.SetId( m_padStacks.size() );
+ m_padStacks.push_back( new PAD_STACK( stack ) );
+
+ return m_padStacks.back();
+ }
+
+
+private:
+ bool generateHeaders();
+ bool writeBoardInfo();
+ bool writeStackupInfo();
+ bool writeDevices();
+ bool writePadStacks();
+ bool writeNets();
+ bool writeNetObjects( const std::vector<BOARD_ITEM*>& aObjects );
+ const std::vector<BOARD_ITEM*> collectNetObjects( int netcode );
+
+ std::vector<PAD_STACK*> m_padStacks;
+ std::map<BOARD_ITEM*, PAD_STACK*> m_padMap;
+
+
+ BOARD* m_board;
+ wxFileName m_outputFilePath;
+ std::shared_ptr<FILE_OUTPUTFORMATTER> m_out;
+ int m_polyId;
+};
+
+
+PAD_STACK::PAD_STACK( BOARD* aBoard, const D_PAD* aPad )
+{
+ m_board = aBoard;
+ m_sx = aPad->GetSize().x;
+ m_sy = aPad->GetSize().y;
+ //printf("sx %d sy %d\n", m_sx, m_sy );
+ m_angle = 180.0 - ( aPad->GetOrientation() / 10.0 );
+ if( m_angle < 0.0 )
+ m_angle += 360.0;
+ m_layers = aPad->GetLayerSet();
+ m_drill = aPad->GetDrillSize().x;
+ m_shape = aPad->GetShape();
+ m_type = PAD_ATTRIB_STANDARD;
+}
+
+PAD_STACK::PAD_STACK( BOARD* aBoard, const VIA* aVia )
+{
+ m_board = aBoard;
+ m_sx = m_sy = aVia->GetWidth();
+ //printf("sx %d sy %d\n", m_sx, m_sy );
+ m_angle = 0;
+ m_layers = LSET::AllCuMask();
+ m_drill = aVia->GetDrillValue();
+ m_shape = PAD_SHAPE_CIRCLE;
+ m_type = PAD_ATTRIB_STANDARD;
+}
+
+bool HYPERLYNX_EXPORTER::generateHeaders()
+{
+ m_out->Print( 0, "{VERSION=2.14}\n" );
+ m_out->Print( 0, "{UNITS=ENGLISH LENGTH}\n\n" );
+ return true;
+}
+
+
+bool HYPERLYNX_EXPORTER::writeBoardInfo()
+{
+ SHAPE_POLY_SET outlines;
+ wxString errText;
+ wxPoint errLoc;
+
+ m_out->Print( 0, "{BOARD \"%s\"\n", (const char*) m_board->GetFileName().c_str() );
+
+ if( !m_board->GetBoardPolygonOutlines( outlines, &errText, &errLoc ) )
+ {
+ return false;
+ }
+
+ for( int o = 0; o < outlines.OutlineCount(); o++ )
+ {
+ const auto& outl = outlines.COutline( o );
+ for( int i = 0; i < outl.SegmentCount(); i++ )
+ {
+ const auto& s = outl.CSegment( i );
+ m_out->Print( 1, "(PERIMETER_SEGMENT X1=%.9f Y1=%.9f X2=%.9f Y2=%.9f)\n",
+ iu2hyp( s.A.x ), iu2hyp( s.A.y ), iu2hyp( s.B.x ), iu2hyp( s.B.y ) );
+ }
+ }
+ m_out->Print( 0, "}\n\n" );
+
+ return true;
+}
+
+bool HYPERLYNX_EXPORTER::writeStackupInfo()
+{
+ auto layers = m_board->GetDesignSettings().GetEnabledLayers().CuStack();
+
+ m_out->Print( 0, "{STACKUP\n" );
+
+ for( auto l : layers )
+ {
+ const auto name = m_board->GetLayerName( l );
+ m_out->Print( 1, "(SIGNAL T=0.002284 P=0.000000 C=1.724e-8 L=\"%s\" M=COPPER)\n",
+ (const char*) name.c_str() );
+ if( l != B_Cu )
+ {
+ m_out->Print( 1, "(DIELECTRIC T=0.007087 C=3.660000 L=\"DE_%s\" M=FR4)\n",
+ (const char*) name.c_str() );
+ }
+ }
+ m_out->Print( 0, "}\n\n" );
+
+ return true;
+}
+
+bool HYPERLYNX_EXPORTER::writeDevices()
+{
+ m_out->Print( 0, "{DEVICES\n" );
+
+ for( auto mod : m_board->Modules() )
+ {
+ wxString ref = mod->GetReference();
+ auto layerName = m_board->GetLayerName( mod->GetLayer() );
+
+ if( ref.IsEmpty() )
+ ref = wxT( "EMPTY" );
+
+ m_out->Print( 1, "(? REF=\"%s\" L=\"%s\")\n", (const char*) ref.c_str(),
+ (const char*) layerName.c_str() );
+ }
+ m_out->Print( 0, "}\n\n" );
+
+ return true;
+}
+
+bool HYPERLYNX_EXPORTER::writePadStacks()
+{
+ for( auto mod : m_board->Modules() )
+ {
+ for( auto pad : mod->Pads() )
+ {
+ auto ps = addPadStack( PAD_STACK( m_board, pad ) );
+ m_padMap[pad] = ps;
+ }
+ }
+
+ for( auto trk : m_board->Tracks() )
+ {
+ if( VIA* via = dyn_cast<VIA*>( trk ) )
+ {
+ auto ps = addPadStack( PAD_STACK( m_board, via ) );
+ m_padMap[via] = ps;
+ }
+ }
+
+ for( auto pstack : m_padStacks )
+ pstack->Format( 0, m_out );
+
+ return true;
+}
+
+bool HYPERLYNX_EXPORTER::writeNetObjects( const std::vector<BOARD_ITEM*>& aObjects )
+{
+
+ for( auto item : aObjects )
+ {
+ if( D_PAD* pad = dyn_cast<D_PAD*>( item ) )
+ {
+ auto pstackIter = m_padMap.find( pad );
+ if( pstackIter != m_padMap.end() )
+ {
+ wxString ref = pad->GetParent()->GetReference();
+ if( ref.IsEmpty() )
+ ref = wxT( "EMPTY" );
+
+ auto padName = pad->GetName();
+
+ if( padName.IsEmpty() )
+ padName = wxT( "1" );
+
+
+ m_out->Print( 1, "(PIN X=%.10f Y=%.10f R=\"%s.%s\" P=%d)\n",
+ iu2hyp( pad->GetPosition().x ), iu2hyp( pad->GetPosition().y ),
+ (const char*) ref.c_str(), (const char*) padName.c_str(),
+ pstackIter->second->GetId() );
+ }
+ }
+ else if( VIA* via = dyn_cast<VIA*>( item ) )
+ {
+ auto pstackIter = m_padMap.find( via );
+ if( pstackIter != m_padMap.end() )
+ {
+ m_out->Print( 1, "(VIA X=%.10f Y=%.10f P=%d)\n", iu2hyp( via->GetPosition().x ),
+ iu2hyp( via->GetPosition().y ), pstackIter->second->GetId() );
+ }
+ }
+ else if( TRACK* track = dyn_cast<TRACK*>( item ) )
+ {
+ const auto layerName = m_board->GetLayerName( track->GetLayer() );
+ m_out->Print( 1, "(SEG X1=%.10f Y1=%.10f X2=%.10f Y2=%.10f W=%.10f L=\"%s\")\n",
+ iu2hyp( track->GetStart().x ), iu2hyp( track->GetStart().y ),
+ iu2hyp( track->GetEnd().x ), iu2hyp( track->GetEnd().y ),
+ iu2hyp( track->GetWidth() ), (const char*) layerName.c_str() );
+ }
+ else if( ZONE_CONTAINER* zone = dyn_cast<ZONE_CONTAINER*>( item ) )
+ {
+ const auto layerName = m_board->GetLayerName( zone->GetLayer() );
+ SHAPE_POLY_SET filledShape = zone->GetFilledPolysList();
+
+ filledShape.Simplify( SHAPE_POLY_SET::PM_FAST );
+
+ for( int i = 0; i < filledShape.OutlineCount(); i++ )
+ {
+ const auto& outl = filledShape.COutline( i );
+
+ auto p0 = outl.CPoint( 0 );
+ m_out->Print( 1, "{POLYGON T=POUR L=\"%s\" ID=%d X=%.10f Y=%.10f\n",
+ (const char*) layerName.c_str(), m_polyId, iu2hyp( p0.x ), iu2hyp( p0.y ) );
+
+ for( int v = 0; v < outl.PointCount(); v++ )
+ {
+ m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( outl.CPoint( v ).x ),
+ iu2hyp( outl.CPoint( v ).y ) );
+ }
+
+ m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( p0.x ), iu2hyp( p0.y ) );
+
+ m_out->Print( 1, "}\n" );
+
+ for( int h = 0; h < filledShape.HoleCount( i ); h++ )
+ {
+ const auto& holeShape = filledShape.CHole( i, h );
+ auto ph0 = holeShape.CPoint( 0 );
+
+ m_out->Print( 1, "{POLYVOID ID=%d X=%.10f Y=%.10f\n", m_polyId, iu2hyp( ph0.x ),
+ iu2hyp( ph0.y ) );
+
+ for( int v = 0; v < holeShape.PointCount(); v++ )
+ {
+ m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n",
+ iu2hyp( holeShape.CPoint( v ).x ),
+ iu2hyp( holeShape.CPoint( v ).y ) );
+ }
+ m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( ph0.x ), iu2hyp( ph0.y ) );
+
+ m_out->Print( 1, "}\n" );
+ }
+
+ m_polyId++;
+ }
+ }
+ }
+
+ return true;
+}
+
+const std::vector<BOARD_ITEM*> HYPERLYNX_EXPORTER::collectNetObjects( int netcode )
+{
+ std::vector<BOARD_ITEM*> rv;
+
+ auto check = [&]( BOARD_CONNECTED_ITEM* item ) -> bool {
+ if( ( item->GetLayerSet() & LSET::AllCuMask() ).none() )
+ return false;
+ if( item->GetNetCode() == netcode || ( netcode < 0 && item->GetNetCode() <= 0 ) )
+ return true;
+ return false;
+ };
+
+ for( auto mod : m_board->Modules() )
+ {
+ for( auto pad : mod->Pads() )
+ {
+ if( check( pad ) )
+ rv.push_back( pad );
+ }
+ }
+
+ for( auto item : m_board->Tracks() )
+ if( check( item ) )
+ rv.push_back( item );
+
+ for( int i = 0; i < m_board->GetAreaCount(); i++ )
+ {
+ auto zone = m_board->GetArea( i );
+ if( check( zone ) )
+ rv.push_back( zone );
+ }
+ return rv;
+}
+
+bool HYPERLYNX_EXPORTER::writeNets()
+{
+ m_polyId = 1;
+
+ for( const auto netInfo : m_board->GetNetInfo() )
+ {
+ int netcode = netInfo->GetNet();
+
+ //printf( " netCode %d '%s' \n", netcode, (const char*) netInfo->GetNetname().c_str() );
+
+ bool isNullNet = netInfo->GetNet() <= 0 || netInfo->GetNetname().IsEmpty();
+ if( isNullNet )
+ continue;
+
+ auto netObjects = collectNetObjects( netcode );
+ if( netObjects.size() )
+ {
+
+ m_out->Print( 0, "{NET \"%s\"\n", (const char*) netInfo->GetNetname().c_str() );
+ writeNetObjects( netObjects );
+ m_out->Print( 0, "}\n\n" );
+ }
+ }
+
+ auto nullNetObjects = collectNetObjects( -1 );
+
+ //printf( "Null net objects: %d\n", nullNetObjects.size() );
+
+ int idx = 0;
+
+ for( auto item : nullNetObjects )
+ {
+ m_out->Print( 0, "{NET \"EmptyNet%d\"\n", idx );
+ writeNetObjects( { item } );
+ m_out->Print( 0, "}\n\n" );
+ idx++;
+ }
+
+ return true;
+}
+
+bool HYPERLYNX_EXPORTER::Run()
+{
+ LOCALE_IO toggle; // toggles on, then off, the C locale.
+
+ m_out.reset( new FILE_OUTPUTFORMATTER( m_outputFilePath.GetFullPath() ) );
+
+ generateHeaders();
+ writeBoardInfo();
+ writeStackupInfo();
+ writeDevices();
+ writePadStacks();
+ writeNets();
+
+ return true;
+}
+
+
+bool ExportBoardToHyperlynx( BOARD* aBoard, const wxFileName& aPath )
+{
+ HYPERLYNX_EXPORTER exporter;
+ exporter.SetBoard( aBoard );
+ exporter.SetOutputFilename( aPath );
+ return exporter.Run();
+}
diff --git a/pcbnew/menubar_pcb_editor.cpp b/pcbnew/menubar_pcb_editor.cpp
index 422456245..3e0711920 100644
--- a/pcbnew/menubar_pcb_editor.cpp
+++ b/pcbnew/menubar_pcb_editor.cpp
@@ -970,4 +970,9 @@ void prepareExportMenu( wxMenu* aParentMenu )
_( "&Footprint Association (.cmp) File..." ),
_( "Export footprint association file (*.cmp) for schematic back annotation" ),
KiBitmap( create_cmp_file_xpm ) );
+
+ AddMenuItem( aParentMenu, ID_GEN_EXPORT_FILE_HYPERLYNX,
+ _( "&Hyperlynx..." ), _( "Hyperlynx export" ),
+ KiBitmap( export_step_xpm ) );
+
}
diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp
index b4db36fb5..369e63a75 100644
--- a/pcbnew/pcb_edit_frame.cpp
+++ b/pcbnew/pcb_edit_frame.cpp
@@ -128,6 +128,7 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME )
EVT_MENU( ID_GEN_EXPORT_FILE_VRML, PCB_EDIT_FRAME::OnExportVRML )
EVT_MENU( ID_GEN_EXPORT_FILE_IDF3, PCB_EDIT_FRAME::OnExportIDF3 )
EVT_MENU( ID_GEN_EXPORT_FILE_STEP, PCB_EDIT_FRAME::OnExportSTEP )
+ EVT_MENU( ID_GEN_EXPORT_FILE_HYPERLYNX, PCB_EDIT_FRAME::OnExportHyperlynx )
EVT_MENU( ID_GEN_IMPORT_SPECCTRA_SESSION,PCB_EDIT_FRAME::ImportSpecctraSession )
EVT_MENU( ID_GEN_IMPORT_SPECCTRA_DESIGN, PCB_EDIT_FRAME::ImportSpecctraDesign )
@@ -1369,3 +1370,31 @@ void PCB_EDIT_FRAME::LockModule( MODULE* aModule, bool aLocked )
}
}
}
+
+bool ExportBoardToHyperlynx( BOARD* aBoard, const wxFileName& aPath );
+
+void PCB_EDIT_FRAME::OnExportHyperlynx( wxCommandEvent& event )
+{
+ wxString wildcard = wxT("*.hyp");
+ wxFileName fn = GetBoard()->GetFileName();
+
+ fn.SetExt( wxT("hyp") );
+
+ wxFileDialog dlg( this,
+ _( "Export Hyperlynx Layout" ),
+ fn.GetPath(),
+ fn.GetFullName(),
+ wildcard,
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if( dlg.ShowModal() != wxID_OK )
+ return;
+
+ fn = dlg.GetPath();
+
+ // always enforce filename extension, user may not have entered it.
+ fn.SetExt( wxT("hyp") );
+
+ ExportBoardToHyperlynx( GetBoard(), fn );
+}
diff --git a/pcbnew/pcb_edit_frame.h b/pcbnew/pcb_edit_frame.h
index a916893b2..cc6f5f89a 100644
--- a/pcbnew/pcb_edit_frame.h
+++ b/pcbnew/pcb_edit_frame.h
@@ -1044,6 +1044,13 @@ public:
*/
void OnExportIDF3( wxCommandEvent& event );
+ /**
+ * Function OnExportHyperlynx
+ * will export the current BOARD to a Hyperlynx HYP file.
+ */
+ void OnExportHyperlynx( wxCommandEvent& event );
+
+
/**
* Function Export_IDF3
* Creates an IDF3 compliant BOARD (*.emn) and LIBRARY (*.emp) file.
diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h
index dfe402af5..0d18e2e00 100644
--- a/pcbnew/pcbnew_id.h
+++ b/pcbnew/pcbnew_id.h
@@ -273,6 +273,7 @@ enum pcbnew_ids
ID_GEN_EXPORT_FILE_IDF3,
ID_GEN_EXPORT_FILE_VRML,
ID_GEN_EXPORT_FILE_STEP,
+ ID_GEN_EXPORT_FILE_HYPERLYNX,
ID_GEN_EXPORT_SPECCTRA,
ID_GEN_EXPORT_FILE_GENCADFORMAT,
ID_GEN_EXPORT_FILE_MODULE_REPORT,
diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt
index 5822170df..eab06c313 100644
--- a/qa/CMakeLists.txt
+++ b/qa/CMakeLists.txt
@@ -19,6 +19,7 @@ add_subdirectory( unit_test_utils )
# Unit tests
add_subdirectory( common )
+add_subdirectory( hyperlynx_export )
add_subdirectory( pcbnew )
add_subdirectory( eeschema )
diff --git a/qa/hyperlynx_export/CMakeLists.txt b/qa/hyperlynx_export/CMakeLists.txt
new file mode 100644
index 000000000..0e279b370
--- /dev/null
+++ b/qa/hyperlynx_export/CMakeLists.txt
@@ -0,0 +1,64 @@
+# This program source code file is part of KiCad, a free EDA CAD application.
+#
+# Copyright (C) 2018 KiCad Developers, see CHANGELOG.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
+
+if( BUILD_GITHUB_PLUGIN )
+ set( GITHUB_PLUGIN_LIBRARIES github_plugin )
+endif()
+
+include_directories( ../../include
+ ../../common
+ ../../pcbnew
+ ../.. )
+
+add_executable( test_hyperlynx_export
+ # stuff from common which is needed...why?
+ ../../common/colors.cpp
+ ../../common/observable.cpp
+
+ # The main test entry points
+ test_hyperlynx_export.cpp
+
+ # Older CMakes cannot link OBJECT libraries
+ # https://cmake.org/pipermail/cmake/2013-November/056263.html
+ $<TARGET_OBJECTS:pcbnew_kiface_objects>
+)
+
+target_link_libraries( test_hyperlynx_export
+ 3d-viewer
+ connectivity
+ pcbcommon
+ pnsrouter
+ pcad2kicadpcb
+ legacy_wx
+ gal
+ common
+ qa_utils
+ lib_dxf
+ idf3
+ unit_test_utils
+ ${wxWidgets_LIBRARIES}
+ ${GITHUB_PLUGIN_LIBRARIES}
+ ${GDI_PLUS_LIBRARIES}
+ ${PYTHON_LIBRARIES}
+ ${Boost_LIBRARIES} # must follow GITHUB
+ ${PCBNEW_EXTRA_LIBS} # -lrt must follow Boost
+)
+
diff --git a/qa/hyperlynx_export/test_hyperlynx_export.cpp b/qa/hyperlynx_export/test_hyperlynx_export.cpp
new file mode 100644
index 000000000..479c25446
--- /dev/null
+++ b/qa/hyperlynx_export/test_hyperlynx_export.cpp
@@ -0,0 +1,61 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018 KiCad Developers, see CHANGELOG.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
+ */
+
+/**
+ * Main file for the pcbnew tests to be compiled
+ */
+
+#include <wx/wx.h>
+#include <class_board.h>
+#include <io_mgr.h>
+#include <kicad_plugin.h>
+
+bool ExportBoardToHyperlynx( BOARD* aBoard, const wxFileName& aPath );
+
+BOARD* loadBoard( const std::string& filename )
+{
+ PLUGIN::RELEASER pi( new PCB_IO );
+ BOARD* brd = nullptr;
+
+ try
+ {
+ brd = pi->Load( wxString( filename.c_str() ), NULL, NULL );
+ }
+ catch( const IO_ERROR& ioe )
+ {
+ wxString msg = wxString::Format( _( "Error loading board.\n%s" ),
+ ioe.Problem() );
+
+ printf( "%s\n", (const char*) msg.mb_str() );
+ return nullptr;
+ }
+ return brd;
+
+}
+
+int main( int argc, char* argv[] )
+{
+ auto brd = loadBoard("test.kicad_pcb");
+ ExportBoardToHyperlynx( brd, wxString( "test.hyp" ) );
+ return 0;
+}
\ No newline at end of file
--
2.17.1
Follow ups