kicad/pcbnew/plugins/altium/altium_pcb.cpp

2476 lines
86 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "altium_pcb.h"
#include "altium_parser_pcb.h"
#include "plugins/altium/altium_parser.h"
#include <class_board.h>
#include <class_dimension.h>
#include <class_drawsegment.h>
#include <class_pcb_text.h>
#include <class_track.h>
#include <class_edge_mod.h>
#include <class_text_mod.h>
#include <board_stackup_manager/stackup_predefined_prms.h>
#include <compoundfilereader.h>
#include <convert_basic_shapes_to_polygon.h>
#include <project.h>
#include <trigo.h>
#include <utf.h>
#include <wx/docview.h>
#include <wx/mstream.h>
#include <wx/wfstream.h>
#include <wx/zstream.h>
void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName,
const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
{
// Open file
FILE* fp = wxFopen( aFileName, "rb" );
if( fp == nullptr )
{
wxLogError( wxString::Format( _( "Cannot open file '%s'" ), aFileName ) );
return;
}
fseek( fp, 0, SEEK_END );
long len = ftell( fp );
if( len < 0 )
{
fclose( fp );
THROW_IO_ERROR( "Reading error, cannot determine length of file" );
}
std::unique_ptr<unsigned char[]> buffer( new unsigned char[len] );
fseek( fp, 0, SEEK_SET );
size_t bytesRead = fread( buffer.get(), sizeof( unsigned char ), len, fp );
fclose( fp );
if( static_cast<size_t>( len ) != bytesRead )
{
THROW_IO_ERROR( "Reading error" );
}
try
{
CFB::CompoundFileReader reader( buffer.get(), bytesRead );
// Parse File
ALTIUM_PCB pcb( aBoard );
pcb.Parse( reader, aFileMapping );
}
catch( CFB::CFBException& exception )
{
THROW_IO_ERROR( exception.what() );
}
}
bool IsAltiumLayerCopper( ALTIUM_LAYER aLayer )
{
return aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER;
}
bool IsAltiumLayerAPlane( ALTIUM_LAYER aLayer )
{
return aLayer >= ALTIUM_LAYER::INTERNAL_PLANE_1 && aLayer <= ALTIUM_LAYER::INTERNAL_PLANE_16;
}
DRAWSEGMENT* ALTIUM_PCB::HelperCreateAndAddDrawsegment( uint16_t aComponent )
{
if( aComponent == ALTIUM_COMPONENT_NONE )
{
DRAWSEGMENT* ds = new DRAWSEGMENT( m_board );
m_board->Add( ds, ADD_MODE::APPEND );
return ds;
}
else
{
if( m_components.size() <= aComponent )
{
THROW_IO_ERROR( wxString::Format(
"Component creator tries to access component id %d of %d existing components",
aComponent, m_components.size() ) );
}
MODULE* module = m_components.at( aComponent );
DRAWSEGMENT* ds = new EDGE_MODULE( module );
module->Add( ds, ADD_MODE::APPEND );
return ds;
}
}
void ALTIUM_PCB::HelperDrawsegmentSetLocalCoord( DRAWSEGMENT* aDs, uint16_t aComponent )
{
if( aComponent != ALTIUM_COMPONENT_NONE )
{
auto em = dynamic_cast<EDGE_MODULE*>( aDs );
if( em )
em->SetLocalCoord();
}
}
PCB_LAYER_ID ALTIUM_PCB::GetKicadLayer( ALTIUM_LAYER aAltiumLayer ) const
{
auto override = m_layermap.find( aAltiumLayer );
if( override != m_layermap.end() )
{
return override->second;
}
switch( aAltiumLayer )
{
case ALTIUM_LAYER::UNKNOWN:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::TOP_LAYER:
return F_Cu;
case ALTIUM_LAYER::MID_LAYER_1:
return In1_Cu; // TODO: stackup same as in KiCad?
case ALTIUM_LAYER::MID_LAYER_2:
return In2_Cu;
case ALTIUM_LAYER::MID_LAYER_3:
return In3_Cu;
case ALTIUM_LAYER::MID_LAYER_4:
return In4_Cu;
case ALTIUM_LAYER::MID_LAYER_5:
return In5_Cu;
case ALTIUM_LAYER::MID_LAYER_6:
return In6_Cu;
case ALTIUM_LAYER::MID_LAYER_7:
return In7_Cu;
case ALTIUM_LAYER::MID_LAYER_8:
return In8_Cu;
case ALTIUM_LAYER::MID_LAYER_9:
return In9_Cu;
case ALTIUM_LAYER::MID_LAYER_10:
return In10_Cu;
case ALTIUM_LAYER::MID_LAYER_11:
return In11_Cu;
case ALTIUM_LAYER::MID_LAYER_12:
return In12_Cu;
case ALTIUM_LAYER::MID_LAYER_13:
return In13_Cu;
case ALTIUM_LAYER::MID_LAYER_14:
return In14_Cu;
case ALTIUM_LAYER::MID_LAYER_15:
return In15_Cu;
case ALTIUM_LAYER::MID_LAYER_16:
return In16_Cu;
case ALTIUM_LAYER::MID_LAYER_17:
return In17_Cu;
case ALTIUM_LAYER::MID_LAYER_18:
return In18_Cu;
case ALTIUM_LAYER::MID_LAYER_19:
return In19_Cu;
case ALTIUM_LAYER::MID_LAYER_20:
return In20_Cu;
case ALTIUM_LAYER::MID_LAYER_21:
return In21_Cu;
case ALTIUM_LAYER::MID_LAYER_22:
return In22_Cu;
case ALTIUM_LAYER::MID_LAYER_23:
return In23_Cu;
case ALTIUM_LAYER::MID_LAYER_24:
return In24_Cu;
case ALTIUM_LAYER::MID_LAYER_25:
return In25_Cu;
case ALTIUM_LAYER::MID_LAYER_26:
return In26_Cu;
case ALTIUM_LAYER::MID_LAYER_27:
return In27_Cu;
case ALTIUM_LAYER::MID_LAYER_28:
return In28_Cu;
case ALTIUM_LAYER::MID_LAYER_29:
return In29_Cu;
case ALTIUM_LAYER::MID_LAYER_30:
return In30_Cu;
case ALTIUM_LAYER::BOTTOM_LAYER:
return B_Cu;
case ALTIUM_LAYER::TOP_OVERLAY:
return F_SilkS;
case ALTIUM_LAYER::BOTTOM_OVERLAY:
return B_SilkS;
case ALTIUM_LAYER::TOP_PASTE:
return F_Paste;
case ALTIUM_LAYER::BOTTOM_PASTE:
return B_Paste;
case ALTIUM_LAYER::TOP_SOLDER:
return F_Mask;
case ALTIUM_LAYER::BOTTOM_SOLDER:
return B_Mask;
case ALTIUM_LAYER::INTERNAL_PLANE_1:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_2:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_3:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_4:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_5:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_6:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_7:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_8:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_9:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_10:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_11:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_12:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_13:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_14:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_15:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::INTERNAL_PLANE_16:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::DRILL_GUIDE:
return Dwgs_User;
case ALTIUM_LAYER::KEEP_OUT_LAYER:
return Margin;
case ALTIUM_LAYER::MECHANICAL_1:
return Dwgs_User; //Edge_Cuts;
case ALTIUM_LAYER::MECHANICAL_2:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_3:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_4:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_5:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_6:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_7:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_8:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_9:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_10:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_11:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_12:
return Dwgs_User;
case ALTIUM_LAYER::MECHANICAL_13:
return F_Fab;
case ALTIUM_LAYER::MECHANICAL_14:
return B_Fab;
case ALTIUM_LAYER::MECHANICAL_15:
return F_CrtYd;
case ALTIUM_LAYER::MECHANICAL_16:
return B_CrtYd;
case ALTIUM_LAYER::DRILL_DRAWING:
return Dwgs_User;
case ALTIUM_LAYER::MULTI_LAYER:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::CONNECTIONS:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::BACKGROUND:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::DRC_ERROR_MARKERS:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::SELECTIONS:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::VISIBLE_GRID_1:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::VISIBLE_GRID_2:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::PAD_HOLES:
return UNDEFINED_LAYER;
case ALTIUM_LAYER::VIA_HOLES:
return UNDEFINED_LAYER;
default:
return UNDEFINED_LAYER;
}
}
ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard )
{
m_board = aBoard;
m_num_nets = 0;
m_highest_pour_index = 0;
}
ALTIUM_PCB::~ALTIUM_PCB()
{
}
void ALTIUM_PCB::Parse( const CFB::CompoundFileReader& aReader,
const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
{
// this vector simply declares in which order which functions to call.
const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
{ true, ALTIUM_PCB_DIR::FILE_HEADER,
[this]( auto aReader, auto fileHeader ) {
this->ParseFileHeader( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::BOARD6,
[this]( auto aReader, auto fileHeader ) {
this->ParseBoard6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::COMPONENTS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseComponents6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::MODELS,
[this, aFileMapping]( auto aReader, auto fileHeader ) {
wxString dir( aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) );
dir.RemoveLast( 4 ); // Remove "Data" from the path
this->ParseModelsData( aReader, fileHeader, dir );
} },
{ true, ALTIUM_PCB_DIR::COMPONENTBODIES6,
[this]( auto aReader, auto fileHeader ) {
this->ParseComponentsBodies6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::NETS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseNets6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::CLASSES6,
[this]( auto aReader, auto fileHeader ) {
this->ParseClasses6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::RULES6,
[this]( auto aReader, auto fileHeader ) {
this->ParseRules6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::DIMENSIONS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseDimensions6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::POLYGONS6,
[this]( auto aReader, auto fileHeader ) {
this->ParsePolygons6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::ARCS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseArcs6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::PADS6,
[this]( auto aReader, auto fileHeader ) {
this->ParsePads6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::VIAS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseVias6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::TRACKS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseTracks6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::TEXTS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseTexts6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::FILLS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseFills6Data( aReader, fileHeader );
} },
{ false, ALTIUM_PCB_DIR::BOARDREGIONS,
[this]( auto aReader, auto fileHeader ) {
this->ParseBoardRegionsData( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseShapeBasedRegions6Data( aReader, fileHeader );
} },
{ true, ALTIUM_PCB_DIR::REGIONS6,
[this]( auto aReader, auto fileHeader ) {
this->ParseRegions6Data( aReader, fileHeader );
} }
};
// Parse data in specified order
for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
{
bool isRequired;
ALTIUM_PCB_DIR directory;
PARSE_FUNCTION_POINTER_fp fp;
std::tie( isRequired, directory, fp ) = cur;
const auto& mappedDirectory = aFileMapping.find( directory );
if( mappedDirectory == aFileMapping.end() )
{
wxASSERT_MSG( !isRequired,
wxString::Format(
"Altium Directory of kind %d was expected, but no mapping is present in the code",
directory ) );
continue;
}
const CFB::COMPOUND_FILE_ENTRY* file =
FindStream( aReader, mappedDirectory->second.c_str() );
if( file != nullptr )
{
fp( aReader, file );
}
else if( isRequired )
{
wxLogError( wxString::Format( _( "File not found: '%s'" ), mappedDirectory->second ) );
}
}
// fixup zone priorities since Altium stores them in the opposite order
for( auto& zone : m_polygons )
{
if( !zone )
continue;
// Altium "fills" - not poured in Altium
if( zone->GetPriority() == 1000 )
{
// Unlikely, but you never know
if( m_highest_pour_index >= 1000 )
zone->SetPriority( m_highest_pour_index + 1 );
continue;
}
int priority = m_highest_pour_index - zone->GetPriority();
zone->SetPriority( priority >= 0 ? priority : 0 );
}
// change priority of outer zone to zero
for( auto& zone : m_outer_plane )
{
zone.second->SetPriority( 0 );
}
// Finish Board by recalculating module boundingboxes
for( auto& module : m_board->Modules() )
{
module->CalculateBoundingBox();
}
// Otherwise we cannot save the imported board
m_board->SetModified();
}
int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
{
if( aId == ALTIUM_NET_UNCONNECTED )
{
return NETINFO_LIST::UNCONNECTED;
}
else if( m_num_nets < aId )
{
THROW_IO_ERROR( wxString::Format(
"Netcode with id %d does not exist. Only %d nets are known", aId, m_num_nets ) );
}
else
{
return aId + 1;
}
}
const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
{
const auto rules = m_rules.find( aKind );
if( rules == m_rules.end() )
{
return nullptr;
}
for( const ARULE6& rule : rules->second )
{
if( rule.name == aName )
{
return &rule;
}
}
return nullptr;
}
const ARULE6* ALTIUM_PCB::GetRuleDefault( ALTIUM_RULE_KIND aKind ) const
{
const auto rules = m_rules.find( aKind );
if( rules == m_rules.end() )
{
return nullptr;
}
for( const ARULE6& rule : rules->second )
{
if( rule.scope1expr == "All" && rule.scope2expr == "All" )
{
return &rule;
}
}
return nullptr;
}
void ALTIUM_PCB::ParseFileHeader(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
reader.ReadAndSetSubrecordLength();
wxString header = reader.ReadWxString();
//std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
//reader.SkipSubrecord();
// TODO: does not seem to work all the time at the moment
//if( reader.GetRemainingBytes() != 0 )
//{
// THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
//}
}
void ALTIUM_PCB::ParseBoard6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
ABOARD6 elem( reader );
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Board6 stream is not fully parsed" );
}
m_board->GetDesignSettings().m_AuxOrigin = elem.sheetpos;
m_board->GetDesignSettings().m_GridOrigin = elem.sheetpos;
// read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
size_t layercount = 0;
for( size_t i = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ )
;
size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
m_board->SetCopperLayerCount( kicadLayercount );
BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
// create board stackup
stackup.RemoveAll(); // Just to be sure
stackup.BuildDefaultStackupList( &designSettings, layercount );
auto it = stackup.GetList().begin();
// find first copper layer
for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
;
auto curLayer = static_cast<int>( F_Cu );
for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
{
// array starts with 0, but stackup with 1
ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
// handle unused layer in case of odd layercount
if( layer.nextId == 0 && layercount != kicadLayercount )
{
m_board->SetLayerName( ( *it )->GetBrdLayerId(), "[unused]" );
if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
{
THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
}
( *it )->SetThickness( 0 );
++it;
if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
{
THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
}
( *it )->SetThickness( 0, 0 );
( *it )->SetThicknessLocked( true, 0 );
++it;
}
m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
static_cast<PCB_LAYER_ID>( curLayer++ ) } );
if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
{
THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
}
( *it )->SetThickness( layer.copperthick );
ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
m_board->SetLayerName( klayer, layer.name );
if( layer.copperthick == 0 )
{
m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
}
else if( IsAltiumLayerAPlane( alayer ) )
{
m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
}
if( klayer == B_Cu )
{
if( layer.nextId != 0 )
{
THROW_IO_ERROR( "Board6 stream, unexpected id while parsing last stackup layer" );
}
// overwrite entry from internal -> bottom
m_layermap[alayer] = B_Cu;
break;
}
++it;
if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
{
THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
}
( *it )->SetThickness( layer.dielectricthick, 0 );
( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
NotSpecifiedPrm() :
wxString( layer.dielectricmaterial ) );
( *it )->SetEpsilonR( layer.dielectricconst, 0 );
++it;
}
HelperCreateBoardOutline( elem.board_vertices );
}
void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
{
if( !aVertices.empty() )
{
const ALTIUM_VERTICE* last = &aVertices.at( 0 );
for( size_t i = 0; i < aVertices.size(); i++ )
{
const ALTIUM_VERTICE* cur = &aVertices.at( ( i + 1 ) % aVertices.size() );
DRAWSEGMENT* ds = new DRAWSEGMENT( m_board );
m_board->Add( ds, ADD_MODE::APPEND );
ds->SetWidth( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
ds->SetLayer( Edge_Cuts );
if( !last->isRound && !cur->isRound )
{
ds->SetShape( STROKE_T::S_SEGMENT );
ds->SetStart( last->position );
ds->SetEnd( cur->position );
}
else if( cur->isRound )
{
ds->SetShape( STROKE_T::S_ARC );
ds->SetAngle( -NormalizeAngleDegreesPos( cur->endangle - cur->startangle ) * 10. );
double startradiant = DEG2RAD( cur->startangle );
wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * cur->radius ),
-KiROUND( std::sin( startradiant ) * cur->radius ) );
wxPoint arcStart = cur->center + arcStartOffset;
ds->SetCenter( cur->center );
ds->SetArcStart( arcStart );
if( !last->isRound )
{
double endradiant = DEG2RAD( cur->endangle );
wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * cur->radius ),
-KiROUND( std::sin( endradiant ) * cur->radius ) );
wxPoint arcEnd = cur->center + arcEndOffset;
DRAWSEGMENT* ds2 = new DRAWSEGMENT( m_board );
ds2->SetShape( STROKE_T::S_SEGMENT );
m_board->Add( ds2, ADD_MODE::APPEND );
ds2->SetWidth( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
ds2->SetLayer( Edge_Cuts );
ds2->SetStart( last->position );
// TODO: this is more of a hack than the real solution
double lineLengthStart = GetLineLength( last->position, arcStart );
double lineLengthEnd = GetLineLength( last->position, arcEnd );
if( lineLengthStart > lineLengthEnd )
{
ds2->SetEnd( cur->center + arcEndOffset );
}
else
{
ds2->SetEnd( cur->center + arcStartOffset );
}
}
}
last = cur;
}
}
}
void ALTIUM_PCB::ParseClasses6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ACLASS6 elem( reader );
if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
{
const NETCLASSPTR& netclass = std::make_shared<NETCLASS>( elem.name );
designSettings.GetNetClasses().Add( netclass );
for( const auto& name : elem.names )
{
netclass->Add(
name ); // TODO: it seems it can happen that we have names not attached to any net.
}
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Classes6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseComponents6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
uint16_t componentId = 0;
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ACOMPONENT6 elem( reader );
MODULE* module = new MODULE( m_board );
m_board->Add( module, ADD_MODE::APPEND );
m_components.emplace_back( module );
wxString pack_ref = elem.sourcelibreference;
wxString lib_ref = elem.sourcefootprintlibrary; // TODO: remove ".PcbLib" part
ReplaceIllegalFileNameChars( lib_ref, '_' );
ReplaceIllegalFileNameChars( pack_ref, '_' );
wxString key = !lib_ref.empty() ? lib_ref + ":" + pack_ref : pack_ref;
LIB_ID fpID;
fpID.Parse( key, LIB_ID::ID_PCB, true );
module->SetFPID( fpID );
module->SetPosition( elem.position );
module->SetOrientationDegrees( elem.rotation );
// KiCad netlisting requires parts to have non-digit + digit annotation.
// If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
wxString reference = elem.sourcedesignator;
if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
reference.Prepend( "UNK" );
module->SetReference( reference );
module->SetLocked( elem.locked );
module->Reference().SetVisible( elem.nameon );
module->Value().SetVisible( elem.commenton );
module->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
componentId++;
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Components6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseComponentsBodies6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ACOMPONENTBODY6 elem( reader ); // TODO: implement
if( elem.component == ALTIUM_COMPONENT_NONE )
{
continue; // TODO: we do not support components for the board yet
}
if( m_components.size() <= elem.component )
{
THROW_IO_ERROR( wxString::Format(
"ComponentsBodies6 stream tries to access component id %d of %d existing components",
elem.component, m_components.size() ) );
}
if( !elem.modelIsEmbedded )
{
continue;
}
auto modelTuple = m_models.find( elem.modelId );
if( modelTuple == m_models.end() )
{
THROW_IO_ERROR( wxString::Format(
"ComponentsBodies6 stream tries to access model id %s which does not exist",
elem.modelId ) );
}
MODULE* module = m_components.at( elem.component );
const wxPoint& modulePosition = module->GetPosition();
MODULE_3D_SETTINGS modelSettings;
modelSettings.m_Filename = modelTuple->second;
modelSettings.m_Offset.x = Iu2Millimeter( (int) elem.modelPosition.x - modulePosition.x );
modelSettings.m_Offset.y = -Iu2Millimeter( (int) elem.modelPosition.y - modulePosition.y );
modelSettings.m_Offset.z = Iu2Millimeter( (int) elem.modelPosition.z );
double orientation = module->GetOrientation();
if( module->IsFlipped() )
{
modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
orientation = -orientation;
}
RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
modelSettings.m_Rotation.x = NormalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
modelSettings.m_Rotation.y = NormalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
modelSettings.m_Rotation.z = NormalizeAngleDegrees(
-elem.modelRotation.z + elem.rotation + orientation / 10, -180, 180 );
modelSettings.m_Opacity = elem.bodyOpacity;
module->Models().push_back( modelSettings );
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "ComponentsBodies6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::HelperParseDimensions6Linear( const ADIMENSION6& aElem )
{
if( aElem.referencePoint.size() != 2 )
{
THROW_IO_ERROR( "Incorrect number of reference points for linear dimension object" );
}
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
aElem.layer ) );
klayer = Eco1_User;
}
wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
ALIGNED_DIMENSION* dimension = new ALIGNED_DIMENSION( m_board );
m_board->Add( dimension, ADD_MODE::APPEND );
dimension->SetPrecision( aElem.textprecission );
dimension->SetLayer( klayer );
dimension->SetStart( referencePoint0 );
if( referencePoint0 != aElem.xy1 )
{
/**
* Basically REFERENCE0POINT and REFERENCE1POINT are the two end points of the dimension.
* XY1 is the position of the arrow above REFERENCE0POINT. those three points are not necesarily
* in 90degree angle, but KiCad requires this to show the correct measurements.
*
* Therefore, we take the vector of REFERENCE0POINT -> XY1, calculate the normal, and intersect it with
* REFERENCE1POINT pointing the same direction as REFERENCE0POINT -> XY1. This should give us a valid
* measurement point where we can place the drawsegment.
*/
wxPoint direction = aElem.xy1 - referencePoint0;
wxPoint directionNormalVector = wxPoint( -direction.y, direction.x );
SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
SEG segm2( referencePoint1, referencePoint1 + direction );
wxPoint intersection( segm1.Intersect( segm2, true, true ).get() );
dimension->SetEnd( intersection );
int height = static_cast<int>( EuclideanNorm( direction ) );
if( direction.x <= 0 && direction.y <= 0 ) // TODO: I suspect this is not always correct
{
height = -height;
}
dimension->SetHeight( height );
}
else
{
dimension->SetEnd( referencePoint1 );
}
dimension->SetLineThickness( aElem.linewidth );
dimension->Text().SetTextThickness( aElem.textlinewidth );
dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
dimension->Text().SetBold( aElem.textbold );
dimension->Text().SetItalic( aElem.textitalic );
switch( aElem.textunit )
{
case ALTIUM_UNIT::INCHES:
dimension->SetUnits( EDA_UNITS::INCHES, false );
break;
case ALTIUM_UNIT::MILS:
dimension->SetUnits( EDA_UNITS::INCHES, true );
break;
case ALTIUM_UNIT::MILLIMETERS:
case ALTIUM_UNIT::CENTIMETER:
dimension->SetUnits( EDA_UNITS::MILLIMETRES, false );
break;
default:
break;
}
}
void ALTIUM_PCB::HelperParseDimensions6Leader( const ADIMENSION6& aElem )
{
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
aElem.layer ) );
klayer = Eco1_User;
}
if( !aElem.referencePoint.empty() )
{
wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
// line
wxPoint last = referencePoint0;
for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
{
DRAWSEGMENT* ds = new DRAWSEGMENT( m_board );
m_board->Add( ds, ADD_MODE::APPEND );
ds->SetShape( STROKE_T::S_SEGMENT );
ds->SetLayer( klayer );
ds->SetWidth( aElem.linewidth );
ds->SetStart( last );
ds->SetEnd( aElem.referencePoint.at( i ) );
last = aElem.referencePoint.at( i );
}
// arrow
if( aElem.referencePoint.size() >= 2 )
{
wxPoint dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
if( dirVec.x != 0 || dirVec.y != 0 )
{
double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
wxPoint arrVec =
wxPoint( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
RotatePoint( &arrVec, 200. );
DRAWSEGMENT* ds1 = new DRAWSEGMENT( m_board );
m_board->Add( ds1, ADD_MODE::APPEND );
ds1->SetShape( STROKE_T::S_SEGMENT );
ds1->SetLayer( klayer );
ds1->SetWidth( aElem.linewidth );
ds1->SetStart( referencePoint0 );
ds1->SetEnd( referencePoint0 + arrVec );
RotatePoint( &arrVec, -400. );
DRAWSEGMENT* ds2 = new DRAWSEGMENT( m_board );
m_board->Add( ds2, ADD_MODE::APPEND );
ds2->SetShape( STROKE_T::S_SEGMENT );
ds2->SetLayer( klayer );
ds2->SetWidth( aElem.linewidth );
ds2->SetStart( referencePoint0 );
ds2->SetEnd( referencePoint0 + arrVec );
}
}
}
if( aElem.textPoint.empty() )
{
wxLogError( "No text position present for leader dimension object" );
return;
}
TEXTE_PCB* text = new TEXTE_PCB( m_board );
m_board->Add( text, ADD_MODE::APPEND );
text->SetText( aElem.textformat );
text->SetPosition( aElem.textPoint.at( 0 ) );
text->SetLayer( klayer );
text->SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
text->SetTextThickness( aElem.textlinewidth );
text->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
text->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
}
void ALTIUM_PCB::HelperParseDimensions6Datum( const ADIMENSION6& aElem )
{
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
aElem.layer ) );
klayer = Eco1_User;
}
for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
{
DRAWSEGMENT* ds1 = new DRAWSEGMENT( m_board );
m_board->Add( ds1, ADD_MODE::APPEND );
ds1->SetShape( STROKE_T::S_SEGMENT );
ds1->SetLayer( klayer );
ds1->SetWidth( aElem.linewidth );
ds1->SetStart( aElem.referencePoint.at( i ) );
// ds1->SetEnd( /* TODO: seems to be based on TEXTY */ );
}
}
void ALTIUM_PCB::HelperParseDimensions6Center( const ADIMENSION6& aElem )
{
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Dimension on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
aElem.layer ) );
klayer = Eco1_User;
}
DRAWSEGMENT* ds1 = new DRAWSEGMENT( m_board );
m_board->Add( ds1, ADD_MODE::APPEND );
ds1->SetShape( STROKE_T::S_SEGMENT );
ds1->SetLayer( klayer );
ds1->SetWidth( aElem.linewidth );
wxPoint vec1 = wxPoint( 0, aElem.height / 2 );
RotatePoint( &vec1, aElem.angle * 10. );
ds1->SetStart( aElem.xy1 + vec1 );
ds1->SetEnd( aElem.xy1 - vec1 );
DRAWSEGMENT* ds2 = new DRAWSEGMENT( m_board );
m_board->Add( ds2, ADD_MODE::APPEND );
ds2->SetShape( STROKE_T::S_SEGMENT );
ds2->SetLayer( klayer );
ds2->SetWidth( aElem.linewidth );
wxPoint vec2 = wxPoint( aElem.height / 2, 0 );
RotatePoint( &vec2, aElem.angle * 10. );
ds2->SetStart( aElem.xy1 + vec2 );
ds2->SetEnd( aElem.xy1 - vec2 );
}
void ALTIUM_PCB::ParseDimensions6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ADIMENSION6 elem( reader );
switch( elem.kind )
{
case ALTIUM_DIMENSION_KIND::LINEAR:
HelperParseDimensions6Linear( elem );
break;
case ALTIUM_DIMENSION_KIND::LEADER:
HelperParseDimensions6Leader( elem );
break;
case ALTIUM_DIMENSION_KIND::DATUM:
wxLogWarning( wxString::Format( "Ignore dimension object of kind %d", elem.kind ) );
// HelperParseDimensions6Datum( elem );
break;
case ALTIUM_DIMENSION_KIND::CENTER:
HelperParseDimensions6Center( elem );
break;
default:
wxLogWarning( wxString::Format( "Ignore dimension object of kind %d", elem.kind ) );
break;
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Dimensions6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseModelsData( const CFB::CompoundFileReader& aReader,
const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString aRootDir )
{
ALTIUM_PARSER reader( aReader, aEntry );
if( reader.GetRemainingBytes() == 0 )
{
return; // fast path: no 3d-models present which need to be imported -> no directory needs to be created
}
wxString projectPath = wxPathOnly( m_board->GetFileName() );
// TODO: set KIPRJMOD always after import (not only when loading project)?
wxSetEnv( PROJECT_VAR_NAME, projectPath );
// TODO: make this path configurable?
const wxString altiumModelDir = "ALTIUM_EMBEDDED_MODELS";
wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
wxString kicadModelPrefix = "${KIPRJMOD}/" + altiumModelDir + "/";
if( !altiumModelsPath.AppendDir( altiumModelDir ) )
{
THROW_IO_ERROR( "Cannot construct directory path for step models" );
}
// Create dir if it does not exist
if( !altiumModelsPath.DirExists() )
{
if( !altiumModelsPath.Mkdir() )
{
wxLogError( wxString::Format(
_( "Cannot create directory \"%s\" -> no 3D-models will be imported." ),
GetChars( altiumModelsPath.GetFullPath() ) ) );
return;
}
}
int idx = 0;
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AMODEL elem( reader );
wxString stepPath = aRootDir + std::to_string( idx++ );
const CFB::COMPOUND_FILE_ENTRY* stepEntry = FindStream( aReader, stepPath.c_str() );
size_t stepSize = static_cast<size_t>( stepEntry->size );
std::unique_ptr<char[]> stepContent( new char[stepSize] );
// read file into buffer
aReader.ReadFile( stepEntry, 0, stepContent.get(), stepSize );
wxFileName storagePath( altiumModelsPath.GetPath(), elem.name );
if( !storagePath.IsDirWritable() )
{
wxLogError(
wxString::Format( _( "You do not have write permissions to save file \"%s\"." ),
GetChars( storagePath.GetFullPath() ) ) );
continue;
}
wxMemoryInputStream stepStream( stepContent.get(), stepSize );
wxZlibInputStream zlibInputStream( stepStream );
wxFileOutputStream outputStream( storagePath.GetFullPath() );
outputStream.Write( zlibInputStream );
outputStream.Close();
m_models.insert( { elem.id, kicadModelPrefix + elem.name } );
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Models stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseNets6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
wxASSERT( m_num_nets == 0 );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ANET6 elem( reader );
m_board->Add( new NETINFO_ITEM( m_board, elem.name, ++m_num_nets ), ADD_MODE::APPEND );
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Nets6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParsePolygons6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
APOLYGON6 elem( reader );
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Polygon on Altium layer %d has no KiCad equivalent. Ignore it instead" ),
elem.layer ) );
m_polygons.emplace_back( nullptr );
continue;
}
ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board );
m_board->Add( zone, ADD_MODE::APPEND );
m_polygons.emplace_back( zone );
zone->SetNetCode( GetNetCode( elem.net ) );
zone->SetLayer( klayer );
zone->SetPosition( elem.vertices.at( 0 ).position );
zone->SetLocked( elem.locked );
zone->SetPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
if( elem.pourindex > m_highest_pour_index )
m_highest_pour_index = elem.pourindex;
for( auto& vertice : elem.vertices )
{
zone->AppendCorner( vertice.position, -1 ); // TODO: arcs
}
// TODO: more flexible rule parsing
const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
if( clearanceRule != nullptr )
{
zone->SetLocalClearance( clearanceRule->planeclearanceClearance );
}
const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
if( polygonConnectRule != nullptr )
{
switch( polygonConnectRule->polygonconnectStyle )
{
case ALTIUM_CONNECT_STYLE::DIRECT:
zone->SetPadConnection( ZONE_CONNECTION::FULL );
break;
case ALTIUM_CONNECT_STYLE::NONE:
zone->SetPadConnection( ZONE_CONNECTION::NONE );
break;
default:
case ALTIUM_CONNECT_STYLE::RELIEF:
zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
break;
}
// TODO: correct variables?
zone->SetThermalReliefSpokeWidth(
polygonConnectRule->polygonconnectReliefconductorwidth );
zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
}
if( IsAltiumLayerAPlane( elem.layer ) )
{
// outer zone will be set to priority 0 later.
zone->SetPriority( 1 );
// check if this is the outer zone by simply comparing the BBOX
const auto& cur_outer_plane = m_outer_plane.find( elem.layer );
if( cur_outer_plane == m_outer_plane.end()
|| zone->GetBoundingBox().Contains(
cur_outer_plane->second->GetBoundingBox() ) )
{
m_outer_plane[elem.layer] = zone;
}
}
if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID
&& elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN )
{
zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
zone->SetHatchThickness( elem.trackwidth );
if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE )
{
// use a small hack to get us only an outline (hopefully)
const EDA_RECT& bbox = zone->GetBoundingBox();
zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
}
else
{
zone->SetHatchGap( elem.gridsize - elem.trackwidth );
}
zone->SetHatchOrientation( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 ? 45 : 0 );
}
zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
ZONE_CONTAINER::GetDefaultHatchPitch(), true );
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Polygons6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseRules6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ARULE6 elem( reader );
m_rules[elem.kind].emplace_back( elem );
}
// sort rules by priority
for( auto&& val : m_rules )
{
std::sort( val.second.begin(), val.second.end(),
[]( const auto& lhs, const auto& rhs ) { return lhs.priority < rhs.priority; } );
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Rules6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseBoardRegionsData(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AREGION6 elem( reader, false );
// TODO: implement?
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "BoardRegions stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseShapeBasedRegions6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AREGION6 elem( reader, true );
if( elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
{
HelperCreateBoardOutline( elem.vertices );
}
else if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || elem.is_keepout )
{
ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board );
m_board->Add( zone, ADD_MODE::APPEND );
zone->SetIsRuleArea( true );
zone->SetDoNotAllowTracks( false );
zone->SetDoNotAllowVias( false );
zone->SetDoNotAllowPads( false );
zone->SetDoNotAllowFootprints( false );
zone->SetDoNotAllowCopperPour( true );
if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
{
zone->SetLayer( F_Cu );
zone->SetLayerSet( LSET::AllCuMask() );
}
else
{
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Zone on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
elem.layer ) );
klayer = Eco1_User;
}
zone->SetLayer( klayer );
}
zone->SetPosition( elem.vertices.at( 0 ).position );
for( auto& vertice : elem.vertices )
{
zone->AppendCorner( vertice.position, -1 );
}
zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
ZONE_CONTAINER::GetDefaultHatchPitch(), true );
}
else if( elem.kind == ALTIUM_REGION_KIND::COPPER )
{
if( elem.subpolyindex == ALTIUM_POLYGON_NONE )
{
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Polygon on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
elem.layer ) );
klayer = Eco1_User;
}
DRAWSEGMENT* ds = new DRAWSEGMENT( m_board );
m_board->Add( ds, ADD_MODE::APPEND );
ds->SetShape( STROKE_T::S_POLYGON );
ds->SetLayer( klayer );
ds->SetWidth( 0 );
std::vector<wxPoint> pts;
for( auto& vertice : elem.vertices )
{
pts.push_back( vertice.position );
}
ds->SetPolyPoints( pts );
}
}
else
{
wxLogError( wxString::Format(
"Ignore polygon shape of kind %d on layer %s, because not implemented yet",
elem.kind, LSET::Name( GetKicadLayer( elem.layer ) ) ) );
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseRegions6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
for( ZONE_CONTAINER* zone : m_polygons )
{
if( zone != nullptr )
{
zone->UnFill(); // just to be sure
}
}
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AREGION6 elem( reader, false );
#if 0 // TODO: it seems this code has multiple issues right now, and we can manually fill anyways
if( elem.subpolyindex != ALTIUM_POLYGON_NONE )
{
if( m_polygons.size() <= elem.subpolyindex )
{
THROW_IO_ERROR( wxString::Format(
"Region stream tries to access polygon id %d of %d existing polygons",
elem.subpolyindex, m_polygons.size() ) );
}
ZONE_CONTAINER *zone = m_polygons.at(elem.subpolyindex);
if( zone == nullptr )
{
continue; // we know the zone id, but because we do not know the layer we did not add it!
}
SHAPE_LINE_CHAIN linechain;
for( auto& vertice : elem.vertices )
{
linechain.Append( vertice.position );
}
linechain.Append( elem.vertices.at( 0 ).position );
linechain.SetClosed( true );
SHAPE_POLY_SET polyset;
polyset.AddOutline( linechain );
polyset.BooleanAdd( zone->GetFilledPolysList(), SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE );
zone->SetFilledPolysList( polyset );
zone->SetIsFilled( true );
}
#endif
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Regions6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseArcs6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AARC6 elem( reader );
if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
{
continue;
}
// element in plane is in fact substracted from the plane. Should be already done by Altium?
/*if( IsAltiumLayerAPlane( elem.layer ) )
{
continue;
}*/
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Arc on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
elem.layer ) );
klayer = Eco1_User;
}
if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
{
DRAWSEGMENT ds( nullptr ); // just a helper to get the graphic
ds.SetWidth( elem.width );
ds.SetCenter( elem.center );
if( elem.startangle == 0. && elem.endangle == 360. )
{ // TODO: other variants to define circle?
ds.SetShape( STROKE_T::S_CIRCLE );
ds.SetArcStart( elem.center - wxPoint( 0, elem.radius ) );
}
else
{
ds.SetShape( STROKE_T::S_ARC );
ds.SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. );
double startradiant = DEG2RAD( elem.startangle );
wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
-KiROUND( std::sin( startradiant ) * elem.radius ) );
ds.SetArcStart( elem.center + arcStartOffset );
}
ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board );
m_board->Add( zone, ADD_MODE::APPEND );
zone->SetLayer( klayer );
zone->SetIsRuleArea( true );
zone->SetDoNotAllowTracks( false );
zone->SetDoNotAllowVias( false );
zone->SetDoNotAllowPads( false );
zone->SetDoNotAllowFootprints( false );
zone->SetDoNotAllowCopperPour( true );
ds.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
false );
zone->Outline()->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // the outline is not a single polygon!
zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
ZONE_CONTAINER::GetDefaultHatchPitch(), true );
continue;
}
if( klayer >= F_Cu && klayer <= B_Cu )
{
double angle = -NormalizeAngleDegreesPos( elem.endangle - elem.startangle );
double startradiant = DEG2RAD( elem.startangle );
wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
-KiROUND( std::sin( startradiant ) * elem.radius ) );
SHAPE_ARC shapeArc( elem.center, elem.center + arcStartOffset, angle, elem.width );
ARC* arc = new ARC( m_board, &shapeArc );
m_board->Add( arc, ADD_MODE::APPEND );
arc->SetWidth( elem.width );
arc->SetLayer( klayer );
arc->SetNetCode( GetNetCode( elem.net ) );
}
else
{
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( elem.component );
ds->SetCenter( elem.center );
ds->SetWidth( elem.width );
ds->SetLayer( klayer );
if( elem.startangle == 0. && elem.endangle == 360. )
{ // TODO: other variants to define circle?
ds->SetShape( STROKE_T::S_CIRCLE );
ds->SetArcStart( elem.center - wxPoint( 0, elem.radius ) );
}
else
{
ds->SetShape( STROKE_T::S_ARC );
ds->SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. );
double startradiant = DEG2RAD( elem.startangle );
wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
-KiROUND( std::sin( startradiant ) * elem.radius ) );
ds->SetArcStart( elem.center + arcStartOffset );
}
HelperDrawsegmentSetLocalCoord( ds, elem.component );
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParsePads6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
APAD6 elem( reader );
// It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
if( !IsAltiumLayerCopper( elem.layer ) && !IsAltiumLayerAPlane( elem.layer )
&& elem.layer != ALTIUM_LAYER::MULTI_LAYER )
{
HelperParsePad6NonCopper( elem );
continue;
}
// Create Pad
MODULE* module = nullptr;
if( elem.component == ALTIUM_COMPONENT_NONE )
{
module = new MODULE( m_board ); // We cannot add a pad directly into the PCB
m_board->Add( module, ADD_MODE::APPEND );
module->SetPosition( elem.position );
}
else
{
if( m_components.size() <= elem.component )
{
THROW_IO_ERROR( wxString::Format(
"Pads6 stream tries to access component id %d of %d existing components",
elem.component, m_components.size() ) );
}
module = m_components.at( elem.component );
}
D_PAD* pad = new D_PAD( module );
module->Add( pad, ADD_MODE::APPEND );
pad->SetName( elem.name );
pad->SetNetCode( GetNetCode( elem.net ) );
pad->SetLocked( elem.is_locked );
pad->SetPosition( elem.position );
pad->SetOrientationDegrees( elem.direction );
pad->SetLocalCoord();
pad->SetSize( elem.topsize );
if( elem.holesize == 0 )
{
pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD );
}
else
{
if( elem.layer != ALTIUM_LAYER::MULTI_LAYER )
{
// TODO: I assume other values are possible as well?
wxLogError( wxString::Format(
"Pad '%s' of Footprint %s is not marked as multilayer, but it is an THT pad",
elem.name, module->GetReference() ) );
}
pad->SetAttribute( elem.plated ? PAD_ATTR_T::PAD_ATTRIB_PTH :
PAD_ATTR_T::PAD_ATTRIB_NPTH );
if( !elem.sizeAndShape || elem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
{
pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) );
}
else
{
switch( elem.sizeAndShape->holeshape )
{
case ALTIUM_PAD_HOLE_SHAPE::ROUND:
wxFAIL_MSG( "Round holes are handled before the switch" );
break;
case ALTIUM_PAD_HOLE_SHAPE::SQUARE:
wxLogWarning( wxString::Format(
_( "Pad '%s' of Footprint %s has a square hole. KiCad does not support this yet" ),
elem.name, module->GetReference() ) );
pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
// TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in this case or rect holes have a different id
break;
case ALTIUM_PAD_HOLE_SHAPE::SLOT:
{
pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_OBLONG );
double normalizedSlotrotation =
NormalizeAngleDegreesPos( elem.sizeAndShape->slotrotation );
if( normalizedSlotrotation == 0. || normalizedSlotrotation == 180. )
{
pad->SetDrillSize( wxSize( elem.sizeAndShape->slotsize, elem.holesize ) );
}
else
{
if( normalizedSlotrotation != 90. && normalizedSlotrotation != 270. )
{
wxLogWarning( wxString::Format(
_( "Pad '%s' of Footprint %s has a hole-rotation of %f degree. KiCad only supports 90 degree angles" ),
elem.name, module->GetReference(), normalizedSlotrotation ) );
}
pad->SetDrillSize( wxSize( elem.holesize, elem.sizeAndShape->slotsize ) );
}
}
break;
default:
case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN:
wxLogError( wxString::Format(
"Pad '%s' of Footprint %s uses a hole of unknown kind %d", elem.name,
module->GetReference(), elem.sizeAndShape->holeshape ) );
pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
break;
}
}
if( elem.sizeAndShape )
{
pad->SetOffset( elem.sizeAndShape->holeoffset[0] );
}
}
if( elem.padmode != ALTIUM_PAD_MODE::SIMPLE )
{
wxLogWarning( wxString::Format(
_( "Pad '%s' of Footprint %s uses a complex pad stack (kind %d), which is not supported yet" ),
elem.name, module->GetReference(), elem.padmode ) );
}
switch( elem.topshape )
{
case ALTIUM_PAD_SHAPE::RECT:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT );
break;
case ALTIUM_PAD_SHAPE::CIRCLE:
if( elem.sizeAndShape
&& elem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
{
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_ROUNDRECT ); // 100 = round, 0 = rectangular
double ratio = elem.sizeAndShape->cornerradius[0] / 200.;
pad->SetRoundRectRadiusRatio( ratio );
}
else if( elem.topsize.x == elem.topsize.y )
{
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CIRCLE );
}
else
{
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_OVAL );
}
break;
case ALTIUM_PAD_SHAPE::OCTAGONAL:
pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CHAMFERED_RECT );
pad->SetChamferPositions( RECT_CHAMFER_ALL );
pad->SetChamferRectRatio( 0.25 );
break;
case ALTIUM_PAD_SHAPE::UNKNOWN:
default:
wxLogError( wxString::Format( "Pad '%s' of Footprint %s uses a unknown pad-shape",
elem.name, module->GetReference() ) );
break;
}
switch( elem.layer )
{
case ALTIUM_LAYER::TOP_LAYER:
pad->SetLayer( F_Cu );
pad->SetLayerSet( D_PAD::SMDMask() );
break;
case ALTIUM_LAYER::BOTTOM_LAYER:
pad->SetLayer( B_Cu );
pad->SetLayerSet( FlipLayerMask( D_PAD::SMDMask() ) );
break;
case ALTIUM_LAYER::MULTI_LAYER:
pad->SetLayerSet( elem.plated ? D_PAD::PTHMask() : D_PAD::UnplatedHoleMask() );
break;
default:
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
pad->SetLayer( klayer );
pad->SetLayerSet( LSET( 1, klayer ) );
break;
}
if( elem.pastemaskexpansionmode == ALTIUM_PAD_RULE::MANUAL )
{
pad->SetLocalSolderPasteMargin( elem.pastemaskexpansionmanual );
}
if( elem.soldermaskexpansionmode == ALTIUM_PAD_RULE::MANUAL )
{
pad->SetLocalSolderMaskMargin( elem.soldermaskexpansionmanual );
}
if( elem.is_tent_top )
{
pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
}
if( elem.is_tent_bottom )
{
pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Pads6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::HelperParsePad6NonCopper( const APAD6& aElem )
{
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Non-Copper Pad on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
aElem.layer ) );
klayer = Eco1_User;
}
if( aElem.net != ALTIUM_NET_UNCONNECTED )
{
wxLogError( wxString::Format(
"Non-Copper Pad '%s' is connected to a net. This is not supported", aElem.name ) );
}
if( aElem.holesize != 0 )
{
wxLogError( wxString::Format(
"Non-Copper Pad '%s' has a hole. This should not happen", aElem.name ) );
}
if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
{
wxLogWarning( wxString::Format(
_( "Non-Copper Pad '%s' uses a complex pad stack (kind %d). This should not happen" ),
aElem.name, aElem.padmode ) );
}
switch( aElem.topshape )
{
case ALTIUM_PAD_SHAPE::RECT:
{
// filled rect
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( aElem.component );
ds->SetShape( STROKE_T::S_POLYGON );
ds->SetLayer( klayer );
ds->SetWidth( 0 );
ds->SetPolyPoints( { aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
if( aElem.direction != 0 )
{
ds->Rotate( aElem.position, aElem.direction * 10 );
}
HelperDrawsegmentSetLocalCoord( ds, aElem.component );
}
break;
case ALTIUM_PAD_SHAPE::CIRCLE:
if( aElem.sizeAndShape
&& aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
{
// filled roundrect
int cornerradius = aElem.sizeAndShape->cornerradius[0];
int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( aElem.component );
ds->SetLayer( klayer );
ds->SetWidth( offset * 2 );
if( cornerradius < 100 )
{
int offsetX = aElem.topsize.x / 2 - offset;
int offsetY = aElem.topsize.y / 2 - offset;
wxPoint p11 = aElem.position + wxPoint( offsetX, offsetY );
wxPoint p12 = aElem.position + wxPoint( offsetX, -offsetY );
wxPoint p22 = aElem.position + wxPoint( -offsetX, -offsetY );
wxPoint p21 = aElem.position + wxPoint( -offsetX, offsetY );
ds->SetShape( STROKE_T::S_POLYGON );
ds->SetPolyPoints( { p11, p12, p22, p21 } );
}
else if( aElem.topsize.x == aElem.topsize.y )
{
// circle
ds->SetShape( STROKE_T::S_CIRCLE );
ds->SetCenter( aElem.position );
ds->SetWidth( aElem.topsize.x / 2 );
ds->SetArcStart( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
}
else if( aElem.topsize.x < aElem.topsize.y )
{
// short vertical line
ds->SetShape( STROKE_T::S_SEGMENT );
wxPoint pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
ds->SetStart( aElem.position + pointOffset );
ds->SetEnd( aElem.position - pointOffset );
}
else
{
// short horizontal line
ds->SetShape( STROKE_T::S_SEGMENT );
wxPoint pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
ds->SetStart( aElem.position + pointOffset );
ds->SetEnd( aElem.position - pointOffset );
}
if( aElem.direction != 0 )
{
ds->Rotate( aElem.position, aElem.direction * 10 );
}
HelperDrawsegmentSetLocalCoord( ds, aElem.component );
}
else if( aElem.topsize.x == aElem.topsize.y )
{
// filled circle
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( aElem.component );
ds->SetShape( STROKE_T::S_CIRCLE );
ds->SetLayer( klayer );
ds->SetCenter( aElem.position );
ds->SetWidth( aElem.topsize.x / 2 );
ds->SetArcStart( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
HelperDrawsegmentSetLocalCoord( ds, aElem.component );
}
else
{
// short line
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( aElem.component );
ds->SetShape( STROKE_T::S_SEGMENT );
ds->SetLayer( klayer );
ds->SetWidth( std::min( aElem.topsize.x, aElem.topsize.y ) );
if( aElem.topsize.x < aElem.topsize.y )
{
wxPoint offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
ds->SetStart( aElem.position + offset );
ds->SetEnd( aElem.position - offset );
}
else
{
wxPoint offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
ds->SetStart( aElem.position + offset );
ds->SetEnd( aElem.position - offset );
}
if( aElem.direction != 0 )
{
ds->Rotate( aElem.position, aElem.direction * 10. );
}
HelperDrawsegmentSetLocalCoord( ds, aElem.component );
}
break;
case ALTIUM_PAD_SHAPE::OCTAGONAL:
{
// filled octagon
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( aElem.component );
ds->SetShape( STROKE_T::S_POLYGON );
ds->SetLayer( klayer );
ds->SetWidth( 0 );
wxPoint p11 = aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 );
wxPoint p12 = aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
wxPoint p22 = aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
wxPoint p21 = aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
wxPoint chamferX( chamfer, 0 );
wxPoint chamferY( 0, chamfer );
ds->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
if( aElem.direction != 0. )
{
ds->Rotate( aElem.position, aElem.direction * 10 );
}
HelperDrawsegmentSetLocalCoord( ds, aElem.component );
}
break;
case ALTIUM_PAD_SHAPE::UNKNOWN:
default:
wxLogError(
wxString::Format( "Non-Copper Pad '%s' uses a unknown pad-shape", aElem.name ) );
break;
}
}
void ALTIUM_PCB::ParseVias6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AVIA6 elem( reader );
VIA* via = new VIA( m_board );
m_board->Add( via, ADD_MODE::APPEND );
via->SetPosition( elem.position );
via->SetWidth( elem.diameter );
via->SetDrill( elem.holesize );
via->SetNetCode( GetNetCode( elem.net ) );
via->SetLocked( elem.is_locked );
bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
|| elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER;
bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
|| elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER;
if( start_layer_outside && end_layer_outside )
{
via->SetViaType( VIATYPE::THROUGH );
}
else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
{
via->SetViaType( VIATYPE::BLIND_BURIED );
}
else
{
via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
}
PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
{
wxLogError( wxString::Format(
"Via from layer %d <-> %d uses non-copper layer. This should not happen.",
elem.layer_start, elem.layer_end ) );
continue; // just assume through-hole instead.
}
// we need VIATYPE set!
via->SetLayerPair( start_klayer, end_klayer );
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Vias6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseTracks6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ATRACK6 elem( reader );
if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
{
continue;
}
// element in plane is in fact substracted from the plane. Already done by Altium?
/*if( IsAltiumLayerAPlane( elem.layer ) )
{
continue;
}*/
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Track on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
elem.layer ) );
klayer = Eco1_User;
}
if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
{
DRAWSEGMENT ds( nullptr ); // just a helper to get the graphic
ds.SetShape( STROKE_T::S_SEGMENT );
ds.SetStart( elem.start );
ds.SetEnd( elem.end );
ds.SetWidth( elem.width );
ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board );
m_board->Add( zone, ADD_MODE::APPEND );
zone->SetLayer( klayer );
zone->SetIsRuleArea( true );
zone->SetDoNotAllowTracks( false );
zone->SetDoNotAllowVias( false );
zone->SetDoNotAllowPads( false );
zone->SetDoNotAllowFootprints( false );
zone->SetDoNotAllowCopperPour( true );
ds.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
false );
zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
ZONE_CONTAINER::GetDefaultHatchPitch(), true );
continue;
}
if( klayer >= F_Cu && klayer <= B_Cu )
{
TRACK* track = new TRACK( m_board );
m_board->Add( track, ADD_MODE::APPEND );
track->SetStart( elem.start );
track->SetEnd( elem.end );
track->SetWidth( elem.width );
track->SetLayer( klayer );
track->SetNetCode( GetNetCode( elem.net ) );
}
else
{
DRAWSEGMENT* ds = HelperCreateAndAddDrawsegment( elem.component );
ds->SetShape( STROKE_T::S_SEGMENT );
ds->SetStart( elem.start );
ds->SetEnd( elem.end );
ds->SetWidth( elem.width );
ds->SetLayer( klayer );
HelperDrawsegmentSetLocalCoord( ds, elem.component );
}
reader.SkipSubrecord();
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseTexts6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
ATEXT6 elem( reader );
if( elem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
{
wxLogWarning( wxString::Format(
_( "Ignore Barcode on Altium layer %d because it is not supported right now." ),
elem.layer ) );
continue;
}
// TODO: better approach to select if item belongs to a MODULE
EDA_TEXT* tx = nullptr;
BOARD_ITEM* itm = nullptr;
if( elem.component == ALTIUM_COMPONENT_NONE )
{
TEXTE_PCB* txp = new TEXTE_PCB( m_board );
tx = txp;
itm = txp;
m_board->Add( txp, ADD_MODE::APPEND );
}
else
{
if( m_components.size() <= elem.component )
{
THROW_IO_ERROR( wxString::Format(
"Texts6 stream tries to access component id %d of %d existing components",
elem.component, m_components.size() ) );
}
MODULE* module = m_components.at( elem.component );
TEXTE_MODULE* txm;
if( elem.isDesignator )
{
txm = &module->Reference();
}
else if( elem.isComment )
{
txm = &module->Value();
}
else
{
txm = new TEXTE_MODULE( module );
module->Add( txm, ADD_MODE::APPEND );
}
txm->SetKeepUpright( false );
tx = txm;
itm = txm;
}
wxString trimmedText = elem.text.Trim();
if( !elem.isDesignator && trimmedText.CmpNoCase( ".Designator" ) == 0 )
{
tx->SetText( "${REFERENCE}" );
}
else if( !elem.isComment && trimmedText.CmpNoCase( ".Comment" ) == 0 )
{
tx->SetText( "${VALUE}" );
}
else if( trimmedText.CmpNoCase( ".Layer_Name" ) == 0 )
{
tx->SetText( "${LAYER}" );
}
else
{
tx->SetText( elem.text );
}
itm->SetPosition( elem.position );
tx->SetTextAngle( elem.rotation * 10. );
if( elem.component != ALTIUM_COMPONENT_NONE )
{
TEXTE_MODULE* txm = dynamic_cast<TEXTE_MODULE*>( tx );
if( txm )
{
double orientation =
static_cast<const MODULE*>( txm->GetParent() )->GetOrientation();
txm->SetTextAngle( txm->GetTextAngle() - orientation );
txm->SetLocalCoord();
}
}
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Text on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
elem.layer ) );
klayer = Eco1_User;
}
itm->SetLayer( klayer );
if( elem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
{
// TODO: why is this required? Somehow, truetype size is calculated differently
tx->SetTextSize( wxSize( elem.height / 2, elem.height / 2 ) );
}
else
{
tx->SetTextSize( wxSize( elem.height, elem.height ) ); // TODO: parse text width
}
tx->SetTextThickness( elem.strokewidth );
tx->SetBold( elem.isBold );
tx->SetItalic( elem.isItalic );
tx->SetMirrored( elem.isMirrored );
if( elem.isDesignator || elem.isComment ) // That's just a bold assumption
{
tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
}
else
{
switch( elem.textposition )
{
case ALTIUM_TEXT_POSITION::LEFT_TOP:
case ALTIUM_TEXT_POSITION::LEFT_CENTER:
case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
break;
case ALTIUM_TEXT_POSITION::CENTER_TOP:
case ALTIUM_TEXT_POSITION::CENTER_CENTER:
case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_CENTER );
break;
case ALTIUM_TEXT_POSITION::RIGHT_TOP:
case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_RIGHT );
break;
default:
wxLogError( "Unexpected horizontal Text Position. This should never happen." );
break;
}
switch( elem.textposition )
{
case ALTIUM_TEXT_POSITION::LEFT_TOP:
case ALTIUM_TEXT_POSITION::CENTER_TOP:
case ALTIUM_TEXT_POSITION::RIGHT_TOP:
tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_TOP );
break;
case ALTIUM_TEXT_POSITION::LEFT_CENTER:
case ALTIUM_TEXT_POSITION::CENTER_CENTER:
case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_CENTER );
break;
case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
break;
default:
wxLogError( "Unexpected vertical text position. This should never happen." );
break;
}
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Texts6 stream is not fully parsed" );
}
}
void ALTIUM_PCB::ParseFills6Data(
const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry )
{
ALTIUM_PARSER reader( aReader, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
AFILL6 elem( reader );
wxPoint p11( elem.pos1.x, elem.pos1.y );
wxPoint p12( elem.pos1.x, elem.pos2.y );
wxPoint p22( elem.pos2.x, elem.pos2.y );
wxPoint p21( elem.pos2.x, elem.pos1.y );
wxPoint center( ( elem.pos1.x + elem.pos2.x ) / 2, ( elem.pos1.y + elem.pos2.y ) / 2 );
PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
if( klayer == UNDEFINED_LAYER )
{
wxLogWarning( wxString::Format(
_( "Fill on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
elem.layer ) );
klayer = Eco1_User;
}
if( elem.is_keepout || elem.net != ALTIUM_NET_UNCONNECTED )
{
ZONE_CONTAINER* zone = new ZONE_CONTAINER( m_board );
m_board->Add( zone, ADD_MODE::APPEND );
zone->SetNetCode( GetNetCode( elem.net ) );
zone->SetLayer( klayer );
zone->SetPosition( elem.pos1 );
zone->SetPriority( 1000 );
const int outlineIdx = -1; // this is the id of the copper zone main outline
zone->AppendCorner( p11, outlineIdx );
zone->AppendCorner( p12, outlineIdx );
zone->AppendCorner( p22, outlineIdx );
zone->AppendCorner( p21, outlineIdx );
// should be correct?
zone->SetLocalClearance( 0 );
zone->SetPadConnection( ZONE_CONNECTION::FULL );
if( elem.is_keepout )
{
zone->SetIsRuleArea( true );
zone->SetDoNotAllowTracks( false );
zone->SetDoNotAllowVias( false );
zone->SetDoNotAllowPads( false );
zone->SetDoNotAllowFootprints( false );
zone->SetDoNotAllowCopperPour( true );
}
if( elem.rotation != 0. )
{
zone->Rotate( center, elem.rotation * 10 );
}
zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
ZONE_CONTAINER::GetDefaultHatchPitch(), true );
}
else
{
DRAWSEGMENT* ds = new DRAWSEGMENT( m_board );
m_board->Add( ds, ADD_MODE::APPEND );
ds->SetShape( STROKE_T::S_POLYGON );
ds->SetLayer( klayer );
ds->SetWidth( 0 );
ds->SetPolyPoints( { p11, p12, p22, p21 } );
if( elem.rotation != 0. )
{
ds->Rotate( center, elem.rotation * 10 );
}
}
}
if( reader.GetRemainingBytes() != 0 )
{
THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
}
}