mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2024-11-25 09:15:01 +00:00
56e0811516
CHANGED: PCB file format now supports saving/loading complex padstacks CHANGED: PTH pads are now rendered per copper layer in the copper color; the PTH pad color is no longer used. ADDED: support for importing complex pad stacks from Altium PCBs Enforce padstack-aware access to pad properties across KiCad Fixes https://gitlab.com/kicad/code/kicad/-/issues/8182
286 lines
9.3 KiB
C++
286 lines
9.3 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* Author: SYSUEric <jzzhuang666@gmail.com>.
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include <confirm.h>
|
|
#include <gestfich.h>
|
|
#include <kiface_base.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <trigo.h>
|
|
#include <build_version.h>
|
|
#include <macros.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <locale_io.h>
|
|
#include <board.h>
|
|
#include <board_design_settings.h>
|
|
#include <footprint.h>
|
|
#include <pad.h>
|
|
#include <pcb_track.h>
|
|
#include <vector>
|
|
#include <cctype>
|
|
#include <odb_netlist.h>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/log.h>
|
|
#include "odb_util.h"
|
|
|
|
|
|
// Compute the side code for a pad. Returns "" if there is no copper
|
|
std::string ODB_NET_LIST::ComputePadAccessSide( BOARD* aBoard, LSET aLayerMask )
|
|
{
|
|
// Non-copper is not interesting here
|
|
aLayerMask &= LSET::AllCuMask();
|
|
if( !aLayerMask.any() )
|
|
return "";
|
|
|
|
// Traditional TH pad
|
|
if( aLayerMask[F_Cu] && aLayerMask[B_Cu] )
|
|
return "B";
|
|
|
|
// Front SMD pad
|
|
if( aLayerMask[F_Cu] )
|
|
return "T";
|
|
|
|
// Back SMD pad
|
|
if( aLayerMask[B_Cu] )
|
|
return "D";
|
|
|
|
// Inner
|
|
for( int layer = In1_Cu; layer < B_Cu; ++layer )
|
|
{
|
|
if( aLayerMask[layer] )
|
|
return "I";
|
|
}
|
|
|
|
// This shouldn't happen
|
|
wxLogDebug( "Unhandled layer mask input when compute pad access side of ODB++ netlist file." );
|
|
return "";
|
|
}
|
|
|
|
|
|
void ODB_NET_LIST::InitPadNetPoints( BOARD* aBoard,
|
|
std::map<size_t, std::vector<ODB_NET_RECORD>>& aRecords )
|
|
{
|
|
VECTOR2I origin = aBoard->GetDesignSettings().GetAuxOrigin();
|
|
|
|
for( FOOTPRINT* footprint : aBoard->Footprints() )
|
|
{
|
|
for( PAD* pad : footprint->Pads() )
|
|
{
|
|
ODB_NET_RECORD net_point;
|
|
net_point.side = ComputePadAccessSide( aBoard, pad->GetLayerSet() );
|
|
|
|
// It could be a mask only pad, we only handle pads with copper here
|
|
if( !net_point.side.empty() && net_point.side != "I" )
|
|
{
|
|
if( pad->GetNetCode() == 0 )
|
|
net_point.netname = "$NONE$";
|
|
else
|
|
net_point.netname = pad->GetNetname();
|
|
// net_point.pin = pad->GetNumber();
|
|
net_point.refdes = footprint->GetReference();
|
|
const VECTOR2I& drill = pad->GetDrillSize();
|
|
net_point.hole = pad->HasHole();
|
|
|
|
if( !net_point.hole )
|
|
net_point.drill_radius = 0;
|
|
else
|
|
net_point.drill_radius = std::min( drill.x, drill.y );
|
|
|
|
net_point.smd = pad->GetAttribute() == PAD_ATTRIB::SMD
|
|
|| pad->GetAttribute() == PAD_ATTRIB::CONN;
|
|
net_point.is_via = false;
|
|
net_point.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH );
|
|
net_point.x_location = pad->GetPosition().x - origin.x;
|
|
net_point.y_location = origin.y - pad->GetPosition().y;
|
|
net_point.x_size = pad->GetSize( PADSTACK::ALL_LAYERS ).x;
|
|
|
|
// Rule: round pads have y = 0
|
|
if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE )
|
|
net_point.y_size = net_point.x_size;
|
|
else
|
|
net_point.y_size = pad->GetSize( PADSTACK::ALL_LAYERS ).y;
|
|
|
|
// net_point.rotation = ( ANGLE_360 - pad->GetOrientation() ).Normalize().AsDegrees();
|
|
|
|
// if( net_point.rotation < 0 )
|
|
// net_point.rotation += 360;
|
|
|
|
// always output NET end point as net test point
|
|
net_point.epoint = "e";
|
|
|
|
// the value indicates which sides are *not* accessible
|
|
net_point.soldermask = 3;
|
|
|
|
if( pad->GetLayerSet()[F_Mask] )
|
|
net_point.soldermask &= ~1;
|
|
|
|
if( pad->GetLayerSet()[B_Mask] )
|
|
net_point.soldermask &= ~2;
|
|
|
|
aRecords[pad->GetNetCode()].push_back( net_point );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the side code for a via.
|
|
std::string ODB_NET_LIST::ComputeViaAccessSide( BOARD* aBoard, int top_layer, int bottom_layer )
|
|
{
|
|
// Easy case for through vias: top_layer is component, bottom_layer is
|
|
// solder, side code is Both
|
|
if( ( top_layer == F_Cu ) && ( bottom_layer == B_Cu ) )
|
|
return "B";
|
|
|
|
// Blind via, reachable from front, Top
|
|
if( top_layer == F_Cu )
|
|
return "T";
|
|
|
|
// Blind via, reachable from bottom, Down
|
|
if( bottom_layer == B_Cu )
|
|
return "D";
|
|
|
|
// It's a buried via, accessible from some inner layer, Inner
|
|
return "I";
|
|
}
|
|
|
|
|
|
void ODB_NET_LIST::InitViaNetPoints( BOARD* aBoard,
|
|
std::map<size_t, std::vector<ODB_NET_RECORD>>& aRecords )
|
|
{
|
|
VECTOR2I origin = aBoard->GetDesignSettings().GetAuxOrigin();
|
|
|
|
// Enumerate all the track segments and keep the vias
|
|
for( auto track : aBoard->Tracks() )
|
|
{
|
|
if( track->Type() == PCB_VIA_T )
|
|
{
|
|
PCB_VIA* via = static_cast<PCB_VIA*>( track );
|
|
PCB_LAYER_ID top_layer, bottom_layer;
|
|
|
|
via->LayerPair( &top_layer, &bottom_layer );
|
|
|
|
ODB_NET_RECORD net_point;
|
|
net_point.side = ComputeViaAccessSide( aBoard, top_layer, bottom_layer );
|
|
|
|
if( net_point.side != "I" )
|
|
{
|
|
NETINFO_ITEM* net = track->GetNet();
|
|
net_point.smd = false;
|
|
net_point.hole = true;
|
|
|
|
if( net->GetNetCode() == 0 )
|
|
net_point.netname = "$NONE$";
|
|
else
|
|
net_point.netname = net->GetNetname();
|
|
|
|
net_point.refdes = "VIA";
|
|
net_point.is_via = true;
|
|
net_point.drill_radius = via->GetDrillValue();
|
|
net_point.mechanical = false;
|
|
net_point.x_location = via->GetPosition().x - origin.x;
|
|
net_point.y_location = origin.y - via->GetPosition().y;
|
|
|
|
// via always has drill radius, Width and Height are 0
|
|
net_point.x_size = 0;
|
|
net_point.y_size = 0; // Round so height = 0
|
|
net_point.epoint = "e"; // only buried via is "m" net mid point
|
|
|
|
// the value indicates which sides are *not* accessible
|
|
net_point.soldermask = 3;
|
|
|
|
if( via->GetLayerSet()[F_Mask] )
|
|
net_point.soldermask &= ~1;
|
|
|
|
if( via->GetLayerSet()[B_Mask] )
|
|
net_point.soldermask &= ~2;
|
|
|
|
aRecords[net->GetNetCode()].push_back( net_point );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ODB_NET_LIST::WriteNetPointRecords( std::map<size_t, std::vector<ODB_NET_RECORD>>& aRecords,
|
|
std::ostream& aStream )
|
|
{
|
|
aStream << "H optimize n staggered n" << std::endl;
|
|
|
|
for( const auto& [key, vec] : aRecords )
|
|
{
|
|
aStream << "$" << key << " " << ODB::GenLegalNetName( vec.front().netname ) << std::endl;
|
|
}
|
|
|
|
aStream << "#" << std::endl << "#Netlist points" << std::endl << "#" << std::endl;
|
|
|
|
for( const auto& [key, vec] : aRecords )
|
|
{
|
|
for( const auto& net_point : vec )
|
|
{
|
|
aStream << key << " ";
|
|
|
|
if( net_point.hole )
|
|
aStream << ODB::Data2String( net_point.drill_radius );
|
|
else
|
|
aStream << 0;
|
|
|
|
aStream << " " << ODB::Data2String( net_point.x_location ) << " "
|
|
<< ODB::Data2String( net_point.y_location ) << " " << net_point.side << " ";
|
|
|
|
if( !net_point.hole )
|
|
aStream << ODB::Data2String( net_point.x_size ) << " "
|
|
<< ODB::Data2String( net_point.y_size ) << " ";
|
|
|
|
std::string exp;
|
|
|
|
if( net_point.soldermask == 3 )
|
|
exp = "c";
|
|
else if( net_point.soldermask == 2 )
|
|
exp = "s";
|
|
else if( net_point.soldermask == 1 )
|
|
exp = "p";
|
|
else if( net_point.soldermask == 0 )
|
|
exp = "e";
|
|
|
|
aStream << net_point.epoint << " " << exp;
|
|
|
|
if( net_point.hole )
|
|
aStream << " staggered 0 0 0";
|
|
|
|
if( net_point.is_via )
|
|
aStream << " v";
|
|
|
|
aStream << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ODB_NET_LIST::Write( std::ostream& aStream )
|
|
{
|
|
std::map<size_t, std::vector<ODB_NET_RECORD>> net_point_records;
|
|
|
|
InitViaNetPoints( m_board, net_point_records );
|
|
|
|
InitPadNetPoints( m_board, net_point_records );
|
|
|
|
WriteNetPointRecords( net_point_records, aStream );
|
|
}
|