/* Build a pad outline as non filled polygon, to draw pads on silkscreen layer * Used only to draw pads outlines on silkscreen layers. */ void EDA_3D_CANVAS::BuildPadShapeThickOutlineAsPolygon( const D_PAD* aPad, CPOLYGONS_LIST& aCornerBuffer, int aWidth, int aCircleToSegmentsCount, double aCorrectionFactor ) { if( aPad->GetShape() == PAD_CIRCLE ) // Draw a ring { TransformRingToPolygon( aCornerBuffer, aPad->ShapePos(), aPad->GetSize().x / 2, aCircleToSegmentsCount, aWidth ); return; } // For other shapes, draw polygon outlines CPOLYGONS_LIST corners; aPad->BuildPadShapePolygon( corners, wxSize( 0, 0 ), aCircleToSegmentsCount, aCorrectionFactor ); // Add outlines as thick segments in polygon buffer for( unsigned ii = 0, jj = corners.GetCornersCount() - 1; ii < corners.GetCornersCount(); jj = ii, ii++ ) { TransformRoundedEndsSegmentToPolygon( aCornerBuffer, corners.GetPos( jj ), corners.GetPos( ii ), aCircleToSegmentsCount, aWidth ); } }
const CPOLYGONS_LIST ConvertPolySetToPolyList(const SHAPE_POLY_SET& aPolyset) { CPOLYGONS_LIST list; CPolyPt corner, firstCorner; const SHAPE_POLY_SET::POLYGON& poly = aPolyset.CPolygon( 0 ); for( unsigned int jj = 0; jj < poly.size() ; jj++ ) { const SHAPE_LINE_CHAIN& path = poly[jj]; for( int i = 0; i < path.PointCount(); i++ ) { const VECTOR2I &v = path.CPoint( i ); corner.x = v.x; corner.y = v.y; corner.end_contour = false; if( i == 0 ) firstCorner = corner; list.AddCorner( corner ); } firstCorner.end_contour = true; list.AddCorner( firstCorner ); } return list; }
const SHAPE_POLY_SET ConvertPolyListToPolySet( const CPOLYGONS_LIST& aList ) { SHAPE_POLY_SET rv; unsigned corners_count = aList.GetCornersCount(); // Enter main outline: this is the first contour unsigned ic = 0; if( !corners_count ) return rv; int index = 0; while( ic < corners_count ) { int hole = -1; if( index == 0 ) { rv.NewOutline(); hole = -1; } else { hole = rv.NewHole(); } while( ic < corners_count ) { rv.Append( aList.GetX( ic ), aList.GetY( ic ), 0, hole ); if( aList.IsEndContour( ic ) ) break; ic++; } ic++; index++; } return rv; }
/* Draw3D_VerticalPolygonalCylinder is a helper function. * * draws a "vertical cylinder" having a polygon shape * from Z position = aZpos to aZpos + aHeight * Used to create the vertical sides of 3D horizontal shapes with thickness. */ static void Draw3D_VerticalPolygonalCylinder( const CPOLYGONS_LIST& aPolysList, int aHeight, int aZpos, bool aInside, double aBiuTo3DUnits ) { if( aHeight == 0 ) return; std::vector<S3D_VERTEX> coords; coords.resize( 4 ); // Init Z position of the 4 points of a GL_QUAD if( aInside ) { coords[0].z = aZpos; coords[1].z = aZpos + aHeight; } else { coords[0].z = aZpos + aHeight; coords[1].z = aZpos; } coords[2].z = coords[1].z; coords[3].z = coords[0].z; // Draw the vertical polygonal side int startContour = 0; for( unsigned ii = 0; ii < aPolysList.GetCornersCount(); ii++ ) { unsigned jj = ii + 1; if( aPolysList.IsEndContour( ii ) || jj >= aPolysList.GetCornersCount() ) { jj = startContour; startContour = ii + 1; } // Build the 4 vertices of each GL_QUAD coords[0].x = aPolysList.GetX( ii ); coords[0].y = -aPolysList.GetY( ii ); coords[1].x = coords[0].x; coords[1].y = coords[0].y; // only z change coords[2].x = aPolysList.GetX( jj ); coords[2].y = -aPolysList.GetY( jj ); coords[3].x = coords[2].x; coords[3].y = coords[2].y; // only z change // Creates the GL_QUAD TransfertToGLlist( coords, aBiuTo3DUnits ); } }
// board edges and cutouts static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb ) { CPOLYGONS_LIST bufferPcbOutlines; // stores the board main outlines CPOLYGONS_LIST allLayerHoles; // Contains through holes, calculated only once allLayerHoles.reserve( 20000 ); // Build a polygon from edge cut items wxString msg; if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) ) { msg << wxT( "\n\n" ) << _( "Unable to calculate the board outlines;\n" "fall back to using the board boundary box." ); wxMessageBox( msg ); } double scale = aModel.scale; int i = 0; int seg; // deal with the solid outlines int nvert = bufferPcbOutlines.GetCornersCount(); while( i < nvert ) { seg = aModel.board.NewContour(); if( seg < 0 ) { msg << wxT( "\n\n" ) << _( "VRML Export Failed:\nCould not add outline to contours." ); wxMessageBox( msg ); return; } while( i < nvert ) { if( bufferPcbOutlines[i].end_contour ) break; aModel.board.AddVertex( seg, bufferPcbOutlines[i].x * scale, -(bufferPcbOutlines[i].y * scale ) ); ++i; } aModel.board.EnsureWinding( seg, false ); ++i; } // deal with the holes nvert = allLayerHoles.GetCornersCount(); i = 0; while( i < nvert ) { seg = aModel.holes.NewContour(); if( seg < 0 ) { msg << wxT( "\n\n" ) << _( "VRML Export Failed:\nCould not add holes to contours." ); wxMessageBox( msg ); return; } while( i < nvert ) { if( allLayerHoles[i].end_contour ) break; aModel.holes.AddVertex( seg, allLayerHoles[i].x * scale, -(allLayerHoles[i].y * scale ) ); ++i; } aModel.holes.EnsureWinding( seg, true ); ++i; } }
void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList_NG( BOARD* aPcb ) { int segsPerCircle; double correctionFactor; int outline_half_thickness = m_ZoneMinThickness / 2; std::unique_ptr<SHAPE_FILE_IO> dumper( new SHAPE_FILE_IO( g_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) ); // Set the number of segments in arc approximations if( m_ArcToSegmentsCount == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ) segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF; else segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; /* calculates the coeff to compensate radius reduction of holes clearance * due to the segment approx. * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount ) */ correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); CPOLYGONS_LIST tmp; if(g_DumpZonesWhenFilling) dumper->BeginGroup("clipper-zone"); SHAPE_POLY_SET solidAreas = ConvertPolyListToPolySet( m_smoothedPoly->m_CornersList ); solidAreas.Inflate( -outline_half_thickness, segsPerCircle ); solidAreas.Simplify( POLY_CALC_MODE ); SHAPE_POLY_SET holes; if(g_DumpZonesWhenFilling) dumper->Write( &solidAreas, "solid-areas" ); tmp.RemoveAllContours(); buildFeatureHoleList( aPcb, holes ); if(g_DumpZonesWhenFilling) dumper->Write( &holes, "feature-holes" ); holes.Simplify( POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &holes, "feature-holes-postsimplify" ); solidAreas.BooleanSubtract( holes, POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &solidAreas, "solid-areas-minus-holes" ); SHAPE_POLY_SET areas_fractured = solidAreas; areas_fractured.Fracture( POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &areas_fractured, "areas_fractured" ); m_FilledPolysList = areas_fractured; // Remove insulated islands: if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); SHAPE_POLY_SET thermalHoles; // Test thermal stubs connections and add polygons to remove unconnected stubs. // (this is a refinement for thermal relief shapes) if( GetNetCode() > 0 ) BuildUnconnectedThermalStubsPolygonList( thermalHoles, aPcb, this, correctionFactor, s_thermalRot ); // remove copper areas corresponding to not connected stubs if( !thermalHoles.IsEmpty() ) { thermalHoles.Simplify( POLY_CALC_MODE ); // Remove unconnected stubs solidAreas.BooleanSubtract( thermalHoles, POLY_CALC_MODE ); if( g_DumpZonesWhenFilling ) dumper->Write( &thermalHoles, "thermal-holes" ); // put these areas in m_FilledPolysList SHAPE_POLY_SET th_fractured = solidAreas; th_fractured.Fracture( POLY_CALC_MODE ); if( g_DumpZonesWhenFilling ) dumper->Write ( &th_fractured, "th_fractured" ); m_FilledPolysList = th_fractured; if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); } if(g_DumpZonesWhenFilling) dumper->EndGroup(); }
/* Plot a solder mask layer. * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers, * unless the minimum thickness is 0. * Currently the algo is: * 1 - build all pad shapes as polygons with a size inflated by * mask clearance + (min width solder mask /2) * 2 - Merge shapes * 3 - deflate result by (min width solder mask /2) * 4 - oring result by all pad shapes as polygons with a size inflated by * mask clearance only (because deflate sometimes creates shape artifacts) * 5 - draw result as polygons * * TODO: * make this calculation only for shapes with clearance near than (min width solder mask) * (using DRC algo) * plot all other shapes by flashing the basing shape * (shapes will be better, and calculations faster) */ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LAYER_MSK aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness ) { LAYER_NUM layer = ( aLayerMask & SOLDERMASK_LAYER_BACK ) ? SOLDERMASK_N_BACK : SOLDERMASK_N_FRONT; int inflate = aMinThickness/2; BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); itemplotter.SetLayerMask( aLayerMask ); // Plot edge layer and graphic items itemplotter.PlotBoardGraphicItems(); for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) { for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item->Next() ) { if( layer != item->GetLayer() ) continue; switch( item->Type() ) { case PCB_MODULE_EDGE_T: itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item ); break; default: break; } } } // Build polygons for each pad shape. // the size of the shape on solder mask should be: // size of pad + clearance around the pad. // clearance = solder mask clearance + extra margin // extra margin is half the min width for solder mask // This extra margin is used to merge too close shapes // (distance < aMinThickness), and will be removed when creating // the actual shapes CPOLYGONS_LIST bufferPolys; // Contains shapes to plot CPOLYGONS_LIST initialPolys; // Contains exact shapes to plot /* calculates the coeff to compensate radius reduction of holes clearance * due to the segment approx ( 1 /cos( PI/circleToSegmentsCount ) */ int circleToSegmentsCount = 32; double correction = 1.0 / cos( M_PI / circleToSegmentsCount ); // Plot pads for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) { // add shapes with exact size module->TransformPadsShapesWithClearanceToPolygon( layer, initialPolys, 0, circleToSegmentsCount, correction ); // add shapes inflated by aMinThickness/2 module->TransformPadsShapesWithClearanceToPolygon( layer, bufferPolys, inflate, circleToSegmentsCount, correction ); } // Plot vias on solder masks, if aPlotOpt.GetPlotViaOnMaskLayer() is true, if( aPlotOpt.GetPlotViaOnMaskLayer() ) { // The current layer is a solder mask, // use the global mask clearance for vias int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin; int via_margin = via_clearance + inflate; for( TRACK* track = aBoard->m_Track; track; track = track->Next() ) { const VIA* via = dynamic_cast<const VIA*>( track ); if( !via ) continue; // vias are plotted only if they are on the corresponding // external copper layer LAYER_MSK via_mask_layer = via->GetLayerMask(); if( via_mask_layer & LAYER_BACK ) via_mask_layer |= SOLDERMASK_LAYER_BACK; if( via_mask_layer & LAYER_FRONT ) via_mask_layer |= SOLDERMASK_LAYER_FRONT; if( ( via_mask_layer & aLayerMask ) == 0 ) continue; via->TransformShapeWithClearanceToPolygon( bufferPolys, via_margin, circleToSegmentsCount, correction ); via->TransformShapeWithClearanceToPolygon( initialPolys, via_clearance, circleToSegmentsCount, correction ); } } // Add filled zone areas for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = aBoard->GetArea( ii ); if( zone->GetLayer() != layer ) continue; zone->TransformOutlinesShapeWithClearanceToPolygon( bufferPolys, inflate, true ); } // Now: // 1 - merge areas which are intersecting, i.e. remove gaps // having a thickness < aMinThickness // 2 - deflate resulting areas by aMinThickness/2 KI_POLYGON_SET areasToMerge; bufferPolys.ExportTo( areasToMerge ); KI_POLYGON_SET initialAreas; initialPolys.ExportTo( initialAreas ); // Merge polygons: because each shape was created with an extra margin // = aMinThickness/2, shapes too close ( dist < aMinThickness ) // will be merged, because they are overlapping KI_POLYGON_SET areas; areas |= areasToMerge; // Deflate: remove the extra margin, to create the actual shapes // Here I am using polygon:resize, because this function creates better shapes // than deflate algo. // Use here deflate with arc creation and 18 segments per circle to create arcs // In boost polygon (at least v 1.54 and previous) in very rare cases resize crashes // with 16 segments (perhaps related to 45 degrees pads). So using 18 segments // is a workaround to try to avoid these crashes areas = resize( areas, -inflate , true, 18 ); // Resize slightly changes shapes. So *ensure* initial shapes are kept areas |= initialAreas; // To avoid a lot of code, use a ZONE_CONTAINER // to plot polygons, because they are exactly like // filled areas in zones ZONE_CONTAINER zone( aBoard ); zone.SetArcSegmentCount( 32 ); zone.SetMinThickness( 0 ); // trace polygons only zone.SetLayer ( layer ); zone.CopyPolygonsFromKiPolygonListToFilledPolysList( areas ); itemplotter.PlotFilledAreas( &zone ); }
/* generate shapes of graphic items (outlines) on layer aLayer as polygons, * and adds these polygons to aCornerBuffer * aCornerBuffer = the buffer to store polygons * aInflateValue = a value to inflate shapes * aCircleToSegmentsCount = number of segments to approximate a circle * aCorrectionFactor = the correction to apply to the circle radius * to generate the polygon. * if aCorrectionFactor = 1.0, the polygon is inside the circle * the radius of circle approximated by segments is * initial radius * aCorrectionFactor */ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet( LAYER_NUM aLayer, CPOLYGONS_LIST& aCornerBuffer, int aInflateValue, int aCircleToSegmentsCount, double aCorrectionFactor ) { std::vector<TEXTE_MODULE *> texts; // List of TEXTE_MODULE to convert EDGE_MODULE* outline; for( EDA_ITEM* item = GraphicalItems(); item != NULL; item = item->Next() ) { switch( item->Type() ) { case PCB_MODULE_TEXT_T: if( ((TEXTE_MODULE*)item)->GetLayer() == aLayer ) texts.push_back( (TEXTE_MODULE *) item ); break; case PCB_MODULE_EDGE_T: outline = (EDGE_MODULE*) item; if( outline->GetLayer() != aLayer ) break; switch( outline->GetShape() ) { case S_SEGMENT: TransformRoundedEndsSegmentToPolygon( aCornerBuffer, outline->GetStart(), outline->GetEnd(), aCircleToSegmentsCount, outline->GetWidth() ); break; case S_CIRCLE: TransformRingToPolygon( aCornerBuffer, outline->GetCenter(), outline->GetRadius(), aCircleToSegmentsCount, outline->GetWidth() ); break; case S_ARC: TransformArcToPolygon( aCornerBuffer, outline->GetCenter(), outline->GetArcStart(), outline->GetAngle(), aCircleToSegmentsCount, outline->GetWidth() ); break; case S_POLYGON: // for outline shape = S_POLYGON: // We must compute true coordinates from m_PolyPoints // which are relative to module position and module orientation = 0 for( unsigned ii = 0; ii < outline->GetPolyPoints().size(); ii++ ) { CPolyPt corner( outline->GetPolyPoints()[ii] ); RotatePoint( &corner.x, &corner.y, GetOrientation() ); corner.x += GetPosition().x; corner.y += GetPosition().y; aCornerBuffer.Append( corner ); } aCornerBuffer.CloseLastContour(); break; default: DBG( printf( "Error: Shape %d not implemented!\n", outline->GetShape() ); ) break; } break; default: break; } }
/* Function TransformOutlinesShapeWithClearanceToPolygon * Convert the zone filled areas polygons to polygons * inflated (optional) by max( aClearanceValue, the zone clearance) * and copy them in aCornerBuffer * param aClearanceValue = the clearance around polygons * param aAddClearance = true to add a clearance area to the polygon * false to create the outline polygon. */ void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( CPOLYGONS_LIST& aCornerBuffer, int aClearanceValue, bool aAddClearance ) { // Creates the zone outlines polygon (with linked holes if any) CPOLYGONS_LIST zoneOutines; BuildFilledSolidAreasPolygons( NULL, &zoneOutines ); // add clearance to outline int clearance = 0; if( aAddClearance ) { clearance = GetClearance(); if( aClearanceValue > clearance ) clearance = aClearanceValue; } // Calculate the polygon with clearance // holes are linked to the main outline, so only one polygon should be created. KI_POLYGON_SET polyset_zone_solid_areas; std::vector<KI_POLY_POINT> cornerslist; unsigned ic = 0; unsigned corners_count = zoneOutines.GetCornersCount(); while( ic < corners_count ) { cornerslist.clear(); KI_POLYGON poly; { for( ; ic < corners_count; ic++ ) { CPolyPt* corner = &zoneOutines[ic]; cornerslist.push_back( KI_POLY_POINT( corner->x, corner->y ) ); if( corner->end_contour ) { ic++; break; } } bpl::set_points( poly, cornerslist.begin(), cornerslist.end() ); polyset_zone_solid_areas.push_back( poly ); } } polyset_zone_solid_areas += clearance; // Put the resulting polygon in aCornerBuffer corners list for( unsigned ii = 0; ii < polyset_zone_solid_areas.size(); ii++ ) { KI_POLYGON& poly = polyset_zone_solid_areas[ii]; CPolyPt corner( 0, 0, false ); for( unsigned jj = 0; jj < poly.size(); jj++ ) { KI_POLY_POINT point = *(poly.begin() + jj); corner.x = point.x(); corner.y = point.y(); corner.end_contour = false; aCornerBuffer.Append( corner ); } aCornerBuffer.CloseLastContour(); } }
void BuildUnconnectedThermalStubsPolygonList( CPOLYGONS_LIST& aCornerBuffer, BOARD* aPcb, ZONE_CONTAINER* aZone, double aArcCorrection, double aRoundPadThermalRotation ) { std::vector<wxPoint> corners_buffer; // a local polygon buffer to store one stub corners_buffer.reserve( 4 ); wxPoint ptTest[4]; int zone_clearance = aZone->GetZoneClearance(); EDA_RECT item_boundingbox; EDA_RECT zone_boundingbox = aZone->GetBoundingBox(); int biggest_clearance = aPcb->GetDesignSettings().GetBiggestClearanceValue(); biggest_clearance = std::max( biggest_clearance, zone_clearance ); zone_boundingbox.Inflate( biggest_clearance ); // half size of the pen used to draw/plot zones outlines int pen_radius = aZone->GetMinThickness() / 2; for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) { for( D_PAD* pad = module->Pads(); pad != NULL; pad = pad->Next() ) { // Rejects non-standard pads with tht-only thermal reliefs if( aZone->GetPadConnection( pad ) == THT_THERMAL && pad->GetAttribute() != PAD_STANDARD ) continue; if( aZone->GetPadConnection( pad ) != THERMAL_PAD && aZone->GetPadConnection( pad ) != THT_THERMAL ) continue; // check if( !pad->IsOnLayer( aZone->GetLayer() ) ) continue; if( pad->GetNetCode() != aZone->GetNetCode() ) continue; // Calculate thermal bridge half width int thermalBridgeWidth = aZone->GetThermalReliefCopperBridge( pad ) - aZone->GetMinThickness(); if( thermalBridgeWidth <= 0 ) continue; // we need the thermal bridge half width // with a small extra size to be sure we create a stub // slightly larger than the actual stub thermalBridgeWidth = ( thermalBridgeWidth + 4 ) / 2; int thermalReliefGap = aZone->GetThermalReliefGap( pad ); item_boundingbox = pad->GetBoundingBox(); item_boundingbox.Inflate( thermalReliefGap ); if( !( item_boundingbox.Intersects( zone_boundingbox ) ) ) continue; // Thermal bridges are like a segment from a starting point inside the pad // to an ending point outside the pad // calculate the ending point of the thermal pad, outside the pad wxPoint endpoint; endpoint.x = ( pad->GetSize().x / 2 ) + thermalReliefGap; endpoint.y = ( pad->GetSize().y / 2 ) + thermalReliefGap; // Calculate the starting point of the thermal stub // inside the pad wxPoint startpoint; int copperThickness = aZone->GetThermalReliefCopperBridge( pad ) - aZone->GetMinThickness(); if( copperThickness < 0 ) copperThickness = 0; // Leave a small extra size to the copper area inside to pad copperThickness += KiROUND( IU_PER_MM * 0.04 ); startpoint.x = std::min( pad->GetSize().x, copperThickness ); startpoint.y = std::min( pad->GetSize().y, copperThickness ); startpoint.x /= 2; startpoint.y /= 2; // This is a CIRCLE pad tweak // for circle pads, the thermal stubs orientation is 45 deg double fAngle = pad->GetOrientation(); if( pad->GetShape() == PAD_CIRCLE ) { endpoint.x = KiROUND( endpoint.x * aArcCorrection ); endpoint.y = endpoint.x; fAngle = aRoundPadThermalRotation; } // contour line width has to be taken into calculation to avoid "thermal stub bleed" endpoint.x += pen_radius; endpoint.y += pen_radius; // compute north, south, west and east points for zone connection. ptTest[0] = wxPoint( 0, endpoint.y ); // lower point ptTest[1] = wxPoint( 0, -endpoint.y ); // upper point ptTest[2] = wxPoint( endpoint.x, 0 ); // right point ptTest[3] = wxPoint( -endpoint.x, 0 ); // left point // Test all sides for( int i = 0; i < 4; i++ ) { // rotate point RotatePoint( &ptTest[i], fAngle ); // translate point ptTest[i] += pad->ShapePos(); if( aZone->HitTestFilledArea( ptTest[i] ) ) continue; corners_buffer.clear(); // polygons are rectangles with width of copper bridge value switch( i ) { case 0: // lower stub corners_buffer.push_back( wxPoint( -thermalBridgeWidth, endpoint.y ) ); corners_buffer.push_back( wxPoint( +thermalBridgeWidth, endpoint.y ) ); corners_buffer.push_back( wxPoint( +thermalBridgeWidth, startpoint.y ) ); corners_buffer.push_back( wxPoint( -thermalBridgeWidth, startpoint.y ) ); break; case 1: // upper stub corners_buffer.push_back( wxPoint( -thermalBridgeWidth, -endpoint.y ) ); corners_buffer.push_back( wxPoint( +thermalBridgeWidth, -endpoint.y ) ); corners_buffer.push_back( wxPoint( +thermalBridgeWidth, -startpoint.y ) ); corners_buffer.push_back( wxPoint( -thermalBridgeWidth, -startpoint.y ) ); break; case 2: // right stub corners_buffer.push_back( wxPoint( endpoint.x, -thermalBridgeWidth ) ); corners_buffer.push_back( wxPoint( endpoint.x, thermalBridgeWidth ) ); corners_buffer.push_back( wxPoint( +startpoint.x, thermalBridgeWidth ) ); corners_buffer.push_back( wxPoint( +startpoint.x, -thermalBridgeWidth ) ); break; case 3: // left stub corners_buffer.push_back( wxPoint( -endpoint.x, -thermalBridgeWidth ) ); corners_buffer.push_back( wxPoint( -endpoint.x, thermalBridgeWidth ) ); corners_buffer.push_back( wxPoint( -startpoint.x, thermalBridgeWidth ) ); corners_buffer.push_back( wxPoint( -startpoint.x, -thermalBridgeWidth ) ); break; } // add computed polygon to list for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint cpos = corners_buffer[ic]; RotatePoint( &cpos, fAngle ); // Rotate according to module orientation cpos += pad->ShapePos(); // Shift origin to position CPolyPt corner; corner.x = cpos.x; corner.y = cpos.y; corner.end_contour = ( ic < (corners_buffer.size() - 1) ) ? false : true; aCornerBuffer.Append( corner ); } } } } }
/* draw all solid polygons found in aPolysList * aZpos = z position in board internal units * aThickness = thickness in board internal units * If aThickness = 0, a polygon area is drawn in a XY plane at Z position = aZpos. * If aThickness > 0, a solid object is drawn. * The top side is located at aZpos + aThickness / 2 * The bottom side is located at aZpos - aThickness / 2 */ void Draw3D_SolidHorizontalPolyPolygons( const CPOLYGONS_LIST& aPolysList, int aZpos, int aThickness, double aBiuTo3DUnits, bool aUseTextures ) { // for Tess callback functions: s_biuTo3Dunits = aBiuTo3DUnits; s_useTextures = aUseTextures; GLUtesselator* tess = gluNewTess(); gluTessCallback( tess, GLU_TESS_BEGIN, ( void (CALLBACK*) () )tessBeginCB ); gluTessCallback( tess, GLU_TESS_END, ( void (CALLBACK*) () )tessEndCB ); gluTessCallback( tess, GLU_TESS_ERROR, ( void (CALLBACK*) () )tessErrorCB ); gluTessCallback( tess, GLU_TESS_VERTEX, ( void (CALLBACK*) () )tessCPolyPt2Vertex ); GLdouble v_data[3]; double zpos = ( aZpos + (aThickness / 2.0) ) * aBiuTo3DUnits; s_currentZpos = zpos; // for Tess callback functions v_data[2] = aZpos + (aThickness / 2.0); // Set normal toward positive Z axis, for a solid object on the top side if( aThickness ) SetNormalZpos(); // gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); // Draw solid areas contained in this list CPOLYGONS_LIST polylist = aPolysList; // temporary copy for gluTessVertex int startContour; for( int side = 0; side < 2; side++ ) { startContour = 1; for( unsigned ii = 0; ii < polylist.GetCornersCount(); ii++ ) { if( startContour == 1 ) { gluTessBeginPolygon( tess, NULL ); gluTessBeginContour( tess ); startContour = 0; } // https://www.opengl.org/sdk/docs/man2/xhtml/gluTessNormal.xml gluTessNormal( tess, 0.0, 0.0, 0.0 ); v_data[0] = polylist.GetX( ii ) * aBiuTo3DUnits; v_data[1] = -polylist.GetY( ii ) * aBiuTo3DUnits; // gluTessVertex store pointers on data, not data, so do not store // different corners values in a temporary variable // but send pointer on each CPolyPt value in polylist // before calling gluDeleteTess gluTessVertex( tess, v_data, &polylist[ii] ); if( polylist.IsEndContour( ii ) ) { gluTessEndContour( tess ); gluTessEndPolygon( tess ); startContour = 1; } } if( aThickness == 0 ) break; // Prepare the bottom side of solid areas zpos = ( aZpos - (aThickness / 2.0) ) * aBiuTo3DUnits; s_currentZpos = zpos; // for Tess callback functions v_data[2] = zpos; // Set normal toward negative Z axis, for a solid object on bottom side SetNormalZneg(); } if( startContour == 0 ) { gluTessEndContour( tess ); gluTessEndPolygon( tess ); } gluDeleteTess( tess ); if( aThickness == 0 ) { return; } // Build the 3D data : vertical side Draw3D_VerticalPolygonalCylinder( polylist, aThickness, aZpos - (aThickness / 2.0), false, aBiuTo3DUnits ); }
/** * Function AddClearanceAreasPolygonsToPolysList * Supports a min thickness area constraint. * Add non copper areas polygons (pads and tracks with clearance) * to the filled copper area found * in BuildFilledPolysListData after calculating filled areas in a zone * Non filled copper areas are pads and track and their clearance areas * The filled copper area must be computed just before. * BuildFilledPolysListData() call this function just after creating the * filled copper area polygon (without clearance areas) * to do that this function: * 1 - Creates the main outline (zone outline) using a correction to shrink the resulting area * with m_ZoneMinThickness/2 value. * The result is areas with a margin of m_ZoneMinThickness/2 * When drawing outline with segments having a thickness of m_ZoneMinThickness, the * outlines will match exactly the initial outlines * 3 - Add all non filled areas (pads, tracks) in group B with a clearance of m_Clearance + * m_ZoneMinThickness/2 * in a buffer * - If Thermal shapes are wanted, add non filled area, in order to create these thermal shapes * 4 - calculates the polygon A - B * 5 - put resulting list of polygons (filled areas) in m_FilledPolysList * This zone contains pads with the same net. * 6 - Remove insulated copper islands * 7 - If Thermal shapes are wanted, remove unconnected stubs in thermal shapes: * creates a buffer of polygons corresponding to stubs to remove * sub them to the filled areas. * Remove new insulated copper islands */ void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList( BOARD* aPcb ) { // Set the number of segments in arc approximations if( m_ArcToSegmentsCount == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ) s_CircleToSegmentsCount = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF; else s_CircleToSegmentsCount = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; /* calculates the coeff to compensate radius reduction of holes clearance * due to the segment approx. * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount ) */ s_Correction = 1.0 / cos( M_PI / s_CircleToSegmentsCount ); // This KI_POLYGON_SET is the area(s) to fill, with m_ZoneMinThickness/2 KI_POLYGON_SET polyset_zone_solid_areas; int margin = m_ZoneMinThickness / 2; /* First, creates the main polygon (i.e. the filled area using only one outline) * to reserve a m_ZoneMinThickness/2 margin around the outlines and holes * this margin is the room to redraw outlines with segments having a width set to * m_ZoneMinThickness * so m_ZoneMinThickness is the min thickness of the filled zones areas * the main polygon is stored in polyset_zone_solid_areas */ CopyPolygonsFromFilledPolysListToKiPolygonList( polyset_zone_solid_areas ); polyset_zone_solid_areas -= margin; if( polyset_zone_solid_areas.size() == 0 ) return; /* Calculates the clearance value that meet DRC requirements * from m_ZoneClearance and clearance from the corresponding netclass * We have a "local" clearance in zones because most of time * clearance between a zone and others items is bigger than the netclass clearance * this is more true for small clearance values * Note also the "local" clearance is used for clearance between non copper items * or items like texts on copper layers */ int zone_clearance = std::max( m_ZoneClearance, GetClearance() ); zone_clearance += margin; /* store holes (i.e. tracks and pads areas as polygons outlines) * in a polygon list */ /* items ouside the zone bounding box are skipped * the bounding box is the zone bounding box + the biggest clearance found in Netclass list */ EDA_RECT item_boundingbox; EDA_RECT zone_boundingbox = GetBoundingBox(); int biggest_clearance = aPcb->GetDesignSettings().GetBiggestClearanceValue(); biggest_clearance = std::max( biggest_clearance, zone_clearance ); zone_boundingbox.Inflate( biggest_clearance ); /* * First : Add pads. Note: pads having the same net as zone are left in zone. * Thermal shapes will be created later if necessary */ int item_clearance; // static to avoid unnecessary memory allocation when filling many zones. static CPOLYGONS_LIST cornerBufferPolysToSubstract; cornerBufferPolysToSubstract.RemoveAllContours(); /* Use a dummy pad to calculate hole clerance when a pad is not on all copper layers * and this pad has a hole * This dummy pad has the size and shape of the hole * Therefore, this dummy pad is a circle or an oval. * A pad must have a parent because some functions expect a non null parent * to find the parent board, and some other data */ MODULE dummymodule( aPcb ); // Creates a dummy parent D_PAD dummypad( &dummymodule ); for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) { D_PAD* nextpad; for( D_PAD* pad = module->Pads(); pad != NULL; pad = nextpad ) { nextpad = pad->Next(); // pad pointer can be modified by next code, so // calculate the next pad here if( !pad->IsOnLayer( GetLayer() ) ) { /* Test for pads that are on top or bottom only and have a hole. * There are curious pads but they can be used for some components that are * inside the board (in fact inside the hole. Some photo diodes and Leds are * like this) */ if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 ) continue; // Use a dummy pad to calculate a hole shape that have the same dimension as // the pad hole dummypad.SetSize( pad->GetDrillSize() ); dummypad.SetOrientation( pad->GetOrientation() ); dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_OBLONG ? PAD_OVAL : PAD_CIRCLE ); dummypad.SetPosition( pad->GetPosition() ); pad = &dummypad; } // Note: netcode <=0 means not connected item if( ( pad->GetNetCode() != GetNetCode() ) || ( pad->GetNetCode() <= 0 ) ) { item_clearance = pad->GetClearance() + margin; item_boundingbox = pad->GetBoundingBox(); item_boundingbox.Inflate( item_clearance ); if( item_boundingbox.Intersects( zone_boundingbox ) ) { int clearance = std::max( zone_clearance, item_clearance ); pad->TransformShapeWithClearanceToPolygon( cornerBufferPolysToSubstract, clearance, s_CircleToSegmentsCount, s_Correction ); } continue; } if( ( GetPadConnection( pad ) == PAD_NOT_IN_ZONE ) || ( pad->GetShape() == PAD_TRAPEZOID ) ) // PAD_TRAPEZOID shapes are not in zones because they are used in microwave apps // and i think it is good that shapes are not changed by thermal pads or others { int gap = zone_clearance; int thermalGap = GetThermalReliefGap( pad ); gap = std::max( gap, thermalGap ); item_boundingbox = pad->GetBoundingBox(); if( item_boundingbox.Intersects( zone_boundingbox ) ) { pad->TransformShapeWithClearanceToPolygon( cornerBufferPolysToSubstract, gap, s_CircleToSegmentsCount, s_Correction ); } } } } /* Add holes (i.e. tracks and vias areas as polygons outlines) * in cornerBufferPolysToSubstract */ for( TRACK* track = aPcb->m_Track; track; track = track->Next() ) { if( !track->IsOnLayer( GetLayer() ) ) continue; if( track->GetNetCode() == GetNetCode() && (GetNetCode() != 0) ) continue; item_clearance = track->GetClearance() + margin; item_boundingbox = track->GetBoundingBox(); if( item_boundingbox.Intersects( zone_boundingbox ) ) { int clearance = std::max( zone_clearance, item_clearance ); track->TransformShapeWithClearanceToPolygon( cornerBufferPolysToSubstract, clearance, s_CircleToSegmentsCount, s_Correction ); } } /* Add module edge items that are on copper layers * Pcbnew allows these items to be on copper layers in microwave applictions * This is a bad thing, but must be handled here, until a better way is found */ for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) { for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item->Next() ) { if( !item->IsOnLayer( GetLayer() ) && !item->IsOnLayer( Edge_Cuts ) ) continue; if( item->Type() != PCB_MODULE_EDGE_T ) continue; item_boundingbox = item->GetBoundingBox(); if( item_boundingbox.Intersects( zone_boundingbox ) ) { ( (EDGE_MODULE*) item )->TransformShapeWithClearanceToPolygon( cornerBufferPolysToSubstract, zone_clearance, s_CircleToSegmentsCount, s_Correction ); } } } // Add graphic items (copper texts) and board edges for( BOARD_ITEM* item = aPcb->m_Drawings; item; item = item->Next() ) { if( item->GetLayer() != GetLayer() && item->GetLayer() != Edge_Cuts ) continue; switch( item->Type() ) { case PCB_LINE_T: ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( cornerBufferPolysToSubstract, zone_clearance, s_CircleToSegmentsCount, s_Correction ); break; case PCB_TEXT_T: ( (TEXTE_PCB*) item )->TransformBoundingBoxWithClearanceToPolygon( cornerBufferPolysToSubstract, zone_clearance ); break; default: break; } } // Add zones outlines having an higher priority and keepout for( int ii = 0; ii < GetBoard()->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = GetBoard()->GetArea( ii ); if( zone->GetLayer() != GetLayer() ) continue; if( !zone->GetIsKeepout() && zone->GetPriority() <= GetPriority() ) continue; if( zone->GetIsKeepout() && ! zone->GetDoNotAllowCopperPour() ) continue; // A highter priority zone or keepout area is found: remove its area item_boundingbox = zone->GetBoundingBox(); if( !item_boundingbox.Intersects( zone_boundingbox ) ) continue; // Add the zone outline area. // However if the zone has the same net as the current zone, // do not add clearance. // the zone will be connected to the current zone, but filled areas // will use different parameters (clearance, thermal shapes ) bool addclearance = GetNetCode() != zone->GetNetCode(); int clearance = zone_clearance; if( zone->GetIsKeepout() ) { addclearance = true; clearance = m_ZoneMinThickness / 2; } zone->TransformOutlinesShapeWithClearanceToPolygon( cornerBufferPolysToSubstract, clearance, addclearance ); } // Remove thermal symbols for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) { for( D_PAD* pad = module->Pads(); pad != NULL; pad = pad->Next() ) { // Rejects non-standard pads with tht-only thermal reliefs if( GetPadConnection( pad ) == THT_THERMAL && pad->GetAttribute() != PAD_STANDARD ) continue; if( GetPadConnection( pad ) != THERMAL_PAD && GetPadConnection( pad ) != THT_THERMAL ) continue; if( !pad->IsOnLayer( GetLayer() ) ) continue; if( pad->GetNetCode() != GetNetCode() ) continue; item_boundingbox = pad->GetBoundingBox(); int thermalGap = GetThermalReliefGap( pad ); item_boundingbox.Inflate( thermalGap, thermalGap ); if( item_boundingbox.Intersects( zone_boundingbox ) ) { CreateThermalReliefPadPolygon( cornerBufferPolysToSubstract, *pad, thermalGap, GetThermalReliefCopperBridge( pad ), m_ZoneMinThickness, s_CircleToSegmentsCount, s_Correction, s_thermalRot ); } } } // cornerBufferPolysToSubstract contains polygons to substract. // polyset_zone_solid_areas contains the main filled area // Calculate now actual solid areas if( cornerBufferPolysToSubstract.GetCornersCount() > 0 ) { KI_POLYGON_SET polyset_holes; cornerBufferPolysToSubstract.ExportTo( polyset_holes ); // Remove holes from initial area.: polyset_zone_solid_areas -= polyset_holes; } // put solid areas in m_FilledPolysList: m_FilledPolysList.RemoveAllContours(); CopyPolygonsFromKiPolygonListToFilledPolysList( polyset_zone_solid_areas ); // Remove insulated islands: if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); // Now we remove all unused thermal stubs. cornerBufferPolysToSubstract.RemoveAllContours(); // Test thermal stubs connections and add polygons to remove unconnected stubs. // (this is a refinement for thermal relief shapes) if( GetNetCode() > 0 ) BuildUnconnectedThermalStubsPolygonList( cornerBufferPolysToSubstract, aPcb, this, s_Correction, s_thermalRot ); // remove copper areas corresponding to not connected stubs if( cornerBufferPolysToSubstract.GetCornersCount() ) { KI_POLYGON_SET polyset_holes; cornerBufferPolysToSubstract.ExportTo( polyset_holes ); // Remove unconnected stubs polyset_zone_solid_areas -= polyset_holes; // put these areas in m_FilledPolysList m_FilledPolysList.RemoveAllContours(); CopyPolygonsFromKiPolygonListToFilledPolysList( polyset_zone_solid_areas ); if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); } cornerBufferPolysToSubstract.RemoveAllContours(); }
/** * Function buildBoard3DAuxLayers * Called by CreateDrawGL_List() * Fills the OpenGL GL_ID_BOARD draw list with items * on aux layers only */ void EDA_3D_CANVAS::buildBoard3DAuxLayers( REPORTER* aErrorMessages, REPORTER* aActivity ) { const int segcountforcircle = 18; double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) ); BOARD* pcb = GetBoard(); CPOLYGONS_LIST bufferPolys; bufferPolys.reserve( 5000 ); // Reserve for items not on board static const LAYER_ID sequence[] = { Dwgs_User, Cmts_User, Eco1_User, Eco2_User, Edge_Cuts, Margin }; for( LSEQ aux( sequence, sequence+DIM(sequence) ); aux; ++aux ) { LAYER_ID layer = *aux; if( !is3DLayerEnabled( layer ) ) continue; if( aActivity ) aActivity->Report( wxString::Format( _( "Build layer %s" ), LSET::Name( layer ) ) ); bufferPolys.RemoveAllContours(); for( BOARD_ITEM* item = pcb->m_Drawings; item; item = item->Next() ) { if( !item->IsOnLayer( layer ) ) continue; switch( item->Type() ) { case PCB_LINE_T: ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( bufferPolys, 0, segcountforcircle, correctionFactor ); break; case PCB_TEXT_T: ( (TEXTE_PCB*) item )->TransformShapeWithClearanceToPolygonSet( bufferPolys, 0, segcountforcircle, correctionFactor ); break; default: break; } } for( MODULE* module = pcb->m_Modules; module; module = module->Next() ) { module->TransformPadsShapesWithClearanceToPolygon( layer, bufferPolys, 0, segcountforcircle, correctionFactor ); module->TransformGraphicShapesWithClearanceToPolygonSet( layer, bufferPolys, 0, segcountforcircle, correctionFactor ); } // bufferPolys contains polygons to merge. Many overlaps . // Calculate merged polygons and remove pads and vias holes if( bufferPolys.GetCornersCount() == 0 ) continue; KI_POLYGON_SET currLayerPolyset; KI_POLYGON_SET polyset; bufferPolys.ExportTo( polyset ); currLayerPolyset += polyset; int thickness = GetPrm3DVisu().GetLayerObjectThicknessBIU( layer ); int zpos = GetPrm3DVisu().GetLayerZcoordBIU( layer ); // for Draw3D_SolidHorizontalPolyPolygons, // zpos it the middle between bottom and top sides. // However for top layers, zpos should be the bottom layer pos, // and for bottom layers, zpos should be the top layer pos. if( Get3DLayer_Z_Orientation( layer ) > 0 ) zpos += thickness/2; else zpos -= thickness/2 ; bufferPolys.RemoveAllContours(); bufferPolys.ImportFrom( currLayerPolyset ); 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 znormal must face the layer direction // because it will draw just one plane if( !thickness ) zNormal = Get3DLayer_Z_Orientation( layer ); setGLTechLayersColor( layer ); Draw3D_SolidHorizontalPolyPolygons( bufferPolys, zpos, thickness, GetPrm3DVisu().m_BiuTo3Dunits, false, zNormal ); } }
void PlotLayerOutlines( BOARD *aBoard, PLOTTER* aPlotter, LAYER_MSK aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) { BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); itemplotter.SetLayerMask( aLayerMask ); CPOLYGONS_LIST outlines; for( LAYER_NUM layer = FIRST_LAYER; layer < NB_PCB_LAYERS; layer++ ) { LAYER_MSK layer_mask = GetLayerMask( layer ); if( (aLayerMask & layer_mask ) == 0 ) continue; outlines.RemoveAllContours(); aBoard->ConvertBrdLayerToPolygonalContours( layer, outlines ); // Merge all overlapping polygons. KI_POLYGON_SET kpolygons; KI_POLYGON_SET ktmp; outlines.ExportTo( ktmp ); kpolygons += ktmp; // Plot outlines std::vector< wxPoint > cornerList; for( unsigned ii = 0; ii < kpolygons.size(); ii++ ) { KI_POLYGON polygon = kpolygons[ii]; // polygon contains only one polygon, but it can have holes linked by // overlapping segments. // To plot clean outlines, we have to break this polygon into more polygons with // no overlapping segments, using Clipper, because boost::polygon // does not allow that ClipperLib::Path raw_polygon; ClipperLib::Paths normalized_polygons; for( unsigned ic = 0; ic < polygon.size(); ic++ ) { KI_POLY_POINT corner = *(polygon.begin() + ic); raw_polygon.push_back( ClipperLib::IntPoint( corner.x(), corner.y() ) ); } ClipperLib::SimplifyPolygon( raw_polygon, normalized_polygons ); // Now we have one or more basic polygons: plot each polygon for( unsigned ii = 0; ii < normalized_polygons.size(); ii++ ) { ClipperLib::Path& polygon = normalized_polygons[ii]; cornerList.clear(); for( unsigned jj = 0; jj < polygon.size(); jj++ ) cornerList.push_back( wxPoint( polygon[jj].X , polygon[jj].Y ) ); // Ensure the polygon is closed if( cornerList[0] != cornerList[cornerList.size()-1] ) cornerList.push_back( cornerList[0] ); aPlotter->PlotPoly( cornerList, NO_FILL ); } } // Plot pad holes if( aPlotOpt.GetDrillMarksType() != PCB_PLOT_PARAMS::NO_DRILL_SHAPE ) { for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) { for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() ) { wxSize hole = pad->GetDrillSize(); if( hole.x == 0 || hole.y == 0 ) continue; if( hole.x == hole.y ) aPlotter->Circle( pad->GetPosition(), hole.x, NO_FILL ); else { wxPoint drl_start, drl_end; int width; pad->GetOblongDrillGeometry( drl_start, drl_end, width ); aPlotter->ThickSegment( pad->GetPosition() + drl_start, pad->GetPosition() + drl_end, width, SKETCH ); } } } } // Plot vias holes for( TRACK* track = aBoard->m_Track; track; track = track->Next() ) { const VIA* via = dyn_cast<const VIA*>( track ); if( via && via->IsOnLayer( layer ) ) // via holes can be not through holes { aPlotter->Circle( via->GetPosition(), via->GetDrillValue(), NO_FILL ); } } } }