Refactor step export to use our normal board processing routines

This commit is contained in:
Marek Roszko 2022-10-08 18:00:05 -04:00
parent e109b5883f
commit 3dd2ae762d
32 changed files with 741 additions and 4186 deletions

View File

@ -32,7 +32,8 @@ public:
m_overwrite( false ),
m_useGridOrigin( false ),
m_useDrillOrigin( false ),
m_includeVirtual( false ),
m_boardOnly( false ),
m_includeExcludedBom( false ),
m_substModels( false ),
m_filename(),
m_outputFile(),
@ -45,7 +46,8 @@ public:
bool m_overwrite;
bool m_useGridOrigin;
bool m_useDrillOrigin;
bool m_includeVirtual;
bool m_boardOnly;
bool m_includeExcludedBom;
bool m_substModels;
wxString m_filename;
wxString m_outputFile;

View File

@ -35,6 +35,7 @@
#define ARG_INPUT "input"
#define ARG_MIN_DISTANCE "--min-distance"
#define ARG_USER_ORIGIN "--user-origin"
#define ARG_BOARD_ONLY "--board-only"
#define REGEX_QUANTITY "([\\s]*[+-]?[\\d]*[.]?[\\d]*)"
#define REGEX_DELIMITER "(?:[\\s]*x)"
@ -67,6 +68,11 @@ CLI::EXPORT_PCB_STEP_COMMAND::EXPORT_PCB_STEP_COMMAND() : COMMAND( "step" )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_BOARD_ONLY )
.help( "only generate a board with no components" )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_MIN_DISTANCE )
.default_value( std::string() )
.help( "Minimum distance between points to treat them as separate ones (default 0.01mm)" );
@ -88,11 +94,12 @@ int CLI::EXPORT_PCB_STEP_COMMAND::Perform( KIWAY& aKiway )
step->m_useDrillOrigin = m_argParser.get<bool>( ARG_DRILL_ORIGIN );
step->m_useGridOrigin = m_argParser.get<bool>( ARG_GRID_ORIGIN );
step->m_includeVirtual = !m_argParser.get<bool>( ARG_NO_VIRTUAL );
step->m_includeExcludedBom = !m_argParser.get<bool>( ARG_NO_VIRTUAL );
step->m_substModels = m_argParser.get<bool>( ARG_SUBST_MODELS );
step->m_overwrite = m_argParser.get<bool>( ARG_FORCE );
step->m_filename = FROM_UTF8( m_argParser.get<std::string>( ARG_INPUT ).c_str() );
step->m_outputFile = FROM_UTF8( m_argParser.get<std::string>( ARG_OUTPUT ).c_str() );
step->m_boardOnly = m_argParser.get<bool>( ARG_BOARD_ONLY );
wxString userOrigin = FROM_UTF8( m_argParser.get<std::string>( ARG_USER_ORIGIN ).c_str() );
if( !userOrigin.IsEmpty() )

View File

@ -102,6 +102,9 @@ public:
///< Not equality operator
bool operator!=( const VECTOR3<T>& aVector ) const;
VECTOR3<T>& operator*=( T val );
VECTOR3<T>& operator/=( T val );
};
@ -175,6 +178,28 @@ bool VECTOR3<T>::operator!=( VECTOR3<T> const& aVector ) const
}
template <class T>
VECTOR3<T>& VECTOR3<T>::operator*=( T aScalar )
{
x = x * aScalar;
y = y * aScalar;
z = z * aScalar;
return *this;
}
template <class T>
VECTOR3<T>& VECTOR3<T>::operator/=( T aScalar )
{
x = x / aScalar;
y = y / aScalar;
z = z / aScalar;
return *this;
}
/* Default specializations */
typedef VECTOR3<double> VECTOR3D;
typedef VECTOR3<int> VECTOR3I;

View File

@ -13,7 +13,6 @@ add_definitions( -DPCBNEW )
add_subdirectory(connectivity)
add_subdirectory(router)
add_subdirectory(exporters/step)
# psnrouter depends on make_lexer outputs in common (bug #1285878 )
add_dependencies( pnsrouter pcbcommon )
@ -223,6 +222,8 @@ set( PCBNEW_EXPORTERS
exporters/export_footprint_associations.cpp
exporters/export_gencad.cpp
exporters/export_idf.cpp
exporters/step/exporter_step.cpp
exporters/step/step_pcb_model.cpp
exporters/exporter_vrml.cpp
exporters/place_file_exporter.cpp
exporters/gen_drill_report_files.cpp
@ -680,7 +681,8 @@ target_link_libraries( pcbnew_kiface_objects
tinyspline_lib
nlohmann_json
rectpack2d
kicad2step_lib
gzip-hpp
${OCC_LIBRARIES}
)
target_include_directories( pcbnew_kiface_objects PRIVATE

View File

@ -47,9 +47,6 @@
#include <filename_resolver.h>
#include <kicad2step.h>
class DIALOG_EXPORT_STEP : public DIALOG_EXPORT_STEP_BASE
{
public:

View File

@ -1,44 +0,0 @@
include_directories( SYSTEM
${OCE_INCLUDE_DIRS}
${OCC_INCLUDE_DIR}
)
set( KS2_LIB_FILES
kicad2step.cpp
pcb/base.cpp
pcb/kicadmodel.cpp
pcb/kicadfootprint.cpp
pcb/kicadpad.cpp
pcb/kicadpcb.cpp
pcb/kicadcurve.cpp
pcb/oce_utils.cpp
)
# Break the library out for re-use by both kicad2step and any qa that needs it
# In future, this could move for re-use by other programs needing s-expr support (?)
add_library( kicad2step_lib STATIC
${KS2_LIB_FILES}
)
target_include_directories( kicad2step_lib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include # for core
${Boost_INCLUDE_DIR} # see commit 03bce554
${CMAKE_SOURCE_DIR}/libs/kimath/include
${INC_AFTER}
)
target_link_libraries( kicad2step_lib
sexpr
common
${wxWidgets_LIBRARIES}
${OCC_LIBRARIES}
${ZLIB_LIBRARIES}
kimath
)
target_include_directories( kicad2step_lib PRIVATE
$<TARGET_PROPERTY:gzip-hpp,INTERFACE_INCLUDE_DIRECTORIES>
)

View File

@ -0,0 +1,361 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2016-2021 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
*/
#include "exporter_step.h"
#include <board.h>
#include <board_design_settings.h>
#include <footprint.h>
#include <fp_lib_table.h>
#include <pgm_base.h>
#include <base_units.h>
#include <filename_resolver.h>
#include <Message.hxx> // OpenCascade messenger
#include <Message_PrinterOStream.hxx> // OpenCascade output messenger
#include <Standard_Failure.hxx> // In open cascade
#include <Standard_Version.hxx>
#include <wx/crt.h>
#define DEFAULT_BOARD_THICKNESS 1.6
void ReportMessage( const wxString& aMessage )
{
wxPrintf( aMessage );
fflush( stdout ); // Force immediate printing (needed on mingw)
}
class KiCadPrinter : public Message_Printer
{
public:
KiCadPrinter( EXPORTER_STEP* aConverter ) : m_converter( aConverter ) {}
protected:
#if OCC_VERSION_HEX < OCC_VERSION_MIN
virtual void Send( const TCollection_ExtendedString& theString,
const Message_Gravity theGravity,
const Standard_Boolean theToPutEol ) const override
{
Send( TCollection_AsciiString( theString ), theGravity, theToPutEol );
}
virtual void Send( const TCollection_AsciiString& theString,
const Message_Gravity theGravity,
const Standard_Boolean theToPutEol ) const override
#else
virtual void send( const TCollection_AsciiString& theString,
const Message_Gravity theGravity ) const override
#endif
{
if( theGravity >= Message_Info )
{
ReportMessage( theString.ToCString() );
#if OCC_VERSION_HEX < OCC_VERSION_MIN
if( theToPutEol )
ReportMessage( wxT( "\n" ) );
#else
ReportMessage( wxT( "\n" ) );
#endif
}
if( theGravity >= Message_Alarm )
m_converter->SetError();
if( theGravity == Message_Fail )
m_converter->SetFail();
}
private:
EXPORTER_STEP* m_converter;
};
EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams ) :
m_params( aParams ),
m_error( false ),
m_fail( false ),
m_hasDrillOrigin( false ),
m_hasGridOrigin( false ),
m_board( aBoard ),
m_pcbModel( nullptr ),
m_pcbName(),
m_minDistance( STEPEXPORT_MIN_DISTANCE ),
m_boardThickness( DEFAULT_BOARD_THICKNESS )
{
m_solderMaskColor = COLOR4D( 0.08, 0.20, 0.14, 0.83 );
m_resolver = std::make_unique<FILENAME_RESOLVER>();
m_resolver->Set3DConfigDir( wxT( "" ) );
m_resolver->SetProgramBase( &Pgm() );
}
EXPORTER_STEP::~EXPORTER_STEP()
{
}
bool EXPORTER_STEP::composePCB( FOOTPRINT* aFootprint, VECTOR2D aOrigin )
{
bool hasdata = false;
if( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) && !m_params.m_includeExcludedBom )
{
return hasdata;
}
// Prefetch the library for this footprint
// In case we need to resolve relative footprint paths
wxString libraryName = aFootprint->GetFPID().GetLibNickname();
wxString footprintBasePath = wxEmptyString;
double posX = aFootprint->GetPosition().x - aOrigin.x;
double posY = (aFootprint->GetPosition().y) - aOrigin.y;
if( m_board->GetProject() )
{
try
{
// FindRow() can throw an exception
const FP_LIB_TABLE_ROW* fpRow =
m_board->GetProject()->PcbFootprintLibs()->FindRow(
libraryName, false );
if( fpRow )
footprintBasePath = fpRow->GetFullURI( true );
}
catch( ... )
{
// Do nothing if the libraryName is not found in lib table
}
}
// Dump the pad holes into the PCB
for( PAD* pad : aFootprint->Pads() )
{
if( m_pcbModel->AddPadHole( pad ) )
hasdata = true;
}
// Exit early if we don't want to include footprint models
if( m_params.m_boardOnly )
{
return hasdata;
}
VECTOR2D newpos( pcbIUScale.IUTomm( posX ), pcbIUScale.IUTomm( posY ) );
for( const FP_3DMODEL& fp_model : aFootprint->Models() )
{
if( !fp_model.m_Show || fp_model.m_Filename.empty() )
continue;
std::vector<wxString> searchedPaths;
wxString mname = m_resolver->ResolvePath( fp_model.m_Filename, wxEmptyString );
if( !wxFileName::FileExists( mname ) )
{
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
"File not found: %s\n" ),
aFootprint->GetReference(), mname ) );
continue;
}
std::string fname( mname.ToUTF8() );
std::string refName( aFootprint->GetReference().ToUTF8() );
try
{
bool bottomSide = aFootprint->GetLayer() == B_Cu;
// the rotation is stored in degrees but opencascade wants radians
VECTOR3D modelRot = fp_model.m_Rotation;
modelRot *= M_PI;
modelRot /= 180.0;
if( m_pcbModel->AddComponent( fname, refName, bottomSide,
newpos,
aFootprint->GetOrientation().AsRadians(),
fp_model.m_Offset, modelRot,
fp_model.m_Scale, m_params.m_substModels ) )
{
hasdata = true;
}
}
catch( const Standard_Failure& e )
{
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
"OpenCASCADE error: %s\n" ),
aFootprint->GetReference(), e.GetMessageString() ) );
}
}
return hasdata;
}
bool EXPORTER_STEP::composePCB()
{
if( m_pcbModel )
return true;
SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
if( !m_board->GetBoardPolygonOutlines( pcbOutlines ) )
{
wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
}
VECTOR2D origin;
// Determine the coordinate system reference:
// Precedence of reference point is Drill Origin > Grid Origin > User Offset
if( m_params.m_useDrillOrigin )
origin = m_board->GetDesignSettings().GetAuxOrigin();
else if( m_params.m_useGridOrigin )
origin = m_board->GetDesignSettings().GetGridOrigin();
else
origin = m_params.m_origin;
m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbName );
// TODO: Handle when top & bottom soldermask colours are different...
m_pcbModel->SetBoardColor( m_solderMaskColor.r, m_solderMaskColor.g, m_solderMaskColor.b );
m_pcbModel->SetPCBThickness( m_boardThickness );
m_pcbModel->SetMinDistance(
std::max( m_params.m_minDistance, STEPEXPORT_MIN_ACCEPTABLE_DISTANCE ) );
m_pcbModel->SetMaxError( m_board->GetDesignSettings().m_MaxError );
for( FOOTPRINT* i : m_board->Footprints() )
composePCB( i, origin );
ReportMessage( wxT( "Create PCB solid model\n" ) );
if( !m_pcbModel->CreatePCB( pcbOutlines ) )
{
ReportMessage( wxT( "could not create PCB solid model\n" ) );
return false;
}
return true;
}
void EXPORTER_STEP::determinePcbThickness()
{
m_boardThickness = DEFAULT_BOARD_THICKNESS;
const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
if( bds.GetStackupDescriptor().GetCount() )
{
int thickness = 0;
for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() )
{
switch( item->GetType() )
{
case BS_ITEM_TYPE_DIELECTRIC: thickness += item->GetThickness(); break;
case BS_ITEM_TYPE_COPPER:
if( item->IsEnabled() )
thickness += item->GetThickness();
default: break;
}
}
m_boardThickness = pcbIUScale.IUTomm( thickness );
}
}
bool EXPORTER_STEP::Export()
{
// setup opencascade message log
Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
Message::DefaultMessenger()->AddPrinter( new KiCadPrinter( this ) );
ReportMessage( _( "Determining PCB data\n" ) );
determinePcbThickness();
try
{
ReportMessage( _( "Build STEP data\n" ) );
if( !composePCB() )
{
ReportMessage( _( "\n** Error building STEP board model. Export aborted. **\n" ) );
return false;
}
ReportMessage( _( "Writing STEP file\n" ) );
if( !m_pcbModel->WriteSTEP( m_outputFile ) )
{
ReportMessage( _( "\n** Error writing STEP file. **\n" ) );
return false;
}
else
{
ReportMessage( wxString::Format( _( "\nSTEP file '%s' created.\n" ), m_outputFile ) );
}
}
catch( const Standard_Failure& e )
{
ReportMessage( e.GetMessageString() );
ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
return false;
}
catch( ... )
{
ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
return false;
}
if( m_fail || m_error )
{
wxString msg;
if( m_fail )
{
msg = _( "Unable to create STEP file.\n"
"Check that the board has a valid outline and models." );
}
else if( m_error )
{
msg = _( "STEP file has been created, but there are warnings." );
}
ReportMessage( msg );
}
return true;
}

