Merge branch 'master' into threading

Conflicts:
	RELEASE_NOTES
felipesanches-svg
Marius Kintel 2012-01-04 23:29:08 +01:00
commit 52dfffd97b
77 changed files with 1092 additions and 411 deletions

View File

@ -4,6 +4,10 @@ OpenSCAD 2012.XX
Features:
o Snappier GUI while performing CGAL computations (computations running in separate thread)
Deprecations:
o The old include syntax "<filename.scad>" without the include keyword is no
longer supported and will cause a syntax error.
OpenSCAD 2011.12
================
@ -19,6 +23,7 @@ o New import() statement reads the correct file format based on the filename ext
(.stl, .dxf and .off is supported)
o The color() statement now supports an alpha parameter, e.g. color(c=[1,0,0], alpha=0.4)
o The color() statement now supports specifying colors as strings, e.g. color("Red")
o The color() statement now overrides colors specified further down in the tree
o if()/else() and the ternary operator can now take any value type as parameter. false, 0, empty string and empty vector or illegal value type will evaluate as false, everything else as true.
o Strings can now be lexographically compared using the <, <=, >, >= operators
o Added PI constant.

View File

@ -22,6 +22,10 @@ unix:freebsd-g++ {
QMAKE_YACC = /usr/local/bin/bison
}
unix:netbsd* {
QMAKE_YACC = /usr/pkg/bin/bison
}
unix:linux* {
exists(/usr/bin/bison) {
QMAKE_YACC = /usr/bin/bison

View File

@ -6,35 +6,65 @@ boost {
!isEmpty(BOOST_DIR) {
QMAKE_INCDIR += $$BOOST_DIR
message("boost location: $$BOOST_DIR")
win32:QMAKE_LIBDIR += -L$$BOOST_DIR/lib
win32: QMAKE_LIBDIR += -L$$BOOST_DIR/lib
}
win32:!CONFIG(mingw-cross-env) {
LIBS += -llibboost_thread-vc90-mt-s-1_46_1 -llibboost_program_options-vc90-mt-s-1_46_1
}
CONFIG(mingw-cross-env) {
DEFINES += BOOST_STATIC
DEFINES += BOOST_THREAD_USE_LIB
DEFINES += Boost_USE_STATIC_LIBS
LIBS += -lboost_thread_win32-mt -lboost_program_options-mt
BOOST_LINK_FLAGS = -lboost_thread_win32-mt -lboost_program_options-mt
}
unix {
BMT_TEST1 = /usr/lib64/libboost*thread-mt*
BMT_TEST2 = /usr/lib/libboost*thread-mt*
BMT_TEST3 = $$BOOST_DIR/lib/libboost*thread-mt*
isEmpty(BOOST_LINK_FLAGS):win32 {
BOOST_LINK_FLAGS = -llibboost_thread-vc90-mt-s-1_46_1 -llibboost_program_options-vc90-mt-s-1_46_1
}
exists($$BMT_TEST1)|exists($$BMT_TEST2)|exists($$BMT_TEST3) {
LIBS += -lboost_thread-mt -lboost_program_options-mt
BOOST_IS_MT = true
}
}
unix|macx {
isEmpty(BOOST_IS_MT) {
LIBS += -lboost_thread -lboost_program_options
# check for OPENSCAD_LIBDIR + multithread
isEmpty(BOOST_LINK_FLAGS) {
OPENSCAD_LIBDIR = $$(OPENSCAD_LIBRARIES)
!isEmpty(OPENSCAD_LIBDIR) {
exists($$OPENSCAD_LIBDIR/lib/libboost*thread-mt*) {
BOOST_LINK_FLAGS = -lboost_thread-mt -lboost_program_options-mt
} else {
exists($$OPENSCAD_LIBDIR/lib/libboost*thread*) {
BOOST_LINK_FLAGS = -lboost_thread -lboost_program_options
}
}
}
}
# check for BOOSTDIR + multithread
isEmpty(BOOST_LINK_FLAGS) {
BOOST_DIR = $$(BOOSTDIR)
!isEmpty(BOOST_DIR) {
exists($$BOOST_DIR/lib/libboost*thread-mt*) {
BOOST_LINK_FLAGS = -lboost_thread-mt -lboost_program_options-mt
} else {
exists($$BOOST_DIR/lib/libboost*thread*) {
BOOST_LINK_FLAGS = -lboost_thread -lboost_program_options
}
}
}
}
isEmpty(BOOST_LINK_FLAGS) {
unix {
BMT_TEST1 = /usr/lib64/libboost*thread-mt*
BMT_TEST2 = /usr/lib/libboost*thread-mt*
BMT_TEST3 = /usr/pkg/lib/libboost*thread-mt* # netbsd
exists($$BMT_TEST1)|exists($$BMT_TEST2)|exists($$BMT_TEST3) {
BOOST_LINK_FLAGS = -lboost_thread-mt -lboost_program_options-mt
}
}
}
isEmpty(BOOST_LINK_FLAGS) {
unix|macx {
BOOST_LINK_FLAGS = -lboost_thread -lboost_program_options
}
}
LIBS += $$BOOST_LINK_FLAGS
}

View File

@ -255,7 +255,6 @@ o variants of module transparent() { %child(); }
o define modules
o define functions
o built-in variables and constants (builtin-tests.scad)
o Write a regression test for the hexagonal cylinder orientation issue
o Caching
- Test that caching is actually performed (speedup + same results)
- Test the modifier characters correctly influence the cache (also when

View File

@ -2,8 +2,8 @@ OpenSCAD Release Checklist
--------------------------
o Update version
release-linux.sh
publish-macosx.sh
scripts/release-linux.sh
scripts/publish-macosx.sh
FIXME: Windows
o Update RELEASE_NOTES
@ -12,7 +12,7 @@ o Tag release
git tag "openscad-2011.12"
o build source package
git archive --format=tar openscad-2011.12 --prefix=openscad-2011.12/ | gzip > openscad-2011.12.src.tar.gz
scripts/git-archive-all.py --prefix=openscad-2011.12/ openscad-2011.12.src.tar.gz
o build binaries
tar xzf openscad-2011.12.src.tar.gz

View File

@ -10,7 +10,7 @@ $ cd tests
$ cmake .
$ make
Windows:
Windows + MSVC:
First, get a normal build working by following instructions at
http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Windows
@ -22,6 +22,10 @@ Then, from the QT command prompt:
> cmake .
> nmake -f Makefile
Cross compiling Linux->Win32:
Please see openscad/tests/CMingw-cross-env.cmake for instructions.
B) Running tests
$ ctest Runs tests enabled by default
@ -50,55 +54,53 @@ Adding a new regression test:
Troubleshooting:
------------------------------
0. Headless unix servers (no X11)
0. Headless unix servers
$ Xvfb :5 -screen 0 800x600x24 &
If you are attempting to run the tests on a unix-like system but only
have shell-console access, you may be able to run the tests by using a
virtual framebuffer program like Xvnc or Xvfb. For example:
$ Xvfb :5 -screen 0 800x600x24 &
$ DISPLAY=:5 ctest
1. Trouble finding libraries
Some versions of Xvfb may fail, however.
1. Trouble finding libraries on unix
To help CMAKE find eigen2, OpenCSG, CGAL, Boost, and GLEW, you can use
environment variables, just like for the main qmake & openscad.pro. Examples:
OPENCSGDIR=~/OpenCSG-1.3.2 EIGEN2DIR=~/eigen2 cmake .
OPENSCAD_LIBRARIES=~ cmake .
CGALDIR=~/CGAL-3.9 BOOSTDIR=~/boost-1.47.0 cmake .
Valid variables are as follows (see CMakeLists.txt for more info):
Valid variables are as follows:
BOOSTDIR, CGALDIR, EIGEN2DIR, GLEWDIR, OPENCSGDIR, OPENSCAD_LIBRARIES
2. Logs
2. Location of logs
Logs of test runs are found in tests/build/Testing/Temporary
Pretty-printed index.html is in a subdir of tests/build/Testing/Temporary
A pretty-printed index.html is in a subdir of tests/build/Testing/Temporary
Expected results are found in tests/regression/*
Actual results are found in tests/build/testname-output/*
3. Cross-compiling
3. Image-based tests takes a long time, they fail, and the log says 'return -11'
Cross-compiling of tests has not been automated nor tested
Imagemagick may have crashed while comparing the expected images to the
test-run generated (actual) images. You can try using the alternate
ImageMagick comparison method by by erasing CMakeCache, and re-running
cmake with -DCOMPARATOR=ncc. This will enable the Normalized Cross
Comparison method.
4. Image-based tests takes a long time, they fail, and it says 'return -11'
Imagemagick may have crashed. You can try using the alternate IM comparator
based on Normalized Cross Correlation. Pass -DCOMPARATOR=ncc to cmake
5. Testing images fails with 'morphology' not found for ImageMagick
4. Testing images fails with 'morphology not found" for ImageMagick in the log
Your version of imagemagick is old. Upgrade, or pass -DCOMPARATOR=old to
cmake. The comparison will be of lowered reliability.
6. Unexplained or bizarre errors.
5. Other issues
This can happen on dynamic-library systems (linux) where you try to use
your own version of a library while the system still has another version
under the system paths. You can diagnose this by looking at your cmake
log as well as your sysinfo.txt file, as well as running 'ldd' against
your binaries, to make sure that the proper versions of libraries are
getting compiled and linked with the test binaries.
7. Other issues
The OpenSCAD User Manual has a section on buildling. Check there for updates:
The OpenSCAD User Manual has a section on buildling. Please check there
for updates:
http://en.wikibooks.org/wiki/OpenSCAD_User_Manual

View File

@ -1,20 +1,37 @@
eigen2 {
CONFIG(mingw-cross-env) {
EIGEN2_INCLUDEPATH = mingw-cross-env/include/eigen2
}
# Optionally specify location of Eigen2 using the
# OPENSCAD_LIBRARIES env. variable
isEmpty(EIGEN2_INCLUDEPATH) {
OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
!isEmpty(OPENSCAD_LIBRARIES_DIR) {
exists($$OPENSCAD_LIBRARIES_DIR/include/eigen2) {
EIGEN2_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/eigen2
}
}
}
# Optionally specify location of Eigen2 using the
# EIGEN2DIR env. variable
EIGEN2_DIR = $$(EIGEN2DIR)
!isEmpty(EIGEN2_DIR) {
EIGEN2_INCLUDEPATH = $$EIGEN2_DIR
}
else {
CONFIG(mingw-cross-env) {
EIGEN2_INCLUDEPATH = mingw-cross-env/include/eigen2
} else {
freebsd-g++: EIGEN2_INCLUDEPATH *= /usr/local/include/eigen2
macx: EIGEN2_INCLUDEPATH *= /opt/local/include/eigen2
!macx:!freebsd-g++:!win32:EIGEN2_INCLUDEPATH *= /usr/include/eigen2
isEmpty(EIGEN2_INCLUDEPATH) {
EIGEN2_DIR = $$(EIGEN2DIR)
!isEmpty(EIGEN2_DIR) {
EIGEN2_INCLUDEPATH = $$EIGEN2_DIR
message("EIGEN2 location: $$EIGEN2_INCLUDEPATH")
}
}
isEmpty(EIGEN2_INCLUDEPATH) {
freebsd-g++: EIGEN2_INCLUDEPATH = /usr/local/include/eigen2
macx: EIGEN2_INCLUDEPATH = /opt/local/include/eigen2
linux*: EIGEN2_INCLUDEPATH = /usr/include/eigen2
netbsd*: EIGEN2_INCLUDEPATH = /usr/pkg/include/eigen2
}
# eigen2 being under 'include/eigen2' needs special prepending
QMAKE_INCDIR_QT = $$EIGEN2_INCLUDEPATH $$QMAKE_INCDIR_QT

View File

@ -20,13 +20,13 @@ difference()
intersection()
{
translate([ -125, -25, -25])
linear_extrude(height = 50, convexity = 1)
translate([ -125, -25, -26])
linear_extrude(height = 52, convexity = 1)
import(file = "example008.dxf", layer = "X");
rotate(90, [0, 1, 0])
translate([ -125, -25, -25])
linear_extrude(height = 50, convexity = 1)
translate([ -125, -25, -26])
linear_extrude(height = 52, convexity = 1)
import(file = "example008.dxf", layer = "X");
}
}

View File

@ -13,6 +13,10 @@ unix:freebsd-g++ {
QMAKE_LEX = /usr/local/bin/flex
}
unix:netbsd* {
QMAKE_LEX = /usr/pkg/bin/flex
}
unix:linux* {
exists(/usr/bin/flex) {
QMAKE_LEX = /usr/bin/flex

@ -1 +1 @@
Subproject commit b7d9ec4c5e5939b2bffcda60b91f4623e9b3c625
Subproject commit f32906ae90b1326a2d7241994e18c6f796799a95

View File

@ -86,6 +86,15 @@ linux*:exists(/usr/lib64/libGLU*)|linux*:exists(/usr/lib/libGLU*) {
LIBS += -lGLU
}
netbsd* {
LIBS += -L/usr/X11R7/lib
QMAKE_LFLAGS += -Wl,-R/usr/X11R7/lib
QMAKE_LFLAGS += -Wl,-R/usr/pkg/lib
!isEmpty(OPENSCAD_LIBDIR) {
QMAKE_LFLAGS += -Wl,-R$$OPENSCAD_LIBDIR/lib
}
}
# See Dec 2011 OpenSCAD mailing list, re: CGAL/GCC bugs.
*g++* {
QMAKE_CXXFLAGS *= -fno-strict-aliasing
@ -146,6 +155,7 @@ HEADERS += src/renderer.h \
src/builtin.h \
src/context.h \
src/csgterm.h \
src/csgtermnormalizer.h \
src/dxfdata.h \
src/dxfdim.h \
src/dxftess.h \
@ -197,6 +207,7 @@ SOURCES += src/mathc99.cc \
src/node.cc \
src/context.cc \
src/csgterm.cc \
src/csgtermnormalizer.cc \
src/polyset.cc \
src/csgops.cc \
src/transform.cc \

View File

@ -0,0 +1,70 @@
diff --git a/example/example.pro b/example/example.pro
index 8891a28..5cb5e81 100644
--- a/example/example.pro
+++ b/example/example.pro
@@ -2,9 +2,16 @@ TEMPLATE = app
TARGET = opencsgexample
CONFIG += opengl warn_on release
-INCLUDEPATH += ../glew/include ../include
-
-LIBS += -L../lib -lopencsg -lglut -L../glew/lib -lGLEW
+INCLUDEPATH += ../include
+LIBS += -L../lib -lopencsg -lGLEW
+macx {
+ INCLUDEPATH += /opt/local/include
+ LIBS += -framework GLUT -L/opt/local/lib
+}
+else {
+ INCLUDEPATH += ../glew/include
+ LIBS += -lglut -L../glew/lib
+}
HEADERS = displaylistPrimitive.h
SOURCES = displaylistPrimitive.cpp main.cpp
diff --git a/opencsg.pro b/opencsg.pro
index b56e622..5cf2d6d 100644
--- a/opencsg.pro
+++ b/opencsg.pro
@@ -1,2 +1,2 @@
TEMPLATE = subdirs
-SUBDIRS = src example
+SUBDIRS = src
diff --git a/src/src.pro b/src/src.pro
index 4843a12..04d3f4d 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -1,10 +1,31 @@
TEMPLATE = lib
TARGET = opencsg
VERSION = 1.3.2
-DESTDIR = ../lib
CONFIG += opengl warn_on release
-INCLUDEPATH += ../include ../glew/include ../
+INCLUDEPATH += ../include ../
+
+# Optionally specify deployment location using the
+# MACOSX_DEPLOY_DIR env. variable
+DEPLOYDIR = $$(MACOSX_DEPLOY_DIR)
+
+!isEmpty(DEPLOYDIR) {
+ message("Deploy")
+ INSTALLDIR = $$(MACOSX_DEPLOY_DIR)
+ INCLUDEPATH += $$(MACOSX_DEPLOY_DIR)/include
+ LIBS += -L$$(MACOSX_DEPLOY_DIR)/lib -lGLEW
+ CONFIG += absolute_library_soname
+ headers.files = ../include/opencsg.h
+ headers.path = $$INSTALLDIR/include
+ INSTALLS += target headers
+ target.path = $$INSTALLDIR/lib
+}
+else {
+ DESTDIR = ../lib
+ INCLUDEPATH += ../glew/include
+ INSTALLS += target
+ target.path = $$DESTDIR
+}
HEADERS = ../include/opencsg.h \
opencsgConfig.h \

171
scripts/git-archive-all.py Executable file
View File

@ -0,0 +1,171 @@
#! /usr/bin/env python
import sys
from os import path, chdir
from subprocess import Popen, PIPE
from sys import argv, stdout
from fnmatch import fnmatch
class GitArchiver(object):
"""
GitArchiver
Scan a git repository and export all tracked files, and submodules.
Checks for .gitattributes files in each directory and uses 'export-ignore'
pattern entries for ignore files in the archive.
Automatically detects output format extension: zip, tar, bz2, or gz
"""
def __init__(self, prefix='', verbose=False, exclude=True, extra=[]):
self.prefix = prefix
self.verbose = verbose
self.exclude = exclude
self.extra = extra
self._excludes = []
def create(self, output_file):
"""
create(str output_file) -> None
Creates the archive, written to the given output_file
Filetype may be one of: gz, zip, bz2, tar
"""
#
# determine the format
#
_, _, format = output_file.rpartition(".")
if format.lower() == 'zip':
from zipfile import ZipFile, ZIP_DEFLATED
output_archive = ZipFile(path.abspath(output_file), 'w')
add = lambda name, arcname: output_archive.write(name, self.prefix + arcname, ZIP_DEFLATED)
elif format.lower() in ['tar', 'bz2', 'gz']:
import tarfile
t_mode = ('w:%s' % format) if format != 'tar' else 'w'
output_archive = tarfile.open(path.abspath(output_file), t_mode)
add = lambda name, arcname: output_archive.add(name, self.prefix + arcname)
else:
raise RuntimeError("Unknown format: '%s'" % format)
#
# compress
#
# extra files first (we may change folder later)
for name in self.extra:
if self.verbose:
toPath = '=> %s%s' % (self.prefix, name) if self.prefix else ""
print 'Compressing %s %s ...' % (name, toPath)
add(name, name)
self._excludes = []
for name, arcname in self.listFiles(path.abspath('')):
if self.verbose:
toPath = '=> %s%s' % (self.prefix, arcname) if self.prefix else ""
print 'Compressing %s %s ...' % (arcname, toPath)
add(name, arcname)
output_archive.close()
def listFiles(self, git_repositary_path, baselevel=''):
"""
listFiles(str git_repository_path, str baselevel='') -> iterator
An iterator method that yields a tuple(filepath, fullpath)
for each file that should be included in the archive.
Skips those that match the exclusion patterns found in
any discovered .gitattributes files along the way.
Recurses into submodules as well.
"""
for filepath in self.runShell('git ls-files --cached --full-name --no-empty-directory'):
fullpath = path.join(baselevel, filepath)
filename = path.basename(filepath)
if self.exclude and filename == '.gitattributes':
self._excludes = []
fh = open(filepath, 'r')
for line in fh:
if not line: break
tokens = line.strip().split()
if 'export-ignore' in tokens[1:]:
self._excludes.append(tokens[0])
fh.close()
if not filename.startswith('.git') and not path.isdir(filepath):
# check the patterns first
ignore = False
for pattern in self._excludes:
if fnmatch(fullpath, pattern) or fnmatch(filename, pattern):
if self.verbose: print 'Exclude pattern matched (%s): %s' % (pattern, fullpath)
ignore = True
break
if ignore:
continue
# baselevel is needed to tell the arhiver where it have to extract file
yield filepath, fullpath
# get paths for every submodule
for submodule in self.runShell("git submodule --quiet foreach 'pwd'"):
chdir(submodule)
# in order to get output path we need to exclude repository path from the submodule path
submodule = submodule[len(git_repositary_path)+1:]
# recursion allows us to process repositories with more than one level of submodules
for git_file in self.listFiles(git_repositary_path, submodule):
yield git_file
@staticmethod
def runShell(cmd):
return Popen(cmd, shell=True, stdout=PIPE).stdout.read().splitlines()
if __name__ == "__main__":
from optparse import OptionParser
parser = OptionParser(usage="usage: %prog [-v] [--prefix PREFIX] [--no-exclude] OUTPUT_FILE", version="%prog 1.0")
parser.add_option('--prefix', type='string', dest='prefix',
default='', help="prepend PREFIX to each filename in the archive")
parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='enable verbose mode')
parser.add_option('--no-exclude', action='store_false', dest='exclude',
default=True, help="Dont read .gitattributes files for patterns containing export-ignore attrib")
parser.add_option('--extra', action='append', dest='extra', default=[],
help="Any additional files to include in the archive.")
options, args = parser.parse_args()
if len(args) != 1:
parser.error('You must specify exactly one output file')
outFile = args[0]
if path.isdir(outFile):
parser.error('You cannot use directory as output')
archiver = GitArchiver(options.prefix,
options.verbose,
options.exclude,
options.extra)
try:
archiver.create(outFile)
except Exception, e:
parser.exit(2, "%s\n" % e)
sys.exit(0)

View File

@ -7,7 +7,9 @@ CGALCache *CGALCache::inst = NULL;
void CGALCache::insert(const std::string &id, const CGAL_Nef_polyhedron &N)
{
this->cache.insert(id, new CGAL_Nef_polyhedron(N), N.weight());
#ifdef DEBUG
PRINTF("CGAL Cache insert: %s (%d verts)", id.substr(0, 40).c_str(), N.weight());
#endif
}
void CGALCache::print()

View File

@ -159,7 +159,7 @@ Response CSGTermEvaluator::visit(State &state, const TransformNode &node)
Response CSGTermEvaluator::visit(State &state, const ColorNode &node)
{
if (state.isPrefix()) {
state.setColor(node.color);
if (!state.color().isValid()) state.setColor(node.color);
}
if (state.isPostfix()) {
applyToChildren(node, CSGT_UNION);

View File

@ -86,7 +86,7 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo,
if (shaderinfo) glUseProgram(shaderinfo[0]);
for (; j < i; j++) {
const Transform3d &m = chain->matrices[j];
double *c = chain->colors[j];
const Color4f &c = chain->colors[j];
glPushMatrix();
glMultMatrixd(m.data());
PolySet::csgmode_e csgmode = chain->types[j] == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL;
@ -99,7 +99,7 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo,
csgmode = PolySet::csgmode_e(csgmode + 10);
} else if (c[0] >= 0 || c[1] >= 0 || c[2] >= 0 || c[3] >= 0) {
// User-defined color or alpha from source
setColor(c, shaderinfo);
setColor(c.data(), shaderinfo);
} else if (chain->types[j] == CSGTerm::TYPE_DIFFERENCE) {
setColor(COLORMODE_CUTOUT, shaderinfo);
} else {

View File

@ -33,6 +33,9 @@ p, li { white-space: pre-wrap; }
<property name="text">
<string>Enable OpenCSG</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>

View File

@ -47,10 +47,16 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent)
// Setup default settings
this->defaultmap["3dview/colorscheme"] = this->colorSchemeChooser->currentItem()->text();
this->defaultmap["editor/fontfamily"] = this->fontChooser->currentText();
this->defaultmap["editor/fontsize"] = this->fontSize->currentText().toUInt();
#ifdef Q_WS_X11
this->defaultmap["editor/fontfamily"] = "Mono";
#elif defined (Q_WS_WIN)
this->defaultmap["editor/fontfamily"] = "Console";
#elif defined (Q_WS_MAC)
this->defaultmap["editor/fontfamily"] = "Monaco";
#endif
this->defaultmap["editor/fontsize"] = 12;
this->defaultmap["advanced/opencsg_show_warning"] = true;
this->defaultmap["advanced/enable_opencsg_opengl1x"] = false;
this->defaultmap["advanced/enable_opencsg_opengl1x"] = true;
// Toolbar
QActionGroup *group = new QActionGroup(this);
@ -212,7 +218,7 @@ void Preferences::updateGUI()
if (!found.isEmpty()) this->colorSchemeChooser->setCurrentItem(found.first());
QString fontfamily = getValue("editor/fontfamily").toString();
int fidx = this->fontChooser->findText(fontfamily);
int fidx = this->fontChooser->findText(fontfamily,Qt::MatchContains);
if (fidx >= 0) {
this->fontChooser->setCurrentIndex(fidx);
}

View File

@ -67,7 +67,7 @@ void ThrownTogetherRenderer::renderCSGChain(CSGChain *chain, bool highlight,
if (polySetVisitMark[std::make_pair(chain->polysets[i].get(), &chain->matrices[i])]++ > 0)
continue;
const Transform3d &m = chain->matrices[i];
double *c = chain->colors[i];
const Color4f &c = chain->colors[i];
glPushMatrix();
glMultMatrixd(m.data());
PolySet::csgmode_e csgmode = chain->types[i] == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL;
@ -93,10 +93,10 @@ void ThrownTogetherRenderer::renderCSGChain(CSGChain *chain, bool highlight,
else csgmode = PolySet::csgmode_e(csgmode);
chain->polysets[i]->render_surface(csgmode, m);
} else if (c[0] >= 0 || c[1] >= 0 || c[2] >= 0 || c[3] >= 0) {
setColor(c);
setColor(c.data());
chain->polysets[i]->render_surface(csgmode, m);
if (showedges) {
glColor4d((c[0]+1)/2, (c[1]+1)/2, (c[2]+1)/2, 1.0);
glColor4f((c[0]+1)/2, (c[1]+1)/2, (c[2]+1)/2, 1.0);
chain->polysets[i]->render_edges(csgmode);
}
} else if (chain->types[i] == CSGTerm::TYPE_DIFFERENCE) {

View File

@ -3,6 +3,7 @@
#include "node.h"
#include "visitor.h"
#include "linalg.h"
class ColorNode : public AbstractNode
{
@ -14,7 +15,7 @@ public:
virtual std::string toString() const;
virtual std::string name() const;
double color[4];
Color4f color;
};
#endif

View File

@ -89,10 +89,9 @@ shared_ptr<CSGTerm> CSGTerm::createCSGTerm(type_e type, CSGTerm *left, CSGTerm *
return createCSGTerm(type, shared_ptr<CSGTerm>(left), shared_ptr<CSGTerm>(right));
}
CSGTerm::CSGTerm(const shared_ptr<PolySet> &polyset, const Transform3d &matrix, const double color[4], const std::string &label)
: type(TYPE_PRIMITIVE), polyset(polyset), label(label), m(matrix)
CSGTerm::CSGTerm(const shared_ptr<PolySet> &polyset, const Transform3d &matrix, const Color4f &color, const std::string &label)
: type(TYPE_PRIMITIVE), polyset(polyset), label(label), m(matrix), color(color)
{
for (int i = 0; i < 4; i++) this->color[i] = color[i];
initBoundingBox();
}
@ -140,127 +139,6 @@ void CSGTerm::initBoundingBox()
}
}
shared_ptr<CSGTerm> CSGTerm::normalize(shared_ptr<CSGTerm> term)
{
// This function implements the CSG normalization
// Reference:
// Goldfeather, J., Molnar, S., Turk, G., and Fuchs, H. Near
// Realtime CSG Rendering Using Tree Normalization and Geometric
// Pruning. IEEE Computer Graphics and Applications, 9(3):20-28,
// 1989.
// http://www.cc.gatech.edu/~turk/my_papers/pxpl_csg.pdf
if (term->type == TYPE_PRIMITIVE) {
return term;
}
do {
while (term && normalize_tail(term)) { }
if (!term || term->type == TYPE_PRIMITIVE) return term;
term->left = normalize(term->left);
} while (term->type != TYPE_UNION &&
(term->right->type != TYPE_PRIMITIVE || term->left->type == TYPE_UNION));
term->right = normalize(term->right);
// FIXME: Do we need to take into account any transformation of item here?
if (!term->right) {
if (term->type == TYPE_UNION || term->type == TYPE_DIFFERENCE) return term->left;
else return term->right;
}
if (!term->left) {
if (term->type == TYPE_UNION) return term->right;
else return term->left;
}
return term;
}
bool CSGTerm::normalize_tail(shared_ptr<CSGTerm> &term)
{
if (term->type == TYPE_UNION || term->type == TYPE_PRIMITIVE) return false;
// Part A: The 'x . (y . z)' expressions
shared_ptr<CSGTerm> x = term->left;
shared_ptr<CSGTerm> y = term->right->left;
shared_ptr<CSGTerm> z = term->right->right;
shared_ptr<CSGTerm> result = term;
// 1. x - (y + z) -> (x - y) - z
if (term->type == TYPE_DIFFERENCE && term->right->type == TYPE_UNION) {
term = createCSGTerm(TYPE_DIFFERENCE,
createCSGTerm(TYPE_DIFFERENCE, x, y),
z);
return true;
}
// 2. x * (y + z) -> (x * y) + (x * z)
else if (term->type == TYPE_INTERSECTION && term->right->type == TYPE_UNION) {
term = createCSGTerm(TYPE_UNION,
createCSGTerm(TYPE_INTERSECTION, x, y),
createCSGTerm(TYPE_INTERSECTION, x, z));
return true;
}
// 3. x - (y * z) -> (x - y) + (x - z)
else if (term->type == TYPE_DIFFERENCE && term->right->type == TYPE_INTERSECTION) {
term = createCSGTerm(TYPE_UNION,
createCSGTerm(TYPE_DIFFERENCE, x, y),
createCSGTerm(TYPE_DIFFERENCE, x, z));
return true;
}
// 4. x * (y * z) -> (x * y) * z
else if (term->type == TYPE_INTERSECTION && term->right->type == TYPE_INTERSECTION) {
term = createCSGTerm(TYPE_INTERSECTION,
createCSGTerm(TYPE_INTERSECTION, x, y),
z);
return true;
}
// 5. x - (y - z) -> (x - y) + (x * z)
else if (term->type == TYPE_DIFFERENCE && term->right->type == TYPE_DIFFERENCE) {
term = createCSGTerm(TYPE_UNION,
createCSGTerm(TYPE_DIFFERENCE, x, y),
createCSGTerm(TYPE_INTERSECTION, x, z));
return true;
}
// 6. x * (y - z) -> (x * y) - z
else if (term->type == TYPE_INTERSECTION && term->right->type == TYPE_DIFFERENCE) {
term = createCSGTerm(TYPE_DIFFERENCE,
createCSGTerm(TYPE_INTERSECTION, x, y),
z);
return true;
}
// Part B: The '(x . y) . z' expressions
x = term->left->left;
y = term->left->right;
z = term->right;
// 7. (x - y) * z -> (x * z) - y
if (term->left->type == TYPE_DIFFERENCE && term->type == TYPE_INTERSECTION) {
term = createCSGTerm(TYPE_DIFFERENCE,
createCSGTerm(TYPE_INTERSECTION, x, z),
y);
return true;
}
// 8. (x + y) - z -> (x - z) + (y - z)
else if (term->left->type == TYPE_UNION && term->type == TYPE_DIFFERENCE) {
term = createCSGTerm(TYPE_UNION,
createCSGTerm(TYPE_DIFFERENCE, x, z),
createCSGTerm(TYPE_DIFFERENCE, y, z));
return true;
}
// 9. (x + y) * z -> (x * z) + (y * z)
else if (term->left->type == TYPE_UNION && term->type == TYPE_INTERSECTION) {
term = createCSGTerm(TYPE_UNION,
createCSGTerm(TYPE_INTERSECTION, x, z),
createCSGTerm(TYPE_INTERSECTION, y, z));
return true;
}
return false;
}
std::string CSGTerm::dump()
{
std::stringstream dump;
@ -281,7 +159,7 @@ CSGChain::CSGChain()
{
}
void CSGChain::add(const shared_ptr<PolySet> &polyset, const Transform3d &m, double *color, CSGTerm::type_e type, std::string label)
void CSGChain::add(const shared_ptr<PolySet> &polyset, const Transform3d &m, const Color4f &color, CSGTerm::type_e type, std::string label)
{
polysets.push_back(polyset);
matrices.push_back(m);

View File

@ -28,14 +28,11 @@ public:
shared_ptr<CSGTerm> right;
BoundingBox bbox;
CSGTerm(const shared_ptr<PolySet> &polyset, const Transform3d &matrix, const double color[4], const std::string &label);
CSGTerm(const shared_ptr<PolySet> &polyset, const Transform3d &matrix, const Color4f &color, const std::string &label);
~CSGTerm();
const BoundingBox &getBoundingBox() const { return this->bbox; }
static shared_ptr<CSGTerm> normalize(shared_ptr<CSGTerm> term);
static bool normalize_tail(shared_ptr<CSGTerm> &term);
std::string dump();
private:
CSGTerm(type_e type, shared_ptr<CSGTerm> left, shared_ptr<CSGTerm> right);
@ -44,7 +41,7 @@ private:
void initBoundingBox();
Transform3d m;
double color[4];
Color4f color;
friend class CSGChain;
};
@ -54,13 +51,13 @@ class CSGChain
public:
std::vector<shared_ptr<PolySet> > polysets;
std::vector<Transform3d> matrices;
std::vector<double*> colors;
std::vector<Color4f> colors;
std::vector<CSGTerm::type_e> types;
std::vector<std::string> labels;
CSGChain();
void add(const shared_ptr<PolySet> &polyset, const Transform3d &m, double *color, CSGTerm::type_e type, std::string label);
void add(const shared_ptr<PolySet> &polyset, const Transform3d &m, const Color4f &color, CSGTerm::type_e type, std::string label);
void import(shared_ptr<CSGTerm> term, CSGTerm::type_e type = CSGTerm::TYPE_UNION);
std::string dump();

150
src/csgtermnormalizer.cc Normal file
View File

@ -0,0 +1,150 @@
#include "csgtermnormalizer.h"
#include "csgterm.h"
#include "printutils.h"
shared_ptr<CSGTerm> CSGTermNormalizer::normalize(const shared_ptr<CSGTerm> &root)
{
shared_ptr<CSGTerm> temp = root;
while (1) {
shared_ptr<CSGTerm> n = normalizePass(temp);
if (temp == n) break;
temp = n;
int num = count(temp);
#ifdef DEBUG
PRINTF("Normalize count: %d\n", num);
#endif
if (num > 5000) {
PRINTF("WARNING: Normalized tree is growing past 5000 elements. Aborting normalization.\n");
return root;
}
}
return temp;
}
shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term)
{
// This function implements the CSG normalization
// Reference:
// Goldfeather, J., Molnar, S., Turk, G., and Fuchs, H. Near
// Realtime CSG Rendering Using Tree Normalization and Geometric
// Pruning. IEEE Computer Graphics and Applications, 9(3):20-28,
// 1989.
// http://www.cc.gatech.edu/~turk/my_papers/pxpl_csg.pdf
if (term->type == CSGTerm::TYPE_PRIMITIVE) {
return term;
}
do {
while (term && normalize_tail(term)) { }
if (!term || term->type == CSGTerm::TYPE_PRIMITIVE) return term;
term->left = normalizePass(term->left);
} while (term->type != CSGTerm::TYPE_UNION &&
(term->right->type != CSGTerm::TYPE_PRIMITIVE || term->left->type == CSGTerm::TYPE_UNION));
term->right = normalizePass(term->right);
// FIXME: Do we need to take into account any transformation of item here?
if (!term->right) {
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_DIFFERENCE) return term->left;
else return term->right;
}
if (!term->left) {
if (term->type == CSGTerm::TYPE_UNION) return term->right;
else return term->left;
}
return term;
}
bool CSGTermNormalizer::normalize_tail(shared_ptr<CSGTerm> &term)
{
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_PRIMITIVE) return false;
// Part A: The 'x . (y . z)' expressions
shared_ptr<CSGTerm> x = term->left;
shared_ptr<CSGTerm> y = term->right->left;
shared_ptr<CSGTerm> z = term->right->right;
shared_ptr<CSGTerm> result = term;
// 1. x - (y + z) -> (x - y) - z
if (term->type == CSGTerm::TYPE_DIFFERENCE && term->right->type == CSGTerm::TYPE_UNION) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE,
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, y),
z);
return true;
}
// 2. x * (y + z) -> (x * y) + (x * z)
else if (term->type == CSGTerm::TYPE_INTERSECTION && term->right->type == CSGTerm::TYPE_UNION) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, y),
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z));
return true;
}
// 3. x - (y * z) -> (x - y) + (x - z)
else if (term->type == CSGTerm::TYPE_DIFFERENCE && term->right->type == CSGTerm::TYPE_INTERSECTION) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, y),
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, z));
return true;
}
// 4. x * (y * z) -> (x * y) * z
else if (term->type == CSGTerm::TYPE_INTERSECTION && term->right->type == CSGTerm::TYPE_INTERSECTION) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION,
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, y),
z);
return true;
}
// 5. x - (y - z) -> (x - y) + (x * z)
else if (term->type == CSGTerm::TYPE_DIFFERENCE && term->right->type == CSGTerm::TYPE_DIFFERENCE) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, y),
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z));
return true;
}
// 6. x * (y - z) -> (x * y) - z
else if (term->type == CSGTerm::TYPE_INTERSECTION && term->right->type == CSGTerm::TYPE_DIFFERENCE) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE,
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, y),
z);
return true;
}
// Part B: The '(x . y) . z' expressions
x = term->left->left;
y = term->left->right;
z = term->right;
// 7. (x - y) * z -> (x * z) - y
if (term->left->type == CSGTerm::TYPE_DIFFERENCE && term->type == CSGTerm::TYPE_INTERSECTION) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE,
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z),
y);
return true;
}
// 8. (x + y) - z -> (x - z) + (y - z)
else if (term->left->type == CSGTerm::TYPE_UNION && term->type == CSGTerm::TYPE_DIFFERENCE) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, z),
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, y, z));
return true;
}
// 9. (x + y) * z -> (x * z) + (y * z)
else if (term->left->type == CSGTerm::TYPE_UNION && term->type == CSGTerm::TYPE_INTERSECTION) {
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z),
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, y, z));
return true;
}
return false;
}
int CSGTermNormalizer::count(const shared_ptr<CSGTerm> &term) const
{
if (!term) return 0;
return term->type == CSGTerm::TYPE_PRIMITIVE ? 1 : 0 + count(term->left) + count(term->right);
}

22
src/csgtermnormalizer.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef CSGTERMNORMALIZER_H_
#define CSGTERMNORMALIZER_H_
#include "memory.h"
class CSGTermNormalizer
{
public:
CSGTermNormalizer() : counter(0) {}
~CSGTermNormalizer() {}
shared_ptr<class CSGTerm> normalize(const shared_ptr<CSGTerm> &term);
private:
shared_ptr<CSGTerm> normalizePass(shared_ptr<CSGTerm> term) ;
bool normalize_tail(shared_ptr<CSGTerm> &term);
int count(const shared_ptr<CSGTerm> &term) const;
int counter;
};
#endif

View File

@ -84,31 +84,22 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output, QProgressDial
stream << x3 << " " << y3 << " " << z3;
std::string vs3 = stream.str();
if (vs1 != vs2 && vs1 != vs3 && vs2 != vs3) {
// The above condition ensures that vs1-vs2, vs1-vs3, and their cross
// product are non-zero. Floating point arithmetic may however truncate
// small values to 0. This can be avoided by first scaling the components
// of vs1-vs2 and vs1-vs3. This has no effect on the resulting unit
// normal vector.
double dn[6] = { x1-x2, y1-y2, z1-z2, x1-x3, y1-y3, z1-z3 };
double maxdn = 0;
int i;
for (i = 0; i < 6; ++i) {
double dx = dn[i];
if (dx < 0) dx = -dx;
if (dx > maxdn) maxdn = dx;
// The above condition ensures that there are 3 distinct vertices, but
// they may be collinear. If they are, the unit normal is meaningless
// so the default value of "1 0 0" can be used. If the vertices are not
// collinear then the unit normal must be calculated from the
// components.
if (!CGAL::collinear(v1.point(),v2.point(),v3.point())) {
CGAL_Polyhedron::Traits::Vector_3 normal = CGAL::normal(v1.point(),v2.point(),v3.point());
output << " facet normal "
<< CGAL::sign(normal.x()) * sqrt(CGAL::to_double(normal.x()*normal.x()/normal.squared_length()))
<< " "
<< CGAL::sign(normal.y()) * sqrt(CGAL::to_double(normal.y()*normal.y()/normal.squared_length()))
<< " "
<< CGAL::sign(normal.z()) * sqrt(CGAL::to_double(normal.z()*normal.z()/normal.squared_length()))
<< "\n";
}
for (i = 0; i < 6; ++i) dn[i] /= maxdn;
double nx = dn[1]*dn[5] - dn[2]*dn[4];
double ny = dn[2]*dn[3] - dn[0]*dn[5];
double nz = dn[0]*dn[4] - dn[1]*dn[3];
double nlength = sqrt(nx*nx + ny*ny + nz*nz);
// Avoid generating normals for polygons with zero area
double eps = 0.000001;
if (nlength < eps) nlength = 1.0;
output << " facet normal "
<< nx / nlength << " "
<< ny / nlength << " "
<< nz / nlength << "\n";
else output << " facet normal 1 0 0\n";
output << " outer loop\n";
output << " vertex " << vs1 << "\n";
output << " vertex " << vs2 << "\n";

View File

@ -154,9 +154,9 @@ void GLView::initializeGL()
"Extensions:\n"
"%s\n",
glewGetString(GLEW_VERSION),
glGetString(GL_VERSION),
glGetString(GL_RENDERER),
glGetString(GL_VENDOR),
glGetString(GL_VERSION),
rbits, gbits, bbits, abits, dbits, sbits,
glGetString(GL_EXTENSIONS));
// FIXME: glGetString(GL_EXTENSIONS) is deprecated in OpenGL 3.0.
@ -500,10 +500,11 @@ void GLView::paintGL()
// FIXME: This was an attempt to keep contrast with background, but is suboptimal
// (e.g. nearly invisible against a gray background).
int r,g,b;
r=g=b=0;
// int r,g,b;
// r=g=b=0;
// bgcol.getRgb(&r, &g, &b);
glColor3d((255.0-r)/255.0, (255.0-g)/255.0, (255.0-b)/255.0);
// glColor3f((255.0f-r)/255.0f, (255.0f-g)/255.0f, (255.0f-b)/255.0f);
glColor3f(0.0f, 0.0f, 0.0f);
glBegin(GL_LINES);
// X Label
glVertex3d(xlabel_x-3, xlabel_y-3, 0); glVertex3d(xlabel_x+3, xlabel_y+3, 0);

View File

@ -114,27 +114,6 @@ use[ \t\r\n>]*"<"[^\t\r\n>]+">" {
return TOK_USE;
}
"<"[^ \t\r\n>]+">" {
char *filename = strdup(yytext+1);
filename[strlen(filename)-1] = 0;
QFileInfo finfo(QDir(parser_source_path), filename);
if (!finfo.exists()) {
finfo = QFileInfo(QDir(librarydir), filename);
}
PRINTF("DEPRECATED: Support for implicit include will be removed in future releases. Use `include <filename>' instead.");
handle_dep(finfo.absoluteFilePath().toStdString());
yyin = fopen(finfo.absoluteFilePath().toLocal8Bit(), "r");
if (!yyin) {
PRINTF("WARNING: Can't open input file `%s'.", filename);
} else {
openfiles.append(yyin);
yypush_buffer_state(yy_create_buffer( yyin, YY_BUF_SIZE ));
BEGIN(INITIAL);
}
free(filename);
}
<<EOF>> {
if(!path_stack.empty())
path_stack.pop();

View File

@ -15,4 +15,10 @@ using Eigen::Transform3d;
BoundingBox operator*(const Transform3d &m, const BoundingBox &box);
class Color4f : public Eigen::Vector4f
{
public:
bool isValid() const { return this->minCoeff() >= 0.0f; }
};
#endif

View File

@ -43,6 +43,7 @@
#endif
#include "ProgressWidget.h"
#include "ThrownTogetherRenderer.h"
#include "csgtermnormalizer.h"
#include <QMenu>
#include <QTime>
@ -181,7 +182,6 @@ MainWindow::MainWindow(const QString &filename)
editor->setTabStopWidth(30);
#endif
editor->setLineWrapping(true); // Not designable
setFont("", 12); // Init default font
this->glview->statusLabel = new QLabel(this);
statusBar()->addWidget(this->glview->statusLabel);
@ -765,15 +765,8 @@ void MainWindow::compileCSG(bool procevents)
if (procevents)
QApplication::processEvents();
this->root_norm_term = this->root_raw_term;
// CSG normalization
while (1) {
shared_ptr<CSGTerm> n = CSGTerm::normalize(this->root_norm_term);
if (this->root_norm_term == n) break;
this->root_norm_term = n;
}
CSGTermNormalizer normalizer;
this->root_norm_term = normalizer.normalize(this->root_raw_term);
assert(this->root_norm_term);
root_chain = new CSGChain();
@ -787,11 +780,7 @@ void MainWindow::compileCSG(bool procevents)
highlights_chain = new CSGChain();
for (unsigned int i = 0; i < highlight_terms.size(); i++) {
while (1) {
shared_ptr<CSGTerm> n = CSGTerm::normalize(highlight_terms[i]);
if (highlight_terms[i] == n) break;
highlight_terms[i] = n;
}
highlight_terms[i] = normalizer.normalize(highlight_terms[i]);
highlights_chain->import(highlight_terms[i]);
}
}
@ -804,11 +793,7 @@ void MainWindow::compileCSG(bool procevents)
background_chain = new CSGChain();
for (unsigned int i = 0; i < background_terms.size(); i++) {
while (1) {
shared_ptr<CSGTerm> n = CSGTerm::normalize(background_terms[i]);
if (background_terms[i] == n) break;
background_terms[i] = n;
}
background_terms[i] = normalizer.normalize(background_terms[i]);
background_chain->import(background_terms[i]);
}
}
@ -1762,8 +1747,9 @@ MainWindow::preferences()
void MainWindow::setFont(const QString &family, uint size)
{
QFont font(editor->font());
QFont font;
if (!family.isEmpty()) font.setFamily(family);
else font.setFixedPitch(true);
if (size > 0) font.setPointSize(size);
font.setStyleHint(QFont::TypeWriter);
editor->setFont(font);

View File

@ -364,6 +364,10 @@ int main(int argc, char **argv)
}
if (dxf_output_file) {
if (root_N.dim != 2) {
fprintf(stderr, "Current top level object is not a 2D object.\n");
exit(1);
}
std::ofstream fstream(dxf_output_file);
if (!fstream.is_open()) {
PRINTF("Can't open file \"%s\" for export", dxf_output_file);

View File

@ -588,12 +588,15 @@ AbstractModule *parse(const char *text, const char *path, int debug)
if (!module)
return NULL;
BOOST_FOREACH(Module::ModuleContainer::value_type &m, module->usedlibs) {
module->usedlibs[m.first] = Module::compile_library(m.first);
if (!module->usedlibs[m.first]) {
PRINTF("WARNING: Failed to compile library `%s'.", m.first.c_str());
module->usedlibs.erase(m.first);
}
// Iterating manually since we want to modify the container while iterating
Module::ModuleContainer::iterator iter = module->usedlibs.begin();
while (iter != module->usedlibs.end()) {
Module::ModuleContainer::iterator curr = iter++;
curr->second = Module::compile_library(curr->first);
if (!curr->second) {
PRINTF("WARNING: Failed to compile library `%s'.", curr->first.c_str());
module->usedlibs.erase(curr);
}
}
parser_error_pos = -1;

View File

@ -2,7 +2,7 @@
#include "rendersettings.h"
#include <QColor>
void Renderer::setColor(const double color[4], GLint *shaderinfo) const
void Renderer::setColor(const float color[4], GLint *shaderinfo) const
{
QColor col = RenderSettings::inst()->color(RenderSettings::OPENCSG_FACE_FRONT_COLOR);
double c[4] = {color[0], color[1], color[2], color[3]};

View File

@ -25,7 +25,7 @@ public:
COLORMODE_BACKGROUND_EDGES
};
virtual void setColor(const double color[4], GLint *shaderinfo = NULL) const;
virtual void setColor(const float color[4], GLint *shaderinfo = NULL) const;
virtual void setColor(ColorMode colormode, GLint *shaderinfo = NULL) const;
};

View File

@ -9,8 +9,8 @@ class State
public:
State(const class AbstractNode *parent)
: parentnode(parent), isprefix(false), ispostfix(false), numchildren(0) {
m = Transform3d::Identity();
for (int i=0;i<4;i++) this->c[i] = -1.0;
this->matrix_ = Transform3d::Identity();
this->color_.fill(-1.0f);
}
virtual ~State() {}
@ -18,15 +18,15 @@ public:
void setPostfix(bool on) { this->ispostfix = on; }
void setNumChildren(unsigned int numc) { this->numchildren = numc; }
void setParent(const AbstractNode *parent) { this->parentnode = parent; }
void setMatrix(const Transform3d &m) { this->m = m; }
void setColor(const double c[4]) { memcpy(this->c, c, 4*sizeof(double)); }
void setMatrix(const Transform3d &m) { this->matrix_ = m; }
void setColor(const Color4f &c) { this->color_ = c; }
bool isPrefix() const { return this->isprefix; }
bool isPostfix() const { return this->ispostfix; }
unsigned int numChildren() const { return this->numchildren; }
const AbstractNode *parent() const { return this->parentnode; }
const Transform3d &matrix() const { return this->m; }
const double *color() const { return this->c; }
const Transform3d &matrix() const { return this->matrix_; }
const Color4f &color() const { return this->color_; }
private:
const AbstractNode * parentnode;
@ -35,8 +35,8 @@ private:
unsigned int numchildren;
// Transformation matrix and color. FIXME: Generalize such state variables?
Transform3d m;
double c[4];
Transform3d matrix_;
Color4f color_;
};
#endif

View File

@ -12,4 +12,9 @@ translate([22,-11,0]) cylinder(h=5, r=5, r1=0, center=true);
translate([22,0,0]) cylinder(h=5, r=5, r2=0);
translate([22,11,0]) cylinder(h=15, r=5, r2=5);
// This tests for hexagonal cylinder orientation, since people
// tend to "abuse" this for captured nut slots
translate([-10,0,0]) cylinder(h=2, r=3, $fn=6);
// FIXME: We could test $fs, $fa, $fn as well

View File

@ -7,7 +7,7 @@ include <non/existent/path/non-file>
//Test with empty path
include <include-test5.scad>
//Test without preceeding space
//Test without preceding space
include<include-test5.scad>
//Test with other strange character that is allowed
@ -34,7 +34,7 @@ module test1()
translate([-2,-2,0]) test6();
//Just to give a top level object
translate([0,-2,0]) sphere(0.7, $fn=16);
translate([0,-2,0]) sphere(test2_variable, $fn=16);
}
test1();

View File

@ -22,7 +22,7 @@ translate([12,0,0]) intersection() {
translate([12,12,0]) intersection() {
cube([10,10,10], center=true);
translate([0,0,7]) cylinder(r=4, h=4, center=true);
translate([0,0,7.01]) cylinder(r=4, h=4, center=true);
}
translate([24,0,0]) intersection() {

View File

@ -4,7 +4,9 @@ include <include-test3.scad>
//Test relative file location
include <../include-test4.scad>
module test2 ()
test2_variable = 0.7;
module test2()
{
cube(center=true);
}

View File

@ -0,0 +1,14 @@
//Test nested use
use <use-test3.scad>
//Test relative file location
use <../use-test4.scad>
test2_variable = 0.7;
module test2()
{
translate([2,0,0]) test3();
translate([2,-2,0]) test4();
cube(center=true);
}

View File

@ -0,0 +1,4 @@
module test3()
{
cylinder(r1=0.7, r2=0.2, center=true);
}

View File

@ -0,0 +1,4 @@
module test4()
{
cylinder(r=0.5, $fn=10, center=true);
}

7
testdata/scad/features/use test6.scad vendored Normal file
View File

@ -0,0 +1,7 @@
module test6()
{
difference() {
cube(center=true);
cylinder(r=0.4, h=2, center=true);
}
}

4
testdata/scad/features/use-test5.scad vendored Normal file
View File

@ -0,0 +1,4 @@
module test5()
{
sphere(r=0.5, $fn=8);
}

39
testdata/scad/features/use-tests.scad vendored Normal file
View File

@ -0,0 +1,39 @@
//Test that the entire path is pushed onto the stack upto the last '/'
use <sub1/sub2/sub3/sub4/use-test2.scad>
//Test that a non existent path/file doesn't screw things up
use <non/existent/path/non-file>
//Test with empty path
use <use-test5.scad>
//Test without preceding space
use<use-test5.scad>
//Test with other strange character that is allowed
use>>>>><use-test5.scad>
//Test that filenames with spaces work
use <use test6.scad>
//Test with empty file
use<test/>
//Test with empty path and file
use </>
module test1()
{
test2();
// test3() and test4() are not directly included and thus not imported into
// this scope
translate([4,0,0]) test3();
translate([4,-2,0]) test4();
translate([-2,0,0]) test5();
translate([-2,-2,0]) test6();
// test2_variable won't be visible
translate([0,-2,0]) sphere(test2_variable, $fn=16);
}
test1();

4
tests/.gitignore vendored
View File

@ -19,4 +19,6 @@ out.png
/echotest
/opencsgtest
/throwntogethertest
/yee_compare
/cgalstlsanitytest
/sysinfo.txt
/CTestCustom.cmake

View File

@ -13,6 +13,9 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}")
# Build debug build as default
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing")
endif()
@ -24,11 +27,11 @@ endif()
# Windows
#
if(WIN32)
if(WIN32 AND MSVC)
set(WIN32_STATIC_BUILD "True")
endif()
if(WIN32_STATIC_BUILD)
if(WIN32_STATIC_BUILD AND MSVC)
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(EMSG "\nTo build Win32 STATIC OpenSCAD please see doc/testing.txt")
message(FATAL_ERROR ${EMSG})
@ -36,7 +39,7 @@ if(WIN32_STATIC_BUILD)
endif()
# Disable warnings
if(WIN32)
if(WIN32 AND MSVC)
# too long decorated names
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4503")
# int cast to bool in CGAL
@ -52,7 +55,7 @@ if(WIN32)
endif()
# Debugging - if you uncomment, use nmake -f Makefile > log.txt (the log is big)
if(WIN32)
if(WIN32 AND MSVC)
# Linker debugging
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -VERBOSE")
@ -60,26 +63,44 @@ if(WIN32)
# you have to pass -DCMAKE_VERBOSE_MAKEFILE=ON to cmake when you run it.
endif()
if(WIN32 AND CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -frounding-math")
endif()
#
# Build test apps
#
function(inclusion user_set_path found_paths)
# If user_set_path indicates an env. variable was specifically
# set by the user, then found_paths become an include priority (prepend);
# otherwise found_paths are stuck on the end of the include flags (append).
# message(STATUS "inclusion ${user_set_path} ${found_paths}")
# message(STATUS "inclusion ${${user_set_path}} ${${found_paths}}")
set( inclusion_match 0 )
foreach( found_path ${${found_paths}} )
if (${found_path} MATCHES ${${user_set_path}}.*)
set( inclusion_match 1 )
endif()
endforeach()
if (user_set_path AND inclusion_match)
include_directories(BEFORE ${${found_paths}})
# message(STATUS "inclusion prepend ${${found_paths}} for ${user_set_path}")
else()
include_directories(AFTER ${${found_paths}})
# message(STATUS "inclusion append ${${found_paths}} for ${user_set_path}")
endif()
set( inclusion_match 0 )
endfunction()
# Boost
if (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
set(BOOST_ROOT "$ENV{OPENSCAD_LIBRARIES}")
endif()
if (NOT $ENV{BOOSTDIR} STREQUAL "")
set(BOOST_DIR "$ENV{BOOSTDIR}")
endif()
if (NOT ${BOOST_DIR} STREQUAL "")
set(BOOST_ROOT ${BOOST_DIR})
message(STATUS "BOOST_ROOT: " ${BOOST_ROOT})
set(Boost_NO_SYSTEM_PATHS "TRUE")
set(Boost_DEBUG TRUE)
endif()
#
# FindBoost.cmake has been included from Cmake's GIT circa the end of 2011
# because most existing installs of cmake had a buggy older version.
#
# Update this if FindBoost.cmake gets out of sync with the current boost release
# set(Boost_ADDITIONAL_VERSIONS "1.47.0" "1.46.0")
if (WIN32)
set(Boost_USE_STATIC_LIBS TRUE)
@ -87,21 +108,32 @@ if (WIN32)
set(BOOST_THREAD_USE_LIB TRUE)
endif()
# Update this if FindBoost.cmake gets out of sync with the current boost release
# set(Boost_ADDITIONAL_VERSIONS "1.47.0" "1.46.0")
find_package( Boost 1.35.0 COMPONENTS thread program_options REQUIRED)
if(Boost_FOUND)
message(STATUS "Boost includes found: " ${Boost_INCLUDE_DIRS})
message(STATUS "Boost libraries found:")
foreach(boostlib ${Boost_LIBRARIES})
message(STATUS " " ${boostlib})
endforeach()
include_directories(${Boost_INCLUDE_DIRS})
else()
message(STATUS "BOOST_ROOT: ${BOOST_ROOT}")
message(FATAL_ERROR "Boost not found.")
if (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
set(BOOST_ROOT "$ENV{OPENSCAD_LIBRARIES}")
if (EXISTS ${BOOST_ROOT}/include/boost)
# if boost is under OPENSCAD_LIBRARIES, then
# don't look in the system paths (workaround FindBoost.cmake bug)
set(Boost_NO_SYSTEM_PATHS "TRUE")
message(STATUS "BOOST_ROOT: " ${BOOST_ROOT})
endif()
endif()
if (NOT $ENV{BOOSTDIR} STREQUAL "")
set(BOOST_ROOT "$ENV{BOOSTDIR}")
set(Boost_NO_SYSTEM_PATHS "TRUE")
set(Boost_DEBUG TRUE)
message(STATUS "BOOST_ROOT: " ${BOOST_ROOT})
endif()
find_package( Boost 1.35.0 COMPONENTS thread program_options REQUIRED)
message(STATUS "Boost includes found: " ${Boost_INCLUDE_DIRS})
message(STATUS "Boost libraries found:")
foreach(boostlib ${Boost_LIBRARIES})
message(STATUS " " ${boostlib})
endforeach()
inclusion(BOOST_ROOT Boost_INCLUDE_DIRS)
# Mac OS X
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
FIND_LIBRARY(COCOA_LIBRARY Cocoa REQUIRED)
@ -109,19 +141,21 @@ endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# Qt4
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
# make /usr/local/include/qt4 come before /usr/local/include (QT4 vs QT3)
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
endif()
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
find_package(OpenGL REQUIRED)
find_package(Qt4 COMPONENTS QtCore QtGui QtOpenGL REQUIRED)
include(${QT_USE_FILE})
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE OFF)
if (MINGW_CROSS_ENV_DIR)
mingw_cross_env_find_qt()
mingw_cross_env_info()
include_directories( ${QT_INCLUDE_DIRS} )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QT_CFLAGS_OTHER}")
else()
find_package(Qt4 COMPONENTS QtCore QtGui QtOpenGL REQUIRED)
include(${QT_USE_FILE})
endif()
set(CMAKE_INCLUDE_DIRECTORIES_BEFORE OFF)
# Eigen2
# Turn off Eigen SIMD optimization
@ -131,24 +165,34 @@ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
endif()
endif()
if (NOT $ENV{EIGEN2DIR} STREQUAL "")
set(EIGEN2_DIR "$ENV{EIGEN2DIR}")
elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
set(EIGEN2_DIR "$ENV{OPENSCAD_LIBRARIES}")
endif()
if (NOT EIGEN2_INCLUDE_DIR)
if (EIGEN2_DIR)
set(EIGEN2_FIND_HINTS "${EIGEN2_DIR}/include/eigen2")
endif()
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
set(EIGEN2_FIND_PATHS /usr/local/include/eigen2)
elseif (${CMAKE_SYSTEM_NAME} MATCHES "NetBSD")
set(EIGEN2_FIND_PATHS /usr/pkg/include/eigen2)
else()
set(EIGEN2_FIND_PATHS /opt/local/include/eigen2 /usr/include/eigen2)
endif()
find_path(EIGEN2_INCLUDE_DIR
Eigen/Core
HINTS $ENV{EIGEN2DIR}
PATHS /opt/local/include/eigen2 /usr/include/eigen2)
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
find_path(EIGEN2_INCLUDE_DIR
Eigen/Core
HINTS $ENV{EIGEN2DIR}
PATHS /usr/local/include/eigen2 )
endif()
HINTS ${EIGEN2_FIND_HINTS}
PATHS ${EIGEN2_FIND_PATHS})
if (NOT EIGEN2_INCLUDE_DIR)
message(FATAL_ERROR "Eigen2 not found")
else()
message(STATUS "Eigen2 found in " ${EIGEN2_INCLUDE_DIR})
endif()
endif()
include_directories(${EIGEN2_INCLUDE_DIR})
inclusion(EIGEN2_DIR EIGEN2_INCLUDE_DIR)
# OpenCSG
if (NOT $ENV{OPENCSGDIR} STREQUAL "")
@ -171,10 +215,14 @@ if (NOT OPENCSG_INCLUDE_DIR)
message(STATUS "OpenCSG library found in " ${OPENCSG_LIBRARY})
endif()
endif()
include_directories(${OPENCSG_INCLUDE_DIR})
inclusion(OPENCSG_DIR OPENCSG_INCLUDE_DIR)
# GLEW
if(WIN32_STATIC_BUILD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGLEW_STATIC")
endif()
if (NOT $ENV{GLEWDIR} STREQUAL "")
set(GLEW_DIR "$ENV{GLEWDIR}")
elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
@ -182,11 +230,8 @@ elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
endif()
find_package(GLEW REQUIRED)
include_directories(${GLEW_INCLUDE_PATH})
if(WIN32_STATIC_BUILD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGLEW_STATIC")
endif()
inclusion( GLEW_DIR GLEW_INCLUDE_PATH )
# Flex/Bison
find_package(BISON REQUIRED)
@ -196,6 +241,11 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
set(FLEX_EXECUTABLE /usr/local/bin/flex)
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD")
include_directories( /usr/pkg/include /usr/X11R7/include )
set(FLEX_EXECUTABLE /usr/pkg/bin/flex)
endif()
find_package(FLEX REQUIRED)
# The COMPILE_FLAGS and forced C++ compiler is just to be compatible with qmake
if (WIN32)
@ -211,8 +261,13 @@ set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c PROPERTIES
if (NOT $ENV{CGALDIR} STREQUAL "")
set(CGAL_DIR "$ENV{CGALDIR}")
elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "")
set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL")
set(CMAKE_MODULE_PATH "${CGAL_DIR}")
if (EXISTS "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL")
set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL")
set(CMAKE_MODULE_PATH "${CGAL_DIR}")
elseif (EXISTS "$ENV{OPENSCAD_LIBRARIES}/include/CGAL")
set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}")
set(CMAKE_MODULE_PATH "${CGAL_DIR}")
endif()
endif()
message(STATUS "CGAL_DIR: " ${CGAL_DIR})
find_package(CGAL REQUIRED)
@ -224,15 +279,17 @@ message(STATUS "CGAL libraries found in " ${CGAL_LIBRARIES_DIR} )
if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6)
message(FATAL_ERROR "CGAL >= 3.6 required")
endif()
include_directories(${CGAL_INCLUDE_DIRS})
inclusion(CGAL_DIR CGAL_INCLUDE_DIRS)
# Imagemagick
find_package(ImageMagick COMPONENTS convert)
if (ImageMagick_convert_FOUND)
message(STATUS "ImageMagick convert executable found: " ${ImageMagick_convert_EXECUTABLE})
else()
message(FATAL_ERROR "Couldn't find imagemagick 'convert' program")
if (NOT SKIP_IMAGEMAGICK)
find_package(ImageMagick COMPONENTS convert)
if (ImageMagick_convert_FOUND)
message(STATUS "ImageMagick convert executable found: " ${ImageMagick_convert_EXECUTABLE})
else()
message(FATAL_ERROR "Couldn't find imagemagick 'convert' program")
endif()
endif()
# Internal includes
@ -253,6 +310,7 @@ set(CORE_SOURCES
../src/node.cc
../src/context.cc
../src/csgterm.cc
../src/csgtermnormalizer.cc
../src/polyset.cc
../src/csgops.cc
../src/transform.cc
@ -319,6 +377,7 @@ set(OFFSCREEN_SOURCES
system-gl.cc)
add_library(tests-core STATIC ${CORE_SOURCES})
target_link_libraries(tests-core ${OPENGL_LIBRARY})
add_library(tests-common STATIC ${COMMON_SOURCES})
target_link_libraries(tests-common tests-core)
add_library(tests-cgal STATIC ${CGAL_SOURCES})
@ -453,8 +512,8 @@ endfunction()
# comparison method to use
if (NOT $ENV{COMPARATOR} STREQUAL "")
set(COMPARATOR "$ENV{COMPARATOR}")
message(STATUS "ImageMagick method modified with COMPARATOR: " ${COMPARATOR})
endif()
message(STATUS "COMPARATOR: " ${COMPARATOR})
#
# This functions adds cmd-line tests given files.
@ -486,7 +545,10 @@ macro(add_cmdline_test TESTCMD TESTSUFFIX)
set(CONFARG CONFIGURATIONS)
set(CONFVAL ${FOUNDCONFIGS})
add_test(NAME ${TEST_FULLNAME} ${CONFARG} ${CONFVAL} COMMAND ${PYTHON_EXECUTABLE} ${tests_SOURCE_DIR}/test_cmdline_tool.py --comparator=${COMPARATOR} -c ${ImageMagick_convert_EXECUTABLE} -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}")
if (MINGW_CROSS_ENV_DIR)
set( MINGW_CROSS_ARG "--mingw-cross-env" )
endif()
add_test(NAME ${TEST_FULLNAME} ${CONFARG} ${CONFVAL} COMMAND ${PYTHON_EXECUTABLE} ${tests_SOURCE_DIR}/test_cmdline_tool.py ${MINGW_CROSS_ARG} --comparator=${COMPARATOR} -c ${ImageMagick_convert_EXECUTABLE} -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}")
endif()
endforeach()
endmacro()
@ -496,6 +558,9 @@ enable_testing()
# set up custom pretty printing of results
set(INFOCMD "execute_process(COMMAND ${CMAKE_CURRENT_BINARY_DIR}/opencsgtest --info OUTPUT_FILE sysinfo.txt)")
if (MINGW_CROSS_ENV_DIR)
set(INFOCMD "execute_process(COMMAND wine ${CMAKE_CURRENT_BINARY_DIR}/opencsgtest --info OUTPUT_FILE sysinfo.txt)")
endif()
set(PRETTYCMD "\"${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_pretty_print.py --builddir=${CMAKE_CURRENT_BINARY_DIR}\"")
set(CTEST_CUSTOM_FILE ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake)
set(CTEST_CUSTOM_TXT "\n
@ -518,7 +583,9 @@ file(GLOB FEATURES_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/*.scad)
# Remove included files not to be used as tests
list(REMOVE_ITEM FEATURES_FILES
${CMAKE_SOURCE_DIR}/../testdata/scad/features/include\ test6.scad
${CMAKE_SOURCE_DIR}/../testdata/scad/features/include-test5.scad)
${CMAKE_SOURCE_DIR}/../testdata/scad/features/include-test5.scad
${CMAKE_SOURCE_DIR}/../testdata/scad/features/use\ test6.scad
${CMAKE_SOURCE_DIR}/../testdata/scad/features/use-test5.scad)
file(GLOB BUGS_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/*.scad)
file(GLOB SCAD_DXF_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/dxf/*.scad)
file(GLOB FUNCTION_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/functions/*.scad)
@ -548,15 +615,20 @@ list(APPEND CGALSTLSANITYTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/no
# Once we're capable of comparing these across platforms, we can put these back in
disable_tests(dumptest_transform-tests
dumptest_render-tests
dumptest_difference-tests
dumptest_intersection-tests
dumptest_example001
dumptest_example005
dumptest_example006
dumptest_example007
dumptest_example008
dumptest_example012
dumptest_example013
dumptest_example016
dumptest_example017
dumptest_example020
dumptest_example021)
dumptest_example021
dumptest_example022)
# FIXME: This test illustrates a weakness in child() combined with modifiers.
# Reenable it when this is improved

View File

@ -0,0 +1,144 @@
#
# CMake Toolchain file for cross compiling OpenSCAD tests linux->mingw-win32
# --------------------------------------------------------------------------
#
# Prerequisites: mingw-cross-env, ImageMagick 6.5.9.3 or newer, wine
#
# Usage:
#
# - follow Brad Pitcher's mingw-cross-env for OpenSCAD setup:
# http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Cross-compiling_for_Windows_on_Linux_or_Mac_OS_X
# - cross-compile openscad.exe, to verify your installation works properly.
# - cd openscad/tests && mkdir build-mingw32 && cd build-mingw32
# - cmake .. -DCMAKE_TOOLCHAIN_FILE=CMingw-cross-env.cmake \
# -DMINGW_CROSS_ENV_DIR=<where mingw-cross-env is installed>
# - make should proceed as normal.
# - now run 'ctest' on your *nix machine.
# The test .exe programs should run under Wine.
#
# See also:
#
# http://lists.gnu.org/archive/html/mingw-cross-env-list/2010-11/threads.html#00078
# (thread "Qt with Cmake")
# http://lists.gnu.org/archive/html/mingw-cross-env-list/2011-01/threads.html#00012
# (thread "Qt: pkg-config files?")
# http://mingw-cross-env.nongnu.org/#requirements
# http://www.vtk.org/Wiki/CMake_Cross_Compiling
# https://bitbucket.org/muellni/mingw-cross-env-cmake/src/2067fcf2d52e/src/cmake-1-toolchain-file.patch
# http://code.google.com/p/qtlobby/source/browse/trunk/toolchain-mingw.cmake
# http://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Link-Options.html
# Makefile.Release generated by qmake
# cmake's FindQt4.cmake & Qt4ConfigDependentSettings.cmake files
# mingw-cross-env's qmake.conf and *.prl files
# mingw-cross-env's pkg-config files in usr/i686-pc-mingw32/lib/pkgconfig
# http://www.vtk.org/Wiki/CMake:How_To_Find_Libraries
#
#
# Notes:
#
# To debug the build process run "make VERBOSE=1". 'strace -f' is also useful.
#
# This file is actually called multiple times by cmake, so various 'if NOT set'
# guards are used to keep programs from running twice.
#
# The test will not currently run under win32 because the ctest harness
# is created by cmake on the machine that it is called on, not on the
# machine it is targeting.
#
#
# Part 1. Find *nix-ImageMagick
#
# The tests run under Wine under *nix. Therefore we need to find the
# ImageMagick comparison program on the *nix machine. It must be
# searched before the 'cross-compile' environment is setup.
#
if (NOT imagemagick_cross_set)
find_package(ImageMagick COMPONENTS convert REQUIRED)
message(STATUS "ImageMagick convert executable found: " ${ImageMagick_convert_EXECUTABLE})
set( SKIP_IMAGEMAGICK TRUE )
set( imagemagick_cross_set )
endif()
#
# Part 2. cross-compiler setup
#
set(MINGW_CROSS_ENV_DIR $ENV{MINGW_CROSS_ENV_DIR})
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_SYSTEM_NAME Windows)
set(MSYS 1)
set(CMAKE_FIND_ROOT_PATH ${MINGW_CROSS_ENV_DIR}/usr/i686-pc-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_C_COMPILER ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-gcc)
set(CMAKE_CXX_COMPILER ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-g++)
set(QT_QMAKE_EXECUTABLE ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-qmake)
set(PKG_CONFIG_EXECUTABLE ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-pkg-config)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
#
# Part 3. library settings for mingw-cross-env
#
set( Boost_USE_STATIC_LIBS ON )
set( Boost_USE_MULTITHREADED ON )
set( Boost_COMPILER "_win32" )
# set( Boost_DEBUG TRUE ) # for debugging cmake's FindBoost, not boost itself
set( OPENSCAD_LIBRARIES ${CMAKE_FIND_ROOT_PATH} )
set( EIGEN2_DIR ${CMAKE_FIND_ROOT_PATH} )
set( CGAL_DIR ${CMAKE_FIND_ROOT_PATH}/lib/CGAL )
set( GLEW_DIR ${CMAKE_FIND_ROOT_PATH} )
#
# Qt4
#
# To workaround problems with CMake's FindQt4.cmake when combined with
# mingw-cross-env (circa early 2012), we here instead use pkg-config. To
# workaround Cmake's insertion of -bdynamic, we stick 'static' on the
# end of QT_LIBRARIES
#
set(QT_QMAKE_EXECUTABLE ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-qmake)
set(QT_MOC_EXECUTABLE ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-moc)
set(QT_UIC_EXECUTABLE ${MINGW_CROSS_ENV_DIR}/usr/bin/i686-pc-mingw32-uic)
function(mingw_cross_env_find_qt)
# called from CMakeLists.txt
find_package( PkgConfig )
pkg_check_modules( QTCORE QtCore )
pkg_check_modules( QTGUI QtGui )
pkg_check_modules( QTOPENGL QtOpenGL )
set(QT_INCLUDE_DIRS ${QTCORE_INCLUDE_DIRS} ${QTGUI_INCLUDE_DIRS} ${QTOPENGL_INCLUDE_DIRS})
set(QT_CFLAGS_OTHER "${QTCORE_CFLAGS_OTHER} ${QTGUI_CFLAGS_OTHER} ${QTOPENGL_CFLAGS_OTHER}")
set(QT_LIBRARIES "${QTCORE_STATIC_LDFLAGS} ${QTGUI_STATIC_LDFLAGS} ${QTOPENGL_STATIC_LDFLAGS};-static")
set(QT_INCLUDE_DIRS ${QT_INCLUDE_DIRS} PARENT_SCOPE)
set(QT_CFLAGS_OTHER ${QT_CFLAGS_OTHER} PARENT_SCOPE)
set(QT_LIBRARIES ${QT_LIBRARIES} PARENT_SCOPE)
endfunction()
function(mingw_cross_env_info)
message(STATUS "QT INCLUDE_DIRS: ${QT_INCLUDE_DIRS}")
message(STATUS "QT LIBRARIES: ${QT_LIBRARIES}")
message(STATUS "QT_CFLAGS_OTHER: ${QT_CFLAGS_OTHER}")
endfunction()
#
# Part 4. -D definitions
#
if( NOT cross_defs_set )
add_definitions( -DGLEW_STATIC ) # FindGLEW.cmake needs this
add_definitions( -DBOOST_STATIC )
add_definitions( -DBOOST_THREAD_USE_LIB )
add_definitions( -DUNICODE ) # because qmake does it
set(cross_defs_set 1)
endif()

View File

@ -11,13 +11,17 @@
# http://openlibraries.org/browser/trunk/FindGLEW.cmake?rev=1383
IF (WIN32)
IF (WIN32 AND MSVC)
IF (WIN32_STATIC_BUILD) # passed from caller
SET(GLEW_LIB_SEARCH_NAME glew32s.lib) # static, non-debug (Release)
ELSE ()
SET(GLEW_LIB_SEARCH_NAME glew32.lib) # other. untested with OpenSCAD
ENDIF()
ELSE () # GCC
SET(GLEW_LIB_SEARCH_NAME "libglew32s.a")
ENDIF ()
IF (WIN32)
FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h
$ENV{PROGRAMFILES}/GLEW/include
${PROJECT_SOURCE_DIR}/src/nvgl/glew/include
@ -33,13 +37,13 @@ ELSE (WIN32)
message(STATUS "GLEW_DIR: " ${GLEW_DIR})
FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h
HINTS ${GLEW_DIR}/include
PATHS /usr/include /usr/local/include
PATHS /usr/include /usr/local/include /usr/pkg/include
NO_DEFAULT_PATH
DOC "The directory where GL/glew.h resides")
FIND_LIBRARY( GLEW_LIBRARY
NAMES GLEW glew
HINTS ${GLEW_DIR}/lib
PATHS /usr/lib /usr/local/lib
PATHS /usr/lib /usr/local/lib /usr/pkg/lib
NO_DEFAULT_PATH
DOC "The GLEW library")
ENDIF (WIN32)

View File

@ -105,10 +105,10 @@ bool create_wgl_dummy_context(OffscreenContext &ctx)
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.hInstance = inst;
wc.lpszClassName = "OpenSCAD";
wc.lpszClassName = (LPCWSTR)"OpenSCAD";
RegisterClass( &wc );
HWND window = CreateWindow( "OpenSCAD", "OpenSCAD",
HWND window = CreateWindow( (LPCWSTR)"OpenSCAD", (LPCWSTR)"OpenSCAD",
WS_CAPTION | WS_POPUPWINDOW, //| WS_VISIBLE,
0, 0, ctx.width, ctx.height, NULL, NULL, inst, NULL );

View File

@ -19,6 +19,7 @@
#include "ThrownTogetherRenderer.h"
#include "csgterm.h"
#include "csgtermnormalizer.h"
#include "OffscreenView.h"
#include <QApplication>
@ -315,12 +316,8 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
}
// CSG normalization
csgInfo.root_norm_term = root_raw_term;
while (1) {
shared_ptr<CSGTerm> n = CSGTerm::normalize(csgInfo.root_norm_term);
if (csgInfo.root_norm_term == n) break;
csgInfo.root_norm_term = n;
}
CSGTermNormalizer normalizer;
csgInfo.root_norm_term = normalizer.normalize(root_raw_term);
assert(csgInfo.root_norm_term);
@ -333,11 +330,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
csgInfo.highlights_chain = new CSGChain();
for (unsigned int i = 0; i < csgInfo.highlight_terms.size(); i++) {
while (1) {
shared_ptr<CSGTerm> n = CSGTerm::normalize(csgInfo.highlight_terms[i]);
if (csgInfo.highlight_terms[i] == n) break;
csgInfo.highlight_terms[i] = n;
}
csgInfo.highlight_terms[i] = normalizer.normalize(csgInfo.highlight_terms[i]);
csgInfo.highlights_chain->import(csgInfo.highlight_terms[i]);
}
}
@ -347,11 +340,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
csgInfo.background_chain = new CSGChain();
for (unsigned int i = 0; i < csgInfo.background_terms.size(); i++) {
while (1) {
shared_ptr<CSGTerm> n = CSGTerm::normalize(csgInfo.background_terms[i]);
if (csgInfo.background_terms[i] == n) break;
csgInfo.background_terms[i] = n;
}
csgInfo.background_terms[i] = normalizer.normalize(csgInfo.background_terms[i]);
csgInfo.background_chain->import(csgInfo.background_terms[i]);
}
}

View File

@ -65,10 +65,9 @@ string dumptree(const Tree &tree, const AbstractNode &node)
int main(int argc, char **argv)
{
#ifdef WIN32
#ifdef _MSC_VER
_set_output_format(_TWO_DIGIT_EXPONENT);
#endif
if (argc != 3) {
fprintf(stderr, "Usage: %s <file.scad> <output.txt>\n", argv[0]);
exit(1);

View File

@ -59,7 +59,7 @@ static void outfile_handler(const std::string &msg, void *userdata) {
int main(int argc, char **argv)
{
#ifdef WIN32
#ifdef _MSC_VER
_set_output_format(_TWO_DIGIT_EXPONENT);
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -29,4 +29,7 @@
multmatrix([[1, 0, 0, 22], [0, 1, 0, 11], [0, 0, 1, 0], [0, 0, 0, 1]]) {
cylinder($fn = 0, $fa = 12, $fs = 2, h = 15, r1 = 5, r2 = 5, center = false);
}
multmatrix([[1, 0, 0, -10], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
cylinder($fn = 6, $fa = 12, $fs = 2, h = 2, r1 = 3, r2 = 3, center = false);
}

View File

@ -24,7 +24,7 @@
multmatrix([[1, 0, 0, 12], [0, 1, 0, 12], [0, 0, 1, 0], [0, 0, 0, 1]]) {
intersection() {
cube(size = [10, 10, 10], center = true);
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 7], [0, 0, 0, 1]]) {
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 7.01], [0, 0, 0, 1]]) {
cylinder($fn = 0, $fa = 12, $fs = 2, h = 4, r1 = 4, r2 = 4, center = true);
}
}

View File

@ -49,7 +49,7 @@
}
}
multmatrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) {
color([1, 0.713725, 0.756863, 1]) {
color([1, 0.713726, 0.756863, 1]) {
sphere($fn = 5, $fa = 12, $fs = 2, r = 0.8);
}
}
@ -79,7 +79,7 @@
}
}
multmatrix([[1, 0, 0, 8], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) {
color([1, 0.498039, 0.313725, 1]) {
color([1, 0.498039, 0.313726, 1]) {
sphere($fn = 5, $fa = 12, $fs = 2, r = 0.8);
}
}

View File

@ -0,0 +1,34 @@
group() {
group() {
multmatrix([[1, 0, 0, 2], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
group() {
cylinder($fn = 0, $fa = 12, $fs = 2, h = 1, r1 = 0.7, r2 = 0.2, center = true);
}
}
multmatrix([[1, 0, 0, 2], [0, 1, 0, -2], [0, 0, 1, 0], [0, 0, 0, 1]]) {
group() {
cylinder($fn = 10, $fa = 12, $fs = 2, h = 1, r1 = 0.5, r2 = 0.5, center = true);
}
}
cube(size = [1, 1, 1], center = true);
}
multmatrix([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]);
multmatrix([[1, 0, 0, 4], [0, 1, 0, -2], [0, 0, 1, 0], [0, 0, 0, 1]]);
multmatrix([[1, 0, 0, -2], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
group() {
sphere($fn = 8, $fa = 12, $fs = 2, r = 0.5);
}
}
multmatrix([[1, 0, 0, -2], [0, 1, 0, -2], [0, 0, 1, 0], [0, 0, 0, 1]]) {
group() {
difference() {
cube(size = [1, 1, 1], center = true);
cylinder($fn = 0, $fa = 12, $fs = 2, h = 2, r1 = 0.4, r2 = 0.4, center = true);
}
}
}
multmatrix([[1, 0, 0, 0], [0, 1, 0, -2], [0, 0, 1, 0], [0, 0, 0, 1]]) {
sphere($fn = 16, $fa = 12, $fs = 2, r = 1);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -61,7 +61,11 @@ def execute_and_redirect(cmd, params, outfile):
else: return retval
def get_normalized_text(filename):
text = open(filename).read()
try:
f = open(filename)
text = f.read()
except:
text = ''
return text.strip("\r\n").replace("\r\n", "\n") + "\n"
def compare_text(expected, actual):
@ -71,8 +75,9 @@ def compare_default(resultfilename):
print >> sys.stderr, 'diff text compare: '
print >> sys.stderr, ' expected textfile: ', expectedfilename
print >> sys.stderr, ' actual textfile: ', resultfilename
if not compare_text(expectedfilename, resultfilename):
execute_and_redirect("diff", [expectedfilename, resultfilename], sys.stderr)
if not compare_text(expectedfilename, resultfilename):
if resultfilename:
execute_and_redirect("diff", [expectedfilename, resultfilename], sys.stderr)
return False
return True
@ -110,7 +115,7 @@ def compare_png(resultfilename):
elif compare_method=='NCC':
thresh = 0.95
ncc_err = float(output.strip())
if ncc_err > thresh: return True
if ncc_err > thresh or ncc_err==0.0: return True
else: print >> sys.stderr, ncc_err, ' Images differ: NCC comparison < ', thresh
return False
@ -136,8 +141,13 @@ def run_test(testname, cmd, args):
outputname = os.path.normpath( outputname )
outfile = open(outputname, "wb")
try:
proc = subprocess.Popen([cmd] + args + [outputname], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if os.path.isfile(cmd+'.exe') and options.mingw_cross_env:
cmdline = ['wine']+[cmd+'.exe'] + args + [outputname]
else:
cmdline = [cmd] + args + [outputname]
proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
errtext = proc.communicate()[1]
if errtext != None and len(errtext) > 0:
print >> sys.stderr, "Error output: " + errtext
@ -166,11 +176,11 @@ def usage():
print >> sys.stderr, " -s, --suffix=<suffix> Write -expected and -actual files with the given suffix instead of .txt"
print >> sys.stderr, " -t, --test=<name> Specify test name instead of deducting it from the argument"
print >> sys.stderr, " -c, --convexec=<name> Path to ImageMagick 'convert' executable"
print >> sys.stderr, " -x, --mingw-cross-env Mingw-cross-env cross compilation"
if __name__ == '__main__':
# Handle command-line arguments
try:
opts, args = getopt.getopt(sys.argv[1:], "gs:c:t:m:", ["generate", "convexec=", "suffix=", "test=", "comparator="])
opts, args = getopt.getopt(sys.argv[1:], "gs:c:t:m:x", ["generate", "convexec=", "suffix=", "test=", "comparator=", "mingw-cross-env"])
except getopt.GetoptError, err:
usage()
sys.exit(2)
@ -192,6 +202,8 @@ if __name__ == '__main__':
options.convert_exec = os.path.normpath( a )
elif o in ("-m", "--comparator"):
options.comparator = a
elif o in ("-x", "--mingw-cross-env"):
options.mingw_cross_env = True
# <cmdline-tool> and <argument>
if len(args) < 2:

View File

@ -1,7 +1,7 @@
# get VERSION from system date
isEmpty(VERSION) {
win32-msvc*: {
win32-msvc*:!mingw-cross-env {
#
# Windows XP date command only has one argument, /t
# and it can print the date in various localized formats.
@ -51,17 +51,18 @@ isEmpty(VERSION) {
} else {
# Unix/Mac
VERSION = $$system(date "+%Y.%m.%d")
VERSION_SPLIT=$$split(VERSION, ".")
VERSION_YEAR=$$member(VERSION_SPLIT, 0)
VERSION_MONTH=$$member(VERSION_SPLIT, 1)
VERSION_DAY=$$member(VERSION_SPLIT, 2)
}
# Fix for problem with integers with leading zeros
# being interpreted by C++ as octals. Now they're doubles.
VERSION_YEAR=$${VERSION_YEAR}.0
VERSION_MONTH=$${VERSION_MONTH}.0
VERSION_DAY=$${VERSION_DAY}.0
}
VERSION_SPLIT=$$split(VERSION, ".")
VERSION_YEAR=$$member(VERSION_SPLIT, 0)
VERSION_MONTH=$$member(VERSION_SPLIT, 1)
VERSION_DAY=$$member(VERSION_SPLIT, 2)
# Fix for problem with integers with leading zeros
# being interpreted by C++ as octals. Now they're doubles.
VERSION_YEAR=$${VERSION_YEAR}.0
VERSION_MONTH=$${VERSION_MONTH}.0
VERSION_DAY=$${VERSION_DAY}.0
DEFINES += OPENSCAD_VERSION=$$VERSION OPENSCAD_YEAR=$$VERSION_YEAR OPENSCAD_MONTH=$$VERSION_MONTH
!isEmpty(VERSION_DAY): DEFINES += OPENSCAD_DAY=$$VERSION_DAY