/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2022-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 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * @file GERBER_plotter.cpp * @brief specialized plotter for GERBER files format */ #include #include #include #include // for KiROUND #include #include #include #include #include #include #include // if GBR_USE_MACROS is defined, pads having a shape that is not a Gerber primitive // will use a macro when possible // Old code will be removed only after many tests // // Note also: setting m_gerberDisableApertMacros to true disable all aperture macros // in Gerber files // #define GBR_USE_MACROS_FOR_CHAMFERED_ROUND_RECT #define GBR_USE_MACROS_FOR_CHAMFERED_RECT #define GBR_USE_MACROS_FOR_ROUNDRECT #define GBR_USE_MACROS_FOR_TRAPEZOID #define GBR_USE_MACROS_FOR_ROTATED_OVAL #define GBR_USE_MACROS_FOR_ROTATED_RECT #define GBR_USE_MACROS_FOR_CUSTOM_PAD // max count of corners to create a aperture macro for a custom shape. // provided just in case a aperture macro type free polygon creates issues // when the number of corners is too high. // (1 corner = up to 24 chars) // Gerber doc say max corners 5000. We use a slightly smaller value. // if a custom shape needs more than GBR_MACRO_FOR_CUSTOM_PAD_MAX_CORNER_COUNT, it // will be plot using a region. #define GBR_MACRO_FOR_CUSTOM_PAD_MAX_CORNER_COUNT 4990 #define AM_FREEPOLY_BASENAME "FreePoly" // A helper function to compare 2 polygons: polygons are similar if they have the same // number of vertices and each vertex coordinate are similar, i.e. if the difference // between coordinates is small ( <= margin to accept rounding issues coming from polygon // geometric transforms like rotation static bool polyCompare( const std::vector& aPolygon, const std::vector& aTestPolygon ) { // fast test: polygon sizes must be the same: if( aTestPolygon.size() != aPolygon.size() ) return false; const int margin = 2; for( size_t jj = 0; jj < aPolygon.size(); jj++ ) { if( std::abs( aPolygon[jj].x - aTestPolygon[jj].x ) > margin || std::abs( aPolygon[jj].y - aTestPolygon[jj].y ) > margin ) return false; } return true; } GERBER_PLOTTER::GERBER_PLOTTER() { workFile = nullptr; finalFile = nullptr; m_currentApertureIdx = -1; m_apertureAttribute = 0; // number of digits after the point (number of digits of the mantissa // Be careful: the Gerber coordinates are stored in an integer // so 6 digits (inches) or 5 digits (mm) is a good value // To avoid overflow, 7 digits (inches) or 6 digits is a max. // with lower values than 6 digits (inches) or 5 digits (mm), // Creating self-intersecting polygons from non-intersecting polygons // happen easily. m_gerberUnitInch = false; m_gerberUnitFmt = 6; m_useX2format = true; m_useNetAttributes = true; m_gerberDisableApertMacros = false; m_hasApertureRoundRect = false; // true is at least one round rect aperture is in use m_hasApertureRotOval = false; // true is at least one oval rotated aperture is in use m_hasApertureRotRect = false; // true is at least one rect. rotated aperture is in use m_hasApertureOutline4P = false; // true is at least one rotated rect or trapezoid pad // aperture is in use m_hasApertureChamferedRect = false; // true is at least one chamfered rect // (no rounded corner) is in use } void GERBER_PLOTTER::SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil, double aScale, bool aMirror ) { wxASSERT( aMirror == false ); m_plotMirror = false; m_plotOffset = aOffset; wxASSERT( aScale == 1 ); // aScale parameter is not used in Gerber m_plotScale = 1; // Plot scale is *always* 1.0 m_IUsPerDecimil = aIusPerDecimil; // gives now a default value to iuPerDeviceUnit (because the units of the caller is now known) // which could be modified later by calling SetGerberCoordinatesFormat() m_iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 ); // We don't handle the filmbox, and it's more useful to keep the // origin at the origin m_paperSize.x = 0; m_paperSize.y = 0; } void GERBER_PLOTTER::SetGerberCoordinatesFormat( int aResolution, bool aUseInches ) { m_gerberUnitInch = aUseInches; m_gerberUnitFmt = aResolution; m_iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 ); if( ! m_gerberUnitInch ) m_iuPerDeviceUnit *= 25.4; // gerber output in mm } void GERBER_PLOTTER::emitDcode( const VECTOR2D& pt, int dcode ) { fprintf( m_outputFile, "X%dY%dD%02d*\n", KiROUND( pt.x ), KiROUND( pt.y ), dcode ); } void GERBER_PLOTTER::ClearAllAttributes() { // Remove all attributes from object attributes dictionary (TO. and TA commands) if( m_useX2format ) fputs( "%TD*%\n", m_outputFile ); else fputs( "G04 #@! TD*\n", m_outputFile ); m_objectAttributesDictionary.clear(); } void GERBER_PLOTTER::clearNetAttribute() { // disable a Gerber net attribute (exists only in X2 with net attributes mode). if( m_objectAttributesDictionary.empty() ) // No net attribute or not X2 mode return; // Remove all net attributes from object attributes dictionary if( m_useX2format ) fputs( "%TD*%\n", m_outputFile ); else fputs( "G04 #@! TD*\n", m_outputFile ); m_objectAttributesDictionary.clear(); } void GERBER_PLOTTER::StartBlock( void* aData ) { // Currently, it is the same as EndBlock(): clear all aperture net attributes EndBlock( aData ); } void GERBER_PLOTTER::EndBlock( void* aData ) { // Remove all net attributes from object attributes dictionary clearNetAttribute(); } void GERBER_PLOTTER::formatNetAttribute( GBR_NETLIST_METADATA* aData ) { // print a Gerber net attribute record. // it is added to the object attributes dictionary // On file, only modified or new attributes are printed. if( aData == nullptr ) return; if( !m_useNetAttributes ) return; bool useX1StructuredComment = !m_useX2format; bool clearDict; std::string short_attribute_string; if( !FormatNetAttribute( short_attribute_string, m_objectAttributesDictionary, aData, clearDict, useX1StructuredComment ) ) return; if( clearDict ) clearNetAttribute(); if( !short_attribute_string.empty() ) fputs( short_attribute_string.c_str(), m_outputFile ); if( m_useX2format && !aData->m_ExtraData.IsEmpty() ) { std::string extra_data = TO_UTF8( aData->m_ExtraData ); fputs( extra_data.c_str(), m_outputFile ); } } bool GERBER_PLOTTER::StartPlot( const wxString& aPageNumber ) { m_hasApertureRoundRect = false; // true is at least one round rect aperture is in use m_hasApertureRotOval = false; // true is at least one oval rotated aperture is in use m_hasApertureRotRect = false; // true is at least one rect. rotated aperture is in use m_hasApertureOutline4P = false; // true is at least one rotated rect/trapezoid aperture // is in use m_hasApertureChamferedRect = false; // true is at least one chamfered rect is in use m_am_freepoly_list.ClearList(); wxASSERT( m_outputFile ); finalFile = m_outputFile; // the actual gerber file will be created later // Create a temp file in system temp to avoid potential network share buffer issues for // the final read and save. m_workFilename = wxFileName::CreateTempFileName( "" ); workFile = wxFopen( m_workFilename, wxT( "wt" ) ); m_outputFile = workFile; wxASSERT( m_outputFile ); if( m_outputFile == nullptr ) return false; for( unsigned ii = 0; ii < m_headerExtraLines.GetCount(); ii++ ) { if( ! m_headerExtraLines[ii].IsEmpty() ) fprintf( m_outputFile, "%s\n", TO_UTF8( m_headerExtraLines[ii] ) ); } // Set coordinate format to 3.6 or 4.5 absolute, leading zero omitted // the number of digits for the integer part of coordinates is needed // in gerber format, but is not very important when omitting leading zeros // It is fixed here to 3 (inch) or 4 (mm), but is not actually used int leadingDigitCount = m_gerberUnitInch ? 3 : 4; fprintf( m_outputFile, "%%FSLAX%d%dY%d%d*%%\n", leadingDigitCount, m_gerberUnitFmt, leadingDigitCount, m_gerberUnitFmt ); fprintf( m_outputFile, "G04 Gerber Fmt %d.%d, Leading zero omitted, Abs format (unit %s)*\n", leadingDigitCount, m_gerberUnitFmt, m_gerberUnitInch ? "inch" : "mm" ); wxString Title = m_creator + wxT( " " ) + GetBuildVersion(); // In gerber files, ASCII7 chars only are allowed. // So use a ISO date format (using a space as separator between date and time), // not a localized date format wxDateTime date = wxDateTime::Now(); fprintf( m_outputFile, "G04 Created by KiCad (%s) date %s*\n", TO_UTF8( Title ), TO_UTF8( date.FormatISOCombined( ' ') ) ); /* Mass parameter: unit = INCHES/MM */ if( m_gerberUnitInch ) fputs( "%MOIN*%\n", m_outputFile ); else fputs( "%MOMM*%\n", m_outputFile ); // Be sure the usual dark polarity is selected: fputs( "%LPD*%\n", m_outputFile ); // Set initial interpolation mode: always G01 (linear): fputs( "G01*\n", m_outputFile ); // Add aperture list start point fputs( "G04 APERTURE LIST*\n", m_outputFile ); // Give a minimal value to the default pen size, used to plot items in sketch mode if( m_renderSettings ) { const int pen_min = 0.1 * m_IUsPerDecimil * 10000 / 25.4; // for min width = 0.1 mm m_renderSettings->SetDefaultPenWidth( std::max( m_renderSettings->GetDefaultPenWidth(), pen_min ) ); } return true; } bool GERBER_PLOTTER::EndPlot() { char line[1024]; wxASSERT( m_outputFile ); /* Outfile is actually a temporary file i.e. workFile */ fputs( "M02*\n", m_outputFile ); fflush( m_outputFile ); fclose( workFile ); workFile = wxFopen( m_workFilename, wxT( "rt" )); wxASSERT( workFile ); m_outputFile = finalFile; // Placement of apertures in RS274X while( fgets( line, 1024, workFile ) ) { fputs( line, m_outputFile ); char* substr = strtok( line, "\n\r" ); if( substr && strcmp( substr, "G04 APERTURE LIST*" ) == 0 ) { // Add aperture list macro: if( m_hasApertureRoundRect || m_hasApertureRotOval || m_hasApertureOutline4P || m_hasApertureRotRect || m_hasApertureChamferedRect || m_am_freepoly_list.AmCount() ) { fputs( "G04 Aperture macros list*\n", m_outputFile ); if( m_hasApertureRoundRect ) fputs( APER_MACRO_ROUNDRECT_HEADER, m_outputFile ); if( m_hasApertureRotOval ) fputs( APER_MACRO_SHAPE_OVAL_HEADER, m_outputFile ); if( m_hasApertureRotRect ) fputs( APER_MACRO_ROT_RECT_HEADER, m_outputFile ); if( m_hasApertureOutline4P ) fputs( APER_MACRO_OUTLINE4P_HEADER, m_outputFile ); if( m_hasApertureChamferedRect ) { fputs( APER_MACRO_OUTLINE5P_HEADER, m_outputFile ); fputs( APER_MACRO_OUTLINE6P_HEADER, m_outputFile ); fputs( APER_MACRO_OUTLINE7P_HEADER, m_outputFile ); fputs( APER_MACRO_OUTLINE8P_HEADER, m_outputFile ); } if( m_am_freepoly_list.AmCount() ) { // aperture sizes are in inch or mm, regardless the // coordinates format double fscale = 0.0001 * m_plotScale / m_IUsPerDecimil; // inches if(! m_gerberUnitInch ) fscale *= 25.4; // size in mm m_am_freepoly_list.Format( m_outputFile, fscale ); } fputs( "G04 Aperture macros list end*\n", m_outputFile ); } writeApertureList(); fputs( "G04 APERTURE END LIST*\n", m_outputFile ); } } fclose( workFile ); fclose( finalFile ); ::wxRemoveFile( m_workFilename ); m_outputFile = nullptr; return true; } void GERBER_PLOTTER::SetCurrentLineWidth( int aWidth, void* aData ) { if( aWidth == DO_NOT_SET_LINE_WIDTH ) return; else if( aWidth == USE_DEFAULT_LINE_WIDTH ) aWidth = m_renderSettings->GetDefaultPenWidth(); wxASSERT_MSG( aWidth >= 0, "Plotter called to set negative pen width" ); GBR_METADATA* gbr_metadata = static_cast( aData ); int aperture_attribute = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( VECTOR2I( aWidth, aWidth ), 0, ANGLE_0, APERTURE::AT_PLOTTING, aperture_attribute ); m_currentPenWidth = aWidth; } int GERBER_PLOTTER::GetOrCreateAperture( const VECTOR2I& aSize, int aRadius, const EDA_ANGLE& aRotation, APERTURE::APERTURE_TYPE aType, int aApertureAttribute ) { int last_D_code = 9; // Search an existing aperture for( int idx = 0; idx < (int)m_apertures.size(); ++idx ) { APERTURE* tool = &m_apertures[idx]; last_D_code = tool->m_DCode; if( (tool->m_Type == aType) && (tool->m_Size == aSize) && (tool->m_Radius == aRadius) && (tool->m_Rotation == aRotation) && (tool->m_ApertureAttribute == aApertureAttribute) ) return idx; } // Allocate a new aperture APERTURE new_tool; new_tool.m_Size = aSize; new_tool.m_Type = aType; new_tool.m_Radius = aRadius; new_tool.m_Rotation = aRotation; new_tool.m_DCode = last_D_code + 1; new_tool.m_ApertureAttribute = aApertureAttribute; m_apertures.push_back( new_tool ); return m_apertures.size() - 1; } int GERBER_PLOTTER::GetOrCreateAperture( const std::vector& aCorners, const EDA_ANGLE& aRotation, APERTURE::APERTURE_TYPE aType, int aApertureAttribute ) { int last_D_code = 9; // For APERTURE::AM_FREE_POLYGON aperture macros, we need to create the macro // on the fly, because due to the fact the vertex count is not a constant we // cannot create a static definition. if( APERTURE::AM_FREE_POLYGON == aType ) { int idx = m_am_freepoly_list.FindAm( aCorners ); if( idx < 0 ) m_am_freepoly_list.Append( aCorners ); } // Search an existing aperture for( int idx = 0; idx < (int)m_apertures.size(); ++idx ) { APERTURE* tool = &m_apertures[idx]; last_D_code = tool->m_DCode; if( (tool->m_Type == aType) && (tool->m_Corners.size() == aCorners.size() ) && (tool->m_Rotation == aRotation) && (tool->m_ApertureAttribute == aApertureAttribute) ) { // A candidate is found. the corner lists must be similar bool is_same = polyCompare( tool->m_Corners, aCorners ); if( is_same ) return idx; } } // Allocate a new aperture APERTURE new_tool; new_tool.m_Corners = aCorners; new_tool.m_Size = VECTOR2I( 0, 0 ); // Not used new_tool.m_Type = aType; new_tool.m_Radius = 0; // Not used new_tool.m_Rotation = aRotation; new_tool.m_DCode = last_D_code + 1; new_tool.m_ApertureAttribute = aApertureAttribute; m_apertures.push_back( new_tool ); return m_apertures.size() - 1; } void GERBER_PLOTTER::selectAperture( const VECTOR2I& aSize, int aRadius, const EDA_ANGLE& aRotation, APERTURE::APERTURE_TYPE aType, int aApertureAttribute ) { bool change = ( m_currentApertureIdx < 0 ) || ( m_apertures[m_currentApertureIdx].m_Type != aType ) || ( m_apertures[m_currentApertureIdx].m_Size != aSize ) || ( m_apertures[m_currentApertureIdx].m_Radius != aRadius ) || ( m_apertures[m_currentApertureIdx].m_Rotation != aRotation ); if( !change ) change = m_apertures[m_currentApertureIdx].m_ApertureAttribute != aApertureAttribute; if( change ) { // Pick an existing aperture or create a new one m_currentApertureIdx = GetOrCreateAperture( aSize, aRadius, aRotation, aType, aApertureAttribute ); fprintf( m_outputFile, "D%d*\n", m_apertures[m_currentApertureIdx].m_DCode ); } } void GERBER_PLOTTER::selectAperture( const std::vector& aCorners, const EDA_ANGLE& aRotation, APERTURE::APERTURE_TYPE aType, int aApertureAttribute ) { bool change = ( m_currentApertureIdx < 0 ) || ( m_apertures[m_currentApertureIdx].m_Type != aType ) || ( m_apertures[m_currentApertureIdx].m_Corners.size() != aCorners.size() ) || ( m_apertures[m_currentApertureIdx].m_Rotation != aRotation ); if( !change ) // Compare corner lists { for( size_t ii = 0; ii < aCorners.size(); ii++ ) { if( aCorners[ii] != m_apertures[m_currentApertureIdx].m_Corners[ii] ) { change = true; break; } } } if( !change ) change = m_apertures[m_currentApertureIdx].m_ApertureAttribute != aApertureAttribute; if( change ) { // Pick an existing aperture or create a new one m_currentApertureIdx = GetOrCreateAperture( aCorners, aRotation, aType, aApertureAttribute ); fprintf( m_outputFile, "D%d*\n", m_apertures[m_currentApertureIdx].m_DCode ); } } void GERBER_PLOTTER::selectAperture( int aDiameter, const EDA_ANGLE& aPolygonRotation, APERTURE::APERTURE_TYPE aType, int aApertureAttribute ) { // Pick an existing aperture or create a new one, matching the // aDiameter, aPolygonRotation, type and attributes for type = // AT_REGULAR_POLY3 to AT_REGULAR_POLY12 wxASSERT( aType>= APERTURE::APERTURE_TYPE::AT_REGULAR_POLY3 && aType <= APERTURE::APERTURE_TYPE::AT_REGULAR_POLY12 ); VECTOR2I size( aDiameter, (int) ( aPolygonRotation.AsDegrees() * 1000.0 ) ); selectAperture( VECTOR2I( 0, 0 ), aDiameter / 2, aPolygonRotation, aType, aApertureAttribute ); } void GERBER_PLOTTER::writeApertureList() { wxASSERT( m_outputFile ); bool useX1StructuredComment = false; if( !m_useX2format ) useX1StructuredComment = true; // Init for( APERTURE& tool : m_apertures ) { // aperture sizes are in inch or mm, regardless the // coordinates format double fscale = 0.0001 * m_plotScale / m_IUsPerDecimil; // inches if(! m_gerberUnitInch ) fscale *= 25.4; // size in mm int attribute = tool.m_ApertureAttribute; if( attribute != m_apertureAttribute ) { fputs( GBR_APERTURE_METADATA::FormatAttribute( (GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB) attribute, useX1StructuredComment ).c_str(), m_outputFile ); } fprintf( m_outputFile, "%%ADD%d", tool.m_DCode ); /* Please note: the Gerber specs for mass parameters say that exponential syntax is *not* allowed and the decimal point should also be always inserted. So the %g format is ruled out, but %f is fine (the # modifier forces the decimal point). Sadly the %f formatter can't remove trailing zeros but that's not a problem, since nothing forbid it (the file is only slightly longer) */ switch( tool.m_Type ) { case APERTURE::AT_CIRCLE: fprintf( m_outputFile, "C,%#f*%%\n", tool.GetDiameter() * fscale ); break; case APERTURE::AT_RECT: fprintf( m_outputFile, "R,%#fX%#f*%%\n", tool.m_Size.x * fscale, tool.m_Size.y * fscale ); break; case APERTURE::AT_PLOTTING: fprintf( m_outputFile, "C,%#f*%%\n", tool.m_Size.x * fscale ); break; case APERTURE::AT_OVAL: fprintf( m_outputFile, "O,%#fX%#f*%%\n", tool.m_Size.x * fscale, tool.m_Size.y * fscale ); break; case APERTURE::AT_REGULAR_POLY: case APERTURE::AT_REGULAR_POLY3: case APERTURE::AT_REGULAR_POLY4: case APERTURE::AT_REGULAR_POLY5: case APERTURE::AT_REGULAR_POLY6: case APERTURE::AT_REGULAR_POLY7: case APERTURE::AT_REGULAR_POLY8: case APERTURE::AT_REGULAR_POLY9: case APERTURE::AT_REGULAR_POLY10: case APERTURE::AT_REGULAR_POLY11: case APERTURE::AT_REGULAR_POLY12: fprintf( m_outputFile, "P,%#fX%dX%#f*%%\n", tool.GetDiameter() * fscale, tool.GetRegPolyVerticeCount(), tool.GetRotation().AsDegrees() ); break; case APERTURE::AM_ROUND_RECT: // Aperture macro for round rect pads { // The aperture macro needs coordinates of the centers of the 4 corners std::vector corners; VECTOR2I half_size( tool.m_Size.x/2-tool.m_Radius, tool.m_Size.y/2-tool.m_Radius ); corners.emplace_back( -half_size.x, -half_size.y ); corners.emplace_back( half_size.x, -half_size.y ); corners.emplace_back( half_size.x, half_size.y ); corners.emplace_back( -half_size.x, half_size.y ); // Rotate the corner coordinates: for( int ii = 0; ii < 4; ii++ ) RotatePoint( corners[ii], -tool.m_Rotation ); fprintf( m_outputFile, "%s,%#fX", APER_MACRO_ROUNDRECT_NAME, tool.m_Radius * fscale ); // Add each corner for( int ii = 0; ii < 4; ii++ ) { fprintf( m_outputFile, "%#fX%#fX", corners[ii].x * fscale, corners[ii].y * fscale ); } fprintf( m_outputFile, "0*%%\n" ); } break; case APERTURE::AM_ROT_RECT: // Aperture macro for rotated rect pads fprintf( m_outputFile, "%s,%#fX%#fX%#f*%%\n", APER_MACRO_ROT_RECT_NAME, tool.m_Size.x * fscale, tool.m_Size.y * fscale, tool.m_Rotation.AsDegrees() ); break; case APERTURE::APER_MACRO_OUTLINE4P: // Aperture macro for trapezoid pads case APERTURE::APER_MACRO_OUTLINE5P: // Aperture macro for chamfered rect pads case APERTURE::APER_MACRO_OUTLINE6P: // Aperture macro for chamfered rect pads case APERTURE::APER_MACRO_OUTLINE7P: // Aperture macro for chamfered rect pads case APERTURE::APER_MACRO_OUTLINE8P: // Aperture macro for chamfered rect pads switch( tool.m_Type ) { case APERTURE::APER_MACRO_OUTLINE4P: fprintf( m_outputFile, "%s,", APER_MACRO_OUTLINE4P_NAME ); break; case APERTURE::APER_MACRO_OUTLINE5P: fprintf( m_outputFile, "%s,", APER_MACRO_OUTLINE5P_NAME ); break; case APERTURE::APER_MACRO_OUTLINE6P: fprintf( m_outputFile, "%s,", APER_MACRO_OUTLINE6P_NAME ); break; case APERTURE::APER_MACRO_OUTLINE7P: fprintf( m_outputFile, "%s,", APER_MACRO_OUTLINE7P_NAME ); break; case APERTURE::APER_MACRO_OUTLINE8P: fprintf( m_outputFile, "%s,", APER_MACRO_OUTLINE8P_NAME ); break; default: break; } // Output all corners (should be 4 to 8 corners) // Remember: the Y coordinate must be negated, due to the fact in Pcbnew // the Y axis is from top to bottom for( size_t ii = 0; ii < tool.m_Corners.size(); ii++ ) { fprintf( m_outputFile, "%#fX%#fX", tool.m_Corners[ii].x * fscale, -tool.m_Corners[ii].y * fscale ); } // close outline and output rotation fprintf( m_outputFile, "%#f*%%\n", tool.m_Rotation.AsDegrees() ); break; case APERTURE::AM_ROTATED_OVAL: // Aperture macro for rotated oval pads // (not rotated is a primitive) // m_Size.x = full length; m_Size.y = width, and the macro aperture expects // the position of ends { // the seg_len is the distance between the 2 circle centers int seg_len = tool.m_Size.x - tool.m_Size.y; // Center of the circle on the segment start point: VECTOR2I start( seg_len/2, 0 ); // Center of the circle on the segment end point: VECTOR2I end( - seg_len/2, 0 ); RotatePoint( start, tool.m_Rotation ); RotatePoint( end, tool.m_Rotation ); fprintf( m_outputFile, "%s,%#fX%#fX%#fX%#fX%#fX0*%%\n", APER_MACRO_SHAPE_OVAL_NAME, tool.m_Size.y * fscale, // width start.x * fscale, -start.y * fscale, // X,Y corner start pos end.x * fscale, -end.y * fscale ); // X,Y cornerend pos } break; case APERTURE::AM_FREE_POLYGON: { // Find the aperture macro name in the list of aperture macro // created on the fly for this polygon: int idx = m_am_freepoly_list.FindAm( tool.m_Corners ); // Write DCODE id ( "%ADDxx" is already in buffer) and rotation // the full line is something like :%ADD12FreePoly1,45.000000*% fprintf( m_outputFile, "%s%d,%#f*%%\n", AM_FREEPOLY_BASENAME, idx, tool.m_Rotation.AsDegrees() ); break; } } m_apertureAttribute = attribute; // Currently reset the aperture attribute. Perhaps a better optimization // is to store the last attribute if( attribute ) { if( m_useX2format ) fputs( "%TD*%\n", m_outputFile ); else fputs( "G04 #@! TD*\n", m_outputFile ); m_apertureAttribute = 0; } } } void GERBER_PLOTTER::PenTo( const VECTOR2I& aPos, char plume ) { wxASSERT( m_outputFile ); VECTOR2D pos_dev = userToDeviceCoordinates( aPos ); switch( plume ) { case 'Z': break; case 'U': emitDcode( pos_dev, 2 ); break; case 'D': emitDcode( pos_dev, 1 ); } m_penState = plume; } void GERBER_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) { std::vector cornerList; cornerList.reserve( 5 ); // Build corners list cornerList.push_back( p1 ); VECTOR2I corner( p1.x, p2.y ); cornerList.push_back( corner ); cornerList.push_back( p2 ); corner.x = p2.x; corner.y = p1.y; cornerList.push_back( corner ); cornerList.push_back( p1 ); PlotPoly( cornerList, fill, width ); } void GERBER_PLOTTER::Circle( const VECTOR2I& aCenter, int aDiameter, FILL_T aFill, int aWidth ) { Arc( aCenter, ANGLE_0, ANGLE_360, aDiameter / 2, aFill, aWidth ); } void GERBER_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth ) { SetCurrentLineWidth( aWidth ); EDA_ANGLE endAngle = aStartAngle + aAngle; // aFill is not used here. plotArc( aCenter, aStartAngle, endAngle, aRadius, false ); } void GERBER_PLOTTER::plotArc( const SHAPE_ARC& aArc, bool aPlotInRegion ) { VECTOR2I start( aArc.GetP0() ); VECTOR2I end( aArc.GetP1() ); VECTOR2I center( aArc.GetCenter() ); if( !aPlotInRegion ) MoveTo( start); else LineTo( start ); VECTOR2D devEnd = userToDeviceCoordinates( end ); // devRelCenter is the position on arc center relative to the arc start, in Gerber coord. // Warning: it is **not** userToDeviceCoordinates( center - start ) when the plotter // has an offset. VECTOR2D devRelCenter = userToDeviceCoordinates( center ) - userToDeviceCoordinates( start ); // We need to know if the arc is CW or CCW in device coordinates, so build this arc. SHAPE_ARC deviceArc( userToDeviceCoordinates( start ), userToDeviceCoordinates( aArc.GetArcMid() ), devEnd, 0 ); fprintf( m_outputFile, "G75*\n" ); // Multiquadrant (360 degrees) mode if( deviceArc.IsClockwise() ) fprintf( m_outputFile, "G02*\n" ); // Active circular interpolation, CW else fprintf( m_outputFile, "G03*\n" ); // Active circular interpolation, CCW fprintf( m_outputFile, "X%dY%dI%dJ%dD01*\n", KiROUND( devEnd.x ), KiROUND( devEnd.y ), KiROUND( devRelCenter.x ), KiROUND( devRelCenter.y ) ); fprintf( m_outputFile, "G01*\n" ); // Back to linear interpolate (perhaps useless here). } void GERBER_PLOTTER::plotArc( const VECTOR2I& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle, int aRadius, bool aPlotInRegion ) { VECTOR2I start, end; start.x = aCenter.x + KiROUND( aRadius * aStartAngle.Cos() ); start.y = aCenter.y + KiROUND( aRadius * aStartAngle.Sin() ); if( !aPlotInRegion ) MoveTo( start ); else LineTo( start ); end.x = aCenter.x + KiROUND( aRadius * aEndAngle.Cos() ); end.y = aCenter.y + KiROUND( aRadius * aEndAngle.Sin() ); VECTOR2D devEnd = userToDeviceCoordinates( end ); // devRelCenter is the position on arc center relative to the arc start, in Gerber coord. VECTOR2D devRelCenter = userToDeviceCoordinates( aCenter ) - userToDeviceCoordinates( start ); fprintf( m_outputFile, "G75*\n" ); // Multiquadrant (360 degrees) mode if( aStartAngle > aEndAngle ) fprintf( m_outputFile, "G03*\n" ); // Active circular interpolation, CCW else fprintf( m_outputFile, "G02*\n" ); // Active circular interpolation, CW fprintf( m_outputFile, "X%dY%dI%dJ%dD01*\n", KiROUND( devEnd.x ), KiROUND( devEnd.y ), KiROUND( devRelCenter.x ), KiROUND( devRelCenter.y ) ); fprintf( m_outputFile, "G01*\n" ); // Back to linear interpolate (perhaps useless here). } void GERBER_PLOTTER::PlotGerberRegion( const SHAPE_LINE_CHAIN& aPoly, GBR_METADATA* aGbrMetadata ) { if( aPoly.PointCount() <= 2 ) return; bool clearTA_AperFunction = false; // true if a TA.AperFunction is used if( aGbrMetadata ) { std::string attrib = aGbrMetadata->m_ApertureMetadata.FormatAttribute( !m_useX2format ); if( !attrib.empty() ) { fputs( attrib.c_str(), m_outputFile ); clearTA_AperFunction = true; } } PlotPoly( aPoly, FILL_T::FILLED_SHAPE, 0 , aGbrMetadata ); // Clear the TA attribute, to avoid the next item to inherit it: if( clearTA_AperFunction ) { if( m_useX2format ) { fputs( "%TD.AperFunction*%\n", m_outputFile ); } else { fputs( "G04 #@! TD.AperFunction*\n", m_outputFile ); } } } void GERBER_PLOTTER::PlotGerberRegion( const std::vector& aCornerList, GBR_METADATA* aGbrMetadata ) { if( aCornerList.size() <= 2 ) return; bool clearTA_AperFunction = false; // true if a TA.AperFunction is used if( aGbrMetadata ) { std::string attrib = aGbrMetadata->m_ApertureMetadata.FormatAttribute( !m_useX2format ); if( !attrib.empty() ) { fputs( attrib.c_str(), m_outputFile ); clearTA_AperFunction = true; } } PlotPoly( aCornerList, FILL_T::FILLED_SHAPE, 0, aGbrMetadata ); // Clear the TA attribute, to avoid the next item to inherit it: if( clearTA_AperFunction ) { if( m_useX2format ) { fputs( "%TD.AperFunction*%\n", m_outputFile ); } else { fputs( "G04 #@! TD.AperFunction*\n", m_outputFile ); } } } void GERBER_PLOTTER::PlotPolyAsRegion( const SHAPE_LINE_CHAIN& aPoly, FILL_T aFill, int aWidth, GBR_METADATA* aGbrMetadata ) { // plot a filled polygon using Gerber region, therefore adding X2 attributes // to the solid polygon if( aWidth ) PlotPoly( aPoly, FILL_T::NO_FILL, aWidth, aGbrMetadata ); if( aFill != FILL_T::NO_FILL ) PlotGerberRegion( aPoly, aGbrMetadata ); } void GERBER_PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aPoly, FILL_T aFill, int aWidth, void* aData ) { if( aPoly.CPoints().size() <= 1 ) return; // Gerber format does not know filled polygons with thick outline // Therefore, to plot a filled polygon with outline having a thickness, // one should plot outline as thick segments GBR_METADATA* gbr_metadata = static_cast( aData ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); if( aFill != FILL_T::NO_FILL ) { fputs( "G36*\n", m_outputFile ); MoveTo( VECTOR2I( aPoly.CPoint( 0 ) ) ); fputs( "G01*\n", m_outputFile ); // Set linear interpolation. for( int ii = 1; ii < aPoly.PointCount(); ii++ ) { int arcindex = aPoly.ArcIndex( ii ); if( arcindex < 0 ) { /// Plain point LineTo( VECTOR2I( aPoly.CPoint( ii ) ) ); } else { const SHAPE_ARC& arc = aPoly.Arc( arcindex ); plotArc( arc, true ); // skip points on arcs, since we plot the arc itself while( ii+1 < aPoly.PointCount() && arcindex == aPoly.ArcIndex( ii+1 ) ) ii++; } } // If the polygon is not closed, close it: if( aPoly.CPoint( 0 ) != aPoly.CPoint( -1 ) ) FinishTo( VECTOR2I( aPoly.CPoint( 0 ) ) ); fputs( "G37*\n", m_outputFile ); } if( aWidth > 0 ) // Draw the polyline/polygon outline { SetCurrentLineWidth( aWidth, gbr_metadata ); MoveTo( VECTOR2I( aPoly.CPoint( 0 ) ) ); for( int ii = 1; ii < aPoly.PointCount(); ii++ ) { int arcindex = aPoly.ArcIndex( ii ); if( arcindex < 0 ) { /// Plain point LineTo( VECTOR2I( aPoly.CPoint( ii ) ) ); } else { const SHAPE_ARC& arc = aPoly.Arc( arcindex ); plotArc( arc, true ); // skip points on arcs, since we plot the arc itself while( ii+1 < aPoly.PointCount() && arcindex == aPoly.ArcIndex( ii+1 ) ) ii++; } } // Ensure the thick outline is closed for filled polygons // (if not filled, could be only a polyline) if( ( aPoly.CPoint( 0 ) != aPoly.CPoint( -1 ) ) && ( aPoly.IsClosed() || aFill != FILL_T::NO_FILL ) ) LineTo( VECTOR2I( aPoly.CPoint( 0 ) ) ); PenFinish(); } } void GERBER_PLOTTER::PlotPoly( const std::vector& aCornerList, FILL_T aFill, int aWidth, void * aData ) { if( aCornerList.size() <= 1 ) return; // Gerber format does not know filled polygons with thick outline // Therefore, to plot a filled polygon with outline having a thickness, // one should plot outline as thick segments GBR_METADATA* gbr_metadata = static_cast( aData ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); if( aFill != FILL_T::NO_FILL ) { fputs( "G36*\n", m_outputFile ); MoveTo( aCornerList[0] ); fputs( "G01*\n", m_outputFile ); // Set linear interpolation. for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) LineTo( aCornerList[ii] ); // If the polygon is not closed, close it: if( aCornerList[0] != aCornerList[aCornerList.size()-1] ) FinishTo( aCornerList[0] ); fputs( "G37*\n", m_outputFile ); } if( aWidth > 0 ) // Draw the polyline/polygon outline { SetCurrentLineWidth( aWidth, gbr_metadata ); MoveTo( aCornerList[0] ); for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) LineTo( aCornerList[ii] ); // Ensure the thick outline is closed for filled polygons // (if not filled, could be only a polyline) if( aFill != FILL_T::NO_FILL && ( aCornerList[aCornerList.size() - 1] != aCornerList[0] ) ) LineTo( aCornerList[0] ); PenFinish(); } } void GERBER_PLOTTER::ThickSegment( const VECTOR2I& start, const VECTOR2I& end, int width, OUTLINE_MODE tracemode, void* aData ) { if( tracemode == FILLED ) { GBR_METADATA *gbr_metadata = static_cast( aData ); SetCurrentLineWidth( width, gbr_metadata ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); MoveTo( start ); FinishTo( end ); } else { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); segmentAsOval( start, end, width, tracemode ); } } void GERBER_PLOTTER::ThickArc( const VECTOR2D& aCentre, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle, double aRadius, int aWidth, OUTLINE_MODE aTraceMode, void* aData ) { GBR_METADATA* gbr_metadata = static_cast( aData ); SetCurrentLineWidth( aWidth, gbr_metadata ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); if( aTraceMode == FILLED ) { Arc( aCentre, aStartAngle, aAngle, aRadius, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } else { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); Arc( aCentre, aStartAngle, aAngle, aRadius - ( aWidth - m_currentPenWidth ) / 2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); Arc( aCentre, aStartAngle, aAngle, aRadius + ( aWidth - m_currentPenWidth ) / 2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } } void GERBER_PLOTTER::ThickRect( const VECTOR2I& p1, const VECTOR2I& p2, int width, OUTLINE_MODE tracemode, void* aData ) { GBR_METADATA *gbr_metadata = static_cast( aData ); SetCurrentLineWidth( width, gbr_metadata ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); if( tracemode == FILLED ) { Rect( p1, p2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } else { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); VECTOR2I offsetp1( p1.x - ( width - m_currentPenWidth ) / 2, p1.y - (width - m_currentPenWidth) / 2 ); VECTOR2I offsetp2( p2.x + ( width - m_currentPenWidth ) / 2, p2.y + (width - m_currentPenWidth) / 2 ); Rect( offsetp1, offsetp2, FILL_T::NO_FILL, -1 ); offsetp1.x += (width - m_currentPenWidth); offsetp1.y += (width - m_currentPenWidth); offsetp2.x -= (width - m_currentPenWidth); offsetp2.y -= (width - m_currentPenWidth); Rect( offsetp1, offsetp2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } } void GERBER_PLOTTER::ThickCircle( const VECTOR2I& pos, int diametre, int width, OUTLINE_MODE tracemode, void* aData ) { GBR_METADATA *gbr_metadata = static_cast( aData ); SetCurrentLineWidth( width, gbr_metadata ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); if( tracemode == FILLED ) { Circle( pos, diametre, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } else { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata ); Circle( pos, diametre - (width - m_currentPenWidth), FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); Circle( pos, diametre + (width - m_currentPenWidth), FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } } void GERBER_PLOTTER::FilledCircle( const VECTOR2I& pos, int diametre, OUTLINE_MODE tracemode, void* aData ) { // A filled circle is a graphic item, not a pad. // So it is drawn, not flashed. GBR_METADATA *gbr_metadata = static_cast( aData ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); if( tracemode == FILLED ) { // Draw a circle of diameter = diameter/2 with a line thickness = radius, // To create a filled circle SetCurrentLineWidth( diametre/2, gbr_metadata ); Circle( pos, diametre/2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } else { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata ); Circle( pos, diametre, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } } void GERBER_PLOTTER::FlashPadCircle( const VECTOR2I& pos, int diametre, OUTLINE_MODE trace_mode, void* aData ) { VECTOR2I size( diametre, diametre ); GBR_METADATA* gbr_metadata = static_cast( aData ); if( trace_mode == SKETCH ) { if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); Circle( pos, diametre, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH ); } else { VECTOR2D pos_dev = userToDeviceCoordinates( pos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( size, 0, ANGLE_0, APERTURE::AT_CIRCLE, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_dev, 3 ); } } void GERBER_PLOTTER::FlashPadOval( const VECTOR2I& aPos, const VECTOR2I& aSize, const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData ) { wxASSERT( m_outputFile ); VECTOR2I size( aSize ); EDA_ANGLE orient( aOrient ); orient.Normalize(); GBR_METADATA* gbr_metadata = static_cast( aData ); // Flash a vertical or horizontal shape (this is a basic aperture). if( orient.IsCardinal() && aTraceMode == FILLED ) { if( orient.IsCardinal90() ) std::swap( size.x, size.y ); VECTOR2D pos_device = userToDeviceCoordinates( aPos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( size, 0, ANGLE_0, APERTURE::AT_OVAL, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_device, 3 ); } else // Plot pad as region. // Only regions and flashed items accept a object attribute TO.P for the pin name { if( aTraceMode == FILLED ) { #ifdef GBR_USE_MACROS_FOR_ROTATED_OVAL if( !m_gerberDisableApertMacros ) #endif { m_hasApertureRotOval = true; // We are using a aperture macro that expect size.y < size.x // i.e draw a horizontal line for rotation = 0.0 // size.x = length, size.y = width if( size.x < size.y ) { std::swap( size.x, size.y ); orient += ANGLE_90; if( orient > ANGLE_180 ) orient -= ANGLE_180; } VECTOR2D pos_device = userToDeviceCoordinates( aPos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( size, 0, orient, APERTURE::AM_ROTATED_OVAL, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_device, 3 ); return; } // Draw the oval as round rect pad with a radius = 50% min size) // In gerber file, it will be drawn as a region with arcs, and can be // detected as pads (similar to a flashed pad) FlashPadRoundRect( aPos, aSize, std::min( aSize.x, aSize.y ) / 2, orient, FILLED, aData ); } else // Non filled shape: plot outlines: { if( size.x > size.y ) { std::swap( size.x, size.y ); if( orient < ANGLE_270 ) orient += ANGLE_90; else orient -= ANGLE_270; } sketchOval( aPos, size, orient, -1 ); } } } void GERBER_PLOTTER::FlashPadRect( const VECTOR2I& pos, const VECTOR2I& aSize, const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData ) { wxASSERT( m_outputFile ); VECTOR2I size( aSize ); GBR_METADATA* gbr_metadata = static_cast( aData ); // Horizontal / vertical rect can use a basic aperture (not a macro) // so use it for rotation n*90 deg if( aOrient.IsCardinal() ) { if( aOrient.IsCardinal90() ) // Build the not rotated equivalent shape: std::swap( size.x, size.y ); if( aTraceMode == SKETCH ) { if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); Rect( VECTOR2I( pos.x - ( size.x / 2 ), pos.y - (size.y / 2 ) ), VECTOR2I( pos.x + ( size.x / 2 ), pos.y + (size.y / 2 ) ), FILL_T::NO_FILL, GetCurrentLineWidth() ); } else { VECTOR2D pos_device = userToDeviceCoordinates( pos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( size, 0, ANGLE_0, APERTURE::AT_RECT, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_device, 3 ); } } else { #ifdef GBR_USE_MACROS_FOR_ROTATED_RECT if( aTraceMode != SKETCH && !m_gerberDisableApertMacros ) { m_hasApertureRotRect = true; VECTOR2D pos_device = userToDeviceCoordinates( pos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( size, 0, aOrient, APERTURE::AM_ROT_RECT, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_device, 3 ); } else #endif { // plot pad shape as Gerber region VECTOR2I coord[4]; // coord[0] is assumed the lower left // coord[1] is assumed the upper left // coord[2] is assumed the upper right // coord[3] is assumed the lower right coord[0].x = -size.x/2; // lower left coord[0].y = size.y/2; coord[1].x = -size.x/2; // upper left coord[1].y = -size.y/2; coord[2].x = size.x/2; // upper right coord[2].y = -size.y/2; coord[3].x = size.x/2; // lower right coord[3].y = size.y/2; FlashPadTrapez( pos, coord, aOrient, aTraceMode, aData ); } } } void GERBER_PLOTTER::FlashPadRoundRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize, int aCornerRadius, const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData ) { GBR_METADATA* gbr_metadata = static_cast( aData ); if( aTraceMode != FILLED ) { SHAPE_POLY_SET outline; TransformRoundChamferedRectToPolygon( outline, aPadPos, aSize, aOrient, aCornerRadius, 0.0, 0, 0, GetPlotterArcHighDef(), ERROR_INSIDE ); SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata ); std::vector cornerList; // TransformRoundRectToPolygon creates only one convex polygon SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); cornerList.reserve( poly.PointCount() + 1 ); for( int ii = 0; ii < poly.PointCount(); ++ii ) cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y ); // Close polygon cornerList.push_back( cornerList[0] ); // plot outlines PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), gbr_metadata ); } else { #ifdef GBR_USE_MACROS_FOR_ROUNDRECT if( !m_gerberDisableApertMacros ) #endif { m_hasApertureRoundRect = true; VECTOR2D pos_dev = userToDeviceCoordinates( aPadPos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( aSize, aCornerRadius, aOrient, APERTURE::AM_ROUND_RECT, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_dev, 3 ); return; } // A Pad RoundRect is plotted as a Gerber region. // Initialize region metadata: bool clearTA_AperFunction = false; // true if a TA.AperFunction is used if( gbr_metadata ) { formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); std::string attrib = gbr_metadata->m_ApertureMetadata.FormatAttribute( !m_useX2format ); if( !attrib.empty() ) { fputs( attrib.c_str(), m_outputFile ); clearTA_AperFunction = true; } } // Plot the region using arcs in corners. plotRoundRectAsRegion( aPadPos, aSize, aCornerRadius, aOrient ); // Clear the TA attribute, to avoid the next item to inherit it: if( clearTA_AperFunction ) { if( m_useX2format ) fputs( "%TD.AperFunction*%\n", m_outputFile ); else fputs( "G04 #@! TD.AperFunction*\n", m_outputFile ); } } } void GERBER_PLOTTER::plotRoundRectAsRegion( const VECTOR2I& aRectCenter, const VECTOR2I& aSize, int aCornerRadius, const EDA_ANGLE& aOrient ) { // The region outline is generated by 4 sides and 4 90 deg arcs // 1 --- 2 // | c | // 4 --- 3 // Note also in user coordinates the Y axis is from top to bottom // for historical reasons. // A helper structure to handle outlines coordinates (segments and arcs) // in user coordinates struct RR_EDGE { VECTOR2I m_start; VECTOR2I m_end; VECTOR2I m_center; EDA_ANGLE m_arc_angle_start; }; int hsizeX = aSize.x/2; int hsizeY = aSize.y/2; RR_EDGE curr_edge; std::vector rr_outline; rr_outline.reserve( 4 ); // Build outline coordinates, relative to rectangle center, rotation 0: // Top left corner 1 (and 4 to 1 left vertical side @ x=-hsizeX) curr_edge.m_start.x = -hsizeX; curr_edge.m_start.y = hsizeY - aCornerRadius; curr_edge.m_end.x = curr_edge.m_start.x; curr_edge.m_end.y = -hsizeY + aCornerRadius; curr_edge.m_center.x = -hsizeX + aCornerRadius; curr_edge.m_center.y = curr_edge.m_end.y; curr_edge.m_arc_angle_start = aOrient + ANGLE_180; rr_outline.push_back( curr_edge ); // Top right corner 2 (and 1 to 2 top horizontal side @ y=-hsizeY) curr_edge.m_start.x = -hsizeX + aCornerRadius; curr_edge.m_start.y = -hsizeY; curr_edge.m_end.x = hsizeX - aCornerRadius; curr_edge.m_end.y = curr_edge.m_start.y; curr_edge.m_center.x = curr_edge.m_end.x; curr_edge.m_center.y = -hsizeY + aCornerRadius; curr_edge.m_arc_angle_start = aOrient + ANGLE_90; rr_outline.push_back( curr_edge ); // bottom right corner 3 (and 2 to 3 right vertical side @ x=hsizeX) curr_edge.m_start.x = hsizeX; curr_edge.m_start.y = -hsizeY + aCornerRadius; curr_edge.m_end.x = curr_edge.m_start.x; curr_edge.m_end.y = hsizeY - aCornerRadius; curr_edge.m_center.x = hsizeX - aCornerRadius; curr_edge.m_center.y = curr_edge.m_end.y; curr_edge.m_arc_angle_start = aOrient + ANGLE_0; rr_outline.push_back( curr_edge ); // bottom left corner 4 (and 3 to 4 bottom horizontal side @ y=hsizeY) curr_edge.m_start.x = hsizeX - aCornerRadius; curr_edge.m_start.y = hsizeY; curr_edge.m_end.x = -hsizeX + aCornerRadius; curr_edge.m_end.y = curr_edge.m_start.y; curr_edge.m_center.x = curr_edge.m_end.x; curr_edge.m_center.y = hsizeY - aCornerRadius; curr_edge.m_arc_angle_start = aOrient - ANGLE_90; rr_outline.push_back( curr_edge ); // Move relative coordinates to the actual location and rotation: VECTOR2I arc_last_center; EDA_ANGLE arc_last_angle = curr_edge.m_arc_angle_start - ANGLE_90; for( RR_EDGE& rr_edge: rr_outline ) { RotatePoint( rr_edge.m_start, aOrient ); RotatePoint( rr_edge.m_end, aOrient ); RotatePoint( rr_edge.m_center, aOrient ); rr_edge.m_start += aRectCenter; rr_edge.m_end += aRectCenter; rr_edge.m_center += aRectCenter; arc_last_center = rr_edge.m_center; } // Ensure the region is a closed polygon, i.e. the end point of last segment // (end of arc) is the same as the first point. Rounding issues can create a // small difference, mainly for rotated pads. // calculate last point (end of last arc): VECTOR2I last_pt; last_pt.x = arc_last_center.x + KiROUND( aCornerRadius * arc_last_angle.Cos() ); last_pt.y = arc_last_center.y - KiROUND( aCornerRadius * arc_last_angle.Sin() ); VECTOR2I first_pt = rr_outline[0].m_start; #if 0 // For test only: if( last_pt != first_pt ) wxLogMessage( wxS( "first pt %d %d last pt %d %d" ), first_pt.x, first_pt.y, last_pt.x, last_pt.y ); #endif fputs( "G36*\n", m_outputFile ); // Start region fputs( "G01*\n", m_outputFile ); // Set linear interpolation. first_pt = last_pt; MoveTo( first_pt ); // Start point of region, must be same as end point for( RR_EDGE& rr_edge: rr_outline ) { if( aCornerRadius ) // Guard: ensure we do not create arcs with radius = 0 { // LineTo( rr_edge.m_end ); // made in plotArc() plotArc( rr_edge.m_center, -rr_edge.m_arc_angle_start, -rr_edge.m_arc_angle_start + ANGLE_90, aCornerRadius, true ); } else { LineTo( rr_edge.m_end ); } } fputs( "G37*\n", m_outputFile ); // Close region } void GERBER_PLOTTER::FlashPadCustom( const VECTOR2I& aPadPos, const VECTOR2I& aSize, const EDA_ANGLE& aOrient, SHAPE_POLY_SET* aPolygons, OUTLINE_MODE aTraceMode, void* aData ) { // A Pad custom is plotted as polygon (a region in Gerber language). GBR_METADATA gbr_metadata; if( aData ) gbr_metadata = *static_cast( aData ); SHAPE_POLY_SET polyshape = aPolygons->CloneDropTriangulation(); if( aTraceMode != FILLED ) { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata ); } std::vector cornerList; for( int cnt = 0; cnt < polyshape.OutlineCount(); ++cnt ) { SHAPE_LINE_CHAIN& poly = polyshape.Outline( cnt ); cornerList.clear(); for( int ii = 0; ii < poly.PointCount(); ++ii ) cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y ); // Close polygon cornerList.push_back( cornerList[0] ); if( aTraceMode == SKETCH ) { PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &gbr_metadata ); } else { #ifdef GBR_USE_MACROS_FOR_CUSTOM_PAD if( m_gerberDisableApertMacros || cornerList.size() > GBR_MACRO_FOR_CUSTOM_PAD_MAX_CORNER_COUNT ) { PlotGerberRegion( cornerList, &gbr_metadata ); } else { // An AM will be created. the shape must be in position 0,0 and orientation 0 // to be able to reuse the same AM for pads having the same shape for( size_t ii = 0; ii < cornerList.size(); ii++ ) { cornerList[ii] -= aPadPos; RotatePoint( cornerList[ii], -aOrient ); } VECTOR2D pos_dev = userToDeviceCoordinates( aPadPos ); selectAperture( cornerList, aOrient, APERTURE::AM_FREE_POLYGON, gbr_metadata.GetApertureAttrib() ); formatNetAttribute( &gbr_metadata.m_NetlistMetadata ); emitDcode( pos_dev, 3 ); } #else PlotGerberRegion( cornerList, &gbr_metadata ); #endif } } } void GERBER_PLOTTER::FlashPadChamferRoundRect( const VECTOR2I& aShapePos, const VECTOR2I& aPadSize, int aCornerRadius, double aChamferRatio, int aChamferPositions, const EDA_ANGLE& aPadOrient, OUTLINE_MODE aPlotMode, void* aData ) { GBR_METADATA gbr_metadata; if( aData ) gbr_metadata = *static_cast( aData ); VECTOR2D pos_device = userToDeviceCoordinates( aShapePos ); SHAPE_POLY_SET outline; std::vector cornerList; bool hasRoundedCorner = aCornerRadius != 0 && aChamferPositions != 15; #ifdef GBR_USE_MACROS_FOR_CHAMFERED_RECT // Sketch mode or round rect shape or Apert Macros disabled if( aPlotMode != FILLED || hasRoundedCorner || m_gerberDisableApertMacros ) #endif { TransformRoundChamferedRectToPolygon( outline, aShapePos, aPadSize, aPadOrient, aCornerRadius, aChamferRatio, aChamferPositions, 0, GetPlotterArcHighDef(), ERROR_INSIDE ); // Build the corner list const SHAPE_LINE_CHAIN& corners = outline.Outline(0); for( int ii = 0; ii < corners.PointCount(); ii++ ) cornerList.emplace_back( corners.CPoint( ii ).x, corners.CPoint( ii ).y ); // Close the polygon cornerList.push_back( cornerList[0] ); if( aPlotMode == SKETCH ) PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &gbr_metadata ); else { #ifdef GBR_USE_MACROS_FOR_CHAMFERED_ROUND_RECT if( m_gerberDisableApertMacros ) { PlotGerberRegion( cornerList, &gbr_metadata ); } else { // An AM will be created. the shape must be in position 0,0 and orientation 0 // to be able to reuse the same AM for pads having the same shape for( size_t ii = 0; ii < cornerList.size(); ii++ ) { cornerList[ii] -= aShapePos; RotatePoint( cornerList[ii], -aPadOrient ); } selectAperture( cornerList, aPadOrient, APERTURE::AM_FREE_POLYGON, gbr_metadata.GetApertureAttrib() ); formatNetAttribute( &gbr_metadata.m_NetlistMetadata ); emitDcode( pos_device, 3 ); } #else PlotGerberRegion( cornerList, &gbr_metadata ); #endif } return; } // Build the chamfered polygon (4 to 8 corners ) TransformRoundChamferedRectToPolygon( outline, VECTOR2I( 0, 0 ), aPadSize, ANGLE_0, 0, aChamferRatio, aChamferPositions, 0, GetPlotterArcHighDef(), ERROR_INSIDE ); // Build the corner list const SHAPE_LINE_CHAIN& corners = outline.Outline(0); // Generate the polygon (4 to 8 corners ) for( int ii = 0; ii < corners.PointCount(); ii++ ) cornerList.emplace_back( corners.CPoint( ii ).x, corners.CPoint( ii ).y ); switch( cornerList.size() ) { case 4: m_hasApertureOutline4P = true; selectAperture( cornerList, aPadOrient, APERTURE::APER_MACRO_OUTLINE4P, gbr_metadata.GetApertureAttrib() ); break; case 5: m_hasApertureChamferedRect = true; selectAperture( cornerList, aPadOrient, APERTURE::APER_MACRO_OUTLINE5P, gbr_metadata.GetApertureAttrib() ); break; case 6: m_hasApertureChamferedRect = true; selectAperture( cornerList, aPadOrient, APERTURE::APER_MACRO_OUTLINE6P, gbr_metadata.GetApertureAttrib() ); break; case 7: m_hasApertureChamferedRect = true; selectAperture( cornerList, aPadOrient, APERTURE::APER_MACRO_OUTLINE7P, gbr_metadata.GetApertureAttrib() ); break; case 8: m_hasApertureChamferedRect = true; selectAperture( cornerList, aPadOrient, APERTURE::APER_MACRO_OUTLINE8P, gbr_metadata.GetApertureAttrib() ); break; default: wxLogMessage( wxS( "FlashPadChamferRoundRect(): Unexpected number of corners (%d)" ), (int)cornerList.size() ); break; } formatNetAttribute( &gbr_metadata.m_NetlistMetadata ); emitDcode( pos_device, 3 ); } void GERBER_PLOTTER::FlashPadTrapez( const VECTOR2I& aPadPos, const VECTOR2I* aCorners, const EDA_ANGLE& aPadOrient, OUTLINE_MODE aTraceMode, void* aData ) { // polygon corners list std::vector cornerList = { aCorners[0], aCorners[1], aCorners[2], aCorners[3] }; // Draw the polygon and fill the interior as required for( unsigned ii = 0; ii < 4; ii++ ) { RotatePoint( cornerList[ii], aPadOrient ); cornerList[ii] += aPadPos; } // Close the polygon cornerList.push_back( cornerList[0] ); GBR_METADATA* gbr_metadata = static_cast( aData ); GBR_METADATA metadata; if( gbr_metadata ) metadata = *gbr_metadata; if( aTraceMode == SKETCH ) { PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &metadata ); return; } // Plot a filled polygon: #ifdef GBR_USE_MACROS_FOR_TRAPEZOID if( !m_gerberDisableApertMacros ) #endif { m_hasApertureOutline4P = true; VECTOR2D pos_dev = userToDeviceCoordinates( aPadPos ); // polygon corners list std::vector corners = { aCorners[0], aCorners[1], aCorners[2], aCorners[3] }; int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; selectAperture( corners, aPadOrient, APERTURE::APER_MACRO_OUTLINE4P, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_dev, 3 ); return; } PlotGerberRegion( cornerList, &metadata ); } void GERBER_PLOTTER::FlashRegularPolygon( const VECTOR2I& aShapePos, int aDiameter, int aCornerCount, const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData ) { GBR_METADATA* gbr_metadata = static_cast( aData ); GBR_METADATA metadata; if( gbr_metadata ) metadata = *gbr_metadata; if( aTraceMode == SKETCH ) { // Build the polygon: std::vector cornerList; EDA_ANGLE angle_delta = ANGLE_360 / aCornerCount; for( int ii = 0; ii < aCornerCount; ii++ ) { EDA_ANGLE rot = aOrient + ( angle_delta * ii ); VECTOR2I vertice( aDiameter / 2, 0 ); RotatePoint( vertice, rot ); vertice += aShapePos; cornerList.push_back( vertice ); } cornerList.push_back( cornerList[0] ); // Close the shape PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &gbr_metadata ); } else { VECTOR2D pos_dev = userToDeviceCoordinates( aShapePos ); int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; APERTURE::APERTURE_TYPE apert_type = (APERTURE::APERTURE_TYPE)(APERTURE::AT_REGULAR_POLY3 + aCornerCount - 3); selectAperture( aDiameter, aOrient, apert_type, aperture_attrib ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); emitDcode( pos_dev, 3 ); } } void GERBER_PLOTTER::Text( const VECTOR2I& aPos, const COLOR4D& aColor, const wxString& aText, const EDA_ANGLE& aOrient, const VECTOR2I& aSize, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify, int aWidth, bool aItalic, bool aBold, bool aMultilineAllowed, KIFONT::FONT* aFont, const KIFONT::METRICS& aFontMetrics, void* aData ) { GBR_METADATA* gbr_metadata = static_cast( aData ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, aWidth, aItalic, aBold, aMultilineAllowed, aFont, aFontMetrics, aData ); } void GERBER_PLOTTER::PlotText( const VECTOR2I& aPos, const COLOR4D& aColor, const wxString& aText, const TEXT_ATTRIBUTES& aAttributes, KIFONT::FONT* aFont, const KIFONT::METRICS& aFontMetrics, void* aData ) { GBR_METADATA* gbr_metadata = static_cast( aData ); if( gbr_metadata ) formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); PLOTTER::PlotText( aPos, aColor, aText, aAttributes, aFont, aFontMetrics, aData ); } void GERBER_PLOTTER::SetLayerPolarity( bool aPositive ) { if( aPositive ) fprintf( m_outputFile, "%%LPD*%%\n" ); else fprintf( m_outputFile, "%%LPC*%%\n" ); } bool APER_MACRO_FREEPOLY::IsSamePoly( const std::vector& aPolygon ) const { return polyCompare( m_Corners, aPolygon ); } void APER_MACRO_FREEPOLY::Format( FILE * aOutput, double aIu2GbrMacroUnit ) { // Write aperture header fprintf( aOutput, "%%AM%s%d*\n", AM_FREEPOLY_BASENAME, m_Id ); fprintf( aOutput, "4,1,%d,", (int)m_Corners.size() ); // Insert a newline after curr_line_count_max coordinates. int curr_line_corner_count = 0; const int curr_line_count_max = 20; // <= 0 to disable newlines for( size_t ii = 0; ii <= m_Corners.size(); ii++ ) { int jj = ii; if( ii >= m_Corners.size() ) jj = 0; // Note: parameter values are always mm or inches fprintf( aOutput, "%#f,%#f,", m_Corners[jj].x * aIu2GbrMacroUnit, -m_Corners[jj].y * aIu2GbrMacroUnit ); if( curr_line_count_max >= 0 && ++curr_line_corner_count >= curr_line_count_max ) { fprintf( aOutput, "\n" ); curr_line_corner_count = 0; } } // output rotation parameter fputs( "$1*%\n", aOutput ); } void APER_MACRO_FREEPOLY_LIST::Format( FILE * aOutput, double aIu2GbrMacroUnit ) { for( int idx = 0; idx < AmCount(); idx++ ) m_AMList[idx].Format( aOutput, aIu2GbrMacroUnit ); } void APER_MACRO_FREEPOLY_LIST::Append( const std::vector& aPolygon ) { m_AMList.emplace_back( aPolygon, AmCount() ); } int APER_MACRO_FREEPOLY_LIST::FindAm( const std::vector& aPolygon ) const { for( int idx = 0; idx < AmCount(); idx++ ) { if( m_AMList[idx].IsSamePoly( aPolygon ) ) return idx; } return -1; }