/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020-2021 Roberto Fernandez Bautista * Copyright (C) 2020-2024 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * @file cadstar_pcb_archive_loader.cpp * @brief Loads a cpa file into a KiCad BOARD object */ #include #include #include // KEY_COPPER, KEY_CORE, KEY_PREPREG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // std::numeric_limits void CADSTAR_PCB_ARCHIVE_LOADER::Load( BOARD* aBoard, PROJECT* aProject ) { m_board = aBoard; m_project = aProject; if( m_progressReporter ) m_progressReporter->SetNumPhases( 3 ); // (0) Read file, (1) Parse file, (2) Load file Parse(); LONGPOINT designLimit = Assignments.Technology.DesignLimit; //Note: can't use getKiCadPoint() due wxPoint being int - need long long to make the check long long designSizeXkicad = (long long) designLimit.x * KiCadUnitMultiplier; long long designSizeYkicad = (long long) designLimit.y * KiCadUnitMultiplier; // Max size limited by the positive dimension of wxPoint (which is an int) 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 / PCB_IU_PER_MM, (double) designSizeYkicad / PCB_IU_PER_MM, (double) maxDesignSizekicad / PCB_IU_PER_MM, (double) maxDesignSizekicad / PCB_IU_PER_MM ) ); } m_designCenter = ( Assignments.Technology.DesignArea.first + Assignments.Technology.DesignArea.second ) / 2; if( Layout.NetSynch == NETSYNCH::WARNING ) { wxLogWarning( _( "The selected file indicates that nets might be out of synchronisation " "with the schematic. It is recommended that you carry out an 'Align Nets' " "procedure in CADSTAR and re-import, to avoid inconsistencies between the " "PCB and the schematic. " ) ); } if( m_progressReporter ) { m_progressReporter->BeginPhase( 2 ); // Significantly most amount of time spent loading coppers compared to all the other steps // (39 seconds vs max of 100ms in other steps). This is due to requirement of boolean // operations to join them together into a single polygon. long numSteps = Layout.Coppers.size(); // A large amount is also spent calculating zone priorities numSteps += ( Layout.Templates.size() * Layout.Templates.size() ) / 2; m_progressReporter->SetMaxProgress( numSteps ); } loadBoardStackup(); remapUnsureLayers(); loadDesignRules(); loadComponentLibrary(); loadGroups(); loadBoards(); loadFigures(); loadTexts(); loadDimensions(); loadAreas(); loadComponents(); loadDocumentationSymbols(); loadTemplates(); loadCoppers(); // Progress reporting is here as significantly most amount of time spent for( PCB_LAYER_ID id : LSET::AllCuMask( m_numCopperLayers ).Seq() ) { if( !calculateZonePriorities( id ) ) { wxLogError( wxString::Format( _( "Unable to determine zone fill priorities for layer " "'%s'. A best attempt has been made but it is " "possible that DRC errors exist and that manual " "editing of the zone priorities is required." ), m_board->GetLayerName( id ) ) ); } } loadNets(); loadTextVariables(); if( Layout.Trunks.size() > 0 ) { wxLogWarning( _( "The CADSTAR design contains Trunk routing elements, which have no KiCad " "equivalent. These elements were not loaded." ) ); } if( Layout.VariantHierarchy.Variants.size() > 0 ) { wxLogWarning( wxString::Format( _( "The CADSTAR design contains variants which has no KiCad equivalent. Only " "the variant '%s' was loaded." ), Layout.VariantHierarchy.Variants.begin()->second.Name ) ); } if( Layout.ReuseBlocks.size() > 0 ) { wxLogWarning( _( "The CADSTAR design contains re-use blocks which has no KiCad equivalent. The " "re-use block information has been discarded during the import." ) ); } wxLogWarning( _( "CADSTAR fonts are different to the ones in KiCad. This will likely result " "in alignment issues that may cause DRC errors. Please review the imported " "text elements carefully and correct manually if required." ) ); wxLogMessage( _( "The CADSTAR design has been imported successfully.\n" "Please review the import errors and warnings (if any)." ) ); } std::vector CADSTAR_PCB_ARCHIVE_LOADER::GetLoadedLibraryFootpints() const { std::vector retval; for( std::pair fpPair : m_libraryMap ) { retval.push_back( static_cast( fpPair.second->Clone() ) ); } return retval; } std::vector> CADSTAR_PCB_ARCHIVE_LOADER::LoadLibrary() { // loading the library after parsing takes almost no time in comparison if( m_progressReporter ) m_progressReporter->SetNumPhases( 2 ); // (0) Read file, (1) Parse file Parse( true /*aLibrary*/); // Some memory handling for( std::pair libItem : m_libraryMap ) { FOOTPRINT* footprint = libItem.second; if( footprint ) delete footprint; } m_libraryMap.clear(); if( m_board ) delete m_board; m_board = new BOARD(); // dummy board for loading m_project = nullptr; m_designCenter = { 0, 0 }; // load footprints at 0,0 loadBoardStackup(); remapUnsureLayers(); loadComponentLibrary(); std::vector> retval; for( auto& [id, footprint] : m_libraryMap ) { footprint->SetParent( nullptr ); // remove association to m_board retval.emplace_back( footprint ); } delete m_board; // Don't delete the generated footprints, but empty the map // (the destructor of this class would end up deleting them if not) m_libraryMap.clear(); return retval; } void CADSTAR_PCB_ARCHIVE_LOADER::logBoardStackupWarning( const wxString& aCadstarLayerName, const PCB_LAYER_ID& aKiCadLayer ) { if( m_logLayerWarnings ) { wxLogWarning( wxString::Format( _( "The CADSTAR layer '%s' has no KiCad equivalent. All elements on this " "layer have been mapped to KiCad layer '%s' instead." ), aCadstarLayerName, LSET::Name( aKiCadLayer ) ) ); } } void CADSTAR_PCB_ARCHIVE_LOADER::logBoardStackupMessage( const wxString& aCadstarLayerName, const PCB_LAYER_ID& aKiCadLayer ) { if( m_logLayerWarnings ) { wxLogMessage( wxString::Format( _( "The CADSTAR layer '%s' has been assumed to be a technical layer. All " "elements on this layer have been mapped to KiCad layer '%s'." ), aCadstarLayerName, LSET::Name( aKiCadLayer ) ) ); } } void CADSTAR_PCB_ARCHIVE_LOADER::initStackupItem( const LAYER& aCadstarLayer, BOARD_STACKUP_ITEM* aKiCadItem, int aDielectricSublayer ) { if( !aCadstarLayer.MaterialId.IsEmpty() ) { MATERIAL material = Assignments.Layerdefs.Materials.at( aCadstarLayer.MaterialId ); aKiCadItem->SetMaterial( material.Name, aDielectricSublayer ); aKiCadItem->SetEpsilonR( material.Permittivity.GetDouble(), aDielectricSublayer ); aKiCadItem->SetLossTangent( material.LossTangent.GetDouble(), aDielectricSublayer ); //TODO add Resistivity when KiCad supports it } if( !aCadstarLayer.Name.IsEmpty() ) aKiCadItem->SetLayerName( aCadstarLayer.Name ); if( aCadstarLayer.Thickness != 0 ) aKiCadItem->SetThickness( getKiCadLength( aCadstarLayer.Thickness ), aDielectricSublayer ); } void CADSTAR_PCB_ARCHIVE_LOADER::loadBoardStackup() { // Structure describing an electrical layer with optional dielectric layers below it // (construction layers in CADSTAR) struct LAYER_BLOCK { LAYER_ID ElecLayerID = wxEmptyString; // Normally not empty, but could be empty if the // first layer in the stackup is a construction // layer std::vector ConstructionLayers; // Normally empty for the last electrical layer // but it is possible to build a board in CADSTAR // with no construction layers or with the bottom // layer being a construction layer bool IsInitialised() { return !ElecLayerID.IsEmpty() || ConstructionLayers.size() > 0; }; }; std::vector cadstarBoardStackup; LAYER_BLOCK currentBlock; bool first = true; // Find the electrical and construction (dielectric) layers in the stackup for( LAYER_ID cadstarLayerID : Assignments.Layerdefs.LayerStack ) { LAYER cadstarLayer = Assignments.Layerdefs.Layers.at( cadstarLayerID ); if( cadstarLayer.Type == LAYER_TYPE::JUMPERLAYER || cadstarLayer.Type == LAYER_TYPE::POWER || cadstarLayer.Type == LAYER_TYPE::ELEC ) { if( currentBlock.IsInitialised() ) { cadstarBoardStackup.push_back( currentBlock ); currentBlock = LAYER_BLOCK(); // reset the block } currentBlock.ElecLayerID = cadstarLayerID; first = false; } else if( cadstarLayer.Type == LAYER_TYPE::CONSTRUCTION ) { if( first ) { wxLogWarning( wxString::Format( _( "The CADSTAR construction layer '%s' is on " "the outer surface of the board. It has been " "ignored." ), cadstarLayer.Name ) ); } else { currentBlock.ConstructionLayers.push_back( cadstarLayerID ); } } } if( currentBlock.IsInitialised() ) cadstarBoardStackup.push_back( currentBlock ); m_numCopperLayers = cadstarBoardStackup.size(); // Special case: last layer in the stackup is a construction layer, drop it if( cadstarBoardStackup.back().ConstructionLayers.size() > 0 ) { for( const LAYER_ID& layerID : cadstarBoardStackup.back().ConstructionLayers ) { LAYER cadstarLayer = Assignments.Layerdefs.Layers.at( layerID ); wxLogWarning( wxString::Format( _( "The CADSTAR construction layer '%s' is on " "the outer surface of the board. It has been " "ignored." ), cadstarLayer.Name ) ); } cadstarBoardStackup.back().ConstructionLayers.clear(); } // Make sure it is an even number of layers (KiCad doesn't yet support unbalanced stack-ups) if( ( m_numCopperLayers % 2 ) != 0 ) { LAYER_BLOCK bottomLayer = cadstarBoardStackup.back(); cadstarBoardStackup.pop_back(); LAYER_BLOCK secondToLastLayer = cadstarBoardStackup.back(); cadstarBoardStackup.pop_back(); LAYER_BLOCK dummyLayer; if( secondToLastLayer.ConstructionLayers.size() > 0 ) { LAYER_ID lastConstruction = secondToLastLayer.ConstructionLayers.back(); if( secondToLastLayer.ConstructionLayers.size() > 1 ) { // At least two construction layers, lets remove one here and use the // other in the dummy layer secondToLastLayer.ConstructionLayers.pop_back(); } else { // There is only one construction layer, lets halve its thickness so it is split // evenly between this layer and the dummy layer Assignments.Layerdefs.Layers.at( lastConstruction ).Thickness /= 2; } dummyLayer.ConstructionLayers.push_back( lastConstruction ); } cadstarBoardStackup.push_back( secondToLastLayer ); cadstarBoardStackup.push_back( dummyLayer ); cadstarBoardStackup.push_back( bottomLayer ); ++m_numCopperLayers; } wxASSERT( m_numCopperLayers == cadstarBoardStackup.size() ); wxASSERT( cadstarBoardStackup.back().ConstructionLayers.size() == 0 ); // Create a new stackup from default stackup list BOARD_DESIGN_SETTINGS& boardDesignSettings = m_board->GetDesignSettings(); BOARD_STACKUP& stackup = boardDesignSettings.GetStackupDescriptor(); stackup.RemoveAll(); m_board->SetEnabledLayers( LSET::AllLayersMask() ); m_board->SetVisibleLayers( LSET::AllLayersMask() ); m_board->SetCopperLayerCount( m_numCopperLayers ); stackup.BuildDefaultStackupList( &m_board->GetDesignSettings(), m_numCopperLayers ); size_t stackIndex = 0; for( BOARD_STACKUP_ITEM* item : stackup.GetList() ) { if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER ) { LAYER_ID layerID = cadstarBoardStackup.at( stackIndex ).ElecLayerID; if( layerID.IsEmpty() ) { // Loading a dummy layer. Make zero thickness so it doesn't affect overall stackup item->SetThickness( 0 ); } else { LAYER copperLayer = Assignments.Layerdefs.Layers.at( layerID ); initStackupItem( copperLayer, item, 0 ); LAYER_T copperType = LAYER_T::LT_SIGNAL; switch( copperLayer.Type ) { case LAYER_TYPE::JUMPERLAYER: copperType = LAYER_T::LT_JUMPER; break; case LAYER_TYPE::ELEC: copperType = LAYER_T::LT_SIGNAL; break; case LAYER_TYPE::POWER: copperType = LAYER_T::LT_POWER; m_powerPlaneLayers.push_back( copperLayer.ID ); //need to add a Copper zone break; default: wxFAIL_MSG( wxT( "Unexpected Layer type. Was expecting an electrical type" ) ); break; } m_board->SetLayerType( item->GetBrdLayerId(), copperType ); m_board->SetLayerName( item->GetBrdLayerId(), item->GetLayerName() ); m_layermap.insert( { copperLayer.ID, item->GetBrdLayerId() } ); } } else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC ) { LAYER_BLOCK layerBlock = cadstarBoardStackup.at( stackIndex ); LAYER_BLOCK layerBlockBelow = cadstarBoardStackup.at( stackIndex + 1 ); if( layerBlock.ConstructionLayers.size() == 0 ) { ++stackIndex; continue; // Older cadstar designs have no construction layers - use KiCad defaults } int dielectricId = stackIndex + 1; item->SetDielectricLayerId( dielectricId ); //Prepreg or core? //Look at CADSTAR layer embedding (see LAYER->Embedding) to check whether the electrical //layer embeds above and below to decide if current layer is prepreg or core if( layerBlock.ElecLayerID.IsEmpty() ) { //Dummy electrical layer, assume prepreg item->SetTypeName( KEY_PREPREG ); } else { LAYER copperLayer = Assignments.Layerdefs.Layers.at( layerBlock.ElecLayerID ); if( layerBlockBelow.ElecLayerID.IsEmpty() ) { // Dummy layer below, just use current layer to decide if( copperLayer.Embedding == EMBEDDING::ABOVE ) item->SetTypeName( KEY_CORE ); else item->SetTypeName( KEY_PREPREG ); } else { LAYER copperLayerBelow = Assignments.Layerdefs.Layers.at( layerBlockBelow.ElecLayerID ); if( copperLayer.Embedding == EMBEDDING::ABOVE ) { // Need to check layer below is embedding downwards if( copperLayerBelow.Embedding == EMBEDDING::BELOW ) item->SetTypeName( KEY_CORE ); else item->SetTypeName( KEY_PREPREG ); } else { item->SetTypeName( KEY_PREPREG ); } } } int dielectricSublayer = 0; for( LAYER_ID constructionLaID : layerBlock.ConstructionLayers ) { LAYER dielectricLayer = Assignments.Layerdefs.Layers.at( constructionLaID ); if( dielectricSublayer ) item->AddDielectricPrms( dielectricSublayer ); initStackupItem( dielectricLayer, item, dielectricSublayer ); m_board->SetLayerName( item->GetBrdLayerId(), item->GetLayerName() ); m_layermap.insert( { dielectricLayer.ID, item->GetBrdLayerId() } ); ++dielectricSublayer; } ++stackIndex; } else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SILKSCREEN ) { item->SetColor( wxT( "White" ) ); } else if( item->GetType() == BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERMASK ) { item->SetColor( wxT( "Green" ) ); } } int thickness = stackup.BuildBoardThicknessFromStackup(); boardDesignSettings.SetBoardThickness( thickness ); boardDesignSettings.m_HasStackup = true; int numElecLayersProcessed = 0; // Map CADSTAR documentation layers to KiCad "User layers" int currentDocLayer = 0; std::vector docLayers = { Dwgs_User, Cmts_User, User_1, User_2, User_3, User_4, User_5, User_6, User_7, User_8, User_9 }; for( LAYER_ID cadstarLayerID : Assignments.Layerdefs.LayerStack ) { LAYER curLayer = Assignments.Layerdefs.Layers.at( cadstarLayerID ); PCB_LAYER_ID kicadLayerID = PCB_LAYER_ID::UNDEFINED_LAYER; wxString layerName = curLayer.Name.Lower(); enum class LOG_LEVEL { NONE, MSG, WARN }; auto selectLayerID = [&]( PCB_LAYER_ID aFront, PCB_LAYER_ID aBack, LOG_LEVEL aLogType ) { if( numElecLayersProcessed >= m_numCopperLayers ) kicadLayerID = aBack; else kicadLayerID = aFront; switch( aLogType ) { case LOG_LEVEL::NONE: break; case LOG_LEVEL::MSG: logBoardStackupMessage( curLayer.Name, kicadLayerID ); break; case LOG_LEVEL::WARN: logBoardStackupWarning( curLayer.Name, kicadLayerID ); break; } }; switch( curLayer.Type ) { case LAYER_TYPE::ALLDOC: case LAYER_TYPE::ALLELEC: case LAYER_TYPE::ALLLAYER: case LAYER_TYPE::ASSCOMPCOPP: case LAYER_TYPE::NOLAYER: //Shouldn't be here if CPA file is correctly parsed and not corrupt THROW_IO_ERROR( wxString::Format( _( "Unexpected layer '%s' in layer stack." ), curLayer.Name ) ); break; case LAYER_TYPE::JUMPERLAYER: case LAYER_TYPE::ELEC: case LAYER_TYPE::POWER: ++numElecLayersProcessed; KI_FALLTHROUGH; case LAYER_TYPE::CONSTRUCTION: //Already dealt with these when loading board stackup break; case LAYER_TYPE::DOC: if( currentDocLayer >= docLayers.size() ) currentDocLayer = 0; kicadLayerID = docLayers.at( currentDocLayer++ ); logBoardStackupMessage( curLayer.Name, kicadLayerID ); break; case LAYER_TYPE::NONELEC: switch( curLayer.SubType ) { case LAYER_SUBTYPE::LAYERSUBTYPE_ASSEMBLY: selectLayerID( PCB_LAYER_ID::F_Fab, PCB_LAYER_ID::B_Fab, LOG_LEVEL::NONE ); break; case LAYER_SUBTYPE::LAYERSUBTYPE_PLACEMENT: selectLayerID( PCB_LAYER_ID::F_CrtYd, PCB_LAYER_ID::B_CrtYd, LOG_LEVEL::NONE ); break; case LAYER_SUBTYPE::LAYERSUBTYPE_NONE: // Generic Non-electrical layer (older CADSTAR versions). // Attempt to detect technical layers by string matching. if( layerName.Contains( wxT( "glue" ) ) || layerName.Contains( wxT( "adhesive" ) ) ) { selectLayerID( PCB_LAYER_ID::F_Adhes, PCB_LAYER_ID::B_Adhes, LOG_LEVEL::MSG ); } else if( layerName.Contains( wxT( "silk" ) ) || layerName.Contains( wxT( "legend" ) ) ) { selectLayerID( PCB_LAYER_ID::F_SilkS, PCB_LAYER_ID::B_SilkS, LOG_LEVEL::MSG ); } else if( layerName.Contains( wxT( "assembly" ) ) || layerName.Contains( wxT( "fabrication" ) ) ) { selectLayerID( PCB_LAYER_ID::F_Fab, PCB_LAYER_ID::B_Fab, LOG_LEVEL::MSG ); } else if( layerName.Contains( wxT( "resist" ) ) || layerName.Contains( wxT( "mask" ) ) ) { selectLayerID( PCB_LAYER_ID::F_Mask, PCB_LAYER_ID::B_Mask, LOG_LEVEL::MSG ); } else if( layerName.Contains( wxT( "paste" ) ) ) { selectLayerID( PCB_LAYER_ID::F_Paste, PCB_LAYER_ID::B_Paste, LOG_LEVEL::MSG ); } else { // Does not appear to be a technical layer - Map to Eco layers for now. selectLayerID( PCB_LAYER_ID::Eco1_User, PCB_LAYER_ID::Eco2_User, LOG_LEVEL::WARN ); } break; case LAYER_SUBTYPE::LAYERSUBTYPE_PASTE: selectLayerID( PCB_LAYER_ID::F_Paste, PCB_LAYER_ID::B_Paste, LOG_LEVEL::MSG ); break; case LAYER_SUBTYPE::LAYERSUBTYPE_SILKSCREEN: selectLayerID( PCB_LAYER_ID::F_SilkS, PCB_LAYER_ID::B_SilkS, LOG_LEVEL::MSG ); break; case LAYER_SUBTYPE::LAYERSUBTYPE_SOLDERRESIST: selectLayerID( PCB_LAYER_ID::F_Mask, PCB_LAYER_ID::B_Mask, LOG_LEVEL::MSG ); break; case LAYER_SUBTYPE::LAYERSUBTYPE_ROUT: case LAYER_SUBTYPE::LAYERSUBTYPE_CLEARANCE: //Unsure what these layer types are used for. Map to Eco layers for now. selectLayerID( PCB_LAYER_ID::Eco1_User, PCB_LAYER_ID::Eco2_User, LOG_LEVEL::WARN ); break; default: wxFAIL_MSG( wxT( "Unknown CADSTAR Layer Sub-type" ) ); break; } break; default: wxFAIL_MSG( wxT( "Unknown CADSTAR Layer Type" ) ); break; } m_layermap.insert( { curLayer.ID, kicadLayerID } ); } } void CADSTAR_PCB_ARCHIVE_LOADER::remapUnsureLayers() { LSET enabledLayers = m_board->GetEnabledLayers(); LSET validRemappingLayers = enabledLayers | LSET::AllBoardTechMask() | LSET::UserMask() | LSET::UserDefinedLayers(); std::vector inputLayers; std::map cadstarLayerNameMap; for( std::pair layerPair : m_layermap ) { LAYER* curLayer = &Assignments.Layerdefs.Layers.at( layerPair.first ); //Only remap documentation and non-electrical layers if( curLayer->Type == LAYER_TYPE::NONELEC || curLayer->Type == LAYER_TYPE::DOC ) { INPUT_LAYER_DESC iLdesc; iLdesc.Name = curLayer->Name; iLdesc.PermittedLayers = validRemappingLayers; iLdesc.AutoMapLayer = layerPair.second; inputLayers.push_back( iLdesc ); cadstarLayerNameMap.insert( { curLayer->Name, curLayer->ID } ); } } if( inputLayers.size() == 0 ) return; // Callback: std::map reMappedLayers = m_layerMappingHandler( inputLayers ); for( std::pair layerPair : reMappedLayers ) { if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER ) { wxFAIL_MSG( wxT( "Unexpected Layer ID" ) ); continue; } LAYER_ID cadstarLayerID = cadstarLayerNameMap.at( layerPair.first ); m_layermap.at( cadstarLayerID ) = layerPair.second; enabledLayers |= LSET( layerPair.second ); } m_board->SetEnabledLayers( enabledLayers ); m_board->SetVisibleLayers( enabledLayers ); } void CADSTAR_PCB_ARCHIVE_LOADER::loadDesignRules() { BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); std::map& spacingCodes = Assignments.Codedefs.SpacingCodes; auto applyRule = [&]( wxString aID, int* aVal ) { if( spacingCodes.find( aID ) == spacingCodes.end() ) wxLogWarning( _( "Design rule %s was not found. This was ignored." ) ); else *aVal = getKiCadLength( spacingCodes.at( aID ).Spacing ); }; //Note: for details on the different spacing codes see SPACINGCODE::ID applyRule( "T_T", &bds.m_MinClearance ); applyRule( "C_B", &bds.m_CopperEdgeClearance ); applyRule( "H_H", &bds.m_HoleToHoleMin ); bds.m_TrackMinWidth = getKiCadLength( Assignments.Technology.MinRouteWidth ); bds.m_ViasMinSize = bds.m_TrackMinWidth; // Not specified, assumed same as track width bds.m_ViasMinAnnularWidth = bds.m_TrackMinWidth / 2; // Not specified, assumed half track width bds.m_MinThroughDrill = PCB_IU_PER_MM * 0.0508; // CADSTAR does not specify a minimum hole size // so set to minimum permitted in KiCad (2 mils) bds.m_HoleClearance = 0; // Testing suggests cadstar might not have a copper-to-hole clearance auto applyNetClassRule = [&]( wxString aID, std::shared_ptr& aNetClassPtr ) { int value = -1; applyRule( aID, &value ); if( value != -1 ) aNetClassPtr->SetClearance( value ); }; applyNetClassRule( "T_T", bds.m_NetSettings->m_DefaultNetClass ); wxLogWarning( _( "KiCad design rules are different from CADSTAR ones. Only the compatible " "design rules were imported. It is recommended that you review the design " "rules that have been applied." ) ); } void CADSTAR_PCB_ARCHIVE_LOADER::loadComponentLibrary() { for( std::pair symPair : Library.ComponentDefinitions ) { SYMDEF_ID key = symPair.first; SYMDEF_PCB component = symPair.second; // Check that we are not loading a documentation symbol. // Documentation symbols in CADSTAR are graphical "footprints" that can be assigned // to any layer. The definition in the library assigns all elements to an undefined layer. LAYER_ID componentLayer; if( component.Figures.size() > 0 ) { FIGURE firstFigure = component.Figures.begin()->second; componentLayer = firstFigure.LayerID; } else if( component.Texts.size() > 0 ) { TEXT firstText = component.Texts.begin()->second; componentLayer = firstText.LayerID; } if( !componentLayer.IsEmpty() && getLayerType( componentLayer ) == LAYER_TYPE::NOLAYER ) continue; // don't process documentation symbols FOOTPRINT* footprint = new FOOTPRINT( m_board ); footprint->SetPosition( getKiCadPoint( component.Origin ) ); LIB_ID libID; libID.Parse( component.BuildLibName(), true ); footprint->SetFPID( libID ); loadLibraryFigures( component, footprint ); loadLibraryAreas( component, footprint ); loadLibraryPads( component, footprint ); loadLibraryCoppers( component, footprint ); // Load coppers after pads to ensure correct // ordering of pads in footprint->Pads() footprint->SetPosition( { 0, 0 } ); // KiCad expects library footprints at 0,0 footprint->SetReference( wxT( "REF**" ) ); footprint->SetValue( libID.GetLibItemName() ); footprint->AutoPositionFields(); m_libraryMap.insert( std::make_pair( key, footprint ) ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryFigures( const SYMDEF_PCB& aComponent, FOOTPRINT* aFootprint ) { for( std::pair figPair : aComponent.Figures ) { FIGURE& fig = figPair.second; for( const PCB_LAYER_ID& layer : getKiCadLayerSet( fig.LayerID ).Seq() ) { drawCadstarShape( fig.Shape, layer, getLineThickness( fig.LineCodeID ), wxString::Format( wxT( "Component %s:%s -> Figure %s" ), aComponent.ReferenceName, aComponent.Alternate, fig.ID ), aFootprint ); } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryCoppers( const SYMDEF_PCB& aComponent, FOOTPRINT* aFootprint ) { bool compCopperError = false; for( COMPONENT_COPPER compCopper : aComponent.ComponentCoppers ) { int lineThickness = getKiCadLength( getCopperCode( compCopper.CopperCodeID ).CopperWidth ); LSET layers = getKiCadLayerSet( compCopper.LayerID ); LSET copperLayers = LSET::AllCuMask() & layers; LSET remainingLayers = layers; if( !compCopperError && copperLayers.count() > 1 && compCopper.AssociatedPadIDs.size() > 0 ) { // TODO: Fix when we have full padstacks wxLogError( _( "Footprint definition '%s' has component copper associated to a pad on " "multiple layers. Custom padstacks are not supported in KiCad. The " "copper items have been imported as graphical elements." ), aComponent.BuildLibName() ); compCopperError = true; } if( compCopper.AssociatedPadIDs.size() > 0 && copperLayers.count() == 1 && compCopper.Shape.Type == SHAPE_TYPE::SOLID ) { // The copper is associated with pads and in an electrical layer which means it can // have a net associated with it. Load as a pad instead. // Note: we can only handle SOLID copper shapes. If the copper shape is an outline or // hatched or outline, then we give up and load as a graphical shape instead. // Find the first non-PCB-only pad. If there are none, use the first one COMPONENT_PAD anchorPad; bool found = false; for( PAD_ID padID : compCopper.AssociatedPadIDs ) { anchorPad = aComponent.ComponentPads.at( padID ); if( !anchorPad.PCBonlyPad ) { found = true; break; } } if( !found ) anchorPad = aComponent.ComponentPads.at( compCopper.AssociatedPadIDs.front() ); std::unique_ptr pad = std::make_unique( aFootprint ); pad->SetAttribute( PAD_ATTRIB::SMD ); pad->SetLayerSet( copperLayers ); pad->SetNumber( anchorPad.Identifier.IsEmpty() ? wxString::Format( wxT( "%ld" ), anchorPad.ID ) : anchorPad.Identifier ); // Custom pad shape with an anchor at the position of one of the associated // pads and same size as the pad. Shape circle as it fits inside a rectangle // but not the other way round PADCODE anchorpadcode = getPadCode( anchorPad.PadCodeID ); int anchorSize = getKiCadLength( anchorpadcode.Shape.Size ); VECTOR2I anchorPos = getKiCadPoint( anchorPad.Position ); if( anchorSize <= 0 ) anchorSize = 1; pad->SetShape( PAD_SHAPE::CUSTOM ); pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); pad->SetSize( { anchorSize, anchorSize } ); pad->SetPosition( anchorPos ); SHAPE_POLY_SET shapePolys = getPolySetFromCadstarShape( compCopper.Shape, lineThickness, aFootprint ); shapePolys.Move( -anchorPos ); pad->AddPrimitivePoly( shapePolys, 0, true ); // Now renumber all the associated pads COMPONENT_PAD associatedPad; for( PAD_ID padID : compCopper.AssociatedPadIDs ) { PAD* assocPad = getPadReference( aFootprint, padID ); assocPad->SetNumber( pad->GetNumber() ); } aFootprint->Add( pad.release(), ADD_MODE::APPEND ); // Append so that we get the correct behaviour // when finding pads by PAD_ID. See loadNets() m_librarycopperpads[aComponent.ID][anchorPad.ID].push_back( aFootprint->Pads().size() ); remainingLayers ^= copperLayers; // don't process copper layers again! } if( remainingLayers.any() ) { for( const PCB_LAYER_ID& layer : remainingLayers.Seq() ) { drawCadstarShape( compCopper.Shape, layer, lineThickness, wxString::Format( wxT( "Component %s:%s -> Copper element" ), aComponent.ReferenceName, aComponent.Alternate ), aFootprint ); } } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryAreas( const SYMDEF_PCB& aComponent, FOOTPRINT* aFootprint ) { for( std::pair areaPair : aComponent.ComponentAreas ) { COMPONENT_AREA& area = areaPair.second; if( area.NoVias || area.NoTracks ) { int lineThickness = 0; // CADSTAR areas only use the line width for display purpose ZONE* zone = getZoneFromCadstarShape( area.Shape, lineThickness, aFootprint ); aFootprint->Add( zone, ADD_MODE::APPEND ); if( isLayerSet( area.LayerID ) ) zone->SetLayerSet( getKiCadLayerSet( area.LayerID ) ); else zone->SetLayer( getKiCadLayer( area.LayerID ) ); zone->SetIsRuleArea( true ); //import all CADSTAR areas as Keepout zones zone->SetDoNotAllowPads( false ); //no CADSTAR equivalent zone->SetZoneName( area.ID ); //There is no distinction between tracks and copper pours in CADSTAR Keepout zones zone->SetDoNotAllowTracks( area.NoTracks ); zone->SetDoNotAllowCopperPour( area.NoTracks ); zone->SetDoNotAllowVias( area.NoVias ); } else { wxString libName = aComponent.ReferenceName; if( !aComponent.Alternate.IsEmpty() ) libName << wxT( " (" ) << aComponent.Alternate << wxT( ")" ); wxLogError( wxString::Format( _( "The CADSTAR area '%s' in library component '%s' does not " "have a KiCad equivalent. The area is neither a via nor " "route keepout area. The area was not imported." ), area.ID, libName ) ); } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryPads( const SYMDEF_PCB& aComponent, FOOTPRINT* aFootprint ) { for( std::pair padPair : aComponent.ComponentPads ) { if( PAD* pad = getKiCadPad( padPair.second, aFootprint ) ) aFootprint->Add( pad, ADD_MODE::APPEND ); // Append so that we get correct behaviour // when finding pads by PAD_ID - see loadNets() } } PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, FOOTPRINT* aParent ) { PADCODE csPadcode = getPadCode( aCadstarPad.PadCodeID ); wxString errorMSG; std::unique_ptr pad = std::make_unique( aParent ); LSET padLayerSet; switch( aCadstarPad.Side ) { case PAD_SIDE::MAXIMUM: //Bottom side padLayerSet |= LSET( 3, B_Cu, B_Paste, B_Mask ); break; case PAD_SIDE::MINIMUM: //TOP side padLayerSet |= LSET( 3, F_Cu, F_Paste, F_Mask ); break; case PAD_SIDE::THROUGH_HOLE: padLayerSet = LSET::AllCuMask() | LSET( 4, F_Mask, B_Mask, F_Paste, B_Paste ); break; default: wxFAIL_MSG( wxT( "Unknown Pad type" ) ); } pad->SetAttribute( PAD_ATTRIB::SMD ); // assume SMD pad for now pad->SetLocalSolderMaskMargin( 0 ); pad->SetLocalSolderPasteMargin( 0 ); pad->SetLocalSolderPasteMarginRatio( 0.0 ); bool complexPadErrorLogged = false; for( auto& reassign : csPadcode.Reassigns ) { PCB_LAYER_ID kiLayer = getKiCadLayer( reassign.first ); CADSTAR_PAD_SHAPE shape = reassign.second; if( shape.Size == 0 ) { padLayerSet.reset( kiLayer ); } else { int newMargin = getKiCadLength( shape.Size - csPadcode.Shape.Size ) / 2; if( kiLayer == F_Mask || kiLayer == B_Mask ) { std::optional localMargin = pad->GetLocalSolderMaskMargin(); if( !localMargin.has_value() ) pad->SetLocalSolderMaskMargin( newMargin ); else if( std::abs( localMargin.value() ) < std::abs( newMargin ) ) pad->SetLocalSolderMaskMargin( newMargin ); } else if( kiLayer == F_Paste || kiLayer == B_Paste ) { std::optional localMargin = pad->GetLocalSolderPasteMargin(); if( !localMargin.has_value() ) pad->SetLocalSolderPasteMargin( newMargin ); else if( std::abs( localMargin.value() ) < std::abs( newMargin ) ) pad->SetLocalSolderPasteMargin( newMargin ); } else { //TODO fix properly when KiCad supports full padstacks if( !complexPadErrorLogged ) { complexPadErrorLogged = true; errorMSG += wxT( "\n - " ) + wxString::Format( _( "The CADSTAR pad definition '%s' is a complex pad stack, " "which is not supported in KiCad. Please review the " "imported pads as they may require manual correction." ), csPadcode.Name ); } } } } pad->SetLayerSet( padLayerSet ); if( aCadstarPad.PCBonlyPad ) { // PCB Only pads in CADSTAR do not have a representation in the schematic - they are // purely mechanical pads that have no net associated with them. Make the pad name // empty to avoid warnings when importing from the schematic pad->SetNumber( wxT( "" ) ); } else { pad->SetNumber( aCadstarPad.Identifier.IsEmpty() ? wxString::Format( wxT( "%ld" ), aCadstarPad.ID ) : aCadstarPad.Identifier ); } if( csPadcode.Shape.Size == 0 ) { if( csPadcode.DrillDiameter == UNDEFINED_VALUE && aCadstarPad.Side == PAD_SIDE::THROUGH_HOLE ) { // Through-hole, zero sized pad?. Lets load this just on the F_Mask for now to // prevent DRC errors. // TODO: This could be a custom padstack, update when KiCad supports padstacks pad->SetAttribute( PAD_ATTRIB::SMD ); pad->SetLayerSet( LSET( 1, F_Mask ) ); } // zero sized pads seems to break KiCad so lets make it very small instead csPadcode.Shape.Size = 1; } VECTOR2I padOffset = { 0, 0 }; // offset of the pad origin (before rotating) VECTOR2I drillOffset = { 0, 0 }; // offset of the drill origin w.r.t. the pad (before rotating) switch( csPadcode.Shape.ShapeType ) { case PAD_SHAPE_TYPE::ANNULUS: //todo fix: use custom shape instead (Donught shape, i.e. a circle with a hole) pad->SetShape( PAD_SHAPE::CIRCLE ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::BULLET: pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); pad->SetChamferPositions( RECT_CHAMFER_POSITIONS::RECT_CHAMFER_BOTTOM_LEFT | RECT_CHAMFER_POSITIONS::RECT_CHAMFER_TOP_LEFT ); pad->SetRoundRectRadiusRatio( 0.5 ); pad->SetChamferRectRatio( 0.0 ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::CIRCLE: pad->SetShape( PAD_SHAPE::CIRCLE ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::DIAMOND: { // Cadstar diamond shape is a square rotated 45 degrees // We convert it in KiCad to a square with chamfered edges int sizeOfSquare = (double) getKiCadLength( csPadcode.Shape.Size ) * sqrt(2.0); pad->SetShape( PAD_SHAPE::RECTANGLE ); pad->SetChamferRectRatio( 0.5 ); pad->SetSize( { sizeOfSquare, sizeOfSquare } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); } break; case PAD_SHAPE_TYPE::FINGER: pad->SetShape( PAD_SHAPE::OVAL ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::OCTAGON: pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); pad->SetChamferPositions( RECT_CHAMFER_POSITIONS::RECT_CHAMFER_ALL ); pad->SetChamferRectRatio( 0.25 ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::RECTANGLE: pad->SetShape( PAD_SHAPE::RECTANGLE ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::ROUNDED_RECT: pad->SetShape( PAD_SHAPE::ROUNDRECT ); pad->SetRoundRectCornerRadius( getKiCadLength( csPadcode.Shape.InternalFeature ) ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::SQUARE: pad->SetShape( PAD_SHAPE::RECTANGLE ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; default: wxFAIL_MSG( wxT( "Unknown Pad Shape" ) ); } if( csPadcode.ReliefClearance != UNDEFINED_VALUE ) pad->SetThermalGap( getKiCadLength( csPadcode.ReliefClearance ) ); if( csPadcode.ReliefWidth != UNDEFINED_VALUE ) pad->SetThermalSpokeWidth( getKiCadLength( csPadcode.ReliefWidth ) ); if( csPadcode.DrillDiameter != UNDEFINED_VALUE ) { if( csPadcode.SlotLength != UNDEFINED_VALUE ) { pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG ); pad->SetDrillSize( { getKiCadLength( (long long) csPadcode.SlotLength + (long long) csPadcode.DrillDiameter ), getKiCadLength( csPadcode.DrillDiameter ) } ); } else { pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); pad->SetDrillSize( { getKiCadLength( csPadcode.DrillDiameter ), getKiCadLength( csPadcode.DrillDiameter ) } ); } drillOffset.x = -getKiCadLength( csPadcode.DrillXoffset ); drillOffset.y = getKiCadLength( csPadcode.DrillYoffset ); if( csPadcode.Plated ) pad->SetAttribute( PAD_ATTRIB::PTH ); else pad->SetAttribute( PAD_ATTRIB::NPTH ); } else { pad->SetDrillSize( { 0, 0 } ); } if( csPadcode.SlotOrientation != 0 ) { LSET lset = pad->GetLayerSet(); lset &= LSET::AllCuMask(); if( lset.size() > 0 ) { SHAPE_POLY_SET padOutline; PCB_LAYER_ID layer = lset.Seq().at( 0 ); int maxError = m_board->GetDesignSettings().m_MaxError; pad->SetPosition( { 0, 0 } ); pad->TransformShapeToPolygon( padOutline, layer, 0, maxError, ERROR_INSIDE ); PCB_SHAPE* padShape = new PCB_SHAPE; padShape->SetShape( SHAPE_T::POLY ); padShape->SetFilled( true ); padShape->SetPolyShape( padOutline ); padShape->SetStroke( STROKE_PARAMS( 0 ) ); padShape->Move( padOffset - drillOffset ); padShape->Rotate( VECTOR2I( 0, 0 ), ANGLE_180 - getAngle( csPadcode.SlotOrientation ) ); SHAPE_POLY_SET editedPadOutline = padShape->GetPolyShape(); if( editedPadOutline.Contains( { 0, 0 } ) ) { pad->SetAnchorPadShape( PAD_SHAPE::RECTANGLE ); pad->SetSize( VECTOR2I( { 4, 4 } ) ); pad->SetShape( PAD_SHAPE::CUSTOM ); pad->AddPrimitive( padShape ); padOffset = { 0, 0 }; } else { // The CADSTAR pad has the hole shape outside the pad shape // Lets just put the hole in the center of the pad instead csPadcode.SlotOrientation = 0; drillOffset = { 0, 0 }; errorMSG += wxT( "\n - " ) + wxString::Format( _( "The CADSTAR pad definition '%s' has the hole shape outside the " "pad shape. The hole has been moved to the center of the pad." ), csPadcode.Name ); } } else { wxFAIL_MSG( wxT( "No copper layers defined in the pad?" ) ); csPadcode.SlotOrientation = 0; pad->SetOffset( drillOffset ); } } else { pad->SetOffset( drillOffset ); } EDA_ANGLE padOrientation = getAngle( aCadstarPad.OrientAngle ) + getAngle( csPadcode.Shape.OrientAngle ); RotatePoint( padOffset, padOrientation ); RotatePoint( drillOffset, padOrientation ); pad->SetPosition( getKiCadPoint( aCadstarPad.Position ) - padOffset - drillOffset ); pad->SetOrientation( padOrientation + getAngle( csPadcode.SlotOrientation ) ); //TODO handle csPadcode.Reassigns when KiCad supports full padstacks //log warnings: if( m_padcodesTested.find( csPadcode.ID ) == m_padcodesTested.end() && !errorMSG.IsEmpty() ) { wxLogError( _( "The CADSTAR pad definition '%s' has import errors: %s" ), csPadcode.Name, errorMSG ); m_padcodesTested.insert( csPadcode.ID ); } return pad.release(); } PAD*& CADSTAR_PCB_ARCHIVE_LOADER::getPadReference( FOOTPRINT* aFootprint, const PAD_ID aCadstarPadID ) { size_t index = aCadstarPadID - (long) 1; if( !( index < aFootprint->Pads().size() ) ) { THROW_IO_ERROR( wxString::Format( _( "Unable to find pad index '%d' in footprint '%s'." ), (long) aCadstarPadID, aFootprint->GetReference() ) ); } return aFootprint->Pads().at( index ); } void CADSTAR_PCB_ARCHIVE_LOADER::loadGroups() { for( std::pair groupPair : Layout.Groups ) { GROUP& csGroup = groupPair.second; PCB_GROUP* kiGroup = new PCB_GROUP( m_board ); m_board->Add( kiGroup ); kiGroup->SetName( csGroup.Name ); kiGroup->SetLocked( csGroup.Fixed ); m_groupMap.insert( { csGroup.ID, kiGroup } ); } //now add any groups to their parent group for( std::pair groupPair : Layout.Groups ) { GROUP& csGroup = groupPair.second; if( !csGroup.GroupID.IsEmpty() ) { if( m_groupMap.find( csGroup.ID ) == m_groupMap.end() ) { THROW_IO_ERROR( wxString::Format( _( "Unable to find group ID %s in the group " "definitions." ), csGroup.ID ) ); } else if( m_groupMap.find( csGroup.ID ) == m_groupMap.end() ) { THROW_IO_ERROR( wxString::Format( _( "Unable to find sub group %s in the group " "map (parent group ID=%s, Name=%s)." ), csGroup.GroupID, csGroup.ID, csGroup.Name ) ); } else { PCB_GROUP* kiCadGroup = m_groupMap.at( csGroup.ID ); PCB_GROUP* parentGroup = m_groupMap.at( csGroup.GroupID ); parentGroup->AddItem( kiCadGroup ); } } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadBoards() { for( std::pair boardPair : Layout.Boards ) { CADSTAR_BOARD& board = boardPair.second; GROUP_ID boardGroup = createUniqueGroupID( wxT( "Board" ) ); drawCadstarShape( board.Shape, PCB_LAYER_ID::Edge_Cuts, getLineThickness( board.LineCodeID ), wxString::Format( wxT( "BOARD %s" ), board.ID ), m_board, boardGroup ); if( !board.GroupID.IsEmpty() ) { addToGroup( board.GroupID, getKiCadGroup( boardGroup ) ); } //TODO process board attributes when KiCad supports them } } void CADSTAR_PCB_ARCHIVE_LOADER::loadFigures() { for( std::pair figPair : Layout.Figures ) { FIGURE& fig = figPair.second; for( const PCB_LAYER_ID& layer : getKiCadLayerSet( fig.LayerID ).Seq() ) { drawCadstarShape( fig.Shape, layer, getLineThickness( fig.LineCodeID ), wxString::Format( wxT( "FIGURE %s" ), fig.ID ), m_board, fig.GroupID ); } //TODO process "swaprule" (doesn't seem to apply to Layout Figures?) //TODO process re-use block when KiCad Supports it //TODO process attributes when KiCad Supports attributes in figures } } void CADSTAR_PCB_ARCHIVE_LOADER::loadTexts() { for( std::pair txtPair : Layout.Texts ) { TEXT& csTxt = txtPair.second; drawCadstarText( csTxt, m_board ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadDimensions() { for( std::pair dimPair : Layout.Dimensions ) { DIMENSION& csDim = dimPair.second; switch( csDim.Type ) { case DIMENSION::TYPE::LINEARDIM: switch( csDim.Subtype ) { case DIMENSION::SUBTYPE::ANGLED: wxLogWarning( wxString::Format( _( "Dimension ID %s is an angled dimension, which " "has no KiCad equivalent. An aligned dimension " "was loaded instead." ), csDim.ID ) ); KI_FALLTHROUGH; case DIMENSION::SUBTYPE::DIRECT: case DIMENSION::SUBTYPE::ORTHOGONAL: { if( csDim.Line.Style == DIMENSION::LINE::STYLE::EXTERNAL ) { wxLogWarning( wxString::Format( _( "Dimension ID %s has 'External' style in CADSTAR. External " "dimension styles are not yet supported in KiCad. The dimension " "object was imported with an internal dimension style instead." ), csDim.ID ) ); } PCB_DIM_ALIGNED* dimension = nullptr; if( csDim.Subtype == DIMENSION::SUBTYPE::ORTHOGONAL ) { dimension = new PCB_DIM_ORTHOGONAL( m_board ); PCB_DIM_ORTHOGONAL* orDim = static_cast( dimension ); if( csDim.ExtensionLineParams.Start.x == csDim.Line.Start.x ) orDim->SetOrientation( PCB_DIM_ORTHOGONAL::DIR::HORIZONTAL ); else orDim->SetOrientation( PCB_DIM_ORTHOGONAL::DIR::VERTICAL ); } else { dimension = new PCB_DIM_ALIGNED( m_board, PCB_DIM_ALIGNED_T ); } m_board->Add( dimension, ADD_MODE::APPEND ); applyDimensionSettings( csDim, dimension ); dimension->SetExtensionHeight( getKiCadLength( csDim.ExtensionLineParams.Overshoot ) ); // Calculate height: VECTOR2I crossbarStart = getKiCadPoint( csDim.Line.Start ); VECTOR2I crossbarEnd = getKiCadPoint( csDim.Line.End ); VECTOR2I crossbarVector = crossbarEnd - crossbarStart; VECTOR2I heightVector = crossbarStart - dimension->GetStart(); double height = 0.0; if( csDim.Subtype == DIMENSION::SUBTYPE::ORTHOGONAL ) { if( csDim.ExtensionLineParams.Start.x == csDim.Line.Start.x ) height = heightVector.y; else height = heightVector.x; } else { EDA_ANGLE angle( crossbarVector ); angle += ANGLE_90; height = heightVector.x * angle.Cos() + heightVector.y * angle.Sin(); } dimension->SetHeight( height ); } break; default: // Radius and diameter dimensions are LEADERDIM (even if not actually leader) // Angular dimensions are always ANGLEDIM wxLogError( _( "Unexpected Dimension type (ID %s). This was not imported." ), csDim.ID ); continue; } break; case DIMENSION::TYPE::LEADERDIM: //TODO: update import when KiCad supports radius and diameter dimensions if( csDim.Line.Style == DIMENSION::LINE::STYLE::INTERNAL ) { // "internal" is a simple double sided arrow from start to end (no extension lines) PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board, PCB_DIM_ALIGNED_T ); m_board->Add( dimension, ADD_MODE::APPEND ); applyDimensionSettings( csDim, dimension ); // Lets set again start/end: dimension->SetStart( getKiCadPoint( csDim.Line.Start ) ); dimension->SetEnd( getKiCadPoint( csDim.Line.End ) ); // Do not use any extension lines: dimension->SetExtensionOffset( 0 ); dimension->SetExtensionHeight( 0 ); dimension->SetHeight( 0 ); } else { // "external" is a "leader" style dimension PCB_DIM_LEADER* leaderDim = new PCB_DIM_LEADER( m_board ); m_board->Add( leaderDim, ADD_MODE::APPEND ); applyDimensionSettings( csDim, leaderDim ); leaderDim->SetStart( getKiCadPoint( csDim.Line.End ) ); /* * In CADSTAR, the resulting shape orientation of the leader dimension depends on * on the positions of the #Start (S) and #End (E) points as shown below. In the * diagrams below, the leader angle (angRad) is represented by HEV * * Orientation 1: (orientX = -1, | Orientation 2: (orientX = 1, * orientY = 1) | orientY = 1) * | * --------V | V---------- * \ | / * \ | / * H _E/ | \E_ H * | * S | S * | * * Orientation 3: (orientX = -1, | Orientation 4: (orientX = 1, * orientY = -1) | orientY = -1) * | * S | S * _ | _ * H E\ | /E H * / | \ * / | \ * ----------V | V----------- * | * * Corner cases: * * It is not possible to generate a leader object with start and end point being * identical. Assume Orientation 2 if start and end points are identical. * * If start and end points are aligned vertically (i.e. S.x == E.x): * - If E.y > S.y - Orientation 2 * - If E.y < S.y - Orientation 4 * * If start and end points are aligned horitontally (i.e. S.y == E.y): * - If E.x > S.x - Orientation 2 * - If E.x < S.x - Orientation 1 */ double angRad = DEG2RAD( getAngleDegrees( csDim.Line.LeaderAngle ) ); double orientX = 1; double orientY = 1; if( csDim.Line.End.x >= csDim.Line.Start.x ) { if( csDim.Line.End.y >= csDim.Line.Start.y ) { //Orientation 2 orientX = 1; orientY = 1; } else { //Orientation 4 orientX = 1; orientY = -1; } } else { if( csDim.Line.End.y >= csDim.Line.Start.y ) { //Orientation 1 orientX = -1; orientY = 1; } else { //Orientation 3 orientX = -1; orientY = -1; } } VECTOR2I endOffset( csDim.Line.LeaderLineLength * cos( angRad ) * orientX, csDim.Line.LeaderLineLength * sin( angRad ) * orientY ); VECTOR2I endPoint = VECTOR2I( csDim.Line.End ) + endOffset; VECTOR2I txtPoint( endPoint.x + ( csDim.Line.LeaderLineExtensionLength * orientX ), endPoint.y ); leaderDim->SetEnd( getKiCadPoint( endPoint ) ); leaderDim->SetTextPos( getKiCadPoint( txtPoint ) ); leaderDim->SetOverrideText( ParseTextFields( csDim.Text.Text, &m_context ) ); leaderDim->SetPrefix( wxEmptyString ); leaderDim->SetSuffix( wxEmptyString ); leaderDim->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX ); if( orientX == 1 ) leaderDim->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); else leaderDim->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); leaderDim->SetExtensionOffset( 0 ); } break; case DIMENSION::TYPE::ANGLEDIM: //TODO: update import when KiCad supports angular dimensions wxLogError( _( "Dimension %s is an angular dimension which has no KiCad equivalent. " "The object was not imported." ), csDim.ID ); break; } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadAreas() { for( std::pair areaPair : Layout.Areas ) { AREA& area = areaPair.second; if( area.NoVias || area.NoTracks || area.Keepout || area.Routing ) { int lineThickness = 0; // CADSTAR areas only use the line width for display purpose ZONE* zone = getZoneFromCadstarShape( area.Shape, lineThickness, m_board ); m_board->Add( zone, ADD_MODE::APPEND ); if( isLayerSet( area.LayerID ) ) zone->SetLayerSet( getKiCadLayerSet( area.LayerID ) ); else zone->SetLayer( getKiCadLayer( area.LayerID ) ); zone->SetIsRuleArea( true ); //import all CADSTAR areas as Keepout zones zone->SetDoNotAllowPads( false ); //no CADSTAR equivalent zone->SetZoneName( area.Name ); zone->SetDoNotAllowFootprints( area.Keepout ); zone->SetDoNotAllowTracks( area.NoTracks ); zone->SetDoNotAllowCopperPour( area.NoTracks ); zone->SetDoNotAllowVias( area.NoVias ); if( area.Placement ) { wxLogWarning( wxString::Format( _( "The CADSTAR area '%s' is marked as a placement " "area in CADSTAR. Placement areas are not " "supported in KiCad. Only the supported elements " "for the area were imported." ), area.Name ) ); } } else { wxLogError( wxString::Format( _( "The CADSTAR area '%s' does not have a KiCad " "equivalent. Pure Placement areas are not supported." ), area.Name ) ); } //todo Process area.AreaHeight when KiCad supports 3D design rules //TODO process attributes //TODO process addition to a group //TODO process "swaprule" //TODO process re-use block } } void CADSTAR_PCB_ARCHIVE_LOADER::loadComponents() { for( std::pair compPair : Layout.Components ) { COMPONENT& comp = compPair.second; if( !comp.VariantID.empty() && comp.VariantParentComponentID != comp.ID ) continue; // Only load master Variant auto fpIter = m_libraryMap.find( comp.SymdefID ); if( fpIter == m_libraryMap.end() ) { THROW_IO_ERROR( wxString::Format( _( "Unable to find component '%s' in the library" "(Symdef ID: '%s')" ), comp.Name, comp.SymdefID ) ); } FOOTPRINT* libFootprint = fpIter->second; // Use Duplicate() to ensure unique KIID for all objects FOOTPRINT* footprint = static_cast( libFootprint->Duplicate() ); m_board->Add( footprint, ADD_MODE::APPEND ); // First lets fix the pad names on the footprint. // CADSTAR defines the pad name in the PART definition and the SYMDEF (i.e. the PCB // footprint definition) uses a numerical sequence. COMP is the only object that has // visibility of both the SYMDEF and PART. if( Parts.PartDefinitions.find( comp.PartID ) != Parts.PartDefinitions.end() ) { PART part = Parts.PartDefinitions.at( comp.PartID ); // Only do this when the number of pins in the part definition equals the number of // pads in the footprint. if( part.Definition.Pins.size() == footprint->Pads().size() ) { for( std::pair pinPair : part.Definition.Pins ) { PART::DEFINITION::PIN pin = pinPair.second; wxString pinName = pin.Name; if( pinName.empty() ) pinName = pin.Identifier; if( pinName.empty() ) pinName = wxString::Format( wxT( "%ld" ), pin.ID ); getPadReference( footprint, pin.ID )->SetNumber( pinName ); } } } //Override pads with pad exceptions if( comp.PadExceptions.size() > 0 ) { SYMDEF_PCB fpLibEntry = Library.ComponentDefinitions.at( comp.SymdefID ); for( std::pair padPair : comp.PadExceptions ) { PADEXCEPTION& padEx = padPair.second; COMPONENT_PAD csPad = fpLibEntry.ComponentPads.at( padPair.first ); // Reset the pad to be around 0,0 csPad.Position -= fpLibEntry.Origin; csPad.Position += m_designCenter; if( !padEx.PadCode.IsEmpty() ) csPad.PadCodeID = padEx.PadCode; if( padEx.OverrideExits ) csPad.Exits = padEx.Exits; if( padEx.OverrideOrientation ) csPad.OrientAngle = padEx.OrientAngle; if( padEx.OverrideSide ) csPad.Side = padEx.Side; // Find the pad in the footprint definition PAD* kiPad = getPadReference( footprint, padEx.ID ); wxString padNumber = kiPad->GetNumber(); delete kiPad; if( ( kiPad = getKiCadPad( csPad, footprint ) ) ) { kiPad->SetNumber( padNumber ); // Change the pointer in the footprint to the newly created pad getPadReference( footprint, padEx.ID ) = kiPad; } } } //set to empty string to avoid duplication when loading attributes: footprint->SetValue( wxEmptyString ); footprint->SetPosition( getKiCadPoint( comp.Origin ) ); footprint->SetOrientation( getAngle( comp.OrientAngle ) ); footprint->SetReference( comp.Name ); if( comp.Mirror ) { EDA_ANGLE mirroredAngle = - getAngle( comp.OrientAngle ); mirroredAngle.Normalize180(); footprint->SetOrientation( mirroredAngle ); footprint->Flip( getKiCadPoint( comp.Origin ), true ); } loadComponentAttributes( comp, footprint ); if( !comp.PartID.IsEmpty() && comp.PartID != wxT( "NO_PART" ) ) footprint->SetLibDescription( getPart( comp.PartID ).Definition.Name ); m_componentMap.insert( { comp.ID, footprint } ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadDocumentationSymbols() { //No KiCad equivalent. Loaded as graphic and text elements instead for( std::pair docPair : Layout.DocumentationSymbols ) { DOCUMENTATION_SYMBOL& docSymInstance = docPair.second; auto docSymIter = Library.ComponentDefinitions.find( docSymInstance.SymdefID ); if( docSymIter == Library.ComponentDefinitions.end() ) { THROW_IO_ERROR( wxString::Format( _( "Unable to find documentation symbol in the " "library (Symdef ID: '%s')" ), docSymInstance.SymdefID ) ); } SYMDEF_PCB& docSymDefinition = ( *docSymIter ).second; VECTOR2I moveVector = getKiCadPoint( docSymInstance.Origin ) - getKiCadPoint( docSymDefinition.Origin ); double rotationAngle = getAngleTenthDegree( docSymInstance.OrientAngle ); double scalingFactor = (double) docSymInstance.ScaleRatioNumerator / (double) docSymInstance.ScaleRatioDenominator; VECTOR2I centreOfTransform = getKiCadPoint( docSymDefinition.Origin ); bool mirrorInvert = docSymInstance.Mirror; //create a group to store the items in wxString groupName = docSymDefinition.ReferenceName; if( !docSymDefinition.Alternate.IsEmpty() ) groupName += wxT( " (" ) + docSymDefinition.Alternate + wxT( ")" ); GROUP_ID groupID = createUniqueGroupID( groupName ); LSEQ layers = getKiCadLayerSet( docSymInstance.LayerID ).Seq(); for( PCB_LAYER_ID layer : layers ) { for( std::pair figPair : docSymDefinition.Figures ) { FIGURE fig = figPair.second; drawCadstarShape( fig.Shape, layer, getLineThickness( fig.LineCodeID ), wxString::Format( wxT( "DOCUMENTATION SYMBOL %s, FIGURE %s" ), docSymDefinition.ReferenceName, fig.ID ), m_board, groupID, moveVector, rotationAngle, scalingFactor, centreOfTransform, mirrorInvert ); } } for( std::pair textPair : docSymDefinition.Texts ) { TEXT txt = textPair.second; drawCadstarText( txt, m_board, groupID, docSymInstance.LayerID, moveVector, rotationAngle, scalingFactor, centreOfTransform, mirrorInvert ); } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadTemplates() { for( std::pair tempPair : Layout.Templates ) { TEMPLATE& csTemplate = tempPair.second; int zonelinethickness = 0; // The line thickness in CADSTAR is only for display purposes but // does not affect the end copper result. ZONE* zone = getZoneFromCadstarShape( csTemplate.Shape, zonelinethickness, m_board ); m_board->Add( zone, ADD_MODE::APPEND ); zone->SetZoneName( csTemplate.Name ); zone->SetLayer( getKiCadLayer( csTemplate.LayerID ) ); zone->SetAssignedPriority( 1 ); // initially 1, we will increase in calculateZonePriorities if( !( csTemplate.NetID.IsEmpty() || csTemplate.NetID == wxT( "NONE" ) ) ) zone->SetNet( getKiCadNet( csTemplate.NetID ) ); if( csTemplate.Pouring.AllowInNoRouting ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has the setting 'Allow in No Routing Areas' " "enabled. This setting has no KiCad equivalent, so it has been ignored." ), csTemplate.Name ) ); } if( csTemplate.Pouring.BoxIsolatedPins ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has the setting 'Box Isolated Pins' " "enabled. This setting has no KiCad equivalent, so it has been ignored." ), csTemplate.Name ) ); } if( csTemplate.Pouring.AutomaticRepour ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has the setting 'Automatic Repour' " "enabled. This setting has no KiCad equivalent, so it has been ignored." ), csTemplate.Name ) ); } // Sliver width has different behaviour to KiCad Zone's minimum thickness // In Cadstar 'Sliver width' has to be greater than the Copper thickness, whereas in // Kicad it is the opposite. if( csTemplate.Pouring.SliverWidth != 0 ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has a non-zero value defined for the " "'Sliver Width' setting. There is no KiCad equivalent for " "this, so this setting was ignored." ), csTemplate.Name ) ); } if( csTemplate.Pouring.MinIsolatedCopper != csTemplate.Pouring.MinDisjointCopper ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has different settings for 'Retain Poured Copper " "- Disjoint' and 'Retain Poured Copper - Isolated'. KiCad does not " "distinguish between these two settings. The setting for disjoint copper " "has been applied as the minimum island area of the KiCad Zone." ), csTemplate.Name ) ); } long long minIslandArea = -1; if( csTemplate.Pouring.MinDisjointCopper != UNDEFINED_VALUE ) { minIslandArea = (long long) getKiCadLength( csTemplate.Pouring.MinDisjointCopper ) * (long long) getKiCadLength( csTemplate.Pouring.MinDisjointCopper ); zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::AREA ); } else { zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::ALWAYS ); } zone->SetMinIslandArea( minIslandArea ); // In cadstar zone clearance is in addition to the global clearance. // TODO: need to create custom rules for individual items: zone to pad, zone to track, etc. int clearance = getKiCadLength( csTemplate.Pouring.AdditionalIsolation ); clearance += m_board->GetDesignSettings().m_MinClearance; zone->SetLocalClearance( clearance ); COPPERCODE pouringCopperCode = getCopperCode( csTemplate.Pouring.CopperCodeID ); int minThickness = getKiCadLength( pouringCopperCode.CopperWidth ); zone->SetMinThickness( minThickness ); if( csTemplate.Pouring.FillType == TEMPLATE::POURING::COPPER_FILL_TYPE::HATCHED ) { zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN ); zone->SetHatchGap( getKiCadHatchCodeGap( csTemplate.Pouring.HatchCodeID ) ); zone->SetHatchThickness( getKiCadHatchCodeThickness( csTemplate.Pouring.HatchCodeID ) ); zone->SetHatchOrientation( getHatchCodeAngle( csTemplate.Pouring.HatchCodeID ) ); } else { zone->SetFillMode( ZONE_FILL_MODE::POLYGONS ); } if( csTemplate.Pouring.ThermalReliefOnPads != csTemplate.Pouring.ThermalReliefOnVias || csTemplate.Pouring.ThermalReliefPadsAngle != csTemplate.Pouring.ThermalReliefViasAngle ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has different settings for thermal relief " "in pads and vias. KiCad only supports one single setting for both. The " "setting for pads has been applied." ), csTemplate.Name ) ); } COPPERCODE reliefCopperCode = getCopperCode( csTemplate.Pouring.ReliefCopperCodeID ); int spokeWidth = getKiCadLength( reliefCopperCode.CopperWidth ); int reliefWidth = getKiCadLength( csTemplate.Pouring.ClearanceWidth ); // Cadstar supports having a spoke width thinner than the minimum thickness of the zone, but // this is not permitted in KiCad. We load it as solid fill instead. if( csTemplate.Pouring.ThermalReliefOnPads && reliefWidth > 0 ) { if( spokeWidth < minThickness ) { wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has thermal reliefs in the original design " "but the spoke width (%.2f mm) is thinner than the minimum thickness of " "the zone (%.2f mm). KiCad requires the minimum thickness of the zone " "to be preserved. Therefore the minimum thickness has been applied as " "the new spoke width and will be applied next time the zones are " "filled." ), csTemplate.Name, (double) getKiCadLength( spokeWidth ) / 1E6, (double) getKiCadLength( minThickness ) / 1E6 ) ); spokeWidth = minThickness; } zone->SetThermalReliefGap( reliefWidth ); zone->SetThermalReliefSpokeWidth( spokeWidth ); zone->SetPadConnection( ZONE_CONNECTION::THERMAL ); } else { zone->SetPadConnection( ZONE_CONNECTION::FULL ); } m_zonesMap.insert( { csTemplate.ID, zone } ); } //Now create power plane layers: for( LAYER_ID layer : m_powerPlaneLayers ) { wxASSERT( Assignments.Layerdefs.Layers.find( layer ) != Assignments.Layerdefs.Layers.end() ); //The net name will equal the layer name wxString powerPlaneLayerName = Assignments.Layerdefs.Layers.at( layer ).Name; NET_ID netid = wxEmptyString; for( std::pair netPair : Layout.Nets ) { NET_PCB net = netPair.second; if( net.Name == powerPlaneLayerName ) { netid = net.ID; break; } } if( netid.IsEmpty() ) { wxLogError( _( "The CADSTAR layer '%s' is defined as a power plane layer. However no " "net with such name exists. The layer has been loaded but no copper " "zone was created." ), powerPlaneLayerName ); } else { for( std::pair boardPair : Layout.Boards ) { //create a zone in each board shape BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); CADSTAR_BOARD& board = boardPair.second; int defaultLineThicknesss = bds.GetLineThickness( PCB_LAYER_ID::Edge_Cuts ); ZONE* zone = getZoneFromCadstarShape( board.Shape, defaultLineThicknesss, m_board ); m_board->Add( zone, ADD_MODE::APPEND ); zone->SetZoneName( powerPlaneLayerName ); zone->SetLayer( getKiCadLayer( layer ) ); zone->SetFillMode( ZONE_FILL_MODE::POLYGONS ); zone->SetPadConnection( ZONE_CONNECTION::FULL ); zone->SetMinIslandArea( -1 ); zone->SetAssignedPriority( 0 ); // Priority always 0 (lowest priority) for implied power planes. zone->SetNet( getKiCadNet( netid ) ); } } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadCoppers() { for( std::pair copPair : Layout.Coppers ) { COPPER& csCopper = copPair.second; checkPoint(); if( !csCopper.PouredTemplateID.IsEmpty() ) { ZONE* pouredZone = m_zonesMap.at( csCopper.PouredTemplateID ); SHAPE_POLY_SET fill; int copperWidth = getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth ); if( csCopper.Shape.Type == SHAPE_TYPE::OPENSHAPE ) { // This is usually for themal reliefs. They are lines of copper with a thickness. // We convert them to an oval in most cases, but handle also the possibility of // encountering arcs in here. std::vector outlineShapes = getShapesFromVertices( csCopper.Shape.Vertices ); for( PCB_SHAPE* shape : outlineShapes ) { SHAPE_POLY_SET poly; if( shape->GetShape() == SHAPE_T::ARC ) { TransformArcToPolygon( poly, shape->GetStart(), shape->GetArcMid(), shape->GetEnd(), copperWidth, ARC_HIGH_DEF, ERROR_LOC::ERROR_INSIDE ); } else { TransformOvalToPolygon( poly, shape->GetStart(), shape->GetEnd(), copperWidth, ARC_HIGH_DEF, ERROR_LOC::ERROR_INSIDE ); } poly.ClearArcs(); fill.BooleanAdd( poly, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } } else { fill = getPolySetFromCadstarShape( csCopper.Shape, -1 ); fill.ClearArcs(); fill.Inflate( copperWidth / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF ); } if( pouredZone->HasFilledPolysForLayer( getKiCadLayer( csCopper.LayerID ) ) ) { fill.BooleanAdd( *pouredZone->GetFill( getKiCadLayer( csCopper.LayerID ) ), SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } fill.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); pouredZone->SetFilledPolysList( getKiCadLayer( csCopper.LayerID ), fill ); pouredZone->SetIsFilled( true ); pouredZone->SetNeedRefill( false ); continue; } // For now we are going to load coppers to a KiCad zone however this isn't perfect //TODO: Load onto a graphical polygon with a net (when KiCad has this feature) if( !m_doneCopperWarning ) { wxLogWarning( _( "The CADSTAR design contains COPPER elements, which have no direct KiCad " "equivalent. These have been imported as a KiCad Zone if solid or hatch " "filled, or as a KiCad Track if the shape was an unfilled outline (open or " "closed)." ) ); m_doneCopperWarning = true; } if( csCopper.Shape.Type == SHAPE_TYPE::OPENSHAPE || csCopper.Shape.Type == SHAPE_TYPE::OUTLINE ) { std::vector outlineShapes = getShapesFromVertices( csCopper.Shape.Vertices ); std::vector outlineTracks = makeTracksFromShapes( outlineShapes, m_board, getKiCadNet( csCopper.NetRef.NetID ), getKiCadLayer( csCopper.LayerID ), getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth ) ); //cleanup for( PCB_SHAPE* shape : outlineShapes ) delete shape; for( CUTOUT cutout : csCopper.Shape.Cutouts ) { std::vector cutoutShapes = getShapesFromVertices( cutout.Vertices ); std::vector cutoutTracks = makeTracksFromShapes( cutoutShapes, m_board, getKiCadNet( csCopper.NetRef.NetID ), getKiCadLayer( csCopper.LayerID ), getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth )); //cleanup for( PCB_SHAPE* shape : cutoutShapes ) delete shape; } } else { ZONE* zone = getZoneFromCadstarShape( csCopper.Shape, getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth ), m_board ); m_board->Add( zone, ADD_MODE::APPEND ); zone->SetZoneName( csCopper.ID ); zone->SetLayer( getKiCadLayer( csCopper.LayerID ) ); zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH ); if( csCopper.Shape.Type == SHAPE_TYPE::HATCHED ) { zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN ); zone->SetHatchGap( getKiCadHatchCodeGap( csCopper.Shape.HatchCodeID ) ); zone->SetHatchThickness( getKiCadHatchCodeThickness( csCopper.Shape.HatchCodeID ) ); zone->SetHatchOrientation( getHatchCodeAngle( csCopper.Shape.HatchCodeID ) ); } else { zone->SetFillMode( ZONE_FILL_MODE::POLYGONS ); } zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER ); zone->SetPadConnection( ZONE_CONNECTION::FULL ); zone->SetNet( getKiCadNet( csCopper.NetRef.NetID ) ); zone->SetAssignedPriority( m_zonesMap.size() + 1 ); // Highest priority (always fill first) SHAPE_POLY_SET fill( *zone->Outline() ); fill.Fracture( SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE ); zone->SetFilledPolysList( getKiCadLayer( csCopper.LayerID ), fill ); } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadNets() { for( std::pair netPair : Layout.Nets ) { NET_PCB net = netPair.second; wxString netnameForErrorReporting = net.Name; std::map netelementSizes; if( netnameForErrorReporting.IsEmpty() ) netnameForErrorReporting = wxString::Format( wxT( "$%ld" ), net.SignalNum ); for( std::pair viaPair : net.Vias ) { NET_PCB::VIA via = viaPair.second; // viasize is used for calculating route offset (as done in CADSTAR post processor) int viaSize = loadNetVia( net.ID, via ); netelementSizes.insert( { viaPair.first, viaSize } ); } for( std::pair pinPair : net.Pins ) { NET_PCB::PIN pin = pinPair.second; FOOTPRINT* footprint = getFootprintFromCadstarID( pin.ComponentID ); if( footprint == nullptr ) { wxLogWarning( wxString::Format( _( "The net '%s' references component ID '%s' which does not exist. " "This has been ignored." ), netnameForErrorReporting, pin.ComponentID ) ); } else if( ( pin.PadID - (long) 1 ) > footprint->Pads().size() ) { wxLogWarning( wxString::Format( _( "The net '%s' references non-existent pad index" " '%d' in component '%s'. This has been " "ignored." ), netnameForErrorReporting, pin.PadID, footprint->GetReference() ) ); } else { // The below works because we have added the pads in the correct order to the // footprint and the PAD_ID in Cadstar is a sequential, numerical ID PAD* pad = getPadReference( footprint, pin.PadID ); pad->SetNet( getKiCadNet( net.ID ) ); // also set the net to any copper pads (i.e. copper elements that we have imported // as pads instead: SYMDEF_ID symdefid = Layout.Components.at( pin.ComponentID ).SymdefID; if( m_librarycopperpads.find( symdefid ) != m_librarycopperpads.end() ) { ASSOCIATED_COPPER_PADS assocPads = m_librarycopperpads.at( symdefid ); if( assocPads.find( pin.PadID ) != assocPads.end() ) { for( PAD_ID copperPadID : assocPads.at( pin.PadID ) ) { PAD* copperpad = getPadReference( footprint, copperPadID ); copperpad->SetNet( getKiCadNet( net.ID ) ); } } } // padsize is used for calculating route offset (as done in CADSTAR post processor) int padsize = std::min( pad->GetSizeX(), pad->GetSizeY() ); netelementSizes.insert( { pinPair.first, padsize } ); } } // For junction points we need to find out the biggest size of the other routes connecting // at the junction in order to correctly apply the same "route offset" operation that the // CADSTAR post processor applies when generating Manufacturing output. The only exception // is if there is just a single route at the junction point, we use that route width auto getJunctionSize = [&]( NETELEMENT_ID aJptNetElemId, const NET_PCB::CONNECTION_PCB& aConnectionToIgnore ) -> int { int jptsize = std::numeric_limits::max(); for( NET_PCB::CONNECTION_PCB connection : net.Connections ) { if( connection.Route.RouteVertices.size() == 0 ) continue; if( connection.StartNode == aConnectionToIgnore.StartNode && connection.EndNode == aConnectionToIgnore.EndNode ) { continue; } if( connection.StartNode == aJptNetElemId ) { int s = getKiCadLength( connection.Route.RouteVertices.front().RouteWidth ); jptsize = std::max( jptsize, s ); } else if( connection.EndNode == aJptNetElemId ) { int s = getKiCadLength( connection.Route.RouteVertices.back().RouteWidth ); jptsize = std::max( jptsize, s ); } } if( jptsize == std::numeric_limits::max() && !aConnectionToIgnore.Route.RouteVertices.empty() ) { // aConnectionToIgnore is actually the only one that has a route, so lets use that // to determine junction size NET_PCB::ROUTE_VERTEX vertex = aConnectionToIgnore.Route.RouteVertices.front(); if( aConnectionToIgnore.EndNode == aJptNetElemId ) vertex = aConnectionToIgnore.Route.RouteVertices.back(); jptsize = getKiCadLength( vertex.RouteWidth ); } return jptsize; }; for( NET_PCB::CONNECTION_PCB connection : net.Connections ) { int startSize = std::numeric_limits::max(); int endSize = std::numeric_limits::max(); if( netelementSizes.find( connection.StartNode ) != netelementSizes.end() ) startSize = netelementSizes.at( connection.StartNode ); else if( net.Junctions.find( connection.StartNode ) != net.Junctions.end() ) startSize = getJunctionSize( connection.StartNode, connection ); if( netelementSizes.find( connection.EndNode ) != netelementSizes.end() ) endSize = netelementSizes.at( connection.EndNode ); else if( net.Junctions.find( connection.EndNode ) != net.Junctions.end() ) endSize = getJunctionSize( connection.EndNode, connection ); startSize /= KiCadUnitMultiplier; endSize /= KiCadUnitMultiplier; if( !connection.Unrouted ) loadNetTracks( net.ID, connection.Route, startSize, endSize ); } } } void CADSTAR_PCB_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; }; if( m_project ) { std::map& txtVars = m_project->GetTextVars(); // Most of the design text fields can be derived from other elements if( Layout.VariantHierarchy.Variants.size() > 0 ) { VARIANT loadedVar = Layout.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 { wxLogError( _( "Text Variables could not be set as there is no project loaded." ) ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadComponentAttributes( const COMPONENT& aComponent, FOOTPRINT* aFootprint ) { for( std::pair attrPair : aComponent.AttributeValues ) { ATTRIBUTE_VALUE& attrval = attrPair.second; if( attrval.HasLocation ) //only import attributes with location. Ignore the rest { addAttribute( attrval.AttributeLocation, attrval.AttributeID, aFootprint, attrval.Value ); } } for( std::pair textlocPair : aComponent.TextLocations ) { TEXT_LOCATION& textloc = textlocPair.second; wxString attrval; if( textloc.AttributeID == COMPONENT_NAME_ATTRID ) { attrval = wxEmptyString; // Designator is loaded separately } else if( textloc.AttributeID == COMPONENT_NAME_2_ATTRID ) { attrval = wxT( "${REFERENCE}" ); } else if( textloc.AttributeID == PART_NAME_ATTRID ) { attrval = getPart( aComponent.PartID ).Name; } else attrval = getAttributeValue( textloc.AttributeID, aComponent.AttributeValues ); addAttribute( textloc, textloc.AttributeID, aFootprint, attrval ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadNetTracks( const NET_ID& aCadstarNetID, const NET_PCB::ROUTE& aCadstarRoute, long aStartWidth, long aEndWidth ) { if( aCadstarRoute.RouteVertices.size() == 0 ) return; std::vector shapes; std::vector routeVertices = aCadstarRoute.RouteVertices; // Add thin route at front so that route offsetting works as expected if( aStartWidth < routeVertices.front().RouteWidth ) { NET_PCB::ROUTE_VERTEX newFrontVertex = aCadstarRoute.RouteVertices.front(); newFrontVertex.RouteWidth = aStartWidth; newFrontVertex.Vertex.End = aCadstarRoute.StartPoint; routeVertices.insert( routeVertices.begin(), newFrontVertex ); } // Add thin route at the back if required if( aEndWidth < routeVertices.back().RouteWidth ) { NET_PCB::ROUTE_VERTEX newBackVertex = aCadstarRoute.RouteVertices.back(); newBackVertex.RouteWidth = aEndWidth; routeVertices.push_back( newBackVertex ); } POINT prevEnd = aCadstarRoute.StartPoint; for( const NET_PCB::ROUTE_VERTEX& v : routeVertices ) { PCB_SHAPE* shape = getShapeFromVertex( prevEnd, v.Vertex ); shape->SetLayer( getKiCadLayer( aCadstarRoute.LayerID ) ); shape->SetStroke( STROKE_PARAMS( getKiCadLength( v.RouteWidth ), LINE_STYLE::SOLID ) ); shape->SetLocked( v.Fixed ); shapes.push_back( shape ); prevEnd = v.Vertex.End; if( !m_doneTearDropWarning && ( v.TeardropAtEnd || v.TeardropAtStart ) ) { // TODO: load teardrops wxLogError( _( "The CADSTAR design contains teardrops. This importer does not yet " "support them, so the teardrops in the design have been ignored." ) ); m_doneTearDropWarning = true; } } NETINFO_ITEM* net = getKiCadNet( aCadstarNetID ); std::vector tracks = makeTracksFromShapes( shapes, m_board, net ); //cleanup for( PCB_SHAPE* shape : shapes ) delete shape; } int CADSTAR_PCB_ARCHIVE_LOADER::loadNetVia( const NET_ID& aCadstarNetID, const NET_PCB::VIA& aCadstarVia ) { PCB_VIA* via = new PCB_VIA( m_board ); m_board->Add( via, ADD_MODE::APPEND ); VIACODE csViaCode = getViaCode( aCadstarVia.ViaCodeID ); LAYERPAIR csLayerPair = getLayerPair( aCadstarVia.LayerPairID ); via->SetPosition( getKiCadPoint( aCadstarVia.Location ) ); via->SetDrill( getKiCadLength( csViaCode.DrillDiameter ) ); via->SetLocked( aCadstarVia.Fixed ); if( csViaCode.Shape.ShapeType != PAD_SHAPE_TYPE::CIRCLE ) { wxLogError( _( "The CADSTAR via code '%s' has different shape from a circle defined. " "KiCad only supports circular vias so this via type has been changed to " "be a via with circular shape of %.2f mm diameter." ), csViaCode.Name, (double) ( (double) getKiCadLength( csViaCode.Shape.Size ) / 1E6 ) ); } via->SetWidth( getKiCadLength( csViaCode.Shape.Size ) ); bool start_layer_outside = csLayerPair.PhysicalLayerStart == 1 || csLayerPair.PhysicalLayerStart == Assignments.Technology.MaxPhysicalLayer; bool end_layer_outside = csLayerPair.PhysicalLayerEnd == 1 || csLayerPair.PhysicalLayerEnd == Assignments.Technology.MaxPhysicalLayer; if( start_layer_outside && end_layer_outside ) { via->SetViaType( VIATYPE::THROUGH ); } else if( ( !start_layer_outside ) && ( !end_layer_outside ) ) { via->SetViaType( VIATYPE::BLIND_BURIED ); } else { via->SetViaType( VIATYPE::MICROVIA ); } via->SetLayerPair( getKiCadCopperLayerID( csLayerPair.PhysicalLayerStart ), getKiCadCopperLayerID( csLayerPair.PhysicalLayerEnd ) ); via->SetNet( getKiCadNet( aCadstarNetID ) ); ///todo add netcode to the via return via->GetWidth(); } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarText( const TEXT& aCadstarText, BOARD_ITEM_CONTAINER* aContainer, const GROUP_ID& aCadstarGroupID, const LAYER_ID& aCadstarLayerOverride, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { PCB_TEXT* txt = new PCB_TEXT( aContainer ); aContainer->Add( txt ); txt->SetText( aCadstarText.Text ); EDA_ANGLE rotationAngle( aRotationAngle, TENTHS_OF_A_DEGREE_T ); VECTOR2I rotatedTextPos = getKiCadPoint( aCadstarText.Position ); RotatePoint( rotatedTextPos, aTransformCentre, rotationAngle ); rotatedTextPos.x = KiROUND( ( rotatedTextPos.x - aTransformCentre.x ) * aScalingFactor ); rotatedTextPos.y = KiROUND( ( rotatedTextPos.y - aTransformCentre.y ) * aScalingFactor ); rotatedTextPos += aTransformCentre; txt->SetTextPos( rotatedTextPos ); txt->SetPosition( rotatedTextPos ); txt->SetTextAngle( getAngle( aCadstarText.OrientAngle ) + rotationAngle ); txt->SetMirrored( aCadstarText.Mirror ); applyTextCode( txt, aCadstarText.TextCodeID ); switch( aCadstarText.Alignment ) { case ALIGNMENT::NO_ALIGNMENT: // Default for Single line text is Bottom Left case ALIGNMENT::BOTTOMLEFT: txt->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case ALIGNMENT::BOTTOMCENTER: txt->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case ALIGNMENT::BOTTOMRIGHT: txt->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; case ALIGNMENT::CENTERLEFT: txt->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case ALIGNMENT::CENTERCENTER: txt->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case ALIGNMENT::CENTERRIGHT: txt->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; case ALIGNMENT::TOPLEFT: txt->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case ALIGNMENT::TOPCENTER: txt->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case ALIGNMENT::TOPRIGHT: txt->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; default: wxFAIL_MSG( wxT( "Unknown Alignment - needs review!" ) ); } if( aMirrorInvert ) txt->Flip( aTransformCentre, true ); //scale it after flipping: if( aScalingFactor != 1.0 ) { VECTOR2I unscaledTextSize = txt->GetTextSize(); int unscaledThickness = txt->GetTextThickness(); VECTOR2I scaledTextSize; scaledTextSize.x = KiROUND( (double) unscaledTextSize.x * aScalingFactor ); scaledTextSize.y = KiROUND( (double) unscaledTextSize.y * aScalingFactor ); txt->SetTextSize( scaledTextSize ); txt->SetTextThickness( KiROUND( (double) unscaledThickness * aScalingFactor ) ); } txt->Move( aMoveVector ); if( aCadstarText.Alignment == ALIGNMENT::NO_ALIGNMENT ) FixTextPositionNoAlignment( txt ); LAYER_ID layersToDrawOn = aCadstarLayerOverride; if( layersToDrawOn.IsEmpty() ) layersToDrawOn = aCadstarText.LayerID; if( isLayerSet( layersToDrawOn ) ) { //Make a copy on each layer for( PCB_LAYER_ID layer : getKiCadLayerSet( layersToDrawOn ).Seq() ) { txt->SetLayer( layer ); PCB_TEXT* newtxt = static_cast( txt->Duplicate() ); m_board->Add( newtxt, ADD_MODE::APPEND ); if( !aCadstarGroupID.IsEmpty() ) addToGroup( aCadstarGroupID, newtxt ); } m_board->Remove( txt ); delete txt; } else { txt->SetLayer( getKiCadLayer( layersToDrawOn ) ); if( !aCadstarGroupID.IsEmpty() ) addToGroup( aCadstarGroupID, txt ); } //TODO Handle different font types when KiCad can support it. } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarShape( const SHAPE& aCadstarShape, const PCB_LAYER_ID& aKiCadLayer, int aLineThickness, const wxString& aShapeName, BOARD_ITEM_CONTAINER* aContainer, const GROUP_ID& aCadstarGroupID, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { auto drawAsOutline = [&]() { drawCadstarVerticesAsShapes( aCadstarShape.Vertices, aKiCadLayer, aLineThickness, aContainer, aCadstarGroupID, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); drawCadstarCutoutsAsShapes( aCadstarShape.Cutouts, aKiCadLayer, aLineThickness, aContainer, aCadstarGroupID, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); }; switch( aCadstarShape.Type ) { case SHAPE_TYPE::OPENSHAPE: case SHAPE_TYPE::OUTLINE: ///TODO update this when Polygons in KiCad can be defined with no fill drawAsOutline(); break; case SHAPE_TYPE::HATCHED: ///TODO update this when Polygons in KiCad can be defined with hatch fill wxLogWarning( wxString::Format( _( "The shape for '%s' is Hatch filled in CADSTAR, which has no KiCad equivalent. " "Using solid fill instead." ), aShapeName ) ); case SHAPE_TYPE::SOLID: { // Special case solid shapes that are effectively a single line if( aCadstarShape.Vertices.size() < 3 ) { drawAsOutline(); break; } PCB_SHAPE* shape = new PCB_SHAPE( aContainer, SHAPE_T::POLY ); shape->SetFilled( true ); SHAPE_POLY_SET shapePolys = getPolySetFromCadstarShape( aCadstarShape, -1, aContainer, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); shapePolys.Fracture( SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE ); shape->SetPolyShape( shapePolys ); shape->SetStroke( STROKE_PARAMS( aLineThickness, LINE_STYLE::SOLID ) ); shape->SetLayer( aKiCadLayer ); aContainer->Add( shape, ADD_MODE::APPEND ); if( !aCadstarGroupID.IsEmpty() ) addToGroup( aCadstarGroupID, shape ); } break; } } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarCutoutsAsShapes( const std::vector& aCutouts, const PCB_LAYER_ID& aKiCadLayer, int aLineThickness, BOARD_ITEM_CONTAINER* aContainer, const GROUP_ID& aCadstarGroupID, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { for( const CUTOUT& cutout : aCutouts ) { drawCadstarVerticesAsShapes( cutout.Vertices, aKiCadLayer, aLineThickness, aContainer, aCadstarGroupID, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); } } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarVerticesAsShapes( const std::vector& aCadstarVertices, const PCB_LAYER_ID& aKiCadLayer, int aLineThickness, BOARD_ITEM_CONTAINER* aContainer, const GROUP_ID& aCadstarGroupID, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { std::vector shapes = getShapesFromVertices( aCadstarVertices, aContainer, aCadstarGroupID, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); for( PCB_SHAPE* shape : shapes ) { shape->SetStroke( STROKE_PARAMS( aLineThickness, LINE_STYLE::SOLID ) ); shape->SetLayer( aKiCadLayer ); shape->SetParent( aContainer ); aContainer->Add( shape, ADD_MODE::APPEND ); } } std::vector CADSTAR_PCB_ARCHIVE_LOADER::getShapesFromVertices( const std::vector& aCadstarVertices, BOARD_ITEM_CONTAINER* aContainer, const GROUP_ID& aCadstarGroupID, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { std::vector shapes; if( aCadstarVertices.size() < 2 ) //need at least two points to draw a segment! (unlikely but possible to have only one) return shapes; const VERTEX* prev = &aCadstarVertices.at( 0 ); // first one should always be a point vertex const VERTEX* cur; for( size_t i = 1; i < aCadstarVertices.size(); i++ ) { cur = &aCadstarVertices.at( i ); shapes.push_back( getShapeFromVertex( prev->End, *cur, aContainer, aCadstarGroupID, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ) ); prev = cur; } return shapes; } PCB_SHAPE* CADSTAR_PCB_ARCHIVE_LOADER::getShapeFromVertex( const POINT& aCadstarStartPoint, const VERTEX& aCadstarVertex, BOARD_ITEM_CONTAINER* aContainer, const GROUP_ID& aCadstarGroupID, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { PCB_SHAPE* shape = nullptr; bool cw = false; VECTOR2I startPoint = getKiCadPoint( aCadstarStartPoint ); VECTOR2I endPoint = getKiCadPoint( aCadstarVertex.End ); VECTOR2I centerPoint; if( aCadstarVertex.Type == VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE || aCadstarVertex.Type == VERTEX_TYPE::CLOCKWISE_SEMICIRCLE ) { centerPoint = ( startPoint + endPoint ) / 2; } else { centerPoint = getKiCadPoint( aCadstarVertex.Center ); } switch( aCadstarVertex.Type ) { case VERTEX_TYPE::POINT: shape = new PCB_SHAPE( aContainer, SHAPE_T::SEGMENT ); shape->SetStart( startPoint ); shape->SetEnd( 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 PCB_SHAPE( aContainer, SHAPE_T::ARC ); shape->SetCenter( centerPoint ); shape->SetStart( startPoint ); EDA_ANGLE arcStartAngle( startPoint - centerPoint ); EDA_ANGLE arcEndAngle( endPoint - centerPoint ); EDA_ANGLE arcAngle = ( arcEndAngle - arcStartAngle ).Normalize(); //TODO: detect if we are supposed to draw a circle instead (i.e. two SEMICIRCLEs // with opposite start/end points and same centre point) if( !cw ) arcAngle.NormalizeNegative(); // anticlockwise arc shape->SetArcAngleAndEnd( arcAngle, true ); break; } } //Apply transforms if( aMirrorInvert ) shape->Flip( aTransformCentre, true ); if( aScalingFactor != 1.0 ) { shape->Move( -1*aTransformCentre ); shape->Scale( aScalingFactor ); shape->Move( aTransformCentre ); } if( aRotationAngle != 0.0 ) shape->Rotate( aTransformCentre, EDA_ANGLE( aRotationAngle, TENTHS_OF_A_DEGREE_T ) ); if( aMoveVector != VECTOR2I{ 0, 0 } ) shape->Move( aMoveVector ); if( !aCadstarGroupID.IsEmpty() ) addToGroup( aCadstarGroupID, shape ); return shape; } ZONE* CADSTAR_PCB_ARCHIVE_LOADER::getZoneFromCadstarShape( const SHAPE& aCadstarShape, const int& aLineThickness, BOARD_ITEM_CONTAINER* aParentContainer ) { ZONE* zone = new ZONE( aParentContainer ); if( aCadstarShape.Type == SHAPE_TYPE::HATCHED ) { zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN ); zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL ); } else { zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH ); } SHAPE_POLY_SET polygon = getPolySetFromCadstarShape( aCadstarShape, aLineThickness ); zone->AddPolygon( polygon.COutline( 0 ) ); for( int i = 0; i < polygon.HoleCount( 0 ); i++ ) zone->AddPolygon( polygon.CHole( 0, i ) ); return zone; } SHAPE_POLY_SET CADSTAR_PCB_ARCHIVE_LOADER::getPolySetFromCadstarShape( const SHAPE& aCadstarShape, int aLineThickness, BOARD_ITEM_CONTAINER* aContainer, const VECTOR2I& aMoveVector, double aRotationAngle, double aScalingFactor, const VECTOR2I& aTransformCentre, bool aMirrorInvert ) { GROUP_ID noGroup = wxEmptyString; std::vector outlineShapes = getShapesFromVertices( aCadstarShape.Vertices, aContainer, noGroup, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); SHAPE_POLY_SET polySet( getLineChainFromShapes( outlineShapes ) ); //cleanup for( PCB_SHAPE* shape : outlineShapes ) delete shape; for( const CUTOUT& cutout : aCadstarShape.Cutouts ) { std::vector cutoutShapes = getShapesFromVertices( cutout.Vertices, aContainer, noGroup, aMoveVector, aRotationAngle, aScalingFactor, aTransformCentre, aMirrorInvert ); polySet.AddHole( getLineChainFromShapes( cutoutShapes ) ); //cleanup for( PCB_SHAPE* shape : cutoutShapes ) delete shape; } polySet.ClearArcs(); if( aLineThickness > 0 ) polySet.Inflate( aLineThickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF ); #ifdef DEBUG for( int i = 0; i < polySet.OutlineCount(); ++i ) { wxASSERT( polySet.Outline( i ).PointCount() > 2 ); for( int j = 0; j < polySet.HoleCount( i ); ++j ) { wxASSERT( polySet.Hole( i, j ).PointCount() > 2 ); } } #endif return polySet; } SHAPE_LINE_CHAIN CADSTAR_PCB_ARCHIVE_LOADER::getLineChainFromShapes( const std::vector& aShapes ) { SHAPE_LINE_CHAIN lineChain; for( PCB_SHAPE* shape : aShapes ) { switch( shape->GetShape() ) { case SHAPE_T::ARC: { SHAPE_ARC arc( shape->GetCenter(), shape->GetStart(), shape->GetArcAngle() ); if( shape->EndsSwapped() ) arc.Reverse(); lineChain.Append( arc ); break; } case SHAPE_T::SEGMENT: lineChain.Append( shape->GetStartX(), shape->GetStartY() ); lineChain.Append( shape->GetEndX(), shape->GetEndY() ); break; default: wxFAIL_MSG( wxT( "Drawsegment type is unexpected. Ignored." ) ); } } // Shouldn't have less than 3 points to make a closed shape! wxASSERT( lineChain.PointCount() > 2 ); // Check if it is closed if( lineChain.GetPoint( 0 ) != lineChain.GetPoint( lineChain.PointCount() - 1 ) ) { lineChain.Append( lineChain.GetPoint( 0 ) ); } lineChain.SetClosed( true ); return lineChain; } std::vector CADSTAR_PCB_ARCHIVE_LOADER::makeTracksFromShapes( const std::vector& aShapes, BOARD_ITEM_CONTAINER* aParentContainer, NETINFO_ITEM* aNet, PCB_LAYER_ID aLayerOverride, int aWidthOverride ) { std::vector tracks; PCB_TRACK* prevTrack = nullptr; PCB_TRACK* track = nullptr; auto addTrack = [&]( PCB_TRACK* aTrack ) { // Ignore zero length tracks in the same way as the CADSTAR postprocessor does // when generating gerbers. Note that CADSTAR reports these as "Route offset // errors" when running a DRC within CADSTAR, so we shouldn't be getting this in // general, however it is used to remove any synthetic points added to // aDrawSegments by the caller of this function. if( aTrack->GetLength() != 0 ) { tracks.push_back( aTrack ); aParentContainer->Add( aTrack, ADD_MODE::APPEND ); } else { delete aTrack; } }; for( PCB_SHAPE* shape : aShapes ) { switch( shape->GetShape() ) { case SHAPE_T::ARC: { SHAPE_ARC arc( shape->GetStart(), shape->GetArcMid(), shape->GetEnd(), 0 ); if( shape->EndsSwapped() ) arc.Reverse(); track = new PCB_ARC( aParentContainer, &arc ); break; } case SHAPE_T::SEGMENT: track = new PCB_TRACK( aParentContainer ); track->SetStart( shape->GetStart() ); track->SetEnd( shape->GetEnd() ); break; default: wxFAIL_MSG( wxT( "Drawsegment type is unexpected. Ignored." ) ); continue; } if( aWidthOverride == -1 ) track->SetWidth( shape->GetWidth() ); else track->SetWidth( aWidthOverride ); if( aLayerOverride == PCB_LAYER_ID::UNDEFINED_LAYER ) track->SetLayer( shape->GetLayer() ); else track->SetLayer( aLayerOverride ); if( aNet != nullptr ) track->SetNet( aNet ); else track->SetNetCode( -1 ); track->SetLocked( shape->IsLocked() ); // Apply route offsetting, mimmicking the behaviour of the CADSTAR post processor if( prevTrack != nullptr ) { int offsetAmount = ( track->GetWidth() / 2 ) - ( prevTrack->GetWidth() / 2 ); if( offsetAmount > 0 ) { // modify the start of the current track VECTOR2I newStart = track->GetStart(); applyRouteOffset( &newStart, track->GetEnd(), offsetAmount ); track->SetStart( newStart ); } else if( offsetAmount < 0 ) { // amend the end of the previous track VECTOR2I newEnd = prevTrack->GetEnd(); applyRouteOffset( &newEnd, prevTrack->GetStart(), -offsetAmount ); prevTrack->SetEnd( newEnd ); } // don't do anything if offsetAmount == 0 // Add a synthetic track of the thinnest width between the tracks // to ensure KiCad features works as expected on the imported design // (KiCad expects tracks are contiguous segments) if( track->GetStart() != prevTrack->GetEnd() ) { int minWidth = std::min( track->GetWidth(), prevTrack->GetWidth() ); PCB_TRACK* synthTrack = new PCB_TRACK( aParentContainer ); synthTrack->SetStart( prevTrack->GetEnd() ); synthTrack->SetEnd( track->GetStart() ); synthTrack->SetWidth( minWidth ); synthTrack->SetLocked( track->IsLocked() ); synthTrack->SetNet( track->GetNet() ); synthTrack->SetLayer( track->GetLayer() ); addTrack( synthTrack ); } } if( prevTrack ) addTrack( prevTrack ); prevTrack = track; } if( track ) addTrack( track ); return tracks; } void CADSTAR_PCB_ARCHIVE_LOADER::addAttribute( const ATTRIBUTE_LOCATION& aCadstarAttrLoc, const ATTRIBUTE_ID& aCadstarAttributeID, FOOTPRINT* aFootprint, const wxString& aAttributeValue ) { PCB_TEXT* txt; if( aCadstarAttributeID == COMPONENT_NAME_ATTRID ) { txt = &aFootprint->Reference(); //text should be set outside this function } else if( aCadstarAttributeID == PART_NAME_ATTRID ) { if( aFootprint->Value().GetText().IsEmpty() ) { // Use PART_NAME_ATTRID as the value is value field is blank aFootprint->SetValue( aAttributeValue ); txt = &aFootprint->Value(); } else { txt = new PCB_TEXT( aFootprint ); aFootprint->Add( txt ); txt->SetText( aAttributeValue ); } txt->SetVisible( false ); //make invisible to avoid clutter. } else if( aCadstarAttributeID != COMPONENT_NAME_2_ATTRID && getAttributeName( aCadstarAttributeID ) == wxT( "Value" ) ) { if( !aFootprint->Value().GetText().IsEmpty() ) { //copy the object aFootprint->Add( aFootprint->Value().Duplicate() ); } aFootprint->SetValue( aAttributeValue ); txt = &aFootprint->Value(); txt->SetVisible( false ); //make invisible to avoid clutter. } else { txt = new PCB_TEXT( aFootprint ); aFootprint->Add( txt ); txt->SetText( aAttributeValue ); txt->SetVisible( false ); //make all user attributes invisible to avoid clutter. //TODO: Future improvement - allow user to decide what to do with attributes } txt->SetPosition( getKiCadPoint( aCadstarAttrLoc.Position ) ); txt->SetLayer( getKiCadLayer( aCadstarAttrLoc.LayerID ) ); txt->SetMirrored( aCadstarAttrLoc.Mirror ); txt->SetTextAngle( getAngle( aCadstarAttrLoc.OrientAngle ) ); if( aCadstarAttrLoc.Mirror ) // If mirroring, invert angle to match CADSTAR txt->SetTextAngle( -txt->GetTextAngle() ); applyTextCode( txt, aCadstarAttrLoc.TextCodeID ); txt->SetKeepUpright( false ); //Keeping it upright seems to result in incorrect orientation switch( aCadstarAttrLoc.Alignment ) { case ALIGNMENT::NO_ALIGNMENT: // Default for Single line text is Bottom Left FixTextPositionNoAlignment( txt ); KI_FALLTHROUGH; case ALIGNMENT::BOTTOMLEFT: txt->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case ALIGNMENT::BOTTOMCENTER: txt->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case ALIGNMENT::BOTTOMRIGHT: txt->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; case ALIGNMENT::CENTERLEFT: txt->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case ALIGNMENT::CENTERCENTER: txt->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case ALIGNMENT::CENTERRIGHT: txt->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; case ALIGNMENT::TOPLEFT: txt->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case ALIGNMENT::TOPCENTER: txt->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case ALIGNMENT::TOPRIGHT: txt->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); txt->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; default: wxFAIL_MSG( wxT( "Unknown Alignment - needs review!" ) ); } //TODO Handle different font types when KiCad can support it. } void CADSTAR_PCB_ARCHIVE_LOADER::applyRouteOffset( VECTOR2I* aPointToOffset, const VECTOR2I& aRefPoint, const long& aOffsetAmount ) { VECTOR2I v( *aPointToOffset - aRefPoint ); int newLength = v.EuclideanNorm() - aOffsetAmount; if( newLength > 0 ) { VECTOR2I offsetted = v.Resize( newLength ) + VECTOR2I( aRefPoint ); aPointToOffset->x = offsetted.x; aPointToOffset->y = offsetted.y; } else { *aPointToOffset = aRefPoint; // zero length track. Needs to be removed to mimmick // cadstar behaviour } } void CADSTAR_PCB_ARCHIVE_LOADER:: applyTextCode( EDA_TEXT* aKiCadText, const TEXTCODE_ID& aCadstarTextCodeID ) { TEXTCODE tc = getTextCode( aCadstarTextCodeID ); aKiCadText->SetTextThickness( getKiCadLength( tc.LineWidth ) ); VECTOR2I textSize; textSize.x = getKiCadLength( tc.Width ); // The width is zero for all non-cadstar fonts. Using a width equal to the height seems // to work well for most fonts. if( textSize.x == 0 ) textSize.x = getKiCadLength( tc.Height ); textSize.y = KiROUND( TXT_HEIGHT_RATIO * (double) getKiCadLength( tc.Height ) ); if( textSize.x == 0 || textSize.y == 0 ) { // Make zero sized text not visible aKiCadText->SetTextSize( VECTOR2I( EDA_UNIT_UTILS::Mils2IU( pcbIUScale, DEFAULT_SIZE_TEXT ), EDA_UNIT_UTILS::Mils2IU( pcbIUScale, DEFAULT_SIZE_TEXT ) ) ); aKiCadText->SetVisible( false ); } else { aKiCadText->SetTextSize( textSize ); } } int CADSTAR_PCB_ARCHIVE_LOADER::getLineThickness( const LINECODE_ID& aCadstarLineCodeID ) { wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID ) != Assignments.Codedefs.LineCodes.end(), m_board->GetDesignSettings().GetLineThickness( PCB_LAYER_ID::Edge_Cuts ) ); return getKiCadLength( Assignments.Codedefs.LineCodes.at( aCadstarLineCodeID ).Width ); } CADSTAR_PCB_ARCHIVE_LOADER::COPPERCODE CADSTAR_PCB_ARCHIVE_LOADER::getCopperCode( const COPPERCODE_ID& aCadstaCopperCodeID ) { wxCHECK( Assignments.Codedefs.CopperCodes.find( aCadstaCopperCodeID ) != Assignments.Codedefs.CopperCodes.end(), COPPERCODE() ); return Assignments.Codedefs.CopperCodes.at( aCadstaCopperCodeID ); } CADSTAR_PCB_ARCHIVE_LOADER::TEXTCODE CADSTAR_PCB_ARCHIVE_LOADER::getTextCode( const TEXTCODE_ID& aCadstarTextCodeID ) { wxCHECK( Assignments.Codedefs.TextCodes.find( aCadstarTextCodeID ) != Assignments.Codedefs.TextCodes.end(), TEXTCODE() ); return Assignments.Codedefs.TextCodes.at( aCadstarTextCodeID ); } CADSTAR_PCB_ARCHIVE_LOADER::PADCODE CADSTAR_PCB_ARCHIVE_LOADER::getPadCode( const PADCODE_ID& aCadstarPadCodeID ) { wxCHECK( Assignments.Codedefs.PadCodes.find( aCadstarPadCodeID ) != Assignments.Codedefs.PadCodes.end(), PADCODE() ); return Assignments.Codedefs.PadCodes.at( aCadstarPadCodeID ); } CADSTAR_PCB_ARCHIVE_LOADER::VIACODE CADSTAR_PCB_ARCHIVE_LOADER::getViaCode( const VIACODE_ID& aCadstarViaCodeID ) { wxCHECK( Assignments.Codedefs.ViaCodes.find( aCadstarViaCodeID ) != Assignments.Codedefs.ViaCodes.end(), VIACODE() ); return Assignments.Codedefs.ViaCodes.at( aCadstarViaCodeID ); } CADSTAR_PCB_ARCHIVE_LOADER::LAYERPAIR CADSTAR_PCB_ARCHIVE_LOADER::getLayerPair( const LAYERPAIR_ID& aCadstarLayerPairID ) { wxCHECK( Assignments.Codedefs.LayerPairs.find( aCadstarLayerPairID ) != Assignments.Codedefs.LayerPairs.end(), LAYERPAIR() ); return Assignments.Codedefs.LayerPairs.at( aCadstarLayerPairID ); } wxString CADSTAR_PCB_ARCHIVE_LOADER::getAttributeName( const ATTRIBUTE_ID& aCadstarAttributeID ) { wxCHECK( Assignments.Codedefs.AttributeNames.find( aCadstarAttributeID ) != Assignments.Codedefs.AttributeNames.end(), wxEmptyString ); return Assignments.Codedefs.AttributeNames.at( aCadstarAttributeID ).Name; } wxString CADSTAR_PCB_ARCHIVE_LOADER::getAttributeValue( const ATTRIBUTE_ID& aCadstarAttributeID, const std::map& aCadstarAttributeMap ) { wxCHECK( aCadstarAttributeMap.find( aCadstarAttributeID ) != aCadstarAttributeMap.end(), wxEmptyString ); return aCadstarAttributeMap.at( aCadstarAttributeID ).Value; } CADSTAR_PCB_ARCHIVE_LOADER::LAYER_TYPE CADSTAR_PCB_ARCHIVE_LOADER::getLayerType( const LAYER_ID aCadstarLayerID ) { if( Assignments.Layerdefs.Layers.find( aCadstarLayerID ) != Assignments.Layerdefs.Layers.end() ) { return Assignments.Layerdefs.Layers.at( aCadstarLayerID ).Type; } return LAYER_TYPE::UNDEFINED; } CADSTAR_PCB_ARCHIVE_LOADER::PART CADSTAR_PCB_ARCHIVE_LOADER::getPart( const PART_ID& aCadstarPartID ) { wxCHECK( Parts.PartDefinitions.find( aCadstarPartID ) != Parts.PartDefinitions.end(), PART() ); return Parts.PartDefinitions.at( aCadstarPartID ); } CADSTAR_PCB_ARCHIVE_LOADER::ROUTECODE CADSTAR_PCB_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_PCB_ARCHIVE_LOADER::HATCHCODE CADSTAR_PCB_ARCHIVE_LOADER::getHatchCode( const HATCHCODE_ID& aCadstarHatchcodeID ) { wxCHECK( Assignments.Codedefs.HatchCodes.find( aCadstarHatchcodeID ) != Assignments.Codedefs.HatchCodes.end(), HATCHCODE() ); return Assignments.Codedefs.HatchCodes.at( aCadstarHatchcodeID ); } EDA_ANGLE CADSTAR_PCB_ARCHIVE_LOADER::getHatchCodeAngle( const HATCHCODE_ID& aCadstarHatchcodeID ) { checkAndLogHatchCode( aCadstarHatchcodeID ); HATCHCODE hcode = getHatchCode( aCadstarHatchcodeID ); if( hcode.Hatches.size() < 1 ) return m_board->GetDesignSettings().GetDefaultZoneSettings().m_HatchOrientation; else return getAngle( hcode.Hatches.at( 0 ).OrientAngle ); } int CADSTAR_PCB_ARCHIVE_LOADER::getKiCadHatchCodeThickness( const HATCHCODE_ID& aCadstarHatchcodeID ) { checkAndLogHatchCode( aCadstarHatchcodeID ); HATCHCODE hcode = getHatchCode( aCadstarHatchcodeID ); if( hcode.Hatches.size() < 1 ) return m_board->GetDesignSettings().GetDefaultZoneSettings().m_HatchThickness; else return getKiCadLength( hcode.Hatches.at( 0 ).LineWidth ); } int CADSTAR_PCB_ARCHIVE_LOADER::getKiCadHatchCodeGap( const HATCHCODE_ID& aCadstarHatchcodeID ) { checkAndLogHatchCode( aCadstarHatchcodeID ); HATCHCODE hcode = getHatchCode( aCadstarHatchcodeID ); if( hcode.Hatches.size() < 1 ) return m_board->GetDesignSettings().GetDefaultZoneSettings().m_HatchGap; else return getKiCadLength( hcode.Hatches.at( 0 ).Step ); } PCB_GROUP* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadGroup( const GROUP_ID& aCadstarGroupID ) { wxCHECK( m_groupMap.find( aCadstarGroupID ) != m_groupMap.end(), nullptr ); return m_groupMap.at( aCadstarGroupID ); } void CADSTAR_PCB_ARCHIVE_LOADER::checkAndLogHatchCode( const HATCHCODE_ID& aCadstarHatchcodeID ) { if( m_hatchcodesTested.find( aCadstarHatchcodeID ) != m_hatchcodesTested.end() ) { return; //already checked } else { HATCHCODE hcode = getHatchCode( aCadstarHatchcodeID ); if( hcode.Hatches.size() != 2 ) { wxLogWarning( wxString::Format( _( "The CADSTAR Hatching code '%s' has %d hatches defined. " "KiCad only supports 2 hatches (crosshatching) 90 degrees apart. " "The imported hatching is crosshatched." ), hcode.Name, (int) hcode.Hatches.size() ) ); } else { if( hcode.Hatches.at( 0 ).LineWidth != hcode.Hatches.at( 1 ).LineWidth ) { wxLogWarning( wxString::Format( _( "The CADSTAR Hatching code '%s' has different line widths for each " "hatch. KiCad only supports one width for the hatching. The imported " "hatching uses the width defined in the first hatch definition, i.e. " "%.2f mm." ), hcode.Name, (double) ( (double) getKiCadLength( hcode.Hatches.at( 0 ).LineWidth ) ) / 1E6 ) ); } if( hcode.Hatches.at( 0 ).Step != hcode.Hatches.at( 1 ).Step ) { wxLogWarning( wxString::Format( _( "The CADSTAR Hatching code '%s' has different step sizes for each " "hatch. KiCad only supports one step size for the hatching. The imported " "hatching uses the step size defined in the first hatching definition, " "i.e. %.2f mm." ), hcode.Name, (double) ( (double) getKiCadLength( hcode.Hatches.at( 0 ).Step ) ) / 1E6 ) ); } if( abs( hcode.Hatches.at( 0 ).OrientAngle - hcode.Hatches.at( 1 ).OrientAngle ) != 90000 ) { wxLogWarning( wxString::Format( _( "The hatches in CADSTAR Hatching code '%s' have an angle " "difference of %.1f degrees. KiCad only supports hatching 90 " "degrees apart. The imported hatching has two hatches 90 " "degrees apart, oriented %.1f degrees from horizontal." ), hcode.Name, getAngle( abs( hcode.Hatches.at( 0 ).OrientAngle - hcode.Hatches.at( 1 ).OrientAngle ) ).AsDegrees(), getAngle( hcode.Hatches.at( 0 ).OrientAngle ).AsDegrees() ) ); } } m_hatchcodesTested.insert( aCadstarHatchcodeID ); } } void CADSTAR_PCB_ARCHIVE_LOADER::applyDimensionSettings( const DIMENSION& aCadstarDim, PCB_DIMENSION_BASE* aKiCadDim ) { UNITS dimensionUnits = aCadstarDim.LinearUnits; LINECODE linecode = Assignments.Codedefs.LineCodes.at( aCadstarDim.Line.LineCodeID ); aKiCadDim->SetLayer( getKiCadLayer( aCadstarDim.LayerID ) ); aKiCadDim->SetPrecision( static_cast( aCadstarDim.Precision ) ); aKiCadDim->SetStart( getKiCadPoint( aCadstarDim.ExtensionLineParams.Start ) ); aKiCadDim->SetEnd( getKiCadPoint( aCadstarDim.ExtensionLineParams.End ) ); aKiCadDim->SetExtensionOffset( getKiCadLength( aCadstarDim.ExtensionLineParams.Offset ) ); aKiCadDim->SetLineThickness( getKiCadLength( linecode.Width ) ); applyTextCode( aKiCadDim, aCadstarDim.Text.TextCodeID ); // Find prefix and suffix: wxString prefix = wxEmptyString; wxString suffix = wxEmptyString; size_t startpos = aCadstarDim.Text.Text.Find( wxT( "<@DISTANCE" ) ); if( startpos != wxNOT_FOUND ) { prefix = ParseTextFields( aCadstarDim.Text.Text.SubString( 0, startpos - 1 ), &m_context ); wxString remainingStr = aCadstarDim.Text.Text.Mid( startpos ); size_t endpos = remainingStr.Find( "@>" ); suffix = ParseTextFields( remainingStr.Mid( endpos + 2 ), &m_context ); } if( suffix.StartsWith( wxT( "mm" ) ) ) { aKiCadDim->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX ); suffix = suffix.Mid( 2 ); } else { aKiCadDim->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX ); } aKiCadDim->SetPrefix( prefix ); aKiCadDim->SetSuffix( suffix ); if( aCadstarDim.LinearUnits == UNITS::DESIGN ) { // For now we will hardcode the units as per the original CADSTAR design. // TODO: update this when KiCad supports design units aKiCadDim->SetPrecision( static_cast( Assignments.Technology.UnitDisplPrecision ) ); dimensionUnits = Assignments.Technology.Units; } switch( dimensionUnits ) { case UNITS::METER: case UNITS::CENTIMETER: case UNITS::MICROMETRE: wxLogWarning( wxString::Format( _( "Dimension ID %s uses a type of unit that " "is not supported in KiCad. Millimeters were " "applied instead." ), aCadstarDim.ID ) ); KI_FALLTHROUGH; case UNITS::MM: aKiCadDim->SetUnitsMode( DIM_UNITS_MODE::MILLIMETRES ); break; case UNITS::INCH: aKiCadDim->SetUnitsMode( DIM_UNITS_MODE::INCHES ); break; case UNITS::THOU: aKiCadDim->SetUnitsMode( DIM_UNITS_MODE::MILS ); break; case UNITS::DESIGN: wxFAIL_MSG( wxT( "We should have handled design units before coming here!" ) ); break; } } bool CADSTAR_PCB_ARCHIVE_LOADER::calculateZonePriorities( PCB_LAYER_ID& aLayer ) { std::map> winningOverlaps; auto inflateValue = [&]( ZONE* aZoneA, ZONE* aZoneB ) { int extra = getKiCadLength( Assignments.Codedefs.SpacingCodes.at( wxT( "C_C" ) ).Spacing ) - m_board->GetDesignSettings().m_MinClearance; int retval = std::max( aZoneA->GetLocalClearance().value(), aZoneB->GetLocalClearance().value() ); retval += extra; return retval; }; // Find the error in fill area when guessing that aHigherZone gets filled before aLowerZone auto errorArea = [&]( ZONE* aLowerZone, ZONE* aHigherZone ) -> double { SHAPE_POLY_SET intersectShape( *aHigherZone->Outline() ); intersectShape.Inflate( inflateValue( aLowerZone, aHigherZone ), CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF ); SHAPE_POLY_SET lowerZoneFill( *aLowerZone->GetFilledPolysList( aLayer ) ); SHAPE_POLY_SET lowerZoneOutline( *aLowerZone->Outline() ); lowerZoneOutline.BooleanSubtract( intersectShape, SHAPE_POLY_SET::PM_FAST ); lowerZoneFill.BooleanSubtract( lowerZoneOutline, SHAPE_POLY_SET::PM_FAST ); double leftOverArea = lowerZoneFill.Area(); return leftOverArea; }; auto intersectionAreaOfZoneOutlines = [&]( ZONE* aZoneA, ZONE* aZoneB ) -> double { SHAPE_POLY_SET outLineA( *aZoneA->Outline() ); outLineA.Inflate( inflateValue( aZoneA, aZoneB ), CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF ); SHAPE_POLY_SET outLineB( *aZoneA->Outline() ); outLineB.Inflate( inflateValue( aZoneA, aZoneB ), CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF ); outLineA.BooleanIntersection( outLineB, SHAPE_POLY_SET::PM_FAST ); return outLineA.Area(); }; // Lambda to determine if the zone with template ID 'a' is lower priority than 'b' auto isLowerPriority = [&]( const TEMPLATE_ID& a, const TEMPLATE_ID& b ) -> bool { return winningOverlaps[b].count( a ) > 0; }; for( std::map::iterator it1 = m_zonesMap.begin(); it1 != m_zonesMap.end(); ++it1 ) { TEMPLATE& thisTemplate = Layout.Templates.at( it1->first ); ZONE* thisZone = it1->second; if( !thisZone->GetLayerSet().Contains( aLayer ) ) continue; for( std::map::iterator it2 = it1; it2 != m_zonesMap.end(); ++it2 ) { TEMPLATE& otherTemplate = Layout.Templates.at( it2->first ); ZONE* otherZone = it2->second; if( thisTemplate.ID == otherTemplate.ID ) continue; if( !otherZone->GetLayerSet().Contains( aLayer ) ) { checkPoint(); continue; } if( intersectionAreaOfZoneOutlines( thisZone, otherZone ) == 0 ) { checkPoint(); continue; // The zones do not interact in any way } SHAPE_POLY_SET thisZonePolyFill = *thisZone->GetFilledPolysList( aLayer ); SHAPE_POLY_SET otherZonePolyFill = *otherZone->GetFilledPolysList( aLayer ); if( thisZonePolyFill.Area() > 0.0 && otherZonePolyFill.Area() > 0.0 ) { // Test if this zone were lower priority than other zone, what is the error? double areaThis = errorArea( thisZone, otherZone ); // Vice-versa double areaOther = errorArea( otherZone, thisZone ); if( areaThis > areaOther ) { // thisTemplate is filled before otherTemplate winningOverlaps[thisTemplate.ID].insert( otherTemplate.ID ); } else { // thisTemplate is filled AFTER otherTemplate winningOverlaps[otherTemplate.ID].insert( thisTemplate.ID ); } } else if( thisZonePolyFill.Area() > 0.0 ) { // The other template is not filled, this one wins winningOverlaps[thisTemplate.ID].insert( otherTemplate.ID ); } else if( otherZonePolyFill.Area() > 0.0 ) { // This template is not filled, the other one wins winningOverlaps[otherTemplate.ID].insert( thisTemplate.ID ); } else { // Neither of the templates is poured - use zone outlines instead (bigger outlines // get a lower priority) if( intersectionAreaOfZoneOutlines( thisZone, otherZone ) != 0 ) { if( thisZone->Outline()->Area() > otherZone->Outline()->Area() ) winningOverlaps[otherTemplate.ID].insert( thisTemplate.ID ); else winningOverlaps[thisTemplate.ID].insert( otherTemplate.ID ); } } checkPoint(); } } // Build a set of unique TEMPLATE_IDs of all the zones that intersect with another one std::set intersectingIDs; for( const std::pair>& idPair : winningOverlaps ) { intersectingIDs.insert( idPair.first ); intersectingIDs.insert( idPair.second.begin(), idPair.second.end() ); } // Now store them in a vector std::vector sortedIDs; for( const TEMPLATE_ID& id : intersectingIDs ) { sortedIDs.push_back( id ); } // sort by priority std::sort( sortedIDs.begin(), sortedIDs.end(), isLowerPriority ); TEMPLATE_ID prevID = wxEmptyString; for( const TEMPLATE_ID& id : sortedIDs ) { if( prevID.IsEmpty() ) { prevID = id; continue; } wxASSERT( !isLowerPriority( id, prevID ) ); int newPriority = m_zonesMap.at( prevID )->GetAssignedPriority(); // Only increase priority of the current zone if( isLowerPriority( prevID, id ) ) newPriority++; m_zonesMap.at( id )->SetAssignedPriority( newPriority ); prevID = id; } // Verify for( const std::pair>& idPair : winningOverlaps ) { const TEMPLATE_ID& winningID = idPair.first; for( const TEMPLATE_ID& losingID : idPair.second ) { if( m_zonesMap.at( losingID )->GetAssignedPriority() > m_zonesMap.at( winningID )->GetAssignedPriority() ) { return false; } } } return true; } FOOTPRINT* CADSTAR_PCB_ARCHIVE_LOADER::getFootprintFromCadstarID( const COMPONENT_ID& aCadstarComponentID ) { if( m_componentMap.find( aCadstarComponentID ) == m_componentMap.end() ) return nullptr; else return m_componentMap.at( aCadstarComponentID ); } VECTOR2I CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPoint( const VECTOR2I& aCadstarPoint ) { VECTOR2I retval; retval.x = ( aCadstarPoint.x - m_designCenter.x ) * KiCadUnitMultiplier; retval.y = -( aCadstarPoint.y - m_designCenter.y ) * KiCadUnitMultiplier; return retval; } NETINFO_ITEM* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadNet( const NET_ID& aCadstarNetID ) { if( aCadstarNetID.IsEmpty() ) { return nullptr; } else if( m_netMap.find( aCadstarNetID ) != m_netMap.end() ) { return m_netMap.at( aCadstarNetID ); } else { wxCHECK( Layout.Nets.find( aCadstarNetID ) != Layout.Nets.end(), nullptr ); NET_PCB csNet = Layout.Nets.at( aCadstarNetID ); wxString newName = csNet.Name; if( csNet.Name.IsEmpty() ) { if( csNet.Pins.size() > 0 ) { // Create default KiCad net naming: NET_PCB::PIN firstPin = ( *csNet.Pins.begin() ).second; //we should have already loaded the component with loadComponents() : FOOTPRINT* m = getFootprintFromCadstarID( firstPin.ComponentID ); newName = wxT( "Net-(" ); newName << m->Reference().GetText(); newName << wxT( "-Pad" ) << wxString::Format( wxT( "%ld" ), firstPin.PadID ); newName << wxT( ")" ); } else { wxFAIL_MSG( wxT( "A net with no pins associated?" ) ); newName = wxT( "csNet-" ); newName << wxString::Format( wxT( "%i" ), csNet.SignalNum ); } } if( !m_doneNetClassWarning && !csNet.NetClassID.IsEmpty() && csNet.NetClassID != wxT( "NONE" ) ) { wxLogMessage( _( "The CADSTAR design contains nets with a 'Net Class' assigned. KiCad " "does not have an equivalent to CADSTAR's Net Class so these elements " "were not imported. Note: KiCad's version of 'Net Class' is closer to " "CADSTAR's 'Net Route Code' (which has been imported for all nets)." ) ); m_doneNetClassWarning = true; } if( !m_doneSpacingClassWarning && !csNet.SpacingClassID.IsEmpty() && csNet.SpacingClassID != wxT( "NONE" ) ) { wxLogWarning( _( "The CADSTAR design contains nets with a 'Spacing Class' assigned. " "KiCad does not have an equivalent to CADSTAR's Spacing Class so " "these elements were not imported. Please review the design rules as " "copper pours will affected by this." ) ); m_doneSpacingClassWarning = true; } std::shared_ptr& netSettings = m_board->GetDesignSettings().m_NetSettings; NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, newName, ++m_numNets ); std::shared_ptr netclass; std::tuple key = { csNet.RouteCodeID, csNet.NetClassID, csNet.SpacingClassID }; if( m_netClassMap.find( key ) != m_netClassMap.end() ) { netclass = m_netClassMap.at( key ); } else { wxString netClassName; ROUTECODE rc = getRouteCode( csNet.RouteCodeID ); netClassName += wxT( "Route code: " ) + rc.Name; if( !csNet.NetClassID.IsEmpty() ) { CADSTAR_NETCLASS nc = Assignments.Codedefs.NetClasses.at( csNet.NetClassID ); netClassName += wxT( " | Net class: " ) + nc.Name; } if( !csNet.SpacingClassID.IsEmpty() ) { SPCCLASSNAME sp = Assignments.Codedefs.SpacingClassNames.at( csNet.SpacingClassID ); netClassName += wxT( " | Spacing class: " ) + sp.Name; } netclass.reset( new NETCLASS( *netSettings->m_DefaultNetClass ) ); netclass->SetName( netClassName ); netSettings->m_NetClasses[ netClassName ] = netclass; netclass->SetTrackWidth( getKiCadLength( rc.OptimalWidth ) ); m_netClassMap.insert( { key, netclass } ); } m_board->GetDesignSettings().m_NetSettings->m_NetClassPatternAssignments.push_back( { std::make_unique( newName, CTX_NETCLASS ), netclass->GetName() } ); netInfo->SetNetClass( netclass ); m_board->Add( netInfo, ADD_MODE::APPEND ); m_netMap.insert( { aCadstarNetID, netInfo } ); return netInfo; } return nullptr; } PCB_LAYER_ID CADSTAR_PCB_ARCHIVE_LOADER::getKiCadCopperLayerID( unsigned int aLayerNum, bool aDetectMaxLayer ) { if( aDetectMaxLayer && aLayerNum == m_numCopperLayers ) return PCB_LAYER_ID::B_Cu; switch( aLayerNum ) { case 1: return PCB_LAYER_ID::F_Cu; case 2: return PCB_LAYER_ID::In1_Cu; case 3: return PCB_LAYER_ID::In2_Cu; case 4: return PCB_LAYER_ID::In3_Cu; case 5: return PCB_LAYER_ID::In4_Cu; case 6: return PCB_LAYER_ID::In5_Cu; case 7: return PCB_LAYER_ID::In6_Cu; case 8: return PCB_LAYER_ID::In7_Cu; case 9: return PCB_LAYER_ID::In8_Cu; case 10: return PCB_LAYER_ID::In9_Cu; case 11: return PCB_LAYER_ID::In10_Cu; case 12: return PCB_LAYER_ID::In11_Cu; case 13: return PCB_LAYER_ID::In12_Cu; case 14: return PCB_LAYER_ID::In13_Cu; case 15: return PCB_LAYER_ID::In14_Cu; case 16: return PCB_LAYER_ID::In15_Cu; case 17: return PCB_LAYER_ID::In16_Cu; case 18: return PCB_LAYER_ID::In17_Cu; case 19: return PCB_LAYER_ID::In18_Cu; case 20: return PCB_LAYER_ID::In19_Cu; case 21: return PCB_LAYER_ID::In20_Cu; case 22: return PCB_LAYER_ID::In21_Cu; case 23: return PCB_LAYER_ID::In22_Cu; case 24: return PCB_LAYER_ID::In23_Cu; case 25: return PCB_LAYER_ID::In24_Cu; case 26: return PCB_LAYER_ID::In25_Cu; case 27: return PCB_LAYER_ID::In26_Cu; case 28: return PCB_LAYER_ID::In27_Cu; case 29: return PCB_LAYER_ID::In28_Cu; case 30: return PCB_LAYER_ID::In29_Cu; case 31: return PCB_LAYER_ID::In30_Cu; case 32: return PCB_LAYER_ID::B_Cu; } return PCB_LAYER_ID::UNDEFINED_LAYER; } bool CADSTAR_PCB_ARCHIVE_LOADER::isLayerSet( const LAYER_ID& aCadstarLayerID ) { wxCHECK( Assignments.Layerdefs.Layers.find( aCadstarLayerID ) != Assignments.Layerdefs.Layers.end(), false ); LAYER& layer = Assignments.Layerdefs.Layers.at( aCadstarLayerID ); switch( layer.Type ) { case LAYER_TYPE::ALLDOC: case LAYER_TYPE::ALLELEC: case LAYER_TYPE::ALLLAYER: return true; default: return false; } return false; } PCB_LAYER_ID CADSTAR_PCB_ARCHIVE_LOADER::getKiCadLayer( const LAYER_ID& aCadstarLayerID ) { if( getLayerType( aCadstarLayerID ) == LAYER_TYPE::NOLAYER ) { //The "no layer" is common for CADSTAR documentation symbols //map it to undefined layer for later processing return PCB_LAYER_ID::UNDEFINED_LAYER; } wxCHECK( m_layermap.find( aCadstarLayerID ) != m_layermap.end(), PCB_LAYER_ID::UNDEFINED_LAYER ); return m_layermap.at( aCadstarLayerID ); } LSET CADSTAR_PCB_ARCHIVE_LOADER::getKiCadLayerSet( const LAYER_ID& aCadstarLayerID ) { LAYER_TYPE layerType = getLayerType( aCadstarLayerID ); switch( layerType ) { case LAYER_TYPE::ALLDOC: return LSET( 4, PCB_LAYER_ID::Dwgs_User, PCB_LAYER_ID::Cmts_User, PCB_LAYER_ID::Eco1_User, PCB_LAYER_ID::Eco2_User ) | LSET::UserDefinedLayers(); case LAYER_TYPE::ALLELEC: return LSET::AllCuMask( m_numCopperLayers ); case LAYER_TYPE::ALLLAYER: return LSET::AllCuMask( m_numCopperLayers ) | LSET( 4, PCB_LAYER_ID::Dwgs_User, PCB_LAYER_ID::Cmts_User, PCB_LAYER_ID::Eco1_User, PCB_LAYER_ID::Eco2_User ) | LSET::UserDefinedLayers() | LSET::AllBoardTechMask(); default: return LSET( getKiCadLayer( aCadstarLayerID ) ); } } void CADSTAR_PCB_ARCHIVE_LOADER::addToGroup( const GROUP_ID& aCadstarGroupID, BOARD_ITEM* aKiCadItem ) { wxCHECK( m_groupMap.find( aCadstarGroupID ) != m_groupMap.end(), ); PCB_GROUP* parentGroup = m_groupMap.at( aCadstarGroupID ); parentGroup->AddItem( aKiCadItem ); } CADSTAR_PCB_ARCHIVE_LOADER::GROUP_ID CADSTAR_PCB_ARCHIVE_LOADER::createUniqueGroupID( const wxString& aName ) { wxString groupName = aName; int num = 0; while( m_groupMap.find( groupName ) != m_groupMap.end() ) { groupName = aName + wxT( "_" ) + wxString::Format( wxT( "%i" ), ++num ); } PCB_GROUP* docSymGroup = new PCB_GROUP( m_board ); m_board->Add( docSymGroup ); docSymGroup->SetName( groupName ); GROUP_ID groupID( groupName ); m_groupMap.insert( { groupID, docSymGroup } ); return groupID; }