summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--RELEASE_NOTES4
-rw-r--r--doc/TODO.txt2
-rw-r--r--doc/release-checklist.txt63
-rw-r--r--eigen.pri8
-rwxr-xr-xscripts/googlecode_upload.py296
-rwxr-xr-xscripts/macosx-build-dependencies.sh24
-rwxr-xr-xscripts/publish-macosx.sh15
-rw-r--r--scripts/setenv-unibuild.sh55
-rwxr-xr-xscripts/uni-build-dependencies.sh4
-rw-r--r--setenv_mac-clang.sh3
-rw-r--r--setenv_mjau.sh6
-rw-r--r--src/AboutDialog.html23
-rw-r--r--src/CGALEvaluator.cc98
-rw-r--r--src/CGALRenderer.cc11
-rw-r--r--src/CGAL_Nef_polyhedron.cc5
-rw-r--r--src/CGAL_Nef_polyhedron.h7
-rw-r--r--src/OpenCSGWarningDialog.cc4
-rw-r--r--src/PolySetCGALEvaluator.cc28
-rw-r--r--src/editor.cc2
-rw-r--r--src/editor.h16
-rw-r--r--src/highlighter.cc299
-rw-r--r--src/highlighter.h23
-rw-r--r--src/mainwin.cc109
-rw-r--r--src/openscad.cc15
-rw-r--r--src/polyset.cc6
-rw-r--r--src/polyset.h15
-rw-r--r--src/version_check.h16
-rw-r--r--testdata/scad/features/difference-tests.scad6
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/cgalpngtest.cc16
-rw-r--r--tests/cgalstlsanitytest.cc2
-rw-r--r--tests/cgaltest.cc2
-rw-r--r--tests/regression/opencsgtest/difference-tests-expected.pngbin11223 -> 11900 bytes
-rw-r--r--tests/regression/throwntogethertest/difference-tests-expected.pngbin11463 -> 11762 bytes
35 files changed, 917 insertions, 276 deletions
diff --git a/README.md b/README.md
index f1d9925..ddad806 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/eigen.pri b/eigen.pri
index efb2d3c..5adac4b 100644
--- a/eigen.pri
+++ b/eigen.pri
@@ -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> &lt;marius@kintel.net&gt; and <a href="http://clifford.at">Clifford Wolf</a> &lt;clifford@clifford.at&gt;
+Copyright (C) 2009-2013 <a href="https://github.com/kintel">Marius Kintel</a> &lt;marius@kintel.net&gt; and <a href="http://clifford.at">Clifford Wolf</a> &lt;clifford@clifford.at&gt;
</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 &amp; 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
index 794104a..a6d863a 100644
--- a/tests/regression/opencsgtest/difference-tests-expected.png
+++ b/tests/regression/opencsgtest/difference-tests-expected.png
Binary files differ
diff --git a/tests/regression/throwntogethertest/difference-tests-expected.png b/tests/regression/throwntogethertest/difference-tests-expected.png
index 183700c..0a27c90 100644
--- a/tests/regression/throwntogethertest/difference-tests-expected.png
+++ b/tests/regression/throwntogethertest/difference-tests-expected.png
Binary files differ
contact: Jan Huwald // Impressum