7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-19 18:51:40 +00:00

ADDED: STL and PLY (since OCCT 7.7.0) 3D model export.

This commit is contained in:
Alex Shvartzkop 2024-11-04 16:14:54 +03:00
parent dbf68a80b8
commit 3b987776c7
14 changed files with 230 additions and 40 deletions

View File

@ -87,6 +87,7 @@ set( OCC_LIBS_POST_78
TKDESTEP
TKDESTL
TKDEVRML
TKDEPLY
)
set(OCC_TYPE "OpenCASCADE Standard Edition")

View File

@ -30,6 +30,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM( JOB_EXPORT_PCB_3D::FORMAT,
{ JOB_EXPORT_PCB_3D::FORMAT::GLB, "step" },
{ JOB_EXPORT_PCB_3D::FORMAT::VRML, "vrml" },
{ JOB_EXPORT_PCB_3D::FORMAT::XAO, "xao" },
{ JOB_EXPORT_PCB_3D::FORMAT::PLY, "ply" },
{ JOB_EXPORT_PCB_3D::FORMAT::STL, "stl" },
} )
@ -49,6 +51,8 @@ wxString EXPORTER_STEP_PARAMS::GetDefaultExportExtension() const
case EXPORTER_STEP_PARAMS::FORMAT::BREP: return wxS( "brep" );
case EXPORTER_STEP_PARAMS::FORMAT::XAO: return wxS( "xao" );
case EXPORTER_STEP_PARAMS::FORMAT::GLB: return wxS( "glb" );
case EXPORTER_STEP_PARAMS::FORMAT::PLY: return wxS( "ply" );
case EXPORTER_STEP_PARAMS::FORMAT::STL: return wxS( "stl" );
default: return wxEmptyString; // shouldn't happen
}
}
@ -63,6 +67,8 @@ wxString EXPORTER_STEP_PARAMS::GetFormatName() const
case EXPORTER_STEP_PARAMS::FORMAT::BREP: return wxS( "BREP" );
case EXPORTER_STEP_PARAMS::FORMAT::XAO: return wxS( "XAO" );
case EXPORTER_STEP_PARAMS::FORMAT::GLB: return wxS( "Binary GLTF" );
case EXPORTER_STEP_PARAMS::FORMAT::PLY: return wxS( "PLY" );
case EXPORTER_STEP_PARAMS::FORMAT::STL: return wxS( "STL" );
default: return wxEmptyString; // shouldn't happen
}
}

View File

@ -62,7 +62,9 @@ public:
STEP,
BREP,
XAO,
GLB
GLB,
PLY,
STL
};
wxString m_NetFilter;
@ -109,7 +111,9 @@ public:
BREP,
XAO,
GLB,
VRML
VRML,
PLY,
STL
};
enum class VRML_UNITS

View File

@ -203,6 +203,8 @@ const std::string FILEEXT::StepFileAbrvExtension( "stp" );
const std::string FILEEXT::GltfBinaryFileExtension( "glb" );
const std::string FILEEXT::BrepFileExtension( "brep" );
const std::string FILEEXT::XaoFileExtension( "xao" );
const std::string FILEEXT::PlyFileExtension( "ply" );
const std::string FILEEXT::StlFileExtension( "stl" );
const wxString FILEEXT::GerberFileExtensionsRegex( "(gbr|gko|pho|(g[tb][alops])|(gm?\\d\\d*)|(gp[tb]))" );

View File

@ -192,6 +192,8 @@ public:
static const std::string GltfBinaryFileExtension;
static const std::string BrepFileExtension;
static const std::string XaoFileExtension;
static const std::string PlyFileExtension;
static const std::string StlFileExtension;
static const std::string KiCadJobSetFileExtension;

View File

