Add vrml export to cli

Roughcut as the vrml exporter needs some loving

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15472
Fixes https://gitlab.com/kicad/code/kicad/-/issues/13952
This commit is contained in:
Marek Roszko 2023-08-24 20:24:51 -04:00
parent b1b66f66f4
commit d193334a10
6 changed files with 232 additions and 106 deletions

View File

@ -32,6 +32,7 @@ public:
m_overwrite( false ),
m_useGridOrigin( false ),
m_useDrillOrigin( false ),
m_hasUserOrigin( false ),
m_boardOnly( false ),
m_includeUnspecified( false ),
m_includeDNP( false ),
@ -44,7 +45,10 @@ public:
m_BoardOutlinesChainingEpsilon( 0.01 ), // 0.01 mm is a good value
m_exportTracks( false ), // Extremely time consuming if true
m_exportZones( false ), // Extremely time consuming if true
m_format( JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN )
m_format( JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN ),
m_vrmlUnits( JOB_EXPORT_PCB_3D::VRML_UNITS::METERS ),
m_vrmlModelDir( wxEmptyString ),
m_vrmlRelativePaths( false )
{
}
@ -52,12 +56,22 @@ public:
{
UNKNOWN, // defefer to arg
STEP,
GLB
GLB,
VRML
};
enum class VRML_UNITS
{
INCHES,
MILLIMETERS,
METERS,
TENTHS // inches
};
bool m_overwrite;
bool m_useGridOrigin;
bool m_useDrillOrigin;
bool m_hasUserOrigin;
bool m_boardOnly;
bool m_includeUnspecified;
bool m_includeDNP;
@ -70,6 +84,10 @@ public:
bool m_exportTracks;
bool m_exportZones;
JOB_EXPORT_PCB_3D::FORMAT m_format;
VRML_UNITS m_vrmlUnits;
wxString m_vrmlModelDir;
bool m_vrmlRelativePaths;
};
#endif

View File