View File

@ -0,0 +1,103 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2016-2021 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
*/
#ifndef EXPORTER_STEP_H
#define EXPORTER_STEP_H
#include "step_pcb_model.h"
#include <geometry/shape_poly_set.h>
#include <gal/color4d.h>
class PCBMODEL;
class BOARD;
class FOOTPRINT;
class FILENAME_RESOLVER;
class EXPORTER_STEP_PARAMS
{
public:
EXPORTER_STEP_PARAMS() :
m_origin(),
m_overwrite( false ),
m_useGridOrigin( false ),
m_useDrillOrigin( false ),
m_includeExcludedBom( true ),
m_substModels( true ),
m_boardOnly( false ),
m_minDistance( 0.00001 ) {};
wxString m_outputFile;
VECTOR2D m_origin;
bool m_overwrite;
bool m_useGridOrigin;
bool m_useDrillOrigin;
bool m_includeExcludedBom;
bool m_substModels;
double m_minDistance;
bool m_boardOnly;
};
class EXPORTER_STEP
{
public:
EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams );
~EXPORTER_STEP();
bool Export();
wxString m_outputFile;
void SetError() { m_error = true; }
void SetFail() { m_error = true; }
private:
bool composePCB();
bool composePCB( FOOTPRINT* aFootprint, VECTOR2D aOrigin );
void determinePcbThickness();
EXPORTER_STEP_PARAMS m_params;
std::unique_ptr<FILENAME_RESOLVER> m_resolver;
bool m_error;
bool m_fail;
bool m_hasDrillOrigin;
bool m_hasGridOrigin;
BOARD* m_board;
std::unique_ptr<STEP_PCB_MODEL> m_pcbModel;
wxString m_pcbName;
// minimum distance between points to treat them as separate entities (mm)
double m_minDistance;
double m_boardThickness;
KIGFX::COLOR4D m_solderMaskColor;
};
#endif

View File

