553 lines
16 KiB
C++
553 lines
16 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2024 Jon Evans <jon@craftyjon.com>
|
|
* Copyright (C) 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
|
|
* 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 <convert_basic_shapes_to_polygon.h> // RECT_CHAMFER_POSITIONS
|
|
#include "padstack.h"
|
|
#include <api/api_enums.h>
|
|
#include <api/api_utils.h>
|
|
#include <api/api_pcb_utils.h>
|
|
#include <api/board/board_types.pb.h>
|
|
#include <pcb_shape.h>
|
|
|
|
|
|
PADSTACK::PADSTACK( BOARD_ITEM* aParent ) :
|
|
m_parent( aParent ),
|
|
m_mode( MODE::NORMAL ),
|
|
m_orientation( ANGLE_0 ),
|
|
m_unconnectedLayerMode( UNCONNECTED_LAYER_MODE::KEEP_ALL ),
|
|
m_customShapeInZoneMode( CUSTOM_SHAPE_ZONE_MODE::OUTLINE )
|
|
{
|
|
m_defaultCopperProps.shape = SHAPE_PROPS();
|
|
m_defaultCopperProps.zone_connection = ZONE_CONNECTION::INHERITED;
|
|
m_defaultCopperProps.thermal_spoke_width = std::nullopt;
|
|
m_defaultCopperProps.thermal_spoke_angle = ANGLE_45;
|
|
m_defaultCopperProps.thermal_gap = std::nullopt;
|
|
|
|
m_drill.shape = PAD_DRILL_SHAPE::CIRCLE;
|
|
m_drill.start = F_Cu;
|
|
m_drill.end = B_Cu;
|
|
|
|
m_secondaryDrill.start = UNDEFINED_LAYER;
|
|
m_secondaryDrill.end = UNDEFINED_LAYER;
|
|
}
|
|
|
|
|
|
PADSTACK::PADSTACK( const PADSTACK& aOther )
|
|
{
|
|
*this = aOther;
|
|
}
|
|
|
|
|
|
PADSTACK& PADSTACK::operator=( const PADSTACK &aOther )
|
|
{
|
|
m_mode = aOther.m_mode;
|
|
m_layerSet = aOther.m_layerSet;
|
|
m_customName = aOther.m_customName;
|
|
m_defaultCopperProps = aOther.m_defaultCopperProps;
|
|
m_frontMaskProps = aOther.m_frontMaskProps;
|
|
m_backMaskProps = aOther.m_backMaskProps;
|
|
m_unconnectedLayerMode = aOther.m_unconnectedLayerMode;
|
|
m_copperOverrides = aOther.m_copperOverrides;
|
|
m_drill = aOther.m_drill;
|
|
m_secondaryDrill = aOther.m_secondaryDrill;
|
|
return *this;
|
|
}
|
|
|
|
|
|
bool PADSTACK::operator==( const PADSTACK& aOther ) const
|
|
{
|
|
return m_mode == aOther.m_mode
|
|
&& m_layerSet == aOther.m_layerSet
|
|
&& m_customName == aOther.m_customName
|
|
&& m_defaultCopperProps == aOther.m_defaultCopperProps
|
|
&& m_frontMaskProps == aOther.m_frontMaskProps
|
|
&& m_backMaskProps == aOther.m_backMaskProps
|
|
&& m_unconnectedLayerMode == aOther.m_unconnectedLayerMode
|
|
&& m_copperOverrides == aOther.m_copperOverrides
|
|
&& m_drill == aOther.m_drill
|
|
&& m_secondaryDrill == aOther.m_secondaryDrill;
|
|
}
|
|
|
|
|
|
bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer )
|
|
{
|
|
using namespace kiapi::board::types;
|
|
PadStack padstack;
|
|
|
|
if( !aContainer.UnpackTo( &padstack ) )
|
|
return false;
|
|
|
|
m_mode = FromProtoEnum<MODE>( padstack.type() );
|
|
|
|
// TODO
|
|
m_layerSet.reset();
|
|
|
|
m_orientation = EDA_ANGLE( padstack.angle().value_degrees(), DEGREES_T );
|
|
|
|
Drill().size = kiapi::common::UnpackVector2( padstack.drill_diameter() );
|
|
Drill().start = FromProtoEnum<PCB_LAYER_ID>( padstack.start_layer() );
|
|
Drill().end = FromProtoEnum<PCB_LAYER_ID>( padstack.end_layer() );
|
|
|
|
// We don't yet support complex padstacks
|
|
if( padstack.layers_size() == 1 )
|
|
{
|
|
const PadStackLayer& layer = padstack.layers( 0 );
|
|
Size() = kiapi::common::UnpackVector2( layer.size() );
|
|
SetLayerSet( kiapi::board::UnpackLayerSet( layer.layers() ) );
|
|
SetShape( FromProtoEnum<PAD_SHAPE>( layer.shape() ) );
|
|
SetAnchorShape( FromProtoEnum<PAD_SHAPE>( layer.custom_anchor_shape() ) );
|
|
|
|
SHAPE_PROPS& props = CopperLayerDefaults().shape;
|
|
props.chamfered_rect_ratio = layer.chamfer_ratio();
|
|
props.round_rect_radius_ratio = layer.corner_rounding_ratio();
|
|
|
|
if( layer.chamfered_corners().top_left() )
|
|
props.chamfered_rect_positions |= RECT_CHAMFER_TOP_LEFT;
|
|
|
|
if( layer.chamfered_corners().top_right() )
|
|
props.chamfered_rect_positions |= RECT_CHAMFER_TOP_RIGHT;
|
|
|
|
if( layer.chamfered_corners().bottom_left() )
|
|
props.chamfered_rect_positions |= RECT_CHAMFER_BOTTOM_LEFT;
|
|
|
|
if( layer.chamfered_corners().bottom_right() )
|
|
props.chamfered_rect_positions |= RECT_CHAMFER_BOTTOM_RIGHT;
|
|
|
|
ClearPrimitives();
|
|
google::protobuf::Any a;
|
|
|
|
for( const GraphicShape& shapeProto : layer.custom_shapes() )
|
|
{
|
|
a.PackFrom( shapeProto );
|
|
std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_parent );
|
|
|
|
if( shape->Deserialize( a ) )
|
|
AddPrimitive( shape.release() );
|
|
}
|
|
}
|
|
|
|
SetUnconnectedLayerMode(
|
|
FromProtoEnum<UNCONNECTED_LAYER_MODE>( padstack.unconnected_layer_removal() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const
|
|
{
|
|
using namespace kiapi::board::types;
|
|
PadStack padstack;
|
|
|
|
padstack.set_type( ToProtoEnum<MODE, PadStackType>( m_mode ) );
|
|
padstack.set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( StartLayer() ) );
|
|
padstack.set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( EndLayer() ) );
|
|
kiapi::common::PackVector2( *padstack.mutable_drill_diameter(), Drill().size );
|
|
padstack.mutable_angle()->set_value_degrees( m_orientation.AsDegrees() );
|
|
|
|
PadStackLayer* stackLayer = padstack.add_layers();
|
|
kiapi::board::PackLayerSet( *stackLayer->mutable_layers(), LayerSet() );
|
|
kiapi::common::PackVector2( *stackLayer->mutable_size(), Size() );
|
|
stackLayer->set_shape( ToProtoEnum<PAD_SHAPE, PadStackShape>( Shape() ) );
|
|
stackLayer->set_custom_anchor_shape( ToProtoEnum<PAD_SHAPE, PadStackShape>( AnchorShape() ) );
|
|
stackLayer->set_chamfer_ratio( CopperLayerDefaults().shape.chamfered_rect_ratio );
|
|
stackLayer->set_corner_rounding_ratio( CopperLayerDefaults().shape.round_rect_radius_ratio );
|
|
|
|
google::protobuf::Any a;
|
|
|
|
for( const std::shared_ptr<PCB_SHAPE>& shape : Primitives() )
|
|
{
|
|
shape->Serialize( a );
|
|
GraphicShape* s = stackLayer->add_custom_shapes();
|
|
a.UnpackTo( s );
|
|
}
|
|
|
|
const int& corners = CopperLayerDefaults().shape.chamfered_rect_positions;
|
|
stackLayer->mutable_chamfered_corners()->set_top_left( corners & RECT_CHAMFER_TOP_LEFT );
|
|
stackLayer->mutable_chamfered_corners()->set_top_right( corners & RECT_CHAMFER_TOP_RIGHT );
|
|
stackLayer->mutable_chamfered_corners()->set_bottom_left( corners & RECT_CHAMFER_BOTTOM_LEFT );
|
|
stackLayer->mutable_chamfered_corners()->set_bottom_right( corners & RECT_CHAMFER_BOTTOM_RIGHT );
|
|
|
|
padstack.set_unconnected_layer_removal(
|
|
ToProtoEnum<UNCONNECTED_LAYER_MODE, UnconnectedLayerRemoval>( m_unconnectedLayerMode ) );
|
|
|
|
aContainer.PackFrom( padstack );
|
|
}
|
|
|
|
|
|
wxString PADSTACK::Name() const
|
|
{
|
|
// TODO
|
|
return wxEmptyString;
|
|
}
|
|
|
|
|
|
PCB_LAYER_ID PADSTACK::StartLayer() const
|
|
{
|
|
return m_drill.start;
|
|
}
|
|
|
|
|
|
PCB_LAYER_ID PADSTACK::EndLayer() const
|
|
{
|
|
return m_drill.end;
|
|
}
|
|
|
|
|
|
PADSTACK::SHAPE_PROPS::SHAPE_PROPS() :
|
|
shape( PAD_SHAPE::CIRCLE ),
|
|
anchor_shape( PAD_SHAPE::CIRCLE ),
|
|
round_rect_corner_radius( 0 ),
|
|
round_rect_radius_ratio( 0.25 ),
|
|
chamfered_rect_ratio( 0.2 ),
|
|
chamfered_rect_positions( RECT_NO_CHAMFER )
|
|
{
|
|
}
|
|
|
|
|
|
bool PADSTACK::SHAPE_PROPS::operator==( const SHAPE_PROPS& aOther ) const
|
|
{
|
|
return shape == aOther.shape && offset == aOther.offset
|
|
&& round_rect_corner_radius == aOther.round_rect_corner_radius
|
|
&& round_rect_radius_ratio == aOther.round_rect_radius_ratio
|
|
&& chamfered_rect_ratio == aOther.chamfered_rect_ratio
|
|
&& chamfered_rect_positions == aOther.chamfered_rect_positions;
|
|
}
|
|
|
|
|
|
bool PADSTACK::COPPER_LAYER_PROPS::operator==( const COPPER_LAYER_PROPS& aOther ) const
|
|
{
|
|
return shape == aOther.shape && zone_connection == aOther.zone_connection
|
|
&& thermal_spoke_width == aOther.thermal_spoke_width
|
|
&& thermal_spoke_angle == aOther.thermal_spoke_angle
|
|
&& thermal_gap == aOther.thermal_gap
|
|
&& custom_shapes == aOther.custom_shapes;
|
|
}
|
|
|
|
|
|
bool PADSTACK::MASK_LAYER_PROPS::operator==( const MASK_LAYER_PROPS& aOther ) const
|
|
{
|
|
return solder_mask_margin == aOther.solder_mask_margin
|
|
&& solder_paste_margin == aOther.solder_paste_margin
|
|
&& solder_paste_margin_ratio == aOther.solder_paste_margin_ratio
|
|
&& has_solder_mask == aOther.has_solder_mask
|
|
&& has_solder_paste == aOther.has_solder_paste;
|
|
}
|
|
|
|
|
|
bool PADSTACK::DRILL_PROPS::operator==( const DRILL_PROPS& aOther ) const
|
|
{
|
|
return size == aOther.size && shape == aOther.shape
|
|
&& start == aOther.start && end == aOther.end;
|
|
}
|
|
|
|
|
|
PAD_SHAPE PADSTACK::Shape( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.shape;
|
|
}
|
|
|
|
|
|
void PADSTACK::SetShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().shape.shape = aShape;
|
|
}
|
|
|
|
|
|
VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().shape.size;
|
|
}
|
|
|
|
|
|
const VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.size;
|
|
}
|
|
|
|
|
|
PAD_DRILL_SHAPE PADSTACK::DrillShape( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return m_drill.shape;
|
|
}
|
|
|
|
|
|
void PADSTACK::SetDrillShape( PAD_DRILL_SHAPE aShape, PCB_LAYER_ID aLayer )
|
|
{
|
|
m_drill.shape = aShape;
|
|
}
|
|
|
|
|
|
VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().shape.offset;
|
|
}
|
|
|
|
|
|
const VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.offset;
|
|
}
|
|
|
|
|
|
PAD_SHAPE PADSTACK::AnchorShape( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.anchor_shape;
|
|
}
|
|
|
|
|
|
void PADSTACK::SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().shape.anchor_shape = aShape;
|
|
}
|
|
|
|
|
|
VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().shape.trapezoid_delta_size;
|
|
}
|
|
|
|
|
|
const VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.trapezoid_delta_size;
|
|
}
|
|
|
|
|
|
double PADSTACK::RoundRectRadiusRatio( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.round_rect_radius_ratio;
|
|
}
|
|
|
|
|
|
void PADSTACK::SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().shape.round_rect_radius_ratio = aRatio;
|
|
}
|
|
|
|
|
|
int PADSTACK::RoundRectRadius( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
const VECTOR2I& size = Size( aLayer );
|
|
return KiROUND( std::min( size.x, size.y ) * RoundRectRadiusRatio( aLayer ) );
|
|
}
|
|
|
|
|
|
void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer )
|
|
{
|
|
const VECTOR2I& size = Size( aLayer );
|
|
int min_r = std::min( size.x, size.y );
|
|
|
|
if( min_r > 0 )
|
|
SetRoundRectRadiusRatio( aRadius / min_r, aLayer );
|
|
}
|
|
|
|
|
|
double PADSTACK::ChamferRatio( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.chamfered_rect_ratio;
|
|
}
|
|
|
|
|
|
void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().shape.chamfered_rect_ratio = aRatio;
|
|
}
|
|
|
|
|
|
int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().shape.chamfered_rect_positions;
|
|
}
|
|
|
|
|
|
const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().shape.chamfered_rect_positions;
|
|
}
|
|
|
|
|
|
void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().shape.chamfered_rect_positions = aPositions;
|
|
}
|
|
|
|
|
|
std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().clearance;
|
|
}
|
|
|
|
|
|
const std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().clearance;
|
|
}
|
|
|
|
|
|
std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer )
|
|
{
|
|
return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_mask_margin
|
|
: m_backMaskProps.solder_mask_margin;
|
|
}
|
|
|
|
|
|
const std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_mask_margin
|
|
: m_backMaskProps.solder_mask_margin;
|
|
}
|
|
|
|
|
|
std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer )
|
|
{
|
|
return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin
|
|
: m_backMaskProps.solder_paste_margin;
|
|
}
|
|
|
|
|
|
const std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin
|
|
: m_backMaskProps.solder_paste_margin;}
|
|
|
|
|
|
std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer )
|
|
{
|
|
return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin_ratio
|
|
: m_backMaskProps.solder_paste_margin_ratio;
|
|
}
|
|
|
|
|
|
const std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin_ratio
|
|
: m_backMaskProps.solder_paste_margin_ratio;}
|
|
|
|
|
|
std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().zone_connection;
|
|
}
|
|
|
|
|
|
const std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().zone_connection;
|
|
}
|
|
|
|
|
|
std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().thermal_spoke_width;
|
|
}
|
|
|
|
|
|
const std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().thermal_spoke_width;
|
|
}
|
|
|
|
|
|
std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().thermal_gap;
|
|
}
|
|
|
|
|
|
const std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().thermal_gap;
|
|
}
|
|
|
|
|
|
EDA_ANGLE PADSTACK::ThermalSpokeAngle( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
const COPPER_LAYER_PROPS& defaults = CopperLayerDefaults();
|
|
|
|
return defaults.thermal_spoke_angle.value_or(
|
|
( defaults.shape.shape == PAD_SHAPE::CIRCLE
|
|
|| ( defaults.shape.shape == PAD_SHAPE::CUSTOM
|
|
&& defaults.shape.anchor_shape == PAD_SHAPE::CIRCLE ) )
|
|
? ANGLE_45 : ANGLE_90 );
|
|
}
|
|
|
|
|
|
void PADSTACK::SetThermalSpokeAngle( EDA_ANGLE aAngle, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().thermal_spoke_angle = aAngle;
|
|
}
|
|
|
|
|
|
std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer )
|
|
{
|
|
return CopperLayerDefaults().custom_shapes;
|
|
}
|
|
|
|
|
|
const std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) const
|
|
{
|
|
return CopperLayerDefaults().custom_shapes;
|
|
}
|
|
|
|
|
|
void PADSTACK::AddPrimitive( PCB_SHAPE* aShape, PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().custom_shapes.emplace_back( aShape );
|
|
}
|
|
|
|
|
|
void PADSTACK::AppendPrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aPrimitivesList,
|
|
PCB_LAYER_ID aLayer )
|
|
{
|
|
for( const std::shared_ptr<PCB_SHAPE>& prim : aPrimitivesList )
|
|
AddPrimitive( new PCB_SHAPE( *prim ) );
|
|
}
|
|
|
|
|
|
void PADSTACK::ReplacePrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aPrimitivesList,
|
|
PCB_LAYER_ID aLayer )
|
|
{
|
|
ClearPrimitives( aLayer );
|
|
|
|
if( aPrimitivesList.size() )
|
|
AppendPrimitives( aPrimitivesList, aLayer );
|
|
}
|
|
|
|
|
|
void PADSTACK::ClearPrimitives( PCB_LAYER_ID aLayer )
|
|
{
|
|
CopperLayerDefaults().custom_shapes.clear();
|
|
}
|
|
|
|
|
|
std::optional<bool> PADSTACK::IsTented( PCB_LAYER_ID aSide ) const
|
|
{
|
|
if( IsFrontLayer( aSide ) )
|
|
return !m_frontMaskProps.has_solder_mask;
|
|
|
|
if( IsBackLayer( aSide ) )
|
|
return !m_backMaskProps.has_solder_mask;
|
|
|
|
wxCHECK_MSG( false, std::nullopt, "IsTented expects a front or back layer" );
|
|
}
|
|
|
|
|
|
IMPLEMENT_ENUM_TO_WXANY( PADSTACK::UNCONNECTED_LAYER_MODE )
|