/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2011 Wayne Stambaugh * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include PCB_SHAPE::PCB_SHAPE( BOARD_ITEM* aParent, KICAD_T aItemType, SHAPE_T aShapeType ) : BOARD_CONNECTED_ITEM( aParent, aItemType ), EDA_SHAPE( aShapeType, pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ), FILL_T::NO_FILL ) { } PCB_SHAPE::PCB_SHAPE( BOARD_ITEM* aParent, SHAPE_T shapetype ) : BOARD_CONNECTED_ITEM( aParent, PCB_SHAPE_T ), EDA_SHAPE( shapetype, pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ), FILL_T::NO_FILL ) { } PCB_SHAPE::~PCB_SHAPE() { } void PCB_SHAPE::Serialize( google::protobuf::Any &aContainer ) const { kiapi::board::types::GraphicShape msg; msg.mutable_id()->set_value( m_Uuid.AsStdString() ); msg.set_layer( ToProtoEnum( GetLayer() ) ); msg.set_locked( IsLocked() ? kiapi::common::types::LockedState::LS_LOCKED : kiapi::common::types::LockedState::LS_UNLOCKED ); msg.mutable_net()->mutable_code()->set_value( GetNetCode() ); msg.mutable_net()->set_name( GetNetname() ); kiapi::common::types::StrokeAttributes* stroke = msg.mutable_attributes()->mutable_stroke(); kiapi::common::types::GraphicFillAttributes* fill = msg.mutable_attributes()->mutable_fill(); stroke->mutable_width()->set_value_nm( GetWidth() ); switch( GetLineStyle() ) { case LINE_STYLE::DEFAULT: stroke->set_style( kiapi::common::types::SLS_DEFAULT ); break; case LINE_STYLE::SOLID: stroke->set_style( kiapi::common::types::SLS_SOLID ); break; case LINE_STYLE::DASH: stroke->set_style( kiapi::common::types::SLS_DASH ); break; case LINE_STYLE::DOT: stroke->set_style( kiapi::common::types::SLS_DOT ); break; case LINE_STYLE::DASHDOT: stroke->set_style( kiapi::common::types::SLS_DASHDOT ); break; case LINE_STYLE::DASHDOTDOT: stroke->set_style( kiapi::common::types::SLS_DASHDOTDOT ); break; default: break; } switch( GetFillMode() ) { case FILL_T::FILLED_SHAPE: fill->set_fill_type( kiapi::common::types::GFT_FILLED ); break; default: fill->set_fill_type( kiapi::common::types::GFT_UNFILLED ); break; } switch( GetShape() ) { case SHAPE_T::SEGMENT: { kiapi::board::types::GraphicSegmentAttributes* segment = msg.mutable_segment(); kiapi::common::PackVector2( *segment->mutable_start(), GetStart() ); kiapi::common::PackVector2( *segment->mutable_end(), GetEnd() ); break; } case SHAPE_T::RECTANGLE: { kiapi::board::types::GraphicRectangleAttributes* rectangle = msg.mutable_rectangle(); kiapi::common::PackVector2( *rectangle->mutable_top_left(), GetStart() ); kiapi::common::PackVector2( *rectangle->mutable_bottom_right(), GetEnd() ); break; } case SHAPE_T::ARC: { kiapi::board::types::GraphicArcAttributes* arc = msg.mutable_arc(); kiapi::common::PackVector2( *arc->mutable_start(), GetStart() ); kiapi::common::PackVector2( *arc->mutable_mid(), GetArcMid() ); kiapi::common::PackVector2( *arc->mutable_end(), GetEnd() ); break; } case SHAPE_T::CIRCLE: { kiapi::board::types::GraphicCircleAttributes* circle = msg.mutable_circle(); kiapi::common::PackVector2( *circle->mutable_center(), GetStart() ); kiapi::common::PackVector2( *circle->mutable_radius_point(), GetEnd() ); break; } case SHAPE_T::POLY: { kiapi::common::types::PolySet* polyset = msg.mutable_polygon(); for( int idx = 0; idx < GetPolyShape().OutlineCount(); ++idx ) { const SHAPE_POLY_SET::POLYGON& poly = GetPolyShape().Polygon( idx ); if( poly.empty() ) continue; kiapi::common::types::PolygonWithHoles* polyMsg = polyset->mutable_polygons()->Add(); kiapi::common::PackPolyLine( *polyMsg->mutable_outline(), poly.front() ); if( poly.size() > 1 ) { for( size_t hole = 1; hole < poly.size(); ++hole ) { kiapi::common::types::PolyLine* pl = polyMsg->mutable_holes()->Add(); kiapi::common::PackPolyLine( *pl, poly[hole] ); } } } break; } case SHAPE_T::BEZIER: { kiapi::board::types::GraphicBezierAttributes* bezier = msg.mutable_bezier(); kiapi::common::PackVector2( *bezier->mutable_start(), GetStart() ); kiapi::common::PackVector2( *bezier->mutable_control1(), GetBezierC1() ); kiapi::common::PackVector2( *bezier->mutable_control2(), GetBezierC2() ); kiapi::common::PackVector2( *bezier->mutable_end(), GetEnd() ); break; } default: wxASSERT_MSG( false, "Unhandled shape in PCB_SHAPE::Serialize" ); } aContainer.PackFrom( msg ); } bool PCB_SHAPE::Deserialize( const google::protobuf::Any &aContainer ) { kiapi::board::types::GraphicShape msg; if( !aContainer.UnpackTo( &msg ) ) return false; // Initialize everything to a known state that doesn't get touched by every // codepath below, to make sure the equality operator is consistent m_start = {}; m_end = {}; m_arcCenter = {}; m_arcMidData = {}; m_bezierC1 = {}; m_bezierC2 = {}; m_editState = 0; m_proxyItem = false; m_endsSwapped = false; const_cast( m_Uuid ) = KIID( msg.id().value() ); SetLocked( msg.locked() == kiapi::common::types::LS_LOCKED ); SetLayer( FromProtoEnum( msg.layer() ) ); SetNetCode( msg.net().code().value() ); SetFilled( msg.attributes().fill().fill_type() == kiapi::common::types::GFT_FILLED ); SetWidth( msg.attributes().stroke().width().value_nm() ); switch( msg.attributes().stroke().style() ) { case kiapi::common::types::SLS_DEFAULT: SetLineStyle( LINE_STYLE::DEFAULT ); break; case kiapi::common::types::SLS_SOLID: SetLineStyle( LINE_STYLE::SOLID ); break; case kiapi::common::types::SLS_DASH: SetLineStyle( LINE_STYLE::DASH ); break; case kiapi::common::types::SLS_DOT: SetLineStyle( LINE_STYLE::DOT ); break; case kiapi::common::types::SLS_DASHDOT: SetLineStyle( LINE_STYLE::DASHDOT ); break; case kiapi::common::types::SLS_DASHDOTDOT: SetLineStyle( LINE_STYLE::DASHDOTDOT ); break; default: break; } if( msg.has_segment() ) { SetShape( SHAPE_T::SEGMENT ); SetStart( kiapi::common::UnpackVector2( msg.segment().start() ) ); SetEnd( kiapi::common::UnpackVector2( msg.segment().end() ) ); } else if( msg.has_rectangle() ) { SetShape( SHAPE_T::RECTANGLE ); SetStart( kiapi::common::UnpackVector2( msg.rectangle().top_left() ) ); SetEnd( kiapi::common::UnpackVector2( msg.rectangle().bottom_right() ) ); } else if( msg.has_arc() ) { SetShape( SHAPE_T::ARC ); SetArcGeometry( kiapi::common::UnpackVector2( msg.arc().start() ), kiapi::common::UnpackVector2( msg.arc().mid() ), kiapi::common::UnpackVector2( msg.arc().end() ) ); } else if( msg.has_circle() ) { SetShape( SHAPE_T::CIRCLE ); SetStart( kiapi::common::UnpackVector2( msg.circle().center() ) ); SetEnd( kiapi::common::UnpackVector2( msg.circle().radius_point() ) ); } else if( msg.has_polygon() ) { SetShape( SHAPE_T::POLY ); const auto& polyMsg = msg.polygon().polygons(); SHAPE_POLY_SET sps; for( const kiapi::common::types::PolygonWithHoles& polygonWithHoles : polyMsg ) { SHAPE_POLY_SET::POLYGON polygon; polygon.emplace_back( kiapi::common::UnpackPolyLine( polygonWithHoles.outline() ) ); for( const kiapi::common::types::PolyLine& holeMsg : polygonWithHoles.holes() ) polygon.emplace_back( kiapi::common::UnpackPolyLine( holeMsg ) ); sps.AddPolygon( polygon ); } SetPolyShape( sps ); } else if( msg.has_bezier() ) { SetShape( SHAPE_T::BEZIER ); SetStart( kiapi::common::UnpackVector2( msg.bezier().start() ) ); SetBezierC1( kiapi::common::UnpackVector2( msg.bezier().control1() ) ); SetBezierC2( kiapi::common::UnpackVector2( msg.bezier().control2() ) ); SetEnd( kiapi::common::UnpackVector2( msg.bezier().end() ) ); } return true; } bool PCB_SHAPE::IsType( const std::vector& aScanTypes ) const { if( BOARD_ITEM::IsType( aScanTypes ) ) return true; bool sametype = false; for( KICAD_T scanType : aScanTypes ) { if( scanType == PCB_LOCATE_BOARD_EDGE_T ) sametype = m_layer == Edge_Cuts; else if( scanType == PCB_SHAPE_LOCATE_ARC_T ) sametype = m_shape == SHAPE_T::ARC; else if( scanType == PCB_SHAPE_LOCATE_CIRCLE_T ) sametype = m_shape == SHAPE_T::CIRCLE; else if( scanType == PCB_SHAPE_LOCATE_RECT_T ) sametype = m_shape == SHAPE_T::RECTANGLE; else if( scanType == PCB_SHAPE_LOCATE_SEGMENT_T ) sametype = m_shape == SHAPE_T::SEGMENT; else if( scanType == PCB_SHAPE_LOCATE_POLY_T ) sametype = m_shape == SHAPE_T::POLY; else if( scanType == PCB_SHAPE_LOCATE_BEZIER_T ) sametype = m_shape == SHAPE_T::BEZIER; if( sametype ) return true; } return false; } bool PCB_SHAPE::IsConnected() const { // Only board-level copper shapes are connectable return IsOnCopperLayer() && !GetParentFootprint(); } void PCB_SHAPE::SetLayer( PCB_LAYER_ID aLayer ) { BOARD_ITEM::SetLayer( aLayer ); if( !IsOnCopperLayer() ) SetNetCode( -1 ); } std::vector PCB_SHAPE::GetConnectionPoints() const { std::vector ret; // For filled shapes, we may as well use a centroid if( IsFilled() ) { ret.emplace_back( GetCenter() ); return ret; } switch( m_shape ) { case SHAPE_T::ARC: ret.emplace_back( GetArcMid() ); KI_FALLTHROUGH; case SHAPE_T::SEGMENT: case SHAPE_T::BEZIER: ret.emplace_back( GetStart() ); ret.emplace_back( GetEnd() ); break; case SHAPE_T::POLY: for( auto iter = GetPolyShape().CIterate(); iter; ++iter ) ret.emplace_back( *iter ); break; case SHAPE_T::RECTANGLE: for( const VECTOR2I& pt : GetRectCorners() ) ret.emplace_back( pt ); break; default: break; } return ret; } int PCB_SHAPE::GetWidth() const { // A stroke width of 0 in PCBNew means no-border, but negative stroke-widths are only used // in EEschema (see SCH_SHAPE::GetPenWidth()). // Since negative stroke widths can trip up down-stream code (such as the Gerber plotter), we // weed them out here. return std::max( EDA_SHAPE::GetWidth(), 0 ); } void PCB_SHAPE::StyleFromSettings( const BOARD_DESIGN_SETTINGS& settings ) { m_stroke.SetWidth( settings.GetLineThickness( GetLayer() ) ); } const VECTOR2I PCB_SHAPE::GetFocusPosition() const { // For some shapes return the visual center, but for not filled polygonal shapes, // the center is usually far from the shape: a point on the outline is better switch( m_shape ) { case SHAPE_T::CIRCLE: if( !IsFilled() ) return VECTOR2I( GetCenter().x + GetRadius(), GetCenter().y ); else return GetCenter(); case SHAPE_T::RECTANGLE: if( !IsFilled() ) return GetStart(); else return GetCenter(); case SHAPE_T::POLY: if( !IsFilled() ) { VECTOR2I pos = GetPolyShape().Outline(0).CPoint(0); return VECTOR2I( pos.x, pos.y ); } else { return GetCenter(); } case SHAPE_T::ARC: return GetArcMid(); case SHAPE_T::BEZIER: return GetStart(); default: return GetCenter(); } } std::vector PCB_SHAPE::GetCorners() const { std::vector pts; if( GetShape() == SHAPE_T::RECTANGLE ) { pts = GetRectCorners(); } else if( GetShape() == SHAPE_T::POLY ) { for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii ) { for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() ) pts.emplace_back( pt ); } } else { UNIMPLEMENTED_FOR( SHAPE_T_asString() ); } while( pts.size() < 4 ) pts.emplace_back( pts.back() + VECTOR2I( 10, 10 ) ); return pts; } void PCB_SHAPE::Move( const VECTOR2I& aMoveVector ) { move( aMoveVector ); } void PCB_SHAPE::Scale( double aScale ) { scale( aScale ); } void PCB_SHAPE::Normalize() { if( m_shape == SHAPE_T::RECTANGLE ) { VECTOR2I start = GetStart(); VECTOR2I end = GetEnd(); BOX2I rect( start, end - start ); rect.Normalize(); SetStart( rect.GetPosition() ); SetEnd( rect.GetEnd() ); } else if( m_shape == SHAPE_T::POLY ) { auto horizontal = []( const SEG& seg ) { return seg.A.y == seg.B.y; }; auto vertical = []( const SEG& seg ) { return seg.A.x == seg.B.x; }; // Convert a poly back to a rectangle if appropriate if( m_poly.OutlineCount() == 1 && m_poly.Outline( 0 ).SegmentCount() == 4 ) { SHAPE_LINE_CHAIN& outline = m_poly.Outline( 0 ); if( horizontal( outline.Segment( 0 ) ) && vertical( outline.Segment( 1 ) ) && horizontal( outline.Segment( 2 ) ) && vertical( outline.Segment( 3 ) ) ) { m_shape = SHAPE_T::RECTANGLE; m_start.x = std::min( outline.Segment( 0 ).A.x, outline.Segment( 0 ).B.x ); m_start.y = std::min( outline.Segment( 1 ).A.y, outline.Segment( 1 ).B.y ); m_end.x = std::max( outline.Segment( 0 ).A.x, outline.Segment( 0 ).B.x ); m_end.y = std::max( outline.Segment( 1 ).A.y, outline.Segment( 1 ).B.y ); } else if( vertical( outline.Segment( 0 ) ) && horizontal( outline.Segment( 1 ) ) && vertical( outline.Segment( 2 ) ) && horizontal( outline.Segment( 3 ) ) ) { m_shape = SHAPE_T::RECTANGLE; m_start.x = std::min( outline.Segment( 1 ).A.x, outline.Segment( 1 ).B.x ); m_start.y = std::min( outline.Segment( 0 ).A.y, outline.Segment( 0 ).B.y ); m_end.x = std::max( outline.Segment( 1 ).A.x, outline.Segment( 1 ).B.x ); m_end.y = std::max( outline.Segment( 0 ).A.y, outline.Segment( 0 ).B.y ); } } } } void PCB_SHAPE::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) { rotate( aRotCentre, aAngle ); } void PCB_SHAPE::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight ) { flip( aCentre, aFlipLeftRight ); SetLayer( FlipLayer( GetLayer(), GetBoard()->GetCopperLayerCount() ) ); } void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis ) { // Mirror an edge of the footprint. the layer is not modified // This is a footprint shape modification. switch( GetShape() ) { case SHAPE_T::ARC: case SHAPE_T::SEGMENT: case SHAPE_T::RECTANGLE: case SHAPE_T::CIRCLE: case SHAPE_T::BEZIER: if( aMirrorAroundXAxis ) { MIRROR( m_start.y, aCentre.y ); MIRROR( m_end.y, aCentre.y ); MIRROR( m_arcCenter.y, aCentre.y ); MIRROR( m_bezierC1.y, aCentre.y ); MIRROR( m_bezierC2.y, aCentre.y ); } else { MIRROR( m_start.x, aCentre.x ); MIRROR( m_end.x, aCentre.x ); MIRROR( m_arcCenter.x, aCentre.x ); MIRROR( m_bezierC1.x, aCentre.x ); MIRROR( m_bezierC2.x, aCentre.x ); } if( GetShape() == SHAPE_T::ARC ) std::swap( m_start, m_end ); if( GetShape() == SHAPE_T::BEZIER ) RebuildBezierToSegmentsPointsList( GetWidth() ); break; case SHAPE_T::POLY: m_poly.Mirror( !aMirrorAroundXAxis, aMirrorAroundXAxis, aCentre ); break; default: UNIMPLEMENTED_FOR( SHAPE_T_asString() ); } } void PCB_SHAPE::SetIsProxyItem( bool aIsProxy ) { PAD* parentPad = nullptr; if( GetBoard() && GetBoard()->IsFootprintHolder() ) { for( FOOTPRINT* fp : GetBoard()->Footprints() ) { for( PAD* pad : fp->Pads() ) { if( pad->IsEntered() ) { parentPad = pad; break; } } } } if( aIsProxy && !m_proxyItem ) { if( GetShape() == SHAPE_T::SEGMENT ) { if( parentPad && parentPad->GetThermalSpokeWidth() ) SetWidth( parentPad->GetThermalSpokeWidth() ); else SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) ); } else { SetWidth( 1 ); } } else if( m_proxyItem && !aIsProxy ) { SetWidth( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) ); } m_proxyItem = aIsProxy; } double PCB_SHAPE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const { constexpr double HIDE = std::numeric_limits::max(); constexpr double SHOW = 0.0; KIGFX::PCB_PAINTER* painter = static_cast( aView->GetPainter() ); KIGFX::PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings(); if( aLayer == LAYER_LOCKED_ITEM_SHADOW ) { // Hide shadow if the main layer is not shown if( !aView->IsLayerVisible( m_layer ) ) return HIDE; // Hide shadow on dimmed tracks if( renderSettings->GetHighContrast() ) { if( m_layer != renderSettings->GetPrimaryHighContrastLayer() ) return HIDE; } } if( FOOTPRINT* parent = GetParentFootprint() ) { if( parent->GetLayer() == F_Cu && !aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) ) return HIDE; if( parent->GetLayer() == B_Cu && !aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) ) return HIDE; } return SHOW; } void PCB_SHAPE::ViewGetLayers( int aLayers[], int& aCount ) const { aLayers[0] = GetLayer(); if( IsOnCopperLayer() ) { aLayers[1] = GetNetnameLayer( aLayers[0] ); aCount = 2; } else { aCount = 1; } if( IsLocked() ) aLayers[ aCount++ ] = LAYER_LOCKED_ITEM_SHADOW; } void PCB_SHAPE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) { if( aFrame->GetName() == PCB_EDIT_FRAME_NAME ) { if( FOOTPRINT* parent = GetParentFootprint() ) aList.emplace_back( _( "Footprint" ), parent->GetReference() ); } aList.emplace_back( _( "Type" ), _( "Drawing" ) ); if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() ) aList.emplace_back( _( "Status" ), _( "Locked" ) ); ShapeGetMsgPanelInfo( aFrame, aList ); aList.emplace_back( _( "Layer" ), GetLayerName() ); } wxString PCB_SHAPE::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const { if( GetNetCode() > 0 ) { return wxString::Format( _( "%s %s on %s" ), GetFriendlyName(), GetNetnameMsg(), GetLayerName() ); } else { return wxString::Format( _( "%s on %s" ), GetFriendlyName(), GetLayerName() ); } } BITMAPS PCB_SHAPE::GetMenuImage() const { if( GetParentFootprint() ) return BITMAPS::show_mod_edge; else return BITMAPS::add_dashed_line; } EDA_ITEM* PCB_SHAPE::Clone() const { return new PCB_SHAPE( *this ); } const BOX2I PCB_SHAPE::ViewBBox() const { BOX2I return_box = EDA_ITEM::ViewBBox(); // Inflate the bounding box by just a bit more for safety. return_box.Inflate( GetWidth() ); return return_box; } std::shared_ptr PCB_SHAPE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const { return std::make_shared( MakeEffectiveShapes() ); } void PCB_SHAPE::swapData( BOARD_ITEM* aImage ) { PCB_SHAPE* image = dynamic_cast( aImage ); wxCHECK( image, /* void */ ); SwapShape( image ); // Swap params not handled by SwapShape( image ) std::swap( m_layer, image->m_layer ); std::swap( m_isKnockout, image->m_isKnockout ); std::swap( m_isLocked, image->m_isLocked ); std::swap( m_flags, image->m_flags ); std::swap( m_parent, image->m_parent ); std::swap( m_forceVisible, image->m_forceVisible ); std::swap( m_netinfo, image->m_netinfo ); } bool PCB_SHAPE::cmp_drawings::operator()( const BOARD_ITEM* aFirst, const BOARD_ITEM* aSecond ) const { if( aFirst->Type() != aSecond->Type() ) return aFirst->Type() < aSecond->Type(); if( aFirst->GetLayer() != aSecond->GetLayer() ) return aFirst->GetLayer() < aSecond->GetLayer(); if( aFirst->Type() == PCB_SHAPE_T ) { const PCB_SHAPE* dwgA = static_cast( aFirst ); const PCB_SHAPE* dwgB = static_cast( aSecond ); if( dwgA->GetShape() != dwgB->GetShape() ) return dwgA->GetShape() < dwgB->GetShape(); } return aFirst->m_Uuid < aSecond->m_Uuid; } void PCB_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const { EDA_SHAPE::TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth ); } bool PCB_SHAPE::operator==( const BOARD_ITEM& aOther ) const { if( aOther.Type() != Type() ) return false; const PCB_SHAPE& other = static_cast( aOther ); if( m_layer != other.m_layer ) return false; if( m_isKnockout != other.m_isKnockout ) return false; if( m_isLocked != other.m_isLocked ) return false; if( m_flags != other.m_flags ) return false; if( m_forceVisible != other.m_forceVisible ) return false; if( m_netinfo->GetNetCode() != other.m_netinfo->GetNetCode() ) return false; return EDA_SHAPE::operator==( other ); } double PCB_SHAPE::Similarity( const BOARD_ITEM& aOther ) const { if( aOther.Type() != Type() ) return 0.0; const PCB_SHAPE& other = static_cast( aOther ); double similarity = 1.0; if( GetLayer() != other.GetLayer() ) similarity *= 0.9; if( m_isKnockout != other.m_isKnockout ) similarity *= 0.9; if( m_isLocked != other.m_isLocked ) similarity *= 0.9; if( m_flags != other.m_flags ) similarity *= 0.9; if( m_forceVisible != other.m_forceVisible ) similarity *= 0.9; if( m_netinfo->GetNetCode() != other.m_netinfo->GetNetCode() ) similarity *= 0.9; similarity *= EDA_SHAPE::Similarity( other ); return similarity; } static struct PCB_SHAPE_DESC { PCB_SHAPE_DESC() { PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); REGISTER_TYPE( PCB_SHAPE ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.InheritsAfter( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( BOARD_CONNECTED_ITEM ) ); propMgr.InheritsAfter( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( EDA_SHAPE ) ); // Need to initialise enum_map before we can use a Property enum for it ENUM_MAP& layerEnum = ENUM_MAP::Instance(); if( layerEnum.Choices().GetCount() == 0 ) { layerEnum.Undefined( UNDEFINED_LAYER ); for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq ) layerEnum.Map( *seq, LSET::Name( *seq ) ); } void ( PCB_SHAPE::*shapeLayerSetter )( PCB_LAYER_ID ) = &PCB_SHAPE::SetLayer; PCB_LAYER_ID ( PCB_SHAPE::*shapeLayerGetter )() const = &PCB_SHAPE::GetLayer; auto layerProperty = new PROPERTY_ENUM( _HKI( "Layer" ), shapeLayerSetter, shapeLayerGetter ); propMgr.ReplaceProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ), layerProperty ); // Only polygons have meaningful Position properties. // On other shapes, these are duplicates of the Start properties. auto isPolygon = []( INSPECTABLE* aItem ) -> bool { if( PCB_SHAPE* shape = dynamic_cast( aItem ) ) return shape->GetShape() == SHAPE_T::POLY; return false; }; propMgr.OverrideAvailability( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( BOARD_ITEM ), _HKI( "Position X" ), isPolygon ); propMgr.OverrideAvailability( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( BOARD_ITEM ), _HKI( "Position Y" ), isPolygon ); propMgr.Mask( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( EDA_SHAPE ), _HKI( "Line Color" ) ); propMgr.Mask( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( EDA_SHAPE ), _HKI( "Fill Color" ) ); auto isCopper = []( INSPECTABLE* aItem ) -> bool { if( PCB_SHAPE* shape = dynamic_cast( aItem ) ) return shape->IsOnCopperLayer(); return false; }; propMgr.OverrideAvailability( TYPE_HASH( PCB_SHAPE ), TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Net" ), isCopper ); auto isPadEditMode = []( BOARD* aBoard ) -> bool { if( aBoard && aBoard->IsFootprintHolder() ) { for( FOOTPRINT* fp : aBoard->Footprints() ) { for( PAD* pad : fp->Pads() ) { if( pad->IsEntered() ) return true; } } } return false; }; auto showNumberBoxProperty = [&]( INSPECTABLE* aItem ) -> bool { if( PCB_SHAPE* shape = dynamic_cast( aItem ) ) { if( shape->GetShape() == SHAPE_T::RECTANGLE ) return isPadEditMode( shape->GetBoard() ); } return false; }; auto showSpokeTemplateProperty = [&]( INSPECTABLE* aItem ) -> bool { if( PCB_SHAPE* shape = dynamic_cast( aItem ) ) { if( shape->GetShape() == SHAPE_T::SEGMENT ) return isPadEditMode( shape->GetBoard() ); } return false; }; const wxString groupPadPrimitives = _HKI( "Pad Primitives" ); propMgr.AddProperty( new PROPERTY( _HKI( "Number Box" ), &PCB_SHAPE::SetIsProxyItem, &PCB_SHAPE::IsProxyItem ), groupPadPrimitives ) .SetAvailableFunc( showNumberBoxProperty ) .SetIsHiddenFromRulesEditor(); propMgr.AddProperty( new PROPERTY( _HKI( "Thermal Spoke Template" ), &PCB_SHAPE::SetIsProxyItem, &PCB_SHAPE::IsProxyItem ), groupPadPrimitives ) .SetAvailableFunc( showSpokeTemplateProperty ) .SetIsHiddenFromRulesEditor(); } } _PCB_SHAPE_DESC;