@ -1,290 +0,0 @@
/*
* This program source code file is part of kicad2mcad
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2016-2022 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
*/
#include <wx/app.h>
#include <wx/cmdline.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/string.h>
#include <wx/filename.h>
#include <wx/crt.h>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <sstream>
#include <cli/exit_codes.h>
#include <pgm_base.h>
#include "kicad2step.h"
#include "pcb/kicadpcb.h"
#include <Message.hxx> // OpenCascade messenger
#include <Message_PrinterOStream.hxx> // OpenCascade output messenger
#include <Standard_Failure.hxx> // In open cascade
#include <Standard_Version.hxx>
#include <locale_io.h>
#define OCC_VERSION_MIN 0x070500
#if OCC_VERSION_HEX < OCC_VERSION_MIN
#include <Message_Messenger.hxx>
#endif
// Horrible hack until we decouple things more
static KICAD2STEP* k2sInstance = nullptr;
void ReportMessage( const wxString& aMessage )
{
if( k2sInstance != nullptr )
k2sInstance->ReportMessage( aMessage );
}
class KiCadPrinter : public Message_Printer
{
public:
KiCadPrinter( KICAD2STEP* aConverter ) : m_converter( aConverter ) {}
protected:
#if OCC_VERSION_HEX < OCC_VERSION_MIN
virtual void Send( const TCollection_ExtendedString& theString,
const Message_Gravity theGravity,
const Standard_Boolean theToPutEol ) const override
{
Send( TCollection_AsciiString( theString ), theGravity, theToPutEol );
}
virtual void Send( const TCollection_AsciiString& theString,
const Message_Gravity theGravity,
const Standard_Boolean theToPutEol ) const override
#else
virtual void send( const TCollection_AsciiString& theString,
const Message_Gravity theGravity ) const override
#endif
{
if( theGravity >= Message_Info )
{
m_converter->ReportMessage( theString.ToCString() );
#if OCC_VERSION_HEX < OCC_VERSION_MIN
if( theToPutEol )
ReportMessage( wxT( "\n" ) );
#else
m_converter->ReportMessage( wxT( "\n" ) );
#endif
}
if( theGravity >= Message_Alarm )
m_converter->SetError();
if( theGravity == Message_Fail )
m_converter->SetFail();
}
private:
KICAD2STEP* m_converter;
};
KICAD2MCAD_PRMS::KICAD2MCAD_PRMS()
{
#ifdef SUPPORTS_IGES
m_fmtIGES = false;
#endif
m_overwrite = false;
m_useGridOrigin = false;
m_useDrillOrigin = false;
m_includeVirtual = true;
m_substModels = false;
m_xOrigin = 0.0;
m_yOrigin = 0.0;
m_minDistance = MIN_DISTANCE;
}
wxString KICAD2MCAD_PRMS::getOutputExt() const
{
#ifdef SUPPORTS_IGES
if( m_fmtIGES )
return wxT( "igs" );
else
#endif
return wxT( "step" );
}
KICAD2STEP::KICAD2STEP( KICAD2MCAD_PRMS aParams ) :
m_params( aParams ), m_error( false ), m_fail( false )
{
}
int KICAD2STEP::DoRun()
{
wxFileName fname( m_params.m_filename );
if( !fname.FileExists() )
{
ReportMessage( wxString::Format( _( "No such file: %s" ), m_params.m_filename ) );
return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
}
wxFileName out_fname;
if( m_params.m_outputFile.empty() )
{
out_fname.Assign( fname.GetFullPath() );
out_fname.SetExt( m_params.getOutputExt() );
}
else
{
out_fname.Assign( m_params.m_outputFile );
// Set the file extension if the user's requested file name does not have an extension.
if( !out_fname.HasExt() )
out_fname.SetExt( m_params.getOutputExt() );
}
if( out_fname.FileExists() && !m_params.m_overwrite )
{
ReportMessage( _( "** Output already exists. Export aborted. **\n"
"Enable the force overwrite flag to overwrite it." ) );
return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
}
LOCALE_IO dummy;
wxString outfile = out_fname.GetFullPath();
KICADPCB pcb( fname.GetName() );
pcb.SetOrigin( m_params.m_xOrigin, m_params.m_yOrigin );
// Set the min dist in mm to consider 2 points at the same place
// This is also the tolerance to consider 2 lines or arcs are connected
// A min value (0.001mm) is needed to have closed board outlines
// 0.01 mm is a good value
pcb.SetMinDistance( std::max( m_params.m_minDistance, MIN_ACCEPTABLE_DISTANCE ) );
ReportMessage( wxString::Format( _( "Read file: '%s'\n" ), m_params.m_filename ) );
Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
Message::DefaultMessenger()->AddPrinter( new KiCadPrinter( this ) );
if( pcb.ReadFile( m_params.m_filename ) )
{
if( m_params.m_useDrillOrigin )
pcb.UseDrillOrigin( true );
if( m_params.m_useGridOrigin )
pcb.UseGridOrigin( true );
bool res;
try
{
ReportMessage( _( "Build STEP data\n" ) );
res = pcb.ComposePCB( m_params.m_includeVirtual, m_params.m_substModels );
if( !res )
{
ReportMessage( _( "\n** Error building STEP board model. Export aborted. **\n" ) );
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
ReportMessage( _( "Write STEP file\n" ) );
#ifdef SUPPORTS_IGES
if( m_fmtIGES )
res = pcb.WriteIGES( outfile );
else
#endif
res = pcb.WriteSTEP( outfile );
if( !res )
{
ReportMessage( _( "\n** Error writing STEP file. **\n" ) );
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
else
{
ReportMessage( wxString::Format( _( "\nSTEP file '%s' created.\n" ), outfile ) );
}
}
catch( const Standard_Failure& e )
{
ReportMessage( e.GetMessageString() );
ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
catch( ... )
{
ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
}
}
else
{
ReportMessage( _( "\n** Error reading kicad_pcb file. **\n" ) );
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
wxString msg;
if( m_fail )
{
msg = _( "Unable to create STEP file.\n"
"Check that the board has a valid outline and models." );
}
else if( m_error )
{
msg = _( "STEP file has been created, but there are warnings." );
}
ReportMessage( msg );
return CLI::EXIT_CODES::SUCCESS;
}
int KICAD2STEP::Run()
{
k2sInstance = this;
int diag = DoRun();
return diag;
}
void KICAD2STEP::ReportMessage( const wxString& aMessage )
{
wxPrintf( aMessage );
fflush( stdout ); // Force immediate printing (needed on mingw)
}

View File

@ -1,76 +0,0 @@
/*
* This program source code file is part of kicad2mcad
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2016-2021 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
*/
#ifndef KICAD2STEP_H
#define KICAD2STEP_H
#include <wx/string.h>
#include <import_export.h>
class APIEXPORT KICAD2MCAD_PRMS // A small class to handle parameters of conversion
{
public:
KICAD2MCAD_PRMS();
///< Return file extension for the selected output format
wxString getOutputExt() const;
#ifdef SUPPORTS_IGES
bool m_fmtIGES;
#endif
bool m_overwrite;
bool m_useGridOrigin;
bool m_useDrillOrigin;
bool m_includeVirtual;
bool m_substModels;
wxString m_filename;
wxString m_outputFile;
double m_xOrigin;
double m_yOrigin;
double m_minDistance;
};
class APIEXPORT KICAD2STEP
{
public:
KICAD2STEP( KICAD2MCAD_PRMS aParams );
int Run();
void ReportMessage( const wxString& aMessage );
void SetError() { m_error = true; }
void SetFail() { m_error = true; }
private:
int DoRun();
KICAD2MCAD_PRMS m_params;
bool m_error;
bool m_fail;
friend class KICAD2STEP_FRAME;
};
#endif

View File

@ -1,299 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
#include <wx/log.h>
#include <iostream>
#include <sstream>
#include <cmath>
#include "sexpr/sexpr.h"
#include "base.h"
static const char bad_position[] = "* corrupt module in PCB file; invalid position";
std::ostream& operator<<( std::ostream& aStream, const DOUBLET& aDoublet )
{
aStream << aDoublet.x << "," << aDoublet.y;
return aStream;
}
std::ostream& operator<<( std::ostream& aStream, const TRIPLET& aTriplet )
{
aStream << aTriplet.x << "," << aTriplet.y << "," << aTriplet.z;
return aStream;
}
bool Get2DPositionAndRotation( const SEXPR::SEXPR* data, DOUBLET& aPosition, double& aRotation )
{
// form: (at X Y {rot})
int nchild = data->GetNumberOfChildren();
if( nchild < 3 )
{
std::ostringstream ostr;
ostr << bad_position << " (line " << data->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
if( data->GetChild( 0 )->GetSymbol() != "at" )
{
std::ostringstream ostr;
ostr << "* SEXPR item is not a position string (line ";
ostr << data->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child = data->GetChild( 1 );
double x;
if( child->IsDouble() )
{
x = child->GetDouble();
}
else if( child->IsInteger() )
{
x = (double) child->GetInteger();
}
else
{
std::ostringstream ostr;
ostr << bad_position << " (line " << child->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
child = data->GetChild( 2 );
double y;
if( child->IsDouble() )
{
y = child->GetDouble();
}
else if( child->IsInteger() )
{
y = (double) child->GetInteger();
}
else
{
std::ostringstream ostr;
ostr << bad_position << " (line " << child->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
aPosition.x = x;
aPosition.y = y;
if( nchild == 3 )
return true;
child = data->GetChild( 3 );
double angle = 0.0;
if( child->IsDouble() )
{
angle = child->GetDouble();
}
else if( child->IsInteger() )
{
angle = (double) child->GetInteger();
}
else
{
std::ostringstream ostr;
ostr << bad_position << " (line " << child->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
while( angle >= 360.0 )
angle -= 360.0;
while( angle <= -360.0 )
angle += 360.0;
aRotation = (angle / 180.0) * M_PI;
return true;
}
bool Get2DCoordinate( const SEXPR::SEXPR* data, DOUBLET& aCoordinate )
{
// form: (at X Y {rot})
int nchild = data->GetNumberOfChildren();
if( nchild < 3 )
{
std::ostringstream ostr;
ostr << bad_position << " (line " << data->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child = data->GetChild( 1 );
double x;
if( child->IsDouble() )
{
x = child->GetDouble();
}
else if( child->IsInteger() )
{
x = (double) child->GetInteger();
}
else
{
std::ostringstream ostr;
ostr << bad_position << " (line " << child->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
child = data->GetChild( 2 );
double y;
if( child->IsDouble() )
{
y = child->GetDouble();
}
else if( child->IsInteger() )
{
y = (double) child->GetInteger();
}
else
{
std::ostringstream ostr;
ostr << bad_position << " (line " << child->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
aCoordinate.x = x;
aCoordinate.y = y;
return true;
}
bool Get3DCoordinate( const SEXPR::SEXPR* data, TRIPLET& aCoordinate )
{
// form: (at X Y Z)
int nchild = data->GetNumberOfChildren();
if( nchild < 4 )
{
std::ostringstream ostr;
ostr << bad_position << " (line " << data->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child;
double val[3];
for( int i = 1; i < 4; ++i )
{
child = data->GetChild( i );
if( child->IsDouble() )
val[i -1] = child->GetDouble();
else if( child->IsInteger() )
val[i -1] = (double) child->GetInteger();
else
{
std::ostringstream ostr;
ostr << bad_position << " (line " << child->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
}
aCoordinate.x = val[0];
aCoordinate.y = val[1];
aCoordinate.z = val[2];
return true;
}
bool GetXYZRotation( const SEXPR::SEXPR* data, TRIPLET& aRotation )
{
const char bad_rotation[] = "* invalid 3D rotation";
if( !Get3DCoordinate( data, aRotation ) )
{
std::ostringstream ostr;
ostr << bad_rotation << " (line " << data->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
if( aRotation.x > 360.0 || aRotation.x < -360.0 )
{
int nr = (int) aRotation.x / 360;
aRotation.x -= ( 360.0 * nr );
}
if( aRotation.y > 360.0 || aRotation.y < -360.0 )
{
int nr = (int) aRotation.y / 360;
aRotation.y -= ( 360.0 * nr );
}
if( aRotation.z > 360.0 || aRotation.z < -360.0 )
{
int nr = (int) aRotation.z / 360;
aRotation.z -= ( 360.0 * nr );
}
aRotation.x *= M_PI / 180.0;
aRotation.y *= M_PI / 180.0;
aRotation.z *= M_PI / 180.0;
return true;
}
std::optional<std::string> GetLayerName( const SEXPR::SEXPR& aLayerElem )
{
std::optional<std::string> layer;
if( aLayerElem.GetNumberOfChildren() == 2 )
{
const SEXPR::SEXPR& layerChild = *aLayerElem.GetChild( 1 );
// The layer child can be quoted (string) or unquoted (symbol) depending on PCB version.
if( layerChild.IsString() )
layer = layerChild.GetString();
else if( layerChild.IsSymbol() )
layer = layerChild.GetSymbol();
}
return layer;
}

View File

@ -1,135 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
/**
* @file base.h
* Provide declarations of items which are basic to all kicad2mcad code.
*/
#ifndef KICADBASE_H
#define KICADBASE_H
#include <optional>
#include <ostream>
///< Default minimum distance between points to treat them as separate ones (mm)
static constexpr double MIN_DISTANCE = 0.01;
static constexpr double MIN_ACCEPTABLE_DISTANCE = 0.001;
namespace SEXPR
{
class SEXPR;
}
enum CURVE_TYPE
{
CURVE_NONE = 0, // invalid curve
CURVE_LINE,
CURVE_POLYGON,
CURVE_ARC,
CURVE_CIRCLE,
CURVE_BEZIER
};
/*
* Layers of importance to MCAD export:
* - LAYER_TOP: specifies that a footprint is on the top of the PCB
* - LAYER_BOTTOM: specifies that a footprint is on the bottom of the PCB
* - LAYER_EDGE: specifies that a Curve is associated with the PCB edge
*/
enum LAYERS
{
LAYER_NONE = 0, // no layer specified (bad object)
LAYER_TOP, // top side
LAYER_BOTTOM, // bottom side
LAYER_EDGE // edge data
};
struct DOUBLET
{
double x;
double y;
DOUBLET() :
x( 0.0 ),
y( 0.0 )
{ }
DOUBLET( double aX, double aY ) :
x( aX ),
y( aY )
{ }
};
std::ostream& operator<<( std::ostream& aStream, const DOUBLET& aDoublet );
struct TRIPLET
{
double x;
double y;
union
{
double z;
double angle;
};
TRIPLET() :
x( 0.0 ),
y( 0.0 ),
z( 0.0 )
{ }
TRIPLET( double aX, double aY, double aZ ) :
x( aX ),
y( aY ),
z( aZ )
{ }
};
std::ostream& operator<<( std::ostream& aStream, const TRIPLET& aTriplet );
bool Get2DPositionAndRotation( const SEXPR::SEXPR* data, DOUBLET& aPosition, double& aRotation );
bool Get2DCoordinate( const SEXPR::SEXPR* data, DOUBLET& aCoordinate );
bool Get3DCoordinate( const SEXPR::SEXPR* data, TRIPLET& aCoordinate );
bool GetXYZRotation( const SEXPR::SEXPR* data, TRIPLET& aRotation );
/**
* Get the layer name from a layer element, if the layer is syntactically valid.
*
* E.g. (layer "Edge.Cuts") -> "Edge.Cuts"
*
* @param aLayerElem the s-expr element to get the name from.
* @return the layer name if valid, else empty.
*/
std::optional<std::string> GetLayerName( const SEXPR::SEXPR& aLayerElem );
#endif // KICADBASE_H

View File

@ -1,295 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2022 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
*/
#include "kicadcurve.h"
#include <sexpr/sexpr.h>
#include <wx/log.h>
#include <cmath>
#include <iostream>
#include <sstream>
#include <../../../libs/kimath/include/geometry/shape_arc.h>
#include <sexpr/sexpr_parser.h>
#include <geometry/shape_line_chain.h>
KICADCURVE::KICADCURVE()
{
m_form = CURVE_NONE;
m_angle = 0.0;
m_radius = 0.0;
m_layer = LAYER_NONE;
m_startangle = 0.0;
m_endangle = 0.0;
m_arcHasMiddlePoint = false;
return;
}
KICADCURVE::~KICADCURVE()
{
return;
}
bool KICADCURVE::Read( SEXPR::SEXPR* aEntry, CURVE_TYPE aCurveType )
{
if( CURVE_LINE != aCurveType && CURVE_ARC != aCurveType
&& CURVE_CIRCLE != aCurveType && CURVE_BEZIER != aCurveType
&& CURVE_POLYGON != aCurveType )
{
wxLogMessage( wxT( "* Unsupported curve type: %d\n" ), aCurveType );
return false;
}
m_form = aCurveType;
int nchild = aEntry->GetNumberOfChildren();
if( ( CURVE_CIRCLE == aCurveType && nchild < 5 )
|| ( CURVE_ARC == aCurveType && nchild < 6 )
|| ( CURVE_LINE == aCurveType && nchild < 5 )
|| ( CURVE_BEZIER == aCurveType && nchild < 5 )
|| ( CURVE_POLYGON == aCurveType && nchild < 5 ) )
{
wxLogMessage( wxT( "* bad curve data; not enough parameters\n" ) );
return false;
}
SEXPR::SEXPR* child;
std::string text;
for( int i = 1; i < nchild; ++i )
{
child = aEntry->GetChild( i );
if( !child->IsList() )
continue;
text = child->GetChild( 0 )->GetSymbol();
if( text == "pts" )
{
// Parse and extract the list of xy coordinates
SEXPR::PARSER parser;
std::unique_ptr<SEXPR::SEXPR> prms = parser.Parse( child->AsString() );
// We need 4 XY parameters (and "pts" that is the first parameter)
if( ( aCurveType == CURVE_BEZIER && prms->GetNumberOfChildren() != 5 )
|| ( aCurveType == CURVE_POLYGON && prms->GetNumberOfChildren() < 4 ) )
return false;
// Extract xy coordinates from pts list
SEXPR::SEXPR_VECTOR const* list = prms->GetChildren();
// The first parameter is "pts", so skip it.
for( SEXPR::SEXPR_VECTOR::size_type ii = 1; ii < list->size(); ++ii )
{
SEXPR::SEXPR* sub_child = ( *list )[ii];
text = sub_child->GetChild( 0 )->GetSymbol();
// inside pts list, parameters are xy point coord
// or a arc (start, middle, end) points
if( text == "xy" )
{
DOUBLET coord;
if( !Get2DCoordinate( sub_child, coord ) )
return false;
if( aCurveType == CURVE_BEZIER )
{
switch( ii )
{
case 1: m_start = coord; break;
case 2: m_bezierctrl1 = coord; break;
case 3: m_bezierctrl2 = coord; break;
case 4: m_end = coord; break;
default:
break;
}
}
else
{
m_poly.push_back( coord );
}
}
else if( text == "arc" )
{
int arc_child = sub_child->GetNumberOfChildren();
DOUBLET arc_start, arc_middle, arc_end;
for( int jj = 1; jj < arc_child; jj++ )
{
SEXPR::SEXPR* curr_child = sub_child->GetChild( jj );
text = curr_child->GetChild( 0 )->GetSymbol();
if( text == "start" )
{
if( !Get2DCoordinate( curr_child, arc_start ) )
return false;
}
else if( text == "end" )
{
if( !Get2DCoordinate( curr_child, arc_end ) )
return false;
}
else if( text == "mid" )
{
if( !Get2DCoordinate( curr_child, arc_middle ) )
return false;
}
}
// To convert arc edge to segments, we are using SHAPE_ARC, but SHAPE_ARC use
// integer coords. So to avoid truncation, use a scaling factor.
// 1e5 is enough.
const double scale = 1e5;
SHAPE_ARC new_arc( VECTOR2I( arc_start.x*scale, arc_start.y*scale ),
VECTOR2I( arc_middle.x*scale, arc_middle.y*scale ),
VECTOR2I( arc_end.x*scale, arc_end.y*scale ), 0 );
double accuracy = 0.005*scale; // Approx accuracy is 5 microns
SHAPE_LINE_CHAIN segs_from_arc = new_arc.ConvertToPolyline( accuracy );
// Add segments to STEP polygon
for( int ll = 0; ll < segs_from_arc.PointCount(); ll++ )
m_poly.emplace_back( segs_from_arc.CPoint( ll ).x / scale,
segs_from_arc.CPoint( ll ).y / scale );
}
}
}
else if( text == "start" || text == "center" )
{
if( !Get2DCoordinate( child, m_start ) )
return false;
}
else if( text == "end" )
{
if( !Get2DCoordinate( child, m_end ) )
return false;
}
else if( text == "mid" )
{
if( !Get2DCoordinate( child, m_middle ) )
return false;
m_arcHasMiddlePoint = true;
}
else if( text == "angle" )
{
if( child->GetNumberOfChildren() < 2
|| ( !child->GetChild( 1 )->IsDouble() && !child->GetChild( 1 )->IsInteger() ) )
{
wxLogMessage( wxT( "* bad angle data\n" ) );
return false;
}
if( child->GetChild( 1 )->IsDouble() )
m_angle = child->GetChild( 1 )->GetDouble();
else
m_angle = child->GetChild( 1 )->GetInteger();
m_angle = m_angle / 180.0 * M_PI;
}
else if( text == "layer" )
{
const std::optional<std::string> layer = GetLayerName( *child );
if( !layer )
{
std::ostringstream ostr;
ostr << "* bad layer data: " << child->AsString();
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
// NOTE: for the moment we only process Edge.Cuts
if( *layer == "Edge.Cuts" )
m_layer = LAYER_EDGE;
}
}
// New arcs are defined by start middle and end points instead of center,
// start and arc angle
// So convert new params to old params
if( CURVE_ARC == aCurveType && m_arcHasMiddlePoint )
{
// To calculate old params, we are using SHAPE_ARC, but SHAPE_ARC use
// integer coords. So to avoid truncation, use a scaling factor.
// 1e5 is enough.
const double scale = 1e5;
SHAPE_ARC new_arc( VECTOR2I( m_start.x*scale, m_start.y*scale ),
VECTOR2I( m_middle.x*scale, m_middle.y*scale ),
VECTOR2I( m_end.x*scale, m_end.y*scale ), 0 );
VECTOR2I center = new_arc.GetCenter();
m_start.x = center.x/scale;
m_start.y = center.y/scale;
m_end.x = new_arc.GetP0().x / scale;
m_end.y = new_arc.GetP0().y / scale;
m_ep.x = new_arc.GetP1().x / scale;
m_ep.y = new_arc.GetP1().y / scale;
m_angle = new_arc.GetCentralAngle().AsRadians();
}
return true;
}
std::string KICADCURVE::Describe() const
{
std::ostringstream desc;
switch( m_form )
{
case CURVE_LINE:
desc << "line start: " << m_start << " end: " << m_end;
break;
case CURVE_ARC:
desc << "arc center: " << m_start << " radius: " << m_radius
<< " angle: " << 180.0 * m_angle / M_PI
<< " arc start: " << m_end << " arc end: " << m_ep;
break;
case CURVE_CIRCLE:
desc << "circle center: " << m_start << " radius: " << m_radius;
break;
case CURVE_BEZIER:
desc << "bezier start: " << m_start << " end: " << m_end
<< " ctrl1: " << m_bezierctrl1 << " ctrl2: " << m_bezierctrl2 ;
break;
default:
desc << "<invalid curve type>";
break;
}
return desc.str();
}

View File

@ -1,71 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
/**
* @file kicadcurve.h
* Declare the Curve (glyph) object.
*/
#ifndef KICADCURVE_H
#define KICADCURVE_H
#include <string>
#include <vector>
#include "base.h"
class KICADCURVE
{
public:
KICADCURVE();
virtual ~KICADCURVE();
bool Read( SEXPR::SEXPR* aEntry, CURVE_TYPE aCurveType );
LAYERS GetLayer()
{
return m_layer;
}
///< Return human-readable description of the curve.
std::string Describe() const;
CURVE_TYPE m_form; // form of curve: line, arc, circle
LAYERS m_layer; // layer of the glyph
DOUBLET m_start; // start point of line or center for arc and circle
DOUBLET m_end; // end point of line, first point on arc or circle
DOUBLET m_middle; // middle point on arc for recent files
DOUBLET m_ep; // actual endpoint, to be computed in the case of arcs
DOUBLET m_bezierctrl1; // for bezier curve only first control point
DOUBLET m_bezierctrl2; // for bezier curve only second control point
double m_radius; // radius; to be computed in the case of arcs and circles
double m_angle; // subtended angle of arc
double m_startangle;
double m_endangle;
bool m_arcHasMiddlePoint; // true if an arc id defined by 3 points
std::vector<DOUBLET> m_poly; // vector of polygon points
};
#endif // KICADCURVE_H

View File

@ -1,474 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright 2018-2022 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
*/
#include "kicadfootprint.h"
#include <filename_resolver.h>
#include "kicadcurve.h"
#include "kicadmodel.h"
#include "kicadpad.h"
#include "oce_utils.h"
#include <sexpr/sexpr.h>
#include <wx/log.h>
#include <wx/filename.h>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <Standard_Failure.hxx>
KICADFOOTPRINT::KICADFOOTPRINT( KICADPCB* aParent )
{
m_parent = aParent;
m_side = LAYER_NONE;
m_rotation = 0.0;
m_smd = false;
m_tht = false;
m_virtual = false;
return;
}
KICADFOOTPRINT::~KICADFOOTPRINT()
{
for( KICADPAD* i : m_pads )
delete i;
for( KICADCURVE* i : m_curves )
delete i;
for( KICADMODEL* i : m_models )
delete i;
return;
}
bool KICADFOOTPRINT::Read( SEXPR::SEXPR* aEntry )
{
if( !aEntry )
return false;
if( aEntry->IsList() )
{
size_t nc = aEntry->GetNumberOfChildren();
SEXPR::SEXPR* child = aEntry->GetChild( 0 );
std::string name = child->GetSymbol();
if( name != "module" && name != "footprint" )
{
std::ostringstream ostr;
ostr << "* BUG: module parser invoked for type '" << name << "'\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
bool result = true;
for( size_t i = 2; i < nc && result; ++i )
{
child = aEntry->GetChild( i );
std::string symname;
// skip the optional locked and/or placed attributes; due to the vagaries of the
// KiCad version of sexpr, the attribute may be a Symbol or a String
if( child->IsSymbol() || child->IsString() )
{
if( child->IsSymbol() )
symname = child->GetSymbol();
else if( child->IsString() )
symname = child->GetString();
if( symname == "locked" || symname == "placed" )
continue;
wxLogMessage( wxT( "* module descr in PCB file at line %d: unexpected keyword "
"'%s'\n" ),
child->GetLineNumber(),
symname.c_str() );
return false;
}
if( !child->IsList() )
{
wxLogMessage( wxT( "* corrupt module in PCB file at line %d\n" ),
child->GetLineNumber() );
return false;
}
symname = child->GetChild( 0 )->GetSymbol();
if( symname == "layer" )
result = parseLayer( child );
else if( symname == "at" )
result = parsePosition( child );
else if( symname == "attr" )
result = parseAttribute( child );
else if( symname == "fp_text" )
result = parseText( child );
else if( symname == "fp_arc" )
result = parseCurve( child, CURVE_ARC );
else if( symname == "fp_line" )
result = parseCurve( child, CURVE_LINE );
else if( symname == "fp_circle" )
result = parseCurve( child, CURVE_CIRCLE );
else if( symname == "fp_rect" )
result = parseRect( child );
else if( symname == "fp_curve" )
result = parseCurve( child, CURVE_BEZIER );
else if( symname == "pad" )
result = parsePad( child );
else if( symname == "model" )
result = parseModel( child );
}
if( !m_smd && !m_tht )
m_virtual = true;
return result;
}
std::ostringstream ostr;
ostr << "* data is not a valid PCB module\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
bool KICADFOOTPRINT::parseRect( SEXPR::SEXPR* data )
{
std::unique_ptr<KICADCURVE> rect = std::make_unique<KICADCURVE>();
if( !rect->Read( data, CURVE_LINE ) )
return false;
// reject any curves not on the Edge.Cuts layer
if( rect->GetLayer() != LAYER_EDGE )
return true;
KICADCURVE* top = new KICADCURVE( *rect );
KICADCURVE* right = new KICADCURVE( *rect );
KICADCURVE* bottom = new KICADCURVE( *rect );
KICADCURVE* left = new KICADCURVE( *rect );
top->m_end.y = right->m_start.y;
m_curves.push_back( top );
right->m_start.x = bottom->m_end.x;
m_curves.push_back( right );
bottom->m_start.y = left->m_end.y;
m_curves.push_back( bottom );
left->m_end.x = top->m_start.x;
m_curves.push_back( left );
return true;
}
bool KICADFOOTPRINT::parseModel( SEXPR::SEXPR* data )
{
KICADMODEL* mp = new KICADMODEL();
if( !mp->Read( data ) )
{
delete mp;
return false;
}
if( mp->Hide() )
delete mp;
else
m_models.push_back( mp );
return true;
}
bool KICADFOOTPRINT::parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType )
{
KICADCURVE* mp = new KICADCURVE();
if( !mp->Read( data, aCurveType ) )
{
delete mp;
return false;
}
// NOTE: for now we are only interested in glyphs on the outline layer
if( LAYER_EDGE != mp->GetLayer() )
{
delete mp;
return true;
}
m_curves.push_back( mp );
return true;
}
bool KICADFOOTPRINT::parseLayer( SEXPR::SEXPR* data )
{
SEXPR::SEXPR* val = data->GetChild( 1 );
std::string layername;
if( val->IsSymbol() )
layername = val->GetSymbol();
else if( val->IsString() )
layername = val->GetString();
else
{
std::ostringstream ostr;
ostr << "* corrupt module in PCB file (line ";
ostr << val->GetLineNumber() << "); layer cannot be parsed\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
int layerId = m_parent->GetLayerId( layername );
if( layerId == 31 )
m_side = LAYER_BOTTOM;
else
m_side = LAYER_TOP;
return true;
}
bool KICADFOOTPRINT::parsePosition( SEXPR::SEXPR* data )
{
return Get2DPositionAndRotation( data, m_position, m_rotation );
}
bool KICADFOOTPRINT::parseAttribute( SEXPR::SEXPR* data )
{
if( data->GetNumberOfChildren() < 2 )
{
std::ostringstream ostr;
ostr << "* corrupt module in PCB file (line ";
ostr << data->GetLineNumber() << "); attribute cannot be parsed\n";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child = data->GetChild( 1 );
std::string text;
if( child->IsSymbol() )
text = child->GetSymbol();
else if( child->IsString() )
text = child->GetString();
// The "virtual" attribute token is obsolete but kicad2step can still be run against older
// board files. It has been replaced with "exclude_from_bom" so any footprint with this
// attribute will behave the same as the "virtual" attribute.
if( text == "smd" )
m_smd = true;
else if( text == "through_hole" )
m_tht = true;
else if( text == "virtual" )
m_virtual = true;
else if( text == "exclude_from_bom" )
m_virtual = true;
return true;
}
bool KICADFOOTPRINT::parseText( SEXPR::SEXPR* data )
{
// we're only interested in the Reference Designator
if( data->GetNumberOfChildren() < 3 )
return true;
SEXPR::SEXPR* child = data->GetChild( 1 );
std::string text;
if( child->IsSymbol() )
text = child->GetSymbol();
else if( child->IsString() )
text = child->GetString();
if( text != "reference" )
return true;
child = data->GetChild( 2 );
if( child->IsSymbol() )
text = child->GetSymbol();
else if( child->IsString() )
text = child->GetString();
m_refdes = text;
return true;
}
bool KICADFOOTPRINT::parsePad( SEXPR::SEXPR* data )
{
KICADPAD* mp = new KICADPAD();
if( !mp->Read( data ) )
{
delete mp;
return false;
}
// NOTE: for now we only accept thru-hole pads
// for the MCAD description
if( !mp->IsThruHole() )
{
delete mp;
return true;
}
m_pads.push_back( mp );
return true;
}
bool KICADFOOTPRINT::ComposePCB( class PCBMODEL* aPCB, FILENAME_RESOLVER* resolver,
DOUBLET aOrigin, bool aComposeVirtual, bool aSubstituteModels )
{
// translate pads and curves to final position and append to PCB.
double dlim = (double)std::numeric_limits< float >::epsilon();
double vsin = sin( m_rotation );
double vcos = cos( m_rotation );
bool hasdata = false;
double posX = m_position.x - aOrigin.x;
double posY = m_position.y - aOrigin.y;
for( KICADCURVE* i : m_curves )
{
if( i->m_layer != LAYER_EDGE || CURVE_NONE == i->m_form )
continue;
KICADCURVE lcurve = *i;
lcurve.m_start.y = -lcurve.m_start.y;
lcurve.m_end.y = -lcurve.m_end.y;
lcurve.m_angle = -lcurve.m_angle;
if( m_rotation < -dlim || m_rotation > dlim )
{
double x = lcurve.m_start.x * vcos - lcurve.m_start.y * vsin;
double y = lcurve.m_start.x * vsin + lcurve.m_start.y * vcos;
lcurve.m_start.x = x;
lcurve.m_start.y = y;
x = lcurve.m_end.x * vcos - lcurve.m_end.y * vsin;
y = lcurve.m_end.x * vsin + lcurve.m_end.y * vcos;
lcurve.m_end.x = x;
lcurve.m_end.y = y;
}
lcurve.m_start.x += posX;
lcurve.m_start.y -= posY;
lcurve.m_end.x += posX;
lcurve.m_end.y -= posY;
if( aPCB->AddOutlineSegment( &lcurve ) )
hasdata = true;
}
for( KICADPAD* i : m_pads )
{
if( !i->IsThruHole() )
continue;
KICADPAD lpad = *i;
lpad.m_position.y = -lpad.m_position.y;
if( m_rotation < -dlim || m_rotation > dlim )
{
double x = lpad.m_position.x * vcos - lpad.m_position.y * vsin;
double y = lpad.m_position.x * vsin + lpad.m_position.y * vcos;
lpad.m_position.x = x;
lpad.m_position.y = y;
}
lpad.m_position.x += posX;
lpad.m_position.y -= posY;
if( aPCB->AddPadHole( &lpad ) )
hasdata = true;
}
if( m_virtual && !aComposeVirtual )
return hasdata;
DOUBLET newpos( posX, posY );
for( KICADMODEL* i : m_models )
{
wxString mname = wxString::FromUTF8Unchecked( i->m_modelname.c_str() );
if( mname.empty() )
continue;
std::vector<wxString> searchedPaths;
mname = resolver->ResolvePath( mname, wxEmptyString );
if( !wxFileName::FileExists( mname ) )
{
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
"File not found: %s\n" ),
m_refdes,
mname) );
continue;
}
std::string fname( mname.ToUTF8() );
try
{
if( aPCB->AddComponent( fname, m_refdes, LAYER_BOTTOM == m_side ? true : false,
newpos, m_rotation, i->m_offset, i->m_rotation, i->m_scale,
aSubstituteModels ) )
{
hasdata = true;
}
}
catch( const Standard_Failure& e)
{
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
"OpenCASCADE error: %s\n" ),
m_refdes,
e.GetMessageString() ) );
}
}
return hasdata;
}

View File

@ -1,81 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
#ifndef KICADFOOTPRINT_H
#define KICADFOOTPRINT_H
#include "base.h"
#include "kicadpcb.h"
#include <string>
#include <vector>
namespace SEXPR
{
class SEXPR;
}
class KICADPAD;
class KICADCURVE;
class KICADMODEL;
class PCBMODEL;
class FILENAME_RESOLVER;
class KICADFOOTPRINT
{
public:
KICADFOOTPRINT( KICADPCB* aParent );
virtual ~KICADFOOTPRINT();
bool Read( SEXPR::SEXPR* aEntry );
bool ComposePCB( class PCBMODEL* aPCB, FILENAME_RESOLVER* resolver,
DOUBLET aOrigin, bool aComposeVirtual = true, bool aSubstituteModels = true );
private:
bool parseModel( SEXPR::SEXPR* data );
bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType );
bool parseLayer( SEXPR::SEXPR* data );
bool parsePosition( SEXPR::SEXPR* data );
bool parseAttribute( SEXPR::SEXPR* data );
bool parseText( SEXPR::SEXPR* data );
bool parsePad( SEXPR::SEXPR* data );
bool parseRect( SEXPR::SEXPR* data );
KICADPCB* m_parent; // The parent KICADPCB, to know layer names
LAYERS m_side;
std::string m_refdes;
DOUBLET m_position;
double m_rotation; // rotation (radians)
bool m_smd; // true for a SMD component
bool m_tht; // true for a through-hole component
bool m_virtual; // true for a virtual (usually mechanical) component
std::vector< KICADPAD* > m_pads;
std::vector< KICADCURVE* > m_curves;
std::vector< KICADMODEL* > m_models;
};
#endif // KICADFOOTPRINT_H

View File

@ -1,124 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2016-2022 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
*/
#include "kicadmodel.h"
#include <sexpr/sexpr.h>
#include <wx/log.h>
#include <iostream>
#include <sstream>
KICADMODEL::KICADMODEL() :
m_hide( false ),
m_scale( 1.0, 1.0, 1.0 ),
m_offset( 0.0, 0.0, 0.0 ),
m_rotation( 0.0, 0.0, 0.0 )
{
}
KICADMODEL::~KICADMODEL()
{
}
bool KICADMODEL::Read( SEXPR::SEXPR* aEntry )
{
// form: ( pad N thru_hole shape (at x y {r}) (size x y) (drill {oval} x {y}) (layers X X X) )
int nchild = aEntry->GetNumberOfChildren();
if( nchild < 2 )
{
std::ostringstream ostr;
ostr << "* invalid model entry";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child = aEntry->GetChild( 1 );
if( child->IsSymbol() )
{
m_modelname = child->GetSymbol();
}
else if( child->IsString() )
{
m_modelname = child->GetString();
}
else
{
std::ostringstream ostr;
ostr << "* invalid model entry; invalid path";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
for( int i = 2; i < nchild; ++i )
{
child = aEntry->GetChild( i );
if( child->IsSymbol() && child->GetSymbol() == "hide" )
{
m_hide = true;
}
else if( child->IsList() )
{
std::string name = child->GetChild( 0 )->GetSymbol();
bool ret = true;
// Version 4.x and prior used 'at' parameter, which was specified in inches.
if( name == "at" )
{
ret = Get3DCoordinate( child->GetChild( 1 ), m_offset );
if( ret )
{
m_offset.x *= 25.4f;
m_offset.y *= 25.4f;
m_offset.z *= 25.4f;
}
}
else if( name == "offset" )
{
// From 5.x onwards, 3D model is provided in 'offset', which is in millimetres.
ret = Get3DCoordinate( child->GetChild( 1 ), m_offset );
}
else if( name == "scale" )
{
ret = Get3DCoordinate( child->GetChild( 1 ), m_scale );
}
else if( name == "rotate" )
{
ret = GetXYZRotation( child->GetChild( 1 ), m_rotation );
}
if( !ret )
return false;
}
}
return true;
}

View File

@ -1,50 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
*
* 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
*/
/**
* @file kicadmodel.h
* declares the 3D model object.
*/
#ifndef KICADMODEL_H
#define KICADMODEL_H
#include "base.h"
class KICADMODEL
{
public:
KICADMODEL();
virtual ~KICADMODEL();
bool Read( SEXPR::SEXPR* aEntry );
bool Hide() const { return m_hide; }
std::string m_modelname;
bool m_hide;
TRIPLET m_scale;
TRIPLET m_offset;
TRIPLET m_rotation;
};
#endif // KICADMODEL_H

View File

@ -1,192 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
#include "kicadpad.h"
#include <sexpr/sexpr.h>
#include <wx/log.h>
#include <iostream>
#include <sstream>
static const char bad_pad[] = "* corrupt module in PCB file; bad pad";
KICADPAD::KICADPAD()
{
m_rotation = 0.0;
m_thruhole = false;
m_drill.oval = false;
return;
}
KICADPAD::~KICADPAD()
{
return;
}
bool KICADPAD::Read( const SEXPR::SEXPR* aEntry )
{
// form: ( pad N thru_hole shape (at x y {r}) (size x y) (drill {oval} x {y}) (layers X X X) )
int nchild = aEntry->GetNumberOfChildren();
if( nchild < 2 )
{
std::ostringstream ostr;
ostr << bad_pad << " (line " << aEntry->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child;
for( int i = 1; i < nchild; ++i )
{
child = aEntry->GetChild( i );
if( child->IsSymbol() &&
( child->GetSymbol() == "thru_hole" || child->GetSymbol() == "np_thru_hole" ) )
{
m_thruhole = true;
continue;
}
if( child->IsList() )
{
std::string name = child->GetChild( 0 )->GetSymbol();
bool ret = true;
if( name == "drill" )
{
// ignore any drill info for SMD pads
if( m_thruhole )
ret = parseDrill( child );
}
else if( name == "at" )
{
ret = Get2DPositionAndRotation( child, m_position, m_rotation );
}
if( !ret )
return false;
}
}
return true;
}
bool KICADPAD::parseDrill( const SEXPR::SEXPR* aDrill )
{
// form: (drill {oval} X {Y})
const char bad_drill[] = "* corrupt pad in PCB file; bad drill";
int nchild = aDrill->GetNumberOfChildren();
if( nchild < 2 )
{
std::ostringstream ostr;
ostr << bad_drill << " (line " << aDrill->GetLineNumber() << ")";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
SEXPR::SEXPR* child = aDrill->GetChild( 1 );
int idx = 1;
m_drill.oval = false;
if( child->IsSymbol() )
{
if( child->GetSymbol() == "oval" )
{
m_drill.oval = true;
child = aDrill->GetChild( ++idx );
}
else
{
std::ostringstream ostr;
ostr << bad_drill << " (line " << child->GetLineNumber();
ostr << ") (unexpected symbol: ";
ostr << child->GetSymbol() << "), nchild = " << nchild;
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
}
double x;
if( child->IsDouble() )
x = child->GetDouble();
else if( child->IsInteger() )
x = (double) child->GetInteger();
else
{
std::ostringstream ostr;
ostr << bad_drill << " (line " << child->GetLineNumber();
ostr << ") (did not find X size)";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
m_drill.size.x = x;
m_drill.size.y = x;
if( ++idx == nchild || !m_drill.oval )
return true;
for( int i = idx; i < nchild; ++i )
{
child = aDrill->GetChild( i );
// NOTE: the Offset of the copper pad is stored in the drill string but since the copper
// is not needed in the MCAD model the offset is simply ignored.
if( !child->IsList() )
{
double y;
if( child->IsDouble() )
y = child->GetDouble();
else if( child->IsInteger() )
y = (double) child->GetInteger();
else
{
std::ostringstream ostr;
ostr << bad_drill << " (line " << child->GetLineNumber();
ostr << ") (did not find Y size)";
wxLogMessage( wxT( "%s\n" ), ostr.str().c_str() );
return false;
}
m_drill.size.y = y;
if( m_drill.size.y != m_drill.size.x )
m_drill.oval = true;
}
}
return true;
}

View File

@ -1,70 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
*/
/**
* @file kicadpad.h
* Declare the PAD description object.
*/
#ifndef KICADPAD_H
#define KICADPAD_H
#include <string>
#include <vector>
#include "base.h"
struct KICADDRILL
{
DOUBLET size;
bool oval;
};
class KICADPAD
{
public:
KICADPAD();
virtual ~KICADPAD();
bool Read( const SEXPR::SEXPR* aEntry );
bool IsThruHole() const
{
return m_thruhole;
}
private:
bool parseDrill( const SEXPR::SEXPR* aDrill );
public:
DOUBLET m_position;
double m_rotation; // rotation (radians)
KICADDRILL m_drill;
private:
bool m_thruhole;
};
#endif // KICADPAD_H

View File

@ -1,662 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2020-2022 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
*/
#include "kicadpcb.h"
#include "kicadcurve.h"
#include "kicadfootprint.h"
#include "oce_utils.h"
#include <sexpr/sexpr.h>
#include <sexpr/sexpr_parser.h>
#include <wx/wxcrtvararg.h>
#include <memory>
#include <string>
#include <wx_filename.h>
#include <pgm_base.h>
KICADPCB::KICADPCB( const wxString& aPcbName )
{
m_resolver.Set3DConfigDir( wxT( "" ) );
m_resolver.SetProgramBase( &Pgm() );
m_topSolderMask = wxColour( 15, 102, 15 );
m_bottomSolderMask = wxColour( 15, 102, 15 );
m_topSilk = wxColour( 240, 240, 240 );
m_bottomSilk = wxColour( 240, 240, 240 );
m_copperFinish = wxColour( 191, 155, 58 );
m_thickness = 1.6;
m_pcb_model = nullptr;
m_minDistance = MIN_DISTANCE;
m_useGridOrigin = false;
m_useDrillOrigin = false;
m_hasGridOrigin = false;
m_hasDrillOrigin = false;
m_pcbName = aPcbName;
}
KICADPCB::~KICADPCB()
{
for( KICADFOOTPRINT* i : m_footprints )
delete i;
for( KICADCURVE* i : m_curves )
delete i;
delete m_pcb_model;
return;
}
bool KICADPCB::ReadFile( const wxString& aFileName )
{
wxFileName fname( aFileName );
if( fname.GetExt() != "kicad_pcb" )
{
ReportMessage( wxString::Format( wxT( "expecting extension kicad_pcb, got %s\n" ),
fname.GetExt() ) );
return false;
}
if( !fname.FileExists() )
{
ReportMessage( wxString::Format( wxT( "No such file: %s\n" ), aFileName ) );
return false;
}
fname.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
m_filename = fname.GetFullPath();
try
{
SEXPR::PARSER parser;
std::string infile( fname.GetFullPath().ToUTF8() );
std::unique_ptr<SEXPR::SEXPR> data( parser.ParseFromFile( infile ) );
if( !data )
{
ReportMessage( wxString::Format( wxT( "No data in file: %s\n" ), aFileName ) );
return false;
}
if( !parsePCB( data.get() ) )
return false;
}
catch( std::exception& e )
{
ReportMessage( wxString::Format( wxT( "error reading file: %s\n%s\n" ),
aFileName,
e.what() ) );
return false;
}
catch( ... )
{
ReportMessage( wxString::Format( wxT( "unexpected exception while reading file: %s\n" ),
aFileName ) );
return false;
}
return true;
}
bool KICADPCB::WriteSTEP( const wxString& aFileName )
{
if( m_pcb_model )
return m_pcb_model->WriteSTEP( aFileName );
return false;
}
#ifdef SUPPORTS_IGES
bool KICADPCB::WriteIGES( const wxString& aFileName )
{
if( m_pcb_model )
return m_pcb_model->WriteIGES( aFileName );
return false;
}
#endif
bool KICADPCB::parsePCB( SEXPR::SEXPR* data )
{
if( nullptr == data )
return false;
if( data->IsList() )
{
size_t nc = data->GetNumberOfChildren();
SEXPR::SEXPR* child = data->GetChild( 0 );
std::string name = child->GetSymbol();
bool result = true;
for( size_t i = 1; i < nc && result; ++i )
{
child = data->GetChild( i );
if( !child->IsList() )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d)\n" ),
child->GetLineNumber() ) );
return false;
}
std::string symname( child->GetChild( 0 )->GetSymbol() );
if( symname == "general" )
result = result && parseGeneral( child );
else if( symname == "setup" )
result = result && parseSetup( child );
else if( symname == "layers" )
result = result && parseLayers( child );
else if( symname == "module" )
result = result && parseModule( child );
else if( symname == "footprint" )
result = result && parseModule( child );
else if( symname == "gr_arc" )
result = result && parseCurve( child, CURVE_ARC );
else if( symname == "gr_line" )
result = result && parseCurve( child, CURVE_LINE );
else if( symname == "gr_rect" )
result = result && parseRect( child );
else if( symname == "gr_poly" )
result = result && parsePolygon( child );
else if( symname == "gr_circle" )
result = result && parseCurve( child, CURVE_CIRCLE );
else if( symname == "gr_curve" )
result = result && parseCurve( child, CURVE_BEZIER );
}
return result;
}
ReportMessage( wxString::Format( wxT( "data is not a valid PCB file: %s\n" ), m_filename ) );
return false;
}
bool KICADPCB::parseGeneral( SEXPR::SEXPR* data )
{
size_t nc = data->GetNumberOfChildren();
SEXPR::SEXPR* child = nullptr;
for( size_t i = 1; i < nc; ++i )
{
child = data->GetChild( i );
if( !child->IsList() )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d)\n" ),
child->GetLineNumber() ) );
return false;
}
// at the moment only the thickness is of interest in the general section
if( child->GetChild( 0 )->GetSymbol() != "thickness" )
continue;
m_thickness = child->GetChild( 1 )->GetDouble();
return true;
}
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d)\n"
"no PCB thickness specified in general section\n" ),
child->GetLineNumber() ) );
return false;
}
bool KICADPCB::parseLayers( SEXPR::SEXPR* data )
{
size_t nc = data->GetNumberOfChildren();
SEXPR::SEXPR* child = nullptr;
// Read the layername and the corresponding layer id list:
for( size_t i = 1; i < nc; ++i )
{
child = data->GetChild( i );
if( !child->IsList() )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d)\n" ),
child->GetLineNumber() ) );
return false;
}
std::string ref;
if( child->GetChild( 1 )->IsSymbol() )
ref = child->GetChild( 1 )->GetSymbol();
else
ref = child->GetChild( 1 )->GetString();
m_layersNames[ref] = child->GetChild( 0 )->GetInteger();
}
return true;
}
bool KICADPCB::parseStackupLayer( SEXPR::SEXPR* data )
{
if( data->IsList() && data->GetNumberOfChildren() >= 3 )
{
size_t nc = data->GetNumberOfChildren();
SEXPR::SEXPR* child = nullptr;
std::string ref;
std::string value;
for( size_t i = 1; i < nc; ++i )
{
child = data->GetChild( i );
if( child->IsList() && child->GetChild( 0 )->GetSymbol() == "type" )
{
if( child->GetChild( 1 )->IsSymbol() )
ref = child->GetChild( 1 )->GetSymbol();
else
ref = child->GetChild( 1 )->GetString();
}
else if( child->IsList() && child->GetChild( 0 )->GetSymbol() == "color" )
{
if( child->GetChild( 1 )->IsSymbol() )
value = child->GetChild( 1 )->GetSymbol();
else
value = child->GetChild( 1 )->GetString();
}
}
if( !value.empty() )
{
wxString colorName( value );
wxColour color;
if( colorName.StartsWith( wxT( "#" ) ) )
color = wxColour( colorName.Left( 7 ) /* drop alpha component */ );
else if( colorName == wxT( "Green" ) )
color = wxColour( 20, 51, 36 );
else if( colorName == wxT( "Light Green" ) )
color = wxColour( 91, 168, 12);
else if( colorName == wxT( "Saturated Green" ) )
color = wxColour( 13, 104, 11 );
else if( colorName == wxT( "Red" ) )
color = wxColour( 181, 19, 21 );
else if( colorName == wxT( "Light Red" ) )
color = wxColour( 210, 40, 14 );
else if( colorName == wxT( "Red/Orange" ) )
color = wxColour( 239, 53, 41 );
else if( colorName == wxT( "Blue" ) )
color = wxColour( 2, 59, 162 );
else if( colorName == wxT( "Light Blue 1" ) )
color = wxColour( 54, 79, 116 );
else if( colorName == wxT( "Light Blue 2" ) )
color = wxColour( 61, 85, 130 );
else if( colorName == wxT( "Green/Blue" ) )
color = wxColour( 21, 70, 80 );
else if( colorName == wxT( "Black" ) )
color = wxColour( 11, 11, 11 );
else if( colorName == wxT( "White" ) )
color = wxColour( 245, 245, 245 );
else if( colorName == wxT( "Purple" ) )
color = wxColour( 32, 2, 53 );
else if( colorName == wxT( "Light Purple" ) )
color = wxColour( 119, 31, 91 );
else if( colorName == wxT( "Yellow" ) )
color = wxColour( 194, 195, 0 );
if( ref == "Top Silk Screen" )
m_topSilk = color;
else if( ref == "Top Solder Mask" )
m_topSolderMask = color;
else if( ref == "Bottom Silk Screen" )
m_bottomSilk = color;
else if( ref == "Bottom Solder Mask" )
m_bottomSolderMask = color;
}
}
return true;
}
bool KICADPCB::parseStackup( SEXPR::SEXPR* data )
{
size_t nc = data->GetNumberOfChildren();
SEXPR::SEXPR* child = nullptr;
for( size_t i = 1; i < nc; ++i )
{
child = data->GetChild( i );
if( !child->IsList() )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d)\n" ),
child->GetLineNumber() ) );
return false;
}
std::string ref;
if( child->GetChild( 0 )->GetSymbol() == "layer" )
{
parseStackupLayer( child );
}
else if( child->GetChild( 0 )->GetSymbol() == "copper_finish" )
{
if( child->GetChild( 1 )->IsSymbol() )
ref = child->GetChild( 1 )->GetSymbol();
else
ref = child->GetChild( 1 )->GetString();
wxString finishName( ref );
if( finishName.EndsWith( wxT( "OSP" ) ) )
{
m_copperFinish = wxColour( 184, 115, 50 );
}
else if( finishName.EndsWith( wxT( "IG" ) )
|| finishName.EndsWith( wxT( "gold" ) ) )
{
m_copperFinish = wxColour( 178, 156, 0 );
}
else if( finishName.StartsWith( wxT( "HAL" ) )
|| finishName.StartsWith( wxT( "HASL" ) )
|| finishName.EndsWith( wxT( "tin" ) )
|| finishName.EndsWith( wxT( "nickel" ) ) )
{
m_copperFinish = wxColour( 160, 160, 160 );
}
else if( finishName.EndsWith( wxT( "silver" ) ) )
{
m_copperFinish = wxColour( 213, 213, 213 );
}
}
}
return true;
}
int KICADPCB::GetLayerId( std::string& aLayerName )
{
int lid = -1;
auto item = m_layersNames.find( aLayerName );
if( item != m_layersNames.end() )
lid = item->second;
return lid;
}
bool KICADPCB::parseSetup( SEXPR::SEXPR* data )
{
size_t nc = data->GetNumberOfChildren();
SEXPR::SEXPR* child = nullptr;
for( size_t i = 1; i < nc; ++i )
{
child = data->GetChild( i );
if( !child->IsList() )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d)\n" ),
child->GetLineNumber() ) );
return false;
}
// at the moment only the Grid and Drill origins are of interest in the setup section
if( child->GetChild( 0 )->GetSymbol() == "grid_origin" )
{
if( child->GetNumberOfChildren() != 3 )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d): grid_origin has "
"%d children (expected: 3)\n" ),
child->GetLineNumber(),
child->GetNumberOfChildren() ) );
return false;
}
m_gridOrigin.x = child->GetChild( 1 )->GetDouble();
m_gridOrigin.y = child->GetChild( 2 )->GetDouble();
m_hasGridOrigin = true;
}
else if( child->GetChild( 0 )->GetSymbol() == "aux_axis_origin" )
{
if( child->GetNumberOfChildren() != 3 )
{
ReportMessage( wxString::Format( wxT( "corrupt PCB file (line %d): aux_axis_origin "
"has %d children (expected: 3)\n" ),
child->GetLineNumber(),
child->GetNumberOfChildren() ) );
return false;
}
m_drillOrigin.x = child->GetChild( 1 )->GetDouble();
m_drillOrigin.y = child->GetChild( 2 )->GetDouble();
m_hasDrillOrigin = true;
}
else if( child->GetChild( 0 )->GetSymbol() == "stackup" )
{
parseStackup( child );
}
}
return true;
}
bool KICADPCB::parseModule( SEXPR::SEXPR* data )
{
KICADFOOTPRINT* footprint = new KICADFOOTPRINT( this );
if( !footprint->Read( data ) )
{
delete footprint;
return false;
}
m_footprints.push_back( footprint );
return true;
}
bool KICADPCB::parseRect( SEXPR::SEXPR* data )
{
KICADCURVE* rect = new KICADCURVE();
if( !rect->Read( data, CURVE_LINE ) )
{
delete rect;
return false;
}
// reject any curves not on the Edge.Cuts layer
if( rect->GetLayer() != LAYER_EDGE )
{
delete rect;
return true;
}
KICADCURVE* top = new KICADCURVE( *rect );
KICADCURVE* right = new KICADCURVE( *rect );
KICADCURVE* bottom = new KICADCURVE( *rect );
KICADCURVE* left = new KICADCURVE( *rect );
delete rect;
top->m_end.y = right->m_start.y;
m_curves.push_back( top );
right->m_start.x = bottom->m_end.x;
m_curves.push_back( right );
bottom->m_start.y = left->m_end.y;
m_curves.push_back( bottom );
left->m_end.x = top->m_start.x;
m_curves.push_back( left );
return true;
}
bool KICADPCB::parsePolygon( SEXPR::SEXPR* data )
{
std::unique_ptr<KICADCURVE> poly = std::make_unique<KICADCURVE>();
if( !poly->Read( data, CURVE_POLYGON ) )
return false;
// reject any curves not on the Edge.Cuts layer
if( poly->GetLayer() != LAYER_EDGE )
return true;
auto pts = poly->m_poly;
for( std::size_t ii = 1; ii < pts.size(); ++ii )
{
KICADCURVE* seg = new KICADCURVE();
seg->m_form = CURVE_LINE;
seg->m_layer = poly->GetLayer();
seg->m_start = pts[ii - 1];
seg->m_end = pts[ii];
m_curves.push_back( seg );
}
KICADCURVE* seg = new KICADCURVE();
seg->m_form = CURVE_LINE;
seg->m_layer = poly->GetLayer();
seg->m_start = pts.back();
seg->m_end = pts.front();
m_curves.push_back( seg );
return true;
}
bool KICADPCB::parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType )
{
KICADCURVE* curve = new KICADCURVE();
if( !curve->Read( data, aCurveType ) )
{
delete curve;
return false;
}
// reject any curves not on the Edge.Cuts layer
if( curve->GetLayer() != LAYER_EDGE )
{
delete curve;
return true;
}
m_curves.push_back( curve );
return true;
}
bool KICADPCB::ComposePCB( bool aComposeVirtual, bool aSubstituteModels )
{
if( m_pcb_model )
return true;
if( m_footprints.empty() && m_curves.empty() )
{
ReportMessage( wxT( "Error: no PCB data (no footprint, no outline) to render\n" ) );
return false;
}
DOUBLET origin;
// Determine the coordinate system reference:
// Precedence of reference point is Drill Origin > Grid Origin > User Offset
if( m_useDrillOrigin && m_hasDrillOrigin )
origin = m_drillOrigin;
else if( m_useGridOrigin && m_hasDrillOrigin )
origin = m_gridOrigin;
else
origin = m_origin;
m_pcb_model = new PCBMODEL( m_pcbName );
// TODO: Handle when top & bottom soldermask colours are different...
m_pcb_model->SetBoardColor( m_topSolderMask.Red() / 255.0,
m_topSolderMask.Green() / 255.0,
m_topSolderMask.Blue() / 255.0 );
m_pcb_model->SetPCBThickness( m_thickness );
m_pcb_model->SetMinDistance( m_minDistance );
for( KICADCURVE* i : m_curves )
{
if( CURVE_NONE == i->m_form || LAYER_EDGE != i->m_layer )
continue;
// adjust the coordinate system
// Note: we negate the Y coordinates due to the fact in Pcbnew the Y axis
// is from top to bottom.
KICADCURVE lcurve = *i;
lcurve.m_start.y = -( lcurve.m_start.y - origin.y );
lcurve.m_end.y = -( lcurve.m_end.y - origin.y );
lcurve.m_start.x -= origin.x;
lcurve.m_end.x -= origin.x;
// used in bezier curves:
lcurve.m_bezierctrl1.y = -( lcurve.m_bezierctrl1.y - origin.y );
lcurve.m_bezierctrl1.x -= origin.x;
lcurve.m_bezierctrl2.y = -( lcurve.m_bezierctrl2.y - origin.y );
lcurve.m_bezierctrl2.x -= origin.x;
if( CURVE_ARC == lcurve.m_form )
lcurve.m_angle = -lcurve.m_angle;
m_pcb_model->AddOutlineSegment( &lcurve );
}
for( KICADFOOTPRINT* i : m_footprints )
i->ComposePCB( m_pcb_model, &m_resolver, origin, aComposeVirtual, aSubstituteModels );
ReportMessage( wxT( "Create PCB solid model\n" ) );
if( !m_pcb_model->CreatePCB() )
{
ReportMessage( wxT( "could not create PCB solid model\n" ) );
delete m_pcb_model;
m_pcb_model = nullptr;
return false;
}
return true;
}

