diff options
35 files changed, 917 insertions, 276 deletions
@@ -186,8 +186,9 @@ attempt an MSVC build on Windows, please see this site: http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Windows To cross-build, first make sure that you have development tools -installed to get GCC. Then after you've cloned this git repository, run -the script that sets up the environment variables. +installed to get GCC. Then after you've cloned this git repository, +start a new clean shell and run the script that sets up the environment +variables. source ./scripts/setenv-mingw-xbuild.sh @@ -195,7 +196,9 @@ Then run the script to download & compile all the prerequisite libraries above: ./scripts/mingw-x-build-dependencies.sh -Then, build OpenSCAD and package it to an installer: +Note that this process can take several hours, as it uses the +http://mxe.cc system to cross-build many libraries. After it is +complete, build OpenSCAD and package it to an installer: ./scripts/release-common.sh mingw32 diff --git a/RELEASE_NOTES b/RELEASE_NOTES index f629cd6..4448e89 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,4 +1,4 @@ -OpenSCAD 2012.11 +OpenSCAD 2013.01 ================ Features: @@ -26,6 +26,8 @@ o cmd-line overrides using -D now also work for USEd modules o Modifier characters can now be used in front of if statements o rotate() with a vector argument with less that 3 elements used uninitialized variables, ending up being non-deterministic. o .csg files will now have relative filenames whenever possible +o Don't just ignore geometric nodes having zero volume/area - when doing difference/intersection, they tend to turn negative objects into positive ones. +o Always use utf-8 file encoding, also under Windows o A lot of build script fixes o Some other crash bugs fixes diff --git a/doc/TODO.txt b/doc/TODO.txt index be70c26..62db614 100644 --- a/doc/TODO.txt +++ b/doc/TODO.txt @@ -113,6 +113,7 @@ o Misc for confirmation. - Go through keyboard shortcuts and make them more conformant to platform standards - Show design info/stats (sizes, caches etc.) + - Support Voice Over and related technologies for vision-impaired users o Cmd-line - Add verbose option (PRINT command from mainwin.cc and progress output) @@ -135,6 +136,7 @@ o Hollow donut problem rendering keeps the hole, but renders slightly incorrect. o CGAL issues - CGAL doesn't handle almost planar polygons. Consider splitting into triangles ourselves. See WillamAdams/dodec.scad +o Look at the EPEC kernel for improved performance (suggested by Giles) LANGUAGE && BUILTINS -------------------- diff --git a/doc/release-checklist.txt b/doc/release-checklist.txt index e85e97c..6d53ad9 100644 --- a/doc/release-checklist.txt +++ b/doc/release-checklist.txt @@ -1,6 +1,8 @@ OpenSCAD Release Checklist -------------------------- +(See bottom of this file for how to build release binaries) + o Update VERSION environment variable export VERSION=2012.08 @@ -12,6 +14,7 @@ o Update VERSION environment variable scripts/publish-mingw-x.sh o Update RELEASE_NOTES +o Update copyright year in AboutDialog.html and mainwin.cc o Tag release git tag "openscad-$VERSION" @@ -19,33 +22,15 @@ o Tag release o build source package scripts/git-archive-all.py --prefix=openscad-$VERSION/ openscad-$VERSION.src.tar.gz -o build binaries - tar xzf openscad-$VERSION.src.tar.gz - cd openscad-$VERSION - Mac OS X - (For Qt-4.7.3: Remove /Developers/Applications/Qt/plugins/qmltooling) - ./scripts/publish-macosx.sh -> OpenSCAD-$VERSION.dmg - Linux: - 32-bit: run on a 32-bit machine or VM - 64-bit: run on a 64-bit machine or VM - ./scripts/release-common.sh -> openscad-$VERSION.x86-ARCH.tar.gz - (where ARCH will be detected and set to 32 or 64) - Windows mingw cross-build: FIXME 32 vs. 64 bit - ./scripts/publish-mingw-x.sh -> mingw32/Openscad-$VERSION.zip - -> mingw32/Openscad-$VERSION-Installer.exe - -o FIXME: Run some tests - -o Remove VERSION environment variable - - export VERSION= +o Sanity check; build a binary or two and manually run some tests o git push --tags -o Upload - - Github - Upload manually here: https://github.com/openscad/openscad/downloads - FIXME: Write a script +o Upload Source package + $ ./scripts/googlecode_upload.py -s 'Source Code' -p openscad -l Featured,Type-Source openscad-$VERSION.src.tar.gz + +o Remove VERSION environment variable + $ unset VERSION o Update web page o Write email to mailing list @@ -53,3 +38,33 @@ o Update external resources: - http://en.wikipedia.org/wiki/OpenSCAD o Notify package managers - Ubuntu: https://launchpad.net/~chrysn + - MacPorts: + + +Build and Upload Release Binaries +--------------------------------- + +$ export VERSION=<openscad version, e.g. 2013.01> +$ tar xzf openscad-$VERSION.src.tar.gz +$ cd openscad-$VERSION + +Mac OS X: + + $ ./scripts/publish-macosx.sh -> OpenSCAD-$VERSION.dmg + +Linux: + 32-bit: run on a 32-bit machine or VM + 64-bit: run on a 64-bit machine or VM + + $ ./scripts/release-common.sh -> openscad-$VERSION.x86-ARCH.tar.gz + (where ARCH will be detected and set to 32 or 64) + $ ./scripts/googlecode_upload.py -s 'Linux Binaries' -p openscad openscad-$VERSION.x86-ARCH.tar.gz -l Featured,OpSys-Linux,Type-Archive openscad-$VERSION.x86-ARCH.tar.gz + o Update web page with download links + +Windows mingw cross-build: FIXME 32 vs. 64 bit + + $ ./scripts/publish-mingw-x.sh -> mingw32/Openscad-$VERSION.zip + -> mingw32/Openscad-$VERSION-Installer.exe + $ ./scripts/googlecode_upload.py -s 'Windows Binaries' -p openscad OpenSCAD-$VERSION.zip -l Featured,OpSys-Windows,Type-Archive OpenSCAD-$VERSION.zip + $ ./scripts/googlecode_upload.py -s 'Windows Installer' -p openscad OpenSCAD-$VERSION-Installer.exe -l Featured,OpSys-Windows,Type-Installer OpenSCAD-$VERSION-Installer.exe + o Update web page with download links @@ -51,7 +51,7 @@ isEmpty(EIGEN_INCLUDEPATH) { netbsd*: EIGEN_INCLUDEPATH = /usr/pkg/include/eigen3 linux*|hurd*|unix: EIGEN_INCLUDEPATH = /usr/include/eigen3 macx: EIGEN_INCLUDEPATH = /opt/local/include/eigen3 - !exists(EIGEN_INCLUDEPATH) { + !exists($$EIGEN_INCLUDEPATH) { freebsd-g++: EIGEN_INCLUDEPATH = /usr/local/include/eigen2 netbsd*: EIGEN_INCLUDEPATH = /usr/pkg/include/eigen2 linux*|hurd*|unix*: EIGEN_INCLUDEPATH = /usr/include/eigen2 @@ -67,7 +67,11 @@ isEmpty(EIGEN_INCLUDEPATH) { } # EIGEN being under 'include/eigen[2-3]' needs special prepending -QMAKE_INCDIR_QT = $$EIGEN_INCLUDEPATH $$QMAKE_INCDIR_QT +contains(QT_VERSION, ^5\\..*) { + QMAKE_INCDIR = $$EIGEN_INCLUDEPATH $$QMAKE_INCDIR +} else { + QMAKE_INCDIR_QT = $$EIGEN_INCLUDEPATH $$QMAKE_INCDIR_QT +} # qmakespecs on netbsd prepend system includes, we need eigen first. netbsd* { diff --git a/scripts/googlecode_upload.py b/scripts/googlecode_upload.py new file mode 100755 index 0000000..188dd6c --- /dev/null +++ b/scripts/googlecode_upload.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# Google Code binary package uploader +# with Insturctions for uploading packages for OpenSCAD +# +# OpenSCAD Usage: +# +# 1. get a google account, get it added to the Google Code OpenSCAD project +# 2. go to https://code.google.com/hosting/settings for username & password +# ----- +# +# security note - +# +# it's not advisable to use a ~/.netrc file to store your password +# keep your googlecode password secret +# only upload from a secure machine +# user's personal data can be at risk if your account +# is compromised and a fake openscad were to be uploaded. +# notify the OpenSCAD maintainer if your computer is stolen or +# your google account is ever compromised. +# ----- +# 4. if you are making a Stable Release, check 'docs/release_checklist.txt' +# 5. create an OpenSCAD package (linux dev snapshot: ./scripts/release-common.sh) +# 6. Run this to do the upload: +# export SUMMARY="Linux x86-64 Snapshot" # replace as appropriate +# export PACKAGEFILE=openscad-2013.01.10.x86-64.tar.gz # replace as appropriate +# python ./scripts/googlecode_upload.py -s '$SUMMARY' -p openscad $PACKAGEFILE +# 7. It will ask for username. Use user.name@gmail.com (include the @ mail address) +# 8. It will ask for password. Copy/paste the password from the https google code settings page above +# Don't use the big bold password, use the 'plain font' password from the 'machine' line +# 9. Wait.... (there is no progress meter). It should say 'success' eventually. +# +# The rest of this file is original from Google with slight modifications by the +# OpenSCAD team. Modifications licensed under the same license as the +# original code from google - the Apache Software License 2.0: +# http://www.apache.org/licenses/LICENSE-2.0 + + +# +# Copyright 2006, 2007 Google Inc. All Rights Reserved. +# Author: danderson@google.com (David Anderson) +# +# Script for uploading files to a Google Code project. +# +# This is intended to be both a useful script for people who want to +# streamline project uploads and a reference implementation for +# uploading files to Google Code projects. +# +# To upload a file to Google Code, you need to provide a path to the +# file on your local machine, a small summary of what the file is, a +# project name, and a valid account that is a member or owner of that +# project. You can optionally provide a list of labels that apply to +# the file. The file will be uploaded under the same name that it has +# in your local filesystem (that is, the "basename" or last path +# component). Run the script with '--help' to get the exact syntax +# and available options. +# +# Note that the upload script requests that you enter your +# googlecode.com password. This is NOT your Gmail account password! +# This is the password you use on googlecode.com for committing to +# Subversion and uploading files. You can find your password by going +# to http://code.google.com/hosting/settings when logged in with your +# Gmail account. If you have already committed to your project's +# Subversion repository, the script will automatically retrieve your +# credentials from there (unless disabled, see the output of '--help' +# for details). +# +# If you are looking at this script as a reference for implementing +# your own Google Code file uploader, then you should take a look at +# the upload() function, which is the meat of the uploader. You +# basically need to build a multipart/form-data POST request with the +# right fields and send it to https://PROJECT.googlecode.com/files . +# Authenticate the request using HTTP Basic authentication, as is +# shown below. +# +# Licensed under the terms of the Apache Software License 2.0: +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Questions, comments, feature requests and patches are most welcome. +# Please direct all of these to the Google Code users group: +# http://groups.google.com/group/google-code-hosting + +"""Google Code file uploader script. +""" + +__author__ = 'danderson@google.com (David Anderson)' + +import httplib +import os.path +import optparse +import getpass +import base64 +import sys + + +def upload(file, project_name, user_name, password, summary, labels=None): + """Upload a file to a Google Code project's file server. + + Args: + file: The local path to the file. + project_name: The name of your project on Google Code. + user_name: Your Google account name. + password: The googlecode.com password for your account. + Note that this is NOT your global Google Account password! + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + + Returns: a tuple: + http_status: 201 if the upload succeeded, something else if an + error occured. + http_reason: The human-readable string associated with http_status + file_url: If the upload succeeded, the URL of the file on Google + Code, None otherwise. + """ + # The login is the user part of user@gmail.com. If the login provided + # is in the full user@domain form, strip it down. + if user_name.endswith('@gmail.com'): + user_name = user_name[:user_name.index('@gmail.com')] + + form_fields = [('summary', summary)] + if labels is not None: + form_fields.extend([('label', l.strip()) for l in labels]) + + content_type, body = encode_upload_request(form_fields, file) + + upload_host = '%s.googlecode.com' % project_name + upload_uri = '/files' + auth_token = base64.b64encode('%s:%s'% (user_name, password)) + headers = { + 'Authorization': 'Basic %s' % auth_token, + 'User-Agent': 'Googlecode.com uploader v0.9.4', + 'Content-Type': content_type, + } + + server = httplib.HTTPSConnection(upload_host) + server.request('POST', upload_uri, body, headers) + resp = server.getresponse() + server.close() + + if resp.status == 201: + location = resp.getheader('Location', None) + else: + location = None + return resp.status, resp.reason, location + + +def encode_upload_request(fields, file_path): + """Encode the given fields and file into a multipart form body. + + fields is a sequence of (name, value) pairs. file is the path of + the file to upload. The file will be uploaded to Google Code with + the same file name. + + Returns: (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla' + CRLF = '\r\n' + + body = [] + + # Add the metadata about the upload first + for key, value in fields: + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="%s"' % key, + '', + value, + ]) + + # Now add the file itself + file_name = os.path.basename(file_path) + f = open(file_path, 'rb') + file_content = f.read() + f.close() + + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="filename"; filename="%s"' + % file_name, + # The upload server determines the mime-type, no need to set it. + 'Content-Type: application/octet-stream', + '', + file_content, + ]) + + # Finalize the form body + body.extend(['--' + BOUNDARY + '--', '']) + + return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body) + + +def upload_find_auth(file_path, project_name, summary, labels=None, + user_name=None, password=None, tries=3): + """Find credentials and upload a file to a Google Code project's file server. + + file_path, project_name, summary, and labels are passed as-is to upload. + + Args: + file_path: The local path to the file. + project_name: The name of your project on Google Code. + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + config_dir: Path to Subversion configuration directory, 'none', or None. + user_name: Your Google account name. + tries: How many attempts to make. + """ + if user_name is None or password is None: + from netrc import netrc + authenticators = None + try: + authenticators = netrc().authenticators("code.google.com") + except: + print "Error accessing netrc authenticator. Trying alternate method" + if authenticators: + if user_name is None: + user_name = authenticators[0] + if password is None: + password = authenticators[2] + + while tries > 0: + if user_name is None: + # Read username if not specified or loaded from svn config, or on + # subsequent tries. + sys.stdout.write('Please enter your googlecode.com username: ') + sys.stdout.flush() + user_name = sys.stdin.readline().rstrip() + if password is None: + # Read password if not loaded from svn config, or on subsequent tries. + print 'Please enter your googlecode.com password.' + print '** Note that this is NOT your Gmail account password! **' + print 'It is the password you use to access Subversion repositories,' + print 'and can be found here: http://code.google.com/hosting/settings' + password = getpass.getpass() + + status, reason, url = upload(file_path, project_name, user_name, password, + summary, labels) + # Returns 403 Forbidden instead of 401 Unauthorized for bad + # credentials as of 2007-07-17. + if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]: + # Rest for another try. + user_name = password = None + tries = tries - 1 + else: + # We're done. + break + + return status, reason, url + + +def main(): + parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY ' + '-p PROJECT [options] FILE') + parser.add_option('-s', '--summary', dest='summary', + help='Short description of the file') + parser.add_option('-p', '--project', dest='project', + help='Google Code project name') + parser.add_option('-u', '--user', dest='user', + help='Your Google Code username') + parser.add_option('-w', '--password', dest='password', + help='Your Google Code password') + parser.add_option('-l', '--labels', dest='labels', + help='An optional list of comma-separated labels to attach ' + 'to the file') + + options, args = parser.parse_args() + + if not options.summary: + parser.error('File summary is missing.') + elif not options.project: + parser.error('Project name is missing.') + elif len(args) < 1: + parser.error('File to upload not provided.') + elif len(args) > 1: + parser.error('Only one file may be specified.') + + file_path = args[0] + + if options.labels: + labels = options.labels.split(',') + else: + labels = None + + status, reason, url = upload_find_auth(file_path, options.project, + options.summary, labels, + options.user, options.password) + if url: + print 'The file was uploaded successfully.' + print 'URL: %s' % url + return 0 + else: + print 'An error occurred. Your file was not uploaded.' + print 'Google Code upload server said: %s (%s)' % (reason, status) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh index 6313b2b..bfe0ede 100755 --- a/scripts/macosx-build-dependencies.sh +++ b/scripts/macosx-build-dependencies.sh @@ -13,11 +13,9 @@ # # Prerequisites: # - MacPorts: curl, cmake -# - Qt4 # # FIXME: # o Verbose option -# o Port to other platforms? # BASEDIR=$PWD/../libraries @@ -41,6 +39,25 @@ printUsage() echo " -c Force use of clang compiler" } +# FIXME: Support gcc/llvm/clang flags. Use -platform <whatever> to make this work? kintel 20130117 +build_qt() +{ + version=$1 + echo "Building Qt" $version "..." + cd $BASEDIR/src + rm -rf qt-everywhere-opensource-src-$version + if [ ! -f qt-everywhere-opensource-src-$version.tar.gz ]; then + curl -O http://releases.qt-project.org/qt4/source/qt-everywhere-opensource-src-$version.tar.gz + fi + tar xzf qt-everywhere-opensource-src-$version.tar.gz + cd qt-everywhere-opensource-src-$version + if $OPTION_32BIT; then + QT_32BIT="-arch x86" + fi + ./configure -prefix $DEPLOYDIR -release $QT_32BIT -arch x86_64 -opensource -confirm-license -fast -no-qt3support -no-svg -no-phonon -no-audio-backend -no-multimedia -no-javascript-jit -no-script -no-scripttools -no-declarative -no-xmlpatterns -nomake demos -nomake examples -nomake docs -nomake translations -no-webkit + make -j6 install +} + # Hack warning: gmplib is built separately in 32-bit and 64-bit mode # and then merged afterwards. gmplib's header files are dependent on # the CPU architecture on which configure was run and will be patched accordingly. @@ -365,8 +382,9 @@ fi echo "Using basedir:" $BASEDIR mkdir -p $SRCDIR $DEPLOYDIR +build_qt 4.8.4 build_eigen 3.1.2 -build_gmp 5.0.5 +build_gmp 5.1.0 build_mpfr 3.1.1 build_boost 1.51.0 # NB! For CGAL, also update the actual download URL in the function diff --git a/scripts/publish-macosx.sh b/scripts/publish-macosx.sh index a2ded8a..e22e5bd 100755 --- a/scripts/publish-macosx.sh +++ b/scripts/publish-macosx.sh @@ -1,11 +1,12 @@ #!/bin/sh -# Set this if we're doing a release build. Comment it out for development builds -#VERSION=2011.12 +# NB! To build a release build, the VERSION environment variable needs to be set. +# See doc/release-checklist.txt if test -z "$VERSION"; then VERSION=`date "+%Y.%m.%d"` COMMIT=-c + SNAPSHOT=true fi # Turn off ccache, just for safety @@ -14,6 +15,9 @@ PATH=${PATH//\/opt\/local\/libexec\/ccache:} # This is the same location as DEPLOYDIR in macosx-build-dependencies.sh export OPENSCAD_LIBRARIES=$PWD/../libraries/install +# Make sure that the correct Qt tools are used +export PATH=$OPENSCAD_LIBRARIES/bin:$PATH + `dirname $0`/release-common.sh -v $VERSION $COMMIT if [[ $? != 0 ]]; then exit 1 @@ -24,10 +28,11 @@ echo "Sanity check of the app bundle..." if [[ $? != 0 ]]; then exit 1 fi -cp OpenSCAD-$VERSION.dmg ~/Dropbox/Public -ln -sf OpenSCAD-$VERSION.dmg ~/Dropbox/Public/OpenSCAD-latest.dmg -echo "Upload in progress..." +echo "Uploading..." +LABELS=OpSys-OSX,Type-Executable +if ! $SNAPSHOT; then LABELS=$LABELS,Featured; fi +`dirname $0`/googlecode_upload.py -s 'Mac OS X Snapshot' -p openscad OpenSCAD-$VERSION.dmg -l $LABELS # Update snapshot filename on wab page `dirname $0`/update-web.sh OpenSCAD-$VERSION.dmg diff --git a/scripts/setenv-unibuild.sh b/scripts/setenv-unibuild.sh index d13782c..881526e 100644 --- a/scripts/setenv-unibuild.sh +++ b/scripts/setenv-unibuild.sh @@ -34,11 +34,6 @@ setenv_common() echo OPENSCAD_LIBRARIES modified echo GLEWDIR modified - if [ "`command -v qmake-qt4`" ]; then - echo "Please re-run qmake-qt4 and run 'make clean' if necessary" - else - echo "Please re-run qmake and run 'make clean' if necessary" - fi } setenv_freebsd() @@ -73,6 +68,49 @@ setenv_linux_clang() echo QMAKESPEC has been modified: $QMAKESPEC } +clean_note() +{ + if [ $QT5_SETUP ]; then + QMAKEBIN=qmake + elif [ "`command -v qmake-qt4`" ]; then + QMAKEBIN=qmake-qt4 + else + QMAKEBIN=qmake + fi + echo "Please re-run" $QMAKEBIN "and run 'make clean' if necessary" +} + +setenv_qt5() +{ + QT5_SETUP=true + if [ ! $QTDIR ]; then + QTDIR=/opt/qt5 + echo Please set QTDIR before running this qt5 script. Assuming $QTDIR + fi + PATH=$QTDIR/bin:$PATH + LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH + LD_RUN_PATH=$QTDIR/lib:$LD_RUN_PATH + if [ "`echo $CC | grep clang`" ]; then + if [ "`uname | grep -i linux`" ]; then + QMAKESPEC=linux-clang + echo QMAKESPEC has been modified: $QMAKESPEC + fi + fi + + export QTDIR + export PATH + export LD_LIBRARY_PATH + export LD_RUN_PATH + export QMAKESPEC + + echo QTDIR is set to: $QTDIR + echo PATH has been modified with $QTDIR/bin + echo LD_LIBRARY_PATH has been modified with $QTDIR/lib + echo LD_RUN_PATH has been modified with $QTDIR/lib + + export QT5_SETUP +} + if [ "`uname | grep -i 'linux\|debian'`" ]; then setenv_common if [ "`echo $* | grep clang`" ]; then @@ -87,3 +125,10 @@ else setenv_common echo unknown system. guessed env variables. see 'setenv-unibuild.sh' fi + +if [ "`echo $* | grep qt5`" ]; then + setenv_qt5 +fi + +clean_note + diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh index 88f2cf8..0c37605 100755 --- a/scripts/uni-build-dependencies.sh +++ b/scripts/uni-build-dependencies.sh @@ -31,6 +31,10 @@ # # . ./scripts/setenv-unibuild.sh clang # +# Enable Qt5 (experimental) +# +# . ./scripts/setenv-unibuild.sh qt5 +# printUsage() { diff --git a/setenv_mac-clang.sh b/setenv_mac-clang.sh index 2bc9234..0dcc51f 100644 --- a/setenv_mac-clang.sh +++ b/setenv_mac-clang.sh @@ -4,8 +4,7 @@ export QMAKESPEC=unsupported/macx-clang #export OPENCSGDIR=$PWD/../OpenCSG-1.3.0 #export CGALDIR=$PWD/../install/CGAL-3.6 -#export QCODEEDITDIR=$PWD/../qcodeedit-2.2.3/install -#export DYLD_LIBRARY_PATH=$OPENCSGDIR/lib:$QCODEEDITDIR/lib +#export DYLD_LIBRARY_PATH=$OPENCSGDIR/lib # ccache: export PATH=/opt/local/libexec/ccache:$PATH diff --git a/setenv_mjau.sh b/setenv_mjau.sh index 851cf0e..677a47a 100644 --- a/setenv_mjau.sh +++ b/setenv_mjau.sh @@ -4,8 +4,10 @@ export QMAKESPEC=macx-g++ #export OPENCSGDIR=$PWD/../OpenCSG-1.3.0 #export CGALDIR=$PWD/../install/CGAL-3.6 -#export QCODEEDITDIR=$PWD/../qcodeedit-2.2.3/install -#export DYLD_LIBRARY_PATH=$OPENCSGDIR/lib:$QCODEEDITDIR/lib +#export DYLD_LIBRARY_PATH=$OPENCSGDIR/lib + +# Own own Qt +export PATH=$OPENSCAD_LIBRARIES/bin:$PATH # ccache: export PATH=/opt/local/libexec/ccache:$PATH diff --git a/src/AboutDialog.html b/src/AboutDialog.html index 371ea46..34e8127 100644 --- a/src/AboutDialog.html +++ b/src/AboutDialog.html @@ -22,7 +22,7 @@ </p> <p> -Copyright (C) 2009-2012 <a href="https://github.com/kintel">Marius Kintel</a> <marius@kintel.net> and <a href="http://clifford.at">Clifford Wolf</a> <clifford@clifford.at> +Copyright (C) 2009-2013 <a href="https://github.com/kintel">Marius Kintel</a> <marius@kintel.net> and <a href="http://clifford.at">Clifford Wolf</a> <clifford@clifford.at> </p> <p> @@ -84,7 +84,7 @@ Please visit this link for a copy of the license: <a href="http://www.gnu.org/li <p> <b><a href="http://www.debian.org">Debian</a> maintainer:</b> - <a href="http://christian.amsuess.com/">Christian M. Amsuess</a> + <a href="http://christian.amsuess.com/">chrysn</a> </p> <p> @@ -105,16 +105,17 @@ Please visit this link for a copy of the license: <a href="http://www.gnu.org/li <p> -<b>Mailing list, bug reports, testing, &c</b> +<b>Mailing list, bug reports, testing, contribs, &c</b> </p> nop head, Triffid Hunter, Len Trigg, Kliment Yanev, Christian Siefkes, -Whosawhatsis, MichaelAtOz, Tony Buser, mrhdias, ibyte8bits, Koen Kooi, -Tomas Mudrunka, knuds, cadr, mshearn, Hans L, Brett Sutton, hmnapier, -Eero af Heurlin, caliston, 5263, ghost, 42loop, uniqx, Michael Thomson, -Michael Ivko, Pierre Doucet, myglc2, Alan Cox, Peter Falke, Michael -Ambrus, Gordon Wrigley, Ed Nisley, Stony Smith, Pasca Andrei, David -Goodenough, William A Adams ... and many others +Andrew Plumb, Whosawhatsis, MichaelAtOz, Tony Buser, mrhdias, +ibyte8bits, Koen Kooi, Tomas Mudrunka, knuds, cadr, mshearn, Hans L, +Brett Sutton, hmnapier, Eero af Heurlin, caliston, 5263, ghost, 42loop, +uniqx, Michael Thomson, Michael Ivko, Pierre Doucet, myglc2, Alan Cox, +Peter Falke, Michael Ambrus, Gordon Wrigley, Ed Nisley, Stony Smith, +Pasca Andrei, David Goodenough, William A Adams, mrrobinson, 1i7, +benhowes ... and many others <p> <b>Hosting & resources</b> @@ -135,6 +136,10 @@ Goodenough, William A Adams ... and many others </p> <p> +Trademarks are property of their owners and do not imply affiliation with OpenSCAD +</p> + +<p> Apologies to anyone left out. Please file an issue on OpenSCAD's github if you know of someone who belongs here. </p> diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index a4744c2..4deb3b3 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -61,14 +61,16 @@ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedr if (target.dim != 2 && target.dim != 3) { assert(false && "Dimension of Nef polyhedron must be 2 or 3"); } - if (src.empty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) + if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) + if (target.isEmpty() && op != CGE_UNION) return; // empty op <something> => empty if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { switch (op) { case CGE_UNION: - target += src; + if (target.isEmpty()) target = src.copy(); + else target += src; break; case CGE_INTERSECTION: target *= src; @@ -110,7 +112,8 @@ CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, CGA if (!isCached(*chnode)) { CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN); } - if (N.empty()) N = chN.copy(); + // Initialize N on first iteration with first expected geometric object + if (N.isNull() && !N.isEmpty()) N = chN.copy(); else process(N, chN, op); chnode->progress_report(); @@ -245,7 +248,6 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) if (!isCached(node)) { // First union all children N = applyToChildren(node, CGE_UNION); - if ( matrix_contains_infinity( node.matrix ) || matrix_contains_nan( node.matrix ) ) { // due to the way parse/eval works we can't currently distinguish between NaN and Inf PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object."); @@ -253,51 +255,53 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) } // Then apply transform - // If there is no geometry under the transform, N will be empty and of dim 0, - // just just silently ignore such nodes - if (N.dim == 2) { - // Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2 - // objects. So we convert in to our internal 2d data format, transform it, - // tesselate it and create a new CGAL_Nef_polyhedron2 from it.. What a hack! - - Eigen::Matrix2f testmat; - testmat << node.matrix(0,0), node.matrix(0,1), node.matrix(1,0), node.matrix(1,1); - if (testmat.determinant() == 0) { - PRINT("Warning: Scaling a 2D object with 0 - removing object"); - N.reset(); - } - else { - CGAL_Aff_transformation2 t( - node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), node.matrix(3,3)); + // If there is no geometry under the transform, N will be empty + // just silently ignore such nodes + if (!N.isNull()) { + if (N.dim == 2) { + // Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2 + // objects. So we convert in to our internal 2d data format, transform it, + // tesselate it and create a new CGAL_Nef_polyhedron2 from it.. What a hack! - DxfData *dd = N.convertToDxfData(); - for (size_t i=0; i < dd->points.size(); i++) { - CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]); - p = t.transform(p); - dd->points[i][0] = to_double(p.x()); - dd->points[i][1] = to_double(p.y()); + Eigen::Matrix2f testmat; + testmat << node.matrix(0,0), node.matrix(0,1), node.matrix(1,0), node.matrix(1,1); + if (testmat.determinant() == 0) { + PRINT("Warning: Scaling a 2D object with 0 - removing object"); + N.reset(); + } + else { + CGAL_Aff_transformation2 t( + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), node.matrix(3,3)); + + DxfData *dd = N.convertToDxfData(); + for (size_t i=0; i < dd->points.size(); i++) { + CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]); + p = t.transform(p); + dd->points[i][0] = to_double(p.x()); + dd->points[i][1] = to_double(p.y()); + } + + PolySet ps; + ps.is2d = true; + dxf_tesselate(&ps, *dd, 0, true, false, 0); + + N = evaluateCGALMesh(ps); + delete dd; } - - PolySet ps; - ps.is2d = true; - dxf_tesselate(&ps, *dd, 0, true, false, 0); - - N = evaluateCGALMesh(ps); - delete dd; - } - } - else if (N.dim == 3) { - if (node.matrix.matrix().determinant() == 0) { - PRINT("Warning: Scaling a 3D object with 0 - removing object"); - N.reset(); } - else { - CGAL_Aff_transformation t( - node.matrix(0,0), node.matrix(0,1), node.matrix(0,2), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,2), node.matrix(1,3), - node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3)); - N.p3->transform(t); + else if (N.dim == 3) { + if (node.matrix.matrix().determinant() == 0) { + PRINT("Warning: Scaling a 3D object with 0 - removing object"); + N.reset(); + } + else { + CGAL_Aff_transformation t( + node.matrix(0,0), node.matrix(0,1), node.matrix(0,2), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,2), node.matrix(1,3), + node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3)); + N.p3->transform(t); + } } } } @@ -388,7 +392,7 @@ void CGALEvaluator::addToParent(const State &state, const AbstractNode &node, co CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const PolySet &ps) { - if (ps.empty()) return CGAL_Nef_polyhedron(); + if (ps.empty()) return CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); if (ps.is2d) { diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index adf1217..4357e44 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -43,7 +43,11 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) { - if (root.dim == 2) { + if (this->root.isNull()) { + this->polyhedron = NULL; + this->polyset = NULL; + } + else if (root.dim == 2) { DxfData *dd = root.convertToDxfData(); this->polyhedron = NULL; this->polyset = new PolySet(); @@ -67,10 +71,6 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) CGAL::OGL::Nef3_Converter<CGAL_Nef_polyhedron3>::convert_to_OGLPolyhedron(*this->root.p3, this->polyhedron); this->polyhedron->init(); } - else { - this->polyhedron = NULL; - this->polyset = NULL; - } } CGALRenderer::~CGALRenderer() @@ -81,6 +81,7 @@ CGALRenderer::~CGALRenderer() void CGALRenderer::draw(bool showfaces, bool showedges) const { + if (this->root.isNull()) return; if (this->root.dim == 2) { // Draw 2D polygons glDisable(GL_LIGHTING); diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index ad2e4e2..8906595 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -61,7 +61,7 @@ CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &o int CGAL_Nef_polyhedron::weight() const { - if (this->empty()) return 0; + if (this->isNull()) return 0; size_t memsize = sizeof(CGAL_Nef_polyhedron); if (this->dim == 2) { @@ -84,8 +84,7 @@ int CGAL_Nef_polyhedron::weight() const */ PolySet *CGAL_Nef_polyhedron::convertToPolyset() { - if (this->empty()) - return new PolySet(); + if (this->isNull()) return new PolySet(); PolySet *ps = NULL; if (this->dim == 2) { ps = new PolySet(); diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index e8c505b..d949a2a 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -8,12 +8,15 @@ class CGAL_Nef_polyhedron { public: - CGAL_Nef_polyhedron() : dim(0) {} + CGAL_Nef_polyhedron(int dim = 0) : dim(dim) {} CGAL_Nef_polyhedron(CGAL_Nef_polyhedron2 *p); CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p); ~CGAL_Nef_polyhedron() {} - bool empty() const { return (dim == 0 || (!p2 && !p3)); } + // Empty means it is a geometric node which has zero area/volume + bool isEmpty() const { return (dim > 0 && !p2 && !p3); } + // Null means the node doesn't contain any geometry (for whatever reason) + bool isNull() const { return !p2 && !p3; } void reset() { dim=0; p2.reset(); p3.reset(); } CGAL_Nef_polyhedron &operator+=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator*=(const CGAL_Nef_polyhedron &other); diff --git a/src/OpenCSGWarningDialog.cc b/src/OpenCSGWarningDialog.cc index 5648576..926a55b 100644 --- a/src/OpenCSGWarningDialog.cc +++ b/src/OpenCSGWarningDialog.cc @@ -8,12 +8,12 @@ OpenCSGWarningDialog::OpenCSGWarningDialog(QWidget*) connect(this->showBox, SIGNAL(toggled(bool)), Preferences::inst()->openCSGWarningBox, SLOT(setChecked(bool))); connect(this->showBox, SIGNAL(toggled(bool)), - Preferences::inst(), SLOT(openCSGWarningChanged(bool))); + Preferences::inst(), SLOT(on_openCSGWarningBox_toggled(bool))); connect(this->enableOpenCSGBox, SIGNAL(toggled(bool)), Preferences::inst()->enableOpenCSGBox, SLOT(setChecked(bool))); connect(this->enableOpenCSGBox, SIGNAL(toggled(bool)), - Preferences::inst(), SLOT(enableOpenCSGChanged(bool))); + Preferences::inst(), SLOT(on_enableOpenCSGBox_toggled(bool))); } void OpenCSGWarningDialog::setText(const QString &text) diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 8e08e19..224e657 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -134,11 +134,11 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); if (N.dim == 3) { - if (sum.empty()) sum = N.copy(); + if (sum.isNull()) sum = N.copy(); else sum += N; } } - if (sum.empty()) return NULL; + if (sum.isNull()) return NULL; if (!sum.p3->is_simple()) { if (!node.cut_mode) { PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design.."); @@ -149,7 +149,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) //std::cout << sum.dump(); //std::cout.flush(); - CGAL_Nef_polyhedron nef_poly; + CGAL_Nef_polyhedron nef_poly(2); if (node.cut_mode) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); @@ -180,7 +180,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) } } - if ( sum.p3->is_empty() ) { + if (sum.p3->is_empty()) { CGAL::set_error_behaviour(old_behaviour); PRINT("WARNING: projection() failed."); return NULL; @@ -204,7 +204,6 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) log << "<!-- volume end. -->\n"; } nef_poly.p2 = zremover.output_nefpoly2d; - nef_poly.dim = 2; } catch (const CGAL::Failure_exception &e) { PRINTB("CGAL error in projection node while flattening: %s", e.what()); } @@ -284,8 +283,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) plist.push_back(p); } // FIXME: Should the CGAL_Nef_polyhedron2 be cached? - if (nef_poly.empty()) { - nef_poly.dim = 2; + if (nef_poly.isEmpty()) { nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); } else { @@ -385,18 +383,18 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const LinearExtrudeNode &node) BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (!N.empty()) { + if (!N.isNull()) { if (N.dim != 2) { PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); } else { - if (sum.empty()) sum = N.copy(); + if (sum.isNull()) sum = N.copy(); else sum += N; } } } - if (sum.empty()) return NULL; + if (sum.isNull()) return NULL; dxf = sum.convertToDxfData();; } else { dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); @@ -485,18 +483,18 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RotateExtrudeNode &node) BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (!N.empty()) { + if (!N.isNull()) { if (N.dim != 2) { PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!"); } else { - if (sum.empty()) sum = N.copy(); + if (sum.isNull()) sum = N.copy(); else sum += N; } } } - if (sum.empty()) return NULL; + if (sum.isNull()) return NULL; dxf = sum.convertToDxfData(); } else { dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); @@ -511,7 +509,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const CgaladvNode &node) { CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); PolySet *ps = NULL; - if (!N.empty()) { + if (!N.isNull()) { ps = N.convertToPolyset(); if (ps) ps->convexity = node.convexity; } @@ -523,7 +521,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RenderNode &node) { CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); PolySet *ps = NULL; - if (!N.empty()) { + if (!N.isNull()) { if (N.dim == 3 && !N.p3->is_simple()) { PRINT("WARNING: Body of render() isn't valid 2-manifold!"); } diff --git a/src/editor.cc b/src/editor.cc index 92aeefe..08bf005 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -1,7 +1,6 @@ #include "editor.h" #include "Preferences.h" -#ifndef _QCODE_EDIT_ void Editor::indentSelection() { QTextCursor cursor = textCursor(); @@ -109,4 +108,3 @@ void Editor::wheelEvent ( QWheelEvent * event ) } } -#endif diff --git a/src/editor.h b/src/editor.h index bb338b0..09484f5 100644 --- a/src/editor.h +++ b/src/editor.h @@ -3,26 +3,11 @@ #include <QWidget> #include <QWheelEvent> -#ifdef _QCODE_EDIT_ -#include <qeditor.h> -class Editor : public QEditor -#else #include <QTextEdit> class Editor : public QTextEdit -#endif { Q_OBJECT public: -#ifdef _QCODE_EDIT_ - Editor(QWidget *parent) : QEditor(parent) {} - QString toPlainText() const { return text(); } - void setPlainText(const QString& text) { setText(text); } -public slots: - //void zoomIn() { zoom(1); } - void zoomIn(int n = 1) { zoom(n); } - //void zoomOut() { zoom(-1); } - void zoomOut(int n = 1) { zoom(-n); } -#else Editor(QWidget *parent) : QTextEdit(parent) { setAcceptRichText(false); } public slots: void zoomIn(); @@ -36,5 +21,4 @@ public slots: void uncommentSelection(); private: void wheelEvent ( QWheelEvent * event ); -#endif }; diff --git a/src/highlighter.cc b/src/highlighter.cc index 64ea980..77d1bb8 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -24,34 +24,295 @@ * */ +/* + Syntax Highlighter for OpenSCAD + based on Syntax Highlight code by Christopher Olah + + Speed Note: + + setFormat() is very slow. normally this doesnt matter because we + only highlight a block or two at once. But when OpenSCAD first starts, + QT automatigically calls 'highlightBlock' on every single textblock in the file + even if it's not visible in the window. On a large file (50,000 lines) this + can take several seconds. + + Also, QT 4.5 and lower do not have rehighlightBlock(), so they will be slow + on large files as well, as they re-highlight everything after each compile. + + The vast majority of OpenSCAD files, however, are not 50,000 lines and + most machines have Qt > 4.5 + + See Also: + + Giles Bathgate's Rapcad lexer-based highlighter: rapcad.org + + Test suite: + +1. action: open example001, remove first {, hit f5 + expected result: error highlight appears on last }, cursor moves there + action: replace first {, hit f5 + expected result: error highlight disappears + +1a. action: open example001, remove first {, hit f5 + expected result: error highlight appears on last }, cursor moves there + action: replace first { with the letter 'P', hit f5 + expected result: error highlight on last } disappears, appears on elsewhere + action: replace first {, hit f5 + expected result: error highlight disappears + +2. action: type a=b into any file + expected result: '=' is highlighted with its appropriate format + +2a. action: type a=b=c=d=e=f= into any file + expected result: each '=' is highlighted with its appropriate format + +3. action: open example001, put '===' after first ; hit f5 + expected result: error highlight appears in === + action: remove '===' + expected result: error highlight disappears + +3a. action: open example001, put '=' after first ; hit f5 + expected result: error highlight appears + action: remove '=' + expected result: error highlight disappears + +3b. action: open example001, put '=' after first { + expected result: error highlight appears + action: remove '=' + expected result: error highlight disappears + +3c. action: open example001, replace first { with '=' + expected result: error highlight appears + action: remove '=', replace with { + expected result: error highlight disappears + +4. action: open example001, remove last ';' but not trailing whitespace/\n + expected result: error highlight appears somewhere near end + action: replace last ';' + expected result: error highlight disappears + +5. action: open file, type in a multi-line comment + expected result: multiline comment should be highlighted appropriately + +6. action: open example001, remove first ')' + expected result: highlight should appear appropriately + +7. action: create a large file (50,000 lines). eg at a bash prompt: + for i in {1..2000}; do cat examples/example001.scad >> test5k.scad ; done + action: open file in openscad + expected result: it should load in a reasonable amount of time + action: scroll to bottom, put '=' after last ; + expected result: there should be a highlight, and a report of syntax error + action: comment out the highlighter code from mainwin.cc, recompile, + run openscad again on the large file. put '=' after last ; + expected result: there should be only a small difference in speed. + +8. action: open any file, and hold down 'f5' key to repeatedly reparse + expected result: no crashing! + +9. action: for i in examples/ex* ; do ./openscad $i ; done + expected result: make sure the colors look harmonious + +10. action: type random string of [][][][]()()[][90,3904,00,000] + expected result: all should be highlighted correctly + +*/ + #include "highlighter.h" -#include "parsersettings.h" // extern int parser_error_pos; +#include <QTextDocument> +#include <QTextCursor> +#include <QColor> +//#include <iostream> -#ifdef _QCODE_EDIT_ -Highlighter::Highlighter(QDocument *parent) -#else Highlighter::Highlighter(QTextDocument *parent) -#endif : QSyntaxHighlighter(parent) { + tokentypes["operator"] << "=" << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";"; + typeformats["operator"].setForeground(Qt::blue); + + tokentypes["keyword"] << "module" << "function" << "for" << "intersection_for" << "if" << "assign"; + typeformats["keyword"].setForeground(QColor("Green")); + typeformats["keyword"].setToolTip("Keyword"); + + tokentypes["transform"] << "scale" << "translate" << "rotate" << "multmatrix" << "color" << "projection" << "hull"; + typeformats["transform"].setForeground(QColor("Indigo")); + + tokentypes["csgop"] << "union" << "intersection" << "difference" << "render"; + typeformats["csgop"].setForeground(QColor("DarkGreen")); + + tokentypes["prim3d"] << "cube" << "cylinder" << "sphere" << "polyhedron"; + typeformats["prim3d"].setForeground(QColor("DarkBlue")); + + tokentypes["prim2d"] << "square" << "polygon" << "circle"; + typeformats["prim2d"].setForeground(QColor("MidnightBlue")); + + tokentypes["import"] << "include" << "use" << "import_stl" << "import" << "import_dxf" << "dxf_dim" << "dxf_cross"; + typeformats["import"].setForeground(Qt::darkYellow); + + tokentypes["special"] << "$children" << "child" << "$fn" << "$fa" << "$fb"; + typeformats["special"].setForeground(Qt::darkGreen); + + tokentypes["extrude"] << "linear_extrude" << "rotate_extrude"; + typeformats["extrude"].setForeground(Qt::darkGreen); + + tokentypes["bracket"] << "[" << "]" << "(" << ")"; + typeformats["bracket"].setForeground(QColor("Green")); + + tokentypes["curlies"] << "{" << "}"; + typeformats["curlies"].setForeground(QColor(32,32,20)); + + tokentypes["bool"] << "true" << "false"; + typeformats["bool"].setForeground(QColor("DarkRed")); + + // Put each tokens into single QHash, mapped to it's format + QList<QString>::iterator ki; + QList<QString> toktypes = tokentypes.keys(); + for ( ki=toktypes.begin(); ki!=toktypes.end(); ++ki ) { + QString toktype = *ki; + QStringList::iterator it; + for ( it = tokentypes[toktype].begin(); it < tokentypes[toktype].end(); ++it) { + QString token = *it; + //std::cout << token.toStdString() << "\n"; + tokenFormats[ token ] = typeformats [ toktype ]; + } + } + + quoteFormat.setForeground(Qt::darkMagenta); + commentFormat.setForeground(Qt::darkCyan); + errorFormat.setBackground(Qt::red); + numberFormat.setForeground(QColor("DarkRed")); + + errorState = false; + errorPos = -1; + lastErrorBlock = parent->begin(); +} + +void Highlighter::highlightError(int error_pos) +{ + errorState = true; + errorPos = error_pos; + + QTextBlock err_block = document()->findBlock( errorPos ); + //std::cout << "error pos: " << error_pos << " doc len: " << document()->characterCount() << "\n"; + + while (err_block.text().remove(QRegExp("\\s+")).size()==0) { + //std::cout << "special case - errors at end of file w whitespace\n"; + err_block = err_block.previous(); + errorPos = err_block.position()+err_block.length() - 2; + } + if ( errorPos == lastDocumentPos()-1 ) { + errorPos--; + } + + int block_last_pos = err_block.position() + err_block.length() - 1; + if ( errorPos == block_last_pos ) { + //std::cout << "special case - errors at ends of certain blocks\n"; + errorPos--; + } + err_block = document()->findBlock(errorPos); + + portable_rehighlightBlock( err_block ); + + errorState = false; + lastErrorBlock = err_block; } -void Highlighter::highlightBlock(const QString &text) +void Highlighter::unhighlightLastError() { - int n = previousBlockState(); - if (n < 0) - n = 0; - int k = n + text.size() + 1; - setCurrentBlockState(k); - if (parser_error_pos >= n && parser_error_pos < k) { - QTextCharFormat style; - style.setBackground(Qt::red); - setFormat(0, text.size(), style); -#if 0 - style.setBackground(Qt::black); - style.setForeground(Qt::white); - setFormat(parser_error_pos - n, 1, style); + portable_rehighlightBlock( lastErrorBlock ); +} + +void Highlighter::portable_rehighlightBlock( const QTextBlock &block ) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)) + rehighlightBlock( block ); +#else + rehighlight(); // slow on very large files #endif +} + +int Highlighter::lastDocumentPos() +{ +#if (QT_VERSION >= QT_VERSION_CHECK(4, 5, 0)) + return document()->characterCount(); +#else + QTextBlock lastblock = document()->lastBlock(); + return lastblock.position() + lastblock.length(); +#endif +} + +void Highlighter::highlightBlock(const QString &text) +{ + int block_first_pos = currentBlock().position(); + //int block_last_pos = block_first_pos + currentBlock().length() - 1; + //std::cout << "block[" << block_first_pos << ":" << block_last_pos << "]" + // << ", err:" << errorPos << "," << errorState + // << ", text:'" << text.toStdString() << "'\n"; + + // Split the block into pieces and highlight each as appropriate + QString newtext = text; + QStringList splitHelpers; + QStringList::iterator sh, token; + // splitHelpers - so "[a+b]" is treated as "[ a + b ]" and formatted + splitHelpers << tokentypes["operator"] << "(" << ")" << "[" << "]" << "," << ":"; + for ( sh = splitHelpers.begin(); sh!=splitHelpers.end(); ++sh ) { + newtext = newtext.replace( *sh, " " + *sh + " "); } + //std::cout << "\nnewtext: " << newtext.toStdString() << "\n"; + QStringList tokens = newtext.split(QRegExp("\\s")); + int tokindex = 0; // tokindex helps w duplicate tokens in a single block + bool numtest; + for ( token = tokens.begin(); token!=tokens.end(); ++token ){ + if ( tokenFormats.contains( *token ) ) { + tokindex = text.indexOf( *token, tokindex ); + setFormat( tokindex, token->size(), tokenFormats[ *token ]); + //std::cout << "found tok '" << (*token).toStdString() << "' at " << tokindex << "\n"; + tokindex += token->size(); + } else { + (*token).toDouble( &numtest ); + if ( numtest ) { + tokindex = text.indexOf( *token, tokindex ); + setFormat( tokindex, token->size(), numberFormat ); + //std::cout << "found num '" << (*token).toStdString() << "' at " << tokindex << "\n"; + tokindex += token->size(); + } + } + } + + // Quoting and Comments. + state_e state = (state_e) previousBlockState(); + for (int n = 0; n < text.size(); ++n){ + if (state == NORMAL){ + if (text[n] == '"'){ + state = QUOTE; + setFormat(n,1,quoteFormat); + } else if (text[n] == '/'){ + if (text[n+1] == '/'){ + setFormat(n,text.size(),commentFormat); + break; + } else if (text[n+1] == '*'){ + setFormat(n++,2,commentFormat); + state = COMMENT; + } + } + } else if (state == QUOTE){ + setFormat(n,1,quoteFormat); + if (text[n] == '"' && text[n-1] != '\\') + state = NORMAL; + } else if (state == COMMENT){ + setFormat(n,1,commentFormat); + if (text[n] == '*' && text[n+1] == '/'){ + setFormat(++n,1,commentFormat); + state = NORMAL; + } + } + } + setCurrentBlockState((int) state); + + // Highlight an error. Do it last to 'overwrite' other formatting. + if (errorState) { + setFormat( errorPos - block_first_pos, 1, errorFormat); + } + } diff --git a/src/highlighter.h b/src/highlighter.h index 1bd54d2..b4ffae8 100644 --- a/src/highlighter.h +++ b/src/highlighter.h @@ -2,20 +2,27 @@ #define HIGHLIGHTER_H_ #include <QSyntaxHighlighter> - -#ifdef _QCODE_EDIT_ -#include "qdocument.h" -#endif +#include <QTextFormat> +#include <QHash> class Highlighter : public QSyntaxHighlighter { public: -#ifdef _QCODE_EDIT_ - Highlighter(QDocument *parent); -#else + enum state_e {NORMAL=-1,QUOTE,COMMENT}; + QHash<QString, QTextCharFormat> tokenFormats; + QTextCharFormat errorFormat, commentFormat, quoteFormat, numberFormat; Highlighter(QTextDocument *parent); -#endif void highlightBlock(const QString &text); + void highlightError(int error_pos); + void unhighlightLastError(); +private: + QTextBlock lastErrorBlock; + int errorPos; + bool errorState; + QMap<QString,QStringList> tokentypes; + QMap<QString,QTextCharFormat> typeformats; + int lastDocumentPos(); + void portable_rehighlightBlock( const QTextBlock &text ); }; #endif diff --git a/src/mainwin.cc b/src/mainwin.cc index dc5b79b..251c6e1 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -70,11 +70,6 @@ #include <QSettings> #include <QProgressDialog> #include <QMutexLocker> -#ifdef _QCODE_EDIT_ -#include "qdocument.h" -#include "qformatscheme.h" -#include "qlanguagefactory.h" -#endif #include <fstream> @@ -115,7 +110,7 @@ static char helptitle[] = #endif "\nhttp://www.openscad.org\n\n"; static char copyrighttext[] = - "Copyright (C) 2009-2012 Marius Kintel <marius@kintel.net> and Clifford Wolf <clifford@clifford.at>\n" + "Copyright (C) 2009-2013 Marius Kintel <marius@kintel.net> and Clifford Wolf <clifford@clifford.at>\n" "\n" "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 " @@ -185,16 +180,8 @@ MainWindow::MainWindow(const QString &filename) fps = 0; fsteps = 1; - highlighter = NULL; -#ifdef _QCODE_EDIT_ - QFormatScheme *formats = new QFormatScheme("qxs/openscad.qxf"); - QDocument::setDefaultFormatScheme(formats); - QLanguageFactory *languages = new QLanguageFactory(formats,this); - languages->addDefinitionPath("qxs"); - languages->setLanguage(editor, "openscad"); -#else + highlighter = new Highlighter(editor->document()); editor->setTabStopWidth(30); -#endif editor->setLineWrapping(true); // Not designable this->glview->statusLabel = new QLabel(this); @@ -348,13 +335,8 @@ MainWindow::MainWindow(const QString &filename) updateRecentFileActions(); connect(editor->document(), SIGNAL(contentsChanged()), this, SLOT(animateUpdateDocChanged())); -#ifdef _QCODE_EDIT_ - connect(editor, SIGNAL(contentModified(bool)), this, SLOT(setWindowModified(bool))); - connect(editor, SIGNAL(contentModified(bool)), fileActionSave, SLOT(setEnabled(bool))); -#else connect(editor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setWindowModified(bool))); connect(editor->document(), SIGNAL(modificationChanged(bool)), fileActionSave, SLOT(setEnabled(bool))); -#endif connect(this->glview, SIGNAL(doAnimateUpdate()), this, SLOT(animateUpdate())); connect(Preferences::inst(), SIGNAL(requestRedraw()), this->glview, SLOT(updateGL())); @@ -462,6 +444,7 @@ void MainWindow::report_func(const class AbstractNode*, void *vp, int mark) QApplication::processEvents(); } + // FIXME: Check if cancel was requested by e.g. Application quit if (thisp->progresswidget->wasCanceled()) throw ProgressCancelException(); } @@ -483,12 +466,7 @@ void MainWindow::openFile(const QString &new_filename) { #ifdef ENABLE_MDI -#ifdef _QCODE_EDIT_ - if (this->editor->document()->lines() > 1 || - !this->editor->document()->text(true, false).trimmed().isEmpty()) { -#else if (!editor->toPlainText().isEmpty()) { -#endif new MainWindow(new_filename); clearCurrentOutput(); return; @@ -957,11 +935,7 @@ void MainWindow::hideEditor() void MainWindow::pasteViewportTranslation() { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); -#else QTextCursor cursor = editor->textCursor(); -#endif QString txt; txt.sprintf("[ %.2f, %.2f, %.2f ]", -this->glview->object_trans_x, -this->glview->object_trans_y, -this->glview->object_trans_z); cursor.insertText(txt); @@ -969,11 +943,7 @@ void MainWindow::pasteViewportTranslation() void MainWindow::pasteViewportRotation() { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); -#else QTextCursor cursor = editor->textCursor(); -#endif QString txt; txt.sprintf("[ %.2f, %.2f, %.2f ]", fmodf(360 - this->glview->object_rot_x + 90, 360), fmodf(360 - this->glview->object_rot_y, 360), fmodf(360 - this->glview->object_rot_z, 360)); @@ -1067,24 +1037,16 @@ bool MainWindow::compileTopLevelDocument(bool reload) QFileInfo(this->fileName).absolutePath().toLocal8Bit(), false); - // Error highlighting - delete this->highlighter; - this->highlighter = NULL; - - if (!this->root_module) { - this->highlighter = new Highlighter(editor->document()); - - if (!animate_panel->isVisible()) { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); - cursor.setPosition(parser_error_pos); -#else + if (!animate_panel->isVisible()) { + highlighter->unhighlightLastError(); + if (!this->root_module) { QTextCursor cursor = editor->textCursor(); cursor.setPosition(parser_error_pos); editor->setTextCursor(cursor); -#endif + highlighter->highlightError( parser_error_pos ); } } + } bool changed = shouldcompiletoplevel; @@ -1236,35 +1198,37 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) PolySetCache::instance()->print(); CGALCache::instance()->print(); - if (root_N->dim == 2) { - PRINT(" Top level object is a 2D object:"); - PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no")); - PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no")); - PRINTB(" Vertices: %6d", root_N->p2->explorer().number_of_vertices()); - PRINTB(" Halfedges: %6d", root_N->p2->explorer().number_of_halfedges()); - PRINTB(" Edges: %6d", root_N->p2->explorer().number_of_edges()); - PRINTB(" Faces: %6d", root_N->p2->explorer().number_of_faces()); - PRINTB(" FaceCycles: %6d", root_N->p2->explorer().number_of_face_cycles()); - PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components()); - } - - if (root_N->dim == 3) { - PRINT(" Top level object is a 3D object:"); - PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no")); - PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no")); - PRINTB(" Vertices: %6d", root_N->p3->number_of_vertices()); - PRINTB(" Halfedges: %6d", root_N->p3->number_of_halfedges()); - PRINTB(" Edges: %6d", root_N->p3->number_of_edges()); - PRINTB(" Halffacets: %6d", root_N->p3->number_of_halffacets()); - PRINTB(" Facets: %6d", root_N->p3->number_of_facets()); - PRINTB(" Volumes: %6d", root_N->p3->number_of_volumes()); + if (!root_N->isNull()) { + if (root_N->dim == 2) { + PRINT(" Top level object is a 2D object:"); + PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no")); + PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no")); + PRINTB(" Vertices: %6d", root_N->p2->explorer().number_of_vertices()); + PRINTB(" Halfedges: %6d", root_N->p2->explorer().number_of_halfedges()); + PRINTB(" Edges: %6d", root_N->p2->explorer().number_of_edges()); + PRINTB(" Faces: %6d", root_N->p2->explorer().number_of_faces()); + PRINTB(" FaceCycles: %6d", root_N->p2->explorer().number_of_face_cycles()); + PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components()); + } + + if (root_N->dim == 3) { + PRINT(" Top level object is a 3D object:"); + PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no")); + PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no")); + PRINTB(" Vertices: %6d", root_N->p3->number_of_vertices()); + PRINTB(" Halfedges: %6d", root_N->p3->number_of_halfedges()); + PRINTB(" Edges: %6d", root_N->p3->number_of_edges()); + PRINTB(" Halffacets: %6d", root_N->p3->number_of_halffacets()); + PRINTB(" Facets: %6d", root_N->p3->number_of_facets()); + PRINTB(" Volumes: %6d", root_N->p3->number_of_volumes()); + } } int s = this->progresswidget->elapsedTime() / 1000; PRINTB("Total rendering time: %d hours, %d minutes, %d seconds", (s / (60*60)) % ((s / 60) % 60) % (s % 60)); this->root_N = root_N; - if (!this->root_N->empty()) { + if (!this->root_N->isNull()) { this->cgalRenderer = new CGALRenderer(*this->root_N); // Go to CGAL view mode if (viewActionCGALGrid->isChecked()) { @@ -1766,11 +1730,13 @@ void MainWindow::helpLibrary() libinfo.sprintf("Boost version: %s\n" "Eigen version: %d.%d.%d\n" "CGAL version: %s\n" - "OpenCSG version: %s\n\n", + "OpenCSG version: %s\n" + "Qt version: %s\n\n", BOOST_LIB_VERSION, EIGEN_WORLD_VERSION, EIGEN_MAJOR_VERSION, EIGEN_MINOR_VERSION, TOSTRING(CGAL_VERSION), - OPENCSG_VERSION_STRING); + OPENCSG_VERSION_STRING, + qVersion()); if (!this->openglbox) { this->openglbox = new QMessageBox(QMessageBox::Information, @@ -1843,6 +1809,7 @@ void MainWindow::quit() QCloseEvent ev; QApplication::sendEvent(QApplication::instance(), &ev); if (ev.isAccepted()) QApplication::instance()->quit(); + // FIXME: Cancel any CGAL calculations } void MainWindow::consoleOutput(const std::string &msg, void *userdata) diff --git a/src/openscad.cc b/src/openscad.cc index df7adb3..472b5d4 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -304,8 +304,19 @@ int main(int argc, char **argv) fs::current_path(original_path); if (deps_output_file) { - if (!write_deps(deps_output_file, - stl_output_file ? stl_output_file : off_output_file)) { + std::string deps_out( deps_output_file ); + std::string geom_out; + if ( stl_output_file ) geom_out = std::string(stl_output_file); + else if ( off_output_file ) geom_out = std::string(off_output_file); + else if ( dxf_output_file ) geom_out = std::string(dxf_output_file); + else { + PRINTB("Output file:%s\n",output_file); + PRINT("Sorry, don't know how to write deps for that file type. Exiting\n"); + exit(1); + } + int result = write_deps( deps_out, geom_out ); + if ( !result ) { + PRINT("error writing deps"); exit(1); } } diff --git a/src/polyset.cc b/src/polyset.cc index 3b3be34..e601585 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -157,7 +157,8 @@ void PolySet::render_surface(csgmode_e csgmode, const Transform3d &m, GLint *sha } #endif /* ENABLE_OPENCSG */ if (this->is2d) { - double zbase = 1; // Render 2D objects 1mm thick + // Render 2D objects 1mm thick, but differences slightly larger + double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; glBegin(GL_TRIANGLES); for (double z = -zbase/2; z < zbase; z += zbase) { @@ -248,7 +249,8 @@ void PolySet::render_edges(csgmode_e csgmode) const { glDisable(GL_LIGHTING); if (this->is2d) { - double zbase = 1; // Render 2D objects 1mm thick + // Render 2D objects 1mm thick, but differences slightly larger + double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; for (double z = -zbase/2; z < zbase; z += zbase) { for (size_t i = 0; i < borders.size(); i++) { diff --git a/src/polyset.h b/src/polyset.h index 4ca57bf..6626f79 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -29,14 +29,15 @@ public: BoundingBox getBoundingBox() const; +#define CSGMODE_DIFFERENCE_FLAG 0x10 enum csgmode_e { - CSGMODE_NONE, - CSGMODE_NORMAL = 1, - CSGMODE_DIFFERENCE = 2, - CSGMODE_BACKGROUND = 11, - CSGMODE_BACKGROUND_DIFFERENCE = 12, - CSGMODE_HIGHLIGHT = 21, - CSGMODE_HIGHLIGHT_DIFFERENCE = 22 + CSGMODE_NONE = 0x00, + CSGMODE_NORMAL = 0x01, + CSGMODE_DIFFERENCE = CSGMODE_NORMAL | CSGMODE_DIFFERENCE_FLAG, + CSGMODE_BACKGROUND = 0x02, + CSGMODE_BACKGROUND_DIFFERENCE = CSGMODE_BACKGROUND | CSGMODE_DIFFERENCE_FLAG, + CSGMODE_HIGHLIGHT = 0x03, + CSGMODE_HIGHLIGHT_DIFFERENCE = CSGMODE_HIGHLIGHT | CSGMODE_DIFFERENCE_FLAG }; void render_surface(csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; diff --git a/src/version_check.h b/src/version_check.h index b6a63ab..db17962 100644 --- a/src/version_check.h +++ b/src/version_check.h @@ -24,26 +24,26 @@ a time, to avoid confusion. #define GMPPATCH 0 #define SYS_GMP_VER (__GNU_MP_VERSION * 10000 + __GNU_MP_VERSION_MINOR * 100 + __GNU_MP_VERSION_PATCHLEVEL * 1) #if SYS_GMP_VER < GMPMAJOR * 10000 + GMPMINOR * 100 + GMPPATCH * 1 -#error GNU GMP library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error GNU GMP library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else #include <mpfr.h> #if MPFR_VERSION < MPFR_VERSION_NUM( 3,0,0 ) -#error GNU MPFR library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error GNU MPFR library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else #include <Eigen/Core> #if not EIGEN_VERSION_AT_LEAST( 2,0,13 ) -#error eigen2 library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error eigen2 library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else #include <boost/version.hpp> // boost 1.3.5 = 103500 #if BOOST_VERSION < 103500 -#error boost library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error boost library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else @@ -51,7 +51,7 @@ a time, to avoid confusion. #include <CGAL/version.h> #if CGAL_VERSION_NR < 1030601000 -#error CGAL library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error CGAL library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else #if CGAL_VERSION_NR < 1040021000 @@ -76,20 +76,20 @@ a time, to avoid confusion. #include <GL/glew.h> // kludge - GLEW doesnt have compiler-accessible version numbering #ifndef GLEW_ARB_occlusion_query2 -#error GLEW library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error GLEW library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else #include <opencsg.h> // 1.3.2 -> 0x0132 #if OPENCSG_VERSION < 0x0132 -#error OPENCSG library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error OPENCSG library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #else #endif // ENABLE_OPENCSG #include <QtCore/qglobal.h> #if QT_VERSION < 0x040400 -#error QT library missing or version too old. See README.md. To force compile, run qmake CONFIG=skip-version-check +#error QT library missing or version too old. See README.md. To force compile, run qmake CONFIG+=skip-version-check #endif // QT diff --git a/testdata/scad/features/difference-tests.scad b/testdata/scad/features/difference-tests.scad index 3bcd9e5..b770764 100644 --- a/testdata/scad/features/difference-tests.scad +++ b/testdata/scad/features/difference-tests.scad @@ -28,3 +28,9 @@ translate([24,0,0]) difference() { cube([10,10,10], center=true); translate([0,0,6.99]) cylinder(r=4, h=4, center=true); } + +// Subtracting something from nothing +translate([24,12,0]) difference() { + cube([0,10,10], center=true); + # cylinder(r=4, h=20, center=true); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 18f4469..87e5319 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -773,7 +773,6 @@ set_test_config(Heavy opencsgtest_minkowski3-tests cgalpngtest_minkowski3-tests cgalpngtest_for-tests cgalpngtest_for-nested-tests - cgalpngtest_difference-tests cgalpngtest_intersection-tests) foreach(FILE ${EXAMPLE_FILES}) diff --git a/tests/cgalpngtest.cc b/tests/cgalpngtest.cc index 08e539e..56861c6 100644 --- a/tests/cgalpngtest.cc +++ b/tests/cgalpngtest.cc @@ -153,30 +153,30 @@ int main(int argc, char **argv) exit(1); } - CGALRenderer cgalRenderer(N); - + CGALRenderer cgalRenderer(N); + BoundingBox bbox; if (cgalRenderer.polyhedron) { CGAL::Bbox_3 cgalbbox = cgalRenderer.polyhedron->bbox(); bbox = BoundingBox(Vector3d(cgalbbox.xmin(), cgalbbox.ymin(), cgalbbox.zmin()), - Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); + Vector3d(cgalbbox.xmax(), cgalbbox.ymax(), cgalbbox.zmax())); } else if (cgalRenderer.polyset) { bbox = cgalRenderer.polyset->getBoundingBox(); } - + Vector3d center = getBoundingCenter(bbox); double radius = getBoundingRadius(bbox); - + Vector3d cameradir(1, 1, -0.5); Vector3d camerapos = center - radius*2*cameradir; csgInfo.glview->setCamera(camerapos, center); - - + + csgInfo.glview->setRenderer(&cgalRenderer); csgInfo.glview->paintGL(); csgInfo.glview->save(outfile); - + delete root_node; delete root_module; diff --git a/tests/cgalstlsanitytest.cc b/tests/cgalstlsanitytest.cc index 137f626..52cfb41 100644 --- a/tests/cgalstlsanitytest.cc +++ b/tests/cgalstlsanitytest.cc @@ -129,7 +129,7 @@ int main(int argc, char **argv) CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node); current_path(original_path); - if (!N.empty()) { + if (!N.isNull()) { std::ofstream outfile; outfile.open(outfilename); diff --git a/tests/cgaltest.cc b/tests/cgaltest.cc index e4761db..b546286 100644 --- a/tests/cgaltest.cc +++ b/tests/cgaltest.cc @@ -122,7 +122,7 @@ int main(int argc, char **argv) CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node); current_path(original_path); - if (!N.empty()) { + if (!N.isNull()) { export_stl(&N, std::cout); } diff --git a/tests/regression/opencsgtest/difference-tests-expected.png b/tests/regression/opencsgtest/difference-tests-expected.png Binary files differindex 794104a..a6d863a 100644 --- a/tests/regression/opencsgtest/difference-tests-expected.png +++ b/tests/regression/opencsgtest/difference-tests-expected.png diff --git a/tests/regression/throwntogethertest/difference-tests-expected.png b/tests/regression/throwntogethertest/difference-tests-expected.png Binary files differindex 183700c..0a27c90 100644 --- a/tests/regression/throwntogethertest/difference-tests-expected.png +++ b/tests/regression/throwntogethertest/difference-tests-expected.png |