kicad/3d-viewer/3d_draw.cpp

1439 lines
44 KiB
C++

/**
* @file 3d_draw.cpp
*/
#include "fctsys.h"
#include "common.h"
#include "trigo.h"
#include "pcbstruct.h"
#include "macros.h"
#include "drawtxt.h"
#include "confirm.h"
#include "class_board_design_settings.h"
#include "colors_selection.h"
#include "3d_viewer.h"
#include "trackball.h"
#if !wxUSE_GLCANVAS
#error Please set wxUSE_GLCANVAS to 1 in setup.h.
#endif
extern void CheckGLError();
static void Draw3D_FilledCircle( double posx, double posy, double rayon,
double hole_rayon, double zpos );
static void Draw3D_FilledSegment( double startx, double starty,
double endx, double endy,
double width, double zpos );
static void Draw3D_FilledCylinder( double posx, double posy, double rayon,
double height, double zpos );
static void Draw3D_FilledSegmentWithHole( double startx, double starty,
double endx, double endy,
double width, double holex,
double holey, double holeradius,
double zpos );
static void Draw3D_ArcSegment( double startx, double starty, double centrex,
double centrey, double arc_angle, double width, double zpos );
static void Draw3D_CircleSegment( double startx, double starty, double endx,
double endy, double width, double zpos );
static int Get3DLayerEnable( int act_layer );
static GLfloat Get3DLayerSide( int act_layer );
#ifndef CALLBACK
#define CALLBACK
#endif
// CALLBACK functions for GLU_TESS
static void CALLBACK tessBeginCB( GLenum which );
static void CALLBACK tessEndCB();
static void CALLBACK tessErrorCB( GLenum errorCode );
static void CALLBACK tessCPolyPt2Vertex( const GLvoid* data );
static void CALLBACK tesswxPoint2Vertex( const GLvoid* data );
void Pcb3D_GLCanvas::Redraw( bool finish )
{
/* SwapBuffer requires the window to be shown before calling */
if( !IsShown() )
return;
#if wxCHECK_VERSION( 2, 7, 0 )
SetCurrent( *m_glRC );
#else
SetCurrent();
#endif
// Set the OpenGL viewport according to the client size of this canvas.
// This is done here rather than in a wxSizeEvent handler because our
// OpenGL rendering context (and thus viewport setting) is used with
// multiple canvases: If we updated the viewport in the wxSizeEvent
// handler, changing the size of one canvas causes a viewport setting that
// is wrong when next another canvas is repainted.
const wxSize ClientSize = GetClientSize();
// *MUST* be called *after* SetCurrent( ):
glViewport( 0, 0, ClientSize.x, ClientSize.y );
InitGL();
glMatrixMode( GL_MODELVIEW ); /* position viewer */
/* transformations */
GLfloat mat[4][4];
// Translate motion first, so rotations don't mess up the orientation...
glTranslatef( g_Draw3d_dx, g_Draw3d_dy, 0.0F );
build_rotmatrix( mat, g_Parm_3D_Visu.m_Quat );
glMultMatrixf( &mat[0][0] );
glRotatef( g_Parm_3D_Visu.m_Rot[0], 1.0, 0.0, 0.0 );
glRotatef( g_Parm_3D_Visu.m_Rot[1], 0.0, 1.0, 0.0 );
glRotatef( g_Parm_3D_Visu.m_Rot[2], 0.0, 0.0, 1.0 );
if( m_gllist )
glCallList( m_gllist );
else
{
CreateDrawGL_List();
}
glFlush();
if( finish );
glFinish();
SwapBuffers();
}
/* Create the draw list items
*/
GLuint Pcb3D_GLCanvas::CreateDrawGL_List()
{
PCB_BASE_FRAME* pcbframe = m_Parent->m_Parent;
BOARD* pcb = pcbframe->GetBoard();
TRACK* track;
SEGZONE* segzone;
int ii;
wxBusyCursor dummy;
m_gllist = glGenLists( 1 );
pcb->ComputeBoundingBox();
g_Parm_3D_Visu.m_BoardSettings = pcb->GetBoardDesignSettings();
g_Parm_3D_Visu.m_BoardSize = pcb->m_BoundaryBox.GetSize();
g_Parm_3D_Visu.m_BoardPos = pcb->m_BoundaryBox.Centre();
g_Parm_3D_Visu.m_BoardPos.y = -g_Parm_3D_Visu.m_BoardPos.y;
g_Parm_3D_Visu.m_Layers = pcb->GetCopperLayerCount();
// Ensure the board has 2 sides for 3D views, because it is hard to find
// a *really* single side board in the true life...
if( g_Parm_3D_Visu.m_Layers < 2 )
g_Parm_3D_Visu.m_Layers = 2;
g_Parm_3D_Visu.m_BoardScale = 2.0 / MAX( g_Parm_3D_Visu.m_BoardSize.x,
g_Parm_3D_Visu.m_BoardSize.y );
// @TODO: epoxy_width (board thickness) must be set by user,
// because all boards thickness no not match with this setup:
// double epoxy_width = 1.6; // epoxy width in mm
g_Parm_3D_Visu.m_Epoxy_Width = pcb->GetBoardDesignSettings()->m_BoardThickness
* g_Parm_3D_Visu.m_BoardScale;
/* calculate z position for each layer */
for( ii = 0; ii < 32; ii++ )
{
if( ii < g_Parm_3D_Visu.m_Layers )
g_Parm_3D_Visu.m_LayerZcoord[ii] =
g_Parm_3D_Visu.m_Epoxy_Width
* ii / (g_Parm_3D_Visu.m_Layers - 1);
else
g_Parm_3D_Visu.m_LayerZcoord[ii] = g_Parm_3D_Visu.m_Epoxy_Width;
}
GLfloat zpos_cu = 10 * g_Parm_3D_Visu.m_BoardScale;
GLfloat zpos_cmp = g_Parm_3D_Visu.m_Epoxy_Width + zpos_cu;
g_Parm_3D_Visu.m_LayerZcoord[ADHESIVE_N_BACK] = -zpos_cu * 2;
g_Parm_3D_Visu.m_LayerZcoord[ADHESIVE_N_FRONT] = zpos_cmp + zpos_cu;
g_Parm_3D_Visu.m_LayerZcoord[SILKSCREEN_N_BACK] = -zpos_cu;
g_Parm_3D_Visu.m_LayerZcoord[SILKSCREEN_N_FRONT] = zpos_cmp;
g_Parm_3D_Visu.m_LayerZcoord[DRAW_N] = zpos_cmp + zpos_cu;
g_Parm_3D_Visu.m_LayerZcoord[COMMENT_N] = zpos_cmp + zpos_cu;
g_Parm_3D_Visu.m_LayerZcoord[ECO1_N] = zpos_cmp + zpos_cu;
g_Parm_3D_Visu.m_LayerZcoord[ECO2_N] = zpos_cmp + zpos_cu;
glNewList( m_gllist, GL_COMPILE_AND_EXECUTE );
glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
/* draw axis */
if( g_Parm_3D_Visu.m_Draw3DAxis )
{
glEnable( GL_COLOR_MATERIAL );
SetGLColor( WHITE );
glBegin( GL_LINES );
glNormal3f( 0.0f, 0.0f, 1.0f ); // Normal is Z axis
glVertex3f( 0.0f, 0.0f, 0.0f );
glVertex3f( 1.0f, 0.0f, 0.0f ); // X axis
glVertex3f( 0.0f, 0.0f, 0.0f );
glVertex3f( 0.0f, -1.0f, 0.0f ); // Y axis
glNormal3f( 1.0f, 0.0f, 0.0f ); // Normal is Y axis
glVertex3f( 0.0f, 0.0f, 0.0f );
glVertex3f( 0.0f, 0.0f, 0.3f ); // Z axis
glEnd();
}
/* Draw epoxy limits (do not use, works and test in progress) */
#if 0
glEnable( GL_FOG );
GLfloat param;
// param = GL_LINEAR;
// glFogfv(GL_FOG_MODE, & param);
param = 0.2;
glFogfv( GL_FOG_DENSITY, &param );
param = g_Parm_3D_Visu.m_LayerZcoord[15];
glFogfv( GL_FOG_END, &param );
glBegin( GL_QUADS );
SetGLColor( g_Parm_3D_Visu.m_BoardSettings->m_LayerColor[LAYER_N_FRONT] );
double sx = DataScale3D * g_Parm_3D_Visu.m_BoardSize.x / 2;
double sy = DataScale3D * g_Parm_3D_Visu.m_BoardSize.y / 2;
double zpos = g_Parm_3D_Visu.m_LayerZcoord[15];
glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis
sx = sy = 0.5;
glVertex3f( -sx, -sy, zpos );
glVertex3f( -sx, sy, zpos );
glVertex3f( sx, sy, zpos );
glVertex3f( sx, -sy, zpos );
glEnd();
glBegin( GL_QUADS );
SetGLColor( g_Parm_3D_Visu.m_BoardSettings->m_LayerColor[LAYER_N_BACK] );
glNormal3f( 0.0, 0.0, -1.0 ); // Normal is -Z axis
glVertex3f( -sx, -sy, 0 );
glVertex3f( -sx, sy, 0 );
glVertex3f( sx, sy, 0 );
glVertex3f( sx, -sy, 0 );
glEnd();
#endif
/* move the board in order to draw it with its center at 0,0 3D
* coordinates */
glTranslatef( -g_Parm_3D_Visu.m_BoardPos.x * g_Parm_3D_Visu.m_BoardScale,
-g_Parm_3D_Visu.m_BoardPos.y * g_Parm_3D_Visu.m_BoardScale,
0.0F );
glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis
/* draw tracks and vias : */
for( track = pcb->m_Track; track != NULL; track = track->Next() )
{
if( track->Type() == TYPE_VIA )
Draw3D_Via( (SEGVIA*) track );
else
Draw3D_Track( track );
}
if( g_Parm_3D_Visu.m_Draw3DZone )
{
// Draw segments used to fill copper areas. outdated!
for( segzone = pcb->m_Zone; segzone != NULL; segzone = segzone->Next() )
{
if( segzone->Type() == TYPE_ZONE )
Draw3D_Track( segzone );
}
// Draw new segments
for( ii = 0; ii < pcb->GetAreaCount(); ii++ )
{
ZONE_CONTAINER* curr_zone = pcb->GetArea( ii );
if( curr_zone->m_FillMode == 0 )
{
// solid polygons only are used to fill areas
if( curr_zone->m_FilledPolysList.size() > 3 )
{
Draw3D_SolidPolygonsInZones( curr_zone );
}
}
else
{
// segments are used to fill areas
for( unsigned iseg = 0; iseg < curr_zone->m_FillSegmList.size(); iseg++ )
{
SEGZONE dummysegment( pcb );
dummysegment.SetLayer( curr_zone->GetLayer() );
dummysegment.m_Width = curr_zone->m_ZoneMinThickness;
dummysegment.m_Start.x = curr_zone->m_FillSegmList[iseg].m_Start.x;
dummysegment.m_Start.y = curr_zone->m_FillSegmList[iseg].m_Start.y;
dummysegment.m_End.x = curr_zone->m_FillSegmList[iseg].m_End.x;
dummysegment.m_End.y = curr_zone->m_FillSegmList[iseg].m_End.y;
Draw3D_Track( &dummysegment );
}
}
}
// Draw copper areas outlines
for( ii = 0; ii < pcb->GetAreaCount(); ii++ )
{
ZONE_CONTAINER* zone = pcb->GetArea( ii );
if( zone->m_FilledPolysList.size() == 0 )
continue;
if( zone->m_ZoneMinThickness <= 1 )
continue;
int imax = zone->m_FilledPolysList.size() - 1;
CPolyPt* firstcorner = &zone->m_FilledPolysList[0];
CPolyPt* begincorner = firstcorner;
SEGZONE dummysegment( pcb );
dummysegment.SetLayer( zone->GetLayer() );
dummysegment.m_Width = zone->m_ZoneMinThickness;
for( int ic = 1; ic <= imax; ic++ )
{
CPolyPt* endcorner = &zone->m_FilledPolysList[ic];
if( begincorner->utility == 0 )
{
// Draw only basic outlines, not extra segments
dummysegment.m_Start.x = begincorner->x;
dummysegment.m_Start.y = begincorner->y;
dummysegment.m_End.x = endcorner->x;
dummysegment.m_End.y = endcorner->y;
Draw3D_Track( &dummysegment );
}
if( (endcorner->end_contour) || (ic == imax) )
{
// the last corner of a filled area is found: draw it
if( endcorner->utility == 0 )
{
// Draw only basic outlines, not extra segments
dummysegment.m_Start.x = endcorner->x;
dummysegment.m_Start.y = endcorner->y;
dummysegment.m_End.x = firstcorner->x;
dummysegment.m_End.y = firstcorner->y;
Draw3D_Track( &dummysegment );
}
ic++;
if( ic < imax - 1 )
begincorner = firstcorner =
&zone->m_FilledPolysList[ic];
}
else
begincorner = endcorner;
}
}
}
/* draw graphic items */
EDA_ITEM* PtStruct;
for( PtStruct = pcb->m_Drawings; PtStruct != NULL; PtStruct = PtStruct->Next() )
{
switch( PtStruct->Type() )
{
case TYPE_DRAWSEGMENT:
Draw3D_DrawSegment( (DRAWSEGMENT*) PtStruct );
break;
case TYPE_TEXTE:
Draw3D_DrawText( (TEXTE_PCB*) PtStruct );
break;
default:
break;
}
}
/* draw footprints */
MODULE* Module = pcb->m_Modules;
for( ; Module != NULL; Module = Module->Next() )
{
Module->Draw3D( this );
}
glEndList();
/* Test for errors */
CheckGLError();
return m_gllist;
}
void Pcb3D_GLCanvas::Draw3D_Track( TRACK* track )
{
double zpos;
int layer = track->GetLayer();
double ox, oy, fx, fy;
double w;
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) == false )
return;
int color = g_ColorsSettings.GetLayerColor( layer );
if( layer == LAST_COPPER_LAYER )
layer = g_Parm_3D_Visu.m_Layers - 1;
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
SetGLColor( color );
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
w = track->m_Width * g_Parm_3D_Visu.m_BoardScale;
ox = track->m_Start.x * g_Parm_3D_Visu.m_BoardScale;
oy = track->m_Start.y * g_Parm_3D_Visu.m_BoardScale;
fx = track->m_End.x * g_Parm_3D_Visu.m_BoardScale;
fy = track->m_End.y * g_Parm_3D_Visu.m_BoardScale;
Draw3D_FilledSegment( ox, -oy, fx, -fy, w, zpos );
}
/**
* Function Draw3D_SolidPolygonsInZones
* draw all solid polygons used as filles areas in a zone
* @param aZone = the zone to draw
*/
void Pcb3D_GLCanvas::Draw3D_SolidPolygonsInZones( ZONE_CONTAINER* aZone )
{
double zpos;
int layer = aZone->GetLayer();
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) == false )
return;
int color = g_ColorsSettings.GetLayerColor( layer );
if( layer == LAST_COPPER_LAYER )
layer = g_Parm_3D_Visu.m_Layers - 1;
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
g_Parm_3D_Visu.m_ActZpos = zpos;
SetGLColor( color );
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
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];
v_data[2] = zpos;
//gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
// Draw solid areas contained in this zone
int StartContour = 1;
for( unsigned ii = 0; ii < aZone->m_FilledPolysList.size(); ii++ )
{
if( StartContour == 1 )
{
gluTessBeginPolygon( tess, NULL );
gluTessBeginContour( tess );
StartContour = 0;
}
v_data[0] = aZone->m_FilledPolysList[ii].x * g_Parm_3D_Visu.m_BoardScale;
v_data[1] = -aZone->m_FilledPolysList[ii].y * g_Parm_3D_Visu.m_BoardScale;
gluTessVertex( tess, v_data, &aZone->m_FilledPolysList[ii] );
if( aZone->m_FilledPolysList[ii].end_contour == 1 )
{
gluTessEndContour( tess );
gluTessEndPolygon( tess );
StartContour = 1;
}
}
gluDeleteTess( tess );
}
/* 3D drawing for a VIA (cylinder + filled circles)
*/
void Pcb3D_GLCanvas::Draw3D_Via( SEGVIA* via )
{
double x, y, r, hole;
int layer, top_layer, bottom_layer;
double zpos, height;
int color;
r = via->m_Width * g_Parm_3D_Visu.m_BoardScale / 2;
hole = via->GetDrillValue();
hole *= g_Parm_3D_Visu.m_BoardScale / 2;
x = via->m_Start.x * g_Parm_3D_Visu.m_BoardScale;
y = via->m_Start.y * g_Parm_3D_Visu.m_BoardScale;
via->ReturnLayerPair( &top_layer, &bottom_layer );
// Drawing filled circles:
for( layer = bottom_layer; layer < g_Parm_3D_Visu.m_Layers; layer++ )
{
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
if( layer < g_Parm_3D_Visu.m_Layers - 1 )
{
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) ==
false )
continue;
color = g_ColorsSettings.GetLayerColor( layer );
}
else
{
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( LAYER_N_FRONT ) ==
false )
continue;
color = g_ColorsSettings.GetLayerColor( LAYER_N_FRONT );
}
SetGLColor( color );
// SetGLColor( LIGHTGRAY );
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
if( layer == LAYER_N_BACK )
zpos = zpos - 5 * g_Parm_3D_Visu.m_BoardScale;
else
zpos = zpos + 5 * g_Parm_3D_Visu.m_BoardScale;
Draw3D_FilledCircle( x, -y, r, hole, zpos );
if( layer >= top_layer )
break;
}
// Drawing hole:
color = g_ColorsSettings.GetItemColor( VIAS_VISIBLE + via->m_Shape );
SetGLColor( color );
height = g_Parm_3D_Visu.m_LayerZcoord[top_layer] -
g_Parm_3D_Visu.m_LayerZcoord[bottom_layer];
Draw3D_FilledCylinder( x, -y, hole, height,
g_Parm_3D_Visu.m_LayerZcoord[bottom_layer] );
}
void Pcb3D_GLCanvas::Draw3D_DrawSegment( DRAWSEGMENT* segment )
{
double x, y, xf, yf;
double zpos, w;
int layer = segment->GetLayer();
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) == false )
return;
int color = g_ColorsSettings.GetLayerColor( layer );
SetGLColor( color );
w = segment->m_Width * g_Parm_3D_Visu.m_BoardScale;
x = segment->m_Start.x * g_Parm_3D_Visu.m_BoardScale;
y = segment->m_Start.y * g_Parm_3D_Visu.m_BoardScale;
xf = segment->m_End.x * g_Parm_3D_Visu.m_BoardScale;
yf = segment->m_End.y * g_Parm_3D_Visu.m_BoardScale;
if( layer == EDGE_N )
{
for( layer = 0; layer < g_Parm_3D_Visu.m_Layers; layer++ )
{
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
switch( segment->m_Shape )
{
case S_ARC:
Draw3D_ArcSegment( x, -y, xf, -yf, (double) segment->m_Angle, w, zpos );
break;
case S_CIRCLE:
Draw3D_CircleSegment( x, -y, xf, -yf, w, zpos );
break;
default:
Draw3D_FilledSegment( x, -y, xf, -yf, w, zpos );
break;
}
}
}
else
{
glNormal3f( 0.0, 0.0, Get3DLayerSide( layer ) );
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
if( Get3DLayerEnable( layer ) )
{
switch( segment->m_Shape )
{
case S_ARC:
Draw3D_ArcSegment( x, -y, xf, -yf, (double) segment->m_Angle, w, zpos );
break;
case S_CIRCLE:
Draw3D_CircleSegment( x, -y, xf, -yf, w, zpos );
break;
default:
Draw3D_FilledSegment( x, -y, xf, -yf, w, zpos );
break;
}
}
}
}
/* function to draw 3D segments, called by DrawGraphicText
* When DrawGraphicText is called to draw a text to an OpenGL DC
* it calls Draw3dTextSegm to each segment to draw.
* 2 parameters used by Draw3D_FilledSegment are not handled by DrawGraphicText
* but are used in Draw3D_FilledSegment().
* they are 2 local variables. This is an ugly, but trivial code.
* Using DrawGraphicText to draw all texts ensure texts have the same shape
* in all contexts
*/
static double s_Text3DWidth, s_Text3DZPos;
static void Draw3dTextSegm( int x0, int y0, int xf, int yf )
{
double startx = x0 * g_Parm_3D_Visu.m_BoardScale;
double starty = y0 * g_Parm_3D_Visu.m_BoardScale;
double endx = xf * g_Parm_3D_Visu.m_BoardScale;
double endy = yf * g_Parm_3D_Visu.m_BoardScale;
Draw3D_FilledSegment( startx, -starty, endx, -endy,
s_Text3DWidth, s_Text3DZPos );
}
void Pcb3D_GLCanvas::Draw3D_DrawText( TEXTE_PCB* text )
{
int layer = text->GetLayer();
if( !Get3DLayerEnable( layer ) )
return;
int color = g_ColorsSettings.GetLayerColor( layer );
SetGLColor( color );
s_Text3DZPos = g_Parm_3D_Visu.m_LayerZcoord[layer];
s_Text3DWidth = text->m_Thickness * g_Parm_3D_Visu.m_BoardScale;
glNormal3f( 0.0, 0.0, Get3DLayerSide( layer ) );
wxSize size = text->m_Size;
if( text->m_Mirror )
NEGATE( size.x );
if( text->m_MultilineAllowed )
{
wxPoint pos = text->m_Pos;
wxArrayString* list = wxStringSplit( text->m_Text, '\n' );
wxPoint offset;
offset.y = text->GetInterline();
RotatePoint( &offset, text->m_Orient );
for( unsigned i = 0; i<list->Count(); i++ )
{
wxString txt = list->Item( i );
DrawGraphicText( NULL, NULL, pos, (EDA_Colors) color,
txt, text->m_Orient, size,
text->m_HJustify, text->m_VJustify,
text->m_Thickness, text->m_Italic,
true, Draw3dTextSegm );
pos += offset;
}
delete (list);
}
else
DrawGraphicText( NULL, NULL, text->m_Pos, (EDA_Colors) color,
text->m_Text, text->m_Orient, size,
text->m_HJustify, text->m_VJustify,
text->m_Thickness, text->m_Italic,
true,
Draw3dTextSegm );
}
void MODULE::Draw3D( Pcb3D_GLCanvas* glcanvas )
{
D_PAD* pad = m_Pads;
/* Draw pads */
glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis
for( ; pad != NULL; pad = pad->Next() )
{
pad->Draw3D( glcanvas );
}
/* Draw module shape: 3D shape if exists (or module outlines if not exists) */
S3D_MASTER* Struct3D = m_3D_Drawings;
bool As3dShape = FALSE;
if( g_Parm_3D_Visu.m_Draw3DModule )
{
glPushMatrix();
glTranslatef( m_Pos.x * g_Parm_3D_Visu.m_BoardScale,
-m_Pos.y * g_Parm_3D_Visu.m_BoardScale,
g_Parm_3D_Visu.m_LayerZcoord[m_Layer] );
if( m_Orient )
{
glRotatef( (double) m_Orient / 10, 0.0, 0.0, 1.0 );
}
if( m_Layer == LAYER_N_BACK )
{
glRotatef( 180.0, 0.0, 1.0, 0.0 );
glRotatef( 180.0, 0.0, 0.0, 1.0 );
}
DataScale3D = g_Parm_3D_Visu.m_BoardScale * UNITS3D_TO_UNITSPCB;
for( ; Struct3D != NULL; Struct3D = Struct3D->Next() )
{
if( !Struct3D->m_Shape3DName.IsEmpty() )
{
As3dShape = TRUE;
Struct3D->ReadData();
}
}
glPopMatrix();
}
EDA_ITEM* Struct = m_Drawings;
glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis
for( ; Struct != NULL; Struct = Struct->Next() )
{
switch( Struct->Type() )
{
case TYPE_TEXTE_MODULE:
break;
case TYPE_EDGE_MODULE:
{
EDGE_MODULE* edge = (EDGE_MODULE*) Struct;
// Draw module edges when no 3d shape exists.
// Always draw pcb edges.
if( !As3dShape || edge->GetLayer() == EDGE_N )
edge->Draw3D( glcanvas );
}
break;
default:
break;
}
}
}
void EDGE_MODULE::Draw3D( Pcb3D_GLCanvas* glcanvas )
{
wxString s;
int dx, dy;
double x, y, fx, fy, w, zpos;
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( m_Layer ) == false )
return;
int color = g_ColorsSettings.GetLayerColor( m_Layer );
SetGLColor( color );
dx = m_End.x;
dy = m_End.y;
w = m_Width * g_Parm_3D_Visu.m_BoardScale;
x = m_Start.x * g_Parm_3D_Visu.m_BoardScale;
y = m_Start.y * g_Parm_3D_Visu.m_BoardScale;
fx = dx * g_Parm_3D_Visu.m_BoardScale;
fy = dy * g_Parm_3D_Visu.m_BoardScale;
if( m_Layer == EDGE_N )
{
for( int layer = 0; layer < g_Parm_3D_Visu.m_Layers; layer++ )
{
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
switch( m_Shape )
{
case S_SEGMENT:
Draw3D_FilledSegment( x, -y, fx, -fy, w, zpos );
break;
case S_CIRCLE:
Draw3D_CircleSegment( x, -y, fx, -fy, w, zpos );
break;
case S_ARC:
Draw3D_ArcSegment( x, -y, fx, -fy, (double) m_Angle, w, zpos );
break;
case S_POLYGON:
{
// We must compute true coordinates from m_PolyPoints
// which are relative to module position and module orientation = 0
std::vector<wxPoint> points = m_PolyPoints;
MODULE* module = (MODULE*) m_Parent;
if( module == NULL )
break;
for( unsigned ii = 0; ii < points.size(); ii++ )
{
wxPoint& pt = points[ii];
RotatePoint( &pt.x, &pt.y, module->m_Orient );
pt += module->m_Pos;
}
glcanvas->Draw3D_Polygon( points, zpos );
}
break;
default:
s.Printf( wxT( "Error: Shape nr %d not implemented!\n" ), m_Shape );
D( printf( "%s", TO_UTF8( s ) ); )
break;
}
}
}
else
{
glNormal3f( 0.0, 0.0, (m_Layer == LAYER_N_BACK) ? -1.0 : 1.0 );
zpos = g_Parm_3D_Visu.m_LayerZcoord[m_Layer];
switch( m_Shape )
{
case S_SEGMENT:
Draw3D_FilledSegment( x, -y, fx, -fy, w, zpos );
break;
case S_CIRCLE:
Draw3D_CircleSegment( x, -y, fx, -fy, w, zpos );
break;
case S_ARC:
Draw3D_ArcSegment( x, -y, fx, -fy, (double) m_Angle, w, zpos );
break;
case S_POLYGON:
{
// We must compute true coordinates from m_PolyPoints
// which are relative to module position and module orientation = 0
std::vector<wxPoint> points = m_PolyPoints;
MODULE* module = (MODULE*) m_Parent;
if( module == NULL )
break;
for( unsigned ii = 0; ii < points.size(); ii++ )
{
wxPoint& pt = points[ii];
RotatePoint( &pt.x, &pt.y, module->m_Orient );
pt += module->m_Pos;
}
glcanvas->Draw3D_Polygon( points, zpos );
}
break;
default:
s.Printf( wxT( "Error: Shape nr %d not implemented!\n" ), m_Shape );
D( printf( "%s", TO_UTF8( s ) ); )
break;
}
}
}
/* Draw 3D pads. */
void D_PAD::Draw3D( Pcb3D_GLCanvas* glcanvas )
{
int ii, ll, layer, nlmax;
int ux0, uy0,
dx, dx0, dy, dy0,
delta_cx, delta_cy,
xc, yc;
int angle, delta_angle;
double scale;
double zpos;
wxPoint shape_pos;
double x, y, r, w, hole, holeX, holeY;
double drillx, drilly;
bool Oncu, Oncmp, Both;
int color;
scale = g_Parm_3D_Visu.m_BoardScale;
holeX = (double) m_Drill.x * scale / 2;
holeY = (double) m_Drill.y * scale / 2;
hole = MIN( holeX, holeY );
/* Calculate the center of the pad. */
shape_pos = ReturnShapePos();
ux0 = shape_pos.x;
uy0 = shape_pos.y;
xc = ux0;
yc = uy0;
dx = dx0 = m_Size.x >> 1;
dy = dy0 = m_Size.y >> 1;
angle = m_Orient;
drillx = m_Pos.x * scale;
drilly = m_Pos.y * scale;
/* Draw the pad hole (TODO: draw OBLONG hole) */
if( holeX && holeY )
{
SetGLColor( DARKGRAY );
Draw3D_FilledCylinder( drillx, -drilly, hole,
g_Parm_3D_Visu.m_LayerZcoord[LAYER_N_FRONT], 0.0 );
}
glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis
nlmax = g_Parm_3D_Visu.m_Layers - 1;
Oncu = (m_Masque_Layer & LAYER_BACK) ? TRUE : FALSE;
Oncmp = (m_Masque_Layer & LAYER_FRONT) ? TRUE : FALSE;
Both = Oncu && Oncmp;
switch( m_PadShape & 0x7F )
{
case PAD_CIRCLE:
x = xc * scale;
y = yc * scale;
r = (double) dx * scale;
for( layer = FIRST_COPPER_LAYER; layer <= LAST_COPPER_LAYER; layer++ )
{
if( layer && (layer == nlmax) )
layer = LAYER_N_FRONT;
if( (layer == LAYER_N_FRONT) && !Oncmp )
continue;
if( (layer == LAYER_N_BACK) && !Oncu )
continue;
if( (layer > FIRST_COPPER_LAYER) && (layer < LAST_COPPER_LAYER)
&& !Both )
continue;
color = g_ColorsSettings.GetLayerColor( layer );
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) ==
false )
continue;
SetGLColor( color );
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
if( layer == LAYER_N_BACK )
zpos = zpos - 5 * g_Parm_3D_Visu.m_BoardScale;
else
zpos = zpos + 5 * g_Parm_3D_Visu.m_BoardScale;
Draw3D_FilledCircle( x, -y, r, hole, zpos );
}
break;
case PAD_OVAL:
if( dx > dy ) /* Horizontal ellipse */
{
delta_cx = dx - dy;
delta_cy = 0;
w = m_Size.y * scale;
delta_angle = angle + 900;
}
else /* Vertical ellipse */
{
delta_cx = 0;
delta_cy = dy - dx;
w = m_Size.x * scale;
delta_angle = angle;
}
RotatePoint( &delta_cx, &delta_cy, angle );
{
double ox, oy, fx, fy;
ox = (double) ( ux0 + delta_cx ) * scale;
oy = (double) ( uy0 + delta_cy ) * scale;
fx = (double) ( ux0 - delta_cx ) * scale;
fy = (double) ( uy0 - delta_cy ) * scale;
for( layer = FIRST_COPPER_LAYER;
layer <= LAST_COPPER_LAYER;
layer++ )
{
if( layer && (layer == nlmax) )
layer = LAYER_N_FRONT;
if( (layer == LAYER_N_FRONT) && !Oncmp )
continue;
if( (layer == LAYER_N_BACK) && !Oncu )
continue;
if( (layer > FIRST_COPPER_LAYER)
&& (layer < LAST_COPPER_LAYER) && !Both )
continue;
color = g_ColorsSettings.GetLayerColor( layer );
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) ==
false )
continue;
SetGLColor( color );
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
if( layer == LAYER_N_BACK )
zpos = zpos - 5 * g_Parm_3D_Visu.m_BoardScale;
else
zpos = zpos + 5 * g_Parm_3D_Visu.m_BoardScale;
Draw3D_FilledSegmentWithHole( ox, -oy, fx, -fy, w, drillx,
-drilly, hole, zpos );
}
}
break;
case PAD_RECT:
case PAD_TRAPEZOID:
{
wxPoint coord[5];
wxRealPoint fcoord[8], f_hole_coord[8];
BuildPadPolygon( coord, wxSize(0,0), angle );
for( ii = 0; ii < 4; ii++ )
{
coord[ii].x += ux0;
coord[ii].y += uy0;
ll = ii * 2;
fcoord[ll].x = coord[ii].x *scale;
fcoord[ll].y = coord[ii].y *scale;
}
for( ii = 0; ii < 7; ii += 2 )
{
ll = ii + 2;
if( ll > 7 )
ll -= 8;
fcoord[ii + 1].x = (fcoord[ii].x + fcoord[ll].x) / 2;
fcoord[ii + 1].y = (fcoord[ii].y + fcoord[ll].y) / 2;
}
for( ii = 0; ii < 8; ii++ )
{
f_hole_coord[ii].x = -hole * 0.707;
f_hole_coord[ii].y = hole * 0.707;
RotatePoint( &f_hole_coord[ii].x, &f_hole_coord[ii].y, angle - (ii * 450) );
f_hole_coord[ii].x += drillx;
f_hole_coord[ii].y += drilly;
}
for( layer = FIRST_COPPER_LAYER; layer <= LAST_COPPER_LAYER; layer++ )
{
if( layer && (layer == nlmax) )
layer = LAYER_N_FRONT;
if( (layer == LAYER_N_FRONT) && !Oncmp )
continue;
if( (layer == LAYER_N_BACK) && !Oncu )
continue;
if( (layer > FIRST_COPPER_LAYER) && (layer < LAST_COPPER_LAYER)
&& !Both )
continue;
color = g_ColorsSettings.GetLayerColor( layer );
glNormal3f( 0.0, 0.0, (layer == LAYER_N_BACK) ? -1.0 : 1.0 );
if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) ==
false )
continue;
SetGLColor( color );
zpos = g_Parm_3D_Visu.m_LayerZcoord[layer];
if( layer == LAYER_N_BACK )
zpos = zpos - 5 * g_Parm_3D_Visu.m_BoardScale;
else
zpos = zpos + 5 * g_Parm_3D_Visu.m_BoardScale;
glBegin( GL_QUAD_STRIP );
for( ii = 0; ii < 8; ii++ )
{
glVertex3f( f_hole_coord[ii].x, -f_hole_coord[ii].y, zpos );
glVertex3f( fcoord[ii].x, -fcoord[ii].y, zpos );
}
glVertex3f( f_hole_coord[0].x, -f_hole_coord[0].y, zpos );
glVertex3f( fcoord[0].x, -fcoord[0].y, zpos );
glEnd();
}
}
break;
default:
break;
}
}
void SetGLColor( int color )
{
double red, green, blue;
StructColors colordata = ColorRefs[color & MASKCOLOR];
red = colordata.m_Red / 255.0;
blue = colordata.m_Blue / 255.0;
green = colordata.m_Green / 255.0;
glColor3f( red, green, blue );
}
static void Draw3D_FilledCircle( double posx, double posy,
double rayon, double hole, double zpos )
{
int ii, slice = 16;
double x, y;
glBegin( GL_QUAD_STRIP );
for( ii = 0; ii <= slice; ii++ )
{
x = hole;
y = 0.0;
RotatePoint( &x, &y, ii * 225 );
glVertex3f( x + posx, y + posy, zpos );
x = rayon;
y = 0.0;
RotatePoint( &x, &y, ii * 225 );
glVertex3f( x + posx, y + posy, zpos );
}
glEnd();
}
static void Draw3D_FilledCylinder( double posx, double posy, double rayon,
double height, double zpos )
{
int ii;
double x, y;
#define NB_SEGM 12
S3D_Vertex coords[4];
double tmp = DataScale3D;
DataScale3D = 1.0; // Coordinate is already in range for Set_Object_Data();
coords[0].x = coords[1].x = posx + rayon;
coords[0].y = coords[1].y = posy;
coords[0].z = coords[3].z = zpos;
coords[1].z = coords[2].z = zpos + height;
for( ii = 0; ii <= NB_SEGM; ii++ )
{
x = rayon;
y = 0.0;
RotatePoint( &x, &y, ii * (3600 / NB_SEGM) );
coords[2].x = coords[3].x = posx + x;
coords[2].y = coords[3].y = posy + y;
Set_Object_Data( coords, 4 );
coords[0].x = coords[2].x;
coords[0].y = coords[2].y;
coords[1].x = coords[3].x;
coords[1].y = coords[3].y;
}
glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis
DataScale3D = tmp;
}
/* Draw a polygon similar to a segment has rounded tips */
static void Draw3D_FilledSegment( double startx, double starty, double endx,
double endy, double width, double zpos )
{
double dx, dy, x, y, firstx = 0, firsty = 0;
int ii, angle;
// Calculate the coordinates of the segment assumed horizontal.
// Then turn the strips of the desired angle.
dx = endx - startx;
dy = endy - starty;
angle = (int) ( ( atan2( dy, dx ) * 1800 / M_PI ) + 0.5 );
RotatePoint( &dx, &dy, angle );
width /= 2;
glBegin( GL_POLYGON );
// Trace the flare to right (1st half polygon at the end of the segment)
for( ii = 0; ii <= 8; ii++ )
{
x = 0.0;
y = -width;
RotatePoint( &x, &y, -ii * 225 );
x += dx;
RotatePoint( &x, &y, -angle );
glVertex3f( startx + x, starty + y, zpos );
if( ii == 0 )
{
firstx = startx + x;
firsty = starty + y;
}
}
// Rounding the left (2nd half polygon is the origin of the segment)
for( ii = 0; ii <= 8; ii++ )
{
int jj = ii * 225;
x = 0.0;
y = width;
RotatePoint( &x, &y, -angle - jj );
glVertex3f( startx + x, starty + y, zpos );
}
glVertex3f( firstx, firsty, zpos );
glEnd();
}
/* Draw a polygon similar to a segment ends with round hole
*/
static void Draw3D_FilledSegmentWithHole( double startx, double starty,
double endx, double endy,
double width, double holex,
double holey, double holeradius,
double zpos )
{
double x, y, xin, yin;
double firstx = 0, firsty = 0, firstxin = 0, firstyin = 0;
int ii, angle, theta;
// Calculate the coordinates of the segment assumed horizontal
// Then turn the strips of the desired angle
// All calculations are done with startx, starty as the origin of the route
endx -= startx;
endy -= starty;
holex -= startx;
holey -= starty;
angle = (int) ( ( atan2( endy, endx ) * 1800 / M_PI ) + 0.5 );
RotatePoint( &endx, &endy, angle );
RotatePoint( &holex, &holey, angle );
width /= 2;
glBegin( GL_QUAD_STRIP );
// Path of the flare to right (1st half polygon at the end of the segment)
// around the half-hole drilling
for( ii = 0; ii <= 8; ii++ )
{
x = 0.0;
y = -width;
xin = 0.0;
yin = -holeradius;
theta = -ii * 225;
RotatePoint( &x, &y, theta );
RotatePoint( &xin, &yin, theta );
x += endx;
RotatePoint( &x, &y, -angle );
xin += holex;
RotatePoint( &xin, &yin, -angle );
glVertex3f( startx + xin, starty + yin, zpos );
glVertex3f( startx + x, starty + y, zpos );
if( ii == 0 )
{
firstx = startx + x;
firsty = starty + y;
firstxin = startx + xin;
firstyin = starty + yin;
}
}
// Layout of the rounded left (2nd half polygon is the origin of the
// segment)
for( ii = 0; ii <= 8; ii++ )
{
theta = -ii * 225;
x = 0.0;
y = width;
RotatePoint( &x, &y, -angle + theta );
xin = 0.0;
yin = holeradius;
RotatePoint( &xin, &yin, theta );
xin += holex;
RotatePoint( &xin, &yin, -angle );
glVertex3f( startx + xin, starty + yin, zpos );
glVertex3f( startx + x, starty + y, zpos );
}
glVertex3f( firstxin, firstyin, zpos );
glVertex3f( firstx, firsty, zpos );
glEnd();
}
static void Draw3D_ArcSegment( double startx, double starty, double centrex,
double centrey, double arc_angle, double width, double zpos )
{
int ii;
int slice = 36; // Number of segments to approximate a circle by segments
double hole, rayon;
double arcStart_Angle;
arcStart_Angle = (atan2( startx - centrex, starty - centrey ) * 1800 / M_PI );
rayon = hypot( startx - centrex, starty - centrey ) + ( width / 2);
hole = rayon - width;
// Calculate the number of segments to approximate this arc
int imax = (int) ( (double) arc_angle * slice / 3600.0 );
if( imax < 0 )
imax = -imax;
if( imax == 0 )
imax = 1;
// Adjust delta_angle to have exactly imax segments in arc_angle
// i.e. arc_angle = imax delta_agnle.
double delta_angle = (double) arc_angle / imax;
glBegin( GL_QUAD_STRIP );
for( ii = 0; ii <= imax; ii++ )
{
double angle = (double) ii * delta_angle;
angle += arcStart_Angle + 900;
double dx = hole;
double dy = 0.0;
RotatePoint( &dx, &dy, (int) angle );
glVertex3f( dx + startx, dy + starty, zpos );
dx = rayon;
dy = 0.0;
RotatePoint( &dx, &dy, (int) angle );
glVertex3f( dx + startx, dy + starty, zpos );
}
glEnd();
}
static void Draw3D_CircleSegment( double startx, double starty, double endx,
double endy, double width, double zpos )
{
int ii, slice = 36;
double x, y, hole, rayon;
rayon = hypot( startx - endx, starty - endy ) + ( width / 2);
hole = rayon - width;
glBegin( GL_QUAD_STRIP );
for( ii = 0; ii <= slice; ii++ )
{
x = hole; y = 0.0;
RotatePoint( &x, &y, ii * 3600 / slice );
glVertex3f( x + startx, y + starty, zpos );
x = rayon; y = 0.0;
RotatePoint( &x, &y, ii * 3600 / slice );
glVertex3f( x + startx, y + starty, zpos );
}
glEnd();
}
/**
* Function Draw3D_Polygon
* draw one solid polygon
* @param aCornersList = a std::vector<wxPoint> liste of corners, in physical coordinates
* @param aZpos = the z position in 3D units
*/
void Pcb3D_GLCanvas::Draw3D_Polygon( std::vector<wxPoint>& aCornersList, double aZpos )
{
g_Parm_3D_Visu.m_ActZpos = aZpos;
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*)() )tesswxPoint2Vertex );
GLdouble v_data[3];
v_data[2] = aZpos;
//gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
// Draw solid polygon
gluTessBeginPolygon( tess, NULL );
gluTessBeginContour( tess );
for( unsigned ii = 0; ii < aCornersList.size(); ii++ )
{
v_data[0] = aCornersList[ii].x * g_Parm_3D_Visu.m_BoardScale;
v_data[1] = -aCornersList[ii].y * g_Parm_3D_Visu.m_BoardScale;
// gluTessVertex store pointers on data, not data, so do not store
// different corners values in a temporary variable
// but send pointer on each corner value in aCornersList
gluTessVertex( tess, v_data, &aCornersList[ii] );
}
gluTessEndContour( tess );
gluTessEndPolygon( tess );
gluDeleteTess( tess );
}
static int Get3DLayerEnable( int act_layer )
{
bool enablelayer;
enablelayer = TRUE;
if( act_layer == DRAW_N && !g_Parm_3D_Visu.m_Draw3DDrawings )
enablelayer = FALSE;
if( act_layer == COMMENT_N && !g_Parm_3D_Visu.m_Draw3DComments )
enablelayer = FALSE;
if( act_layer == ECO1_N && !g_Parm_3D_Visu.m_Draw3DEco1 )
enablelayer = FALSE;
if( act_layer == ECO2_N && !g_Parm_3D_Visu.m_Draw3DEco2 )
enablelayer = FALSE;
return enablelayer;
}
static GLfloat Get3DLayerSide( int act_layer )
{
GLfloat nZ;
nZ = 1.0;
if( ( act_layer <= LAST_COPPER_LAYER - 1 )
|| ( act_layer == ADHESIVE_N_BACK )
|| ( act_layer == SOLDERPASTE_N_BACK )
|| ( act_layer == SILKSCREEN_N_BACK )
|| ( act_layer == SOLDERMASK_N_BACK ) )
nZ = -1.0;
return nZ;
}
///////////////////////////////////////////////////////////////////////////////
// GLU_TESS CALLBACKS
///////////////////////////////////////////////////////////////////////////////
void CALLBACK tessBeginCB( GLenum which )
{
glBegin( which );
}
void CALLBACK tessEndCB()
{
glEnd();
}
void CALLBACK tessCPolyPt2Vertex( const GLvoid* data )
{
// cast back to double type
const CPolyPt* ptr = (const CPolyPt*) data;
glVertex3f( ptr->x * g_Parm_3D_Visu.m_BoardScale,
-ptr->y * g_Parm_3D_Visu.m_BoardScale,
g_Parm_3D_Visu.m_ActZpos );
}
void CALLBACK tesswxPoint2Vertex( const GLvoid* data )
{
const wxPoint* ptr = (const wxPoint*) data;
glVertex3f( ptr->x * g_Parm_3D_Visu.m_BoardScale,
-ptr->y * g_Parm_3D_Visu.m_BoardScale,
g_Parm_3D_Visu.m_ActZpos );
}
void CALLBACK tessErrorCB( GLenum errorCode )
{
const GLubyte* errorStr;
errorStr = gluErrorString( errorCode );
// DEBUG //
D( printf( "Tess ERROR: %s\n", errorStr ); )
}