View File

@ -1,135 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021-2022 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
*/
/**
* @file kicadpcb.h
* declares the main PCB object
*/
#ifndef KICADPCB_H
#define KICADPCB_H
#include <wx/string.h>
#include <wx/colour.h>
#include <string>
#include <vector>
#include <filename_resolver.h>
#include "base.h"
#ifdef SUPPORTS_IGES
#undef SUPPORTS_IGES
#endif
extern void ReportMessage( const wxString& aMessage );
namespace SEXPR
{
class SEXPR;
}
class KICADFOOTPRINT;
class KICADCURVE;
class PCBMODEL;
class KICADPCB
{
public:
KICADPCB( const wxString& aPcbName );
virtual ~KICADPCB();
int GetLayerId( std::string& aLayerName );
void SetOrigin( double aXOrigin, double aYOrigin )
{
m_origin.x = aXOrigin;
m_origin.y = aYOrigin;
}
void UseGridOrigin( bool aUseOrigin )
{
m_useGridOrigin = aUseOrigin;
}
void UseDrillOrigin( bool aUseOrigin )
{
m_useDrillOrigin = aUseOrigin;
}
void SetMinDistance( double aDistance )
{
m_minDistance = aDistance;
}
bool ReadFile( const wxString& aFileName );
bool ComposePCB( bool aComposeVirtual = true, bool aSubstituteModels = true );
bool WriteSTEP( const wxString& aFileName );
#ifdef SUPPORTS_IGES
bool WriteIGES( const wxString& aFileName );
#endif
private:
bool parsePCB( SEXPR::SEXPR* data );
bool parseGeneral( SEXPR::SEXPR* data );
bool parseSetup( SEXPR::SEXPR* data );
bool parseStackup( SEXPR::SEXPR* data );
bool parseStackupLayer( SEXPR::SEXPR* data );
bool parseLayers( SEXPR::SEXPR* data );
bool parseModule( SEXPR::SEXPR* data );
bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType );
bool parseRect( SEXPR::SEXPR* data );
bool parsePolygon( SEXPR::SEXPR* data );
private:
FILENAME_RESOLVER m_resolver;
wxString m_filename;
PCBMODEL* m_pcb_model;
DOUBLET m_origin;
DOUBLET m_gridOrigin;
DOUBLET m_drillOrigin;
bool m_useGridOrigin;
bool m_useDrillOrigin;
bool m_hasGridOrigin; // indicates origin found in source file
bool m_hasDrillOrigin; // indicates origin found in source file
// minimum distance between points to treat them as separate entities (mm)
double m_minDistance;
// the names of layers in use, and the internal layer ID
std::map<std::string, int> m_layersNames;
// PCB parameters/entities
double m_thickness;
wxColour m_topSolderMask;
wxColour m_bottomSolderMask; // Not currently used.
wxColour m_topSilk; // Not currently used.
wxColour m_bottomSilk; // Not currently used.
wxColour m_copperFinish; // Not currently used.
std::vector<KICADFOOTPRINT*> m_footprints;
std::vector<KICADCURVE*> m_curves;
wxString m_pcbName;
};
#endif // KICADPCB_H

