void TransformOvalClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoint aEnd, int aWidth, int aCircleToSegmentsCount, double aCorrectionFactor ) { // To build the polygonal shape outside the actual shape, we use a bigger // radius to build rounded ends. // However, the width of the segment is too big. // so, later, we will clamp the polygonal shape with the bounding box // of the segment. int radius = aWidth / 2; // Note if we want to compensate the radius reduction of a circle due to // the segment approx, aCorrectionFactor must be calculated like this: // For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) // aCorrectionFactor is 1 /cos( PI/s_CircleToSegmentsCount ) radius = radius * aCorrectionFactor; // make segments outside the circles // end point is the coordinate relative to aStart wxPoint endp = aEnd - aStart; wxPoint startp = aStart; wxPoint corner; SHAPE_POLY_SET polyshape; polyshape.NewOutline(); // normalize the position in order to have endp.x >= 0 // it makes calculations more easy to understand if( endp.x < 0 ) { endp = aStart - aEnd; startp = aEnd; } // delta_angle is in radian double delta_angle = atan2( (double)endp.y, (double)endp.x ); int seg_len = KiROUND( EuclideanNorm( endp ) ); double delta = 3600.0 / aCircleToSegmentsCount; // rot angle in 0.1 degree // Compute the outlines of the segment, and creates a polygon // Note: the polygonal shape is built from the equivalent horizontal // segment starting ar 0,0, and ending at seg_len,0 // add right rounded end: for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ ) { corner = wxPoint( 0, radius ); RotatePoint( &corner, delta*ii ); corner.x += seg_len; polyshape.Append( corner.x, corner.y ); } // Finish arc: corner = wxPoint( seg_len, -radius ); polyshape.Append( corner.x, corner.y ); // add left rounded end: for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ ) { corner = wxPoint( 0, -radius ); RotatePoint( &corner, delta*ii ); polyshape.Append( corner.x, corner.y ); } // Finish arc: corner = wxPoint( 0, radius ); polyshape.Append( corner.x, corner.y ); // Now, clamp the polygonal shape (too big) with the segment bounding box // the polygonal shape bbox equivalent to the segment has a too big height, // and the right width if( aCorrectionFactor > 1.0 ) { SHAPE_POLY_SET bbox; bbox.NewOutline(); // Build the bbox (a horizontal rectangle). int halfwidth = aWidth / 2; // Use the exact segment width for the bbox height corner.x = -radius - 2; // use a bbox width slightly bigger to avoid // creating useless corner at segment ends corner.y = halfwidth; bbox.Append( corner.x, corner.y ); corner.y = -halfwidth; bbox.Append( corner.x, corner.y ); corner.x = radius + seg_len + 2; bbox.Append( corner.x, corner.y ); corner.y = halfwidth; bbox.Append( corner.x, corner.y ); // Now, clamp the shape polyshape.BooleanIntersection( bbox, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // Note the final polygon is a simple, convex polygon with no hole // due to the shape of initial polygons } // Rotate and move the polygon to its right location polyshape.Rotate( delta_angle, VECTOR2I( 0, 0 ) ); polyshape.Move( startp ); aCornerBuffer.Append( polyshape); }
void Convert_path_polygon_to_polygon_blocks_and_dummy_blocks( const SHAPE_POLY_SET &aMainPath, CGENERICCONTAINER2D &aDstContainer, float aBiuTo3DunitsScale, float aDivFactor, const BOARD_ITEM &aBoardItem ) { BOX2I pathBounds = aMainPath.BBox(); // Get the path wxASSERT( aMainPath.OutlineCount() == 1 ); const SHAPE_POLY_SET::POLYGON& curr_polywithholes = aMainPath.CPolygon( 0 ); wxASSERT( curr_polywithholes.size() == 1 ); const SHAPE_LINE_CHAIN& path = curr_polywithholes[0]; // a simple polygon // Convert the points to segments class CBBOX2D bbox; bbox.Reset(); // Contains the main list of segments and each segment normal interpolated SEGMENTS_WIDTH_NORMALS segments_and_normals; // Contains a closed polygon used to calc if points are inside SEGMENTS segments; segments_and_normals.resize( path.PointCount() ); segments.resize( path.PointCount() ); for( int i = 0; i < path.PointCount(); i++ ) { const VECTOR2I& a = path.CPoint( i ); const SFVEC2F point ( (float)( a.x) * aBiuTo3DunitsScale, (float)(-a.y) * aBiuTo3DunitsScale ); bbox.Union( point ); segments_and_normals[i].m_Start = point; segments[i].m_Start = point; } bbox.ScaleNextUp(); // Calc the slopes, normals and some statistics about this polygon unsigned int i; unsigned int j = segments_and_normals.size() - 1; // Temporary normal to the segment, it will later be used for interpolation std::vector< SFVEC2F > tmpSegmentNormals; tmpSegmentNormals.resize( segments_and_normals.size() ); float medOfTheSquaresSegmentLength = 0.0f; #ifdef PRINT_STATISTICS_3D_VIEWER float minLength = FLT_MAX; #endif for( i = 0; i < segments_and_normals.size(); j = i++ ) { const SFVEC2F slope = segments_and_normals[j].m_Start - segments_and_normals[i].m_Start; segments_and_normals[i].m_Precalc_slope = slope; // Calculate constants for each segment segments[i].m_inv_JY_minus_IY = 1.0f / ( segments_and_normals[j].m_Start.y - segments_and_normals[i].m_Start.y ); segments[i].m_JX_minus_IX = ( segments_and_normals[j].m_Start.x - segments_and_normals[i].m_Start.x ); // The normal orientation expect a fixed polygon orientation (!TODO: which one?) //tmpSegmentNormals[i] = glm::normalize( SFVEC2F( -slope.y, +slope.x ) ); tmpSegmentNormals[i] = glm::normalize( SFVEC2F( slope.y, -slope.x ) ); const float length = slope.x * slope.x + slope.y * slope.y; #ifdef PRINT_STATISTICS_3D_VIEWER if( length < minLength ) minLength = length; #endif medOfTheSquaresSegmentLength += length; } #ifdef PRINT_STATISTICS_3D_VIEWER float minSegmentLength = sqrt( minLength ); #endif // This calc an approximation of medium lengths, that will be used to calc // the size of the division. medOfTheSquaresSegmentLength /= segments_and_normals.size(); medOfTheSquaresSegmentLength = sqrt( medOfTheSquaresSegmentLength ); // Compute the normal interpolation // If calculate the dot between the segments, if they are above/below some // threshould it will not interpolated it (ex: if you are in a edge corner // or in a smooth transaction) j = segments_and_normals.size() - 1; for( i = 0; i < segments_and_normals.size(); j = i++ ) { const SFVEC2F normalBeforeSeg = tmpSegmentNormals[j]; const SFVEC2F normalSeg = tmpSegmentNormals[i]; const SFVEC2F normalAfterSeg = tmpSegmentNormals[ (i + 1) % segments_and_normals.size() ]; const float dotBefore = glm::dot( normalBeforeSeg, normalSeg ); const float dotAfter = glm::dot( normalAfterSeg, normalSeg ); if( dotBefore < 0.7f ) segments_and_normals[i].m_Normals.m_Start = normalSeg; else segments_and_normals[i].m_Normals.m_Start = glm::normalize( (((normalBeforeSeg * dotBefore ) + normalSeg) * 0.5f) ); if( dotAfter < 0.7f ) segments_and_normals[i].m_Normals.m_End = normalSeg; else segments_and_normals[i].m_Normals.m_End = glm::normalize( (((normalAfterSeg * dotAfter ) + normalSeg) * 0.5f) ); } if( aDivFactor == 0.0f ) aDivFactor = medOfTheSquaresSegmentLength; SFVEC2UI grid_divisions; grid_divisions.x = (unsigned int)( (bbox.GetExtent().x / aDivFactor) ); grid_divisions.y = (unsigned int)( (bbox.GetExtent().y / aDivFactor) ); grid_divisions = glm::clamp( grid_divisions , SFVEC2UI( 1, 1 ), SFVEC2UI( MAX_NR_DIVISIONS, MAX_NR_DIVISIONS ) ); // Calculate the steps advance of the grid SFVEC2F blockAdvance; blockAdvance.x = bbox.GetExtent().x / (float)grid_divisions.x; blockAdvance.y = bbox.GetExtent().y / (float)grid_divisions.y; wxASSERT( blockAdvance.x > 0.0f ); wxASSERT( blockAdvance.y > 0.0f ); const int leftToRight_inc = (pathBounds.GetRight() - pathBounds.GetLeft()) / grid_divisions.x; const int topToBottom_inc = (pathBounds.GetBottom() - pathBounds.GetTop()) / grid_divisions.y; // Statistics unsigned int stats_n_empty_blocks = 0; unsigned int stats_n_dummy_blocks = 0; unsigned int stats_n_poly_blocks = 0; unsigned int stats_sum_size_of_polygons = 0; // Step by each block of a grid trying to extract segments and create // polygon blocks int topToBottom = pathBounds.GetTop(); float blockY = bbox.Max().y; for( unsigned int iy = 0; iy < grid_divisions.y; iy++ ) { int leftToRight = pathBounds.GetLeft(); float blockX = bbox.Min().x; for( unsigned int ix = 0; ix < grid_divisions.x; ix++ ) { CBBOX2D blockBox( SFVEC2F( blockX, blockY - blockAdvance.y ), SFVEC2F( blockX + blockAdvance.x, blockY ) ); // Make the box large to it will catch (intersect) the edges blockBox.ScaleNextUp(); blockBox.ScaleNextUp(); blockBox.ScaleNextUp(); SEGMENTS_WIDTH_NORMALS extractedSegments; extractPathsFrom( segments_and_normals, blockBox, extractedSegments ); if( extractedSegments.empty() ) { SFVEC2F p1( blockBox.Min().x, blockBox.Min().y ); SFVEC2F p2( blockBox.Max().x, blockBox.Min().y ); SFVEC2F p3( blockBox.Max().x, blockBox.Max().y ); SFVEC2F p4( blockBox.Min().x, blockBox.Max().y ); if( polygon_IsPointInside( segments, p1 ) || polygon_IsPointInside( segments, p2 ) || polygon_IsPointInside( segments, p3 ) || polygon_IsPointInside( segments, p4 ) ) { // In this case, the segments are not intersecting the // polygon, so it means that if any point is inside it, // then all other are inside the polygon. // This is a full bbox inside, so add a dummy box aDstContainer.Add( new CDUMMYBLOCK2D( blockBox, aBoardItem ) ); stats_n_dummy_blocks++; } else { // Points are outside, so this block complety missed the polygon // In this case, no objects need to be added stats_n_empty_blocks++; } } else { // At this point, the borders of polygon were intersected by the // bounding box, so we must calculate a new polygon that will // close that small block. // This block will be used to calculate if points are inside // the (sub block) polygon. SHAPE_POLY_SET subBlockPoly; SHAPE_LINE_CHAIN sb = SHAPE_LINE_CHAIN( VECTOR2I( leftToRight, topToBottom ), VECTOR2I( leftToRight + leftToRight_inc, topToBottom ), VECTOR2I( leftToRight + leftToRight_inc, topToBottom + topToBottom_inc ), VECTOR2I( leftToRight, topToBottom + topToBottom_inc ) ); //sb.Append( leftToRight, topToBottom ); sb.SetClosed( true ); subBlockPoly.AddOutline( sb ); // We need here a strictly simple polygon with outlines and holes SHAPE_POLY_SET solution; solution.BooleanIntersection( aMainPath, subBlockPoly, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); OUTERS_AND_HOLES outersAndHoles; outersAndHoles.m_Holes.clear(); outersAndHoles.m_Outers.clear(); for( int idx = 0; idx < solution.OutlineCount(); idx++ ) { const SHAPE_LINE_CHAIN & outline = solution.Outline( idx ); SEGMENTS solutionSegment; polygon_Convert( outline, solutionSegment, aBiuTo3DunitsScale ); outersAndHoles.m_Outers.push_back( solutionSegment ); stats_sum_size_of_polygons += solutionSegment.size(); for( int holeIdx = 0; holeIdx < solution.HoleCount( idx ); holeIdx++ ) { const SHAPE_LINE_CHAIN & hole = solution.Hole( idx, holeIdx ); polygon_Convert( hole, solutionSegment, aBiuTo3DunitsScale ); outersAndHoles.m_Holes.push_back( solutionSegment ); stats_sum_size_of_polygons += solutionSegment.size(); } } if( !outersAndHoles.m_Outers.empty() ) { aDstContainer.Add( new CPOLYGONBLOCK2D( extractedSegments, outersAndHoles, aBoardItem ) ); stats_n_poly_blocks++; } } blockX += blockAdvance.x; leftToRight += leftToRight_inc; } blockY -= blockAdvance.y; topToBottom += topToBottom_inc; } #ifdef PRINT_STATISTICS_3D_VIEWER printf( "////////////////////////////////////////////////////////////////////////////////\n" ); printf( "Convert_path_polygon_to_polygon_blocks_and_dummy_blocks\n" ); printf( " grid_divisions (%u, %u)\n", grid_divisions.x, grid_divisions.y ); printf( " N Total Blocks %u\n", grid_divisions.x * grid_divisions.y ); printf( " N Empty Blocks %u\n", stats_n_empty_blocks ); printf( " N Dummy Blocks %u\n", stats_n_dummy_blocks ); printf( " N Polyg Blocks %u\n", stats_n_poly_blocks ); printf( " Med N Seg Poly %u\n", stats_sum_size_of_polygons / stats_n_poly_blocks ); printf( " medOfTheSquaresSegmentLength %f\n", medOfTheSquaresSegmentLength ); printf( " minSegmentLength %f\n", minSegmentLength ); printf( " aDivFactor %f\n", aDivFactor ); printf( "////////////////////////////////////////////////////////////////////////////////\n" ); #endif }
bool DRC_COURTYARD_OVERLAP::RunDRC( BOARD& aBoard ) const { wxLogTrace( DRC_COURTYARD_TRACE, "Running DRC: Courtyard" ); // Detects missing (or malformed) footprint courtyard, // and for footprint with courtyard, courtyards overlap. wxString msg; bool success = true; const DRC_MARKER_FACTORY& marker_factory = GetMarkerFactory(); // Update courtyard polygons, and test for missing courtyard definition: for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) { wxPoint pos = footprint->GetPosition(); bool is_ok = footprint->BuildPolyCourtyard(); if( !is_ok && aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) { auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) ); HandleMarker( std::move( marker ) ); success = false; } if( !aBoard.GetDesignSettings().m_RequireCourtyards ) continue; if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 && footprint->GetPolyCourtyardBack().OutlineCount() == 0 && is_ok ) { auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) ); HandleMarker( std::move( marker ) ); success = false; } } if( !aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) return success; wxLogTrace( DRC_COURTYARD_TRACE, "Checking for courtyard overlap" ); // Now test for overlapping on top layer: SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) { if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 ) continue; // No courtyard defined for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) { if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 ) continue; // No courtyard defined courtyard.RemoveAllContours(); courtyard.Append( footprint->GetPolyCourtyardFront() ); // Build the common area between footprint and the candidate: courtyard.BooleanIntersection( candidate->GetPolyCourtyardFront(), SHAPE_POLY_SET::PM_FAST ); // If no overlap, courtyard is empty (no common area). // Therefore if a common polygon exists, this is a DRC error if( courtyard.OutlineCount() ) { //Overlap between footprint and candidate VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); HandleMarker( std::move( marker ) ); success = false; } } } // Test for overlapping on bottom layer: for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) { if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) continue; // No courtyard defined for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) { if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 ) continue; // No courtyard defined courtyard.RemoveAllContours(); courtyard.Append( footprint->GetPolyCourtyardBack() ); // Build the common area between footprint and the candidate: courtyard.BooleanIntersection( candidate->GetPolyCourtyardBack(), SHAPE_POLY_SET::PM_FAST ); // If no overlap, courtyard is empty (no common area). // Therefore if a common polygon exists, this is a DRC error if( courtyard.OutlineCount() ) { //Overlap between footprint and candidate VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); HandleMarker( std::move( marker ) ); success = false; } } } return success; }