Add support for Space Navigator devices using both HIDAPI and libspnav.

This commit is contained in:
Jochen Kunz 2016-09-28 15:32:43 +02:00 committed by Torsten Paul
parent 5cb40a2aee
commit bb03df4747
12 changed files with 572 additions and 47 deletions

View file

@ -21,3 +21,5 @@ include(freetype.pri)
include(fontconfig.pri)
include(scintilla.pri)
include(c++11.pri)
include(hidapi.pri)
include(spnav.pri)

38
hidapi.pri Normal file
View file

@ -0,0 +1,38 @@
# Detect hidapi, then use this priority list to determine
# which library to use:
#
# Priority
# 1. HIDAPI_INCLUDEPATH / HIDAPI_LIBPATH (qmake parameter, not checked it given on commandline)
# 2. OPENSCAD_LIBRARIES (environment variable)
# 3. system's standard include paths from pkg-config
hidapi {
# read environment variables
OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
HIDAPI_DIR = $$(HIDAPIDIR)
!isEmpty(OPENSCAD_LIBRARIES_DIR) {
isEmpty(HIDAPI_INCLUDEPATH) {
exists($$OPENSCAD_LIBRARIES_DIR/include/hidapi) {
HIDAPI_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/hidapi
HIDAPI_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
}
}
}
isEmpty(HIDAPI_INCLUDEPATH) {
HIDAPI_CFLAGS = $$system("pkg-config --cflags hidapi-libusb")
} else {
HIDAPI_CFLAGS = -I$$HIDAPI_INCLUDEPATH
}
isEmpty(HIDAPI_LIBPATH) {
HIDAPI_LIBS = $$system("pkg-config --libs hidapi-libusb")
} else {
HIDAPI_LIBS = -L$$HIDAPI_LIBPATH -lhidapi-libusb
}
QMAKE_CXXFLAGS += $$HIDAPI_CFLAGS
LIBS += $$HIDAPI_LIBS
}

View file

