Commit 418c5e78 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Merge branch 'swig' into 'development'

Initial implementation of a Lua scripting layer

Over the last several months a scripting layer that allows pipelines to be defined in Lua has been developed. It uses SWIG to generate Lua modules with bindings for CAMPVis classes. It is an opt-in feature, and tries to be as non-intrusive to standard CAMPVis code as possible.

The implementation of the scripting layer has reached a state where it's possible to write fully-functional pipelines in Lua. In fact, 2 existing pipelines have been reimplemented in Lua for testing purposes and added to the project. In my opinion, this marks a good point to merge the initial implementation into the development branch — that would make it easier to test and improve it.

Naturally, there are still many rough edges that should eventually be dealt with, but they can be addressed separately as new features:
- bindings coverage is rather low
- Lua pipelines currently need to be statically registered
- pipeline definition syntax could be streamlined (e.g. by getting rid of the `instance` global variable)
parents 307c3da2 2a838d8f
......@@ -29,3 +29,6 @@ moc_*.cxx_parameters
# TBB library in ext/
ext/tbb
# SWIG wrapper code
*LUA_wrap.cxx
[submodule "ext/lua"]
path = ext/lua
url = https://github.com/LuaDist/lua
ignore = dirty
......@@ -16,7 +16,9 @@ OPTION(CAMPVIS_DEBUG "Activate debug code?"
OPTION(CAMPVIS_BUILD_APPLICATION "Build CAMPVis Application" ON)
OPTION(CAMPVIS_BUILD_CORE "Build CAMPVis Core" ON)
OPTION(CAMPVIS_BUILD_MODULES "Build CAMPVis Modules" ON)
OPTION(CAMPVIS_ENABLE_SCRIPTING "Add support for scripting CAMPVis using Lua" OFF)
OPTION(CAMPVIS_BUILD_LIB_TGT "Build TGT Library" ON)
OPTION(CAMPVIS_BUILD_LIB_LUA "Build Lua Library" OFF)
OPTION(CAMPVIS_BUILD_DOXYGEN "Build Doxygen Documentation" OFF)
OPTION(CAMPVIS_DEPLOY_SHADERS "Deploy Shader files to binary directory" OFF)
......@@ -45,6 +47,18 @@ IF(CAMPVIS_BUILD_LIB_TGT)
ADD_SUBDIRECTORY(ext/tgt)
ENDIF()
IF(CAMPVIS_BUILD_LIB_LUA)
# Keep LUA_BUILD_AS_DLL in sync with BUILD_SHARED_LIBS
SET(LUA_BUILD_AS_DLL ${BUILD_SHARED_LIBS} CACHE BOOL "Build Lua library as DLL" FORCE)
ADD_SUBDIRECTORY(ext/lua EXCLUDE_FROM_ALL)
# If the above command failed, provide a hint how to fix the problem
IF(NOT TARGET liblua)
MESSAGE(SEND_ERROR "Did you forget to run `git submodule update --init`?")
ENDIF(NOT TARGET liblua)
ENDIF()
IF(CAMPVIS_BUILD_CORE)
ADD_SUBDIRECTORY(core)
ENDIF()
......@@ -54,6 +68,11 @@ IF(CAMPVIS_BUILD_MODULES)
ENDIF()
IF(CAMPVIS_ENABLE_SCRIPTING)
ADD_SUBDIRECTORY(scripting)
LIST(APPEND CampvisGlobalDefinitions "-DCAMPVIS_HAS_SCRIPTING")
ENDIF()
IF(CAMPVIS_BUILD_APPLICATION)
ADD_SUBDIRECTORY(application)
ENDIF()
......
......@@ -93,21 +93,25 @@ LIST(APPEND CampvisApplicationSources ${CampvisApplicationFormsHeaders})
LINK_DIRECTORIES(${CampvisGlobalLinkDirectories} ${CampvisModulesLinkDirectories})
SET(CampvisMainLibs campvis-core campvis-modules tgt)
IF(CAMPVIS_ENABLE_SCRIPTING)
LIST(APPEND CampvisMainLibs campvis-scripting)
ENDIF(CAMPVIS_ENABLE_SCRIPTING)
ADD_EXECUTABLE(campvis-application
${CampvisApplicationSources} ${CampvisApplicationHeaders}
${CampvisApplicationMoc}
)
ADD_DEFINITIONS(${CampvisGlobalDefinitions} ${CampvisModulesDefinitions} ${CampvisApplicationDefinitions} ${QT_DEFINITIONS})
INCLUDE_DIRECTORIES(${CampvisGlobalIncludeDirs} ${CampvisModulesIncludeDirs})
TARGET_LINK_LIBRARIES(campvis-application campvis-core campvis-modules tgt ${CampvisGlobalExternalLibs} ${CampvisModulesExternalLibs} ${QT_LIBRARIES})
TARGET_LINK_LIBRARIES(campvis-application ${CampvisMainLibs} ${CampvisGlobalExternalLibs} ${CampvisModulesExternalLibs} ${QT_LIBRARIES})
IF(CAMPVIS_GROUP_SOURCE_FILES)
DEFINE_SOURCE_GROUPS_FROM_SUBDIR(CampvisApplicationSources ${CampvisHome} "")
DEFINE_SOURCE_GROUPS_FROM_SUBDIR(CampvisApplicationHeaders ${CampvisHome} "")
ENDIF()
IF(CAMPVIS_DEPLOY_SHADERS)
LIST(APPEND CampvisShaderDirectories "application/glsl")
LIST(APPEND CampvisShaderDirectories "core/glsl")
......
......@@ -46,6 +46,10 @@
#include "core/pipeline/visualizationprocessor.h"
#include "modules/pipelinefactory.h"
#ifdef CAMPVIS_HAS_SCRIPTING
#include "scripting/gen_pipelineregistration.h"
#endif
namespace campvis {
const std::string CampVisApplication::loggerCat_ = "CAMPVis.application.CampVisApplication";
......
# Try to find LUA library and include path. Once done this will define:
# LUA_FOUND
# LUA_DEFINITIONS
# LUA_INCLUDE_DIR
# LUA_LIBRARY
IF(WIN32)
# For now Lua needs to be built as part of CAMPVis on Windows
IF(CAMPVIS_BUILD_LIB_LUA)
SET(LUA_DIR "${CampvisHome}/ext/lua" CACHE PATH "If Lua is not found, set this path")
SET(LUA_INCLUDE_DIR "${LUA_DIR}/src" "${CMAKE_BINARY_DIR}/ext/lua")
IF(LUA_INCLUDE_DIR)
SET(LUA_LIBRARY liblua)
SET(LUA_FOUND TRUE)
ELSE()
SET(LUA_FOUND FALSE)
ENDIF()
ENDIF(CAMPVIS_BUILD_LIB_LUA)
ELSE(WIN32)
# TODO: not tested
MESSAGE(FATAL_ERROR "FindLua.cmake doesn't support platforms other than Windows yet")
ENDIF(WIN32)
MARK_AS_ADVANCED(LUA_DIR LUA_INCLUDE_DIR LUA_LIBRARY)
......@@ -67,6 +67,61 @@ MACRO(PARSE_HEADER_FOR_PIPELINE FileName)
ENDFOREACH()
ENDMACRO(PARSE_HEADER_FOR_PIPELINE)
MACRO(ADD_SCRIPTED_PIPELINE_REGISTRATION ScriptFile PipelineName)
LIST(APPEND ScriptedPipelineRegistrationScriptFiles ${ScriptFile})
LIST(APPEND ScriptedPipelineRegistrationPipelineNames ${PipelineName})
ENDMACRO(ADD_SCRIPTED_PIPELINE_REGISTRATION)
MACRO(WRITE_SCRIPTED_PIPELINE_REGISTRATION FileName)
MESSAGE(STATUS "* Generating scripted pipeline registration header: ${FileName}")
SET(PipelineRegistrationSource "// WARNING: This file is automatically generated by CMake, do not modify!\n\n" )
LIST(APPEND PipelineRegistrationSource "// Include required headers:\n"
"#include \"scripting/scriptedpipelineregistrar.h\"\n")
LIST(APPEND PipelineRegistrationSource "\nnamespace campvis {\n\tnamespace {\n" )
LIST(APPEND PipelineRegistrationSource "\t\t// Instantiate templated ScriptedPipelineRegistrars to register pipelines.\n" )
WHILE(ScriptedPipelineRegistrationPipelineNames)
LIST(GET ScriptedPipelineRegistrationPipelineNames 0 PipelineName)
LIST(REMOVE_AT ScriptedPipelineRegistrationPipelineNames 0)
LIST(GET ScriptedPipelineRegistrationScriptFiles 0 ScriptFile)
LIST(REMOVE_AT ScriptedPipelineRegistrationScriptFiles 0)
LIST(APPEND PipelineRegistrationSource "\t\tchar ${PipelineName}PipelineId[] = \"${PipelineName}\"\;\n"
"\t\tchar ${PipelineName}PipelineScript[] = \"${ScriptFile}\"\;\n"
"\t\ttemplate class ScriptedPipelineRegistrar"
"<IdentifiableLuaPipeline<${PipelineName}PipelineId>, "
"${PipelineName}PipelineScript>\;\n")
ENDWHILE(ScriptedPipelineRegistrationPipelineNames)
LIST(APPEND PipelineRegistrationSource "\t}\n}\n" )
FILE(WRITE ${FileName} ${PipelineRegistrationSource})
ENDMACRO(WRITE_SCRIPTED_PIPELINE_REGISTRATION)
MACRO(PARSE_SCRIPT_FOR_PIPELINE FileName)
FILE(READ ${FileName} content)
# Build a regex matching pipeline declarations and extracting their names
SET(DeclarationRegex "pipeline = campvis.newPipeline\\(\"([A-Za-z0-9_]+)\"\\)")
# Find all pipeline declarations
STRING(REGEX MATCHALL ${DeclarationRegex} matches ${content})
LIST(LENGTH matches MatchCount)
IF(MatchCount LESS 1)
MESSAGE(WARNING "No pipelines found in script '${FileName}'")
ELSEIF(MatchCount GREATER 1)
MESSAGE(WARNING "Script '${FileName}' defines multiple pipelines; ignoring")
ELSE(MatchCount LESS 1)
# Extract pipeline name and register
STRING(REGEX REPLACE ${DeclarationRegex} "\\1" RESULT ${matches})
ADD_SCRIPTED_PIPELINE_REGISTRATION(${FileName} ${RESULT})
ENDIF(MatchCount LESS 1)
ENDMACRO(PARSE_SCRIPT_FOR_PIPELINE)
MACRO(INCLUDE_MODULE ModuleDirectory ModuleListFile)
STRING(TOUPPER ${ModuleDirectory} ModuleDirectoryUpper)
SET(ThisModDir ${ModulesDir}/${ModuleDirectory})
......
%module campvis
%include factory.i
%include std_string.i
%import "ext/tgt/bindings/tgt.i"
%include "ext/sigslot/sigslot.i"
%{
#include "core/datastructures/abstractdata.h"
#include "core/datastructures/imagedata.h"
#include "core/eventhandlers/trackballnavigationeventlistener.h"
#include "core/properties/cameraproperty.h"
#include "core/properties/genericproperty.h"
#include "core/properties/numericproperty.h"
#include "core/properties/floatingpointproperty.h"
#include "core/properties/datanameproperty.h"
#include "core/properties/transferfunctionproperty.h"
#include "core/pipeline/abstractprocessor.h"
#include "core/pipeline/autoevaluationpipeline.h"
#include "core/pipeline/visualizationprocessor.h"
#include "core/classification/tfgeometry1d.h"
#include "core/classification/geometry1dtransferfunction.h"
%}
%inline {
static const char* const SOURCE_DIR = CAMPVIS_SOURCE_DIR;
// Template specialisations and instantiations required to get signals to work in Lua
namespace sigslot {
template<>
struct LuaConnectionArgTraits<campvis::AbstractProcessor*> {
static const char* const typeName;
};
const char* const LuaConnectionArgTraits<campvis::AbstractProcessor*>::typeName = "campvis::AbstractProcessor *";
}
}
%template(sigslot_signal1_AbstractProcessor) sigslot::signal1<campvis::AbstractProcessor*>;
namespace campvis {
class AbstractProperty {
public:
AbstractProperty(const std::string& name, const std::string& title);
virtual ~AbstractProperty();
virtual void init();
virtual void deinit();
const std::string& getName() const;
const std::string& getTitle() const;
bool isVisible() const;
void setVisible(bool isVisible);
virtual void addSharedProperty(AbstractProperty* prop);
};
template<typename T>
class GenericProperty : public AbstractProperty {
public:
GenericProperty(const std::string& name, const std::string& title, const T& value);
virtual ~GenericProperty();
const T& getValue() const;
virtual void setValue(const T& value);
};
%template(StringProperty) GenericProperty<std::string>;
typedef GenericProperty<std::string> StringProperty;
/* DataNameProperty */
class DataNameProperty : public StringProperty {
public:
enum DataAccessInfo {
READ,
WRITE
};
DataNameProperty(const std::string& name, const std::string& title, const std::string& value,
DataAccessInfo access);
virtual ~DataNameProperty();
};
/* NumericProperty */
template<typename T>
class NumericProperty : public GenericProperty<T> {
public:
NumericProperty(
const std::string& name,
const std::string& title,
const T& value,
const T& minValue,
const T& maxValue,
const T& stepValue = T(1));
virtual ~NumericProperty();
};
%template(Ivec2GenericProperty) GenericProperty< tgt::Vector2<int> >;
%template(IVec2Property) NumericProperty< tgt::Vector2<int> >;
typedef NumericProperty< tgt::Vector2<int> > IVec2Property;
template<typename T>
struct FloatingPointPropertyTraits {};
template<>
struct FloatingPointPropertyTraits<float> {
typedef int DecimalsType;
};
%template() FloatingPointPropertyTraits<float>;
template<typename T>
class FloatingPointProperty : public NumericProperty<T> {
public:
typedef typename FloatingPointPropertyTraits<T>::DecimalsType DecimalsType;
FloatingPointProperty(
const std::string& name,
const std::string& title,
const T& value,
const T& minValue,
const T& maxValue,
const T& stepValue = T(0.01f),
const DecimalsType& decimals = DecimalsType(3));
virtual ~FloatingPointProperty();
};
%template(FloatGenericProperty) GenericProperty<float>;
%template(FloatNumericProperty) NumericProperty<float>;
%template(FloatProperty) FloatingPointProperty<float>;
typedef FloatingPointProperty< float > FloatProperty;
/* CameraProperty */
%template(GenericProperty_Camera) GenericProperty<tgt::Camera>;
class CameraProperty : public GenericProperty<tgt::Camera> {
public:
CameraProperty(const std::string& name, const std::string& title, tgt::Camera cam = tgt::Camera());
virtual ~CameraProperty();
};
/* TFGeometry1D */
%nodefaultctor TFGeometry1D;
class TFGeometry1D {
public:
virtual ~TFGeometry1D();
static TFGeometry1D* createQuad(const tgt::vec2& interval, const tgt::col4& leftColor, const tgt::vec4& rightColor);
};
/* AbstractTransferFunction */
class AbstractTransferFunction {
public:
AbstractTransferFunction(const tgt::svec3& size, const tgt::vec2& intensityDomain = tgt::vec2(0.f, 1.f));
virtual ~AbstractTransferFunction();
virtual AbstractTransferFunction* clone() const = 0;
};
/* GenericGeometryTransferFunction */
template<class T>
class GenericGeometryTransferFunction : public AbstractTransferFunction {
public:
GenericGeometryTransferFunction(const tgt::vec3& size, const tgt::vec2& intensityDomain = tgt::vec2(0.f, 1.f));
virtual ~GenericGeometryTransferFunction();
void addGeometry(T* geometry);
};
/* Geometry1DTransferFunction */
%template(GenericGeometryTransferFunction_TFGeometry1D) GenericGeometryTransferFunction<TFGeometry1D>;
class Geometry1DTransferFunction : public GenericGeometryTransferFunction<TFGeometry1D> {
public:
Geometry1DTransferFunction(size_t size, const tgt::vec2& intensityDomain = tgt::vec2(0.f, 1.f));
virtual ~Geometry1DTransferFunction();
virtual Geometry1DTransferFunction* clone() const;
};
/* TransferFunctionProperty */
class TransferFunctionProperty : public AbstractProperty {
public:
TransferFunctionProperty(const std::string& name, const std::string& title, AbstractTransferFunction* tf);
virtual ~TransferFunctionProperty();
AbstractTransferFunction* getTF();
void replaceTF(AbstractTransferFunction* tf);
};
/* IHasWorldBounds */
class IHasWorldBounds {
public:
IHasWorldBounds();
virtual ~IHasWorldBounds();
virtual tgt::Bounds getWorldBounds() const = 0;
};
/* AbstractData */
class AbstractData {
public:
AbstractData();
virtual ~AbstractData();
virtual AbstractData* clone() const = 0;
};
/* ImageData */
class ImageData : public AbstractData, public IHasWorldBounds {
public:
ImageData(size_t dimensionality, const tgt::svec3& size, size_t numChannels);
virtual ~ImageData();
virtual ImageData* clone() const;
virtual tgt::Bounds getWorldBounds() const;
};
/* Downcast the return value of DataHandle::getData to appropriate subclass */
%factory(AbstractData* campvis::DataHandle::getData, campvis::ImageData);
/* DataHandle */
class DataHandle {
public:
explicit DataHandle(AbstractData* data = 0);
DataHandle(const DataHandle& rhs);
DataHandle& operator=(const DataHandle& rhs);
virtual ~DataHandle();
const AbstractData* getData() const;
};
/* DataContainer */
class DataContainer {
public:
DataContainer(const std::string& name);
~DataContainer();
DataHandle addData(const std::string& name, AbstractData* data);
void addDataHandle(const std::string& name, const DataHandle& dh);
bool hasData(const std::string& name) const;
DataHandle getData(const std::string& name) const;
void removeData(const std::string& name);
void clear();
};
/* Downcast the return value of HasPropertyCollection::getProperty to appropriate subclass */
%factory(AbstractProperty* campvis::HasPropertyCollection::getProperty,
campvis::FloatProperty, campvis::IVec2Property, campvis::TransferFunctionProperty,
campvis::DataNameProperty, campvis::StringProperty, campvis::CameraProperty);
/* Downcast the return value of HasPropertyCollection::getNestedProperty to appropriate subclass */
%factory(AbstractProperty* campvis::HasPropertyCollection::getNestedProperty,
campvis::FloatProperty, campvis::IVec2Property, campvis::TransferFunctionProperty,
campvis::DataNameProperty, campvis::StringProperty, campvis::CameraProperty);
/* HasPropertyCollection */
class HasPropertyCollection {
public:
HasPropertyCollection();
virtual ~HasPropertyCollection() = 0;
virtual void addProperty(AbstractProperty& prop);
void removeProperty(AbstractProperty& prop);
AbstractProperty* getProperty(const std::string& name) const;
AbstractProperty* getNestedProperty(const std::string& name) const;
};
/* AbstractProcessor */
class AbstractProcessor : public HasPropertyCollection {
public:
enum InvalidationLevel {
VALID = 0,
INVALID_RESULT = 1 << 0,
INVALID_SHADER = 1 << 1,
INVALID_PROPERTIES = 1 << 2,
FIRST_FREE_TO_USE_INVALIDATION_LEVEL = 1 << 3
};
const std::string getName() const = 0;
void addProperty(AbstractProperty& prop, int invalidationLevel);
void setPropertyInvalidationLevel(AbstractProperty& prop, int invalidationLevel);
%immutable;
sigslot::signal1<AbstractProcessor*> s_validated;
%mutable;
};
/* AbstractPipeline */
class AbstractPipeline : public HasPropertyCollection {
public:
AbstractPipeline(DataContainer* dc);
virtual ~AbstractPipeline();
virtual const std::string getName() const = 0;
const DataContainer& getDataContainer() const;
DataContainer& getDataContainer();
};
/* AutoEvaluationPipeline */
class AutoEvaluationPipeline : public AbstractPipeline {
public:
virtual void addProcessor(AbstractProcessor* processor);
void addEventListenerToBack(tgt::EventListener* e);
};
/* VisualizationProcessor */
class VisualizationProcessor : public AbstractProcessor {
public:
explicit VisualizationProcessor(IVec2Property* viewportSizeProp);
~VisualizationProcessor();
};
/* TrackballNavigationEventListener */
class TrackballNavigationEventListener : public tgt::EventListener {
public:
TrackballNavigationEventListener(CameraProperty* cameraProperty, IVec2Property* viewportSizeProp);
virtual ~TrackballNavigationEventListener();
void addLqModeProcessor(VisualizationProcessor* vp);
void removeLqModeProcessor(VisualizationProcessor* vp);
void reinitializeCamera(const IHasWorldBounds* hwb);
void reinitializeCamera(const tgt::Bounds& worldBounds);
};
}
%luacode {
function campvis.newPipeline (name, o)
if not name then
error("A name must be provided when creating a new pipeline!")
end
o = o or {} -- create object if user does not provide one
setmetatable(o, {__index = instance})
return o
end
print("Module campvis loaded")
}
Subproject commit 96245accd1539c8bb0d8be310eaa9cf5fbab9fed
This diff is collapsed.
%module tgt
%include "stdint.i"
%{
#include "ext/tgt/camera.h"
#include "ext/tgt/vector.h"
#include "ext/tgt/event/eventlistener.h"
%}
namespace tgt {
/* Vector2 */
template<class T>
struct Vector2 {
Vector2(T t);
Vector2(T t1, T t2);
static Vector2<T> zero;
};
%template(ivec2) Vector2<int>;
typedef Vector2<int> ivec2;
%template(vec2) Vector2<float>;
typedef Vector2<float> vec2;
/* Vector3 */
template<class T>
struct Vector3 {
Vector3();
explicit Vector3(T v);
Vector3(T t1, T t2, T t3);
Vector3(const Vector2<T>& vec, T z_);
Vector3(T _x, const Vector2<T>& v);
static Vector3<T> zero;
};
%template(vec3) Vector3<float>;
typedef Vector3<float> vec3;
%template(svec3) Vector3<size_t>;
typedef Vector3<size_t> svec3;
/* Vector4 */
template<class T>
struct</