@ -74,7 +74,7 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aNa
m_argParser.add_argument( ARG_FORMAT )
.default_value( std::string( "step" ) )
.help( UTF8STDSTR(
_( "Output file format, options: step, brep, xao, glb (binary glTF)" ) ) );
_( "Output file format, options: step, brep, xao, glb (binary glTF), ply, stl" ) ) );
}
m_argParser.add_argument( ARG_FORCE, "-f" )
@ -92,9 +92,8 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aNa
_( "Exclude 3D models for components with 'Do not populate' attribute" ) ) )
.flag();
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::STEP || m_format == JOB_EXPORT_PCB_3D::FORMAT::BREP
|| m_format == JOB_EXPORT_PCB_3D::FORMAT::XAO
|| m_format == JOB_EXPORT_PCB_3D::FORMAT::GLB )
if( m_format != JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN
&& m_format != JOB_EXPORT_PCB_3D::FORMAT::VRML )
{
m_argParser.add_argument( ARG_GRID_ORIGIN )
.help( UTF8STDSTR( _( "Use Grid Origin for output origin" ) ) )
@ -205,9 +204,8 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
std::unique_ptr<JOB_EXPORT_PCB_3D> step( new JOB_EXPORT_PCB_3D( true ) );
EXPORTER_STEP_PARAMS& params = step->m_3dparams;
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::STEP || m_format == JOB_EXPORT_PCB_3D::FORMAT::BREP
|| m_format == JOB_EXPORT_PCB_3D::FORMAT::XAO
|| m_format == JOB_EXPORT_PCB_3D::FORMAT::GLB )
if( m_format != JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN
&& m_format != JOB_EXPORT_PCB_3D::FORMAT::VRML )
{
params.m_UseDrillOrigin = m_argParser.get<bool>( ARG_DRILL_ORIGIN );
params.m_UseGridOrigin = m_argParser.get<bool>( ARG_GRID_ORIGIN );
@ -254,6 +252,10 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::XAO;
else if( format == wxS( "glb" ) )
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::GLB;
else if( format == wxS( "ply" ) )
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::PLY;
else if( format == wxS( "stl" ) )
step->m_format = JOB_EXPORT_PCB_3D::FORMAT::STL;
else
{
wxFprintf( stderr, _( "Invalid format specified\n" ) );
@ -329,9 +331,8 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway )
step->m_hasUserOrigin = true;
}
if( m_format == JOB_EXPORT_PCB_3D::FORMAT::STEP || m_format == JOB_EXPORT_PCB_3D::FORMAT::BREP
|| m_format == JOB_EXPORT_PCB_3D::FORMAT::XAO
|| m_format == JOB_EXPORT_PCB_3D::FORMAT::GLB )
if( m_format != JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN
&& m_format != JOB_EXPORT_PCB_3D::FORMAT::VRML )
{
wxString minDistance =
From_UTF8( m_argParser.get<std::string>( ARG_MIN_DISTANCE ).c_str() );

View File

@ -120,6 +120,8 @@ static CLI::PCB_EXPORT_3D_COMMAND exportPcbStepCmd{ "step", UTF8STDSTR( _
static CLI::PCB_EXPORT_3D_COMMAND exportPcbBrepCmd{ "brep", UTF8STDSTR( _( "Export BREP" ) ), JOB_EXPORT_PCB_3D::FORMAT::BREP };
static CLI::PCB_EXPORT_3D_COMMAND exportPcbXaoCmd{ "xao", UTF8STDSTR( _( "Export XAO" ) ), JOB_EXPORT_PCB_3D::FORMAT::XAO };
static CLI::PCB_EXPORT_3D_COMMAND exportPcbVrmlCmd{ "vrml", UTF8STDSTR( _( "Export VRML" ) ), JOB_EXPORT_PCB_3D::FORMAT::VRML };
static CLI::PCB_EXPORT_3D_COMMAND exportPcbPlyCmd{ "ply", UTF8STDSTR( _( "Export PLY" ) ), JOB_EXPORT_PCB_3D::FORMAT::PLY };
static CLI::PCB_EXPORT_3D_COMMAND exportPcbStlCmd{ "stl", UTF8STDSTR( _( "Export STL" ) ), JOB_EXPORT_PCB_3D::FORMAT::STL };
static CLI::PCB_EXPORT_SVG_COMMAND exportPcbSvgCmd{};
static CLI::PCB_EXPORT_PDF_COMMAND exportPcbPdfCmd{};
static CLI::PCB_EXPORT_POS_COMMAND exportPcbPosCmd{};
@ -198,7 +200,9 @@ static std::vector<COMMAND_ENTRY> commandStack = {
&exportPcbStepCmd,
&exportPcbSvgCmd,
&exportPcbVrmlCmd,
&exportPcbXaoCmd
&exportPcbXaoCmd,
&exportPcbPlyCmd,
&exportPcbStlCmd
}
}
}

View File

@ -56,14 +56,18 @@
static const std::vector<wxString> c_formatCommand = { FILEEXT::StepFileExtension,
FILEEXT::GltfBinaryFileExtension,
FILEEXT::XaoFileExtension,
FILEEXT::BrepFileExtension };
FILEEXT::BrepFileExtension,
FILEEXT::PlyFileExtension,
FILEEXT::StlFileExtension};
// Maps file extensions to m_choiceFormat selection
static const std::map<wxString, int> c_formatExtToChoice = { { FILEEXT::StepFileExtension, 0 },
{ FILEEXT::StepFileAbrvExtension, 0 },
{ FILEEXT::GltfBinaryFileExtension, 1 },
{ FILEEXT::XaoFileExtension, 2 },
{ FILEEXT::BrepFileExtension, 3 } };
{ FILEEXT::BrepFileExtension, 3 },
{ FILEEXT::PlyFileExtension, 4 },
{ FILEEXT::StlFileExtension, 5 }};
@ -380,7 +384,11 @@ void DIALOG_EXPORT_STEP::onBrowseClicked( wxCommandEvent& aEvent )
+ _( "XAO files" )
+ AddFileExtListToFilter( { FILEEXT::XaoFileExtension} ) + "|"
+ _( "BREP (OCCT) files" )
+ AddFileExtListToFilter( { FILEEXT::BrepFileExtension } );
+ AddFileExtListToFilter( { FILEEXT::BrepFileExtension } ) + "|"
+ _( "PLY files" )
+ AddFileExtListToFilter( { FILEEXT::PlyFileExtension} ) + "|"
+ _( "STL files" )
+ AddFileExtListToFilter( { FILEEXT::StlFileExtension} );
// clang-format on
// Build the absolute path of current output directory to preselect it in the file browser.

