Merge branch 'master' of github.com:openscad/openscad into msys2

This commit is contained in:
don bright 2015-06-23 20:19:04 -07:00
commit 5e93aa14af
29 changed files with 1298 additions and 1167 deletions

1
.gitignore vendored
View file

@ -37,6 +37,7 @@ testdata/scad/misc/use-tests.scad
/lexer_lex.cpp
/parser_yacc.cpp
/OpenSCAD.app
/openscad.appdata.xml
/openscad.pro.user
/openscad
/locale/*/*/*.mo

View file

@ -231,9 +231,9 @@ For a 64-bit Windows cross-build, replace 32 with 64 in the above instructions.
### Compilation
First, run 'qmake openscad.pro' from Qt4 to generate a Makefile.
First, run 'qmake openscad.pro' from Qt to generate a Makefile.
On some systems, depending on which version(s) of Qt you have installed, you may need to specify which version you want to use, e.g. by running 'qmake4', 'qmake-qt4' or something alike.
On some systems, depending on which version(s) of Qt you have installed, you may need to specify which version you want to use, e.g. by running 'qmake4', 'qmake-qt4', 'qmake -qt=qt5', or something alike.
Then run make. Finally you might run 'make install' as root or simply copy the
'openscad' binary (OpenSCAD.app on Mac OS X) to the bin directory of your choice.

9
contrib/appdata.its Normal file
View file

@ -0,0 +1,9 @@
<!-- Copyright 2013 Richard Hughes <richard@hughsie.com> -->
<its:rules
xmlns:its="http://www.w3.org/2005/11/its"
version="1.0">
<its:translateRule translate="no" selector="/component"/>
<its:translateRule translate="yes"
selector="/component/summary |
/component/description"/>
</its:rules>

View file

@ -50,6 +50,7 @@ files.
$ make clean && qmake && make
Then run the script to scan the source files, and regenerate .pot & .po files.
You'll need itstool (http://itstool.org/) installed.
$ ./scripts/translation-update.sh

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<application>
<id type="desktop">openscad.desktop</id>
<licence>CC0</licence>
<component type="desktop">
<id>openscad.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0+ and CC0-1.0</project_license>
<name>OpenSCAD</name>
<summary>The Programmers Solid 3D CAD Modeller</summary>
<description>
<p>OpenSCAD is a software for creating solid 3D CAD models. Unlike most free software for creating 3D models (such as Blender) it does not focus on the artistic aspects of 3D modelling but instead on the CAD aspects. Thus it might be the application you are looking for when you are planning to create 3D models of machine parts but pretty sure is not what you are looking for when you are more interested in creating computer-animated movies.</p>
@ -9,8 +11,12 @@
<p>OpenSCAD provides two main modelling techniques: First there is constructive solid geometry (aka CSG) and second there is extrusion of 2D outlines. As data exchange format format for this 2D outlines Autocad DXF files are used. In addition to 2D paths for extrusion it is also possible to read design parameters from DXF files. Besides DXF files OpenSCAD can read and create 3D models in the STL and OFF file formats.</p>
</description>
<screenshots>
<screenshot type="default" width="800" height="437">http://www.openscad.org/images/appdata-screenshot-1.png</screenshot>
<screenshot width="800" height="465">http://www.openscad.org/images/appdata-screenshot-2.png</screenshot>
<screenshot type="default">
<image>http://www.openscad.org/images/appdata-screenshot-1.png</image>
</screenshot>
<screenshot>
<image>http://www.openscad.org/images/appdata-screenshot-2.png</image>
</screenshot>
</screenshots>
<url type="homepage">http://www.openscad.org/</url>
</application>
</component>

View file

@ -188,13 +188,19 @@ CONFIG += fontconfig
CONFIG += gettext
#Uncomment the following line to enable the QScintilla editor
CONFIG += scintilla
!nogui {
CONFIG += scintilla
}
# Make experimental features available
experimental {
DEFINES += ENABLE_EXPERIMENTAL
}
nogui {
DEFINES += OPENSCAD_NOGUI
}
mdi {
DEFINES += ENABLE_MDI
}
@ -482,6 +488,8 @@ HEADERS += src/cgal.h \
src/Polygon2d-CGAL.h
SOURCES += src/cgalutils.cc \
src/cgalutils-applyops.cc \
src/cgalutils-project.cc \
src/cgalutils-tess.cc \
src/cgalutils-polyhedron.cc \
src/CGALCache.cc \

View file

@ -25,6 +25,9 @@ updatepot()
| awk '{ printf "#: examples/examples.json:%d\nmsgid %s\nmsgstr \"\"\n\n", $1, $2 }' \
> ./locale/json-strings.pot
# extract strings from appdata file
itstool -o ./locale/appdata-strings.pot ./openscad.appdata.xml.in --its=./contrib/appdata.its
VER=`date +"%Y.%m.%d"`
OPTS=
OPTS=$OPTS' --package-name=OpenSCAD'
@ -41,7 +44,7 @@ updatepot()
exit 1
fi
cmd="${GETTEXT_PATH}msgcat -o ./locale/openscad.pot ./locale/openscad-tmp.pot ./locale/json-strings.pot"
cmd="${GETTEXT_PATH}msgcat -o ./locale/openscad.pot ./locale/openscad-tmp.pot ./locale/json-strings.pot ./locale/appdata-strings.pot"
echo $cmd
$cmd
if [ ! $? = 0 ]; then
@ -50,7 +53,7 @@ updatepot()
fi
sed -e s/"CHARSET"/"UTF-8"/g ./locale/openscad.pot > ./locale/openscad.pot.new && mv ./locale/openscad.pot.new ./locale/openscad.pot
rm -f ./locale/json-strings.pot ./locale/openscad-tmp.pot
rm -f ./locale/json-strings.pot ./locale/openscad-tmp.pot ./locale/appdata-strings.pot
}
updatepo()
@ -80,6 +83,26 @@ updatemo()
exit 1
fi
done
if which itstool > /dev/null 2>&1; then
# ugly workaround for bug https://bugs.freedesktop.org/show_bug.cgi?id=90937
for LANGCODE in `cat locale/LINGUAS | grep -v "#"`; do
ln -s openscad.mo ./locale/$LANGCODE/LC_MESSAGES/$LANGCODE.mo
done
# generate translated appdata file
itstool -j ./openscad.appdata.xml.in -o ./openscad.appdata.xml ./locale/*/LC_MESSAGES/[a-z][a-z].mo
# clean the mess
for LANGCODE in `cat locale/LINGUAS | grep -v "#"`; do
unlink ./locale/$LANGCODE/LC_MESSAGES/$LANGCODE.mo
done
else
if [ x"$(uname -s)" = x"Linux" ]; then
echo "itstool missing, won't apply translations to openscad.appdata.xml"
fi
cp -f ./openscad.appdata.xml.in ./openscad.appdata.xml
fi
}
GETTEXT_PATH=""

View file

@ -1,5 +1,8 @@
#!/bin/bash
qmake CONFIG+=experimental CONFIG+=nogui
make
cd tests
cmake .
if [[ $? != 0 ]]; then

View file

@ -79,10 +79,22 @@ check_env()
detect_glu()
{
detect_glu_result=
if [ -e $DEPLOYDIR/include/GL/glu.h ]; then detect_glu_result=1; fi
if [ -e /usr/include/GL/glu.h ]; then detect_glu_result=1; fi
if [ -e /usr/local/include/GL/glu.h ]; then detect_glu_result=1; fi
if [ -e /usr/pkg/X11R7/include/GL/glu.h ]; then detect_glu_result=1; fi
if [ -e $DEPLOYDIR/include/GL/glu.h ]; then
detect_glu_include=$DEPLOYDIR/include
detect_glu_result=1;
fi
if [ -e /usr/include/GL/glu.h ]; then
detect_glu_include=/usr/include
detect_glu_result=1;
fi
if [ -e /usr/local/include/GL/glu.h ]; then
detect_glu_include=/usr/local/include
detect_glu_result=1;
fi
if [ -e /usr/pkg/X11R7/include/GL/glu.h ]; then
detect_glu_include=/usr/pkg/X11R7/include
detect_glu_result=1;
fi
return
}
@ -505,10 +517,20 @@ build_opencsg()
cp opencsg.pro opencsg.pro.bak
cat opencsg.pro.bak | sed s/example// > opencsg.pro
detect_glu
GLU_INCLUDE=$detect_glu_include
if [ ! $detect_glu_result ]; then
build_glu 9.0.0
fi
if [ "`command -v qmake-qt4`" ]; then
OPENCSG_QMAKE=qmake-qt4
elif [ "`command -v qmake4`" ]; then
OPENCSG_QMAKE=qmake4
elif [ "`command -v qmake-qt5`" ]; then
OPENCSG_QMAKE=qmake-qt5
elif [ "`command -v qmake5`" ]; then
OPENCSG_QMAKE=qmake5
elif [ "`command -v qmake`" ]; then
OPENCSG_QMAKE=qmake
else
@ -518,15 +540,18 @@ build_opencsg()
cp src/Makefile src/Makefile.bak
cat Makefile.bak | sed s/example// |sed s/glew// > Makefile
cat src/Makefile.bak | sed s@^INCPATH.*@INCPATH\ =\ -I$BASEDIR/include\ -I../include\ -I..\ -I.@ > src/Makefile
cat src/Makefile.bak | sed s@^INCPATH.*@INCPATH\ =\ -I$BASEDIR/include\ -I../include\ -I..\ -I$GLU_INCLUDE -I.@ > src/Makefile
cp src/Makefile src/Makefile.bak2
cat src/Makefile.bak2 | sed s@^LIBS.*@LIBS\ =\ -L$BASEDIR/lib\ -L/usr/X11R6/lib\ -lGLU\ -lGL@ > src/Makefile
tmp=$version
detect_glu
if [ ! $detect_glu_result ]; then build_glu 9.0.0 ; fi
version=$tmp
fi
if [ ! $OPENCSG_QMAKE = "make" ]; then
OPENCSG_QMAKE=$OPENCSG_QMAKE' "QMAKE_CXXFLAGS+=-I'$GLU_INCLUDE'"'
fi
echo OPENCSG_QMAKE: $OPENCSG_QMAKE
cd $BASEDIR/src/OpenCSG-$version/src
$OPENCSG_QMAKE
@ -776,6 +801,11 @@ if [ $1 ]; then
build_gettext 0.18.3.1
exit $?
fi
if [ $1 = "harfbuzz" ]; then
# debian 7 lacks only harfbuzz
build_harfbuzz 0.9.23 --with-glib=yes
exit $?
fi
if [ $1 = "glib2" ]; then
# such a huge build, put here by itself
build_pkgconfig 0.28

View file

@ -4,13 +4,24 @@
# this assumes you have sudo installed and running, or are running as root.
#
get_fedora_deps()
get_fedora_deps_yum()
{
yum -y install qt5-qtbase-devel bison flex eigen3-devel harfbuzz-devel \
fontconfig-devel freetype-devel \
boost-devel mpfr-devel gmp-devel glew-devel CGAL-devel gcc gcc-c++ pkgconfig \
opencsg-devel git libXmu-devel curl imagemagick ImageMagick glib2-devel make \
xorg-x11-server-Xvfb gettext
xorg-x11-server-Xvfb gettext qscintilla-devel qscintilla-qt5-devel \
mesa-dri-drivers
}
get_fedora_deps_dnf()
{
dnf -y install qt5-qtbase-devel bison flex eigen3-devel harfbuzz-devel \
fontconfig-devel freetype-devel \
boost-devel mpfr-devel gmp-devel glew-devel CGAL-devel gcc gcc-c++ pkgconfig \
opencsg-devel git libXmu-devel curl ImageMagick glib2-devel make \
xorg-x11-server-Xvfb gettext qscintilla-devel qscintilla-qt5-devel \
mesa-dri-drivers
}
get_qomo_deps()
@ -43,8 +54,13 @@ get_netbsd_deps()
get_opensuse_deps()
{
zypper install libeigen3-devel mpfr-devel gmp-devel boost-devel \
libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl \
glib2-devel gettext
libqt4-devel glew-devel cmake git bison flex cgal-devel curl \
glib2-devel gettext freetype-devel harfbuzz-devel libqscintilla-devel \
xvfb-run imagemagick opencsg-devel
echo if you are missing opencsg, please add the -graphics- repository
echo find your version from cat /etc/issue, then replace it below, then run
echo " zypper ar -f http://download.opensuse.org/repositories/graphics/openSUSE_13.2 graphics"
echo " zypper install opencsg-devel"
}
get_mageia_deps()
@ -58,12 +74,30 @@ get_mageia_deps()
get_debian_deps()
{
apt-get -y install \
build-essential curl libffi-dev qtbase5-dev libqt5scintilla2-dev \
build-essential curl libffi-dev \
libxmu-dev cmake bison flex git-core libboost-all-dev \
libXi-dev libmpfr-dev libboost-dev libglew-dev \
libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev \
imagemagick libfontconfig-dev libfreetype6-dev \
libharfbuzz-dev gtk-doc-tools libglib2.0-dev gettext
gtk-doc-tools libglib2.0-dev gettext xvfb pkg-config ragel
}
get_debian_8_deps()
{
get_debian_deps
apt-get -y install libharfbuzz-dev qtbase5-dev libqt5scintilla2-dev
}
get_debian_7_deps()
{
get_debian_deps
apt-get -y install libqt4-dev libqscintilla2-dev
}
get_ubuntu_14_deps()
{
get_debian_8_deps
apt-get -y install qt5-qmake
}
get_msys2_x86_64_deps()
@ -116,62 +150,3 @@ unknown()
echo "in README.md using your system's package manager."
}
try_using_etc_issue()
{
if [ "`grep -i ubuntu /etc/issue`" ]; then
get_debian_deps
elif [ "`grep -i debian /etc/issue`" ]; then
get_debian_deps
elif [ "`grep -i raspbian /etc/issue`" ]; then
get_debian_deps
elif [ "`grep -i mint /etc/issue`" ]; then
get_debian_deps
elif [ "`grep -i suse /etc/issue`" ]; then
get_opensuse_deps
elif [ "`grep -i fedora /etc/issue`" ]; then
get_fedora_deps
elif [ "`grep -i red.hat /etc/issue`" ]; then
get_fedora_deps
elif [ "`grep -i mageia /etc/issue`" ]; then
get_mageia_deps
elif [ "`grep -i qomo /etc/issue`" ]; then
get_qomo_deps
else
unknown
fi
}
try_using_uname()
{
if [ "`uname -a | grep -i x86_64.*Msys`" ]; then
get_msys2_x86_64_deps
elif [ "`uname -a | grep -i i686.*Msys`" ]; then
get_msys2_i686_deps
elif [ "`uname | grep -i freebsd `" ]; then
get_freebsd_deps
elif [ "`uname | grep -i netbsd`" ]; then
get_netbsd_deps
else
unknown
fi
}
try_using_rpm()
{
if [ "`rpm -qa | grep altlinux`" ]; then
get_altlinux_deps
else
unknown
fi
}
if [ -e /etc/issue ]; then
try_using_etc_issue
elif [ "`command -v uname`" ]; then
try_using_uname
elif [ "`command -v rpm`" ]; then
try_using_uname
else
unknown
fi

View file

@ -15,6 +15,7 @@
#include <QMutex>
#include <QSet>
#include <QTime>
#include <QIODevice>
enum export_type_e {
EXPORT_TYPE_UNKNOWN,
@ -113,6 +114,7 @@ private:
void compile(bool reload, bool forcedone = false);
void compileCSG(bool procevents);
bool maybeSave();
void saveError(const QIODevice &file, const std::string &msg);
bool checkEditorModified();
QString dumpCSGTree(AbstractNode *root);
static void consoleOutput(const std::string &msg, void *userdata);

View file

@ -113,7 +113,9 @@ std::string PlatformUtils::userLibraryPath()
try {
std::string pathstr = PlatformUtils::documentsPath();
if (pathstr=="") return "";
path = boosty::canonical(fs::path( pathstr ));
path = fs::path( pathstr );
if (!fs::exists(path)) return "";
path = boosty::canonical( path );
//PRINTB("path size %i",boosty::stringy(path).size());
//PRINTB("lib path found: [%s]", path );
if (path.empty()) return "";
@ -134,7 +136,9 @@ std::string PlatformUtils::backupPath()
try {
std::string pathstr = PlatformUtils::documentsPath();
if (pathstr=="") return "";
path = boosty::canonical(fs::path( pathstr ));
path = fs::path( pathstr );
if (!fs::exists(path)) return "";
path = boosty::canonical( path );
if (path.empty()) return "";
path /= OPENSCAD_FOLDER_NAME;
path /= "backups";

422
src/cgalutils-applyops.cc Normal file
View file

@ -0,0 +1,422 @@
// this file is split into many separate cgalutils* files
// in order to workaround gcc 4.9.1 crashing on systems with only 2GB of RAM
#ifdef ENABLE_CGAL
#include "cgalutils.h"
#include "polyset.h"
#include "printutils.h"
#include "Polygon2d.h"
#include "polyset-utils.h"
#include "grid.h"
#include "node.h"
#include "cgal.h"
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/normal_vector_newell_3.h>
#include <CGAL/Handle_hash_function.h>
#include <CGAL/config.h>
#include <CGAL/version.h>
// Apply CGAL bugfix for CGAL-4.5.x
#if CGAL_VERSION_NR > CGAL_VERSION_NUMBER(4,5,1) || CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,5,0)
#include <CGAL/convex_hull_3.h>
#else
#include "convex_hull_3_bugfix.h"
#endif
#include "svg.h"
#include "Reindexer.h"
#include "GeometryUtils.h"
#include <map>
#include <queue>
#include <boost/foreach.hpp>
#include <boost/unordered_set.hpp>
namespace CGALUtils {
template<typename Polyhedron>
bool is_weakly_convex(Polyhedron const& p) {
for (typename Polyhedron::Edge_const_iterator i = p.edges_begin(); i != p.edges_end(); ++i) {
typename Polyhedron::Plane_3 p(i->opposite()->vertex()->point(), i->vertex()->point(), i->next()->vertex()->point());
if (p.has_on_positive_side(i->opposite()->next()->vertex()->point()) &&
CGAL::squared_distance(p, i->opposite()->next()->vertex()->point()) > 1e-8) {
return false;
}
}
// Also make sure that there is only one shell:
boost::unordered_set<typename Polyhedron::Facet_const_handle, typename CGAL::Handle_hash_function> visited;
// c++11
// visited.reserve(p.size_of_facets());
std::queue<typename Polyhedron::Facet_const_handle> to_explore;
to_explore.push(p.facets_begin()); // One arbitrary facet
visited.insert(to_explore.front());
while (!to_explore.empty()) {
typename Polyhedron::Facet_const_handle f = to_explore.front();
to_explore.pop();
typename Polyhedron::Facet::Halfedge_around_facet_const_circulator he, end;
end = he = f->facet_begin();
CGAL_For_all(he,end) {
typename Polyhedron::Facet_const_handle o = he->opposite()->facet();
if (!visited.count(o)) {
visited.insert(o);
to_explore.push(o);
}
}
}
return visited.size() == p.size_of_facets();
}
/*!
Applies op to all children and returns the result.
The child list should be guaranteed to contain non-NULL 3D or empty Geometry objects
*/
CGAL_Nef_polyhedron *applyOperator(const Geometry::ChildList &children, OpenSCADOperator op)
{
CGAL_Nef_polyhedron *N = NULL;
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
try {
// Speeds up n-ary union operations significantly
CGAL::Nef_nary_union_3<CGAL_Nef_polyhedron3> nary_union;
int nary_union_num_inserted = 0;
BOOST_FOREACH(const Geometry::ChildItem &item, children) {
const shared_ptr<const Geometry> &chgeom = item.second;
shared_ptr<const CGAL_Nef_polyhedron> chN =
dynamic_pointer_cast<const CGAL_Nef_polyhedron>(chgeom);
if (!chN) {
const PolySet *chps = dynamic_cast<const PolySet*>(chgeom.get());
if (chps) chN.reset(createNefPolyhedronFromGeometry(*chps));
}
if (op == OPENSCAD_UNION) {
if (!chN->isEmpty()) {
// nary_union.add_polyhedron() can issue assertion errors:
// https://github.com/openscad/openscad/issues/802
nary_union.add_polyhedron(*chN->p3);
nary_union_num_inserted++;
}
continue;
}
// Initialize N with first expected geometric object
if (!N) {
N = new CGAL_Nef_polyhedron(*chN);
continue;
}
// Intersecting something with nothing results in nothing
if (chN->isEmpty()) {
if (op == OPENSCAD_INTERSECTION) *N = *chN;
continue;
}
// empty op <something> => empty
if (N->isEmpty()) continue;
switch (op) {
case OPENSCAD_INTERSECTION:
*N *= *chN;
break;
case OPENSCAD_DIFFERENCE:
*N -= *chN;
break;
case OPENSCAD_MINKOWSKI:
N->minkowski(*chN);
break;
default:
PRINTB("ERROR: Unsupported CGAL operator: %d", op);
}
item.first->progress_report();
}
if (op == OPENSCAD_UNION && nary_union_num_inserted > 0) {
N = new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron3(nary_union.get_union()));
}
}
// union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad
catch (const CGAL::Failure_exception &e) {
std::string opstr = op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_UNION ? "union" : "UNKNOWN";
PRINTB("ERROR: CGAL error in CGALUtils::applyBinaryOperator %s: %s", opstr % e.what());
}
CGAL::set_error_behaviour(old_behaviour);
return N;
}
bool applyHull(const Geometry::ChildList &children, PolySet &result)
{
typedef CGAL::Epick K;
// Collect point cloud
// NB! CGAL's convex_hull_3() doesn't like std::set iterators, so we use a list
// instead.
std::list<K::Point_3> points;
BOOST_FOREACH(const Geometry::ChildItem &item, children) {
const shared_ptr<const Geometry> &chgeom = item.second;
const CGAL_Nef_polyhedron *N = dynamic_cast<const CGAL_Nef_polyhedron *>(chgeom.get());
if (N) {
if (!N->isEmpty()) {
for (CGAL_Nef_polyhedron3::Vertex_const_iterator i = N->p3->vertices_begin(); i != N->p3->vertices_end(); ++i) {
points.push_back(vector_convert<K::Point_3>(i->point()));
}
}
} else {
const PolySet *ps = dynamic_cast<const PolySet *>(chgeom.get());
if (ps) {
BOOST_FOREACH(const Polygon &p, ps->polygons) {
BOOST_FOREACH(const Vector3d &v, p) {
points.push_back(K::Point_3(v[0], v[1], v[2]));
}
}
}
}
}
if (points.size() <= 3) return false;
// Apply hull
bool success = false;
if (points.size() >= 4) {
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
try {
CGAL::Polyhedron_3<K> r;
CGAL::convex_hull_3(points.begin(), points.end(), r);
PRINTDB("After hull vertices: %d", r.size_of_vertices());
PRINTDB("After hull facets: %d", r.size_of_facets());
PRINTDB("After hull closed: %d", r.is_closed());
PRINTDB("After hull valid: %d", r.is_valid());
success = !createPolySetFromPolyhedron(r, result);
}
catch (const CGAL::Assertion_exception &e) {
PRINTB("ERROR: CGAL error in applyHull(): %s", e.what());
}
CGAL::set_error_behaviour(old_behaviour);
}
return success;
}
/*!
children cannot contain NULL objects
*/
Geometry const * applyMinkowski(const Geometry::ChildList &children)
{
CGAL::Timer t,t_tot;
assert(children.size() >= 2);
Geometry::ChildList::const_iterator it = children.begin();
t_tot.start();
Geometry const* operands[2] = {it->second.get(), NULL};
try {
while (++it != children.end()) {
operands[1] = it->second.get();
typedef CGAL::Epick Hull_kernel;
std::list<CGAL_Polyhedron> P[2];
std::list<CGAL::Polyhedron_3<Hull_kernel> > result_parts;
for (size_t i = 0; i < 2; i++) {
CGAL_Polyhedron poly;
const PolySet * ps = dynamic_cast<const PolySet *>(operands[i]);
const CGAL_Nef_polyhedron * nef = dynamic_cast<const CGAL_Nef_polyhedron *>(operands[i]);
if (ps) CGALUtils::createPolyhedronFromPolySet(*ps, poly);
else if (nef && nef->p3->is_simple()) nefworkaround::convert_to_Polyhedron<CGAL_Kernel3>(*nef->p3, poly);
else throw 0;
if ((ps && ps->is_convex()) ||
(!ps && is_weakly_convex(poly))) {
PRINTDB("Minkowski: child %d is convex and %s",i % (ps?"PolySet":"Nef"));
P[i].push_back(poly);
} else {
CGAL_Nef_polyhedron3 decomposed_nef;
if (ps) {
PRINTDB("Minkowski: child %d is nonconvex PolySet, transforming to Nef and decomposing...", i);
CGAL_Nef_polyhedron *p = createNefPolyhedronFromGeometry(*ps);
if (!p->isEmpty()) decomposed_nef = *p->p3;
delete p;
} else {
PRINTDB("Minkowski: child %d is nonconvex Nef, decomposing...",i);
decomposed_nef = *nef->p3;
}
t.start();
CGAL::convex_decomposition_3(decomposed_nef);
// the first volume is the outer volume, which ignored in the decomposition
CGAL_Nef_polyhedron3::Volume_const_iterator ci = ++decomposed_nef.volumes_begin();
for(; ci != decomposed_nef.volumes_end(); ++ci) {
if(ci->mark()) {
CGAL_Polyhedron poly;
decomposed_nef.convert_inner_shell_to_polyhedron(ci->shells_begin(), poly);
P[i].push_back(poly);
}
}
PRINTDB("Minkowski: decomposed into %d convex parts", P[i].size());
t.stop();
PRINTDB("Minkowski: decomposition took %f s", t.time());
}
}
std::vector<Hull_kernel::Point_3> points[2];
std::vector<Hull_kernel::Point_3> minkowski_points;
for (size_t i = 0; i < P[0].size(); i++) {
for (size_t j = 0; j < P[1].size(); j++) {
t.start();
points[0].clear();
points[1].clear();
for (int k = 0; k < 2; k++) {
std::list<CGAL_Polyhedron>::iterator it = P[k].begin();
std::advance(it, k==0?i:j);
CGAL_Polyhedron const& poly = *it;
points[k].reserve(poly.size_of_vertices());
for (CGAL_Polyhedron::Vertex_const_iterator pi = poly.vertices_begin(); pi != poly.vertices_end(); ++pi) {
CGAL_Polyhedron::Point_3 const& p = pi->point();
points[k].push_back(Hull_kernel::Point_3(to_double(p[0]),to_double(p[1]),to_double(p[2])));
}
}
minkowski_points.clear();
minkowski_points.reserve(points[0].size() * points[1].size());
for (size_t i = 0; i < points[0].size(); i++) {
for (size_t j = 0; j < points[1].size(); j++) {
minkowski_points.push_back(points[0][i]+(points[1][j]-CGAL::ORIGIN));
}
}
if (minkowski_points.size() <= 3) {
t.stop();
continue;
}
CGAL::Polyhedron_3<Hull_kernel> result;
t.stop();
PRINTDB("Minkowski: Point cloud creation (%d ⨉ %d -> %d) took %f ms", points[0].size() % points[1].size() % minkowski_points.size() % (t.time()*1000));
t.reset();
t.start();
CGAL::convex_hull_3(minkowski_points.begin(), minkowski_points.end(), result);
std::vector<Hull_kernel::Point_3> strict_points;
strict_points.reserve(minkowski_points.size());
for (CGAL::Polyhedron_3<Hull_kernel>::Vertex_iterator i = result.vertices_begin(); i != result.vertices_end(); ++i) {
Hull_kernel::Point_3 const& p = i->point();
CGAL::Polyhedron_3<Hull_kernel>::Vertex::Halfedge_handle h,e;
h = i->halfedge();
e = h;
bool collinear = false;
bool coplanar = true;
do {
Hull_kernel::Point_3 const& q = h->opposite()->vertex()->point();
if (coplanar && !CGAL::coplanar(p,q,
h->next_on_vertex()->opposite()->vertex()->point(),
h->next_on_vertex()->next_on_vertex()->opposite()->vertex()->point())) {
coplanar = false;
}
for (CGAL::Polyhedron_3<Hull_kernel>::Vertex::Halfedge_handle j = h->next_on_vertex();
j != h && !collinear && ! coplanar;
j = j->next_on_vertex()) {
Hull_kernel::Point_3 const& r = j->opposite()->vertex()->point();
if (CGAL::collinear(p,q,r)) {
collinear = true;
}
}
h = h->next_on_vertex();
} while (h != e && !collinear);
if (!collinear && !coplanar)
strict_points.push_back(p);
}
result.clear();
CGAL::convex_hull_3(strict_points.begin(), strict_points.end(), result);
t.stop();
PRINTDB("Minkowski: Computing convex hull took %f s", t.time());
t.reset();
result_parts.push_back(result);
}
}
if (it != boost::next(children.begin()))
delete operands[0];
if (result_parts.size() == 1) {
PolySet *ps = new PolySet(3,true);
createPolySetFromPolyhedron(*result_parts.begin(), *ps);
operands[0] = ps;
} else if (!result_parts.empty()) {
t.start();
PRINTDB("Minkowski: Computing union of %d parts",result_parts.size());
Geometry::ChildList fake_children;
for (std::list<CGAL::Polyhedron_3<Hull_kernel> >::iterator i = result_parts.begin(); i != result_parts.end(); ++i) {
PolySet ps(3,true);
createPolySetFromPolyhedron(*i, ps);
fake_children.push_back(std::make_pair((const AbstractNode*)NULL,
shared_ptr<const Geometry>(createNefPolyhedronFromGeometry(ps))));
}
CGAL_Nef_polyhedron *N = CGALUtils::applyOperator(fake_children, OPENSCAD_UNION);
// FIXME: This hould really never throw.
// Assert once we figured out what went wrong with issue #1069?
if (!N) throw 0;
t.stop();
PRINTDB("Minkowski: Union done: %f s",t.time());
t.reset();
operands[0] = N;
} else {
operands[0] = new CGAL_Nef_polyhedron();
}
}
t_tot.stop();
PRINTDB("Minkowski: Total execution time %f s", t_tot.time());
t_tot.reset();
return operands[0];
}
catch (...) {
// If anything throws we simply fall back to Nef Minkowski
PRINTD("Minkowski: Falling back to Nef Minkowski");
CGAL_Nef_polyhedron *N = applyOperator(children, OPENSCAD_MINKOWSKI);
return N;
}
}
}; // namespace CGALUtils
#endif // ENABLE_CGAL

