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
|
||||
TKOpenGl
|
||||
TKPrim
|
||||
TKRWMesh
|
||||
TKService
|
||||
TKShHealing
|
||||
TKSTEP209
|
||||
|
@ -92,12 +93,12 @@ if(WIN32)
|
|||
/opt/opencascade/lib
|
||||
)
|
||||
elseif(VCPKG_TOOLCHAIN)
|
||||
FIND_PATH(OCC_INCLUDE_DIR
|
||||
FIND_PATH(OCC_INCLUDE_DIR
|
||||
NAMES Standard_Version.hxx
|
||||
PATH_SUFFIXES
|
||||
include/opencascade
|
||||
)
|
||||
FIND_LIBRARY(OCC_LIBRARY
|
||||
FIND_LIBRARY(OCC_LIBRARY
|
||||
NAMES TKernel
|
||||
HINTS
|
||||
${OCC_LIBRARY_DIR}
|
||||
|
|
|
@ -50,7 +50,8 @@ public:
|
|||
enum class FORMAT
|
||||
{
|
||||
UNKNOWN, // defefer to arg
|
||||
STEP
|
||||
STEP,
|
||||
GLB
|
||||
};
|
||||
|
||||
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 )
|
||||
.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 )
|
||||
|
@ -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() );
|
||||
|
||||
if( format == wxS( "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
|
||||
{
|
||||
wxFprintf( stderr, _( "Invalid format specified\n" ) );
|
||||
|
|
|
@ -184,6 +184,7 @@ static std::vector<COMMAND_ENTRY> commandStack = {
|
|||
{
|
||||
&exportPcbCmd,
|
||||
{
|
||||
&exportPcb3dCmd,
|
||||
&exportPcbDrillCmd,
|
||||
&exportPcbDxfCmd,
|
||||
&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 ) :
|
||||
m_params( aParams ),
|
||||
m_error( false ),
|
||||
|
@ -458,9 +480,18 @@ bool EXPORTER_STEP::Export()
|
|||
msg.Printf( _( "Board Thickness from stackup: %.3f mm\n" ), m_boardThickness );
|
||||
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
|
||||
{
|
||||
ReportMessage( _( "Build STEP data\n" ) );
|
||||
ReportMessage( wxString::Format( _( "Build %s data\n" ), m_params.GetFormatName() ) );
|
||||
|
||||
if( !buildBoard3DShapes() )
|
||||
{
|
||||
|
@ -468,27 +499,37 @@ bool EXPORTER_STEP::Export()
|
|||
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;
|
||||
}
|
||||
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 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -496,12 +537,14 @@ bool EXPORTER_STEP::Export()
|
|||
{
|
||||
if( m_fail )
|
||||
{
|
||||
msg = _( "Unable to create STEP file.\n"
|
||||
"Check that the board has a valid outline and models." );
|
||||
msg = wxString::Format( _( "Unable to create %s file.\n"
|
||||
"Check that the board has a valid outline and models." ),
|
||||
m_params.GetFormatName() );
|
||||
}
|
||||
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 );
|
||||
|
|
|
@ -54,9 +54,16 @@ public:
|
|||
m_substModels( true ),
|
||||
m_BoardOutlinesChainingEpsilon( BOARD_DEFAULT_CHAINING_EPSILON ),
|
||||
m_boardOnly( false ),
|
||||
m_exportTracks( false )
|
||||
m_exportTracks( false ),
|
||||
m_format( FORMAT::STEP )
|
||||
{};
|
||||
|
||||
enum class FORMAT
|
||||
{
|
||||
STEP,
|
||||
GLB
|
||||
};
|
||||
|
||||
wxString m_outputFile;
|
||||
|
||||
VECTOR2D m_origin;
|
||||
|
@ -70,6 +77,10 @@ public:
|
|||
double m_BoardOutlinesChainingEpsilon;
|
||||
bool m_boardOnly;
|
||||
bool m_exportTracks;
|
||||
FORMAT m_format;
|
||||
|
||||
wxString GetDefaultExportExtension();
|
||||
wxString GetFormatName();
|
||||
};
|
||||
|
||||
class EXPORTER_STEP
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
#include <footprint.h>
|
||||
#include <pad.h>
|
||||
#include <kiplatform/io.h>
|
||||
#include <string_utils.h>
|
||||
#include <build_version.h>
|
||||
|
||||
#include "step_pcb_model.h"
|
||||
#include "streamwrapper.h"
|
||||
|
@ -89,6 +91,8 @@
|
|||
#include <gp_Pnt.hxx>
|
||||
#include <Geom_BezierCurve.hxx>
|
||||
|
||||
#include <RWGltf_CafWriter.hxx>
|
||||
|
||||
#include <macros.h>
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -1330,3 +1334,88 @@ TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document )& source,
|
|||
|
||||
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 <vector>
|
||||
|
||||
#include <Standard_Version.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <TDocStd_Document.hxx>
|
||||
#include <XCAFApp_Application.hxx>
|
||||
|
@ -143,6 +144,22 @@ public:
|
|||
// write the assembly model in STEP format
|
||||
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:
|
||||
/**
|
||||
* @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 );
|
||||
|
||||
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_exportTracks = aStepJob->m_exportTracks;
|
||||
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_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 );
|
||||
stepExporter.m_outputFile = aStepJob->m_outputFile;
|
||||
|
||||
|
|
Loading…
Reference in New Issue