View File

@ -24,7 +24,7 @@ DIALOG_EXPORT_STEP_BASE::DIALOG_EXPORT_STEP_BASE( wxWindow* parent, wxWindowID i
m_txtFormat->Wrap( -1 );
bSizerTop->Add( m_txtFormat, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
wxString m_choiceFormatChoices[] = { _("STEP"), _("GLB (Binary glTF)"), _("XAO"), _("BREP (OCCT)") };
wxString m_choiceFormatChoices[] = { _("STEP"), _("GLB (Binary glTF)"), _("XAO"), _("BREP (OCCT)"), _("PLY (ASCII)"), _("STL") };
int m_choiceFormatNChoices = sizeof( m_choiceFormatChoices ) / sizeof( wxString );
m_choiceFormat = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceFormatNChoices, m_choiceFormatChoices, 0 );
m_choiceFormat->SetSelection( 0 );

View File

@ -153,7 +153,7 @@
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="choices">&quot;STEP&quot; &quot;GLB (Binary glTF)&quot; &quot;XAO&quot; &quot;BREP (OCCT)&quot;</property>
<property name="choices">&quot;STEP&quot; &quot;GLB (Binary glTF)&quot; &quot;XAO&quot; &quot;BREP (OCCT)&quot; &quot;PLY (ASCII)&quot; &quot;STL&quot;</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>

View File

@ -537,6 +537,14 @@ void EXPORTER_STEP::initOutputVariant()
m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_GLTF );
break;
case EXPORTER_STEP_PARAMS::FORMAT::PLY:
m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PLY );
break;
case EXPORTER_STEP_PARAMS::FORMAT::STL:
m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL );
break;
default:
m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN );
break;
@ -723,6 +731,10 @@ bool EXPORTER_STEP::Export()
success = m_pcbModel->WriteXAO( m_outputFile );
else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::GLB )
success = m_pcbModel->WriteGLTF( m_outputFile );
else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PLY )
success = m_pcbModel->WritePLY( m_outputFile );
else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL )
success = m_pcbModel->WriteSTL( m_outputFile );
if( !success )
{

View File

@ -115,9 +115,11 @@
#include <GC_MakeCircle.hxx>
#include <RWGltf_CafWriter.hxx>
#include <StlAPI_Writer.hxx>
#if OCC_VERSION_HEX >= 0x070700
#include <VrmlAPI_CafReader.hxx>
#include <RWPly_CafWriter.hxx>
#endif
#include <macros.h>
@ -2894,6 +2896,34 @@ TDF_Label STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document ) & source,
}
bool STEP_PCB_MODEL::performMeshing( Handle( XCAFDoc_ShapeTool ) & aShapeTool )
{
TDF_LabelSequence freeShapes;
aShapeTool->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;
aShapeTool->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.14;
const Standard_Real angularDeflection = DEG2RAD( 30.0 );
BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
Standard_True );
}
return true;
}
bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
{
if( !isBoardOutlineValid() )
@ -2906,27 +2936,7 @@ bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
m_outFmt = OUTPUT_FORMAT::FMT_OUT_GLTF;
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.14;
const Standard_Real angularDeflection = DEG2RAD( 30.0 );
BRepMesh_IncrementalMesh mesh( shape, linearDeflection, Standard_False, angularDeflection,
Standard_True );
}
performMeshing( m_assy );
wxFileName fn( aFileName );
@ -2979,3 +2989,123 @@ bool STEP_PCB_MODEL::WriteGLTF( const wxString& aFileName )
return success;
}
bool STEP_PCB_MODEL::WritePLY( const wxString& aFileName )
{
#if OCC_VERSION_HEX < 0x070700
#warning "PLY export is not supported before OCCT 7.7.0"
ReportMessage( wxT( "PLY export is not supported before OCCT 7.7.0\n" ) );
return false;
#else
if( !isBoardOutlineValid() )
{
ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
"'%s'.\n" ),
aFileName ) );
return false;
}
m_outFmt = OUTPUT_FORMAT::FMT_OUT_PLY;
performMeshing( m_assy );
wxFileName fn( aFileName );
const char* tmpFname = "$tempfile$.ply";
RWPly_CafWriter cafWriter( tmpFname );
cafWriter.SetFaceId( true ); // TODO: configurable SetPartId/SetFaceId
cafWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit( 0.001 );
cafWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(
RWMesh_CoordinateSystem_Zup );
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(), tmpFname );
if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
{
ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
tmpFname, fn.GetFullName() ) );
success = false;
}
}
wxSetWorkingDirectory( currCWD );
return success;
#endif
}
bool STEP_PCB_MODEL::WriteSTL( const wxString& aFileName )
{
if( !isBoardOutlineValid() )
{
ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
"'%s'.\n" ),
aFileName ) );
return false;
}
m_outFmt = OUTPUT_FORMAT::FMT_OUT_STL;
performMeshing( m_assy );
wxFileName fn( aFileName );
const char* tmpFname = "$tempfile$.stl";
// 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 );
bool success = StlAPI_Writer().Write( getOneShape( m_assy ), tmpFname );
if( success )
{
// Preserve the permissions of the current file
KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname );
if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) )
{
ReportMessage( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
tmpFname, fn.GetFullName() ) );
success = false;
}
}
wxSetWorkingDirectory( currCWD );
return success;
}

