diff --git a/.gitmodules b/.gitmodules
index 1c1bd9b62eff6c8d7f491492b292007d41e6e639..b03f7351c6e6a4eaaa5521f6c7a44deded20a9a2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "ext/nanogui"]
 	path = ext/nanogui
 	url = https://github.com/wjakob/nanogui.git
+[submodule "SDK/C++/public/ext/pybind11"]
+	path = SDK/C++/public/ext/pybind11
+	url = https://github.com/pybind/pybind11.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 87114cb8b4834724d4930f70557781bebc3f3847..4bf0f37db31f35fe4c97c09f25a6add778d80c90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,6 +3,7 @@ include (CheckIncludeFile)
 include (CheckIncludeFileCXX)
 include (CheckFunctionExists)
 include(CheckLanguage)
+include(ExternalProject)
 
 if (WIN32)
 	set(CMAKE_GENERATOR_TOOLSET "host=x64")
@@ -476,6 +477,7 @@ if (WITH_SDK)
 	if (NOT WIN32)
 		add_subdirectory(SDK/C)
 	endif()
+	add_subdirectory(SDK/C++)
 endif()
 
 if (HAVE_AVFORMAT)
diff --git a/SDK/C++/CMakeLists.txt b/SDK/C++/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8bec8ee83108171e91209a80092813a0915d8466
--- /dev/null
+++ b/SDK/C++/CMakeLists.txt
@@ -0,0 +1,24 @@
+add_library(voltu SHARED
+	private/system.cpp
+	private/feed_impl.cpp
+	private/room_impl.cpp
+	private/frame_impl.cpp
+	private/image_impl.cpp
+	private/observer_impl.cpp
+	private/pointcloud_impl.cpp
+)
+
+target_include_directories(voltu
+	PUBLIC public/include
+	PRIVATE src)
+
+target_link_libraries(voltu ftlcommon ftldata ftlctrl ftlrgbd ftlstreams ftlrender Threads::Threads ${OpenCV_LIBS} openvr ftlnet nanogui ${NANOGUI_EXTRA_LIBS} ceres nvidia-ml)
+
+ExternalProject_Add(
+	voltu_sdk
+	SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/public"
+	BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/sdk"
+	INSTALL_COMMAND ""
+	BUILD_ALWAYS true
+	CMAKE_ARGS -DOpenCV_DIR=${OpenCV_DIR}
+)
diff --git a/SDK/C++/private/feed_impl.cpp b/SDK/C++/private/feed_impl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..001b2974f802d5d46091c9b8f820fdba72729ad3
--- /dev/null
+++ b/SDK/C++/private/feed_impl.cpp
@@ -0,0 +1,25 @@
+#include "feed_impl.hpp"
+
+using voltu::internal::FeedImpl;
+
+FeedImpl::FeedImpl(ftl::stream::Feed* feed, uint32_t fsid)
+ : feed_(feed)
+{
+	fsids_.insert(fsid);
+}
+
+FeedImpl::~FeedImpl()
+{
+	remove();
+}
+	
+std::string FeedImpl::getURI()
+{
+	return feed_->getURI(*fsids_.begin());
+}
+
+void FeedImpl::remove()
+{
+
+}
+
diff --git a/SDK/C++/private/feed_impl.hpp b/SDK/C++/private/feed_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6eae89a31f769385770854475c416b74df5abdaa
--- /dev/null
+++ b/SDK/C++/private/feed_impl.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <voltu/feed.hpp>
+#include <ftl/streams/feed.hpp>
+#include <unordered_set>
+
+namespace voltu
+{
+namespace internal
+{
+
+class FeedImpl : public voltu::Feed
+{
+public:
+	FeedImpl(ftl::stream::Feed*, uint32_t fsid);
+	~FeedImpl();
+	
+	std::string getURI() override;
+
+	void remove() override;
+
+private:
+	ftl::stream::Feed *feed_;
+	std::unordered_set<uint32_t> fsids_;
+};
+
+}
+}
diff --git a/SDK/C++/private/frame_impl.cpp b/SDK/C++/private/frame_impl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a6057f093bc149f9877e09cb096a6f661f8e39fe
--- /dev/null
+++ b/SDK/C++/private/frame_impl.cpp
@@ -0,0 +1,60 @@
+#include "frame_impl.hpp"
+#include "image_impl.hpp"
+#include <ftl/rgbd/frame.hpp>
+#include <voltu/types/errors.hpp>
+
+using voltu::internal::FrameImpl;
+
+FrameImpl::FrameImpl()
+{
+
+}
+
+FrameImpl::~FrameImpl()
+{
+
+}
+
+std::list<voltu::ImagePtr> FrameImpl::getImageSet(voltu::Channel c)
+{
+	std::list<voltu::ImagePtr> result;
+	ftl::codecs::Channel channel = ftl::codecs::Channel::Colour;
+
+	switch (c)
+	{
+	case voltu::Channel::kColour	: channel = ftl::codecs::Channel::Colour; break;
+	case voltu::Channel::kDepth		: channel = ftl::codecs::Channel::Depth; break;
+	default: throw voltu::exceptions::BadImageChannel();
+	}
+
+	for (const auto &fs : framesets_)
+	{
+		for (const auto &f : fs->frames)
+		{
+			if (f.hasChannel(channel))
+			{
+				auto img = std::make_shared<voltu::internal::ImageImpl>(
+					f.cast<ftl::rgbd::Frame>(), channel
+				);
+				result.push_back(img);
+			}
+		}
+	}
+
+	return result;
+}
+
+voltu::PointCloudPtr FrameImpl::getPointCloud(voltu::PointCloudFormat cloudfmt, voltu::PointFormat pointfmt)
+{
+	return nullptr;
+}
+
+void FrameImpl::pushFrameSet(const std::shared_ptr<ftl::data::FrameSet> &fs)
+{
+	framesets_.push_back(fs);
+}
+
+int64_t FrameImpl::getTimestamp()
+{
+	return 0;
+}
diff --git a/SDK/C++/private/frame_impl.hpp b/SDK/C++/private/frame_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ee6aec4a7da48c086df4c6475125ab7cc36efc95
--- /dev/null
+++ b/SDK/C++/private/frame_impl.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <voltu/types/frame.hpp>
+#include <voltu/types/image.hpp>
+#include <voltu/types/channel.hpp>
+#include <ftl/data/new_frameset.hpp>
+
+namespace voltu
+{
+namespace internal
+{
+
+class FrameImpl : public voltu::Frame
+{
+public:
+	FrameImpl();
+	~FrameImpl();
+
+	std::list<voltu::ImagePtr> getImageSet(voltu::Channel) override;
+
+	voltu::PointCloudPtr getPointCloud(voltu::PointCloudFormat cloudfmt, voltu::PointFormat pointfmt) override;
+
+	int64_t getTimestamp() override;
+
+	void pushFrameSet(const std::shared_ptr<ftl::data::FrameSet> &fs);
+
+	inline const std::list<std::shared_ptr<ftl::data::FrameSet>> &getInternalFrameSets() const { return framesets_; }
+
+private:
+	std::list<std::shared_ptr<ftl::data::FrameSet>> framesets_;
+};
+
+}
+}
diff --git a/SDK/C++/private/image_impl.cpp b/SDK/C++/private/image_impl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f8bf63a27001f0540f008eed4a5f4bb5bd8dfb75
--- /dev/null
+++ b/SDK/C++/private/image_impl.cpp
@@ -0,0 +1,125 @@
+#include "image_impl.hpp"
+
+using voltu::internal::ImageImpl;
+
+ImageImpl::ImageImpl(const ftl::rgbd::Frame& f, ftl::codecs::Channel c)
+ : frame_(f), channel_(c)
+{
+
+}
+
+ImageImpl::~ImageImpl()
+{
+
+}
+
+voltu::ImageData ImageImpl::getHost()
+{
+	cv::Mat m = frame_.get<cv::Mat>(channel_);
+	voltu::ImageData r;
+	r.data = m.data;
+	r.width = m.cols;
+	r.height = m.rows;
+	r.pitch = m.step;
+
+	if (m.type() == CV_8UC4)
+	{
+		r.format = voltu::ImageFormat::kBGRA8;
+	}
+	else if (m.type() == CV_32F)
+	{
+		r.format = voltu::ImageFormat::kFloat32;
+	}
+
+	return r;
+}
+
+voltu::ImageData ImageImpl::getDevice()
+{
+	cv::cuda::GpuMat m = frame_.get<cv::cuda::GpuMat>(channel_);
+	voltu::ImageData r;
+	r.data = m.data;
+	r.width = m.cols;
+	r.height = m.rows;
+	r.pitch = m.step;
+
+	if (m.type() == CV_8UC4)
+	{
+		r.format = voltu::ImageFormat::kBGRA8;
+	}
+	else if (m.type() == CV_32F)
+	{
+		r.format = voltu::ImageFormat::kFloat32;
+	}
+
+	return r;
+}
+
+bool ImageImpl::isDevice()
+{
+	return true;
+}
+
+voltu::Channel ImageImpl::getChannel()
+{
+	switch (channel_)
+	{
+	case ftl::codecs::Channel::Colour		: return voltu::Channel::kColour;
+	case ftl::codecs::Channel::Depth		: return voltu::Channel::kDepth;
+	default: return voltu::Channel::kInvalid;
+	}
+}
+
+std::string ImageImpl::getName()
+{
+	return std::to_string(frame_.frameset()) + std::string("-") + std::to_string(frame_.source());
+}
+
+voltu::Intrinsics ImageImpl::getIntrinsics()
+{
+	auto raw = frame_.getLeft();
+	voltu::Intrinsics result;
+	result.width = raw.width;
+	result.height = raw.height;
+	result.principle_x = raw.cx;
+	result.principle_y = raw.cy;
+	result.focal_x = raw.fx;
+	result.focal_y = raw.fy;
+	return result;
+}
+
+voltu::StereoIntrinsics ImageImpl::getStereoIntrinsics()
+{
+	auto raw = frame_.getLeft();
+	voltu::StereoIntrinsics result;
+	result.width = raw.width;
+	result.height = raw.height;
+	result.principle_x = raw.cx;
+	result.principle_y = raw.cy;
+	result.focal_x = raw.fx;
+	result.focal_y = raw.fy;
+	result.min_depth = raw.minDepth;
+	result.max_depth = raw.maxDepth;
+	result.baseline = raw.baseline;
+	return result;
+}
+
+Eigen::Matrix4d ImageImpl::getPose()
+{
+	return frame_.getPose();
+}
+
+int64_t ImageImpl::getTimestamp()
+{
+	return frame_.timestamp();
+}
+
+int ImageImpl::getCameraNumber()
+{
+	return frame_.source();
+}
+
+uint32_t ImageImpl::getUniqueId()
+{
+	return frame_.id().id;
+}
diff --git a/SDK/C++/private/image_impl.hpp b/SDK/C++/private/image_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..189a1c19fd13366e8728a50494a6ee9b1e2c4c5f
--- /dev/null
+++ b/SDK/C++/private/image_impl.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <voltu/types/image.hpp>
+#include <ftl/rgbd/frame.hpp>
+
+namespace voltu
+{
+namespace internal
+{
+
+class ImageImpl : public voltu::Image
+{
+public:
+	ImageImpl(const ftl::rgbd::Frame&, ftl::codecs::Channel c);
+	~ImageImpl();
+
+	voltu::ImageData getHost() override;
+
+	voltu::ImageData getDevice() override;
+
+	bool isDevice() override;
+
+	voltu::Channel getChannel() override;
+
+	std::string getName() override;
+
+	voltu::Intrinsics getIntrinsics() override;
+
+	voltu::StereoIntrinsics getStereoIntrinsics() override;
+
+	Eigen::Matrix4d getPose() override;
+
+	int64_t getTimestamp() override;
+
+	//virtual voltu::RoomId getRoomId() override;
+
+	int getCameraNumber() override;
+
+	uint32_t getUniqueId() override;
+
+private:
+	const ftl::rgbd::Frame &frame_;
+	ftl::codecs::Channel channel_;
+};
+
+}
+}
diff --git a/SDK/C++/private/observer_impl.cpp b/SDK/C++/private/observer_impl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..325d24cf39eb8a0a32e5390f6ef0c84a5ab4a7f8
--- /dev/null
+++ b/SDK/C++/private/observer_impl.cpp
@@ -0,0 +1,182 @@
+#include "observer_impl.hpp"
+#include "frame_impl.hpp"
+#include <voltu/types/errors.hpp>
+
+#include <ftl/render/CUDARender.hpp>
+#include <ftl/rgbd/frame.hpp>
+
+using voltu::internal::ObserverImpl;
+using ftl::rgbd::Capability;
+
+ObserverImpl::ObserverImpl(ftl::Configurable *base)
+{
+	pool_ = new ftl::data::Pool(2,5);
+	rend_ = ftl::create<ftl::render::CUDARender>(base, "camN");
+
+	intrinsics_.fx = 700.0f;
+	intrinsics_.fy = 700.0f;
+	intrinsics_.width = 1280;
+	intrinsics_.height = 720;
+	intrinsics_.cx = -1280.0f/2.0f;
+	intrinsics_.cy = -720.0f/2.0f;
+	intrinsics_.minDepth = 0.1f;
+	intrinsics_.maxDepth = 12.0f;
+	pose_.setIdentity();
+
+	calibration_uptodate_.clear();
+}
+
+ObserverImpl::~ObserverImpl()
+{
+
+}
+
+void ObserverImpl::setResolution(uint32_t w, uint32_t h)
+{
+	if (w < 100 || w > 10000 || h < 100 || h > 10000)
+	{
+		throw voltu::exceptions::BadParameterValue();
+	}
+	intrinsics_.width = w;
+	intrinsics_.height = h;
+	intrinsics_.cx = -int(w)/2;
+	intrinsics_.cy = -int(h)/2;
+	calibration_uptodate_.clear();
+}
+
+void ObserverImpl::setFocalLength(uint32_t f)
+{
+	if (f < 100 || f > 10000)
+	{
+		throw voltu::exceptions::BadParameterValue();
+	}
+	intrinsics_.fx = f;
+	intrinsics_.fy = f;
+	calibration_uptodate_.clear();
+}
+
+void ObserverImpl::setStereo(bool)
+{
+
+}
+
+bool ObserverImpl::waitCompletion(int timeout)
+{
+	if (frame_complete_) return true;
+
+	frame_complete_ = true;
+
+	try
+	{
+		rend_->render();
+		rend_->end();
+	}
+	catch(const std::exception& e)
+	{
+		throw voltu::exceptions::InternalRenderError();
+	}
+	
+	cudaSafeCall(cudaStreamSynchronize(frameset_->frames[0].stream()));
+	return true;
+}
+
+void ObserverImpl::submit(const voltu::FramePtr& frame)
+{
+	if (frame_complete_)
+	{
+		frame_complete_ = false;
+		frameset_ = _makeFrameSet();
+
+		ftl::rgbd::Frame &rgbdframe = frameset_->frames[0].cast<ftl::rgbd::Frame>();
+
+		if (!rgbdframe.has(ftl::codecs::Channel::Calibration) || calibration_uptodate_.test_and_set())
+		{
+			rgbdframe.setLeft() = intrinsics_;
+
+			auto &cap = rgbdframe.create<std::unordered_set<Capability>>(ftl::codecs::Channel::Capabilities);
+			cap.clear();
+			cap.emplace(Capability::VIDEO);
+			cap.emplace(Capability::MOVABLE);
+			cap.emplace(Capability::ADJUSTABLE);
+			cap.emplace(Capability::VIRTUAL);
+			cap.emplace(Capability::LIVE);
+			if (rend_->value("projection", 0) == int(ftl::rgbd::Projection::EQUIRECTANGULAR)) cap.emplace(Capability::EQUI_RECT);
+
+			auto &meta = rgbdframe.create<std::map<std::string,std::string>>(ftl::codecs::Channel::MetaData);
+			meta["name"] = std::string("Virtual Camera"); //host_->value("name", host_->getID());
+			//meta["id"] = host_->getID();
+			meta["uri"] = std::string("device:render");
+			meta["device"] = std::string("CUDA Render");
+		}
+		//if (!rgbdframe.has(ftl::codecs::Channel::Pose))
+		//{
+			rgbdframe.setPose() = pose_.cast<double>();
+		//}
+
+		int width = rgbdframe.getLeft().width;
+		int height = rgbdframe.getLeft().height;
+		
+		// FIXME: Create opengl buffers here and map to cuda?
+		auto &colour = rgbdframe.create<cv::cuda::GpuMat>(ftl::codecs::Channel::Colour);
+		colour.create(height, width, CV_8UC4);
+		rgbdframe.create<cv::cuda::GpuMat>(ftl::codecs::Channel::Depth).create(height, width, CV_32F);
+		rgbdframe.createTexture<float>(ftl::codecs::Channel::Depth);
+
+		rend_->begin(rgbdframe, ftl::codecs::Channel::Colour);
+	}
+
+	auto *fimp = dynamic_cast<voltu::internal::FrameImpl*>(frame.get());
+	if (!fimp)
+	{
+		throw voltu::exceptions::InvalidFrameObject();
+	}
+
+	const auto &sets = fimp->getInternalFrameSets();
+	for (const auto &fs : sets)
+	{
+		Eigen::Matrix4d pose;
+		pose.setIdentity();
+
+		try
+		{
+			rend_->submit(fs.get(), ftl::codecs::Channels<0>(ftl::codecs::Channel::Colour), pose);
+		}
+		catch (...)
+		{
+			throw voltu::exceptions::InternalRenderError();
+		}
+	}
+}
+
+void ObserverImpl::setPose(const Eigen::Matrix4f &p)
+{
+	pose_ = p;
+}
+
+voltu::FramePtr ObserverImpl::getFrame()
+{
+	waitCompletion(0);
+	auto f = std::make_shared<voltu::internal::FrameImpl>();
+	f->pushFrameSet(frameset_);
+	return f;
+}
+
+voltu::PropertyPtr ObserverImpl::property(voltu::ObserverProperty)
+{
+	throw voltu::exceptions::InvalidProperty();
+}
+
+std::shared_ptr<ftl::data::FrameSet> ObserverImpl::_makeFrameSet()
+{
+	int64_t timestamp = ftl::timer::get_time();
+
+	auto newf = std::make_shared<ftl::data::FrameSet>(pool_, ftl::data::FrameID(id_,255), timestamp);
+	for (size_t i=0; i<size_; ++i) {
+		newf->frames.push_back(std::move(pool_->allocate(ftl::data::FrameID(id_, i), timestamp)));
+	}
+
+	newf->mask = 0xFF;
+	newf->clearFlags();
+	newf->store();
+	return newf;
+}
diff --git a/SDK/C++/private/observer_impl.hpp b/SDK/C++/private/observer_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..817e2babdb45b2beb23380f7b6e4ee20ca7e87c2
--- /dev/null
+++ b/SDK/C++/private/observer_impl.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <voltu/observer.hpp>
+#include <ftl/render/renderer.hpp>
+#include <ftl/data/framepool.hpp>
+#include <ftl/data/new_frameset.hpp>
+#include <ftl/rgbd/camera.hpp>
+#include <Eigen/Eigen>
+
+namespace voltu
+{
+namespace internal
+{
+
+class ObserverImpl : public voltu::Observer
+{
+public:
+	explicit ObserverImpl(ftl::Configurable *base);
+
+	~ObserverImpl();
+
+	void setResolution(uint32_t w, uint32_t h) override;
+
+	void setFocalLength(uint32_t f) override;
+
+	void setStereo(bool) override;
+
+	bool waitCompletion(int timeout) override;
+
+	void submit(const voltu::FramePtr&) override;
+
+	void setPose(const Eigen::Matrix4f &) override;
+
+	voltu::FramePtr getFrame() override;
+
+	voltu::PropertyPtr property(voltu::ObserverProperty) override;
+
+private:
+	ftl::render::FSRenderer *rend_;
+	ftl::data::Pool *pool_;
+	int id_;
+	size_t size_ = 1;
+	std::shared_ptr<ftl::data::FrameSet> frameset_;
+	bool frame_complete_ = true;
+	ftl::rgbd::Camera intrinsics_;
+	Eigen::Matrix4f pose_;
+	std::atomic_flag calibration_uptodate_;
+
+	std::shared_ptr<ftl::data::FrameSet> _makeFrameSet();
+};
+
+}
+}
diff --git a/SDK/C++/private/pointcloud_impl.cpp b/SDK/C++/private/pointcloud_impl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9de38019a659f29d434c7c182e06f0cf2813645d
--- /dev/null
+++ b/SDK/C++/private/pointcloud_impl.cpp
@@ -0,0 +1,16 @@
+#include "pointcloud_impl.hpp"
+
+using voltu::internal::PointCloudUnstructured;
+using voltu::PointCloudData;
+
+PointCloudData PointCloudUnstructured::getHost()
+{
+	// TODO: Generate point cloud on GPU
+	return {};
+}
+
+PointCloudData PointCloudUnstructured::getDevice()
+{
+	// TODO: Generate point cloud on GPU
+	return {};
+}
diff --git a/SDK/C++/private/pointcloud_impl.hpp b/SDK/C++/private/pointcloud_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6f5f0258e957200b775eb7dfc0feb3ea2906e8e6
--- /dev/null
+++ b/SDK/C++/private/pointcloud_impl.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <voltu/types/pointcloud.hpp>
+
+namespace voltu
+{
+namespace internal
+{
+
+class PointCloudUnstructured : public voltu::PointCloud
+{
+public:
+	voltu::PointCloudData getHost() override;
+
+	voltu::PointCloudData getDevice() override;
+
+private:
+};
+
+}
+}
diff --git a/SDK/C++/private/room_impl.cpp b/SDK/C++/private/room_impl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9df583c86610ecfeb4c0140ee659201ba76a1f29
--- /dev/null
+++ b/SDK/C++/private/room_impl.cpp
@@ -0,0 +1,90 @@
+#include <voltu/room.hpp>
+#include "room_impl.hpp"
+#include "frame_impl.hpp"
+#include <voltu/types/errors.hpp>
+
+using voltu::internal::RoomImpl;
+
+RoomImpl::RoomImpl(ftl::stream::Feed* feed)
+ : feed_(feed)
+{
+
+}
+
+bool RoomImpl::waitNextFrame(int64_t timeout)
+{
+	if (!filter_)
+	{
+		filter_ = feed_->filter(fsids_, {ftl::codecs::Channel::Colour, ftl::codecs::Channel::Depth});
+		filter_->on([this](const std::shared_ptr<ftl::data::FrameSet> &fs)
+		{
+			UNIQUE_LOCK(mutex_, lk);
+			last_seen_ = std::max(last_seen_, fs->timestamp());
+			cv_.notify_all();
+			return true;
+		});
+	}
+
+	std::unique_lock<std::mutex> lk(mutex_);
+
+	if (last_read_ >= last_seen_)
+	{
+		if (timeout > 0)
+		{
+			cv_.wait_for(lk, std::chrono::seconds(timeout), [this] {
+				return last_read_ < last_seen_;
+			});
+
+			return last_read_ < last_seen_;
+		}
+		else if (timeout == 0)
+		{
+			return false;
+		}
+		else
+		{
+			cv_.wait(lk, [this] {
+				return last_read_ < last_seen_;
+			});
+		}
+	}
+
+	return true;
+}
+
+voltu::FramePtr RoomImpl::getFrame()
+{
+	auto f = std::make_shared<voltu::internal::FrameImpl>();
+
+	std::unique_lock<std::mutex> lk(mutex_);
+	int count = 0;
+
+	for (auto fsid : fsids_)
+	{
+		auto fs = feed_->getFrameSet(fsid);
+		if (!fs) continue;
+
+		f->pushFrameSet(fs);
+		last_read_ = std::max(last_read_, fs->timestamp());
+		++count;
+	}
+
+	if (count == 0) throw voltu::exceptions::NoFrame();
+
+	return f;
+}
+
+std::string RoomImpl::getName()
+{
+	return "";
+}
+
+void RoomImpl::addFrameSet(uint32_t fsid)
+{
+	fsids_.insert(fsid);
+}
+
+bool RoomImpl::active()
+{
+	return ftl::running;
+}
diff --git a/SDK/C++/private/room_impl.hpp b/SDK/C++/private/room_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6c57dfcbb499c32f487d952c7b021da3ce148faa
--- /dev/null
+++ b/SDK/C++/private/room_impl.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <ftl/streams/feed.hpp>
+#include <ftl/threads.hpp>
+#include <condition_variable>
+
+namespace voltu
+{
+namespace internal
+{
+
+class RoomImpl : public voltu::Room
+{
+public:
+	explicit RoomImpl(ftl::stream::Feed*);
+
+	bool waitNextFrame(int64_t) override;
+
+	voltu::FramePtr getFrame() override;
+
+	std::string getName() override;
+
+	bool active() override;
+
+	void addFrameSet(uint32_t fsid);
+
+private:
+	ftl::stream::Feed* feed_;
+	std::unordered_set<uint32_t> fsids_;
+	ftl::stream::Feed::Filter* filter_=nullptr;
+	MUTEX mutex_;
+	std::condition_variable cv_;
+	int64_t last_seen_ = -1;
+	int64_t last_read_ = -1;
+};
+
+}
+}
diff --git a/SDK/C++/private/system.cpp b/SDK/C++/private/system.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3f25035dafd59dd81b9e2dad5609cac569b870c8
--- /dev/null
+++ b/SDK/C++/private/system.cpp
@@ -0,0 +1,94 @@
+#include "system_impl.hpp"
+#include "feed_impl.hpp"
+#include "room_impl.hpp"
+#include "observer_impl.hpp"
+#include <voltu/voltu.hpp>
+#include <voltu/types/errors.hpp>
+#include <ftl/timer.hpp>
+#include <iostream>
+
+using voltu::internal::SystemImpl;
+
+static bool g_isinit = false;
+
+#if defined(WIN32)
+#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
+#else
+#define EXTERN_DLL_EXPORT extern "C"
+#endif
+
+EXTERN_DLL_EXPORT voltu::System* voltu_initialise()
+{
+	if (!g_isinit)
+	{
+		return new SystemImpl();
+	}
+	else
+	{
+		throw voltu::exceptions::AlreadyInit();
+	}
+	return nullptr;
+}
+
+SystemImpl::SystemImpl()
+{
+	int argc = 1;
+	char arg1[] = {'v','o','l','t','u',0};
+	char* argv[] = {arg1,0};
+
+	root_ = ftl::configure(argc, argv, "sdk");
+	net_ = ftl::create<ftl::net::Universe>(root_, "net");
+	feed_ = ftl::create<ftl::stream::Feed>(root_, "system", net_);
+
+	net_->start();
+
+	ftl::timer::start(false);
+}
+
+SystemImpl::~SystemImpl()
+{
+	ftl::timer::stop(true);
+	ftl::pool.stop(true);
+}
+
+voltu::Version SystemImpl::getVersion() const
+{
+	voltu::Version v;
+	v.major = VOLTU_VERSION_MAJOR;
+	v.minor = VOLTU_VERSION_MINOR;
+	v.patch = VOLTU_VERSION_PATCH;
+	return v;
+}
+
+voltu::FeedPtr SystemImpl::open(const std::string& uri)
+{
+	try
+	{
+		uint32_t fsid = feed_->add(uri);
+		return std::make_shared<voltu::internal::FeedImpl>(feed_, fsid);
+	}
+	catch(const std::exception &e)
+	{
+		throw voltu::exceptions::BadSourceURI();
+	}
+};
+
+std::list<voltu::RoomId> SystemImpl::listRooms()
+{
+	auto fsids = feed_->listFrameSets();
+	std::list<voltu::RoomId> res;
+	for (unsigned int fsid : fsids) res.push_front(static_cast<voltu::RoomId>(fsid));
+	return res;
+}
+
+voltu::RoomPtr SystemImpl::getRoom(voltu::RoomId id)
+{
+	auto s = std::make_shared<voltu::internal::RoomImpl>(feed_);
+	s->addFrameSet(id);
+	return s;
+}
+
+voltu::ObserverPtr SystemImpl::createObserver()
+{
+	return std::make_shared<voltu::internal::ObserverImpl>(root_);
+}
diff --git a/SDK/C++/private/system_impl.hpp b/SDK/C++/private/system_impl.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f11f48891d7a3b838303abdda47f240c63d60da7
--- /dev/null
+++ b/SDK/C++/private/system_impl.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <voltu/system.hpp>
+
+#include <ftl/streams/feed.hpp>
+#include <ftl/net/universe.hpp>
+
+namespace voltu
+{
+namespace internal
+{
+
+class SystemImpl : public voltu::System
+{
+public:
+	SystemImpl();
+	~SystemImpl();
+
+	voltu::Version getVersion() const override;
+
+	voltu::RoomPtr createRoom() override { return nullptr; };
+
+	voltu::ObserverPtr createObserver() override;
+
+	voltu::FeedPtr open(const std::string&) override;
+
+	std::list<voltu::RoomId> listRooms() override;
+
+	voltu::RoomPtr getRoom(voltu::RoomId) override;
+
+private:
+	ftl::Configurable* root_;
+	ftl::stream::Feed* feed_;
+	ftl::net::Universe* net_;
+};
+
+}  // namespace internal
+}  // namespace voltu
diff --git a/SDK/C++/public/CMakeLists.txt b/SDK/C++/public/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae8c7da08bfad909195d334e699fdbba5655e0bf
--- /dev/null
+++ b/SDK/C++/public/CMakeLists.txt
@@ -0,0 +1,68 @@
+cmake_minimum_required (VERSION 3.16.0)
+
+project (voltu_sdk VERSION 0.0.1)
+
+include(GNUInstallDirs)
+
+option(WITH_OPENCV "Build with OpenCV wrapper" ON)
+option(WITH_PYTHON "Build Python module" OFF)
+
+find_package( Eigen3 REQUIRED NO_MODULE )
+find_package( Threads REQUIRED )
+
+if (WITH_OPENCV)
+	find_package( OpenCV REQUIRED )
+endif()
+
+if(WIN32)
+	add_definitions(-DWIN32)
+	set(CMAKE_GENERATOR_TOOLSET "host=x64")
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2 /MP4 /std:c++14 /wd4996")
+	set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DFTL_DEBUG /Wall")
+	set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2")
+	set(OS_LIBS "")
+else()
+	add_definitions(-DUNIX)
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -march=native -mfpmath=sse -Wall -Werror=unused-result -Werror=return-type -pthread")
+	set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg")
+	set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
+	set(OS_LIBS "dl")
+endif()
+
+set(VOLTU_SRCS
+	voltu.cpp
+)
+
+set(OPTIONAL_DEPENDENCIES)
+
+if (WITH_OPENCV)
+	list(APPEND OPTIONAL_DEPENDENCIES ${OpenCV_LIBS})
+	list(APPEND VOLTU_SRCS voltu_cv.cpp)
+	set(CMAKE_REQUIRED_INCLUDES ${OpenCV_INCLUDE_DIRS})
+endif()
+
+add_library(voltu_sdk STATIC ${VOLTU_SRCS})
+
+target_include_directories(voltu_sdk
+	PUBLIC include)
+target_link_libraries(voltu_sdk ${OS_LIBS} Threads::Threads ${OPTIONAL_DEPENDENCIES} Eigen3::Eigen)
+
+add_executable(voltu_basic_test
+	samples/basic_test/main.cpp
+)
+target_link_libraries(voltu_basic_test voltu_sdk)
+
+add_executable(voltu_basic_file
+	samples/basic_file/main.cpp
+)
+target_link_libraries(voltu_basic_file voltu_sdk)
+
+add_executable(voltu_basic_virtual_cam
+	samples/basic_virtual_cam/main.cpp
+)
+target_link_libraries(voltu_basic_virtual_cam voltu_sdk)
+
+if (WITH_PYTHON)
+	add_subdirectory(ext/pybind11)
+	add_subdirectory(python)
+endif()
diff --git a/SDK/C++/public/ext/pybind11 b/SDK/C++/public/ext/pybind11
new file mode 160000
index 0000000000000000000000000000000000000000..06a54018c8a9fd9a7be5f5b56414b5da9259f637
--- /dev/null
+++ b/SDK/C++/public/ext/pybind11
@@ -0,0 +1 @@
+Subproject commit 06a54018c8a9fd9a7be5f5b56414b5da9259f637
diff --git a/SDK/C++/public/include/voltu/defines.hpp b/SDK/C++/public/include/voltu/defines.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4f5ded24e0c89cfdbb5b002d1e2d830c1f90aeb2
--- /dev/null
+++ b/SDK/C++/public/include/voltu/defines.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#ifndef PY_API
+/// include function or method in Python API
+#define PY_API
+#endif
+
+#ifndef PY_NO_SHARED_PTR
+/// Ownership is not passed with std::shared_ptr<>
+#define PY_NO_SHARED_PTR
+#endif
+
+#ifndef PY_RV_LIFETIME_PARENT
+/// Lifetime of the return value is tied to the lifetime of a parent object
+#define PY_RV_LIFETIME_PARENT
+#endif
diff --git a/SDK/C++/public/include/voltu/feed.hpp b/SDK/C++/public/include/voltu/feed.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7a58301960f8b065955528d32d309198b85ad96e
--- /dev/null
+++ b/SDK/C++/public/include/voltu/feed.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "defines.hpp"
+
+#include <memory>
+#include <string>
+
+namespace voltu
+{
+
+enum class FeedType
+{
+	kInvalid = 0,
+	kMonoCamera = 1,
+	kStereoCamera = 2,
+	kDepthCamera = 3,
+	kVirtual = 4,
+	kScreen = 5,
+	kRoom = 6,
+	kRooms = 7
+};
+
+class Feed
+{
+public:
+	PY_API virtual std::string getURI() = 0;
+
+	PY_API virtual void remove() = 0;
+
+	// Get rooms
+};
+
+typedef std::shared_ptr<voltu::Feed> FeedPtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/initialise.hpp b/SDK/C++/public/include/voltu/initialise.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e44799a67837ad543664b6c81316c82c98f9747e
--- /dev/null
+++ b/SDK/C++/public/include/voltu/initialise.hpp
@@ -0,0 +1,9 @@
+#pragma once
+#include <memory>
+#include <voltu/system.hpp>
+#include "defines.hpp"
+
+namespace voltu
+{
+	PY_API std::shared_ptr<voltu::System> instance();
+}
diff --git a/SDK/C++/public/include/voltu/observer.hpp b/SDK/C++/public/include/voltu/observer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8f9cad3b74076de36662469829549ddbdd3595bc
--- /dev/null
+++ b/SDK/C++/public/include/voltu/observer.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "defines.hpp"
+
+#include <voltu/types/frame.hpp>
+#include <voltu/types/property.hpp>
+#include <memory>
+#include <Eigen/Eigen>
+
+namespace voltu
+{
+
+enum class ObserverProperty
+{
+	kInvalid			= 0,
+	kBackgroundColour	= 1001,  // String or Int
+	kPointCloudMode		= 1002,  // Bool
+	kProjection			= 1003,  // Enum or Int
+	kAntiAliasing		= 1004,  // Bool
+	kDepthOnly			= 1005,  // Bool
+	kColourSources		= 1006,  // Bool
+	kName				= 1007,  // String
+};
+
+class Observer
+{
+public:
+	PY_API virtual void setResolution(uint32_t w, uint32_t h) = 0;
+
+	PY_API virtual void setFocalLength(uint32_t f) = 0;
+
+	PY_API virtual void setStereo(bool) = 0;
+
+	PY_API virtual bool waitCompletion(int timeout) = 0;
+
+	PY_API virtual void submit(const voltu::FramePtr&) = 0;
+
+	PY_API virtual void setPose(const Eigen::Matrix4f &) = 0;
+
+	PY_API virtual voltu::FramePtr getFrame() = 0;
+
+	PY_API virtual voltu::PropertyPtr property(voltu::ObserverProperty) = 0;
+};
+
+typedef std::shared_ptr<Observer> ObserverPtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/opencv.hpp b/SDK/C++/public/include/voltu/opencv.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..467a793e8ecff579444b8afede9a1dd740aac05c
--- /dev/null
+++ b/SDK/C++/public/include/voltu/opencv.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <opencv2/core/mat.hpp>
+#include <opencv2/core/cuda_types.hpp>
+#include <voltu/types/image.hpp>
+
+namespace voltu
+{
+namespace cv
+{
+
+void convert(voltu::ImagePtr img, ::cv::Mat &mat);
+
+void convert(voltu::ImagePtr img, ::cv::cuda::GpuMat &mat);
+
+void visualise(voltu::ImagePtr img, ::cv::Mat &mat);
+
+}
+}
\ No newline at end of file
diff --git a/SDK/C++/public/include/voltu/room.hpp b/SDK/C++/public/include/voltu/room.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..da09776227d4e25aeef9f763a0f26270941e090b
--- /dev/null
+++ b/SDK/C++/public/include/voltu/room.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "defines.hpp"
+#include <voltu/types/frame.hpp>
+#include <memory>
+
+namespace voltu
+{
+
+enum class RoomType
+{
+	kInvalid = 0,
+	kPhysical = 1,
+	kComposite = 2
+};
+
+typedef unsigned int RoomId;
+
+class Room
+{
+public:
+	PY_API virtual bool waitNextFrame(int64_t) = 0;
+
+	PY_API inline bool hasNextFrame() { return waitNextFrame(0); };
+
+	PY_API virtual voltu::FramePtr getFrame() = 0;
+
+	PY_API virtual std::string getName() = 0;
+
+	PY_API virtual bool active() = 0;
+};
+
+typedef std::shared_ptr<Room> RoomPtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/source.hpp b/SDK/C++/public/include/voltu/source.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2009a607a477381f9b00395a5cf789b5de1218fb
--- /dev/null
+++ b/SDK/C++/public/include/voltu/source.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <memory>
+
+namespace voltu
+{
+
+class Source
+{
+public:
+};
+
+typedef std::shared_ptr<Source> SourcePtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/system.hpp b/SDK/C++/public/include/voltu/system.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf927eeba5cb61615ab598a1af61da9aaec2a997
--- /dev/null
+++ b/SDK/C++/public/include/voltu/system.hpp
@@ -0,0 +1,35 @@
+#pragma once
+#include "defines.hpp"
+
+#include <voltu/room.hpp>
+#include <voltu/observer.hpp>
+#include <voltu/feed.hpp>
+#include <list>
+
+namespace voltu
+{
+
+struct Version
+{
+	int major;
+	int minor;
+	int patch;
+};
+
+class System
+{
+public:
+	virtual voltu::Version getVersion() const = 0;
+
+	PY_API virtual voltu::RoomPtr createRoom() = 0;
+
+	PY_API virtual voltu::ObserverPtr createObserver() = 0;
+
+	PY_API virtual voltu::FeedPtr open(const std::string&) = 0;
+
+	PY_API virtual std::list<voltu::RoomId> listRooms() = 0;
+
+	PY_API virtual voltu::RoomPtr getRoom(voltu::RoomId) = 0;
+};
+
+}
diff --git a/SDK/C++/public/include/voltu/types/channel.hpp b/SDK/C++/public/include/voltu/types/channel.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..07b7c9abadf3caaa36e1fcd8d01f08d496d1a5b0
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/channel.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace voltu {
+	enum class Channel {
+		kInvalid = 0,
+		kColour = 1,
+		kDepth = 2,
+		kNormals = 3
+	};
+}
diff --git a/SDK/C++/public/include/voltu/types/errors.hpp b/SDK/C++/public/include/voltu/types/errors.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9426428014094370c26160ab2f4ec2c893205a97
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/errors.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <exception>
+
+namespace voltu
+{
+namespace exceptions
+{
+
+struct Exception : public std::exception
+{
+	virtual const char* what() const noexcept { return "VolTu General Error"; }
+};
+
+}
+}
+
+#define VOLTU_EXCEPTION(NAME,BASE,MSG) struct NAME : public voltu::exceptions::BASE { virtual const char* what() const noexcept { return MSG; } };
+
+namespace voltu
+{
+namespace exceptions
+{
+
+VOLTU_EXCEPTION(BadImageChannel, Exception, "Invalid image channel");
+VOLTU_EXCEPTION(NoFrame, Exception, "No frame available");
+VOLTU_EXCEPTION(AlreadyInit, Exception, "VolTu already initialised");
+VOLTU_EXCEPTION(LibraryLoadFailed, Exception, "Could not load VolTu library");
+VOLTU_EXCEPTION(LibraryVersionMismatch, Exception, "Wrong version of library found");
+VOLTU_EXCEPTION(BadSourceURI, Exception, "Bad source URI");
+VOLTU_EXCEPTION(InvalidFrameObject, Exception, "Invalid Frame object");
+VOLTU_EXCEPTION(InternalRenderError, Exception, "Internal renderer error");
+VOLTU_EXCEPTION(InvalidProperty, Exception, "Unknown property enum");
+VOLTU_EXCEPTION(BadParameterValue, Exception, "Method parameter is not valid");
+
+}
+}
diff --git a/SDK/C++/public/include/voltu/types/frame.hpp b/SDK/C++/public/include/voltu/types/frame.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..baea806fc9db7d32c43f121bee7901d949378afc
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/frame.hpp
@@ -0,0 +1,26 @@
+#pragma once
+#include "../defines.hpp"
+
+#include <voltu/types/channel.hpp>
+#include <voltu/types/image.hpp>
+#include <voltu/types/pointcloud.hpp>
+#include <voltu/types/intrinsics.hpp>
+#include <list>
+#include <memory>
+
+namespace voltu
+{
+
+class Frame
+{
+public:
+	PY_API PY_RV_LIFETIME_PARENT virtual std::list<voltu::ImagePtr> getImageSet(voltu::Channel channel) = 0;
+
+	PY_API PY_RV_LIFETIME_PARENT virtual voltu::PointCloudPtr getPointCloud(voltu::PointCloudFormat cloudfmt, voltu::PointFormat pointfmt) = 0;
+
+	virtual int64_t getTimestamp() = 0;
+};
+
+typedef std::shared_ptr<Frame> FramePtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/types/image.hpp b/SDK/C++/public/include/voltu/types/image.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..76d5ec5f099223e36d49cdc6dce4f9435d64d8aa
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/image.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "../defines.hpp"
+
+#include <voltu/types/channel.hpp>
+#include <voltu/types/intrinsics.hpp>
+#include <memory>
+
+#include <Eigen/Eigen>
+
+namespace voltu
+{
+
+enum class ImageFormat
+{
+	kInvalid = 0,
+	kFloat32 = 1,
+	kBGRA8 = 2
+};
+
+PY_NO_SHARED_PTR struct ImageData
+{
+	ImageFormat format = ImageFormat::kInvalid;
+	unsigned char* data = nullptr;
+	unsigned int pitch = 0;
+	unsigned int width = 0;
+	unsigned int height = 0;
+};
+
+class Image
+{
+public:
+	PY_API PY_RV_LIFETIME_PARENT virtual ImageData getHost() = 0;
+
+	virtual ImageData getDevice() = 0;
+
+	virtual bool isDevice() = 0;
+
+	PY_API virtual voltu::Channel getChannel() = 0;
+
+	PY_API virtual std::string getName() = 0;
+
+	PY_API virtual voltu::Intrinsics getIntrinsics() = 0;
+
+	PY_API virtual Eigen::Matrix4d getPose() = 0;
+
+	PY_API virtual voltu::StereoIntrinsics getStereoIntrinsics() = 0;
+
+	PY_API virtual int64_t getTimestamp() = 0;
+
+	//virtual voltu::RoomId getRoomId() = 0;
+
+	PY_API virtual int getCameraNumber() = 0;
+
+	PY_API virtual uint32_t getUniqueId() = 0;
+};
+
+typedef std::shared_ptr<Image> ImagePtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/types/intrinsics.hpp b/SDK/C++/public/include/voltu/types/intrinsics.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..70d1dfe2c2f3ba6f8138e9c4dbb1ac618ae25187
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/intrinsics.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+namespace voltu
+{
+
+struct Intrinsics
+{
+	unsigned int width;
+	unsigned int height;
+	float principle_x;
+	float principle_y;
+	float focal_x;
+	float focal_y;
+};
+
+struct StereoIntrinsics : public Intrinsics
+{
+	float baseline;
+	float doffs;
+	float min_depth;
+	float max_depth;
+};
+
+}
diff --git a/SDK/C++/public/include/voltu/types/pointcloud.hpp b/SDK/C++/public/include/voltu/types/pointcloud.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c602a4f6a007b2b2b123bc263f766665e35b8af7
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/pointcloud.hpp
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <memory>
+
+namespace voltu
+{
+
+enum class PointCloudFormat
+{
+	kInvalid = 0,
+	kUnstructured = 1
+};
+
+enum class PointFormat
+{
+	kInvalid = 0,
+	kXYZ_Float = 1,
+	kXYZRGB_Float = 2,
+	kXYZ_NxNyNz_Float = 3,
+	kXYZ_NxNyNz_RGB_Float = 4,
+	kXYZ_Float_RGB_Int8 = 5,
+	kXYZ_NxNyNz_Float_RGB_Int8 = 6
+};
+
+struct PointCloudData
+{
+	PointCloudFormat format = PointCloudFormat::kInvalid;
+	unsigned char* data = nullptr;
+	unsigned int element_size;
+	size_t size;
+};
+
+class PointCloud
+{
+public:
+	virtual PointCloudData getHost() = 0;
+
+	virtual PointCloudData getDevice() = 0;
+};
+
+typedef std::shared_ptr<PointCloud> PointCloudPtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/types/property.hpp b/SDK/C++/public/include/voltu/types/property.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2fa3c43b44ab95503b1cca587ef4aab8cd1cf66b
--- /dev/null
+++ b/SDK/C++/public/include/voltu/types/property.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "../defines.hpp"
+#include <memory>
+
+namespace voltu
+{
+
+class Property
+{
+public:
+	PY_API virtual void setInt(int) = 0;
+
+	PY_API virtual void setFloat(float) = 0;
+
+	PY_API virtual void setString(const std::string &) = 0;
+
+	PY_API virtual void setBool(bool) = 0;
+
+	template <typename T>
+	inline void setEnum(T e) { setInt(static_cast<int>(e)); }
+
+	PY_API virtual int getInt() = 0;
+
+	PY_API virtual float getFloat() = 0;
+
+	PY_API virtual std::string getString() = 0;
+
+	PY_API virtual bool getBool() = 0;
+
+	template <typename T>
+	inline T getEnum() { return static_cast<int>(getInt()); }
+};
+
+typedef std::shared_ptr<Property> PropertyPtr;
+
+}
diff --git a/SDK/C++/public/include/voltu/voltu.hpp b/SDK/C++/public/include/voltu/voltu.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..acb2836f45b5c643465bf20a999795555d8225de
--- /dev/null
+++ b/SDK/C++/public/include/voltu/voltu.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+// Bump these for each release
+#define VOLTU_VERSION_MAJOR 0    // For API incompatible changes
+#define VOLTU_VERSION_MINOR 1    // For binary compatibility and extensions
+#define VOLTU_VERSION_PATCH 0    // Binary compatible internal fixes
+
+#define VOLTU_VERSION ((VOLTU_VERSION_MAJOR*10000) + (VOLTU_VERSION_MINOR*100) + VOLTU_VERSION_PATCH)
+
+#include <voltu/system.hpp>
+#include <voltu/initialise.hpp>
diff --git a/SDK/C++/public/python/CMakeLists.txt b/SDK/C++/public/python/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..20c4ce7c6697e30ffce941c74efbdf71f2f060e7
--- /dev/null
+++ b/SDK/C++/public/python/CMakeLists.txt
@@ -0,0 +1,33 @@
+set(SDK_AUTO_HEADERS
+    voltu/types/channel.hpp
+    voltu/types/frame.hpp
+    voltu/types/image.hpp
+    voltu/types/intrinsics.hpp
+    voltu/observer.hpp
+    voltu/feed.hpp
+    voltu/initialise.hpp
+    voltu/room.hpp
+    voltu/source.hpp
+    voltu/system.hpp
+)
+
+add_custom_command(
+    OUTPUT automatic_bindings.cpp
+    COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen.py
+            automatic_bindings.cpp
+            ${CMAKE_CURRENT_SOURCE_DIR}/../include
+            ${SDK_AUTO_HEADERS}
+
+    DEPENDS voltu_sdk
+)
+
+pybind11_add_module(voltu_sdk_py MODULE
+    automatic_bindings.cpp
+    module.cpp
+)
+
+target_include_directories(voltu_sdk_py PUBLIC include)
+target_include_directories(voltu_sdk_py PRIVATE .)
+
+target_link_libraries(voltu_sdk_py PUBLIC voltu_sdk)
+set_target_properties(voltu_sdk_py PROPERTIES OUTPUT_NAME voltu)
diff --git a/SDK/C++/public/python/CppHeaderParser/CppHeaderParser.py b/SDK/C++/public/python/CppHeaderParser/CppHeaderParser.py
new file mode 100644
index 0000000000000000000000000000000000000000..af29b45878272ad88a1b03fec08b126d3d41ff8b
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/CppHeaderParser.py
@@ -0,0 +1,3715 @@
+#!/usr/bin/python
+#
+# Author: Jashua R. Cloutier (contact via https://bitbucket.org/senex)
+# Project: http://senexcanis.com/open-source/cppheaderparser/
+#
+# Copyright (C) 2011, Jashua R. Cloutier
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in
+#   the documentation and/or other materials provided with the
+#   distribution.
+#
+# * Neither the name of Jashua R. Cloutier nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.  Stories,
+#   blog entries etc making reference to this project may mention the
+#   name Jashua R. Cloutier in terms of project originator/creator etc.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+# The CppHeaderParser.py script is written in Python 2.4 and released to
+# the open source community for continuous improvements under the BSD
+# 2.0 new license, which can be found at:
+#
+#   http://www.opensource.org/licenses/bsd-license.php
+#
+
+from __future__ import print_function
+
+
+from collections import deque
+import os
+import sys
+import re
+import io
+
+import inspect
+
+from .lexer import Lexer
+from .doxygen import extract_doxygen_method_params
+
+try:
+    from .version import __version__
+except ImportError:
+    __version__ = "devel"
+
+version = __version__
+
+# Controls error_print
+print_errors = 1
+# Controls warning_print
+print_warnings = 1
+# Controls debug_print
+debug = 1 if os.environ.get("CPPHEADERPARSER_DEBUG") == "1" else 0
+# Controls trace_print
+debug_trace = 0
+
+if sys.version_info >= (3, 3):
+    # `raise e from src_e` syntax only supported on python 3.3+
+    exec("def raise_exc(e, src_e): raise e from src_e", globals())
+else:
+
+    def raise_exc(e, src_e):
+        raise e
+
+
+def error_print(fmt, *args):
+    if print_errors:
+        fmt = "[%4d] " + fmt
+        args = (inspect.currentframe().f_back.f_lineno,) + args
+        print(fmt % args)
+
+
+def warning_print(fmt, *args):
+    if print_warnings:
+        fmt = "[%4d] " + fmt
+        args = (inspect.currentframe().f_back.f_lineno,) + args
+        print(fmt % args)
+
+
+if debug:
+
+    def debug_print(fmt, *args):
+        fmt = "[%4d] " + fmt
+        args = (inspect.currentframe().f_back.f_lineno,) + args
+        print(fmt % args)
+
+
+else:
+
+    def debug_print(fmt, *args):
+        pass
+
+
+if debug_trace:
+
+    def trace_print(*args):
+        sys.stdout.write("[%s] " % (inspect.currentframe().f_back.f_lineno))
+        for a in args:
+            sys.stdout.write("%s " % a)
+        sys.stdout.write("\n")
+
+
+else:
+
+    def trace_print(*args):
+        pass
+
+
+#: Access specifiers
+supportedAccessSpecifier = ["public", "protected", "private"]
+
+#: Symbols to ignore, usually special macros
+ignoreSymbols = ["Q_OBJECT"]
+
+
+# Track what was added in what order and at what depth
+parseHistory = []
+
+
+def is_namespace(nameStack):
+    """Determines if a namespace is being specified"""
+    if len(nameStack) == 0:
+        return False
+    if nameStack[0] == "namespace":
+        return True
+    return False
+
+
+_fundamentals = {
+    "size_t",
+    "struct",
+    "union",
+    "unsigned",
+    "signed",
+    "bool",
+    "char",
+    "short",
+    "int",
+    "float",
+    "double",
+    "long",
+    "void",
+    "*",
+}
+
+
+def is_fundamental(s):
+    for a in s.split():
+        if a not in _fundamentals:
+            return False
+    return True
+
+
+def is_function_pointer_stack(stack):
+    """Count how many non-nested paranthesis are in the stack.  Useful for determining if a stack is a function pointer"""
+    paren_depth = 0
+    paren_count = 0
+    star_after_first_paren = False
+    last_e = None
+    for e in stack:
+        if e == "(":
+            paren_depth += 1
+        elif e == ")" and paren_depth > 0:
+            paren_depth -= 1
+            if paren_depth == 0:
+                paren_count += 1
+        elif e == "*" and last_e == "(" and paren_count == 0 and paren_depth == 1:
+            star_after_first_paren = True
+        last_e = e
+
+    if star_after_first_paren and paren_count == 2:
+        return True
+    else:
+        return False
+
+
+def is_method_namestack(stack):
+    r = False
+    if "(" not in stack:
+        r = False
+    elif stack[0] == "typedef":
+        r = False  # TODO deal with typedef function prototypes
+    # elif '=' in stack and stack.index('=') < stack.index('(') and stack[stack.index('=')-1] != 'operator': r = False    #disabled July6th - allow all operators
+    elif "operator" in stack:
+        r = True  # allow all operators
+    elif "{" in stack and stack.index("{") < stack.index("("):
+        r = False  # struct that looks like a method/class
+    elif "(" in stack and ")" in stack:
+        if stack[-1] == ":":
+            r = True
+        elif "{" in stack and "}" in stack:
+            r = True
+        elif stack[-1] == ";":
+            if is_function_pointer_stack(stack):
+                r = False
+            else:
+                r = True
+        elif "{" in stack:
+            r = True  # ideally we catch both braces... TODO
+    else:
+        r = False
+    # Test for case of property set to something with parens such as "static const int CONST_A = (1 << 7) - 1;"
+    if r and "(" in stack and "=" in stack and "operator" not in stack:
+        if stack.index("=") < stack.index("("):
+            r = False
+    return r
+
+
+def is_property_namestack(nameStack):
+    r = False
+    if "(" not in nameStack and ")" not in nameStack:
+        r = True
+    elif (
+        "(" in nameStack
+        and "=" in nameStack
+        and nameStack.index("=") < nameStack.index("(")
+    ):
+        r = True
+    # See if we are a function pointer
+    if not r and is_function_pointer_stack(nameStack):
+        r = True
+    return r
+
+
+def is_enum_namestack(nameStack):
+    """Determines if a namestack is an enum namestack"""
+    if not nameStack:
+        return False
+    if nameStack[0] == "enum":
+        return True
+    if len(nameStack) > 1 and nameStack[0] == "typedef" and nameStack[1] == "enum":
+        return True
+    return False
+
+
+def set_location_info(thing, location):
+    filename, line_number = location
+    if filename:
+        thing["filename"] = filename
+    thing["line_number"] = line_number
+
+
+_nhack = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
+
+
+def _split_namespace(namestack):
+    """
+    Given a list of name elements, find the namespace portion
+    and return that as a string
+
+    :rtype: Tuple[str, list]
+    """
+    # TODO: this should be using tokens instead of nhack
+
+    last_colon = None
+    for i, n in enumerate(namestack):
+        if n == "::":
+            last_colon = i
+        if i and n != "::" and not _nhack.match(n):
+            break
+
+    if last_colon:
+        ns, namestack = (
+            "".join(namestack[: last_colon + 1]),
+            namestack[last_colon + 1 :],
+        )
+    else:
+        ns = ""
+
+    return ns, namestack
+
+
+def _iter_ns_str_reversed(namespace):
+    """
+    Take a namespace string, and yield successively smaller portions
+    of it (ex foo::bar::baz::, foo::bar::, foo::). The last item yielded
+    will always be an empty string
+    """
+    if namespace:
+        parts = namespace.split("::")
+        for i in range(len(parts) - 1, 0, -1):
+            yield "::".join(parts[:i]) + "::"
+
+    yield ""
+
+
+def _split_by_comma(namestack):
+    while namestack:
+        if "," not in namestack:
+            yield namestack
+            break
+        idx = namestack.index(",")
+        ns = namestack[:idx]
+        yield ns
+        namestack = namestack[idx + 1 :]
+
+
+class TagStr(str):
+    """Wrapper for a string that allows us to store the line number associated with it"""
+
+    def __new__(cls, *args, **kwargs):
+        location = kwargs.pop("location")
+        s = str.__new__(cls, *args, **kwargs)
+        s.location = location
+        return s
+
+
+class CppParseError(Exception):
+    def __init__(self, msg, tok=None):
+        Exception.__init__(self, msg)
+        self.tok = tok
+
+
+class CppTemplateParam(dict):
+    """
+    Dictionary that contains the following:
+
+    - ``decltype`` - If True, this is a decltype
+    - ``param`` - Parameter value or typename
+    - ``params`` - Only present if a template specialization. Is a list of
+        :class:`.CppTemplateParam`
+    - ``...`` - If True, indicates a parameter pack
+    """
+
+    def __init__(self):
+        self["param"] = ""
+        self["decltype"] = False
+        self["..."] = False
+
+    def __str__(self):
+        s = []
+        if self["decltype"]:
+            s.append("decltype")
+
+        s.append(self["param"])
+
+        params = self.get("params")
+        if params is not None:
+            s.append("<%s>" % ",".join(str(p) for p in params))
+
+        if self["..."]:
+            if s:
+                s[-1] = s[-1] + "..."
+            else:
+                s.append("...")
+
+        return "".join(s)
+
+
+class CppBaseDecl(dict):
+    """
+    Dictionary that contains the following
+
+    - ``access`` - Anything in supportedAccessSpecifier
+    - ``class`` - Name of the type, along with template specializations
+    - ``decl_name`` - Name of the type only
+    - ``decl_params`` - Only present if a template specialization (Foo<int>).
+      Is a list of :class:`.CppTemplateParam`.
+    - ``decltype`` - True/False indicates a decltype, the contents are in
+      ``decl_name``
+    - ``virtual`` - True/False indicates virtual inheritance
+    - ``...`` - True/False indicates a parameter pack
+
+    """
+
+    def __init__(self, default_access):
+        self["access"] = default_access
+        self["class"] = ""
+        self["decl_name"] = ""
+        self["decltype"] = False
+        self["virtual"] = False
+        self["..."] = False
+
+    def _fix_classname(self):
+        # set class to the full decl for legacy reasons
+        params = self.get("decl_params")
+
+        if self["decltype"]:
+            s = "decltype"
+        else:
+            s = ""
+
+        s += self["decl_name"]
+        if params:
+            s += "<%s>" % (",".join(str(p) for p in params))
+
+        if self["..."]:
+            s += "..."
+
+        return s
+
+
+def _consume_parens(stack):
+    i = 0
+    sl = len(stack)
+    nested = 1
+    while i < sl:
+        t = stack[i]
+        i += 1
+        if t == ")":
+            nested -= 1
+            if nested == 0:
+                return i
+        elif t == "(":
+            nested += 1
+
+    raise CppParseError("Unmatched (")
+
+
+def _parse_template_decl(stack):
+    debug_print("_parse_template_decl: %s", stack)
+    params = []
+    param = CppTemplateParam()
+    i = 0
+    sl = len(stack)
+    init = True
+    require_ending = False
+    while i < sl:
+        t = stack[i]
+        i += 1
+        if init:
+            init = False
+            if t == "decltype":
+                param["decltype"] = True
+                continue
+
+        if t == ",":
+            params.append(param)
+            init = True
+            require_ending = False
+            param = CppTemplateParam()
+
+            continue
+        elif t == ">":
+            params.append(param)
+            return params, i - 1
+
+        if require_ending:
+            raise CppParseError("expected comma, found %s" % t)
+
+        if t == "...":
+            param["..."] = True
+            require_ending = True
+        elif t == "(":
+            s = stack[i:]
+            n = _consume_parens(s)
+            i += n
+            param["param"] = param["param"] + "".join(s[:n])
+        else:
+            if t and t[0] == "<":
+                param["params"], n = _parse_template_decl([t[1:]] + stack[i:])
+                i += n
+            else:
+                param["param"] = param["param"] + t
+
+    raise CppParseError("Unmatched <")
+
+
+def _parse_cppclass_name(c, stack):
+    # ignore first thing
+    i = 1
+    sl = len(stack)
+    name = ""
+    require_ending = False
+    while i < sl:
+        t = stack[i]
+        i += 1
+
+        if t == ":":
+            if i >= sl:
+                raise CppParseError("class decl ended with ':'")
+            break
+        elif t == "::":
+            name += "::"
+            continue
+        elif t == "final":
+            c["final"] = True
+            continue
+
+        if require_ending:
+            raise CppParseError("Unexpected '%s' in class" % ("".join(stack[i - 1 :])))
+
+        if t and t[0] == "<":
+            c["class_params"], n = _parse_template_decl([t[1:]] + stack[i:])
+            i += n
+            require_ending = True
+        else:
+            name += t
+
+    c["namespace"] = ""
+
+    # backwards compat
+    if name.startswith("anon-"):
+        if (
+            name.startswith("anon-class-")
+            or name.startswith("anon-struct-")
+            or name.startswith("anon-union-")
+        ):
+            name = "<" + name + ">"
+    c["name"] = name
+    c["bare_name"] = name
+    debug_print("Found class '%s'", name)
+
+    # backwards compat
+    classParams = c.get("class_params")
+    if classParams is not None:
+        c["name"] = c["name"] + "<%s>" % ",".join(str(p) for p in classParams)
+
+    return i
+
+
+def _parse_cpp_base(stack, default_access):
+    debug_print("Parsing base: %s (access %s)", stack, default_access)
+    inherits = []
+    i = 0
+    sl = len(stack)
+    init = True
+    base = CppBaseDecl(default_access)
+    require_ending = False
+    while i < sl:
+        t = stack[i]
+        i += 1
+
+        if init:
+            if t in supportedAccessSpecifier:
+                base["access"] = t
+                continue
+            elif t == "virtual":
+                base["virtual"] = True
+                continue
+
+            init = False
+
+            if t == "decltype":
+                base["decltype"] = True
+                continue
+
+        if t == ",":
+            inherits.append(base)
+            base = CppBaseDecl(default_access)
+            init = True
+            require_ending = False
+            continue
+
+        if require_ending:
+            if t == "::":
+                if "decl_params" in base:
+                    base["decl_name"] = base._fix_classname()
+                    del base["decl_params"]
+                    base["..."]
+                require_ending = False
+            else:
+                raise CppParseError("expected comma, found '%s'" % t)
+
+        if t == "(":
+            s = stack[i:]
+            n = _consume_parens(s)
+            i += n
+            base["decl_name"] = base["decl_name"] + "".join(s[:n])
+        elif t == "...":
+            base["..."] = True
+            require_ending = True
+        else:
+            if t[0] == "<":
+                base["decl_params"], n = _parse_template_decl([t[1:]] + stack[i:])
+                i += n
+                require_ending = True
+            else:
+                base["decl_name"] = base["decl_name"] + t
+
+    # backwards compat
+    inherits.append(base)
+    for base in inherits:
+        base["class"] = base._fix_classname()
+
+    return inherits
+
+
+class CppClass(dict):
+    """
+    Dictionary that contains at least the following keys:
+
+    * ``name`` - Name of the class
+    * ``doxygen`` - Doxygen comments associated with the class if they exist
+    * ``inherits`` - List of Classes that this one inherits. Values are
+      :class:`.CppBaseDecl`
+    * ``methods`` - Dictionary where keys are from supportedAccessSpecifier
+      and values are a lists of :class:`.CppMethod`
+    * ``namespace`` - Namespace of class
+    * ``properties`` - Dictionary where keys are from supportedAccessSpecifier
+      and values are lists of :class:`.CppVariable`
+    * ``enums`` - Dictionary where keys are from supportedAccessSpecifier and
+      values are lists of :class:`.CppEnum`
+    * ``nested_classes`` - Classes and structs defined within this class
+    * ``final`` - True if final
+    * ``abstract`` - True if abstract
+    * ``using`` - Using directives in this class scope: key is name for lookup,
+      value is :class:`.CppVariable`
+    * ``parent`` - If not None, the class that this class is nested in
+
+    An example of how this could look is as follows::
+
+        {
+            'name': ""
+            'inherits':[]
+            'methods':
+            {
+                'public':[],
+                'protected':[],
+                'private':[]
+            },
+            'properties':
+            {
+                'public':[],
+                'protected':[],
+                'private':[]
+            },
+            'enums':
+            {
+                'public':[],
+                'protected':[],
+                'private':[]
+            }
+        }
+    """
+
+    def get_all_methods(self):
+        r = []
+        for typ in supportedAccessSpecifier:
+            r += self["methods"][typ]
+        return r
+
+    def get_all_method_names(self):
+        r = []
+        for typ in supportedAccessSpecifier:
+            r += self.get_method_names(typ)  # returns list
+        return r
+
+    def get_all_pure_virtual_methods(self):
+        r = {}
+        for typ in supportedAccessSpecifier:
+            r.update(self.get_pure_virtual_methods(typ))  # returns dict
+        return r
+
+    def get_method_names(self, type="public"):
+        return [meth["name"] for meth in self["methods"][type]]
+
+    def get_pure_virtual_methods(self, type="public"):
+        r = {}
+        for meth in self["methods"][type]:
+            if meth["pure_virtual"]:
+                r[meth["name"]] = meth
+        return r
+
+    def _lookup_type(self, name):
+        # TODO: should have indexes for these lookups... and they
+        #       should be more unified
+        for access in supportedAccessSpecifier:
+            for e in self["enums"][access]:
+                if e.get("name") == name:
+                    return {
+                        "enum": self["name"] + "::" + e["name"],
+                        "type": e["name"],
+                        "namespace": e["namespace"],
+                    }
+        for n in self["nested_classes"]:
+            if n["name"] == name:
+                return {"raw_type": self["name"] + "::" + n["name"], "type": n["name"]}
+
+    def __init__(self, nameStack, curTemplate, doxygen, location, defaultAccess):
+        self["nested_classes"] = []
+        self["parent"] = None
+        self["abstract"] = False
+        self["final"] = False
+        self._public_enums = {}
+        self._public_typedefs = {}
+        self._public_forward_declares = []
+        self["namespace"] = ""
+        self["using"] = {}
+
+        debug_print("Class:    %s", nameStack)
+        debug_print("Template: %s", curTemplate)
+
+        if len(nameStack) < 2:
+            nameStack.insert(1, "")  # anonymous struct
+        if doxygen:
+            self["doxygen"] = doxygen
+
+        # consume name of class, with any namespaces or templates
+        n = _parse_cppclass_name(self, nameStack)
+
+        # consume bases
+        baseStack = nameStack[n:]
+        if baseStack:
+            self["inherits"] = _parse_cpp_base(baseStack, defaultAccess)
+        else:
+            self["inherits"] = []
+
+        set_location_info(self, location)
+
+        if curTemplate:
+            self["template"] = curTemplate
+            trace_print("Setting template to '%s'" % self["template"])
+
+        methodAccessSpecificList = {}
+        propertyAccessSpecificList = {}
+        enumAccessSpecificList = {}
+        typedefAccessSpecificList = {}
+        forwardAccessSpecificList = {}
+
+        for accessSpecifier in supportedAccessSpecifier:
+            methodAccessSpecificList[accessSpecifier] = []
+            propertyAccessSpecificList[accessSpecifier] = []
+            enumAccessSpecificList[accessSpecifier] = []
+            typedefAccessSpecificList[accessSpecifier] = []
+            forwardAccessSpecificList[accessSpecifier] = []
+
+        self["methods"] = methodAccessSpecificList
+        self["properties"] = propertyAccessSpecificList
+        self["enums"] = enumAccessSpecificList
+        self["typedefs"] = typedefAccessSpecificList
+        self["forward_declares"] = forwardAccessSpecificList
+
+    def show(self):
+        """Convert class to a string"""
+        namespace_prefix = ""
+        if self["namespace"]:
+            namespace_prefix = self["namespace"] + "::"
+        rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"])
+        if self["final"]:
+            rtn += " final"
+        if self["abstract"]:
+            rtn += "    (abstract)\n"
+        else:
+            rtn += "\n"
+
+        if "doxygen" in list(self.keys()):
+            rtn += self["doxygen"] + "\n"
+        if "parent" in list(self.keys()) and self["parent"]:
+            rtn += "parent class: " + self["parent"]["name"] + "\n"
+
+        if "inherits" in list(self.keys()):
+            rtn += "  Inherits: "
+            for inheritClass in self["inherits"]:
+                if inheritClass["virtual"]:
+                    rtn += "virtual "
+                rtn += "%s %s, " % (inheritClass["access"], inheritClass["class"])
+            rtn += "\n"
+        rtn += "  {\n"
+        for accessSpecifier in supportedAccessSpecifier:
+            rtn += "    %s\n" % (accessSpecifier)
+            # Enums
+            if len(self["enums"][accessSpecifier]):
+                rtn += "        <Enums>\n"
+            for enum in self["enums"][accessSpecifier]:
+                rtn += "            %s\n" % (repr(enum))
+            # Properties
+            if len(self["properties"][accessSpecifier]):
+                rtn += "        <Properties>\n"
+            for property in self["properties"][accessSpecifier]:
+                rtn += "            %s\n" % (repr(property))
+            # Methods
+            if len(self["methods"][accessSpecifier]):
+                rtn += "        <Methods>\n"
+            for method in self["methods"][accessSpecifier]:
+                rtn += "\t\t" + method.show() + "\n"
+        rtn += "  }\n"
+        print(rtn)
+
+    def __str__(self):
+        """Convert class to a string"""
+        namespace_prefix = ""
+        if self["namespace"]:
+            namespace_prefix = self["namespace"] + "::"
+        rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"])
+        if self["final"]:
+            rtn += " final"
+        if self["abstract"]:
+            rtn += "    (abstract)\n"
+        else:
+            rtn += "\n"
+
+        if "doxygen" in list(self.keys()):
+            rtn += self["doxygen"] + "\n"
+        if "parent" in list(self.keys()) and self["parent"]:
+            rtn += "parent class: " + self["parent"]["name"] + "\n"
+
+        if "inherits" in list(self.keys()) and len(self["inherits"]):
+            rtn += "Inherits: "
+            for inheritClass in self["inherits"]:
+                if inheritClass.get("virtual", False):
+                    rtn += "virtual "
+                rtn += "%s %s, " % (inheritClass["access"], inheritClass["class"])
+            rtn += "\n"
+        rtn += "{\n"
+        for accessSpecifier in supportedAccessSpecifier:
+            rtn += "%s\n" % (accessSpecifier)
+            # Enums
+            if len(self["enums"][accessSpecifier]):
+                rtn += "    // Enums\n"
+            for enum in self["enums"][accessSpecifier]:
+                rtn += "    %s\n" % (repr(enum))
+            # Properties
+            if len(self["properties"][accessSpecifier]):
+                rtn += "    // Properties\n"
+            for property in self["properties"][accessSpecifier]:
+                rtn += "    %s\n" % (repr(property))
+            # Methods
+            if len(self["methods"][accessSpecifier]):
+                rtn += "    // Methods\n"
+            for method in self["methods"][accessSpecifier]:
+                rtn += "   %s\n" % (repr(method))
+        rtn += "}\n"
+        return rtn
+
+
+class CppUnion(CppClass):
+    """
+    Dictionary that contains at least the following keys:
+
+    * ``name`` - Name of the union
+    * ``doxygen`` - Doxygen comments associated with the union if they exist
+    * ``members`` - List of members of the union
+    """
+
+    def __init__(self, nameStack, doxygen, location):
+        CppClass.__init__(self, nameStack, None, doxygen, location, "public")
+        self["members"] = self["properties"]["public"]
+
+    def transform_to_union_keys(self):
+        print("union keys: %s" % list(self.keys()))
+        for key in [
+            "inherits",
+            "parent",
+            "abstract",
+            "namespace",
+            "typedefs",
+            "methods",
+        ]:
+            del self[key]
+
+    def show(self):
+        """Convert class to a string"""
+        print(self)
+
+    def __str__(self):
+        """Convert class to a string"""
+        namespace_prefix = ""
+        if self["namespace"]:
+            namespace_prefix = self["namespace"] + "::"
+        rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"])
+        if self["abstract"]:
+            rtn += "    (abstract)\n"
+        else:
+            rtn += "\n"
+
+        if "doxygen" in list(self.keys()):
+            rtn += self["doxygen"] + "\n"
+        if "parent" in list(self.keys()) and self["parent"]:
+            rtn += "parent class: " + self["parent"]["name"] + "\n"
+
+        rtn += "{\n"
+        for member in self["members"]:
+            rtn += "    %s\n" % (repr(member))
+        rtn += "}\n"
+        return rtn
+
+
+class _CppMethod(dict):
+    def _params_helper1(self, stack):
+        # deal with "throw" keyword
+        if "throw" in stack:
+            stack = stack[: stack.index("throw")]
+
+        ## remove GCC keyword __attribute__(...) and preserve returns ##
+        cleaned = []
+        hit = False
+        hitOpen = 0
+        hitClose = 0
+        for a in stack:
+            if a == "__attribute__":
+                hit = True
+            if hit:
+                if a == "(":
+                    hitOpen += 1
+                elif a == ")":
+                    hitClose += 1
+                if a == ")" and hitOpen == hitClose:
+                    hit = False
+            else:
+                cleaned.append(a)
+        stack = cleaned
+
+        # also deal with attribute((const)) function prefix #
+        # TODO this needs to be better #
+        if len(stack) > 5:
+            a = "".join(stack)
+            if a.startswith("((__const__))"):
+                stack = stack[5:]
+            elif a.startswith("__attribute__((__const__))"):
+                stack = stack[6:]
+
+        stack = stack[stack.index("(") + 1 :]
+        if not stack:
+            return []
+        if (
+            len(stack) >= 3 and stack[0] == ")" and stack[1] == ":"
+        ):  # is this always a constructor?
+            self["constructor"] = True
+            return []
+
+        stack.reverse()
+        _end_ = stack.index(")")
+        stack.reverse()
+        stack = stack[: len(stack) - (_end_ + 1)]
+        if "(" not in stack:
+            return stack  # safe to return, no defaults that init a class
+
+        return stack
+
+    def _params_helper2(self, params):
+        for p in params:
+            p["method"] = self  # save reference in variable to parent method
+            p["parent"] = self
+            if "::" in p["type"]:
+                ns = p["type"].split("::")[0]
+                if ns not in Resolver.NAMESPACES and ns in Resolver.CLASSES:
+                    p["type"] = self["namespace"] + p["type"]
+            else:
+                p["namespace"] = self["namespace"]
+
+
+class CppMethod(_CppMethod):
+    """
+    Dictionary that contains at least the following keys:
+
+    * ``rtnType`` - Return type of the method (ex. "int")
+    * ``name`` - Name of the method
+    * ``doxygen`` - Doxygen comments associated with the method if they exist
+    * ``parameters`` - List of :class:`.CppVariable`
+    * ``parent`` - If not None, the class this method belongs to
+    """
+
+    def show(self):
+        r = ["method name: %s (%s)" % (self["name"], self["debug"])]
+        if self["returns"]:
+            r.append("returns: %s" % self["returns"])
+        if self["parameters"]:
+            r.append("number arguments: %s" % len(self["parameters"]))
+        if self["pure_virtual"]:
+            r.append("pure virtual: %s" % self["pure_virtual"])
+        if self["constructor"]:
+            r.append("constructor")
+        if self["destructor"]:
+            r.append("destructor")
+        return "\n\t\t  ".join(r)
+
+    def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location):
+        debug_print("Method:   %s", nameStack)
+        debug_print("Template: %s", curTemplate)
+
+        if doxygen:
+            self["doxygen"] = doxygen
+
+        # Remove leading keywords
+        for i, word in enumerate(nameStack):
+            if word not in Resolver.C_KEYWORDS:
+                nameStack = nameStack[i:]
+                break
+
+        if "operator" in nameStack:
+            rtnType = " ".join(nameStack[: nameStack.index("operator")])
+            self["name"] = "".join(
+                nameStack[nameStack.index("operator") : nameStack.index("(")]
+            )
+        else:
+            rtnType = " ".join(nameStack[: nameStack.index("(") - 1])
+            self["name"] = " ".join(
+                nameStack[nameStack.index("(") - 1 : nameStack.index("(")]
+            )
+
+        if len(rtnType) == 0 or self["name"] == curClass:
+            rtnType = "void"
+
+        self["rtnType"] = (
+            rtnType.replace(" :: ", "::")
+            .replace(" < ", "<")
+            .replace(" > ", "> ")
+            .replace(">>", "> >")
+            .replace(" ,", ",")
+        )
+
+        # deal with "noexcept" specifier/operator
+        self["noexcept"] = None
+        if "noexcept" in nameStack:
+            noexcept_idx = nameStack.index("noexcept")
+            hit = True
+            cleaned = nameStack[:noexcept_idx]
+            parentCount = 0
+            noexcept = "noexcept"
+            for a in nameStack[noexcept_idx + 1 :]:
+                if a == "noexcept":
+                    hit = True
+                if hit:
+                    if a == "(":
+                        parentCount += 1
+                    elif a == ")":
+                        parentCount -= 1
+                    elif parentCount == 0 and a != "noexcept":
+                        hit = False
+                        cleaned.append(a)
+                        continue  # noexcept without parenthesis
+                    if a == ")" and parentCount == 0:
+                        hit = False
+                    noexcept += a
+                else:
+                    cleaned.append(a)
+            self["noexcept"] = noexcept
+            nameStack = cleaned
+
+        for spec in ["const", "final", "override"]:
+            self[spec] = False
+            for i in reversed(nameStack):
+                if i == spec:
+                    self[spec] = True
+                    break
+                elif i == ")":
+                    break
+
+        self.update(methinfo)
+        set_location_info(self, location)
+
+        paramsStack = self._params_helper1(nameStack)
+
+        debug_print("curTemplate: %s", curTemplate)
+        if curTemplate:
+            self["template"] = curTemplate
+            debug_print("SET self['template'] to `%s`", self["template"])
+
+        params = []
+        # See if there is a doxygen comment for the variable
+        if "doxygen" in self:
+            doxyVarDesc = extract_doxygen_method_params(self["doxygen"])
+        else:
+            doxyVarDesc = {}
+
+        # non-vararg by default
+        self["vararg"] = False
+        # Create the variable now
+        while len(paramsStack):
+            # Find commas that are not nexted in <>'s like template types
+            open_template_count = 0
+            open_paren_count = 0
+            open_brace_count = 0
+            param_separator = 0
+            i = 0
+            for elm in paramsStack:
+                if elm in "<>(){},":
+                    if elm == ",":
+                        if (
+                            open_template_count == 0
+                            and open_paren_count == 0
+                            and open_brace_count == 0
+                        ):
+                            param_separator = i
+                            break
+                    elif "<" == elm:
+                        open_template_count += 1
+                    elif ">" == elm:
+                        open_template_count -= 1
+                    elif "(" == elm:
+                        open_paren_count += 1
+                    elif ")" == elm:
+                        open_paren_count -= 1
+                    elif "{" == elm:
+                        open_brace_count += 1
+                    elif "}" == elm:
+                        open_brace_count -= 1
+                i += 1
+
+            if param_separator:
+                tpstack = paramsStack[0:param_separator]
+                param = CppVariable(
+                    tpstack,
+                    None,
+                    getattr(tpstack[0], "location", location),
+                    doxyVarDesc=doxyVarDesc,
+                )
+                if len(list(param.keys())):
+                    params.append(param)
+                paramsStack = paramsStack[param_separator + 1 :]
+            elif len(paramsStack) and paramsStack[0] == "...":
+                self["vararg"] = True
+                paramsStack = paramsStack[1:]
+            else:
+                param = CppVariable(
+                    paramsStack,
+                    None,
+                    getattr(paramsStack[0], "location", location),
+                    doxyVarDesc=doxyVarDesc,
+                )
+                if len(list(param.keys())):
+                    params.append(param)
+                break
+
+        # foo(void) should be zero parameters
+        if len(params) == 1 and params[0]["type"] == "void":
+            params = []
+
+        self["parameters"] = params
+        self._params_helper2(params)  # mods params inplace
+
+    def __str__(self):
+        filter_keys = ("parent", "defined", "operator", "returns_reference")
+        cpy = dict((k, v) for (k, v) in list(self.items()) if k not in filter_keys)
+        return "%s" % cpy
+
+
+_var_keywords = {
+    n: 0
+    for n in "constant constexpr reference pointer static typedefs class fundamental unresolved".split()
+}
+
+
+class _CppVariable(dict):
+    def _name_stack_helper(self, stack):
+        stack = list(stack)
+        if "=" not in stack:  # TODO refactor me
+            # check for array[n] and deal with funny array syntax: "int myvar:99"
+            array = []
+            while stack and stack[-1].isdigit():
+                array.append(stack.pop())
+            if array:
+                array.reverse()
+                self["array"] = int("".join(array))
+            if stack and stack[-1].endswith(":"):
+                stack[-1] = stack[-1][:-1]
+
+        while stack and not stack[-1]:
+            stack.pop()  # can be empty
+        return stack
+
+    def init(self):
+        # assert self['name']    # allow unnamed variables, methods like this: "void func(void);"
+        a = []
+        self["aliases"] = []
+        self["parent"] = None
+        self["typedef"] = None
+        self.update(_var_keywords)
+        for b in self["type"].split():
+            if b == "__const__":
+                b = "const"
+            a.append(b)
+        self["type"] = " ".join(a)
+
+
+class CppVariable(_CppVariable):
+    """
+    Dictionary that contains at least the following keys:
+
+    * ``type`` - Type for the variable (ex. "const string &")
+    * ``name`` - Name of the variable (ex. "numItems")
+    * ``namespace`` - Namespace
+    * ``desc`` - If a method/function parameter, doxygen description for this parameter (optional)
+    * ``doxygen`` - If a normal property/variable, doxygen description for this
+    * ``default`` - Default value of the variable, this key will only
+      exist if there is a default value
+    * ``extern`` - True if its an extern, False if not
+    * ``parent`` - If not None, either the class this is a property of, or the
+      method this variable is a parameter in
+    """
+
+    Vars = []
+
+    def __init__(self, nameStack, doxygen, location, **kwargs):
+        debug_print("var trace %s", nameStack)
+        if len(nameStack) and nameStack[0] == "extern":
+            self["extern"] = True
+            del nameStack[0]
+        else:
+            self["extern"] = False
+
+        _stack_ = nameStack
+        if "[" in nameStack:  # strip off array informatin
+            arrayStack = nameStack[nameStack.index("[") :]
+            if nameStack.count("[") > 1:
+                debug_print("Multi dimensional array")
+                debug_print("arrayStack=%s", arrayStack)
+                nums = [x for x in arrayStack if x.isdigit()]
+                # Calculate size by multiplying all dimensions
+                p = 1
+                for n in nums:
+                    p *= int(n)
+                # Multi dimensional array
+                self["array_size"] = p
+                self["multi_dimensional_array"] = 1
+                self["multi_dimensional_array_size"] = "x".join(nums)
+            else:
+                debug_print("Array")
+                if len(arrayStack) == 3:
+                    self["array_size"] = arrayStack[1]
+            nameStack = nameStack[: nameStack.index("[")]
+            self["array"] = 1
+        else:
+            self["array"] = 0
+        nameStack = self._name_stack_helper(nameStack)
+
+        if doxygen:
+            self["doxygen"] = doxygen
+
+        debug_print("Variable: %s", nameStack)
+
+        set_location_info(self, location)
+        self["function_pointer"] = 0
+
+        if len(nameStack) < 2:  # +++
+            if len(nameStack) == 1:
+                self["type"] = nameStack[0]
+                self["name"] = ""
+            else:
+                error_print("%s", _stack_)
+                assert 0
+
+        elif is_function_pointer_stack(nameStack):  # function pointer
+            self["type"] = " ".join(
+                nameStack[: nameStack.index("(") + 2]
+                + nameStack[nameStack.index(")") :]
+            )
+            self["name"] = " ".join(
+                nameStack[nameStack.index("(") + 2 : nameStack.index(")")]
+            )
+            self["function_pointer"] = 1
+
+        elif "=" in nameStack:
+            self["type"] = " ".join(nameStack[: nameStack.index("=") - 1])
+            self["name"] = nameStack[nameStack.index("=") - 1]
+            default = " ".join(nameStack[nameStack.index("=") + 1 :])
+            default = self._filter_name(default)
+            self["default"] = default
+            # backwards compat; deprecate camelCase in dicts
+            self["defaultValue"] = default
+
+        elif is_fundamental(nameStack[-1]) or nameStack[-1] in [">", "<", ":", "."]:
+            # Un named parameter
+            self["type"] = " ".join(nameStack)
+            self["name"] = ""
+
+        else:  # common case
+            self["type"] = " ".join(nameStack[:-1])
+            self["name"] = nameStack[-1]
+
+        self["type"] = self._filter_name(self["type"])
+
+        # Optional doxygen description
+        try:
+            self["desc"] = kwargs["doxyVarDesc"][self["name"]]
+        except:
+            pass
+
+        self.init()
+        CppVariable.Vars.append(self)  # save and resolve later
+
+    def _filter_name(self, name):
+        name = name.replace(" :", ":").replace(": ", ":")
+        name = name.replace(" < ", "<")
+        name = name.replace(" > ", "> ").replace(">>", "> >")
+        name = name.replace(") >", ")>")
+        name = name.replace(" {", "{").replace(" }", "}")
+        name = name.replace(" ,", ",")
+        return name
+
+    def __str__(self):
+        keys_white_list = [
+            "constant",
+            "name",
+            "reference",
+            "type",
+            "static",
+            "pointer",
+            "desc",
+            "line_number",
+            "extern",
+        ]
+        cpy = dict((k, v) for (k, v) in list(self.items()) if k in keys_white_list)
+        if "array_size" in self:
+            cpy["array_size"] = self["array_size"]
+        return "%s" % cpy
+
+
+class _CppEnum(dict):
+    def resolve_enum_values(self, values):
+        """Evaluates the values list of dictionaries passed in and figures out what the enum value
+        for each enum is editing in place:
+
+        Example
+        From: [{'name': 'ORANGE'},
+               {'name': 'RED'},
+               {'name': 'GREEN', 'value': '8'}]
+        To:   [{'name': 'ORANGE', 'value': 0},
+               {'name': 'RED', 'value': 1},
+               {'name': 'GREEN', 'value': 8}]
+        """
+        t = int
+        i = 0
+        names = [v["name"] for v in values]
+        for v in values:
+            if "value" in v:
+                a = v["value"].strip()
+                # Remove single quotes from single quoted chars (unless part of some expression
+                if len(a) == 3 and a[0] == "'" and a[2] == "'":
+                    a = v["value"] = a[1]
+                if a.lower().startswith("0x"):
+                    try:
+                        i = a = int(a, 16)
+                    except:
+                        pass
+                elif a.isdigit():
+                    i = a = int(a)
+                elif a in names:
+                    for other in values:
+                        if other["name"] == a:
+                            v["value"] = other["value"]
+                            break
+
+                elif '"' in a or "'" in a:
+                    t = str  # only if there are quotes it this a string enum
+                else:
+                    try:
+                        a = i = ord(a)
+                    except:
+                        pass
+                # Allow access of what is in the file pre-convert if converted
+                if v["value"] != str(a):
+                    v["raw_value"] = v["value"]
+                v["value"] = a
+            else:
+                v["value"] = i
+            try:
+                v["value"] = v["value"].replace(" < < ", " << ").replace(" >> ", " >> ")
+            except:
+                pass
+            i += 1
+        self["type"] = t
+
+
+class CppEnum(_CppEnum):
+    """Contains the following keys:
+
+    * ``name`` - Name of the enum (ex. "ItemState")
+    * ``namespace`` - Namespace containing the enum
+    * ``isclass`` - True if created via 'enum class' or 'enum struct'
+    * ``values`` - List of values. The values are a dictionary with
+      the following key/values:
+
+      - ``name`` - name of the key (ex. "PARSING_HEADER"),
+      - ``value`` - Specified value of the enum, this key will only exist
+        if a value for a given enum value was defined
+    """
+
+    def __init__(self, name, doxygen, location):
+        if doxygen:
+            self["doxygen"] = doxygen
+        if name:
+            self["name"] = name
+        self["namespace"] = ""
+        self["typedef"] = False
+        self["isclass"] = False
+        self["values"] = []
+        set_location_info(self, location)
+
+
+class _CppPreprocessorLiteral(dict):
+    """Implementation for #pragmas, #defines and #includes, contains the
+    following keys:
+
+    * ``value`` the value literal of the preprocessor item
+    * ``line_number`` line number at which the item was found
+    """
+
+    def __init__(self, macro, location):
+        self["value"] = re.split("[\t ]+", macro, 1)[1].strip()
+        set_location_info(self, location)
+
+    def __str__(self):
+        return self["value"]
+
+
+# Implementation is shared between CppPragma, CppDefine, CppInclude but they are
+# distinct so that they may diverge if required without interface-breaking
+# changes
+class CppPragma(_CppPreprocessorLiteral):
+    pass
+
+
+class CppDefine(_CppPreprocessorLiteral):
+    pass
+
+
+class CppInclude(_CppPreprocessorLiteral):
+    pass
+
+
+C99_NONSTANDARD = {
+    "int8": "signed char",
+    "int16": "short int",
+    "int32": "int",
+    "int64": "int64_t",  # this can be: long int (64bit), or long long int (32bit)
+    "uint": "unsigned int",
+    "uint8": "unsigned char",
+    "uint16": "unsigned short int",
+    "uint32": "unsigned int",
+    "uint64": "uint64_t",  # depends on host bits
+}
+
+
+def standardize_fundamental(s):
+    if s in C99_NONSTANDARD:
+        return C99_NONSTANDARD[s]
+    else:
+        return s
+
+
+class Resolver(object):
+    C_FUNDAMENTAL = "size_t unsigned signed bool char wchar short int float double long void".split()
+    C_FUNDAMENTAL += "struct union enum".split()
+    C_FUNDAMENTAL = set(C_FUNDAMENTAL)
+
+    C_MODIFIERS = "* & const constexpr static mutable".split()
+    C_MODIFIERS = set(C_MODIFIERS)
+
+    C_KEYWORDS = "extern virtual static explicit inline friend".split()
+    C_KEYWORDS = set(C_KEYWORDS)
+
+    SubTypedefs = {}  # TODO deprecate?
+    NAMESPACES = []
+    CLASSES = {}
+
+    def initextra(self):
+        self.typedefs = {}
+        self.typedefs_order = []
+        self.classes_order = []
+        self.namespaces = Resolver.NAMESPACES  # save all namespaces
+        self.stack = (
+            []
+        )  # full name stack, good idea to keep both stacks? (simple stack and full stack)
+        self._classes_brace_level = {}  # class name : level
+        self._forward_decls = []
+        self._template_typenames = []  # template<typename XXX>
+
+    def current_namespace(self):
+        return self.cur_namespace(True)
+
+    def cur_namespace(self, add_double_colon=False):
+        rtn = ""
+        i = 0
+        while i < len(self.nameSpaces):
+            rtn += self.nameSpaces[i]
+            if add_double_colon or i < len(self.nameSpaces) - 1:
+                rtn += "::"
+            i += 1
+        return rtn
+
+    def guess_ctypes_type(self, string):
+        pointers = string.count("*")
+        string = string.replace("*", "")
+
+        a = string.split()
+        if "unsigned" in a:
+            u = "u"
+        else:
+            u = ""
+        if "long" in a and "double" in a:
+            b = "longdouble"  # there is no ctypes.c_ulongdouble (this is a 64bit float?)
+        elif a.count("long") == 2 and "int" in a:
+            b = "%sint64" % u
+        elif a.count("long") == 2:
+            b = "%slonglong" % u
+        elif "long" in a:
+            b = "%slong" % u
+        elif "double" in a:
+            b = "double"  # no udouble in ctypes
+        elif "short" in a:
+            b = "%sshort" % u
+        elif "char" in a:
+            b = "%schar" % u
+        elif "wchar" in a:
+            b = "wchar"
+        elif "bool" in a:
+            b = "bool"
+        elif "float" in a:
+            b = "float"
+
+        elif "int" in a:
+            b = "%sint" % u
+        elif "int8" in a:
+            b = "int8"
+        elif "int16" in a:
+            b = "int16"
+        elif "int32" in a:
+            b = "int32"
+        elif "int64" in a:
+            b = "int64"
+
+        elif "uint" in a:
+            b = "uint"
+        elif "uint8" in a:
+            b = "uint8"
+        elif "uint16" in a:
+            b = "uint16"
+        elif "uint32" in a:
+            b = "uint32"
+        elif "uint64" in a:
+            b = "uint64"
+
+        elif "size_t" in a:
+            b = "size_t"
+        elif "void" in a:
+            b = "void_p"
+
+        elif string in ("struct", "union"):
+            b = "void_p"  # what should be done here? don't trust struct, it could be a class, no need to expose via ctypes
+        else:
+            b = "void_p"
+
+        if not pointers:
+            return "ctypes.c_%s" % b
+        else:
+            x = ""
+            for i in range(pointers):
+                x += "ctypes.POINTER("
+            x += "ctypes.c_%s" % b
+            x += ")" * pointers
+            return x
+
+    def _remove_modifiers(self, vtype):
+        return " ".join(x for x in vtype.split() if x not in self.C_MODIFIERS)
+
+    def _create_raw_type(self, vtype):
+        lt = vtype.find("<")
+        if lt != -1:
+            gt = vtype.rfind(">")
+            vtype = (
+                self._remove_modifiers(vtype[:lt])
+                + vtype[lt : gt + 1]
+                + self._remove_modifiers(vtype[gt + 1 :])
+            )
+        else:
+            vtype = self._remove_modifiers(vtype)
+
+        return vtype
+
+    def resolve_type(self, string, result):  # recursive
+        """
+        keeps track of useful things like: how many pointers, number of typedefs, is fundamental or a class, etc...
+        """
+        ## be careful with templates, what is inside <something*> can be a pointer but the overall type is not a pointer
+        ## these come before a template
+        s = string.split("<")[0].split()
+        result["constant"] += s.count("const")
+        result["constexpr"] += s.count("constexpr")
+        result["static"] += s.count("static")
+        result["mutable"] = "mutable" in s
+
+        ## these come after a template
+        s = string.split(">")[-1]
+        result["pointer"] += s.count("*")
+        result["reference"] += s.count("&")
+
+        x = string
+        alias = False
+        for a in self.C_MODIFIERS:
+            x = x.replace(a, "")
+        for y in x.split():
+            if y not in self.C_FUNDAMENTAL:
+                alias = y
+                break
+
+        # if alias == 'class':
+        #    result['class'] = result['name']    # forward decl of class
+        #    result['forward_decl'] = True
+        if alias == "__extension__":
+            result["fundamental_extension"] = True
+        elif alias:
+            if alias in result["aliases"]:
+                # already resolved
+                return
+            result["aliases"].append(alias)
+            if alias in C99_NONSTANDARD:
+                result["type"] = C99_NONSTANDARD[alias]
+                result["typedef"] = alias
+                result["typedefs"] += 1
+            elif alias in self.typedefs:
+                result["typedefs"] += 1
+                result["typedef"] = alias
+                self.resolve_type(self.typedefs[alias], result)
+            elif alias in self.classes:
+                klass = self.classes[alias]
+                result["fundamental"] = False
+                result["class"] = klass
+                result["unresolved"] = False
+            else:
+                used = None
+
+                # Search for in parents
+                if not used:
+                    parent = result["parent"]
+                    while parent:
+                        p_using = parent.get("using")
+                        if p_using:
+                            used = p_using.get(alias)
+                            if used:
+                                break
+                        lookup = getattr(parent, "_lookup_type", None)
+                        if lookup:
+                            used = lookup(alias)
+                            if used:
+                                break
+                        parent = parent["parent"]
+
+                if not used and self.using:
+                    # search for type in all enclosing namespaces
+                    # TODO: would be nice if namespaces were an object?
+                    for ns in _iter_ns_str_reversed(result.get("namespace", "")):
+                        nsalias = ns + alias
+                        used = self.using.get(nsalias)
+                        if used:
+                            break
+
+                if used:
+                    for i in ("enum", "type", "namespace", "ctypes_type", "raw_type"):
+                        if i in used:
+                            result[i] = used[i]
+                    result["unresolved"] = False
+        else:
+            result["fundamental"] = True
+            result["unresolved"] = False
+
+    def finalize_vars(self):
+        # for c in self.classes.values():
+        #    for var in c.get_all_properties(): var['parent'] = c['name']
+
+        ## RESOLVE ##
+        for var in CppVariable.Vars:
+            self.resolve_type(var["type"], var)
+            # if 'method' in var and var['method']['name'] ==  '_notifyCurrentCamera': print(var); assert 0
+
+        # then find concrete type and best guess ctypes type #
+        for var in CppVariable.Vars:
+            if not var["aliases"]:  # var['fundamental']:
+                var["ctypes_type"] = self.guess_ctypes_type(var["type"])
+            else:
+                var["unresolved"] = False  # below may test to True
+                if var["class"]:
+                    var["ctypes_type"] = "ctypes.c_void_p"
+                else:
+                    assert var["aliases"]
+                    tag = var["aliases"][0]
+
+                    klass = None
+                    nestedEnum = None
+                    nestedStruct = None
+                    nestedTypedef = None
+
+                    parent = var["parent"]
+                    while parent:
+                        nestedEnum = getattr(parent, "_public_enums", {}).get(tag)
+                        if nestedEnum:
+                            break
+                        nestedTypedef = getattr(parent, "_public_typedefs", {}).get(tag)
+                        if nestedTypedef:
+                            break
+                        parent = parent["parent"]
+
+                    if "<" in tag:  # should also contain '>'
+                        var["template"] = tag  # do not resolve templates
+                        var["ctypes_type"] = "ctypes.c_void_p"
+                        var["unresolved"] = True
+
+                    elif nestedEnum:
+                        enum = nestedEnum
+                        etype = enum.get("type")
+                        if etype is int:
+                            var["ctypes_type"] = "ctypes.c_int"
+                            var["raw_type"] = "int"
+
+                        elif etype is str:
+                            var["ctypes_type"] = "ctypes.c_char_p"
+                            var["raw_type"] = "char*"
+
+                        if "method" in var:
+                            var["enum"] = var["method"]["path"] + "::" + enum["name"]
+                        else:
+                            var["enum"] = enum["name"]
+                        var["fundamental"] = True
+
+                    elif nestedStruct:
+                        var["ctypes_type"] = "ctypes.c_void_p"
+                        var["raw_type"] = (
+                            var["method"]["path"] + "::" + nestedStruct["type"]
+                        )
+                        var["fundamental"] = False
+
+                    elif nestedTypedef:
+                        var["fundamental"] = is_fundamental(nestedTypedef)
+                        if not var["fundamental"] and "method" in var:
+                            var["raw_type"] = var["method"]["path"] + "::" + tag
+
+                    else:
+                        _tag = tag
+                        if "::" in tag and tag.split("::")[0] in self.namespaces:
+                            tag = tag.split("::")[-1]
+                        con = self.concrete_typedef(_tag)
+                        if con:
+                            var["concrete_type"] = con
+                            var["ctypes_type"] = self.guess_ctypes_type(
+                                var["concrete_type"]
+                            )
+
+                        elif tag in self._forward_decls:
+                            var["forward_declared"] = tag
+                            var["ctypes_type"] = "ctypes.c_void_p"
+
+                        elif tag in self.global_enums:
+                            enum = self.global_enums[tag]
+                            enum_type = enum.get("type")
+                            if enum_type is int:
+                                var["ctypes_type"] = "ctypes.c_int"
+                                var["raw_type"] = "int"
+                            elif enum_type is str:
+                                var["ctypes_type"] = "ctypes.c_char_p"
+                                var["raw_type"] = "char*"
+                            var["enum"] = enum["namespace"] + enum["name"]
+                            var["fundamental"] = True
+
+                        elif var["parent"] and var["unresolved"]:
+                            warning_print("WARN unresolved %s", _tag)
+                            var["ctypes_type"] = "ctypes.c_void_p"
+                            var["unresolved"] = True
+
+                        elif tag.count("::") == 1:
+                            trace_print("trying to find nested something in", tag)
+                            a = tag.split("::")[0]
+                            b = tag.split("::")[-1]
+                            if (
+                                a in self.classes
+                            ):  # a::b is most likely something nested in a class
+                                klass = self.classes[a]
+                                if b in klass._public_enums:
+                                    trace_print("...found nested enum", b)
+                                    enum = klass._public_enums[b]
+                                    etype = enum.get("type")
+                                    if etype is int:
+                                        var["ctypes_type"] = "ctypes.c_int"
+                                        var["raw_type"] = "int"
+                                    elif etype is str:
+                                        var["ctypes_type"] = "ctypes.c_char_p"
+                                        var["raw_type"] = "char*"
+                                    try:
+                                        if "method" in var:
+                                            var["enum"] = (
+                                                var["method"]["path"]
+                                                + "::"
+                                                + enum["name"]
+                                            )
+                                        else:  # class property
+                                            var["unresolved"] = True
+                                    except:
+                                        var["unresolved"] = True
+
+                                    var["fundamental"] = True
+
+                                else:
+                                    var["unresolved"] = True  # TODO klass._public_xxx
+
+                            elif (
+                                a in self.namespaces
+                            ):  # a::b can also be a nested namespace
+                                if b in self.global_enums:
+                                    enum = self.global_enums[b]
+                                    trace_print(enum)
+                                trace_print(var)
+                                assert 0
+
+                            elif (
+                                b in self.global_enums
+                            ):  # falling back, this is a big ugly
+                                enum = self.global_enums[b]
+                                assert a in enum["namespace"].split("::")
+                                etype = enum.get("type")
+                                if etype is int:
+                                    var["ctypes_type"] = "ctypes.c_int"
+                                    var["raw_type"] = "int"
+                                elif etype is str:
+                                    var["ctypes_type"] = "ctypes.c_char_p"
+                                    var["raw_type"] = "char*"
+                                var["fundamental"] = True
+
+                            else:  # boost::gets::crazy
+                                trace_print("NAMESPACES", self.namespaces)
+                                trace_print(a, b)
+                                trace_print("---- boost gets crazy ----")
+                                var["ctypes_type"] = "ctypes.c_void_p"
+                                var["unresolved"] = True
+
+                        elif "namespace" in var and self.concrete_typedef(
+                            var["namespace"] + tag
+                        ):
+                            # print( 'TRYING WITH NS', var['namespace'] )
+                            con = self.concrete_typedef(var["namespace"] + tag)
+                            if con:
+                                var["typedef"] = var["namespace"] + tag
+                                var["type"] = con
+                                if "struct" in con.split():
+                                    var["raw_type"] = var["typedef"]
+                                    var["ctypes_type"] = "ctypes.c_void_p"
+                                else:
+                                    self.resolve_type(var["type"], var)
+                                    var["ctypes_type"] = self.guess_ctypes_type(
+                                        var["type"]
+                                    )
+
+                        elif "::" in var:
+                            var["ctypes_type"] = "ctypes.c_void_p"
+                            var["unresolved"] = True
+
+                        elif tag in self.SubTypedefs:  # TODO remove SubTypedefs
+                            if (
+                                "property_of_class" in var
+                                or "property_of_struct" in var
+                            ):
+                                trace_print(
+                                    "class:", self.SubTypedefs[tag], "tag:", tag
+                                )
+                                var["typedef"] = self.SubTypedefs[tag]  # class name
+                                var["ctypes_type"] = "ctypes.c_void_p"
+                            else:
+                                trace_print("WARN-this should almost never happen!")
+                                trace_print(var)
+                                trace_print("-" * 80)
+                                var["unresolved"] = True
+
+                        elif tag in self._template_typenames:
+                            var["typename"] = tag
+                            var["ctypes_type"] = "ctypes.c_void_p"
+                            var[
+                                "unresolved"
+                            ] = True  # TODO, how to deal with templates?
+
+                        elif tag.startswith(
+                            "_"
+                        ):  # assume starting with underscore is not important for wrapping
+                            warning_print("WARN unresolved %s", _tag)
+                            var["ctypes_type"] = "ctypes.c_void_p"
+                            var["unresolved"] = True
+
+                        else:
+                            trace_print("WARN: unknown type", var)
+                            assert (
+                                "property_of_class" in var or "property_of_struct"
+                            )  # only allow this case
+                            var["unresolved"] = True
+
+                    ## if not resolved and is a method param, not going to wrap these methods  ##
+                    if var["unresolved"] and "method" in var:
+                        var["method"]["unresolved_parameters"] = True
+
+        # create stripped raw_type #
+        for var in CppVariable.Vars:
+            if "raw_type" not in var:
+                var["raw_type"] = self._create_raw_type(var["type"])
+
+                # if 'AutoConstantEntry' in var['raw_type']: print(var); assert 0
+                if var["class"]:
+                    if "::" not in var["raw_type"]:
+                        if not var["class"]["parent"]:
+                            var["raw_type"] = (
+                                var["class"]["namespace"] + "::" + var["raw_type"]
+                            )
+                        else:
+                            parent = var["class"]["parent"]
+                            var["raw_type"] = (
+                                parent["namespace"]
+                                + "::"
+                                + var["class"]["name"]
+                                + "::"
+                                + var["raw_type"]
+                            )
+
+                    elif (
+                        "::" in var["raw_type"]
+                        and var["raw_type"].split("::")[0] not in self.namespaces
+                    ):
+                        var["raw_type"] = (
+                            var["class"]["namespace"] + "::" + var["raw_type"]
+                        )
+                    else:
+                        var["unresolved"] = True
+
+                elif "forward_declared" in var and "namespace" in var:
+                    if "::" not in var["raw_type"]:
+                        var["raw_type"] = var["namespace"] + var["raw_type"]
+                    elif (
+                        "::" in var["raw_type"]
+                        and var["raw_type"].split("::")[0] in self.namespaces
+                    ):
+                        pass
+                    else:
+                        trace_print("-" * 80)
+                        trace_print(var)
+                        raise NotImplementedError
+
+            ## need full name space for classes in raw type ##
+            if var["raw_type"].startswith("::"):
+                # print(var)
+                # print('NAMESPACE', var['class']['namespace'])
+                # print( 'PARENT NS', var['class']['parent']['namespace'] )
+                # assert 0
+                var["unresolved"] = True
+                if "method" in var:
+                    var["method"]["unresolved_parameters"] = True
+                # var['raw_type'] = var['raw_type'][2:]
+
+        # Take care of #defines and #pragmas etc
+        trace_print("Processing precomp_macro_buf: %s" % self._precomp_macro_buf)
+        for m, location in self._precomp_macro_buf:
+            macro = m.replace("<CppHeaderParser_newline_temp_replacement>\\n", "\n")
+            ml = macro.lower()
+            try:
+                if ml.startswith("#define"):
+                    trace_print("Adding #define %s" % macro)
+                    define = CppDefine(macro, location)
+                    self.defines.append(define["value"])
+                    self.defines_detail.append(define)
+                elif ml.startswith("#pragma"):
+                    trace_print("Adding #pragma %s" % macro)
+                    pragma = CppPragma(macro, location)
+                    self.pragmas_detail.append(pragma)
+                    self.pragmas.append(pragma["value"])
+                elif ml.startswith("#include"):
+                    trace_print("Adding #include %s" % macro)
+                    include = CppInclude(macro, location)
+                    self.includes.append(include["value"])
+                    self.includes_detail.append(include)
+                else:
+                    debug_print("Cant detect what to do with precomp macro '%s'", macro)
+            except:
+                pass
+        self._precomp_macro_buf = None
+
+    def concrete_typedef(self, key):
+        if key not in self.typedefs:
+            # print( 'FAILED typedef', key )
+            return None
+        while key in self.typedefs:
+            prev = key
+            key = self.typedefs[key]
+            if "<" in key or ">" in key:
+                return prev  # stop at template
+            if key.startswith("std::"):
+                return key  # stop at std lib
+        return key
+
+
+class _CppHeader(Resolver):
+    def finalize(self):
+        self.finalize_vars()
+        # finalize classes and method returns types
+        for cls in list(self.classes.values()):
+            for meth in cls.get_all_methods():
+                if meth["pure_virtual"]:
+                    cls["abstract"] = True
+
+                # hack
+                rtnType = {
+                    "aliases": [],
+                    "parent": cls,
+                    "unresolved": True,
+                    "constant": 0,
+                    "constexpr": 0,
+                    "static": 0,
+                    "pointer": 0,
+                    "reference": 0,
+                    "typedefs": 0,
+                }
+                self.resolve_type(meth["rtnType"], rtnType)
+                if not rtnType["unresolved"]:
+                    if "enum" in rtnType:
+                        meth["rtnType"] = rtnType["enum"]
+                    elif "raw_type" in rtnType:
+                        meth["rtnType"] = rtnType["raw_type"]
+
+                # TODO: all of this needs to die and be replaced by CppVariable
+
+                if (
+                    not meth["returns_fundamental"]
+                    and meth["returns"] in C99_NONSTANDARD
+                ):
+                    meth["returns"] = C99_NONSTANDARD[meth["returns"]]
+                    meth["returns_fundamental"] = True
+
+                elif not meth["returns_fundamental"]:  # describe the return type
+                    con = None
+                    if cls["namespace"] and "::" not in meth["returns"]:
+                        con = self.concrete_typedef(
+                            cls["namespace"] + "::" + meth["returns"]
+                        )
+                    else:
+                        con = self.concrete_typedef(meth["returns"])
+
+                    if con:
+                        meth["returns_concrete"] = con
+                        meth["returns_fundamental"] = is_fundamental(con)
+
+                    elif meth["returns"] in self.classes:
+                        trace_print("meth returns class:", meth["returns"])
+                        meth["returns_class"] = True
+
+                    elif meth["returns"] in self.SubTypedefs:
+                        meth["returns_class"] = True
+                        meth["returns_nested"] = self.SubTypedefs[meth["returns"]]
+
+                    elif meth["returns"] in cls._public_enums:
+                        enum = cls._public_enums[meth["returns"]]
+                        meth["returns_enum"] = enum.get("type")
+                        meth["returns_fundamental"] = True
+                        if enum.get("type") == int:
+                            meth["returns"] = "int"
+                        else:
+                            meth["returns"] = "char*"
+
+                    elif meth["returns"] in self.global_enums:
+                        enum = self.global_enums[meth["returns"]]
+                        meth["returns_enum"] = enum.get("type")
+                        meth["returns_fundamental"] = True
+                        if enum.get("type") == int:
+                            meth["returns"] = "int"
+                        else:
+                            meth["returns"] = "char*"
+
+                    elif meth["returns"].count("::") == 1:
+                        trace_print(meth)
+                        a, b = meth["returns"].split("::")
+                        if a in self.namespaces:
+                            if b in self.classes:
+                                klass = self.classes[b]
+                                meth["returns_class"] = a + "::" + b
+                            elif "<" in b and ">" in b:
+                                meth["returns_unknown"] = True
+                            elif b in self.global_enums:
+                                enum = self.global_enums[b]
+                                meth["returns_enum"] = enum.get("type")
+                                meth["returns_fundamental"] = True
+                                if enum.get("type") == int:
+                                    meth["returns"] = "int"
+                                else:
+                                    meth["returns"] = "char*"
+
+                            else:
+                                trace_print(a, b)
+                                trace_print(meth)
+                                meth["returns_unknown"] = True  # +++
+
+                        elif a in self.classes:
+                            klass = self.classes[a]
+                            if b in klass._public_enums:
+                                trace_print("...found nested enum", b)
+                                enum = klass._public_enums[b]
+                                meth["returns_enum"] = enum.get("type")
+                                meth["returns_fundamental"] = True
+                                if enum.get("type") == int:
+                                    meth["returns"] = "int"
+                                else:
+                                    meth["returns"] = "char*"
+
+                            elif b in klass._public_forward_declares:
+                                meth["returns_class"] = True
+
+                            elif b in klass._public_typedefs:
+                                typedef = klass._public_typedefs[b]
+                                meth["returns_fundamental"] = is_fundamental(typedef)
+
+                            else:
+                                trace_print(
+                                    meth
+                                )  # should be a nested class, TODO fix me.
+                                meth["returns_unknown"] = True
+
+                    elif "::" in meth["returns"]:
+                        trace_print("TODO namespace or extra nested return:", meth)
+                        meth["returns_unknown"] = True
+                    else:
+                        trace_print(
+                            "WARN: UNKNOWN RETURN", meth["name"], meth["returns"]
+                        )
+                        meth["returns_unknown"] = True
+
+                if meth["returns"].startswith(":: "):
+                    meth["returns"] = meth["returns"].replace(":: ", "::")
+
+        for cls in list(self.classes.values()):
+            methnames = cls.get_all_method_names()
+            pvm = cls.get_all_pure_virtual_methods()
+
+            for d in cls["inherits"]:
+                c = d["class"]
+                a = d["access"]  # do not depend on this to be 'public'
+                trace_print("PARENT CLASS:", c)
+                if c not in self.classes:
+                    trace_print("WARN: parent class not found")
+                if c in self.classes and self.classes[c]["abstract"]:
+                    p = self.classes[c]
+                    for meth in p.get_all_methods():  # p["methods"]["public"]:
+                        trace_print(
+                            "\t\tmeth",
+                            meth["name"],
+                            "pure virtual",
+                            meth["pure_virtual"],
+                        )
+                        if meth["pure_virtual"] and meth["name"] not in methnames:
+                            cls["abstract"] = True
+                            break
+
+    _method_type_defaults = {
+        n: False
+        for n in "defined deleted pure_virtual operator constructor destructor extern template virtual static explicit inline friend returns returns_pointer returns_fundamental returns_class default".split()
+    }
+
+    def parse_method_type(self, stack):
+        trace_print("meth type info", stack)
+        info = {
+            "debug": " ".join(stack)
+            .replace(" :: ", "::")
+            .replace(" < ", "<")
+            .replace(" > ", "> ")
+            .replace(" >", ">")
+            .replace(">>", "> >")
+            .replace(">>", "> >"),
+            "class": None,
+            "namespace": self.cur_namespace(add_double_colon=True),
+        }
+
+        info.update(self._method_type_defaults)
+
+        header = stack[: stack.index("(")]
+        header = " ".join(header)
+        header = header.replace(" :: ", "::")
+        header = header.replace(" < ", "<")
+        header = header.replace(" > ", "> ")
+        header = header.replace("default ", "default")
+        header = header.strip()
+
+        if stack[-1] == "{":
+            info["defined"] = True
+            self._discard_contents("{", "}")
+            self.braceHandled = True
+        elif stack[-1] == ";":
+            info["defined"] = False
+        elif stack[-1] == ":":
+            info["defined"] = True
+            self._discard_ctor_initializer()
+            self.braceHandled = True
+        else:
+            assert 0
+
+        if len(stack) > 3 and stack[-1] == ";" and stack[-3] == "=":
+            if stack[-2] == "0":
+                info["pure_virtual"] = True
+            elif stack[-2] == "delete":
+                info["deleted"] = True
+
+        r = header.split()
+        name = None
+        if "operator" in stack:  # rare case op overload defined outside of class
+            op = stack[stack.index("operator") + 1 : stack.index("(")]
+            op = "".join(op)
+            if not op:
+                if " ".join(["operator", "(", ")", "("]) in " ".join(stack):
+                    op = "()"
+                else:
+                    trace_print("Error parsing operator")
+                    return None
+
+            info["operator"] = op
+            name = "operator" + op
+            a = stack[: stack.index("operator")]
+
+        elif r:
+            name = r[-1]
+            a = r[:-1]  # strip name
+
+        if name is None:
+            return None
+        # if name.startswith('~'): name = name[1:]
+
+        while a and a[0] == "}":  # strip - can have multiple } }
+            a = a[1:]
+
+        if "::" in name:
+            # klass,name = name.split('::')    # methods can be defined outside of class
+            klass = name[: name.rindex("::")]
+            name = name.split("::")[-1]
+            info["class"] = klass
+            if klass in self.classes and not self.curClass:
+                # Class function defined outside the class
+                return None
+        #    info['name'] = name
+        # else: info['name'] = name
+
+        if name.startswith("~"):
+            info["destructor"] = True
+            if "default;" in stack:
+                info["defined"] = True
+                info["default"] = True
+            name = name[1:]
+        elif not a or (name == self.curClass and len(self.curClass)):
+            info["constructor"] = True
+            if "default;" in stack:
+                info["defined"] = True
+                info["default"] = True
+
+        info["name"] = name
+
+        for tag in self.C_KEYWORDS:
+            if tag in a:
+                info[tag] = True
+                a.remove(tag)  # inplace
+        if "template" in a:
+            a.remove("template")
+            b = " ".join(a)
+            if ">" in b:
+                info["template"] = b[: b.index(">") + 1]
+                info["returns"] = b[
+                    b.index(">") + 1 :
+                ]  # find return type, could be incorrect... TODO
+                if "<typename" in info["template"].split():
+                    typname = info["template"].split()[-1]
+                    typname = typname[:-1]  # strip '>'
+                    if typname not in self._template_typenames:
+                        self._template_typenames.append(typname)
+            else:
+                info["returns"] = " ".join(a)
+        else:
+            info["returns"] = " ".join(a)
+        info["returns"] = info["returns"].replace(" <", "<").strip()
+
+        ## be careful with templates, do not count pointers inside template
+        info["returns_pointer"] = info["returns"].split(">")[-1].count("*")
+        if info["returns_pointer"]:
+            info["returns"] = info["returns"].replace("*", "").strip()
+
+        info["returns_reference"] = "&" in info["returns"]
+        if info["returns"]:
+            info["returns"] = info["returns"].replace("&", "").strip()
+
+        a = []
+        for b in info["returns"].split():
+            if b == "__const__":
+                info["returns_const"] = True
+            elif b == "const":
+                info["returns_const"] = True
+            else:
+                a.append(b)
+        info["returns"] = " ".join(a)
+
+        info["returns_fundamental"] = is_fundamental(info["returns"])
+        return info
+
+    def _evaluate_method_stack(self):
+        """Create a method out of the name stack"""
+
+        info = self.parse_method_type(self.stack)
+        if info:
+            if (
+                info["class"] and info["class"] in self.classes
+            ):  # case where methods are defined outside of class
+                newMethod = CppMethod(
+                    self.nameStack,
+                    info["name"],
+                    info,
+                    self.curTemplate,
+                    self._get_stmt_doxygen(),
+                    self._get_location(self.nameStack),
+                )
+                klass = self.classes[info["class"]]
+                klass["methods"]["public"].append(newMethod)
+                newMethod["parent"] = klass
+                if klass["namespace"]:
+                    newMethod["path"] = klass["namespace"] + "::" + klass["name"]
+                else:
+                    newMethod["path"] = klass["name"]
+
+            elif self.curClass:  # normal case
+                newMethod = CppMethod(
+                    self.nameStack,
+                    self.curClass,
+                    info,
+                    self.curTemplate,
+                    self._get_stmt_doxygen(),
+                    self._get_location(self.nameStack),
+                )
+                klass = self.classes[self.curClass]
+                klass["methods"][self.curAccessSpecifier].append(newMethod)
+                newMethod["parent"] = klass
+                if klass["namespace"]:
+                    newMethod["path"] = klass["namespace"] + "::" + klass["name"]
+                else:
+                    newMethod["path"] = klass["name"]
+            else:  # non class functions
+                debug_print("FREE FUNCTION")
+                newMethod = CppMethod(
+                    self.nameStack,
+                    None,
+                    info,
+                    self.curTemplate,
+                    self._get_stmt_doxygen(),
+                    self._get_location(self.nameStack),
+                )
+                newMethod["parent"] = None
+                self.functions.append(newMethod)
+            global parseHistory
+            parseHistory.append(
+                {
+                    "braceDepth": self.braceDepth,
+                    "item_type": "method",
+                    "item": newMethod,
+                }
+            )
+        else:
+            trace_print("free function?", self.nameStack)
+
+        self.stack = []
+        self.stmtTokens = []
+
+    def _parse_typedef(self, stack, namespace=""):
+        if not stack or "typedef" not in stack:
+            return
+        stack = list(stack)  # copy just to be safe
+        if stack[-1] == ";":
+            stack.pop()
+
+        while stack and stack[-1].isdigit():
+            stack.pop()  # throw away array size for now
+
+        idx = stack.index("typedef")
+        if stack[-1] == "]":
+            try:
+                name = namespace + "".join(stack[-4:])
+                # Strip off the array part so the rest of the parsing is better
+                stack = stack[:-3]
+            except:
+                name = namespace + stack[-1]
+        else:
+            name = namespace + stack[-1]
+        s = ""
+        for a in stack[idx + 1 : -1]:
+            if a == "{":
+                break
+            if not s or s[-1] in ":<>" or a in ":<>":
+                s += a  # keep compact
+            else:
+                s += " " + a  # spacing
+
+        r = {"name": name, "raw": s, "type": s}
+        if not is_fundamental(s):
+            if "struct" in s.split():
+                pass  # TODO is this right? "struct ns::something"
+            elif "::" not in s:
+                s = (
+                    namespace + s
+                )  # only add the current name space if no namespace given
+            r["type"] = s
+        if s:
+            return r
+
+    def _evaluate_typedef(self):
+        ns = self.cur_namespace(add_double_colon=True)
+        res = self._parse_typedef(self.stack, ns)
+        if res:
+            name = res["name"]
+            self.typedefs[name] = res["type"]
+            if name not in self.typedefs_order:
+                self.typedefs_order.append(name)
+
+    def _evaluate_property_stack(self, clearStack=True, addToVar=None):
+        """Create a Property out of the name stack"""
+        global parseHistory
+        debug_print("trace")
+        if self.nameStack[0] == "typedef":
+            assert self.stack and self.stack[-1] == ";"
+            if self.curClass:
+                typedef = self._parse_typedef(self.stack)
+                name = typedef["name"]
+                klass = self.classes[self.curClass]
+                klass["typedefs"][self.curAccessSpecifier].append(name)
+                if self.curAccessSpecifier == "public":
+                    klass._public_typedefs[name] = typedef["type"]
+                Resolver.SubTypedefs[name] = self.curClass
+            else:
+                assert 0
+        elif self.curClass:
+            if len(self.nameStack) == 1:
+                # See if we can de anonymize the type
+                filteredParseHistory = [
+                    h for h in parseHistory if h["braceDepth"] == self.braceDepth
+                ]
+                if (
+                    len(filteredParseHistory)
+                    and filteredParseHistory[-1]["item_type"] == "class"
+                ):
+                    self.nameStack.insert(0, filteredParseHistory[-1]["item"]["name"])
+                    debug_print(
+                        "DEANONYMOIZING %s to type '%s'",
+                        self.nameStack[1],
+                        self.nameStack[0],
+                    )
+            if "," in self.nameStack:  # Maybe we have a variable list
+                # Figure out what part is the variable separator but remember templates of function pointer
+                # First find left most comma outside of a > and )
+                leftMostComma = 0
+                for i in range(0, len(self.nameStack)):
+                    name = self.nameStack[i]
+                    if name in (">", ")"):
+                        leftMostComma = 0
+                    if leftMostComma == 0 and name == ",":
+                        leftMostComma = i
+                # Is it really a list of variables?
+                if leftMostComma != 0:
+                    trace_print(
+                        "Multiple variables for namestack in %s.  Separating processing"
+                        % self.nameStack
+                    )
+                    orig_nameStack = self.nameStack[:]
+
+                    type_nameStack = orig_nameStack[: leftMostComma - 1]
+                    for name in orig_nameStack[leftMostComma - 1 :: 2]:
+                        self.nameStack = type_nameStack + [name]
+                        self._evaluate_property_stack(
+                            clearStack=False, addToVar=addToVar
+                        )
+                    return
+
+            newVar = CppVariable(
+                self.nameStack,
+                self._get_stmt_doxygen(),
+                self._get_location(self.nameStack),
+            )
+            newVar["namespace"] = self.current_namespace()
+            if self.curClass:
+                klass = self.classes[self.curClass]
+                klass["properties"][self.curAccessSpecifier].append(newVar)
+                newVar["property_of_class"] = klass["name"]
+                newVar["parent"] = klass
+            parseHistory.append(
+                {"braceDepth": self.braceDepth, "item_type": "variable", "item": newVar}
+            )
+            if addToVar:
+                newVar.update(addToVar)
+        else:
+            debug_print("Found Global variable")
+            newVar = CppVariable(
+                self.nameStack,
+                self._get_stmt_doxygen(),
+                self._get_location(self.nameStack),
+            )
+            if addToVar:
+                newVar.update(addToVar)
+            self.variables.append(newVar)
+
+        if clearStack:
+            self.stack = []  # CLEAR STACK
+            self.stmtTokens = []
+
+    def _evaluate_class_stack(self):
+        """Create a Class out of the name stack (but not its parts)"""
+        # dont support sub classes today
+        # print( 'eval class stack', self.nameStack )
+        parent = self.curClass
+        if parent:
+            debug_print("found nested subclass")
+            self.accessSpecifierStack.append(self.curAccessSpecifier)
+
+        # When dealing with typedefed structs, get rid of typedef keyword to handle later on
+        if self.nameStack[0] == "typedef":
+            del self.nameStack[0]
+
+        if len(self.nameStack) == 1:
+            if self.nameStack[0] == "struct":
+                self.anon_struct_counter += 1
+                # We cant handle more than 1 anonymous struct, so name them uniquely
+                self.nameStack.append("anon-struct-%d" % self.anon_struct_counter)
+            elif self.nameStack[0] == "union":
+                self.anon_union_counter += 1
+                # We cant handle more than 1 anonymous union, so name them uniquely
+                self.nameStack.append("anon-union-%d" % self.anon_union_counter)
+            elif self.nameStack[0] == "class":
+                self.anon_class_counter += 1
+                # We cant handle more than 1 anonymous class, so name them uniquely
+                self.nameStack.append("anon-class-%d" % self.anon_class_counter)
+
+        if self.nameStack[0] == "class":
+            self.curAccessSpecifier = "private"
+        else:  # struct/union
+            self.curAccessSpecifier = "public"
+        debug_print(
+            "curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier
+        )
+        if self.nameStack[0] == "union":
+            newClass = CppUnion(
+                self.nameStack,
+                self._get_stmt_doxygen(),
+                self._get_location(self.nameStack),
+            )
+        else:
+            newClass = CppClass(
+                self.nameStack,
+                self.curTemplate,
+                self._get_stmt_doxygen(),
+                self._get_location(self.nameStack),
+                self.curAccessSpecifier,
+            )
+        newClass["declaration_method"] = self.nameStack[0]
+        self.classes_order.append(newClass)  # good idea to save ordering
+        self.stack = []  # fixes if class declared with ';' in closing brace
+        self.stmtTokens = []
+        classKey = newClass["name"]
+
+        if parent:
+            newClass["namespace"] = self.classes[parent]["namespace"] + "::" + parent
+            newClass["parent"] = self.classes[parent]
+            newClass["access_in_parent"] = self.accessSpecifierStack[-1]
+            self.classes[parent]["nested_classes"].append(newClass)
+            ## supports nested classes with the same name ##
+            self.curClass = key = parent + "::" + classKey
+            self._classes_brace_level[key] = self.braceDepth
+
+        elif newClass["parent"]:  # nested class defined outside of parent.  A::B {...}
+            pcls = newClass["parent"]
+            parent = pcls["name"]
+            newClass["namespace"] = pcls["namespace"] + "::" + parent
+            pcls["nested_classes"].append(newClass)
+            ## supports nested classes with the same name ##
+            self.curClass = key = parent + "::" + classKey
+            self._classes_brace_level[key] = self.braceDepth
+
+        else:
+            newClass["namespace"] = self.cur_namespace()
+            self.curClass = key = classKey
+            self._classes_brace_level[classKey] = self.braceDepth
+
+        if not key.endswith("::") and not key.endswith(" ") and len(key) != 0:
+            if key in self.classes:
+                trace_print("ERROR name collision:", key)
+                self.classes[key].show()
+                trace_print("-" * 80)
+                newClass.show()
+                assert key not in self.classes  # namespace collision
+        self.classes[key] = newClass
+        global parseHistory
+        parseHistory.append(
+            {"braceDepth": self.braceDepth, "item_type": "class", "item": newClass}
+        )
+
+    def evalute_forward_decl(self):
+        trace_print("FORWARD DECL", self.nameStack)
+        assert self.nameStack[0] in ("class", "struct")
+        name = self.nameStack[-1]
+        if self.curClass:
+            klass = self.classes[self.curClass]
+            klass["forward_declares"][self.curAccessSpecifier].append(name)
+            if self.curAccessSpecifier == "public":
+                klass._public_forward_declares.append(name)
+        else:
+            self._forward_decls.append(name)
+
+
+# fmt: off
+_namestack_append_tokens = {
+    "{",
+    "}",
+    "[",
+    "]",
+    "=",
+    ",",
+    "\\",
+    "DIVIDE",
+    "|",
+    "%",
+    "^",
+    "!",
+    "NUMBER",
+    "FLOAT_NUMBER",
+    "-",
+    "+",
+    "STRING_LITERAL",
+    "ELLIPSIS",
+    "DBL_COLON",
+    "SHIFT_LEFT",
+}
+
+_namestack_pass_tokens = {
+    "'",
+    "." # preserve behaviour and eat individual fullstops
+}
+
+_namestack_str_tokens = {
+    "NAME",
+    "&",
+    "*",
+    "<",
+    ">",
+    "CHAR_LITERAL"
+}
+# fmt: on
+
+
+class CppHeader(_CppHeader):
+    """Parsed C++ class header"""
+
+    IGNORE_NAMES = "__extension__".split()
+
+    def show(self):
+        for className in list(self.classes.keys()):
+            self.classes[className].show()
+
+    def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
+        """Create the parsed C++ header file parse tree
+
+        headerFileName - Name of the file to parse OR actual file contents (depends on argType)
+        argType - Indicates how to interpret headerFileName as a file string or file name
+        kwargs - Supports the following keywords
+        """
+        ## reset global state ##
+        CppVariable.Vars = []
+
+        if argType == "file":
+            self.headerFileName = os.path.expandvars(headerFileName)
+            self.mainClass = os.path.split(self.headerFileName)[1][:-2]
+            headerFileStr = ""
+        elif argType == "string":
+            self.headerFileName = ""
+            self.mainClass = "???"
+            headerFileStr = headerFileName
+        else:
+            raise Exception("Arg type must be either file or string")
+        self.curClass = ""
+
+        # nested classes have parent::nested, but no extra namespace,
+        # this keeps the API compatible, TODO proper namespace for everything.
+        Resolver.CLASSES = {}
+
+        #: Dictionary of classes found in the header file. The key is the name
+        #: of the class, value is :class:`.CppClass`
+        self.classes = Resolver.CLASSES
+
+        #: List of free functions as :class:`.CppMethod`
+        self.functions = []
+
+        #: List of #pragma directives found as strings
+        self.pragmas = []
+
+        #: List of pragmas with location information
+        self.pragmas_detail = []
+
+        #: List of #define directives found
+        self.defines = []
+
+        #: List of #define directives found, with location information
+        self.defines_detail = []
+
+        #: List of #include directives found
+        self.includes = []
+
+        #: List of #include directives found with location information
+        self.includes_detail = []
+
+        #: Filenames encountered in #line directives while parsing
+        self.headerFileNames = []
+
+        self._precomp_macro_buf = (
+            []
+        )  # for internal purposes, will end up filling out pragmras and defines at the end
+
+        #: List of enums in this header as :class:`.CppEnum`
+        self.enums = []
+
+        #: List of variables in this header as :class:`.CppVariable`
+        self.variables = []
+        self.global_enums = {}
+        self.nameStack = []
+
+        #: Namespaces in this header
+        self.nameSpaces = []
+        self.curAccessSpecifier = "private"  # private is default
+        self.curTemplate = None
+        self.accessSpecifierStack = []
+        debug_print(
+            "curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier
+        )
+        self.initextra()
+        # Old namestacks for a given level
+        self.nameStackHistory = []
+        self.anon_struct_counter = 0
+        self.anon_union_counter = 0
+        self.anon_class_counter = 0
+
+        #: Using directives in this header outside of class scope: key is
+        #: full name for lookup, value is :class:`.CppVariable`
+        self.using = {}
+
+        if len(self.headerFileName):
+            fd = io.open(self.headerFileName, "r", encoding=encoding)
+            headerFileStr = "".join(fd.readlines())
+            fd.close()
+
+        # Make sure supportedAccessSpecifier are sane
+        for i in range(0, len(supportedAccessSpecifier)):
+            if " " not in supportedAccessSpecifier[i]:
+                continue
+            supportedAccessSpecifier[i] = re.sub(
+                "[ ]+", " ", supportedAccessSpecifier[i]
+            ).strip()
+
+        # Change multi line #defines and expressions to single lines maintaining line nubmers
+        # Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements
+        matches = re.findall(r"(?m)^(?:.*\\\r?\n)+.*$", headerFileStr)
+        is_define = re.compile(r"[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]")
+        for m in matches:
+            # Keep the newlines so that linecount doesnt break
+            num_newlines = len([a for a in m if a == "\n"])
+            if is_define.match(m):
+                new_m = m.replace("\n", "<CppHeaderParser_newline_temp_replacement>\\n")
+            else:
+                # Just expression taking up multiple lines, make it take 1 line for easier parsing
+                new_m = m.replace("\\\n", " ")
+            if num_newlines > 0:
+                new_m += "\n" * (num_newlines)
+            headerFileStr = headerFileStr.replace(m, new_m)
+
+        # Filter out Extern "C" statements.  These are order dependent
+        matches = re.findall(
+            re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr
+        )
+        for m in matches:
+            # Keep the newlines so that linecount doesnt break
+            num_newlines = len([a for a in m if a == "\n"])
+            headerFileStr = headerFileStr.replace(m, "\n" * num_newlines)
+        headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr)
+
+        # Filter out any ignore symbols that end with "()" to account for #define magic functions
+        for ignore in ignoreSymbols:
+            if not ignore.endswith("()"):
+                continue
+            while True:
+                locStart = headerFileStr.find(ignore[:-1])
+                if locStart == -1:
+                    break
+                locEnd = None
+                # Now walk till we find the last paren and account for sub parens
+                parenCount = 1
+                inQuotes = False
+                for i in range(locStart + len(ignore) - 1, len(headerFileStr)):
+                    c = headerFileStr[i]
+                    if not inQuotes:
+                        if c == "(":
+                            parenCount += 1
+                        elif c == ")":
+                            parenCount -= 1
+                        elif c == '"':
+                            inQuotes = True
+                        if parenCount == 0:
+                            locEnd = i + 1
+                            break
+                    else:
+                        if c == '"' and headerFileStr[i - 1] != "\\":
+                            inQuotes = False
+
+                if locEnd:
+                    # Strip it out but keep the linecount the same so line numbers are right
+                    match_str = headerFileStr[locStart:locEnd]
+                    debug_print("Striping out '%s'", match_str)
+                    num_newlines = len([a for a in match_str if a == "\n"])
+                    headerFileStr = headerFileStr.replace(
+                        headerFileStr[locStart:locEnd], "\n" * num_newlines
+                    )
+
+        self.braceDepth = 0
+
+        lex = Lexer(self.headerFileName)
+        lex.input(headerFileStr)
+        self.lex = lex
+        self.headerFileNames = lex.filenames
+
+        #
+        # A note on parsing methodology
+        #
+        # The idea here is to consume as many tokens as needed to determine
+        # what the thing is that we're parsing. While some items can be identified
+        # early, typically the code below consumes until a '{', '}', or ; and
+        # then looks at the accumulated tokens to figure out what it is.
+        #
+        # Unfortunately, the code isn't always particularly consistent (but
+        # it's slowly getting there!), so take this with a grain of salt.
+        #
+
+        self._doxygen_cache = None
+        self.braceHandled = False
+        tok = None
+        self.stmtTokens = []
+        parenDepth = 0
+
+        try:
+            while True:
+                tok = lex.token(eof_ok=True)
+                if not tok:
+                    break
+                tok.value = TagStr(tok.value, location=tok.location)
+
+                # debug_print("TOK: %s", tok)
+                if tok.type == "NAME":
+                    if tok.value in self.IGNORE_NAMES:
+                        continue
+                    elif tok.value == "template":
+                        self._doxygen_cache = self.lex.get_doxygen()
+                        self._parse_template()
+                        continue
+                    elif tok.value == "alignas":
+                        self._parse_attribute_specifier_seq(tok)
+                        continue
+                    elif tok.value == "__attribute__":
+                        self._parse_gcc_attribute()
+                        continue
+                    elif not self.stack and tok.value == "static_assert":
+                        self._next_token_must_be("(")
+                        self._discard_contents("(", ")")
+                        continue
+
+                elif tok.type == "DBL_LBRACKET":
+                    self._parse_attribute_specifier_seq(tok)
+                    continue
+
+                # TODO: get rid of stack, move to stmtTokens
+                self.stack.append(tok.value)
+                self.stmtTokens.append(tok)
+
+                nslen = len(self.nameStack)
+
+                if tok.type in ("PRECOMP_MACRO", "PRECOMP_MACRO_CONT"):
+                    debug_print("PRECOMP: %s", tok)
+                    self._precomp_macro_buf.append((tok.value, tok.location))
+                    self.stack = []
+                    self.stmtTokens = []
+                    self.nameStack = []
+                    continue
+
+                if parenDepth == 0 and tok.type == "{":
+                    if len(self.nameStack) >= 2 and is_namespace(
+                        self.nameStack
+                    ):  # namespace {} with no name used in boost, this sets default?
+                        if (
+                            self.nameStack[1]
+                            == "__IGNORED_NAMESPACE__CppHeaderParser__"
+                        ):  # Used in filtering extern "C"
+                            self.nameStack[1] = ""
+                        self.nameSpaces.append(self.nameStack[1])
+                        ns = self.cur_namespace()
+                        self.stack = []
+                        self.stmtTokens = []
+                        if ns not in self.namespaces:
+                            self.namespaces.append(ns)
+                    # Detect special condition of macro magic before class declaration so we
+                    # can filter it out
+                    if "class" in self.nameStack and self.nameStack[0] != "class":
+                        classLocationNS = self.nameStack.index("class")
+                        classLocationS = self.stack.index("class")
+                        if (
+                            "(" not in self.nameStack[classLocationNS:]
+                            and self.nameStack[classLocationNS - 1] != "enum"
+                        ):
+                            debug_print(
+                                "keyword 'class' found in unexpected location in nameStack, must be following #define magic.  Process that before moving on"
+                            )
+                            origNameStack = self.nameStack
+                            origStack = self.stack
+                            # Process first part of stack which is probably #define macro magic and may cause issues
+                            self.nameStack = self.nameStack[:classLocationNS]
+                            self.stack = self.stack[:classLocationS]
+                            try:
+                                self._evaluate_stack()
+                            except:
+                                debug_print("Error processing #define magic... Oh well")
+                            # Process rest of stack
+                            self.nameStack = origNameStack[classLocationNS:]
+                            self.stack = origStack[classLocationS:]
+
+                    # If set to True, indicates that the callee consumed
+                    # all of the tokens between { and }
+                    self.braceHandled = False
+                    if self.nameStack:
+                        self._evaluate_stack()
+                    if self.stack and self.stack[0] == "class":
+                        self.stack = []
+                        self.stmtTokens = []
+                    if not self.braceHandled:
+                        self.braceDepth += 1
+
+                elif parenDepth == 0 and tok.type == "}":
+                    if self.braceDepth == 0:
+                        continue
+                    if self.braceDepth == len(self.nameSpaces):
+                        tmp = self.nameSpaces.pop()
+                        self.stack = []  # clear stack when namespace ends?
+                        self.stmtTokens = []
+                    else:
+                        self._evaluate_stack()
+                    self.braceDepth -= 1
+
+                    # if self.curClass:
+                    #     debug_print(
+                    #         "CURBD %s", self._classes_brace_level[self.curClass]
+                    #     )
+
+                    if (self.braceDepth == 0) or (
+                        self.curClass
+                        and self._classes_brace_level[self.curClass] == self.braceDepth
+                    ):
+                        trace_print("END OF CLASS DEF")
+                        if self.accessSpecifierStack:
+                            self.curAccessSpecifier = self.accessSpecifierStack[-1]
+                            self.accessSpecifierStack = self.accessSpecifierStack[:-1]
+                        if self.curClass and self.classes[self.curClass]["parent"]:
+                            thisClass = self.classes[self.curClass]
+                            self.curClass = self.curClass[
+                                : -(len(thisClass["name"]) + 2)
+                            ]
+
+                            # Detect anonymous union members
+                            if (
+                                self.curClass
+                                and thisClass["declaration_method"] == "union"
+                                and thisClass["name"].startswith("<")
+                                and self.lex.token_if(";")
+                            ):
+                                debug_print("Creating anonymous union")
+                                # Force the processing of an anonymous union
+                                self.nameStack = [""]
+                                self.stack = self.nameStack + [";"]
+                                debug_print("pre eval anon stack")
+                                self._evaluate_stack(";")
+                                debug_print("post eval anon stack")
+                                self.stack = []
+                                self.nameStack = []
+                                self.stmtTokens = []
+                        else:
+                            self.curClass = ""
+                        self.stack = []
+                        self.stmtTokens = []
+                elif tok.type in _namestack_append_tokens:
+                    self.nameStack.append(tok.value)
+                    nameStackAppended = True
+                elif tok.type in _namestack_pass_tokens:
+                    pass
+                elif tok.type in _namestack_str_tokens:
+                    if tok.value in ignoreSymbols:
+                        debug_print("Ignore symbol %s", tok.value)
+                    elif tok.value == "class":
+                        self.nameStack.append(tok.value)
+                    else:
+                        self.nameStack.append(tok.value)
+                elif tok.type == ":":
+                    if self.nameStack and self.nameStack[0] in supportedAccessSpecifier:
+                        specifier = " ".join(self.nameStack)
+                        if specifier in supportedAccessSpecifier:
+                            self.curAccessSpecifier = specifier
+                        else:
+                            self.curAccessSpecifier = self.nameStack[0]
+                        debug_print(
+                            "curAccessSpecifier updated to %s", self.curAccessSpecifier
+                        )
+                        self.nameStack = []
+                        self.stack = []
+                        self.stmtTokens = []
+                    elif is_method_namestack(self.stack):
+                        debug_print("trace")
+                        self._evaluate_method_stack()
+                        self.nameStack = []
+                        self.stack = []
+                        self.stmtTokens = []
+                    else:
+                        self.nameStack.append(tok.value)
+
+                elif tok.type == ";":
+                    self._evaluate_stack(tok.type)
+                    self.stack = []
+                    self.nameStack = []
+                    self.stmtTokens = []
+                elif tok.type == "(":
+                    parenDepth += 1
+                    self.nameStack.append(tok.value)
+                    nameStackAppended = True
+                elif tok.type == ")":
+                    self.nameStack.append(tok.value)
+                    nameStackAppended = True
+                    if parenDepth != 0:
+                        parenDepth -= 1
+
+                newNsLen = len(self.nameStack)
+                if nslen != newNsLen and newNsLen == 1:
+                    if not self.curTemplate:
+                        self._doxygen_cache = self.lex.get_doxygen()
+
+        except Exception as e:
+            if debug:
+                raise
+            context = ""
+            if isinstance(e, CppParseError):
+                context = ": " + str(e)
+                if e.tok:
+                    tok = e.tok
+
+            if tok:
+                filename, lineno = tok.location
+                msg = (
+                    "Not able to parse %s on line %d evaluating '%s'%s\nError around: %s"
+                    % (filename, lineno, tok.value, context, " ".join(self.nameStack))
+                )
+            else:
+                msg = "Error parsing %s%s\nError around: %s" % (
+                    self.headerFileName,
+                    context,
+                    " ".join(self.nameStack),
+                )
+
+            raise_exc(
+                CppParseError(msg),
+                e,
+            )
+
+        self.finalize()
+        global parseHistory
+        parseHistory = []
+        # Delete some temporary variables
+        for key in [
+            "_precomp_macro_buf",
+            "_doxygen_cache",
+            "braceHandled",
+            "lex",
+            "nameStack",
+            "nameSpaces",
+            "curAccessSpecifier",
+            "accessSpecifierStack",
+            "nameStackHistory",
+            "anon_struct_counter",
+            "anon_union_counter",
+            "anon_class_counter",
+            "_classes_brace_level",
+            "_forward_decls",
+            "stack",
+            "mainClass",
+            "_template_typenames",
+            "braceDepth",
+            "stmtTokens",
+            "typedefs_order",
+            "curTemplate",
+        ]:
+            del self.__dict__[key]
+
+    def _get_location(self, stack):
+        if stack:
+            location = getattr(stack[0], "location", None)
+            if location is not None:
+                return location
+
+        return self.lex.current_location()
+
+    def _get_stmt_doxygen(self):
+        # retrieves the doxygen comment associated with an accumulated
+        # statement (since doxygen comments have to be retrieved immediately)
+        doxygen, self._doxygen_cache = self._doxygen_cache, ""
+        if not doxygen:
+            doxygen = self.lex.get_doxygen()
+        return doxygen
+
+    def _parse_error(self, tokens, expected):
+        if not tokens:
+            # common case after a failed token_if
+            errtok = self.lex.token()
+        else:
+            errtok = tokens[-1]
+        if expected:
+            expected = ", expected '" + expected + "'"
+
+        msg = "unexpected '%s'%s" % (errtok.value, expected)
+
+        # TODO: better error message
+        return CppParseError(msg, errtok)
+
+    def _next_token_must_be(self, *tokenTypes):
+        tok = self.lex.token()
+        if tok.type not in tokenTypes:
+            raise self._parse_error((tok,), "' or '".join(tokenTypes))
+        return tok
+
+    _end_balanced_tokens = {">", "}", "]", ")", "DBL_RBRACKET"}
+    _balanced_token_map = {
+        "<": ">",
+        "{": "}",
+        "(": ")",
+        "[": "]",
+        "DBL_LBRACKET": "DBL_RBRACKET",
+    }
+
+    def _consume_balanced_tokens(self, *init_tokens):
+
+        _balanced_token_map = self._balanced_token_map
+
+        consumed = list(init_tokens)
+        match_stack = deque((_balanced_token_map[tok.type] for tok in consumed))
+        get_token = self.lex.token
+
+        while True:
+            tok = get_token()
+            consumed.append(tok)
+
+            if tok.type in self._end_balanced_tokens:
+                expected = match_stack.pop()
+                if tok.type != expected:
+                    # hack: ambiguous right-shift issues here, really
+                    # should be looking at the context
+                    if tok.type == ">":
+                        tok = self.lex.token_if(">")
+                        if tok:
+                            consumed.append(tok)
+                            match_stack.append(expected)
+                            continue
+
+                    raise self._parse_error(consumed, expected)
+                if len(match_stack) == 0:
+                    return consumed
+
+                continue
+
+            next_end = _balanced_token_map.get(tok.type)
+            if next_end:
+                match_stack.append(next_end)
+
+    def _discard_contents(self, start_type, end_type):
+        # use this instead of consume_balanced_tokens because
+        # we don't care at all about the internals
+        level = 1
+        get_token = self.lex.token
+        while True:
+            tok = get_token()
+            if tok.type == start_type:
+                level += 1
+            elif tok.type == end_type:
+                level -= 1
+                if level == 0:
+                    break
+
+    def _discard_ctor_initializer(self):
+        """
+        ctor_initializer: ":" mem_initializer_list
+
+        mem_initializer_list: mem_initializer ["..."]
+                            | mem_initializer "," mem_initializer_list ["..."]
+
+        mem_initializer: mem_initializer_id "(" [expression_list] ")"
+                       | mem_initializer_id braced_init_list
+
+        mem_initializer_id: class_or_decltype
+                          | IDENTIFIER
+        """
+        debug_print("discarding ctor intializer")
+        # all of this is discarded.. the challenge is to determine
+        # when the initializer ends and the function starts
+        while True:
+            tok = self.lex.token()
+            if tok.type == "DBL_COLON":
+                tok = self.lex.token()
+
+            if tok.type == "decltype":
+                tok = self._next_token_must_be("(")
+                self._consume_balanced_tokens(tok)
+                tok = self.lex.token()
+
+            # each initializer is either foo() or foo{}, so look for that
+            while True:
+                if tok.type not in ("{", "("):
+                    tok = self.lex.token()
+                    continue
+
+                if tok.type == "{":
+                    self._discard_contents("{", "}")
+                elif tok.type == "(":
+                    self._discard_contents("(", ")")
+
+                tok = self.lex.token()
+                break
+
+            # at the end
+            if tok.type == "ELLIPSIS":
+                tok = self.lex.token()
+
+            if tok.type == ",":
+                continue
+            elif tok.type == "{":
+                # reached the function
+                self._discard_contents("{", "}")
+                return
+            else:
+                raise self._parse_error((tok,), ",' or '{")
+
+    def _evaluate_stack(self, token=None):
+        """Evaluates the current name stack"""
+
+        nameStackCopy = self.nameStack[:]
+
+        debug_print(
+            "Evaluating stack %s\n       BraceDepth: %s (called from %d)",
+            self.nameStack,
+            self.braceDepth,
+            inspect.currentframe().f_back.f_lineno,
+        )
+
+        # Handle special case of overloading operator ()
+        if "operator()(" in "".join(self.nameStack):
+            operator_index = self.nameStack.index("operator")
+            self.nameStack.pop(operator_index + 2)
+            self.nameStack.pop(operator_index + 1)
+            self.nameStack[operator_index] = "operator()"
+
+        if len(self.curClass):
+            debug_print("%s (%s) ", self.curClass, self.curAccessSpecifier)
+        else:
+            debug_print("<anonymous> (%s) ", self.curAccessSpecifier)
+
+        # Filter special case of array with casting in it
+        try:
+            bracePos = self.nameStack.index("[")
+            parenPos = self.nameStack.index("(")
+            if bracePos == parenPos - 1:
+                endParen = self.nameStack.index(")")
+                self.nameStack = (
+                    self.nameStack[: bracePos + 1] + self.nameStack[endParen + 1 :]
+                )
+                debug_print("Filtered namestack to=%s", self.nameStack)
+        except:
+            pass
+
+        # if 'typedef' in self.nameStack: self._evaluate_typedef()        # allows nested typedefs, probably a bad idea
+        if (
+            not self.curClass
+            and "typedef" in self.nameStack
+            and (
+                (
+                    "struct" not in self.nameStack
+                    and "union" not in self.nameStack
+                    and "enum" not in self.nameStack
+                )
+                or self.stack[-1] == ";"
+            )
+        ):
+            debug_print("trace")
+            trace_print("typedef %s", self.stack)
+            self._evaluate_typedef()
+            return
+
+        elif len(self.nameStack) == 0:
+            debug_print("trace (Empty Stack)")
+            return
+        elif self.nameStack[0] == "namespace":
+            # Taken care of outside of here
+            pass
+        elif len(self.nameStack) == 2 and self.nameStack[0] == "extern":
+            debug_print("trace extern")
+            self.stack = []
+            self.stmtTokens = []
+        elif (
+            len(self.nameStack) == 2 and self.nameStack[0] == "friend"
+        ):  # friend class declaration
+            pass
+        elif len(self.nameStack) >= 2 and self.nameStack[0] == "using":
+            if self.nameStack[1] == "namespace":
+                pass
+            else:
+                if len(self.nameStack) > 3 and self.nameStack[2] == "=":
+                    # using foo = ns::bar
+                    # -> type alias: same behavior in all scopes
+                    alias = self.nameStack[1]
+                    ns, stack = _split_namespace(self.nameStack[3:])
+                    atype = CppVariable(
+                        stack, self._get_stmt_doxygen(), self._get_location(stack)
+                    )
+
+                    # namespace refers to the embedded type
+                    atype["namespace"] = ns
+                    atype["using_type"] = "typealias"
+                    atype["typealias"] = alias
+                else:
+                    # using foo::bar
+                    # -> in global scope this is bringing in something
+                    #    from a different namespace
+                    # -> in class scope this is bringing in a member
+                    #    from a base class
+                    ns, stack = _split_namespace(self.nameStack[1:])
+                    atype = CppVariable(
+                        stack, self._get_stmt_doxygen(), self._get_location(stack)
+                    )
+                    alias = atype["type"]
+                    atype["using_type"] = "declaration"
+                    if self.curClass:
+                        atype["baseclass"] = ns
+                    else:
+                        atype["namespace"] = ns
+
+                atype["raw_type"] = ns + atype["type"]
+
+                if self.curClass:
+                    klass = self.classes[self.curClass]
+                    klass["using"][alias] = atype
+                else:
+                    # lookup is done
+                    alias = self.current_namespace() + alias
+                    self.using[alias] = atype
+        elif is_method_namestack(self.stack) and "(" in self.nameStack:
+            debug_print("trace")
+            self._evaluate_method_stack()
+        elif is_enum_namestack(self.nameStack):
+            debug_print("trace")
+            self._parse_enum()
+            self.stack = []
+            self.stmtTokens = []
+        elif (
+            len(self.nameStack) == 1
+            and len(self.nameStackHistory) > self.braceDepth
+            and (
+                self.nameStackHistory[self.braceDepth][0][0:2] == ["typedef", "struct"]
+                or self.nameStackHistory[self.braceDepth][0][0:2]
+                == ["typedef", "union"]
+            )
+        ):
+            # Look for the name of a typedef struct: struct typedef {...] StructName; or unions to get renamed
+            debug_print("found the naming of a union")
+            type_name_to_rename = self.nameStackHistory[self.braceDepth][1]
+            new_name = self.nameStack[0]
+            type_to_rename = self.classes[type_name_to_rename]
+            type_to_rename["name"] = self.nameStack[0]
+            # Now re install it in its new location
+            self.classes[new_name] = type_to_rename
+            if new_name != type_name_to_rename:
+                del self.classes[type_name_to_rename]
+        elif is_property_namestack(self.nameStack) and self.stack[-1] == ";":
+            debug_print("trace")
+            if self.nameStack[0] in ("class", "struct") and len(self.stack) == 3:
+                self.evalute_forward_decl()
+            elif len(self.nameStack) >= 2 and (
+                self.nameStack[0] == "friend" and self.nameStack[1] == "class"
+            ):
+                pass
+            else:
+                self._evaluate_property_stack()  # catches class props and structs in a namespace
+
+        elif (
+            self.nameStack[0] in ("class", "struct", "union")
+            or self.nameStack[0] == "typedef"
+            and self.nameStack[1] in ("struct", "union")
+        ):
+            # Parsing a union can reuse much of the class parsing
+            debug_print("trace")
+            self._evaluate_class_stack()
+
+        elif not self.curClass:
+            debug_print("trace")
+        elif self.braceDepth < 1:
+            debug_print("trace")
+            # Ignore global stuff for now
+            debug_print("Global stuff: %s" % self.nameStack)
+        elif self.braceDepth > len(self.nameSpaces) + 1:
+            debug_print("trace")
+        else:
+            debug_print("Discarded statement %s" % (self.nameStack,))
+
+        try:
+            self.nameStackHistory[self.braceDepth] = (nameStackCopy, self.curClass)
+        except:
+            self.nameStackHistory.append((nameStackCopy, self.curClass))
+
+        # its a little confusing to have some if/else above return and others not, and then clearning the nameStack down here
+        self.nameStack = []
+        self.lex.doxygenCommentCache = ""
+        self.curTemplate = None
+
+    def _parse_template(self):
+        tok = self._next_token_must_be("<")
+        consumed = self._consume_balanced_tokens(tok)
+        tmpl = " ".join(tok.value for tok in consumed)
+        tmpl = (
+            tmpl.replace(" :: ", "::")
+            .replace(" <", "<")
+            .replace("< ", "<")
+            .replace(" >", ">")
+            .replace("> ", ">")
+            .replace(" , ", ", ")
+            .replace(" = ", "=")
+        )
+        self.curTemplate = "template" + tmpl
+
+    def _parse_gcc_attribute(self):
+        tok1 = self._next_token_must_be("(")
+        tok2 = self._next_token_must_be("(")
+        self._consume_balanced_tokens(tok1, tok2)
+
+    _attribute_specifier_seq_start_types = ("DBL_LBRACKET", "NAME")
+    _attribute_specifier_seq_start_values = ("[[", "alignas")
+
+    def _parse_attribute_specifier_seq(self, tok):
+        # TODO: retain the attributes and do something with them
+        # attrs = []
+
+        while True:
+            if tok.type == "DBL_LBRACKET":
+                tokens = self._consume_balanced_tokens(tok)
+                # attrs.append(Attribute(tokens))
+            elif tok.type == "NAME" and tok.value == "alignas":
+                next_tok = self._next_token_must_be("(")
+                tokens = self._consume_balanced_tokens(next_tok)
+                # attrs.append(AlignasAttribute(tokens))
+            else:
+                self.lex.return_token(tok)
+                break
+
+            # multiple attributes can be specified
+            tok = self.lex.token_if(*self._attribute_specifier_seq_start_types)
+            if tok is None:
+                break
+
+        # return attrs
+
+    def _parse_enum(self):
+        """
+        opaque_enum_declaration: enum_key [attribute_specifier_seq] IDENTIFIER [enum_base] ";"
+
+        enum_specifier: enum_head "{" [enumerator_list] "}"
+                      | enum_head "{" enumerator_list "," "}"
+
+        enum_head: enum_key [attribute_specifier_seq] [IDENTIFIER] [enum_base]
+                 | enum_key [attribute_specifier_seq] nested_name_specifier IDENTIFIER [enum_base]
+
+        enum_key: "enum"
+                | "enum" "class"
+                | "enum" "struct"
+
+        enum_base: ":" type_specifier_seq
+        """
+        debug_print("parsing enum")
+
+        is_typedef = False
+        self.lex.return_tokens(self.stmtTokens)
+
+        doxygen = self._get_stmt_doxygen()
+
+        tok = self.lex.token()
+        if tok.value == "typedef":
+            is_typedef = True
+            tok = self.lex.token()
+
+        if tok.value != "enum":
+            raise self._parse_error((tok,), "enum")
+
+        location = tok.location
+
+        is_class = False
+        nametok = self.lex.token()
+        if nametok.value in ("class", "struct"):
+            is_class = True
+            nametok = self.lex.token()
+
+        if nametok.value == "__attribute__":
+            self._parse_gcc_attribute()
+            nametok = self.lex.token()
+
+        if nametok.value in self._attribute_specifier_seq_start_values:
+            self._parse_attribute_specifier_seq(nametok)
+            nametok = self.lex.token()
+
+        # TODO: nested_name_specifier
+        name = ""
+        if nametok.type == "NAME":
+            name = nametok.value
+            debug_print("enum name is '%s'", name)
+            tok = self.lex.token()
+        else:
+            debug_print("anonymous enum")
+            tok = nametok
+
+        base = []
+        if tok.type == ":":
+            while True:
+                tok = self.lex.token()
+                if tok.type in ("{", ";"):
+                    break
+                base.append(tok.value)
+
+        newEnum = CppEnum(name, doxygen, location)
+        if is_typedef:
+            newEnum["typedef"] = True
+        if is_class:
+            newEnum["isclass"] = True
+        if base:
+            newEnum["type"] = "".join(base)
+
+        instancesData = []
+
+        if tok.type == "{":
+            self.braceHandled = True
+            self._parse_enumerator_list(newEnum["values"])
+            newEnum.resolve_enum_values(newEnum["values"])
+            tok = self.lex.token()
+
+        if tok.value == "__attribute__":
+            self._parse_gcc_attribute()
+            tok = self.lex.token()
+
+        if tok.type == "NAME":
+            if newEnum["typedef"]:
+                newEnum["name"] = tok.value
+                self._next_token_must_be(";")
+            else:
+                # this is an instance of the enum
+                instancesData.append(tok.value)
+                while True:
+                    tok = self.lex.token()
+                    if tok.type == ";":
+                        break
+                    instancesData.append(tok.value)
+        elif tok.type != ";":
+            raise self._parse_error((tok,), ";")
+
+        self._install_enum(newEnum, instancesData)
+
+    def _install_enum(self, newEnum, instancesData):
+        if len(self.curClass):
+            newEnum["namespace"] = self.cur_namespace(False)
+            klass = self.classes[self.curClass]
+            klass["enums"][self.curAccessSpecifier].append(newEnum)
+            if self.curAccessSpecifier == "public" and "name" in newEnum:
+                klass._public_enums[newEnum["name"]] = newEnum
+        else:
+            newEnum["namespace"] = self.cur_namespace(True)
+            self.enums.append(newEnum)
+            if "name" in newEnum and newEnum["name"]:
+                self.global_enums[newEnum["name"]] = newEnum
+
+        # This enum has instances, turn them into properties
+        if instancesData:
+            instances = list(_split_by_comma(instancesData))
+            instanceType = "enum"
+            if "name" in newEnum:
+                instanceType = newEnum["name"]
+            addToVar = {"enum_type": newEnum}
+            for instance in instances:
+                self.nameStack = [instanceType] + instance
+                self._evaluate_property_stack(clearStack=False, addToVar=addToVar)
+
+    def _parse_enumerator_list(self, values):
+        """
+        enumerator_list: enumerator_definition
+                       | enumerator_list "," enumerator_definition
+
+        enumerator_definition: enumerator
+                             | enumerator "=" constant_expression
+
+        enumerator: IDENTIFIER
+        """
+        while True:
+            name_tok = self._next_token_must_be("}", "NAME")
+            if name_tok.value == "}":
+                return
+
+            value = {"name": name_tok.value}
+            doxygen = self.lex.get_doxygen()
+            if doxygen:
+                value["doxygen"] = doxygen
+            values.append(value)
+
+            debug_print("enumerator value '%s'", value["name"])
+
+            tok = self._next_token_must_be("}", ",", "=", "DBL_LBRACKET")
+            if tok.type == "DBL_LBRACKET":
+                self._parse_attribute_specifier_seq(tok)
+                tok = self._next_token_must_be("}", ",", "=")
+
+            if tok.type == "}":
+                return
+            elif tok.type == ",":
+                continue
+            elif tok.type == "=":
+                v = []
+                while True:
+                    tok = self.lex.token()
+                    if tok.type == "}":
+                        value["value"] = " ".join(v)
+                        return
+                    elif tok.type == ",":
+                        value["value"] = " ".join(v)
+                        break
+                    elif tok.type in self._balanced_token_map:
+                        v.extend(t.value for t in self._consume_balanced_tokens(tok))
+                    else:
+                        v.append(tok.value)
+
+    def _strip_parent_keys(self):
+        """Strip all parent (and method) keys to prevent loops"""
+        obj_queue = [self]
+        while len(obj_queue):
+            obj = obj_queue.pop()
+            trace_print("pop %s type %s" % (obj, type(obj)))
+            try:
+                if "parent" in obj.keys():
+                    del obj["parent"]
+                    trace_print("Stripped parent from %s" % obj.keys())
+            except:
+                pass
+            try:
+                if "method" in obj.keys():
+                    del obj["method"]
+                    trace_print("Stripped method from %s" % obj.keys())
+            except:
+                pass
+            # Figure out what sub types are one of ours
+            try:
+                if not hasattr(obj, "keys"):
+                    obj = obj.__dict__
+                for k in obj.keys():
+                    trace_print("-Try key %s" % (k))
+                    trace_print("-type  %s" % (type(obj[k])))
+                    if k in ["nameStackHistory", "parent", "_public_typedefs"]:
+                        continue
+                    if type(obj[k]) == list:
+                        for i in obj[k]:
+                            trace_print("push l %s" % i)
+                            obj_queue.append(i)
+                    elif type(obj[k]) == dict:
+                        if len(obj):
+                            trace_print("push d %s" % obj[k])
+                            obj_queue.append(obj[k])
+                    elif type(obj[k]) == type(type(0)):
+                        if type(obj[k]) == int:
+                            obj[k] = "int"
+                        elif type(obj[k]) == str:
+                            obj[k] = "string"
+                        else:
+                            obj[k] = "???"
+                    trace_print("next key\n")
+            except:
+                trace_print("Exception")
+
+    def toJSON(self, indent=4, separators=None):
+        """Converts a parsed structure to JSON"""
+        import json
+
+        self._strip_parent_keys()
+        try:
+            del self.__dict__["classes_order"]
+        except:
+            pass
+        return json.dumps(self.__dict__, indent=indent, separators=separators)
+
+    def __repr__(self):
+        rtn = {
+            "classes": self.classes,
+            "functions": self.functions,
+            "enums": self.enums,
+            "variables": self.variables,
+        }
+        return repr(rtn)
+
+    def __str__(self):
+        rtn = ""
+        for className in list(self.classes.keys()):
+            rtn += "%s\n" % self.classes[className]
+        if self.functions:
+            rtn += "// functions\n"
+            for f in self.functions:
+                rtn += "%s\n" % f
+        if self.variables:
+            rtn += "// variables\n"
+            for f in self.variables:
+                rtn += "%s\n" % f
+        if self.enums:
+            rtn += "// enums\n"
+            for f in self.enums:
+                rtn += "%s\n" % f
+        return rtn
diff --git a/SDK/C++/public/python/CppHeaderParser/LICENSE.txt b/SDK/C++/public/python/CppHeaderParser/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..21a244d477c68e67dc235c21978ec324d0298a34
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/LICENSE.txt
@@ -0,0 +1,33 @@
+Copyright (C) 2011, Jashua R. Cloutier
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in
+  the documentation and/or other materials provided with the
+  distribution.
+
+* Neither the name of Jashua R. Cloutier nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.  Stories,
+  blog entries etc making reference to this project may mention the
+  name Jashua R. Cloutier in terms of project originator/creator etc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/SDK/C++/public/python/CppHeaderParser/README.rst b/SDK/C++/public/python/CppHeaderParser/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..77e2eb6516a8b08b78eab03894923cb7bcb5bfc0
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/README.rst
@@ -0,0 +1,67 @@
+robotpy-cppheaderparser
+=======================
+
+|Build Status|
+
+CppHeaderParser is a pure python C++ header parser that parses C++
+headers and creates a data structure that you can use to do many types
+of things. We’ve found it particularly useful for creating programs that
+generate python wrappers around existing C++ programs.
+
+robotpy-cppheaderparser is a fork of the `CppHeaderParser`_ library
+originally created by @senex. CppHeaderParser is an excellent library
+and critical to some of the stuff we do in the RobotPy project.
+Unfortunately, the maintainer seems to be busy, so
+robotpy-cppheaderparser was born.
+
+We aim to maintain (some) compatibility with the existing code and make
+improvements and bugfixes as we need them -- though some decisions made
+early on in this code's development means some compatibility may be broken
+as things get fixed.
+
+If you find an bug, we encourage you to submit a pull request! New
+changes will only be accepted if there are tests to cover the change you
+made (and if they don’t break existing tests).
+
+.. note:: CppHeaderParser only does some very minimal interpretation of
+          preprocessor directives -- and we're looking at removing some
+          of that from this library. If you need anything complex, you
+          should preprocess the code yourself. You can use the excellent
+          pure python preprocessor `pcpp`_, or the preprocessing facilities
+          provided by your favorite compiler.
+
+Documentation
+-------------
+
+Documentation can be found at https://cppheaderparser.readthedocs.io
+
+Install
+-------
+
+::
+
+   pip install robotpy-cppheaderparser
+
+License
+-------
+
+BSD License
+
+Authors
+-------
+
+Originally developed by Jashua Cloutier, this fork is maintained by the
+RobotPy project.
+
+Past contributors include:
+
+* Jashua Cloutier
+* Chris Love
+* HartsAntler
+
+.. _CppHeaderParser: https://bitbucket.org/senex/cppheaderparser
+
+.. _pcpp: https://github.com/ned14/pcpp
+
+.. |Build Status| image:: https://travis-ci.org/robotpy/robotpy-cppheaderparser.svg?branch=master
+   :target: https://travis-ci.org/robotpy/robotpy-cppheaderparser
\ No newline at end of file
diff --git a/SDK/C++/public/python/CppHeaderParser/__init__.py b/SDK/C++/public/python/CppHeaderParser/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..facdf9b7b972cfda0148c0017cff5c5f80cb4cb6
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/__init__.py
@@ -0,0 +1,7 @@
+# CppHeaderParser package
+# Author: Jashua Cloutier (contact via sourceforge username:senexcanis)
+
+from .CppHeaderParser import *
+from .CppHeaderParser import __version__
+
+# __all__ = ['CppHeaderParser']
diff --git a/SDK/C++/public/python/CppHeaderParser/doxygen.py b/SDK/C++/public/python/CppHeaderParser/doxygen.py
new file mode 100644
index 0000000000000000000000000000000000000000..cac2b35e1e535832de78c75474b7bbe75956592f
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/doxygen.py
@@ -0,0 +1,32 @@
+def extract_doxygen_method_params(doxystr):
+    """
+    Given a doxygen string for a method, extract parameter descriptions
+    """
+    doxyVarDesc = {}
+    doxyLines = doxystr.split("\n")
+    lastParamDesc = ""
+    for doxyLine in doxyLines:
+        if " @param " in doxyLine or " \\param " in doxyLine:
+            try:
+                # Strip out the param
+                doxyLine = doxyLine[doxyLine.find("param ") + 6 :]
+                (var, desc) = doxyLine.split(" ", 1)
+                doxyVarDesc[var] = desc.strip()
+                lastParamDesc = var
+            except:
+                pass
+        elif " @return " in doxyLine or " \return " in doxyLine:
+            lastParamDesc = ""
+            # not handled for now
+        elif lastParamDesc:
+            try:
+                doxyLine = doxyLine.strip()
+                if " " not in doxyLine:
+                    lastParamDesc = ""
+                    continue
+                doxyLine = doxyLine[doxyLine.find(" ") + 1 :]
+                doxyVarDesc[lastParamDesc] += " " + doxyLine
+            except:
+                pass
+
+    return doxyVarDesc
diff --git a/SDK/C++/public/python/CppHeaderParser/lexer.py b/SDK/C++/public/python/CppHeaderParser/lexer.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa62a1a419d0c41f811f3e39fb90f72c54ce62f9
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/lexer.py
@@ -0,0 +1,233 @@
+from collections import deque
+import ply.lex as lex
+import re
+
+_line_re = re.compile(r'^#line (\d+) "(.*)"')
+
+
+class Lexer(object):
+
+    tokens = [
+        "NUMBER",
+        "FLOAT_NUMBER",
+        "NAME",
+        "COMMENT_SINGLELINE",
+        "COMMENT_MULTILINE",
+        "PRECOMP_MACRO",
+        "PRECOMP_MACRO_CONT",
+        "DIVIDE",
+        "CHAR_LITERAL",
+        "STRING_LITERAL",
+        "NEWLINE",
+        "ELLIPSIS",
+        "DBL_LBRACKET",
+        "DBL_RBRACKET",
+        "DBL_COLON",
+        "SHIFT_LEFT",
+    ]
+
+    literals = [
+        "<",
+        ">",
+        "(",
+        ")",
+        "{",
+        "}",
+        "[",
+        "]",
+        ";",
+        ":",
+        ",",
+        "\\",
+        "|",
+        "%",
+        "^",
+        "!",
+        "*",
+        "-",
+        "+",
+        "&",
+        "=",
+        "'",
+        ".",
+    ]
+
+    t_ignore = " \t\r?@\f"
+    t_NUMBER = r"[0-9][0-9XxA-Fa-f]*"
+    t_FLOAT_NUMBER = r"[-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?"
+    t_NAME = r"[A-Za-z_~][A-Za-z0-9_]*"
+
+    def t_PRECOMP_MACRO(self, t):
+        r"\#.*"
+        m = _line_re.match(t.value)
+        if m:
+            filename = m.group(2)
+            if filename not in self._filenames_set:
+                self.filenames.append(filename)
+                self._filenames_set.add(filename)
+            self.filename = filename
+
+            self.line_offset = 1 + self.lex.lineno - int(m.group(1))
+
+        else:
+            return t
+
+    t_PRECOMP_MACRO_CONT = r".*\\\n"
+
+    def t_COMMENT_SINGLELINE(self, t):
+        r"\/\/.*\n?"
+        if t.value.startswith("///") or t.value.startswith("//!"):
+            self.comments.append(t.value.lstrip("\t ").rstrip("\n"))
+        t.lexer.lineno += t.value.count("\n")
+        return t
+
+    t_DIVIDE = r"/(?!/)"
+    t_CHAR_LITERAL = "'.'"
+    t_ELLIPSIS = r"\.\.\."
+    t_DBL_LBRACKET = r"\[\["
+    t_DBL_RBRACKET = r"\]\]"
+    t_DBL_COLON = r"::"
+    t_SHIFT_LEFT = r"<<"
+    # SHIFT_RIGHT introduces ambiguity
+
+    # found at http://wordaligned.org/articles/string-literals-and-regular-expressions
+    # TODO: This does not work with the string "bla \" bla"
+    t_STRING_LITERAL = r'"([^"\\]|\\.)*"'
+
+    # Found at http://ostermiller.org/findcomment.html
+    def t_COMMENT_MULTILINE(self, t):
+        r"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/\n?"
+        if t.value.startswith("/**") or t.value.startswith("/*!"):
+            # not sure why, but get double new lines
+            v = t.value.replace("\n\n", "\n")
+            # strip prefixing whitespace
+            v = re.sub("\n[\\s]+\\*", "\n*", v)
+            self.comments = v.splitlines()
+        t.lexer.lineno += t.value.count("\n")
+        return t
+
+    def t_NEWLINE(self, t):
+        r"\n+"
+        t.lexer.lineno += len(t.value)
+        del self.comments[:]
+        return t
+
+    def t_error(self, v):
+        print("Lex error: ", v)
+
+    def __init__(self, filename):
+        self.lex = lex.lex(module=self)
+        self.input = self.lex.input
+
+        # For tracking current file/line position
+        self.filename = filename
+        self.line_offset = 0
+
+        self.filenames = []
+        self._filenames_set = set()
+
+        if self.filename:
+            self.filenames.append(filename)
+            self._filenames_set.add(filename)
+
+        # Doxygen comments
+        self.comments = []
+
+        self.lookahead = deque()
+
+    def current_location(self):
+        if self.lookahead:
+            return self.lookahead[0].location
+        return self.filename, self.lex.lineno - self.line_offset
+
+    def get_doxygen(self):
+        """
+        This should be called after the first element of something has
+        been consumed.
+
+        It will lookahead for comments that come after the item, if prior
+        comments don't exist.
+        """
+
+        # Assumption: This function is either called at the beginning of a
+        # statement or at the end of a statement
+
+        if self.comments:
+            comments = self.comments
+        else:
+            comments = []
+            # only look for comments until a newline (including lookahead)
+            for tok in self.lookahead:
+                if tok.type == "NEWLINE":
+                    return ""
+
+            while True:
+                tok = self.lex.token()
+                comments.extend(self.comments)
+
+                if tok is None:
+                    break
+
+                tok.location = (self.filename, tok.lineno - self.line_offset)
+                ttype = tok.type
+                if ttype == "NEWLINE":
+                    self.lookahead.append(tok)
+                    break
+
+                if ttype not in self._discard_types:
+                    self.lookahead.append(tok)
+
+                if ttype == "NAME":
+                    break
+
+                del self.comments[:]
+
+        comments = "\n".join(comments)
+        del self.comments[:]
+        return comments
+
+    _discard_types = {"NEWLINE", "COMMENT_SINGLELINE", "COMMENT_MULTILINE"}
+
+    def token(self, eof_ok=False):
+        tok = None
+        while self.lookahead:
+            tok = self.lookahead.popleft()
+            if tok.type not in self._discard_types:
+                return tok
+
+        while True:
+            tok = self.lex.token()
+            if tok is None:
+                if not eof_ok:
+                    raise EOFError("unexpected end of file")
+                break
+
+            if tok.type not in self._discard_types:
+                tok.location = (self.filename, tok.lineno - self.line_offset)
+                break
+
+        return tok
+
+    def token_if(self, *types):
+        tok = self.token(eof_ok=True)
+        if tok is None:
+            return None
+        if tok.type not in types:
+            # put it back on the left in case it was retrieved
+            # from the lookahead buffer
+            self.lookahead.appendleft(tok)
+            return None
+        return tok
+
+    def return_token(self, tok):
+        self.lookahead.appendleft(tok)
+
+    def return_tokens(self, toks):
+        self.lookahead.extendleft(reversed(toks))
+
+
+if __name__ == "__main__":
+    try:
+        lex.runmain(lexer=Lexer(None))
+    except EOFError:
+        pass
diff --git a/SDK/C++/public/python/CppHeaderParser/tojson.py b/SDK/C++/public/python/CppHeaderParser/tojson.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9ea24a2f3bab6eb5e13a416d3c9c93f0aef6825
--- /dev/null
+++ b/SDK/C++/public/python/CppHeaderParser/tojson.py
@@ -0,0 +1,10 @@
+# Utility module
+
+import sys
+import json
+
+from .CppHeaderParser import CppHeader
+
+if __name__ == "__main__":
+    for arg in sys.argv[1:]:
+        print(CppHeader(arg).toJSON())
diff --git a/SDK/C++/public/python/README.md b/SDK/C++/public/python/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a1a955eba963313d9d1f1d2eaca1466715fc070d
--- /dev/null
+++ b/SDK/C++/public/python/README.md
@@ -0,0 +1,36 @@
+# Python API generator
+
+Dependencies
+ * python3-ply (cpp header parser)
+
+Build system uses Pybind11 to generate a python module. Most of the bindings are
+automatically generated by gen.py script which is automatically called by CMake
+when necessary.
+
+Headers included for automatic binding generation are defined in
+SDK_AUTO_HEADERS variable in CMakeLists.txt. Included pybind headers are defined
+in automatic_bindings.cpp.in, which to which generated code is inserted before
+build. Several (empty) macros are used in headers to annoate Python API details.
+
+ * PY_API function/method is to be included in Python API
+ * PY_NO_SHARED_PTR shared_ptr<> is not used with instances of this class.
+   https://pybind11.readthedocs.io/en/latest/advanced/smart_ptrs.html?#std-shared-ptr
+ * PY_RV_LIFETIME_PARENT lifetime of method's return valued is tied to
+   lifetime of parent objects (this). (return_value_policy::reference_internal
+   is set for this method)
+   https://pybind11.readthedocs.io/en/latest/advanced/functions.html#return-value-policies
+
+## Notes:
+ * Binding to default constructor is generated for structs. Class constructors
+   are not available via Python at the moment.
+ * Keyword arguments are supported in Python when function arguments are named
+   in the header.
+ * Default arguments are supported (extracted from header).
+ * Public class properties are available in python, read-only if const,
+   otherwise read write.
+
+## Not supported (yet) in automatic binding generation:
+ * Nested classes
+ * Static members
+ * Constructors
+ * Automatic documentation (Doxygen)
diff --git a/SDK/C++/public/python/automatic_bindings.cpp.in b/SDK/C++/public/python/automatic_bindings.cpp.in
new file mode 100644
index 0000000000000000000000000000000000000000..291de8913a266dd029a6e7ccbc4e7f3796ed82d3
--- /dev/null
+++ b/SDK/C++/public/python/automatic_bindings.cpp.in
@@ -0,0 +1,15 @@
+{includes}
+
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <pybind11/eigen.h>
+
+#include "types/image.hpp"
+
+namespace py = pybind11;
+
+using namespace voltu;
+
+void py_automatic_bindings(py::module& m) {{
+    {code}
+}}
diff --git a/SDK/C++/public/python/examples/example1.py b/SDK/C++/public/python/examples/example1.py
new file mode 100644
index 0000000000000000000000000000000000000000..1cc1a26681470b9212662da2959224daf35bf89c
--- /dev/null
+++ b/SDK/C++/public/python/examples/example1.py
@@ -0,0 +1,33 @@
+# Simple example which opens file provided as command line argument
+# and displays the first source in a window (using OpenCV)
+
+import cv2
+import voltu
+import time
+import sys
+import os
+
+if len(sys.argv) != 2:
+	print("%s filename" % os.path.basename(__file__))
+	exit(1)
+
+#if not os.path.exists(sys.argv[1]):
+#	print("can't find %s" % sys.argv[1])
+#	exit(1)
+
+api = voltu.instance()
+api.open(sys.argv[1])
+room = api.getRoom(0)
+
+while True:
+	try:
+		room.waitNextFrame(1000)
+		frames = room.getFrame().getImageSet(voltu.Channel.kColour)
+		im = frames[0].getHost()
+		cv2.imshow("im", im)
+
+		if cv2.waitKey(10) == 27:
+			break
+
+	except Exception as e:
+		print(e)
diff --git a/SDK/C++/public/python/examples/example2.py b/SDK/C++/public/python/examples/example2.py
new file mode 100644
index 0000000000000000000000000000000000000000..629fec948de0f93c9d94184e8a79033afb211896
--- /dev/null
+++ b/SDK/C++/public/python/examples/example2.py
@@ -0,0 +1,35 @@
+# Simple example which opens file provided as command line argument
+# and renders a virtual camera into window (using OpenCV)
+
+import cv2
+import voltu
+import time
+import sys
+import os
+
+if len(sys.argv) != 2:
+	print("%s filename" % os.path.basename(__file__))
+	exit(1)
+
+#if not os.path.exists(sys.argv[1]):
+#	print("can't find %s" % sys.argv[1])
+#	exit(1)
+
+api = voltu.instance()
+api.open(sys.argv[1])
+room = api.getRoom(0)
+cam = api.createCamera()
+
+while True:
+	try:
+		room.waitNextFrame(1000)
+		cam.submit(room.getFrame())
+		frames = cam.getFrame().getImageSet(voltu.Channel.kColour)
+		im = frames[0].getHost()
+		cv2.imshow("im", im)
+
+		if cv2.waitKey(10) == 27:
+			break
+
+	except Exception as e:
+		print(e)
diff --git a/SDK/C++/public/python/gen.py b/SDK/C++/public/python/gen.py
new file mode 100755
index 0000000000000000000000000000000000000000..00e8252c7efea6975acc331c5076453aa5c3695b
--- /dev/null
+++ b/SDK/C++/public/python/gen.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+
+from CppHeaderParser import CppHeader, CppParseError
+
+# Extract line from file, as not directly with CppHeader
+def read_line(file, lineno):
+    with open(file) as f:
+        for i, line in enumerate(f, 1):
+            if i == lineno:
+                return line
+
+
+def get_loc_msg(data):
+    return "({0}:{1})".format(data["filename"], data["line_number"])
+
+def print_warn(msg, loc=None):
+    if loc is not None:
+        msg += " " + get_loc_msg(loc)
+    print("WARNING: %s" % msg, file=sys.stderr)
+
+def print_err(msg, loc=None):
+    if loc is not None:
+        msg += " " + get_loc_msg(loc)
+    print("ERROR: %s" % msg, file=sys.stderr)
+
+def include_in_api(data):
+    return "PY_API" in data["debug"]
+
+def create_enum_bindigs(enum, parent=[], pybind_handle="", export_values=False):
+    name_full = parent + [enum["name"]]
+    name_py = enum["name"]
+    cpp = []
+    cpp.append("py::enum_<{name_full}>({handle}, \"{name}\")".format(
+        name_full = "::".join(name_full),
+        handle = pybind_handle,
+        name = name_py
+    ))
+    for val in enum["values"]:
+        cpp.append("\t.value(\"{name}\", {name_cpp})".format(
+            name = val["name"],
+            name_cpp = "::".join(name_full + [val["name"]])
+        ))
+
+    if export_values:
+        cpp.append("\t.export_values()")
+
+    return "\n\t".join(cpp)
+
+def create_function_bindings(func, parent=[], pybind_handle=None):
+    func_name = func["name"]
+    full_name = parent + [func_name]
+    full_name_cpp = "::".join(full_name)
+
+    if "PY_API" not in func["debug"]:
+        print_err("%s not included in Python API" % full_name_cpp, func)
+        raise ValueError("No PY_API")
+
+    args = ["\"{0}\"".format(func_name), "&{0}".format(full_name_cpp)]
+
+    for param in func["parameters"]:
+        param_name = param["name"]
+
+        if  param_name == "&":
+            print_warn("Argument name missing for %s" % full_name_cpp, func)
+
+            continue
+
+        if "default" in param:
+            args.append("py::arg(\"{0}\") = {1}".format(
+                param_name, param["default"]))
+        else:
+            args.append("py::arg(\"{0}\")".format(param_name))
+
+    if "PY_RV_LIFETIME_PARENT" in func["debug"]:
+        if func["parent"] is None:
+            print_err("PY_RV_LIFETIME_PARENT used for function", func)
+            raise ValueError()
+
+        args.append("py::return_value_policy::reference_internal")
+
+    cpp = "def({0})".format(", ".join(args))
+    if pybind_handle is not None:
+        cpp = pybind_handle + "." + cpp
+
+    return cpp
+
+def create_class_bindings(cls, parent=[], pybind_handle=""):
+    cls_name = cls["name"]
+    full_name = parent + [cls_name]
+    cpp = []
+
+    if "PY_NO_SHARED_PTR" not in cls["debug"]:
+        cls_cpp = "py::class_<{name}, std::shared_ptr<{name}>>({handle}, \"{name}\")"
+    else:
+        cls_cpp = "py::class_<{name}>({handle}, \"{name}\")"
+    cpp.append(cls_cpp.format(handle=pybind_handle, name=cls_name))
+
+    if cls["declaration_method"] == "struct":
+        cpp.append(".def(py::init<>())")
+
+    for method in cls["methods"]["public"]:
+        if include_in_api(method):
+            cpp.append("." + create_function_bindings(method, full_name))
+
+    for field in cls["properties"]["public"]:
+        if field["constant"]:
+            field_cpp = ".def_property_readonly(\"{name}\", &{cpp_name})"
+        else:
+            field_cpp = ".def_readwrite(\"{name}\", &{cpp_name})"
+        field_name = field["name"]
+        field_name_cpp = "::".join(full_name + [field_name])
+        cpp.append(field_cpp.format(name=field_name, cpp_name=field_name_cpp))
+
+    return "\n\t\t".join(cpp)
+
+if __name__ == "__main__":
+
+    from pprint import pprint
+
+    if (len(sys.argv) < 4):
+        print("gen.py output include_directory input files ...")
+        exit(1)
+
+    handle = "m"
+    fout = sys.argv[1]
+    includedir = sys.argv[2]
+    fsin = sys.argv[3:]
+
+    out = []
+    includes = []
+    for fname in fsin:
+        includes.append(fname)
+
+        hdr = CppHeader(os.path.join(includedir, fname))
+        # note .strip("::"), inconsistent behavior for classes vs enum/func
+
+        for data in hdr.enums:
+            ns = data["namespace"].strip("::") # bug? in parser
+            out.append(create_enum_bindigs(data, [ns], handle) + ";")
+
+        for data in hdr.classes.values():
+            ns = data["namespace"]
+            # workaround: parser does not save debug in same way as for funcs
+            data["debug"] = read_line(data["filename"], data["line_number"])
+            out.append(create_class_bindings(data, [ns], handle) + ";")
+
+        for data in hdr.functions:
+            ns = data["namespace"].strip("::") # bug? in parser
+            if include_in_api(data):
+                out.append(create_function_bindings(data, [ns], handle) + ";")
+
+    includes = "\n".join("#include <{0}>".format(i) for i in includes)
+    template_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "automatic_bindings.cpp.in")
+    with open(template_file, "r") as f:
+        template = f.read()
+    out = template.format(includes=includes, code="\n\t".join(out))
+    with open(fout, "w",) as f:
+        f.write(out)
diff --git a/SDK/C++/public/python/module.cpp b/SDK/C++/public/python/module.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5ba4e9e2a48f24cbe34353796bf23728f2f93656
--- /dev/null
+++ b/SDK/C++/public/python/module.cpp
@@ -0,0 +1,20 @@
+
+#include "module.hpp"
+
+#include <voltu/initialise.hpp>
+#include <voltu/types/errors.hpp>
+
+namespace py = pybind11;
+
+void py_exceptions(pybind11::module& m) {
+	py::register_exception<voltu::exceptions::Exception>(m, "Error");
+	py::register_exception<voltu::exceptions::BadImageChannel>(m, "ErrorBadImageChannel");
+	py::register_exception<voltu::exceptions::NoFrame>(m, "ErrorNoFrame");
+	py::register_exception<voltu::exceptions::AlreadyInit>(m, "ErrorAlreadyInit");
+	py::register_exception<voltu::exceptions::BadSourceURI>(m, "ErrorBadSourceURI");
+}
+
+PYBIND11_MODULE(voltu, m) {
+	py_exceptions(m);
+	py_automatic_bindings(m);
+}
diff --git a/SDK/C++/public/python/module.hpp b/SDK/C++/public/python/module.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b605cf7f6b7030701858cf8d4cf2ffbba95636bf
--- /dev/null
+++ b/SDK/C++/public/python/module.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <pybind11/pybind11.h>
+
+void py_automatic_bindings(pybind11::module& m);
diff --git a/SDK/C++/public/python/types/image.hpp b/SDK/C++/public/python/types/image.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..15cd41034f8cd748dd9413793e1bbe9ce8825496
--- /dev/null
+++ b/SDK/C++/public/python/types/image.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <pybind11/pybind11.h>
+#include <pybind11/numpy.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+#include <voltu/types/channel.hpp>
+#include <voltu/types/image.hpp>
+
+#include <cstdint>
+#include <exception>
+
+namespace pybind11 {
+namespace detail {
+
+template<>
+struct type_caster<voltu::ImageData> {
+public:
+    PYBIND11_TYPE_CASTER(voltu::ImageData, _("Image"));
+
+    bool load(py::handle src, bool convert) {
+        throw std::runtime_error("ImageData conversion python->c++ not supported");
+        return true;
+    }
+
+    static py::handle cast(const voltu::ImageData& src, py::return_value_policy policy, py::handle parent) {
+        switch(src.format) {
+            case voltu::ImageFormat::kFloat32:
+                return py::array(
+                    { size_t(src.height), size_t(src.width) },
+                    { size_t(src.pitch), sizeof(float) },
+                    reinterpret_cast<float*>(src.data)
+                ).release();
+
+            case voltu::ImageFormat::kBGRA8:
+                return py::array(
+                    { size_t(src.height), size_t(src.width), size_t(4) },
+                    { size_t(src.pitch), size_t(4)*sizeof(uint8_t), size_t(1)*sizeof(uint8_t) },
+                    reinterpret_cast<uint8_t*>(src.data)
+                ).release();
+
+            default:
+                return py::array();
+        }
+    }
+};
+
+} // namespace detail
+} // namespace pybind11
+
diff --git a/SDK/C++/public/samples/basic_file/main.cpp b/SDK/C++/public/samples/basic_file/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c983864275a217fc1d2cc6628012dc698f8ad19e
--- /dev/null
+++ b/SDK/C++/public/samples/basic_file/main.cpp
@@ -0,0 +1,56 @@
+#include <voltu/voltu.hpp>
+#include <voltu/opencv.hpp>
+#include <iostream>
+#include <thread>
+#include <chrono>
+
+#include <opencv2/highgui.hpp>
+
+using std::cout;
+using std::endl;
+using std::string;
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) return -1;
+
+	auto vtu = voltu::instance();
+
+	if (!vtu->open(argv[1]))
+	{
+		cout << "Could not open source" << endl;
+		return -1;
+	}
+
+	while (vtu->listRooms().size() == 0)
+	{
+		std::this_thread::sleep_for(std::chrono::milliseconds(100));
+	}
+
+	auto room = vtu->getRoom(vtu->listRooms().front());
+	if (!room)
+	{
+		cout << "Could not get room" << endl;
+		return -1;
+	}
+	cout << "Room Name: " << room->getName() << endl;
+
+	while (room->active())
+	{
+		if (!room->waitNextFrame(1000)) break;
+
+		auto frame = room->getFrame();
+		auto imgset = frame->getImageSet(voltu::Channel::kDepth);
+
+		for (auto img : imgset)
+		{
+			cv::Mat m;
+			voltu::cv::visualise(img, m);
+			cv::imshow(string("Image-") + img->getName(), m);
+		}
+
+		if (cv::waitKey(1) == 27) break;
+	}
+
+	return 0;
+}
diff --git a/SDK/C++/public/samples/basic_test/main.cpp b/SDK/C++/public/samples/basic_test/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3f41fc38a592097f032cedb90836c142381036aa
--- /dev/null
+++ b/SDK/C++/public/samples/basic_test/main.cpp
@@ -0,0 +1,36 @@
+#include <voltu/voltu.hpp>
+#include <iostream>
+#include <thread>
+#include <chrono>
+
+using std::cout;
+using std::endl;
+using std::string;
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) return -1;
+
+	auto vtu = voltu::instance();
+
+	if (!vtu)
+	{
+		cout << "Failed to start VolTu" << endl;
+		return -1;
+	}
+
+	if (!vtu->open(argv[1]))
+	{
+		cout << "Could not open source" << endl;
+		return -1;
+	}
+
+	while (vtu->listRooms().size() == 0)
+	{
+		std::this_thread::sleep_for(std::chrono::milliseconds(100));
+	}
+
+	auto room = vtu->getRoom(vtu->listRooms().front());
+
+	return 0;
+}
diff --git a/SDK/C++/public/samples/basic_virtual_cam/main.cpp b/SDK/C++/public/samples/basic_virtual_cam/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f1b2634b7135bfb3c303fdd6fd54e81da6a69a38
--- /dev/null
+++ b/SDK/C++/public/samples/basic_virtual_cam/main.cpp
@@ -0,0 +1,80 @@
+#include <voltu/voltu.hpp>
+#include <voltu/opencv.hpp>
+#include <iostream>
+#include <thread>
+#include <chrono>
+
+#include <opencv2/highgui.hpp>
+
+using std::cout;
+using std::endl;
+using std::string;
+
+int main(int argc, char **argv)
+{
+	if (argc != 2) return -1;
+
+	auto vtu = voltu::instance();
+
+	if (!vtu->open(argv[1]))
+	{
+		cout << "Could not open source" << endl;
+		return -1;
+	}
+
+	while (vtu->listRooms().size() == 0)
+	{
+		std::this_thread::sleep_for(std::chrono::milliseconds(100));
+	}
+
+	auto room = vtu->getRoom(vtu->listRooms().front());
+	if (!room)
+	{
+		cout << "Could not get room" << endl;
+		return -1;
+	}
+	cout << "Room Name: " << room->getName() << endl;
+
+	auto obs = vtu->createObserver();
+
+	obs->setResolution(1280, 720);
+	obs->setFocalLength(900);
+	obs->setStereo(false);
+
+	Eigen::Matrix4f pose;
+	pose.setIdentity();
+	float posz = 0.0f;
+	float dz = 0.05f;
+
+	while (room->active())
+	{
+		// Note: waitNextFrame is optional, getFrame will get old frame otherwise
+		//if (!room->waitNextFrame(1000)) break;
+		auto rframe = room->getFrame();
+
+		Eigen::Affine3f aff = Eigen::Affine3f::Identity();
+		aff.translate(Eigen::Vector3f(0.0f, 0.0f, posz));
+		pose = aff.matrix();
+		posz += dz;
+		if (posz > 1.5f) dz = -dz;
+		if (posz < -1.0f) dz = -dz;
+		obs->setPose(pose);
+
+		obs->submit(rframe);
+		if (!obs->waitCompletion(1000)) break;
+		
+		auto cframe = obs->getFrame();
+		auto imgset = cframe->getImageSet(voltu::Channel::kColour);
+
+		for (auto img : imgset)
+		{
+			cv::Mat m;
+			voltu::cv::convert(img, m);
+			cv::imshow(string("Camera-") + img->getName(), m);
+		}
+
+		if (cv::waitKey(20) == 27) break;
+	}
+
+	return 0;
+}
diff --git a/SDK/C++/public/voltu.cpp b/SDK/C++/public/voltu.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..25b0f4019ccd8c5df7c3090b7c7ea3198162baef
--- /dev/null
+++ b/SDK/C++/public/voltu.cpp
@@ -0,0 +1,95 @@
+#include <voltu/initialise.hpp>
+#include <voltu/types/errors.hpp>
+#include <voltu/voltu.hpp>
+
+#if defined(WIN32)
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+static bool g_init = false;
+
+typedef void* Library;
+
+static Library loadLibrary(const char *file)
+{
+#if defined(WIN32)
+	return (Library)LoadLibraryEx(file, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+#else
+	return (Library)dlopen(file, RTLD_LOCAL | RTLD_NOW);
+#endif
+}
+
+static void *getFunction(Library lib, const char *fname)
+{
+#if defined(WIN32)
+	return (void*)GetProcAddress((HMODULE)lib, fname);
+#else
+	return dlsym(lib, fname);
+#endif
+}
+
+
+static void unloadLibrary(Library lib)
+{
+	if (!lib) return;
+
+#if defined(WIN32)
+	FreeLibrary((HMODULE)lib);
+#else
+	dlclose(lib);
+#endif
+}
+
+static std::string locateLibrary()
+{
+#if defined(WIN32)
+	return "voltu.dll";
+#else
+	return "libvoltu.so";
+#endif
+}
+
+std::shared_ptr<voltu::System> voltu::instance()
+{
+	if (g_init) return nullptr;
+	
+	std::string name = locateLibrary();
+	Library handle = loadLibrary(name.c_str());
+
+	if (handle)
+	{
+		voltu::System* (*init)();
+		init = (voltu::System* (*)())getFunction(handle, "voltu_initialise");
+
+		if (init)
+		{
+			g_init = true;
+			std::shared_ptr<voltu::System> instance(init());
+
+			if (!instance)
+			{
+				throw voltu::exceptions::LibraryLoadFailed();
+			}
+
+			auto ver = instance->getVersion();
+			if (ver.major != VOLTU_VERSION_MAJOR || ver.minor != VOLTU_VERSION_MINOR)
+			{
+				throw voltu::exceptions::LibraryVersionMismatch();
+			}
+
+			return instance;
+		}
+		else
+		{
+			throw voltu::exceptions::LibraryLoadFailed();
+		}
+	}
+	else
+	{
+		throw voltu::exceptions::LibraryLoadFailed();
+	}
+
+	return nullptr;
+}
diff --git a/SDK/C++/public/voltu_cv.cpp b/SDK/C++/public/voltu_cv.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3b77155b6450560eb7808f7294013926fea382e7
--- /dev/null
+++ b/SDK/C++/public/voltu_cv.cpp
@@ -0,0 +1,50 @@
+#include <voltu/opencv.hpp>
+
+#include <opencv2/imgproc.hpp>
+
+void voltu::cv::convert(voltu::ImagePtr img, ::cv::Mat &mat)
+{
+	voltu::ImageData data = img->getHost();
+
+	if (data.format == voltu::ImageFormat::kBGRA8)
+	{
+		mat = ::cv::Mat(data.height, data.width, CV_8UC4, data.data);
+	}
+	else if (data.format == voltu::ImageFormat::kFloat32)
+	{
+		mat = ::cv::Mat(data.height, data.width, CV_32FC1, data.data);
+	}
+	else
+	{
+		mat = ::cv::Mat();
+	}
+}
+
+void voltu::cv::convert(voltu::ImagePtr img, ::cv::cuda::GpuMat &mat)
+{
+
+}
+
+void voltu::cv::visualise(voltu::ImagePtr img, ::cv::Mat &mat)
+{
+	voltu::ImageData data = img->getHost();
+
+	if (data.format == voltu::ImageFormat::kBGRA8)
+	{
+		voltu::cv::convert(img, mat);
+	}
+	else if (data.format == voltu::ImageFormat::kFloat32)
+	{
+		::cv::Mat tmp;
+		voltu::cv::convert(img, tmp);
+
+		::cv::normalize(tmp, tmp, 0, 255, ::cv::NORM_MINMAX);
+		tmp.convertTo(tmp, CV_8U);
+		
+		//#if OPENCV_VERSION >= 40102
+		//cv::applyColorMap(tmp, mat, cv::COLORMAP_TURBO);
+		//#else
+		::cv::applyColorMap(tmp, mat, ::cv::COLORMAP_INFERNO);
+		//#endif
+	}
+}