@ -41,6 +41,9 @@
#define ARG_INCLUDE_TRACKS "--include-tracks"
#define ARG_INCLUDE_ZONES "--include-zones"
#define ARG_FORMAT "--format"
#define ARG_VRML_UNITS "--units"
#define ARG_VRML_MODELS_DIR "--models-dir"
#define ARG_VRML_MODELS_RELATIVE "--models-relative"
#define REGEX_QUANTITY "([\\s]*[+-]?[\\d]*[.]?[\\d]*)"
#define REGEX_DELIMITER "(?:[\\s]*x)"
@ -58,59 +61,85 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aName,
.help( UTF8STDSTR( _( "Output file format, options: step, glb (binary glTF)" ) ) );
}
m_argParser.add_argument( ARG_DRILL_ORIGIN )
.help( UTF8STDSTR( _( "Use Drill Origin for output origin" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_GRID_ORIGIN )
.help( UTF8STDSTR( _( "Use Grid Origin for output origin" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_NO_UNSPECIFIED )
.help( UTF8STDSTR( _( "Exclude 3D models for components with 'Unspecified' footprint type" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_NO_DNP )
.help( UTF8STDSTR( _( "Exclude 3D models for components with 'Do not populate' attribute" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( "--subst-models" )
.help( UTF8STDSTR( _( "Substitute STEP or IGS models with the same name in place of VRML models" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_FORCE, "-f" )
.help( UTF8STDSTR( _( "Overwrite output file" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_BOARD_ONLY )
.help( UTF8STDSTR( _( "Only generate a board with no components" ) ) )
.implicit_value( true )
.default_value( false );
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::STEP || m_format == JOB_EXPORT_PCB_3D::FORMAT::GLB )
{
m_argParser.add_argument( ARG_GRID_ORIGIN )
.help( UTF8STDSTR( _( "Use Grid Origin for output origin" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_INCLUDE_TRACKS )
.help( UTF8STDSTR( _( "Export tracks (extremely time consuming)" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_DRILL_ORIGIN )
.help( UTF8STDSTR( _( "Use Drill Origin for output origin" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_INCLUDE_ZONES )
.help( UTF8STDSTR( _( "Export zones (extremely time consuming)" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_NO_UNSPECIFIED )
.help( UTF8STDSTR( _(
"Exclude 3D models for components with 'Unspecified' footprint type" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_MIN_DISTANCE )
.default_value( std::string( "0.01mm" ) )
.help( UTF8STDSTR( _( "Minimum distance between points to treat them as separate ones" ) ) );
m_argParser.add_argument( ARG_NO_DNP )
.help( UTF8STDSTR(
_( "Exclude 3D models for components with 'Do not populate' attribute" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( "--subst-models" )
.help( UTF8STDSTR( _( "Substitute STEP or IGS models with the same name in place "
"of VRML models" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_BOARD_ONLY )
.help( UTF8STDSTR( _( "Only generate a board with no components" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_INCLUDE_TRACKS )
.help( UTF8STDSTR( _( "Export tracks (extremely time consuming)" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_INCLUDE_ZONES )
.help( UTF8STDSTR( _( "Export zones (extremely time consuming)" ) ) )
.implicit_value( true )
.default_value( false );
m_argParser.add_argument( ARG_MIN_DISTANCE )
.default_value( std::string( "0.01mm" ) )
.help( UTF8STDSTR(
_( "Minimum distance between points to treat them as separate ones" ) ) );
}
m_argParser.add_argument( ARG_USER_ORIGIN )
.default_value( std::string() )
.help( UTF8STDSTR( _( "User-specified output origin ex. 1x1in, 1x1inch, 25.4x25.4mm (default unit mm)" ) ) );
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
{
m_argParser.add_argument( ARG_VRML_UNITS )
.default_value( std::string( "in" ) )
.help( UTF8STDSTR(
_( "Output units; ascii or csv format only; valid options: mm, m, in, tenths" ) ) );
m_argParser.add_argument( ARG_VRML_MODELS_DIR )
.default_value( std::string( "" ) )
.help( UTF8STDSTR(
_( "Name of folder to create and store 3d models in, if not specified or "
"empty, the models will be embedded in main exported vrml file" ) ) );
m_argParser.add_argument( ARG_VRML_MODELS_RELATIVE )
.help( UTF8STDSTR( _( "Used with --models-dir to output relative paths in the resulting file" ) ) )
.implicit_value( true )
.default_value( false );
}
m_argParser.add_argument( "-o", ARG_OUTPUT )
.default_value( std::string() )
.help( UTF8STDSTR( _( "Output file name" ) ) );
@ -122,17 +151,21 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
{
std::unique_ptr<JOB_EXPORT_PCB_3D> step( new JOB_EXPORT_PCB_3D( true ) );
step->m_useDrillOrigin = m_argParser.get<bool>( ARG_DRILL_ORIGIN );
step->m_useGridOrigin = m_argParser.get<bool>( ARG_GRID_ORIGIN );
step->m_includeUnspecified = !m_argParser.get<bool>( ARG_NO_UNSPECIFIED );
step->m_includeDNP = !m_argParser.get<bool>( ARG_NO_DNP );
step->m_substModels = m_argParser.get<bool>( ARG_SUBST_MODELS );
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::STEP || m_format == JOB_EXPORT_PCB_3D::FORMAT::GLB )
{
step->m_useDrillOrigin = m_argParser.get<bool>( ARG_DRILL_ORIGIN );
step->m_useGridOrigin = m_argParser.get<bool>( ARG_GRID_ORIGIN );
step->m_includeUnspecified = !m_argParser.get<bool>( ARG_NO_UNSPECIFIED );
step->m_includeDNP = !m_argParser.get<bool>( ARG_NO_DNP );
step->m_substModels = m_argParser.get<bool>( ARG_SUBST_MODELS );
step->m_exportTracks = m_argParser.get<bool>( ARG_INCLUDE_TRACKS );
step->m_exportZones = m_argParser.get<bool>( ARG_INCLUDE_ZONES );
step->m_boardOnly = m_argParser.get<bool>( ARG_BOARD_ONLY );
}
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 );
step->m_exportTracks = m_argParser.get<bool>( ARG_INCLUDE_TRACKS );
step->m_exportZones = m_argParser.get<bool>( ARG_INCLUDE_ZONES );
step->m_format = m_format;
if( step->m_format == JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN )
@ -150,6 +183,29 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
}
}
if( step->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
{
wxString units = FROM_UTF8( m_argParser.get<std::string>( ARG_VRML_UNITS ).c_str() );
if( units == wxS( "in" ) )
step->m_vrmlUnits = JOB_EXPORT_PCB_3D::VRML_UNITS::INCHES;
else if( units == wxS( "mm" ) )
step->m_vrmlUnits = JOB_EXPORT_PCB_3D::VRML_UNITS::MILLIMETERS;
else if( units == wxS( "m" ) )
step->m_vrmlUnits = JOB_EXPORT_PCB_3D::VRML_UNITS::METERS;
else if( units == wxS( "tenths" ) )
step->m_vrmlUnits = JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS;
else
{
wxFprintf( stderr, _( "Invalid units specified\n" ) );
return EXIT_CODES::ERR_ARGS;
}
step->m_vrmlModelDir = FROM_UTF8( m_argParser.get<std::string>( ARG_VRML_MODELS_DIR ).c_str() );
step->m_vrmlRelativePaths = m_argParser.get<bool>( ARG_VRML_MODELS_RELATIVE );
}
wxString userOrigin = FROM_UTF8( m_argParser.get<std::string>( ARG_USER_ORIGIN ).c_str() );
LOCALE_IO dummy; // Switch to "C" locale
@ -192,31 +248,36 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
return CLI::EXIT_CODES::ERR_ARGS;
}
}
step->m_hasUserOrigin = true;
}
wxString minDistance = FROM_UTF8( m_argParser.get<std::string>( ARG_MIN_DISTANCE ).c_str() );
if( !minDistance.IsEmpty() )
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::STEP || m_format == JOB_EXPORT_PCB_3D::FORMAT::GLB )
{
std::regex re_pattern( REGEX_QUANTITY REGEX_UNIT,
std::regex_constants::icase );
std::smatch sm;
std::string str( minDistance.ToUTF8() );
std::regex_search( str, sm, re_pattern );
step->m_BoardOutlinesChainingEpsilon = atof( sm.str( 1 ).c_str() );
wxString minDistance =
FROM_UTF8( m_argParser.get<std::string>( ARG_MIN_DISTANCE ).c_str() );
std::string tunit( sm[2] );
if( tunit.size() > 0 ) // No unit accepted ( default = mm )
if( !minDistance.IsEmpty() )
{
if( !tunit.compare( "in" ) || !tunit.compare( "inch" ) )
std::regex re_pattern( REGEX_QUANTITY REGEX_UNIT, std::regex_constants::icase );
std::smatch sm;
std::string str( minDistance.ToUTF8() );
std::regex_search( str, sm, re_pattern );
step->m_BoardOutlinesChainingEpsilon = atof( sm.str( 1 ).c_str() );
std::string tunit( sm[2] );
if( tunit.size() > 0 ) // No unit accepted ( default = mm )
{
step->m_BoardOutlinesChainingEpsilon *= 25.4;
}
else if( tunit.compare( "mm" ) )
{
std::cout << m_argParser;
return CLI::EXIT_CODES::ERR_ARGS;
if( !tunit.compare( "in" ) || !tunit.compare( "inch" ) )
{
step->m_BoardOutlinesChainingEpsilon *= 25.4;
}
else if( tunit.compare( "mm" ) )
{
std::cout << m_argParser;
return CLI::EXIT_CODES::ERR_ARGS;
}
}
}
}

View File

@ -132,6 +132,7 @@ static CLI::PCB_EXPORT_DRILL_COMMAND exportPcbDrillCmd{};
static CLI::PCB_EXPORT_DXF_COMMAND exportPcbDxfCmd{};
static CLI::PCB_EXPORT_3D_COMMAND exportPcbGlbCmd{ "glb", JOB_EXPORT_PCB_3D::FORMAT::GLB };
static CLI::PCB_EXPORT_3D_COMMAND exportPcbStepCmd{ "step", JOB_EXPORT_PCB_3D::FORMAT::STEP };
static CLI::PCB_EXPORT_3D_COMMAND exportPcbVrmlCmd{ "vrml", JOB_EXPORT_PCB_3D::FORMAT::VRML };
static CLI::PCB_EXPORT_SVG_COMMAND exportPcbSvgCmd{};
static CLI::PCB_EXPORT_PDF_COMMAND exportPcbPdfCmd{};
static CLI::PCB_EXPORT_POS_COMMAND exportPcbPosCmd{};
@ -192,7 +193,8 @@ static std::vector<COMMAND_ENTRY> commandStack = {
&exportPcbPdfCmd,
&exportPcbPosCmd,
&exportPcbStepCmd,
&exportPcbSvgCmd
&exportPcbSvgCmd,
&exportPcbVrmlCmd
}
}
}

View File

@ -231,9 +231,9 @@ void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event )
{
// Origin = board center:
BOARD* pcb = GetBoard();
VECTOR2I center = pcb->GetBoundingBox().GetCenter();
aXRef = pcbIUScale.IUTomm( center.x );
aYRef = pcbIUScale.IUTomm( center.y );
BOX2I bbox = pcb->ComputeBoundingBox( true );
aXRef = pcbIUScale.IUTomm( bbox.GetCenter().x );
aYRef = pcbIUScale.IUTomm( bbox.GetCenter().y );
}
double scale = scaleList[dlg.GetUnits()]; // final scale export

View File

@ -1025,7 +1025,6 @@ void EXPORTER_PCB_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint, std::ostream
auto sM = aFootprint->Models().begin();
auto eM = aFootprint->Models().end();
wxFileName subdir( m_Subdir3DFpModels, wxT( "" ) );
while( sM != eM )
{
@ -1245,7 +1244,11 @@ bool EXPORTER_PCB_VRML::ExportVRML_File( PROJECT* aProject, wxString *aMessages,
SetScale( aMMtoWRMLunit );
m_UseInlineModelsInBrdfile = aExport3DFiles;
m_Subdir3DFpModels = a3D_Subdir;
wxFileName subdir( a3D_Subdir, wxT( "" ) );
// convert the subdir path to a absolute full one with the output file as the cwd
m_Subdir3DFpModels = subdir.GetAbsolutePath( wxFileName( aFullFileName ).GetPath() );
m_UseRelPathIn3DModelFilename = aUseRelativePaths;
m_Cache3Dmodels = aProject->Get3DCacheManager();
@ -1329,11 +1332,9 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMt
void EXPORTER_PCB_VRML::ExportFp3DModelsAsLinkedFile( const wxString& aFullFileName )
{
// check if the 3D Subdir exists - create if not
wxFileName subdir( m_Subdir3DFpModels, wxT( "" ) );
if( ! subdir.DirExists() )
if( !wxDir::Exists( m_Subdir3DFpModels ) )
{
if( !wxDir::Make( subdir.GetFullPath() ) )
if( !wxDir::Make( m_Subdir3DFpModels ) )
throw( std::runtime_error( "Could not create 3D model subdirectory" ) );
}

View File

@ -59,6 +59,7 @@
#include <plugins/kicad/pcb_plugin.h>
#include <reporter.h>
#include <wildcards_and_files_ext.h>
#include <export_vrml.h>
#include "pcbnew_scripting_helpers.h"
@ -96,36 +97,79 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
BOARD* brd = LoadBoard( aStepJob->m_filename );
EXPORTER_STEP_PARAMS params;
params.m_exportTracks = aStepJob->m_exportTracks;
params.m_exportZones = aStepJob->m_exportZones;
params.m_includeUnspecified = aStepJob->m_includeUnspecified;
params.m_includeDNP = aStepJob->m_includeDNP;
params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon;
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_boardOnly = aStepJob->m_boardOnly;
switch( aStepJob->m_format )
if( aStepJob->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
{
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
double scale = 0.0;
switch ( aStepJob->m_vrmlUnits )
{
case JOB_EXPORT_PCB_3D::VRML_UNITS::MILLIMETERS: scale = 1.0; break;
case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
case JOB_EXPORT_PCB_3D::VRML_UNITS::INCHES: scale = 1.0 / 25.4; break;
}
EXPORTER_VRML vrmlExporter( brd );
wxString messages;
double originX = pcbIUScale.IUTomm( aStepJob->m_xOrigin );
double originY = pcbIUScale.IUTomm( aStepJob->m_yOrigin );
if( !aStepJob->m_hasUserOrigin )
{
BOX2I bbox = brd->ComputeBoundingBox( true );
originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
}
bool success = vrmlExporter.ExportVRML_File(
brd->GetProject(), &messages, aStepJob->m_outputFile, scale,
!aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
aStepJob->m_vrmlModelDir, originX, originY );
if ( success )
{
m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ), aStepJob->m_outputFile ),
RPT_SEVERITY_INFO );
}
else
{
m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
}
EXPORTER_STEP stepExporter( brd, params );
stepExporter.m_outputFile = aStepJob->m_outputFile;
if( !stepExporter.Export() )
else
{
return CLI::EXIT_CODES::ERR_UNKNOWN;
EXPORTER_STEP_PARAMS params;
params.m_exportTracks = aStepJob->m_exportTracks;
params.m_exportZones = aStepJob->m_exportZones;
params.m_includeUnspecified = aStepJob->m_includeUnspecified;
params.m_includeDNP = aStepJob->m_includeDNP;
params.m_BoardOutlinesChainingEpsilon = aStepJob->m_BoardOutlinesChainingEpsilon;
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_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;
if( !stepExporter.Export() )
{
return CLI::EXIT_CODES::ERR_UNKNOWN;
}
}
return CLI::EXIT_CODES::OK;