View file

@ -11,13 +11,8 @@
#include <boost/foreach.hpp>
namespace /* anonymous */ {
template<typename Result, typename V>
Result vector_convert(V const& v) {
return Result(CGAL::to_double(v[0]),CGAL::to_double(v[1]),CGAL::to_double(v[2]));
}
#undef GEN_SURFACE_DEBUG
namespace /* anonymous */ {
template <typename Polyhedron>
class CGAL_Build_PolySet : public CGAL::Modifier_base<typename Polyhedron::HalfedgeDS>

266
src/cgalutils-project.cc Normal file
View file

@ -0,0 +1,266 @@
// this file is split into many separate cgalutils* files
// in order to workaround gcc 4.9.1 crashing on systems with only 2GB of RAM
#ifdef ENABLE_CGAL
#include "cgalutils.h"
#include "polyset.h"
#include "printutils.h"
#include "Polygon2d.h"
#include "polyset-utils.h"
#include "grid.h"
#include "node.h"
#include "cgal.h"
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/normal_vector_newell_3.h>
#include <CGAL/Handle_hash_function.h>
#include <CGAL/config.h>
#include <CGAL/version.h>
// Apply CGAL bugfix for CGAL-4.5.x
#if CGAL_VERSION_NR > CGAL_VERSION_NUMBER(4,5,1) || CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4,5,0)
#include <CGAL/convex_hull_3.h>
#else
#include "convex_hull_3_bugfix.h"
#endif
#include "svg.h"
#include "Reindexer.h"
#include "GeometryUtils.h"
#include <map>
#include <queue>
#include <boost/foreach.hpp>
#include <boost/unordered_set.hpp>
static void add_outline_to_poly(CGAL_Nef_polyhedron2::Explorer &explorer,
CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator circ,
CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator end,
bool positive,
Polygon2d *poly) {
Outline2d outline;
CGAL_For_all(circ, end) {
if (explorer.is_standard(explorer.target(circ))) {
CGAL_Nef_polyhedron2::Explorer::Point ep = explorer.point(explorer.target(circ));
outline.vertices.push_back(Vector2d(to_double(ep.x()),
to_double(ep.y())));
}
}
if (!outline.vertices.empty()) {
outline.positive = positive;
poly->addOutline(outline);
}
}
static Polygon2d *convertToPolygon2d(const CGAL_Nef_polyhedron2 &p2)
{
Polygon2d *poly = new Polygon2d;
typedef CGAL_Nef_polyhedron2::Explorer Explorer;
typedef Explorer::Face_const_iterator fci_t;
typedef Explorer::Halfedge_around_face_const_circulator heafcc_t;
Explorer E = p2.explorer();
for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) {
if (!fit->mark()) continue;
heafcc_t fcirc(E.face_cycle(fit)), fend(fcirc);
add_outline_to_poly(E, fcirc, fend, true, poly);
for (CGAL_Nef_polyhedron2::Explorer::Hole_const_iterator j = E.holes_begin(fit);j != E.holes_end(fit); ++j) {
CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator hcirc(j), hend(hcirc);
add_outline_to_poly(E, hcirc, hend, false, poly);
}
}
poly->setSanitized(true);
return poly;
}
/*
ZRemover
This class converts one or more Nef3 polyhedra into a Nef2 polyhedron by
stripping off the 'z' coordinates from the vertices. The resulting Nef2
poly is accumulated in the 'output_nefpoly2d' member variable.
The 'z' coordinates will either be all 0s, for an xy-plane intersected Nef3,
or, they will be a mixture of -eps and +eps, for a thin-box intersected Nef3.
Notes on CGAL's Nef Polyhedron2:
1. The 'mark' on a 2d Nef face is important when doing unions/intersections.
If the 'mark' of a face is wrong the resulting nef2 poly will be unexpected.
2. The 'mark' can be dependent on the points fed to the Nef2 constructor.
This is why we iterate through the 3d faces using the halfedge cycle
source()->target() instead of the ordinary source()->source(). The
the latter can generate sequences of points that will fail the
the CGAL::is_simple_2() test, resulting in improperly marked nef2 polys.
3. 3d facets have 'two sides'. we throw out the 'down' side to prevent dups.
The class uses the 'visitor' pattern from the CGAL manual. See also
http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html
http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3_ref/Class_Nef_polyhedron3.html
OGL_helper.h
*/
class ZRemover {
public:
CGAL_Nef_polyhedron2::Boundary boundary;
boost::shared_ptr<CGAL_Nef_polyhedron2> tmpnef2d;
boost::shared_ptr<CGAL_Nef_polyhedron2> output_nefpoly2d;
CGAL::Direction_3<CGAL_Kernel3> up;
ZRemover()
{
output_nefpoly2d.reset( new CGAL_Nef_polyhedron2() );
boundary = CGAL_Nef_polyhedron2::INCLUDED;
up = CGAL::Direction_3<CGAL_Kernel3>(0,0,1);
}
void visit( CGAL_Nef_polyhedron3::Vertex_const_handle ) {}
void visit( CGAL_Nef_polyhedron3::Halfedge_const_handle ) {}
void visit( CGAL_Nef_polyhedron3::SHalfedge_const_handle ) {}
void visit( CGAL_Nef_polyhedron3::SHalfloop_const_handle ) {}
void visit( CGAL_Nef_polyhedron3::SFace_const_handle ) {}
void visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet );
};
void ZRemover::visit(CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet)
{
PRINTDB(" <!-- ZRemover Halffacet visit. Mark: %i --> ",hfacet->mark());
if (hfacet->plane().orthogonal_direction() != this->up) {
PRINTD(" <!-- ZRemover down-facing half-facet. skipping -->");
PRINTD(" <!-- ZRemover Halffacet visit end-->");
return;
}
// possible optimization - throw out facets that are vertically oriented
CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator fci;
int contour_counter = 0;
CGAL_forall_facet_cycles_of(fci, hfacet) {
if (fci.is_shalfedge()) {
PRINTD(" <!-- ZRemover Halffacet cycle begin -->");
CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(fci), cend(c1);
std::vector<CGAL_Nef_polyhedron2::Explorer::Point> contour;
CGAL_For_all(c1, cend) {
CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->target()->point();
CGAL_Nef_polyhedron2::Explorer::Point point2d(CGAL::to_double(point3d.x()),
CGAL::to_double(point3d.y()));
contour.push_back(point2d);
}
if (contour.size()==0) continue;
if (OpenSCAD::debug!="")
PRINTDB(" <!-- is_simple_2: %i -->", CGAL::is_simple_2(contour.begin(), contour.end()));
tmpnef2d.reset(new CGAL_Nef_polyhedron2(contour.begin(), contour.end(), boundary));
if (contour_counter == 0) {
PRINTDB(" <!-- contour is a body. make union(). %i points -->", contour.size());
*(output_nefpoly2d) += *(tmpnef2d);
} else {
PRINTDB(" <!-- contour is a hole. make intersection(). %i points -->", contour.size());
*(output_nefpoly2d) *= *(tmpnef2d);
}
/*log << "\n<!-- ======== output tmp nef: ==== -->\n"
<< OpenSCAD::dump_svg(*tmpnef2d) << "\n"
<< "\n<!-- ======== output accumulator: ==== -->\n"
<< OpenSCAD::dump_svg(*output_nefpoly2d) << "\n";*/
contour_counter++;
} else {
PRINTD(" <!-- ZRemover trivial facet cycle skipped -->");
}
PRINTD(" <!-- ZRemover Halffacet cycle end -->");
}
PRINTD(" <!-- ZRemover Halffacet visit end -->");
}
namespace CGALUtils {
Polygon2d *project(const CGAL_Nef_polyhedron &N, bool cut)
{
Polygon2d *poly = NULL;
if (N.getDimension() != 3) return poly;
CGAL_Nef_polyhedron newN;
if (cut) {
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
try {
CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0);
newN.p3.reset(new CGAL_Nef_polyhedron3(N.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY)));
}
catch (const CGAL::Failure_exception &e) {
PRINTDB("CGALUtils::project during plane intersection: %s", e.what());
try {
PRINTD("Trying alternative intersection using very large thin box: ");
std::vector<CGAL_Point_3> pts;
// dont use z of 0. there are bugs in CGAL.
double inf = 1e8;
double eps = 0.001;
CGAL_Point_3 minpt(-inf, -inf, -eps);
CGAL_Point_3 maxpt( inf, inf, eps);
CGAL_Iso_cuboid_3 bigcuboid(minpt, maxpt);
for (int i=0;i<8;i++) pts.push_back(bigcuboid.vertex(i));
CGAL_Polyhedron bigbox;
CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox);
CGAL_Nef_polyhedron3 nef_bigbox(bigbox);
newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*N.p3)));
}
catch (const CGAL::Failure_exception &e) {
PRINTB("ERROR: CGAL error in CGALUtils::project during bigbox intersection: %s", e.what());
}
}
if (!newN.p3 || newN.p3->is_empty()) {
CGAL::set_error_behaviour(old_behaviour);
PRINT("WARNING: projection() failed.");
return poly;
}
PRINTDB("%s",OpenSCAD::svg_header(480, 100000));
try {
ZRemover zremover;
CGAL_Nef_polyhedron3::Volume_const_iterator i;
CGAL_Nef_polyhedron3::Shell_entry_const_iterator j;
CGAL_Nef_polyhedron3::SFace_const_handle sface_handle;
for (i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i) {
PRINTDB("<!-- volume. mark: %s -->",i->mark());
for (j = i->shells_begin(); j != i->shells_end(); ++j) {
PRINTDB("<!-- shell. (vol mark was: %i)", i->mark());;
sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle(j);
newN.p3->visit_shell_objects(sface_handle , zremover);
PRINTD("<!-- shell. end. -->");
}
PRINTD("<!-- volume end. -->");
}
poly = convertToPolygon2d(*zremover.output_nefpoly2d);
} catch (const CGAL::Failure_exception &e) {
PRINTB("ERROR: CGAL error in CGALUtils::project while flattening: %s", e.what());
}
PRINTD("</svg>");
CGAL::set_error_behaviour(old_behaviour);
}
// In projection mode all the triangles are projected manually into the XY plane
else {
PolySet ps(3);
bool err = CGALUtils::createPolySetFromNefPolyhedron3(*N.p3, ps);
if (err) {
PRINT("ERROR: Nef->PolySet failed");
return poly;
}
poly = PolysetUtils::project(ps);
}
return poly;
}
} // namespace
#endif // ENABLE_CGAL

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,13 @@ typedef CGAL::Point_3<K> Vertex3K;
typedef std::vector<Vertex3K> PolygonK;
typedef std::vector<PolygonK> PolyholeK;
namespace /* anonymous */ {
template<typename Result, typename V>
Result vector_convert(V const& v) {
return Result(CGAL::to_double(v[0]),CGAL::to_double(v[1]),CGAL::to_double(v[2]));
}
}
namespace CGALUtils {
bool applyHull(const Geometry::ChildList &children, PolySet &P);
CGAL_Nef_polyhedron *applyOperator(const Geometry::ChildList &children, OpenSCADOperator op);

View file

@ -319,10 +319,7 @@ std::string ImportNode::toString() const
"scale = " << this->scale << ", "
"convexity = " << this->convexity << ", "
"$fn = " << this->fn << ", $fa = " << this->fa << ", $fs = " << this->fs
#ifndef OPENSCAD_TESTING
// timestamp is needed for caching, but disturbs the test framework
<< ", " "timestamp = " << (fs::exists(path) ? fs::last_write_time(path) : 0)
#endif
<< ")";

View file

@ -142,10 +142,7 @@ std::string LinearExtrudeNode::toString() const
"file = " << this->filename << ", "
"layer = " << QuotedString(this->layername) << ", "
"origin = [" << this->origin_x << ", " << this->origin_y << "], "
#ifndef OPENSCAD_TESTING
// timestamp is needed for caching, but disturbs the test framework
<< "timestamp = " << (fs::exists(path) ? fs::last_write_time(path) : 0) << ", "
#endif
;
}
stream <<

View file

@ -93,6 +93,16 @@
#include <QClipboard>
#include <QDesktopWidget>
#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
// Set dummy for Qt versions that do not have QSaveFile
#define QT_FILE_SAVE_CLASS QFile
#define QT_FILE_SAVE_COMMIT true
#else
#include <QSaveFile>
#define QT_FILE_SAVE_CLASS QSaveFile
#define QT_FILE_SAVE_COMMIT if (saveOk) { saveOk = file.commit(); } else { file.cancelWriting(); }
#endif
#include <fstream>
#include <algorithm>
@ -1322,6 +1332,16 @@ void MainWindow::saveBackup()
return writeBackup(this->tempFile);
}
void MainWindow::saveError(const QIODevice &file, const std::string &msg) {
const std::string messageFormat = msg + " %s (%s)";
const char *fileName = this->fileName.toLocal8Bit().constData();
PRINTB(messageFormat.c_str(), fileName % file.errorString().toLocal8Bit().constData());
const std::string dialogFormatStr = msg + "\n\"%1\"\n(%2)";
const QString dialogFormat(dialogFormatStr.c_str());
QMessageBox::warning(this, windowTitle(), dialogFormat.arg(this->fileName).arg(file.errorString()));
}
/*!
Save current document.
Should _always_ write to disk, since this is called by SaveAs - i.e. don't try to be
@ -1331,26 +1351,37 @@ void MainWindow::actionSave()
{
if (this->fileName.isEmpty()) {
actionSaveAs();
return;
}
setCurrentOutput();
// If available (>= Qt 5.1), use QSaveFile to ensure the file is not
// destroyed if the device is full. Unfortunately this is not working
// as advertised (at least in Qt 5.3) as it does not detect the device
// full properly and happily commits a 0 byte file.
// Checking the QTextStream status flag after flush() seems to catch
// this condition.
QT_FILE_SAVE_CLASS file(this->fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
saveError(file, _("Failed to open file for writing"));
}
else {
setCurrentOutput();
QFile file(this->fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
PRINTB("Failed to open file for writing: %s (%s)",
this->fileName.toLocal8Bit().constData() % file.errorString().toLocal8Bit().constData());
QMessageBox::warning(this, windowTitle(), tr("Failed to open file for writing:\n %1 (%2)")
.arg(this->fileName).arg(file.errorString()));
}
else {
QTextStream writer(&file);
writer.setCodec("UTF-8");
writer << this->editor->toPlainText();
PRINTB("Saved design '%s'.", this->fileName.toLocal8Bit().constData());
QTextStream writer(&file);
writer.setCodec("UTF-8");
writer << this->editor->toPlainText();
writer.flush();
bool saveOk = writer.status() == QTextStream::Ok;
QT_FILE_SAVE_COMMIT;
if (saveOk) {
PRINTB(_("Saved design '%s'."), this->fileName.toLocal8Bit().constData());
this->editor->setContentModified(false);
} else {
saveError(file, _("Error saving design"));
}
clearCurrentOutput();
updateRecentFiles();
}
clearCurrentOutput();
updateRecentFiles();
}
void MainWindow::actionSaveAs()

View file

@ -272,12 +272,10 @@ Camera get_camera(po::variables_map vm)
return camera;
}
#ifdef OPENSCAD_TESTING
#undef OPENSCAD_QTGUI
#else
#define OPENSCAD_QTGUI 1
#ifndef OPENSCAD_NOGUI
#include <QApplication>
#include <QSettings>
#define OPENSCAD_QTGUI 1
#endif
static bool checkAndExport(shared_ptr<const Geometry> root_geom, unsigned nd,
enum FileFormat format, const char *filename)

View file

@ -102,9 +102,7 @@ void parser_init()
}
}
#ifndef OPENSCAD_TESTING
add_librarydir(PlatformUtils::userLibraryPath());
#endif
add_librarydir(boosty::absolute(PlatformUtils::resourcePath("libraries")).string());
}

View file

@ -104,10 +104,7 @@ std::string RotateExtrudeNode::toString() const
"layer = " << QuotedString(this->layername) << ", "
"origin = [" << std::dec << this->origin_x << ", " << this->origin_y << "], "
"scale = " << this->scale << ", "
#ifndef OPENSCAD_TESTING
// timestamp is needed for caching, but disturbs the test framework
<< "timestamp = " << (fs::exists(path) ? fs::last_write_time(path) : 0) << ", "
#endif
;
}
stream <<

View file

@ -311,10 +311,7 @@ std::string SurfaceNode::toString() const
stream << this->name() << "(file = " << this->filename
<< ", center = " << (this->center ? "true" : "false")
<< ", invert = " << (this->invert ? "true" : "false")
#ifndef OPENSCAD_TESTING
// timestamp is needed for caching, but disturbs the test framework
<< ", " "timestamp = " << (fs::exists(path) ? fs::last_write_time(path) : 0)
#endif
<< ")";
return stream.str();

View file

@ -201,33 +201,12 @@ public:
if (op1 == 0) {
return "0"; // Don't return -0 (exactly -0 and 0 equal 0)
}
#ifdef OPENSCAD_TESTING
// Quick and dirty hack to work around floating point rounding differences
// across platforms for testing purposes.
std::stringstream tmp;
tmp.precision(12);
tmp.setf(std::ios_base::fixed);
tmp << op1;
std::string tmpstr = tmp.str();
size_t endpos = tmpstr.find_last_not_of('0');
if (tmpstr[endpos] == '.') endpos--;
tmpstr = tmpstr.substr(0, endpos+1);
size_t dotpos = tmpstr.find('.');
if (dotpos != std::string::npos) {
if (tmpstr.size() - dotpos > 12) tmpstr.erase(dotpos + 12);
while (tmpstr[tmpstr.size()-1] == '0') tmpstr.erase(tmpstr.size()-1);
}
if (tmpstr.compare("-0") == 0) tmpstr = "0";
tmpstr = two_digit_exp_format(tmpstr);
return tmpstr;
#else
// attempt to emulate Qt's QString.sprintf("%g"); from old OpenSCAD.
// see https://github.com/openscad/openscad/issues/158
std::stringstream tmp;
tmp.unsetf(std::ios::floatfield);
tmp << op1;
return tmp.str();
#endif
}
std::string operator()(const boost::blank &) const {

View file

@ -89,7 +89,7 @@ a time, to avoid confusion.
#else
#endif // ENABLE_OPENCSG
#ifndef OPENSCAD_TESTING
#ifndef OPENSCAD_NOGUI
#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

View file

@ -344,9 +344,19 @@ if (NOT OPENGL_GLU_FOUND)
message(FATAL "GLU library not found")
endif()
set(OPENGL_LIBRARIES ${OPENGL_glu_LIBRARY} ${OPENGL_LIBRARIES})
message(STATUS "OpenGL LIBRARIES: ")
foreach(GLLIB ${OPENGL_LIBRARIES})
message(STATUS " " ${GLLIB})
endif()
message(STATUS "OPENGL_LIBRARIES: ")
foreach(GLLIB ${OPENGL_LIBRARIES})
message(STATUS " " ${GLLIB})
endforeach()
if (UNIX)
# see github issue 1355. as of writing the code for offscreen GL context
# setup requires X11 on Un*x like systems (not Apple/Win).
find_package(X11 REQUIRED)
message(STATUS "X11_INCLUDE_DIR: " ${X11_INCLUDE_DIR})
message(STATUS "X11_LIBRARIES: ")
foreach(XLIB ${X11_LIBRARIES})
message(STATUS " " ${XLIB})
endforeach()
endif()
@ -514,8 +524,12 @@ foreach(CGAL3RDPLIB ${CGAL_3RD_PARTY_LIBRARIES})
endforeach()
if(${CMAKE_CXX_COMPILER} MATCHES ".*clang.*" AND NOT ${CGAL_CXX_FLAGS_INIT} STREQUAL "" )
string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
# clang fails to build several included standard C++ libs on some
# machines when FORTIFY_SOURCE is enabled.
message(STATUS "disabling FORTIFY_SOURCE: https://bugzilla.redhat.com/show_bug.cgi?id=1188075")
string(REPLACE "FORTIFY_SOURCE=2" "FORTIFY_SOURCE=0" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT})
endif()
# GLib2
@ -648,7 +662,7 @@ if (DEFINED OPENSCAD_DAY)
add_definitions(-DOPENSCAD_DAY=${OPENSCAD_DAY})
endif()
add_definitions(-DOPENSCAD_TESTING -DENABLE_EXPERIMENTAL)
add_definitions(-DENABLE_EXPERIMENTAL -DOPENSCAD_NOGUI)
# Search for MCAD in correct place
set(CTEST_ENVIRONMENT "${CTEST_ENVIRONMENT};OPENSCADPATH=${CMAKE_CURRENT_SOURCE_DIR}/../libraries")
@ -665,6 +679,8 @@ elseif(UNIX)
set(OFFSCREEN_CTX_SOURCE "OffscreenContextGLX.cc" CACHE TYPE STRING)
set(OFFSCREEN_IMGUTILS_SOURCE "imageutils-lodepng.cc" CACHE TYPE STRING)
set(PLATFORMUTILS_SOURCE "PlatformUtils-posix.cc" CACHE TYPE STRING)
# X11 needed for Offscreen OpenGL on current Un*x, see github issue 1355
set(OFFSCREEN_CTX_LIBRARIES ${X11_LIBRARIES} CACHE TYPE STRING)
elseif(WIN32)
message(STATUS "Offscreen OpenGL Context - using Microsoft WGL")
set(OFFSCREEN_CTX_SOURCE "OffscreenContextWGL.cc" CACHE TYPE STRING)
@ -743,6 +759,8 @@ set(CGAL_SOURCES
../src/CSGTermEvaluator.cc
../src/CGAL_Nef_polyhedron.cc
../src/cgalutils.cc
../src/cgalutils-applyops.cc
../src/cgalutils-project.cc
../src/cgalutils-tess.cc
../src/cgalutils-polyhedron.cc
../src/CGALCache.cc
@ -819,6 +837,7 @@ endif()
add_library(tests-offscreen STATIC ${OFFSCREEN_SOURCES})
set_target_properties(tests-offscreen PROPERTIES COMPILE_FLAGS "${ENABLE_OPENCSG_FLAG} -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}")
target_link_libraries(tests-offscreen ${OFFSCREEN_CTX_LIBRARIES})
#
# modulecachetest
@ -840,43 +859,29 @@ set_target_properties(cgalcachetest PROPERTIES COMPILE_FLAGS "-DENABLE_CGAL ${CG
target_link_libraries(cgalcachetest tests-cgal ${GLEW_LIBRARIES} ${OPENCSG_LIBRARY} ${APP_SERVICES_LIBRARY})
#
# openscad no-qt
# openscad_nogui - an OpenSCAD binary build without Qt
# Enabled by using -DNOGUI=1 as a cmake parameter. Only kept for backwards compatibility and in case
# someone needs that binary.
#
add_executable(openscad_nogui ../src/openscad.cc)
set_target_properties(openscad_nogui PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -DEIGEN_DONT_ALIGN ${ENABLE_OPENCSG_FLAG} -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}")
target_link_libraries(openscad_nogui tests-offscreen tests-cgal ${GLEW_LIBRARIES} ${OPENCSG_LIBRARY} ${APP_SERVICES_LIBRARY})
# also run translation compilation to verify the files are without syntax errors
add_custom_target(locale_files ALL COMMAND ${CMAKE_SOURCE_DIR}/../scripts/translation-make.sh)
#
# GUI binary tests
#
#if(APPLE)
# set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../OpenSCAD.app/Contents/MacOS/OpenSCAD")
#elseif (MINGW_CROSS_ENV_DIR)
# set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../mingw32/release/openscad.exe")
#elseif(WIN32)
# set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../Release/openscad.exe")
#else()
# set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../openscad")
#endif()
#if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/openscad")
# set(OPENSCAD_BINPATH "${CMAKE_CURRENT_BINARY_DIR}/openscad")
#endif()
#if(EXISTS "${OPENSCAD_BINPATH}")
# message(STATUS "Found OpenSCAD binary: ${OPENSCAD_BINPATH}")
#else()
# message(STATUS "Couldn't find the OpenSCAD binary: ${OPENSCAD_BINPATH}")
# message(FATAL_ERROR "Please build the OpenSCAD binary and place it here: ${OPENSCAD_BINPATH}" )
#endif()
if(WIN32)
set(OPENSCAD_BINPATH "${CMAKE_CURRENT_BINARY_DIR}/openscad_nogui.exe")
if(APPLE)
set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../OpenSCAD.app/Contents/MacOS/OpenSCAD")
elseif (MINGW_CROSS_ENV_DIR)
set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../mingw32/release/openscad.exe")
elseif(WIN32)
set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../Release/openscad.exe")
else()
set(OPENSCAD_BINPATH "${CMAKE_CURRENT_BINARY_DIR}/openscad_nogui")
set(OPENSCAD_BINPATH "${CMAKE_CURRENT_SOURCE_DIR}/../openscad")
endif()
if(EXISTS "${OPENSCAD_BINPATH}")
message(STATUS "Found OpenSCAD binary: ${OPENSCAD_BINPATH}")
else()
message(STATUS "Couldn't find the OpenSCAD binary: ${OPENSCAD_BINPATH}")
message(FATAL_ERROR "Please build the OpenSCAD binary and place it here: ${OPENSCAD_BINPATH}" )
endif()
#

View file

@ -116,7 +116,7 @@ if args.format != 'csg':
create_png_cmd = [args.openscad, newscadfile, '-o', pngfile] + remaining_args
print >> sys.stderr, 'Running OpenSCAD #2:'
print >> sys.stderr, ' '.join(create_png_cmd)
fontdir = os.path.join(os.path.dirname(args.openscad), "..", "testdata");
fontdir = os.path.join(os.path.dirname(__file__), "..", "testdata");
fontenv = os.environ.copy();
fontenv["OPENSCAD_FONT_PATH"] = fontdir;
result = subprocess.call(create_png_cmd, env = fontenv);

View file

@ -93,12 +93,38 @@ def execute_and_redirect(cmd, params, outfile):
if outfile == subprocess.PIPE: return (retval, out)
else: return retval
def normalize_string(s):
"""Apply all modifications to an output string which would have been
applied if OPENSCAD_TESTING was defined at build time of the executable.
This truncates all floats, removes ', timestamp = ...' parts. The function
is idempotent.
This also normalizes away import paths from 'file = ' arguments."""
s = re.sub(', timestamp = [0-9]+', '', s)
def floatrep(match):
value = float(match.groups()[0])
if abs(value) < 10**-12:
return "0"
if abs(value) >= 10**6:
return "%d"%value
return "%.6g"%value
s = re.sub('(-?[0-9]+\\.[0-9]+(e[+-][0-9]+)?)', floatrep, s)
def pathrep(match):
return match.groups()[0] + match.groups()[2]
s = re.sub('(file = ")([^"/]*/)*([^"]*")', pathrep, s)
return s
def get_normalized_text(filename):
try:
f = open(filename)
text = f.read()
except:
text = ''
text = normalize_string(text)
return text.strip("\r\n").replace("\r\n", "\n") + "\n"
def compare_text(expected, actual):