View File

@ -30,9 +30,6 @@
#include <string>
#include <utility>
#include <vector>
#include "base.h"
#include "kicadpcb.h"
#include "kicadcurve.h"
#include <BRepBuilderAPI_MakeWire.hxx>
#include <TDocStd_Document.hxx>
@ -41,65 +38,34 @@
#include <TopoDS_Shape.hxx>
#include <TopoDS_Edge.hxx>
#include <math/vector2d.h>
#include <math/vector3.h>
#include <geometry/shape_poly_set.h>
///< Default minimum distance between points to treat them as separate ones (mm)
static constexpr double STEPEXPORT_MIN_DISTANCE = 0.01;
static constexpr double STEPEXPORT_MIN_ACCEPTABLE_DISTANCE = 0.001;
class PAD;
typedef std::pair< std::string, TDF_Label > MODEL_DATUM;
typedef std::map< std::string, TDF_Label > MODEL_MAP;
class KICADPAD;
extern void ReportMessage( const wxString& aMessage );
class OUTLINE
class STEP_PCB_MODEL
{
public:
OUTLINE();
virtual ~OUTLINE();
void Clear();
// attempt to add a curve to the outline; on success returns true
bool AddSegment( const KICADCURVE& aCurve );
bool IsClosed()
{
return m_closed;
}
void SetMinSqDistance( double aDistance )
{
m_minDistance2 = aDistance;
}
bool MakeShape( TopoDS_Shape& aShape, double aThickness );
private:
bool addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBLET& aLastPoint );
bool testClosed( const KICADCURVE& aFrontCurve, const KICADCURVE& aBackCurve );
public:
std::list< KICADCURVE > m_curves; // list of contiguous segments
private:
bool m_closed; // set true if the loop is closed
double m_minDistance2; // min squared distance to treat points as separate entities (mm)
};
class PCBMODEL
{
public:
PCBMODEL( const wxString& aPcbName );
virtual ~PCBMODEL();
// add an outline segment (must be in final position)
bool AddOutlineSegment( KICADCURVE* aCurve );
STEP_PCB_MODEL( const wxString& aPcbName );
virtual ~STEP_PCB_MODEL();
// add a pad hole or slot (must be in final position)
bool AddPadHole( const KICADPAD* aPad );
bool AddPadHole( const PAD* aPad );
// add a component at the given position and orientation
bool AddComponent( const std::string& aFileName, const std::string& aRefDes,
bool aBottom, DOUBLET aPosition, double aRotation,
TRIPLET aOffset, TRIPLET aOrientation, TRIPLET aScale,
bool aSubstituteModels = true );
bool AddComponent( const std::string& aFileName, const std::string& aRefDes, bool aBottom,
VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
VECTOR3D aOrientation, VECTOR3D aScale, bool aSubstituteModels = true );
void SetBoardColor( double r, double g, double b );
@ -112,8 +78,12 @@ public:
// Set the minimum distance (in mm) to consider 2 points have the same coordinates
void SetMinDistance( double aDistance );
void SetMaxError( int aMaxError ) { m_maxError = aMaxError; }
// create the PCB model using the current outlines and drill holes
bool CreatePCB();
bool CreatePCB( SHAPE_POLY_SET& aOutline );
bool MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& chain, double aThickness );
#ifdef SUPPORTS_IGES
// write the assembly model in IGES format
@ -136,17 +106,17 @@ private:
* @param aErrorMessage (can be nullptr) is an error message to be displayed on error.
* @return true if successfully loaded, false on error.
*/
bool getModelLabel( const std::string& aFileNameUTF8, TRIPLET aScale, TDF_Label& aLabel,
bool getModelLabel( const std::string& aFileNameUTF8, VECTOR3D aScale, TDF_Label& aLabel,
bool aSubstituteModels, wxString* aErrorMessage = nullptr );
bool getModelLocation( bool aBottom, DOUBLET aPosition, double aRotation, TRIPLET aOffset,
TRIPLET aOrientation, TopLoc_Location& aLocation );
bool getModelLocation( bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
VECTOR3D aOrientation, TopLoc_Location& aLocation );
bool readIGES( Handle( TDocStd_Document )& m_doc, const char* fname );
bool readSTEP( Handle( TDocStd_Document )& m_doc, const char* fname );
TDF_Label transferModel( Handle( TDocStd_Document )& source,
Handle( TDocStd_Document )& dest, TRIPLET aScale );
TDF_Label transferModel( Handle( TDocStd_Document )& source, Handle( TDocStd_Document ) & dest,
VECTOR3D aScale );
Handle( XCAFApp_Application ) m_app;
Handle( TDocStd_Document ) m_doc;
@ -163,13 +133,13 @@ private:
double m_minx; // leftmost curve point
double m_minDistance2; // minimum squared distance between items (mm)
std::list<KICADCURVE>::iterator m_mincurve; // iterator to the leftmost curve
std::list<KICADCURVE> m_curves;
std::vector<TopoDS_Shape> m_cutouts;
/// Name of the PCB, which will most likely be the file name of the path.
wxString m_pcbName;
int m_maxError;
};
#endif // OCE_VIS_OCE_UTILS_H

