Add binary GLTF export option to kicad-cli
Not by any means finished
This commit is contained in:
parent
5870b4f373
commit
50ac5db8d2
|
@ -53,6 +53,7 @@ set( OCC_LIBS
|
||||||
TKOffset
|
TKOffset
|
||||||
TKOpenGl
|
TKOpenGl
|
||||||
TKPrim
|
TKPrim
|
||||||
|
TKRWMesh
|
||||||
TKService
|
TKService
|
||||||
TKShHealing
|
TKShHealing
|
||||||
TKSTEP209
|
TKSTEP209
|
||||||
|
|
|
@ -50,7 +50,8 @@ public:
|
||||||
enum class FORMAT
|
enum class FORMAT
|
||||||
{
|
{
|
||||||
UNKNOWN, // defefer to arg
|
UNKNOWN, // defefer to arg
|
||||||
STEP
|
STEP,
|
||||||
|
GLB
|
||||||
};
|
};
|
||||||
|
|
||||||
bool m_overwrite;
|
bool m_overwrite;
|
||||||
|
|
|
@ -54,7 +54,7 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aName,
|
||||||
{
|
{
|
||||||
m_argParser.add_argument( ARG_FORMAT )
|
m_argParser.add_argument( ARG_FORMAT )
|
||||||
.default_value( std::string( "step" ) )
|
.default_value( std::string( "step" ) )
|
||||||
.help( UTF8STDSTR( _( "Output file format, options: step" ) ) );
|
.help( UTF8STDSTR( _( "Output file format, options: step, glb (binary glTF)" ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
m_argParser.add_argument( ARG_DRILL_ORIGIN )
|
m_argParser.add_argument( ARG_DRILL_ORIGIN )
|
||||||
|
@ -133,9 +133,9 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
|
||||||
wxString format = FROM_UTF8( m_argParser.get<std::string>( ARG_FORMAT ).c_str() );
|
wxString format = FROM_UTF8( m_argParser.get<std::string>( ARG_FORMAT ).c_str() );
|
||||||
|
|
||||||
if( format == wxS( "step" ) )
|
if( format == wxS( "step" ) )
|
||||||
{
|
|
||||||
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::STEP;
|
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::STEP;
|
||||||
}
|
else if( format == wxS( "glb" ) )
|
||||||
|
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::GLB;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
wxFprintf( stderr, _( "Invalid format specified\n" ) );
|
wxFprintf( stderr, _( "Invalid format specified\n" ) );
|
||||||
|
|
|
@ -184,6 +184,7 @@ static std::vector<COMMAND_ENTRY> commandStack = {
|
||||||
{
|
{
|
||||||
&exportPcbCmd,
|
&exportPcbCmd,
|
||||||
{
|
{
|
||||||
|
&exportPcb3dCmd,
|
||||||
&exportPcbDrillCmd,
|
&exportPcbDrillCmd,
|
||||||
&exportPcbDxfCmd,
|
&exportPcbDxfCmd,
|
||||||
&exportPcbGerberCmd,
|
&exportPcbGerberCmd,
|
||||||
|
|
|
@ -110,6 +110,28 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
wxString EXPORTER_STEP_PARAMS::GetDefaultExportExtension()
|
||||||
|
{
|
||||||
|
switch( m_format )
|
||||||
|
{
|
||||||
|
case EXPORTER_STEP_PARAMS::FORMAT::STEP: return wxS( "step" ); break;
|
||||||
|
case EXPORTER_STEP_PARAMS::FORMAT::GLB: return wxS( "glb" ); break;
|
||||||
|
default: return wxEmptyString; // shouldn't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString EXPORTER_STEP_PARAMS::GetFormatName()
|
||||||
|
{
|
||||||
|
switch( m_format )
|
||||||
|
{
|
||||||
|
// honestly these names shouldn't be translated since they are mostly industry standard acronyms
|
||||||
|
case EXPORTER_STEP_PARAMS::FORMAT::STEP: return wxS( "STEP" ); break;
|
||||||
|
case EXPORTER_STEP_PARAMS::FORMAT::GLB: return wxS("Binary GLTF" ); break;
|
||||||
|
default: return wxEmptyString; // shouldn't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams ) :
|
EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams ) :
|
||||||
m_params( aParams ),
|
m_params( aParams ),
|
||||||
m_error( false ),
|
m_error( false ),
|
||||||
|
@ -458,9 +480,18 @@ bool EXPORTER_STEP::Export()
|
||||||
msg.Printf( _( "Board Thickness from stackup: %.3f mm\n" ), m_boardThickness );
|
msg.Printf( _( "Board Thickness from stackup: %.3f mm\n" ), m_boardThickness );
|
||||||
ReportMessage( msg );
|
ReportMessage( msg );
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ReportMessage( _( "Build STEP data\n" ) );
|
ReportMessage( wxString::Format( _( "Build %s data\n" ), m_params.GetFormatName() ) );
|
||||||
|
|
||||||
if( !buildBoard3DShapes() )
|
if( !buildBoard3DShapes() )
|
||||||
{
|
{
|
||||||
|
@ -468,27 +499,37 @@ bool EXPORTER_STEP::Export()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportMessage( _( "Writing STEP file\n" ) );
|
ReportMessage( wxString::Format( _( "Writing %s file\n" ), m_params.GetFormatName() ) );
|
||||||
|
|
||||||
if( !m_pcbModel->WriteSTEP( m_outputFile ) )
|
bool success = true;
|
||||||
|
if( m_params.m_format == EXPORTER_STEP_PARAMS::FORMAT::STEP )
|
||||||
|
success = m_pcbModel->WriteSTEP( m_outputFile );
|
||||||
|
else if( m_params.m_format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
|
||||||
|
success = m_pcbModel->WriteGLTF( m_outputFile );
|
||||||
|
|
||||||
|
if( !success )
|
||||||
{
|
{
|
||||||
ReportMessage( _( "\n** Error writing STEP file. **\n" ) );
|
ReportMessage( wxString::Format( _( "\n** Error writing %s file. **\n" ),
|
||||||
|
m_params.GetFormatName() ) );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReportMessage( wxString::Format( _( "\nSTEP file '%s' created.\n" ), m_outputFile ) );
|
ReportMessage( wxString::Format( _( "\%s file '%s' created.\n" ),
|
||||||
|
m_params.GetFormatName(), m_outputFile ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( const Standard_Failure& e )
|
catch( const Standard_Failure& e )
|
||||||
{
|
{
|
||||||
ReportMessage( e.GetMessageString() );
|
ReportMessage( e.GetMessageString() );
|
||||||
ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
|
ReportMessage( wxString::Format( _( "\n** Error exporting %s file. Export aborted. **\n" ),
|
||||||
|
m_params.GetFormatName() ) );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch( ... )
|
catch( ... )
|
||||||
{
|
{
|
||||||
ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
|
ReportMessage( wxString::Format( _( "\n** Error exporting %s file. Export aborted. **\n" ),
|
||||||
|
m_params.GetFormatName() ) );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,12 +537,14 @@ bool EXPORTER_STEP::Export()
|
||||||
{
|
{
|
||||||
if( m_fail )
|
if( m_fail )
|
||||||
{
|
{
|
||||||
msg = _( "Unable to create STEP file.\n"
|
msg = wxString::Format( _( "Unable to create %s file.\n"
|
||||||
"Check that the board has a valid outline and models." );
|
"Check that the board has a valid outline and models." ),
|
||||||
|
m_params.GetFormatName() );
|
||||||
}
|
}
|
||||||
else if( m_error || m_warn )
|
else if( m_error || m_warn )
|
||||||
{
|
{
|
||||||
msg = _( "STEP file has been created, but there are warnings." );
|
msg = wxString::Format( _( "%s file has been created, but there are warnings." ),
|
||||||
|
m_params.GetFormatName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportMessage( msg );
|
ReportMessage( msg );
|
||||||
|
|
|
@ -54,9 +54,16 @@ public:
|
||||||
m_substModels( true ),
|
m_substModels( true ),
|
||||||
m_BoardOutlinesChainingEpsilon( BOARD_DEFAULT_CHAINING_EPSILON ),
|
m_BoardOutlinesChainingEpsilon( BOARD_DEFAULT_CHAINING_EPSILON ),
|
||||||
m_boardOnly( false ),
|
m_boardOnly( false ),
|
||||||
m_exportTracks( false )
|
m_exportTracks( false ),
|
||||||
|
m_format( FORMAT::STEP )
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
enum class FORMAT
|
||||||
|
{
|
||||||
|
STEP,
|
||||||
|
GLB
|
||||||
|
};
|
||||||
|
|
||||||
wxString m_outputFile;
|
wxString m_outputFile;
|
||||||
|
|
||||||
VECTOR2D m_origin;
|
VECTOR2D m_origin;
|
||||||
|
@ -70,6 +77,10 @@ public:
|
||||||
double m_BoardOutlinesChainingEpsilon;
|
double m_BoardOutlinesChainingEpsilon;
|
||||||
bool m_boardOnly;
|
bool m_boardOnly;
|
||||||
bool m_exportTracks;
|
bool m_exportTracks;
|
||||||
|
FORMAT m_format;
|
||||||
|
|
||||||
|
wxString GetDefaultExportExtension();
|
||||||
|
wxString GetFormatName();
|
||||||
};
|
};
|
||||||
|
|
||||||
class EXPORTER_STEP
|
class EXPORTER_STEP
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
#include <footprint.h>
|
#include <footprint.h>
|
||||||
#include <pad.h>
|
#include <pad.h>
|
||||||
#include <kiplatform/io.h>
|
#include <kiplatform/io.h>
|
||||||
|
#include <string_utils.h>
|
||||||
|
#include <build_version.h>
|
||||||
|
|
||||||
#include "step_pcb_model.h"
|
#include "step_pcb_model.h"
|
||||||
#include "streamwrapper.h"
|
#include "streamwrapper.h"
|
||||||
|
@ -89,6 +91,8 @@
|
||||||
#include <gp_Pnt.hxx>
|
#include <gp_Pnt.hxx>
|
||||||
#include <Geom_BezierCurve.hxx>
|
#include <Geom_BezierCurve.hxx>
|
||||||
|
|
||||||
|
#include <RWGltf_CafWriter.hxx>
|
||||||
|
|
||||||
#include <macros.h>
|
#include <macros.h>
|
||||||
|
|
||||||
static constexpr double USER_PREC = 1e-4;
|
static constexpr double USER_PREC = 1e-4;
|
||||||
|
@ -1038,7 +1042,7 @@ bool STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D a
|
||||||
else // Substitution is not allowed
|
else // Substitution is not allowed
|
||||||
{
|
{
|
||||||
if( aErrorMessage )
|
if( aErrorMessage )
|
||||||
aErrorMessage->Printf( wxT( "Cannot add a VRML model to a STEP file.\n" ) );
|
aErrorMessage->Printf( wxT( "Cannot load any VRML model for this export.\n" ) );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1330,3 +1334,88 @@ TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document )& source,
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
|
||||||
|
{
|
||||||
|
if( !isBoardOutlineValid() )
|
||||||
|
{
|
||||||
|
ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
|
||||||
|
"'%s'.\n" ),
|
||||||
|
aFileName ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TDF_LabelSequence freeShapes;
|
||||||
|
m_assy->GetFreeShapes( freeShapes );
|
||||||
|
|
||||||
|
ReportMessage( wxT( "Meshing model\n" ) );
|
||||||
|
|
||||||
|
// GLTF is a mesh format, we have to trigger opencascade to mesh the shapes we composited into the asesmbly
|
||||||
|
// To mesh models, lets just grab the free shape root and execute on them
|
||||||
|
for( Standard_Integer i = 1; i <= freeShapes.Length(); ++i )
|
||||||
|
{
|
||||||
|
TDF_Label label = freeShapes.Value( i );
|
||||||
|
TopoDS_Shape shape;
|
||||||
|
m_assy->GetShape( label, shape );
|
||||||
|
|
||||||
|
// These deflection values basically affect the accuracy of the mesh generated, a tighter
|
||||||
|
// deflection will result in larger meshes
|
||||||
|
// We could make this a tunable parameter, but for now fix it
|
||||||
|
const Standard_Real linearDeflection = 0.01;
|
||||||
|
const Standard_Real angularDeflection = 0.5;
|
||||||
|
BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
|
||||||
|
Standard_True );
|
||||||
|
}
|
||||||
|
|
||||||
|
wxFileName fn( aFileName );
|
||||||
|
|
||||||
|
const char* tmpGltfname = "$tempfile$.glb";
|
||||||
|
RWGltf_CafWriter cafWriter( tmpGltfname, true );
|
||||||
|
|
||||||
|
cafWriter.SetTransformationFormat( RWGltf_WriterTrsfFormat_Compact );
|
||||||
|
cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
|
||||||
|
cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
|
||||||
|
RWMesh_CoordinateSystem_Zup );
|
||||||
|
#if OCC_VERSION_HEX >= 0x070700
|
||||||
|
cafWriter.SetParallel( true );
|
||||||
|
#endif
|
||||||
|
TColStd_IndexedDataMapOfStringString metadata;
|
||||||
|
|
||||||
|
metadata.Add( TCollection_AsciiString( "pcb_name" ),
|
||||||
|
TCollection_ExtendedString( fn.GetName().wc_str() ) );
|
||||||
|
metadata.Add( TCollection_AsciiString( "source_pcb_file" ),
|
||||||
|
TCollection_ExtendedString( fn.GetFullName().wc_str() ) );
|
||||||
|
metadata.Add( TCollection_AsciiString( "generator" ),
|
||||||
|
TCollection_AsciiString( wxString::Format( wxS( "KiCad %s" ), GetSemanticVersion() ).ToAscii() ) );
|
||||||
|
metadata.Add( TCollection_AsciiString( "generated_at" ),
|
||||||
|
TCollection_AsciiString( GetISO8601CurrentDateTime().ToAscii() ) );
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
// Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
|
||||||
|
wxString currCWD = wxGetCwd();
|
||||||
|
wxString workCWD = fn.GetPath();
|
||||||
|
|
||||||
|
if( !workCWD.IsEmpty() )
|
||||||
|
wxSetWorkingDirectory( workCWD );
|
||||||
|
|
||||||
|
success = cafWriter.Perform( m_doc, metadata, Message_ProgressRange() );
|
||||||
|
|
||||||
|
if( success )
|
||||||
|
{
|
||||||
|
// Preserve the permissions of the current file
|
||||||
|
KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpGltfname );
|
||||||
|
|
||||||
|
if( !wxRenameFile( tmpGltfname, fn.GetFullName(), true ) )
|
||||||
|
{
|
||||||
|
ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
|
||||||
|
tmpGltfname, fn.GetFullName() ) );
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSetWorkingDirectory( currCWD );
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
|
@ -31,6 +31,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <Standard_Version.hxx>
|
||||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||||
#include <TDocStd_Document.hxx>
|
#include <TDocStd_Document.hxx>
|
||||||
#include <XCAFApp_Application.hxx>
|
#include <XCAFApp_Application.hxx>
|
||||||
|
@ -143,6 +144,22 @@ public:
|
||||||
// write the assembly model in STEP format
|
// write the assembly model in STEP format
|
||||||
bool WriteSTEP( const wxString& aFileName );
|
bool WriteSTEP( const wxString& aFileName );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the assembly in binary GLTF Format
|
||||||
|
*
|
||||||
|
* We only support binary GLTF because GLTF is weird
|
||||||
|
* Officially, binary GLTF is actually json+binary in one file
|
||||||
|
* If we elected non-binary output with opecascade, it will generate
|
||||||
|
* that one file as two separate files, one containing json that references the binary
|
||||||
|
* Which is actually more annoying to deal with (to do the temp file rename, since we dont
|
||||||
|
* control the binary name) and silly when you can just have the one file.
|
||||||
|
*
|
||||||
|
* @param aFileName Output file path
|
||||||
|
*
|
||||||
|
* @return true if the write succeeded without error
|
||||||
|
*/
|
||||||
|
bool WriteGLTF( const wxString& aFileName );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @return true if the board(s) outline is valid. False otherwise
|
* @return true if the board(s) outline is valid. False otherwise
|
||||||
|
|
|
@ -96,15 +96,6 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
|
||||||
|
|
||||||
BOARD* brd = LoadBoard( aStepJob->m_filename );
|
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;
|
EXPORTER_STEP_PARAMS params;
|
||||||
params.m_exportTracks = aStepJob->m_exportTracks;
|
params.m_exportTracks = aStepJob->m_exportTracks;
|
||||||
params.m_includeUnspecified = aStepJob->m_includeUnspecified;
|
params.m_includeUnspecified = aStepJob->m_includeUnspecified;
|
||||||
|
@ -117,6 +108,17 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
|
||||||
params.m_useGridOrigin = aStepJob->m_useGridOrigin;
|
params.m_useGridOrigin = aStepJob->m_useGridOrigin;
|
||||||
params.m_boardOnly = aStepJob->m_boardOnly;
|
params.m_boardOnly = aStepJob->m_boardOnly;
|
||||||
|
|
||||||
|
switch( aStepJob->m_format )
|
||||||
|
{
|
||||||
|
case JOB_EXPORT_PCB_3D::FORMAT::STEP:
|
||||||
|
params.m_format = EXPORTER_STEP_PARAMS::FORMAT::STEP;
|
||||||
|
break;
|
||||||
|
case JOB_EXPORT_PCB_3D::FORMAT::GLB:
|
||||||
|
params.m_format = EXPORTER_STEP_PARAMS::FORMAT::GLB;
|
||||||
|
break;
|
||||||
|
default: return CLI::EXIT_CODES::ERR_UNKNOWN; // should have gotten here
|
||||||
|
}
|
||||||
|
|
||||||
EXPORTER_STEP stepExporter( brd, params );
|
EXPORTER_STEP stepExporter( brd, params );
|
||||||
stepExporter.m_outputFile = aStepJob->m_outputFile;
|
stepExporter.m_outputFile = aStepJob->m_outputFile;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue