2022-10-08 22:00:05 +00:00
|
|
|
/*
|
|
|
|
* 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>
|
2024-04-22 00:39:43 +00:00
|
|
|
* Copyright (C) 2016-2024 KiCad Developers, see AUTHORS.txt for contributors.
|
2022-10-08 22:00:05 +00:00
|
|
|
*
|
|
|
|
* 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"
|
2024-04-24 15:28:44 +00:00
|
|
|
#include <advanced_config.h>
|
2022-10-08 22:00:05 +00:00
|
|
|
#include <board.h>
|
|
|
|
#include <board_design_settings.h>
|
|
|
|
#include <footprint.h>
|
2024-05-19 01:28:43 +00:00
|
|
|
#include <pcb_textbox.h>
|
2023-03-02 18:38:19 +00:00
|
|
|
#include <pcb_track.h>
|
2023-03-06 15:35:54 +00:00
|
|
|
#include <pcb_shape.h>
|
2023-03-02 18:38:19 +00:00
|
|
|
#include <pad.h>
|
2023-08-21 02:10:43 +00:00
|
|
|
#include <zone.h>
|
2022-10-08 22:00:05 +00:00
|
|
|
#include <fp_lib_table.h>
|
2023-03-02 18:38:19 +00:00
|
|
|
#include "step_pcb_model.h"
|
2022-10-08 22:00:05 +00:00
|
|
|
|
|
|
|
#include <pgm_base.h>
|
|
|
|
#include <base_units.h>
|
|
|
|
#include <filename_resolver.h>
|
2023-01-02 23:34:13 +00:00
|
|
|
#include <trace_helpers.h>
|
2023-09-28 03:15:54 +00:00
|
|
|
#include <project_pcb.h>
|
2024-04-22 01:05:46 +00:00
|
|
|
#include <wildcards_and_files_ext.h>
|
2022-10-08 22:00:05 +00:00
|
|
|
|
|
|
|
#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>
|
2023-03-29 16:53:28 +00:00
|
|
|
#include <wx/log.h>
|
2023-09-07 11:22:10 +00:00
|
|
|
#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
|
2022-10-08 22:00:05 +00:00
|
|
|
|
2022-11-13 07:18:10 +00:00
|
|
|
#define OCC_VERSION_MIN 0x070500
|
|
|
|
|
|
|
|
#if OCC_VERSION_HEX < OCC_VERSION_MIN
|
|
|
|
#include <Message_Messenger.hxx>
|
|
|
|
#endif
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2023-01-02 23:34:13 +00:00
|
|
|
if( theGravity >= Message_Warning
|
|
|
|
|| ( wxLog::IsAllowedTraceMask( traceKiCad2Step ) && theGravity == Message_Info ) )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
ReportMessage( theString.ToCString() );
|
|
|
|
|
|
|
|
#if OCC_VERSION_HEX < OCC_VERSION_MIN
|
|
|
|
if( theToPutEol )
|
|
|
|
ReportMessage( wxT( "\n" ) );
|
|
|
|
#else
|
|
|
|
ReportMessage( wxT( "\n" ) );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2023-01-02 23:34:13 +00:00
|
|
|
if( theGravity == Message_Warning )
|
|
|
|
m_converter->SetWarn();
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
if( theGravity >= Message_Alarm )
|
|
|
|
m_converter->SetError();
|
|
|
|
|
|
|
|
if( theGravity == Message_Fail )
|
|
|
|
m_converter->SetFail();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
EXPORTER_STEP* m_converter;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-08-19 15:40:45 +00:00
|
|
|
wxString EXPORTER_STEP_PARAMS::GetDefaultExportExtension()
|
|
|
|
{
|
|
|
|
switch( m_format )
|
|
|
|
{
|
2024-02-05 11:05:31 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::STEP: return wxS( "step" );
|
2024-04-17 14:31:07 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::BREP: return wxS( "brep" );
|
2024-04-25 22:53:39 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::XAO: return wxS( "xao" );
|
2024-02-05 11:05:31 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::GLB: return wxS( "glb" );
|
|
|
|
default: return wxEmptyString; // shouldn't happen
|
2023-08-19 15:40:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString EXPORTER_STEP_PARAMS::GetFormatName()
|
|
|
|
{
|
|
|
|
switch( m_format )
|
|
|
|
{
|
|
|
|
// honestly these names shouldn't be translated since they are mostly industry standard acronyms
|
2024-02-05 11:05:31 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::STEP: return wxS( "STEP" );
|
2024-04-17 14:31:07 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::BREP: return wxS( "BREP" );
|
2024-04-25 22:53:39 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::XAO: return wxS( "XAO" );
|
2024-04-17 14:31:07 +00:00
|
|
|
case EXPORTER_STEP_PARAMS::FORMAT::GLB: return wxS( "Binary GLTF" );
|
2024-02-05 11:05:31 +00:00
|
|
|
default: return wxEmptyString; // shouldn't happen
|
2023-08-19 15:40:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams ) :
|
|
|
|
m_params( aParams ),
|
|
|
|
m_error( false ),
|
|
|
|
m_fail( false ),
|
2023-01-02 23:34:13 +00:00
|
|
|
m_warn( false ),
|
2022-10-08 22:00:05 +00:00
|
|
|
m_board( aBoard ),
|
2024-04-22 00:39:43 +00:00
|
|
|
m_pcbModel( nullptr )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
2023-03-06 10:47:29 +00:00
|
|
|
m_copperColor = COLOR4D( 0.7, 0.61, 0.0, 1.0 );
|
2022-10-08 22:00:05 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( m_params.m_exportComponents )
|
|
|
|
m_padColor = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
|
|
|
|
else
|
|
|
|
m_padColor = m_copperColor;
|
|
|
|
|
|
|
|
// TODO: make configurable
|
|
|
|
m_platingThickness = pcbIUScale.mmToIU( 0.025 );
|
|
|
|
|
2023-03-22 07:49:26 +00:00
|
|
|
// Init m_pcbBaseName to the board short filename (no path, no ext)
|
2023-03-21 08:30:11 +00:00
|
|
|
// m_pcbName is used later to identify items in step file
|
|
|
|
wxFileName fn( aBoard->GetFileName() );
|
|
|
|
m_pcbBaseName = fn.GetName();
|
|
|
|
|
2024-04-22 01:05:46 +00:00
|
|
|
// Remove the autosave prefix
|
|
|
|
m_pcbBaseName.StartsWith( FILEEXT::AutoSaveFilePrefix, &m_pcbBaseName );
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
m_resolver = std::make_unique<FILENAME_RESOLVER>();
|
|
|
|
m_resolver->Set3DConfigDir( wxT( "" ) );
|
2023-01-30 03:19:37 +00:00
|
|
|
// needed to add the project to the search stack
|
|
|
|
m_resolver->SetProject( aBoard->GetProject() );
|
2022-10-08 22:00:05 +00:00
|
|
|
m_resolver->SetProgramBase( &Pgm() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EXPORTER_STEP::~EXPORTER_STEP()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-06 10:47:29 +00:00
|
|
|
bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
2024-04-24 15:28:44 +00:00
|
|
|
bool hasdata = false;
|
|
|
|
std::vector<PAD*> padsMatchingNetFilter;
|
2024-05-19 01:28:43 +00:00
|
|
|
int maxError = m_board->GetDesignSettings().m_MaxError;
|
2022-10-08 22:00:05 +00:00
|
|
|
|
2023-02-28 20:01:01 +00:00
|
|
|
// Dump the pad holes into the PCB
|
|
|
|
for( PAD* pad : aFootprint->Pads() )
|
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
std::shared_ptr<SHAPE_SEGMENT> holeShape = pad->GetEffectiveHoleShape();
|
|
|
|
|
|
|
|
SHAPE_POLY_SET holePoly;
|
|
|
|
holeShape->TransformToPolygon( holePoly, maxError, ERROR_INSIDE );
|
|
|
|
|
|
|
|
for( PCB_LAYER_ID pcblayer : pad->GetLayerSet().Seq() )
|
|
|
|
{
|
|
|
|
if( pad->IsOnLayer( pcblayer ) )
|
|
|
|
m_poly_holes[pcblayer].Append( holePoly );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( pad->HasHole() )
|
|
|
|
{
|
|
|
|
int platingThickness = pad->GetAttribute() == PAD_ATTRIB::PTH ? m_platingThickness : 0;
|
|
|
|
|
|
|
|
if( m_pcbModel->AddHole( *holeShape, platingThickness, F_Cu, B_Cu, false, aOrigin ) )
|
|
|
|
hasdata = true;
|
|
|
|
|
|
|
|
//// Cut holes in silkscreen (buggy: insufficient polyset self-intersection checking)
|
|
|
|
//if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
|
|
|
|
//{
|
|
|
|
// m_poly_holes[F_SilkS].Append( holePoly );
|
|
|
|
// m_poly_holes[B_SilkS].Append( holePoly );
|
|
|
|
//}
|
|
|
|
}
|
2023-03-02 18:38:19 +00:00
|
|
|
|
2024-04-24 15:28:44 +00:00
|
|
|
if( !m_params.m_netFilter.IsEmpty() && !pad->GetNetname().Matches( m_params.m_netFilter ) )
|
|
|
|
continue;
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( m_params.m_exportPads )
|
2023-03-02 18:38:19 +00:00
|
|
|
{
|
2024-04-25 12:34:49 +00:00
|
|
|
if( m_pcbModel->AddPadShape( pad, aOrigin, false ) )
|
2023-03-02 18:38:19 +00:00
|
|
|
hasdata = true;
|
2024-05-19 01:28:43 +00:00
|
|
|
|
|
|
|
if( m_params.m_exportSoldermask )
|
|
|
|
{
|
|
|
|
for( PCB_LAYER_ID pcblayer : pad->GetLayerSet().Seq() )
|
|
|
|
{
|
|
|
|
if( pcblayer != F_Mask && pcblayer != B_Mask )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
SHAPE_POLY_SET poly;
|
|
|
|
|
|
|
|
pad->TransformShapeToPolygon( poly, pcblayer, pad->GetSolderMaskExpansion(),
|
|
|
|
maxError, ERROR_INSIDE );
|
|
|
|
|
|
|
|
m_poly_shapes[pcblayer].Append( poly );
|
|
|
|
}
|
|
|
|
}
|
2023-03-02 18:38:19 +00:00
|
|
|
}
|
2024-04-24 15:28:44 +00:00
|
|
|
|
|
|
|
padsMatchingNetFilter.push_back( pad );
|
2023-02-28 20:01:01 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
// Build 3D shapes of the footprint graphic items:
|
|
|
|
for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
|
2023-03-06 10:47:29 +00:00
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
if( IsCopperLayer( pcblayer ) && !m_params.m_exportTracksVias )
|
|
|
|
continue;
|
2024-04-22 00:39:43 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
SHAPE_POLY_SET buffer;
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
aFootprint->TransformFPShapesToPolySet( buffer, pcblayer, 0, maxError, ERROR_INSIDE,
|
|
|
|
true, /* include text */
|
|
|
|
true, /* include shapes */
|
|
|
|
false /* include private items */ );
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( m_params.m_netFilter.IsEmpty() || !IsCopperLayer( pcblayer ) )
|
|
|
|
{
|
|
|
|
m_poly_shapes[pcblayer].Append( buffer );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Only add shapes colliding with any matching pads
|
|
|
|
for( const SHAPE_POLY_SET::POLYGON& poly : buffer.CPolygons() )
|
2024-04-24 15:28:44 +00:00
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
for( PAD* pad : padsMatchingNetFilter )
|
2024-04-24 15:28:44 +00:00
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
if( !pad->IsOnLayer( pcblayer ) )
|
|
|
|
continue;
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
std::shared_ptr<SHAPE_POLY_SET> padPoly = pad->GetEffectivePolygon();
|
|
|
|
SHAPE_POLY_SET gfxPoly( poly );
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( padPoly->Collide( &gfxPoly ) )
|
|
|
|
{
|
|
|
|
m_poly_shapes[pcblayer].Append( gfxPoly );
|
|
|
|
break;
|
2024-04-24 15:28:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-22 00:39:43 +00:00
|
|
|
}
|
2023-03-06 10:47:29 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 14:30:31 +00:00
|
|
|
if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_includeUnspecified )
|
|
|
|
{
|
|
|
|
return hasdata;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ( aFootprint->GetAttributes() & FP_DNP ) && !m_params.m_includeDNP )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
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 =
|
2023-09-28 03:15:54 +00:00
|
|
|
PROJECT_PCB::PcbFootprintLibs( m_board->GetProject() )->FindRow( libraryName, false );
|
2022-10-08 22:00:05 +00:00
|
|
|
|
|
|
|
if( fpRow )
|
|
|
|
footprintBasePath = fpRow->GetFullURI( true );
|
|
|
|
}
|
|
|
|
catch( ... )
|
|
|
|
{
|
|
|
|
// Do nothing if the libraryName is not found in lib table
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exit early if we don't want to include footprint models
|
2024-04-22 00:39:43 +00:00
|
|
|
if( m_params.m_boardOnly || !m_params.m_exportComponents )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
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;
|
2023-01-30 03:19:37 +00:00
|
|
|
wxString mname = m_resolver->ResolvePath( fp_model.m_Filename, footprintBasePath );
|
2022-10-08 22:00:05 +00:00
|
|
|
|
|
|
|
|
2024-02-16 04:10:36 +00:00
|
|
|
if( mname.empty() || !wxFileName::FileExists( mname ) )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
2024-02-16 04:10:36 +00:00
|
|
|
// the error path will return an empty name sometimes, at least report back the original filename
|
|
|
|
if( mname.empty() )
|
|
|
|
mname = fp_model.m_Filename;
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-06 10:47:29 +00:00
|
|
|
bool EXPORTER_STEP::buildTrack3DShape( PCB_TRACK* aTrack, VECTOR2D aOrigin )
|
2023-03-02 18:38:19 +00:00
|
|
|
{
|
2024-04-24 15:28:44 +00:00
|
|
|
if( !m_params.m_netFilter.IsEmpty() && !aTrack->GetNetname().Matches( m_params.m_netFilter ) )
|
|
|
|
return true;
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
int maxError = m_board->GetDesignSettings().m_MaxError;
|
|
|
|
|
2023-03-02 18:38:19 +00:00
|
|
|
if( aTrack->Type() == PCB_VIA_T )
|
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
|
|
|
|
|
|
|
|
std::shared_ptr<SHAPE_SEGMENT> holeShape = via->GetEffectiveHoleShape();
|
|
|
|
SHAPE_POLY_SET holePoly;
|
|
|
|
holeShape->TransformToPolygon( holePoly, maxError, ERROR_INSIDE );
|
|
|
|
|
|
|
|
LSET layers( via->GetLayerSet() & m_layersToExport );
|
|
|
|
|
|
|
|
for( PCB_LAYER_ID pcblayer : layers.Seq() )
|
|
|
|
{
|
|
|
|
const std::shared_ptr<SHAPE>& shape = via->GetEffectiveShape( pcblayer );
|
|
|
|
|
|
|
|
SHAPE_POLY_SET poly;
|
|
|
|
shape->TransformToPolygon( poly, maxError, ERROR_INSIDE );
|
|
|
|
m_poly_shapes[pcblayer].Append( poly );
|
|
|
|
m_poly_holes[pcblayer].Append( holePoly );
|
|
|
|
}
|
|
|
|
|
|
|
|
//// Cut holes in silkscreen (buggy: insufficient polyset self-intersection checking)
|
|
|
|
//if( m_layersToExport.Contains( F_SilkS ) || m_layersToExport.Contains( B_SilkS ) )
|
|
|
|
//{
|
|
|
|
// m_poly_holes[F_SilkS].Append( holePoly );
|
|
|
|
// m_poly_holes[B_SilkS].Append( holePoly );
|
|
|
|
//}
|
|
|
|
|
|
|
|
PCB_LAYER_ID top_layer, bot_layer;
|
|
|
|
via->LayerPair( &top_layer, &bot_layer );
|
|
|
|
|
|
|
|
m_pcbModel->AddHole( *holeShape, m_platingThickness, top_layer, bot_layer, true, aOrigin );
|
|
|
|
m_pcbModel->AddBarrel( *holeShape, top_layer, bot_layer, true, aOrigin );
|
|
|
|
|
|
|
|
return true;
|
2023-03-02 18:38:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PCB_LAYER_ID pcblayer = aTrack->GetLayer();
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( !m_layersToExport.Contains( pcblayer ) )
|
2023-03-02 18:38:19 +00:00
|
|
|
return false;
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
aTrack->TransformShapeToPolygon( m_poly_shapes[pcblayer], pcblayer, 0, maxError, ERROR_INSIDE );
|
2023-03-02 18:38:19 +00:00
|
|
|
|
2023-03-06 15:35:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-08-21 02:10:43 +00:00
|
|
|
void EXPORTER_STEP::buildZones3DShape( VECTOR2D aOrigin )
|
|
|
|
{
|
|
|
|
for( ZONE* zone : m_board->Zones() )
|
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
LSET layers = zone->GetLayerSet();
|
|
|
|
|
|
|
|
if( ( layers & LSET::AllCuMask() ).count() && !m_params.m_netFilter.IsEmpty()
|
|
|
|
&& !zone->GetNetname().Matches( m_params.m_netFilter ) )
|
|
|
|
{
|
2024-04-24 15:28:44 +00:00
|
|
|
continue;
|
2024-05-19 01:28:43 +00:00
|
|
|
}
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
for( PCB_LAYER_ID layer : layers.Seq() )
|
2023-08-21 02:10:43 +00:00
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
SHAPE_POLY_SET fill_shape;
|
|
|
|
zone->TransformSolidAreasShapesToPolygon( layer, fill_shape );
|
|
|
|
fill_shape.Unfracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
2023-08-21 02:10:43 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
fill_shape.SimplifyOutlines( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
m_poly_shapes[layer].Append( fill_shape );
|
2023-08-21 02:10:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-06 15:35:54 +00:00
|
|
|
bool EXPORTER_STEP::buildGraphic3DShape( BOARD_ITEM* aItem, VECTOR2D aOrigin )
|
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
PCB_LAYER_ID pcblayer = aItem->GetLayer();
|
2023-03-06 15:35:54 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( !m_layersToExport.Contains( pcblayer ) )
|
2023-03-06 15:35:54 +00:00
|
|
|
return false;
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
int maxError = m_board->GetDesignSettings().m_MaxError;
|
2024-04-24 15:28:44 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
switch( aItem->Type() )
|
|
|
|
{
|
|
|
|
case PCB_SHAPE_T:
|
|
|
|
{
|
|
|
|
PCB_SHAPE* graphic = static_cast<PCB_SHAPE*>( aItem );
|
2023-03-06 15:35:54 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
if( IsCopperLayer( pcblayer ) && !m_params.m_netFilter.IsEmpty()
|
|
|
|
&& !graphic->GetNetname().Matches( m_params.m_netFilter ) )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2023-03-06 15:35:54 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
graphic->TransformShapeToPolygon( m_poly_shapes[pcblayer], pcblayer, 0, maxError,
|
|
|
|
ERROR_INSIDE );
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2023-03-06 15:35:54 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
case PCB_TEXT_T:
|
|
|
|
{
|
|
|
|
PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
|
|
|
|
|
|
|
|
text->TransformTextToPolySet( m_poly_shapes[pcblayer], 0, maxError, ERROR_INSIDE );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case PCB_TEXTBOX_T:
|
|
|
|
{
|
|
|
|
PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
|
|
|
|
|
|
|
|
textbox->TransformTextToPolySet( m_poly_shapes[pcblayer], 0, maxError, ERROR_INSIDE );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case PCB_TABLE_T:
|
|
|
|
// JEY TODO: tables
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: wxFAIL_MSG( "buildGraphic3DShape: unhandled item type" );
|
|
|
|
}
|
2023-03-02 18:38:19 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-06 10:47:29 +00:00
|
|
|
bool EXPORTER_STEP::buildBoard3DShapes()
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
if( m_pcbModel )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
SHAPE_POLY_SET pcbOutlines; // stores the board main outlines
|
|
|
|
|
2023-05-16 11:39:16 +00:00
|
|
|
if( !m_board->GetBoardPolygonOutlines( pcbOutlines,
|
|
|
|
/* error handler */ nullptr,
|
|
|
|
/* allows use arcs in outlines */ true ) )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2023-03-21 08:30:11 +00:00
|
|
|
m_pcbModel = std::make_unique<STEP_PCB_MODEL>( m_pcbBaseName );
|
2022-10-08 22:00:05 +00:00
|
|
|
|
2023-03-06 10:47:29 +00:00
|
|
|
m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b );
|
2024-05-19 01:28:43 +00:00
|
|
|
m_pcbModel->SetPadColor( m_padColor.r, m_padColor.g, m_padColor.b );
|
2022-10-08 22:00:05 +00:00
|
|
|
|
2024-04-26 12:25:45 +00:00
|
|
|
m_pcbModel->SetStackup( m_board->GetStackupOrDefault() );
|
2024-05-19 01:28:43 +00:00
|
|
|
m_pcbModel->SetEnabledLayers( m_layersToExport );
|
2024-04-19 00:42:41 +00:00
|
|
|
m_pcbModel->SetFuseShapes( m_params.m_fuseShapes );
|
2024-04-24 15:28:44 +00:00
|
|
|
m_pcbModel->SetNetFilter( m_params.m_netFilter );
|
2023-02-28 15:34:07 +00:00
|
|
|
|
2023-03-02 18:38:19 +00:00
|
|
|
// Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines,
|
|
|
|
// not to set OCC chaining epsilon (much smaller)
|
|
|
|
//
|
|
|
|
// Set the min distance between 2 points for OCC to see these 2 points as merged
|
|
|
|
// OCC_MAX_DISTANCE_TO_MERGE_POINTS is acceptable for OCC, otherwise there are issues
|
|
|
|
// to handle the shapes chaining on copper layers, because the Z dist is 0.035 mm and the
|
|
|
|
// min dist must be much smaller (we use 0.001 mm giving good results)
|
|
|
|
m_pcbModel->OCCSetMergeMaxDistance( OCC_MAX_DISTANCE_TO_MERGE_POINTS );
|
2022-10-08 22:00:05 +00:00
|
|
|
|
|
|
|
m_pcbModel->SetMaxError( m_board->GetDesignSettings().m_MaxError );
|
|
|
|
|
2023-03-02 18:38:19 +00:00
|
|
|
// For copper layers, only pads and tracks are added, because adding everything on copper
|
|
|
|
// generate unreasonable file sizes and take a unreasonable calculation time.
|
|
|
|
for( FOOTPRINT* fp : m_board->Footprints() )
|
2023-03-06 10:47:29 +00:00
|
|
|
buildFootprint3DShapes( fp, origin );
|
2023-03-02 18:38:19 +00:00
|
|
|
|
2024-04-22 00:39:43 +00:00
|
|
|
if( m_params.m_exportTracksVias )
|
2023-03-02 18:38:19 +00:00
|
|
|
{
|
|
|
|
for( PCB_TRACK* track : m_board->Tracks() )
|
2023-03-06 10:47:29 +00:00
|
|
|
buildTrack3DShape( track, origin );
|
2023-03-02 18:38:19 +00:00
|
|
|
}
|
2022-10-08 22:00:05 +00:00
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
for( BOARD_ITEM* item : m_board->Drawings() )
|
|
|
|
buildGraphic3DShape( item, origin );
|
|
|
|
|
|
|
|
SHAPE_POLY_SET pcbOutlinesNoArcs = pcbOutlines;
|
|
|
|
pcbOutlinesNoArcs.ClearArcs();
|
|
|
|
|
|
|
|
for( PCB_LAYER_ID pcblayer : m_layersToExport.Seq() )
|
2024-04-22 00:39:43 +00:00
|
|
|
{
|
2024-05-19 01:28:43 +00:00
|
|
|
SHAPE_POLY_SET poly = m_poly_shapes[pcblayer];
|
|
|
|
poly.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
|
|
|
|
poly.SimplifyOutlines( pcbIUScale.mmToIU( 0.003 ) );
|
|
|
|
poly.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
|
|
|
|
SHAPE_POLY_SET holes = m_poly_holes[pcblayer];
|
|
|
|
holes.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
|
|
|
|
// Mask layer is negative
|
|
|
|
if( pcblayer == F_Mask || pcblayer == B_Mask )
|
|
|
|
{
|
|
|
|
SHAPE_POLY_SET mask = pcbOutlinesNoArcs;
|
|
|
|
|
|
|
|
mask.BooleanSubtract( poly, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
mask.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
|
|
|
|
poly = mask;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Subtract holes
|
|
|
|
poly.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
|
|
|
|
// Clip to board outline
|
|
|
|
poly.BooleanIntersection( pcbOutlinesNoArcs, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pcbModel->AddPolygonShapes( &poly, pcblayer, origin );
|
2024-04-22 00:39:43 +00:00
|
|
|
}
|
2023-08-21 02:10:43 +00:00
|
|
|
|
|
|
|
if( m_params.m_exportZones )
|
|
|
|
{
|
|
|
|
buildZones3DShape( origin );
|
|
|
|
}
|
2023-03-06 10:47:29 +00:00
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
ReportMessage( wxT( "Create PCB solid model\n" ) );
|
|
|
|
|
2023-02-28 15:34:07 +00:00
|
|
|
wxString msg;
|
|
|
|
msg.Printf( wxT( "Board outline: find %d initial points\n" ), pcbOutlines.FullPointCount() );
|
|
|
|
ReportMessage( msg );
|
|
|
|
|
2024-04-22 00:39:43 +00:00
|
|
|
if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_exportBoardBody ) )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
ReportMessage( wxT( "could not create PCB solid model\n" ) );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool EXPORTER_STEP::Export()
|
|
|
|
{
|
2023-08-24 11:02:10 +00:00
|
|
|
// Display the export time, for statistics
|
2024-03-03 18:39:53 +00:00
|
|
|
int64_t stats_startExportTime = GetRunningMicroSecs();
|
2023-08-24 11:02:10 +00:00
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
// setup opencascade message log
|
|
|
|
Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
|
|
|
|
Message::DefaultMessenger()->AddPrinter( new KiCadPrinter( this ) );
|
|
|
|
|
|
|
|
ReportMessage( _( "Determining PCB data\n" ) );
|
|
|
|
|
2023-08-19 15:40:45 +00:00
|
|
|
if( m_params.m_outputFile.IsEmpty() )
|
|
|
|
{
|
|
|
|
wxFileName fn = m_board->GetFileName();
|
|
|
|
fn.SetName( fn.GetName() );
|
|
|
|
fn.SetExt( m_params.GetDefaultExportExtension() );
|
|
|
|
|
|
|
|
m_params.m_outputFile = fn.GetFullName();
|
|
|
|
}
|
|
|
|
|
2024-05-19 01:28:43 +00:00
|
|
|
m_layersToExport = LSET::ExternalCuMask();
|
|
|
|
|
|
|
|
if( m_params.m_exportInnerCopper )
|
|
|
|
m_layersToExport |= LSET::InternalCuMask();
|
|
|
|
|
|
|
|
if( m_params.m_exportSilkscreen )
|
|
|
|
{
|
|
|
|
m_layersToExport.set( F_SilkS );
|
|
|
|
m_layersToExport.set( B_SilkS );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( m_params.m_exportSoldermask )
|
|
|
|
{
|
|
|
|
m_layersToExport.set( F_Mask );
|
|
|
|
m_layersToExport.set( B_Mask );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_layersToExport &= m_board->GetEnabledLayers();
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
try
|
|
|
|
{
|
2023-08-19 15:40:45 +00:00
|
|
|
ReportMessage( wxString::Format( _( "Build %s data\n" ), m_params.GetFormatName() ) );
|
2022-11-13 23:01:48 +00:00
|
|
|
|
2023-03-06 10:47:29 +00:00
|
|
|
if( !buildBoard3DShapes() )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
|
|
|
ReportMessage( _( "\n** Error building STEP board model. Export aborted. **\n" ) );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-08-19 15:40:45 +00:00
|
|
|
ReportMessage( wxString::Format( _( "Writing %s file\n" ), m_params.GetFormatName() ) );
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
if( m_params.m_format == EXPORTER_STEP_PARAMS::FORMAT::STEP )
|
2023-11-19 12:21:25 +00:00
|
|
|
success = m_pcbModel->WriteSTEP( m_outputFile, m_params.m_optimizeStep );
|
2024-04-17 14:31:07 +00:00
|
|
|
else if( m_params.m_format == EXPORTER_STEP_PARAMS::FORMAT::BREP )
|
|
|
|
success = m_pcbModel->WriteBREP( m_outputFile );
|
2024-04-25 22:53:39 +00:00
|
|
|
else if( m_params.m_format == EXPORTER_STEP_PARAMS::FORMAT::XAO )
|
|
|
|
success = m_pcbModel->WriteXAO( m_outputFile );
|
2023-08-19 15:40:45 +00:00
|
|
|
else if( m_params.m_format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
|
|
|
|
success = m_pcbModel->WriteGLTF( m_outputFile );
|
2022-11-13 23:01:48 +00:00
|
|
|
|
2023-08-19 15:40:45 +00:00
|
|
|
if( !success )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
2023-08-19 15:40:45 +00:00
|
|
|
ReportMessage( wxString::Format( _( "\n** Error writing %s file. **\n" ),
|
|
|
|
m_params.GetFormatName() ) );
|
2022-10-08 22:00:05 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-03 01:43:01 +00:00
|
|
|
ReportMessage( wxString::Format( _( "%s file '%s' created.\n" ),
|
2023-08-19 15:40:45 +00:00
|
|
|
m_params.GetFormatName(), m_outputFile ) );
|
2022-10-08 22:00:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch( const Standard_Failure& e )
|
|
|
|
{
|
|
|
|
ReportMessage( e.GetMessageString() );
|
2023-08-19 15:40:45 +00:00
|
|
|
ReportMessage( wxString::Format( _( "\n** Error exporting %s file. Export aborted. **\n" ),
|
|
|
|
m_params.GetFormatName() ) );
|
2022-10-08 22:00:05 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch( ... )
|
|
|
|
{
|
2023-08-19 15:40:45 +00:00
|
|
|
ReportMessage( wxString::Format( _( "\n** Error exporting %s file. Export aborted. **\n" ),
|
|
|
|
m_params.GetFormatName() ) );
|
2022-10-08 22:00:05 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( m_fail || m_error )
|
|
|
|
{
|
2024-04-22 00:39:43 +00:00
|
|
|
wxString msg;
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
if( m_fail )
|
|
|
|
{
|
2024-04-22 00:39:43 +00:00
|
|
|
msg = wxString::Format( _( "Unable to create %s file.\n"
|
2023-08-19 15:40:45 +00:00
|
|
|
"Check that the board has a valid outline and models." ),
|
|
|
|
m_params.GetFormatName() );
|
2022-10-08 22:00:05 +00:00
|
|
|
}
|
2023-01-02 23:34:13 +00:00
|
|
|
else if( m_error || m_warn )
|
2022-10-08 22:00:05 +00:00
|
|
|
{
|
2023-08-19 15:40:45 +00:00
|
|
|
msg = wxString::Format( _( "%s file has been created, but there are warnings." ),
|
|
|
|
m_params.GetFormatName() );
|
2022-10-08 22:00:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ReportMessage( msg );
|
|
|
|
}
|
|
|
|
|
2023-08-24 11:02:10 +00:00
|
|
|
// Display calculation time in seconds
|
|
|
|
double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6;
|
|
|
|
ReportMessage( wxString::Format( _( "\nExport time %.3f s\n" ), calculation_time ) );
|
|
|
|
|
2022-10-08 22:00:05 +00:00
|
|
|
return true;
|
2023-02-28 20:01:01 +00:00
|
|
|
}
|