/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Alex Shvartzkop * Copyright (C) 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum LENGTH_TUNING_MODE { SINGLE, DIFF_PAIR, DIFF_PAIR_SKEW }; static LENGTH_TUNING_MODE TuningFromString( const std::string& aStr ) { if( aStr == "single" ) return LENGTH_TUNING_MODE::SINGLE; else if( aStr == "diff_pair" ) return LENGTH_TUNING_MODE::DIFF_PAIR; else if( aStr == "diff_pair_skew" ) return LENGTH_TUNING_MODE::DIFF_PAIR_SKEW; else { wxFAIL_MSG( wxS( "Unknown length tuning token" ) ); return LENGTH_TUNING_MODE::SINGLE; } } static std::string TuningToString( const LENGTH_TUNING_MODE aTuning ) { switch( aTuning ) { case LENGTH_TUNING_MODE::SINGLE: return "single"; case LENGTH_TUNING_MODE::DIFF_PAIR: return "diff_pair"; case LENGTH_TUNING_MODE::DIFF_PAIR_SKEW: return "diff_pair_skew"; default: wxFAIL; return ""; } } static LENGTH_TUNING_MODE FromPNSMode( PNS::ROUTER_MODE aRouterMode ) { switch( aRouterMode ) { case PNS::PNS_MODE_TUNE_SINGLE: return LENGTH_TUNING_MODE::SINGLE; case PNS::PNS_MODE_TUNE_DIFF_PAIR: return LENGTH_TUNING_MODE::DIFF_PAIR; case PNS::PNS_MODE_TUNE_DIFF_PAIR_SKEW: return LENGTH_TUNING_MODE::DIFF_PAIR_SKEW; default: return LENGTH_TUNING_MODE::SINGLE; } } static PNS::MEANDER_SIDE SideFromString( const std::string& aStr ) { if( aStr == "default" ) return PNS::MEANDER_SIDE_DEFAULT; else if( aStr == "left" ) return PNS::MEANDER_SIDE_LEFT; else if( aStr == "right" ) return PNS::MEANDER_SIDE_RIGHT; else { wxFAIL_MSG( wxS( "Unknown meander side token" ) ); return PNS::MEANDER_SIDE_DEFAULT; } } static std::string StatusToString( const PNS::MEANDER_PLACER_BASE::TUNING_STATUS aStatus ) { switch( aStatus ) { case PNS::MEANDER_PLACER_BASE::TOO_LONG: return "too_long"; case PNS::MEANDER_PLACER_BASE::TOO_SHORT: return "too_short"; case PNS::MEANDER_PLACER_BASE::TUNED: return "tuned"; default: wxFAIL; return ""; } } static PNS::MEANDER_PLACER_BASE::TUNING_STATUS StatusFromString( const std::string& aStr ) { if( aStr == "too_long" ) return PNS::MEANDER_PLACER_BASE::TOO_LONG; else if( aStr == "too_short" ) return PNS::MEANDER_PLACER_BASE::TOO_SHORT; else if( aStr == "tuned" ) return PNS::MEANDER_PLACER_BASE::TUNED; else { wxFAIL_MSG( wxS( "Unknown tuning status token" ) ); return PNS::MEANDER_PLACER_BASE::TUNED; } } static std::string SideToString( const PNS::MEANDER_SIDE aValue ) { switch( aValue ) { case PNS::MEANDER_SIDE_DEFAULT: return "default"; case PNS::MEANDER_SIDE_LEFT: return "left"; case PNS::MEANDER_SIDE_RIGHT: return "right"; default: wxFAIL; return ""; } } class PCB_GENERATOR_MEANDERS : public PCB_GENERATOR { public: static const wxString GENERATOR_TYPE; static const wxString DISPLAY_NAME; PCB_GENERATOR_MEANDERS( BOARD_ITEM* aParent = nullptr, PCB_LAYER_ID aLayer = F_Cu, LENGTH_TUNING_MODE aMode = LENGTH_TUNING_MODE::SINGLE ) : PCB_GENERATOR( aParent, aLayer ), m_singleSide( false ), m_rounded( true ), m_tuningMode( aMode ), m_tuningStatus( PNS::MEANDER_PLACER_BASE::TUNING_STATUS::TUNED ) { m_generatorType = GENERATOR_TYPE; m_name = DISPLAY_NAME; m_minAmplitude = pcbIUScale.mmToIU( 0.1 ); m_maxAmplitude = pcbIUScale.mmToIU( 2.0 ); m_spacing = pcbIUScale.mmToIU( 0.6 ); m_targetLength = pcbIUScale.mmToIU( 100 ); m_targetSkew = pcbIUScale.mmToIU( 0 ); m_end = VECTOR2I( pcbIUScale.mmToIU( 10 ), 0 ); m_cornerRadiusPercentage = 100; m_initialSide = PNS::MEANDER_SIDE_DEFAULT; } wxString GetGeneratorType() const override { return wxS( "meanders" ); } int snapToNearestTrackPoint( VECTOR2I& aP, BOARD* aBoard, int aNet ) { SEG::ecoord minDistSq = VECTOR2I::ECOORD_MAX; VECTOR2I closestPt = aP; int closestNet = -1; for( PCB_TRACK *track : aBoard->Tracks() ) { if( aNet >= 0 && track->GetNetCode() != aNet ) continue; SEG seg ( track->GetStart(), track->GetEnd() ); VECTOR2I nearest = seg.NearestPoint( aP ); SEG::ecoord distSq = ( nearest - aP ).SquaredEuclideanNorm(); if( distSq < minDistSq ) { minDistSq = distSq; closestPt = nearest; closestNet = track->GetNetCode(); } } if( minDistSq != VECTOR2I::ECOORD_MAX ) { aP = closestPt; return closestNet; } return -1; } bool baselineValid() { if( m_tuningMode == DIFF_PAIR ) { return( m_baseLine && m_baseLine->PointCount() > 1 && m_baseLineCoupled && m_baseLineCoupled->PointCount() > 1 ); } else { return( m_baseLine && m_baseLine->PointCount() > 1 ); } } static PCB_GENERATOR_MEANDERS* CreateNew( GENERATOR_TOOL* aTool, PCB_BASE_EDIT_FRAME* aFrame, BOARD_CONNECTED_ITEM* aStartItem, LENGTH_TUNING_MODE aMode ) { BOARD* board = aStartItem->GetBoard(); std::shared_ptr& drcEngine = board->GetDesignSettings().m_DRCEngine; DRC_CONSTRAINT constraint; PCB_LAYER_ID layer = aStartItem->GetLayer(); PNS::RULE_RESOLVER* resolver = aTool->Router()->GetRuleResolver(); if( aMode == SINGLE && resolver->DpCoupledNet( aStartItem->GetNet() ) ) aMode = DIFF_PAIR; PCB_GENERATOR_MEANDERS* meander = new PCB_GENERATOR_MEANDERS( board, layer, aMode ); constraint = drcEngine->EvalRules( LENGTH_CONSTRAINT, aStartItem, nullptr, layer ); if( aMode == DIFF_PAIR_SKEW ) { if( constraint.IsNull() ) { WX_UNIT_ENTRY_DIALOG dlg( aFrame, _( "Tune Skew" ), _( "Target skew:" ), 0 ); if( dlg.ShowModal() != wxID_OK ) return nullptr; meander->m_targetSkew = dlg.GetValue(); meander->m_overrideCustomRules = true; } else { meander->m_targetSkew = constraint.GetValue().Opt(); meander->m_overrideCustomRules = false; } } else { if( constraint.IsNull() ) { WX_UNIT_ENTRY_DIALOG dlg( aFrame, _( "Tune Length" ), _( "Target length:" ), 100 * PCB_IU_PER_MM ); if( dlg.ShowModal() != wxID_OK ) return nullptr; meander->m_targetLength = dlg.GetValue(); meander->m_overrideCustomRules = true; } else { meander->m_targetLength = constraint.GetValue().Opt(); meander->m_overrideCustomRules = false; } } meander->SetFlags( IS_NEW ); return meander; } void EditStart( GENERATOR_TOOL* aTool, BOARD* aBoard, PCB_BASE_EDIT_FRAME* aFrame, BOARD_COMMIT* aCommit ) override { m_removedItems.clear(); if( aCommit ) { if( IsNew() ) aCommit->Add( this ); else aCommit->Modify( this ); } // Remove ourselves from the selection so that we don't redraw our existing children as // part of the selection VIEW_GROUP (which ignores the HIDDEN flags). PCB_SELECTION_TOOL*selectionTool = aFrame->GetToolManager()->GetTool(); selectionTool->RemoveItemFromSel( this, true ); int layer = GetLayer(); PNS::ROUTER* router = aTool->Router(); aTool->ClearRouterCommit(); router->SyncWorld(); if( !baselineValid() ) { InitBaseLine( router, layer, aBoard ); } } PNS::LINKED_ITEM* PickSegment( PNS::ROUTER* aRouter, const VECTOR2I& aWhere, int aLayer, VECTOR2I& aPointOut ) { static const int candidateCount = 2; PNS::LINKED_ITEM* prioritized[candidateCount]; SEG::ecoord dist[candidateCount]; VECTOR2I point[candidateCount]; for( int i = 0; i < candidateCount; i++ ) { prioritized[i] = nullptr; dist[i] = VECTOR2I::ECOORD_MAX; } auto haveCandidates = [&]() { for( PNS::ITEM* item : prioritized ) { if( item ) return true; } return false; }; for( bool useClearance : { false, true } ) { PNS::ITEM_SET candidates = aRouter->QueryHoverItems( aWhere, useClearance ); for( PNS::ITEM* item : candidates.Items() ) { if( !item->OfKind( PNS::ITEM::SEGMENT_T | PNS::ITEM::ARC_T ) ) continue; if( !item->IsRoutable() ) continue; if( !item->Layers().Overlaps( aLayer ) ) continue; PNS::LINKED_ITEM* linked = static_cast( item ); if( item->Kind() & PNS::ITEM::ARC_T ) { SEG::ecoord d0 = ( item->Anchor( 0 ) - aWhere ).SquaredEuclideanNorm(); SEG::ecoord d1 = ( item->Anchor( 1 ) - aWhere ).SquaredEuclideanNorm(); if( d0 <= dist[1] ) { prioritized[1] = linked; dist[1] = d0; point[1] = item->Anchor( 0 ); } if( d1 <= dist[1] ) { prioritized[1] = linked; dist[1] = d1; point[1] = item->Anchor( 1 ); } } else if( item->Kind() & PNS::ITEM::SEGMENT_T ) { PNS::SEGMENT* segm = static_cast( item ); VECTOR2I nearest = segm->CLine().NearestPoint( aWhere, false ); SEG::ecoord dd = ( aWhere - nearest ).SquaredEuclideanNorm(); if( dd <= dist[1] ) { prioritized[1] = segm; dist[1] = dd; point[1] = nearest; } } } if( haveCandidates() ) break; } PNS::LINKED_ITEM* rv = nullptr; for( int i = 0; i < candidateCount; i++ ) { PNS::LINKED_ITEM* item = prioritized[i]; if( item && ( aLayer < 0 || item->Layers().Overlaps( aLayer ) ) ) { rv = item; aPointOut = point[i]; break; } } return rv; } bool initBaseLine( PNS::ROUTER* router, int layer, BOARD* aBoard, VECTOR2I& aStart, VECTOR2I& aEnd, int aNetCode, std::optional& aBaseLine ) { PNS::NODE* world = router->GetWorld(); snapToNearestTrackPoint( aStart, aBoard, aNetCode ); snapToNearestTrackPoint( aEnd, aBoard, aNetCode ); VECTOR2I startSnapPoint, endSnapPoint; PNS::LINKED_ITEM* startItem = PickSegment( router, aStart, layer, startSnapPoint ); PNS::LINKED_ITEM* endItem = PickSegment( router, aEnd, layer, endSnapPoint ); wxASSERT( startItem ); wxASSERT( endItem ); if( !startItem || !endItem || startSnapPoint == endSnapPoint ) return false; PNS::LINE line = world->AssembleLine( startItem, nullptr, false, true ); const SHAPE_LINE_CHAIN& chain = line.CLine(); wxASSERT( line.ContainsLink( endItem ) ); wxASSERT( chain.PointOnEdge( startSnapPoint, 1 ) ); wxASSERT( chain.PointOnEdge( endSnapPoint, 1 ) ); SHAPE_LINE_CHAIN pre; SHAPE_LINE_CHAIN mid; SHAPE_LINE_CHAIN post; chain.Split( startSnapPoint, endSnapPoint, pre, mid, post ); aBaseLine = mid; return true; } bool InitBaseLine( PNS::ROUTER* router, int layer, BOARD* aBoard ) { m_baseLineCoupled.reset(); int netCode = snapToNearestTrackPoint( m_origin, aBoard, -1 ); if( !initBaseLine( router, layer, aBoard, m_origin, m_end, netCode, m_baseLine ) ) return false; if( m_tuningMode == DIFF_PAIR ) { PNS::RULE_RESOLVER* resolver = router->GetRuleResolver(); if( PNS::NET_HANDLE handle = resolver->DpCoupledNet( aBoard->FindNet( netCode ) ) ) { int coupledNet = static_cast( handle )->GetNetCode(); VECTOR2I coupledStart = m_origin; VECTOR2I coupledEnd = m_end; return initBaseLine( router, layer, aBoard, coupledStart, coupledEnd, coupledNet, m_baseLineCoupled ); } return false; } return true; } void removeToBaseline( PNS::ROUTER* aRouter, int aLayer, SHAPE_LINE_CHAIN& baseLine ) { VECTOR2I startSnapPoint, endSnapPoint; std::optional line = getLine( baseLine.CPoint( 0 ), baseLine.CPoint( -1 ), aRouter, aLayer, startSnapPoint, endSnapPoint ); wxCHECK( line, /* void */ ); SHAPE_LINE_CHAIN pre; SHAPE_LINE_CHAIN mid; SHAPE_LINE_CHAIN post; line->CLine().Split( startSnapPoint, endSnapPoint, pre, mid, post ); // LINE does not have a separate remover, as LINEs are never truly a member of the tree for( PNS::LINKED_ITEM* li : line->Links() ) aRouter->GetInterface()->RemoveItem( li ); aRouter->GetWorld()->Remove( *line ); SHAPE_LINE_CHAIN straightChain; straightChain.Append( pre ); straightChain.Append( baseLine ); straightChain.Append( post ); straightChain.Simplify(); PNS::LINE straightLine( *line, straightChain ); // LINE does not have a separate remover, as LINEs are never truly a member of the tree aRouter->GetWorld()->Add( straightLine, false ); for( PNS::LINKED_ITEM* li : straightLine.Links() ) aRouter->GetInterface()->AddItem( li ); } void Remove( GENERATOR_TOOL* aTool, BOARD* aBoard, PCB_BASE_EDIT_FRAME* aFrame, BOARD_COMMIT* aCommit ) override { aTool->Router()->SyncWorld(); PNS::ROUTER* router = aTool->Router(); int layer = GetLayer(); // Ungroup first so that undo works if( !GetItems().empty() ) { PCB_GENERATOR* group = this; PICKED_ITEMS_LIST undoList; for( BOARD_ITEM* member : group->GetItems() ) undoList.PushItem( ITEM_PICKER( nullptr, member, UNDO_REDO::UNGROUP ) ); group->RemoveAll(); aFrame->SaveCopyInUndoList( undoList, UNDO_REDO::UNGROUP ); } else { aCommit->Push( "" ); } aCommit->Remove( this ); aTool->ClearRouterCommit(); if( baselineValid() ) { removeToBaseline( router, layer, *m_baseLine ); removeToBaseline( router, layer, *m_baseLineCoupled ); } std::set clearRouterRemovedItems = aTool->GetRouterCommitRemovedItems(); std::set clearRouterAddedItems = aTool->GetRouterCommitAddedItems(); for( BOARD_ITEM* item : clearRouterRemovedItems ) { item->ClearSelected(); aCommit->Remove( item ); } for( BOARD_ITEM* item : clearRouterAddedItems ) { aCommit->Add( item ); } aCommit->Push( "Remove Meander", APPEND_UNDO ); } std::optional getLine( const VECTOR2I& aStart, const VECTOR2I& aEnd, PNS::ROUTER* router, int layer, VECTOR2I& aStartOut, VECTOR2I& aEndOut ) { PNS::NODE* world = router->GetWorld(); PNS::LINKED_ITEM* startItem = PickSegment( router, aStart, layer, aStartOut ); PNS::LINKED_ITEM* endItem = PickSegment( router, aEnd, layer, aEndOut ); wxASSERT( startItem ); wxASSERT( endItem ); if( !startItem || !endItem ) return std::nullopt; PNS::LINE line = world->AssembleLine( startItem, nullptr, false, true ); SHAPE_LINE_CHAIN oldChain = line.CLine(); wxCHECK( line.ContainsLink( endItem ), std::nullopt ); wxASSERT( oldChain.PointOnEdge( aStartOut, 1 ) ); wxASSERT( oldChain.PointOnEdge( aEndOut, 1 ) ); return line; } PNS::MEANDER_SETTINGS ToMeanderSettings() { PNS::MEANDER_SETTINGS settings; settings.m_cornerStyle = m_rounded ? PNS::MEANDER_STYLE::MEANDER_STYLE_ROUND : PNS::MEANDER_STYLE::MEANDER_STYLE_CHAMFER; settings.m_minAmplitude = m_minAmplitude; settings.m_maxAmplitude = m_maxAmplitude; settings.m_spacing = m_spacing; settings.m_targetLength = m_targetLength; settings.m_targetSkew = m_targetSkew; settings.m_overrideCustomRules = m_overrideCustomRules; settings.m_singleSided = m_singleSide; settings.m_segmentSide = m_initialSide; settings.m_cornerRadiusPercentage = m_cornerRadiusPercentage; return settings; } void FromMeanderSettings( const PNS::MEANDER_SETTINGS& aSettings ) { m_rounded = aSettings.m_cornerStyle == PNS::MEANDER_STYLE::MEANDER_STYLE_ROUND; m_minAmplitude = aSettings.m_minAmplitude; m_maxAmplitude = aSettings.m_maxAmplitude; m_spacing = aSettings.m_spacing; m_targetLength = aSettings.m_targetLength; m_targetSkew = aSettings.m_targetSkew; m_overrideCustomRules = aSettings.m_overrideCustomRules; m_singleSide = aSettings.m_singleSided; m_initialSide = aSettings.m_segmentSide; m_cornerRadiusPercentage = aSettings.m_cornerRadiusPercentage; } PNS::ROUTER_MODE ToPNSMode() { switch( m_tuningMode ) { case LENGTH_TUNING_MODE::SINGLE: return PNS::PNS_MODE_TUNE_SINGLE; case LENGTH_TUNING_MODE::DIFF_PAIR: return PNS::PNS_MODE_TUNE_DIFF_PAIR; case LENGTH_TUNING_MODE::DIFF_PAIR_SKEW: return PNS::PNS_MODE_TUNE_DIFF_PAIR_SKEW; default: return PNS::PNS_MODE_TUNE_SINGLE; } } bool resetToBaseline( PNS::ROUTER* aRouter, int aLayer, PCB_BASE_EDIT_FRAME* aFrame, SHAPE_LINE_CHAIN& aBaseLine, bool aPrimary ) { PNS::NODE* world = aRouter->GetWorld(); VECTOR2I startSnapPoint, endSnapPoint; std::optional line = getLine( aBaseLine.CPoint( 0 ), aBaseLine.CPoint( -1 ), aRouter, aLayer, startSnapPoint, endSnapPoint ); wxCHECK( line, false ); SHAPE_LINE_CHAIN straightChain; { SHAPE_LINE_CHAIN pre, mid, post; line->CLine().Split( startSnapPoint, endSnapPoint, pre, mid, post ); straightChain.Append( pre ); straightChain.Append( aBaseLine ); straightChain.Append( post ); straightChain.Simplify(); } // LINE does not have a separate remover, as LINEs are never truly a member of the tree for( PNS::LINKED_ITEM* li : line->Links() ) { if( li->Parent() ) { aFrame->GetCanvas()->GetView()->Hide( li->Parent() ); m_removedItems.insert( li->Parent() ); } } world->Remove( *line ); PNS::LINE straightLine( *line, straightChain ); world->Add( straightLine, false ); if( aPrimary ) { m_origin = straightChain.NearestPoint( m_origin ); m_end = straightChain.NearestPoint( m_end ); // Don't allow points too close if( ( m_end - m_origin ).EuclideanNorm() < pcbIUScale.mmToIU( 0.1 ) ) { m_origin = startSnapPoint; m_end = endSnapPoint; } { SHAPE_LINE_CHAIN pre, mid, post; straightChain.Split( m_origin, m_end, pre, mid, post ); m_baseLine = mid; } } else { VECTOR2I start = straightChain.NearestPoint( m_origin ); VECTOR2I end = straightChain.NearestPoint( m_end ); { SHAPE_LINE_CHAIN pre, mid, post; straightChain.Split( start, end, pre, mid, post ); m_baseLineCoupled = mid; } } return true; } bool Update( GENERATOR_TOOL* aTool, BOARD* aBoard, PCB_BASE_EDIT_FRAME* aFrame, BOARD_COMMIT* aCommit ) override { PNS::ROUTER* router = aTool->Router(); PNS_KICAD_IFACE* iface = aTool->GetInterface(); int layer = GetLayer(); iface->SetStartLayer( layer ); if( router->RoutingInProgress() ) { router->StopRouting(); } if( !baselineValid() ) { InitBaseLine( router, layer, aBoard ); } else { if( resetToBaseline( router, layer, aFrame, *m_baseLine, true ) ) { m_origin = m_baseLine->CPoint( 0 ); m_end = m_baseLine->CPoint( -1 ); } else { InitBaseLine( router, layer, aBoard ); return false; } if( m_tuningMode == DIFF_PAIR && !resetToBaseline( router, layer, aFrame, *m_baseLineCoupled, false ) ) { InitBaseLine( router, layer, aBoard ); return false; } } // Snap points VECTOR2I startSnapPoint, endSnapPoint; PNS::LINKED_ITEM* startItem = PickSegment( router, m_origin, layer, startSnapPoint ); PNS::LINKED_ITEM* endItem = PickSegment( router, m_end, layer, endSnapPoint ); wxASSERT( startItem ); wxASSERT( endItem ); if( !startItem || !endItem ) return false; router->SetMode( ToPNSMode() ); if( !router->StartRouting( startSnapPoint, startItem, layer ) ) return false; auto placer = static_cast( router->Placer() ); PNS::MEANDER_SETTINGS settings = ToMeanderSettings(); placer->UpdateSettings( settings ); router->Move( m_end, nullptr ); m_lastNetName = iface->GetNetName( startItem->Net() ); m_tuningInfo = placer->TuningInfo( aFrame->GetUserUnits() ); m_tuningStatus = placer->TuningStatus(); return true; } void EditPush( GENERATOR_TOOL* aTool, BOARD* aBoard, PCB_BASE_EDIT_FRAME* aFrame, BOARD_COMMIT* aCommit, const wxString& aCommitMsg = wxEmptyString, int aCommitFlags = 0 ) override { PNS::ROUTER* router = aTool->Router(); if( router->RoutingInProgress() ) { router->FixRoute( m_end, nullptr, true ); router->StopRouting(); std::set routerRemovedItems = aTool->GetRouterCommitRemovedItems(); std::set routerAddedItems = aTool->GetRouterCommitAddedItems(); for( BOARD_ITEM* item : m_removedItems ) { aCommit->Remove( item ); } m_removedItems.clear(); for( BOARD_ITEM* item : routerRemovedItems ) { aCommit->Remove( item ); } for( BOARD_ITEM* item : routerAddedItems ) { item->SetSelected(); AddItem( item ); aCommit->Add( item ); } } if( aCommitMsg.IsEmpty() ) aCommit->Push( _( "Edit Meander" ), aCommitFlags ); else aCommit->Push( aCommitMsg, aCommitFlags ); } void EditRevert( GENERATOR_TOOL* aTool, BOARD* aBoard, PCB_BASE_EDIT_FRAME* aFrame, BOARD_COMMIT* aCommit ) override { for( BOARD_ITEM* item : m_removedItems ) aFrame->GetCanvas()->GetView()->Hide( item, false ); m_removedItems.clear(); aTool->Router()->StopRouting(); if( aCommit ) aCommit->Revert(); } bool MakeEditPoints( std::shared_ptr points ) const override { points->AddPoint( m_origin ); points->AddPoint( m_end ); SEG base = m_baseLine && m_baseLine->SegmentCount() > 0 ? m_baseLine->CSegment( 0 ) : SEG( m_origin, m_end ); int offset = m_maxAmplitude; if( m_initialSide == -1 ) offset *= -1; VECTOR2I widthHandleOffset = ( base.B - base.A ).Perpendicular().Resize( offset ); points->AddPoint( m_origin + widthHandleOffset ); points->Point( 2 ).SetGridConstraint( IGNORE_GRID ); VECTOR2I spacingHandleOffset = widthHandleOffset + ( base.B - base.A ).Resize( KiROUND( m_spacing * 1.5 ) ); points->AddPoint( m_origin + spacingHandleOffset ); points->Point( 3 ).SetGridConstraint( IGNORE_GRID ); return true; } bool UpdateFromEditPoints( std::shared_ptr aEditPoints, BOARD_COMMIT* aCommit ) override { SEG base = m_baseLine && m_baseLine->SegmentCount() > 0 ? m_baseLine->CSegment( 0 ) : SEG( m_origin, m_end ); m_origin = aEditPoints->Point( 0 ).GetPosition(); m_end = aEditPoints->Point( 1 ).GetPosition(); if( aEditPoints->Point( 2 ).IsActive() ) { VECTOR2I wHandle = aEditPoints->Point( 2 ).GetPosition(); int value = base.LineDistance( wHandle ); SetMaxAmplitude( KiROUND( value / pcbIUScale.mmToIU( 0.1 ) ) * pcbIUScale.mmToIU( 0.1 ) ); int side = base.Side( wHandle ); if( side < 0 ) m_initialSide = PNS::MEANDER_SIDE_LEFT; else m_initialSide = PNS::MEANDER_SIDE_RIGHT; } if( aEditPoints->Point( 3 ).IsActive() ) { VECTOR2I wHandle = aEditPoints->Point( 2 ).GetPosition(); VECTOR2I sHandle = aEditPoints->Point( 3 ).GetPosition(); int value = KiROUND( SEG( m_origin, wHandle ).LineDistance( sHandle ) / 1.5 ); SetSpacing( KiROUND( value / pcbIUScale.mmToIU( 0.01 ) ) * pcbIUScale.mmToIU( 0.01 ) ); } return true; } bool UpdateEditPoints( std::shared_ptr aEditPoints ) override { SEG base = m_baseLine && m_baseLine->SegmentCount() > 0 ? m_baseLine->CSegment( 0 ) : SEG( m_origin, m_end ); int offset = m_maxAmplitude; if( m_initialSide == -1 ) offset *= -1; VECTOR2I widthHandleOffset = ( base.B - base.A ).Perpendicular().Resize( offset ); aEditPoints->Point( 0 ).SetPosition( m_origin ); aEditPoints->Point( 1 ).SetPosition( m_end ); aEditPoints->Point( 2 ).SetPosition( m_origin + widthHandleOffset ); VECTOR2I spacingHandleOffset = widthHandleOffset + ( base.B - base.A ).Resize( KiROUND( m_spacing * 1.5 ) ); aEditPoints->Point( 3 ).SetPosition( m_origin + spacingHandleOffset ); return true; } SHAPE_LINE_CHAIN GetRectShape() const { SHAPE_LINE_CHAIN chain; if( m_baseLine ) { bool singleSided = m_singleSide; if( singleSided ) { SHAPE_LINE_CHAIN left, right; if( m_baseLine->OffsetLine( m_maxAmplitude, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_LOW_DEF, left, right, true ) ) { chain.Append( m_baseLine->CPoint( 0 ) ); chain.Append( m_initialSide >= 0 ? right : left ); chain.Append( m_baseLine->CPoint( -1 ) ); return chain; } else { singleSided = false; } } if( !singleSided ) { SHAPE_POLY_SET poly; poly.OffsetLineChain( *m_baseLine, m_maxAmplitude * 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_LOW_DEF, false ); if( poly.OutlineCount() > 0 ) { chain = poly.Outline( 0 ); } } } return chain; } void Move( const VECTOR2I& aMoveVector ) override { m_origin += aMoveVector; m_end += aMoveVector; } const BOX2I GetBoundingBox() const override { return GetRectShape().BBox(); } void ViewGetLayers( int aLayers[], int& aCount ) const override { aCount = 0; aLayers[aCount++] = LAYER_ANCHOR; } bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override { return GetRectShape().Collide( aPosition, aAccuracy ); } bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const override { return GetBoundingBox().Intersects( aRect ); } const BOX2I ViewBBox() const override { return GetBoundingBox(); } EDA_ITEM* Clone() const override { return new PCB_GENERATOR_MEANDERS( *this ); } void swapData( BOARD_ITEM* aImage ) override { wxASSERT( aImage->Type() == PCB_GENERATOR_T ); std::swap( *this, *static_cast( aImage ) ); } void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final { if( !IsSelected() && !IsNew() ) return; KIGFX::PREVIEW::DRAW_CONTEXT ctx( *aView ); int size = KiROUND( aView->ToWorld( EDIT_POINT::POINT_SIZE ) * 0.8 ); if( m_baseLine ) { for( int i = 0; i < m_baseLine->SegmentCount(); i++ ) { SEG seg = m_baseLine->CSegment( i ); ctx.DrawLine( seg.A, seg.B, false ); } } else { ctx.DrawLine( m_origin, m_end, false ); } if( m_baseLineCoupled ) { for( int i = 0; i < m_baseLineCoupled->SegmentCount(); i++ ) { SEG seg = m_baseLineCoupled->CSegment( i ); ctx.DrawLine( seg.A, seg.B, false ); } } SHAPE_LINE_CHAIN chain = GetRectShape(); for( int i = 0; i < chain.SegmentCount(); i++ ) { SEG seg = chain.Segment( i ); ctx.DrawLineDashed( seg.A, seg.B, size, size / 2, false ); } } const VECTOR2I& GetEnd() const { return m_end; } void SetEnd( const VECTOR2I& aValue ) { m_end = aValue; } int GetEndX() const { return m_end.x; } void SetEndX( int aValue ) { m_end.x = aValue; } int GetEndY() const { return m_end.y; } void SetEndY( int aValue ) { m_end.y = aValue; } LENGTH_TUNING_MODE GetTuningMode() const { return m_tuningMode; } void SetTuningMode( LENGTH_TUNING_MODE aValue ) { m_tuningMode = aValue; } int GetMinAmplitude() const { return m_minAmplitude; } void SetMinAmplitude( int aValue ) { m_minAmplitude = aValue; } int GetMaxAmplitude() const { return m_maxAmplitude; } void SetMaxAmplitude( int aValue ) { m_maxAmplitude = aValue; } PNS::MEANDER_SIDE GetInitialSide() const { return m_initialSide; } void SetInitialSide( PNS::MEANDER_SIDE aValue ) { m_initialSide = aValue; } int GetSpacing() const { return m_spacing; } void SetSpacing( int aValue ) { m_spacing = aValue; } long long int GetTargetLength() const { return m_targetLength; } void SetTargetLength( long long int aValue ) { m_targetLength = aValue; } int GetTargetSkew() const { return m_targetSkew; } void SetTargetSkew( int aValue ) { m_targetSkew = aValue; } bool GetOverrideCustomRules() const { return m_overrideCustomRules; } void SetOverrideCustomRules( bool aOverride ) { m_overrideCustomRules = aOverride; } int GetCornerRadiusPercentage() const { return m_cornerRadiusPercentage; } void SetCornerRadiusPercentage( int aValue ) { m_cornerRadiusPercentage = aValue; } bool IsSingleSided() const { return m_singleSide; } void SetSingleSided( bool aValue ) { m_singleSide = aValue; } bool IsRounded() const { return m_rounded; } void SetRounded( bool aValue ) { m_rounded = aValue; } std::vector> GetRowData() override { std::vector> data = PCB_GENERATOR::GetRowData(); data.emplace_back( _HKI( "Net" ), m_lastNetName ); data.emplace_back( _HKI( "Tuning" ), m_tuningInfo ); return data; } const STRING_ANY_MAP GetProperties() const override { STRING_ANY_MAP props = PCB_GENERATOR::GetProperties(); props.set( "tuning_mode", TuningToString( m_tuningMode ) ); props.set( "initial_side", SideToString( m_initialSide ) ); props.set( "last_status", StatusToString( m_tuningStatus ) ); props.set( "end", m_end ); props.set( "corner_radius_percent", m_cornerRadiusPercentage ); props.set( "single_sided", m_singleSide ); props.set( "rounded", m_rounded ); props.set_iu( "max_amplitude", m_maxAmplitude ); props.set_iu( "min_spacing", m_spacing ); props.set_iu( "target_length", m_targetLength ); props.set_iu( "target_skew", m_targetSkew ); props.set( "last_netname", m_lastNetName ); props.set( "last_tuning", m_tuningInfo ); props.set( "override_custom_rules", m_overrideCustomRules ); if( m_baseLine ) props.set( "base_line", wxAny( *m_baseLine ) ); if( m_baseLineCoupled ) props.set( "base_line_coupled", wxAny( *m_baseLineCoupled ) ); return props; } void SetProperties( const STRING_ANY_MAP& aProps ) override { PCB_GENERATOR::SetProperties( aProps ); wxString tuningMode; aProps.get_to( "tuning_mode", tuningMode ); m_tuningMode = TuningFromString( tuningMode.utf8_string() ); wxString side; aProps.get_to( "initial_side", side ); m_initialSide = SideFromString( side.utf8_string() ); wxString status; aProps.get_to( "last_status", status ); m_tuningStatus = StatusFromString( status.utf8_string() ); aProps.get_to( "end", m_end ); aProps.get_to( "corner_radius_percent", m_cornerRadiusPercentage ); aProps.get_to( "single_sided", m_singleSide ); aProps.get_to( "side", m_initialSide ); aProps.get_to( "rounded", m_rounded ); aProps.get_to_iu( "max_amplitude", m_maxAmplitude ); aProps.get_to_iu( "min_spacing", m_spacing ); aProps.get_to_iu( "target_length", m_targetLength ); aProps.get_to_iu( "target_skew", m_targetSkew ); aProps.get_to( "override_custom_rules", m_overrideCustomRules ); aProps.get_to( "last_netname", m_lastNetName ); aProps.get_to( "last_tuning", m_tuningInfo ); if( auto baseLine = aProps.get_opt( "base_line" ) ) m_baseLine = *baseLine; if( auto baseLineCoupled = aProps.get_opt( "base_line_coupled" ) ) m_baseLineCoupled = *baseLineCoupled; } void ShowPropertiesDialog( PCB_BASE_EDIT_FRAME* aEditFrame ) override { PNS::MEANDER_SETTINGS settings = ToMeanderSettings(); DRC_CONSTRAINT constraint; if( !m_items.empty() ) { BOARD_ITEM* startItem = *m_items.begin(); std::shared_ptr& drcEngine = GetBoard()->GetDesignSettings().m_DRCEngine; constraint = drcEngine->EvalRules( LENGTH_CONSTRAINT, startItem, nullptr, GetLayer() ); if( !constraint.IsNull() && !settings.m_overrideCustomRules ) settings.m_targetLength = constraint.GetValue().Opt(); } DIALOG_MEANDER_PROPERTIES dlg( aEditFrame, settings, ToPNSMode(), constraint ); if( dlg.ShowModal() == wxID_OK ) { BOARD_COMMIT commit( aEditFrame ); commit.Modify( this ); FromMeanderSettings( settings ); commit.Push( _( "Edit Meander Properties" ) ); } aEditFrame->GetToolManager()->PostAction( PCB_ACTIONS::regenerateItem, this ); } void UpdateStatus( GENERATOR_TOOL* aTool, PCB_BASE_EDIT_FRAME* aFrame, STATUS_TEXT_POPUP* aPopup ) override { auto* placer = dynamic_cast( aTool->Router()->Placer() ); if( !placer ) return; aPopup->SetText( placer->TuningInfo( aFrame->GetUserUnits() ) ); // Determine the background color first and choose a contrasting value COLOR4D bg( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); double h, s, l; bg.ToHSL( h, s, l ); switch( placer->TuningStatus() ) { case PNS::MEANDER_PLACER_BASE::TUNED: if( l < 0.5 ) aPopup->SetTextColor( wxColor( 127, 200, 127 ) ); else aPopup->SetTextColor( wxColor( 0, 92, 0 ) ); break; case PNS::MEANDER_PLACER_BASE::TOO_SHORT: if( l < 0.5 ) aPopup->SetTextColor( wxColor( 242, 100, 126 ) ); else aPopup->SetTextColor( wxColor( 122, 0, 0 ) ); break; case PNS::MEANDER_PLACER_BASE::TOO_LONG: if( l < 0.5 ) aPopup->SetTextColor( wxColor( 66, 184, 235 ) ); else aPopup->SetTextColor( wxColor( 19, 19, 195 ) ); break; } } protected: VECTOR2I m_end; int m_minAmplitude; int m_maxAmplitude; int m_spacing; long long int m_targetLength; int m_targetSkew; bool m_overrideCustomRules; int m_cornerRadiusPercentage; PNS::MEANDER_SIDE m_initialSide; std::optional m_baseLine; std::optional m_baseLineCoupled; bool m_singleSide; bool m_rounded; LENGTH_TUNING_MODE m_tuningMode; wxString m_lastNetName; wxString m_tuningInfo; PNS::MEANDER_PLACER_BASE::TUNING_STATUS m_tuningStatus; // Temp storage during editing std::set m_removedItems; }; const wxString PCB_GENERATOR_MEANDERS::DISPLAY_NAME = _HKI( "Meanders" ); const wxString PCB_GENERATOR_MEANDERS::GENERATOR_TYPE = wxS( "meanders" ); using SCOPED_DRAW_MODE = SCOPED_SET_RESET; #define HITTEST_THRESHOLD_PIXELS 5 int DRAWING_TOOL::PlaceMeander( const TOOL_EVENT& aEvent ) { if( m_inDrawingTool ) return 0; REENTRANCY_GUARD guard( &m_inDrawingTool ); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear ); m_frame->PushTool( aEvent ); Activate(); LENGTH_TUNING_MODE mode = FromPNSMode( aEvent.Parameter() ); KIGFX::VIEW_CONTROLS* controls = getViewControls(); BOARD* board = m_frame->GetBoard(); PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool(); GENERAL_COLLECTORS_GUIDE guide = m_frame->GetCollectorsGuide(); GENERATOR_TOOL* generatorTool = m_toolMgr->GetTool(); PNS::ROUTER* router = generatorTool->Router(); SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::MEANDER ); m_pickerItem = nullptr; m_meander = nullptr; // Add a VIEW_GROUP that serves as a preview for the new item m_preview.Clear(); m_view->Add( &m_preview ); auto setCursor = [&]() { m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::BULLSEYE ); controls->ShowCursor( true ); }; auto updateMeander = [&]() { if( m_meander && m_meander->GetPosition() != m_meander->GetEnd() ) { m_meander->EditStart( generatorTool, m_board, m_frame, nullptr ); m_meander->Update( generatorTool, m_board, m_frame, nullptr ); m_statusPopup->Popup(); canvas()->SetStatusPopup( m_statusPopup.get() ); m_view->Update( &m_preview ); m_meander->UpdateStatus( generatorTool, m_frame, m_statusPopup.get() ); m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) ); } }; // Set initial cursor setCursor(); while( TOOL_EVENT* evt = Wait() ) { setCursor(); VECTOR2D cursorPos = controls->GetMousePosition(); if( evt->IsCancelInteractive() || evt->IsActivate() ) { if( m_meander ) { // First click already made; clean up meander preview m_meander->EditRevert( generatorTool, m_board, m_frame, nullptr ); m_preview.Clear(); delete m_meander; m_meander = nullptr; } break; } else if( evt->IsMotion() ) { if( !m_meander ) { // First click not yet made; we're in highlight-net-under-cursor mode GENERAL_COLLECTOR collector; collector.m_Threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) ); collector.Collect( board, { PCB_TRACE_T, PCB_ARC_T }, cursorPos, guide ); if( collector.GetCount() > 1 ) selectionTool->GuessSelectionCandidates( collector, cursorPos ); BOARD_ITEM* item = collector.GetCount() == 1 ? collector[ 0 ] : nullptr; if( !m_pickerItem ) { m_pickerItem = static_cast( item ); generatorTool->HighlightNets( m_pickerItem ); } else { m_pickerItem = static_cast( item ); generatorTool->UpdateHighlightedNets( m_pickerItem ); } } else { // First click already made; we're in preview-meander mode m_meander->SetEnd( cursorPos ); updateMeander(); } } else if( evt->IsClick( BUT_LEFT ) ) { if( m_pickerItem && !m_meander ) { // First click; create a meander generatorTool->HighlightNets( nullptr ); m_frame->SetActiveLayer( m_pickerItem->GetLayer() ); m_meander = PCB_GENERATOR_MEANDERS::CreateNew( generatorTool, m_frame, m_pickerItem, mode ); int dummyDist; int dummyClearance = std::numeric_limits::max() / 2; VECTOR2I closestPt; m_pickerItem->GetEffectiveShape()->Collide( cursorPos, dummyClearance, &dummyDist, &closestPt ); m_meander->SetPosition( closestPt ); m_meander->SetEnd( closestPt ); m_preview.Add( m_meander ); } else if( m_pickerItem && m_meander ) { // Second click; we're done BOARD_COMMIT commit( m_frame ); m_meander->EditStart( generatorTool, m_board, m_frame, &commit ); m_meander->Update( generatorTool, m_board, m_frame, &commit ); m_meander->EditPush( generatorTool, m_board, m_frame, &commit, _( "Tune" ) ); break; } } else if( evt->IsClick( BUT_RIGHT ) ) { PCB_SELECTION dummy; m_menu.ShowContextMenu( dummy ); } else if( evt->IsAction( &PCB_ACTIONS::spacingIncrease ) || evt->IsAction( &PCB_ACTIONS::spacingDecrease ) ) { if( m_meander ) { auto* placer = static_cast( router->Placer() ); placer->SpacingStep( evt->IsAction( &PCB_ACTIONS::spacingIncrease ) ? 1 : -1 ); m_meander->SetSpacing( placer->MeanderSettings().m_spacing ); updateMeander(); } } else if( evt->IsAction( &PCB_ACTIONS::amplIncrease ) || evt->IsAction( &PCB_ACTIONS::amplDecrease ) ) { if( m_meander ) { auto* placer = static_cast( router->Placer() ); placer->AmplitudeStep( evt->IsAction( &PCB_ACTIONS::amplIncrease ) ? 1 : -1 ); m_meander->SetMaxAmplitude( placer->MeanderSettings().m_maxAmplitude ); updateMeander(); } } // TODO: It'd be nice to be able to say "don't allow any non-trivial editing actions", // but we don't at present have that, so we just knock out some of the egregious ones. else if( ZONE_FILLER_TOOL::IsZoneFillAction( evt ) ) { wxBell(); } else { evt->SetPassEvent(); } controls->CaptureCursor( m_meander != nullptr ); controls->SetAutoPan( m_meander != nullptr ); } controls->CaptureCursor( false ); controls->SetAutoPan( false ); controls->ForceCursorPosition( false ); controls->ShowCursor( false ); m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); canvas()->SetStatusPopup( nullptr ); m_statusPopup->Hide(); generatorTool->HighlightNets( nullptr ); m_preview.Clear(); m_view->Remove( &m_preview ); m_frame->GetCanvas()->Refresh(); if( m_meander ) selectionTool->AddItemToSel( m_meander ); m_frame->PopTool( aEvent ); return 0; } static struct PCB_GENERATOR_MEANDERS_DESC { PCB_GENERATOR_MEANDERS_DESC() { ENUM_MAP::Instance() .Map( LENGTH_TUNING_MODE::SINGLE, _HKI( "Single track" ) ) /*.Map( LENGTH_TUNING_MODE::DIFF_PAIR, _HKI( "Diff. pair" ) )*/ // Not supported .Map( LENGTH_TUNING_MODE::DIFF_PAIR_SKEW, _HKI( "Diff. pair Skew" ) ); ENUM_MAP::Instance() .Map( PNS::MEANDER_SIDE_LEFT, _HKI( "Left" ) ) .Map( PNS::MEANDER_SIDE_RIGHT, _HKI( "Right" ) ) .Map( PNS::MEANDER_SIDE_DEFAULT, _HKI( "Default" ) ); PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); REGISTER_TYPE( PCB_GENERATOR_MEANDERS ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.InheritsAfter( TYPE_HASH( PCB_GENERATOR_MEANDERS ), TYPE_HASH( PCB_GENERATOR ) ); propMgr.InheritsAfter( TYPE_HASH( PCB_GENERATOR_MEANDERS ), TYPE_HASH( BOARD_ITEM ) ); const wxString groupTab = _HKI( "Meander Properties" ); propMgr.AddProperty( new PROPERTY( _HKI( "End X" ), &PCB_GENERATOR_MEANDERS::SetEndX, &PCB_GENERATOR_MEANDERS::GetEndX, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_X_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "End Y" ), &PCB_GENERATOR_MEANDERS::SetEndY, &PCB_GENERATOR_MEANDERS::GetEndY, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_Y_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Tuning mode" ), &PCB_GENERATOR_MEANDERS::SetTuningMode, &PCB_GENERATOR_MEANDERS::GetTuningMode ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Min amplitude" ), &PCB_GENERATOR_MEANDERS::SetMinAmplitude, &PCB_GENERATOR_MEANDERS::GetMinAmplitude, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_X_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Max amplitude" ), &PCB_GENERATOR_MEANDERS::SetMaxAmplitude, &PCB_GENERATOR_MEANDERS::GetMaxAmplitude, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_X_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Initial side" ), &PCB_GENERATOR_MEANDERS::SetInitialSide, &PCB_GENERATOR_MEANDERS::GetInitialSide ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Min spacing" ), &PCB_GENERATOR_MEANDERS::SetSpacing, &PCB_GENERATOR_MEANDERS::GetSpacing, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_X_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Corner radius %" ), &PCB_GENERATOR_MEANDERS::SetCornerRadiusPercentage, &PCB_GENERATOR_MEANDERS::GetCornerRadiusPercentage, PROPERTY_DISPLAY::PT_DEFAULT, ORIGIN_TRANSFORMS::NOT_A_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Target length" ), &PCB_GENERATOR_MEANDERS::SetTargetLength, &PCB_GENERATOR_MEANDERS::GetTargetLength, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_X_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Target skew" ), &PCB_GENERATOR_MEANDERS::SetTargetSkew, &PCB_GENERATOR_MEANDERS::GetTargetSkew, PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::ABS_X_COORD ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Override custom rules" ), &PCB_GENERATOR_MEANDERS::SetOverrideCustomRules, &PCB_GENERATOR_MEANDERS::GetOverrideCustomRules ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Single-sided" ), &PCB_GENERATOR_MEANDERS::SetSingleSided, &PCB_GENERATOR_MEANDERS::IsSingleSided ), groupTab ); propMgr.AddProperty( new PROPERTY( _HKI( "Rounded" ), &PCB_GENERATOR_MEANDERS::SetRounded, &PCB_GENERATOR_MEANDERS::IsRounded ), groupTab ); } } _PCB_GENERATOR_MEANDERS_DESC; ENUM_TO_WXANY( LENGTH_TUNING_MODE ) ENUM_TO_WXANY( PNS::MEANDER_SIDE ) static GENERATORS_MGR::REGISTER registerMe;