View File

@ -39,6 +39,7 @@
#include <pcb_item_containers.h>
#include <fp_text.h>
#include <functional>
#include <math/vector3.h>
class LINE_READER;
class EDA_3D_CANVAS;
@ -87,11 +88,6 @@ public:
{
}
struct VECTOR3D
{
double x, y, z;
};
VECTOR3D m_Scale; ///< 3D model scaling factor (dimensionless)
VECTOR3D m_Rotation; ///< 3D model rotation (degrees)
VECTOR3D m_Offset; ///< 3D model offset (mm)

View File

@ -19,7 +19,6 @@
*/
#include "pcbnew_jobs_handler.h"
#include <kicad2step.h>
#include <jobs/job_export_pcb_gerber.h>
#include <jobs/job_export_pcb_drill.h>
#include <jobs/job_export_pcb_dxf.h>
@ -32,6 +31,7 @@
#include <plotters/plotter_gerber.h>
#include <plotters/plotters_pslike.h>
#include <exporters/place_file_exporter.h>
#include <exporters/step/exporter_step.h>
#include "gerber_placefile_writer.h"
#include <pgm_base.h>
#include <pcbplot.h>
@ -65,23 +65,39 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
if( aStepJob == nullptr )
return CLI::EXIT_CODES::ERR_UNKNOWN;
KICAD2MCAD_PRMS params;
if( aJob->IsCli() )
wxPrintf( _( "Loading board\n" ) );
BOARD* brd = LoadBoard( aStepJob->m_filename );
if( aStepJob->m_outputFile.IsEmpty() )
{
wxFileName fn = brd->GetFileName();
fn.SetName( fn.GetName() );
fn.SetExt( wxS( "step" ) );
aStepJob->m_outputFile = fn.GetFullName();
}
EXPORTER_STEP_PARAMS params;
params.m_includeExcludedBom = aStepJob->m_includeExcludedBom;
params.m_minDistance = aStepJob->m_minDistance;
params.m_overwrite = aStepJob->m_overwrite;
params.m_substModels = aStepJob->m_substModels;
params.m_origin = VECTOR2D( aStepJob->m_xOrigin, aStepJob->m_yOrigin );
params.m_useDrillOrigin = aStepJob->m_useDrillOrigin;
params.m_useGridOrigin = aStepJob->m_useGridOrigin;
params.m_overwrite = aStepJob->m_overwrite;
params.m_includeVirtual = aStepJob->m_includeVirtual;
params.m_filename = aStepJob->m_filename;
params.m_outputFile = aStepJob->m_outputFile;
params.m_xOrigin = aStepJob->m_xOrigin;
params.m_yOrigin = aStepJob->m_yOrigin;
params.m_minDistance = aStepJob->m_minDistance;
params.m_substModels = aStepJob->m_substModels;
params.m_boardOnly = aStepJob->m_boardOnly;
// we might need the lifetime of the converter to continue until frame destruction
// due to the gui parameter
KICAD2STEP* converter = new KICAD2STEP( params );
EXPORTER_STEP stepExporter( brd, params );
stepExporter.m_outputFile = aStepJob->m_outputFile;
return converter->Run();
if( !stepExporter.Export() )
{
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
return CLI::EXIT_CODES::OK;
}

