|
|
/**
* @file am_primitive.cpp */
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 1992-2017 Jean-Pierre Charras <jp.charras at wanadoo.fr> * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
#include <trigo.h>
#include <convert_basic_shapes_to_polygon.h>
#include <math/util.h> // for KiROUND
#include <gerbview.h>
#include <gerber_file_image.h>
/**
* Convert a distance given in floating point to our internal units. */extern int scaletoIU( double aCoord, bool isMetric );
/**
* Translate a point from the aperture macro coordinate system to our * deci-mils coordinate system. * * @return The GerbView coordinate system vector. */static VECTOR2I mapPt( double x, double y, bool isMetric ){ VECTOR2I ret( scaletoIU( x, isMetric ), scaletoIU( y, isMetric ) );
return ret;}
bool AM_PRIMITIVE::IsAMPrimitiveExposureOn( APERTURE_MACRO* aApertMacro ) const{ /*
* Some but not all primitives use the first parameter as an exposure control. * Others are always ON. * In a aperture macro shape, a basic primitive with exposure off is a hole in the shape * it is NOT a negative shape */ wxASSERT( m_Params.size() );
switch( m_Primitive_id ) { case AMP_CIRCLE: case AMP_LINE2: case AMP_LINE20: case AMP_LINE_CENTER: case AMP_LINE_LOWER_LEFT: case AMP_OUTLINE: case AMP_POLYGON: // All have an exposure parameter and can return a value (0 or 1)
return m_Params[0].GetValueFromMacro( aApertMacro ) != 0; break;
case AMP_THERMAL: // Exposure is always on
case AMP_MOIRE: // Exposure is always on
case AMP_UNKNOWN: default: return 1; // All have no exposure parameter and are always 0N return true
break; }}
void AM_PRIMITIVE::ConvertBasicShapeToPolygon( APERTURE_MACRO* aApertMacro, SHAPE_POLY_SET& aShapeBuffer ){ // Draw the primitive shape for flashed items.
// Note: rotation of primitives inside a macro must be always done around the macro origin.
// Create a static buffer to avoid a lot of memory reallocation.
static std::vector<VECTOR2I> polybuffer; polybuffer.clear();
aApertMacro->EvalLocalParams( *this );
switch( m_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( aApertMacro, polybuffer );
// shape rotation (if any):
if( m_Params.size() >= 5 ) { EDA_ANGLE rotation( m_Params[4].GetValueFromMacro( aApertMacro ), DEGREES_T );
if( !rotation.IsZero() ) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( polybuffer[ii], -rotation ); } }
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( aApertMacro, polybuffer );
// shape rotation:
EDA_ANGLE rotation( m_Params[6].GetValueFromMacro( aApertMacro ), DEGREES_T );
if( !rotation.IsZero() ) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( polybuffer[ii], -rotation ); }
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( aApertMacro, polybuffer );
// shape rotation:
EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
if( !rotation.IsZero() ) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( polybuffer[ii], -rotation ); }
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( aApertMacro, polybuffer );
// shape rotation:
EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
if( !rotation.IsZero() ) { for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) RotatePoint( polybuffer[ii], -rotation ); } 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<VECTOR2I> subshape_poly; VECTOR2I center( mapPt( m_Params[0].GetValueFromMacro( aApertMacro ), m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) ); ConvertShapeToPolygon( aApertMacro, subshape_poly );
// shape rotation:
EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
// 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; EDA_ANGLE sub_rotation = ANGLE_90 * ii;
for( unsigned jj = 0; jj < polybuffer.size(); jj++ ) RotatePoint( polybuffer[jj], -sub_rotation );
// Move to center position given by the tool, and rotate the full shape around
// the center position (origin of the macro):
for( unsigned jj = 0; jj < polybuffer.size(); jj++ ) { polybuffer[jj] += center; RotatePoint( polybuffer[jj], -rotation ); }
aShapeBuffer.NewOutline();
for( unsigned jj = 0; jj < polybuffer.size(); jj++ ) aShapeBuffer.Append( polybuffer[jj] );
aShapeBuffer.Append( polybuffer[0] ); } } break;
case AMP_MOIRE: { /* Moire, Primitive Code 6
* The moire primitive is a cross hair centered on concentric rings (annuli). * Exposure is always on. */
/* 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, * crosshair len, rotation. The type is not stored in parameters list, so the first * parameter is pos.x. */ int outerDiam = scaletoIU( m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric ); int penThickness = scaletoIU( m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ); int gap = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ); int numCircles = KiROUND( m_Params[5].GetValueFromMacro( aApertMacro ) );
// Adjust the allowed approx error to convert arcs to segments:
int arc_to_seg_error = gerbIUScale.mmToIU( 0.005 ); // Allow 5 microns
// Draw circles @ position pos.x, pos.y given by the tool:
VECTOR2I center( mapPt( m_Params[0].GetValueFromMacro( aApertMacro ), m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );
EDA_ANGLE rotation( m_Params[8].GetValueFromMacro( aApertMacro ), DEGREES_T );
// 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;
// calculate the rotated position of the center:
VECTOR2I circle_center = center; RotatePoint( circle_center, -rotation );
// 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, circle_center, outerDiam / 2, arc_to_seg_error, ERROR_INSIDE ); } else { TransformRingToPolygon( aShapeBuffer, circle_center, ( outerDiam - penThickness ) / 2, penThickness, arc_to_seg_error, ERROR_INSIDE ); } }
// Draw the cross:
ConvertShapeToPolygon( aApertMacro, polybuffer );
for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { // move crossair shape to center and rotate shape:
polybuffer[ii] += center; RotatePoint( polybuffer[ii], -rotation ); }
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 */ // m_Params[0] is the exposure and m_Params[1] is the corners count after the first corner
int numCorners = (int) m_Params[1].GetValueFromMacro( aApertMacro );
// the shape rotation is the last param of list, after corners
int last_prm = m_Params.size() - 1; EDA_ANGLE rotation( m_Params[last_prm].GetValueFromMacro( aApertMacro ), DEGREES_T ); VECTOR2I 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; // m_Params[2] is the first X coordinate
for( int i = 0; i <= numCorners; ++i ) { pos.x = scaletoIU( m_Params[prm_idx].GetValueFromMacro( aApertMacro ), m_GerbMetric ); prm_idx++; pos.y = scaletoIU( m_Params[prm_idx].GetValueFromMacro( aApertMacro ), 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 ); }
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 */ VECTOR2I curPos( mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );
// Creates the shape:
ConvertShapeToPolygon( aApertMacro, polybuffer );
// move and rotate polygonal shape
EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );
for( unsigned ii = 0; ii < polybuffer.size(); ii++ ) { polybuffer[ii] += curPos; RotatePoint( polybuffer[ii], -rotation ); }
break; }
case AMP_COMMENT: case AMP_UNKNOWN: break; }
if( polybuffer.size() > 1 ) // a valid polygon has more than 1 corner
{ aShapeBuffer.NewOutline();
for( unsigned jj = 0; jj < polybuffer.size(); jj++ ) aShapeBuffer.Append( polybuffer[jj] );
// Close the shape:
aShapeBuffer.Append( polybuffer[0] ); }}
void AM_PRIMITIVE::ConvertShapeToPolygon( APERTURE_MACRO* aApertMacro, std::vector<VECTOR2I>& aBuffer ){ switch( m_Primitive_id ) { case AMP_CIRCLE: { /* 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 */ int radius = scaletoIU( m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
// A circle primitive can have a 0 size (for instance when used in roundrect macro),
// so skip it
if( radius <= 0 ) break;
VECTOR2I center = mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ); VECTOR2I corner;
const int seg_per_circle = 64; // Number of segments to approximate a circle
EDA_ANGLE delta = ANGLE_360 / seg_per_circle;
for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta ) { corner.x = radius; corner.y = 0; RotatePoint( corner, angle ); corner += center; aBuffer.push_back( corner ); }
break; }
case AMP_LINE2: case AMP_LINE20: // Line with rectangle ends. (Width, start and end pos + rotation)
{ int width = scaletoIU( m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ); VECTOR2I start = mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ); VECTOR2I end = mapPt( m_Params[4].GetValueFromMacro( aApertMacro ), m_Params[5].GetValueFromMacro( aApertMacro ), m_GerbMetric ); VECTOR2I delta = end - start; int len = delta.EuclideanNorm();
// To build the polygon, we must create a horizontal polygon starting to "start"
// and rotate it to have the end point to "end"
VECTOR2I currpt; currpt.y += width / 2; // Upper left
aBuffer.push_back( currpt ); currpt.x = len; // Upper right
aBuffer.push_back( currpt ); currpt.y -= width; // lower right
aBuffer.push_back( currpt ); currpt.x = 0; // lower left
aBuffer.push_back( currpt );
// Rotate rectangle and move it to the actual start point
EDA_ANGLE angle( delta );
for( unsigned ii = 0; ii < 4; ii++ ) { RotatePoint( aBuffer[ii], -angle ); aBuffer[ii] += start; }
break; }
case AMP_LINE_CENTER: { VECTOR2I size = mapPt( m_Params[1].GetValueFromMacro( aApertMacro ), m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric ); VECTOR2I pos = mapPt( m_Params[3].GetValueFromMacro( aApertMacro ), m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );
// Build poly:
pos.x -= size.x / 2; pos.y -= size.y / 2; // Lower left
aBuffer.push_back( pos ); pos.y += size.y; // Upper left
aBuffer.push_back( pos ); pos.x += size.x; // Upper right
aBuffer.push_back( pos ); pos.y -= size.y; // lower right
aBuffer.push_back( pos ); break; }
case AMP_LINE_LOWER_LEFT: { VECTOR2I size = mapPt( m_Params[1].GetValueFromMacro( aApertMacro ), m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric ); VECTOR2I lowerLeft = mapPt( m_Params[3].GetValueFromMacro( aApertMacro ), m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );
// Build poly:
aBuffer.push_back( lowerLeft ); lowerLeft.y += size.y; // Upper left
aBuffer.push_back( lowerLeft ); lowerLeft.x += size.x; // Upper right
aBuffer.push_back( lowerLeft ); lowerLeft.y -= size.y; // lower right
aBuffer.push_back( lowerLeft ); break; }
case AMP_THERMAL: { // Only 1/4 of the full shape is built, because the other 3 shapes will be draw from
// this first rotated by 90, 180 and 270 deg.
// m_Params = center.x (unused here), center.y (unused here), outside diam, inside diam,
// crosshair thickness.
int outerRadius = scaletoIU( m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2; int innerRadius = scaletoIU( m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
// Safety checks to guarantee no divide-by-zero
outerRadius = std::max( 1, outerRadius ); innerRadius = std::max( 1, innerRadius );
int halfthickness = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2; EDA_ANGLE angle_start( asin( (double) halfthickness / innerRadius ), RADIANS_T );
// Draw shape in the first quadrant (X and Y > 0)
VECTOR2I pos, startpos;
// Inner arc
startpos.x = innerRadius; EDA_ANGLE angle_end = ANGLE_90 - angle_start;
for( EDA_ANGLE angle = angle_start; angle < angle_end; angle += EDA_ANGLE( 10, DEGREES_T ) ) { pos = startpos; RotatePoint( pos, angle ); aBuffer.push_back( pos ); }
// Last point
pos = startpos; RotatePoint( pos, angle_end ); aBuffer.push_back( pos );
// outer arc
startpos.x = outerRadius; startpos.y = 0; angle_start = EDA_ANGLE( asin( (double) halfthickness / outerRadius ), RADIANS_T ); angle_end = ANGLE_90 - angle_start;
// First point, near Y axis, outer arc
for( EDA_ANGLE angle = angle_end; angle > angle_start; angle -= EDA_ANGLE( 10, DEGREES_T ) ) { pos = startpos; RotatePoint( pos, angle ); aBuffer.push_back( pos ); }
// last point
pos = startpos; RotatePoint( pos, angle_start ); aBuffer.push_back( pos );
aBuffer.push_back( aBuffer[0] ); // Close poly
} break;
case AMP_MOIRE: { // A cross hair with n concentric circles. Only the cross is built as
// polygon because circles can be drawn easily
int crossHairThickness = scaletoIU( m_Params[6].GetValueFromMacro( aApertMacro ), m_GerbMetric ); int crossHairLength = scaletoIU( m_Params[7].GetValueFromMacro( aApertMacro ), m_GerbMetric );
// Create cross. First create 1/4 of the shape.
// Others point are the same, rotated by 90, 180 and 270 deg
VECTOR2I pos( crossHairThickness / 2, crossHairLength / 2 ); aBuffer.push_back( pos ); pos.y = crossHairThickness / 2; aBuffer.push_back( pos ); pos.x = -crossHairLength / 2; aBuffer.push_back( pos ); pos.y = -crossHairThickness / 2; aBuffer.push_back( pos );
// Copy the 4 shape, rotated by 90, 180 and 270 deg
for( int jj = 1; jj <= 3; jj ++ ) { for( int ii = 0; ii < 4; ii++ ) { pos = aBuffer[ii]; RotatePoint( pos, ANGLE_90 * jj ); aBuffer.push_back( pos ); } }
break; }
case AMP_OUTLINE: // already is a polygon. Do nothing
break;
case AMP_POLYGON: // Creates a regular polygon
{ int vertexcount = KiROUND( m_Params[1].GetValueFromMacro( aApertMacro ) ); int radius = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
// rs274x said: vertex count = 3 ... 10, and the first corner is on the X axis
if( vertexcount < 3 ) vertexcount = 3;
if( vertexcount > 10 ) vertexcount = 10;
for( int ii = 0; ii <= vertexcount; ii++ ) { VECTOR2I pos( radius, 0 ); RotatePoint( pos, ANGLE_360 * ii / vertexcount ); aBuffer.push_back( pos ); }
break; }
case AMP_COMMENT: case AMP_UNKNOWN: break; }}
|