/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2011 Wayne Stambaugh * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** * @file dcode.cpp * @brief D_CODE class implementation */ #include #include #include #include #include #define DCODE_DEFAULT_SIZE gerbIUScale.mmToIU( 0.1 ) /* Format Gerber: NOTES: * Tools and D_CODES * tool number (identification of shapes) * 1 to 999 * * D_CODES: * D01 ... D9 = command codes: * D01 = activating light (pen down) while moving * D02 = light extinction (pen up) while moving * D03 = Flash * D04 to D09 = non used * D10 ... D999 = Identification Tool (Shape id) * * For tools defining a shape): * DCode min = D10 * DCode max = 999 */ D_CODE::D_CODE( int num_dcode ) { m_Num_Dcode = num_dcode; Clear_D_CODE_Data(); } D_CODE::~D_CODE() { } void D_CODE::Clear_D_CODE_Data() { m_Size.x = DCODE_DEFAULT_SIZE; m_Size.y = DCODE_DEFAULT_SIZE; m_ApertType = APT_CIRCLE; m_Drill.x = m_Drill.y = 0; m_DrillShape = APT_DEF_NO_HOLE; m_InUse = false; m_Defined = false; m_Macro = nullptr; m_Rotation = ANGLE_0; m_EdgesCount = 0; m_Polygon.RemoveAllContours(); } const wxChar* D_CODE::ShowApertureType( APERTURE_T aType ) { const wxChar* ret; switch( aType ) { case APT_CIRCLE: ret = wxT( "Round" ); break; case APT_RECT: ret = wxT( "Rect" ); break; case APT_OVAL: ret = wxT( "Oval" ); break; case APT_POLYGON: ret = wxT( "Poly" ); break; case APT_MACRO: ret = wxT( "Macro" ); break; default: ret = wxT( "???" ); break; } return ret; } int D_CODE::GetShapeDim( GERBER_DRAW_ITEM* aParent ) { int dim = 0; switch( m_ApertType ) { case APT_CIRCLE: dim = m_Size.x; break; case APT_RECT: case APT_OVAL: dim = std::min( m_Size.x, m_Size.y ); break; case APT_POLYGON: dim = std::min( m_Size.x, m_Size.y ); break; case APT_MACRO: if( m_Macro ) { if( m_Polygon.OutlineCount() == 0 ) ConvertShapeToPolygon( aParent ); BOX2I bbox = m_Polygon.BBox(); dim = std::min( bbox.GetWidth(), bbox.GetHeight() ); } break; default: break; } return dim; } void D_CODE::DrawFlashedShape( const GERBER_DRAW_ITEM* aParent, wxDC* aDC, const COLOR4D& aColor, const VECTOR2I& aShapePos, bool aFilledShape ) { int radius; switch( m_ApertType ) { case APT_CIRCLE: radius = m_Size.x >> 1; if( !aFilledShape ) { GRCircle( aDC, aParent->GetABPosition(aShapePos), radius, 0, aColor ); } else if( m_DrillShape == APT_DEF_NO_HOLE ) { GRFilledCircle( aDC, aParent->GetABPosition(aShapePos), radius, 0, aColor, aColor ); } else if( m_DrillShape == APT_DEF_ROUND_HOLE ) // round hole in shape { int width = (m_Size.x - m_Drill.x ) / 2; GRCircle( aDC, aParent->GetABPosition(aShapePos), radius - (width / 2), width, aColor ); } else // rectangular hole { if( m_Polygon.OutlineCount() == 0 ) ConvertShapeToPolygon( aParent ); DrawFlashedPolygon( aParent, aDC, aColor, aFilledShape, aShapePos ); } break; case APT_RECT: { VECTOR2I start; start.x = aShapePos.x - m_Size.x / 2; start.y = aShapePos.y - m_Size.y / 2; VECTOR2I end = start + m_Size; start = aParent->GetABPosition( start ); end = aParent->GetABPosition( end ); if( !aFilledShape ) { GRRect( aDC, start, end, 0, aColor ); } else if( m_DrillShape == APT_DEF_NO_HOLE ) { GRFilledRect( aDC, start, end, 0, aColor, aColor ); } else { if( m_Polygon.OutlineCount() == 0 ) ConvertShapeToPolygon( aParent ); DrawFlashedPolygon( aParent, aDC, aColor, aFilledShape, aShapePos ); } } break; case APT_OVAL: { VECTOR2I start = aShapePos; VECTOR2I end = aShapePos; if( m_Size.x > m_Size.y ) // horizontal oval { int delta = ( m_Size.x - m_Size.y ) / 2; start.x -= delta; end.x += delta; radius = m_Size.y; // Width in fact } else // vertical oval { int delta = ( m_Size.y - m_Size.x ) / 2; start.y -= delta; end.y += delta; radius = m_Size.x; // Width in fact } start = aParent->GetABPosition( start ); end = aParent->GetABPosition( end ); if( !aFilledShape ) { GRCSegm( aDC, start, end, radius, aColor ); } else if( m_DrillShape == APT_DEF_NO_HOLE ) { GRFilledSegment( aDC, start, end, radius, aColor ); } else { if( m_Polygon.OutlineCount() == 0 ) ConvertShapeToPolygon( aParent ); DrawFlashedPolygon( aParent, aDC, aColor, aFilledShape, aShapePos ); } } break; case APT_MACRO: case APT_POLYGON: if( m_Polygon.OutlineCount() == 0 ) ConvertShapeToPolygon( aParent ); DrawFlashedPolygon( aParent, aDC, aColor, aFilledShape, aShapePos ); break; } } void D_CODE::DrawFlashedPolygon( const GERBER_DRAW_ITEM* aParent, wxDC* aDC, const COLOR4D& aColor, bool aFilled, const VECTOR2I& aPosition ) { if( m_Polygon.OutlineCount() == 0 ) return; int pointCount = m_Polygon.VertexCount(); std::vector points; points.reserve( pointCount ); for( int ii = 0; ii < pointCount; ii++ ) { VECTOR2I p( m_Polygon.CVertex( ii ).x, m_Polygon.CVertex( ii ).y ); points[ii] = p + aPosition; points[ii] = aParent->GetABPosition( points[ii] ); } GRClosedPoly( aDC, pointCount, &points[0], aFilled, aColor ); } // TODO(snh): Remove the hard-coded count #define SEGS_CNT 64 // number of segments to approximate a circle // A helper function for D_CODE::ConvertShapeToPolygon(). Add a hole to a polygon static void addHoleToPolygon( SHAPE_POLY_SET* aPolygon, APERTURE_DEF_HOLETYPE aHoleShape, const VECTOR2I& aSize, const VECTOR2I& aAnchorPos ); void D_CODE::ConvertShapeToPolygon( const GERBER_DRAW_ITEM* aParent ) { VECTOR2I initialpos; VECTOR2I currpos; m_Polygon.RemoveAllContours(); switch( m_ApertType ) { case APT_CIRCLE: // creates only a circle with rectangular hole { // Adjust the allowed approx error to convert arcs to segments: int arc_to_seg_error = gerbIUScale.mmToIU( 0.005 ); // Allow 5 microns TransformCircleToPolygon( m_Polygon, initialpos, m_Size.x >> 1, arc_to_seg_error, ERROR_INSIDE ); addHoleToPolygon( &m_Polygon, m_DrillShape, m_Drill, initialpos ); } break; case APT_RECT: m_Polygon.NewOutline(); currpos.x = m_Size.x / 2; currpos.y = m_Size.y / 2; initialpos = currpos; m_Polygon.Append( VECTOR2I( currpos ) ); currpos.x -= m_Size.x; m_Polygon.Append( VECTOR2I( currpos ) ); currpos.y -= m_Size.y; m_Polygon.Append( VECTOR2I( currpos ) ); currpos.x += m_Size.x; m_Polygon.Append( VECTOR2I( currpos ) ); currpos.y += m_Size.y; m_Polygon.Append( VECTOR2I( currpos ) ); // close polygon m_Polygon.Append( VECTOR2I( initialpos ) ); addHoleToPolygon( &m_Polygon, m_DrillShape, m_Drill, initialpos ); break; case APT_OVAL: { m_Polygon.NewOutline(); int delta, radius; // we create an horizontal oval shape. then rotate if needed if( m_Size.x > m_Size.y ) // horizontal oval { delta = ( m_Size.x - m_Size.y ) / 2; radius = m_Size.y / 2; } else // vertical oval { delta = (m_Size.y - m_Size.x) / 2; radius = m_Size.x / 2; } currpos.y = radius; initialpos = currpos; m_Polygon.Append( VECTOR2I( currpos ) ); // build the right arc of the shape unsigned ii = 0; for( ; ii <= SEGS_CNT / 2; ii++ ) { currpos = initialpos; RotatePoint( currpos, ANGLE_360 * ii / SEGS_CNT ); currpos.x += delta; m_Polygon.Append( VECTOR2I( currpos ) ); } // build the left arc of the shape for( ii = SEGS_CNT / 2; ii <= SEGS_CNT; ii++ ) { currpos = initialpos; RotatePoint( currpos, ANGLE_360 * ii / SEGS_CNT ); currpos.x -= delta; m_Polygon.Append( currpos ); } m_Polygon.Append( initialpos ); // close outline if( m_Size.y > m_Size.x ) // vertical oval, rotate polygon. m_Polygon.Rotate( ANGLE_90 ); addHoleToPolygon( &m_Polygon, m_DrillShape, m_Drill, initialpos ); } break; case APT_POLYGON: m_Polygon.NewOutline(); currpos.x = m_Size.x >> 1; // first point is on X axis initialpos = currpos; // rs274x said: m_EdgesCount = 3 ... 12 if( m_EdgesCount < 3 ) m_EdgesCount = 3; if( m_EdgesCount > 12 ) m_EdgesCount = 12; for( int ii = 0; ii < m_EdgesCount; ii++ ) { currpos = initialpos; RotatePoint( currpos, ANGLE_360 * ii / m_EdgesCount ); m_Polygon.Append( currpos ); } addHoleToPolygon( &m_Polygon, m_DrillShape, m_Drill, initialpos ); if( !m_Rotation.IsZero() ) // rotate polygonal shape: m_Polygon.Rotate( m_Rotation ); break; case APT_MACRO: APERTURE_MACRO* macro = GetMacro(); SHAPE_POLY_SET* macroShape = macro->GetApertureMacroShape( aParent, initialpos ); m_Polygon.Append( *macroShape ); break; } } // The helper function for D_CODE::ConvertShapeToPolygon(). // Add a hole to a polygon static void addHoleToPolygon( SHAPE_POLY_SET* aPolygon, APERTURE_DEF_HOLETYPE aHoleShape, const VECTOR2I& aSize, const VECTOR2I& aAnchorPos ) { VECTOR2I currpos; SHAPE_POLY_SET holeBuffer; if( aHoleShape == APT_DEF_ROUND_HOLE ) { // Adjust the allowed approx error to convert arcs to segments: int arc_to_seg_error = gerbIUScale.mmToIU( 0.005 ); // Allow 5 microns TransformCircleToPolygon( holeBuffer, VECTOR2I( 0, 0 ), aSize.x / 2, arc_to_seg_error, ERROR_INSIDE ); } else if( aHoleShape == APT_DEF_RECT_HOLE ) { holeBuffer.NewOutline(); currpos.x = aSize.x / 2; currpos.y = aSize.y / 2; holeBuffer.Append( VECTOR2I( currpos ) ); // link to hole and begin hole currpos.x -= aSize.x; holeBuffer.Append( VECTOR2I( currpos ) ); currpos.y -= aSize.y; holeBuffer.Append( VECTOR2I( currpos ) ); currpos.x += aSize.x; holeBuffer.Append( VECTOR2I( currpos ) ); currpos.y += aSize.y; holeBuffer.Append( VECTOR2I( currpos ) ); // close hole } aPolygon->BooleanSubtract( holeBuffer, SHAPE_POLY_SET::PM_FAST ); aPolygon->Fracture( SHAPE_POLY_SET::PM_FAST ); }