/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 Roberto Fernandez Bautista <@Qbort>
* Copyright (C) 2020 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 .
*/
/**
* @file cadstar_pcb_archive_loader.cpp
* @brief Loads a cpa file into a KiCad BOARD object
*/
#include
#include // KEY_COPPER, KEY_CORE, KEY_PREPREG
#include
#include
#include // DRAWSEGMENT
#include
#include
#include
#include
#include
#include
#include
#include // std::numeric_limits
void CADSTAR_PCB_ARCHIVE_LOADER::Load( ::BOARD* aBoard )
{
mBoard = aBoard;
Parse();
LONGPOINT designLimit = Assignments.Technology.DesignLimit;
//Note: can't use getKiCadPoint() due wxPoint being int - need long long to make the check
long long designSizeXkicad = (long long) designLimit.x * KiCadUnitMultiplier;
long long designSizeYkicad = (long long) designLimit.y * KiCadUnitMultiplier;
// Max size limited by the positive dimention of wxPoint
long long maxDesignSizekicad = (long long) std::numeric_limits::max();
if( designSizeXkicad > maxDesignSizekicad || designSizeYkicad > maxDesignSizekicad )
THROW_IO_ERROR( wxString::Format(
_( "The design is too large and cannot be imported into KiCad. \n"
"Please reduce the maximum design size in CADSTAR by navigating to: \n"
"Design Tab -> Properties -> Design Options -> Maximum Design Size. \n"
"Current Design size: %.2f, %.2f milimetres. \n"
"Maximum permitted design size: %.2f, %.2f milimetres.\n" ),
(double) designSizeXkicad / 1E6, (double) designSizeYkicad / 1E6,
(double) maxDesignSizekicad / 1E6, (double) maxDesignSizekicad / 1E6 ) );
mDesignCenter =
( Assignments.Technology.DesignArea.first + Assignments.Technology.DesignArea.second )
/ 2;
if( Layout.NetSynch == NETSYNCH::WARNING )
wxLogWarning(
_( "The selected file indicates that nets might be out of synchronisation "
"with the schematic. It is recommended that you carry out an 'Align Nets' "
"procedure in CADSTAR and re-import, to avoid inconsistencies between the "
"PCB and the schematic. " ) );
loadBoardStackup();
loadComponentLibrary();
loadBoards();
loadFigures();
loadAreas();
loadComponents();
loadTemplates();
loadCoppers();
loadNets();
//TODO: process all other items
wxLogMessage(
_( "The CADSTAR design has been imported successfully.\n"
"Please review the import errors and warnings (if any)." ) );
}
void CADSTAR_PCB_ARCHIVE_LOADER::logBoardStackupWarning(
const wxString& aCadstarLayerName, const PCB_LAYER_ID& aKiCadLayer )
{
wxLogWarning( wxString::Format(
_( "The CADSTAR layer '%s' has no KiCad equivalent. All elements on this "
"layer have been mapped to KiCad layer '%s' instead." ),
aCadstarLayerName, LSET::Name( aKiCadLayer ) ) );
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadBoardStackup()
{
std::map& cpaLayers = Assignments.Layerdefs.Layers;
std::map& cpaMaterials = Assignments.Layerdefs.Materials;
std::vector& cpaLayerStack = Assignments.Layerdefs.LayerStack;
unsigned numElecAndPowerLayers = 0;
BOARD_DESIGN_SETTINGS& designSettings = mBoard->GetDesignSettings();
BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
int noOfKiCadStackupLayers = 0;
int lastElectricalLayerIndex = 0;
int dielectricSublayer = 0;
int numDielectricLayers = 0;
bool prevWasDielectric = false;
BOARD_STACKUP_ITEM* tempKiCadLayer;
std::vector layerIDs;
//Remove all layers except required ones
stackup.RemoveAll();
layerIDs.push_back( PCB_LAYER_ID::F_CrtYd );
layerIDs.push_back( PCB_LAYER_ID::B_CrtYd );
layerIDs.push_back( PCB_LAYER_ID::Margin );
layerIDs.push_back( PCB_LAYER_ID::Edge_Cuts );
designSettings.SetEnabledLayers( LSET( &layerIDs[0], layerIDs.size() ) );
for( auto it = cpaLayerStack.begin(); it != cpaLayerStack.end(); ++it )
{
LAYER curLayer = cpaLayers[*it];
BOARD_STACKUP_ITEM_TYPE kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_UNDEFINED;
LAYER_T copperType = LAYER_T::LT_UNDEFINED;
PCB_LAYER_ID kicadLayerID = PCB_LAYER_ID::UNDEFINED_LAYER;
wxString layerTypeName = wxEmptyString;
if( cpaLayers.count( *it ) == 0 )
wxASSERT_MSG( true, wxT( "Unable to find layer index" ) );
if( prevWasDielectric && ( curLayer.Type != LAYER_TYPE::CONSTRUCTION ) )
{
stackup.Add( tempKiCadLayer ); //only add dielectric layers here after all are done
dielectricSublayer = 0;
prevWasDielectric = false;
noOfKiCadStackupLayers++;
}
switch( curLayer.Type )
{
case LAYER_TYPE::ALLDOC:
case LAYER_TYPE::ALLELEC:
case LAYER_TYPE::ALLLAYER:
case LAYER_TYPE::ASSCOMPCOPP:
case LAYER_TYPE::NOLAYER:
//Shouldn't be here if CPA file is correctly parsed and not corrupt
THROW_IO_ERROR( wxString::Format(
_( "Unexpected layer '%s' in layer stack." ), curLayer.Name ) );
continue;
case LAYER_TYPE::JUMPERLAYER:
copperType = LAYER_T::LT_JUMPER;
kicadLayerID = getKiCadCopperLayerID( ++numElecAndPowerLayers );
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER;
layerTypeName = KEY_COPPER;
break;
case LAYER_TYPE::ELEC:
copperType = LAYER_T::LT_SIGNAL;
kicadLayerID = getKiCadCopperLayerID( ++numElecAndPowerLayers );
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER;
layerTypeName = KEY_COPPER;
break;
case LAYER_TYPE::POWER:
copperType = LAYER_T::LT_POWER;
kicadLayerID = getKiCadCopperLayerID( ++numElecAndPowerLayers );
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER;
layerTypeName = KEY_COPPER;
mPowerPlaneLayers.push_back( curLayer.ID ); //we will need to add a Copper zone
break;
case LAYER_TYPE::CONSTRUCTION:
kicadLayerID = PCB_LAYER_ID::UNDEFINED_LAYER;
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC;
prevWasDielectric = true;
layerTypeName = KEY_PREPREG;
//TODO handle KEY_CORE and KEY_PREPREG
//will need to look at CADSTAR layer embedding (see LAYER->Embedding) to
//check electrical layers above and below to decide if current layer is prepreg
// or core
break;
case LAYER_TYPE::DOC:
if( numElecAndPowerLayers > 0 )
kicadLayerID = PCB_LAYER_ID::Dwgs_User;
else
kicadLayerID = PCB_LAYER_ID::Cmts_User;
logBoardStackupWarning( curLayer.Name, kicadLayerID );
//TODO: allow user to decide which layer this should be mapped onto.
break;
case LAYER_TYPE::NONELEC:
switch( curLayer.SubType )
{
case LAYER_SUBTYPE::LAYERSUBTYPE_ASSEMBLY:
if( numElecAndPowerLayers > 0 )
kicadLayerID = PCB_LAYER_ID::B_Fab;
else
kicadLayerID = PCB_LAYER_ID::F_Fab;
break;
case LAYER_SUBTYPE::LAYERSUBTYPE_PLACEMENT:
if( numElecAndPowerLayers > 0 )
kicadLayerID = PCB_LAYER_ID::B_CrtYd;
else
kicadLayerID = PCB_LAYER_ID::F_CrtYd;
break;
case LAYER_SUBTYPE::LAYERSUBTYPE_NONE:
if( curLayer.Name.Lower().Contains( "glue" )
|| curLayer.Name.Lower().Contains( "adhesive" ) )
{
if( numElecAndPowerLayers > 0 )
kicadLayerID = PCB_LAYER_ID::B_Adhes;
else
kicadLayerID = PCB_LAYER_ID::F_Adhes;
wxLogMessage( wxString::Format(
_( "The CADSTAR layer '%s' has been assumed to be an adhesive layer. "
"All elements on this layer have been mapped to KiCad layer '%s'." ),
curLayer.Name, LSET::Name( kicadLayerID ) ) );
//TODO: allow user to decide if this is actually an adhesive layer or not.
}
else
{
if( numElecAndPowerLayers > 0 )
kicadLayerID = PCB_LAYER_ID::Eco2_User;
else
kicadLayerID = PCB_LAYER_ID::Eco1_User;
logBoardStackupWarning( curLayer.Name, kicadLayerID );
//TODO: allow user to decide which layer this should be mapped onto.
}
break;
case LAYER_SUBTYPE::LAYERSUBTYPE_PASTE:
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERPASTE;
if( numElecAndPowerLayers > 0 )
{
kicadLayerID = PCB_LAYER_ID::B_Paste;
layerTypeName = _HKI( "Bottom Solder Paste" );
}
else
{
kicadLayerID = PCB_LAYER_ID::F_Paste;
layerTypeName = _HKI( "Top Solder Paste" );
}
break;
case LAYER_SUBTYPE::LAYERSUBTYPE_SILKSCREEN:
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SILKSCREEN;
if( numElecAndPowerLayers > 0 )
{
kicadLayerID = PCB_LAYER_ID::B_SilkS;
layerTypeName = _HKI( "Bottom Silk Screen" );
}
else
{
kicadLayerID = PCB_LAYER_ID::F_SilkS;
layerTypeName = _HKI( "Top Silk Screen" );
}
break;
case LAYER_SUBTYPE::LAYERSUBTYPE_SOLDERRESIST:
kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERMASK;
if( numElecAndPowerLayers > 0 )
{
kicadLayerID = PCB_LAYER_ID::B_Mask;
layerTypeName = _HKI( "Bottom Solder Mask" );
}
else
{
kicadLayerID = PCB_LAYER_ID::F_Mask;
layerTypeName = _HKI( "Top Solder Mask" );
}
break;
default:
wxASSERT_MSG( true, wxT( "Unknown CADSTAR Layer Sub-type" ) );
break;
}
break;
default:
wxASSERT_MSG( true, wxT( "Unknown CADSTAR Layer Type" ) );
break;
}
mLayermap.insert( std::make_pair( curLayer.ID, kicadLayerID ) );
if( dielectricSublayer == 0 )
tempKiCadLayer = new BOARD_STACKUP_ITEM( kicadLayerType );
tempKiCadLayer->SetLayerName( curLayer.Name );
tempKiCadLayer->SetBrdLayerId( kicadLayerID );
if( prevWasDielectric )
{
wxASSERT_MSG( kicadLayerID == PCB_LAYER_ID::UNDEFINED_LAYER,
wxT( "Error Processing Dielectric Layer. "
"Expected to have undefined layer type" ) );
if( dielectricSublayer == 0 )
tempKiCadLayer->SetDielectricLayerId( ++numDielectricLayers );
else
tempKiCadLayer->AddDielectricPrms( dielectricSublayer );
}
if( curLayer.MaterialId != UNDEFINED_MATERIAL_ID )
{
tempKiCadLayer->SetMaterial(
cpaMaterials[curLayer.MaterialId].Name, dielectricSublayer );
tempKiCadLayer->SetEpsilonR( cpaMaterials[curLayer.MaterialId].Permittivity.GetDouble(),
dielectricSublayer );
tempKiCadLayer->SetLossTangent(
cpaMaterials[curLayer.MaterialId].LossTangent.GetDouble(), dielectricSublayer );
//TODO add Resistivity when KiCad supports it
}
tempKiCadLayer->SetThickness(
curLayer.Thickness * KiCadUnitMultiplier, dielectricSublayer );
if( layerTypeName != wxEmptyString )
tempKiCadLayer->SetTypeName( layerTypeName );
if( !prevWasDielectric )
{
stackup.Add( tempKiCadLayer ); //only add non-dielectric layers here
++noOfKiCadStackupLayers;
layerIDs.push_back( tempKiCadLayer->GetBrdLayerId() );
designSettings.SetEnabledLayers( LSET( &layerIDs[0], layerIDs.size() ) );
}
else
++dielectricSublayer;
if( copperType != LAYER_T::LT_UNDEFINED )
{
wxASSERT( mBoard->SetLayerType( tempKiCadLayer->GetBrdLayerId(),
copperType ) ); //move to outside, need to enable layer in board first
lastElectricalLayerIndex = noOfKiCadStackupLayers - 1;
wxASSERT( mBoard->SetLayerName(
tempKiCadLayer->GetBrdLayerId(), tempKiCadLayer->GetLayerName() ) );
//TODO set layer names for other CADSTAR layers when KiCad supports custom
//layer names on non-copper layers
mCopperLayers.insert( std::make_pair( curLayer.PhysicalLayer, curLayer.ID ) );
}
}
//change last copper layer to be B_Cu instead of an inner layer
LAYER_ID cadstarlastElecLayer = mCopperLayers.rbegin()->second;
PCB_LAYER_ID lastElecBrdId =
stackup.GetStackupLayer( lastElectricalLayerIndex )->GetBrdLayerId();
std::remove( layerIDs.begin(), layerIDs.end(), lastElecBrdId );
layerIDs.push_back( PCB_LAYER_ID::B_Cu );
tempKiCadLayer = stackup.GetStackupLayer( lastElectricalLayerIndex );
tempKiCadLayer->SetBrdLayerId( PCB_LAYER_ID::B_Cu );
wxASSERT( mBoard->SetLayerName(
tempKiCadLayer->GetBrdLayerId(), tempKiCadLayer->GetLayerName() ) );
mLayermap.at( cadstarlastElecLayer ) = PCB_LAYER_ID::B_Cu;
//make all layers enabled and visible
mBoard->SetEnabledLayers( LSET( &layerIDs[0], layerIDs.size() ) );
mBoard->SetVisibleLayers( LSET( &layerIDs[0], layerIDs.size() ) );
mBoard->SetCopperLayerCount( numElecAndPowerLayers );
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadComponentLibrary()
{
for( std::pair symPair : Library.ComponentDefinitions )
{
SYMDEF_ID key = symPair.first;
SYMDEF component = symPair.second;
wxString moduleName = component.ReferenceName
+ ( ( component.Alternate.size() > 0 ) ?
( wxT( " (" ) + component.Alternate + wxT( ")" ) ) :
wxT( "" ) );
MODULE* m = new MODULE( mBoard );
m->SetPosition( getKiCadPoint( component.Origin ) );
LIB_ID libID;
libID.Parse( moduleName, LIB_ID::LIB_ID_TYPE::ID_PCB, true );
m->SetFPID( libID );
loadLibraryFigures( component, m );
loadLibraryPads( component, m );
mLibraryMap.insert( std::make_pair( key, m ) );
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryFigures( const SYMDEF& aComponent, MODULE* aModule )
{
for( std::pair figPair : aComponent.Figures )
{
FIGURE& fig = figPair.second;
drawCadstarShape( fig.Shape, getKiCadLayer( fig.LayerID ), fig.LineCodeID,
wxString::Format( "Component %s:%s -> Figure %s", aComponent.ReferenceName,
aComponent.Alternate, fig.ID ),
aModule );
//TODO process addition to a group
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryPads( const SYMDEF& aComponent, MODULE* aModule )
{
for( std::pair padPair : aComponent.Pads )
{
PAD& csPad = padPair.second; //Cadstar pad
PADCODE csPadcode = getPadCode( csPad.PadCodeID );
D_PAD* pad = new D_PAD( aModule );
aModule->Add( pad, ADD_MODE::INSERT ); // insert so that we get correct behaviour when finding pads
// in the module by PAD_ID - see loadNets()
switch( csPad.Side )
{
case PAD_SIDE::MAXIMUM: //Bottom side
pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD );
pad->SetLayerSet( LSET( 3, B_Cu, B_Paste, B_Mask ) );
break;
case PAD_SIDE::MINIMUM: //TOP side
pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD );
pad->SetLayerSet( LSET( 3, F_Cu, F_Paste, F_Mask ) );
break;
case PAD_SIDE::THROUGH_HOLE:
if( csPadcode.Plated )
pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_STANDARD );
else
pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_HOLE_NOT_PLATED );
pad->SetLayerSet( pad->StandardMask() ); // for now we will assume no paste layers
//TODO: We need to read the csPad->Reassigns vector to make sure no paste
break;
default:
wxASSERT_MSG( true, "Unknown Pad type" );
}
pad->SetName( csPad.Identifier.IsEmpty() ? wxString::Format( wxT( "%i" ), csPad.ID ) :
csPad.Identifier );
pad->SetPos0( getKiCadPoint( csPad.Position ) - aModule->GetPosition() );
pad->SetOrientation( getAngleTenthDegree( csPad.OrientAngle ) );
switch( csPadcode.Shape.ShapeType )
{
case PAD_SHAPE_TYPE::ANNULUS:
//todo fix: use custom shape instead
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CIRCLE );
pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::BULLET:
//todo fix: use custom shape instead (a bullet has the left size flat and right
//side rounded, before rotation is applied)
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_OVAL );
pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size
+ (long long) csPadcode.Shape.LeftLength
+ (long long) csPadcode.Shape.RightLength ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::CIRCLE:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CIRCLE );
pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::DIAMOND:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT );
pad->SetOrientation( pad->GetOrientation() + 450.0 ); // rotate 45deg anticlockwise
pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::FINGER:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_OVAL );
pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size
+ (long long) csPadcode.Shape.LeftLength
+ (long long) csPadcode.Shape.RightLength ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::OCTAGON:
pad->SetShape( PAD_SHAPE_CHAMFERED_RECT );
pad->SetChamferPositions( RECT_CHAMFER_POSITIONS::RECT_CHAMFER_ALL );
pad->SetChamferRectRatio( 0.25 );
pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::RECTANGLE:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT );
pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size
+ (long long) csPadcode.Shape.LeftLength
+ (long long) csPadcode.Shape.RightLength ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::ROUNDED_RECT:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT );
pad->SetRoundRectCornerRadius( getKiCadLength( csPadcode.Shape.InternalFeature ) );
pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size
+ (long long) csPadcode.Shape.LeftLength
+ (long long) csPadcode.Shape.RightLength ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
case PAD_SHAPE_TYPE::SQUARE:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT );
pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ),
getKiCadLength( csPadcode.Shape.Size ) } );
break;
default:
wxASSERT_MSG( true, "Unknown Pad Shape" );
}
if( csPadcode.ReliefClearance != UNDEFINED_VALUE )
pad->SetThermalGap( getKiCadLength( csPadcode.ReliefClearance ) );
if( csPadcode.ReliefWidth != UNDEFINED_VALUE )
pad->SetThermalWidth( getKiCadLength( csPadcode.ReliefWidth ) );
pad->SetOrientation( pad->GetOrientation() + getAngleTenthDegree( csPadcode.Shape.OrientAngle ) );
if( csPadcode.DrillDiameter != UNDEFINED_VALUE )
{
if( csPadcode.SlotLength != UNDEFINED_VALUE )
pad->SetDrillSize( { getKiCadLength( csPadcode.DrillDiameter ),
getKiCadLength( (long long) csPadcode.DrillOversize
+ (long long) csPadcode.DrillDiameter ) } );
else
pad->SetDrillSize( { getKiCadLength( csPadcode.DrillDiameter ),
getKiCadLength( csPadcode.DrillDiameter ) } );
}
//TODO handle csPadcode.Reassigns when KiCad supports full padstacks
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadBoards()
{
for( std::pair boardPair : Layout.Boards )
{
BOARD& board = boardPair.second;
drawCadstarShape( board.Shape, PCB_LAYER_ID::Edge_Cuts, board.LineCodeID,
wxString::Format( "BOARD %s", board.ID ), mBoard );
//TODO process board attributes
//TODO process addition to a group
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadFigures()
{
for( std::pair figPair : Layout.Figures )
{
FIGURE& fig = figPair.second;
drawCadstarShape( fig.Shape, getKiCadLayer( fig.LayerID ), fig.LineCodeID,
wxString::Format( "FIGURE %s", fig.ID ), mBoard );
//TODO process addition to a group
//TODO process "swaprule"
//TODO process re-use block when KiCad Supports it
//TODO process attributes when KiCad Supports attributes in figures
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadAreas()
{
for( std::pair areaPair : Layout.Areas )
{
AREA& area = areaPair.second;
if( area.NoVias || area.NoTracks || area.Keepout )
{
ZONE_CONTAINER* zone =
getZoneFromCadstarShape( area.Shape, getLineThickness( area.LineCodeID ) );
mBoard->Add( zone, ADD_MODE::APPEND );
zone->SetLayer( getKiCadLayer( area.LayerID ) );
zone->SetIsKeepout( true ); //import all CADSTAR areas as Keepout zones
zone->SetDoNotAllowPads( false ); //no CADSTAR equivalent
zone->SetZoneName( area.Name );
zone->SetDoNotAllowFootprints( area.Keepout );
zone->SetDoNotAllowTracks( area.NoTracks );
zone->SetDoNotAllowCopperPour( area.NoTracks );
zone->SetDoNotAllowVias( area.NoVias );
if( area.Placement || area.Routing )
wxLogWarning( wxString::Format(
_( "The CADSTAR area '%s' is defined as a placement and/or routing area "
"in CADSTAR, in addition to Keepout. Placement or Routing areas are "
"not supported in KiCad. Only the supported elements were imported." ),
area.Name ) );
}
else
{
wxLogError(
wxString::Format( _( "The CADSTAR area '%s' does not have a KiCad equivalent. "
"Pure Placement or Routing areas are not supported." ),
area.Name ) );
}
//todo Process area.AreaHeight when KiCad supports 3D design rules
//TODO process attributes
//TODO process addition to a group
//TODO process "swaprule"
//TODO process re-use block
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadComponents()
{
for( std::pair compPair : Layout.Components )
{
COMPONENT& comp = compPair.second;
auto moduleIter = mLibraryMap.find( comp.SymdefID );
if( moduleIter == mLibraryMap.end() )
{
THROW_IO_ERROR( wxString::Format( _( "Unable to find component '%s' in the library"
"(Symdef ID: '%s')" ),
comp.Name, comp.SymdefID ) );
}
// copy constructor to clone the module from the library
MODULE* m = new MODULE( *moduleIter->second );
const_cast( m->m_Uuid ) = KIID();
mBoard->Add( m, ADD_MODE::APPEND );
//set to empty string to avoid duplication when loading attributes:
m->SetValue( wxEmptyString );
m->SetPosition( getKiCadPoint( comp.Origin ) );
m->SetOrientation( getAngleTenthDegree( comp.OrientAngle ) );
m->SetReference( comp.Name );
if( comp.Mirror )
{
m->Flip( getKiCadPoint( comp.Origin ), false );
m->SetOrientation( m->GetOrientation() + 1800.0 );
}
loadComponentAttributes( comp, m );
m->SetDescription( getPart( comp.PartID ).Definition.Name );
mComponentMap.insert( { comp.ID, m } );
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadTemplates()
{
for( std::pair tempPair : Layout.Templates )
{
TEMPLATE& csTemplate = tempPair.second;
ZONE_CONTAINER* zone = getZoneFromCadstarShape(
csTemplate.Shape, getLineThickness( csTemplate.LineCodeID ) );
mBoard->Add( zone, ADD_MODE::APPEND );
zone->SetZoneName( csTemplate.Name );
zone->SetLayer( getKiCadLayer( csTemplate.LayerID ) );
if( !csTemplate.NetID.IsEmpty() )
zone->SetNet( getKiCadNet( csTemplate.NetID ) );
if( csTemplate.Pouring.AllowInNoRouting )
wxLogError( wxString::Format(
_( "The CADSTAR template '%s' has the setting 'Allow in No Routing Areas' "
"enabled. This setting has no KiCad equivalent, so it has been ignored." ),
csTemplate.Name ) );
if( csTemplate.Pouring.BoxIsolatedPins )
wxLogError( wxString::Format(
_( "The CADSTAR template '%s' has the setting 'Box Isolated Pins'"
"enabled. This setting has no KiCad equivalent, so it has been ignored." ),
csTemplate.Name ) );
if( csTemplate.Pouring.AutomaticRepour )
wxLogWarning( wxString::Format(
_( "The CADSTAR template '%s' has the setting 'Automatic Repour'"
"enabled. This setting has no KiCad equivalent, so it has been ignored." ),
csTemplate.Name ) );
// Sliver width has different behaviour to KiCad Zone's minimum thickness
// In Cadstar 'Sliver width' has to be greater than the Copper thickness, whereas in
// Kicad it is the opposite.
if( csTemplate.Pouring.SliverWidth != 0 )
wxLogError( wxString::Format(
_( "The CADSTAR template '%s' has a non-zero value defined for the "
"'Sliver Width' setting. There is no KiCad equivalent for "
"this, so this setting was ignored." ),
csTemplate.Name ) );
if( csTemplate.Pouring.MinIsolatedCopper != csTemplate.Pouring.MinDisjointCopper )
wxLogError( wxString::Format(
_( "The CADSTAR template '%s' has different settings for 'Retain Poured Copper "
"- Disjoint' and 'Retain Poured Copper - Isolated'. KiCad does not "
"distinguish between these two settings. The setting for disjoint copper "
"has been applied as the minimum island area of the KiCad Zone." ),
csTemplate.Name ) );
if( csTemplate.Pouring.MinDisjointCopper < 0 )
zone->SetMinIslandArea( -1 );
else
zone->SetMinIslandArea(
(long long) getKiCadLength( csTemplate.Pouring.MinDisjointCopper )
* (long long) getKiCadLength( csTemplate.Pouring.MinDisjointCopper ) );
zone->SetZoneClearance( getKiCadLength( csTemplate.Pouring.AdditionalIsolation ) );
if( csTemplate.Pouring.FillType == TEMPLATE::POURING::COPPER_FILL_TYPE::HATCHED )
{
zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
zone->SetHatchGap( getKiCadHatchCodeGap( csTemplate.Pouring.HatchCodeID ) );
zone->SetHatchThickness( getKiCadHatchCodeThickness( csTemplate.Pouring.HatchCodeID ) );
zone->SetHatchOrientation( getHatchCodeAngleDegrees( csTemplate.Pouring.HatchCodeID ) );
}
else
{
zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
}
if( csTemplate.Pouring.ThermalReliefOnPads != csTemplate.Pouring.ThermalReliefOnVias
|| csTemplate.Pouring.ThermalReliefPadsAngle
!= csTemplate.Pouring.ThermalReliefViasAngle )
wxLogWarning( wxString::Format(
_( "The CADSTAR template '%s' has different settings for thermal relief "
"in pads and vias. KiCad only supports one single setting for both. The "
"setting for pads has been applied." ),
csTemplate.Name ) );
if( csTemplate.Pouring.ThermalReliefOnPads )
{
zone->SetThermalReliefGap( getKiCadLength( csTemplate.Pouring.ClearanceWidth ) );
zone->SetThermalReliefCopperBridge( getKiCadLength(
getCopperCode( csTemplate.Pouring.ReliefCopperCodeID ).CopperWidth ) );
zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
}
else
zone->SetPadConnection( ZONE_CONNECTION::FULL );
}
//Now create power plane layers:
for( LAYER_ID layer : mPowerPlaneLayers )
{
wxASSERT( Assignments.Layerdefs.Layers.find( layer ) != Assignments.Layerdefs.Layers.end() );
//The net name will equal the layer name
wxString powerPlaneLayerName = Assignments.Layerdefs.Layers.at( layer ).Name;
NET_ID netid = wxEmptyString;
for( std::pair netPair : Layout.Nets )
{
NET net = netPair.second;
if( net.Name == powerPlaneLayerName )
{
netid = net.ID;
break;
}
}
if( netid.IsEmpty() )
{
wxLogError( wxString::Format(
_( "The CADSTAR layer '%s' is defined as a power plane layer. However no "
"net with such name exists. The layer has been loaded but no copper zone "
"was created." ),
powerPlaneLayerName ) );
}
else
{
for( std::pair boardPair : Layout.Boards )
{
//create a zone in each board shape
BOARD& board = boardPair.second;
int defaultLineThicknesss =
mBoard->GetDesignSettings().GetLineThickness( PCB_LAYER_ID::Edge_Cuts );
ZONE_CONTAINER* zone =
getZoneFromCadstarShape( board.Shape, defaultLineThicknesss );
mBoard->Add( zone, ADD_MODE::APPEND );
zone->SetZoneName( powerPlaneLayerName );
zone->SetLayer( getKiCadLayer( layer ) );
zone->SetFillMode( ZONE_FILL_MODE::POLYGONS );
zone->SetPadConnection( ZONE_CONNECTION::FULL );
zone->SetMinIslandArea( -1 );
zone->SetNet( getKiCadNet( netid ) );
}
}
}
}
void CADSTAR_PCB_ARCHIVE_LOADER::loadCoppers()
{
for( std::pair copPair : Layout.Coppers )
{
COPPER& csCopper = copPair.second;
if( !csCopper.PouredTemplateID.IsEmpty() )
continue; //ignore copper related to a template as we've already loaded it!
// For now we are going to load coppers to a KiCad zone however this isn't perfect
//TODO: Load onto a graphical polygon with a net (when KiCad has this feature)
if( !mDoneCopperWarning )
{
wxLogWarning(
_( "The CADSTAR design contains COPPER elements, which have no direct KiCad "
"equivalent. These have been imported as a KiCad Zone if solid or hatch "
"filled, or as a KiCad Track if the shape was an unfilled outline (open or "
"closed)." ) );
mDoneCopperWarning = true;
}
if( csCopper.Shape.Type == SHAPE_TYPE::OPENSHAPE
|| csCopper.Shape.Type == SHAPE_TYPE::OUTLINE )
{
std::vector outlineSegments =
getDrawSegmentsFromVertices( csCopper.Shape.Vertices );
std::vector