kicad-source/eeschema/sch_io/cadstar/cadstar_sch_archive_loader.cpp
Wayne Stambaugh 7acd057c86 Fix broken library symbol links when importing CADSTAR schematics.
Under certain circumstances, the library symbol links were missing
the library nickname causing the schematic symbols to appear as if
the library symbol was missing.

Added a helper function to create the symbol library name to avoid
code duplication.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17143
2024-05-09 10:23:28 -04:00

3463 lines
132 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020-2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
* Copyright (C) 2020-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file cadstar_sch_archive_loader.cpp
* @brief Loads a csa file into a KiCad SCHEMATIC object
*/
#include <sch_io/cadstar/cadstar_sch_archive_loader.h>
#include <io/cadstar/cadstar_parts_lib_parser.h>
#include <bus_alias.h>
#include <core/mirror.h>
#include <core/kicad_algo.h>
#include <eda_text.h>
#include <macros.h>
#include <progress_reporter.h>
#include <string_utils.h>
#include <sch_bus_entry.h>
#include <sch_edit_frame.h> //SYMBOL_ORIENTATION_T
#include <sch_io/sch_io_mgr.h>
#include <sch_junction.h>
#include <sch_line.h>
#include <sch_screen.h>
#include <sch_shape.h>
#include <sch_sheet.h>
#include <sch_sheet_path.h>
#include <sch_sheet_pin.h>
#include <sch_label.h>
#include <schematic.h>
#include <sim/sim_model.h>
#include <trigo.h>
#include <wildcards_and_files_ext.h>
const wxString PartNameFieldName = "Part Name";
const wxString PartNumberFieldName = "Part Number";
const wxString PartVersionFieldName = "Part Version";
const wxString PartAcceptanceFieldName = "Part Acceptance";
wxString CADSTAR_SCH_ARCHIVE_LOADER::CreateLibName( const wxFileName& aFileName,
const SCH_SHEET* aRootSheet )
{
wxString libName = aFileName.GetName();
if( libName.IsEmpty() && aRootSheet )
{
wxFileName fn( aRootSheet->GetFileName() );
libName = fn.GetName();
}
if( libName.IsEmpty() )
libName = "noname";
libName = LIB_ID::FixIllegalChars( libName, true ).wx_str();
return libName;
}
std::vector<LIB_SYMBOL*> CADSTAR_SCH_ARCHIVE_LOADER::LoadPartsLib( const wxString& aFilename )
{
if( m_progressReporter )
m_progressReporter->SetNumPhases( 3 ); // (0) Read csa, (1) Parse csa, (3) Load lib
Parse();
CADSTAR_PARTS_LIB_PARSER p;
if( !p.CheckFileHeader( aFilename.utf8_string() ) )
THROW_IO_ERROR( _( "File does not appear to be a CADSTAR parts Library file" ) );
// TODO: we could add progress reporting for reading .lib
CADSTAR_PARTS_LIB_MODEL csLib = p.ReadFile( aFilename.utf8_string() );
if( m_progressReporter )
{
m_progressReporter->BeginPhase( 2 );
long numSteps = csLib.m_PartEntries.size();
m_progressReporter->SetMaxProgress( numSteps );
}
std::vector<LIB_SYMBOL*> retVal;
for( const CADSTAR_PART_ENTRY& part : csLib.m_PartEntries )
{
std::unique_ptr<LIB_SYMBOL> loadedPart = loadLibPart( part );
checkPoint();
if( loadedPart )
retVal.push_back( loadedPart.release() );
}
return retVal;
}
std::unique_ptr<LIB_SYMBOL>
CADSTAR_SCH_ARCHIVE_LOADER::loadLibPart( const CADSTAR_PART_ENTRY& aPart )
{
wxString escapedPartName = EscapeString( aPart.m_Name, CTX_LIBID );
std::unique_ptr<LIB_SYMBOL> retSym;
int unit = 0;
for( const CADSTAR_PART_SYMBOL_ENTRY& sym : aPart.m_Symbols )
{
++unit;
wxString alternateName = sym.m_SymbolAlternateName.value_or( "" );
SYMDEF_ID symbolID = getSymDefFromName( sym.m_SymbolName, alternateName );
if( !Library.SymbolDefinitions.count( symbolID ) )
{
m_reporter->Report( wxString::Format( _( "Unable to find symbol %s, referenced by part "
"%s. The part was not loaded." ),
generateLibName( sym.m_SymbolName, alternateName ),
aPart.m_Name ),
RPT_SEVERITY_ERROR );
return nullptr;
}
// Load the graphical symbol for this gate
std::unique_ptr<LIB_SYMBOL> kiSymDef( loadSymdef( symbolID )->Duplicate() );
if( (int)sym.m_Pins.size() != kiSymDef->GetPinCount() )
{
m_reporter->Report( wxString::Format( _( "Inconsistent pin numbers in symbol %s "
"compared to the one defined in part %s. The "
"part was not loaded." ),
generateLibName( sym.m_SymbolName, alternateName ),
aPart.m_Name ),
RPT_SEVERITY_ERROR );
return nullptr;
}
wxASSERT( m_symDefTerminalsMap.count( symbolID ) ); //loadSymDef should have populated this
// Update the pin numbers to match those defined in the Cadstar part
for( auto& [storedPinNum, termID] : m_symDefTerminalsMap[symbolID] )
{
wxCHECK( termID > 0 && sym.m_Pins.size() >= size_t( termID ), nullptr );
SCH_PIN* pin = kiSymDef->GetPin( storedPinNum );
size_t termIdx = size_t( termID ) - 1;
// For now leave numerical pin number. Otherwise, when loading the
// .cpa file we won't be able to link up to the footprint pads, but if
// we solve this, we could then load alphanumeric pin numbers as below:
//
// if( aPart.m_PinNamesMap.count( termID ) )
// partPinNum = wxString( aPart.m_PinNamesMap.at( termID ) );
//
wxString partPinNum = wxString::Format( "%ld", sym.m_Pins[termIdx].m_Identifier );
pin->SetNumber( partPinNum );
if( aPart.m_PinNamesMap.count( termID ) )
pin->SetName( HandleTextOverbar( aPart.m_PinNamesMap.at( termID ) ) );
else if( aPart.m_PinLabelsMap.count( termID ) )
pin->SetName( HandleTextOverbar( aPart.m_PinLabelsMap.at( termID ) ) );
pin->SetType( getKiCadPinType( sym.m_Pins[termIdx].m_Type ) );
// @todo: Load pin/gate swapping information once kicad supports this
}
if( unit == 1 )
{
wxCHECK( kiSymDef->GetUnitCount() == 1, nullptr );
// The first unit can just be moved to the part symbol
retSym = std::move( kiSymDef );
retSym->SetUnitCount( aPart.m_Symbols.size(), true );
retSym->SetName( escapedPartName );
retSym->GetReferenceField().SetText( aPart.m_ComponentStem );
retSym->GetValueField().SetText( aPart.m_Value.value_or( "" ) );
addNewFieldToSymbol( PartNameFieldName, retSym )->SetText( aPart.m_Name );
retSym->SetDescription( aPart.m_Description.value_or( "" ) );
auto addFieldIfHasValue =
[&]( const wxString& name, const std::optional<std::string>& value )
{
if( value.has_value() )
addNewFieldToSymbol( name, retSym )->SetText( value.value() );
};
addFieldIfHasValue( PartNumberFieldName, aPart.m_Number );
addFieldIfHasValue( PartVersionFieldName, aPart.m_Version );
addFieldIfHasValue( PartAcceptanceFieldName, aPart.m_AcceptancePartName );
setFootprintOnSymbol( retSym, aPart.m_Pcb_component,
aPart.m_Pcb_alternate.value_or( "" ) );
if( aPart.m_SpiceModel.has_value() )
{
wxString modelVal = wxString::Format( "model=\"%s\"", aPart.m_SpiceModel.value() );
addNewFieldToSymbol( SIM_DEVICE_FIELD, retSym )->SetText( "SPICE" );
addNewFieldToSymbol( SIM_PARAMS_FIELD, retSym )->SetText( modelVal );
}
// Load all part attributes, regardless of original cadstar type, to the symbol
// @todo some cadstar part attributes have a "read-only" flag. We should load this
// when KiCad supports read-only fields.
for( auto& [fieldName, value] : aPart.m_UserAttributes )
addNewFieldToSymbol( fieldName, retSym )->SetText( value );
for( auto& [fieldName, attrValue] : aPart.m_SchAttributes )
addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );
for( auto& [fieldName, attrValue] : aPart.m_PcbAttributes )
addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );
for( auto& [fieldName, attrValue] : aPart.m_SchAndPcbAttributes )
addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );
for( auto& [fieldName, attrValue] : aPart.m_PartAttributes )
addNewFieldToSymbol( fieldName, retSym )->SetText( attrValue.m_Value );
// Load all hidden pins onto the first unit of the symbol in KiCad
// We load them in a spiral sequence, starting at the center of the symbol BBOX
VECTOR2I symCenter = retSym->GetBodyBoundingBox( unit, 0, false, false ).GetCenter();
symCenter.y = -symCenter.y; // need to invert the y coord for lib symbols.
VECTOR2I delta( 0, 1 );
VECTOR2I direction( 0, -1 );
int spacing = schIUScale.MilsToIU( 50 ); // for now, place on a 50mil grid
for( auto& [signalName, csPinVector] : aPart.m_HiddenPins )
{
for( const CADSTAR_PART_PIN& csPin : csPinVector )
{
std::unique_ptr<SCH_PIN> pin = std::make_unique<SCH_PIN>( retSym.get() );
long pinNum = csPin.m_Identifier;
pin->SetNumber( wxString::Format( "%ld", pinNum ) );
pin->SetName( signalName );
pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );
pin->SetVisible( false );
// Generate the coordinate for the pin. We don't want overlapping pins
// and ideally close to the center of the symbol, so we load pins in a
// spiral sequence around the center
if( delta.x == delta.y
|| ( delta.x < 0 && delta.x == -delta.y )
|| ( delta.x > 0 && delta.x == 1 - delta.y ) )
{
// change direction
direction = { -direction.y, direction.x };
}
delta += direction;
VECTOR2I offset = delta * spacing;
pin->SetPosition( symCenter + offset );
pin->SetLength( 0 ); //CADSTAR Pins are just a point (have no length)
pin->SetShape( GRAPHIC_PINSHAPE::LINE );
pin->SetUnit( unit );
retSym->AddDrawItem( pin.release() );
}
}
}
else
{ // Source: Dest:
copySymbolItems( kiSymDef, retSym, unit, false /* aOverrideFields */ );
}
retSym->SetShowPinNames( aPart.m_PinsVisible );
retSym->SetShowPinNumbers( aPart.m_PinsVisible );
}
return retSym;
}
void CADSTAR_SCH_ARCHIVE_LOADER::copySymbolItems( std::unique_ptr<LIB_SYMBOL>& aSourceSym,
std::unique_ptr<LIB_SYMBOL>& aDestSym,
int aDestUnit, bool aOverrideFields )
{
// Ensure there are no items on the unit we want to load onto
for( SCH_ITEM* item : aDestSym->GetUnitDrawItems( aDestUnit, 0 /*aConvert*/ ) )
aDestSym->RemoveDrawItem( item );
// Copy all draw items
for( SCH_ITEM* newItem : aSourceSym->GetUnitDrawItems( 1, 0 /*aConvert*/ ) )
{
SCH_ITEM* itemCopy = static_cast<SCH_ITEM*>( newItem->Clone() );
itemCopy->SetParent( aDestSym.get() );
itemCopy->SetUnit( aDestUnit );
aDestSym->AddDrawItem( itemCopy );
}
//Copy / override all fields
if( aOverrideFields )
{
std::vector<SCH_FIELD*> fieldsToCopy;
aSourceSym->GetFields( fieldsToCopy );
for( SCH_FIELD* templateField : fieldsToCopy )
{
SCH_FIELD* appliedField = addNewFieldToSymbol( templateField->GetName(), aDestSym );
templateField->Copy( appliedField );
}
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::Load( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet )
{
wxCHECK( aSchematic, /* void */ );
if( m_progressReporter )
m_progressReporter->SetNumPhases( 3 ); // (0) Read file, (1) Parse file, (2) Load file
Parse();
checkDesignLimits(); // Throws if error found
// Assume the center at 0,0 since we are going to be translating the design afterwards anyway
m_designCenter = { 0, 0 };
m_schematic = aSchematic;
m_rootSheet = aRootSheet;
if( m_progressReporter )
{
m_progressReporter->BeginPhase( 2 );
long numSteps = 11; // one step for each of below functions + one at the end of import
// Step 4 is by far the longest - add granularity in reporting
numSteps += Parts.PartDefinitions.size();
m_progressReporter->SetMaxProgress( numSteps );
}
loadTextVariables(); // Load text variables right at the start to ensure bounding box
// calculations work correctly for text items
checkPoint(); // Step 1
loadSheets();
checkPoint(); // Step 2
loadHierarchicalSheetPins();
checkPoint(); // Step 3
loadPartsLibrary();
checkPoint(); // Step 4, Subdivided into extra steps
loadSchematicSymbolInstances();
checkPoint(); // Step 5
loadBusses();
checkPoint(); // Step 6
loadNets();
checkPoint(); // Step 7
loadFigures();
checkPoint(); // Step 8
loadTexts();
checkPoint(); // Step 9
loadDocumentationSymbols();
checkPoint(); // Step 10
if( Schematic.VariantHierarchy.Variants.size() > 0 )
{
m_reporter->Report( wxString::Format( _( "The CADSTAR design contains variants which has "
"no KiCad equivalent. Only the master variant "
"('%s') was loaded." ),
Schematic.VariantHierarchy.Variants.at( "V0" ).Name ),
RPT_SEVERITY_WARNING );
}
if( Schematic.Groups.size() > 0 )
{
m_reporter->Report( _( "The CADSTAR design contains grouped items which has no KiCad "
"equivalent. Any grouped items have been ungrouped." ),
RPT_SEVERITY_WARNING );
}
if( Schematic.ReuseBlocks.size() > 0 )
{
m_reporter->Report( _( "The CADSTAR design contains re-use blocks which has no KiCad "
"equivalent. The re-use block information has been discarded during "
"the import." ),
RPT_SEVERITY_WARNING );
}
// For all sheets, center all elements and re calculate the page size:
for( std::pair<LAYER_ID, SCH_SHEET*> sheetPair : m_sheetMap )
{
SCH_SHEET* sheet = sheetPair.second;
// Calculate the new sheet size.
BOX2I sheetBoundingBox;
for( SCH_ITEM* item : sheet->GetScreen()->Items() )
{
BOX2I bbox;
// Only use the visible fields of the symbols to calculate their bounding box
// (hidden fields could be very long and artificially enlarge the sheet bounding box)
if( item->Type() == SCH_SYMBOL_T )
{
SCH_SYMBOL* comp = static_cast<SCH_SYMBOL*>( item );
bbox = comp->GetBodyAndPinsBoundingBox();
for( const SCH_FIELD& field : comp->GetFields() )
{
if( field.IsVisible() )
bbox.Merge( field.GetBoundingBox() );
}
}
else if( item->Type() == SCH_TEXT_T )
{
SCH_TEXT* txtItem = static_cast<SCH_TEXT*>( item );
wxString txt = txtItem->GetText();
if( txt.Contains( "${" ) )
continue; // We can't calculate bounding box of text items with variables
else
bbox = txtItem->GetBoundingBox();
}
else
{
bbox = item->GetBoundingBox();
}
sheetBoundingBox.Merge( bbox );
}
// Find the screen grid of the original CADSTAR design
int grid = Assignments.Grids.ScreenGrid.Param1;
if( Assignments.Grids.ScreenGrid.Type == GRID_TYPE::FRACTIONALGRID )
grid = grid / Assignments.Grids.ScreenGrid.Param2;
else if( Assignments.Grids.ScreenGrid.Param2 > grid )
grid = Assignments.Grids.ScreenGrid.Param2;
grid = getKiCadLength( grid );
auto roundToNearestGrid =
[&]( int aNumber ) -> int
{
int error = aNumber % grid;
int absError = sign( error ) * error;
if( absError > ( grid / 2 ) )
return aNumber + ( sign( error ) * grid ) - error;
else
return aNumber - error;
};
// When exporting to pdf, CADSTAR applies a margin of 3% of the longest dimension (height
// or width) to all 4 sides (top, bottom, left right). For the import, we are also rounding
// the margin to the nearest grid, ensuring all items remain on the grid.
VECTOR2I targetSheetSize = sheetBoundingBox.GetSize();
int longestSide = std::max( targetSheetSize.x, targetSheetSize.y );
int margin = ( (double) longestSide * 0.03 );
margin = roundToNearestGrid( margin );
targetSheetSize += margin * 2;
// Update page size always
PAGE_INFO pageInfo = sheet->GetScreen()->GetPageSettings();
pageInfo.SetWidthMils( schIUScale.IUToMils( targetSheetSize.x ) );
pageInfo.SetHeightMils( schIUScale.IUToMils( targetSheetSize.y ) );
// Set the new sheet size.
sheet->GetScreen()->SetPageSettings( pageInfo );
VECTOR2I pageSizeIU = sheet->GetScreen()->GetPageSettings().GetSizeIU( schIUScale.IU_PER_MILS );
VECTOR2I sheetcentre( pageSizeIU.x / 2, pageSizeIU.y / 2 );
VECTOR2I itemsCentre = sheetBoundingBox.Centre();
// round the translation to nearest point on the grid
VECTOR2I translation = sheetcentre - itemsCentre;
translation.x = roundToNearestGrid( translation.x );
translation.y = roundToNearestGrid( translation.y );
// Translate the items.
std::vector<SCH_ITEM*> allItems;
std::copy( sheet->GetScreen()->Items().begin(), sheet->GetScreen()->Items().end(),
std::back_inserter( allItems ) );
for( SCH_ITEM* item : allItems )
{
item->Move( translation );
item->ClearFlags();
sheet->GetScreen()->Update( item );
}
}
checkPoint();
m_reporter->Report( _( "CADSTAR fonts are different to the ones in KiCad. This will likely "
"result in alignment issues. Please review the imported text elements "
"carefully and correct manually if required." ),
RPT_SEVERITY_WARNING );
m_reporter->Report( _( "The CADSTAR design has been imported successfully.\n"
"Please review the import errors and warnings (if any)." ) );
}
void CADSTAR_SCH_ARCHIVE_LOADER::checkDesignLimits()
{
LONGPOINT designLimit = Assignments.Settings.DesignLimit;
//Note: can't use getKiCadPoint() due VECTOR2I being int - need long long to make the check
long long designSizeXkicad = (long long) designLimit.x / KiCadUnitDivider;
long long designSizeYkicad = (long long) designLimit.y / KiCadUnitDivider;
// Max size limited by the positive dimension of VECTOR2I (which is an int)
constexpr long long maxDesignSizekicad = std::numeric_limits<int>::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 millimeters. \n"
"Maximum permitted design size: %.2f, %.2f millimeters.\n" ),
(double) designSizeXkicad / SCH_IU_PER_MM,
(double) designSizeYkicad / SCH_IU_PER_MM,
(double) maxDesignSizekicad / SCH_IU_PER_MM,
(double) maxDesignSizekicad / SCH_IU_PER_MM ) );
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadSheets()
{
const std::vector<LAYER_ID>& orphanSheets = findOrphanSheets();
SCH_SHEET_PATH rootPath;
rootPath.push_back( m_rootSheet );
rootPath.SetPageNumber( wxT( "1" ) );
if( orphanSheets.size() > 1 )
{
int x = 1;
int y = 1;
for( LAYER_ID sheetID : orphanSheets )
{
VECTOR2I pos( x * schIUScale.MilsToIU( 1000 ), y * schIUScale.MilsToIU( 1000 ) );
VECTOR2I siz( schIUScale.MilsToIU( 1000 ), schIUScale.MilsToIU( 1000 ) );
loadSheetAndChildSheets( sheetID, pos, siz, rootPath );
x += 2;
if( x > 10 ) // start next row
{
x = 1;
y += 2;
}
}
}
else if( orphanSheets.size() > 0 )
{
LAYER_ID rootSheetID = orphanSheets.at( 0 );
wxFileName loadedFilePath = wxFileName( Filename );
std::string filename = wxString::Format( "%s_%02d", loadedFilePath.GetName(),
getSheetNumber( rootSheetID ) )
.ToStdString();
ReplaceIllegalFileNameChars( &filename );
filename += wxT( "." ) + wxString( FILEEXT::KiCadSchematicFileExtension );
wxFileName fn( m_schematic->Prj().GetProjectPath() + filename );
m_rootSheet->GetScreen()->SetFileName( fn.GetFullPath() );
m_sheetMap.insert( { rootSheetID, m_rootSheet } );
loadChildSheets( rootSheetID, rootPath );
}
else if( Header.Format.Type == "SYMBOL" )
{
THROW_IO_ERROR( _( "The selected file is a CADSTAR symbol library. It does not contain a "
"schematic design so cannot be imported/opened in this way." ) );
}
else
{
THROW_IO_ERROR( _( "The CADSTAR schematic might be corrupt: there is no root sheet." ) );
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadHierarchicalSheetPins()
{
for( std::pair<BLOCK_ID, BLOCK> blockPair : Schematic.Blocks )
{
BLOCK& block = blockPair.second;
LAYER_ID sheetID = "";
if( block.Type == BLOCK::TYPE::PARENT )
sheetID = block.LayerID;
else if( block.Type == BLOCK::TYPE::CHILD )
sheetID = block.AssocLayerID;
else
continue;
if( m_sheetMap.find( sheetID ) != m_sheetMap.end() )
{
SCH_SHEET* sheet = m_sheetMap.at( sheetID );
for( std::pair<TERMINAL_ID, TERMINAL> termPair : block.Terminals )
{
TERMINAL term = termPair.second;
wxString name = "YOU SHOULDN'T SEE THIS TEXT. THIS IS A BUG.";
SCH_HIERLABEL* sheetPin = nullptr;
if( block.Type == BLOCK::TYPE::PARENT )
sheetPin = new SCH_HIERLABEL();
else if( block.Type == BLOCK::TYPE::CHILD )
sheetPin = new SCH_SHEET_PIN( sheet );
sheetPin->SetText( name );
sheetPin->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED );
sheetPin->SetSpinStyle( getSpinStyle( term.OrientAngle, false ) );
sheetPin->SetPosition( getKiCadPoint( term.Position ) );
if( sheetPin->Type() == SCH_SHEET_PIN_T )
sheet->AddPin( (SCH_SHEET_PIN*) sheetPin );
else
sheet->GetScreen()->Append( sheetPin );
BLOCK_PIN_ID blockPinID = std::make_pair( block.ID, term.ID );
m_sheetPinMap.insert( { blockPinID, sheetPin } );
}
}
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadPartsLibrary()
{
for( std::pair<PART_ID, PART> partPair : Parts.PartDefinitions )
{
PART_ID partID = partPair.first;
PART part = partPair.second;
wxString escapedPartName = EscapeString( part.Name, CTX_LIBID );
LIB_SYMBOL* kiSym = new LIB_SYMBOL( escapedPartName );
kiSym->SetUnitCount( part.Definition.GateSymbols.size() );
bool ok = true;
for( std::pair<GATE_ID, PART::DEFINITION::GATE> gatePair : part.Definition.GateSymbols )
{
GATE_ID gateID = gatePair.first;
PART::DEFINITION::GATE gate = gatePair.second;
SYMDEF_ID symbolID = getSymDefFromName( gate.Name, gate.Alternate );
if( symbolID.IsEmpty() )
{
m_reporter->Report( wxString::Format( _( "Part definition '%s' references symbol "
"'%s' (alternate '%s') which could not be "
"found in the symbol library. The part has "
"not been loaded into the KiCad library." ),
part.Name,
gate.Name,
gate.Alternate ),
RPT_SEVERITY_WARNING);
ok = false;
break;
}
m_partSymbolsMap.insert( { { partID, gateID }, symbolID } );
loadSymbolGateAndPartFields( symbolID, part, gateID, kiSym );
}
if( ok && part.Definition.GateSymbols.size() != 0 )
{
m_loadedSymbols.push_back( kiSym );
}
else
{
if( part.Definition.GateSymbols.size() == 0 )
{
m_reporter->Report( wxString::Format( _( "Part definition '%s' has an incomplete "
"definition (no symbol definitions are "
"associated with it). The part has not "
"been loaded into the KiCad library." ),
part.Name ),
RPT_SEVERITY_WARNING );
}
// Don't save in the library, but still keep it cached as some of the units might have
// been loaded correctly (saving us time later on), plus the part definition contains
// the part name, which is important to load
}
m_partMap.insert( { partID, kiSym } );
checkPoint();
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadSchematicSymbolInstances()
{
for( std::pair<SYMBOL_ID, SYMBOL> symPair : Schematic.Symbols )
{
SYMBOL sym = symPair.second;
if( !sym.VariantID.empty() && sym.VariantParentSymbolID != sym.ID )
continue; // Only load master Variant
if( sym.IsComponent )
{
if( m_partMap.find( sym.PartRef.RefID ) == m_partMap.end() )
{
m_reporter->Report( wxString::Format( _( "Symbol '%s' references part '%s' which "
"could not be found in the library. The "
"symbol was not loaded" ),
sym.ComponentRef.Designator,
sym.PartRef.RefID ),
RPT_SEVERITY_ERROR );
continue;
}
if( sym.GateID.IsEmpty() )
sym.GateID = wxT( "A" ); // Assume Gate "A" if unspecified
PART_GATE_ID partSymbolID = { sym.PartRef.RefID, sym.GateID };
LIB_SYMBOL* kiSym = m_partMap.at( sym.PartRef.RefID );
bool copy = false;
// The symbol definition in the part either does not exist for this gate number
// or is different to the symbol instance. We need to reload the gate for this
// symbol
if( m_partSymbolsMap.find( partSymbolID ) == m_partSymbolsMap.end()
|| m_partSymbolsMap.at( partSymbolID ) != sym.SymdefID )
{
kiSym = new LIB_SYMBOL( *kiSym ); // Make a copy
copy = true;
const PART& part = Parts.PartDefinitions.at( sym.PartRef.RefID );
loadSymbolGateAndPartFields( sym.SymdefID, part, sym.GateID, kiSym );
}
LIB_SYMBOL* scaledPart = getScaledLibPart( kiSym, sym.ScaleRatioNumerator,
sym.ScaleRatioDenominator );
EDA_ANGLE symOrient = ANGLE_0;
SCH_SYMBOL* symbol = loadSchematicSymbol( sym, *scaledPart, symOrient );
delete scaledPart;
if( copy )
delete kiSym;
SCH_FIELD* refField = symbol->GetField( REFERENCE_FIELD );
sym.ComponentRef.Designator.Replace( wxT( "\n" ), wxT( "\\n" ) );
sym.ComponentRef.Designator.Replace( wxT( "\r" ), wxT( "\\r" ) );
sym.ComponentRef.Designator.Replace( wxT( "\t" ), wxT( "\\t" ) );
sym.ComponentRef.Designator.Replace( wxT( " " ), wxT( "_" ) );
refField->SetText( sym.ComponentRef.Designator );
loadSymbolFieldAttribute( sym.ComponentRef.AttrLoc, symOrient, sym.Mirror, refField );
if( sym.HasPartRef )
{
SCH_FIELD* partField = symbol->FindField( PartNameFieldName );
if( !partField )
{
int fieldID = symbol->GetFieldCount();
partField = symbol->AddField( SCH_FIELD( VECTOR2I(), fieldID, symbol,
PartNameFieldName ) );
}
wxASSERT( partField->GetName() == PartNameFieldName );
wxString partname = getPart( sym.PartRef.RefID ).Name;
partname.Replace( wxT( "\n" ), wxT( "\\n" ) );
partname.Replace( wxT( "\r" ), wxT( "\\r" ) );
partname.Replace( wxT( "\t" ), wxT( "\\t" ) );
partField->SetText( partname );
loadSymbolFieldAttribute( sym.PartRef.AttrLoc, symOrient, sym.Mirror, partField );
partField->SetVisible( SymbolPartNameColor.IsVisible );
}
for( auto& attr : sym.AttributeValues )
{
ATTRIBUTE_VALUE attrVal = attr.second;
if( attrVal.HasLocation )
{
wxString attrName = getAttributeName( attrVal.AttributeID );
SCH_FIELD* attrField = symbol->FindField( attrName );
if( !attrField )
{
int fieldID = symbol->GetFieldCount();
attrField = symbol->AddField( SCH_FIELD( VECTOR2I(), fieldID, symbol,
attrName ) );
}
wxASSERT( attrField->GetName() == attrName );
attrVal.Value.Replace( wxT( "\n" ), wxT( "\\n" ) );
attrVal.Value.Replace( wxT( "\r" ), wxT( "\\r" ) );
attrVal.Value.Replace( wxT( "\t" ), wxT( "\\t" ) );
attrField->SetText( attrVal.Value );
loadSymbolFieldAttribute( attrVal.AttributeLocation, symOrient, sym.Mirror,
attrField );
attrField->SetVisible( isAttributeVisible( attrVal.AttributeID ) );
}
}
}
else if( sym.IsSymbolVariant )
{
if( Library.SymbolDefinitions.find( sym.SymdefID ) == Library.SymbolDefinitions.end() )
{
THROW_IO_ERROR( wxString::Format( _( "Symbol ID '%s' references library symbol "
"'%s' which could not be found in the "
"library. Did you export all items of the "
"design?" ),
sym.ID,
sym.PartRef.RefID ) );
}
SYMDEF_SCM libSymDef = Library.SymbolDefinitions.at( sym.SymdefID );
if( libSymDef.Terminals.size() != 1 )
{
THROW_IO_ERROR( wxString::Format( _( "Symbol ID '%s' is a signal reference or "
"global signal but it has too many pins. The "
"expected number of pins is 1 but %d were "
"found." ),
sym.ID,
libSymDef.Terminals.size() ) );
}
if( sym.SymbolVariant.Type == SYMBOLVARIANT::TYPE::GLOBALSIGNAL )
{
SYMDEF_ID symID = sym.SymdefID;
LIB_SYMBOL* kiPart = nullptr;
// In CADSTAR "GlobalSignal" is a special type of symbol which defines
// a Power Symbol. The "Alternate" name defines the default net name of
// the power symbol but this can be overridden in the design itself.
wxString libraryNetName = Library.SymbolDefinitions.at( symID ).Alternate;
// Name of the net that the symbol instance in CADSTAR refers to:
wxString symbolInstanceNetName = sym.SymbolVariant.Reference;
symbolInstanceNetName = EscapeString( symbolInstanceNetName, CTX_LIBID );
// Name of the symbol we will use for saving the part in KiCad
// Note: In CADSTAR all power symbols will start have the reference name be
// "GLOBALSIGNAL" followed by the default net name, so it makes sense to save
// the symbol in KiCad as the default net name as well.
wxString libPartName = libraryNetName;
// In CADSTAR power symbol instances can refer to a different net to that defined
// in the library. This causes problems in KiCad v6 as it breaks connectivity when
// the user decides to update all symbols from library. We handle this by creating
// individual versions of the power symbol for each net name.
if( libPartName != symbolInstanceNetName )
{
libPartName += wxT( " (" ) + symbolInstanceNetName + wxT( ")" );
}
if( m_powerSymLibMap.find( libPartName ) == m_powerSymLibMap.end() )
{
const LIB_SYMBOL* templatePart = loadSymdef( symID );
wxCHECK( templatePart, /*void*/ );
kiPart = new LIB_SYMBOL( *templatePart );
kiPart->SetPower();
kiPart->SetName( libPartName );
kiPart->GetValueField().SetText( symbolInstanceNetName );
kiPart->SetShowPinNames( false );
kiPart->SetShowPinNumbers( false );
std::vector<SCH_PIN*> pins = kiPart->GetAllLibPins();
wxCHECK( pins.size() == 1, /*void*/ );
pins.at( 0 )->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN );
pins.at( 0 )->SetName( symbolInstanceNetName );
if( libSymDef.TextLocations.find( SIGNALNAME_ORIGIN_ATTRID )
!= libSymDef.TextLocations.end() )
{
TEXT_LOCATION& txtLoc =
libSymDef.TextLocations.at( SIGNALNAME_ORIGIN_ATTRID );
VECTOR2I valPos = getKiCadLibraryPoint( txtLoc.Position, libSymDef.Origin );
kiPart->GetValueField().SetPosition( valPos );
kiPart->GetValueField().SetVisible( true );
}
else
{
kiPart->GetValueField().SetVisible( false );
}
kiPart->GetReferenceField().SetText( "#PWR" );
kiPart->GetReferenceField().SetVisible( false );
m_loadedSymbols.push_back( kiPart );
m_powerSymLibMap.insert( { libPartName, kiPart } );
}
else
{
kiPart = m_powerSymLibMap.at( libPartName );
wxASSERT( kiPart->GetValueField().GetText() == symbolInstanceNetName );
}
LIB_SYMBOL* scaledPart = getScaledLibPart( kiPart, sym.ScaleRatioNumerator,
sym.ScaleRatioDenominator );
EDA_ANGLE returnedOrient = ANGLE_0;
SCH_SYMBOL* symbol = loadSchematicSymbol( sym, *scaledPart, returnedOrient );
m_powerSymMap.insert( { sym.ID, symbol } );
delete scaledPart;
}
else if( sym.SymbolVariant.Type == SYMBOLVARIANT::TYPE::SIGNALREF )
{
// There should only be one pin and we'll use that to set the position
TERMINAL& symbolTerminal = libSymDef.Terminals.begin()->second;
VECTOR2I terminalPosOffset = symbolTerminal.Position - libSymDef.Origin;
EDA_ANGLE rotate = getAngle( sym.OrientAngle );
if( sym.Mirror )
rotate += ANGLE_180;
RotatePoint( terminalPosOffset, -rotate );
SCH_GLOBALLABEL* netLabel = new SCH_GLOBALLABEL;
netLabel->SetPosition( getKiCadPoint( (VECTOR2I)sym.Origin + terminalPosOffset ) );
netLabel->SetText( "***UNKNOWN NET****" ); // This should be later updated when we load the netlist
netLabel->SetTextSize( VECTOR2I( schIUScale.MilsToIU( 50 ), schIUScale.MilsToIU( 50 ) ) );
SYMDEF_SCM symbolDef = Library.SymbolDefinitions.at( sym.SymdefID );
if( symbolDef.TextLocations.count( LINK_ORIGIN_ATTRID ) )
{
TEXT_LOCATION linkOrigin = symbolDef.TextLocations.at( LINK_ORIGIN_ATTRID );
applyTextSettings( netLabel, linkOrigin.TextCodeID, linkOrigin.Alignment,
linkOrigin.Justification );
}
netLabel->SetSpinStyle( getSpinStyle( sym.OrientAngle, sym.Mirror ) );
if( libSymDef.Alternate.Lower().Contains( "in" ) )
netLabel->SetShape( LABEL_FLAG_SHAPE::L_INPUT );
else if( libSymDef.Alternate.Lower().Contains( "bi" ) )
netLabel->SetShape( LABEL_FLAG_SHAPE::L_BIDI );
else if( libSymDef.Alternate.Lower().Contains( "out" ) )
netLabel->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT );
else
netLabel->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED );
SCH_SCREEN* screen = m_sheetMap.at( sym.LayerID )->GetScreen();
// autoplace intersheet refs
netLabel->AutoplaceFields( screen, false );
screen->Append( netLabel );
m_globalLabelsMap.insert( { sym.ID, netLabel } );
}
else
{
wxASSERT_MSG( false, "Unknown Symbol Variant." );
}
}
else
{
m_reporter->Report( wxString::Format( _( "Symbol ID '%s' is of an unknown type. It is "
"neither a symbol or a net power / symbol. "
"The symbol was not loaded." ),
sym.ID ),
RPT_SEVERITY_ERROR );
}
if( sym.ScaleRatioDenominator != 1 || sym.ScaleRatioNumerator != 1 )
{
wxString symbolName = sym.ComponentRef.Designator;
if( symbolName.empty() )
symbolName = wxString::Format( "ID: %s", sym.ID );
else
symbolName += sym.GateID;
m_reporter->Report( wxString::Format( _( "Symbol '%s' is scaled in the original "
"CADSTAR schematic but this is not supported "
"in KiCad. When the symbol is reloaded from "
"the library, it will revert to the original "
"1:1 scale." ),
symbolName,
sym.PartRef.RefID ),
RPT_SEVERITY_ERROR );
}
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadBusses()
{
for( std::pair<BUS_ID, BUS> busPair : Schematic.Buses )
{
BUS bus = busPair.second;
bool firstPt = true;
VERTEX last;
if( bus.LayerID != wxT( "NO_SHEET" ) )
{
SCH_SCREEN* screen = m_sheetMap.at( bus.LayerID )->GetScreen();
std::shared_ptr<BUS_ALIAS> kiBusAlias = std::make_shared<BUS_ALIAS>();
kiBusAlias->SetName( bus.Name );
kiBusAlias->SetParent( screen );
screen->AddBusAlias( kiBusAlias );
m_busesMap.insert( { bus.ID, kiBusAlias } );
SCH_LABEL* label = new SCH_LABEL();
wxString busname = HandleTextOverbar( bus.Name );
label->SetText( wxT( "{" ) + busname + wxT( "}" ) );
label->SetVisible( true );
screen->Append( label );
SHAPE_LINE_CHAIN busLineChain; // to compute nearest segment to bus label
for( const VERTEX& cur : bus.Shape.Vertices )
{
busLineChain.Append( getKiCadPoint( cur.End ) );
if( firstPt )
{
last = cur;
firstPt = false;
if( !bus.HasBusLabel )
{
// Add a bus label on the starting point if the original CADSTAR design
// does not have an explicit label
label->SetPosition( getKiCadPoint( last.End ) );
}
continue;
}
SCH_LINE* kiBus = new SCH_LINE();
kiBus->SetStartPoint( getKiCadPoint( last.End ) );
kiBus->SetEndPoint( getKiCadPoint( cur.End ) );
kiBus->SetLayer( LAYER_BUS );
kiBus->SetLineWidth( getLineThickness( bus.LineCodeID ) );
screen->Append( kiBus );
last = cur;
}
if( bus.HasBusLabel )
{
//lets find the closest point in the busline to the label
VECTOR2I busLabelLoc = getKiCadPoint( bus.BusLabel.Position );
VECTOR2I nearestPt = busLineChain.NearestPoint( busLabelLoc );
label->SetPosition( nearestPt );
applyTextSettings( label, bus.BusLabel.TextCodeID, bus.BusLabel.Alignment,
bus.BusLabel.Justification );
// Re-set bus name as it might have been "double-escaped" after applyTextSettings
label->SetText( wxT( "{" ) + busname + wxT( "}" ) );
// Note orientation of the bus label will be determined in loadNets
// (the position of the wire will determine how best to place the bus label)
}
}
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadNets()
{
for( std::pair<NET_ID, NET_SCH> netPair : Schematic.Nets )
{
NET_SCH net = netPair.second;
wxString netName = net.Name;
std::map<NETELEMENT_ID, SCH_LABEL*> netlabels;
if( netName.IsEmpty() )
netName = wxString::Format( "$%ld", net.SignalNum );
netName = HandleTextOverbar( netName );
for( std::pair<NETELEMENT_ID, NET_SCH::SYM_TERM> terminalPair : net.Terminals )
{
NET_SCH::SYM_TERM netTerm = terminalPair.second;
if( m_powerSymMap.find( netTerm.SymbolID ) != m_powerSymMap.end() )
{
SCH_FIELD* val = m_powerSymMap.at( netTerm.SymbolID )->GetField( VALUE_FIELD );
val->SetText( netName );
val->SetBold( false );
val->SetVisible( false );
if( netTerm.HasNetLabel )
{
val->SetVisible( true );
val->SetPosition( getKiCadPoint( netTerm.NetLabel.Position ) );
applyTextSettings( val, netTerm.NetLabel.TextCodeID, netTerm.NetLabel.Alignment,
netTerm.NetLabel.Justification, netTerm.NetLabel.OrientAngle,
netTerm.NetLabel.Mirror );
}
}
else if( m_globalLabelsMap.find( netTerm.SymbolID ) != m_globalLabelsMap.end() )
{
m_globalLabelsMap.at( netTerm.SymbolID )->SetText( netName );
LAYER_ID sheet = Schematic.Symbols.at( netTerm.SymbolID ).LayerID;
if( m_sheetMap.count( sheet ) )
{
SCH_SCREEN* screen = m_sheetMap.at( sheet )->GetScreen();
// autoplace intersheet refs again since we've changed the name
m_globalLabelsMap.at( netTerm.SymbolID )->AutoplaceFields( screen, false );
}
}
else if( !net.Name.IsEmpty() && Schematic.Symbols.count( netTerm.SymbolID )
&& netTerm.HasNetLabel )
{
// This is a named net that connects to a schematic symbol pin - we need to put a label
SCH_LABEL* label = new SCH_LABEL();
label->SetText( netName );
POINT pinLocation = getLocationOfNetElement( net, netTerm.ID );
label->SetPosition( getKiCadPoint( pinLocation ) );
label->SetVisible( true );
applyTextSettings( label, netTerm.NetLabel.TextCodeID, netTerm.NetLabel.Alignment,
netTerm.NetLabel.Justification );
netlabels.insert( { netTerm.ID, label } );
LAYER_ID sheet = Schematic.Symbols.at( netTerm.SymbolID ).LayerID;
m_sheetMap.at( sheet )->GetScreen()->Append( label );
}
}
auto getHierarchicalLabel =
[&]( const NETELEMENT_ID& aNode ) -> SCH_HIERLABEL*
{
if( aNode.Contains( "BLKT" ) )
{
NET_SCH::BLOCK_TERM blockTerm = net.BlockTerminals.at( aNode );
BLOCK_PIN_ID blockPinID = std::make_pair( blockTerm.BlockID,
blockTerm.TerminalID );
if( m_sheetPinMap.find( blockPinID ) != m_sheetPinMap.end() )
return m_sheetPinMap.at( blockPinID );
}
return nullptr;
};
//Add net name to all hierarchical pins (block terminals in CADSTAR)
for( std::pair<NETELEMENT_ID, NET_SCH::BLOCK_TERM> blockPair : net.BlockTerminals )
{
SCH_HIERLABEL* label = getHierarchicalLabel( blockPair.first );
if( label )
label->SetText( netName );
}
// Load all bus entries and add net label if required
for( std::pair<NETELEMENT_ID, NET_SCH::BUS_TERM> busPair : net.BusTerminals )
{
NET_SCH::BUS_TERM busTerm = busPair.second;
BUS bus = Schematic.Buses.at( busTerm.BusID );
if( !alg::contains( m_busesMap.at( bus.ID )->Members(), netName ) )
m_busesMap.at( bus.ID )->Members().emplace_back( netName );
SCH_BUS_WIRE_ENTRY* busEntry =
new SCH_BUS_WIRE_ENTRY( getKiCadPoint( busTerm.FirstPoint ), false );
VECTOR2I size =
getKiCadPoint( busTerm.SecondPoint ) - getKiCadPoint( busTerm.FirstPoint );
busEntry->SetSize( VECTOR2I( size.x, size.y ) );
m_sheetMap.at( bus.LayerID )->GetScreen()->Append( busEntry );
// Always add a label at bus terminals to ensure connectivity.
// If the original design does not have a label, just make it very small
// to keep connectivity but make the design look visually similar to
// the original.
SCH_LABEL* label = new SCH_LABEL();
label->SetText( netName );
label->SetPosition( getKiCadPoint( busTerm.SecondPoint ) );
label->SetVisible( true );
if( busTerm.HasNetLabel )
{
applyTextSettings( label, busTerm.NetLabel.TextCodeID, busTerm.NetLabel.Alignment,
busTerm.NetLabel.Justification );
}
else
{
label->SetTextSize( VECTOR2I( SMALL_LABEL_SIZE, SMALL_LABEL_SIZE ) );
}
netlabels.insert( { busTerm.ID, label } );
m_sheetMap.at( bus.LayerID )->GetScreen()->Append( label );
}
for( std::pair<NETELEMENT_ID, NET_SCH::DANGLER> danglerPair : net.Danglers )
{
NET_SCH::DANGLER dangler = danglerPair.second;
SCH_LABEL* label = new SCH_LABEL();
label->SetPosition( getKiCadPoint( dangler.Position ) );
label->SetVisible( true );
if( dangler.HasNetLabel )
{
applyTextSettings( label, dangler.NetLabel.TextCodeID, dangler.NetLabel.Alignment,
dangler.NetLabel.Justification );
}
label->SetText( netName ); // set text after applying settings to avoid double-escaping
netlabels.insert( { dangler.ID, label } );
m_sheetMap.at( dangler.LayerID )->GetScreen()->Append( label );
}
for( NET_SCH::CONNECTION_SCH conn : net.Connections )
{
if( conn.LayerID == wxT( "NO_SHEET" ) )
continue; // No point loading virtual connections. KiCad handles that internally
POINT start = getLocationOfNetElement( net, conn.StartNode );
POINT end = getLocationOfNetElement( net, conn.EndNode );
if( start.x == UNDEFINED_VALUE || end.x == UNDEFINED_VALUE )
continue;
// Connections in CADSTAR are always implied between symbols even if the route
// doesn't start and end exactly at the connection points
if( conn.Path.size() < 1 || conn.Path.front() != start )
conn.Path.insert( conn.Path.begin(), start );
if( conn.Path.size() < 2 || conn.Path.back() != end )
conn.Path.push_back( end );
bool firstPt = true;
bool secondPt = false;
VECTOR2I last;
SCH_LINE* wire = nullptr;
SHAPE_LINE_CHAIN wireChain; // Create a temp. line chain representing the connection
for( const POINT& pt : conn.Path )
wireChain.Append( getKiCadPoint( pt ) );
// AUTO-FIX SHEET PINS
//--------------------
// KiCad constrains the sheet pin on the edge of the sheet object whereas in
// CADSTAR it can be anywhere. Let's find the intersection of the wires with the sheet
// and place the hierarchical
std::vector<NETELEMENT_ID> nodes;
nodes.push_back( conn.StartNode );
nodes.push_back( conn.EndNode );
for( const NETELEMENT_ID& node : nodes )
{
SCH_HIERLABEL* sheetPin = getHierarchicalLabel( node );
if( sheetPin )
{
if( sheetPin->Type() == SCH_SHEET_PIN_T
&& SCH_SHEET::ClassOf( sheetPin->GetParent() ) )
{
SCH_SHEET* parentSheet = static_cast<SCH_SHEET*>( sheetPin->GetParent() );
VECTOR2I sheetSize = parentSheet->GetSize();
VECTOR2I sheetPosition = parentSheet->GetPosition();
int leftSide = sheetPosition.x;
int rightSide = sheetPosition.x + sheetSize.x;
int topSide = sheetPosition.y;
int botSide = sheetPosition.y + sheetSize.y;
SHAPE_LINE_CHAIN sheetEdge;
sheetEdge.Append( leftSide, topSide );
sheetEdge.Append( rightSide, topSide );
sheetEdge.Append( rightSide, botSide );
sheetEdge.Append( leftSide, botSide );
sheetEdge.Append( leftSide, topSide );
SHAPE_LINE_CHAIN::INTERSECTIONS wireToSheetIntersects;
if( !wireChain.Intersect( sheetEdge, wireToSheetIntersects ) )
{
// The block terminal is outside the block shape in the original
// CADSTAR design. Since KiCad's Sheet Pin will already be constrained
// on the edge, we will simply join to it with a straight line.
if( node == conn.StartNode )
wireChain = wireChain.Reverse();
wireChain.Append( sheetPin->GetPosition() );
if( node == conn.StartNode )
wireChain = wireChain.Reverse();
}
else
{
// The block terminal is either inside or on the shape edge. Lets use
// the first intersection point.
VECTOR2I intsctPt = wireToSheetIntersects.at( 0 ).p;
int intsctIndx = wireChain.FindSegment( intsctPt );
wxASSERT_MSG( intsctIndx != -1, "Can't find intersecting segment" );
if( node == conn.StartNode )
wireChain.Replace( 0, intsctIndx, intsctPt );
else
wireChain.Replace( intsctIndx + 1, /*end index*/ -1, intsctPt );
sheetPin->SetPosition( intsctPt );
}
}
}
}
auto fixNetLabelsAndSheetPins =
[&]( const EDA_ANGLE& aWireAngle, NETELEMENT_ID& aNetEleID )
{
SPIN_STYLE spin = getSpinStyle( aWireAngle );
if( netlabels.find( aNetEleID ) != netlabels.end() )
netlabels.at( aNetEleID )->SetSpinStyle( spin.MirrorY() );
SCH_HIERLABEL* sheetPin = getHierarchicalLabel( aNetEleID );
if( sheetPin )
sheetPin->SetSpinStyle( spin.MirrorX() );
};
// Now we can load the wires and fix the label orientations
for( const VECTOR2I& pt : wireChain.CPoints() )
{
if( firstPt )
{
last = pt;
firstPt = false;
secondPt = true;
continue;
}
if( secondPt )
{
secondPt = false;
EDA_ANGLE wireAngle( last - pt );
fixNetLabelsAndSheetPins( wireAngle, conn.StartNode );
}
wire = new SCH_LINE();
wire->SetStartPoint( last );
wire->SetEndPoint( pt );
wire->SetLayer( LAYER_WIRE );
if( !conn.ConnectionLineCode.IsEmpty() )
wire->SetLineWidth( getLineThickness( conn.ConnectionLineCode ) );
last = pt;
m_sheetMap.at( conn.LayerID )->GetScreen()->Append( wire );
}
//Fix labels on the end wire
if( wire )
{
EDA_ANGLE wireAngle( wire->GetEndPoint() - wire->GetStartPoint() );
fixNetLabelsAndSheetPins( wireAngle, conn.EndNode );
}
}
for( std::pair<NETELEMENT_ID, NET_SCH::JUNCTION_SCH> juncPair : net.Junctions )
{
NET_SCH::JUNCTION_SCH junc = juncPair.second;
SCH_JUNCTION* kiJunc = new SCH_JUNCTION();
kiJunc->SetPosition( getKiCadPoint( junc.Location ) );
m_sheetMap.at( junc.LayerID )->GetScreen()->Append( kiJunc );
if( junc.HasNetLabel )
{
// In CADSTAR the label can be placed anywhere, but in KiCad it has to be placed
// in the same location as the junction for it to be connected to it.
SCH_LABEL* label = new SCH_LABEL();
label->SetText( netName );
label->SetPosition( getKiCadPoint( junc.Location ) );
label->SetVisible( true );
EDA_ANGLE labelAngle = getAngle( junc.NetLabel.OrientAngle );
SPIN_STYLE spin = getSpinStyle( labelAngle );
label->SetSpinStyle( spin );
m_sheetMap.at( junc.LayerID )->GetScreen()->Append( label );
}
}
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadFigures()
{
for( std::pair<FIGURE_ID, FIGURE> figPair : Schematic.Figures )
{
FIGURE fig = figPair.second;
loadFigure( fig, fig.LayerID, LAYER_NOTES );
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadTexts()
{
for( std::pair<TEXT_ID, TEXT> textPair : Schematic.Texts )
{
TEXT txt = textPair.second;
SCH_TEXT* kiTxt = getKiCadSchText( txt );
loadItemOntoKiCadSheet( txt.LayerID, kiTxt );
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadDocumentationSymbols()
{
for( std::pair<DOCUMENTATION_SYMBOL_ID, DOCUMENTATION_SYMBOL> docSymPair :
Schematic.DocumentationSymbols )
{
DOCUMENTATION_SYMBOL docSym = docSymPair.second;
if( Library.SymbolDefinitions.find( docSym.SymdefID ) == Library.SymbolDefinitions.end() )
{
m_reporter->Report( wxString::Format( _( "Documentation Symbol '%s' refers to symbol "
"definition ID '%s' which does not exist in "
"the library. The symbol was not loaded." ),
docSym.ID,
docSym.SymdefID ),
RPT_SEVERITY_ERROR );
continue;
}
SYMDEF_SCM docSymDef = Library.SymbolDefinitions.at( docSym.SymdefID );
VECTOR2I moveVector = getKiCadPoint( docSym.Origin ) - getKiCadPoint( docSymDef.Origin );
EDA_ANGLE rotationAngle = getAngle( docSym.OrientAngle );
double scalingFactor = (double) docSym.ScaleRatioNumerator
/ (double) docSym.ScaleRatioDenominator;
VECTOR2I centreOfTransform = getKiCadPoint( docSymDef.Origin );
bool mirrorInvert = docSym.Mirror;
for( std::pair<FIGURE_ID, FIGURE> figPair : docSymDef.Figures )
{
FIGURE fig = figPair.second;
loadFigure( fig, docSym.LayerID, LAYER_NOTES, moveVector, rotationAngle, scalingFactor,
centreOfTransform, mirrorInvert );
}
for( std::pair<TEXT_ID, TEXT> textPair : docSymDef.Texts )
{
TEXT txt = textPair.second;
txt.Mirror = ( txt.Mirror ) ? !mirrorInvert : mirrorInvert;
txt.OrientAngle = docSym.OrientAngle - txt.OrientAngle;
SCH_TEXT* kiTxt = getKiCadSchText( txt );
VECTOR2I newPosition = applyTransform( kiTxt->GetPosition(), moveVector, rotationAngle,
scalingFactor, centreOfTransform, mirrorInvert );
int newTxtWidth = KiROUND( kiTxt->GetTextWidth() * scalingFactor );
int newTxtHeight = KiROUND( kiTxt->GetTextHeight() * scalingFactor );
int newTxtThickness = KiROUND( kiTxt->GetTextThickness() * scalingFactor );
kiTxt->SetPosition( newPosition );
kiTxt->SetTextWidth( newTxtWidth );
kiTxt->SetTextHeight( newTxtHeight );
kiTxt->SetTextThickness( newTxtThickness );
loadItemOntoKiCadSheet( docSym.LayerID, kiTxt );
}
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadTextVariables()
{
auto findAndReplaceTextField =
[&]( TEXT_FIELD_NAME aField, wxString aValue )
{
if( m_context.TextFieldToValuesMap.find( aField ) != m_context.TextFieldToValuesMap.end() )
{
if( m_context.TextFieldToValuesMap.at( aField ) != aValue )
{
m_context.TextFieldToValuesMap.at( aField ) = aValue;
m_context.InconsistentTextFields.insert( aField );
return false;
}
}
else
{
m_context.TextFieldToValuesMap.insert( { aField, aValue } );
}
return true;
};
PROJECT* pj = &m_schematic->Prj();
if( pj )
{
std::map<wxString, wxString>& txtVars = pj->GetTextVars();
// Most of the design text fields can be derived from other elements
if( Schematic.VariantHierarchy.Variants.size() > 0 )
{
VARIANT loadedVar = Schematic.VariantHierarchy.Variants.begin()->second;
findAndReplaceTextField( TEXT_FIELD_NAME::VARIANT_NAME, loadedVar.Name );
findAndReplaceTextField( TEXT_FIELD_NAME::VARIANT_DESCRIPTION, loadedVar.Description );
}
findAndReplaceTextField( TEXT_FIELD_NAME::DESIGN_TITLE, Header.JobTitle );
for( std::pair<TEXT_FIELD_NAME, wxString> txtvalue : m_context.TextFieldToValuesMap )
{
wxString varName = CADSTAR_TO_KICAD_FIELDS.at( txtvalue.first );
wxString varValue = txtvalue.second;
txtVars.insert( { varName, varValue } );
}
for( std::pair<wxString, wxString> txtvalue : m_context.FilenamesToTextMap )
{
wxString varName = txtvalue.first;
wxString varValue = txtvalue.second;
txtVars.insert( { varName, varValue } );
}
}
else
{
m_reporter->Report( _( "Text Variables could not be set as there is no project attached." ),
RPT_SEVERITY_ERROR );
}
}
SCH_FIELD*
CADSTAR_SCH_ARCHIVE_LOADER::addNewFieldToSymbol( const wxString& aFieldName,
std::unique_ptr<LIB_SYMBOL>& aKiCadSymbol )
{
// First Check if field already exists
SCH_FIELD* existingField = aKiCadSymbol->FindField( aFieldName );
if( existingField != nullptr )
return existingField;
int newfieldID = aKiCadSymbol->GetFieldCount();
SCH_FIELD* newfield = new SCH_FIELD( aKiCadSymbol.get(), newfieldID );
newfield->SetName( aFieldName );
newfield->SetVisible( false );
aKiCadSymbol->AddField( newfield );
/*
@todo we should load that a field is a URL by checking if it starts with "Link"
e.g.:
if( aFieldName.Lower().StartsWith( "link" ) )
newfield->SetAsURL*/
return newfield;
}
const LIB_SYMBOL* CADSTAR_SCH_ARCHIVE_LOADER::loadSymdef( const SYMDEF_ID& aSymdefID )
{
wxCHECK( Library.SymbolDefinitions.find( aSymdefID ) != Library.SymbolDefinitions.end(), nullptr );
if( m_symDefMap.count( aSymdefID ) )
return m_symDefMap.at( aSymdefID ).get(); // return a non-owning ptr
SYMDEF_SCM csSym = Library.SymbolDefinitions.at( aSymdefID );
std::unique_ptr<LIB_SYMBOL> kiSym = std::make_unique<LIB_SYMBOL>( csSym.BuildLibName() );
const int gateNumber = 1; // Always load to gate "A" - we will change the unit later
// Load Graphical Figures
for( std::pair<FIGURE_ID, FIGURE> figPair : csSym.Figures )
{
FIGURE fig = figPair.second;
int lineThickness = getLineThickness( fig.LineCodeID );
LINE_STYLE linestyle = getLineStyle( fig.LineCodeID );
if( fig.Shape.Type == SHAPE_TYPE::OPENSHAPE )
{
loadLibrarySymbolShapeVertices( fig.Shape.Vertices, csSym.Origin, kiSym.get(),
gateNumber,
lineThickness );
}
else
{
SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::POLY, LAYER_DEVICE );
shape->SetPolyShape( fig.Shape.ConvertToPolySet(
[&]( const VECTOR2I& aPt )
{
return getKiCadLibraryPoint( aPt, csSym.Origin );
},
ARC_ACCURACY ) );
shape->SetUnit( gateNumber );
shape->SetStroke( STROKE_PARAMS( lineThickness, linestyle ) );
if( fig.Shape.Type == SHAPE_TYPE::SOLID )
shape->SetFillMode( FILL_T::FILLED_SHAPE );
else if( fig.Shape.Type == SHAPE_TYPE::OUTLINE )
shape->SetFillMode( FILL_T::NO_FILL );
else if( fig.Shape.Type == SHAPE_TYPE::HATCHED ) // We don't have an equivalent
shape->SetFillMode( FILL_T::FILLED_WITH_BG_BODYCOLOR );
kiSym->AddDrawItem( shape );
}
}
PINNUM_TO_TERMINAL_MAP pinNumToTerminals;
// Load Pins
for( std::pair<TERMINAL_ID, TERMINAL> termPair : csSym.Terminals )
{
TERMINAL term = termPair.second;
wxString pinNum = wxString::Format( "%ld", term.ID );
wxString pinName = wxEmptyString;
std::unique_ptr<SCH_PIN> pin = std::make_unique<SCH_PIN>( kiSym.get() );
// Assume passive pin for now (we will set it later once we load the parts)
pin->SetType( ELECTRICAL_PINTYPE::PT_PASSIVE );
pin->SetPosition( getKiCadLibraryPoint( term.Position, csSym.Origin ) );
pin->SetLength( 0 ); //CADSTAR Pins are just a point (have no length)
pin->SetShape( GRAPHIC_PINSHAPE::LINE );
pin->SetUnit( gateNumber );
pin->SetNumber( pinNum );
pin->SetName( pinName );
// TC0 is the default CADSTAR text size for name/number if none specified
int pinNumberHeight = getTextHeightFromTextCode( wxT( "TC0" ) );
int pinNameHeight = getTextHeightFromTextCode( wxT( "TC0" ) );
if( csSym.PinNumberLocations.count( term.ID ) )
{
PIN_NUM_LABEL_LOC pinNumLocation = csSym.PinNumberLocations.at( term.ID );
pinNumberHeight = getTextHeightFromTextCode( pinNumLocation.TextCodeID );
}
if( csSym.PinLabelLocations.count( term.ID ) )
{
PIN_NUM_LABEL_LOC pinNameLocation = csSym.PinLabelLocations.at( term.ID );
pinNameHeight = getTextHeightFromTextCode( pinNameLocation.TextCodeID );
}
pin->SetNumberTextSize( pinNumberHeight );
pin->SetNameTextSize( pinNameHeight );
pinNumToTerminals.insert( { pin->GetNumber(), term.ID } );
kiSym->AddDrawItem( pin.release() );
}
m_symDefTerminalsMap.insert( { aSymdefID, pinNumToTerminals } );
fixUpLibraryPins( kiSym.get(), gateNumber );
// Load Text items
for( std::pair<TEXT_ID, TEXT> textPair : csSym.Texts )
{
TEXT csText = textPair.second;
VECTOR2I pos = getKiCadLibraryPoint( csText.Position, csSym.Origin );
auto libtext = std::make_unique<SCH_TEXT>( pos, csText.Text, LAYER_DEVICE );
libtext->SetUnit( gateNumber );
libtext->SetPosition( getKiCadLibraryPoint( csText.Position, csSym.Origin ) );
libtext->SetMultilineAllowed( true ); // temporarily so that we calculate bbox correctly
applyTextSettings( libtext.get(), csText.TextCodeID, csText.Alignment, csText.Justification,
csText.OrientAngle, csText.Mirror );
// Split out multi line text items into individual text elements
if( csText.Text.Contains( "\n" ) )
{
wxArrayString strings;
wxStringSplit( csText.Text, strings, '\n' );
wxPoint firstLinePos;
for( size_t ii = 0; ii < strings.size(); ++ii )
{
BOX2I bbox = libtext->GetTextBox( ii );
VECTOR2I linePos = { bbox.GetLeft(), -bbox.GetBottom() };
RotatePoint( linePos, libtext->GetTextPos(), -libtext->GetTextAngle() );
SCH_TEXT* textLine = static_cast<SCH_TEXT*>( libtext->Duplicate() );
textLine->SetText( strings[ii] );
textLine->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
textLine->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
textLine->SetTextPos( linePos );
// Multiline text not allowed in LIB_TEXT
textLine->SetMultilineAllowed( false );
kiSym->AddDrawItem( textLine );
}
}
else
{
// Multiline text not allowed in LIB_TEXT
libtext->SetMultilineAllowed( false );
kiSym->AddDrawItem( libtext.release() );
}
}
// CADSTAR uses TC1 when fields don't have explicit text/attribute location
static const TEXTCODE_ID defaultTextCode = "TC1";
// Load field locations (Attributes in CADSTAR)
// Symbol name (e.g. R1)
if( csSym.TextLocations.count( SYMBOL_NAME_ATTRID ) )
{
TEXT_LOCATION& textLoc = csSym.TextLocations.at( SYMBOL_NAME_ATTRID );
applyToLibraryFieldAttribute( textLoc, csSym.Origin, &kiSym->GetReferenceField() );
}
else
{
applyTextCodeIfExists( &kiSym->GetReferenceField(), defaultTextCode );
}
// Always add the part name field (even if it doesn't have a specific location defined)
SCH_FIELD* partField = addNewFieldToSymbol( PartNameFieldName, kiSym );
wxCHECK( partField, nullptr );
wxASSERT( partField->GetName() == PartNameFieldName );
if( csSym.TextLocations.count( PART_NAME_ATTRID ) )
{
TEXT_LOCATION& textLoc = csSym.TextLocations.at( PART_NAME_ATTRID );
applyToLibraryFieldAttribute( textLoc, csSym.Origin, partField );
}
else
{
applyTextCodeIfExists( partField, defaultTextCode );
}
partField->SetVisible( SymbolPartNameColor.IsVisible );
for( auto& [attributeId, textLocation] : csSym.TextLocations )
{
if( attributeId == PART_NAME_ATTRID || attributeId == SYMBOL_NAME_ATTRID
|| attributeId == SIGNALNAME_ORIGIN_ATTRID || attributeId == LINK_ORIGIN_ATTRID )
{
continue;
}
wxString attributeName = getAttributeName( attributeId );
SCH_FIELD* field = addNewFieldToSymbol( attributeName, kiSym );
applyToLibraryFieldAttribute( textLocation, csSym.Origin, field );
}
for( auto& [attributeId, attrValue] : csSym.AttributeValues )
{
if( attributeId == PART_NAME_ATTRID || attributeId == SYMBOL_NAME_ATTRID
|| attributeId == SIGNALNAME_ORIGIN_ATTRID || attributeId == LINK_ORIGIN_ATTRID )
{
continue;
}
wxString attributeName = getAttributeName( attributeId );
SCH_FIELD* field = addNewFieldToSymbol( attributeName, kiSym );
if( attrValue.HasLocation )
applyToLibraryFieldAttribute( attrValue.AttributeLocation, csSym.Origin, field );
else
applyTextCodeIfExists( field, defaultTextCode );
}
m_symDefMap.insert( { aSymdefID, std::move( kiSym ) } );
return m_symDefMap.at( aSymdefID ).get(); // return a non-owning ptr
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadSymbolGateAndPartFields( const SYMDEF_ID& aSymdefID,
const PART& aCadstarPart,
const GATE_ID& aGateID,
LIB_SYMBOL* aSymbol )
{
wxCHECK( Library.SymbolDefinitions.find( aSymdefID ) != Library.SymbolDefinitions.end(), /*void*/ );
std::unique_ptr<LIB_SYMBOL> kiSymDef( loadSymdef( aSymdefID )->Duplicate() );
wxCHECK( kiSymDef, /*void*/ );
//todo: need to use unique_ptr more. For now just create it here and release at end of function
std::unique_ptr<LIB_SYMBOL> tempSymbol( aSymbol );
// Update the pin numbers to match those defined in the Cadstar part
TERMINAL_TO_PINNUM_MAP pinNumMap;
for( auto&& [storedPinNum, termID] : m_symDefTerminalsMap[aSymdefID] )
{
PART::DEFINITION::PIN csPin = getPartDefinitionPin( aCadstarPart, aGateID, termID );
SCH_PIN* pin = kiSymDef->GetPin( storedPinNum );
wxString pinName = HandleTextOverbar( csPin.Label );
wxString pinNum = HandleTextOverbar( csPin.Name );
if( pinNum.IsEmpty() )
{
if( !csPin.Identifier.IsEmpty() )
pinNum = csPin.Identifier;
else if( csPin.ID == UNDEFINED_VALUE )
pinNum = wxString::Format( "%ld", termID );
else
pinNum = wxString::Format( "%ld", csPin.ID );
}
pin->SetType( getKiCadPinType( csPin.Type ) );
pin->SetNumber( pinNum );
pin->SetName( pinName );
pinNumMap.insert( { termID, pinNum } );
}
m_pinNumsMap.insert( { aCadstarPart.ID + aGateID, pinNumMap } );
// COPY ITEMS
int gateNumber = getKiCadUnitNumberFromGate( aGateID );
copySymbolItems( kiSymDef, tempSymbol, gateNumber );
// Hide the value field for now (it might get unhidden if an attribute exists in the cadstar
// design with the text "Value"
tempSymbol->GetValueField().SetVisible( false );
SCH_FIELD* partNameField = tempSymbol->FindField( PartNameFieldName );
if( partNameField )
partNameField->SetText( EscapeFieldText( aCadstarPart.Name ) );
const POINT& symDefOrigin = Library.SymbolDefinitions.at( aSymdefID ).Origin;
wxString footprintRefName = wxEmptyString;
wxString footprintAlternateName = wxEmptyString;
auto loadLibraryField = [&]( const ATTRIBUTE_VALUE& aAttributeVal )
{
wxString attrName = getAttributeName( aAttributeVal.AttributeID );
// Remove invalid field characters
wxString attributeValue = aAttributeVal.Value;
attributeValue.Replace( wxT( "\n" ), wxT( "\\n" ) );
attributeValue.Replace( wxT( "\r" ), wxT( "\\r" ) );
attributeValue.Replace( wxT( "\t" ), wxT( "\\t" ) );
//TODO: Handle "links": In cadstar a field can be a "link" if its name starts
// with the characters "Link ". Need to figure out how to convert them to
// equivalent in KiCad.
if( attrName == wxT( "(PartDefinitionNameStem)" ) )
{
//Space not allowed in Reference field
attributeValue.Replace( wxT( " " ), "_" );
tempSymbol->GetReferenceField().SetText( attributeValue );
return;
}
else if( attrName == wxT( "(PartDescription)" ) )
{
tempSymbol->SetDescription( attributeValue );
return;
}
else if( attrName == wxT( "(PartDefinitionReferenceName)" ) )
{
footprintRefName = attributeValue;
return;
}
else if( attrName == wxT( "(PartDefinitionAlternateName)" ) )
{
footprintAlternateName = attributeValue;
return;
}
bool attrIsNew = tempSymbol->FindField( attrName ) == nullptr;
SCH_FIELD* attrField = addNewFieldToSymbol( attrName, tempSymbol );
wxASSERT( attrField->GetName() == attrName );
attrField->SetText( aAttributeVal.Value );
attrField->SetUnit( gateNumber );
const ATTRIBUTE_ID& attrid = aAttributeVal.AttributeID;
attrField->SetVisible( isAttributeVisible( attrid ) );
if( aAttributeVal.HasLocation )
{
// Check if the part itself defined a location for the field
applyToLibraryFieldAttribute( aAttributeVal.AttributeLocation, symDefOrigin,
attrField );
}
else if( attrIsNew )
{
attrField->SetVisible( false );
applyTextSettings( attrField, wxT( "TC1" ), ALIGNMENT::NO_ALIGNMENT,
JUSTIFICATION::LEFT, false, true );
}
};
// Load all attributes in the Part Definition
for( auto& [attrId, attrVal] : aCadstarPart.Definition.AttributeValues )
loadLibraryField( attrVal );
// Load all attributes in the Part itself.
for( auto& [attrId, attrVal] : aCadstarPart.AttributeValues )
loadLibraryField( attrVal );
setFootprintOnSymbol( tempSymbol, footprintRefName, footprintAlternateName );
if( aCadstarPart.Definition.HidePinNames )
{
tempSymbol->SetShowPinNames( false );
tempSymbol->SetShowPinNumbers( false );
}
aSymbol = tempSymbol.release();
}
void CADSTAR_SCH_ARCHIVE_LOADER::setFootprintOnSymbol( std::unique_ptr<LIB_SYMBOL>& aKiCadSymbol,
const wxString& aFootprintName,
const wxString& aFootprintAlternate )
{
wxString fpNameInLibrary = generateLibName( aFootprintName, aFootprintAlternate );
if( !fpNameInLibrary.IsEmpty() )
{
wxArrayString fpFilters;
fpFilters.Add( aFootprintName ); // In cadstar one footprint has several "alternates"
if( !aFootprintAlternate.IsEmpty() )
fpFilters.Add( fpNameInLibrary );
aKiCadSymbol->SetFPFilters( fpFilters );
LIB_ID libID( m_footprintLibName, fpNameInLibrary );
aKiCadSymbol->GetFootprintField().SetText( libID.Format() );
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadLibrarySymbolShapeVertices( const std::vector<VERTEX>& aCadstarVertices,
const VECTOR2I& aSymbolOrigin,
LIB_SYMBOL* aSymbol,
int aGateNumber,
int aLineThickness )
{
const VERTEX* prev = &aCadstarVertices.at( 0 );
const VERTEX* cur;
wxASSERT_MSG( prev->Type == VERTEX_TYPE::POINT, "First vertex should always be a point." );
for( size_t i = 1; i < aCadstarVertices.size(); i++ )
{
cur = &aCadstarVertices.at( i );
SCH_SHAPE* shape = nullptr;
bool cw = false;
VECTOR2I startPoint = getKiCadLibraryPoint( prev->End, aSymbolOrigin );
VECTOR2I endPoint = getKiCadLibraryPoint( cur->End, aSymbolOrigin );
VECTOR2I centerPoint;
if( cur->Type == VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE
|| cur->Type == VERTEX_TYPE::CLOCKWISE_SEMICIRCLE )
{
centerPoint = ( startPoint + endPoint ) / 2;
}
else
{
centerPoint = getKiCadLibraryPoint( cur->Center, aSymbolOrigin );
}
switch( cur->Type )
{
case VERTEX_TYPE::POINT:
shape = new SCH_SHAPE( SHAPE_T::POLY, LAYER_DEVICE );
shape->AddPoint( startPoint );
shape->AddPoint( endPoint );
break;
case VERTEX_TYPE::CLOCKWISE_SEMICIRCLE:
case VERTEX_TYPE::CLOCKWISE_ARC:
cw = true;
KI_FALLTHROUGH;
case VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE:
case VERTEX_TYPE::ANTICLOCKWISE_ARC:
shape = new SCH_SHAPE( SHAPE_T::ARC, LAYER_DEVICE );
shape->SetPosition( centerPoint );
if( cw )
{
shape->SetStart( endPoint );
shape->SetEnd( startPoint );
}
else
{
shape->SetStart( startPoint );
shape->SetEnd( endPoint );
}
break;
}
shape->SetUnit( aGateNumber );
shape->SetStroke( STROKE_PARAMS( aLineThickness, LINE_STYLE::SOLID ) );
aSymbol->AddDrawItem( shape, false );
prev = cur;
}
aSymbol->GetDrawItems().sort();
}
void CADSTAR_SCH_ARCHIVE_LOADER::applyToLibraryFieldAttribute( const ATTRIBUTE_LOCATION& aCadstarAttrLoc,
const VECTOR2I& aSymbolOrigin,
SCH_FIELD* aKiCadField )
{
aKiCadField->SetTextPos( getKiCadLibraryPoint( aCadstarAttrLoc.Position, aSymbolOrigin ) );
applyTextSettings( aKiCadField, aCadstarAttrLoc.TextCodeID, aCadstarAttrLoc.Alignment,
aCadstarAttrLoc.Justification, aCadstarAttrLoc.OrientAngle,
aCadstarAttrLoc.Mirror );
}
SCH_SYMBOL* CADSTAR_SCH_ARCHIVE_LOADER::loadSchematicSymbol( const SYMBOL& aCadstarSymbol,
const LIB_SYMBOL& aKiCadPart,
EDA_ANGLE& aComponentOrientation )
{
wxString libName = CreateLibName( m_footprintLibName, m_rootSheet );
LIB_ID libId;
libId.SetLibItemName( aKiCadPart.GetName() );
libId.SetLibNickname( libName );
int unit = getKiCadUnitNumberFromGate( aCadstarSymbol.GateID );
SCH_SHEET_PATH sheetpath;
SCH_SHEET* kiSheet = m_sheetMap.at( aCadstarSymbol.LayerID );
m_rootSheet->LocatePathOfScreen( kiSheet->GetScreen(), &sheetpath );
SCH_SYMBOL* symbol = new SCH_SYMBOL( aKiCadPart, libId, &sheetpath, unit );
if( aCadstarSymbol.IsComponent )
symbol->SetRef( &sheetpath, aCadstarSymbol.ComponentRef.Designator );
symbol->SetPosition( getKiCadPoint( aCadstarSymbol.Origin ) );
EDA_ANGLE compAngle = getAngle( aCadstarSymbol.OrientAngle );
int compOrientation = 0;
if( aCadstarSymbol.Mirror )
{
compAngle = -compAngle;
compOrientation += SYMBOL_ORIENTATION_T::SYM_MIRROR_Y;
}
compOrientation += getComponentOrientation( compAngle, aComponentOrientation );
EDA_ANGLE test1( compAngle );
EDA_ANGLE test2( aComponentOrientation );
if( test1.Normalize180() != test2.Normalize180() )
{
m_reporter->Report( wxString::Format( _( "Symbol '%s' is rotated by an angle of %.1f "
"degrees in the original CADSTAR design but "
"KiCad only supports rotation angles multiples "
"of 90 degrees. The connecting wires will need "
"manual fixing." ),
aCadstarSymbol.ComponentRef.Designator,
compAngle.AsDegrees() ),
RPT_SEVERITY_ERROR );
}
symbol->SetOrientation( compOrientation );
if( m_sheetMap.find( aCadstarSymbol.LayerID ) == m_sheetMap.end() )
{
m_reporter->Report( wxString::Format( _( "Symbol '%s' references sheet ID '%s' which does "
"not exist in the design. The symbol was not "
"loaded." ),
aCadstarSymbol.ComponentRef.Designator,
aCadstarSymbol.LayerID ),
RPT_SEVERITY_ERROR );
delete symbol;
return nullptr;
}
wxString gate = ( aCadstarSymbol.GateID.IsEmpty() ) ? wxString( wxT( "A" ) ) : aCadstarSymbol.GateID;
wxString partGateIndex = aCadstarSymbol.PartRef.RefID + gate;
//Handle pin swaps
if( m_pinNumsMap.find( partGateIndex ) != m_pinNumsMap.end() )
{
TERMINAL_TO_PINNUM_MAP termNumMap = m_pinNumsMap.at( partGateIndex );
std::map<wxString, SCH_PIN*> pinNumToLibPinMap;
for( auto& term : termNumMap )
{
wxString pinNum = term.second;
pinNumToLibPinMap.insert( { pinNum,
symbol->GetLibSymbolRef()->GetPin( term.second ) } );
}
auto replacePinNumber =
[&]( wxString aOldPinNum, wxString aNewPinNum )
{
if( aOldPinNum == aNewPinNum )
return;
SCH_PIN* libpin = pinNumToLibPinMap.at( aOldPinNum );
libpin->SetNumber( HandleTextOverbar( aNewPinNum ) );
};
//Older versions of Cadstar used pin numbers
for( auto& pinPair : aCadstarSymbol.PinNumbers )
{
SYMBOL::PIN_NUM pin = pinPair.second;
replacePinNumber( termNumMap.at( pin.TerminalID ),
wxString::Format( "%ld", pin.PinNum ) );
}
//Newer versions of Cadstar use pin names
for( auto& pinPair : aCadstarSymbol.PinNames )
{
SYMPINNAME_LABEL pin = pinPair.second;
replacePinNumber( termNumMap.at( pin.TerminalID ), pin.NameOrLabel );
}
symbol->UpdatePins();
}
kiSheet->GetScreen()->Append( symbol );
return symbol;
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadSymbolFieldAttribute( const ATTRIBUTE_LOCATION& aCadstarAttrLoc,
const EDA_ANGLE& aComponentOrientation,
bool aIsMirrored,
SCH_FIELD* aKiCadField )
{
aKiCadField->SetPosition( getKiCadPoint( aCadstarAttrLoc.Position ) );
aKiCadField->SetVisible( true );
ALIGNMENT alignment = aCadstarAttrLoc.Alignment;
EDA_ANGLE textAngle = getAngle( aCadstarAttrLoc.OrientAngle );
if( aIsMirrored )
{
// We need to change the aligment when the symbol is mirrored based on the text orientation
// To ensure the anchor point is the same in KiCad.
int textIsVertical = KiROUND( textAngle.AsDegrees() / 90.0 ) % 2;
if( textIsVertical )
alignment = rotate180( alignment );
alignment = mirrorX( alignment );
}
applyTextSettings( aKiCadField, aCadstarAttrLoc.TextCodeID, alignment,
aCadstarAttrLoc.Justification,
getCadstarAngle( textAngle - aComponentOrientation ),
aCadstarAttrLoc.Mirror );
}
int CADSTAR_SCH_ARCHIVE_LOADER::getComponentOrientation( const EDA_ANGLE& aOrientAngle,
EDA_ANGLE& aReturnedOrientation )
{
int compOrientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_0;
EDA_ANGLE oDeg = aOrientAngle;
oDeg.Normalize180();
if( oDeg >= -ANGLE_45 && oDeg <= ANGLE_45 )
{
compOrientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_0;
aReturnedOrientation = ANGLE_0;
}
else if( oDeg >= ANGLE_45 && oDeg <= ANGLE_135 )
{
compOrientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_90;
aReturnedOrientation = ANGLE_90;
}
else if( oDeg >= ANGLE_135 || oDeg <= -ANGLE_135 )
{
compOrientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_180;
aReturnedOrientation = ANGLE_180;
}
else
{
compOrientation = SYMBOL_ORIENTATION_T::SYM_ORIENT_270;
aReturnedOrientation = ANGLE_270;
}
return compOrientation;
}
CADSTAR_SCH_ARCHIVE_LOADER::POINT
CADSTAR_SCH_ARCHIVE_LOADER::getLocationOfNetElement( const NET_SCH& aNet,
const NETELEMENT_ID& aNetElementID )
{
// clang-format off
auto logUnknownNetElementError =
[&]()
{
m_reporter->Report( wxString::Format( _( "Net %s references unknown net element %s. "
"The net was not properly loaded and may "
"require manual fixing." ),
getNetName( aNet ),
aNetElementID ),
RPT_SEVERITY_ERROR );
return POINT();
};
// clang-format on
if( aNetElementID.Contains( "J" ) ) // Junction
{
if( aNet.Junctions.find( aNetElementID ) == aNet.Junctions.end() )
return logUnknownNetElementError();
return aNet.Junctions.at( aNetElementID ).Location;
}
else if( aNetElementID.Contains( "P" ) ) // Terminal/Pin of a symbol
{
if( aNet.Terminals.find( aNetElementID ) == aNet.Terminals.end() )
return logUnknownNetElementError();
SYMBOL_ID symid = aNet.Terminals.at( aNetElementID ).SymbolID;
TERMINAL_ID termid = aNet.Terminals.at( aNetElementID ).TerminalID;
if( Schematic.Symbols.find( symid ) == Schematic.Symbols.end() )
return logUnknownNetElementError();
SYMBOL sym = Schematic.Symbols.at( symid );
SYMDEF_ID symdefid = sym.SymdefID;
VECTOR2I symbolOrigin = sym.Origin;
if( Library.SymbolDefinitions.find( symdefid ) == Library.SymbolDefinitions.end() )
return logUnknownNetElementError();
VECTOR2I libpinPosition =
Library.SymbolDefinitions.at( symdefid ).Terminals.at( termid ).Position;
VECTOR2I libOrigin = Library.SymbolDefinitions.at( symdefid ).Origin;
VECTOR2I pinOffset = libpinPosition - libOrigin;
pinOffset.x = ( pinOffset.x * sym.ScaleRatioNumerator ) / sym.ScaleRatioDenominator;
pinOffset.y = ( pinOffset.y * sym.ScaleRatioNumerator ) / sym.ScaleRatioDenominator;
VECTOR2I pinPosition = symbolOrigin + pinOffset;
EDA_ANGLE compAngle = getAngle( sym.OrientAngle );
if( sym.Mirror )
pinPosition.x = ( 2 * symbolOrigin.x ) - pinPosition.x;
EDA_ANGLE adjustedOrientation;
getComponentOrientation( compAngle, adjustedOrientation );
RotatePoint( pinPosition, symbolOrigin, -adjustedOrientation );
POINT retval;
retval.x = pinPosition.x;
retval.y = pinPosition.y;
return retval;
}
else if( aNetElementID.Contains( "BT" ) ) // Bus Terminal
{
if( aNet.BusTerminals.find( aNetElementID ) == aNet.BusTerminals.end() )
return logUnknownNetElementError();
return aNet.BusTerminals.at( aNetElementID ).SecondPoint;
}
else if( aNetElementID.Contains( "BLKT" ) ) // Block Terminal (sheet hierarchy connection)
{
if( aNet.BlockTerminals.find( aNetElementID ) == aNet.BlockTerminals.end() )
return logUnknownNetElementError();
BLOCK_ID blockid = aNet.BlockTerminals.at( aNetElementID ).BlockID;
TERMINAL_ID termid = aNet.BlockTerminals.at( aNetElementID ).TerminalID;
if( Schematic.Blocks.find( blockid ) == Schematic.Blocks.end() )
return logUnknownNetElementError();
return Schematic.Blocks.at( blockid ).Terminals.at( termid ).Position;
}
else if( aNetElementID.Contains( "D" ) ) // Dangler
{
if( aNet.Danglers.find( aNetElementID ) == aNet.Danglers.end() )
return logUnknownNetElementError();
return aNet.Danglers.at( aNetElementID ).Position;
}
else
{
return logUnknownNetElementError();
}
}
wxString CADSTAR_SCH_ARCHIVE_LOADER::getNetName( const NET_SCH& aNet )
{
wxString netname = aNet.Name;
if( netname.IsEmpty() )
netname = wxString::Format( "$%ld", aNet.SignalNum );
return netname;
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadShapeVertices( const std::vector<VERTEX>& aCadstarVertices,
LINECODE_ID aCadstarLineCodeID,
LAYER_ID aCadstarSheetID,
SCH_LAYER_ID aKiCadSchLayerID,
const VECTOR2I& aMoveVector,
const EDA_ANGLE& aRotation,
const double& aScalingFactor,
const VECTOR2I& aTransformCentre,
const bool& aMirrorInvert )
{
int lineWidth = KiROUND( getLineThickness( aCadstarLineCodeID ) * aScalingFactor );
LINE_STYLE lineStyle = getLineStyle( aCadstarLineCodeID );
const VERTEX* prev = &aCadstarVertices.at( 0 );
const VERTEX* cur;
wxASSERT_MSG( prev->Type == VERTEX_TYPE::POINT,
"First vertex should always be a point vertex" );
auto pointTransform =
[&]( const VECTOR2I& aV )
{
return applyTransform( getKiCadPoint( aV ), aMoveVector, aRotation,
aScalingFactor, aTransformCentre, aMirrorInvert );
};
for( size_t ii = 1; ii < aCadstarVertices.size(); ii++ )
{
cur = &aCadstarVertices.at( ii );
VECTOR2I transformedStartPoint = pointTransform( prev->End );
VECTOR2I transformedEndPoint = pointTransform( cur->End );
switch( cur->Type )
{
case VERTEX_TYPE::CLOCKWISE_SEMICIRCLE:
case VERTEX_TYPE::CLOCKWISE_ARC:
case VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE:
case VERTEX_TYPE::ANTICLOCKWISE_ARC:
{
SHAPE_ARC tempArc = cur->BuildArc( transformedStartPoint, pointTransform );
SCH_SHAPE* arcShape = new SCH_SHAPE( SHAPE_T::ARC, LAYER_NOTES, lineWidth );
arcShape->SetArcGeometry( tempArc.GetP0(), tempArc.GetArcMid(), tempArc.GetP1() );
loadItemOntoKiCadSheet( aCadstarSheetID, arcShape );
break;
}
case VERTEX_TYPE::POINT:
{
SCH_LINE* segment = new SCH_LINE();
segment->SetLayer( aKiCadSchLayerID );
segment->SetLineWidth( lineWidth );
segment->SetLineStyle( lineStyle );
segment->SetStartPoint( transformedStartPoint );
segment->SetEndPoint( transformedEndPoint );
loadItemOntoKiCadSheet( aCadstarSheetID, segment );
break;
}
default:
wxFAIL_MSG( "Unknown CADSTAR Vertex type" );
}
prev = cur;
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadFigure( const FIGURE& aCadstarFigure,
const LAYER_ID& aCadstarSheetIDOverride,
SCH_LAYER_ID aKiCadSchLayerID,
const VECTOR2I& aMoveVector,
const EDA_ANGLE& aRotation,
const double& aScalingFactor,
const VECTOR2I& aTransformCentre,
const bool& aMirrorInvert )
{
loadShapeVertices( aCadstarFigure.Shape.Vertices, aCadstarFigure.LineCodeID,
aCadstarSheetIDOverride, aKiCadSchLayerID, aMoveVector, aRotation,
aScalingFactor, aTransformCentre, aMirrorInvert );
for( const CUTOUT& cutout : aCadstarFigure.Shape.Cutouts )
{
loadShapeVertices( cutout.Vertices, aCadstarFigure.LineCodeID, aCadstarSheetIDOverride,
aKiCadSchLayerID, aMoveVector, aRotation, aScalingFactor,
aTransformCentre, aMirrorInvert );
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadSheetAndChildSheets( const LAYER_ID& aCadstarSheetID,
const VECTOR2I& aPosition,
const VECTOR2I& aSheetSize,
const SCH_SHEET_PATH& aParentSheet )
{
wxCHECK_MSG( m_sheetMap.find( aCadstarSheetID ) == m_sheetMap.end(), ,
"Sheet already loaded!" );
SCH_SHEET* sheet = new SCH_SHEET(
/* aParent */ aParentSheet.Last(),
/* aPosition */ aPosition,
/* aSize */ VECTOR2I( aSheetSize ) );
SCH_SCREEN* screen = new SCH_SCREEN( m_schematic );
SCH_SHEET_PATH instance( aParentSheet );
sheet->SetScreen( screen );
wxString name = Sheets.SheetNames.at( aCadstarSheetID );
SCH_FIELD& sheetNameField = sheet->GetFields()[SHEETNAME];
SCH_FIELD& filenameField = sheet->GetFields()[SHEETFILENAME];
sheetNameField.SetText( name );
int sheetNum = getSheetNumber( aCadstarSheetID );
wxString loadedFilename = wxFileName( Filename ).GetName();
std::string filename = wxString::Format( "%s_%02d", loadedFilename, sheetNum ).ToStdString();
ReplaceIllegalFileNameChars( &filename );
filename += wxT( "." ) + wxString( FILEEXT::KiCadSchematicFileExtension );
filenameField.SetText( filename );
wxFileName fn( m_schematic->Prj().GetProjectPath() + filename );
sheet->GetScreen()->SetFileName( fn.GetFullPath() );
aParentSheet.Last()->GetScreen()->Append( sheet );
instance.push_back( sheet );
wxString pageNumStr = wxString::Format( "%d", getSheetNumber( aCadstarSheetID ) );
instance.SetPageNumber( pageNumStr );
sheet->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false );
m_sheetMap.insert( { aCadstarSheetID, sheet } );
loadChildSheets( aCadstarSheetID, instance );
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadChildSheets( const LAYER_ID& aCadstarSheetID,
const SCH_SHEET_PATH& aSheet )
{
wxCHECK_MSG( m_sheetMap.find( aCadstarSheetID ) != m_sheetMap.end(), ,
"FIXME! Parent sheet should be loaded before attempting to load subsheets" );
for( std::pair<BLOCK_ID, BLOCK> blockPair : Schematic.Blocks )
{
BLOCK& block = blockPair.second;
if( block.LayerID == aCadstarSheetID && block.Type == BLOCK::TYPE::CHILD )
{
if( block.AssocLayerID == wxT( "NO_LINK" ) )
{
if( block.Figures.size() > 0 )
{
m_reporter->Report( wxString::Format( _( "The block ID %s (Block name: '%s') "
"is drawn on sheet '%s' but is not "
"linked to another sheet in the "
"design. KiCad requires all sheet "
"symbols to be associated to a sheet, "
"so the block was not loaded." ),
block.ID, block.Name,
Sheets.SheetNames.at( aCadstarSheetID ) ),
RPT_SEVERITY_ERROR );
}
continue;
}
// In KiCad you can only draw rectangular shapes whereas in Cadstar arbitrary shapes
// are allowed. We will calculate the extents of the Cadstar shape and draw a rectangle
std::pair<VECTOR2I, VECTOR2I> blockExtents;
if( block.Figures.size() > 0 )
{
blockExtents = getFigureExtentsKiCad( block.Figures.begin()->second );
}
else
{
THROW_IO_ERROR( wxString::Format( _( "The CADSTAR schematic might be corrupt: "
"Block %s references a child sheet but has no "
"Figure defined." ),
block.ID ) );
}
loadSheetAndChildSheets( block.AssocLayerID, blockExtents.first, blockExtents.second,
aSheet );
// Hide all KiCad sheet properties (sheet name/filename is not applicable in CADSTAR)
SCH_SHEET* loadedSheet = m_sheetMap.at( block.AssocLayerID );
SCH_FIELDS fields = loadedSheet->GetFields();
for( SCH_FIELD& field : fields )
{
field.SetVisible( false );
}
if( block.HasBlockLabel )
{
//@todo use below code when KiCad supports multi-line fields
/*
// Add the block label as a separate field
SCH_FIELD blockNameField( getKiCadPoint( block.BlockLabel.Position ), 2,
loadedSheet, wxString( "Block name" ) );
blockNameField.SetText( block.Name );
blockNameField.SetVisible( true );
applyTextSettings( &blockNameField,
block.BlockLabel.TextCodeID,
block.BlockLabel.Alignment,
block.BlockLabel.Justification,
block.BlockLabel.OrientAngle,
block.BlockLabel.Mirror );
fields.push_back( blockNameField );*/
// For now as as a text item (supports multi-line properly)
SCH_TEXT* kiTxt = new SCH_TEXT();
kiTxt->SetParent( m_schematic );
kiTxt->SetPosition( getKiCadPoint( block.BlockLabel.Position ) );
kiTxt->SetText( block.Name );
applyTextSettings( kiTxt, block.BlockLabel.TextCodeID, block.BlockLabel.Alignment,
block.BlockLabel.Justification, block.BlockLabel.OrientAngle,
block.BlockLabel.Mirror );
loadItemOntoKiCadSheet( aCadstarSheetID, kiTxt );
}
loadedSheet->SetFields( fields );
}
}
}
std::vector<CADSTAR_SCH_ARCHIVE_LOADER::LAYER_ID> CADSTAR_SCH_ARCHIVE_LOADER::findOrphanSheets()
{
std::vector<LAYER_ID> childSheets, orphanSheets;
//Find all sheets that are child of another
for( std::pair<BLOCK_ID, BLOCK> blockPair : Schematic.Blocks )
{
BLOCK& block = blockPair.second;
LAYER_ID& assocSheetID = block.AssocLayerID;
if( block.Type == BLOCK::TYPE::CHILD )
childSheets.push_back( assocSheetID );
}
//Add sheets that do not have a parent
for( const LAYER_ID& sheetID : Sheets.SheetOrder )
{
if( std::find( childSheets.begin(), childSheets.end(), sheetID ) == childSheets.end() )
orphanSheets.push_back( sheetID );
}
return orphanSheets;
}
int CADSTAR_SCH_ARCHIVE_LOADER::getSheetNumber( const LAYER_ID& aCadstarSheetID )
{
int i = 1;
for( const LAYER_ID& sheetID : Sheets.SheetOrder )
{
if( sheetID == aCadstarSheetID )
return i;
++i;
}
return -1;
}
void CADSTAR_SCH_ARCHIVE_LOADER::loadItemOntoKiCadSheet( const LAYER_ID& aCadstarSheetID,
SCH_ITEM* aItem )
{
wxCHECK_MSG( aItem, /*void*/, wxT( "aItem is null" ) );
if( aCadstarSheetID == "ALL_SHEETS" )
{
SCH_ITEM* duplicateItem = nullptr;
for( std::pair<LAYER_ID, SHEET_NAME> sheetPair : Sheets.SheetNames )
{
LAYER_ID sheetID = sheetPair.first;
duplicateItem = aItem->Duplicate();
m_sheetMap.at( sheetID )->GetScreen()->Append( aItem->Duplicate() );
}
//Get rid of the extra copy:
delete aItem;
aItem = duplicateItem;
}
else if( aCadstarSheetID == "NO_SHEET" )
{
wxFAIL_MSG( wxT( "Trying to add an item to NO_SHEET? This might be a documentation symbol." ) );
}
else
{
if( m_sheetMap.find( aCadstarSheetID ) != m_sheetMap.end() )
{
m_sheetMap.at( aCadstarSheetID )->GetScreen()->Append( aItem );
}
else
{
delete aItem;
wxFAIL_MSG( wxT( "Unknown Sheet ID." ) );
}
}
}
CADSTAR_SCH_ARCHIVE_LOADER::SYMDEF_ID
CADSTAR_SCH_ARCHIVE_LOADER::getSymDefFromName( const wxString& aSymdefName,
const wxString& aSymDefAlternate )
{
if( m_SymDefNamesCache.size() != Library.SymbolDefinitions.size() )
{
// Re-initialise
m_SymDefNamesCache.clear();
m_DefaultSymDefNamesCache.clear();
// Create a lower case cache to avoid searching each time
for( auto& [id, symdef] : Library.SymbolDefinitions )
{
wxString refKey = symdef.ReferenceName.Lower();
wxString altKey = symdef.Alternate.Lower();
m_SymDefNamesCache[{ refKey, altKey }] = id;
// Secondary cache to find symbols just by the Name (e.g. if the alternate
// does not exist, we still want to return a symbo - the same behaviour
// as CADSTAR
if( !m_DefaultSymDefNamesCache.count( refKey ) )
{
m_DefaultSymDefNamesCache.insert( { refKey, id } );
}
else if( altKey.IsEmpty() )
{
// Always use the empty alternate if it exists
m_DefaultSymDefNamesCache[refKey] = id;
}
}
}
wxString refKeyToFind = aSymdefName.Lower();
wxString altKeyToFind = aSymDefAlternate.Lower();
if( m_SymDefNamesCache.count( { refKeyToFind, altKeyToFind } ) )
{
return m_SymDefNamesCache[{ refKeyToFind, altKeyToFind }];
}
else if( m_DefaultSymDefNamesCache.count( refKeyToFind ) )
{
return m_DefaultSymDefNamesCache[refKeyToFind];
}
return SYMDEF_ID();
}
bool CADSTAR_SCH_ARCHIVE_LOADER::isAttributeVisible( const ATTRIBUTE_ID& aCadstarAttributeID )
{
// Use CADSTAR visibility settings to determine if an attribute is visible
if( AttrColors.AttributeColors.find( aCadstarAttributeID ) != AttrColors.AttributeColors.end() )
return AttrColors.AttributeColors.at( aCadstarAttributeID ).IsVisible;
return false; // If there is no visibility setting, assume not displayed
}
int CADSTAR_SCH_ARCHIVE_LOADER::getLineThickness( const LINECODE_ID& aCadstarLineCodeID )
{
wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID )
!= Assignments.Codedefs.LineCodes.end(),
schIUScale.MilsToIU( DEFAULT_WIRE_WIDTH_MILS ) );
return getKiCadLength( Assignments.Codedefs.LineCodes.at( aCadstarLineCodeID ).Width );
}
LINE_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getLineStyle( const LINECODE_ID& aCadstarLineCodeID )
{
wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID )
!= Assignments.Codedefs.LineCodes.end(),
LINE_STYLE::SOLID );
// clang-format off
switch( Assignments.Codedefs.LineCodes.at( aCadstarLineCodeID ).Style )
{
case LINESTYLE::DASH: return LINE_STYLE::DASH;
case LINESTYLE::DASHDOT: return LINE_STYLE::DASHDOT;
case LINESTYLE::DASHDOTDOT: return LINE_STYLE::DASHDOT; //TODO: update in future
case LINESTYLE::DOT: return LINE_STYLE::DOT;
case LINESTYLE::SOLID: return LINE_STYLE::SOLID;
default: return LINE_STYLE::DEFAULT;
}
// clang-format on
}
CADSTAR_SCH_ARCHIVE_LOADER::TEXTCODE
CADSTAR_SCH_ARCHIVE_LOADER::getTextCode( const TEXTCODE_ID& aCadstarTextCodeID )
{
wxCHECK( Assignments.Codedefs.TextCodes.find( aCadstarTextCodeID )
!= Assignments.Codedefs.TextCodes.end(),
TEXTCODE() );
return Assignments.Codedefs.TextCodes.at( aCadstarTextCodeID );
}
int CADSTAR_SCH_ARCHIVE_LOADER::getTextHeightFromTextCode( const TEXTCODE_ID& aCadstarTextCodeID )
{
TEXTCODE txtCode = getTextCode( aCadstarTextCodeID );
return KiROUND( (double) getKiCadLength( txtCode.Height ) * TXT_HEIGHT_RATIO );
}
wxString CADSTAR_SCH_ARCHIVE_LOADER::getAttributeName( const ATTRIBUTE_ID& aCadstarAttributeID )
{
wxCHECK( Assignments.Codedefs.AttributeNames.find( aCadstarAttributeID )
!= Assignments.Codedefs.AttributeNames.end(),
aCadstarAttributeID );
return Assignments.Codedefs.AttributeNames.at( aCadstarAttributeID ).Name;
}
CADSTAR_SCH_ARCHIVE_LOADER::PART
CADSTAR_SCH_ARCHIVE_LOADER::getPart( const PART_ID& aCadstarPartID )
{
wxCHECK( Parts.PartDefinitions.find( aCadstarPartID ) != Parts.PartDefinitions.end(), PART() );
return Parts.PartDefinitions.at( aCadstarPartID );
}
CADSTAR_SCH_ARCHIVE_LOADER::ROUTECODE
CADSTAR_SCH_ARCHIVE_LOADER::getRouteCode( const ROUTECODE_ID& aCadstarRouteCodeID )
{
wxCHECK( Assignments.Codedefs.RouteCodes.find( aCadstarRouteCodeID )
!= Assignments.Codedefs.RouteCodes.end(),
ROUTECODE() );
return Assignments.Codedefs.RouteCodes.at( aCadstarRouteCodeID );
}
CADSTAR_SCH_ARCHIVE_LOADER::PART::DEFINITION::PIN
CADSTAR_SCH_ARCHIVE_LOADER::getPartDefinitionPin( const PART& aCadstarPart, const GATE_ID& aGateID,
const TERMINAL_ID& aTerminalID )
{
for( std::pair<PART_DEFINITION_PIN_ID, PART::DEFINITION::PIN> pinPair :
aCadstarPart.Definition.Pins )
{
PART::DEFINITION::PIN partPin = pinPair.second;
if( partPin.TerminalGate == aGateID && partPin.TerminalPin == aTerminalID )
return partPin;
}
return PART::DEFINITION::PIN();
}
ELECTRICAL_PINTYPE CADSTAR_SCH_ARCHIVE_LOADER::getKiCadPinType( const CADSTAR_PIN_TYPE& aPinType )
{
switch( aPinType )
{
case CADSTAR_PIN_TYPE::UNCOMMITTED: return ELECTRICAL_PINTYPE::PT_PASSIVE;
case CADSTAR_PIN_TYPE::INPUT: return ELECTRICAL_PINTYPE::PT_INPUT;
case CADSTAR_PIN_TYPE::OUTPUT_OR: return ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR;
case CADSTAR_PIN_TYPE::OUTPUT_NOT_OR: return ELECTRICAL_PINTYPE::PT_OUTPUT;
case CADSTAR_PIN_TYPE::OUTPUT_NOT_NORM_OR: return ELECTRICAL_PINTYPE::PT_OUTPUT;
case CADSTAR_PIN_TYPE::POWER: return ELECTRICAL_PINTYPE::PT_POWER_IN;
case CADSTAR_PIN_TYPE::GROUND: return ELECTRICAL_PINTYPE::PT_POWER_IN;
case CADSTAR_PIN_TYPE::TRISTATE_BIDIR: return ELECTRICAL_PINTYPE::PT_BIDI;
case CADSTAR_PIN_TYPE::TRISTATE_INPUT: return ELECTRICAL_PINTYPE::PT_INPUT;
case CADSTAR_PIN_TYPE::TRISTATE_DRIVER: return ELECTRICAL_PINTYPE::PT_OUTPUT;
}
return ELECTRICAL_PINTYPE::PT_UNSPECIFIED;
}
int CADSTAR_SCH_ARCHIVE_LOADER::getKiCadUnitNumberFromGate( const GATE_ID& aCadstarGateID )
{
if( aCadstarGateID.IsEmpty() )
return 1;
return (int) aCadstarGateID.Upper().GetChar( 0 ) - (int) wxUniChar( 'A' ) + 1;
}
SPIN_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getSpinStyle( const long long& aCadstarOrientation,
bool aMirror )
{
EDA_ANGLE orientation = getAngle( aCadstarOrientation );
SPIN_STYLE spinStyle = getSpinStyle( orientation );
if( aMirror )
{
spinStyle = spinStyle.RotateCCW();
spinStyle = spinStyle.RotateCCW();
}
return spinStyle;
}
SPIN_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getSpinStyle( const EDA_ANGLE& aOrientation )
{
SPIN_STYLE spinStyle = SPIN_STYLE::LEFT;
EDA_ANGLE oDeg = aOrientation;
oDeg.Normalize180();
if( oDeg >= -ANGLE_45 && oDeg <= ANGLE_45 )
spinStyle = SPIN_STYLE::RIGHT; // 0deg
else if( oDeg >= ANGLE_45 && oDeg <= ANGLE_135 )
spinStyle = SPIN_STYLE::UP; // 90deg
else if( oDeg >= ANGLE_135 || oDeg <= -ANGLE_135 )
spinStyle = SPIN_STYLE::LEFT; // 180deg
else
spinStyle = SPIN_STYLE::BOTTOM; // 270deg
return spinStyle;
}
CADSTAR_SCH_ARCHIVE_LOADER::ALIGNMENT
CADSTAR_SCH_ARCHIVE_LOADER::mirrorX( const ALIGNMENT& aCadstarAlignment )
{
switch( aCadstarAlignment )
{
// Change left to right:
case ALIGNMENT::NO_ALIGNMENT:
case ALIGNMENT::BOTTOMLEFT: return ALIGNMENT::BOTTOMRIGHT;
case ALIGNMENT::CENTERLEFT: return ALIGNMENT::CENTERRIGHT;
case ALIGNMENT::TOPLEFT: return ALIGNMENT::TOPRIGHT;
//Change right to left:
case ALIGNMENT::BOTTOMRIGHT: return ALIGNMENT::BOTTOMLEFT;
case ALIGNMENT::CENTERRIGHT: return ALIGNMENT::CENTERLEFT;
case ALIGNMENT::TOPRIGHT: return ALIGNMENT::TOPLEFT;
// Center alignment does not mirror:
case ALIGNMENT::BOTTOMCENTER:
case ALIGNMENT::CENTERCENTER:
case ALIGNMENT::TOPCENTER: return aCadstarAlignment;
// Shouldn't be here
default: wxFAIL_MSG( "Unknown Cadstar Alignment" ); return aCadstarAlignment;
}
}
CADSTAR_SCH_ARCHIVE_LOADER::ALIGNMENT
CADSTAR_SCH_ARCHIVE_LOADER::rotate180( const ALIGNMENT& aCadstarAlignment )
{
switch( aCadstarAlignment )
{
case ALIGNMENT::NO_ALIGNMENT:
case ALIGNMENT::BOTTOMLEFT: return ALIGNMENT::TOPRIGHT;
case ALIGNMENT::BOTTOMCENTER: return ALIGNMENT::TOPCENTER;
case ALIGNMENT::BOTTOMRIGHT: return ALIGNMENT::TOPLEFT;
case ALIGNMENT::TOPLEFT: return ALIGNMENT::BOTTOMRIGHT;
case ALIGNMENT::TOPCENTER: return ALIGNMENT::BOTTOMCENTER;
case ALIGNMENT::TOPRIGHT: return ALIGNMENT::BOTTOMLEFT;
case ALIGNMENT::CENTERLEFT: return ALIGNMENT::CENTERRIGHT;
case ALIGNMENT::CENTERCENTER: return ALIGNMENT::CENTERCENTER;
case ALIGNMENT::CENTERRIGHT: return ALIGNMENT::CENTERLEFT;
// Shouldn't be here
default: wxFAIL_MSG( "Unknown Cadstar Alignment" ); return aCadstarAlignment;
}
}
void CADSTAR_SCH_ARCHIVE_LOADER::applyTextCodeIfExists( EDA_TEXT* aKiCadTextItem,
const TEXTCODE_ID& aCadstarTextCodeID )
{
// Ensure we have no Cadstar overbar characters
wxString escapedText = HandleTextOverbar( aKiCadTextItem->GetText() );
aKiCadTextItem->SetText( escapedText );
if( !Assignments.Codedefs.TextCodes.count( aCadstarTextCodeID ) )
return;
TEXTCODE textCode = getTextCode( aCadstarTextCodeID );
int textHeight = KiROUND( (double) getKiCadLength( textCode.Height ) * TXT_HEIGHT_RATIO );
int textWidth = getKiCadLength( textCode.Width );
// The width is zero for all non-cadstar fonts. Using a width equal to 2/3 the height seems
// to work well for most fonts.
if( textWidth == 0 )
textWidth = getKiCadLength( 2LL * textCode.Height / 3LL );
aKiCadTextItem->SetTextWidth( textWidth );
aKiCadTextItem->SetTextHeight( textHeight );
#if 0
// EEschema currently supports only normal vs bold for text thickness.
aKiCadTextItem->SetTextThickness( getKiCadLength( textCode.LineWidth ) );
#endif
// Must come after SetTextSize()
aKiCadTextItem->SetBold( textCode.Font.Modifier1 == FONT_BOLD );
aKiCadTextItem->SetItalic( textCode.Font.Italic );
}
void CADSTAR_SCH_ARCHIVE_LOADER::applyTextSettings( EDA_TEXT* aKiCadTextItem,
const TEXTCODE_ID& aCadstarTextCodeID,
const ALIGNMENT& aCadstarAlignment,
const JUSTIFICATION& aCadstarJustification,
const long long aCadstarOrientAngle,
bool aMirrored )
{
applyTextCodeIfExists( aKiCadTextItem, aCadstarTextCodeID );
aKiCadTextItem->SetTextAngle( getAngle( aCadstarOrientAngle ) );
// Justification ignored for now as not supported in Eeschema, but leaving this code in
// place for future upgrades.
// TODO update this when Eeschema supports justification independent of anchor position.
ALIGNMENT textAlignment = aCadstarAlignment;
// KiCad mirrors the justification and alignment when the symbol is mirrored but CADSTAR
// specifies it post-mirroring. In contrast, if the text item itself is mirrored (not
// supported in KiCad), CADSTAR specifies the alignment and justification pre-mirroring
if( aMirrored )
textAlignment = mirrorX( aCadstarAlignment );
auto setAlignment =
[&]( EDA_TEXT* aText, ALIGNMENT aAlignment )
{
switch( aAlignment )
{
case ALIGNMENT::NO_ALIGNMENT: // Bottom left of the first line
//No exact KiCad equivalent, so lets move the position of the text
FixTextPositionNoAlignment( aText );
KI_FALLTHROUGH;
case ALIGNMENT::BOTTOMLEFT:
aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case ALIGNMENT::BOTTOMCENTER:
aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
break;
case ALIGNMENT::BOTTOMRIGHT:
aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
case ALIGNMENT::CENTERLEFT:
aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case ALIGNMENT::CENTERCENTER:
aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
break;
case ALIGNMENT::CENTERRIGHT:
aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
case ALIGNMENT::TOPLEFT:
aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case ALIGNMENT::TOPCENTER:
aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
break;
case ALIGNMENT::TOPRIGHT:
aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
}
};
SPIN_STYLE spin = getSpinStyle( aCadstarOrientAngle, aMirrored );
EDA_ITEM* textEdaItem = dynamic_cast<EDA_ITEM*>( aKiCadTextItem );
wxCHECK( textEdaItem, /* void */ ); // ensure this is a EDA_ITEM
if( textEdaItem->Type() == SCH_FIELD_T )
{
// Spin style not used. All text justifications are permitted. However, only orientations
// of 0 deg or 90 deg are supported
EDA_ANGLE angle = aKiCadTextItem->GetTextAngle();
angle.Normalize();
int quadrant = KiROUND( angle.AsDegrees() / 90.0 );
quadrant %= 4;
switch( quadrant )
{
case 0:
angle = ANGLE_HORIZONTAL;
break;
case 1:
angle = ANGLE_VERTICAL;
break;
case 2:
angle = ANGLE_HORIZONTAL;
textAlignment = rotate180( textAlignment );
break;
case 3:
angle = ANGLE_VERTICAL;
textAlignment = rotate180( textAlignment );
break;
default:
wxFAIL_MSG( "Unknown Quadrant" );
}
aKiCadTextItem->SetTextAngle( angle );
setAlignment( aKiCadTextItem, textAlignment );
}
else if( textEdaItem->Type() == SCH_TEXT_T )
{
// Note spin style in a SCH_TEXT results in a vertical alignment GR_TEXT_V_ALIGN_BOTTOM
// so need to adjust the location of the text element based on Cadstar's original text
// alignment (anchor position).
setAlignment( aKiCadTextItem, textAlignment );
BOX2I bb = textEdaItem->GetBoundingBox();
int off = static_cast<SCH_TEXT*>( aKiCadTextItem )->GetTextOffset();
VECTOR2I pos;
// Change the anchor point of the text item to make it match the same bounding box
// And correct the error introduced by the text offsetting in KiCad
switch( spin )
{
case SPIN_STYLE::BOTTOM: pos = { bb.GetRight() - off, bb.GetTop() }; break;
case SPIN_STYLE::UP: pos = { bb.GetRight() - off, bb.GetBottom() }; break;
case SPIN_STYLE::LEFT: pos = { bb.GetRight() , bb.GetBottom() + off }; break;
case SPIN_STYLE::RIGHT: pos = { bb.GetLeft() , bb.GetBottom() + off }; break;
default: wxFAIL_MSG( "Unexpected Spin Style" ); break;
}
aKiCadTextItem->SetTextPos( pos );
switch( spin )
{
case SPIN_STYLE::RIGHT: // Horiz Normal Orientation
aKiCadTextItem->SetTextAngle( ANGLE_HORIZONTAL );
aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case SPIN_STYLE::UP: // Vert Orientation UP
aKiCadTextItem->SetTextAngle( ANGLE_VERTICAL );
aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
break;
case SPIN_STYLE::LEFT: // Horiz Orientation - Right justified
aKiCadTextItem->SetTextAngle( ANGLE_HORIZONTAL );
aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
case SPIN_STYLE::BOTTOM: // Vert Orientation BOTTOM
aKiCadTextItem->SetTextAngle( ANGLE_VERTICAL );
aKiCadTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
break;
default:
wxFAIL_MSG( "Unexpected Spin Style" );
break;
}
aKiCadTextItem->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
}
else if( SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( aKiCadTextItem ) )
{
// We don't want to change position of net labels as that would break connectivity
label->SetSpinStyle( spin );
}
else
{
wxFAIL_MSG( "Unexpected item type" );
}
}
SCH_TEXT* CADSTAR_SCH_ARCHIVE_LOADER::getKiCadSchText( const TEXT& aCadstarTextElement )
{
SCH_TEXT* kiTxt = new SCH_TEXT();
kiTxt->SetParent( m_schematic ); // set to the schematic for now to avoid asserts
kiTxt->SetPosition( getKiCadPoint( aCadstarTextElement.Position ) );
kiTxt->SetText( aCadstarTextElement.Text );
applyTextSettings( kiTxt, aCadstarTextElement.TextCodeID, aCadstarTextElement.Alignment,
aCadstarTextElement.Justification, aCadstarTextElement.OrientAngle,
aCadstarTextElement.Mirror );
return kiTxt;
}
LIB_SYMBOL* CADSTAR_SCH_ARCHIVE_LOADER::getScaledLibPart( const LIB_SYMBOL* aSymbol,
long long aScalingFactorNumerator,
long long aScalingFactorDenominator )
{
LIB_SYMBOL* retval = new LIB_SYMBOL( *aSymbol );
if( aScalingFactorNumerator == aScalingFactorDenominator )
return retval; // 1:1 scale, nothing to do
auto scaleLen =
[&]( int aLength ) -> int
{
return( aLength * aScalingFactorNumerator ) / aScalingFactorDenominator;
};
auto scalePt =
[&]( VECTOR2I aCoord ) -> VECTOR2I
{
return VECTOR2I( scaleLen( aCoord.x ), scaleLen( aCoord.y ) );
};
auto scaleSize =
[&]( VECTOR2I aSize ) -> VECTOR2I
{
return VECTOR2I( scaleLen( aSize.x ), scaleLen( aSize.y ) );
};
LIB_ITEMS_CONTAINER& items = retval->GetDrawItems();
for( SCH_ITEM& item : items )
{
switch( item.Type() )
{
case KICAD_T::SCH_SHAPE_T:
{
SCH_SHAPE& shape = static_cast<SCH_SHAPE&>( item );
if( shape.GetShape() == SHAPE_T::ARC )
{
shape.SetPosition( scalePt( shape.GetPosition() ) );
shape.SetStart( scalePt( shape.GetStart() ) );
shape.SetEnd( scalePt( shape.GetEnd() ) );
}
else if( shape.GetShape() == SHAPE_T::POLY )
{
SHAPE_LINE_CHAIN& poly = shape.GetPolyShape().Outline( 0 );
for( size_t ii = 0; ii < poly.GetPointCount(); ++ii )
poly.SetPoint( ii, scalePt( poly.CPoint( ii ) ) );
}
break;
}
case KICAD_T::SCH_PIN_T:
{
SCH_PIN& pin = static_cast<SCH_PIN&>( item );
pin.SetPosition( scalePt( pin.GetPosition() ) );
pin.SetLength( scaleLen( pin.GetLength() ) );
break;
}
case KICAD_T::SCH_TEXT_T:
{
SCH_TEXT& txt = static_cast<SCH_TEXT&>( item );
txt.SetPosition( scalePt( txt.GetPosition() ) );
txt.SetTextSize( scaleSize( txt.GetTextSize() ) );
break;
}
default:
break;
}
}
return retval;
}
void CADSTAR_SCH_ARCHIVE_LOADER::fixUpLibraryPins( LIB_SYMBOL* aSymbolToFix, int aGateNumber )
{
auto compLambda =
[]( const VECTOR2I& aA, const VECTOR2I& aB )
{
return LexicographicalCompare( aA, aB ) < 0;
};
// Store a list of vertical or horizontal segments in the symbol
// Note: Need the custom comparison function to ensure the map is sorted correctly
std::map<VECTOR2I, SHAPE_LINE_CHAIN, decltype( compLambda )> uniqueSegments( compLambda );
LIB_ITEMS_CONTAINER::ITERATOR shapeIt = aSymbolToFix->GetDrawItems().begin( SCH_SHAPE_T );
for( ; shapeIt != aSymbolToFix->GetDrawItems().end( SCH_SHAPE_T ); ++shapeIt )
{
SCH_SHAPE& shape = static_cast<SCH_SHAPE&>( *shapeIt );
if( aGateNumber > 0 && shape.GetUnit() != aGateNumber )
continue;
if( shape.GetShape() != SHAPE_T::POLY )
continue;
SHAPE_LINE_CHAIN poly = shape.GetPolyShape().Outline( 0 );
if( poly.GetPointCount() == 2 )
{
VECTOR2I pt0 = poly.CPoint( 0 );
VECTOR2I pt1 = poly.CPoint( 1 );
if( pt0 != pt1 && uniqueSegments.count( pt0 ) == 0 && uniqueSegments.count( pt1 ) == 0 )
{
// we are only interested in vertical or horizontal segments
if( pt0.x == pt1.x || pt0.y == pt1.y )
{
uniqueSegments.insert( { pt0, poly } );
uniqueSegments.insert( { pt1, poly } );
}
}
}
}
for( SCH_PIN* pin : aSymbolToFix->GetPins( aGateNumber ) )
{
auto setPinOrientation =
[&]( const EDA_ANGLE& aAngle )
{
EDA_ANGLE angle( aAngle );
angle.Normalize180();
if( angle >= -ANGLE_45 && angle <= ANGLE_45 )
pin->SetOrientation( PIN_ORIENTATION::PIN_RIGHT ); // 0 degrees
else if( angle >= ANGLE_45 && angle <= ANGLE_135 )
pin->SetOrientation( PIN_ORIENTATION::PIN_UP ); // 90 degrees
else if( angle >= ANGLE_135 || angle <= -ANGLE_135 )
pin->SetOrientation( PIN_ORIENTATION::PIN_LEFT ); // 180 degrees
else
pin->SetOrientation( PIN_ORIENTATION::PIN_DOWN ); // -90 degrees
};
if( uniqueSegments.count( pin->GetPosition() ) )
{
SHAPE_LINE_CHAIN& poly = uniqueSegments.at( pin->GetPosition() );
VECTOR2I otherPt = poly.CPoint( 0 );
if( otherPt == pin->GetPosition() )
otherPt = poly.CPoint( 1 );
VECTOR2I vec( otherPt - pin->GetPosition() );
pin->SetLength( vec.EuclideanNorm() );
setPinOrientation( EDA_ANGLE( vec ) );
}
}
}
std::pair<VECTOR2I, VECTOR2I>
CADSTAR_SCH_ARCHIVE_LOADER::getFigureExtentsKiCad( const FIGURE& aCadstarFigure )
{
VECTOR2I upperLeft( Assignments.Settings.DesignLimit.x, 0 );
VECTOR2I lowerRight( 0, Assignments.Settings.DesignLimit.y );
for( const VERTEX& v : aCadstarFigure.Shape.Vertices )
{
if( upperLeft.x > v.End.x )
upperLeft.x = v.End.x;
if( upperLeft.y < v.End.y )
upperLeft.y = v.End.y;
if( lowerRight.x < v.End.x )
lowerRight.x = v.End.x;
if( lowerRight.y > v.End.y )
lowerRight.y = v.End.y;
}
for( CUTOUT cutout : aCadstarFigure.Shape.Cutouts )
{
for( const VERTEX& v : aCadstarFigure.Shape.Vertices )
{
if( upperLeft.x > v.End.x )
upperLeft.x = v.End.x;
if( upperLeft.y < v.End.y )
upperLeft.y = v.End.y;
if( lowerRight.x < v.End.x )
lowerRight.x = v.End.x;
if( lowerRight.y > v.End.y )
lowerRight.y = v.End.y;
}
}
VECTOR2I upperLeftKiCad = getKiCadPoint( upperLeft );
VECTOR2I lowerRightKiCad = getKiCadPoint( lowerRight );
VECTOR2I size = lowerRightKiCad - upperLeftKiCad;
return { upperLeftKiCad, VECTOR2I( abs( size.x ), abs( size.y ) ) };
}
VECTOR2I CADSTAR_SCH_ARCHIVE_LOADER::getKiCadPoint( const VECTOR2I& aCadstarPoint )
{
VECTOR2I retval;
retval.x = getKiCadLength( aCadstarPoint.x - m_designCenter.x );
retval.y = -getKiCadLength( aCadstarPoint.y - m_designCenter.y );
return retval;
}
VECTOR2I CADSTAR_SCH_ARCHIVE_LOADER::getKiCadLibraryPoint( const VECTOR2I& aCadstarPoint,
const VECTOR2I& aCadstarCentre )
{
VECTOR2I retval;
retval.x = getKiCadLength( aCadstarPoint.x - aCadstarCentre.x );
retval.y = -getKiCadLength( aCadstarPoint.y - aCadstarCentre.y );
return retval;
}
VECTOR2I CADSTAR_SCH_ARCHIVE_LOADER::applyTransform( const VECTOR2I& aPoint,
const VECTOR2I& aMoveVector,
const EDA_ANGLE& aRotation,
const double& aScalingFactor,
const VECTOR2I& aTransformCentre,
const bool& aMirrorInvert )
{
VECTOR2I retVal = aPoint;
if( aScalingFactor != 1.0 )
{
//scale point
retVal -= aTransformCentre;
retVal.x = KiROUND( retVal.x * aScalingFactor );
retVal.y = KiROUND( retVal.y * aScalingFactor );
retVal += aTransformCentre;
}
if( aMirrorInvert )
MIRROR( retVal.x, aTransformCentre.x );
if( !aRotation.IsZero() )
RotatePoint( retVal, aTransformCentre, aRotation );
if( aMoveVector != VECTOR2I{ 0, 0 } )
retVal += aMoveVector;
return retVal;
}
double CADSTAR_SCH_ARCHIVE_LOADER::getPolarRadius( const VECTOR2I& aPoint )
{
return sqrt( ( (double) aPoint.x * (double) aPoint.x )
+ ( (double) aPoint.y * (double) aPoint.y ) );
}