mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
Also cleans up a misconception about table header borders, and renames the getter/setter to be clearer. Also makes sure that table cells are updated when the table layer changes. And another bug where we were writing the grey color value back to the cell for hidden cells. Fixes https://gitlab.com/kicad/code/kicad/-/issues/20319
1239 lines
43 KiB
C++
1239 lines
43 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright The 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 <algorithm> // for min
|
|
#include <bitset> // for bitset, operator&, __bi...
|
|
#include <math.h> // for abs
|
|
|
|
#include <geometry/seg.h> // for SEG
|
|
#include <geometry/shape_circle.h>
|
|
#include <geometry/shape_line_chain.h> // for SHAPE_LINE_CHAIN
|
|
#include <geometry/shape_poly_set.h> // for SHAPE_POLY_SET, SHAPE_P...
|
|
#include <geometry/shape_segment.h>
|
|
#include <string_utils.h>
|
|
#include <macros.h>
|
|
#include <math/util.h> // for KiROUND
|
|
#include <math/vector2d.h> // for VECTOR2I
|
|
#include <plotters/plotter_gerber.h>
|
|
#include <trigo.h>
|
|
#include <font/stroke_font.h>
|
|
#include <gal/gal_display_options.h>
|
|
#include <callback_gal.h>
|
|
#include <core/typeinfo.h> // for dyn_cast, PCB_DIMENSION_T
|
|
#include <gbr_metadata.h>
|
|
#include <gbr_netlist_metadata.h> // for GBR_NETLIST_METADATA
|
|
#include <layer_ids.h> // for LSET, IsCopperLayer
|
|
#include <lset.h>
|
|
#include <pcbplot.h>
|
|
#include <pcb_plot_params.h> // for PCB_PLOT_PARAMS, PCB_PL...
|
|
#include <advanced_config.h>
|
|
|
|
#include <pcb_dimension.h>
|
|
#include <pcb_shape.h>
|
|
#include <footprint.h>
|
|
#include <pcb_track.h>
|
|
#include <pad.h>
|
|
#include <pcb_target.h>
|
|
#include <pcb_text.h>
|
|
#include <pcb_textbox.h>
|
|
#include <pcb_tablecell.h>
|
|
#include <pcb_table.h>
|
|
#include <zone.h>
|
|
|
|
#include <wx/debug.h> // for wxASSERT_MSG
|
|
|
|
|
|
COLOR4D BRDITEMS_PLOTTER::getColor( int aLayer ) const
|
|
{
|
|
COLOR4D color = ColorSettings()->GetColor( aLayer );
|
|
|
|
// A hack to avoid plotting a white item in white color on white paper
|
|
if( color == COLOR4D::WHITE )
|
|
color = COLOR4D( LIGHTGRAY );
|
|
|
|
return color;
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotPadNumber( const PAD* aPad, const COLOR4D& aColor )
|
|
{
|
|
wxString padNumber = UnescapeString( aPad->GetNumber() );
|
|
|
|
if( padNumber.IsEmpty() )
|
|
return;
|
|
|
|
BOX2I padBBox = aPad->GetBoundingBox();
|
|
VECTOR2I position = padBBox.Centre();
|
|
VECTOR2I padsize = padBBox.GetSize();
|
|
|
|
// TODO(JE) padstacks
|
|
if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM )
|
|
{
|
|
// See if we have a number box
|
|
for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) )
|
|
{
|
|
if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::RECTANGLE )
|
|
{
|
|
position = primitive->GetCenter();
|
|
RotatePoint( position, aPad->GetOrientation() );
|
|
position += aPad->ShapePos( PADSTACK::ALL_LAYERS );
|
|
|
|
padsize.x = abs( primitive->GetBotRight().x - primitive->GetTopLeft().x );
|
|
padsize.y = abs( primitive->GetBotRight().y - primitive->GetTopLeft().y );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( aPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CUSTOM )
|
|
{
|
|
// Don't allow a 45° rotation to bloat a pad's bounding box unnecessarily
|
|
int limit = KiROUND( std::min( aPad->GetSize( PADSTACK::ALL_LAYERS ).x,
|
|
aPad->GetSize( PADSTACK::ALL_LAYERS ).y ) * 1.1 );
|
|
|
|
if( padsize.x > limit && padsize.y > limit )
|
|
{
|
|
padsize.x = limit;
|
|
padsize.y = limit;
|
|
}
|
|
}
|
|
|
|
TEXT_ATTRIBUTES textAttrs;
|
|
|
|
if( padsize.x < ( padsize.y * 0.95 ) )
|
|
{
|
|
textAttrs.m_Angle = ANGLE_90;
|
|
std::swap( padsize.x, padsize.y );
|
|
}
|
|
|
|
// approximate the size of the pad number text:
|
|
// We use a size for at least 3 chars, to give a good look even for short numbers
|
|
int tsize = KiROUND( padsize.x / std::max( PrintableCharCount( padNumber ), 3 ) );
|
|
tsize = std::min( tsize, padsize.y );
|
|
|
|
// enforce a max size
|
|
tsize = std::min( tsize, pcbIUScale.mmToIU( 5.0 ) );
|
|
|
|
textAttrs.m_Size = VECTOR2I( tsize, tsize );
|
|
|
|
// use a somewhat spindly font to go with the outlined pads
|
|
textAttrs.m_StrokeWidth = KiROUND( tsize / 12.0 );
|
|
|
|
m_plotter->PlotText( position, aColor, padNumber, textAttrs );
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, PCB_LAYER_ID aLayer, const COLOR4D& aColor,
|
|
OUTLINE_MODE aPlotMode )
|
|
{
|
|
VECTOR2I shape_pos = aPad->ShapePos( aLayer );
|
|
GBR_METADATA metadata;
|
|
|
|
bool plotOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
|
|
bool plotOnExternalCopperLayer = ( m_layerMask & LSET::ExternalCuMask() ).any();
|
|
|
|
// Pad not on the solder mask layer cannot be soldered.
|
|
// therefore it can have a specific aperture attribute.
|
|
// Not yet in use.
|
|
// bool isPadOnBoardTechLayers = ( aPad->GetLayerSet() & LSET::AllBoardTechMask() ).any();
|
|
|
|
metadata.SetCmpReference( aPad->GetParentFootprint()->GetReference() );
|
|
|
|
if( plotOnCopperLayer )
|
|
{
|
|
metadata.SetNetAttribType( GBR_NETINFO_ALL );
|
|
metadata.SetCopper( true );
|
|
|
|
// Gives a default attribute, for instance for pads used as tracks in net ties:
|
|
// Connector pads and SMD pads are on external layers
|
|
// if on internal layers, they are certainly used as net tie
|
|
// and are similar to tracks: just conductor items
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
|
|
|
|
const bool useUTF8 = false;
|
|
const bool useQuoting = false;
|
|
metadata.SetPadName( aPad->GetNumber(), useUTF8, useQuoting );
|
|
|
|
if( !aPad->GetNumber().IsEmpty() )
|
|
metadata.SetPadPinFunction( aPad->GetPinFunction(), useUTF8, useQuoting );
|
|
|
|
metadata.SetNetName( aPad->GetNetname() );
|
|
|
|
// Some pads are mechanical pads ( through hole or smd )
|
|
// when this is the case, they have no pad name and/or are not plated.
|
|
// In this case gerber files have slightly different attributes.
|
|
if( aPad->GetAttribute() == PAD_ATTRIB::NPTH || aPad->GetNumber().IsEmpty() )
|
|
metadata.m_NetlistMetadata.m_NotInNet = true;
|
|
|
|
if( !plotOnExternalCopperLayer )
|
|
{
|
|
// the .P object attribute (GBR_NETLIST_METADATA::GBR_NETINFO_PAD)
|
|
// is used on outer layers, unless the component is embedded
|
|
// or a "etched" component (fp only drawn, not a physical component)
|
|
// Currently, Pcbnew does not handle embedded component, so we disable the .P
|
|
// attribute on internal layers
|
|
// Note the Gerber doc is not really clear about through holes pads about the .P
|
|
metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET |
|
|
GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
|
|
|
|
}
|
|
|
|
// Some attributes are reserved to the external copper layers:
|
|
// GBR_APERTURE_ATTRIB_CONNECTORPAD and GBR_APERTURE_ATTRIB_SMDPAD_CUDEF
|
|
// for instance.
|
|
// Pad with type PAD_ATTRIB::CONN or PAD_ATTRIB::SMD that is not on outer layer
|
|
// has its aperture attribute set to GBR_APERTURE_ATTRIB_CONDUCTOR
|
|
switch( aPad->GetAttribute() )
|
|
{
|
|
case PAD_ATTRIB::NPTH: // Mechanical pad through hole
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
|
|
break;
|
|
|
|
case PAD_ATTRIB::PTH : // Pad through hole, a hole is also expected
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_COMPONENTPAD );
|
|
break;
|
|
|
|
case PAD_ATTRIB::CONN: // Connector pads, no solder paste but with solder mask.
|
|
if( plotOnExternalCopperLayer )
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONNECTORPAD );
|
|
break;
|
|
|
|
case PAD_ATTRIB::SMD: // SMD pads (on external copper layer only)
|
|
// with solder paste and mask
|
|
if( plotOnExternalCopperLayer )
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_SMDPAD_CUDEF );
|
|
break;
|
|
}
|
|
|
|
// Fabrication properties can have specific GBR_APERTURE_METADATA options
|
|
// that replace previous aperture attribute:
|
|
switch( aPad->GetProperty() )
|
|
{
|
|
case PAD_PROP::BGA: // Only applicable to outer layers
|
|
if( plotOnExternalCopperLayer )
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_BGAPAD_CUDEF );
|
|
break;
|
|
|
|
case PAD_PROP::FIDUCIAL_GLBL:
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_GLBL );
|
|
break;
|
|
|
|
case PAD_PROP::FIDUCIAL_LOCAL:
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_LOCAL );
|
|
break;
|
|
|
|
case PAD_PROP::TESTPOINT: // Only applicable to outer layers
|
|
if( plotOnExternalCopperLayer )
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_TESTPOINT );
|
|
break;
|
|
|
|
case PAD_PROP::HEATSINK:
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_HEATSINKPAD );
|
|
break;
|
|
|
|
case PAD_PROP::CASTELLATED:
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CASTELLATEDPAD );
|
|
break;
|
|
|
|
case PAD_PROP::NONE:
|
|
case PAD_PROP::MECHANICAL:
|
|
break;
|
|
}
|
|
|
|
// Ensure NPTH pads have *always* the GBR_APERTURE_ATTRIB_WASHERPAD attribute
|
|
if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
|
|
metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
|
|
}
|
|
else
|
|
{
|
|
metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
|
|
}
|
|
|
|
// Set plot color (change WHITE to LIGHTGRAY because
|
|
// the white items are not seen on a white paper or screen
|
|
m_plotter->SetColor( aColor != WHITE ? aColor : LIGHTGRAY );
|
|
|
|
if( aPlotMode == SKETCH )
|
|
m_plotter->SetCurrentLineWidth( GetSketchPadLineWidth(), &metadata );
|
|
|
|
switch( aPad->GetShape( aLayer ) )
|
|
{
|
|
case PAD_SHAPE::CIRCLE:
|
|
m_plotter->FlashPadCircle( shape_pos, aPad->GetSize( aLayer ).x,
|
|
aPlotMode, &metadata );
|
|
break;
|
|
|
|
case PAD_SHAPE::OVAL:
|
|
m_plotter->FlashPadOval( shape_pos, aPad->GetSize( aLayer ),
|
|
aPad->GetOrientation(), aPlotMode, &metadata );
|
|
break;
|
|
|
|
case PAD_SHAPE::RECTANGLE:
|
|
m_plotter->FlashPadRect( shape_pos, aPad->GetSize( aLayer ),
|
|
aPad->GetOrientation(), aPlotMode, &metadata );
|
|
break;
|
|
|
|
case PAD_SHAPE::ROUNDRECT:
|
|
m_plotter->FlashPadRoundRect( shape_pos, aPad->GetSize( aLayer ),
|
|
aPad->GetRoundRectCornerRadius( aLayer ),
|
|
aPad->GetOrientation(), aPlotMode, &metadata );
|
|
break;
|
|
|
|
case PAD_SHAPE::TRAPEZOID:
|
|
{
|
|
// Build the pad polygon in coordinates relative to the pad
|
|
// (i.e. for a pad at pos 0,0, rot 0.0). Needed to use aperture macros,
|
|
// to be able to create a pattern common to all trapezoid pads having the same shape
|
|
VECTOR2I coord[4];
|
|
|
|
// Order is lower left, lower right, upper right, upper left.
|
|
VECTOR2I half_size = aPad->GetSize( aLayer ) / 2;
|
|
VECTOR2I trap_delta = aPad->GetDelta( aLayer ) / 2;
|
|
|
|
coord[0] = VECTOR2I( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
|
|
coord[1] = VECTOR2I( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
|
|
coord[2] = VECTOR2I( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
|
|
coord[3] = VECTOR2I( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
|
|
|
|
m_plotter->FlashPadTrapez( shape_pos, coord, aPad->GetOrientation(), aPlotMode, &metadata );
|
|
}
|
|
break;
|
|
|
|
case PAD_SHAPE::CHAMFERED_RECT:
|
|
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
|
|
{
|
|
GERBER_PLOTTER* gerberPlotter = static_cast<GERBER_PLOTTER*>( m_plotter );
|
|
|
|
gerberPlotter->FlashPadChamferRoundRect( shape_pos,
|
|
aPad->GetSize( aLayer ),
|
|
aPad->GetRoundRectCornerRadius( aLayer ),
|
|
aPad->GetChamferRectRatio( aLayer ),
|
|
aPad->GetChamferPositions( aLayer ), aPad->GetOrientation(),
|
|
aPlotMode, &metadata );
|
|
break;
|
|
}
|
|
|
|
KI_FALLTHROUGH;
|
|
|
|
default:
|
|
case PAD_SHAPE::CUSTOM:
|
|
{
|
|
const std::shared_ptr<SHAPE_POLY_SET>& polygons =
|
|
aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );
|
|
|
|
if( polygons->OutlineCount() )
|
|
{
|
|
m_plotter->FlashPadCustom( shape_pos, aPad->GetSize( aLayer ), aPad->GetOrientation(),
|
|
polygons.get(), aPlotMode, &metadata );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotFootprintTextItems( const FOOTPRINT* aFootprint )
|
|
{
|
|
if( !GetPlotFPText() )
|
|
return;
|
|
|
|
const PCB_TEXT* reference = &aFootprint->Reference();
|
|
PCB_LAYER_ID refLayer = reference->GetLayer();
|
|
|
|
// Reference and value have special controls for forcing their plotting
|
|
if( GetPlotReference()
|
|
&& m_layerMask[refLayer]
|
|
&& reference->IsVisible()
|
|
&& !( aFootprint->IsDNP() && hideDNPItems( refLayer ) ) )
|
|
{
|
|
PlotText( reference, refLayer, reference->IsKnockout(), reference->GetFontMetrics(),
|
|
aFootprint->IsDNP() && crossoutDNPItems( refLayer ) );
|
|
}
|
|
|
|
const PCB_TEXT* value = &aFootprint->Value();
|
|
PCB_LAYER_ID valueLayer = value->GetLayer();
|
|
|
|
if( GetPlotValue()
|
|
&& m_layerMask[valueLayer]
|
|
&& value->IsVisible()
|
|
&& !( aFootprint->IsDNP() && hideDNPItems( valueLayer ) ) )
|
|
{
|
|
PlotText( value, valueLayer, value->IsKnockout(), value->GetFontMetrics(),
|
|
false );
|
|
}
|
|
|
|
std::vector<PCB_TEXT*> texts;
|
|
|
|
// Skip the reference and value texts that are handled specially
|
|
for( PCB_FIELD* field : aFootprint->GetFields() )
|
|
{
|
|
if( field->IsReference() || field->IsValue() )
|
|
continue;
|
|
|
|
if( field->IsVisible() )
|
|
texts.push_back( field );
|
|
}
|
|
|
|
for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
|
|
{
|
|
if( PCB_TEXT* textItem = dynamic_cast<PCB_TEXT*>( item ) )
|
|
texts.push_back( textItem );
|
|
}
|
|
|
|
for( const PCB_TEXT* text : texts )
|
|
{
|
|
PCB_LAYER_ID textLayer = text->GetLayer();
|
|
bool strikeout = false;
|
|
|
|
if( textLayer == Edge_Cuts || textLayer >= PCB_LAYER_ID_COUNT )
|
|
continue;
|
|
|
|
if( aFootprint->IsDNP() && hideDNPItems( textLayer ) )
|
|
continue;
|
|
|
|
if( !m_layerMask[textLayer] || aFootprint->GetPrivateLayers().test( textLayer ) )
|
|
continue;
|
|
|
|
if( text->GetText() == wxT( "${REFERENCE}" ) )
|
|
{
|
|
if( !GetPlotReference() )
|
|
continue;
|
|
|
|
strikeout = aFootprint->IsDNP() && crossoutDNPItems( textLayer );
|
|
}
|
|
|
|
if( text->GetText() == wxT( "${VALUE}" ) )
|
|
{
|
|
if( !GetPlotValue() )
|
|
continue;
|
|
}
|
|
|
|
PlotText( text, textLayer, text->IsKnockout(), text->GetFontMetrics(), strikeout );
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotBoardGraphicItem( const BOARD_ITEM* item )
|
|
{
|
|
switch( item->Type() )
|
|
{
|
|
case PCB_SHAPE_T:
|
|
PlotShape( static_cast<const PCB_SHAPE*>( item ) );
|
|
break;
|
|
|
|
case PCB_TEXT_T:
|
|
{
|
|
const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item );
|
|
PlotText( text, text->GetLayer(), text->IsKnockout(), text->GetFontMetrics() );
|
|
break;
|
|
}
|
|
|
|
case PCB_TEXTBOX_T:
|
|
{
|
|
m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );
|
|
|
|
const PCB_TEXTBOX* textbox = static_cast<const PCB_TEXTBOX*>( item );
|
|
PlotText( textbox, textbox->GetLayer(), textbox->IsKnockout(), textbox->GetFontMetrics() );
|
|
|
|
if( textbox->IsBorderEnabled() )
|
|
PlotShape( textbox );
|
|
|
|
m_plotter->SetTextMode( GetTextMode() );
|
|
break;
|
|
}
|
|
|
|
case PCB_TABLE_T:
|
|
{
|
|
const PCB_TABLE* table = static_cast<const PCB_TABLE*>( item );
|
|
|
|
m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );
|
|
|
|
for( const PCB_TABLECELL* cell : table->GetCells() )
|
|
PlotText( cell, cell->GetLayer(), cell->IsKnockout(), cell->GetFontMetrics() );
|
|
|
|
PlotTableBorders( table );
|
|
|
|
m_plotter->SetTextMode( GetTextMode() );
|
|
break;
|
|
}
|
|
|
|
case PCB_DIM_ALIGNED_T:
|
|
case PCB_DIM_CENTER_T:
|
|
case PCB_DIM_RADIAL_T:
|
|
case PCB_DIM_ORTHOGONAL_T:
|
|
case PCB_DIM_LEADER_T:
|
|
m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );
|
|
|
|
PlotDimension( static_cast<const PCB_DIMENSION_BASE*>( item ) );
|
|
|
|
m_plotter->SetTextMode( GetTextMode() );
|
|
break;
|
|
|
|
case PCB_TARGET_T:
|
|
PlotPcbTarget( static_cast<const PCB_TARGET*>( item ) );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotDimension( const PCB_DIMENSION_BASE* aDim )
|
|
{
|
|
if( !m_layerMask[aDim->GetLayer()] )
|
|
return;
|
|
|
|
COLOR4D color = ColorSettings()->GetColor( aDim->GetLayer() );
|
|
|
|
// Set plot color (change WHITE to LIGHTGRAY because
|
|
// the white items are not seen on a white paper or screen
|
|
m_plotter->SetColor( color != WHITE ? color : LIGHTGRAY);
|
|
|
|
PlotText( aDim, aDim->GetLayer(), false, aDim->GetFontMetrics() );
|
|
|
|
PCB_SHAPE temp_item;
|
|
|
|
temp_item.SetStroke( STROKE_PARAMS( aDim->GetLineThickness(), LINE_STYLE::SOLID ) );
|
|
temp_item.SetLayer( aDim->GetLayer() );
|
|
|
|
for( const std::shared_ptr<SHAPE>& shape : aDim->GetShapes() )
|
|
{
|
|
switch( shape->Type() )
|
|
{
|
|
case SH_SEGMENT:
|
|
{
|
|
const SEG& seg = static_cast<const SHAPE_SEGMENT*>( shape.get() )->GetSeg();
|
|
|
|
temp_item.SetShape( SHAPE_T::SEGMENT );
|
|
temp_item.SetStart( seg.A );
|
|
temp_item.SetEnd( seg.B );
|
|
|
|
PlotShape( &temp_item );
|
|
break;
|
|
}
|
|
|
|
case SH_CIRCLE:
|
|
{
|
|
VECTOR2I start( shape->Centre() );
|
|
int radius = static_cast<const SHAPE_CIRCLE*>( shape.get() )->GetRadius();
|
|
|
|
temp_item.SetShape( SHAPE_T::CIRCLE );
|
|
temp_item.SetFilled( false );
|
|
temp_item.SetStart( start );
|
|
temp_item.SetEnd( VECTOR2I( start.x + radius, start.y ) );
|
|
|
|
PlotShape( &temp_item );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotPcbTarget( const PCB_TARGET* aMire )
|
|
{
|
|
int dx1, dx2, dy1, dy2, radius;
|
|
|
|
if( !m_layerMask[aMire->GetLayer()] )
|
|
return;
|
|
|
|
m_plotter->SetColor( getColor( aMire->GetLayer() ) );
|
|
|
|
PCB_SHAPE temp_item;
|
|
|
|
temp_item.SetShape( SHAPE_T::CIRCLE );
|
|
temp_item.SetFilled( false );
|
|
temp_item.SetStroke( STROKE_PARAMS( aMire->GetWidth(), LINE_STYLE::SOLID ) );
|
|
temp_item.SetLayer( aMire->GetLayer() );
|
|
temp_item.SetStart( aMire->GetPosition() );
|
|
radius = aMire->GetSize() / 3;
|
|
|
|
if( aMire->GetShape() ) // temp_item X
|
|
radius = aMire->GetSize() / 2;
|
|
|
|
// Draw the circle
|
|
temp_item.SetEnd( VECTOR2I( temp_item.GetStart().x + radius, temp_item.GetStart().y ) );
|
|
|
|
PlotShape( &temp_item );
|
|
|
|
temp_item.SetShape( SHAPE_T::SEGMENT );
|
|
|
|
radius = aMire->GetSize() / 2;
|
|
dx1 = radius;
|
|
dy1 = 0;
|
|
dx2 = 0;
|
|
dy2 = radius;
|
|
|
|
if( aMire->GetShape() ) // Shape X
|
|
{
|
|
dx1 = dy1 = radius;
|
|
dx2 = dx1;
|
|
dy2 = -dy1;
|
|
}
|
|
|
|
VECTOR2I mirePos( aMire->GetPosition() );
|
|
|
|
// Draw the X or + temp_item:
|
|
temp_item.SetStart( VECTOR2I( mirePos.x - dx1, mirePos.y - dy1 ) );
|
|
temp_item.SetEnd( VECTOR2I( mirePos.x + dx1, mirePos.y + dy1 ) );
|
|
PlotShape( &temp_item );
|
|
|
|
temp_item.SetStart( VECTOR2I( mirePos.x - dx2, mirePos.y - dy2 ) );
|
|
temp_item.SetEnd( VECTOR2I( mirePos.x + dx2, mirePos.y + dy2 ) );
|
|
PlotShape( &temp_item );
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotFootprintGraphicItems( const FOOTPRINT* aFootprint )
|
|
{
|
|
for( const BOARD_ITEM* item : aFootprint->GraphicalItems() )
|
|
{
|
|
PCB_LAYER_ID itemLayer = item->GetLayer();
|
|
|
|
if( aFootprint->GetPrivateLayers().test( itemLayer ) )
|
|
continue;
|
|
|
|
if( aFootprint->IsDNP() && hideDNPItems( itemLayer ) )
|
|
continue;
|
|
|
|
if( !( m_layerMask & item->GetLayerSet() ).any() )
|
|
continue;
|
|
|
|
switch( item->Type() )
|
|
{
|
|
case PCB_SHAPE_T:
|
|
PlotShape( static_cast<const PCB_SHAPE*>( item ) );
|
|
break;
|
|
|
|
case PCB_TEXTBOX_T:
|
|
{
|
|
const PCB_TEXTBOX* textbox = static_cast<const PCB_TEXTBOX*>( item );
|
|
|
|
m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );
|
|
|
|
PlotText( textbox, textbox->GetLayer(), textbox->IsKnockout(),
|
|
textbox->GetFontMetrics() );
|
|
|
|
if( textbox->IsBorderEnabled() )
|
|
PlotShape( textbox );
|
|
|
|
m_plotter->SetTextMode( GetTextMode() );
|
|
break;
|
|
}
|
|
|
|
case PCB_DIM_ALIGNED_T:
|
|
case PCB_DIM_CENTER_T:
|
|
case PCB_DIM_RADIAL_T:
|
|
case PCB_DIM_ORTHOGONAL_T:
|
|
case PCB_DIM_LEADER_T:
|
|
PlotDimension( static_cast<const PCB_DIMENSION_BASE*>( item ) );
|
|
break;
|
|
|
|
case PCB_TEXT_T:
|
|
// Plotted in PlotFootprintTextItems()
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( item->GetClass() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotText( const EDA_TEXT* aText, PCB_LAYER_ID aLayer, bool aIsKnockout,
|
|
const KIFONT::METRICS& aFontMetrics, bool aStrikeout )
|
|
{
|
|
KIFONT::FONT* font = aText->GetFont();
|
|
|
|
if( !font )
|
|
{
|
|
wxString defaultFontName; // empty string is the KiCad stroke font
|
|
|
|
if( m_plotter->RenderSettings() )
|
|
defaultFontName = m_plotter->RenderSettings()->GetDefaultFont();
|
|
|
|
font = KIFONT::FONT::GetFont( defaultFontName, aText->IsBold(), aText->IsItalic() );
|
|
}
|
|
|
|
wxString shownText( aText->GetShownText( true ) );
|
|
|
|
if( shownText.IsEmpty() )
|
|
return;
|
|
|
|
if( !m_layerMask[aLayer] )
|
|
return;
|
|
|
|
GBR_METADATA gbr_metadata;
|
|
|
|
if( IsCopperLayer( aLayer ) )
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
|
|
|
|
COLOR4D color = getColor( aLayer );
|
|
m_plotter->SetColor( color );
|
|
|
|
VECTOR2I pos = aText->GetTextPos();
|
|
|
|
TEXT_ATTRIBUTES attrs = aText->GetAttributes();
|
|
attrs.m_StrokeWidth = aText->GetEffectiveTextPenWidth();
|
|
attrs.m_Angle = aText->GetDrawRotation();
|
|
attrs.m_Multiline = false;
|
|
|
|
m_plotter->SetCurrentLineWidth( attrs.m_StrokeWidth );
|
|
|
|
auto strikeoutText =
|
|
[&]( const PCB_TEXT* text )
|
|
{
|
|
SHAPE_POLY_SET textPoly;
|
|
|
|
text->TransformTextToPolySet( textPoly, 0, ARC_LOW_DEF, ERROR_INSIDE );
|
|
textPoly.Rotate( -text->GetDrawRotation(), text->GetDrawPos() );
|
|
|
|
BOX2I rect = textPoly.BBox();
|
|
VECTOR2I start( rect.GetLeft() - attrs.m_StrokeWidth,
|
|
( rect.GetTop() + rect.GetBottom() ) / 2 );
|
|
VECTOR2I end( rect.GetRight() + attrs.m_StrokeWidth,
|
|
( rect.GetTop() + rect.GetBottom() ) / 2 );
|
|
|
|
RotatePoint( start, text->GetDrawPos(), text->GetDrawRotation() );
|
|
RotatePoint( end, text->GetDrawPos(), text->GetDrawRotation() );
|
|
|
|
m_plotter->ThickSegment( start, end, attrs.m_StrokeWidth, FILLED, nullptr );
|
|
};
|
|
|
|
if( aIsKnockout )
|
|
{
|
|
SHAPE_POLY_SET finalPoly;
|
|
|
|
if( const PCB_TEXT* text = dynamic_cast<const PCB_TEXT*>( aText) )
|
|
{
|
|
text->TransformTextToPolySet( finalPoly, 0, m_board->GetDesignSettings().m_MaxError,
|
|
ERROR_INSIDE );
|
|
}
|
|
else if( const PCB_TEXTBOX* textbox = dynamic_cast<const PCB_TEXTBOX*>( aText ) )
|
|
{
|
|
textbox->TransformTextToPolySet( finalPoly, 0, m_board->GetDesignSettings().m_MaxError,
|
|
ERROR_INSIDE );
|
|
}
|
|
|
|
finalPoly.Fracture();
|
|
|
|
for( int ii = 0; ii < finalPoly.OutlineCount(); ++ii )
|
|
m_plotter->PlotPoly( finalPoly.Outline( ii ), FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
if( font->IsOutline() && !m_board->GetEmbeddedFiles()->GetAreFontsEmbedded() )
|
|
{
|
|
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
|
|
|
|
CALLBACK_GAL callback_gal( empty_opts,
|
|
// Stroke callback
|
|
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
|
|
{
|
|
m_plotter->ThickSegment( aPt1, aPt2, attrs.m_StrokeWidth, FILLED, nullptr );
|
|
},
|
|
// Polygon callback
|
|
[&]( const SHAPE_LINE_CHAIN& aPoly )
|
|
{
|
|
m_plotter->PlotPoly( aPoly, FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
|
|
} );
|
|
|
|
callback_gal.DrawGlyphs( *aText->GetRenderCache( font, shownText ) );
|
|
}
|
|
else if( aText->IsMultilineAllowed() )
|
|
{
|
|
std::vector<VECTOR2I> positions;
|
|
wxArrayString strings_list;
|
|
wxStringSplit( shownText, strings_list, '\n' );
|
|
positions.reserve( strings_list.Count() );
|
|
|
|
aText->GetLinePositions( positions, (int) strings_list.Count() );
|
|
|
|
for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
|
|
{
|
|
wxString& txt = strings_list.Item( ii );
|
|
m_plotter->PlotText( positions[ii], color, txt, attrs, font, aFontMetrics,
|
|
&gbr_metadata );
|
|
}
|
|
|
|
if( aStrikeout && strings_list.Count() == 1 )
|
|
strikeoutText( static_cast<const PCB_TEXT*>( aText ) );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->PlotText( pos, color, shownText, attrs, font, aFontMetrics, &gbr_metadata );
|
|
|
|
if( aStrikeout )
|
|
strikeoutText( static_cast<const PCB_TEXT*>( aText ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotZone( const ZONE* aZone, PCB_LAYER_ID aLayer,
|
|
const SHAPE_POLY_SET& aPolysList )
|
|
{
|
|
if( aPolysList.IsEmpty() )
|
|
return;
|
|
|
|
GBR_METADATA gbr_metadata;
|
|
|
|
if( aZone->IsOnCopperLayer() )
|
|
{
|
|
gbr_metadata.SetNetName( aZone->GetNetname() );
|
|
gbr_metadata.SetCopper( true );
|
|
|
|
// Zones with no net name can exist.
|
|
// they are not used to connect items, so the aperture attribute cannot
|
|
// be set as conductor
|
|
if( aZone->GetNetname().IsEmpty() )
|
|
{
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
|
|
}
|
|
else
|
|
{
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
|
|
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
|
|
}
|
|
}
|
|
|
|
m_plotter->SetColor( getColor( aLayer ) );
|
|
|
|
m_plotter->StartBlock( nullptr ); // Clean current object attributes
|
|
|
|
/*
|
|
* In non filled mode the outline is plotted, but not the filling items
|
|
*/
|
|
|
|
for( int idx = 0; idx < aPolysList.OutlineCount(); ++idx )
|
|
{
|
|
const SHAPE_LINE_CHAIN& outline = aPolysList.Outline( idx );
|
|
|
|
// Plot the current filled area (as region for Gerber plotter to manage attributes)
|
|
if( GetPlotMode() == FILLED )
|
|
{
|
|
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
|
|
{
|
|
static_cast<GERBER_PLOTTER*>( m_plotter )->PlotGerberRegion( outline,
|
|
&gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->PlotPoly( outline, FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_plotter->SetCurrentLineWidth( -1 );
|
|
}
|
|
}
|
|
|
|
m_plotter->EndBlock( nullptr ); // Clear object attributes
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape )
|
|
{
|
|
if( !( m_layerMask & aShape->GetLayerSet() ).any() )
|
|
return;
|
|
|
|
OUTLINE_MODE plotMode = GetPlotMode();
|
|
int thickness = aShape->GetWidth();
|
|
int margin = thickness; // unclamped thickness (can be negative)
|
|
LINE_STYLE lineStyle = aShape->GetStroke().GetLineStyle();
|
|
bool onCopperLayer = ( LSET::AllCuMask() & m_layerMask ).any();
|
|
bool onSolderMaskLayer = ( LSET( { F_Mask, B_Mask } ) & m_layerMask ).any();
|
|
|
|
if( onSolderMaskLayer
|
|
&& aShape->HasSolderMask()
|
|
&& IsExternalCopperLayer( aShape->GetLayer() ) )
|
|
{
|
|
margin += 2 * aShape->GetSolderMaskExpansion();
|
|
thickness = std::max( margin, 0 );
|
|
}
|
|
|
|
m_plotter->SetColor( getColor( aShape->GetLayer() ) );
|
|
|
|
const FOOTPRINT* parentFP = aShape->GetParentFootprint();
|
|
GBR_METADATA gbr_metadata;
|
|
|
|
if( parentFP )
|
|
{
|
|
gbr_metadata.SetCmpReference( parentFP->GetReference() );
|
|
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
|
|
}
|
|
|
|
if( parentFP && parentFP->IsDNP() && GetSketchDNPFPsOnFabLayers() )
|
|
{
|
|
if( aShape->GetLayer() == F_Fab || aShape->GetLayer() == B_Fab )
|
|
plotMode = SKETCH;
|
|
}
|
|
|
|
if( aShape->GetLayer() == Edge_Cuts )
|
|
{
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT );
|
|
}
|
|
else if( onCopperLayer )
|
|
{
|
|
if( parentFP )
|
|
{
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_ETCHEDCMP );
|
|
gbr_metadata.SetCopper( true );
|
|
}
|
|
else if( aShape->GetNetCode() > 0 )
|
|
{
|
|
gbr_metadata.SetCopper( true );
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
|
|
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
|
|
gbr_metadata.SetNetName( aShape->GetNetname() );
|
|
}
|
|
else
|
|
{
|
|
// Graphic items (PCB_SHAPE, TEXT) having no net have the NonConductor attribute
|
|
// Graphic items having a net have the Conductor attribute, but are not (yet?)
|
|
// supported in Pcbnew
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
|
|
}
|
|
}
|
|
|
|
if( lineStyle <= LINE_STYLE::FIRST_TYPE )
|
|
{
|
|
switch( aShape->GetShape() )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
m_plotter->ThickSegment( aShape->GetStart(), aShape->GetEnd(), thickness, plotMode,
|
|
&gbr_metadata );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
if( aShape->IsSolidFill() )
|
|
{
|
|
int diameter = aShape->GetRadius() * 2 + thickness;
|
|
|
|
if( margin < 0 )
|
|
{
|
|
diameter += margin;
|
|
diameter = std::max( diameter, 0 );
|
|
}
|
|
|
|
m_plotter->FilledCircle( aShape->GetStart(), diameter, plotMode, &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->ThickCircle( aShape->GetStart(), aShape->GetRadius() * 2, thickness,
|
|
plotMode, &gbr_metadata );
|
|
}
|
|
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
{
|
|
// when startAngle == endAngle ThickArc() doesn't know whether it's 0 deg and 360 deg
|
|
// but it is a circle
|
|
if( std::abs( aShape->GetArcAngle().AsDegrees() ) == 360.0 )
|
|
{
|
|
m_plotter->ThickCircle( aShape->GetCenter(), aShape->GetRadius() * 2, thickness,
|
|
plotMode, &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->ThickArc( *aShape, plotMode, &gbr_metadata, thickness );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SHAPE_T::BEZIER:
|
|
m_plotter->BezierCurve( aShape->GetStart(), aShape->GetBezierC1(),
|
|
aShape->GetBezierC2(), aShape->GetEnd(), 0, thickness );
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
if( aShape->IsPolyShapeValid() )
|
|
{
|
|
if( plotMode == SKETCH )
|
|
{
|
|
for( auto it = aShape->GetPolyShape().CIterateSegments( 0 ); it; it++ )
|
|
{
|
|
const SEG& seg = it.Get();
|
|
m_plotter->ThickSegment( seg.A, seg.B, thickness, SKETCH, &gbr_metadata );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_plotter->SetCurrentLineWidth( thickness, &gbr_metadata );
|
|
|
|
// Draw the polygon: only one polygon is expected
|
|
// However we provide a multi polygon shape drawing
|
|
// ( for the future or to show a non expected shape )
|
|
// This must be simplified and fractured to prevent overlapping polygons
|
|
// from generating invalid Gerber files
|
|
SHAPE_POLY_SET tmpPoly = aShape->GetPolyShape().CloneDropTriangulation();
|
|
tmpPoly.Fracture();
|
|
|
|
if( margin < 0 )
|
|
{
|
|
tmpPoly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS,
|
|
m_board->GetDesignSettings().m_MaxError );
|
|
}
|
|
|
|
FILL_T fill = aShape->IsSolidFill() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL;
|
|
|
|
for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj )
|
|
{
|
|
SHAPE_LINE_CHAIN& poly = tmpPoly.Outline( jj );
|
|
|
|
// Ensure the polygon is closed:
|
|
poly.SetClosed( true );
|
|
|
|
// Plot the current filled area
|
|
// (as region for Gerber plotter to manage attributes)
|
|
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
|
|
{
|
|
GERBER_PLOTTER* gbr_plotter = static_cast<GERBER_PLOTTER*>( m_plotter );
|
|
gbr_plotter->PlotPolyAsRegion( poly, fill, thickness, &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->PlotPoly( poly, fill, thickness, &gbr_metadata );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case SHAPE_T::RECTANGLE:
|
|
{
|
|
std::vector<VECTOR2I> pts = aShape->GetRectCorners();
|
|
|
|
if( plotMode == SKETCH )
|
|
{
|
|
m_plotter->ThickSegment( pts[0], pts[1], thickness, SKETCH, &gbr_metadata );
|
|
m_plotter->ThickSegment( pts[1], pts[2], thickness, SKETCH, &gbr_metadata );
|
|
m_plotter->ThickSegment( pts[2], pts[3], thickness, SKETCH, &gbr_metadata );
|
|
m_plotter->ThickSegment( pts[3], pts[0], thickness, SKETCH, &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
SHAPE_POLY_SET poly;
|
|
poly.NewOutline();
|
|
|
|
for( const VECTOR2I& pt : pts )
|
|
poly.Append( pt );
|
|
|
|
if( margin < 0 )
|
|
{
|
|
poly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS,
|
|
m_board->GetDesignSettings().m_MaxError );
|
|
}
|
|
|
|
FILL_T fill_mode = aShape->IsSolidFill() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL;
|
|
|
|
if( poly.OutlineCount() > 0 )
|
|
{
|
|
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
|
|
{
|
|
GERBER_PLOTTER* gbr_plotter = static_cast<GERBER_PLOTTER*>( m_plotter );
|
|
gbr_plotter->PlotPolyAsRegion( poly.COutline( 0 ), fill_mode, thickness,
|
|
&gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->PlotPoly( poly.COutline( 0 ), fill_mode, thickness,
|
|
&gbr_metadata );
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( aShape->SHAPE_T_asString() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::vector<SHAPE*> shapes = aShape->MakeEffectiveShapes( true );
|
|
|
|
for( SHAPE* shape : shapes )
|
|
{
|
|
STROKE_PARAMS::Stroke( shape, lineStyle, aShape->GetWidth(),
|
|
m_plotter->RenderSettings(),
|
|
[&]( const VECTOR2I& a, const VECTOR2I& b )
|
|
{
|
|
m_plotter->ThickSegment( a, b, thickness, plotMode,
|
|
&gbr_metadata );
|
|
} );
|
|
}
|
|
|
|
for( SHAPE* shape : shapes )
|
|
delete shape;
|
|
}
|
|
|
|
if( aShape->IsHatchedFill() )
|
|
{
|
|
for( int ii = 0; ii < aShape->GetHatching().OutlineCount(); ++ii )
|
|
{
|
|
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
|
|
{
|
|
GERBER_PLOTTER* gbr_plotter = static_cast<GERBER_PLOTTER*>( m_plotter );
|
|
gbr_plotter->PlotPolyAsRegion( aShape->GetHatching().Outline( ii ),
|
|
FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->PlotPoly( aShape->GetHatching().Outline( ii ), FILL_T::FILLED_SHAPE,
|
|
0, &gbr_metadata );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotTableBorders( const PCB_TABLE* aTable )
|
|
{
|
|
if( !m_layerMask[aTable->GetLayer()] )
|
|
return;
|
|
|
|
GBR_METADATA gbr_metadata;
|
|
|
|
if( const FOOTPRINT* parentFP = aTable->GetParentFootprint() )
|
|
{
|
|
gbr_metadata.SetCmpReference( parentFP->GetReference() );
|
|
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
|
|
}
|
|
|
|
aTable->DrawBorders(
|
|
[&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
|
|
{
|
|
int lineWidth = stroke.GetWidth();
|
|
LINE_STYLE lineStyle = stroke.GetLineStyle();
|
|
|
|
if( lineStyle <= LINE_STYLE::FIRST_TYPE )
|
|
{
|
|
m_plotter->ThickSegment( ptA, ptB, lineWidth, GetPlotMode(), &gbr_metadata );
|
|
}
|
|
else
|
|
{
|
|
SHAPE_SEGMENT seg( ptA, ptB );
|
|
|
|
STROKE_PARAMS::Stroke( &seg, lineStyle, lineWidth, m_plotter->RenderSettings(),
|
|
[&]( const VECTOR2I& a, const VECTOR2I& b )
|
|
{
|
|
m_plotter->ThickSegment( a, b, lineWidth, GetPlotMode(),
|
|
&gbr_metadata );
|
|
} );
|
|
}
|
|
} );
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::plotOneDrillMark( PAD_DRILL_SHAPE aDrillShape, const VECTOR2I& aDrillPos,
|
|
const VECTOR2I& aDrillSize, const VECTOR2I& aPadSize,
|
|
const EDA_ANGLE& aOrientation, int aSmallDrill )
|
|
{
|
|
VECTOR2I drillSize = aDrillSize;
|
|
|
|
// Small drill marks have no significance when applied to slots
|
|
if( aSmallDrill && aDrillShape == PAD_DRILL_SHAPE::CIRCLE )
|
|
drillSize.x = std::min( aSmallDrill, drillSize.x );
|
|
|
|
// Round holes only have x diameter, slots have both
|
|
drillSize.x -= getFineWidthAdj();
|
|
drillSize.x = std::clamp( drillSize.x, 1, aPadSize.x - 1 );
|
|
|
|
if( aDrillShape == PAD_DRILL_SHAPE::OBLONG )
|
|
{
|
|
drillSize.y -= getFineWidthAdj();
|
|
drillSize.y = std::clamp( drillSize.y, 1, aPadSize.y - 1 );
|
|
|
|
m_plotter->FlashPadOval( aDrillPos, drillSize, aOrientation, GetPlotMode(), nullptr );
|
|
}
|
|
else
|
|
{
|
|
m_plotter->FlashPadCircle( aDrillPos, drillSize.x, GetPlotMode(), nullptr );
|
|
}
|
|
}
|
|
|
|
|
|
void BRDITEMS_PLOTTER::PlotDrillMarks()
|
|
{
|
|
bool onCopperLayer = ( LSET::AllCuMask() & m_layerMask ).any();
|
|
int smallDrill = 0;
|
|
|
|
if( GetDrillMarksType() == DRILL_MARKS::SMALL_DRILL_SHAPE )
|
|
smallDrill = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize );
|
|
|
|
/* In the filled trace mode drill marks are drawn white-on-black to knock-out the underlying
|
|
pad. This works only for drivers supporting color change, obviously... it means that:
|
|
- PS, SVG and PDF output is correct (i.e. you have a 'donut' pad)
|
|
- In HPGL you can't see them
|
|
- In gerbers you can't see them, too. This is arguably the right thing to do since having
|
|
drill marks and high speed drill stations is a sure recipe for broken tools and angry
|
|
manufacturers. If you *really* want them you could start a layer with negative polarity
|
|
to knock-out the film.
|
|
- In DXF they go into the 'WHITE' layer. This could be useful.
|
|
*/
|
|
if( GetPlotMode() == FILLED && onCopperLayer )
|
|
m_plotter->SetColor( WHITE );
|
|
|
|
for( PCB_TRACK* track : m_board->Tracks() )
|
|
{
|
|
if( track->Type() == PCB_VIA_T )
|
|
{
|
|
const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
|
|
|
|
// Via are not always on all layers
|
|
if( ( via->GetLayerSet() & m_layerMask ).none() )
|
|
continue;
|
|
|
|
plotOneDrillMark( PAD_DRILL_SHAPE::CIRCLE, via->GetStart(),
|
|
VECTOR2I( via->GetDrillValue(), 0 ),
|
|
VECTOR2I( via->GetWidth( PADSTACK::ALL_LAYERS ), 0 ),
|
|
ANGLE_0, smallDrill );
|
|
}
|
|
}
|
|
|
|
for( FOOTPRINT* footprint : m_board->Footprints() )
|
|
{
|
|
for( PAD* pad : footprint->Pads() )
|
|
{
|
|
if( pad->GetDrillSize().x == 0 )
|
|
continue;
|
|
|
|
plotOneDrillMark( pad->GetDrillShape(), pad->GetPosition(), pad->GetDrillSize(),
|
|
pad->GetSize( PADSTACK::ALL_LAYERS ), pad->GetOrientation(), smallDrill );
|
|
}
|
|
}
|
|
|
|
if( GetPlotMode() == FILLED && onCopperLayer )
|
|
m_plotter->SetColor( BLACK );
|
|
}
|