@ -149,6 +149,7 @@ netbsd* {
# See Dec 2011 OpenSCAD mailing list, re: CGAL/GCC bugs.
*g++* {
QMAKE_CXXFLAGS *= -fno-strict-aliasing
QMAKE_CXXFLAGS += -std=c++11
QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-local-typedefs # ignored before 4.8
}
@ -184,6 +185,8 @@ CONFIG += freetype
CONFIG += fontconfig
CONFIG += gettext
CONFIG += libxml2
CONFIG += hidapi
CONFIG += spnav
#Uncomment the following line to enable the QScintilla editor
!nogui {
@ -261,6 +264,7 @@ HEADERS += src/version_check.h \
src/ThrownTogetherRenderer.h \
src/CGAL_OGL_Polyhedron.h \
src/OGL_helper.h \
src/SixDoFDev.h \
src/QGLView.h \
src/GLView.h \
src/MainWindow.h \
@ -432,6 +436,7 @@ SOURCES += \
src/OpenCSGWarningDialog.cc \
src/editor.cc \
src/GLView.cc \
src/SixDoFDev.cc \
src/QGLView.cc \
src/AutoUpdater.cc \
\

37
spnav.pri Normal file
View file

@ -0,0 +1,37 @@
# Detect spnav, then use this priority list to determine
# which library to use:
#
# Priority
# 1. SPNAV_INCLUDEPATH / SPNAV_LIBPATH (qmake parameter, not checked it given on commandline)
# 2. OPENSCAD_LIBRARIES (environment variable)
# 3. system's standard include paths from pkg-config
spnav {
# read environment variables
OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
SPNAV_DIR = $$(SPNAVDIR)
!isEmpty(OPENSCAD_LIBRARIES_DIR) {
isEmpty(SPNAV_INCLUDEPATH) {
exists($$OPENSCAD_LIBRARIES_DIR/include) {
SPNAV_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include
SPNAV_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
} else {
SPNAV_INCLUDEPATH = $$LIBRARIES_DIR/include
SPNAV_LIBPATH = $$LIBRARIES_DIR/lib
}
}
}
isEmpty(SPNAV_INCLUDEPATH) {
SPNAV_CFLAGS = -I/usr/include
}
isEmpty(SPNAV_LIBPATH) {
SPNAV_LIBS = -L/usr/lib -lspnav
}
QMAKE_CXXFLAGS += $$SPNAV_CFLAGS
LIBS += $$SPNAV_LIBS
}

View file

@ -126,6 +126,8 @@ Please visit this link for a copy of the license: <a href="http://www.gnu.org/li
</p>
<p>
Jochen Kunz,
Gert Menke,
<a href="https://github.com/OskarLinde">OskarLinde</a>,
<a href="https://github.com/GilesBathgate">Giles Bathgate</a>,
<a href="https://github.com/chrysn">chrysn</a>,

View file

@ -166,6 +166,7 @@ private slots:
// Mac OSX FindBuffer support
void findBufferChanged();
void updateFindBuffer(QString);
bool event(QEvent* event);
protected:
virtual bool eventFilter(QObject* obj, QEvent *event);

View file

@ -255,65 +255,35 @@ void QGLView::mouseMoveEvent(QMouseEvent *event)
) {
// Left button rotates in xz, Shift-left rotates in xy
// On Mac, Ctrl-Left is handled as right button on other platforms
cam.object_rot.x() += dy;
if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0)
cam.object_rot.y() += dx;
rotate( dy, dx, 0.0);
else
cam.object_rot.z() += dx;
normalizeAngle(cam.object_rot.x());
normalizeAngle(cam.object_rot.y());
normalizeAngle(cam.object_rot.z());
rotate( dy, 0.0, dx);
} else {
// Right button pans in the xz plane
// Middle button pans in the xy plane
// Shift-right and Shift-middle zooms
if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0) {
cam.zoom(-12.0 * dy);
translate( 0.0, -12.0 * dy, 0.0);
} else {
double mx = +(dx) * 3.0 * cam.zoomValue() / QWidget::width();
double mz = -(dy) * 3.0 * cam.zoomValue() / QWidget::height();
double my = 0;
double mx = +(dx) * 3.0 * cam.zoomValue() / QWidget::width();
double mz = -(dy) * 3.0 * cam.zoomValue() / QWidget::height();
double my = 0;
#if (QT_VERSION < QT_VERSION_CHECK(4, 7, 0))
if (event->buttons() & Qt::MidButton) {
if (event->buttons() & Qt::MidButton)
#else
if (event->buttons() & Qt::MiddleButton) {
if (event->buttons() & Qt::MiddleButton)
#endif
my = mz;
mz = 0;
// actually lock the x-position
// (turns out to be easier to use than xy panning)
mx = 0;
}
Matrix3d aax, aay, aaz, tm3;
aax = Eigen::AngleAxisd(-(cam.object_rot.x()/180) * M_PI, Vector3d::UnitX());
aay = Eigen::AngleAxisd(-(cam.object_rot.y()/180) * M_PI, Vector3d::UnitY());
aaz = Eigen::AngleAxisd(-(cam.object_rot.z()/180) * M_PI, Vector3d::UnitZ());
tm3 = Matrix3d::Identity();
tm3 = aaz * (aay * (aax * tm3));
Matrix4d tm;
tm = Matrix4d::Identity();
for (int i=0;i<3;i++) for (int j=0;j<3;j++) tm(j,i)=tm3(j,i);
Matrix4d vec;
vec <<
0, 0, 0, mx,
0, 0, 0, my,
0, 0, 0, mz,
0, 0, 0, 1
;
tm = tm * vec;
cam.object_trans.x() += tm(0,3);
cam.object_trans.y() += tm(1,3);
cam.object_trans.z() += tm(2,3);
{
my = mz;
mz = 0;
// actually lock the x-position
// (turns out to be easier to use than xy panning)
mx = 0;
}
translate( mx, my, mz);
}
}
updateGL();
emit doAnimateUpdate();
}
last_mouse = this_mouse;
}
@ -363,3 +333,81 @@ void QGLView::setOrthoMode(bool enabled) {
if (enabled) this->cam.setProjection(Camera::ORTHOGONAL);
else this->cam.setProjection(Camera::PERSPECTIVE);
}
void
QGLView::SixDoFDev_translate( SixDoFDevEventTranslate* event) {
if (event->x == 0 && event->y == 0 && event->z == 0) {
return;
}
translate( event->x * 0.0001 * cam.zoomValue(), event->y * 0.1, -event->z * 0.0001 * cam.zoomValue());
}
void
QGLView::SixDoFDev_rotate( SixDoFDevEventRotate* event) {
if (event->x == 0 && event->y == 0 && event->z == 0) {
return;
}
rotate( event->x * 0.01, event->y * -0.01, event->z * -0.01);
}
void
QGLView::SixDoFDev_button( SixDoFDevEventButton* event) {
if ((event->down & 1) != 0) {
cam.object_trans << 0,0,0;
updateGL();
}
if ((event->down & 2) != 0) {
resetView();
updateGL();
}
}
void
QGLView::translate( double x, double y, double z) {
cam.zoom( y);
y = 0;
Matrix3d aax, aay, aaz, tm3;
aax = Eigen::AngleAxisd( -(cam.object_rot.x() / 180) * M_PI, Vector3d::UnitX());
aay = Eigen::AngleAxisd( -(cam.object_rot.y() / 180) * M_PI, Vector3d::UnitY());
aaz = Eigen::AngleAxisd( -(cam.object_rot.z() / 180) * M_PI, Vector3d::UnitZ());
tm3 = Matrix3d::Identity();
tm3 = aaz * (aay * (aax * tm3));
Matrix4d tm;
tm = Matrix4d::Identity();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
tm( j, i) = tm3( j, i);
}
}
Matrix4d vec;
vec <<
0, 0, 0, x,
0, 0, 0, y,
0, 0, 0, z,
0, 0, 0, 1
;
tm = tm * vec;
cam.object_trans.x() += tm( 0, 3);
cam.object_trans.y() += tm( 1, 3);
cam.object_trans.z() += tm( 2, 3);
updateGL();
emit doAnimateUpdate();
}
void
QGLView::rotate( double x, double y, double z) {
cam.object_rot.x() += x;
cam.object_rot.y() += y;
cam.object_rot.z() += z;
normalizeAngle( cam.object_rot.x());
normalizeAngle( cam.object_rot.y());
normalizeAngle( cam.object_rot.z());
updateGL();
emit doAnimateUpdate();
}

