From c68e3ceb44767a7cb30854c5e978bdb6007b7902 Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Mon, 22 Apr 2024 03:39:43 +0300 Subject: [PATCH] ADDED: Export inner PCB copper layers to STEP / BREP / GLTF. Also adds options to exclude board body and components. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16855 --- common/jobs/job_export_pcb_3d.cpp | 11 +- common/jobs/job_export_pcb_3d.h | 5 +- kicad/cli/command_pcb_export_3d.cpp | 23 +- pcbnew/dialogs/dialog_export_step.cpp | 30 +- pcbnew/dialogs/dialog_export_step_base.cpp | 11 +- pcbnew/dialogs/dialog_export_step_base.fbp | 197 ++++++++++- pcbnew/dialogs/dialog_export_step_base.h | 3 + pcbnew/exporters/step/exporter_step.cpp | 135 +++----- pcbnew/exporters/step/exporter_step.h | 20 +- pcbnew/exporters/step/step_pcb_model.cpp | 370 +++++++++++++-------- pcbnew/exporters/step/step_pcb_model.h | 39 +-- pcbnew/pcbnew_jobs_handler.cpp | 5 +- 12 files changed, 575 insertions(+), 274 deletions(-) diff --git a/common/jobs/job_export_pcb_3d.cpp b/common/jobs/job_export_pcb_3d.cpp index 9f2f5b1759..758274ee61 100644 --- a/common/jobs/job_export_pcb_3d.cpp +++ b/common/jobs/job_export_pcb_3d.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Mark Roszko - * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2023-2024 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 @@ -38,9 +38,12 @@ JOB_EXPORT_PCB_3D::JOB_EXPORT_PCB_3D( bool aIsCli ) : m_yOrigin( 0.0 ), // max dist to chain 2 items (lines or curves) to build the board outlines m_BoardOutlinesChainingEpsilon( 0.01 ), // 0.01 mm is a good value - m_exportTracks( false ), // Time consuming if true - m_exportZones( false ), // Time consuming if true - m_fuseShapes( false ), // Time consuming if true + m_exportBoardBody( true ), + m_exportComponents( true ), + m_exportTracks( false ), + m_exportZones( false ), + m_exportInnerCopper( false ), + m_fuseShapes( false ), m_format( JOB_EXPORT_PCB_3D::FORMAT::UNKNOWN ), m_vrmlUnits( JOB_EXPORT_PCB_3D::VRML_UNITS::METERS ), m_vrmlModelDir( wxEmptyString ), diff --git a/common/jobs/job_export_pcb_3d.h b/common/jobs/job_export_pcb_3d.h index f7264ae6dd..0e2d08bc57 100644 --- a/common/jobs/job_export_pcb_3d.h +++ b/common/jobs/job_export_pcb_3d.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2022 Mark Roszko - * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2024 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 @@ -61,8 +61,11 @@ public: double m_xOrigin; double m_yOrigin; double m_BoardOutlinesChainingEpsilon; + bool m_exportBoardBody; + bool m_exportComponents; bool m_exportTracks; bool m_exportZones; + bool m_exportInnerCopper; bool m_fuseShapes; JOB_EXPORT_PCB_3D::FORMAT m_format; diff --git a/kicad/cli/command_pcb_export_3d.cpp b/kicad/cli/command_pcb_export_3d.cpp index dbfe1c2247..1ae75f6244 100644 --- a/kicad/cli/command_pcb_export_3d.cpp +++ b/kicad/cli/command_pcb_export_3d.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2022 Mark Roszko - * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2024 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 @@ -37,8 +37,11 @@ #define ARG_MIN_DISTANCE "--min-distance" #define ARG_USER_ORIGIN "--user-origin" #define ARG_BOARD_ONLY "--board-only" +#define ARG_NO_BOARD_BODY "--no-board-body" +#define ARG_NO_COMPONENTS "--no-components" #define ARG_INCLUDE_TRACKS "--include-tracks" #define ARG_INCLUDE_ZONES "--include-zones" +#define ARG_INCLUDE_INNER_COPPER "--include-inner-copper" #define ARG_FUSE_SHAPES "--fuse-shapes" #define ARG_NO_OPTIMIZE_STEP "--no-optimize-step" #define ARG_FORMAT "--format" @@ -103,15 +106,26 @@ CLI::PCB_EXPORT_3D_COMMAND::PCB_EXPORT_3D_COMMAND( const std::string& aNa .help( UTF8STDSTR( _( "Only generate a board with no components" ) ) ) .flag(); + m_argParser.add_argument( ARG_NO_BOARD_BODY ) + .help( UTF8STDSTR( _( "Exclude board body" ) ) ) + .flag(); + + m_argParser.add_argument( ARG_NO_COMPONENTS ) + .help( UTF8STDSTR( _( "Exclude 3D models for components" ) ) ) + .flag(); + m_argParser.add_argument( ARG_INCLUDE_TRACKS ) .help( UTF8STDSTR( _( "Export tracks" ) ) ) - .implicit_value( true ) - .default_value( false ); + .flag(); m_argParser.add_argument( ARG_INCLUDE_ZONES ) .help( UTF8STDSTR( _( "Export zones" ) ) ) .flag(); + m_argParser.add_argument( ARG_INCLUDE_INNER_COPPER ) + .help( UTF8STDSTR( _( "Export elements on inner copper layers" ) ) ) + .flag(); + m_argParser.add_argument( ARG_FUSE_SHAPES ) .help( UTF8STDSTR( _( "Fuse overlapping geometry together" ) ) ) .flag(); @@ -166,8 +180,11 @@ int CLI::PCB_EXPORT_3D_COMMAND::doPerform( KIWAY& aKiway ) step->m_useDrillOrigin = m_argParser.get( ARG_DRILL_ORIGIN ); step->m_useGridOrigin = m_argParser.get( ARG_GRID_ORIGIN ); step->m_substModels = m_argParser.get( ARG_SUBST_MODELS ); + step->m_exportBoardBody = !m_argParser.get( ARG_NO_BOARD_BODY ); + step->m_exportComponents = !m_argParser.get( ARG_NO_COMPONENTS ); step->m_exportTracks = m_argParser.get( ARG_INCLUDE_TRACKS ); step->m_exportZones = m_argParser.get( ARG_INCLUDE_ZONES ); + step->m_exportInnerCopper = m_argParser.get( ARG_INCLUDE_INNER_COPPER ); step->m_fuseShapes = m_argParser.get( ARG_FUSE_SHAPES ); step->m_boardOnly = m_argParser.get( ARG_BOARD_ONLY ); } diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp index 80112d7462..11e80246f0 100644 --- a/pcbnew/dialogs/dialog_export_step.cpp +++ b/pcbnew/dialogs/dialog_export_step.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 Cirilo Bernardo - * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-2024 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 @@ -117,9 +117,12 @@ private: bool m_noUnspecified; // remember last preference for No Unspecified Component bool m_noDNP; // remember last preference for No DNP Component static bool m_optimizeStep; // remember last preference for Optimize STEP file (stored only for the session) + static bool m_exportBoardBody; // remember last preference to export board body (stored only for the session) + static bool m_exportComponents; // remember last preference to export components (stored only for the session) static bool m_exportTracks; // remember last preference to export tracks (stored only for the session) - static bool m_exportZones; // remember last preference to export tracks (stored only for the session) - static bool m_fuseShapes; // remember last preference to export tracks (stored only for the session) + static bool m_exportZones; // remember last preference to export zones (stored only for the session) + static bool m_fuseShapes; // remember last preference to fuse shapes (stored only for the session) + static bool m_exportInnerCopper; // remember last preference to export inner layers (stored only for the session) wxString m_boardPath; // path to the exported board file static int m_toleranceLastChoice; // Store m_tolerance option during a session }; @@ -127,8 +130,11 @@ private: int DIALOG_EXPORT_STEP::m_toleranceLastChoice = -1; // Use default bool DIALOG_EXPORT_STEP::m_optimizeStep = true; +bool DIALOG_EXPORT_STEP::m_exportBoardBody = true; +bool DIALOG_EXPORT_STEP::m_exportComponents = true; bool DIALOG_EXPORT_STEP::m_exportTracks = false; bool DIALOG_EXPORT_STEP::m_exportZones = false; +bool DIALOG_EXPORT_STEP::m_exportInnerCopper = false; bool DIALOG_EXPORT_STEP::m_fuseShapes = false; DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath ) : @@ -180,8 +186,11 @@ DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& m_noDNP = cfg->m_ExportStep.no_dnp; m_cbOptimizeStep->SetValue( m_optimizeStep ); + m_cbExportBody->SetValue( m_exportBoardBody ); + m_cbExportComponents->SetValue( m_exportComponents ); m_cbExportTracks->SetValue( m_exportTracks ); m_cbExportZones->SetValue( m_exportZones ); + m_cbExportInnerCopper->SetValue( m_exportInnerCopper ); m_cbFuseShapes->SetValue( m_fuseShapes ); m_cbRemoveUnspecified->SetValue( m_noUnspecified ); m_cbRemoveDNP->SetValue( m_noDNP ); @@ -276,8 +285,11 @@ DIALOG_EXPORT_STEP::~DIALOG_EXPORT_STEP() m_toleranceLastChoice = m_choiceTolerance->GetSelection(); m_optimizeStep = m_cbOptimizeStep->GetValue(); + m_exportBoardBody = m_cbExportBody->GetValue(); + m_exportComponents = m_cbExportComponents->GetValue(); m_exportTracks = m_cbExportTracks->GetValue(); m_exportZones = m_cbExportZones->GetValue(); + m_exportInnerCopper = m_cbExportInnerCopper->GetValue(); m_fuseShapes = m_cbFuseShapes->GetValue(); } @@ -385,8 +397,11 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) double tolerance; // default value in mm m_toleranceLastChoice = m_choiceTolerance->GetSelection(); m_optimizeStep = m_cbOptimizeStep->GetValue(); + m_exportBoardBody = m_cbExportBody->GetValue(); + m_exportComponents = m_cbExportComponents->GetValue(); m_exportTracks = m_cbExportTracks->GetValue(); m_exportZones = m_cbExportZones->GetValue(); + m_exportInnerCopper = m_cbExportInnerCopper->GetValue(); m_fuseShapes = m_cbFuseShapes->GetValue(); switch( m_choiceTolerance->GetSelection() ) @@ -475,12 +490,21 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent ) if( !m_optimizeStep ) cmdK2S.Append( wxT( " --no-optimize-step" ) ); + if( !m_exportBoardBody ) + cmdK2S.Append( wxT( " --no-board-body" ) ); + + if( !m_exportComponents ) + cmdK2S.Append( wxT( " --no-components" ) ); + if( m_exportTracks ) cmdK2S.Append( wxT( " --include-tracks" ) ); if( m_exportZones ) cmdK2S.Append( wxT( " --include-zones" ) ); + if( m_exportInnerCopper ) + cmdK2S.Append( wxT( " --include-inner-copper" ) ); + if( m_fuseShapes ) cmdK2S.Append( wxT( " --fuse-shapes" ) ); diff --git a/pcbnew/dialogs/dialog_export_step_base.cpp b/pcbnew/dialogs/dialog_export_step_base.cpp index 5f42e7b14a..9c5f9b8649 100644 --- a/pcbnew/dialogs/dialog_export_step_base.cpp +++ b/pcbnew/dialogs/dialog_export_step_base.cpp @@ -151,16 +151,25 @@ DIALOG_EXPORT_STEP_BASE::DIALOG_EXPORT_STEP_BASE( wxWindow* parent, wxWindowID i m_staticline1 = new wxStaticLine( sbOtherOptions->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); sbOtherOptions->Add( m_staticline1, 0, wxEXPAND|wxTOP|wxBOTTOM, 5 ); + m_cbExportBody = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export board body"), wxDefaultPosition, wxDefaultSize, 0 ); + sbOtherOptions->Add( m_cbExportBody, 0, wxBOTTOM|wxRIGHT, 5 ); + + m_cbExportComponents = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export components"), wxDefaultPosition, wxDefaultSize, 0 ); + sbOtherOptions->Add( m_cbExportComponents, 0, wxBOTTOM|wxRIGHT, 5 ); + m_cbExportTracks = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export tracks, pads and vias"), wxDefaultPosition, wxDefaultSize, 0 ); m_cbExportTracks->SetToolTip( _("Export tracks, pads and vias on external copper layers.") ); - sbOtherOptions->Add( m_cbExportTracks, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + sbOtherOptions->Add( m_cbExportTracks, 0, wxBOTTOM|wxRIGHT, 5 ); m_cbExportZones = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export zones"), wxDefaultPosition, wxDefaultSize, 0 ); m_cbExportZones->SetToolTip( _("Export zones on external copper layers.") ); sbOtherOptions->Add( m_cbExportZones, 0, wxBOTTOM|wxRIGHT, 5 ); + m_cbExportInnerCopper = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Export inner copper layers"), wxDefaultPosition, wxDefaultSize, 0 ); + sbOtherOptions->Add( m_cbExportInnerCopper, 0, wxBOTTOM|wxEXPAND|wxRIGHT, 5 ); + m_cbFuseShapes = new wxCheckBox( sbOtherOptions->GetStaticBox(), wxID_ANY, _("Fuse shapes (time consuming)"), wxDefaultPosition, wxDefaultSize, 0 ); m_cbFuseShapes->SetToolTip( _("Combine intersecting geometry into one shape.") ); diff --git a/pcbnew/dialogs/dialog_export_step_base.fbp b/pcbnew/dialogs/dialog_export_step_base.fbp index 0d3d0ffd21..bf300ef92c 100644 --- a/pcbnew/dialogs/dialog_export_step_base.fbp +++ b/pcbnew/dialogs/dialog_export_step_base.fbp @@ -1496,7 +1496,137 @@ 5 - wxTOP|wxBOTTOM|wxRIGHT + wxBOTTOM|wxRIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Export board body + + 0 + + + 0 + + 1 + m_cbExportBody + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxBOTTOM|wxRIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Export components + + 0 + + + 0 + + 1 + m_cbExportComponents + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxBOTTOM|wxRIGHT 0 1 @@ -1624,6 +1754,71 @@ + + 5 + wxBOTTOM|wxEXPAND|wxRIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Export inner copper layers + + 0 + + + 0 + + 1 + m_cbExportInnerCopper + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + 5 wxBOTTOM|wxRIGHT diff --git a/pcbnew/dialogs/dialog_export_step_base.h b/pcbnew/dialogs/dialog_export_step_base.h index b582b733a3..9be34cdab8 100644 --- a/pcbnew/dialogs/dialog_export_step_base.h +++ b/pcbnew/dialogs/dialog_export_step_base.h @@ -68,8 +68,11 @@ class DIALOG_EXPORT_STEP_BASE : public DIALOG_SHIM wxCheckBox* m_cbOptimizeStep; wxCheckBox* m_cbExportCompound_hidden; wxStaticLine* m_staticline1; + wxCheckBox* m_cbExportBody; + wxCheckBox* m_cbExportComponents; wxCheckBox* m_cbExportTracks; wxCheckBox* m_cbExportZones; + wxCheckBox* m_cbExportInnerCopper; wxCheckBox* m_cbFuseShapes; wxCheckBox* m_cbExportSilkscreen_hidden; wxCheckBox* m_cbExportSoldermask_hidden; diff --git a/pcbnew/exporters/step/exporter_step.cpp b/pcbnew/exporters/step/exporter_step.cpp index c5199abfbd..6a1635ed30 100644 --- a/pcbnew/exporters/step/exporter_step.cpp +++ b/pcbnew/exporters/step/exporter_step.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2022 Mark Roszko * Copyright (C) 2016 Cirilo Bernardo - * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-2024 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 @@ -143,8 +143,7 @@ EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams m_fail( false ), m_warn( false ), m_board( aBoard ), - m_pcbModel( nullptr ), - m_boardThickness( DEFAULT_BOARD_THICKNESS_MM ) + m_pcbModel( nullptr ) { m_solderMaskColor = COLOR4D( 0.08, 0.20, 0.14, 0.83 ); m_copperColor = COLOR4D( 0.7, 0.61, 0.0, 1.0 ); @@ -177,7 +176,7 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri if( m_pcbModel->AddPadHole( pad, aOrigin ) ) hasdata = true; - if( ExportTracksAndVias() ) + if( m_params.m_exportTracksVias ) { if( m_pcbModel->AddPadShape( pad, aOrigin ) ) hasdata = true; @@ -185,17 +184,18 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri } // Build 3D shapes of the footprint graphic items on external layers: - if( ExportTracksAndVias() ) + if( m_params.m_exportTracksVias ) { int maxError = m_board->GetDesignSettings().m_MaxError; - aFootprint->TransformFPShapesToPolySet( m_top_copper_shapes, F_Cu, 0, maxError, ERROR_INSIDE, - false, /* include text */ - true, /* include shapes */ - false /* include private items */ ); - aFootprint->TransformFPShapesToPolySet( m_bottom_copper_shapes, B_Cu, 0, maxError, ERROR_INSIDE, - false, /* include text */ - true, /* include shapes */ - false /* include private items */ ); + + for( PCB_LAYER_ID pcblayer : aFootprint->GetLayerSet().CuStack() ) + { + aFootprint->TransformFPShapesToPolySet( m_poly_copper_shapes[pcblayer], pcblayer, 0, + maxError, ERROR_INSIDE, + false, /* include text */ + true, /* include shapes */ + false /* include private items */ ); + } } if( ( !(aFootprint->GetAttributes() & (FP_THROUGH_HOLE|FP_SMD)) ) && !m_params.m_includeUnspecified ) @@ -234,7 +234,7 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri } // Exit early if we don't want to include footprint models - if( m_params.m_boardOnly ) + if( m_params.m_boardOnly || !m_params.m_exportComponents ) { return hasdata; } @@ -244,7 +244,6 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri for( const FP_3DMODEL& fp_model : aFootprint->Models() ) { if( !fp_model.m_Show || fp_model.m_Filename.empty() ) - continue; std::vector searchedPaths; @@ -305,17 +304,15 @@ bool EXPORTER_STEP::buildTrack3DShape( PCB_TRACK* aTrack, VECTOR2D aOrigin ) PCB_LAYER_ID pcblayer = aTrack->GetLayer(); - if( pcblayer != F_Cu && pcblayer != B_Cu ) + if( !IsCopperLayer( pcblayer ) ) return false; if( aTrack->Type() == PCB_ARC_T ) { int maxError = m_board->GetDesignSettings().m_MaxError; - if( pcblayer == F_Cu ) - aTrack->TransformShapeToPolygon( m_top_copper_shapes, pcblayer, 0, maxError, ERROR_INSIDE ); - else - aTrack->TransformShapeToPolygon( m_bottom_copper_shapes, pcblayer, 0, maxError, ERROR_INSIDE ); + aTrack->TransformShapeToPolygon( m_poly_copper_shapes[pcblayer], pcblayer, 0, maxError, + ERROR_INSIDE ); } else m_pcbModel->AddTrackSegment( aTrack, aOrigin ); @@ -328,16 +325,13 @@ void EXPORTER_STEP::buildZones3DShape( VECTOR2D aOrigin ) { for( ZONE* zone : m_board->Zones() ) { - for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() ) + for( PCB_LAYER_ID layer : zone->GetLayerSet().CuStack() ) { - if( layer == F_Cu || layer == B_Cu ) - { - SHAPE_POLY_SET copper_shape; - zone->TransformSolidAreasShapesToPolygon( layer, copper_shape ); - copper_shape.Unfracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + SHAPE_POLY_SET copper_shape; + zone->TransformSolidAreasShapesToPolygon( layer, copper_shape ); + copper_shape.Unfracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - m_pcbModel->AddCopperPolygonShapes( &copper_shape, layer == F_Cu, aOrigin, false ); - } + m_pcbModel->AddCopperPolygonShapes( &copper_shape, layer, aOrigin, false ); } } } @@ -347,24 +341,18 @@ bool EXPORTER_STEP::buildGraphic3DShape( BOARD_ITEM* aItem, VECTOR2D aOrigin ) { PCB_SHAPE* graphic = dynamic_cast( aItem ); - if( ! graphic ) + if( !graphic ) return false; PCB_LAYER_ID pcblayer = graphic->GetLayer(); - if( pcblayer != F_Cu && pcblayer != B_Cu ) + if( !IsCopperLayer( pcblayer ) ) return false; - SHAPE_POLY_SET copper_shapes; int maxError = m_board->GetDesignSettings().m_MaxError; - - if( pcblayer == F_Cu ) - graphic->TransformShapeToPolygon( m_top_copper_shapes, pcblayer, 0, - maxError, ERROR_INSIDE ); - else - graphic->TransformShapeToPolygon( m_bottom_copper_shapes, pcblayer, 0, - maxError, ERROR_INSIDE ); + graphic->TransformShapeToPolygon( m_poly_copper_shapes[pcblayer], pcblayer, 0, maxError, + ERROR_INSIDE ); return true; } @@ -384,6 +372,13 @@ bool EXPORTER_STEP::buildBoard3DShapes() wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) ); } + LSET layersToExport = LSET::ExternalCuMask(); + + if( m_params.m_exportInnerCopper ) + layersToExport |= LSET::InternalCuMask(); + + layersToExport &= m_board->GetEnabledLayers(); + VECTOR2D origin; // Determine the coordinate system reference: @@ -401,7 +396,10 @@ bool EXPORTER_STEP::buildBoard3DShapes() m_pcbModel->SetBoardColor( m_solderMaskColor.r, m_solderMaskColor.g, m_solderMaskColor.b ); m_pcbModel->SetCopperColor( m_copperColor.r, m_copperColor.g, m_copperColor.b ); - m_pcbModel->SetPCBThickness( m_boardThickness ); + wxCHECK( m_board->GetDesignSettings().m_HasStackup, false ); + + m_pcbModel->SetStackup( m_board->GetDesignSettings().GetStackupDescriptor() ); + m_pcbModel->SetEnabledLayers( layersToExport ); m_pcbModel->SetFuseShapes( m_params.m_fuseShapes ); // Note: m_params.m_BoardOutlinesChainingEpsilon is used only to build the board outlines, @@ -420,7 +418,7 @@ bool EXPORTER_STEP::buildBoard3DShapes() for( FOOTPRINT* fp : m_board->Footprints() ) buildFootprint3DShapes( fp, origin ); - if( ExportTracksAndVias() ) + if( m_params.m_exportTracksVias ) { for( PCB_TRACK* track : m_board->Tracks() ) buildTrack3DShape( track, origin ); @@ -429,8 +427,11 @@ bool EXPORTER_STEP::buildBoard3DShapes() buildGraphic3DShape( item, origin ); } - m_pcbModel->AddCopperPolygonShapes( &m_top_copper_shapes, true, origin, true ); - m_pcbModel->AddCopperPolygonShapes( &m_bottom_copper_shapes, false, origin, true ); + for( PCB_LAYER_ID pcblayer : layersToExport.Seq() ) + { + m_pcbModel->AddCopperPolygonShapes( &m_poly_copper_shapes[pcblayer], pcblayer, origin, + true ); + } if( m_params.m_exportZones ) { @@ -443,7 +444,7 @@ bool EXPORTER_STEP::buildBoard3DShapes() msg.Printf( wxT( "Board outline: find %d initial points\n" ), pcbOutlines.FullPointCount() ); ReportMessage( msg ); - if( !m_pcbModel->CreatePCB( pcbOutlines, origin ) ) + if( !m_pcbModel->CreatePCB( pcbOutlines, origin, m_params.m_exportBoardBody ) ) { ReportMessage( wxT( "could not create PCB solid model\n" ) ); return false; @@ -453,46 +454,6 @@ bool EXPORTER_STEP::buildBoard3DShapes() } -void EXPORTER_STEP::calculatePcbThickness() -{ - m_boardThickness = DEFAULT_BOARD_THICKNESS_MM; - - const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); - - if( bds.GetStackupDescriptor().GetCount() ) - { - int thickness = 0; - - for( BOARD_STACKUP_ITEM* item : bds.GetStackupDescriptor().GetList() ) - { - switch( item->GetType() ) - { - case BS_ITEM_TYPE_DIELECTRIC: - // Dielectric can have sub-layers. Layer 0 is the main layer - // Not frequent, but possible - for( int idx = 0; idx < item->GetSublayersCount(); idx++ ) - thickness += item->GetThickness( idx ); - - break; - - case BS_ITEM_TYPE_COPPER: - case BS_ITEM_TYPE_SOLDERMASK: - if( item->IsEnabled() ) - thickness += item->GetThickness(); - - break; - - default: - break; - } - } - - if( thickness > 0 ) - m_boardThickness = pcbIUScale.IUTomm( thickness ); - } -} - - bool EXPORTER_STEP::Export() { // Display the export time, for statistics @@ -503,10 +464,6 @@ bool EXPORTER_STEP::Export() Message::DefaultMessenger()->AddPrinter( new KiCadPrinter( this ) ); ReportMessage( _( "Determining PCB data\n" ) ); - calculatePcbThickness(); - wxString msg; - msg.Printf( _( "Board Thickness from stackup: %.3f mm\n" ), m_boardThickness ); - ReportMessage( msg ); if( m_params.m_outputFile.IsEmpty() ) { @@ -565,9 +522,11 @@ bool EXPORTER_STEP::Export() if( m_fail || m_error ) { + wxString msg; + if( m_fail ) { - msg = wxString::Format( _( "Unable to create %s file.\n" + msg = wxString::Format( _( "Unable to create %s file.\n" "Check that the board has a valid outline and models." ), m_params.GetFormatName() ); } diff --git a/pcbnew/exporters/step/exporter_step.h b/pcbnew/exporters/step/exporter_step.h index a24023fbc2..3f82da8229 100644 --- a/pcbnew/exporters/step/exporter_step.h +++ b/pcbnew/exporters/step/exporter_step.h @@ -28,6 +28,7 @@ #include #include +#include // Default value to chain 2 shapes when creating the board outlines // from shapes on Edges.Cut layer @@ -54,8 +55,11 @@ public: m_substModels( true ), m_BoardOutlinesChainingEpsilon( BOARD_DEFAULT_CHAINING_EPSILON ), m_boardOnly( false ), - m_exportTracks( false ), + m_exportBoardBody( true ), + m_exportComponents( true ), + m_exportTracksVias( false ), m_exportZones( false ), + m_exportInnerCopper( false ), m_fuseShapes( false ), m_optimizeStep( true ), m_format( FORMAT::STEP ) @@ -80,8 +84,11 @@ public: bool m_substModels; double m_BoardOutlinesChainingEpsilon; bool m_boardOnly; - bool m_exportTracks; + bool m_exportBoardBody; + bool m_exportComponents; + bool m_exportTracksVias; bool m_exportZones; + bool m_exportInnerCopper; bool m_fuseShapes; bool m_optimizeStep; FORMAT m_format; @@ -104,16 +111,12 @@ public: void SetFail() { m_fail = true; } void SetWarn() { m_warn = true; } - /// Return rue to export tracks and vias on top and bottom copper layers - bool ExportTracksAndVias() { return m_params.m_exportTracks; } - private: bool buildBoard3DShapes(); bool buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOrigin ); bool buildTrack3DShape( PCB_TRACK* aTrack, VECTOR2D aOrigin ); void buildZones3DShape( VECTOR2D aOrigin ); bool buildGraphic3DShape( BOARD_ITEM* aItem, VECTOR2D aOrigin ); - void calculatePcbThickness(); EXPORTER_STEP_PARAMS m_params; std::unique_ptr m_resolver; @@ -130,10 +133,7 @@ private: /// used to identify items in step file wxString m_pcbBaseName; - double m_boardThickness; - - SHAPE_POLY_SET m_top_copper_shapes; - SHAPE_POLY_SET m_bottom_copper_shapes; + std::map m_poly_copper_shapes; KIGFX::COLOR4D m_solderMaskColor; KIGFX::COLOR4D m_copperColor; diff --git a/pcbnew/exporters/step/step_pcb_model.cpp b/pcbnew/exporters/step/step_pcb_model.cpp index 3e223e0732..5b8576751c 100644 --- a/pcbnew/exporters/step/step_pcb_model.cpp +++ b/pcbnew/exporters/step/step_pcb_model.cpp @@ -45,6 +45,8 @@ #include #include #include +#include +#include #include "step_pcb_model.h" #include "streamwrapper.h" @@ -59,27 +61,33 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include #include -#include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -87,19 +95,10 @@ #include #include -#include -#include -#include -#include -#include -#include - #include -#include #include #include -#include #include #include #include @@ -203,8 +202,6 @@ STEP_PCB_MODEL::STEP_PCB_MODEL( const wxString& aPcbName ) m_components = 0; m_precision = USER_PREC; m_angleprec = USER_ANGLE_PREC; - m_boardThickness = BOARD_THICKNESS_DEFAULT_MM; - m_copperThickness = COPPER_THICKNESS_DEFAULT_MM; m_mergeOCCMaxDist = OCC_MAX_DISTANCE_TO_MERGE_POINTS; m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller m_pcbName = aPcbName; @@ -219,141 +216,149 @@ STEP_PCB_MODEL::~STEP_PCB_MODEL() m_doc->Close(); } + bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin ) { bool success = true; - for( PCB_LAYER_ID pcb_layer = F_Cu; ; pcb_layer = B_Cu ) + for( PCB_LAYER_ID pcb_layer : aPad->GetLayerSet().Seq() ) { + if( !IsCopperLayer( pcb_layer ) ) + continue; + + if( !m_enabledLayers.Contains( pcb_layer ) ) + continue; + TopoDS_Shape curr_shape; - double Zpos = pcb_layer == F_Cu ? m_boardThickness : -m_copperThickness; - if( aPad->IsOnLayer( pcb_layer ) ) + double Zpos, thickness; + getLayerZPlacement( pcb_layer, Zpos, thickness ); + + // Make a shape on copper layers + std::shared_ptr effShapePtr = aPad->GetEffectiveShape( pcb_layer ); + + wxCHECK( effShapePtr->Type() == SHAPE_TYPE::SH_COMPOUND, false ); + SHAPE_COMPOUND* compoundShape = static_cast( effShapePtr.get() ); + + std::vector topodsShapes; + + for( SHAPE* shape : compoundShape->Shapes() ) { - // Make a shape on top/bottom copper layer - std::shared_ptr effShapePtr = aPad->GetEffectiveShape( pcb_layer ); - - wxCHECK( effShapePtr->Type() == SHAPE_TYPE::SH_COMPOUND, false ); - SHAPE_COMPOUND* compoundShape = static_cast( effShapePtr.get() ); - - std::vector topodsShapes; - - for( SHAPE* shape : compoundShape->Shapes() ) + if( shape->Type() == SHAPE_TYPE::SH_SEGMENT || shape->Type() == SHAPE_TYPE::SH_CIRCLE ) { - if( shape->Type() == SHAPE_TYPE::SH_SEGMENT - || shape->Type() == SHAPE_TYPE::SH_CIRCLE ) + VECTOR2I start, end; + int width = 0; + + if( shape->Type() == SHAPE_TYPE::SH_SEGMENT ) { - VECTOR2I start, end; - int width = 0; + SHAPE_SEGMENT* sh_seg = static_cast( shape ); - if( shape->Type() == SHAPE_TYPE::SH_SEGMENT ) - { - SHAPE_SEGMENT* sh_seg = static_cast( shape ); + start = sh_seg->GetSeg().A; + end = sh_seg->GetSeg().B; + width = sh_seg->GetWidth(); + } + else if( shape->Type() == SHAPE_TYPE::SH_CIRCLE ) + { + SHAPE_CIRCLE* sh_circ = static_cast( shape ); - start = sh_seg->GetSeg().A; - end = sh_seg->GetSeg().B; - width = sh_seg->GetWidth(); - } - else if( shape->Type() == SHAPE_TYPE::SH_CIRCLE ) - { - SHAPE_CIRCLE* sh_circ = static_cast( shape ); + start = end = sh_circ->GetCenter(); + width = sh_circ->GetRadius() * 2; + } - start = end = sh_circ->GetCenter(); - width = sh_circ->GetRadius() * 2; - } + TopoDS_Shape topods_shape; - TopoDS_Shape topods_shape; - - if( MakeShapeAsThickSegment( topods_shape, start, end, width, m_copperThickness, - Zpos, aOrigin ) ) - { - topodsShapes.emplace_back( topods_shape ); - } - else - { - success = false; - } + if( MakeShapeAsThickSegment( topods_shape, start, end, width, thickness, Zpos, + aOrigin ) ) + { + topodsShapes.emplace_back( topods_shape ); } else { - SHAPE_POLY_SET polySet; - shape->TransformToPolygon( polySet, ARC_HIGH_DEF, ERROR_INSIDE ); - - success &= MakeShapes( topodsShapes, polySet, m_copperThickness, Zpos, aOrigin ); + success = false; } } - - // Fuse shapes - if( topodsShapes.size() == 1 ) - { - m_board_copper_pads.emplace_back( topodsShapes.front() ); - } else { - BRepAlgoAPI_Fuse mkFuse; - TopTools_ListOfShape shapeArguments, shapeTools; + SHAPE_POLY_SET polySet; + shape->TransformToPolygon( polySet, ARC_HIGH_DEF, ERROR_INSIDE ); - for( TopoDS_Shape& sh : topodsShapes ) - { - if( sh.IsNull() ) - continue; - - if( shapeArguments.IsEmpty() ) - shapeArguments.Append( sh ); - else - shapeTools.Append( sh ); - } - - mkFuse.SetRunParallel( true ); - mkFuse.SetToFillHistory( false ); - mkFuse.SetArguments( shapeArguments ); - mkFuse.SetTools( shapeTools ); - mkFuse.Build(); - - if( mkFuse.IsDone() ) - { - TopoDS_Shape fusedShape = mkFuse.Shape(); - - ShapeUpgrade_UnifySameDomain unify( fusedShape, false, true, false ); - unify.History() = nullptr; - unify.Build(); - - TopoDS_Shape unifiedShapes = unify.Shape(); - - if( !unifiedShapes.IsNull() ) - { - m_board_copper_pads.emplace_back( unifiedShapes ); - } - else - { - ReportMessage( - _( "** ShapeUpgrade_UnifySameDomain produced a null shape **\n" ) ); - m_board_copper_pads.emplace_back( fusedShape ); - } - } - else - { - for( TopoDS_Shape& sh : topodsShapes ) - m_board_copper_pads.emplace_back( sh ); - } + success &= MakeShapes( topodsShapes, polySet, thickness, Zpos, aOrigin ); } } - if( pcb_layer == B_Cu ) - break; + // Fuse shapes + if( topodsShapes.size() == 1 ) + { + m_board_copper_pads.emplace_back( topodsShapes.front() ); + } + else + { + BRepAlgoAPI_Fuse mkFuse; + TopTools_ListOfShape shapeArguments, shapeTools; + + for( TopoDS_Shape& sh : topodsShapes ) + { + if( sh.IsNull() ) + continue; + + if( shapeArguments.IsEmpty() ) + shapeArguments.Append( sh ); + else + shapeTools.Append( sh ); + } + + mkFuse.SetRunParallel( true ); + mkFuse.SetToFillHistory( false ); + mkFuse.SetArguments( shapeArguments ); + mkFuse.SetTools( shapeTools ); + mkFuse.Build(); + + if( mkFuse.IsDone() ) + { + TopoDS_Shape fusedShape = mkFuse.Shape(); + + ShapeUpgrade_UnifySameDomain unify( fusedShape, false, true, false ); + unify.History() = nullptr; + unify.Build(); + + TopoDS_Shape unifiedShapes = unify.Shape(); + + if( !unifiedShapes.IsNull() ) + { + m_board_copper_pads.emplace_back( unifiedShapes ); + } + else + { + ReportMessage( + _( "** ShapeUpgrade_UnifySameDomain produced a null shape **\n" ) ); + m_board_copper_pads.emplace_back( fusedShape ); + } + } + else + { + for( TopoDS_Shape& sh : topodsShapes ) + m_board_copper_pads.emplace_back( sh ); + } + } } if( aPad->GetAttribute() == PAD_ATTRIB::PTH && aPad->IsOnLayer( F_Cu ) && aPad->IsOnLayer( B_Cu ) ) { + double f_pos, f_thickness; + double b_pos, b_thickness; + getLayerZPlacement( F_Cu, f_pos, f_thickness ); + getLayerZPlacement( B_Cu, b_pos, b_thickness ); + double top = std::max( f_pos, f_pos + f_thickness ); + double bottom = std::min( b_pos, b_pos + b_thickness ); + TopoDS_Shape plating; std::shared_ptr seg_hole = aPad->GetEffectiveHoleShape(); double width = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y ); if( MakeShapeAsThickSegment( plating, seg_hole->GetSeg().A, seg_hole->GetSeg().B, width, - m_boardThickness + m_copperThickness * 2, -m_copperThickness, - aOrigin ) ) + ( top - bottom ), bottom, aOrigin ) ) { m_board_copper_pads.push_back( plating ); } @@ -394,15 +399,16 @@ bool STEP_PCB_MODEL::AddTrackSegment( const PCB_TRACK* aTrack, const VECTOR2D& a { PCB_LAYER_ID pcblayer = aTrack->GetLayer(); - if( pcblayer != F_Cu && pcblayer != B_Cu ) - return false; + if( !m_enabledLayers.Contains( pcblayer ) ) + return true; TopoDS_Shape shape; - double zposition = pcblayer == F_Cu ? m_boardThickness : -m_copperThickness; + + double zposition, thickness; + getLayerZPlacement( pcblayer, zposition, thickness ); bool success = MakeShapeAsThickSegment( shape, aTrack->GetStart(), aTrack->GetEnd(), - aTrack->GetWidth(), m_copperThickness, - zposition, aOrigin ); + aTrack->GetWidth(), thickness, zposition, aOrigin ); if( success ) m_board_copper_tracks.push_back( shape ); @@ -411,7 +417,65 @@ bool STEP_PCB_MODEL::AddTrackSegment( const PCB_TRACK* aTrack, const VECTOR2D& a } -bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop, +void STEP_PCB_MODEL::getLayerZPlacement( const PCB_LAYER_ID aLayer, double& aZPos, + double& aThickness ) +{ + int z = 0; + int thickness = 0; + bool wasPrepreg = false; + + const std::vector& materials = m_stackup.GetList(); + + for( auto it = materials.rbegin(); it != materials.rend(); ++it ) + { + const BOARD_STACKUP_ITEM* item = *it; + + if( item->GetType() == BS_ITEM_TYPE_COPPER ) + { + // Inner copper position is usually inside prepreg + if( ( wasPrepreg || aLayer == B_Cu ) && aLayer != F_Cu ) + thickness = -item->GetThickness(); + else + thickness = item->GetThickness(); + + if( item->GetBrdLayerId() == aLayer ) + break; + + z += thickness; + } + else if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) + { + wasPrepreg = ( item->GetTypeName() == KEY_PREPREG ); + + // Dielectric can have sub-layers. Layer 0 is the main layer + // Not frequent, but possible + for( int idx = 0; idx < item->GetSublayersCount(); idx++ ) + z += item->GetThickness( idx ); + } + } + + aZPos = pcbIUScale.IUTomm( z ); + aThickness = pcbIUScale.IUTomm( thickness ); +} + + +void STEP_PCB_MODEL::getBoardBodyZPlacement( double& aZPos, double& aThickness ) +{ + double f_pos, f_thickness; + double b_pos, b_thickness; + getLayerZPlacement( F_Cu, f_pos, f_thickness ); + getLayerZPlacement( B_Cu, b_pos, b_thickness ); + double top = std::min( f_pos, f_pos + f_thickness ); + double bottom = std::max( b_pos, b_pos + b_thickness ); + + aThickness = ( top - bottom ); + aZPos = bottom; + + wxASSERT( aZPos == 0.0 ); +} + + +bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, PCB_LAYER_ID aLayer, const VECTOR2D& aOrigin, bool aTrack ) { bool success = true; @@ -419,14 +483,18 @@ bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, if( aPolyShapes->IsEmpty() ) return true; - double z_pos = aOnTop ? m_boardThickness : -m_copperThickness; + if( !m_enabledLayers.Contains( aLayer ) ) + return true; - if( !MakeShapes( aTrack ? m_board_copper_tracks : m_board_copper_zones, *aPolyShapes, - m_copperThickness, z_pos, aOrigin ) ) + double z_pos, thickness; + getLayerZPlacement( aLayer, z_pos, thickness ); + + if( !MakeShapes( aTrack ? m_board_copper_tracks : m_board_copper_zones, *aPolyShapes, thickness, + z_pos, aOrigin ) ) { - ReportMessage( wxString::Format( - wxT( "Could not add shape (%d points) to copper layer on %s.\n" ), - aPolyShapes->FullPointCount(), aOnTop ? wxT( "top" ) : wxT( "bottom" ) ) ); + ReportMessage( + wxString::Format( wxT( "Could not add shape (%d points) to copper layer on %s.\n" ), + aPolyShapes->FullPointCount(), LayerName( aLayer ) ) ); success = false; } @@ -446,7 +514,15 @@ bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ) const double margin = 0.01; // a small margin on the Z axix to be sure the hole // is bigger than the board with copper // must be > OCC_MAX_DISTANCE_TO_MERGE_POINTS - double holeZsize = m_boardThickness + ( m_copperThickness * 2 ) + ( margin * 2 ); + + double f_pos, f_thickness; + double b_pos, b_thickness; + getLayerZPlacement( F_Cu, f_pos, f_thickness ); + getLayerZPlacement( B_Cu, b_pos, b_thickness ); + double top = std::max( f_pos, f_pos + f_thickness ); + double bottom = std::min( b_pos, b_pos + b_thickness ); + + double holeZsize = ( top - bottom ) + ( margin * 2 ); std::shared_ptr seg_hole = aPad->GetEffectiveHoleShape(); @@ -456,7 +532,7 @@ bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ) TopoDS_Shape copperHole, boardHole; if( MakeShapeAsThickSegment( copperHole, seg_hole->GetSeg().A, seg_hole->GetSeg().B, - copperDrill, holeZsize, -m_copperThickness - margin, aOrigin ) ) + copperDrill, holeZsize, bottom - margin, aOrigin ) ) { m_copperCutouts.push_back( copperHole ); } @@ -466,7 +542,7 @@ bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin ) } if( MakeShapeAsThickSegment( boardHole, seg_hole->GetSeg().A, seg_hole->GetSeg().B, boardDrill, - holeZsize, -m_copperThickness - margin, aOrigin ) ) + holeZsize, bottom - margin, aOrigin ) ) { m_boardCutouts.push_back( boardHole ); } @@ -534,14 +610,9 @@ bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std:: } -void STEP_PCB_MODEL::SetPCBThickness( double aThickness ) +void STEP_PCB_MODEL::SetEnabledLayers( const LSET& aLayers ) { - if( aThickness < 0.0 ) - m_boardThickness = BOARD_THICKNESS_DEFAULT_MM; - else if( aThickness < BOARD_THICKNESS_MIN_MM ) - m_boardThickness = BOARD_THICKNESS_MIN_MM; - else - m_boardThickness = aThickness; + m_enabledLayers = aLayers; } @@ -551,6 +622,12 @@ void STEP_PCB_MODEL::SetFuseShapes( bool aValue ) } +void STEP_PCB_MODEL::SetStackup( const BOARD_STACKUP& aStackup ) +{ + m_stackup = aStackup; +} + + void STEP_PCB_MODEL::SetBoardColor( double r, double g, double b ) { m_boardColor[0] = r; @@ -1001,7 +1078,7 @@ bool STEP_PCB_MODEL::MakeShapes( std::vector& aShapes, const SHAPE } -bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) +bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin, bool aPushBoardBody ) { if( m_hasPCB ) { @@ -1014,10 +1091,14 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() ); m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked - // Support for more than one main outline (more than one board) ReportMessage( wxString::Format( wxT( "Build board outlines (%d outlines) with %d points.\n" ), aOutline.OutlineCount(), aOutline.FullPointCount() ) ); + + double boardThickness; + double boardZPos; + getBoardBodyZPlacement( boardZPos, boardThickness ); + #if 0 // This code should work, and it is working most of time // However there are issues if the main outline is a circle with holes: @@ -1025,7 +1106,7 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) // see bug https://gitlab.com/kicad/code/kicad/-/issues/17446 // (Holes are missing from STEP export with circular PCB outline) // Hard to say if the bug is in our code or in OCC 7.7 - if( !MakeShapes( m_board_outlines, aOutline, m_boardThickness, 0.0, aOrigin ) ) + if( !MakeShapes( m_board_outlines, aOutline, boardThickness, boardZPos, aOrigin ) ) { // Error ReportMessage( wxString::Format( @@ -1043,18 +1124,19 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) if( contId == 0 ) // main Outline { - if( !MakeShapes( m_board_outlines, polyset, m_boardThickness, 0.0, aOrigin ) ) + if( !MakeShapes( m_board_outlines, polyset, boardThickness, boardZPos, aOrigin ) ) ReportMessage( wxT( "OCC error creating main outline.\n" ) ); } else // Hole inside the main outline { - if( !MakeShapes( m_boardCutouts, polyset, m_boardThickness, 0.0, aOrigin ) ) + if( !MakeShapes( m_boardCutouts, polyset, boardThickness, boardZPos, aOrigin ) ) ReportMessage( wxT( "OCC error creating hole in main outline.\n" ) ); } } } #endif + // Even if we've disabled board body export, we still need the shapes for bounding box calculations. Bnd_Box brdBndBox; for( const TopoDS_Shape& brdShape : m_board_outlines ) @@ -1312,7 +1394,8 @@ bool STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ) pushToAssembly( m_board_copper_pads, copper_color, "pad" ); pushToAssembly( m_board_copper_fused, copper_color, "copper" ); - pushToAssembly( m_board_outlines, board_color, "PCB" ); + if( aPushBoardBody ) + pushToAssembly( m_board_outlines, board_color, "PCB" ); #if( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 ) m_assy->UpdateAssemblies(); @@ -1719,10 +1802,17 @@ bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double // Offset board thickness aOffset.z += BOARD_OFFSET; + double boardThickness; + double boardZPos; + getBoardBodyZPlacement( boardZPos, boardThickness ); + double top = std::max( boardZPos, boardZPos + boardThickness ); + double bottom = std::min( boardZPos, boardZPos + boardThickness ); + gp_Trsf lRot; if( aBottom ) { + aOffset.z -= bottom; lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation ); lPos.Multiply( lRot ); lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI ); @@ -1730,7 +1820,7 @@ bool STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double } else { - aOffset.z += m_boardThickness; + aOffset.z += top; lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation ); lPos.Multiply( lRot ); } diff --git a/pcbnew/exporters/step/step_pcb_model.h b/pcbnew/exporters/step/step_pcb_model.h index 26657d69e0..6e7c90dab2 100644 --- a/pcbnew/exporters/step/step_pcb_model.h +++ b/pcbnew/exporters/step/step_pcb_model.h @@ -31,17 +31,14 @@ #include #include -#include -#include -#include -#include -#include +#include +#include #include -#include #include #include #include +#include /** * Default distance between points to treat them as separate ones (mm) @@ -69,6 +66,10 @@ static constexpr double ARC_TO_SEGMENT_MAX_ERROR_MM = 0.005; class PAD; +class TDocStd_Document; +class XCAFApp_Application; +class XCAFDoc_ShapeTool; + typedef std::pair< std::string, TDF_Label > MODEL_DATUM; typedef std::map< std::string, TDF_Label > MODEL_MAP; @@ -93,7 +94,7 @@ public: bool AddTrackSegment( const PCB_TRACK* aTrack, const VECTOR2D& aOrigin ); // add a set of polygons (must be in final position) on top or bottom of the board as copper - bool AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop, + bool AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, PCB_LAYER_ID aLayer, const VECTOR2D& aOrigin, bool aTrack ); // add a component at the given position and orientation @@ -104,15 +105,12 @@ public: void SetBoardColor( double r, double g, double b ); void SetCopperColor( double r, double g, double b ); - // set the thickness of the PCB (mm); the top of the PCB shall be at Z = aThickness - // aThickness < 0.0 == use default thickness - // aThickness <= THICKNESS_MIN == use THICKNESS_MIN - // aThickness > THICKNESS_MIN == use aThickness - void SetPCBThickness( double aThickness ); + void SetEnabledLayers( const LSET& aLayers ); - // enable fusing the geometry void SetFuseShapes( bool aValue ); + void SetStackup( const BOARD_STACKUP& aStackup ); + // Set the max distance (in mm) to consider 2 points have the same coordinates // and can be merged void OCCSetMergeMaxDistance( double aDistance = OCC_MAX_DISTANCE_TO_MERGE_POINTS ); @@ -120,7 +118,7 @@ public: void SetMaxError( int aMaxError ) { m_maxError = aMaxError; } // create the PCB model using the current outlines and drill holes - bool CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ); + bool CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin, bool aPushBoardBody ); /** * Convert a SHAPE_POLY_SET to TopoDS_Shape's (polygonal vertical prisms) @@ -196,12 +194,9 @@ private: */ bool isBoardOutlineValid(); - /** create one solid board using current outline and drill holes set - * @param aIdx is the main outline index - * @param aOutline is the set of outlines with holes - * @param aOrigin is the coordinate origin for 3 view - */ - bool createOneBoard( int aIdx, SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin ); + void getLayerZPlacement( const PCB_LAYER_ID aLayer, double& aZPos, double& aThickness ); + + void getBoardBodyZPlacement( double& aZPos, double& aThickness ); /** * Load a 3D model data. @@ -240,8 +235,8 @@ private: double m_angleprec; // angle numeric precision double m_boardColor[3]; // board body, RGB values double m_copperColor[3]; // copper, RGB values - double m_boardThickness; // PCB thickness, mm - double m_copperThickness; // copper thickness, mm + BOARD_STACKUP m_stackup; // board stackup + LSET m_enabledLayers; // a set of layers enabled for export double m_minx; // leftmost curve point double m_mergeOCCMaxDist; // minimum distance (mm) below which two diff --git a/pcbnew/pcbnew_jobs_handler.cpp b/pcbnew/pcbnew_jobs_handler.cpp index c428be3eae..6def196da2 100644 --- a/pcbnew/pcbnew_jobs_handler.cpp +++ b/pcbnew/pcbnew_jobs_handler.cpp @@ -190,8 +190,11 @@ int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob ) else { EXPORTER_STEP_PARAMS params; - params.m_exportTracks = aStepJob->m_exportTracks; + params.m_exportBoardBody = aStepJob->m_exportBoardBody; + params.m_exportComponents = aStepJob->m_exportComponents; + params.m_exportTracksVias = aStepJob->m_exportTracks; params.m_exportZones = aStepJob->m_exportZones; + params.m_exportInnerCopper = aStepJob->m_exportInnerCopper; params.m_fuseShapes = aStepJob->m_fuseShapes; params.m_includeUnspecified = aStepJob->m_includeUnspecified; params.m_includeDNP = aStepJob->m_includeDNP;