/* draw a cylinder (a tube) using 3D primitives. * the cylinder axis is parallel to the Z axis * If aHeight = height of the cylinder is 0, only one ring will be drawn * If aThickness = 0, only one cylinder will be drawn */ void Draw3D_ZaxisCylinder( wxPoint aCenterPos, int aRadius, int aHeight, int aThickness, int aZpos, double aBiuTo3DUnits ) { const int slice = SEGM_PER_CIRCLE; CPOLYGONS_LIST outer_cornerBuffer; TransformCircleToPolygon( outer_cornerBuffer, aCenterPos, aRadius + (aThickness / 2), slice ); std::vector<S3D_VERTEX> coords; coords.resize( 4 ); CPOLYGONS_LIST inner_cornerBuffer; if( aThickness ) // build the the vertical inner polygon (hole) TransformCircleToPolygon( inner_cornerBuffer, aCenterPos, aRadius - (aThickness / 2), slice ); if( aHeight ) { // Draw the vertical outer side Draw3D_VerticalPolygonalCylinder( outer_cornerBuffer, aHeight, aZpos, false, aBiuTo3DUnits ); if( aThickness ) // Draws the vertical inner side (hole) Draw3D_VerticalPolygonalCylinder( inner_cornerBuffer, aHeight, aZpos, true, aBiuTo3DUnits ); } if( aThickness ) { // draw top (front) and bottom (back) horizontal sides (rings) SetNormalZpos(); outer_cornerBuffer.Append( inner_cornerBuffer ); CPOLYGONS_LIST polygon; ConvertPolysListWithHolesToOnePolygon( outer_cornerBuffer, polygon ); // draw top (front) horizontal ring Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos + aHeight, 0, aBiuTo3DUnits, false ); if( aHeight ) { // draw bottom (back) horizontal ring SetNormalZneg(); Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos, 0, aBiuTo3DUnits, false ); } } SetNormalZpos(); }
// The helper function for D_CODE::ConvertShapeToPolygon(). // Add a hole to a polygon static void addHoleToPolygon( SHAPE_POLY_SET* aPolygon, APERTURE_DEF_HOLETYPE aHoleShape, wxSize aSize, wxPoint aAnchorPos ) { wxPoint currpos; SHAPE_POLY_SET holeBuffer; if( aHoleShape == APT_DEF_ROUND_HOLE ) { TransformCircleToPolygon( holeBuffer, wxPoint( 0, 0 ), aSize.x / 2, SEGS_CNT ); } 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 ); // Needed for legacy canvas only aPolygon->Fracture( SHAPE_POLY_SET::PM_FAST ); }
bool D_PAD::BuildPadDrillShapePolygon( SHAPE_POLY_SET& aCornerBuffer, int aInflateValue, int aError ) const { wxSize drillsize = GetDrillSize(); if( !drillsize.x || !drillsize.y ) return false; if( drillsize.x == drillsize.y ) // usual round hole { int radius = ( drillsize.x / 2 ) + aInflateValue; TransformCircleToPolygon( aCornerBuffer, GetPosition(), radius, aError ); } else // Oblong hole { wxPoint start, end; int width; GetOblongDrillGeometry( start, end, width ); width += aInflateValue * 2; TransformRoundedEndsSegmentToPolygon( aCornerBuffer, GetPosition() + start, GetPosition() + end, aError, width ); } return true; }
/** * Function TransformRingToPolygon * Creates a polygon from a ring * Convert arcs to multiple straight segments * @param aCornerBuffer = a buffer to store the polygon * @param aCentre = centre of the arc or circle * @param aRadius = radius of the circle * @param aCircleToSegmentsCount = the number of segments to approximate a circle * @param aWidth = width (thickness) of the ring */ void TransformRingToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aCentre, int aRadius, int aCircleToSegmentsCount, int aWidth ) { // Compute the corners positions and creates the poly wxPoint curr_point; int inner_radius = aRadius - ( aWidth / 2 ); int outer_radius = inner_radius + aWidth; if( inner_radius <= 0 ) { //In this case, the ring is just a circle (no hole inside) TransformCircleToPolygon( aCornerBuffer, aCentre, aRadius + ( aWidth / 2 ), aCircleToSegmentsCount ); return; } aCornerBuffer.NewOutline(); // Draw the inner circle of the ring int delta = 3600 / aCircleToSegmentsCount; // rotate angle in 0.1 degree for( int ii = 0; ii < 3600; ii += delta ) { curr_point.x = inner_radius; curr_point.y = 0; RotatePoint( &curr_point, ii ); curr_point += aCentre; aCornerBuffer.Append( curr_point.x, curr_point.y ); } // Draw the last point of inner circle aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y ); // Draw the outer circle of the ring // the first point creates also a segment from the inner to the outer polygon for( int ii = 0; ii < 3600; ii += delta ) { curr_point.x = outer_radius; curr_point.y = 0; RotatePoint( &curr_point, -ii ); curr_point += aCentre; aCornerBuffer.Append( curr_point.x, curr_point.y ); } // Draw the last point of outer circle aCornerBuffer.Append( aCentre.x + outer_radius, aCentre.y ); // And connect the outer polygon to the inner polygon,. // because a segment from inner to the outer polygon was already created, // the final polygon is the inner and the outer outlines connected by // 2 overlapping segments aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y ); }
void TRACK::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aError, bool ignoreLineWidth ) const { wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for tracks." ); int radius = ( m_Width / 2 ) + aClearanceValue; switch( Type() ) { case PCB_VIA_T: { TransformCircleToPolygon( aCornerBuffer, m_Start, radius, aError ); } break; default: TransformOvalClearanceToPolygon( aCornerBuffer, m_Start, m_End, m_Width + ( 2 * aClearanceValue ), aError ); break; } }
/* Merge all basic shapes, converted to a polygon in one polygon, * return true if OK, false in there is more than one polygon * in aMergedPolygon */ bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon ) { auto board = GetBoard(); int maxError = ARC_HIGH_DEF; if( board ) maxError = board->GetDesignSettings().m_MaxError; // if aMergedPolygon == NULL, use m_customShapeAsPolygon as target if( !aMergedPolygon ) aMergedPolygon = &m_customShapeAsPolygon; aMergedPolygon->RemoveAllContours(); // Add the anchor pad shape in aMergedPolygon, others in aux_polyset: // The anchor pad is always at 0,0 switch( GetAnchorPadShape() ) { default: case PAD_SHAPE_CIRCLE: TransformCircleToPolygon( *aMergedPolygon, wxPoint( 0, 0 ), GetSize().x / 2, maxError ); break; case PAD_SHAPE_RECT: { SHAPE_RECT rect( -GetSize().x / 2, -GetSize().y / 2, GetSize().x, GetSize().y ); aMergedPolygon->AddOutline( rect.Outline() ); break; } } if( !buildCustomPadPolygon( aMergedPolygon, maxError ) ) return false; m_boundingRadius = -1; // The current bouding radius is no more valid. return aMergedPolygon->OutlineCount() <= 1; }
/** * Function TransformShapeWithClearanceToPolygon * Convert the track shape to a closed polygon * Used in filling zones calculations * Circles (vias) and arcs (ends of tracks) are approximated by segments * @param aCornerBuffer = a buffer to store the polygon * @param aClearanceValue = the clearance around the pad * @param aCircleToSegmentsCount = the number of segments to approximate a circle * @param aCorrectionFactor = the correction to apply to circles radius to keep * clearance when the circle is approximated by segment bigger or equal * to the real clearance value (usually near from 1.0) */ void TRACK::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aCircleToSegmentsCount, double aCorrectionFactor ) const { switch( Type() ) { case PCB_VIA_T: { int radius = (m_Width / 2) + aClearanceValue; radius = KiROUND( radius * aCorrectionFactor ); TransformCircleToPolygon( aCornerBuffer, m_Start, radius, aCircleToSegmentsCount ); } break; default: TransformRoundedEndsSegmentToPolygon( aCornerBuffer, m_Start, m_End, aCircleToSegmentsCount, m_Width + ( 2 * aClearanceValue) ); break; } }
void EDA_3D_CANVAS::buildBoardThroughHolesPolygonList( SHAPE_POLY_SET& allBoardHoles, int aSegCountPerCircle, bool aOptimizeLargeCircles ) { // hole diameter value to change seg count by circle: int small_hole_limit = Millimeter2iu( 1.0 ); int copper_thickness = GetPrm3DVisu().GetCopperThicknessBIU(); BOARD* pcb = GetBoard(); // Build holes of through vias: for( TRACK* track = pcb->m_Track; track; track = track->Next() ) { if( track->Type() != PCB_VIA_T ) continue; VIA *via = static_cast<VIA*>( track ); if( via->GetViaType() != VIA_THROUGH ) continue; int holediameter = via->GetDrillValue(); int hole_outer_radius = (holediameter + copper_thickness) / 2; TransformCircleToPolygon( allBoardHoles, via->GetStart(), hole_outer_radius, aSegCountPerCircle ); } // Build holes of through pads: for( MODULE* footprint = pcb->m_Modules; footprint; footprint = footprint->Next() ) { for( D_PAD* pad = footprint->Pads(); pad; pad = pad->Next() ) { // Calculate a factor to apply to segcount for large holes ( > 1 mm) // (bigger pad drill size -> more segments) because holes in pads can have // very different sizes and optimizing this segcount gives a better look // Mainly mounting holes have a size bigger than small_hole_limit wxSize padHole = pad->GetDrillSize(); if( ! padHole.x ) // Not drilled pad like SMD pad continue; // we use the hole diameter to calculate the seg count. // for round holes, padHole.x == padHole.y // for oblong holes, the diameter is the smaller of (padHole.x, padHole.y) int diam = std::min( padHole.x, padHole.y ); int segcount = aSegCountPerCircle; if( diam > small_hole_limit ) { double segFactor = (double)diam / small_hole_limit; segcount = (int)(aSegCountPerCircle * segFactor); // limit segcount to 48. For a circle this is a very good approx. if( segcount > 48 ) segcount = 48; } // The hole in the body is inflated by copper thickness. int inflate = copper_thickness; // If not plated, no copper. if( pad->GetAttribute () == PAD_HOLE_NOT_PLATED ) inflate = 0; pad->BuildPadDrillShapePolygon( allBoardHoles, inflate, segcount ); } } allBoardHoles.Simplify(); }
void EDA_3D_CANVAS::buildBoard3DView( GLuint aBoardList, GLuint aBodyOnlyList, REPORTER* aErrorMessages, REPORTER* aActivity ) { BOARD* pcb = GetBoard(); // If FL_RENDER_SHOW_HOLES_IN_ZONES is true, holes are correctly removed from copper zones areas. // If FL_RENDER_SHOW_HOLES_IN_ZONES is false, holes are not removed from copper zones areas, // but the calculation time is twice shorter. bool remove_Holes = isEnabled( FL_RENDER_SHOW_HOLES_IN_ZONES ); bool realistic_mode = isRealisticMode(); bool useTextures = isRealisticMode() && isEnabled( FL_RENDER_TEXTURES ); // Number of segments to convert a circle to polygon // We use 2 values: the first gives a good shape (for instanes rond pads) // the second is used to speed up calculations, when a poor approximation is acceptable (holes) const int segcountforcircle = 18; double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2.0) ); const int segcountLowQuality = 12; // segments to draw a circle with low quality // to reduce time calculations // for holes and items which do not need // a fine representation double correctionFactorLQ = 1.0 / cos( M_PI / (segcountLowQuality * 2.0) ); SHAPE_POLY_SET bufferPolys; // copper areas: tracks, pads and filled zones areas // when holes are removed from zones SHAPE_POLY_SET bufferPcbOutlines; // stores the board main outlines SHAPE_POLY_SET bufferZonesPolys; // copper filled zones areas // when holes are not removed from zones SHAPE_POLY_SET currLayerHoles; // Contains holes for the current layer SHAPE_POLY_SET allLayerHoles; // Contains holes for all layers // Build a polygon from edge cut items wxString msg; if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) ) { if( aErrorMessages ) { msg << wxT("\n") << _("Unable to calculate the board outlines.\n" "Therefore use the board boundary box.") << wxT("\n\n"); aErrorMessages->Report( msg, REPORTER::RPT_WARNING ); } } // Build board holes, with optimization of large holes shape. buildBoardThroughHolesPolygonList( allLayerHoles, segcountLowQuality, true ); LSET cu_set = LSET::AllCuMask( GetPrm3DVisu().m_CopperLayersCount ); glNewList( aBoardList, GL_COMPILE ); for( LSEQ cu = cu_set.CuStack(); cu; ++cu ) { LAYER_ID layer = *cu; // Skip non enabled layers in normal mode, // and internal layers in realistic mode if( !is3DLayerEnabled( layer ) ) continue; if( aActivity ) aActivity->Report( wxString::Format( _( "Build layer %s" ), LSET::Name( layer ) ) ); bufferPolys.RemoveAllContours(); bufferZonesPolys.RemoveAllContours(); currLayerHoles.RemoveAllContours(); // Draw track shapes: for( TRACK* track = pcb->m_Track; track; track = track->Next() ) { if( !track->IsOnLayer( layer ) ) continue; track->TransformShapeWithClearanceToPolygon( bufferPolys, 0, segcountforcircle, correctionFactor ); // Add blind/buried via holes if( track->Type() == PCB_VIA_T ) { VIA *via = static_cast<VIA*>( track ); if( via->GetViaType() == VIA_THROUGH ) continue; // already done int holediameter = via->GetDrillValue(); int thickness = GetPrm3DVisu().GetCopperThicknessBIU(); int hole_outer_radius = (holediameter + thickness) / 2; TransformCircleToPolygon( currLayerHoles, via->GetStart(), hole_outer_radius, segcountLowQuality ); } } // draw pad shapes for( MODULE* module = pcb->m_Modules; module; module = module->Next() ) { // Note: NPTH pads are not drawn on copper layers when the pad // has same shape as its hole module->TransformPadsShapesWithClearanceToPolygon( layer, bufferPolys, 0, segcountforcircle, correctionFactor, true ); // Micro-wave modules may have items on copper layers module->TransformGraphicShapesWithClearanceToPolygonSet( layer, bufferPolys, 0, segcountforcircle, correctionFactor ); // pad holes are already in list. } // Draw copper zones. Note: // * if the holes are removed from copper zones // the polygons are stored in bufferPolys (which contains all other polygons) // * if the holes are NOT removed from copper zones // the polygons are stored in bufferZonesPolys if( isEnabled( FL_ZONE ) ) { for( int ii = 0; ii < pcb->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = pcb->GetArea( ii ); LAYER_NUM zonelayer = zone->GetLayer(); if( zonelayer == layer ) { zone->TransformSolidAreasShapesToPolygonSet( remove_Holes ? bufferPolys : bufferZonesPolys, segcountLowQuality, correctionFactorLQ ); } } } // draw graphic items on copper layers (texts) for( BOARD_ITEM* item = pcb->m_Drawings; item; item = item->Next() ) { if( !item->IsOnLayer( layer ) ) continue; switch( item->Type() ) { case PCB_LINE_T: // should not exist on copper layers ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( bufferPolys, 0, segcountforcircle, correctionFactor ); break; case PCB_TEXT_T: ( (TEXTE_PCB*) item )->TransformShapeWithClearanceToPolygonSet( bufferPolys, 0, segcountLowQuality, correctionFactor ); break; default: break; } } // bufferPolys contains polygons to merge. Many overlaps . // Calculate merged polygons if( bufferPolys.IsEmpty() ) continue; // Use Clipper lib to subtract holes to copper areas if( currLayerHoles.OutlineCount() ) { currLayerHoles.Append(allLayerHoles); currLayerHoles.Simplify(); bufferPolys.BooleanSubtract( currLayerHoles ); } else bufferPolys.BooleanSubtract( allLayerHoles ); int thickness = GetPrm3DVisu().GetLayerObjectThicknessBIU( layer ); int zpos = GetPrm3DVisu().GetLayerZcoordBIU( layer ); float zNormal = 1.0f; // When using thickness it will draw first the top and then botton (with z inverted) // If we are not using thickness, then the z-normal has to match the layer direction // because just one plane will be drawn if( !thickness ) zNormal = Get3DLayer_Z_Orientation( layer ); if( realistic_mode ) { setGLCopperColor(); } else { EDA_COLOR_T color = g_ColorsSettings.GetLayerColor( layer ); SetGLColor( color ); } // If holes are removed from copper zones, bufferPolys contains all polygons // to draw (tracks+zones+texts). Draw3D_SolidHorizontalPolyPolygons( bufferPolys, zpos, thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, zNormal ); // If holes are not removed from copper zones (for calculation time reasons, // the zone polygons are stored in bufferZonesPolys and have to be drawn now: if( !bufferZonesPolys.IsEmpty() ) { Draw3D_SolidHorizontalPolyPolygons( bufferZonesPolys, zpos, thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, zNormal ); } } if( aActivity ) aActivity->Report( _( "Build board body" ) ); // Draw plated vertical holes inside the board, but not always. They are drawn: // - if the board body is not shown, to show the holes. // - or if the copper thickness is shown if( !isEnabled( FL_SHOW_BOARD_BODY ) || isEnabled( FL_USE_COPPER_THICKNESS ) ) { // Draw vias holes (vertical cylinders) for( const TRACK* track = pcb->m_Track; track; track = track->Next() ) { if( track->Type() == PCB_VIA_T ) { const VIA *via = static_cast<const VIA*>(track); draw3DViaHole( via ); } } // Draw pads holes (vertical cylinders) for( const MODULE* module = pcb->m_Modules; module; module = module->Next() ) { for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() ) if( pad->GetAttribute () != PAD_HOLE_NOT_PLATED ) draw3DPadHole( pad ); } } glEndList(); // Build the body board: glNewList( aBodyOnlyList, GL_COMPILE ); if( isRealisticMode() ) { setGLEpoxyColor( 1.00 ); } else { EDA_COLOR_T color = g_ColorsSettings.GetLayerColor( Edge_Cuts ); SetGLColor( color, 0.7 ); } float copper_thickness = GetPrm3DVisu().GetCopperThicknessBIU(); // a small offset between substrate and external copper layer to avoid artifacts // when drawing copper items on board float epsilon = Millimeter2iu( 0.01 ); float zpos = GetPrm3DVisu().GetLayerZcoordBIU( B_Cu ); float board_thickness = GetPrm3DVisu().GetLayerZcoordBIU( F_Cu ) - GetPrm3DVisu().GetLayerZcoordBIU( B_Cu ); // items on copper layers and having a thickness = copper_thickness // are drawn from zpos - copper_thickness/2 to zpos + copper_thickness // therefore substrate position is copper_thickness/2 to // substrate_height - copper_thickness/2 zpos += (copper_thickness + epsilon) / 2.0f; board_thickness -= copper_thickness + epsilon; bufferPcbOutlines.BooleanSubtract( allLayerHoles ); if( !bufferPcbOutlines.IsEmpty() ) { Draw3D_SolidHorizontalPolyPolygons( bufferPcbOutlines, zpos + board_thickness / 2.0, board_thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, 1.0f ); } glEndList(); }
void D_PAD::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aError, bool ignoreLineWidth ) const { wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for pads." ); double angle = m_Orient; int dx = (m_Size.x / 2) + aClearanceValue; int dy = (m_Size.y / 2) + aClearanceValue; wxPoint padShapePos = ShapePos(); /* Note: for pad having a shape offset, * the pad position is NOT the shape position */ switch( GetShape() ) { case PAD_SHAPE_CIRCLE: { TransformCircleToPolygon( aCornerBuffer, padShapePos, dx, aError ); } break; case PAD_SHAPE_OVAL: // An oval pad has the same shape as a segment with rounded ends { int width; wxPoint shape_offset; if( dy > dx ) // Oval pad X/Y ratio for choosing translation axis { shape_offset.y = dy - dx; width = dx * 2; } else //if( dy <= dx ) { shape_offset.x = dy - dx; width = dy * 2; } RotatePoint( &shape_offset, angle ); wxPoint start = padShapePos - shape_offset; wxPoint end = padShapePos + shape_offset; TransformOvalClearanceToPolygon( aCornerBuffer, start, end, width, aError ); } break; case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_RECT: { wxPoint corners[4]; BuildPadPolygon( corners, wxSize( 0, 0 ), angle ); SHAPE_POLY_SET outline; outline.NewOutline(); for( int ii = 0; ii < 4; ii++ ) { corners[ii] += padShapePos; outline.Append( corners[ii].x, corners[ii].y ); } int numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int rounding_radius = KiROUND( aClearanceValue * correction ); outline.Inflate( rounding_radius, numSegs ); aCornerBuffer.Append( outline ); } break; case PAD_SHAPE_CHAMFERED_RECT: case PAD_SHAPE_ROUNDRECT: { SHAPE_POLY_SET outline; int radius = GetRoundRectCornerRadius() + aClearanceValue; int numSegs = std::max( GetArcToSegmentCount( radius, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int clearance = KiROUND( aClearanceValue * correction ); int rounding_radius = GetRoundRectCornerRadius() + clearance; wxSize shapesize( m_Size ); shapesize.x += clearance * 2; shapesize.y += clearance * 2; bool doChamfer = GetShape() == PAD_SHAPE_CHAMFERED_RECT; TransformRoundChamferedRectToPolygon( outline, padShapePos, shapesize, angle, rounding_radius, doChamfer ? GetChamferRectRatio() : 0.0, doChamfer ? GetChamferPositions() : 0, aError ); aCornerBuffer.Append( outline ); } break; case PAD_SHAPE_CUSTOM: { int numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int clearance = KiROUND( aClearanceValue * correction ); SHAPE_POLY_SET outline; // Will contain the corners in board coordinates outline.Append( m_customShapeAsPolygon ); CustomShapeAsPolygonToBoardPosition( &outline, GetPosition(), GetOrientation() ); outline.Simplify( SHAPE_POLY_SET::PM_FAST ); outline.Inflate( clearance, numSegs ); outline.Fracture( SHAPE_POLY_SET::PM_FAST ); aCornerBuffer.Append( outline ); } break; } }
void AM_PRIMITIVE::DrawBasicShape( const GERBER_DRAW_ITEM* aParent, SHAPE_POLY_SET& aShapeBuffer, wxPoint aShapePos ) { #define TO_POLY_SHAPE { aShapeBuffer.NewOutline(); \ for( unsigned jj = 0; jj < polybuffer.size(); jj++ )\ aShapeBuffer.Append( polybuffer[jj].x, polybuffer[jj].y );\ aShapeBuffer.Append( polybuffer[0].x, polybuffer[0].y );} // Draw the primitive shape for flashed items. static std::vector<wxPoint> polybuffer; // create a static buffer to avoid a lot of memory reallocation polybuffer.clear(); wxPoint curPos = aShapePos; D_CODE* tool = aParent->GetDcodeDescr(); double rotation; switch( primitive_id ) { case AMP_CIRCLE: // Circle, given diameter and position { /* Generated by an aperture macro declaration like: * "1,1,0.3,0.5, 1.0*" * type (1), exposure, diameter, pos.x, pos.y, <rotation> * <rotation> is a optional parameter: rotation from origin. * type is not stored in parameters list, so the first parameter is exposure */ ConvertShapeToPolygon( aParent, polybuffer ); // shape rotation (if any): if( params.size() >= 5 ) { rotation = params[4].GetValue( tool ) * 10.0; if( rotation != 0) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( &polybuffer[ii], -rotation ); } } // Move to current position: for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; } break; case AMP_LINE2: case AMP_LINE20: // Line with rectangle ends. (Width, start and end pos + rotation) { /* Vector Line, Primitive Code 20. * A vector line is a rectangle defined by its line width, start and end points. * The line ends are rectangular. */ /* Generated by an aperture macro declaration like: * "2,1,0.3,0,0, 0.5, 1.0,-135*" * type (2), exposure, width, start.x, start.y, end.x, end.y, rotation * type is not stored in parameters list, so the first parameter is exposure */ ConvertShapeToPolygon( aParent, polybuffer ); // shape rotation: rotation = params[6].GetValue( tool ) * 10.0; if( rotation != 0) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( &polybuffer[ii], -rotation ); } // Move to current position: for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; } break; case AMP_LINE_CENTER: { /* Center Line, Primitive Code 21 * A center line primitive is a rectangle defined by its width, height, and center point */ /* Generated by an aperture macro declaration like: * "21,1,0.3,0.03,0,0,-135*" * type (21), exposure, ,width, height, center pos.x, center pos.y, rotation * type is not stored in parameters list, so the first parameter is exposure */ ConvertShapeToPolygon( aParent, polybuffer ); // shape rotation: rotation = params[5].GetValue( tool ) * 10.0; if( rotation != 0 ) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( &polybuffer[ii], -rotation ); } // Move to current position: for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; } break; case AMP_LINE_LOWER_LEFT: { /* Generated by an aperture macro declaration like: * "22,1,0.3,0.03,0,0,-135*" * type (22), exposure, ,width, height, corner pos.x, corner pos.y, rotation * type is not stored in parameters list, so the first parameter is exposure */ ConvertShapeToPolygon( aParent, polybuffer ); // shape rotation: rotation = params[5].GetValue( tool ) * 10.0; if( rotation != 0) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( &polybuffer[ii], -rotation ); } // Move to current position: for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; } break; case AMP_THERMAL: { /* Generated by an aperture macro declaration like: * "7, 0,0,1.0,0.3,0.01,-13*" * type (7), center.x , center.y, outside diam, inside diam, crosshair thickness, rotation * type is not stored in parameters list, so the first parameter is center.x * * The thermal primitive is a ring (annulus) interrupted by four gaps. Exposure is always on. */ std::vector<wxPoint> subshape_poly; curPos += mapPt( params[0].GetValue( tool ), params[1].GetValue( tool ), m_GerbMetric ); ConvertShapeToPolygon( aParent, subshape_poly ); // shape rotation: rotation = params[5].GetValue( tool ) * 10.0; // Because a thermal shape has 4 identical sub-shapes, only one is created in subshape_poly. // We must draw 4 sub-shapes rotated by 90 deg for( int ii = 0; ii < 4; ii++ ) { polybuffer = subshape_poly; double sub_rotation = rotation + 900 * ii; for( unsigned jj = 0; jj < polybuffer.size(); jj++ ) RotatePoint( &polybuffer[jj], -sub_rotation ); // Move to current position: for( unsigned jj = 0; jj < polybuffer.size(); jj++ ) { polybuffer[jj] += curPos; polybuffer[jj] = aParent->GetABPosition( polybuffer[jj] ); } TO_POLY_SHAPE; } } break; case AMP_MOIRE: { /* Moir�, Primitive Code 6 * The moir� primitive is a cross hair centered on concentric rings (annuli). * Exposure is always on. */ curPos += mapPt( params[0].GetValue( tool ), params[1].GetValue( tool ), m_GerbMetric ); /* Generated by an aperture macro declaration like: * "6,0,0,0.125,.01,0.01,3,0.003,0.150,0" * type(6), pos.x, pos.y, diam, penwidth, gap, circlecount, crosshair thickness, crosshaire len, rotation * type is not stored in parameters list, so the first parameter is pos.x */ int outerDiam = scaletoIU( params[2].GetValue( tool ), m_GerbMetric ); int penThickness = scaletoIU( params[3].GetValue( tool ), m_GerbMetric ); int gap = scaletoIU( params[4].GetValue( tool ), m_GerbMetric ); int numCircles = KiROUND( params[5].GetValue( tool ) ); // Draw circles: wxPoint center = aParent->GetABPosition( curPos ); // adjust outerDiam by this on each nested circle int diamAdjust = (gap + penThickness) * 2; for( int i = 0; i < numCircles; ++i, outerDiam -= diamAdjust ) { if( outerDiam <= 0 ) break; // Note: outerDiam is the outer diameter of the ring. // the ring graphic diameter is (outerDiam - penThickness) if( outerDiam <= penThickness ) { // No room to draw a ring (no room for the hole): // draw a circle instead (with no hole), with the right diameter TransformCircleToPolygon( aShapeBuffer, center, outerDiam / 2, seg_per_circle ); } else TransformRingToPolygon( aShapeBuffer, center, (outerDiam - penThickness) / 2, seg_per_circle, penThickness ); } // Draw the cross: ConvertShapeToPolygon( aParent, polybuffer ); rotation = params[8].GetValue( tool ) * 10.0; for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { // shape rotation: RotatePoint( &polybuffer[ii], -rotation ); // Move to current position: polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; } break; case AMP_OUTLINE: { /* Outline, Primitive Code 4 * An outline primitive is an area enclosed by an n-point polygon defined by its start point and n * subsequent points. The outline must be closed, i.e. the last point must be equal to the start * point. There must be at least one subsequent point (to close the outline). * The outline of the primitive is actually the contour (see 2.6) that consists of linear segments * only, so it must conform to all the requirements described for contours. * Warning: Make no mistake: n is the number of subsequent points, being the number of * vertices of the outline or one less than the number of coordinate pairs. */ /* Generated by an aperture macro declaration like: * "4,1,3,0.0,0.0,0.0,0.5,0.5,0.5,0.5,0.0,-25" * type(4), exposure, corners count, corner1.x, corner.1y, ..., corner1.x, corner.1y, rotation * type is not stored in parameters list, so the first parameter is exposure */ // params[0] is the exposure and params[1] is the corners count after the first corner int numCorners = (int) params[1].GetValue( tool ); // the shape rotation is the last param of list, after corners int last_prm = params.size() - 1; rotation = params[last_prm].GetValue( tool ) * 10.0; wxPoint pos; // Read points. // Note: numCorners is the polygon corner count, following the first corner // * the polygon is always closed, // * therefore the last XY coordinate is the same as the first int prm_idx = 2; // params[2] is the first X coordinate for( int i = 0; i <= numCorners; ++i ) { pos.x = scaletoIU( params[prm_idx].GetValue( tool ), m_GerbMetric ); prm_idx++; pos.y = scaletoIU( params[prm_idx].GetValue( tool ), m_GerbMetric ); prm_idx++; polybuffer.push_back(pos); // Guard: ensure prm_idx < last_prm // I saw malformed gerber files with numCorners = number // of coordinates instead of number of coordinates following the first point if( prm_idx >= last_prm ) break; } // rotate polygon and move it to the actual position // shape rotation: for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { RotatePoint( &polybuffer[ii], -rotation ); } // Move to current position: for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; } break; case AMP_POLYGON: /* Polygon, Primitive Code 5 * A polygon primitive is a regular polygon defined by the number of vertices n, the center point * and the diameter of the circumscribed circle */ /* Generated by an aperture macro declaration like: * "5,1,0.6,0,0,0.5,25" * type(5), exposure, vertices count, pox.x, pos.y, diameter, rotation * type is not stored in parameters list, so the first parameter is exposure */ curPos += mapPt( params[2].GetValue( tool ), params[3].GetValue( tool ), m_GerbMetric ); // Creates the shape: ConvertShapeToPolygon( aParent, polybuffer ); // rotate polygon and move it to the actual position rotation = params[5].GetValue( tool ) * 10.0; for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { RotatePoint( &polybuffer[ii], -rotation ); polybuffer[ii] += curPos; polybuffer[ii] = aParent->GetABPosition( polybuffer[ii] ); } TO_POLY_SHAPE; break; case AMP_EOF: // not yet supported, waiting for you. break; case AMP_UNKNOWN: default: DBG( printf( "AM_PRIMITIVE::DrawBasicShape() err: unknown prim id %d\n",primitive_id) ); break; } }
bool D_PAD::buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError ) { SHAPE_POLY_SET aux_polyset; for( unsigned cnt = 0; cnt < m_basicShapes.size(); ++cnt ) { const PAD_CS_PRIMITIVE& bshape = m_basicShapes[cnt]; switch( bshape.m_Shape ) { case S_CURVE: { std::vector<wxPoint> ctrlPoints = { bshape.m_Start, bshape.m_Ctrl1, bshape.m_Ctrl2, bshape.m_End }; BEZIER_POLY converter( ctrlPoints ); std::vector< wxPoint> poly; converter.GetPoly( poly, bshape.m_Thickness ); for( unsigned ii = 1; ii < poly.size(); ii++ ) { TransformRoundedEndsSegmentToPolygon( aux_polyset, poly[ii - 1], poly[ii], aError, bshape.m_Thickness ); } break; } case S_SEGMENT: // usual segment : line with rounded ends { TransformRoundedEndsSegmentToPolygon( aux_polyset, bshape.m_Start, bshape.m_End, aError, bshape.m_Thickness ); break; } case S_ARC: // Arc with rounded ends { TransformArcToPolygon( aux_polyset, bshape.m_Start, bshape.m_End, bshape.m_ArcAngle, aError, bshape.m_Thickness ); break; } case S_CIRCLE: // ring or circle { if( bshape.m_Thickness ) // ring TransformRingToPolygon( aux_polyset, bshape.m_Start, bshape.m_Radius, aError, bshape.m_Thickness ); else // Filled circle TransformCircleToPolygon( aux_polyset, bshape.m_Start, bshape.m_Radius, aError ); break; } case S_POLYGON: // polygon if( bshape.m_Poly.size() < 2 ) break; // Malformed polygon. { // Insert the polygon: const std::vector< wxPoint>& poly = bshape.m_Poly; aux_polyset.NewOutline(); if( bshape.m_Thickness ) { SHAPE_POLY_SET polyset; polyset.NewOutline(); for( unsigned ii = 0; ii < poly.size(); ii++ ) { polyset.Append( poly[ii].x, poly[ii].y ); } int numSegs = std::max( GetArcToSegmentCount( bshape.m_Thickness / 2, aError, 360.0 ), 6 ); polyset.Inflate( bshape.m_Thickness / 2, numSegs ); aux_polyset.Append( polyset ); } else for( unsigned ii = 0; ii < poly.size(); ii++ ) aux_polyset.Append( poly[ii].x, poly[ii].y ); } break; default: break; } } aux_polyset.Simplify( SHAPE_POLY_SET::PM_FAST ); // Merge all polygons with the initial pad anchor shape if( aux_polyset.OutlineCount() ) { aMergedPolygon->BooleanAdd( aux_polyset, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); aMergedPolygon->Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } return aMergedPolygon->OutlineCount() <= 1; }
/* Function TransformShapeWithClearanceToPolygon * Convert the pad shape to a closed polygon * Used in filling zones calculations and 3D view generation * Circles and arcs are approximated by segments * aCornerBuffer = a SHAPE_POLY_SET to store the polygon corners * aClearanceValue = the clearance around the pad * aCircleToSegmentsCount = the number of segments to approximate a circle * aCorrectionFactor = the correction to apply to circles radius to keep * clearance when the circle is approximated by segment bigger or equal * to the real clearance value (usually near from 1.0) */ void D_PAD:: TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aCircleToSegmentsCount, double aCorrectionFactor ) const { double angle = m_Orient; int dx = (m_Size.x / 2) + aClearanceValue; int dy = (m_Size.y / 2) + aClearanceValue; wxPoint PadShapePos = ShapePos(); /* Note: for pad having a shape offset, * the pad position is NOT the shape position */ switch( GetShape() ) { case PAD_SHAPE_CIRCLE: dx = KiROUND( dx * aCorrectionFactor ); TransformCircleToPolygon( aCornerBuffer, PadShapePos, dx, aCircleToSegmentsCount ); break; case PAD_SHAPE_OVAL: // An oval pad has the same shape as a segment with rounded ends { int width; wxPoint shape_offset; if( dy > dx ) // Oval pad X/Y ratio for choosing translation axis { dy = KiROUND( dy * aCorrectionFactor ); shape_offset.y = dy - dx; width = dx * 2; } else //if( dy <= dx ) { dx = KiROUND( dx * aCorrectionFactor ); shape_offset.x = dy - dx; width = dy * 2; } RotatePoint( &shape_offset, angle ); wxPoint start = PadShapePos - shape_offset; wxPoint end = PadShapePos + shape_offset; TransformRoundedEndsSegmentToPolygon( aCornerBuffer, start, end, aCircleToSegmentsCount, width ); } break; case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_RECT: { wxPoint corners[4]; BuildPadPolygon( corners, wxSize( 0, 0 ), angle ); SHAPE_POLY_SET outline; outline.NewOutline(); for( int ii = 0; ii < 4; ii++ ) { corners[ii] += PadShapePos; outline.Append( corners[ii].x, corners[ii].y ); } double rounding_radius = aClearanceValue * aCorrectionFactor; outline.Inflate( (int) rounding_radius, aCircleToSegmentsCount ); aCornerBuffer.Append( outline ); } break; } }
/** * Function ConvertOutlineToPolygon * build a polygon (with holes) from a DRAWSEGMENT list, which is expected to be * a outline, therefore a closed main outline with perhaps closed inner outlines. * These closed inner outlines are considered as holes in the main outline * @param aSegList the initial list of drawsegments (only lines, circles and arcs). * @param aPolygons will contain the complex polygon. * @param aTolerance is the max distance between points that is still accepted as connected (internal units) * @param aErrorText is a wxString to return error message. * @param aErrorLocation is the optional position of the error in the outline */ bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SET& aPolygons, wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation ) { if( aSegList.size() == 0 ) return true; wxString msg; // Make a working copy of aSegList, because the list is modified during calculations std::vector< DRAWSEGMENT* > segList = aSegList; DRAWSEGMENT* graphic; wxPoint prevPt; // Find edge point with minimum x, this should be in the outer polygon // which will define the perimeter Edge.Cuts polygon. wxPoint xmin = wxPoint( INT_MAX, 0 ); int xmini = 0; for( size_t i = 0; i < segList.size(); i++ ) { graphic = (DRAWSEGMENT*) segList[i]; switch( graphic->GetShape() ) { case S_SEGMENT: { if( graphic->GetStart().x < xmin.x ) { xmin = graphic->GetStart(); xmini = i; } if( graphic->GetEnd().x < xmin.x ) { xmin = graphic->GetEnd(); xmini = i; } } break; case S_ARC: // Freerouter does not yet understand arcs, so approximate // an arc with a series of short lines and put those // line segments into the !same! PATH. { wxPoint pstart = graphic->GetArcStart(); wxPoint center = graphic->GetCenter(); double angle = -graphic->GetAngle(); double radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 ); wxPoint pt; for( int step = 1; step<=steps; ++step ) { double rotation = ( angle * step ) / steps; pt = pstart; RotatePoint( &pt, center, rotation ); if( pt.x < xmin.x ) { xmin = pt; xmini = i; } } } break; case S_CIRCLE: { wxPoint pt = graphic->GetCenter(); // pt has minimum x point pt.x -= graphic->GetRadius(); // when the radius <= 0, this is a mal-formed circle. Skip it if( graphic->GetRadius() > 0 && pt.x < xmin.x ) { xmin = pt; xmini = i; } } break; case S_CURVE: { graphic->RebuildBezierToSegmentsPointsList( graphic->GetWidth() ); for( unsigned int jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) { wxPoint pt = graphic->GetBezierPoints()[jj]; if( pt.x < xmin.x ) { xmin = pt; xmini = i; } } } break; case S_POLYGON: { const auto poly = graphic->GetPolyShape(); MODULE* module = aSegList[0]->GetParentModule(); double orientation = module ? module->GetOrientation() : 0.0; VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 ); for( auto iter = poly.CIterate(); iter; iter++ ) { auto pt = *iter; RotatePoint( pt, orientation ); pt += offset; if( pt.x < xmin.x ) { xmin.x = pt.x; xmin.y = pt.y; xmini = i; } } } break; default: break; } } // Grab the left most point, assume its on the board's perimeter, and see if we // can put enough graphics together by matching endpoints to formulate a cohesive // polygon. graphic = (DRAWSEGMENT*) segList[xmini]; // The first DRAWSEGMENT is in 'graphic', ok to remove it from 'items' segList.erase( segList.begin() + xmini ); // Output the Edge.Cuts perimeter as circle or polygon. if( graphic->GetShape() == S_CIRCLE ) { int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_LOW_DEF, 360.0 ); TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(), steps ); } else if( graphic->GetShape() == S_POLYGON ) { MODULE* module = graphic->GetParentModule(); // NULL for items not in footprints double orientation = module ? module->GetOrientation() : 0.0; VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 ); aPolygons.NewOutline(); for( auto it = graphic->GetPolyShape().CIterate( 0 ); it; it++ ) { auto pt = *it; RotatePoint( pt, orientation ); pt += offset; aPolygons.Append( pt ); } } else { // Polygon start point. Arbitrarily chosen end of the // segment and build the poly from here. wxPoint startPt = wxPoint( graphic->GetEnd() ); prevPt = graphic->GetEnd(); aPolygons.NewOutline(); aPolygons.Append( prevPt ); // Do not append the other end point yet of this 'graphic', this first // 'graphic' might be an arc or a curve. for(;;) { switch( graphic->GetShape() ) { case S_SEGMENT: { wxPoint nextPt; // Use the line segment end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) nextPt = graphic->GetEnd(); else nextPt = graphic->GetStart(); aPolygons.Append( nextPt ); prevPt = nextPt; } break; case S_ARC: // We do not support arcs in polygons, so approximate // an arc with a series of short lines and put those // line segments into the !same! PATH. { wxPoint pstart = graphic->GetArcStart(); wxPoint pend = graphic->GetArcEnd(); wxPoint pcenter = graphic->GetCenter(); double angle = -graphic->GetAngle(); double radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 ); if( !close_enough( prevPt, pstart, aTolerance ) ) { wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) ); angle = -angle; std::swap( pstart, pend ); } wxPoint nextPt; for( int step = 1; step<=steps; ++step ) { double rotation = ( angle * step ) / steps; nextPt = pstart; RotatePoint( &nextPt, pcenter, rotation ); aPolygons.Append( nextPt ); } prevPt = nextPt; } break; case S_CURVE: // We do not support Bezier curves in polygons, so approximate // with a series of short lines and put those // line segments into the !same! PATH. { wxPoint nextPt; bool reverse = false; // Use the end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) nextPt = graphic->GetEnd(); else { nextPt = graphic->GetStart(); reverse = true; } if( reverse ) { for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- ) aPolygons.Append( graphic->GetBezierPoints()[jj] ); } else { for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) aPolygons.Append( graphic->GetBezierPoints()[jj] ); } prevPt = nextPt; } break; default: if( aErrorText ) { msg.Printf( "Unsupported DRAWSEGMENT type %s.", BOARD_ITEM::ShowShape( graphic->GetShape() ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = graphic->GetPosition(); return false; } // Get next closest segment. graphic = findPoint( prevPt, segList, aTolerance ); // If there are no more close segments, check if the board // outline polygon can be closed. if( !graphic ) { if( close_enough( startPt, prevPt, aTolerance ) ) { // Close the polygon back to start point // aPolygons.Append( startPt ); // not needed } else { if( aErrorText ) { msg.Printf( _( "Unable to find segment with an endpoint of (%s, %s)." ), StringFromValue( MILLIMETRES, prevPt.x, true ), StringFromValue( MILLIMETRES, prevPt.y, true ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = prevPt; return false; } break; } } } while( segList.size() ) { // emit a signal layers keepout for every interior polygon left... int hole = aPolygons.NewHole(); graphic = (DRAWSEGMENT*) segList[0]; segList.erase( segList.begin() ); // Both circles and polygons on the edge cuts layer are closed items that // do not connect to other elements, so we process them independently if( graphic->GetShape() == S_POLYGON ) { MODULE* module = graphic->GetParentModule(); // NULL for items not in footprints double orientation = module ? module->GetOrientation() : 0.0; VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 ); for( auto it = graphic->GetPolyShape().CIterate(); it; it++ ) { auto val = *it; RotatePoint( val, orientation ); val += offset; aPolygons.Append( val, -1, hole ); } } else if( graphic->GetShape() == S_CIRCLE ) { // make a circle by segments; wxPoint center = graphic->GetCenter(); double angle = 3600.0; wxPoint start = center; int radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, 360.0 ); wxPoint nextPt; start.x += radius; for( int step = 0; step < steps; ++step ) { double rotation = ( angle * step ) / steps; nextPt = start; RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation ); aPolygons.Append( nextPt, -1, hole ); } } else { // Polygon start point. Arbitrarily chosen end of the // segment and build the poly from here. wxPoint startPt( graphic->GetEnd() ); prevPt = graphic->GetEnd(); aPolygons.Append( prevPt, -1, hole ); // do not append the other end point yet, this first 'graphic' might be an arc for(;;) { switch( graphic->GetShape() ) { case S_SEGMENT: { wxPoint nextPt; // Use the line segment end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) { nextPt = graphic->GetEnd(); } else { nextPt = graphic->GetStart(); } prevPt = nextPt; aPolygons.Append( prevPt, -1, hole ); } break; case S_ARC: // Freerouter does not yet understand arcs, so approximate // an arc with a series of short lines and put those // line segments into the !same! PATH. { wxPoint pstart = graphic->GetArcStart(); wxPoint pend = graphic->GetArcEnd(); wxPoint pcenter = graphic->GetCenter(); double angle = -graphic->GetAngle(); int radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 ); if( !close_enough( prevPt, pstart, aTolerance ) ) { wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) ); angle = -angle; std::swap( pstart, pend ); } wxPoint nextPt; for( int step = 1; step <= steps; ++step ) { double rotation = ( angle * step ) / steps; nextPt = pstart; RotatePoint( &nextPt, pcenter, rotation ); aPolygons.Append( nextPt, -1, hole ); } prevPt = nextPt; } break; case S_CURVE: // We do not support Bezier curves in polygons, so approximate // with a series of short lines and put those // line segments into the !same! PATH. { wxPoint nextPt; bool reverse = false; // Use the end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) nextPt = graphic->GetEnd(); else { nextPt = graphic->GetStart(); reverse = true; } if( reverse ) { for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- ) aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole ); } else { for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole ); } prevPt = nextPt; } break; default: if( aErrorText ) { msg.Printf( "Unsupported DRAWSEGMENT type %s.", BOARD_ITEM::ShowShape( graphic->GetShape() ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = graphic->GetPosition(); return false; } // Get next closest segment. graphic = findPoint( prevPt, segList, aTolerance ); // If there are no more close segments, check if polygon // can be closed. if( !graphic ) { if( close_enough( startPt, prevPt, aTolerance ) ) { // Close the polygon back to start point // aPolygons.Append( startPt, -1, hole ); // not needed } else { if( aErrorText ) { msg.Printf( _( "Unable to find segment with an endpoint of (%s, %s)." ), StringFromValue( MILLIMETRES, prevPt.x, true ), StringFromValue( MILLIMETRES, prevPt.y, true ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = prevPt; return false; } break; } } } } return true; }