View File

@ -452,7 +452,7 @@ struct AMODEL
wxString id;
bool isEmbedded;
FP_3DMODEL::VECTOR3D rotation;
VECTOR3D rotation;
explicit AMODEL( ALTIUM_PARSER& aReader );
};
@ -564,8 +564,8 @@ struct ACOMPONENTBODY6
wxString modelId;
bool modelIsEmbedded;
FP_3DMODEL::VECTOR3D modelPosition;
FP_3DMODEL::VECTOR3D modelRotation;
VECTOR3D modelPosition;
VECTOR3D modelRotation;
double rotation;
double bodyOpacity;

View File

@ -25,4 +25,3 @@ add_subdirectory( gerbview )
add_subdirectory( eeschema )
add_subdirectory( libs )
add_subdirectory( pcbnew )
add_subdirectory( utils/kicad2step )

View File

@ -51,5 +51,33 @@ BOOST_AUTO_TEST_CASE( test_dot_product, *boost::unit_test::tolerance( 0.000001 )
BOOST_CHECK( v1.Dot( v2 ) == 1 );
}
BOOST_AUTO_TEST_CASE( test_equality_ops, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR3I v1( 1, 1, 1 );
VECTOR3I v2( 2, 2, 2 );
VECTOR3I v3( 1, 1, 1 );
BOOST_CHECK( v1 == v3 );
BOOST_CHECK( v1 != v2 );
}
BOOST_AUTO_TEST_CASE( test_scalar_multiply, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR3I v1( 1, 1, 1 );
v1 *= 5;
BOOST_CHECK( v1 == VECTOR3( 5, 5, 5 ) );
}
BOOST_AUTO_TEST_CASE( test_scalar_divide, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR3I v1( 5, 5, 5 );
v1 /= 5;
BOOST_CHECK( v1 == VECTOR3( 1, 1, 1 ) );
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -1,63 +0,0 @@
# This program source code file is part of KiCad, a free EDA CAD application.
#
# Copyright (C) 2019 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
# kicad2step s-expr handling routines
if( NOT TARGET kicad2step_lib )
# Can't build this test without the underlying library
return()
endif()
set( K2S_TEST_SRCS
test_module.cpp
pcb/test_base.cpp
)
if( MINGW )
list( APPEND K2S_TEST_SRCS ${CMAKE_SOURCE_DIR}/common/streamwrapper.cpp )
endif( MINGW )
if( WIN32 )
# We want to declare a resource manifest on Windows to enable UTF8 mode
# Without UTF8 mode, some random IO tests may fail, we set the active code page on normal kicad to UTF8 as well
if( MINGW )
# QA_KICAD2STEP_RESOURCES variable is set by the macro.
mingw_resource_compiler( qa_kicad2step )
else()
set( QA_KICAD2STEP_RESOURCES ${CMAKE_SOURCE_DIR}/resources/msw/qa_kicad2step.rc )
endif()
endif()
add_executable( qa_kicad2step
${K2S_TEST_SRCS}
${QA_KICAD2STEP_RESOURCES} )
target_link_libraries( qa_kicad2step
kicad2step_lib
qa_utils
${wxWidgets_LIBRARIES}
)
target_include_directories( qa_sexpr PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
kicad_add_boost_test( qa_kicad2step qa_kicad2step )

View File

@ -1,202 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019 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
*/
/**
* @file
* Test suite for PCB "base" sexpr parsing
*/
#include <qa_utils/wx_utils/unit_test_utils.h>
// Code under test
#include <pcb/base.h>
#include <sexpr/sexpr_parser.h>
#include <cmath>
namespace BOOST_TEST_PRINT_NAMESPACE_OPEN
{
template <>
struct print_log_value<DOUBLET>
{
inline void operator()( std::ostream& os, DOUBLET const& o )
{
os << "DOUBLET[ " << o.x << ", " << o.y << " ]";
}
};
}
BOOST_TEST_PRINT_NAMESPACE_CLOSE
/**
* Radians from degrees
*/
constexpr double DegToRad( double aDeg )
{
return aDeg * M_PI / 180.0;
}
class TEST_PCB_BASE_FIXTURE
{
public:
SEXPR::PARSER m_parser;
};
/**
* Declare the test suite
*/
BOOST_FIXTURE_TEST_SUITE( PcbBase, TEST_PCB_BASE_FIXTURE )
struct TEST_2D_POS_ROT
{
std::string m_sexp;
bool m_valid;
DOUBLET m_exp_pos;
double m_exp_rot;
};
/**
* Test the Get2DPositionAndRotation function
*/
BOOST_AUTO_TEST_CASE( SexprTo2DPosAndRot )
{
const std::vector<TEST_2D_POS_ROT> cases = {
{
"(at 0 0 0)",
true,
{ 0.0, 0.0 },
0.0,
},
{
"(at 3.14 4.12 90.4)",
true,
{ 3.14, 4.12 },
DegToRad( 90.4 ),
},
{
"(at 3.14)", // no enough params
false,
{},
{},
},
{
"(att 3.14 4.12 90.4)", // bad symbol name
false,
{},
{},
},
};
for( const auto& c : cases )
{
BOOST_TEST_CONTEXT( c.m_sexp )
{
DOUBLET gotPos;
double gotRot;
std::unique_ptr<SEXPR::SEXPR> sexpr( m_parser.Parse( c.m_sexp ) );
const bool ret = Get2DPositionAndRotation( sexpr.get(), gotPos, gotRot );
BOOST_CHECK_EQUAL( ret, c.m_valid );
if( !ret )
continue;
const double tolPercent = 0.00001; // seems small enough
BOOST_CHECK_CLOSE( gotPos.x, c.m_exp_pos.x, tolPercent );
BOOST_CHECK_CLOSE( gotPos.y, c.m_exp_pos.y, tolPercent );
BOOST_CHECK_CLOSE( gotRot, c.m_exp_rot, tolPercent );
}
}
}
struct TEST_LAYER_NAME
{
std::string m_sexp;
bool m_valid;
std::string m_layer;
};
/**
* Test the layer list parser.
*/
BOOST_AUTO_TEST_CASE( TestGetLayerName )
{
const std::vector<TEST_LAYER_NAME> cases = {
{
// Quoted string - OK
"(layer \"Edge.Cuts\")",
true,
"Edge.Cuts",
},
{
// Old KiCads exported without quotes (so, as symbols)
"(layer Edge.Cuts)",
true,
"Edge.Cuts",
},
{
// No atoms
"(layer)",
false,
{},
},
{
// Too many atoms in list
"(layer \"Edge.Cuts\" 2)",
false,
{},
},
{
// Wrong atom type
"(layer 2)",
false,
{},
},
};
for( const auto& c : cases )
{
BOOST_TEST_CONTEXT( c.m_sexp )
{
std::unique_ptr<SEXPR::SEXPR> sexpr( m_parser.Parse( c.m_sexp ) );
const std::optional<std::string> ret = GetLayerName( *sexpr );
BOOST_CHECK_EQUAL( !!ret, c.m_valid );
if( ret )
{
BOOST_CHECK_EQUAL( *ret, c.m_layer );
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -1,48 +0,0 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019 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
*/
/**
* Main file for the Eeschema tests to be compiled
*/
#include <boost/test/unit_test.hpp>
#include <wx/init.h>
bool init_unit_test()
{
boost::unit_test::framework::master_test_suite().p_name.value = "S-expression module tests";
return wxInitialize();
}
int main( int argc, char* argv[] )
{
int ret = boost::unit_test::unit_test_main( &init_unit_test, argc, argv );
// This causes some glib warnings on GTK3 (http://trac.wxwidgets.org/ticket/18274)
// but without it, Valgrind notices a lot of leaks from WX
wxUninitialize();
return ret;
}