Tutorials: 3D Plugin Class
This section contains a description of two very simple plugins of the PLUGIN_3D class and walks the user through the setup and building of the code.
Basic 3D Plugin
This tutorial walks the user through the development of a very basic 3D plugin named “PLUGIN_3D_DEMO1”. The purpose of this tutorial is only to demonstrate the construction of a very basic 3D plugin which does nothing other than provide a few filter strings which permit the KiCad user to filter file names while browsing for 3D models. The code demonstrated here is the absolute minimum requirement for any 3D plugin and can be used as a template for creating more advanced plugins.
In order to build the demo project we require the following:
KiCad plugin headers
KiCad Scene Graph library
kicad_3dsg
To automatically detect the KiCad headers and library we shall use a CMake FindPackage script; the script supplied in this tutorial should work on Linux and Windows if the relevant header files are installed to ${KICAD_ROOT_DIR}/kicad and the KiCad Scene Graph library is installed in ${KICAD_ROOT_DIR}/lib.
To start let’s create a project directory and the FindPackage script:
mkdir demo && cd demoexport DEMO_ROOT=${PWD}mkdir CMakeModules && cd CMakeModulescat > FindKICAD.cmake << _EOFfind_path( KICAD_INCLUDE_DIR kicad/plugins/kicad_plugin.hPATHS ${KICAD_ROOT_DIR}/include $ENV{KICAD_ROOT_DIR}/includeDOC "Kicad plugins header path.")if( NOT ${KICAD_INCLUDE_DIR} STREQUAL "KICAD_INCLUDE_DIR-NOTFOUND" )# attempt to extract the version information from sg_version.hfind_file( KICAD_SGVERSION sg_version.hPATHS ${KICAD_INCLUDE_DIR}PATH_SUFFIXES kicad/plugins/3dapiNO_DEFAULT_PATH )if( NOT ${KICAD_SGVERSION} STREQUAL "KICAD_SGVERSION-NOTFOUND" )# extract the "#define KICADSG_VERSION*" linesfile( STRINGS ${KICAD_SGVERSION} _version REGEX "^#define.*KICADSG_VERSION.*" )foreach( SVAR ${_version} )string( REGEX MATCH KICADSG_VERSION_[M,A,J,O,R,I,N,P,T,C,H,E,V,I,S]* _VARNAME ${SVAR} )string( REGEX MATCH [0-9]+ _VALUE ${SVAR} )if( NOT ${_VARNAME} STREQUAL "" AND NOT ${_VALUE} STREQUAL "" )set( _${_VARNAME} ${_VALUE} )endif()endforeach()#ensure that NOT SG3D_VERSION* will evaluate to '0'if( NOT _KICADSG_VERSION_MAJOR )set( _KICADSG_VERSION_MAJOR 0 )endif()if( NOT _KICADSG_VERSION_MINOR )set( _KICADSG_VERSION_MINOR 0 )endif()if( NOT _KICADSG_VERSION_PATCH )set( _KICADSG_VERSION_PATCH 0 )endif()if( NOT _KICADSG_VERSION_REVISION )set( _KICADSG_VERSION_REVISION 0 )endif()set( KICAD_VERSION ${_KICADSG_VERSION_MAJOR}.${_KICADSG_VERSION_MINOR}.${_KICADSG_VERSION_PATCH}.${_KICADSG_VERSION_REVISION} )unset( KICAD_SGVERSION CACHE )endif()endif()find_library( KICAD_LIBRARYNAMES kicad_3dsgPATHS${KICAD_ROOT_DIR}/lib $ENV{KICAD_ROOT_DIR}/lib${KICAD_ROOT_DIR}/bin $ENV{KICAD_ROOT_DIR}/binDOC "Kicad scenegraph library path.")include( FindPackageHandleStandardArgs )FIND_PACKAGE_HANDLE_STANDARD_ARGS( KICADREQUIRED_VARSKICAD_INCLUDE_DIRKICAD_LIBRARYKICAD_VERSIONVERSION_VAR KICAD_VERSION )mark_as_advanced( KICAD_INCLUDE_DIR )set( KICAD_VERSION_MAJOR ${_KICADSG_VERSION_MAJOR} CACHE INTERNAL "" )set( KICAD_VERSION_MINOR ${_KICADSG_VERSION_MINOR} CACHE INTERNAL "" )set( KICAD_VERSION_PATCH ${_KICADSG_VERSION_PATCH} CACHE INTERNAL "" )set( KICAD_VERSION_TWEAK ${_KICADSG_VERSION_REVISION} CACHE INTERNAL "" )_EOF
Kicad and its plugin headers must be installed; if they are installed to a user directory or under /opt on Linux, or you are using Windows, you will need to set the KICAD_ROOT_DIR environment variable to point to the directory containing the KiCad include and lib directories. For OS X the FindPackage script presented here may require some adjustments.
To configure and build the tutorial code we will use CMake and create a CMakeLists.txt script file:
cd ${DEMO_ROOT}cat > CMakeLists.txt << _EOF# declare the name of the projectproject( PLUGIN_DEMO )# check that we have a version of CMake with all required featurescmake_minimum_required( VERSION 2.8.12 FATAL_ERROR )# inform CMake of where to find the FindKICAD scriptset( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules )# attempt to discover the installed kicad headers and library# and set the variables:# KICAD_INCLUDE_DIR# KICAD_LIBRARYfind_package( KICAD 1.0 REQUIRED )# add the kicad include directory to the compiler's search pathinclude_directories( ${KICAD_INCLUDE_DIR}/kicad )# create a plugin named s3d_plugin_demo1add_library( s3d_plugin_demo1 MODULEsrc/s3d_plugin_demo1.cpp)_EOF
The first demo project is very basic; it consists of a single file with no external link dependencies other than the compiler defaults. We start by creating a source directory:
cd ${DEMO_ROOT}mkdir src && cd srcexport DEMO_SRC=${PWD}
Now we create the plugin source itself:
s3d_plugin_demo1.cpp
#include <iostream>// the 3d_plugin.h header defines the functions required of 3D plugins#include "plugins/3d/3d_plugin.h"// define the version information of this plugin; do not confuse this// with the Plugin Class version which is defined in 3d_plugin.h#define PLUGIN_3D_DEMO1_MAJOR 1#define PLUGIN_3D_DEMO1_MINOR 0#define PLUGIN_3D_DEMO1_PATCH 0#define PLUGIN_3D_DEMO1_REVNO 0// implement the function which provides users with this plugin's nameconst char* GetKicadPluginName( void ){return "PLUGIN_3D_DEMO1";}// implement the function which provides users with this plugin's versionvoid GetPluginVersion( unsigned char* Major, unsigned char* Minor,unsigned char* Patch, unsigned char* Revision ){if( Major )*Major = PLUGIN_3D_DEMO1_MAJOR;if( Minor )*Minor = PLUGIN_3D_DEMO1_MINOR;if( Patch )*Patch = PLUGIN_3D_DEMO1_PATCH;if( Revision )*Revision = PLUGIN_3D_DEMO1_REVNO;return;}// number of extensions supported; on *NIX systems the extensions are// provided twice - once in lower case and once in upper case letters#ifdef _WIN32#define NEXTS 7#else#define NEXTS 14#endif// number of filter sets supported#define NFILS 5// define the extension strings and filter strings which this// plugin will supply to the userstatic char ext0[] = "wrl";static char ext1[] = "x3d";static char ext2[] = "emn";static char ext3[] = "iges";static char ext4[] = "igs";static char ext5[] = "stp";static char ext6[] = "step";#ifdef _WIN32static char fil0[] = "VRML 1.0/2.0 (*.wrl)|*.wrl";static char fil1[] = "X3D (*.x3d)|*.x3d";static char fil2[] = "IDF 2.0/3.0 (*.emn)|*.emn";static char fil3[] = "IGESv5.3 (*.igs;*.iges)|*.igs;*.iges";static char fil4[] = "STEP (*.stp;*.step)|*.stp;*.step";#elsestatic char ext7[] = "WRL";static char ext8[] = "X3D";static char ext9[] = "EMN";static char ext10[] = "IGES";static char ext11[] = "IGS";static char ext12[] = "STP";static char ext13[] = "STEP";static char fil0[] = "VRML 1.0/2.0 (*.wrl;*.WRL)|*.wrl;*.WRL";static char fil1[] = "X3D (*.x3d;*.X3D)|*.x3d;*.X3D";static char fil2[] = "IDF 2.0/3.0 (*.emn;*.EMN)|*.emn;*.EMN";static char fil3[] = "IGESv5.3 (*.igs;*.iges;*.IGS;*.IGES)|*.igs;*.iges;*.IGS;*.IGES";static char fil4[] = "STEP (*.stp;*.step;*.STP;*.STEP)|*.stp;*.step;*.STP;*.STEP";#endif// instantiate a convenient data structure for accessing the// lists of extension and filter stringsstatic struct FILE_DATA{char const* extensions[NEXTS];char const* filters[NFILS];FILE_DATA(){extensions[0] = ext0;extensions[1] = ext1;extensions[2] = ext2;extensions[3] = ext3;extensions[4] = ext4;extensions[5] = ext5;extensions[6] = ext6;filters[0] = fil0;filters[1] = fil1;filters[2] = fil2;filters[3] = fil3;filters[4] = fil4;#ifndef _WIN32extensions[7] = ext7;extensions[8] = ext8;extensions[9] = ext9;extensions[10] = ext10;extensions[11] = ext11;extensions[12] = ext12;extensions[13] = ext13;#endifreturn;}} file_data;// return the number of extensions supported by this pluginint GetNExtensions( void ){return NEXTS;}// return the indexed extension stringchar const* GetModelExtension( int aIndex ){if( aIndex < 0 || aIndex >= NEXTS )return NULL;return file_data.extensions[aIndex];}// return the number of filter strings provided by this pluginint GetNFilters( void ){return NFILS;}// return the indexed filter stringchar const* GetFileFilter( int aIndex ){if( aIndex < 0 || aIndex >= NFILS )return NULL;return file_data.filters[aIndex];}// return false since this plugin does not provide visualization databool CanRender( void ){return false;}// return NULL since this plugin does not provide visualization dataSCENEGRAPH* Load( char const* aFileName ){// this dummy plugin does not support rendering of any modelsreturn NULL;}
This source file meets all the minimum requirements to implement a 3D plugin. The plugin does not produce any data for rendering models but it can provide KiCad with a list of supported model file extensions and file extension filters to enhance the 3D model file selection dialog. Within KiCad the extension strings are used to select the plugins which may be used to load a specified model; for example, if the plugin is wrl then KiCad will invoke each plugin which claims to support the extension wrl until a plugin returns visualization data. The file filters provided by each plugin are passed to the 3D file selector dialog to improve the browsing UI.
To build the plugin:
cd ${DEMO_ROOT}# export KICAD_ROOT_DIR if necessarymkdir build && cd buildcmake .. && make
The plugin will be built but not installed; you must copy the plugin file to KiCad’s plugin directory if you wish to load the plugin.
Advanced 3D Plugin
This tutorial walks the user through the development of a 3D plugin named “PLUGIN_3D_DEMO2”. The purpose of this tutorial is to demonstrate the construction of a very basic scene graph which the KiCad previewer can render. The plugin claims to handle files of type txt. Although the file must exist in order for the cache manager to invoke the plugin, the file contents are not processed by this plugin; instead, the plugin simply creates a scene graph containing a pair of tetrahedra. This tutorial assumes that the first tutorial had been completed and that the CMakeLists.txt and FindKICAD.cmake script files have been created.
Place the new source file in the same directory as the previous tutorial’s source file and we will extend the previous tutorial’s CMakeLists.txt file to build this tutorial. Since this plugin will create a scene graph for KiCad we need to link to KiCad’s scene graph library kicad_3dsg. KiCad’s Scene Graph Library provides a set of classes which can be used to build the Scene Graph Object; the Scene Graph Object is an intermediate data visualization format used by the 3D Cache Manager. All plugins which support model visualization must translate the model data into a scene graph via this library.
The first step is to extend CMakeLists.txt to build this tutorial project:
cd ${DEMO_ROOT}cat >> CMakeLists.txt << _EOFadd_library( s3d_plugin_demo2 MODULEsrc/s3d_plugin_demo2.cpp)target_link_libraries( s3d_plugin_demo2 ${KICAD_LIBRARY} )_EOF
Now we change to the source directory and create the source file:
cd ${DEMO_SRC}
s3d_plugin_demo2.cpp
#include <cmath>// 3D Plugin Class declarations#include "plugins/3d/3d_plugin.h"// interface to KiCad Scene Graph Library#include "plugins/3dapi/ifsg_all.h"// version information for this plugin#define PLUGIN_3D_DEMO2_MAJOR 1#define PLUGIN_3D_DEMO2_MINOR 0#define PLUGIN_3D_DEMO2_PATCH 0#define PLUGIN_3D_DEMO2_REVNO 0// provide the name of this pluginconst char* GetKicadPluginName( void ){return "PLUGIN_3D_DEMO2";}// provide the version of this pluginvoid GetPluginVersion( unsigned char* Major, unsigned char* Minor,unsigned char* Patch, unsigned char* Revision ){if( Major )*Major = PLUGIN_3D_DEMO2_MAJOR;if( Minor )*Minor = PLUGIN_3D_DEMO2_MINOR;if( Patch )*Patch = PLUGIN_3D_DEMO2_PATCH;if( Revision )*Revision = PLUGIN_3D_DEMO2_REVNO;return;}// number of extensions supported#ifdef _WIN32#define NEXTS 1#else#define NEXTS 2#endif// number of filter sets supported#define NFILS 1static char ext0[] = "txt";#ifdef _WIN32static char fil0[] = "demo (*.txt)|*.txt";#elsestatic char ext1[] = "TXT";static char fil0[] = "demo (*.txt;*.TXT)|*.txt;*.TXT";#endifstatic struct FILE_DATA{char const* extensions[NEXTS];char const* filters[NFILS];FILE_DATA(){extensions[0] = ext0;filters[0] = fil0;#ifndef _WIN32extensions[1] = ext1;#endifreturn;}} file_data;int GetNExtensions( void ){return NEXTS;}char const* GetModelExtension( int aIndex ){if( aIndex < 0 || aIndex >= NEXTS )return NULL;return file_data.extensions[aIndex];}int GetNFilters( void ){return NFILS;}char const* GetFileFilter( int aIndex ){if( aIndex < 0 || aIndex >= NFILS )return NULL;return file_data.filters[aIndex];}// return true since this plugin can provide visualization databool CanRender( void ){return true;}// create the visualization dataSCENEGRAPH* Load( char const* aFileName ){// For this demonstration we create a tetrahedron (tx1) consisting// of a SCENEGRAPH (VRML Transform) which in turn contains 4// SGSHAPE (VRML Shape) objects representing each of the sides of// the tetrahedron. Each Shape is associated with a color (SGAPPEARANCE)// and a SGFACESET (VRML Geometry->indexedFaceSet). Each SGFACESET is// associated with a vertex list (SGCOORDS), a per-vertex normals// list (SGNORMALS), and a coordinate index (SGCOORDINDEX). One shape// is used to represent each face so that we may use per-vertex-per-face// normals.//// The tetrahedron in turn is a child of a top level SCENEGRAPH (tx0)// which has a second SCENEGRAPH child (tx2) which is a transformation// of the tetrahedron tx1 (rotation + translation). This demonstrates// the reuse of components within the scene graph hierarchy.// define the vertices of the tetrahedron// face 1: 0, 3, 1// face 2: 0, 2, 3// face 3: 1, 3, 2// face 4: 0, 1, 2double SQ2 = sqrt( 0.5 );SGPOINT vert[4];vert[0] = SGPOINT( 1.0, 0.0, -SQ2 );vert[1] = SGPOINT( -1.0, 0.0, -SQ2 );vert[2] = SGPOINT( 0.0, 1.0, SQ2 );vert[3] = SGPOINT( 0.0, -1.0, SQ2 );// create the top level transform; this will hold all other// scenegraph objects; a transform may hold other transforms and// shapesIFSG_TRANSFORM* tx0 = new IFSG_TRANSFORM( true );// create the transform which will house the shapesIFSG_TRANSFORM* tx1 = new IFSG_TRANSFORM( tx0->GetRawPtr() );// add a shape which we will use to define one face of the tetrahedron;// shapes hold facesets and appearancesIFSG_SHAPE* shape = new IFSG_SHAPE( *tx1 );// add a faceset; these contain coordinate lists, coordinate indices,// vertex lists, vertex indices, and may also contain color lists and// their indices.IFSG_FACESET* face = new IFSG_FACESET( *shape );IFSG_COORDS* cp = new IFSG_COORDS( *face );cp->AddCoord( vert[0] );cp->AddCoord( vert[3] );cp->AddCoord( vert[1] );// coordinate indices - note: enforce triangles;// in real plugins where it is not necessarily possible// to determine which side a triangle is visible from,// 2 point orders must be specified for each triangleIFSG_COORDINDEX* coordIdx = new IFSG_COORDINDEX( *face );coordIdx->AddIndex( 0 );coordIdx->AddIndex( 1 );coordIdx->AddIndex( 2 );// create an appearance; appearances are owned by shapes// magentaIFSG_APPEARANCE* material = new IFSG_APPEARANCE( *shape);material->SetSpecular( 0.1, 0.0, 0.1 );material->SetDiffuse( 0.8, 0.0, 0.8 );material->SetAmbient( 0.2, 0.2, 0.2 );material->SetShininess( 0.2 );// normalsIFSG_NORMALS* np = new IFSG_NORMALS( *face );SGVECTOR nval = S3D::CalcTriNorm( vert[0], vert[3], vert[1] );np->AddNormal( nval );np->AddNormal( nval );np->AddNormal( nval );//// Shape2// Note: we reuse the IFSG* wrappers to create and manipulate new// data structures.//shape->NewNode( *tx1 );face->NewNode( *shape );coordIdx->NewNode( *face );cp->NewNode( *face );np->NewNode( *face );// verticescp->AddCoord( vert[0] );cp->AddCoord( vert[2] );cp->AddCoord( vert[3] );// indicescoordIdx->AddIndex( 0 );coordIdx->AddIndex( 1 );coordIdx->AddIndex( 2 );// normalsnval = S3D::CalcTriNorm( vert[0], vert[2], vert[3] );np->AddNormal( nval );np->AddNormal( nval );np->AddNormal( nval );// color (red)material->NewNode( *shape );material->SetSpecular( 0.2, 0.0, 0.0 );material->SetDiffuse( 0.9, 0.0, 0.0 );material->SetAmbient( 0.2, 0.2, 0.2 );material->SetShininess( 0.1 );//// Shape3//shape->NewNode( *tx1 );face->NewNode( *shape );coordIdx->NewNode( *face );cp->NewNode( *face );np->NewNode( *face );// verticescp->AddCoord( vert[1] );cp->AddCoord( vert[3] );cp->AddCoord( vert[2] );// indicescoordIdx->AddIndex( 0 );coordIdx->AddIndex( 1 );coordIdx->AddIndex( 2 );// normalsnval = S3D::CalcTriNorm( vert[1], vert[3], vert[2] );np->AddNormal( nval );np->AddNormal( nval );np->AddNormal( nval );// color (green)material->NewNode( *shape );material->SetSpecular( 0.0, 0.1, 0.0 );material->SetDiffuse( 0.0, 0.9, 0.0 );material->SetAmbient( 0.2, 0.2, 0.2 );material->SetShininess( 0.1 );//// Shape4//shape->NewNode( *tx1 );face->NewNode( *shape );coordIdx->NewNode( *face );cp->NewNode( *face );np->NewNode( *face );// verticescp->AddCoord( vert[0] );cp->AddCoord( vert[1] );cp->AddCoord( vert[2] );// indicescoordIdx->AddIndex( 0 );coordIdx->AddIndex( 1 );coordIdx->AddIndex( 2 );// normalsnval = S3D::CalcTriNorm( vert[0], vert[1], vert[2] );np->AddNormal( nval );np->AddNormal( nval );np->AddNormal( nval );// color (blue)material->NewNode( *shape );material->SetSpecular( 0.0, 0.0, 0.1 );material->SetDiffuse( 0.0, 0.0, 0.9 );material->SetAmbient( 0.2, 0.2, 0.2 );material->SetShininess( 0.1 );// create a copy of the entire tetrahedron shifted Z+2 and rotated 2/3PIIFSG_TRANSFORM* tx2 = new IFSG_TRANSFORM( tx0->GetRawPtr() );tx2->AddRefNode( *tx1 );tx2->SetTranslation( SGPOINT( 0, 0, 2 ) );tx2->SetRotation( SGVECTOR( 0, 0, 1 ), M_PI*2.0/3.0 );SGNODE* data = tx0->GetRawPtr();// delete the wrappersdelete shape;delete face;delete coordIdx;delete material;delete cp;delete np;delete tx0;delete tx1;delete tx2;return (SCENEGRAPH*)data;}