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 demo
export DEMO_ROOT=${PWD}
mkdir CMakeModules && cd CMakeModules
cat > FindKICAD.cmake << _EOF
find_path( KICAD_INCLUDE_DIR kicad/plugins/kicad_plugin.h
PATHS ${KICAD_ROOT_DIR}/include $ENV{KICAD_ROOT_DIR}/include
DOC "Kicad plugins header path."
)
if( NOT ${KICAD_INCLUDE_DIR} STREQUAL "KICAD_INCLUDE_DIR-NOTFOUND" )
# attempt to extract the version information from sg_version.h
find_file( KICAD_SGVERSION sg_version.h
PATHS ${KICAD_INCLUDE_DIR}
PATH_SUFFIXES kicad/plugins/3dapi
NO_DEFAULT_PATH )
if( NOT ${KICAD_SGVERSION} STREQUAL "KICAD_SGVERSION-NOTFOUND" )
# extract the "#define KICADSG_VERSION*" lines
file( 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_LIBRARY
NAMES kicad_3dsg
PATHS
${KICAD_ROOT_DIR}/lib $ENV{KICAD_ROOT_DIR}/lib
${KICAD_ROOT_DIR}/bin $ENV{KICAD_ROOT_DIR}/bin
DOC "Kicad scenegraph library path."
)
include( FindPackageHandleStandardArgs )
FIND_PACKAGE_HANDLE_STANDARD_ARGS( KICAD
REQUIRED_VARS
KICAD_INCLUDE_DIR
KICAD_LIBRARY
KICAD_VERSION
VERSION_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 project
project( PLUGIN_DEMO )
# check that we have a version of CMake with all required features
cmake_minimum_required( VERSION 2.8.12 FATAL_ERROR )
# inform CMake of where to find the FindKICAD script
set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules )
# attempt to discover the installed kicad headers and library
# and set the variables:
# KICAD_INCLUDE_DIR
# KICAD_LIBRARY
find_package( KICAD 1.0 REQUIRED )
# add the kicad include directory to the compiler's search path
include_directories( ${KICAD_INCLUDE_DIR}/kicad )
# create a plugin named s3d_plugin_demo1
add_library( s3d_plugin_demo1 MODULE
src/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 src
export 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 name
const char* GetKicadPluginName( void )
{
return "PLUGIN_3D_DEMO1";
}
// implement the function which provides users with this plugin's version
void 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 user
static 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 _WIN32
static 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";
#else
static 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 strings
static 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 _WIN32
extensions[7] = ext7;
extensions[8] = ext8;
extensions[9] = ext9;
extensions[10] = ext10;
extensions[11] = ext11;
extensions[12] = ext12;
extensions[13] = ext13;
#endif
return;
}
} file_data;
// return the number of extensions supported by this plugin
int GetNExtensions( void )
{
return NEXTS;
}
// return the indexed extension string
char 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 plugin
int GetNFilters( void )
{
return NFILS;
}
// return the indexed filter string
char 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 data
bool CanRender( void )
{
return false;
}
// return NULL since this plugin does not provide visualization data
SCENEGRAPH* Load( char const* aFileName )
{
// this dummy plugin does not support rendering of any models
return 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 necessary
mkdir build && cd build
cmake .. && 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 << _EOF
add_library( s3d_plugin_demo2 MODULE
src/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 plugin
const char* GetKicadPluginName( void )
{
return "PLUGIN_3D_DEMO2";
}
// provide the version of this plugin
void 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 1
static char ext0[] = "txt";
#ifdef _WIN32
static char fil0[] = "demo (*.txt)|*.txt";
#else
static char ext1[] = "TXT";
static char fil0[] = "demo (*.txt;*.TXT)|*.txt;*.TXT";
#endif
static struct FILE_DATA
{
char const* extensions[NEXTS];
char const* filters[NFILS];
FILE_DATA()
{
extensions[0] = ext0;
filters[0] = fil0;
#ifndef _WIN32
extensions[1] = ext1;
#endif
return;
}
} 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 data
bool CanRender( void )
{
return true;
}
// create the visualization data
SCENEGRAPH* 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, 2
double 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
// shapes
IFSG_TRANSFORM* tx0 = new IFSG_TRANSFORM( true );
// create the transform which will house the shapes
IFSG_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 appearances
IFSG_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 triangle
IFSG_COORDINDEX* coordIdx = new IFSG_COORDINDEX( *face );
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// create an appearance; appearances are owned by shapes
// magenta
IFSG_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 );
// normals
IFSG_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 );
// vertices
cp->AddCoord( vert[0] );
cp->AddCoord( vert[2] );
cp->AddCoord( vert[3] );
// indices
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// normals
nval = 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 );
// vertices
cp->AddCoord( vert[1] );
cp->AddCoord( vert[3] );
cp->AddCoord( vert[2] );
// indices
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// normals
nval = 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 );
// vertices
cp->AddCoord( vert[0] );
cp->AddCoord( vert[1] );
cp->AddCoord( vert[2] );
// indices
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// normals
nval = 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/3PI
IFSG_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 wrappers
delete shape;
delete face;
delete coordIdx;
delete material;
delete cp;
delete np;
delete tx0;
delete tx1;
delete tx2;
return (SCENEGRAPH*)data;
}