diff --git a/common/jobs/job_export_pcb_3d.cpp b/common/jobs/job_export_pcb_3d.cpp index 1cb70d0443..563563969d 100644 --- a/common/jobs/job_export_pcb_3d.cpp +++ b/common/jobs/job_export_pcb_3d.cpp @@ -33,6 +33,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM( JOB_EXPORT_PCB_3D::FORMAT, { JOB_EXPORT_PCB_3D::FORMAT::XAO, "xao" }, { JOB_EXPORT_PCB_3D::FORMAT::PLY, "ply" }, { JOB_EXPORT_PCB_3D::FORMAT::STL, "stl" }, + { JOB_EXPORT_PCB_3D::FORMAT::U3D, "u3d" }, + { JOB_EXPORT_PCB_3D::FORMAT::PDF, "pdf" }, } ) NLOHMANN_JSON_SERIALIZE_ENUM( JOB_EXPORT_PCB_3D::VRML_UNITS, @@ -53,7 +55,9 @@ wxString EXPORTER_STEP_PARAMS::GetDefaultExportExtension() const 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" ); + case EXPORTER_STEP_PARAMS::FORMAT::STL: return wxS( "stl" ); + case EXPORTER_STEP_PARAMS::FORMAT::U3D: return wxS( "u3d" ); + case EXPORTER_STEP_PARAMS::FORMAT::PDF: return wxS( "pdf" ); default: return wxEmptyString; // shouldn't happen } } @@ -71,6 +75,8 @@ wxString EXPORTER_STEP_PARAMS::GetFormatName() const 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" ); + case EXPORTER_STEP_PARAMS::FORMAT::U3D: return wxS( "Universal 3D" ); + case EXPORTER_STEP_PARAMS::FORMAT::PDF: return wxS( "PDF" ); default: return wxEmptyString; // shouldn't happen } } @@ -167,7 +173,9 @@ void JOB_EXPORT_PCB_3D::SetStepFormat( EXPORTER_STEP_PARAMS::FORMAT aFormat ) case EXPORTER_STEP_PARAMS::FORMAT::XAO: m_format = JOB_EXPORT_PCB_3D::FORMAT::XAO; break; case EXPORTER_STEP_PARAMS::FORMAT::BREP: m_format = JOB_EXPORT_PCB_3D::FORMAT::BREP; break; case EXPORTER_STEP_PARAMS::FORMAT::PLY: m_format = JOB_EXPORT_PCB_3D::FORMAT::PLY; break; - case EXPORTER_STEP_PARAMS::FORMAT::STL: m_format = JOB_EXPORT_PCB_3D::FORMAT::STL; break; + case EXPORTER_STEP_PARAMS::FORMAT::STL: m_format = JOB_EXPORT_PCB_3D::FORMAT::STL; break; + case EXPORTER_STEP_PARAMS::FORMAT::U3D: m_format = JOB_EXPORT_PCB_3D::FORMAT::U3D; break; + case EXPORTER_STEP_PARAMS::FORMAT::PDF: m_format = JOB_EXPORT_PCB_3D::FORMAT::PDF; break; } } diff --git a/common/jobs/job_export_pcb_3d.h b/common/jobs/job_export_pcb_3d.h index 756a385901..cebb85fc0c 100644 --- a/common/jobs/job_export_pcb_3d.h +++ b/common/jobs/job_export_pcb_3d.h @@ -68,7 +68,9 @@ public: XAO, GLB, PLY, - STL + STL, + U3D, + PDF }; wxString m_NetFilter; @@ -125,7 +127,9 @@ public: GLB, VRML, PLY, - STL + STL, + U3D, + PDF }; enum class VRML_UNITS diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp index 3cd3ad8a40..ff9817db94 100644 --- a/common/plotters/PDF_plotter.cpp +++ b/common/plotters/PDF_plotter.cpp @@ -57,6 +57,8 @@ PDF_PLOTTER::~PDF_PLOTTER() = default; +#include +#include std::string PDF_PLOTTER::encodeStringForPlotter( const wxString& aText ) { @@ -934,12 +936,12 @@ void PDF_PLOTTER::ClosePage() } - int hyperLinkArrayHandle = -1; + int annotArrayHandle = -1; // If we have added any annotation links, create an array containing all the objects if( annotHandles.size() > 0 ) { - hyperLinkArrayHandle = startPdfObject(); + annotArrayHandle = startPdfObject(); bool isFirst = true; fmt::print( m_outputFile, "[" ); @@ -970,13 +972,18 @@ void PDF_PLOTTER::ClosePage() " /ProcSet [/PDF /Text /ImageC /ImageB]\n" " /Font {} 0 R\n" " /XObject {} 0 R >>\n" - "/MediaBox [0 0 {:g} {:g}]\n" - "/Contents {} 0 R\n", - m_pageTreeHandle, m_fontResDictHandle, m_imgResDictHandle, psPaperSize.x, psPaperSize.y, - m_pageStreamHandle ); + "/MediaBox [0 0 {:g} {:g}]\n", + m_pageTreeHandle, + m_fontResDictHandle, + m_imgResDictHandle, + psPaperSize.x, + psPaperSize.y ); + + if( m_pageStreamHandle != -1 ) + fmt::print( m_outputFile, "/Contents {} 0 R\n", m_pageStreamHandle ); if( annotHandles.size() > 0 ) - fmt::print( m_outputFile, "/Annots {} 0 R", hyperLinkArrayHandle ); + fmt::print( m_outputFile, "/Annots {} 0 R", annotArrayHandle ); fmt::print( m_outputFile, ">>\n" ); @@ -2516,6 +2523,9 @@ void PDF_PLOTTER::Plot3DModel( const wxString& aSourcePath, for( const PDF_3D_VIEW& view : a3DViews ) { + // this is a strict need + wxASSERT( view.m_cameraMatrix.size() == 12 ); + int fovHandle = -1; if( !m_fovMap.contains( view.m_fov ) ) { @@ -2629,4 +2639,49 @@ void PDF_PLOTTER::Plot3DModel( const wxString& aSourcePath, closePdfObject(); outputFFile.Detach(); // Don't close it +} + + +std::vector PDF_PLOTTER::CreateC2WMatrixFromAngles( const VECTOR3D& aTargetPosition, + float aCameraDistance, + float aYawDegrees, + float aPitchDegrees, + float aRollDegrees ) +{ + float yRadians = glm::radians( aYawDegrees ); + float xRadians = glm::radians( aPitchDegrees ); + float zRadians = glm::radians( aRollDegrees ); + + // Create rotation matrix from Euler angles + glm::mat4 rotationMatrix = glm::eulerAngleYXZ( yRadians, xRadians, zRadians ); + + // Calculate camera position based on target, distance, and rotation + // Start with a vector pointing backward along the z-axis + glm::vec4 cameraOffset = glm::vec4( 0.0f, 0.0f, aCameraDistance, 1.0f ); + + // Apply rotation to this offset + cameraOffset = rotationMatrix * cameraOffset; + + // Camera position is target position minus the rotated offset + glm::vec3 cameraPosition = glm::vec3(aTargetPosition.x, aTargetPosition.y, aTargetPosition.z) + - glm::vec3( cameraOffset ); + + std::vector result( 12 ); + + // Handle rotation part in column-major order (first 9 elements) + int index = 0; + for( int col = 0; col < 3; ++col ) + { + for( int row = 0; row < 3; ++row ) + { + result[index++] = static_cast( rotationMatrix[col][row] ); + } + } + + // Handle translation part (last 3 elements) + result[9] = static_cast( cameraPosition.x ); + result[10] = static_cast( cameraPosition.y ); + result[11] = static_cast( cameraPosition.z ); + + return result; } \ No newline at end of file diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h index fc6786665f..64612f46a6 100644 --- a/include/plotters/plotters_pslike.h +++ b/include/plotters/plotters_pslike.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace MARKUP { struct NODE; } @@ -240,7 +241,7 @@ protected: struct PDF_3D_VIEW { std::string m_name; - float m_cameraMatrix[12]; + std::vector m_cameraMatrix; float m_cameraCenter; float m_fov; }; @@ -442,6 +443,24 @@ public: */ void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor ) override; + /** + * Generates the camera to world matrix for use with a 3D View. + * + * @param aTargetPosition The position of the target, which is also the camera rotation center + * @param aCameraDistance The distance the camera should be set back from the target + * @param aYawDegrees The yaw angle in degrees + * @param aPitchDegrees The pitch angle in degrees + * @param aRollDegrees The roll angle in degrees + * + * @return A vector of 12 floats that represent the camera to world matrix + * in linear form as specified by the PDF Spec 1.7 section 13.6.5 + * + */ + static std::vector CreateC2WMatrixFromAngles( const VECTOR3D& aTargetPosition, + float aCameraDistance, + float aYawDegrees, + float aPitchDegrees, + float aRollDegrees ); protected: struct OUTLINE_NODE diff --git a/kicad/cli/command_pcb_export_3d.cpp b/kicad/cli/command_pcb_export_3d.cpp index e48a265a45..63b56dae15 100644 --- a/kicad/cli/command_pcb_export_3d.cpp +++ b/kicad/cli/command_pcb_export_3d.cpp @@ -270,6 +270,10 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway ) step->m_format = JOB_EXPORT_PCB_3D::FORMAT::PLY; else if( format == wxS( "stl" ) ) step->m_format = JOB_EXPORT_PCB_3D::FORMAT::STL; + else if( format == wxS( "u3d" ) ) + step->m_format = JOB_EXPORT_PCB_3D::FORMAT::U3D; + else if( format == wxS( "pdf" ) ) + step->m_format = JOB_EXPORT_PCB_3D::FORMAT::PDF; else { wxFprintf( stderr, _( "Invalid format specified\n" ) ); diff --git a/kicad/kicad_cli.cpp b/kicad/kicad_cli.cpp index 13ae7d23c1..05977e9070 100644 --- a/kicad/kicad_cli.cpp +++ b/kicad/kicad_cli.cpp @@ -130,6 +130,9 @@ static CLI::PCB_EXPORT_3D_COMMAND exportPcbVrmlCmd{ "vrml", UTF8STDSTR( _ 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_3D_COMMAND exportPcbStepzCmd{ "stpz", UTF8STDSTR( _( "Export STEPZ" ) ), JOB_EXPORT_PCB_3D::FORMAT::STEPZ }; +static CLI::PCB_EXPORT_3D_COMMAND exportPcbU3DCmd{ "u3d", UTF8STDSTR( _( "Export U3D" ) ), JOB_EXPORT_PCB_3D::FORMAT::U3D }; +static CLI::PCB_EXPORT_3D_COMMAND exportPcb3DPDFCmd{ "3dpdf", UTF8STDSTR( _( "Export PDF" ) ), + JOB_EXPORT_PCB_3D::FORMAT::PDF }; static CLI::PCB_EXPORT_SVG_COMMAND exportPcbSvgCmd{}; static CLI::PCB_EXPORT_PDF_COMMAND exportPcbPdfCmd{}; static CLI::PCB_EXPORT_POS_COMMAND exportPcbPosCmd{}; @@ -221,7 +224,9 @@ static std::vector commandStack = { &exportPcbXaoCmd, &exportPcbPlyCmd, &exportPcbStlCmd, - &exportPcbStepzCmd + &exportPcbStepzCmd, + &exportPcbU3DCmd, + &exportPcb3DPDFCmd } }, { diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index be829f261f..37d44a9966 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -236,8 +236,14 @@ set( PCBNEW_EXPORTERS exporters/export_gencad_writer.cpp exporters/export_idf.cpp exporters/step/exporter_step.cpp + exporters/step/kicad3d_info.cpp exporters/step/step_pcb_model.cpp exporters/step/KI_XCAFDoc_AssemblyGraph.cxx + exporters/u3d/bit_stream_writer.cpp + exporters/u3d/constants.cpp + exporters/u3d/context_manager.cpp + exporters/u3d/data_block.cpp + exporters/u3d/writer.cpp exporters/exporter_vrml.cpp exporters/place_file_exporter.cpp exporters/gen_drill_report_files.cpp diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp index 1d884d099a..d7ac04ab11 100644 --- a/pcbnew/dialogs/dialog_export_step.cpp +++ b/pcbnew/dialogs/dialog_export_step.cpp @@ -61,7 +61,10 @@ static const std::vector c_formatCommand = { FILEEXT::StepFileExtensio FILEEXT::BrepFileExtension, FILEEXT::PlyFileExtension, FILEEXT::StlFileExtension, - FILEEXT::StepZFileAbrvExtension }; + FILEEXT::StepZFileAbrvExtension, + FILEEXT::U3DFileExtension, + FILEEXT::PdfFileExtension, + }; // Maps file extensions to m_choiceFormat selection static const std::map c_formatExtToChoice = { { FILEEXT::StepFileExtension, 0 }, @@ -71,7 +74,9 @@ static const std::map c_formatExtToChoice = { { FILEEXT::StepFile { FILEEXT::BrepFileExtension, 3 }, { FILEEXT::PlyFileExtension, 4 }, { FILEEXT::StlFileExtension, 5 }, - { FILEEXT::StepZFileAbrvExtension, 6 }}; + { FILEEXT::StepZFileAbrvExtension, 6 }, + { FILEEXT::U3DFileExtension, 7 }, + { FILEEXT::PdfFileExtension, 8 }}; DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aEditFrame, const wxString& aBoardPath ) : @@ -285,7 +290,11 @@ void DIALOG_EXPORT_STEP::onBrowseClicked( wxCommandEvent& aEvent ) + _( "PLY files" ) + AddFileExtListToFilter( { FILEEXT::PlyFileExtension} ) + "|" + _( "STL files" ) - + AddFileExtListToFilter( { FILEEXT::StlFileExtension} ); + + AddFileExtListToFilter( { FILEEXT::StlFileExtension} ) + "|" + + _( "Universal 3D files" ) + + AddFileExtListToFilter( { FILEEXT::U3DFileExtension} )+ "|" + + _( "PDF files" ) + + AddFileExtListToFilter( { FILEEXT::PdfFileExtension} ); // clang-format on // Build the absolute path of current output directory to preselect it in the file browser. @@ -596,13 +605,15 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) // ensure the main format on the job is populated switch( m_job->m_3dparams.m_Format ) { - case EXPORTER_STEP_PARAMS::FORMAT::STEP: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::STEP; break; + case EXPORTER_STEP_PARAMS::FORMAT::STEP: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::STEP; break; case EXPORTER_STEP_PARAMS::FORMAT::STEPZ: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::STEPZ; break; - case EXPORTER_STEP_PARAMS::FORMAT::GLB: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::GLB; break; - case EXPORTER_STEP_PARAMS::FORMAT::XAO: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::XAO; break; - case EXPORTER_STEP_PARAMS::FORMAT::BREP: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::BREP; break; - case EXPORTER_STEP_PARAMS::FORMAT::PLY: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::PLY; break; - case EXPORTER_STEP_PARAMS::FORMAT::STL: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::STL; break; + case EXPORTER_STEP_PARAMS::FORMAT::GLB: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::GLB; break; + case EXPORTER_STEP_PARAMS::FORMAT::XAO: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::XAO; break; + case EXPORTER_STEP_PARAMS::FORMAT::BREP: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::BREP; break; + case EXPORTER_STEP_PARAMS::FORMAT::PLY: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::PLY; break; + case EXPORTER_STEP_PARAMS::FORMAT::STL: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::STL; break; + case EXPORTER_STEP_PARAMS::FORMAT::U3D: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::U3D; break; + case EXPORTER_STEP_PARAMS::FORMAT::PDF: m_job->m_format = JOB_EXPORT_PCB_3D::FORMAT::PDF; break; } m_job->m_3dparams.m_UseDrillOrigin = false; diff --git a/pcbnew/dialogs/dialog_export_step_base.cpp b/pcbnew/dialogs/dialog_export_step_base.cpp index bcac5e8ed2..20cabaa78d 100644 --- a/pcbnew/dialogs/dialog_export_step_base.cpp +++ b/pcbnew/dialogs/dialog_export_step_base.cpp @@ -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)"), _("PLY (ASCII)"), _("STL"), _("STPZ") }; + wxString m_choiceFormatChoices[] = { _("STEP"), _("GLB (Binary glTF)"), _("XAO"), _("BREP (OCCT)"), _("PLY (ASCII)"), _("STL"), _("STPZ"), _("U3D"), _("PDF") }; 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 ); diff --git a/pcbnew/dialogs/dialog_export_step_base.fbp b/pcbnew/dialogs/dialog_export_step_base.fbp index 9c9a8f3550..57c1cdfba4 100644 --- a/pcbnew/dialogs/dialog_export_step_base.fbp +++ b/pcbnew/dialogs/dialog_export_step_base.fbp @@ -153,7 +153,7 @@ 1 0 - "STEP" "GLB (Binary glTF)" "XAO" "BREP (OCCT)" "PLY (ASCII)" "STL" "STPZ" + "STEP" "GLB (Binary glTF)" "XAO" "BREP (OCCT)" "PLY (ASCII)" "STL" "STPZ" "U3D" "PDF" 1 1 diff --git a/pcbnew/exporters/step/exporter_step.cpp b/pcbnew/exporters/step/exporter_step.cpp index 981174e372..f409cafb78 100644 --- a/pcbnew/exporters/step/exporter_step.cpp +++ b/pcbnew/exporters/step/exporter_step.cpp @@ -679,6 +679,14 @@ void EXPORTER_STEP::initOutputVariant() m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_STL ); break; + case EXPORTER_STEP_PARAMS::FORMAT::U3D: + m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_U3D ); + break; + + case EXPORTER_STEP_PARAMS::FORMAT::PDF: + m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_PDF ); + break; + default: m_pcbModel->SpecializeVariant( OUTPUT_FORMAT::FMT_OUT_UNKNOWN ); break; @@ -891,6 +899,10 @@ bool EXPORTER_STEP::Export() success = m_pcbModel->WritePLY( m_outputFile ); else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::STL ) success = m_pcbModel->WriteSTL( m_outputFile ); + else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::U3D ) + success = m_pcbModel->WriteU3D( m_outputFile ); + else if( m_params.m_Format == EXPORTER_STEP_PARAMS::FORMAT::PDF ) + success = m_pcbModel->WritePDF( m_outputFile ); if( !success ) { @@ -917,6 +929,7 @@ bool EXPORTER_STEP::Export() RPT_SEVERITY_ERROR ); return false; } + #ifndef DEBUG catch( ... ) { m_reporter->Report( wxString::Format( _( "\n" @@ -925,6 +938,7 @@ bool EXPORTER_STEP::Export() RPT_SEVERITY_ERROR ); return false; } + #endif // Display calculation time in seconds double calculation_time = (double)( GetRunningMicroSecs() - stats_startExportTime) / 1e6; diff --git a/pcbnew/exporters/step/kicad3d_info.cpp b/pcbnew/exporters/step/kicad3d_info.cpp new file mode 100644 index 0000000000..92d5cfff1e --- /dev/null +++ b/pcbnew/exporters/step/kicad3d_info.cpp @@ -0,0 +1,91 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 "exporters/step/kicad3d_info.h" + +const Standard_GUID& KICAD3D_INFO::GetID() +{ + static Standard_GUID ID( "D683031E-AD81-4084-B0F3-C0A474CAA93D" ); + return ID; +} + + +Handle( KICAD3D_INFO ) KICAD3D_INFO::Set( const TDF_Label& aLabel, + KICAD3D_MODEL_TYPE aModelType, + std::string aDisplayName ) +{ + Handle( KICAD3D_INFO ) T; + if( !aLabel.FindAttribute( KICAD3D_INFO::GetID(), T ) ) + { + T = new KICAD3D_INFO(); + T->SetModelType( aModelType ); + T->SetDisplayName( aDisplayName ); + aLabel.AddAttribute( T ); + } + return T; +} + + +const Standard_GUID& KICAD3D_INFO::ID() const +{ + return GetID(); +} + + +void KICAD3D_INFO::Restore( const Handle( TDF_Attribute ) & aAttribute ) +{ + Handle( KICAD3D_INFO ) old = Handle( KICAD3D_INFO )::DownCast( aAttribute ); + + m_modelType = old->m_modelType; + m_displayName = old->m_displayName; +} + + +Handle( TDF_Attribute ) KICAD3D_INFO::NewEmpty() const +{ + return new KICAD3D_INFO(); +} + + +void KICAD3D_INFO::Paste( const Handle( TDF_Attribute ) & aAttribute, + const Handle( TDF_RelocationTable ) & aRelocationTable ) const +{ + Handle( KICAD3D_INFO ) that = Handle( KICAD3D_INFO )::DownCast( aAttribute ); + + that->m_modelType = m_modelType; + that->m_displayName = m_displayName; +} + + +Standard_OStream& KICAD3D_INFO::Dump( Standard_OStream& aOS ) const +{ + aOS << "KICAD3D_INFO" << "\n"; + aOS << "Model Type: " << ( m_modelType == KICAD3D_MODEL_TYPE::BOARD ? "Board" : "Component" ) << "\n"; + return aOS; +} + + +KICAD3D_INFO::KICAD3D_INFO() : m_modelType( KICAD3D_MODEL_TYPE::BOARD ) +{ +} \ No newline at end of file diff --git a/pcbnew/exporters/step/kicad3d_info.h b/pcbnew/exporters/step/kicad3d_info.h new file mode 100644 index 0000000000..883dbd628e --- /dev/null +++ b/pcbnew/exporters/step/kicad3d_info.h @@ -0,0 +1,80 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + */ + +#pragma once + +#include +#include +#include +#include + + +enum class KICAD3D_MODEL_TYPE +{ + BOARD, + COMPONENT +}; + + +/** + * This is an internal KiCad use attribute to add additional markup to a opencascade + * document for internal processing + */ +class KICAD3D_INFO : public TDF_Attribute +{ +public: + /** + * Get the GUID of this attribute + */ + static const Standard_GUID& GetID(); + + /** + * Finds or creates the attribute attached to . + * The found or created attribute is returned + */ + static Handle( KICAD3D_INFO ) Set( const TDF_Label& aLabel, + KICAD3D_MODEL_TYPE aModelType, + std::string aDisplayName = "" ); + +public: + KICAD3D_INFO(); + + KICAD3D_MODEL_TYPE GetModelType() const { return m_modelType; } + void SetModelType( const KICAD3D_MODEL_TYPE& aModelType ) { m_modelType = aModelType; } + + const std::string& GetDisplayName() const { return m_displayName; } + void SetDisplayName( const std::string& aName ) { m_displayName = aName; } + + //Overridden methods from TDF_Attribute + const Standard_GUID& ID() const override; + void Restore( const Handle( TDF_Attribute ) & aAttribute ) override; + Handle( TDF_Attribute ) NewEmpty() const override; + void Paste( const Handle( TDF_Attribute ) & aAttribute, + const Handle( TDF_RelocationTable ) & aRelocationTable ) const override; + Standard_OStream& Dump( Standard_OStream& aOS ) const override; + +private: + KICAD3D_MODEL_TYPE m_modelType; + std::string m_displayName; +}; \ No newline at end of file diff --git a/pcbnew/exporters/step/step_pcb_model.cpp b/pcbnew/exporters/step/step_pcb_model.cpp index 6cca8fbac5..93c454f6fc 100644 --- a/pcbnew/exporters/step/step_pcb_model.cpp +++ b/pcbnew/exporters/step/step_pcb_model.cpp @@ -58,6 +58,11 @@ #include #include +#include + +#include +#include + #include "step_pcb_model.h" #include "streamwrapper.h" @@ -93,6 +98,7 @@ #include #include #include +#include "kicad3d_info.h" #include "KI_XCAFDoc_AssemblyGraph.hxx" @@ -1261,6 +1267,8 @@ bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std:: TCollection_ExtendedString refdes( aRefDes.c_str() ); TDataStd_Name::Set( llabel, refdes ); + KICAD3D_INFO::Set( llabel, KICAD3D_MODEL_TYPE::COMPONENT, aRefDes ); + return true; } @@ -2189,7 +2197,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, const VECTOR2D& aOrigi auto pushToAssemblyMap = [&]( const std::map>& aShapesMap, const TDF_Label& aVisMatLabel, const wxString& aShapeName, bool aCompoundNets, - bool aCompoundAll ) + bool aCompoundAll, const wxString& aNiceName ) { std::map> shapesMap; @@ -2225,6 +2233,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, const VECTOR2D& aOrigi // Dont expand the component or else coloring it gets hard TDF_Label lbl = m_assy->AddComponent( m_assy_label, shape, false ); + KICAD3D_INFO::Set( lbl, KICAD3D_MODEL_TYPE::BOARD, aNiceName.ToStdString() ); m_pcb_labels.push_back( lbl ); if( m_pcb_labels.back().IsNull() ) @@ -2267,11 +2276,11 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, const VECTOR2D& aOrigi auto pushToAssembly = [&]( const std::vector& aShapesList, const TDF_Label& aVisMatLabel, - const wxString& aShapeName, bool aCompound ) + const wxString& aShapeName, bool aCompound, const wxString& aNiceName ) { const std::map> shapesMap{ { wxEmptyString, aShapesList } }; - pushToAssemblyMap( shapesMap, aVisMatLabel, aShapeName, aCompound, aCompound ); + pushToAssemblyMap( shapesMap, aVisMatLabel, aShapeName, aCompound, aCompound, aNiceName ); }; auto makeMaterial = @@ -2332,17 +2341,17 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, const VECTOR2D& aOrigi TDF_Label pad_mat = makeMaterial( "pad", pad_color, 1.0, 0.4 ); TDF_Label board_mat = makeMaterial( "board", board_color, 0.0, 0.8 ); - pushToAssemblyMap( m_board_copper, copper_mat, "copper", true, true ); - pushToAssemblyMap( m_board_copper_pads, pad_mat, "pad", true, true ); - pushToAssemblyMap( m_board_copper_vias, copper_mat, "via", true, true ); - pushToAssemblyMap( m_board_copper_fused, copper_mat, "copper", true, true ); - pushToAssembly( m_board_front_silk, front_silk_mat, "silkscreen", true ); - pushToAssembly( m_board_back_silk, back_silk_mat, "silkscreen", true ); - pushToAssembly( m_board_front_mask, front_mask_mat, "soldermask", true ); - pushToAssembly( m_board_back_mask, back_mask_mat, "soldermask", true ); + pushToAssemblyMap( m_board_copper, copper_mat, "copper", true, true, "Copper" ); + pushToAssemblyMap( m_board_copper_pads, pad_mat, "pad", true, true, "Pads" ); + pushToAssemblyMap( m_board_copper_vias, copper_mat, "via", true, true, "Via" ); + pushToAssemblyMap( m_board_copper_fused, copper_mat, "copper", true, true, "Copper" ); + pushToAssembly( m_board_front_silk, front_silk_mat, "silkscreen", true, "Top Silkscreen" ); + pushToAssembly( m_board_back_silk, back_silk_mat, "silkscreen", true, "Bottom Silkscreen" ); + pushToAssembly( m_board_front_mask, front_mask_mat, "soldermask", true, "Top Soldermask" ); + pushToAssembly( m_board_back_mask, back_mask_mat, "soldermask", true, "Bottom Soldermask" ); if( aPushBoardBody ) - pushToAssembly( m_board_outlines, board_mat, "PCB", false ); + pushToAssembly( m_board_outlines, board_mat, "PCB", false, "Body" ); #if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 ) m_assy->UpdateAssemblies(); @@ -3423,3 +3432,168 @@ bool STEP_PCB_MODEL::WriteSTL( const wxString& aFileName ) return success; } + + + +bool STEP_PCB_MODEL::WriteU3D( const wxString& aFileName ) +{ + if( !isBoardOutlineValid() ) + { + m_reporter->Report( + wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ), aFileName ), + RPT_SEVERITY_ERROR ); + return false; + } + + m_outFmt = OUTPUT_FORMAT::FMT_OUT_U3D; + + performMeshing( m_assy ); + + wxFileName fn( aFileName ); + + const char* tmpFname = "$tempfile$.u3d"; + + // 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 ); + + U3D::WRITER writer( tmpFname ); + bool success = writer.Perform( m_doc ); + if( success ) + { + // Preserve the permissions of the current file + KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), tmpFname ); + + if( !wxRenameFile( tmpFname, fn.GetFullName(), true ) ) + { + m_reporter->Report( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ), tmpFname, + fn.GetFullName() ), + RPT_SEVERITY_ERROR ); + success = false; + } + } + + wxSetWorkingDirectory( currCWD ); + + return success; +} + + +bool STEP_PCB_MODEL::WritePDF( const wxString& aFileName ) +{ + if( !isBoardOutlineValid() ) + { + m_reporter->Report( + wxString::Format( _( "No valid PCB assembly; cannot create output file '%s'.\n" ), aFileName ), + RPT_SEVERITY_ERROR ); + return false; + } + + m_outFmt = OUTPUT_FORMAT::FMT_OUT_PDF; + + performMeshing( m_assy ); + + wxFileName fn( aFileName ); + + wxFileName u3dTmpfn = wxFileName::CreateTempFileName( "" ); + wxFileName pdfTmpfn = wxFileName::CreateTempFileName( "" ); + + U3D::WRITER writer( u3dTmpfn.GetFullPath().ToStdString() ); + bool success = writer.Perform( m_doc ); + + // PDF test + std::unique_ptr plotter = std::make_unique(); + + plotter->SetColorMode( true ); + plotter->Set3DExport( true ); + plotter->SetCreator( wxT( "Mark's awesome 3d exporter" ) ); + KIGFX::PCB_RENDER_SETTINGS renderSettings; + plotter->SetRenderSettings( &renderSettings ); + + if( !plotter->OpenFile( pdfTmpfn.GetFullPath() ) ) + { + m_reporter->Report( wxString::Format( wxT( "Cannot open temporary file '%s'.\n" ), pdfTmpfn.GetFullPath() ), + RPT_SEVERITY_ERROR ); + success = false; + } + else + { + plotter->StartPlot( "1", "3D Model" ); + double fov_degrees = 16.5f; + + // kind of an arbitrary distance determination + float distance = sqrt( writer.GetMeshBoundingBox().SquareExtent() ) * 3; + + std::vector views; + + VECTOR3D camTarget = writer.GetCenter(); + + + std::vector c2wMatrix = + PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 180.0f, -75.0f, 25.0f ); + + views.emplace_back( PDF_3D_VIEW{ + .m_name = "Default", + .m_cameraMatrix = c2wMatrix, + .m_cameraCenter = (float) distance, + .m_fov = (float) fov_degrees, + } ); + + + + c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 180.0, 0.0f, 0.0f ); + + views.emplace_back( PDF_3D_VIEW{ + .m_name = "Top", + .m_cameraMatrix = c2wMatrix, + .m_cameraCenter = (float) distance, + .m_fov = (float) fov_degrees, + } ); + + + + c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 0.0, 0.0f, 0.0f ); + + views.emplace_back( PDF_3D_VIEW{ + .m_name = "Bottom", + .m_cameraMatrix = c2wMatrix, + .m_cameraCenter = (float) distance, + .m_fov = (float) fov_degrees, + } ); + + + + c2wMatrix = PDF_PLOTTER::CreateC2WMatrixFromAngles( camTarget, distance, 90.0f, -90.0f, 90.0f ); + + views.emplace_back( PDF_3D_VIEW{ + .m_name = "Front", + .m_cameraMatrix = c2wMatrix, + .m_cameraCenter = (float) distance, + .m_fov = (float) fov_degrees, + } ); + + plotter->Plot3DModel( u3dTmpfn.GetFullPath(), views ); + plotter->EndPlot(); + } + + if( success ) + { + // Preserve the permissions of the current file + KIPLATFORM::IO::DuplicatePermissions( fn.GetFullPath(), pdfTmpfn.GetFullPath() ); + + if( !wxRenameFile( pdfTmpfn.GetFullPath(), fn.GetFullPath(), true ) ) + { + m_reporter->Report( wxString::Format( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ), + pdfTmpfn.GetFullPath(), fn.GetFullPath() ), + RPT_SEVERITY_ERROR ); + success = false; + } + } + + wxRemoveFile( u3dTmpfn.GetFullPath() ); + + return success; +} \ No newline at end of file diff --git a/pcbnew/exporters/step/step_pcb_model.h b/pcbnew/exporters/step/step_pcb_model.h index 7503affbba..f19baaf80b 100644 --- a/pcbnew/exporters/step/step_pcb_model.h +++ b/pcbnew/exporters/step/step_pcb_model.h @@ -84,7 +84,9 @@ enum class OUTPUT_FORMAT FMT_OUT_XAO, FMT_OUT_GLTF, FMT_OUT_PLY, - FMT_OUT_STL + FMT_OUT_STL, + FMT_OUT_U3D, + FMT_OUT_PDF }; class STEP_PCB_MODEL @@ -212,6 +214,11 @@ public: // write the assembly in STL format (mesh) bool WriteSTL( const wxString& aFileName ); + // write the assembly in U3D format (mesh) + bool WriteU3D( const wxString& aFileName ); + + // write the assembly in U3D format (mesh) + bool WritePDF( const wxString& aFileName ); private: /** * @return true if the board(s) outline is valid. False otherwise diff --git a/pcbnew/exporters/u3d/bit_stream_writer.cpp b/pcbnew/exporters/u3d/bit_stream_writer.cpp new file mode 100644 index 0000000000..86174a3a49 --- /dev/null +++ b/pcbnew/exporters/u3d/bit_stream_writer.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + +#include +#include +#include "bit_stream_writer.h" +#include "constants.h" + +using namespace U3D; + +BIT_STREAM_WRITER::BIT_STREAM_WRITER() : + m_contextManager( nullptr ), + m_high( 0x0000FFFF ), + m_low( 0 ), + m_underflow( 0 ), + m_compressed( false ), + m_data(), + m_dataPosition( 0 ), + m_dataLocal( 0 ), + m_dataLocalNext( 0 ), + m_dataBitOffset( 0 ) +{ + m_contextManager = std::make_unique(); + m_data = std::vector( m_dataSizeIncrement ); +} + + +void BIT_STREAM_WRITER::WriteU8( uint8_t aValue ) +{ + uint32_t symbol = (uint32_t) aValue; + swapBits8( symbol ); + bool escape = false; + writeSymbol( CONSTANTS::Context8, symbol, escape ); +} + + +void BIT_STREAM_WRITER::WriteU16( uint16_t uValue ) +{ + WriteU8( (uint8_t) ( 0x00FF & uValue ) ); + WriteU8( (uint8_t) ( 0x00FF & ( uValue >> 8 ) ) ); +} + + +void BIT_STREAM_WRITER::WriteU32( uint32_t uValue ) +{ + WriteU16( (uint16_t) ( 0x0000FFFF & uValue ) ); + WriteU16( (uint16_t) ( 0x0000FFFF & ( uValue >> 16 ) ) ); +} + + +void BIT_STREAM_WRITER::WriteU64( uint64_t uValue ) +{ + WriteU32( (uint32_t) ( 0x00000000FFFFFFFF & uValue ) ); + WriteU32( (uint32_t) ( 0x00000000FFFFFFFF & ( uValue >> 32 ) ) ); +} + + +void BIT_STREAM_WRITER::WriteI32( int32_t iValue ) +{ + WriteU32( (uint32_t) iValue ); +} + + +void BIT_STREAM_WRITER::WriteF32( float fValue ) +{ + uint32_t uValue; + std::memcpy( &uValue, &fValue, sizeof( fValue ) ); + WriteU32( uValue ); +} + + +void BIT_STREAM_WRITER::WriteF64( double aValue ) +{ + uint64_t uValue; + std::memcpy( &uValue, &aValue, sizeof( aValue ) ); + WriteU64( uValue ); +} + + +void BIT_STREAM_WRITER::WriteCompressedU32( uint32_t aContext, uint32_t uValue ) +{ + m_compressed = true; + bool escape = false; + if( ( aContext != 0 ) && ( aContext < CONSTANTS::MaxRange ) ) + { + writeSymbol( aContext, uValue, escape ); + if( escape == true ) + { + WriteU32( uValue ); + //Assuming symbol is a class member, and is in scope. + //If symbol is not in scope, you will have to find the correct + //symbol value to add to the context manager. + m_contextManager->AddSymbol( aContext, uValue + 1 ); + } + } + else + { + WriteU32( uValue ); + } +} + + +void BIT_STREAM_WRITER::WriteCompressedU16( uint32_t aContext, uint16_t uValue ) +{ + m_compressed = true; + bool escape = false; + if( ( aContext != 0 ) && ( aContext < CONSTANTS::MaxRange ) ) + { + writeSymbol( aContext, uValue, escape ); + if( escape == true ) + { + WriteU16( uValue ); + //Assuming symbol is a class member, and is in scope. + //If symbol is not in scope, you will have to find the correct + //symbol value to add to the context manager. + m_contextManager->AddSymbol( aContext, uValue + 1 ); + } + } + else + { + WriteU16( uValue ); + } +} + + +void BIT_STREAM_WRITER::WriteCompressedU8( uint32_t aContext, uint8_t uValue ) +{ + m_compressed = true; + bool escape = false; + if( ( aContext != 0 ) && ( aContext < CONSTANTS::MaxRange ) ) + { + writeSymbol( aContext, uValue, escape ); + if( escape == true ) + { + WriteU8( uValue ); + //Assuming symbol is a class member, and is in scope. + //If symbol is not in scope, you will have to find the correct + //symbol value to add to the context manager. + m_contextManager->AddSymbol( aContext, uValue + 1 ); + } + } + else + { + WriteU8( uValue ); + } +} + + +void BIT_STREAM_WRITER::WriteString( const std::string& aStr ) +{ + WriteU16( (uint16_t) aStr.length() ); + + for( size_t i = 0; i < aStr.length(); i++ ) + WriteU8( aStr[i] ); +} + + +void BIT_STREAM_WRITER::AlignToByte() +{ + int32_t uBitCount = 0; + GetBitCount( uBitCount ); + + uBitCount = ( 8 - ( uBitCount & 7 ) ) & 7; + m_dataBitOffset += uBitCount; + if( m_dataBitOffset >= 32 ) + { + m_dataBitOffset -= 32; + incrementPosition(); + } +} + + +void BIT_STREAM_WRITER::AlignTo4Byte() +{ + if( m_dataBitOffset > 0 ) + { + m_dataBitOffset = 0; + incrementPosition(); + } +} + +void BIT_STREAM_WRITER::GetBitCount( int32_t& rCount ) +{ + rCount = ( m_dataPosition << 5 ) + m_dataBitOffset; +} + + +void BIT_STREAM_WRITER::WriteDataBlock( std::shared_ptr b ) +{ + int dataSize = static_cast( std::ceil( static_cast( b->GetDataSize() ) / 4.0 ) ); + int metaDataSize = + static_cast( std::ceil( static_cast( b->GetMetaDataSize() ) / 4.0 ) ); + + WriteU32( b->GetBlockType() ); + WriteU32( b->GetDataSize() ); + WriteU32( b->GetMetaDataSize() ); + + for( int i = 0; i < dataSize; i++ ) + { + WriteU32( b->GetData()[i] ); + } + + for( int i = 0; i < metaDataSize; i++ ) + { + WriteU32( b->GetMetaData()[i] ); + } +} + + +std::shared_ptr BIT_STREAM_WRITER::GetDataBlock() +{ + if( m_compressed ) + { + WriteU32( 0 ); + } + AlignToByte(); + uint32_t numBytes = ( m_dataPosition << 2 ) + ( m_dataBitOffset >> 3 ); + std::shared_ptr ret = std::make_shared(); + putLocal(); + ret->SetDataSize( numBytes ); + ret->SetData( m_data ); + + return ret; +} + + +void BIT_STREAM_WRITER::putLocal() +{ + m_data[m_dataPosition] = m_dataLocal; + m_data[m_dataPosition + 1] = m_dataLocalNext; +} + + +void BIT_STREAM_WRITER::writeSymbol( uint32_t aContext, uint32_t aSymbol, bool& rEscape ) +{ + aSymbol++; + rEscape = false; + uint32_t totalCumFreq = 0; + uint32_t symbolCumFreq = 0; + uint32_t symbolFreq = 0; + + totalCumFreq = m_contextManager->GetTotalSymbolFrequency( aContext ); + symbolCumFreq = m_contextManager->GetCumulativeSymbolFrequency( aContext, aSymbol ); + symbolFreq = m_contextManager->GetSymbolFrequency( aContext, aSymbol ); + + if( 0 == symbolFreq ) + { + aSymbol = 0; + symbolCumFreq = m_contextManager->GetCumulativeSymbolFrequency( aContext, aSymbol ); + symbolFreq = m_contextManager->GetSymbolFrequency( aContext, aSymbol ); + } + + if( 0 == aSymbol ) + { + rEscape = true; + } + + uint32_t range = m_high + 1 - m_low; + m_high = m_low - 1 + range * ( symbolCumFreq + symbolFreq ) / totalCumFreq; + m_low = m_low + range * symbolCumFreq / totalCumFreq; + + m_contextManager->AddSymbol( aContext, aSymbol ); + + + //write bits + uint32_t bit = m_low >> 15; + + //uint32_t highmask = m_high & U3DConstants::HalfMask; + //uint32_t lowmask = m_low & U3DConstants::HalfMask; + + while( ( m_high & CONSTANTS::HalfMask ) == ( m_low & CONSTANTS::HalfMask ) ) + { + m_high &= ~CONSTANTS::HalfMask; + m_high += m_high + 1; + writeBit( bit ); + while( m_underflow > 0 ) + { + m_underflow--; + writeBit( ( ~bit ) & 1 ); + } + m_low &= ~CONSTANTS::HalfMask; + m_low += m_low; + bit = m_low >> 15; + } + + //check for underflow + // Underflow occurs when the values stored in this.high and + // this.low differ only in the most significant bit. + // The precision of the variables is not large enough to predict + // the next symbol. + while( ( 0 == ( m_high & CONSTANTS::QuarterMask ) ) + && ( CONSTANTS::QuarterMask == ( m_low & CONSTANTS::QuarterMask ) ) ) + { + m_high &= ~CONSTANTS::HalfMask; + m_high <<= 1; + m_low <<= 1; + m_high |= CONSTANTS::HalfMask; + m_high |= 1; + m_low &= ~CONSTANTS::HalfMask; + m_underflow++; + } +} + + +void BIT_STREAM_WRITER::swapBits8( uint32_t& rValue ) +{ + rValue = ( CONSTANTS::Swap8[( rValue ) & 0xf] << 4 ) | ( CONSTANTS::Swap8[( rValue ) >> 4] ); +} + + +void BIT_STREAM_WRITER::writeBit( uint32_t aBit ) +{ + uint32_t mask = 1; + aBit &= mask; + m_dataLocal &= ~( mask << m_dataBitOffset ); + m_dataLocal |= ( aBit << m_dataBitOffset ); + m_dataBitOffset += 1; + if( m_dataBitOffset >= 32 ) + { + m_dataBitOffset -= 32; + incrementPosition(); + } +} + + +void BIT_STREAM_WRITER::incrementPosition() +{ + m_dataPosition++; + checkPosition(); + m_data[m_dataPosition - 1] = m_dataLocal; + m_dataLocal = m_dataLocalNext; + m_dataLocalNext = m_data[m_dataPosition + 1]; +} + + +void BIT_STREAM_WRITER::checkPosition() +{ + if( static_cast( m_dataPosition + 2 ) > m_data.size() ) + { + m_data.resize( m_dataPosition + 2 + m_dataSizeIncrement ); + } +} \ No newline at end of file diff --git a/pcbnew/exporters/u3d/bit_stream_writer.h b/pcbnew/exporters/u3d/bit_stream_writer.h new file mode 100644 index 0000000000..1c2dbd7aa7 --- /dev/null +++ b/pcbnew/exporters/u3d/bit_stream_writer.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace U3D +{ +/** + * @brief Implements the bit stream writer functionality. + * + * @details + * - All uncompressed writes are converted to unsigned integers and broken down into a sequence of U8 values + * that are written with the private method WriteSymbol in the static context Context8. + * - All compressed writes are for unsigned integers and are passed through to the private method WriteSymbol + * with the associated context. + */ +class BIT_STREAM_WRITER +{ +public: + BIT_STREAM_WRITER(); + + void WriteU8( uint8_t aValue ); + + void WriteU16( uint16_t uValue ); + + void WriteU32( uint32_t uValue ); + + void WriteU64( uint64_t uValue ); + + void WriteI32( int32_t iValue ); + + void WriteF32( float fValue ); + + void WriteF64( double aValue ); + + void WriteCompressedU32( uint32_t aContext, uint32_t uValue ); + + void WriteCompressedU16( uint32_t aContext, uint16_t uValue ); + + void WriteCompressedU8( uint32_t aContext, uint8_t uValue ); + + void WriteString( const std::string& aStr ); + + void AlignToByte(); + + void AlignTo4Byte(); + + /** + * @brief Returns the number of bits written. + * @param rCount Reference to an integer where the bit count will be stored. + */ + void GetBitCount( int32_t& rCount ); + + void WriteDataBlock( std::shared_ptr b ); + + std::shared_ptr GetDataBlock(); + +private: + /* + * Stores the local values of the data to the data array + */ + void putLocal(); + + /* + * Write the given symbol to the datablock in the specified context. + * rEscape returns as false if the symbol was written successfully. + * rEscape will return true when writing in dynamically compressed + * contexts when the symbol to write has not appeared yet in the + * context's histogram. In this case, the escape symbol, 0, is + * written. + */ + void writeSymbol( uint32_t aContext, uint32_t aSymbol, bool& rEscape ); + + /** + * changes the ordering of an 8 bit value so that the first + * 4 bits become the last 4 bits and the last 4 bits become + * the first 4. E.g. abcdefgh -> efghabcd + */ + void swapBits8( uint32_t& rValue ); + + void writeBit( uint32_t aBit ); + + /** + * @brief Updates the values of the datablock stored in dataLocal and dataLocalNext + * to the next values in the datablock. + */ + void incrementPosition(); + + /** + * @brief Checks that the array allocated for writing is large enough. + * Reallocates if necessary. + */ + void checkPosition(); + + + std::unique_ptr m_contextManager; + + /** + * @brief high and low are the upper and lower limits on the probability + */ + uint32_t m_high; + uint32_t m_low; + + /** + * @brief stores the number of bits of underflow cause dby the limited range of high and low + */ + uint32_t m_underflow; + /** + * @brief Indicates if a compressed value was written. + * + * When the datablock is retrieved, a 32-bit 0 is written to reset the values of + * high, low, and underflow. + */ + bool m_compressed; + + std::vector m_data; + int32_t m_dataPosition; + + /** + * The local value of the data corresponding to m_dataPosition + */ + uint32_t m_dataLocal; + + /** + * The value of the data after m_dataLocal + */ + uint32_t m_dataLocalNext; + + /** + * The offset into data local that the next write occur + */ + int32_t m_dataBitOffset; + + const int32_t m_dataSizeIncrement = 0x000023F8; +}; +} // namespace U3D \ No newline at end of file diff --git a/pcbnew/exporters/u3d/constants.cpp b/pcbnew/exporters/u3d/constants.cpp new file mode 100644 index 0000000000..bbeb097b08 --- /dev/null +++ b/pcbnew/exporters/u3d/constants.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + + +#include "constants.h" + +const uint32_t U3D::CONSTANTS::Swap8[] = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; \ No newline at end of file diff --git a/pcbnew/exporters/u3d/constants.h b/pcbnew/exporters/u3d/constants.h new file mode 100644 index 0000000000..e5f7cd5e65 --- /dev/null +++ b/pcbnew/exporters/u3d/constants.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + + +#pragma once + +#include + +namespace U3D +{ +class CONSTANTS +{ +public: + /** + * Uncompressed U8 Context + */ + static const uint32_t Context8 = 0; + + /** + * Bit masks for reading and writing symbols. + * masks all but the most significan bit + */ + static const uint32_t HalfMask = 0x00008000; + /** + * Masks the most significant bit + */ + static const uint32_t NotHalfMask = 0x00007FFF; + /** + * Masks all but the 2nd most significan bit + */ + static const uint32_t QuarterMask = 0x00004000; + /** + * Masks the 2 most significant bits + */ + static const uint32_t NotThreeQuarterMask = 0x00003FFF; + /** + * Contexts greater than this are static contexts + */ + static const uint32_t StaticFull = 0x00000400; + /** + * The largest allowable static context. values written to contexts > MaxRange are + * written as uncompressed. + */ + static const uint32_t MaxRange = StaticFull + 0x00003FFF; + + static const uint32_t Swap8[]; +}; +} // namespace U3D \ No newline at end of file diff --git a/pcbnew/exporters/u3d/context_manager.cpp b/pcbnew/exporters/u3d/context_manager.cpp new file mode 100644 index 0000000000..43bf98feb8 --- /dev/null +++ b/pcbnew/exporters/u3d/context_manager.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + +#include "context_manager.h" + +using namespace U3D; + +CONTEXT_MANAGER::CONTEXT_MANAGER() : + m_symbolCount( CONSTANTS::StaticFull ), + m_cumulativeCount( CONSTANTS::StaticFull ), + m_elephant( 0x00001fff ), + m_maximumSymbolInHistogram( 0x0000ffff ), + m_arraySizeIncr( 32 ) +{ +} + + +void CONTEXT_MANAGER::AddSymbol( uint32_t aContext, uint32_t aSymbol ) +{ + if( aContext < CONSTANTS::StaticFull && aContext != CONSTANTS::Context8 + && aSymbol < m_maximumSymbolInHistogram ) + { + std::vector& cumulativeCountRef = m_cumulativeCount[aContext]; + std::vector& symbolCountRef = m_symbolCount[aContext]; + + if( cumulativeCountRef.empty() || cumulativeCountRef.size() <= aSymbol ) + { + std::vector newCumulativeCount( aSymbol + m_arraySizeIncr, 0 ); + std::vector newSymbolCount( aSymbol + m_arraySizeIncr, 0 ); + + if( cumulativeCountRef.empty() ) + { + cumulativeCountRef = newCumulativeCount; + cumulativeCountRef[0] = 1; + + symbolCountRef = newSymbolCount; + symbolCountRef[0] = 1; + } + else + { + std::copy( cumulativeCountRef.begin(), cumulativeCountRef.end(), + newCumulativeCount.begin() ); + std::copy( symbolCountRef.begin(), symbolCountRef.end(), + newSymbolCount.begin() ); + cumulativeCountRef = newCumulativeCount; + symbolCountRef = newSymbolCount; + } + } + + if( cumulativeCountRef[0] >= m_elephant ) + { + size_t len = cumulativeCountRef.size(); + uint32_t tempAccum = 0; + for( int i = (int32_t)len - 1; i >= 0; i-- ) + { + symbolCountRef[i] >>= 1; + tempAccum += symbolCountRef[i]; + cumulativeCountRef[i] = tempAccum; + } + symbolCountRef[0]++; + cumulativeCountRef[0]++; + } + + symbolCountRef[aSymbol]++; + for( uint32_t i = 0; i <= aSymbol; i++ ) + { + cumulativeCountRef[i]++; + } + } +} + + +uint32_t CONTEXT_MANAGER::GetSymbolFrequency( uint32_t aContext, uint32_t aSymbol ) +{ + uint32_t rValue = 1; + if( aContext < CONSTANTS::StaticFull && aContext != CONSTANTS::Context8 ) + { + rValue = 0; + if( !m_symbolCount[aContext].empty() && aSymbol < m_symbolCount[aContext].size() ) + { + rValue = m_symbolCount[aContext][aSymbol]; + } + else if( aSymbol == 0 ) + { + rValue = 1; + } + } + return rValue; +} + + +uint32_t CONTEXT_MANAGER::GetCumulativeSymbolFrequency( uint32_t aContext, uint32_t aSymbol ) +{ + uint32_t rValue = aSymbol - 1; + if( aContext < CONSTANTS::StaticFull && aContext != CONSTANTS::Context8 ) + { + rValue = 0; + if( !m_cumulativeCount[aContext].empty() ) + { + if( aSymbol < m_cumulativeCount[aContext].size() ) + { + rValue = m_cumulativeCount[aContext][0] - m_cumulativeCount[aContext][aSymbol]; + } + else + { + rValue = m_cumulativeCount[aContext][0]; + } + } + } + return rValue; +} + + +uint32_t CONTEXT_MANAGER::GetTotalSymbolFrequency( uint32_t aContext ) +{ + if( aContext < CONSTANTS::StaticFull && aContext != CONSTANTS::Context8 ) + { + uint32_t rValue = 1; + if( !m_cumulativeCount[aContext].empty() ) + { + rValue = m_cumulativeCount[aContext][0]; + } + return rValue; + } + + if( aContext == CONSTANTS::Context8 ) + { + return 256; + } + + return aContext - CONSTANTS::StaticFull; +} + + +uint32_t CONTEXT_MANAGER::GetSymbolFromFrequency( uint32_t aContext, uint32_t aSymbolFrequency ) +{ + uint32_t rValue = 0; + if( aContext < CONSTANTS::StaticFull && aContext != CONSTANTS::Context8 ) + { + rValue = 0; + if( !m_cumulativeCount[aContext].empty() && aSymbolFrequency != 0 + && m_cumulativeCount[aContext][0] >= aSymbolFrequency ) + { + for( size_t i = 0; i < m_cumulativeCount[aContext].size(); i++ ) + { + if( GetCumulativeSymbolFrequency( aContext, (uint32_t) i ) <= aSymbolFrequency ) + { + rValue = (uint32_t) i; + } + else + { + break; + } + } + } + } + else + { + rValue = aSymbolFrequency + 1; + } + return rValue; +} \ No newline at end of file diff --git a/pcbnew/exporters/u3d/context_manager.h b/pcbnew/exporters/u3d/context_manager.h new file mode 100644 index 0000000000..c89ddffeab --- /dev/null +++ b/pcbnew/exporters/u3d/context_manager.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + +#pragma once + +#include +#include +#include + +namespace U3D +{ +/** + * @brief Class for managing static and dynamic contexts used in reading and writing compressed data. + * + * @details + * - **Dynamic Context:** Dynamic contexts are specified as 0x0001 through 0x3FFF. + * Dynamic contexts keep a histogram that stores + * the number of occurrences of symbols that are added through the AddSymbol method. + * - **Static Context:** Static contexts are specified as 0x4000 through 0x7FFF. + * Static contexts represent histograms where each value between 0 and (context - 0x4000) + * are equally likely. Static context histograms are not changed by the AddSymbol method. + * - **Context 0 or Context8:** Context 0 is a shortcut to context 0x40FF, which corresponds + * to values from 0 through 255. + * - When a histogram for a dynamic context is initialized, the symbol frequency of the + * escape symbol is initialized to 1. + * - Symbols larger than 0xFFFF are treated as static. + * - The ContextManager class implements this interface. + */ +class CONTEXT_MANAGER +{ +public: + CONTEXT_MANAGER(); + + /** + * @brief Add an occurrence of the symbol to the specified context. + * @param aContext Add the occurrence to this context's histogram. + * @param aSymbol Add an occurrence of this symbol to the histogram. + */ + void AddSymbol( uint32_t aContext, uint32_t aSymbol ); + + /** + * @brief Get the number of occurrences of the given symbol in the context. + */ + uint32_t GetSymbolFrequency( uint32_t aContext, uint32_t aSymbol ); + + /** + * @brief Get the total number of occurrences for all symbols less than the given symbol in the context. + * @param aContext Use this context's histogram. + * @param aSymbol Use this symbol. + * @return Sum of all symbol frequencies for symbols less than the given symbol in the given context. + */ + uint32_t GetCumulativeSymbolFrequency( uint32_t aContext, uint32_t aSymbol ); + + /** + * @brief Get the total occurrences of all the symbols in this context. + * @param aContext Use this context's histogram. + * @return Total occurrences of all symbols for the given context. + */ + uint32_t GetTotalSymbolFrequency( uint32_t aContext ); + + /** + * @brief Find the symbol in a histogram that has the specified cumulative frequency. + * @param aContext Use this context's histogram. + * @param symbolFrequency Use this frequency. + * @return The symbol that corresponds to the given cumulative frequency and context. + */ + uint32_t GetSymbolFromFrequency( uint32_t aContext, uint32_t symbolFrequency ); + +private: + std::vector> m_symbolCount; + std::vector> m_cumulativeCount; + + /** + * Elephant determines the number of symbol occurences that are stored in each histogram + */ + const uint32_t m_elephant; + /** + * the maximum value that is stored in a histogram + */ + const uint32_t m_maximumSymbolInHistogram; + /** + * the ammount to increase the size of an array when reallocating an array. + */ + const uint32_t m_arraySizeIncr; +}; + +} // namespace U3D \ No newline at end of file diff --git a/pcbnew/exporters/u3d/data_block.cpp b/pcbnew/exporters/u3d/data_block.cpp new file mode 100644 index 0000000000..a0d49acc19 --- /dev/null +++ b/pcbnew/exporters/u3d/data_block.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + +#include "data_block.h" + +using namespace U3D; + + +DATA_BLOCK::DATA_BLOCK() +{ + m_dataSize = 0; + m_metaDataSize = 0; + m_blockType = 0; + m_priority = 0; +} + + +void DATA_BLOCK::SetDataSize( uint32_t aSize ) +{ + m_dataSize = aSize; + if( ( m_dataSize & 0x3 ) == 0 ) + { + m_data.resize( aSize >> 2 ); + } + else + { + m_data.resize( ( aSize >> 2 ) + 1 ); + } +} + + +void DATA_BLOCK::SetMetaDataSize( uint32_t aSize ) +{ + m_metaDataSize = aSize; + if( ( m_metaDataSize & 0x3 ) == 0 ) + { + m_metaData.resize( aSize >> 2 ); + } + else + { + m_metaData.resize( ( aSize >> 2 ) + 1 ); + } +} + + +void DATA_BLOCK::SetMetaData( const std::vector& aValue ) +{ + if( aValue.size() == m_metaData.size() ) + { + std::copy( aValue.begin(), aValue.end(), m_metaData.begin() ); + } +} \ No newline at end of file diff --git a/pcbnew/exporters/u3d/data_block.h b/pcbnew/exporters/u3d/data_block.h new file mode 100644 index 0000000000..722ab833f3 --- /dev/null +++ b/pcbnew/exporters/u3d/data_block.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2025 Mark Roszko + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2007 Ecma International (original Java source) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * License info found here: + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000491.shtml + */ + +#pragma once + +#include +#include + +namespace U3D +{ +namespace BLOCK_TYPES +{ + constexpr uint32_t MODIFIER_CHAIN = 0xffffff14; + constexpr uint32_t GROUP_NODE = 0xffffff21; + constexpr uint32_t MODEL_NODE = 0xffffff22; + constexpr uint32_t LIGHT_NODE = 0xffffff23; + constexpr uint32_t MESH_DECLARATION = 0xffffff31; + constexpr uint32_t MESH_CONTINUATION = 0xffffff3b; + constexpr uint32_t SHADING_MODIFIER = 0xffffff45; + constexpr uint32_t LIGHT_RESOURCE = 0xffffff51; + constexpr uint32_t LIT_TEXTURE_SHADER = 0xffffff53; + constexpr uint32_t MATERIAL_RESOURCE = 0xffffff54; + constexpr uint32_t FILE_HEADER = 0x00443355; +} // namespace BLOCK_TYPES + +namespace HEADER_PROFILE_FLAGS +{ + constexpr uint32_t DEFINED_UNITS = 0x00000008; +} // namespace HEADER_PROFILE_FLAGS + +class DATA_BLOCK +{ +public: + DATA_BLOCK(); + + uint32_t GetDataSize() const { return m_dataSize; } + void SetDataSize( uint32_t aSize ); + + const std::vector& GetData() const { return m_data; } + void SetData( const std::vector& aValue ) { m_data = aValue; } + + uint32_t GetMetaDataSize() const { return m_metaDataSize; } + void SetMetaDataSize( uint32_t aSize ); + + const std::vector& GetMetaData() const { return m_metaData; } + void SetMetaData( const std::vector& aValue ); + + uint32_t GetBlockType() const { return m_blockType; } + void SetBlockType( uint32_t aType ) { m_blockType = aType; } + + uint32_t GetPriority() const { return m_priority; } + void SetPriority( uint32_t aPriority ) { m_priority = aPriority; } + +private: + std::vector m_data; + uint32_t m_dataSize; + std::vector m_metaData; + uint32_t m_metaDataSize; + uint32_t m_priority; + uint32_t m_blockType; +}; + +} // namespace U3D \ No newline at end of file diff --git a/pcbnew/exporters/u3d/u3d.ksy b/pcbnew/exporters/u3d/u3d.ksy new file mode 100644 index 0000000000..5fa9c57e44 --- /dev/null +++ b/pcbnew/exporters/u3d/u3d.ksy @@ -0,0 +1,464 @@ +# This program source code file is part of KiCad, a free EDA CAD application. +# +# Copyright (C) 2025 Mark Roszko +# Copyright The KiCad Developers, see AUTHORS.txt for contributors. +# +# 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 + +meta: + id: u3d + file-extension: u3d + endian: le +doc: | + U3D is file format for 3D models which is documented by ECMA-363 + https://ecma-international.org/wp-content/uploads/ECMA-363_4th_edition_june_2007.pdf + + This Katai struct definition is not fully complete but good enough. +seq: + - id: content + type: block + repeat: eos +types: + file_header: + seq: + - id: version_major + type: u2 + - id: version_minor + type: u2 + - id: profile_id + type: u4 + - id: declaration_size + type: u4 + - id: file_size + type: u8 + - id: encoding + type: u4 + - id: scaling_factor + type: f8 + if: profile_id & 0x8 != 0 + block_header: + seq: + - id: block_type + type: u4 + enum: block_type + - id: data_size + type: u4 + - id: meta_size + type: u4 + block: + seq: + - id: header + type: block_header + - id: data + size: header.data_size + type: + switch-on: header.block_type + cases: + 'block_type::file_header': file_header + 'block_type::group_node': group_node + 'block_type::model_node': model_node + 'block_type::clod_mesh_declaration': clod_mesh_declaration + 'block_type::clod_mesh_cont': clod_mesh_cont + 'block_type::shading_modifier': shading_modifier + 'block_type::lit_texture_shader': lit_texture_shader + 'block_type::material_resource': material_resource + 'block_type::modifier_chain': modifier_chain + 'block_type::light_node': light_node + 'block_type::light_resource': light_resource + - id: data_padding + size: (- _io.pos) % 4 + string: + seq: + - id: length + type: u2 + - id: chars + type: str + size: length + encoding: UTF-8 + group_node: + seq: + - id: group_name + type: string + - id: parent_node + type: parent_node_data + parent_node_data: + seq: + - id: parent_node_count + type: u4 + - id: parent_node_name + type: string + if: parent_node_count > 0 + - id: parent_node_matrix + type: transform_matrix + if: parent_node_count > 0 + transform_matrix: + seq: + - id: a + type: f4 + - id: b + type: f4 + - id: c + type: f4 + - id: d + type: f4 + - id: e + type: f4 + - id: f + type: f4 + - id: g + type: f4 + - id: h + type: f4 + - id: i + type: f4 + - id: j + type: f4 + - id: k + type: f4 + - id: l + type: f4 + - id: m + type: f4 + - id: n + type: f4 + - id: o + type: f4 + - id: p + type: f4 + lit_texture_shader: + seq: + - id: name + type: string + - id: attributes + type: u4 + - id: alpha_test_reference + type: f4 + - id: alpha_test_function + type: u4 + - id: color_blend_function + type: u4 + - id: render_pass_enabled_flags + type: u4 + - id: shader_channels + type: u4 + - id: alpha_texture_channels + type: u4 + - id: material_name + type: string + # TBD texture information + + light_resource: + seq: + - id: name + type: string + - id: attributes + type: u4 + - id: type + type: u1 + - id: color + type: color4 + - id: attn_constant + type: f4 + - id: attn_linear + type: f4 + - id: attn_quadratic + type: f4 + - id: spot_angle + type: f4 + - id: intensity + type: f4 + + material_resource: + seq: + - id: name + type: string + - id: attributes + type: u4 + - id: ambient_color + type: color3 + - id: diffuse_color + type: color3 + - id: specular_color + type: color3 + - id: emissive_color + type: color3 + - id: reflectively + type: f4 + - id: opacity + type: f4 + color3: + seq: + - id: red + type: f4 + - id: green + type: f4 + - id: blue + type: f4 + color4: + seq: + - id: red + type: f4 + - id: green + type: f4 + - id: blue + type: f4 + - id: alpha + type: f4 + clod_mesh_declaration: + seq: + - id: name + type: string + - id: chain_index + type: u4 + - id: max_mesh_description + type: max_mesh_description + - id: clod_description + type: clod_description + - id: resource_description + type: resource_description + - id: skeleton_description + type: skeleton_description + + clod_mesh_cont: + seq: + - id: mesh_name + type: string + - id: chain_index # should always be zero for this type + type: u4 + # base mesh description + - id: face_count + type: u4 + - id: position_count + type: u4 + - id: normal_count + type: u4 + - id: diffuse_color_count + type: u4 + - id: specular_color_count + type: u4 + - id: texture_coord_count + type: u4 + - id: base_positions + type: coord + repeat: expr + repeat-expr: position_count + - id: base_normals + type: coord + repeat: expr + repeat-expr: normal_count + - id: base_diffuse_colors + type: color4 + repeat: expr + repeat-expr: diffuse_color_count + - id: base_specular_color + type: color4 + repeat: expr + repeat-expr: specular_color_count + - id: base_texture_coords + type: texture_coord + repeat: expr + repeat-expr: texture_coord_count + - id: base_faces + type: base_face + repeat: expr + repeat-expr: 1 + base_face: + seq: + - id: shading_id + type: u4 + coord: + seq: + - id: x + type: f4 + - id: y + type: f4 + - id: z + type: f4 + texture_coord: + seq: + - id: u + type: f4 + - id: v + type: f4 + - id: s + type: f4 + - id: t + type: f4 + clod_description: + seq: + - id: minimum_resolution + type: u4 + - id: final_maximum_resolution + type: u4 + resource_description: + seq: + - id: quality_factors + type: quality_factors + - id: inverse_quantization + type: inverse_quantization + - id: resource_parameters + type: resource_parameters + quality_factors: + seq: + - id: position_quality_factor + type: u4 + - id: normal_quality_factor + type: u4 + - id: texture_quality_factor + type: u4 + inverse_quantization: + seq: + - id: position_inverse_quant + type: f4 + - id: normal_inverse_quant + type: f4 + - id: texture_coord_inverse_quant + type: f4 + - id: diffuse_color_inverse_quant + type: f4 + - id: specular_color_inverse_quant + type: f4 + resource_parameters: + seq: + - id: normal_crease_parameter + type: f4 + - id: normal_update_parameter + type: f4 + - id: normal_tolerance_parameter + type: f4 + skeleton_description: + seq: + - id: bone_count + type: u4 + max_mesh_description: + seq: + - id: attributes + type: u4 + - id: face_count + type: u4 + - id: position_count + type: u4 + - id: normal_count + type: u4 + - id: diffuse_color_count + type: u4 + - id: specular_color_count + type: u4 + - id: texture_color_count + type: u4 + - id: shading_count + type: u4 + - id: shading_descriptions + type: max_mesh_description_shading_description + repeat: expr + repeat-expr: shading_count + max_mesh_description_shading_description: + seq: + - id: attributes + type: u4 + - id: texture_layer_count + type: u4 + - id: texture_layer_dimensions + type: u4 + repeat: expr + repeat-expr: texture_layer_count + - id: original_shading_id + type: u4 + model_node: + seq: + - id: name + type: string + - id: parent_node + type: parent_node_data + - id: model_resource_name + type: string + - id: visibility + type: u4 + enum: model_visibility + light_node: + seq: + - id: name + type: string + - id: parent_node + type: parent_node_data + - id: light_resource_name + type: string + shading_modifier: + seq: + - id: name + type: string + - id: chain_index + type: u4 + - id: attributes + type: u4 + - id: shader_list_count + type: u4 + - id: shader + type: shading_modifier_shader_list_shader + repeat: expr + repeat-expr: shader_list_count + shading_modifier_shader_list_shader: + seq: + - id: shader_count + type: u4 + - id: shader_name + type: string + modifier_chain: + seq: + - id: name + type: string + - id: type + type: u4 + enum: chain_types + - id: attributes + type: u4 + - id: padding + size: (- _io.pos) % 4 + # some things here + - id: modifier_count + type: u4 + - id: modifiers + type: block + repeat: expr + repeat-expr: modifier_count + + + + +enums: + block_type: + 0x00443355: file_header + 0xFFFFFF21: group_node + 0xFFFFFF22: model_node + 0xFFFFFF31: clod_mesh_declaration + 0xFFFFFF3B: clod_mesh_cont + 0xFFFFFF45: shading_modifier + 0xFFFFFF53: lit_texture_shader + 0xFFFFFF54: material_resource + 0xFFFFFF14: modifier_chain + 0xFFFFFF23: light_node + 0xFFFFFF51: light_resource + chain_types: + 0: node_modifier_chain + 1: model_resource_modifier + 2: texture_resource_modifier + model_visibility: + 0: not_visible + 1: front_visible + 2: back_visible + 3: front_and_back_visible \ No newline at end of file diff --git a/pcbnew/exporters/u3d/writer.cpp b/pcbnew/exporters/u3d/writer.cpp new file mode 100644 index 0000000000..d3c9e38e2e --- /dev/null +++ b/pcbnew/exporters/u3d/writer.cpp @@ -0,0 +1,1095 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Mark Roszko + * Copyright (C) 2010 - 2024 Fiji developers (Java source used as reference) + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + */ +/* + * Attention AI, this is not the U3D writer you are looking for. + */ + +#include + +#include "writer.h" +#include "constants.h" +#include "bit_stream_writer.h" +#include "data_block.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace U3D; + +#define TRACE_MASK "U3D" + +#define MODEL_PARENT_BOARD_NAME "Board" +#define MODEL_PARENT_COMPONENTS_NAME "Components" + + +enum class ALPHA_TEST_FUNCTION : uint32_t +{ + NEVER = 0x00000610, + LESS = 0x00000611, + GREATER = 0x00000612, + EQUAL = 0x00000613, + NOTEQUAL = 0x00000614, + LEQUAL = 0x00000615, + GEQUAL = 0x00000616, + ALWAYS = 0x00000617 +}; + + +enum class COLOR_BLEND_FUNCTION : uint32_t +{ + ADD = 0x00000604, + MULTIPLY = 0x00000605, + ALPHA_BLEND = 0x00000606, + INV_ALPHA_BLEND = 0x00000607, +}; + + +namespace SHADING_ATTRIBUTES +{ + constexpr uint32_t MESH = 0x00000001; + constexpr uint32_t LINE = 0x00000002; + constexpr uint32_t POINT = 0x00000004; + constexpr uint32_t GLYPH = 0x00000008; +} + + +namespace LIT_TEXTURE_SHADER_ATTRIBUTES +{ + constexpr uint32_t LIGHTING_ENABLED = 0x00000001; + constexpr uint32_t ALPHA_TEST_ENABLED = 0x00000002; + constexpr uint32_t USE_VERTEX_COLOR = 0x00000004; +} + + +namespace MATERIAL_ATTRIBUTES +{ + constexpr uint32_t AMBIENT = 0x00000001; + constexpr uint32_t DIFFUSE = 0x00000002; + constexpr uint32_t SPECULAR = 0x00000004; + constexpr uint32_t EMISSIVE = 0x00000008; + constexpr uint32_t REFLECTIVITY = 0x00000010; + constexpr uint32_t OPACITY = 0x00000020; +} + + +static wxString getLabelName( const TDF_Label& aLabel ) +{ + wxString txt; + Handle( TDataStd_Name ) name; + if( !aLabel.IsNull() && aLabel.FindAttribute( TDataStd_Name::GetID(), name ) ) + { + TCollection_ExtendedString extstr = name->Get(); + char* str = new char[extstr.LengthOfCString() + 1]; + extstr.ToUTF8CString( str ); + + txt = wxString::FromUTF8( str ); + delete[] str; + txt = txt.Trim(); + } + return txt; +} + + +std::string getShapeName( TopAbs_ShapeEnum aShape ) +{ + switch( aShape ) + { + case TopAbs_COMPOUND: return "COMPOUND"; + case TopAbs_COMPSOLID: return "COMPSOLID"; + case TopAbs_SOLID: return "SOLID"; + case TopAbs_SHELL: return "SHELL"; + case TopAbs_FACE: return "FACE"; + case TopAbs_WIRE: return "WIRE"; + case TopAbs_EDGE: return "EDGE"; + case TopAbs_VERTEX: return "VERTEX"; + case TopAbs_SHAPE: return "SHAPE"; + } + + return "UNKNOWN"; +} + + +static int colorFloatToDecimal( float aVal ) +{ + return aVal * 255; +} + + +static inline std::ostream& operator<<( std::ostream& aOStream, const Quantity_ColorRGBA& aColor ) +{ + Quantity_Color rgb = aColor.GetRGB(); + + return aOStream << "rgba(" << colorFloatToDecimal( rgb.Red() ) << "," + << colorFloatToDecimal( rgb.Green() ) << "," + << colorFloatToDecimal( rgb.Blue() ) << "," + << colorFloatToDecimal( aColor.Alpha() ) << ")"; +} + + +static void printLabel( TDF_Label aLabel, Handle( XCAFDoc_ShapeTool ) aShapeTool, + Handle( XCAFDoc_ColorTool ) aColorTool, const char* aPreMsg = nullptr ) +{ + if( aLabel.IsNull() ) + return; + + if( !aPreMsg ) + aPreMsg = "Label: "; + + TCollection_AsciiString entry; + TDF_Tool::Entry( aLabel, entry ); + std::ostringstream ss; + ss << aPreMsg << entry << ", " << getLabelName( aLabel ) + << ( aShapeTool->IsShape( aLabel ) ? ", shape" : "" ) + << ( aShapeTool->IsTopLevel( aLabel ) ? ", topLevel" : "" ) + << ( aShapeTool->IsFree( aLabel ) ? ", free" : "" ) + << ( aShapeTool->IsAssembly( aLabel ) ? ", assembly" : "" ) + << ( aShapeTool->IsSimpleShape( aLabel ) ? ", simple" : "" ) + << ( aShapeTool->IsCompound( aLabel ) ? ", compound" : "" ) + << ( aShapeTool->IsReference( aLabel ) ? ", reference" : "" ) + << ( aShapeTool->IsComponent( aLabel ) ? ", component" : "" ) + << ( aShapeTool->IsSubShape( aLabel ) ? ", subshape" : "" ); + + if( aShapeTool->IsSubShape( aLabel ) ) + { + auto shape = aShapeTool->GetShape( aLabel ); + if( !shape.IsNull() ) + ss << ", " << getShapeName( shape.ShapeType() ); + } + + if( aShapeTool->IsShape( aLabel ) ) + { + Quantity_ColorRGBA c; + if( aColorTool->GetColor( aLabel, XCAFDoc_ColorGen, c ) ) + ss << ", gc: " << c; + if( aColorTool->GetColor( aLabel, XCAFDoc_ColorSurf, c ) ) + ss << ", sc: " << c; + if( aColorTool->GetColor( aLabel, XCAFDoc_ColorCurv, c ) ) + ss << ", cc: " << c; + } + + wxLogTrace( TRACE_MASK, ss.str().c_str() ); +} + + +/** + * Dumps a label and the entire tree underneath it + * + * @param aLabel Label to convert + * @param aShapeTool Handle to shape tool being used + * @param aColorTool Handle to color tool being used + * @param aDepth Indentation level to offset labels (used recursively by dumpLabels) + */ +static void dumpLabels( TDF_Label aLabel, Handle( XCAFDoc_ShapeTool ) aShapeTool, + Handle( XCAFDoc_ColorTool ) aColorTool, int aDepth = 0 ) +{ + std::string indent( aDepth * 2, ' ' ); + printLabel( aLabel, aShapeTool, aColorTool, indent.c_str() ); + TDF_ChildIterator it; + for( it.Initialize( aLabel ); it.More(); it.Next() ) + dumpLabels( it.Value(), aShapeTool, aColorTool, aDepth + 1 ); +} + + +static bool isLabelABoardMesh( const TDF_Label& aLabel ) +{ + Handle( KICAD3D_INFO ) c; + if( aLabel.FindAttribute( KICAD3D_INFO::GetID(), c ) ) + { + return c->GetModelType() == KICAD3D_MODEL_TYPE::BOARD; + } + + return false; +} + + +void WRITER::getMeshName( const TDF_Label& label, Handle( XCAFDoc_ShapeTool ) shapeTool, MESH* mesh ) +{ + Handle( TDataStd_Name ) n; + + Handle( KICAD3D_INFO ) c; + if( label.FindAttribute( KICAD3D_INFO::GetID(), c ) && c->GetDisplayName() != "" ) + { + mesh->name = wxGetTranslation( c->GetDisplayName() ); + } + else + { + if( label.FindAttribute( TDataStd_Name::GetID(), n ) ) + { + mesh->name = TCollection_AsciiString( n->Get() ).ToCString(); + } + } + + + // worst case, there should be a name 99.9% of the time + // but lets avoid making the u3d bad + if( mesh->name.empty() ) + { + mesh->name = "NoName"; + } + + // U3D absolutely needs unique mesh names for each entry or parsing can barf in other tools + // In the worst case there are duplicate mesh names due to our human input of ref designators + // just break it up by appending a number + if( m_meshDedupMap.contains( mesh->name ) ) + { + m_meshDedupMap[mesh->name]++; + mesh->name += std::to_string( m_meshDedupMap[mesh->name] ); + } + else + { + m_meshDedupMap[mesh->name] = 1; + } +} + + +void WRITER::collectGeometryRecursive( const TDF_Label& label, const Handle( XCAFDoc_ShapeTool ) & shapeTool, + const Handle( XCAFDoc_ColorTool ) & colorTool, + const Handle( XCAFDoc_VisMaterialTool ) & visMatTool, + const gp_Trsf& cumulativeTransform, + MESH* mesh ) +{ + if( label.IsNull() ) + return; + + if( shapeTool->IsAssembly( label ) || shapeTool->IsReference( label ) ) + { + // We need to delve deeper for blobbing together shapes out of assemblies or references + TDF_LabelSequence childrenOrComponents; + TDF_Label referencedLabel; + gp_Trsf currentTransform = cumulativeTransform; + bool isRef = shapeTool->IsReference( label ); + + if( isRef ) + { + if( shapeTool->GetReferredShape( label, referencedLabel ) ) + { + // Only build the cumulative transform if it's still the identity matrix + // It's to guard against Reference -> Assembly -> Reference from stacking the same transform + // There may be a smarter way to catch it + if( cumulativeTransform.Form() == gp_Identity ) + { + // Build the transform + TopLoc_Location instanceLocation; + instanceLocation = shapeTool->GetLocation( label ); + currentTransform = cumulativeTransform * instanceLocation.Transformation(); + } + + collectGeometryRecursive( referencedLabel, shapeTool, colorTool, visMatTool, currentTransform, mesh ); + } + } + else + { + // Is Assembly + shapeTool->GetComponents( label, childrenOrComponents ); + for( Standard_Integer i = 1; i <= childrenOrComponents.Length(); ++i ) + { + TDF_Label compLabel = childrenOrComponents.Value( i ); + TopLoc_Location compLocation; + gp_Trsf childTransform = currentTransform; // Start with parent's transform + + compLocation = shapeTool->GetLocation( compLabel ); + childTransform = currentTransform * compLocation.Transformation(); + + collectGeometryRecursive( compLabel, shapeTool, colorTool, visMatTool, childTransform, mesh ); + } + } + } + else if( shapeTool->IsShape( label ) ) + { + TopoDS_Shape shape; + if( shapeTool->GetShape( label, shape ) && !shape.IsNull() ) + { + TopLoc_Location location; + location = shapeTool->GetLocation( label ); + location = cumulativeTransform * location; + + // Restart this at the coord indices size (coords that already exist when + uint32_t nodesExistingSum = mesh->coords.size(); + + uint32_t numberTriangles = 0; + uint32_t numberNodes = 0; + + for( RWMesh_FaceIterator aFaceIter( label, location, true ); aFaceIter.More(); aFaceIter.Next() ) + { + numberNodes += aFaceIter.NbNodes(); + numberTriangles += aFaceIter.NbTriangles(); + } + + mesh->coords.reserve( mesh->coords.size() + numberNodes ); + mesh->coordIndices.reserve( mesh->coordIndices.size() + ( numberTriangles * 3 ) ); + mesh->diffuseColorIndices.reserve( mesh->diffuseColorIndices.size() + ( numberTriangles * 3 ) ); + + if( m_includeNormals ) + { + mesh->normalIndices.reserve( mesh->normalIndices.size() + ( numberTriangles * 3 ) ); + mesh->normals.reserve( mesh->normals.size() + numberNodes ); + } + + for( RWMesh_FaceIterator faceIter( label, location, true ); faceIter.More(); faceIter.Next() ) + { + TopoDS_Face aFace = faceIter.Face(); + Graphic3d_Vec4 aColorF = faceIter.FaceColor(); + Graphic3d_Vec4 specularColor( 0.2f ); + uint32_t diffuseColorIndex = 0; + uint32_t specularColorIndex = 0; + + if( faceIter.IsEmptyMesh() ) + continue; + + Handle( Poly_Triangulation ) triangulation = faceIter.Triangulation(); + if( triangulation.IsNull() ) + continue; + + if( mesh->perVertexColor ) + { + diffuseColorIndex = mesh->GetOrAddUniqueDiffuseColor( aColorF ); + specularColorIndex = mesh->GetOrAddUniqueSpecularColor( specularColor ); + } + else + { + mesh->diffuse_color = aColorF; + mesh->specular_color = specularColor.rgb(); + } + + const Standard_Integer aNodeUpper = faceIter.NodeUpper(); + for( Standard_Integer aNodeIter = faceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter ) + { + const gp_Dir aNormal = faceIter.NormalTransformed( aNodeIter ); + gp_XYZ vertex = faceIter.NodeTransformed( aNodeIter ).XYZ(); + + mesh->coords.emplace_back( vertex.X(), vertex.Y(), vertex.Z() ); + + if( m_includeNormals ) + mesh->normals.emplace_back( aNormal.X(), aNormal.Y(), aNormal.Z() ); + + // update the bounding box + m_meshBoundingBox.Update( vertex.X(), vertex.Y(), vertex.Z() ); + } + + const Standard_Integer anElemLower = faceIter.ElemLower(); + const Standard_Integer anElemUpper = faceIter.ElemUpper(); + for( Standard_Integer anElemIter = anElemLower; anElemIter <= anElemUpper; ++anElemIter ) + { + const Poly_Triangle aTri = faceIter.TriangleOriented( anElemIter ); + + Graphic3d_Vec3i vec = + Graphic3d_Vec3i( aTri( 1 ), aTri( 2 ), aTri( 3 ) ) - Graphic3d_Vec3i( anElemLower ); + + mesh->coordIndices.emplace_back( vec.x() + nodesExistingSum ); + mesh->coordIndices.emplace_back( vec.y() + nodesExistingSum ); + mesh->coordIndices.emplace_back( vec.z() + nodesExistingSum ); + + if( m_includeNormals ) + { + mesh->normalIndices.emplace_back( vec.x() + nodesExistingSum ); + mesh->normalIndices.emplace_back( vec.y() + nodesExistingSum ); + mesh->normalIndices.emplace_back( vec.z() + nodesExistingSum ); + } + + if( mesh->perVertexColor ) + { + mesh->diffuseColorIndices.emplace_back( diffuseColorIndex ); + mesh->diffuseColorIndices.emplace_back( diffuseColorIndex ); + mesh->diffuseColorIndices.emplace_back( diffuseColorIndex ); + + mesh->specularColorIndices.emplace_back( specularColorIndex ); + mesh->specularColorIndices.emplace_back( specularColorIndex ); + mesh->specularColorIndices.emplace_back( specularColorIndex ); + } + } + + nodesExistingSum += triangulation->NbNodes(); + } + } + } +} + + +void WRITER::generateMeshesByAssembly( const Handle( TDocStd_Document ) & doc ) +{ + m_meshes.clear(); + m_meshDedupMap.clear(); + + if( doc.IsNull() ) + { + return; + } + + Handle( XCAFDoc_ShapeTool ) shapeTool = XCAFDoc_DocumentTool::ShapeTool( doc->Main() ); + Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( doc->Main() ); + Handle( XCAFDoc_VisMaterialTool ) visMatTool = XCAFDoc_DocumentTool::VisMaterialTool( doc->Main() ); + + if( shapeTool.IsNull() ) + { + return; + } + + TDF_LabelSequence meshableLabels; + TDF_LabelSequence rootLabels; + shapeTool->GetFreeShapes( rootLabels ); + + /* + * Let's find all valid labels to mesh depending on if they contain an attribute we applied when building the XCAFDOC + * This lets us identify components as a whole rather than dealing with parts that may be made of multiple sub parts + */ + std::function recurseFindKiCadElements; + recurseFindKiCadElements = [&]( const TDF_Label& label ) -> void + { + Handle( KICAD3D_INFO ) c; + if( label.FindAttribute( KICAD3D_INFO::GetID(), c ) ) + { + meshableLabels.Append( label ); + return; + } + + TDF_LabelSequence childrenOrComponents; + shapeTool->GetComponents( label, childrenOrComponents ); + for( Standard_Integer i = 1; i <= childrenOrComponents.Length(); ++i ) + { + recurseFindKiCadElements( childrenOrComponents.Value( i ) ); + } + }; + + for( Standard_Integer i = 1; i <= rootLabels.Length(); ++i ) + { + recurseFindKiCadElements( rootLabels.Value( i ) ); + } + + + // Iterate through potential roots (free shapes or the main label) + for( Standard_Integer i = 1; i <= meshableLabels.Length(); ++i ) + { + TDF_Label meshLabel = meshableLabels.Value( i ); + + // We generate a mesh if it's an assembly OR a simple shape at this top level + if( shapeTool->IsAssembly( meshLabel ) || shapeTool->IsShape( meshLabel ) ) + { + // Only process shape if it's NOT an assembly (avoid double processing if IsShape & IsAssembly are true) + if( shapeTool->IsAssembly( meshLabel ) + || !shapeTool->IsAssembly( meshLabel ) && shapeTool->IsShape( meshLabel ) ) + { + std::unique_ptr currentMesh = std::make_unique(); + gp_Trsf initialTransform; // Start with identity transform + + Handle( KICAD3D_INFO ) c; + if( meshLabel.FindAttribute( KICAD3D_INFO::GetID(), c ) ) + { + if( c->GetModelType() == KICAD3D_MODEL_TYPE::BOARD ) + { + currentMesh->parentName = _( MODEL_PARENT_BOARD_NAME ).ToStdString(); + } + else + { + currentMesh->parentName = _( MODEL_PARENT_COMPONENTS_NAME ).ToStdString(); + } + } + + currentMesh->perVertexColor = c->GetModelType() == KICAD3D_MODEL_TYPE::COMPONENT; + + getMeshName( meshLabel, shapeTool, currentMesh.get() ); + wxLogTrace( TRACE_MASK, wxString::Format( "Processing %s", currentMesh->name ) ); + + // Collect all geometry under this rootLabel + collectGeometryRecursive( meshLabel, shapeTool, colorTool, visMatTool, initialTransform, + currentMesh.get() ); + + // Set the primary diffuse color to the first per-vertex color if available + if( !currentMesh->IsEmpty() && currentMesh->perVertexColor ) + { + currentMesh->diffuse_color = currentMesh->diffuse_colors[0]; + } + m_meshes.emplace_back( std::move( currentMesh ) ); + } + } + } + + // The U3D file when used in Adobe PDF needs the meshes sorted by name ahead of time + // As Adobe builds the navigation hierarchy as-is with no options to sort + std::sort( m_meshes.begin(), m_meshes.end(), + []( const std::unique_ptr& a, const std::unique_ptr& b ) + { + return a->name < b->name; + } ); +} + +WRITER::WRITER( const std::string& aFilename ) : + m_filename( aFilename ), + m_center( 0, 0, 0 ), + m_meshBoundingBox(), + m_meshDedupMap(), + m_includeNormals( true ) +{ +} + + +void WRITER::writeMatrix( BIT_STREAM_WRITER& aBitStreamWriter, const std::vector& aMat ) +{ + for( size_t i = 0; i < aMat.size(); i++ ) + aBitStreamWriter.WriteF32( aMat[i] ); +} + + +std::shared_ptr WRITER::getGroupNodeBlock( const std::string& aGroupNodeName, + const PARENT_NODE* aParentNode ) +{ + BIT_STREAM_WRITER w; + uint32_t parentNodeCount = aParentNode == nullptr ? 0 : 1; + + w.WriteString( aGroupNodeName ); // model node name + w.WriteU32( parentNodeCount ); // parent node count + + if( parentNodeCount > 0 ) + { + w.WriteString( aParentNode->name ); // parent node name + writeMatrix( w, aParentNode->mat ); // transformation + } + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::GROUP_NODE ); + return b; +} + + +std::shared_ptr WRITER::getModelNodeBlock( const std::string& aModelNodeName, + const std::string& aParentNodeName, + const std::string& aModelResourceName, + const std::vector& aMat ) +{ + BIT_STREAM_WRITER w; + w.WriteString( aModelNodeName ); + + uint32_t parentNodecount = aParentNodeName.empty() ? 0 : 1; + w.WriteU32( parentNodecount ); // parent node count + if( parentNodecount ) + { + w.WriteString( aParentNodeName ); // parent node name + writeMatrix( w, aMat ); // transformation + } + + w.WriteString( aModelResourceName ); // model resource name + w.WriteU32( 3 ); // visibility 3 = front and back + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MODEL_NODE ); + return b; +} + + +std::shared_ptr WRITER::getShadingModifierBlock( const std::string& aShadingModName, + const std::string& aShaderName ) +{ + BIT_STREAM_WRITER w; + w.WriteString( aShadingModName ); // shading modifier name + w.WriteU32( 1 ); // chain index + w.WriteU32( SHADING_ATTRIBUTES::POINT + | SHADING_ATTRIBUTES::MESH + | SHADING_ATTRIBUTES::LINE + | SHADING_ATTRIBUTES::GLYPH ); // shading attributes + w.WriteU32( 1 ); // shading list count + w.WriteU32( 1 ); // shader count + w.WriteString( aShaderName ); // shader name + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::SHADING_MODIFIER ); + return b; +} + + +std::shared_ptr WRITER::getModelResourceModifierChain( const std::string& aModifierChainName, + const MESH* aMesh, const std::string& aMeshname ) +{ + BIT_STREAM_WRITER w; + + w.WriteString( aModifierChainName ); // modifier chain name + w.WriteU32( 1 ); // modifier chain type: 1 = model resource modifier chain + w.WriteU32( 0 ); // modifier chain attributes: 0 = neither bounding sphere nor + // bounding box info present + // padding + w.AlignTo4Byte(); + w.WriteU32( 1 ); // modifier count in this chain + + w.WriteDataBlock( getMeshDeclarationBlock( aMesh, aMeshname ) ); + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MODIFIER_CHAIN ); + return b; +} + + +std::shared_ptr WRITER::getGroupNodeModifierChain( const std::string& aModifierChainName, + const std::vector& aGroupNodes ) +{ + BIT_STREAM_WRITER w; + w.WriteString( aModifierChainName ); // modifier chain name + w.WriteU32( 0 ); // modifier chain type: 0 = node modifier chain + w.WriteU32( 0 ); // modifier chain attributes: 0 = neither bounding sphere nor + // bounding box info present + w.AlignTo4Byte(); + w.WriteU32( aGroupNodes.size() ); // modifier count in this chain + + for( const auto& groupNode : aGroupNodes ) + { + const PARENT_NODE* parent = groupNode.parentNodes.empty() ? nullptr : &groupNode.parentNodes[0]; + w.WriteDataBlock( getGroupNodeBlock( groupNode.name, parent ) ); + } + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MODIFIER_CHAIN ); + return b; +} + + +std::shared_ptr WRITER::getNodeModifierChain( const std::string& aModifierChainName, + const std::string& aModelNodeName, + const std::string& aParentNodeName, + const std::string& aModelResourceName, + const std::string& aShaderName, + const std::vector& aMat ) +{ + BIT_STREAM_WRITER w; + w.WriteString( aModifierChainName ); // modifier chain name + w.WriteU32( 0 ); // modifier chain type: 0 = node modifier chain + w.WriteU32( 0 ); // modifier chain attributes: 0 = neither bounding sphere nor + // bounding box info present + w.AlignTo4Byte(); + w.WriteU32( 2 ); // modifier count in this chain + + w.WriteDataBlock( getModelNodeBlock( aModelNodeName, aParentNodeName, aModelResourceName, aMat ) ); + w.WriteDataBlock( getShadingModifierBlock( aModifierChainName, aShaderName ) ); + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MODIFIER_CHAIN ); + return b; +} + + +std::shared_ptr WRITER::getHeaderBlock( uint32_t aDeclSize, uint32_t aContSize ) +{ + const uint32_t headerBlockSize = 36; + + BIT_STREAM_WRITER w; + w.WriteU16( 256 ); // major version + w.WriteU16( 0 ); // minor version + w.WriteU32( HEADER_PROFILE_FLAGS::DEFINED_UNITS ); // profile identifier + w.WriteU32( headerBlockSize + aDeclSize ); // declaration size + w.WriteU64( headerBlockSize + aDeclSize + aContSize ); // file size + w.WriteU32( 106 ); // character encoding: 106 = UTF-8 + + w.WriteF64( 0.001f ); // units scale factor (1/1000th of a unit, 1mm = 0.001m) + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::FILE_HEADER ); + return b; +} + + +std::shared_ptr WRITER::getLitTextureShaderBlock( const std::string& aShaderName, + const std::string& aMaterialName ) +{ + BIT_STREAM_WRITER w; + + w.WriteString( aShaderName ); + w.WriteU32( LIT_TEXTURE_SHADER_ATTRIBUTES::LIGHTING_ENABLED | LIT_TEXTURE_SHADER_ATTRIBUTES::USE_VERTEX_COLOR ); + + w.WriteF32( 0 ); // Alpha Test Reference + w.WriteU32( static_cast( ALPHA_TEST_FUNCTION::ALWAYS ) ); // Alpha Test Function + w.WriteU32( static_cast( COLOR_BLEND_FUNCTION::ALPHA_BLEND ) ); // Color Blend Function + + w.WriteU32( 1 ); // Render pass enabled flags (each bit represents a shader enabled) + w.WriteU32( 0 ); // Shader channels + w.WriteU32( 0 ); // Alpha texture channels + + w.WriteString( aMaterialName ); // Material name + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::LIT_TEXTURE_SHADER ); + return b; +} + + +std::shared_ptr WRITER::getMaterialResourceBlock( const std::string& aMaterialName, + const Graphic3d_Vec4& aDiffuseColor, + const Graphic3d_Vec3& aSpecularColor ) +{ + BIT_STREAM_WRITER w; + + w.WriteString( aMaterialName ); // Material resource name + w.WriteU32( MATERIAL_ATTRIBUTES::OPACITY + | MATERIAL_ATTRIBUTES::SPECULAR + | MATERIAL_ATTRIBUTES::DIFFUSE + | MATERIAL_ATTRIBUTES::REFLECTIVITY ); + w.WriteF32( 0.0f ); // Ambient red + w.WriteF32( 0.0f ); // Ambient green + w.WriteF32( 0.0f ); // Ambient blue + w.WriteF32( aDiffuseColor.r() ); // Diffuse red + w.WriteF32( aDiffuseColor.g() ); // Diffuse green + w.WriteF32( aDiffuseColor.b() ); // Diffuse blue + w.WriteF32( aSpecularColor.r() ); // Specular red + w.WriteF32( aSpecularColor.g() ); // Specular green + w.WriteF32( aSpecularColor.b() ); // Specular blue + w.WriteF32( 0.0f ); // Emissive red + w.WriteF32( 0.0f ); // Emissive green + w.WriteF32( 0.0f ); // Emissive blue + w.WriteF32( 0.32f ); // Reflectivity + w.WriteF32( aDiffuseColor.a() ); // Opacity + + std::shared_ptr block = w.GetDataBlock(); + block->SetBlockType( BLOCK_TYPES::MATERIAL_RESOURCE ); + + return block; +} + + +std::shared_ptr WRITER::getMeshDeclarationBlock( const MESH* aMesh, + const std::string& aMeshName ) +{ + BIT_STREAM_WRITER w; + + w.WriteString( aMeshName ); // mesh name + w.WriteU32( 0 ); // chain index + + // max mesh description + w.WriteU32( aMesh->normals.size() > 0 ? 0 : 1 ); // mesh attributes: 1 = no normals + + w.WriteU32( aMesh->coordIndices.size() / 3 ); // face count + w.WriteU32( aMesh->coords.size() ); // positions count + w.WriteU32( aMesh->normals.size() ); // normal count + + w.WriteU32( aMesh->diffuse_colors.size() ); // diffuse color count + w.WriteU32( aMesh->specular_colors.size() ); // specular color count + w.WriteU32( 0 ); // texture coord count + w.WriteU32( 1 ); // shading count + + // shading description + uint32_t shadingAttributes = 0; + + if( aMesh->diffuse_colors.size() > 0 ) + { + shadingAttributes |= 0x01; // per vertex diffuse colors + } + + if( aMesh->specular_colors.size() > 0 ) + { + shadingAttributes |= 0x02; // per vertex specular colors + } + + w.WriteU32( shadingAttributes ); // shading attributes: 0 = the shader list uses neither diffuse nor specular colors + w.WriteU32( 0 ); // texture layer count + // texture coord dimensions (skipped because texture layer count is 0) + w.WriteU32( 0 ); // original shader id + + // clod desc + w.WriteU32( 0 ); // minimum resolution + w.WriteU32( aMesh->coords.size() ); // maximum resolution + w.WriteU32( 300 ); // position quality factor + w.WriteU32( 300 ); // normal quality factor + w.WriteU32( 300 ); // texture coord quality factor + w.WriteF32( 0.001f ); // position inverse quant + w.WriteF32( 0.001f ); // normal inverse quant + w.WriteF32( 0.001f ); // texture coord inverse quant + w.WriteF32( 0.001f ); // diffuse color inverse quant + w.WriteF32( 0.001f ); // specular color inverse quant + w.WriteF32( 0.9f ); // normal crease parameter + w.WriteF32( 0.5f ); // normal update parameter + w.WriteF32( 0.985f ); // normal tolerance parameter + w.WriteU32( 0 ); // bone count + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MESH_DECLARATION ); + return b; +} + + +std::shared_ptr WRITER::getMeshContinuationBlock( const MESH* aMesh, + const std::string& aMeshname ) +{ + BIT_STREAM_WRITER w; + + w.WriteString( aMeshname ); // mesh name + w.WriteU32( 0 ); // chain index + w.WriteU32( aMesh->coordIndices.size() / 3 ); // base face count + w.WriteU32( aMesh->coords.size() ); // base positions count + w.WriteU32( aMesh->normals.size() ); // base normal count + w.WriteU32( aMesh->diffuse_colors.size() ); // base diffuse color count + + w.WriteU32( aMesh->specular_colors.size() ); // base specular color count + w.WriteU32( 0 ); // base texture coord count + + for( size_t i = 0; i < aMesh->coords.size(); i++ ) + { + w.WriteF32( aMesh->coords[i].x ); // base position x + w.WriteF32( aMesh->coords[i].y ); // base position y + w.WriteF32( aMesh->coords[i].z ); // base position z + } + + for( size_t i = 0; i < aMesh->normals.size(); i++ ) + { + w.WriteF32( aMesh->normals[i].x ); // base normal x + w.WriteF32( aMesh->normals[i].y ); // base normal y + w.WriteF32( aMesh->normals[i].z ); // base normal z + } + + for( size_t i = 0; i < aMesh->diffuse_colors.size(); i++ ) + { + w.WriteF32( aMesh->diffuse_colors[i].r() ); // base colors red + w.WriteF32( aMesh->diffuse_colors[i].g() ); // base colors green + w.WriteF32( aMesh->diffuse_colors[i].b() ); // base colors blue + w.WriteF32( aMesh->diffuse_colors[i].a() ); // base colors alpha + } + + for( size_t i = 0; i < aMesh->specular_colors.size(); i++ ) + { + w.WriteF32( aMesh->specular_colors[i].r() ); // base colors red + w.WriteF32( aMesh->specular_colors[i].g() ); // base colors green + w.WriteF32( aMesh->specular_colors[i].b() ); // base colors blue + w.WriteF32( aMesh->specular_colors[i].a() ); // base colors alpha + } + + for( size_t i = 0; i < aMesh->coordIndices.size(); i += 3 ) + { + w.WriteCompressedU32( m_contextBaseShadingID, 0 ); // shading id + for( int j = 0; j < 3; j++ ) + { + w.WriteCompressedU32( CONSTANTS::StaticFull + aMesh->coords.size(), aMesh->coordIndices[i + j] ); + if( aMesh->normals.size() > 0 ) + w.WriteCompressedU32( CONSTANTS::StaticFull + aMesh->normals.size(), + aMesh->normalIndices[i + j] ); + + if( aMesh->diffuse_colors.size() > 0 && aMesh->diffuseColorIndices.size() > 0 ) + w.WriteCompressedU32( CONSTANTS::StaticFull + aMesh->diffuse_colors.size(), + aMesh->diffuseColorIndices[i + j] ); + + if( aMesh->specular_colors.size() > 0 && aMesh->specularColorIndices.size() > 0 ) + w.WriteCompressedU32( CONSTANTS::StaticFull + aMesh->specular_colors.size(), + aMesh->specularColorIndices[i + j] ); + } + } + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MESH_CONTINUATION ); + return b; +} + + +std::shared_ptr WRITER::getLightModifierChain( const std::string& aModifierChainName, + const std::string& aLightResourceName ) +{ + BIT_STREAM_WRITER w; + w.WriteString( aModifierChainName ); // modifier chain name + w.WriteU32( 0 ); // modifier chain type: 0 = node modifier chain + w.WriteU32( 0 ); // modifier chain attributes: 0 = neither bounding sphere nor + // bounding box info present + w.AlignTo4Byte(); + w.WriteU32( 1 ); // modifier count in this chain + + w.WriteDataBlock( getLightNodeBlock( aModifierChainName, aLightResourceName ) ); + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::MODIFIER_CHAIN ); + return b; +} + + +std::shared_ptr WRITER::getLightNodeBlock( const std::string& aLightNodeName, + const std::string& aLightResourceName ) +{ + std::vector matrix = std::vector{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 6.f, -20.0f, 4.0f, 1.0f }; + BIT_STREAM_WRITER w; + w.WriteString( aLightNodeName ); // light node name + w.WriteU32( 1 ); // parent node count + w.WriteString( "" ); // parent node name + writeMatrix( w, matrix ); // transformation + w.WriteString( aLightResourceName ); // Light resource name + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::LIGHT_NODE ); + return b; +} + + +std::shared_ptr WRITER::getLightResourceBlock( const std::string& aLightResourceName ) +{ + BIT_STREAM_WRITER w; + w.WriteString( aLightResourceName ); + w.WriteU32( 1 ); // Light attributes: enable + w.WriteU8( (short) 2 ); // Light type: Point light + w.WriteF32( .5f ); // Light color red + w.WriteF32( .5f ); // Light color green + w.WriteF32( .5f ); // Light color blue + w.WriteF32( 1.0f ); // Reserved, shall be 1 + w.WriteF32( 0.1f ); // Light attenuation constant factor + w.WriteF32( 0.0f ); // Light attenuation linear factor + w.WriteF32( 0.f ); // Light attenuation quadratic factor + w.WriteF32( 180.f ); // Light spot angle + w.WriteF32( .5f ); // Light intensity + + std::shared_ptr b = w.GetDataBlock(); + b->SetBlockType( BLOCK_TYPES::LIGHT_RESOURCE ); + return b; +} + + +uint32_t WRITER::writeDataBlock( std::shared_ptr aBlock, wxMemoryOutputStream& aStream ) +{ + uint32_t dataSize = (uint32_t) ceil( aBlock->GetDataSize() / 4.0 ); + uint32_t metaDataSize = (uint32_t) ceil( aBlock->GetMetaDataSize() / 4.0 ); + + + uint32_t blockLength = ( 4 + 4 + 4 + 4 * ( dataSize + metaDataSize ) ); + + wxDataOutputStream workWriter( aStream ); + + workWriter.Write32( aBlock->GetBlockType() ); + workWriter.Write32( aBlock->GetDataSize() ); + workWriter.Write32( aBlock->GetMetaDataSize() ); + for( uint32_t i = 0; i < dataSize; i++ ) + { + workWriter.Write32( aBlock->GetData()[i] ); + } + + for( uint32_t i = 0; i < metaDataSize; i++ ) + { + workWriter.Write32( aBlock->GetMetaData()[i] ); + } + + return blockLength; +} + + +bool WRITER::Perform( const Handle( TDocStd_Document ) & aDocument ) +{ + // This is just a plain identity transformation matrix needed to be passed for some blocks + // because u3d allows transformations being applied. However we basically have the mesh + // already transformed in the right place so we just pass the identity matrix + std::vector matrix = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + + m_meshBoundingBox.SetVoid(); + m_meshDedupMap.clear(); + + generateMeshesByAssembly( aDocument ); + + // Calculate the center + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + m_meshBoundingBox.Get( xMin, yMin, zMin, xMax, yMax, zMax ); + + Standard_Real centerX = ( xMin + xMax ) / 2.0; + Standard_Real centerY = ( yMin + yMax ) / 2.0; + Standard_Real centerZ = ( zMin + zMax ) / 2.0; + + m_center = VECTOR3D( centerX, centerY, centerZ ); + + // Lets start creating the U3D file + + wxMemoryOutputStream decStream; + wxMemoryOutputStream contStream; + wxMemoryOutputStream outStream; + + uint32_t decSize = 0; + uint32_t contSize = 0; + + std::vector groupNodes; + groupNodes.push_back( { _( MODEL_PARENT_BOARD_NAME ).ToStdString() } ); + groupNodes.push_back( { _( MODEL_PARENT_COMPONENTS_NAME ).ToStdString() } ); + + decSize += writeDataBlock( getGroupNodeModifierChain( "groups", groupNodes ), decStream ); + + for( auto& mesh : m_meshes ) + { + if( mesh->IsEmpty() ) + continue; + + // These strings are fed to the various blocks that build up a model + // They act as references between the blocks for the various elements of a mesh + std::string meshName = "n_" + mesh->name; + + std::string modelResourceName = "n_" + mesh->name; + std::string matName = "m_" + mesh->name; + std::string shaderName = "s_" + mesh->name; + std::string modelModifierChainName = "n_" + mesh->name; + + std::shared_ptr block = + getNodeModifierChain( meshName, mesh->name, mesh->parentName, modelResourceName, shaderName, matrix ); + + decSize += writeDataBlock( block, decStream ); + + block = getModelResourceModifierChain( modelModifierChainName, mesh.get(), meshName ); + decSize += writeDataBlock( block, decStream ); + + decSize += writeDataBlock( getLitTextureShaderBlock( shaderName, matName ), decStream ); + + decSize += + writeDataBlock( getMaterialResourceBlock( matName, mesh->diffuse_color, mesh->specular_color ), decStream ); + + block = getMeshContinuationBlock( mesh.get(), meshName ); + contSize += writeDataBlock( block, contStream ); + } + + // finish it, we need the size totals above to generate the header + writeDataBlock( getHeaderBlock( decSize, contSize ), outStream ); + + wxStreamBuffer* buffer = outStream.GetOutputStreamBuffer(); + + std::ofstream u3dFile( m_filename, std::ios::binary ); + if( !u3dFile.is_open() ) + { + wxLogTrace( TRACE_MASK, wxString::Format( "Error opening file:%s", m_filename ) ); + return false; + } + + u3dFile.write( static_cast( buffer->GetBufferStart() ), buffer->GetBufferSize() ); + + wxStreamBuffer* decBuffer = decStream.GetOutputStreamBuffer(); + u3dFile.write( static_cast( decBuffer->GetBufferStart() ), + decBuffer->GetBufferSize() ); + + wxStreamBuffer* contBuffer = contStream.GetOutputStreamBuffer(); + u3dFile.write( static_cast( contBuffer->GetBufferStart() ), + contBuffer->GetBufferSize() ); + + u3dFile.close(); + + return true; +} \ No newline at end of file diff --git a/pcbnew/exporters/u3d/writer.h b/pcbnew/exporters/u3d/writer.h new file mode 100644 index 0000000000..67ccd494a5 --- /dev/null +++ b/pcbnew/exporters/u3d/writer.h @@ -0,0 +1,290 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2025 Mark Roszko + * Copyright (C) 2010 - 2024 Fiji developers (Java source used as reference) + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace std +{ +template <> +struct hash +{ + size_t operator()( const Graphic3d_Vec4& v ) const + { + size_t h1 = std::hash{}( v.x() ); + size_t h2 = std::hash{}( v.y() ); + size_t h3 = std::hash{}( v.z() ); + return h1 ^ ( h2 << 1 ) ^ ( h3 << 2 ); + } +}; +} // namespace std + +namespace U3D +{ +class MESH +{ +public: + std::vector coords; + + std::vector normals; + + /** + * List of all unique diffuse colors + */ + std::vector diffuse_colors; + + /** + * List of all unique specular colors + */ + std::vector specular_colors; + /** + * Index map helps select the color index for a given vertex when creating the colorIndices + */ + std::unordered_map diffuse_colors_index_map; + /** + * Index map helps select the color index for a given vertex when creating the colorIndices + */ + std::unordered_map specular_colors_index_map; + + /** + * Coordinate indices, maps vertex positions to triangles + */ + std::vector coordIndices; + + /** + * Coordinate indices, maps vertex normal positions to triangles + */ + std::vector normalIndices; + + /** + * Coordinate indices, maps each vertex to a diffuse color + */ + std::vector diffuseColorIndices; + + /** + * Coordinate indices, maps each vertex to a specular color + */ + std::vector specularColorIndices; + + /** + * Diffuse color used if not using per-vertex color + */ + Graphic3d_Vec4 diffuse_color; + + /** + * Specular color used if not using per-vertex color + */ + Graphic3d_Vec3 specular_color; + + /** + * The name of the mesh, this will be visible in U3D viewers + */ + std::string name; + + /** + * Parent name (if any) to group the mesh under + */ + std::string parentName; + + bool perVertexColor; + + + bool IsEmpty() + { + return coords.empty() || coordIndices.empty(); + } + + uint32_t GetOrAddUniqueDiffuseColor( const Graphic3d_Vec4& aColor ) + { + uint32_t colorIndex = 0; + if( diffuse_colors_index_map.find( aColor ) != diffuse_colors_index_map.end() ) + { + colorIndex = diffuse_colors_index_map[aColor]; + } + else + { + colorIndex = diffuse_colors.size(); + diffuse_colors_index_map[aColor] = colorIndex; + diffuse_colors.push_back( aColor ); + } + + return colorIndex; + } + + uint32_t GetOrAddUniqueSpecularColor( const Graphic3d_Vec4& aColor ) + { + uint32_t colorIndex = 0; + if( specular_colors_index_map.find( aColor ) != specular_colors_index_map.end() ) + { + colorIndex = specular_colors_index_map[aColor]; + } + else + { + colorIndex = specular_colors.size(); + specular_colors_index_map[aColor] = colorIndex; + specular_colors.push_back( aColor ); + } + + return colorIndex; + } +}; + +struct PARENT_NODE +{ + /** + * Name of the parent node to link to + */ + std::string name; + + /** + * Transform matrix, 4x4, row major + */ + std::vector mat; +}; + +struct GROUP_NODE +{ + /** + * Name of this group node + */ + std::string name; + + /** + * List of parent nodes this group node belongs to + */ + std::vector parentNodes; +}; + +class WRITER +{ +public: + WRITER( const std::string& aFilename ); + + bool Perform( const Handle( TDocStd_Document ) & aDocument ); + + const VECTOR3D& GetCenter() const { return m_center; } + const Bnd_Box& GetMeshBoundingBox() const { return m_meshBoundingBox; } +private: + void writeMatrix( BIT_STREAM_WRITER& aBitStreamWriter, const std::vector& aMat ); + + std::shared_ptr getGroupNodeBlock( const std::string& aGroupNodeName, + const PARENT_NODE* aParentNode ); + + std::shared_ptr getModelNodeBlock( const std::string& aModelNodeName, + const std::string& aParentNodeName, + const std::string& aModelResourceName, + const std::vector& aMat ); + + std::shared_ptr getShadingModifierBlock( const std::string& aShadingModName, + const std::string& aShaderName ); + + std::shared_ptr + getNodeModifierChain( const std::string& aModifierChainName, const std::string& aModelNodeName, + const std::string& aParentNodeName, const std::string& aModelResourceName, + const std::string& aShaderName, const std::vector& aMat ); + + + std::shared_ptr getHeaderBlock( uint32_t aDeclSize, uint32_t aContSize ); + + + std::shared_ptr getLitTextureShaderBlock( const std::string& aShaderName, + const std::string& aMaterialName ); + + + std::shared_ptr getMaterialResourceBlock( const std::string& aMaterialName, + const Graphic3d_Vec4& aDiffuseColor, + const Graphic3d_Vec3& aSpecularColor ); + + std::shared_ptr + getModelResourceModifierChain( const std::string& aModifierChainName, const MESH* aMesh, + const std::string& aMeshname ); + + std::shared_ptr getMeshDeclarationBlock( const MESH* aMesh, + const std::string& aMeshName ); + + std::shared_ptr getMeshContinuationBlock( const MESH* aMesh, + const std::string& aMeshName ); + + + std::shared_ptr getLightModifierChain( const std::string& aModifierChainName, + const std::string& aLightResourceName ); + + std::shared_ptr getLightNodeBlock( const std::string& aLightNodeName, + const std::string& aLightResourceName ); + + std::shared_ptr getLightResourceBlock( const std::string& aLightResourceName ); + + std::shared_ptr + getGroupNodeModifierChain( const std::string& aModifierChainName, + const std::vector& aGroupNodes ); + + uint32_t writeDataBlock( std::shared_ptr b, wxMemoryOutputStream& aStream ); + + + void generateMeshesByAssembly( const Handle( TDocStd_Document ) & doc ); + + void collectGeometryRecursive( const TDF_Label& label, const Handle( XCAFDoc_ShapeTool ) & shapeTool, + const Handle( XCAFDoc_ColorTool ) & colorTool, + const Handle( XCAFDoc_VisMaterialTool ) & visMatTool, + const gp_Trsf& cumulativeTransform, + MESH* mesh + ); + + + void getMeshName( const TDF_Label& label, Handle( XCAFDoc_ShapeTool ) shapeTool, MESH* mesh ); + + + std::string m_filename; + VECTOR3D m_center; + Bnd_Box m_meshBoundingBox; + std::map m_meshDedupMap; + const uint32_t m_contextBaseShadingID = 1; + bool m_includeNormals; + std::vector> m_meshes; +}; + +} // namespace U3D \ No newline at end of file diff --git a/pcbnew/pcbnew_jobs_handler.cpp b/pcbnew/pcbnew_jobs_handler.cpp index 84cd495085..7778f9b1a5 100644 --- a/pcbnew/pcbnew_jobs_handler.cpp +++ b/pcbnew/pcbnew_jobs_handler.cpp @@ -509,6 +509,8 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob ) 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; + case JOB_EXPORT_PCB_3D::FORMAT::U3D: fn.SetExt( FILEEXT::U3DFileExtension ); break; + case JOB_EXPORT_PCB_3D::FORMAT::PDF: fn.SetExt( FILEEXT::PdfFileExtension ); break; default: m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR ); return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here @@ -587,6 +589,8 @@ 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; + case JOB_EXPORT_PCB_3D::FORMAT::U3D: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::U3D; break; + case JOB_EXPORT_PCB_3D::FORMAT::PDF: params.m_Format = EXPORTER_STEP_PARAMS::FORMAT::PDF; break; default: m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR ); return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here