/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Roberto Fernandez Bautista <@Qbort> * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * @file cadstar_pcb_archive_loader.cpp * @brief Loads a cpa file into a KiCad BOARD object */ #include #include // KEY_COPPER, KEY_CORE, KEY_PREPREG #include #include #include // DRAWSEGMENT #include #include #include #include #include #include #include #include // std::numeric_limits void CADSTAR_PCB_ARCHIVE_LOADER::Load( ::BOARD* aBoard ) { mBoard = aBoard; Parse(); LONGPOINT designLimit = Assignments.Technology.DesignLimit; //Note: can't use getKiCadPoint() due wxPoint being int - need long long to make the check long long designSizeXkicad = (long long) designLimit.x * KiCadUnitMultiplier; long long designSizeYkicad = (long long) designLimit.y * KiCadUnitMultiplier; // Max size limited by the positive dimention of wxPoint long long maxDesignSizekicad = (long long) std::numeric_limits::max(); if( designSizeXkicad > maxDesignSizekicad || designSizeYkicad > maxDesignSizekicad ) THROW_IO_ERROR( wxString::Format( _( "The design is too large and cannot be imported into KiCad. \n" "Please reduce the maximum design size in CADSTAR by navigating to: \n" "Design Tab -> Properties -> Design Options -> Maximum Design Size. \n" "Current Design size: %.2f, %.2f milimetres. \n" "Maximum permitted design size: %.2f, %.2f milimetres.\n" ), (double) designSizeXkicad / 1E6, (double) designSizeYkicad / 1E6, (double) maxDesignSizekicad / 1E6, (double) maxDesignSizekicad / 1E6 ) ); mDesignCenter = ( Assignments.Technology.DesignArea.first + Assignments.Technology.DesignArea.second ) / 2; if( Layout.NetSynch == NETSYNCH::WARNING ) wxLogWarning( _( "The selected file indicates that nets might be out of synchronisation " "with the schematic. It is recommended that you carry out an 'Align Nets' " "procedure in CADSTAR and re-import, to avoid inconsistencies between the " "PCB and the schematic. " ) ); loadBoardStackup(); loadComponentLibrary(); loadBoards(); loadFigures(); loadAreas(); loadComponents(); loadTemplates(); loadCoppers(); loadNets(); //TODO: process all other items wxLogMessage( _( "The CADSTAR design has been imported successfully.\n" "Please review the import errors and warnings (if any)." ) ); } void CADSTAR_PCB_ARCHIVE_LOADER::logBoardStackupWarning( const wxString& aCadstarLayerName, const PCB_LAYER_ID& aKiCadLayer ) { wxLogWarning( wxString::Format( _( "The CADSTAR layer '%s' has no KiCad equivalent. All elements on this " "layer have been mapped to KiCad layer '%s' instead." ), aCadstarLayerName, LSET::Name( aKiCadLayer ) ) ); } void CADSTAR_PCB_ARCHIVE_LOADER::loadBoardStackup() { std::map& cpaLayers = Assignments.Layerdefs.Layers; std::map& cpaMaterials = Assignments.Layerdefs.Materials; std::vector& cpaLayerStack = Assignments.Layerdefs.LayerStack; unsigned numElecAndPowerLayers = 0; BOARD_DESIGN_SETTINGS& designSettings = mBoard->GetDesignSettings(); BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor(); int noOfKiCadStackupLayers = 0; int lastElectricalLayerIndex = 0; int dielectricSublayer = 0; int numDielectricLayers = 0; bool prevWasDielectric = false; BOARD_STACKUP_ITEM* tempKiCadLayer; std::vector layerIDs; //Remove all layers except required ones stackup.RemoveAll(); layerIDs.push_back( PCB_LAYER_ID::F_CrtYd ); layerIDs.push_back( PCB_LAYER_ID::B_CrtYd ); layerIDs.push_back( PCB_LAYER_ID::Margin ); layerIDs.push_back( PCB_LAYER_ID::Edge_Cuts ); designSettings.SetEnabledLayers( LSET( &layerIDs[0], layerIDs.size() ) ); for( auto it = cpaLayerStack.begin(); it != cpaLayerStack.end(); ++it ) { LAYER curLayer = cpaLayers[*it]; BOARD_STACKUP_ITEM_TYPE kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_UNDEFINED; LAYER_T copperType = LAYER_T::LT_UNDEFINED; PCB_LAYER_ID kicadLayerID = PCB_LAYER_ID::UNDEFINED_LAYER; wxString layerTypeName = wxEmptyString; if( cpaLayers.count( *it ) == 0 ) wxASSERT_MSG( true, wxT( "Unable to find layer index" ) ); if( prevWasDielectric && ( curLayer.Type != LAYER_TYPE::CONSTRUCTION ) ) { stackup.Add( tempKiCadLayer ); //only add dielectric layers here after all are done dielectricSublayer = 0; prevWasDielectric = false; noOfKiCadStackupLayers++; } switch( curLayer.Type ) { case LAYER_TYPE::ALLDOC: case LAYER_TYPE::ALLELEC: case LAYER_TYPE::ALLLAYER: case LAYER_TYPE::ASSCOMPCOPP: case LAYER_TYPE::NOLAYER: //Shouldn't be here if CPA file is correctly parsed and not corrupt THROW_IO_ERROR( wxString::Format( _( "Unexpected layer '%s' in layer stack." ), curLayer.Name ) ); continue; case LAYER_TYPE::JUMPERLAYER: copperType = LAYER_T::LT_JUMPER; kicadLayerID = getKiCadCopperLayerID( ++numElecAndPowerLayers ); kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER; layerTypeName = KEY_COPPER; break; case LAYER_TYPE::ELEC: copperType = LAYER_T::LT_SIGNAL; kicadLayerID = getKiCadCopperLayerID( ++numElecAndPowerLayers ); kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER; layerTypeName = KEY_COPPER; break; case LAYER_TYPE::POWER: copperType = LAYER_T::LT_POWER; kicadLayerID = getKiCadCopperLayerID( ++numElecAndPowerLayers ); kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_COPPER; layerTypeName = KEY_COPPER; mPowerPlaneLayers.push_back( curLayer.ID ); //we will need to add a Copper zone break; case LAYER_TYPE::CONSTRUCTION: kicadLayerID = PCB_LAYER_ID::UNDEFINED_LAYER; kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_DIELECTRIC; prevWasDielectric = true; layerTypeName = KEY_PREPREG; //TODO handle KEY_CORE and KEY_PREPREG //will need to look at CADSTAR layer embedding (see LAYER->Embedding) to //check electrical layers above and below to decide if current layer is prepreg // or core break; case LAYER_TYPE::DOC: if( numElecAndPowerLayers > 0 ) kicadLayerID = PCB_LAYER_ID::Dwgs_User; else kicadLayerID = PCB_LAYER_ID::Cmts_User; logBoardStackupWarning( curLayer.Name, kicadLayerID ); //TODO: allow user to decide which layer this should be mapped onto. break; case LAYER_TYPE::NONELEC: switch( curLayer.SubType ) { case LAYER_SUBTYPE::LAYERSUBTYPE_ASSEMBLY: if( numElecAndPowerLayers > 0 ) kicadLayerID = PCB_LAYER_ID::B_Fab; else kicadLayerID = PCB_LAYER_ID::F_Fab; break; case LAYER_SUBTYPE::LAYERSUBTYPE_PLACEMENT: if( numElecAndPowerLayers > 0 ) kicadLayerID = PCB_LAYER_ID::B_CrtYd; else kicadLayerID = PCB_LAYER_ID::F_CrtYd; break; case LAYER_SUBTYPE::LAYERSUBTYPE_NONE: if( curLayer.Name.Lower().Contains( "glue" ) || curLayer.Name.Lower().Contains( "adhesive" ) ) { if( numElecAndPowerLayers > 0 ) kicadLayerID = PCB_LAYER_ID::B_Adhes; else kicadLayerID = PCB_LAYER_ID::F_Adhes; wxLogMessage( wxString::Format( _( "The CADSTAR layer '%s' has been assumed to be an adhesive layer. " "All elements on this layer have been mapped to KiCad layer '%s'." ), curLayer.Name, LSET::Name( kicadLayerID ) ) ); //TODO: allow user to decide if this is actually an adhesive layer or not. } else { if( numElecAndPowerLayers > 0 ) kicadLayerID = PCB_LAYER_ID::Eco2_User; else kicadLayerID = PCB_LAYER_ID::Eco1_User; logBoardStackupWarning( curLayer.Name, kicadLayerID ); //TODO: allow user to decide which layer this should be mapped onto. } break; case LAYER_SUBTYPE::LAYERSUBTYPE_PASTE: kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERPASTE; if( numElecAndPowerLayers > 0 ) { kicadLayerID = PCB_LAYER_ID::B_Paste; layerTypeName = _HKI( "Bottom Solder Paste" ); } else { kicadLayerID = PCB_LAYER_ID::F_Paste; layerTypeName = _HKI( "Top Solder Paste" ); } break; case LAYER_SUBTYPE::LAYERSUBTYPE_SILKSCREEN: kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SILKSCREEN; if( numElecAndPowerLayers > 0 ) { kicadLayerID = PCB_LAYER_ID::B_SilkS; layerTypeName = _HKI( "Bottom Silk Screen" ); } else { kicadLayerID = PCB_LAYER_ID::F_SilkS; layerTypeName = _HKI( "Top Silk Screen" ); } break; case LAYER_SUBTYPE::LAYERSUBTYPE_SOLDERRESIST: kicadLayerType = BOARD_STACKUP_ITEM_TYPE::BS_ITEM_TYPE_SOLDERMASK; if( numElecAndPowerLayers > 0 ) { kicadLayerID = PCB_LAYER_ID::B_Mask; layerTypeName = _HKI( "Bottom Solder Mask" ); } else { kicadLayerID = PCB_LAYER_ID::F_Mask; layerTypeName = _HKI( "Top Solder Mask" ); } break; default: wxASSERT_MSG( true, wxT( "Unknown CADSTAR Layer Sub-type" ) ); break; } break; default: wxASSERT_MSG( true, wxT( "Unknown CADSTAR Layer Type" ) ); break; } mLayermap.insert( std::make_pair( curLayer.ID, kicadLayerID ) ); if( dielectricSublayer == 0 ) tempKiCadLayer = new BOARD_STACKUP_ITEM( kicadLayerType ); tempKiCadLayer->SetLayerName( curLayer.Name ); tempKiCadLayer->SetBrdLayerId( kicadLayerID ); if( prevWasDielectric ) { wxASSERT_MSG( kicadLayerID == PCB_LAYER_ID::UNDEFINED_LAYER, wxT( "Error Processing Dielectric Layer. " "Expected to have undefined layer type" ) ); if( dielectricSublayer == 0 ) tempKiCadLayer->SetDielectricLayerId( ++numDielectricLayers ); else tempKiCadLayer->AddDielectricPrms( dielectricSublayer ); } if( curLayer.MaterialId != UNDEFINED_MATERIAL_ID ) { tempKiCadLayer->SetMaterial( cpaMaterials[curLayer.MaterialId].Name, dielectricSublayer ); tempKiCadLayer->SetEpsilonR( cpaMaterials[curLayer.MaterialId].Permittivity.GetDouble(), dielectricSublayer ); tempKiCadLayer->SetLossTangent( cpaMaterials[curLayer.MaterialId].LossTangent.GetDouble(), dielectricSublayer ); //TODO add Resistivity when KiCad supports it } tempKiCadLayer->SetThickness( curLayer.Thickness * KiCadUnitMultiplier, dielectricSublayer ); if( layerTypeName != wxEmptyString ) tempKiCadLayer->SetTypeName( layerTypeName ); if( !prevWasDielectric ) { stackup.Add( tempKiCadLayer ); //only add non-dielectric layers here ++noOfKiCadStackupLayers; layerIDs.push_back( tempKiCadLayer->GetBrdLayerId() ); designSettings.SetEnabledLayers( LSET( &layerIDs[0], layerIDs.size() ) ); } else ++dielectricSublayer; if( copperType != LAYER_T::LT_UNDEFINED ) { wxASSERT( mBoard->SetLayerType( tempKiCadLayer->GetBrdLayerId(), copperType ) ); //move to outside, need to enable layer in board first lastElectricalLayerIndex = noOfKiCadStackupLayers - 1; wxASSERT( mBoard->SetLayerName( tempKiCadLayer->GetBrdLayerId(), tempKiCadLayer->GetLayerName() ) ); //TODO set layer names for other CADSTAR layers when KiCad supports custom //layer names on non-copper layers mCopperLayers.insert( std::make_pair( curLayer.PhysicalLayer, curLayer.ID ) ); } } //change last copper layer to be B_Cu instead of an inner layer LAYER_ID cadstarlastElecLayer = mCopperLayers.rbegin()->second; PCB_LAYER_ID lastElecBrdId = stackup.GetStackupLayer( lastElectricalLayerIndex )->GetBrdLayerId(); std::remove( layerIDs.begin(), layerIDs.end(), lastElecBrdId ); layerIDs.push_back( PCB_LAYER_ID::B_Cu ); tempKiCadLayer = stackup.GetStackupLayer( lastElectricalLayerIndex ); tempKiCadLayer->SetBrdLayerId( PCB_LAYER_ID::B_Cu ); wxASSERT( mBoard->SetLayerName( tempKiCadLayer->GetBrdLayerId(), tempKiCadLayer->GetLayerName() ) ); mLayermap.at( cadstarlastElecLayer ) = PCB_LAYER_ID::B_Cu; //make all layers enabled and visible mBoard->SetEnabledLayers( LSET( &layerIDs[0], layerIDs.size() ) ); mBoard->SetVisibleLayers( LSET( &layerIDs[0], layerIDs.size() ) ); mBoard->SetCopperLayerCount( numElecAndPowerLayers ); } void CADSTAR_PCB_ARCHIVE_LOADER::loadComponentLibrary() { for( std::pair symPair : Library.ComponentDefinitions ) { SYMDEF_ID key = symPair.first; SYMDEF component = symPair.second; wxString moduleName = component.ReferenceName + ( ( component.Alternate.size() > 0 ) ? ( wxT( " (" ) + component.Alternate + wxT( ")" ) ) : wxT( "" ) ); MODULE* m = new MODULE( mBoard ); m->SetPosition( getKiCadPoint( component.Origin ) ); LIB_ID libID; libID.Parse( moduleName, LIB_ID::LIB_ID_TYPE::ID_PCB, true ); m->SetFPID( libID ); loadLibraryFigures( component, m ); loadLibraryPads( component, m ); mLibraryMap.insert( std::make_pair( key, m ) ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryFigures( const SYMDEF& aComponent, MODULE* aModule ) { for( std::pair figPair : aComponent.Figures ) { FIGURE& fig = figPair.second; drawCadstarShape( fig.Shape, getKiCadLayer( fig.LayerID ), fig.LineCodeID, wxString::Format( "Component %s:%s -> Figure %s", aComponent.ReferenceName, aComponent.Alternate, fig.ID ), aModule ); //TODO process addition to a group } } void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryPads( const SYMDEF& aComponent, MODULE* aModule ) { for( std::pair padPair : aComponent.Pads ) { PAD& csPad = padPair.second; //Cadstar pad PADCODE csPadcode = getPadCode( csPad.PadCodeID ); D_PAD* pad = new D_PAD( aModule ); aModule->Add( pad, ADD_MODE::INSERT ); // insert so that we get correct behaviour when finding pads // in the module by PAD_ID - see loadNets() switch( csPad.Side ) { case PAD_SIDE::MAXIMUM: //Bottom side pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD ); pad->SetLayerSet( LSET( 3, B_Cu, B_Paste, B_Mask ) ); break; case PAD_SIDE::MINIMUM: //TOP side pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_SMD ); pad->SetLayerSet( LSET( 3, F_Cu, F_Paste, F_Mask ) ); break; case PAD_SIDE::THROUGH_HOLE: if( csPadcode.Plated ) pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_STANDARD ); else pad->SetAttribute( PAD_ATTR_T::PAD_ATTRIB_HOLE_NOT_PLATED ); pad->SetLayerSet( pad->StandardMask() ); // for now we will assume no paste layers //TODO: We need to read the csPad->Reassigns vector to make sure no paste break; default: wxASSERT_MSG( true, "Unknown Pad type" ); } pad->SetName( csPad.Identifier.IsEmpty() ? wxString::Format( wxT( "%i" ), csPad.ID ) : csPad.Identifier ); pad->SetPos0( getKiCadPoint( csPad.Position ) - aModule->GetPosition() ); pad->SetOrientation( getAngleTenthDegree( csPad.OrientAngle ) ); switch( csPadcode.Shape.ShapeType ) { case PAD_SHAPE_TYPE::ANNULUS: //todo fix: use custom shape instead pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CIRCLE ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::BULLET: //todo fix: use custom shape instead (a bullet has the left size flat and right //side rounded, before rotation is applied) pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_OVAL ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::CIRCLE: pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_CIRCLE ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::DIAMOND: pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT ); pad->SetOrientation( pad->GetOrientation() + 450.0 ); // rotate 45deg anticlockwise pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::FINGER: pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_OVAL ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::OCTAGON: pad->SetShape( PAD_SHAPE_CHAMFERED_RECT ); pad->SetChamferPositions( RECT_CHAMFER_POSITIONS::RECT_CHAMFER_ALL ); pad->SetChamferRectRatio( 0.25 ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::RECTANGLE: pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::ROUNDED_RECT: pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT ); pad->SetRoundRectCornerRadius( getKiCadLength( csPadcode.Shape.InternalFeature ) ); pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::SQUARE: pad->SetShape( PAD_SHAPE_T::PAD_SHAPE_RECT ); pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), getKiCadLength( csPadcode.Shape.Size ) } ); break; default: wxASSERT_MSG( true, "Unknown Pad Shape" ); } if( csPadcode.ReliefClearance != UNDEFINED_VALUE ) pad->SetThermalGap( getKiCadLength( csPadcode.ReliefClearance ) ); if( csPadcode.ReliefWidth != UNDEFINED_VALUE ) pad->SetThermalWidth( getKiCadLength( csPadcode.ReliefWidth ) ); pad->SetOrientation( pad->GetOrientation() + getAngleTenthDegree( csPadcode.Shape.OrientAngle ) ); if( csPadcode.DrillDiameter != UNDEFINED_VALUE ) { if( csPadcode.SlotLength != UNDEFINED_VALUE ) pad->SetDrillSize( { getKiCadLength( csPadcode.DrillDiameter ), getKiCadLength( (long long) csPadcode.DrillOversize + (long long) csPadcode.DrillDiameter ) } ); else pad->SetDrillSize( { getKiCadLength( csPadcode.DrillDiameter ), getKiCadLength( csPadcode.DrillDiameter ) } ); } //TODO handle csPadcode.Reassigns when KiCad supports full padstacks } } void CADSTAR_PCB_ARCHIVE_LOADER::loadBoards() { for( std::pair boardPair : Layout.Boards ) { BOARD& board = boardPair.second; drawCadstarShape( board.Shape, PCB_LAYER_ID::Edge_Cuts, board.LineCodeID, wxString::Format( "BOARD %s", board.ID ), mBoard ); //TODO process board attributes //TODO process addition to a group } } void CADSTAR_PCB_ARCHIVE_LOADER::loadFigures() { for( std::pair figPair : Layout.Figures ) { FIGURE& fig = figPair.second; drawCadstarShape( fig.Shape, getKiCadLayer( fig.LayerID ), fig.LineCodeID, wxString::Format( "FIGURE %s", fig.ID ), mBoard ); //TODO process addition to a group //TODO process "swaprule" //TODO process re-use block when KiCad Supports it //TODO process attributes when KiCad Supports attributes in figures } } void CADSTAR_PCB_ARCHIVE_LOADER::loadAreas() { for( std::pair areaPair : Layout.Areas ) { AREA& area = areaPair.second; if( area.NoVias || area.NoTracks || area.Keepout ) { ZONE_CONTAINER* zone = getZoneFromCadstarShape( area.Shape, getLineThickness( area.LineCodeID ) ); mBoard->Add( zone, ADD_MODE::APPEND ); zone->SetLayer( getKiCadLayer( area.LayerID ) ); zone->SetIsKeepout( true ); //import all CADSTAR areas as Keepout zones zone->SetDoNotAllowPads( false ); //no CADSTAR equivalent zone->SetZoneName( area.Name ); zone->SetDoNotAllowFootprints( area.Keepout ); zone->SetDoNotAllowTracks( area.NoTracks ); zone->SetDoNotAllowCopperPour( area.NoTracks ); zone->SetDoNotAllowVias( area.NoVias ); if( area.Placement || area.Routing ) wxLogWarning( wxString::Format( _( "The CADSTAR area '%s' is defined as a placement and/or routing area " "in CADSTAR, in addition to Keepout. Placement or Routing areas are " "not supported in KiCad. Only the supported elements were imported." ), area.Name ) ); } else { wxLogError( wxString::Format( _( "The CADSTAR area '%s' does not have a KiCad equivalent. " "Pure Placement or Routing areas are not supported." ), area.Name ) ); } //todo Process area.AreaHeight when KiCad supports 3D design rules //TODO process attributes //TODO process addition to a group //TODO process "swaprule" //TODO process re-use block } } void CADSTAR_PCB_ARCHIVE_LOADER::loadComponents() { for( std::pair compPair : Layout.Components ) { COMPONENT& comp = compPair.second; auto moduleIter = mLibraryMap.find( comp.SymdefID ); if( moduleIter == mLibraryMap.end() ) { THROW_IO_ERROR( wxString::Format( _( "Unable to find component '%s' in the library" "(Symdef ID: '%s')" ), comp.Name, comp.SymdefID ) ); } // copy constructor to clone the module from the library MODULE* m = new MODULE( *moduleIter->second ); const_cast( m->m_Uuid ) = KIID(); mBoard->Add( m, ADD_MODE::APPEND ); //set to empty string to avoid duplication when loading attributes: m->SetValue( wxEmptyString ); m->SetPosition( getKiCadPoint( comp.Origin ) ); m->SetOrientation( getAngleTenthDegree( comp.OrientAngle ) ); m->SetReference( comp.Name ); if( comp.Mirror ) { m->Flip( getKiCadPoint( comp.Origin ), false ); m->SetOrientation( m->GetOrientation() + 1800.0 ); } loadComponentAttributes( comp, m ); m->SetDescription( getPart( comp.PartID ).Definition.Name ); mComponentMap.insert( { comp.ID, m } ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadTemplates() { for( std::pair tempPair : Layout.Templates ) { TEMPLATE& csTemplate = tempPair.second; ZONE_CONTAINER* zone = getZoneFromCadstarShape( csTemplate.Shape, getLineThickness( csTemplate.LineCodeID ) ); mBoard->Add( zone, ADD_MODE::APPEND ); zone->SetZoneName( csTemplate.Name ); zone->SetLayer( getKiCadLayer( csTemplate.LayerID ) ); if( !csTemplate.NetID.IsEmpty() ) zone->SetNet( getKiCadNet( csTemplate.NetID ) ); if( csTemplate.Pouring.AllowInNoRouting ) wxLogError( wxString::Format( _( "The CADSTAR template '%s' has the setting 'Allow in No Routing Areas' " "enabled. This setting has no KiCad equivalent, so it has been ignored." ), csTemplate.Name ) ); if( csTemplate.Pouring.BoxIsolatedPins ) wxLogError( wxString::Format( _( "The CADSTAR template '%s' has the setting 'Box Isolated Pins'" "enabled. This setting has no KiCad equivalent, so it has been ignored." ), csTemplate.Name ) ); if( csTemplate.Pouring.AutomaticRepour ) wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has the setting 'Automatic Repour'" "enabled. This setting has no KiCad equivalent, so it has been ignored." ), csTemplate.Name ) ); // Sliver width has different behaviour to KiCad Zone's minimum thickness // In Cadstar 'Sliver width' has to be greater than the Copper thickness, whereas in // Kicad it is the opposite. if( csTemplate.Pouring.SliverWidth != 0 ) wxLogError( wxString::Format( _( "The CADSTAR template '%s' has a non-zero value defined for the " "'Sliver Width' setting. There is no KiCad equivalent for " "this, so this setting was ignored." ), csTemplate.Name ) ); if( csTemplate.Pouring.MinIsolatedCopper != csTemplate.Pouring.MinDisjointCopper ) wxLogError( wxString::Format( _( "The CADSTAR template '%s' has different settings for 'Retain Poured Copper " "- Disjoint' and 'Retain Poured Copper - Isolated'. KiCad does not " "distinguish between these two settings. The setting for disjoint copper " "has been applied as the minimum island area of the KiCad Zone." ), csTemplate.Name ) ); if( csTemplate.Pouring.MinDisjointCopper < 0 ) zone->SetMinIslandArea( -1 ); else zone->SetMinIslandArea( (long long) getKiCadLength( csTemplate.Pouring.MinDisjointCopper ) * (long long) getKiCadLength( csTemplate.Pouring.MinDisjointCopper ) ); zone->SetZoneClearance( getKiCadLength( csTemplate.Pouring.AdditionalIsolation ) ); if( csTemplate.Pouring.FillType == TEMPLATE::POURING::COPPER_FILL_TYPE::HATCHED ) { zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN ); zone->SetHatchGap( getKiCadHatchCodeGap( csTemplate.Pouring.HatchCodeID ) ); zone->SetHatchThickness( getKiCadHatchCodeThickness( csTemplate.Pouring.HatchCodeID ) ); zone->SetHatchOrientation( getHatchCodeAngleDegrees( csTemplate.Pouring.HatchCodeID ) ); } else { zone->SetFillMode( ZONE_FILL_MODE::POLYGONS ); } if( csTemplate.Pouring.ThermalReliefOnPads != csTemplate.Pouring.ThermalReliefOnVias || csTemplate.Pouring.ThermalReliefPadsAngle != csTemplate.Pouring.ThermalReliefViasAngle ) wxLogWarning( wxString::Format( _( "The CADSTAR template '%s' has different settings for thermal relief " "in pads and vias. KiCad only supports one single setting for both. The " "setting for pads has been applied." ), csTemplate.Name ) ); if( csTemplate.Pouring.ThermalReliefOnPads ) { zone->SetThermalReliefGap( getKiCadLength( csTemplate.Pouring.ClearanceWidth ) ); zone->SetThermalReliefCopperBridge( getKiCadLength( getCopperCode( csTemplate.Pouring.ReliefCopperCodeID ).CopperWidth ) ); zone->SetPadConnection( ZONE_CONNECTION::THERMAL ); } else zone->SetPadConnection( ZONE_CONNECTION::FULL ); } //Now create power plane layers: for( LAYER_ID layer : mPowerPlaneLayers ) { wxASSERT( Assignments.Layerdefs.Layers.find( layer ) != Assignments.Layerdefs.Layers.end() ); //The net name will equal the layer name wxString powerPlaneLayerName = Assignments.Layerdefs.Layers.at( layer ).Name; NET_ID netid = wxEmptyString; for( std::pair netPair : Layout.Nets ) { NET net = netPair.second; if( net.Name == powerPlaneLayerName ) { netid = net.ID; break; } } if( netid.IsEmpty() ) { wxLogError( wxString::Format( _( "The CADSTAR layer '%s' is defined as a power plane layer. However no " "net with such name exists. The layer has been loaded but no copper zone " "was created." ), powerPlaneLayerName ) ); } else { for( std::pair boardPair : Layout.Boards ) { //create a zone in each board shape BOARD& board = boardPair.second; int defaultLineThicknesss = mBoard->GetDesignSettings().GetLineThickness( PCB_LAYER_ID::Edge_Cuts ); ZONE_CONTAINER* zone = getZoneFromCadstarShape( board.Shape, defaultLineThicknesss ); mBoard->Add( zone, ADD_MODE::APPEND ); zone->SetZoneName( powerPlaneLayerName ); zone->SetLayer( getKiCadLayer( layer ) ); zone->SetFillMode( ZONE_FILL_MODE::POLYGONS ); zone->SetPadConnection( ZONE_CONNECTION::FULL ); zone->SetMinIslandArea( -1 ); zone->SetNet( getKiCadNet( netid ) ); } } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadCoppers() { for( std::pair copPair : Layout.Coppers ) { COPPER& csCopper = copPair.second; if( !csCopper.PouredTemplateID.IsEmpty() ) continue; //ignore copper related to a template as we've already loaded it! // For now we are going to load coppers to a KiCad zone however this isn't perfect //TODO: Load onto a graphical polygon with a net (when KiCad has this feature) if( !mDoneCopperWarning ) { wxLogWarning( _( "The CADSTAR design contains COPPER elements, which have no direct KiCad " "equivalent. These have been imported as a KiCad Zone if solid or hatch " "filled, or as a KiCad Track if the shape was an unfilled outline (open or " "closed)." ) ); mDoneCopperWarning = true; } if( csCopper.Shape.Type == SHAPE_TYPE::OPENSHAPE || csCopper.Shape.Type == SHAPE_TYPE::OUTLINE ) { std::vector outlineSegments = getDrawSegmentsFromVertices( csCopper.Shape.Vertices ); std::vector outlineTracks = makeTracksFromDrawsegments( outlineSegments, mBoard, getKiCadNet(csCopper.NetRef.NetID) , getKiCadLayer( csCopper.LayerID ), getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth ) ); //cleanup for( DRAWSEGMENT* ds : outlineSegments ) { if( ds ) delete ds; } for( CUTOUT cutout : csCopper.Shape.Cutouts ) { std::vector cutoutSeg = getDrawSegmentsFromVertices( cutout.Vertices ); std::vector cutoutTracks = makeTracksFromDrawsegments( cutoutSeg, mBoard, getKiCadNet( csCopper.NetRef.NetID ), getKiCadLayer( csCopper.LayerID ), getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth ) ); //cleanup for( DRAWSEGMENT* ds : cutoutSeg ) { if( ds ) delete ds; } } } else { ZONE_CONTAINER* zone = getZoneFromCadstarShape( csCopper.Shape, getKiCadLength( getCopperCode( csCopper.CopperCodeID ).CopperWidth ) ); mBoard->Add( zone, ADD_MODE::APPEND ); zone->SetZoneName( csCopper.ID ); zone->SetLayer( getKiCadLayer( csCopper.LayerID ) ); 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( getHatchCodeAngleDegrees( csCopper.Shape.HatchCodeID ) ); } else { zone->SetFillMode( ZONE_FILL_MODE::POLYGONS ); } zone->SetPadConnection( ZONE_CONNECTION::FULL ); zone->SetNet( getKiCadNet( csCopper.NetRef.NetID ) ); } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadNets() { for( std::pair netPair : Layout.Nets ) { NET net = netPair.second; wxString netname = net.Name; if( netname.IsEmpty() ) netname = "$" + net.SignalNum; for( NET::CONNECTION connection : net.Connections ) { if( !connection.Unrouted ) loadNetTracks( net.ID, connection.Route ); //TODO: all other elements } for( std::pair viaPair : net.Vias ) { NET::VIA via = viaPair.second; loadNetVia( net.ID, via ); } for( std::pair pinPair : net.Pins ) { NET::PIN pin = pinPair.second; MODULE* m = getModuleFromCadstarID( pin.ComponentID ); if( m == nullptr ) { wxLogWarning( wxString::Format( _( "The net '%s' references component ID '%s' which does not exist. " "This has been ignored," ), netname, pin.ComponentID ) ); } else if( ( pin.PadID - (long) 1 ) > m->Pads().size() ) { wxLogWarning( wxString::Format( _( "The net '%s' references non-existent pad index '%d' in component '%s'. " "This has been ignored." ), netname, pin.PadID, m->GetReference() ) ); } else { // The below works because we have added the pads in the correct order to the module and // it so happens that PAD_ID in Cadstar is a sequential, numerical ID m->Pads().at( pin.PadID - (long) 1 )->SetNet( getKiCadNet( net.ID ) ); } } } } void CADSTAR_PCB_ARCHIVE_LOADER::loadComponentAttributes( const COMPONENT& aComponent, MODULE* aModule ) { 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, aModule, 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, aModule, attrval ); } } void CADSTAR_PCB_ARCHIVE_LOADER::loadNetTracks( const NET_ID& aCadstarNetID, const NET::ROUTE& aCadstarRoute ) { std::vector dsVector; POINT prevEnd = aCadstarRoute.StartPoint; for( const NET::ROUTE_VERTEX& v : aCadstarRoute.RouteVertices ) { DRAWSEGMENT* ds = getDrawSegmentFromVertex( prevEnd, v.Vertex ); ds->SetLayer( getKiCadLayer( aCadstarRoute.LayerID ) ); ds->SetWidth( getKiCadLength( v.RouteWidth ) ); dsVector.push_back( ds ); prevEnd = v.Vertex.End; } //Todo add real netcode to the tracks std::vector tracks = makeTracksFromDrawsegments( dsVector, mBoard, getKiCadNet( aCadstarNetID ) ); //cleanup for( DRAWSEGMENT* ds : dsVector ) { if( ds ) delete ds; } } void CADSTAR_PCB_ARCHIVE_LOADER::loadNetVia( const NET_ID& aCadstarNetID, const NET::VIA& aCadstarVia ) { VIA* via = new VIA( mBoard ); mBoard->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( wxString::Format( _( "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 } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarShape( const SHAPE& aCadstarShape, const PCB_LAYER_ID& aKiCadLayer, const LINECODE_ID& aCadstarLinecodeID, const wxString& aShapeName, BOARD_ITEM_CONTAINER* aContainer ) { int lineThickness = getLineThickness( aCadstarLinecodeID ); switch( aCadstarShape.Type ) { case SHAPE_TYPE::OPENSHAPE: case SHAPE_TYPE::OUTLINE: ///TODO update this when Polygons in KiCad can be defined with no fill drawCadstarVerticesAsSegments( aCadstarShape.Vertices, aKiCadLayer, lineThickness, aContainer ); drawCadstarCutoutsAsSegments( aCadstarShape.Cutouts, aKiCadLayer, lineThickness, aContainer ); 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: { DRAWSEGMENT* ds; if( isModule( aContainer ) ) ds = new EDGE_MODULE( (MODULE*) aContainer, STROKE_T::S_POLYGON ); else { ds = new DRAWSEGMENT( aContainer ); ds->SetShape( STROKE_T::S_POLYGON ); } ds->SetPolyShape( getPolySetFromCadstarShape( aCadstarShape, -1, aContainer ) ); ds->SetWidth( lineThickness ); ds->SetLayer( aKiCadLayer ); aContainer->Add( ds, ADD_MODE::APPEND ); } break; } } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarCutoutsAsSegments( const std::vector& aCutouts, const PCB_LAYER_ID& aKiCadLayer, const int& aLineThickness, BOARD_ITEM_CONTAINER* aContainer ) { for( CUTOUT cutout : aCutouts ) { drawCadstarVerticesAsSegments( cutout.Vertices, aKiCadLayer, aLineThickness, aContainer ); } } void CADSTAR_PCB_ARCHIVE_LOADER::drawCadstarVerticesAsSegments( const std::vector& aCadstarVertices, const PCB_LAYER_ID& aKiCadLayer, const int& aLineThickness, BOARD_ITEM_CONTAINER* aContainer ) { std::vector drawSegments = getDrawSegmentsFromVertices( aCadstarVertices, aContainer ); for( DRAWSEGMENT* ds : drawSegments ) { ds->SetWidth( aLineThickness ); ds->SetLayer( aKiCadLayer ); ds->SetParent( aContainer ); aContainer->Add( ds, ADD_MODE::APPEND ); } } std::vector CADSTAR_PCB_ARCHIVE_LOADER::getDrawSegmentsFromVertices( const std::vector& aCadstarVertices, BOARD_ITEM_CONTAINER* aContainer ) { std::vector drawSegments; if( aCadstarVertices.size() < 2 ) //need at least two points to draw a segment! (unlikely but possible to have only one) return drawSegments; 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 ); drawSegments.push_back( getDrawSegmentFromVertex( prev->End, *cur, aContainer ) ); prev = cur; } return drawSegments; } DRAWSEGMENT* CADSTAR_PCB_ARCHIVE_LOADER::getDrawSegmentFromVertex( const POINT& aCadstarStartPoint, const VERTEX& aCadstarVertex, BOARD_ITEM_CONTAINER* aContainer ) { DRAWSEGMENT* ds; bool cw = false; double arcStartAngle, arcEndAngle, arcAngle; wxPoint startPoint = getKiCadPoint( aCadstarStartPoint ); wxPoint endPoint = getKiCadPoint( aCadstarVertex.End ); wxPoint 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: if( isModule( aContainer ) ) ds = new EDGE_MODULE( (MODULE*) aContainer, STROKE_T::S_SEGMENT ); else { ds = new DRAWSEGMENT( aContainer ); ds->SetShape( STROKE_T::S_SEGMENT ); } ds->SetStart( startPoint ); ds->SetEnd( endPoint ); break; case VERTEX_TYPE::CLOCKWISE_SEMICIRCLE: case VERTEX_TYPE::CLOCKWISE_ARC: cw = true; case VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE: case VERTEX_TYPE::ANTICLOCKWISE_ARC: if( isModule( aContainer ) ) ds = new EDGE_MODULE( (MODULE*) aContainer, STROKE_T::S_ARC ); else { ds = new DRAWSEGMENT( aContainer ); ds->SetShape( STROKE_T::S_ARC ); } ds->SetArcStart( startPoint ); ds->SetCenter( centerPoint ); arcStartAngle = getPolarAngle( startPoint - centerPoint ); arcEndAngle = getPolarAngle( endPoint - centerPoint ); arcAngle = arcEndAngle - arcStartAngle; //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 ) ds->SetAngle( NormalizeAnglePos( arcAngle ) ); else ds->SetAngle( NormalizeAngleNeg( arcAngle ) ); break; } if( isModule( aContainer ) && ds != nullptr ) ( (EDGE_MODULE*) ds )->SetLocalCoord(); return ds; } ZONE_CONTAINER* CADSTAR_PCB_ARCHIVE_LOADER::getZoneFromCadstarShape( const SHAPE& aCadstarShape, const int& aLineThickness ) { ZONE_CONTAINER* zone = new ZONE_CONTAINER( mBoard ); 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, const int& aLineThickness, BOARD_ITEM_CONTAINER* aContainer ) { std::vector outlineSegments = getDrawSegmentsFromVertices( aCadstarShape.Vertices, aContainer ); SHAPE_POLY_SET polySet( getLineChainFromDrawsegments( outlineSegments ) ); //cleanup for( DRAWSEGMENT* ds : outlineSegments ) { if( ds ) delete ds; } for( CUTOUT cutout : aCadstarShape.Cutouts ) { std::vector cutoutSeg = getDrawSegmentsFromVertices( cutout.Vertices ); polySet.AddHole( getLineChainFromDrawsegments( cutoutSeg ) ); //cleanup for( DRAWSEGMENT* ds : cutoutSeg ) { if( ds ) delete ds; } } if( aLineThickness > 0 ) polySet.Inflate( aLineThickness / 2, 32, SHAPE_POLY_SET::CORNER_STRATEGY::ROUND_ALL_CORNERS ); //Make a new polyset with no holes //TODO: Using strictly simple to be safe, but need to find out if PM_FAST works okay polySet.Fracture( SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE ); return polySet; } SHAPE_LINE_CHAIN CADSTAR_PCB_ARCHIVE_LOADER::getLineChainFromDrawsegments( const std::vector aDrawsegments ) { SHAPE_LINE_CHAIN lineChain; for( DRAWSEGMENT* ds : aDrawsegments ) { switch( ds->GetShape() ) { case STROKE_T::S_ARC: { if( ds->GetClass() == wxT( "MGRAPHIC" ) ) { EDGE_MODULE* em = (EDGE_MODULE*) ds; SHAPE_ARC arc( em->GetStart0(), em->GetEnd0(), (double) em->GetAngle() / 10.0 ); lineChain.Append( arc ); } else { SHAPE_ARC arc( ds->GetCenter(), ds->GetArcStart(), (double) ds->GetAngle() / 10.0 ); lineChain.Append( arc ); } } break; case STROKE_T::S_SEGMENT: if( ds->GetClass() == wxT( "MGRAPHIC" ) ) { EDGE_MODULE* em = (EDGE_MODULE*) ds; lineChain.Append( em->GetStart0().x, em->GetStart0().y ); lineChain.Append( em->GetEnd0().x, em->GetEnd0().y ); } else { lineChain.Append( ds->GetStartX(), ds->GetStartY() ); lineChain.Append( ds->GetEndX(), ds->GetEndY() ); } break; default: wxASSERT_MSG( true, "Drawsegment type is unexpected. Ignored." ); } } lineChain.SetClosed( true ); //todo check if it is closed return lineChain; } std::vector CADSTAR_PCB_ARCHIVE_LOADER::makeTracksFromDrawsegments( const std::vector aDrawsegments, BOARD_ITEM_CONTAINER* aParentContainer, NETINFO_ITEM* aNet, const PCB_LAYER_ID& aLayerOverride, int aWidthOverride ) { std::vector tracks; for( DRAWSEGMENT* ds : aDrawsegments ) { TRACK* track; switch( ds->GetShape() ) { case STROKE_T::S_ARC: if( ds->GetClass() == wxT( "MGRAPHIC" ) ) { EDGE_MODULE* em = (EDGE_MODULE*) ds; SHAPE_ARC arc( em->GetStart0(), em->GetEnd0(), (double) em->GetAngle() / 10.0 ); track = new ARC( aParentContainer, &arc ); } else { SHAPE_ARC arc( ds->GetCenter(), ds->GetArcStart(), (double) ds->GetAngle() / 10.0 ); track = new ARC( aParentContainer, &arc ); } break; case STROKE_T::S_SEGMENT: if( ds->GetClass() == wxT( "MGRAPHIC" ) ) { EDGE_MODULE* em = (EDGE_MODULE*) ds; track = new TRACK( aParentContainer ); track->SetStart( em->GetStart0() ); track->SetEnd( em->GetEnd0() ); } else { track = new TRACK( aParentContainer ); track->SetStart( ds->GetStart() ); track->SetEnd( ds->GetEnd() ); } break; default: wxASSERT_MSG( true, "Drawsegment type is unexpected. Ignored." ); continue; } if( aWidthOverride == -1 ) track->SetWidth( ds->GetWidth() ); else track->SetWidth( aWidthOverride ); if( aLayerOverride == PCB_LAYER_ID::UNDEFINED_LAYER ) track->SetLayer( ds->GetLayer() ); else track->SetLayer( aLayerOverride ); if( aNet != nullptr ) track->SetNet( aNet ); tracks.push_back( track ); aParentContainer->Add( track, ADD_MODE::APPEND ); } return tracks; } void CADSTAR_PCB_ARCHIVE_LOADER::addAttribute( const ATTRIBUTE_LOCATION& aCadstarAttrLoc, const ATTRIBUTE_ID& aCadstarAttributeID, MODULE* aModule, const wxString& aAttributeValue ) { TEXTE_MODULE* txt; if( aCadstarAttributeID == COMPONENT_NAME_ATTRID ) txt = &aModule->Reference(); //text should be set outside this function else if( aCadstarAttributeID == PART_NAME_ATTRID ) { if( aModule->Value().GetText().IsEmpty() ) { // Use PART_NAME_ATTRID as the value is value field is blank aModule->SetValue( aAttributeValue ); txt = &aModule->Value(); } else { txt = new TEXTE_MODULE( aModule ); aModule->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( !aModule->Value().GetText().IsEmpty() ) { //copy the object aModule->Add( new TEXTE_MODULE( aModule->Value() ) ); } aModule->SetValue( aAttributeValue ); txt = &aModule->Value(); txt->SetVisible( false ); //make invisible to avoid clutter. } else { txt = new TEXTE_MODULE( aModule ); aModule->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 } wxPoint rotatedTextPos = getKiCadPoint( aCadstarAttrLoc.Position ) - aModule->GetPosition(); RotatePoint( &rotatedTextPos, -aModule->GetOrientation() ); txt->SetTextPos( getKiCadPoint( aCadstarAttrLoc.Position ) ); txt->SetPos0( rotatedTextPos ); txt->SetLayer( getKiCadLayer( aCadstarAttrLoc.LayerID ) ); txt->SetMirrored( aCadstarAttrLoc.Mirror ); txt->SetTextAngle( getAngleTenthDegree( aCadstarAttrLoc.OrientAngle ) - aModule->GetOrientation() ); TEXTCODE tc = getTextCode( aCadstarAttrLoc.TextCodeID ); txt->SetTextThickness( getKiCadLength( tc.LineWidth ) ); txt->SetTextSize( { getKiCadLength( tc.Width ), getKiCadLength( tc.Height ) } ); switch( aCadstarAttrLoc.Alignment ) { case ALIGNMENT::NO_ALIGNMENT: // Default for Single line text is Bottom Left case ALIGNMENT::BOTTOMLEFT: txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); break; case ALIGNMENT::BOTTOMCENTER: txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER ); break; case ALIGNMENT::BOTTOMRIGHT: txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); break; case ALIGNMENT::CENTERLEFT: txt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); break; case ALIGNMENT::CENTERCENTER: txt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER ); break; case ALIGNMENT::CENTERRIGHT: txt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); break; case ALIGNMENT::TOPLEFT: txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); break; case ALIGNMENT::TOPCENTER: txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER ); break; case ALIGNMENT::TOPRIGHT: txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); break; default: wxASSERT_MSG( true, "Unknown Aligment - needs review!" ); } //TODO Handle different font types when KiCad can support it. } int CADSTAR_PCB_ARCHIVE_LOADER::getLineThickness( const LINECODE_ID& aCadstarLineCodeID ) { wxCHECK( Assignments.Codedefs.LineCodes.find( aCadstarLineCodeID ) != Assignments.Codedefs.LineCodes.end(), mBoard->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::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::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 ); } double CADSTAR_PCB_ARCHIVE_LOADER::getHatchCodeAngleDegrees( const HATCHCODE_ID& aCadstarHatchcodeID ) { checkAndLogHatchCode( aCadstarHatchcodeID ); HATCHCODE hcode = getHatchCode( aCadstarHatchcodeID ); if( hcode.Hatches.size() < 1 ) return mBoard->GetDesignSettings().GetDefaultZoneSettings().m_HatchOrientation; else return getAngleDegrees( 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 mBoard->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 mBoard->GetDesignSettings().GetDefaultZoneSettings().m_HatchGap; else return getKiCadLength( hcode.Hatches.at( 0 ).Step ); } void CADSTAR_PCB_ARCHIVE_LOADER::checkAndLogHatchCode( const HATCHCODE_ID& aCadstarHatchcodeID ) { if( mHatchcodesTested.find( aCadstarHatchcodeID ) != mHatchcodesTested.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 haching. 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 haching. 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, getAngleDegrees( abs( hcode.Hatches.at( 0 ).OrientAngle - hcode.Hatches.at( 1 ).OrientAngle ) ), getAngleDegrees( hcode.Hatches.at( 0 ).OrientAngle ) ) ); } } mHatchcodesTested.insert( aCadstarHatchcodeID ); } } MODULE* CADSTAR_PCB_ARCHIVE_LOADER::getModuleFromCadstarID( const COMPONENT_ID& aCadstarComponentID ) { if( mComponentMap.find( aCadstarComponentID ) == mComponentMap.end() ) return nullptr; else return mComponentMap.at( aCadstarComponentID ); } wxPoint CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPoint( wxPoint aCadstarPoint ) { wxPoint retval; retval.x = ( aCadstarPoint.x - mDesignCenter.x ) * KiCadUnitMultiplier; retval.y = -( aCadstarPoint.y - mDesignCenter.y ) * KiCadUnitMultiplier; return retval; } double CADSTAR_PCB_ARCHIVE_LOADER::getPolarAngle( wxPoint aPoint ) { return NormalizeAnglePos( ArcTangente( aPoint.y, aPoint.x ) ); } NETINFO_ITEM* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadNet( const NET_ID& aCadstarNetID ) { if( aCadstarNetID.IsEmpty() ) return nullptr; else if( mNetMap.find( aCadstarNetID ) != mNetMap.end() ) { return mNetMap.at(aCadstarNetID); } else { wxCHECK( Layout.Nets.find( aCadstarNetID ) != Layout.Nets.end(), nullptr ); NET csNet = Layout.Nets.at( aCadstarNetID ); NETINFO_ITEM* netInfo = new NETINFO_ITEM( mBoard, csNet.Name, ++mNumNets ); mBoard->Add( netInfo, ADD_MODE::APPEND ); //todo also add the Netclass return netInfo; } return nullptr; } PCB_LAYER_ID CADSTAR_PCB_ARCHIVE_LOADER::getKiCadCopperLayerID( unsigned int aLayerNum ) { if(aLayerNum == Assignments.Technology.MaxPhysicalLayer) return PCB_LAYER_ID::B_Cu; switch( aLayerNum ) { // clang-format off 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; } // clang-format on 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( Assignments.Layerdefs.Layers.find( aCadstarLayerID ) != Assignments.Layerdefs.Layers.end() ) { if( Assignments.Layerdefs.Layers.at( aCadstarLayerID ).Type == 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( mLayermap.find( aCadstarLayerID ) != mLayermap.end(), PCB_LAYER_ID::UNDEFINED_LAYER ); return mLayermap.at(aCadstarLayerID); } LSET CADSTAR_PCB_ARCHIVE_LOADER::getKiCadLayerSet( const LAYER_ID& aCadstarLayerID ) { LAYER& layer = Assignments.Layerdefs.Layers.at( aCadstarLayerID ); switch( layer.Type ) { 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 ); case LAYER_TYPE::ALLELEC: return LSET::AllCuMask(); case LAYER_TYPE::ALLLAYER: return LSET::AllLayersMask(); default: return LSET( getKiCadLayer( aCadstarLayerID ) ); } }