/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020-2021 Roberto Fernandez Bautista * 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 . */ /** * @file cadstar_sch_archive_loader.cpp * @brief Loads a csa file into a KiCad SCHEMATIC object */ #include #include #include #include #include #include #include #include #include #include #include #include #include //SYMBOL_ORIENTATION_T #include #include #include #include #include #include #include #include #include #include #include #include #include const wxString PartNameFieldName = "Part Name"; const wxString PartNumberFieldName = "Part Number"; const wxString PartVersionFieldName = "Part Version"; const wxString PartAcceptanceFieldName = "Part Acceptance"; std::vector 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( _( "The selected 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 retVal; for( const CADSTAR_PART_ENTRY& part : csLib.m_PartEntries ) { std::unique_ptr loadedPart = loadLibPart( part ); checkPoint(); if( loadedPart ) retVal.push_back( loadedPart.release() ); } return retVal; } std::unique_ptr CADSTAR_SCH_ARCHIVE_LOADER::loadLibPart( const CADSTAR_PART_ENTRY& aPart ) { wxString escapedPartName = EscapeString( aPart.m_Name, CTX_LIBID ); std::unique_ptr 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 kiSymDef( loadSymdef( symbolID )->Duplicate() ); if( sym.m_Pins.size() != kiSymDef->GetPinCount() ) { m_reporter->Report( wxString::Format( _( "Inconsitent 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 ); LIB_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 ) ) ); 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& aFieldName, const std::optional& aFieldValue ) { if( aFieldValue.has_value() ) addNewFieldToSymbol( aFieldName, retSym )->SetText( aFieldValue.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_TYPE_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 pin = std::make_unique( 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& aSourceSym, std::unique_ptr& aDestSym, int aDestUnit, bool aOverrideFields ) { // Ensure there are no items on the unit we want to load onto for( LIB_ITEM* item : aDestSym->GetUnitDrawItems( aDestUnit, 0 /*aConvert*/ ) ) aDestSym->RemoveDrawItem( item ); // Copy all draw items for( LIB_ITEM* newItem : aSourceSym->GetUnitDrawItems( 1, 0 /*aConvert*/ ) ) { LIB_ITEM* itemCopy = static_cast( newItem->Clone() ); itemCopy->SetParent( aDestSym.get() ); itemCopy->SetUnit( aDestUnit ); aDestSym->AddDrawItem( itemCopy ); } //Copy / override all fields if( aOverrideFields ) { std::vector fieldsToCopy; aSourceSym->GetFields( fieldsToCopy ); for( LIB_FIELD* templateField : fieldsToCopy ) { LIB_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 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( 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( 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 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::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& 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( "." ) + 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 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 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->SetTextSpinStyle( 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 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 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 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 ); LIB_PINS 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->SetTextSpinStyle( 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 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 kiBusAlias = std::make_shared(); 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 netPair : Schematic.Nets ) { NET_SCH net = netPair.second; wxString netName = net.Name; std::map netlabels; if( netName.IsEmpty() ) netName = wxString::Format( "$%ld", net.SignalNum ); netName = HandleTextOverbar( netName ); for( std::pair 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 = [&]( 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 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 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 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( 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 nodes; nodes.push_back( conn.StartNode ); nodes.push_back( conn.EndNode ); for( 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( 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 ) { TEXT_SPIN_STYLE spin = getSpinStyle( aWireAngle ); if( netlabels.find( aNetEleID ) != netlabels.end() ) netlabels.at( aNetEleID )->SetTextSpinStyle( spin.MirrorY() ); SCH_HIERLABEL* sheetPin = getHierarchicalLabel( aNetEleID ); if( sheetPin ) sheetPin->SetTextSpinStyle( 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 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 ); TEXT_SPIN_STYLE spin = getSpinStyle( labelAngle ); label->SetTextSpinStyle( spin ); m_sheetMap.at( junc.LayerID )->GetScreen()->Append( label ); } } } } void CADSTAR_SCH_ARCHIVE_LOADER::loadFigures() { for( std::pair figPair : Schematic.Figures ) { FIGURE fig = figPair.second; loadFigure( fig, fig.LayerID, LAYER_NOTES ); } } void CADSTAR_SCH_ARCHIVE_LOADER::loadTexts() { for( std::pair 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 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 figPair : docSymDef.Figures ) { FIGURE fig = figPair.second; loadFigure( fig, docSym.LayerID, LAYER_NOTES, moveVector, rotationAngle, scalingFactor, centreOfTransform, mirrorInvert ); } for( std::pair 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& 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 txtvalue : m_context.TextFieldToValuesMap ) { wxString varName = CADSTAR_TO_KICAD_FIELDS.at( txtvalue.first ); wxString varValue = txtvalue.second; txtVars.insert( { varName, varValue } ); } for( std::pair 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 ); } } LIB_FIELD* CADSTAR_SCH_ARCHIVE_LOADER::addNewFieldToSymbol( const wxString& aFieldName, std::unique_ptr& aKiCadSymbol ) { // First Check if field already exists LIB_FIELD* existingField = aKiCadSymbol->FindField( aFieldName ); if( existingField != nullptr ) return existingField; int newfieldID = aKiCadSymbol->GetFieldCount(); LIB_FIELD* newfield = new LIB_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 kiSym = std::make_unique( csSym.BuildLibName() ); const int gateNumber = 1; // Always load to gate "A" - we will change the unit later // Load Graphical Figures for( std::pair figPair : csSym.Figures ) { FIGURE fig = figPair.second; int lineThickness = getLineThickness( fig.LineCodeID ); PLOT_DASH_TYPE linestyle = getLineStyle( fig.LineCodeID ); if( fig.Shape.Type == SHAPE_TYPE::OPENSHAPE ) { loadLibrarySymbolShapeVertices( fig.Shape.Vertices, csSym.Origin, kiSym.get(), gateNumber, lineThickness ); } else { LIB_SHAPE* shape = new LIB_SHAPE( kiSym.get(), SHAPE_T::POLY ); 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 termPair : csSym.Terminals ) { TERMINAL term = termPair.second; wxString pinNum = wxString::Format( "%ld", term.ID ); wxString pinName = wxEmptyString; std::unique_ptr pin = std::make_unique( 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 textPair : csSym.Texts ) { TEXT csText = textPair.second; std::unique_ptr libtext = std::make_unique( kiSym.get() ); libtext->SetText( csText.Text ); 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, true ); VECTOR2I linePos = { bbox.GetLeft(), -bbox.GetBottom() }; RotatePoint( linePos, libtext->GetTextPos(), -libtext->GetTextAngle() ); LIB_TEXT* textLine = static_cast( libtext->Clone() ); 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) LIB_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 ); LIB_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 ); LIB_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 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 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 ); LIB_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 ); LIB_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; LIB_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 ); } }; // 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& 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& aCadstarVertices, 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 ); LIB_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 LIB_SHAPE( aSymbol, SHAPE_T::POLY ); 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 LIB_SHAPE( aSymbol, SHAPE_T::ARC ); 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, PLOT_DASH_TYPE::SOLID ) ); aSymbol->AddDrawItem( shape ); prev = cur; } } void CADSTAR_SCH_ARCHIVE_LOADER::applyToLibraryFieldAttribute( const ATTRIBUTE_LOCATION& aCadstarAttrLoc, VECTOR2I aSymbolOrigin, LIB_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 ) { LIB_ID libId; libId.SetLibItemName( aKiCadPart.GetName() ); 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 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; LIB_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(); } return POINT(); } 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& 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 ); PLOT_DASH_TYPE 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, 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( CUTOUT cutout : aCadstarFigure.Shape.Cutouts ) { loadShapeVertices( cutout.Vertices, aCadstarFigure.LineCodeID, aCadstarSheetIDOverride, aKiCadSchLayerID, aMoveVector, aRotation, aScalingFactor, aTransformCentre, aMirrorInvert ); } } void CADSTAR_SCH_ARCHIVE_LOADER::loadSheetAndChildSheets( LAYER_ID aCadstarSheetID, const VECTOR2I& aPosition, 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( "." ) + 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( 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 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 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::findOrphanSheets() { std::vector childSheets, orphanSheets; //Find all sheets that are child of another for( std::pair 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( 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( LAYER_ID aCadstarSheetID ) { int i = 1; for( LAYER_ID sheetID : Sheets.SheetOrder ) { if( sheetID == aCadstarSheetID ) return i; ++i; } return -1; } void CADSTAR_SCH_ARCHIVE_LOADER::loadItemOntoKiCadSheet( LAYER_ID aCadstarSheetID, SCH_ITEM* aItem ) { wxCHECK_MSG( aItem, /*void*/, "aItem is null" ); if( aCadstarSheetID == "ALL_SHEETS" ) { SCH_ITEM* duplicateItem; for( std::pair 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" ) { wxASSERT_MSG( false, "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; wxASSERT_MSG( false, "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 ); } PLOT_DASH_TYPE CADSTAR_SCH_ARCHIVE_LOADER::getLineStyle( const LINECODE_ID& aCadstarLineCodeID ) { wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID ) != Assignments.Codedefs.LineCodes.end(), PLOT_DASH_TYPE::SOLID ); // clang-format off switch( Assignments.Codedefs.LineCodes.at( aCadstarLineCodeID ).Style ) { case LINESTYLE::DASH: return PLOT_DASH_TYPE::DASH; case LINESTYLE::DASHDOT: return PLOT_DASH_TYPE::DASHDOT; case LINESTYLE::DASHDOTDOT: return PLOT_DASH_TYPE::DASHDOT; //TODO: update in future case LINESTYLE::DOT: return PLOT_DASH_TYPE::DOT; case LINESTYLE::SOLID: return PLOT_DASH_TYPE::SOLID; default: return PLOT_DASH_TYPE::DEFAULT; } // clang-format on return PLOT_DASH_TYPE(); } 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 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; } TEXT_SPIN_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getSpinStyle( const long long& aCadstarOrientation, bool aMirror ) { EDA_ANGLE orientation = getAngle( aCadstarOrientation ); TEXT_SPIN_STYLE spinStyle = getSpinStyle( orientation ); if( aMirror ) { spinStyle = spinStyle.RotateCCW(); spinStyle = spinStyle.RotateCCW(); } return spinStyle; } TEXT_SPIN_STYLE CADSTAR_SCH_ARCHIVE_LOADER::getSpinStyle( const EDA_ANGLE& aOrientation ) { TEXT_SPIN_STYLE spinStyle = TEXT_SPIN_STYLE::LEFT; EDA_ANGLE oDeg = aOrientation; oDeg.Normalize180(); if( oDeg >= -ANGLE_45 && oDeg <= ANGLE_45 ) spinStyle = TEXT_SPIN_STYLE::RIGHT; // 0deg else if( oDeg >= ANGLE_45 && oDeg <= ANGLE_135 ) spinStyle = TEXT_SPIN_STYLE::UP; // 90deg else if( oDeg >= ANGLE_135 || oDeg <= -ANGLE_135 ) spinStyle = TEXT_SPIN_STYLE::LEFT; // 180deg else spinStyle = TEXT_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 ); aKiCadTextItem->SetTextThickness( getKiCadLength( textCode.LineWidth ) ); 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; } }; TEXT_SPIN_STYLE spin = getSpinStyle( aCadstarOrientAngle, aMirrored ); EDA_ITEM* textEdaItem = dynamic_cast( aKiCadTextItem ); wxCHECK( textEdaItem, /* void */ ); // ensure this is a EDA_ITEM switch( textEdaItem->Type() ) { // Some KiCad schematic text items only permit a limited amount of angles // and text justifications case LIB_TEXT_T: case SCH_FIELD_T: case LIB_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 ); return; } case 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( 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 TEXT_SPIN_STYLE::BOTTOM: pos = { bb.GetRight() - off, bb.GetTop() }; break; case TEXT_SPIN_STYLE::UP: pos = { bb.GetRight() - off, bb.GetBottom() }; break; case TEXT_SPIN_STYLE::LEFT: pos = { bb.GetRight() , bb.GetBottom() + off }; break; case TEXT_SPIN_STYLE::RIGHT: pos = { bb.GetLeft() , bb.GetBottom() + off }; break; } aKiCadTextItem->SetTextPos( pos ); } KI_FALLTHROUGH; // We don't want to change position of net labels as that would break connectivity case SCH_LABEL_T: case SCH_GLOBAL_LABEL_T: case SCH_HIER_LABEL_T: case SCH_SHEET_PIN_T: static_cast( aKiCadTextItem )->SetTextSpinStyle( spin ); return; default: wxFAIL_MSG( "Unexpected item type" ); return; } } 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( LIB_ITEM& item : items ) { switch( item.Type() ) { case KICAD_T::LIB_SHAPE_T: { LIB_SHAPE& shape = static_cast( 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::LIB_PIN_T: { LIB_PIN& pin = static_cast( item ); pin.SetPosition( scalePt( pin.GetPosition() ) ); pin.SetLength( scaleLen( pin.GetLength() ) ); break; } case KICAD_T::LIB_TEXT_T: { LIB_TEXT& txt = static_cast( 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 uniqueSegments( compLambda ); LIB_ITEMS_CONTAINER::ITERATOR shapeIt = aSymbolToFix->GetDrawItems().begin( LIB_SHAPE_T ); for( ; shapeIt != aSymbolToFix->GetDrawItems().end( LIB_SHAPE_T ); ++shapeIt ) { LIB_SHAPE& shape = static_cast( *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 } ); } } } } LIB_PINS pins; aSymbolToFix->GetPins( pins, aGateNumber ); for( auto& pin : pins ) { auto setPinOrientation = [&]( const EDA_ANGLE& aAngle ) { EDA_ANGLE angle( aAngle ); angle.Normalize180(); if( angle >= -ANGLE_45 && angle <= ANGLE_45 ) pin->SetOrientation( 'R' ); // 0 degrees else if( angle >= ANGLE_45 && angle <= ANGLE_135 ) pin->SetOrientation( 'U' ); // 90 degrees else if( angle >= ANGLE_135 || angle <= -ANGLE_135 ) pin->SetOrientation( 'L' ); // 180 degrees else pin->SetOrientation( 'D' ); // -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 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 ) ); }