View File

@ -82,7 +82,9 @@ enum class OUTPUT_FORMAT
FMT_OUT_IGES,
FMT_OUT_BREP,
FMT_OUT_XAO,
FMT_OUT_GLTF
FMT_OUT_GLTF,
FMT_OUT_PLY,
FMT_OUT_STL
};
class STEP_PCB_MODEL
@ -192,6 +194,12 @@ public:
*/
bool WriteGLTF( const wxString& aFileName );
// write the assembly in PLY format (mesh)
bool WritePLY( const wxString& aFileName );
// write the assembly in STL format (mesh)
bool WriteSTL( const wxString& aFileName );
private:
/**
* @return true if the board(s) outline is valid. False otherwise
@ -226,6 +234,8 @@ private:
bool readSTEP( Handle( TDocStd_Document ) & aDoc, const char* aFname );
bool readVRML( Handle( TDocStd_Document ) & aDoc, const char* aFname );
bool performMeshing( Handle( XCAFDoc_ShapeTool ) & aShapeTool );
TDF_Label transferModel( Handle( TDocStd_Document )& source, Handle( TDocStd_Document ) & dest,
VECTOR3D aScale );

View File

@ -315,6 +315,10 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
break;
case JOB_EXPORT_PCB_3D::FORMAT::GLB: fn.SetExt( FILEEXT::GltfBinaryFileExtension );
break;
case JOB_EXPORT_PCB_3D::FORMAT::PLY: fn.SetExt( FILEEXT::PlyFileExtension );
break;
case JOB_EXPORT_PCB_3D::FORMAT::STL: fn.SetExt( FILEEXT::StlFileExtension );
break;
default:
return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
}
@ -383,6 +387,12 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
case JOB_EXPORT_PCB_3D::FORMAT::GLB:
params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::GLB;
break;
case JOB_EXPORT_PCB_3D::FORMAT::PLY:
params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::PLY;
break;
case JOB_EXPORT_PCB_3D::FORMAT::STL:
params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::STL;
break;
default:
return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
}