diff --git a/common.pri b/common.pri index da1fee00..f5346d1a 100644 --- a/common.pri +++ b/common.pri @@ -21,3 +21,5 @@ include(freetype.pri) include(fontconfig.pri) include(scintilla.pri) include(c++11.pri) +include(hidapi.pri) +include(spnav.pri) diff --git a/hidapi.pri b/hidapi.pri new file mode 100644 index 00000000..66f56a24 --- /dev/null +++ b/hidapi.pri @@ -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 +} diff --git a/openscad.pro b/openscad.pro index f16f6f44..f677b994 100644 --- a/openscad.pro +++ b/openscad.pro @@ -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 \ \ diff --git a/spnav.pri b/spnav.pri new file mode 100644 index 00000000..3becda1d --- /dev/null +++ b/spnav.pri @@ -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 +} diff --git a/src/AboutDialog.html b/src/AboutDialog.html index d079c233..903a3bd5 100644 --- a/src/AboutDialog.html +++ b/src/AboutDialog.html @@ -126,6 +126,8 @@ Please visit this link for a copy of the license: OskarLinde, Giles Bathgate, chrysn, diff --git a/src/MainWindow.h b/src/MainWindow.h index c1a8c96d..bd8d0dc1 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -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); diff --git a/src/QGLView.cc b/src/QGLView.cc index cabcb5b9..19b94575 100644 --- a/src/QGLView.cc +++ b/src/QGLView.cc @@ -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(); +} + diff --git a/src/QGLView.h b/src/QGLView.h index e675e539..55ef4926 100644 --- a/src/QGLView.h +++ b/src/QGLView.h @@ -14,6 +14,7 @@ #include #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); diff --git a/src/SixDoFDev.cc b/src/SixDoFDev.cc new file mode 100644 index 00000000..b0ee816b --- /dev/null +++ b/src/SixDoFDev.cc @@ -0,0 +1,218 @@ +/* + * OpenSCAD (www.openscad.org) + * Copyright (C) 2009-2011 Clifford Wolf and + * Marius Kintel + * + * 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 +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#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; + diff --git a/src/SixDoFDev.h b/src/SixDoFDev.h new file mode 100644 index 00000000..681578b2 --- /dev/null +++ b/src/SixDoFDev.h @@ -0,0 +1,155 @@ +/* + * OpenSCAD (www.openscad.org) + * Copyright (C) 2009-2011 Clifford Wolf and + * Marius Kintel + * + * 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 + +#include + +#include +#include + + +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; + diff --git a/src/mainwin.cc b/src/mainwin.cc index 1a03c511..2c947a93 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -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) diff --git a/src/openscad.cc b/src/openscad.cc index 8e38d198..2704f4a5 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -57,6 +57,7 @@ #include "csgnode.h" #include "CSGTreeEvaluator.h" +#include "SixDoFDev.h" #include @@ -753,6 +754,7 @@ int gui(vector &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;