View file

@ -14,6 +14,7 @@
#include <Eigen/Geometry>
#include "GLView.h"
#include "renderer.h"
#include "SixDoFDev.h"
class QGLView :
#ifdef USE_QOPENGLWIDGET
@ -21,7 +22,7 @@ class QGLView :
#else
public QGLWidget,
#endif
public GLView
public GLView, public SixDoFDevEventHandler
{
Q_OBJECT
Q_PROPERTY(bool showFaces READ showFaces WRITE setShowFaces);
@ -66,6 +67,10 @@ public slots:
inline void updateGL() { update(); }
#endif
void SixDoFDev_translate( SixDoFDevEventTranslate* event);
void SixDoFDev_rotate( SixDoFDevEventRotate* event);
void SixDoFDev_button( SixDoFDevEventButton* event);
public:
QLabel *statusLabel;
#ifdef USE_QOPENGLWIDGET
@ -83,6 +88,8 @@ private:
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void translate( double x, double y, double z);
void rotate( double x, double y, double z);
void initializeGL();
void resizeGL(int w, int h);

218
src/SixDoFDev.cc Normal file
View file

@ -0,0 +1,218 @@
/*
* OpenSCAD (www.openscad.org)
* Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
* Marius Kintel <marius@kintel.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* As a special exception, you have permission to link this program
* with the CGAL library and distribute executables, as long as you
* follow the requirements of the GNU GPL in regard to all of the
* software in the executable aside from CGAL.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* Initial implementation by Jochen Kunz and Gert Menke provided as
* Public Domain.
*/
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <thread>
#include <QWidget>
#include <QApplication>
#include <hidapi.h>
#include <spnav.h>
#include "SixDoFDev.h"
#include "printutils.h"
#define WSTRLEN 256
#define BUFLEN 16
#define VENDOR_ID 0x046d
#define PRODUCT_ID 0xc626
using namespace std;
const QEvent::Type SixDoFDevEvent::my_type = static_cast< QEvent::Type>( QEvent::registerEventType());
static void
sleep_iter( void) {
struct timespec schnarch = { 1, 0};
while (nanosleep( &schnarch, &schnarch) < 0) {};
}
class SixDoFDev::Impl {
private:
SixDoFDev& parent;
void hidapi_input( hid_device* hid_dev);
void spnav_input( void);
public:
Impl( SixDoFDev& parent);
void operator()( void);
};
SixDoFDev::Impl::Impl( SixDoFDev& p) :
parent( p) {
}
void
SixDoFDev::Impl::operator()( void) {
if (hid_init() < 0) {
PRINTD( "Can't hid_init().\n");
return;
}
for (;; sleep_iter()) {
if (spnav_open() >= 0) {
spnav_input();
}
// Try a list of product IDs. But up to now I know of only one.
hid_device* hid_dev;
hid_dev = hid_open( VENDOR_ID, PRODUCT_ID, NULL);
if (hid_dev != NULL) {
hidapi_input( hid_dev);
}
}
}
void
SixDoFDev::Impl::spnav_input( void) {
spnav_event spn_ev;
while (spnav_wait_event( &spn_ev)) {
QWidget* widget = QApplication::activeWindow();
if (widget == 0) {
continue;
}
if (spn_ev.type == SPNAV_EVENT_MOTION) {
if (spn_ev.motion.x != 0 || spn_ev.motion.y != 0 || spn_ev.motion.z != 0) {
QEvent* event = new SixDoFDevEventTranslate(
spn_ev.motion.x, -spn_ev.motion.z, -spn_ev.motion.y);
QCoreApplication::postEvent( widget, event);
}
if (spn_ev.motion.rx != 0 || spn_ev.motion.ry != 0 || spn_ev.motion.rz != 0) {
QEvent* event = new SixDoFDevEventRotate(
spn_ev.motion.rx, -spn_ev.motion.rz, -spn_ev.motion.ry);
QCoreApplication::postEvent( widget, event);
}
} else {
if (spn_ev.button.press) {
QEvent* event = new SixDoFDevEventButton( 1 << spn_ev.button.bnum, 0);
QCoreApplication::postEvent( widget, event);
} else {
QEvent* event = new SixDoFDevEventButton( 0, 1 << spn_ev.button.bnum);
QCoreApplication::postEvent( widget, event);
}
}
}
spnav_close();
}
void
SixDoFDev::Impl::hidapi_input( hid_device* hid_dev) {
unsigned char buf[ BUFLEN];
int len;
uint16_t buttons = 0;
while ((len = hid_read( hid_dev, buf, BUFLEN)) > 0) {
// QWidget* widget = QApplication::focusWidget(); doesn't do the tric.
QWidget* widget = QApplication::activeWindow();
if (widget == 0) {
continue;
}
QEvent* event = 0;
if ((buf[ 0] == 1 || buf[ 0] == 2) && len == 7) {
// Values are in the range -10..10 at min. speed and -2595..2595 at max. speed.
int16_t x_value = buf[ 1] | buf[ 2] << 8;
int16_t y_value = buf[ 3] | buf[ 4] << 8;
int16_t z_value = buf[ 5] | buf[ 6] << 8;
if (x_value == 0 && y_value == 0 && z_value == 0) {
continue;
}
if (buf[ 0] == 1) {
event = new SixDoFDevEventTranslate( x_value, y_value, z_value);
} else {
event = new SixDoFDevEventRotate( x_value, y_value, z_value);
}
}
if (buf[ 0] == 3 && len == 3) {
// Is either 0, 1 or 2 on MacOS.
uint16_t bitmask = buf[ 1] | buf[ 2] << 8;
uint16_t down = bitmask;
uint16_t up = 0;
if (bitmask == 0) {
up = buttons;
buttons = 0;
} else {
buttons |= bitmask;
}
if (down != 0 || up != 0) {
event = new SixDoFDevEventButton( down, up);
}
}
if (event != 0) {
QCoreApplication::postEvent( widget, event);
}
}
hid_close( hid_dev);
}
SixDoFDev::SixDoFDev( void) :
pimpl_thread( NULL) {
};
void
SixDoFDev::operator()( void) {
pimpl_thread = new std::thread( Impl( *this));
pimpl_thread->detach();
};
SixDoFDev::~SixDoFDev( void) {
if (pimpl_thread != NULL) {
delete pimpl_thread;
}
};
SixDoFDev spacenav;

155
src/SixDoFDev.h Normal file
View file

@ -0,0 +1,155 @@
/*
* OpenSCAD (www.openscad.org)
* Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
* Marius Kintel <marius@kintel.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* As a special exception, you have permission to link this program
* with the CGAL library and distribute executables, as long as you
* follow the requirements of the GNU GPL in regard to all of the
* software in the executable aside from CGAL.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#pragma once
#include <stdint.h>
#include <thread>
#include <QObject>
#include <QEvent>
class SixDoFDevEventTranslate;
class SixDoFDevEventRotate;
class SixDoFDevEventButton;
class SixDoFDevEventHandler {
public:
virtual ~SixDoFDevEventHandler( void) {};
virtual void SixDoFDev_translate( SixDoFDevEventTranslate* event) = 0;
virtual void SixDoFDev_rotate( SixDoFDevEventRotate* event) = 0;
virtual void SixDoFDev_button( SixDoFDevEventButton* event) = 0;
};
class SixDoFDevEvent :
public QEvent {
public:
SixDoFDevEvent( void) : QEvent( my_type) {};
virtual ~SixDoFDevEvent( void) {};
virtual void deliver( SixDoFDevEventHandler* receiver) = 0;
static const QEvent::Type my_type;
};
class SixDoFDevEventTranslate :
public SixDoFDevEvent {
public:
const int x;
const int y;
const int z;
SixDoFDevEventTranslate( int u, int v, int w) :
x( u),
y( v),
z( w) {
}
void deliver( SixDoFDevEventHandler* receiver) {
receiver->SixDoFDev_translate( this);
}
};
class SixDoFDevEventRotate :
public SixDoFDevEvent {
public:
const int x;
const int y;
const int z;
SixDoFDevEventRotate( int u, int v, int w) :
x( u),
y( v),
z( w) {
}
void deliver( SixDoFDevEventHandler* receiver) {
receiver->SixDoFDev_rotate( this);
}
};
class SixDoFDevEventButton :
public SixDoFDevEvent {
public:
const unsigned int down;
const unsigned int up;
SixDoFDevEventButton( unsigned int d, unsigned int u) :
down( d),
up( u) {
}
void deliver( SixDoFDevEventHandler* receiver) {
receiver->SixDoFDev_button( this);
}
};
class SixDoFDev {
public:
SixDoFDev( void);
~SixDoFDev( void);
void operator()( void);
private:
class Impl;
std::thread* pimpl_thread;
};
extern SixDoFDev spacenav;

View file

@ -59,6 +59,7 @@
#include "ThrownTogetherRenderer.h"
#include "CSGTreeNormalizer.h"
#include "QGLView.h"
#include "SixDoFDev.h"
#ifdef Q_OS_MAC
#include "CocoaUtils.h"
#endif
@ -1567,6 +1568,15 @@ void MainWindow::findBufferChanged() {
}
}
bool MainWindow::event(QEvent* event) {
if (event->type() == SixDoFDevEvent::my_type) {
static_cast< SixDoFDevEvent*>( event)->deliver( qglview);
event->accept();
return true;
}
return QMainWindow::event( event);
}
bool MainWindow::eventFilter(QObject* obj, QEvent *event)
{
if (obj == find_panel)

View file

@ -57,6 +57,7 @@
#include "csgnode.h"
#include "CSGTreeEvaluator.h"
#include "SixDoFDev.h"
#include <sstream>
@ -753,6 +754,7 @@ int gui(vector<string> &inputFiles, const fs::path &original_path, int argc, cha
}
app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
spacenav();
int rc = app.exec();
for(auto &mainw : scadApp->windowManager.getWindows()) delete mainw;
return rc;