summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE_NOTES26
-rw-r--r--cgal.pri2
-rw-r--r--doc/TODO.txt16
-rw-r--r--doc/release-checklist.txt21
-rw-r--r--flex.pri2
-rw-r--r--openscad.pro4
-rw-r--r--scripts/installer.nsi30
-rwxr-xr-xscripts/macosx-build-dependencies.sh8
-rwxr-xr-xscripts/publish-macosx.sh2
-rwxr-xr-xscripts/release-linux.sh6
-rw-r--r--src/MainWindow.h3
-rw-r--r--src/MainWindow.ui8
-rw-r--r--src/Preferences.cc16
-rw-r--r--src/Preferences.h1
-rw-r--r--src/Preferences.ui56
-rw-r--r--src/cgal.h6
-rw-r--r--src/cgaladv.cc35
-rw-r--r--src/cgaladv_convexhull2.cc55
-rw-r--r--src/cgaladv_minkowski2.cc99
-rw-r--r--src/export.cc53
-rw-r--r--src/glview.cc52
-rw-r--r--src/mainwin.cc127
-rw-r--r--src/openscad.cc24
-rw-r--r--src/primitives.cc119
-rw-r--r--src/transform.cc20
-rw-r--r--testdata/scad/convex_hull.scad43
-rw-r--r--testdata/scad/minkowski.scad68
-rw-r--r--testdata/scad/non-aff-matrix.scad6
-rw-r--r--testdata/scad/testcolornames.scad159
29 files changed, 843 insertions, 224 deletions
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 3cfb955..5364e0a 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,8 +1,28 @@
-OpenSCAD 2011.XX
+OpenSCAD 20xx.yy
================
-o Added rands() function
-o Added sign() function
+
+OpenSCAD 2011.06
+================
+
+o Added "Export as Image" menu.
+
+Bugfixes:
+o Cylinder tesselation broke existing models which are using cylinders
+ for e.g. captured nut slots and are dependent on the orientation not
+ changing.
+o DXF output couldn't be imported into e.g. AutoCAD and Solidworks after updating
+ to using the AutoCAD 2000 (AC1015) format. Reverted to the old entity-only output,
+ causing LWPOLYLINES to not exported allowed anymore.
+
+
+
+OpenSCAD 2011.04
+================
+
+o Added hull() for convex hulls (2D object only)
+o minkowski() now supports 2D objects
+o Added functions: rands(), sign()
o Now supports escaping of the following characters in strings: \n, \t, \r, \\, \"
o Support nested includes
o Improved parsing of numbers
diff --git a/cgal.pri b/cgal.pri
index 3a30b22..49e44de 100644
--- a/cgal.pri
+++ b/cgal.pri
@@ -14,7 +14,7 @@ cgal {
}
win32 {
- LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-gd
+ LIBS += $$CGAL_DIR/auxiliary/gmp/lib/libmpfr-4.lib -lCGAL-vc90-mt-s
} else {
LIBS += -lgmp -lmpfr -lCGAL
}
diff --git a/doc/TODO.txt b/doc/TODO.txt
index cca4a2a..b9440c6 100644
--- a/doc/TODO.txt
+++ b/doc/TODO.txt
@@ -8,6 +8,7 @@ o It's now possible to start a new rendering while one is already running (which
-> turn off most (or all) interaction while rendering
-> Lock all or only one MainWindow (MDI)?
o Look into the polygon winding and rotate_extrude() problem reported by Britton
+o CGAL Aff_transformation_3 doesn't support non-affine transformations (non-aff-matrix.scad)
STL Import BUGS
---------------
@@ -102,9 +103,15 @@ o Editor wishlist
in the source code in the 3D view
- Tabbed editor for designs including other files
- C-c/C-v should work on the focused widget, not always in the editor
-
+o Error reporting/debugging
+ - Provide better error messages when polygon ordering causes CGAL errors:
+ o Supply syntax highlighting of the exact polygon indices which are
+ reported to be wrong
+ o Provide some interaction for debug walk-through?
+ - Provide visual highlighting of geometry corresponding to code
+ -> could aid debugging a lot
o Computation
- - Run CGAL rendering in a backgroud thread
+ - Run CGAL rendering in a background thread
- Enable viewing/editing while rendering
- Progress: Call progresswidget more often to avoid app hanging for multiple
seconds (i.e. make cancel button more responsive)
@@ -146,7 +153,7 @@ o Language Frontend
- Rethink for vs. intersection_for vs. group. Should for loops
generate child lists instead, and make these passable to other
modules or accessible by child()?
- - constants: PI
+ - constants: PI, OpenSCAD version
o DXF Import/Export
- Use dxflib from RibbonSoft for import/export? -> investigate
- Import
@@ -167,6 +174,8 @@ o Misc
- Add 'lines' object type for non-solid 2d drawings
- Is there a reason why modules like echo, empty if, empty for loop returns an
empty AbstractNode instead of being ignored?
+ - Bug: Using the background operator (%) on the only object in a scene triggers a
+ CSG error: No top level object found
o Grammar
- dim->name -> dim->label
- A random(seed) function
@@ -213,6 +222,7 @@ o Write a simple test script that collects verified and current STL renderings
o Write simple driver scripts for comparing output of above command
o Collect "all" available OpenSCAD scripts from the internets and run the integration
tests on them all
+o Write a regression test for the hexagonal cylinder orientation issue
INFRASTRUCTURE
--------------
diff --git a/doc/release-checklist.txt b/doc/release-checklist.txt
index d0e1174..a455ca9 100644
--- a/doc/release-checklist.txt
+++ b/doc/release-checklist.txt
@@ -12,25 +12,28 @@ o Tag release
git tag "openscad-2011.01"
o build source package
- git archive --format=tar openscad-2011.01 --prefix=openscad-2011.01/ | gzip > openscad-2011.01.tar.gz
+ git archive --format=tar openscad-2011.01 --prefix=openscad-2011.01/ | gzip > openscad-2011.01.src.tar.gz
o build binaries
+ tar xzf openscad-2011.01.src.tar.gz
+ cd openscad-2011.01
Mac OS X
- - publish-macosx.sh -> OpenSCAD-2011.01.dmg
+ For Qt-4.7.3: Remove /Developers/Applications/Qt/plugins/qmltooling
+ ./scripts/publish-macosx.sh -> OpenSCAD-2011.01.dmg
Linux: FIXME 32 vs. 64 bit
- - release-linux.sh
+ ./scripts/release-linux.sh
Windows: FIXME 32 vs. 64 bit
+o FIXME: Run some tests
+
o Set back version: release-linux.sh, publish-macosx.sh, FIXME: Windows
+o git push --tags
+
o Upload
- Github
Upload manually here: https://github.com/openscad/openscad/downloads
FIXME: Write a script
- - Google code
- - Get password from https://code.google.com/hosting/settings
- ./scripts/googlecode_upload.py -u kintel -w <passwd> -s "OpenSCAD 2011.11 Windows" -p openscad openscad-2011.01.win32.zip
- ./scripts/googlecode_upload.py -u kintel -w <passwd> -s "OpenSCAD 2011.11 Linux x86" -p openscad openscad-2011.01.linux-x86.tar.gz
- ./scripts/googlecode_upload.py -u kintel -w <passwd> -s "OpenSCAD 2011.11 Mac OS X" -p openscad openscad-2011.01.dmg
- ./scripts/googlecode_upload.py -u kintel -w <passwd> -s "OpenSCAD 2011.11 Source code" -p openscad openscad-2011.01.src.tar.gz
+o Update web page
+o Write email to mailing list
diff --git a/flex.pri b/flex.pri
index 15fccd0..2042952 100644
--- a/flex.pri
+++ b/flex.pri
@@ -3,7 +3,7 @@
flex.name = Flex ${QMAKE_FILE_IN}
flex.input = FLEXSOURCES
flex.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.lexer.cpp
-flex.commands = flex -P ${QMAKE_FILE_BASE} -o ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.lexer.cpp ${QMAKE_FILE_IN}
+flex.commands = flex -P ${QMAKE_FILE_BASE} -o${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.lexer.cpp ${QMAKE_FILE_IN}
flex.CONFIG += target_predeps
flex.variable_out = GENERATED_SOURCES
silent:flex.commands = @echo Lex ${QMAKE_FILE_IN} && $$flex.commands
diff --git a/openscad.pro b/openscad.pro
index 4fe0483..6b85d59 100644
--- a/openscad.pro
+++ b/openscad.pro
@@ -162,6 +162,7 @@ SOURCES += src/openscad.cc \
src/primitives.cc \
src/projection.cc \
src/cgaladv.cc \
+ src/cgaladv_convexhull2.cc \
src/cgaladv_minkowski3.cc \
src/cgaladv_minkowski2.cc \
src/surface.cc \
@@ -181,7 +182,7 @@ SOURCES += src/openscad.cc \
src/Preferences.cc \
src/progress.cc \
src/editor.cc \
- src/mathc99.cc
+ src/mathc99.cc
macx {
HEADERS += src/AppleEvents.h \
@@ -199,4 +200,3 @@ INSTALLS += examples
libraries.path = /usr/local/share/openscad/libraries/
libraries.files = libraries/*
INSTALLS += libraries
-
diff --git a/scripts/installer.nsi b/scripts/installer.nsi
new file mode 100644
index 0000000..269a30c
--- /dev/null
+++ b/scripts/installer.nsi
@@ -0,0 +1,30 @@
+!include "FileAssociation.nsh"
+Name "OpenSCAD"
+OutFile "openscad_setup.exe"
+InstallDir $PROGRAMFILES\OpenSCAD
+DirText "This will install OpenSCAD on your computer. Choose a directory"
+Section "install"
+SetOutPath $INSTDIR
+File openscad.exe
+File /r examples
+File /r libraries
+${registerExtension} "$INSTDIR\openscad.exe" ".scad" "OpenSCAD_File"
+CreateShortCut $SMPROGRAMS\OpenSCAD.lnk $INSTDIR\openscad.exe
+WriteUninstaller $INSTDIR\Uninstall.exe
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenSCAD" "DisplayName" "OpenSCAD (remove only)"
+WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenSCAD" "UninstallString" "$INSTDIR\Uninstall.exe"
+SectionEnd
+Section "Uninstall"
+${unregisterExtension} ".scad" "OpenSCAD_File"
+Delete $INSTDIR\Uninstall.exe
+Delete $INSTDIR\MyProg.exe
+Delete $SMPROGRAMS\OpenSCAD.lnk
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\OpenSCAD"
+RMDir /r $INSTDIR\examples
+RMDir /r $INSTDIR\libraries\mcad
+Delete $INSTDIR\libraries\boxes.scad
+Delete $INSTDIR\libraries\shapes.scad
+RMDir $INSTDIR\libraries
+Delete $INSTDIR\openscad.exe
+RMDir $INSTDIR
+SectionEnd
diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh
index f5a44d0..e011582 100755
--- a/scripts/macosx-build-dependencies.sh
+++ b/scripts/macosx-build-dependencies.sh
@@ -17,8 +17,8 @@
# o Port to other platforms?
#
-BASEDIR=/Users/kintel/code/metalab/checkout/OpenSCAD/libraries
-OPENSCADDIR=/Users/kintel/code/metalab/checkout/OpenSCAD/openscad-release
+BASEDIR=/Users/kintel/code/OpenSCAD/libraries
+OPENSCADDIR=/Users/kintel/code/OpenSCAD/openscad
SRCDIR=$BASEDIR/src
DEPLOYDIR=$BASEDIR/install
@@ -46,7 +46,7 @@ build_gmp()
# 64-bit version
mkdir build-x86_64
cd build-x86_64
- ../configure --prefix=$DEPLOYDIR/x86_64 "CFLAGS=-mmacosx-version-min=10.5" LDFLAGS="-mmacosx-version-min=10.5" --enable-cxx
+ ../configure --prefix=$DEPLOYDIR/x86_64 "CFLAGS=-mmacosx-version-min=10.5 -arch x86_64" LDFLAGS="-mmacosx-version-min=10.5 -arch x86_64" ABI=64 --enable-cxx
make install
# merge
@@ -158,7 +158,7 @@ build_opencsg()
echo "Using basedir:" $BASEDIR
mkdir -p $SRCDIR $DEPLOYDIR
build_gmp 5.0.1
-build_mpfr 3.0.0
+build_mpfr 3.0.1
build_boost 1.46.1
build_cgal 3.7
build_glew 1.5.8
diff --git a/scripts/publish-macosx.sh b/scripts/publish-macosx.sh
index 11820df..6415b52 100755
--- a/scripts/publish-macosx.sh
+++ b/scripts/publish-macosx.sh
@@ -1,7 +1,7 @@
#!/bin/sh
VERSION=`date "+%Y.%m.%d"`
-#VERSION=2010.05
+#VERSION=2011.06
# This is the same location as DEPLOYDIR in macosx-build-dependencies.sh
export MACOSX_DEPLOY_DIR=$PWD/../libraries/install
diff --git a/scripts/release-linux.sh b/scripts/release-linux.sh
index 8f532e0..35d177f 100755
--- a/scripts/release-linux.sh
+++ b/scripts/release-linux.sh
@@ -2,7 +2,7 @@
# WARNING: This script might only work with the authors setup...
VERSION=`date "+%Y.%m.%d"`
-#VERSION=2010.05
+#VERSION=2011.06
set -ex
@@ -30,8 +30,8 @@ gcc -o chrpath_linux scripts/chrpath_linux.c
./chrpath_linux -d release/lib/openscad/openscad
ldd openscad | sed -re 's,.* => ,,; s,[\t ].*,,;' -e '/Qt|boost/ { p; d; };' \
- -e '/lib(audio|CGAL|GLEW|opencsg|png)\.so/ { p; d; };' \
- -e 'd;' | xargs cp -vt release/lib/openscad/
+ -e '/lib(audio|CGAL|GLEW|opencsg|png|gmp|gmpxx|mpfr)\.so/ { p; d; };' \
+ -e 'd;' | xargs cp -vt release/lib/openscad/
strip release/lib/openscad/*
cat > release/install.sh << "EOT"
diff --git a/src/MainWindow.h b/src/MainWindow.h
index c0a9844..43ab273 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -79,6 +79,8 @@ private:
static void consoleOutput(const QString &msg, void *userdata) {
static_cast<MainWindow*>(userdata)->console->append(msg);
}
+ void loadViewSettings();
+ void loadDesignSettings();
private slots:
void actionNew();
@@ -109,6 +111,7 @@ private slots:
void actionExportSTL();
void actionExportOFF();
void actionExportDXF();
+ void actionExportImage();
void actionFlushCaches();
public:
diff --git a/src/MainWindow.ui b/src/MainWindow.ui
index f61d240..6548c8e 100644
--- a/src/MainWindow.ui
+++ b/src/MainWindow.ui
@@ -112,7 +112,7 @@
<x>0</x>
<y>0</y>
<width>681</width>
- <height>25</height>
+ <height>22</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@@ -178,6 +178,7 @@
<addaction name="designActionExportSTL"/>
<addaction name="designActionExportOFF"/>
<addaction name="designActionExportDXF"/>
+ <addaction name="designActionExportImage"/>
<addaction name="designActionFlushCaches"/>
</widget>
<widget class="QMenu" name="menu_View">
@@ -651,6 +652,11 @@
<string>Automatic Reload and Compile</string>
</property>
</action>
+ <action name="designActionExportImage">
+ <property name="text">
+ <string>Export as Image...</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/src/Preferences.cc b/src/Preferences.cc
index 6419944..eb6af61 100644
--- a/src/Preferences.cc
+++ b/src/Preferences.cc
@@ -40,6 +40,7 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent)
this->defaultmap["3dview/colorscheme"] = this->colorSchemeChooser->currentItem()->text();
this->defaultmap["editor/fontfamily"] = this->fontChooser->currentText();
this->defaultmap["editor/fontsize"] = this->fontSize->currentText().toUInt();
+ this->defaultmap["editor/opengl20_warning_show"] = true;
// Toolbar
QActionGroup *group = new QActionGroup(this);
@@ -97,7 +98,8 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent)
this, SLOT(fontFamilyChanged(const QString &)));
connect(this->fontSize, SIGNAL(editTextChanged(const QString &)),
this, SLOT(fontSizeChanged(const QString &)));
-
+ connect(this->OpenGL20WarningCheckbox, SIGNAL(clicked(bool)),
+ this, SLOT(OpenGL20WarningChanged(bool)));
updateGUI();
}
@@ -148,6 +150,13 @@ void Preferences::fontSizeChanged(const QString &size)
emit fontChanged(getValue("editor/fontfamily").toString(), intsize);
}
+void
+Preferences::OpenGL20WarningChanged(bool state)
+{
+ QSettings settings;
+ settings.setValue("editor/opengl20_warning_show",state);
+}
+
void Preferences::keyPressEvent(QKeyEvent *e)
{
#ifdef Q_WS_MAC
@@ -185,6 +194,7 @@ QVariant Preferences::getValue(const QString &key) const
void Preferences::updateGUI()
{
+ QSettings settings;
QList<QListWidgetItem *> found =
this->colorSchemeChooser->findItems(getValue("3dview/colorscheme").toString(),
Qt::MatchExactly);
@@ -204,6 +214,9 @@ void Preferences::updateGUI()
else {
this->fontSize->setEditText(fontsize);
}
+
+ bool opengl20_warning_show = getValue("editor/opengl20_warning_show").toBool();
+ this->OpenGL20WarningCheckbox->setChecked(opengl20_warning_show);
}
void Preferences::apply() const
@@ -211,4 +224,3 @@ void Preferences::apply() const
emit fontChanged(getValue("editor/fontfamily").toString(), getValue("editor/fontsize").toUInt());
emit requestRedraw();
}
-
diff --git a/src/Preferences.h b/src/Preferences.h
index 39599fd..79fa7ab 100644
--- a/src/Preferences.h
+++ b/src/Preferences.h
@@ -34,6 +34,7 @@ public slots:
void colorSchemeChanged();
void fontFamilyChanged(const QString &);
void fontSizeChanged(const QString &);
+ void OpenGL20WarningChanged(bool);
signals:
void requestRedraw() const;
diff --git a/src/Preferences.ui b/src/Preferences.ui
index 13db9f3..2ab7646 100644
--- a/src/Preferences.ui
+++ b/src/Preferences.ui
@@ -179,71 +179,29 @@
<number>0</number>
</property>
<item>
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>120</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_2">
- <property name="enabled">
- <bool>false</bool>
- </property>
+ <widget class="QCheckBox" name="OpenGL20WarningCheckbox">
<property name="text">
- <string>advanced</string>
+ <string>Show OpenGL 2.0 warning &amp;&amp; rendering info</string>
</property>
</widget>
</item>
<item>
- <spacer name="horizontalSpacer_3">
+ <spacer name="verticalSpacer_2">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>20</height>
+ <width>20</width>
+ <height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer_3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>120</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</widget>
</widget>
diff --git a/src/cgal.h b/src/cgal.h
index 6cbb251..f9161cc 100644
--- a/src/cgal.h
+++ b/src/cgal.h
@@ -10,6 +10,9 @@
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Nef_polyhedron_3.h>
#include <CGAL/IO/Polyhedron_iostream.h>
+#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
+#include <CGAL/Polygon_2.h>
+#include <CGAL/Polygon_with_holes_2.h>
typedef CGAL::Extended_cartesian<CGAL::Gmpq> CGAL_Kernel2;
typedef CGAL::Nef_polyhedron_2<CGAL_Kernel2> CGAL_Nef_polyhedron2;
@@ -24,6 +27,9 @@ typedef CGAL_Nef_polyhedron3::Aff_transformation_3 CGAL_Aff_transformation;
typedef CGAL_Nef_polyhedron3::Vector_3 CGAL_Vector;
typedef CGAL_Nef_polyhedron3::Plane_3 CGAL_Plane;
typedef CGAL_Nef_polyhedron3::Point_3 CGAL_Point;
+typedef CGAL::Exact_predicates_exact_constructions_kernel CGAL_ExactKernel2;
+typedef CGAL::Polygon_2<CGAL_ExactKernel2> CGAL_Poly2;
+typedef CGAL::Polygon_with_holes_2<CGAL_ExactKernel2> CGAL_Poly2h;
struct CGAL_Nef_polyhedron
{
diff --git a/src/cgaladv.cc b/src/cgaladv.cc
index bf2e85a..dd797fd 100644
--- a/src/cgaladv.cc
+++ b/src/cgaladv.cc
@@ -34,12 +34,14 @@
#ifdef ENABLE_CGAL
extern CGAL_Nef_polyhedron3 minkowski3(CGAL_Nef_polyhedron3 a, CGAL_Nef_polyhedron3 b);
extern CGAL_Nef_polyhedron2 minkowski2(CGAL_Nef_polyhedron2 a, CGAL_Nef_polyhedron2 b);
+extern CGAL_Nef_polyhedron2 convexhull2(std::list<CGAL_Nef_polyhedron2> a);
#endif
enum cgaladv_type_e {
MINKOWSKI,
GLIDE,
- SUBDIV
+ SUBDIV,
+ HULL
};
class CgaladvModule : public AbstractModule
@@ -125,6 +127,7 @@ void register_builtin_cgaladv()
builtin_modules["minkowski"] = new CgaladvModule(MINKOWSKI);
builtin_modules["glide"] = new CgaladvModule(GLIDE);
builtin_modules["subdiv"] = new CgaladvModule(SUBDIV);
+ builtin_modules["hull"] = new CgaladvModule(HULL);
}
#ifdef ENABLE_CGAL
@@ -174,6 +177,29 @@ CGAL_Nef_polyhedron CgaladvNode::render_cgal_nef_polyhedron() const
PRINT("WARNING: subdiv() is not implemented yet!");
}
+ if (type == HULL)
+ {
+ std::list<CGAL_Nef_polyhedron2> polys;
+ bool all2d = true;
+ foreach(AbstractNode * v, children) {
+ if (v->modinst->tag_background)
+ continue;
+ N = v->render_cgal_nef_polyhedron();
+ if (N.dim == 3) {
+ //polys.push_back(tmp.p3);
+ PRINT("WARNING: hull() is not implemented yet for 3D objects!");
+ all2d=false;
+ }
+ if (N.dim == 2) {
+ polys.push_back(N.p2);
+ }
+ v->progress_report();
+ }
+
+ if (all2d)
+ N.p2 = convexhull2(polys);
+ }
+
cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight());
print_messages_pop();
progress_report();
@@ -192,6 +218,9 @@ CSGTerm *CgaladvNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlight
if (type == SUBDIV)
return render_csg_term_from_nef(m, highlights, background, "subdiv", this->convexity);
+ if (type == HULL)
+ return render_csg_term_from_nef(m, highlights, background, "hull", this->convexity);
+
return NULL;
}
@@ -199,7 +228,7 @@ CSGTerm *CgaladvNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlight
CSGTerm *CgaladvNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const
{
- PRINT("WARNING: Found minkowski(), glide() or subdiv() statement but compiled without CGAL support!");
+ PRINT("WARNING: Found minkowski(), glide(), subdiv() or hull() statement but compiled without CGAL support!");
return NULL;
}
@@ -217,6 +246,8 @@ QString CgaladvNode::dump(QString indent) const
}
if (type == SUBDIV)
text.sprintf("subdiv(level = %d, convexity = %d) {\n", this->level, this->convexity);
+ if (type == HULL)
+ text.sprintf("hull() {\n");
foreach (AbstractNode *v, this->children)
text += v->dump(indent + QString("\t"));
text += indent + "}\n";
diff --git a/src/cgaladv_convexhull2.cc b/src/cgaladv_convexhull2.cc
new file mode 100644
index 0000000..448dd4b
--- /dev/null
+++ b/src/cgaladv_convexhull2.cc
@@ -0,0 +1,55 @@
+/*
+ * OpenSCAD (www.openscad.org)
+ * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
+ * Marius Kintel <marius@kintel.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * As a special exception, you have permission to link this program
+ * with the CGAL library and distribute executables, as long as you
+ * follow the requirements of the GNU GPL in regard to all of the
+ * software in the executable aside from CGAL.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifdef ENABLE_CGAL
+
+#include "cgal.h"
+#include <CGAL/convex_hull_2.h>
+
+extern CGAL_Nef_polyhedron2 convexhull2(std::list<CGAL_Nef_polyhedron2> a);
+extern CGAL_Poly2 nef2p2(CGAL_Nef_polyhedron2 p);
+
+CGAL_Nef_polyhedron2 convexhull2(std::list<CGAL_Nef_polyhedron2> a)
+{
+ std::list<CGAL_Nef_polyhedron2::Point> points;
+
+ std::list<CGAL_Nef_polyhedron2>::iterator i;
+ for (i=a.begin(); i!=a.end(); i++) {
+ CGAL_Poly2 ap=nef2p2(*i);
+ for (size_t j=0;j<ap.size();j++) {
+ double x=to_double(ap[j].x()),y=to_double(ap[j].y());
+ CGAL_Nef_polyhedron2::Point p=CGAL_Nef_polyhedron2::Point(x,y);
+ points.push_back(p);
+ }
+ }
+
+ std::list<CGAL_Nef_polyhedron2::Point> result;
+ CGAL::convex_hull_2(points.begin(),points.end(),std::back_inserter(result));
+
+ return CGAL_Nef_polyhedron2(result.begin(),result.end(),CGAL_Nef_polyhedron2::INCLUDED);
+}
+
+#endif
diff --git a/src/cgaladv_minkowski2.cc b/src/cgaladv_minkowski2.cc
index 6a4b31c..b722708 100644
--- a/src/cgaladv_minkowski2.cc
+++ b/src/cgaladv_minkowski2.cc
@@ -31,20 +31,53 @@
#include "grid.h"
#include "cgal.h"
-#if 0
-#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/minkowski_sum_2.h>
extern CGAL_Nef_polyhedron2 minkowski2(CGAL_Nef_polyhedron2 a, CGAL_Nef_polyhedron2 b);
+extern CGAL_Poly2 nef2p2(CGAL_Nef_polyhedron2 p);
-struct K2 : public CGAL::Exact_predicates_exact_constructions_kernel {};
-typedef CGAL::Polygon_2<K2> Poly2;
-typedef CGAL::Polygon_with_holes_2<K2> Poly2h;
+//-----------------------------------------------------------------------------
+// Pretty-print a CGAL polygon.
+//
+template<class Kernel, class Container>
+void print_polygon (const CGAL::Polygon_2<Kernel, Container>& P)
+{
+ typename CGAL::Polygon_2<Kernel, Container>::Vertex_const_iterator vit;
+
+ std::cout << "[ " << P.size() << " vertices:";
+ for (vit = P.vertices_begin(); vit != P.vertices_end(); ++vit)
+ std::cout << " (" << *vit << ')';
+ std::cout << " ]" << std::endl;
+}
-static Poly2 nef2p2(CGAL_Nef_polyhedron2 p)
+//-----------------------------------------------------------------------------
+// Pretty-print a polygon with holes.
+//
+template<class Kernel, class Container>
+void print_polygon_with_holes (const CGAL::Polygon_with_holes_2<Kernel, Container>& pwh) {
+ if (! pwh.is_unbounded()) {
+ std::cout << "{ Outer boundary = ";
+ print_polygon (pwh.outer_boundary());
+ } else
+ std::cout << "{ Unbounded polygon." << std::endl;
+
+ typename CGAL::Polygon_with_holes_2<Kernel,Container>::Hole_const_iterator hit;
+ unsigned int k = 1;
+
+ std::cout << " " << pwh.number_of_holes() << " holes:" << std::endl;
+ for (hit = pwh.holes_begin(); hit != pwh.holes_end(); ++hit, ++k) {
+ std::cout << " Hole #" << k << " = ";
+ print_polygon (*hit);
+ }
+ std::cout << " }" << std::endl;
+
+ return;
+}
+
+CGAL_Poly2 nef2p2(CGAL_Nef_polyhedron2 p)
{
- std::list<K2::Point_2> points;
+ std::list<CGAL_ExactKernel2::Point_2> points;
Grid2d<int> grid(GRID_COARSE);
typedef CGAL_Nef_polyhedron2::Explorer Explorer;
@@ -52,9 +85,13 @@ static Poly2 nef2p2(CGAL_Nef_polyhedron2 p)
typedef Explorer::Halfedge_around_face_const_circulator heafcc_t;
Explorer E = p.explorer();
- for (fci_t fit = E.faces_begin(), fend = E.faces_end(); fit != fend; ++fit)
+ for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit)
{
- if (fit != E.faces_begin()) {
+ if (!E.mark(fit)) {
+ continue;
+ }
+ //if (fit != E.faces_begin()) {
+ if (points.size() != 0) {
PRINT("WARNING: minkowski() is not implemented for 2d objects with holes!");
break;
}
@@ -65,33 +102,41 @@ static Poly2 nef2p2(CGAL_Nef_polyhedron2 p)
Explorer::Point ep = E.point(E.target(fcirc));
double x = to_double(ep.x()), y = to_double(ep.y());
grid.align(x, y);
- points.push_back(K2::Point_2(x, y));
+ points.push_back(CGAL_ExactKernel2::Point_2(x, y));
}
}
}
- return Poly2(points.begin(), points.end());
+ return CGAL_Poly2(points.begin(), points.end());
}
-
-CGAL_Nef_polyhedron2 minkowski2(CGAL_Nef_polyhedron2 a, CGAL_Nef_polyhedron2 b)
-{
- Poly2 ap = nef2p2(a), bp = nef2p2(b);
- Poly2h x = minkowski_sum_2(ap, bp);
- /** FIXME **/
-
- PRINT("WARNING: minkowski() is not implemented yet for 2d objects!");
- return CGAL_Nef_polyhedron2();
+static CGAL_Nef_polyhedron2 p2nef2(CGAL_Poly2 p2) {
+ std::list<CGAL_Nef_polyhedron2::Point> points;
+ for (size_t j = 0; j < p2.size(); j++) {
+ double x = to_double(p2[j].x());
+ double y = to_double(p2[j].y());
+ CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y);
+ points.push_back(p);
+ }
+ return CGAL_Nef_polyhedron2(points.begin(), points.end(), CGAL_Nef_polyhedron2::INCLUDED);
}
-#else
-
-CGAL_Nef_polyhedron2 minkowski2(CGAL_Nef_polyhedron2, CGAL_Nef_polyhedron2)
+CGAL_Nef_polyhedron2 minkowski2(CGAL_Nef_polyhedron2 a, CGAL_Nef_polyhedron2 b)
{
- PRINT("WARNING: minkowski() is not implemented yet for 2d objects!");
- return CGAL_Nef_polyhedron2();
+ CGAL_Poly2 ap = nef2p2(a), bp = nef2p2(b);
+
+ if (ap.size() == 0) {
+ PRINT("WARNING: minkowski() could not get any points from object 1!");
+ return CGAL_Nef_polyhedron2();
+ } else if (bp.size() == 0) {
+ PRINT("WARNING: minkowski() could not get any points from object 2!");
+ return CGAL_Nef_polyhedron2();
+ } else {
+ CGAL_Poly2h x = minkowski_sum_2(ap, bp);
+
+ // Make a CGAL_Nef_polyhedron2 out of just the boundary for starters
+ return p2nef2(x.outer_boundary());
+ }
}
#endif
-#endif
-
diff --git a/src/export.cc b/src/export.cc
index 884e139..8e0ab16 100644
--- a/src/export.cc
+++ b/src/export.cc
@@ -167,20 +167,6 @@ void export_dxf(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *
}
setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output
-
- // Some importers (e.g. QCAD) needs a HEADER section specifying AutoCAD 2000 as
- // the file format for LWPOLYLINE entities to work
- fprintf(f, " 0\n"
- "SECTION\n"
- " 2\n"
- "HEADER\n"
- " 9\n"
- "$ACADVER\n"
- " 1\n"
- "AC1015\n"
- " 0\n"
- "ENDSEC\n");
-
// Some importers (e.g. Inkscape) needs a BLOCKS section to be present
fprintf(f, " 0\n"
"SECTION\n"
@@ -197,29 +183,26 @@ void export_dxf(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *
DxfData dd(*root_N);
for (int i=0; i<dd.paths.size(); i++)
{
- if (dd.paths[i].points.size() < 2)
- // not a valid polygon
- continue;
- // Use the LWPOLYLINE class - this makes it easier to handle complete
- // objects (as paths) in Inkscape.
- fprintf(f, " 0\n");
- fprintf(f, "LWPOLYLINE\n");
- // Some importers (e.g. Inkscape) need a layer to be specified
- fprintf(f, " 8\n");
- fprintf(f, "0\n");
- // number of vertices
- fprintf(f, " 90\n");
- fprintf(f, "%d\n", dd.paths[i].points.size());
- // polygon flag (closed, ...)
- fprintf(f, " 70\n");
- fprintf(f, "%d\n", dd.paths[i].is_closed ? 1 : 0);
- // add all points
- for (int j=0; j<dd.paths[i].points.size(); j++) {
- DxfData::Point *p = dd.paths[i].points[j];
+ for (int j=1; j<dd.paths[i].points.size(); j++) {
+ DxfData::Point *p1 = dd.paths[i].points[j-1];
+ DxfData::Point *p2 = dd.paths[i].points[j];
+ double x1 = p1->x;
+ double y1 = p1->y;
+ double x2 = p2->x;
+ double y2 = p2->y;
+ fprintf(f, " 0\n");
+ fprintf(f, "LINE\n");
+ // Some importers (e.g. Inkscape) needs a layer to be specified
+ fprintf(f, " 8\n");
+ fprintf(f, "0\n");
fprintf(f, " 10\n");
- fprintf(f, "%f\n", p->x);
+ fprintf(f, "%f\n", x1);
+ fprintf(f, " 11\n");
+ fprintf(f, "%f\n", x2);
fprintf(f, " 20\n");
- fprintf(f, "%f\n", p->y);
+ fprintf(f, "%f\n", y1);
+ fprintf(f, " 21\n");
+ fprintf(f, "%f\n", y2);
}
}
diff --git a/src/glview.cc b/src/glview.cc
index 870a1c9..ef023f2 100644
--- a/src/glview.cc
+++ b/src/glview.cc
@@ -29,9 +29,15 @@
#include <QApplication>
#include <QWheelEvent>
+#include <QCheckBox>
+#include <QDialogButtonBox>
#include <QMouseEvent>
#include <QMessageBox>
+#include <QPushButton>
+#include <QSettings>
#include <QTimer>
+#include <QTextEdit>
+#include <QVBoxLayout>
#include "mathc99.h"
#include <stdio.h>
@@ -180,7 +186,10 @@ void GLView::initializeGL()
}
} else {
opencsg_support = false;
- QTimer::singleShot(0, this, SLOT(display_opengl20_warning()));
+ QSettings settings;
+ if (settings.value("editor/opengl20_warning_show",true).toBool()) {
+ QTimer::singleShot(0, this, SLOT(display_opengl20_warning()));
+ }
}
#endif /* ENABLE_OPENCSG */
}
@@ -188,6 +197,9 @@ void GLView::initializeGL()
#ifdef ENABLE_OPENCSG
void GLView::display_opengl20_warning()
{
+ // data
+ QString title = QString("GLEW: GL_VERSION_2_0 is not supported!");
+
QString rendererinfo;
rendererinfo.sprintf("GLEW version %s\n"
"%s (%s)\n"
@@ -196,11 +208,43 @@ void GLView::display_opengl20_warning()
glGetString(GL_RENDERER), glGetString(GL_VENDOR),
glGetString(GL_VERSION));
- QMessageBox::warning(NULL, "GLEW: GL_VERSION_2_0 is not supported!",
- QString("Warning: No support for OpenGL 2.0 found! OpenCSG View has been disabled.\n\n"
+ QString message = QString("Warning: No support for OpenGL 2.0 found! OpenCSG View has been disabled.\n\n"
"It is highly recommended to use OpenSCAD on a system with OpenGL 2.0 "
"support. Please check if OpenGL 2.0 drivers are available for your "
- "graphics hardware.\n\n%1").arg(rendererinfo));
+ "graphics hardware. Your renderer information is as follows:\n\n%1").arg(rendererinfo);
+
+ QString note = QString("Uncheck to hide this message in the future");
+
+ // presentation
+ QDialog *dialog = new QDialog(this);
+ dialog->setSizeGripEnabled(true);
+ dialog->setWindowTitle(title);
+ dialog->resize(500,300);
+
+ QVBoxLayout *layout = new QVBoxLayout(dialog);
+ dialog->setLayout(layout);
+
+ QTextEdit *textEdit = new QTextEdit(dialog);
+ textEdit->setPlainText(message);
+ layout->addWidget(textEdit);
+
+ QCheckBox *checkbox = new QCheckBox(note,dialog);
+ checkbox->setCheckState(Qt::Checked);
+ layout->addWidget(checkbox);
+
+ QDialogButtonBox *buttonbox =
+ new QDialogButtonBox( QDialogButtonBox::Ok, Qt::Horizontal,dialog);
+ layout->addWidget(buttonbox);
+ buttonbox->button(QDialogButtonBox::Ok)->setFocus();
+ buttonbox->button(QDialogButtonBox::Ok)->setDefault(true);
+
+ // action
+ connect(buttonbox, SIGNAL(accepted()), dialog, SLOT(accept()));
+ connect(checkbox, SIGNAL(clicked(bool)),
+ Preferences::inst()->OpenGL20WarningCheckbox, SLOT(setChecked(bool)));
+ connect(checkbox, SIGNAL(clicked(bool)),
+ Preferences::inst(), SLOT(OpenGL20WarningChanged(bool)));
+ dialog->exec();
}
#endif
diff --git a/src/mainwin.cc b/src/mainwin.cc
index d5da7aa..2255ac3 100644
--- a/src/mainwin.cc
+++ b/src/mainwin.cc
@@ -96,6 +96,37 @@ static char copyrighttext[] =
"the Free Software Foundation; either version 2 of the License, or"
"(at your option) any later version.";
+static void
+settings_setValueList(const QString &key,const QList<int> &list)
+{
+ QSettings settings;
+ settings.beginWriteArray(key);
+ for (int i=0;i<list.size(); ++i) {
+ settings.setArrayIndex(i);
+ settings.setValue("entry",list[i]);
+ }
+ settings.endArray();
+}
+
+QList<int>
+settings_valueList(const QString &key, const QList<int> &defaultList = QList<int>())
+{
+ QSettings settings;
+ QList<int> result;
+ if (settings.contains(key+"/size")){
+ int length = settings.beginReadArray(key);
+ for (int i = 0; i < length; ++i) {
+ settings.setArrayIndex(i);
+ result += settings.value("entry").toInt();
+ }
+ settings.endArray();
+ return result;
+ } else {
+ return defaultList;
+ }
+
+}
+
MainWindow::MainWindow(const QString &filename)
{
setupUi(this);
@@ -237,6 +268,7 @@ MainWindow::MainWindow(const QString &filename)
connect(this->designActionExportSTL, SIGNAL(triggered()), this, SLOT(actionExportSTL()));
connect(this->designActionExportOFF, SIGNAL(triggered()), this, SLOT(actionExportOFF()));
connect(this->designActionExportDXF, SIGNAL(triggered()), this, SLOT(actionExportDXF()));
+ connect(this->designActionExportImage, SIGNAL(triggered()), this, SLOT(actionExportImage()));
connect(this->designActionFlushCaches, SIGNAL(triggered()), this, SLOT(actionFlushCaches()));
// View menu
@@ -312,27 +344,69 @@ MainWindow::MainWindow(const QString &filename)
this, SLOT(setFont(const QString&,uint)));
Preferences::inst()->apply();
+ // make sure it looks nice..
+ QSettings settings;
+ resize(settings.value("window/size", QSize(800, 600)).toSize());
+ move(settings.value("window/position", QPoint(0, 0)).toPoint());
+ QList<int> s1sizes = settings_valueList("window/splitter1sizes",QList<int>()<<400<<400);
+ QList<int> s2sizes = settings_valueList("window/splitter2sizes",QList<int>()<<400<<200);
+ splitter1->setSizes(s1sizes);
+ splitter2->setSizes(s2sizes);
// display this window and check for OpenGL 2.0 (OpenCSG) support
viewModeThrownTogether();
show();
- // make sure it looks nice..
- resize(800, 600);
- splitter1->setSizes(QList<int>() << 400 << 400);
- splitter2->setSizes(QList<int>() << 400 << 200);
-
#ifdef ENABLE_OPENCSG
viewModeOpenCSG();
#else
viewModeThrownTogether();
#endif
- viewPerspective();
+ loadViewSettings();
+ loadDesignSettings();
setAcceptDrops(true);
clearCurrentOutput();
}
+void
+MainWindow::loadViewSettings(){
+ QSettings settings;
+ if (settings.value("view/showEdges").toBool()) {
+ viewActionShowEdges->setChecked(true);
+ viewModeShowEdges();
+ }
+ if (settings.value("view/showAxes").toBool()) {
+ viewActionShowAxes->setChecked(true);
+ viewModeShowAxes();
+ }
+ if (settings.value("view/showCrosshairs").toBool()) {
+ viewActionShowCrosshairs->setChecked(true);
+ viewModeShowCrosshairs();
+ }
+ if (settings.value("view/orthogonalProjection").toBool()) {
+ viewOrthogonal();
+ } else {
+ viewPerspective();
+ }
+ if (settings.value("view/hideConsole").toBool()) {
+ viewActionHide->setChecked(true);
+ hideConsole();
+ }
+ if (settings.value("view/hideEditor").toBool()) {
+ editActionHide->setChecked(true);
+ hideEditor();
+ }
+}
+
+void
+MainWindow::loadDesignSettings()
+{
+ QSettings settings;
+ if (settings.value("design/autoReload").toBool())
+ designActionAutoReload->setChecked(true);
+}
+
MainWindow::~MainWindow()
{
if (root_module)
@@ -929,10 +1003,13 @@ void MainWindow::actionReload()
void MainWindow::hideEditor()
{
+ QSettings settings;
if (editActionHide->isChecked()) {
editor->hide();
+ settings.setValue("view/hideEditor",true);
} else {
editor->show();
+ settings.setValue("view/hideEditor",false);
}
}
@@ -973,6 +1050,8 @@ void MainWindow::checkAutoReload()
void MainWindow::autoReloadSet(bool on)
{
+ QSettings settings;
+ settings.setValue("design/autoReload",designActionAutoReload->isChecked());
if (on) {
autoReloadInfo = QString();
autoReloadTimer->start(200);
@@ -1320,6 +1399,24 @@ void MainWindow::actionExportDXF()
#endif /* ENABLE_CGAL */
}
+void MainWindow::actionExportImage()
+{
+ QImage img = screen->grabFrameBuffer();
+ setCurrentOutput();
+
+ QString img_filename = QFileDialog::getSaveFileName(this,
+ "Export Image", "", "PNG Files (*.png)");
+ if (img_filename.isEmpty()) {
+ PRINTF("No filename specified. Image export aborted.");
+ clearCurrentOutput();
+ return;
+ }
+
+ img.save(img_filename, "PNG");
+
+ clearCurrentOutput();
+}
+
void MainWindow::actionFlushCaches()
{
PolySet::ps_cache.clear();
@@ -1603,17 +1700,23 @@ void MainWindow::viewModeThrownTogether()
void MainWindow::viewModeShowEdges()
{
+ QSettings settings;
+ settings.setValue("view/showEdges",viewActionShowEdges->isChecked());
screen->updateGL();
}
void MainWindow::viewModeShowAxes()
{
+ QSettings settings;
+ settings.setValue("view/showAxes",viewActionShowAxes->isChecked());
screen->setShowAxes(viewActionShowAxes->isChecked());
screen->updateGL();
}
void MainWindow::viewModeShowCrosshairs()
{
+ QSettings settings;
+ settings.setValue("view/showCrosshairs",viewActionShowCrosshairs->isChecked());
screen->setShowCrosshairs(viewActionShowCrosshairs->isChecked());
screen->updateGL();
}
@@ -1717,6 +1820,8 @@ void MainWindow::viewCenter()
void MainWindow::viewPerspective()
{
+ QSettings settings;
+ settings.setValue("view/orthogonalProjection",false);
viewActionPerspective->setChecked(true);
viewActionOrthogonal->setChecked(false);
screen->setOrthoMode(false);
@@ -1725,6 +1830,8 @@ void MainWindow::viewPerspective()
void MainWindow::viewOrthogonal()
{
+ QSettings settings;
+ settings.setValue("view/orthogonalProjection",true);
viewActionPerspective->setChecked(false);
viewActionOrthogonal->setChecked(true);
screen->setOrthoMode(true);
@@ -1733,10 +1840,13 @@ void MainWindow::viewOrthogonal()
void MainWindow::hideConsole()
{
+ QSettings settings;
if (viewActionHide->isChecked()) {
console->hide();
+ settings.setValue("view/hideConsole",true);
} else {
console->show();
+ settings.setValue("view/hideConsole",false);
}
}
@@ -1803,6 +1913,11 @@ MainWindow::maybeSave()
void MainWindow::closeEvent(QCloseEvent *event)
{
if (maybeSave()) {
+ QSettings settings;
+ settings.setValue("window/size", size());
+ settings.setValue("window/position", pos());
+ settings_setValueList("window/splitter1sizes",splitter1->sizes());
+ settings_setValueList("window/splitter2sizes",splitter2->sizes());
event->accept();
} else {
event->ignore();
diff --git a/src/openscad.cc b/src/openscad.cc
index bc1d845..bf22246 100644
--- a/src/openscad.cc
+++ b/src/openscad.cc
@@ -140,19 +140,25 @@ int main(int argc, char **argv)
desc.add_options()
("help,h", "help message")
("version,v", "print the version")
- ("s", po::value<string>(), "stl-file")
- ("o", po::value<string>(), "off-file")
- ("x", po::value<string>(), "dxf-file")
- ("d", po::value<string>(), "deps-file")
- ("m", po::value<string>(), "make file")
- ("D", po::value<vector<string> >(), "var=val")
- ;
+ ("s,s", po::value<string>(), "stl-file")
+ ("o,o", po::value<string>(), "off-file")
+ ("x,x", po::value<string>(), "dxf-file")
+ ("d,d", po::value<string>(), "deps-file")
+ ("m,m", po::value<string>(), "makefile")
+ ("D,D", po::value<vector<string> >(), "var=val");
+
+ po::options_description hidden("Hidden options");
+ hidden.add_options()
+ ("input-file", po::value< vector<string> >(), "input file");
po::positional_options_description p;
p.add("input-file", -1);
+ po::options_description all_options;
+ all_options.add(desc).add(hidden);
+
po::variables_map vm;
- po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
+ po::store(po::command_line_parser(argc, argv).options(all_options).positional(p).run(), vm);
// po::notify(vm);
if (vm.count("help")) help(argv[0]);
@@ -348,7 +354,7 @@ int main(int argc, char **argv)
new MainWindow(qfilename);
vector<string> inputFiles;
if (vm.count("input-file")) {
- inputFiles = vm["input-files"].as<vector<string> >();
+ inputFiles = vm["input-file"].as<vector<string> >();
for (vector<string>::const_iterator i = inputFiles.begin()+1; i != inputFiles.end(); i++) {
new MainWindow(QFileInfo(original_path, i->c_str()).absoluteFilePath());
}
diff --git a/src/primitives.cc b/src/primitives.cc
index 5ba32fe..5180c16 100644
--- a/src/primitives.cc
+++ b/src/primitives.cc
@@ -34,6 +34,8 @@
#include "printutils.h"
#include <assert.h>
+#define F_MINIMUM 0.01
+
enum primitive_type_e {
CUBE,
SPHERE,
@@ -57,7 +59,6 @@ class PrimitiveNode : public AbstractPolyNode
public:
bool center;
double x, y, z, h, r1, r2;
- static const double F_MINIMUM = 0.01;
double fn, fs, fa;
primitive_type_e type;
int convexity;
@@ -106,13 +107,13 @@ AbstractNode *PrimitiveModule::evaluate(const Context *ctx, const ModuleInstanti
node->fs = c.lookup_variable("$fs").num;
node->fa = c.lookup_variable("$fa").num;
- if (node->fs < PrimitiveNode::F_MINIMUM) {
- PRINTF("WARNING: $fs too small - clamping to %f", PrimitiveNode::F_MINIMUM);
- node->fs = PrimitiveNode::F_MINIMUM;
+ if (node->fs < F_MINIMUM) {
+ PRINTF("WARNING: $fs too small - clamping to %f", F_MINIMUM);
+ node->fs = F_MINIMUM;
}
- if (node->fa < PrimitiveNode::F_MINIMUM) {
- PRINTF("WARNING: $fa too small - clamping to %f", PrimitiveNode::F_MINIMUM);
- node->fa = PrimitiveNode::F_MINIMUM;
+ if (node->fa < F_MINIMUM) {
+ PRINTF("WARNING: $fa too small - clamping to %f", F_MINIMUM);
+ node->fa = F_MINIMUM;
}
@@ -219,6 +220,19 @@ int get_fragments_from_r(double r, double fn, double fs, double fa)
return (int)ceil(fmax(fmin(360.0 / fa, r*M_PI / fs), 5));
}
+struct point2d {
+ double x, y;
+};
+
+static void generate_circle(point2d *circle, double r, int fragments)
+{
+ for (int i=0; i<fragments; i++) {
+ double phi = (M_PI*2*i) / fragments;
+ circle[i].x = r*cos(phi);
+ circle[i].y = r*sin(phi);
+ }
+}
+
PolySet *PrimitiveNode::render_polyset(render_mode_e) const
{
PolySet *p = new PolySet();
@@ -279,70 +293,66 @@ PolySet *PrimitiveNode::render_polyset(render_mode_e) const
if (type == SPHERE && r1 > 0)
{
- struct point2d {
- double x, y;
- };
-
struct ring_s {
- int fragments;
point2d *points;
- double r, z;
+ double z;
};
- int rings = get_fragments_from_r(r1, fn, fs, fa);
+ int fragments = get_fragments_from_r(r1, fn, fs, fa);
+ int rings = fragments/2;
+// Uncomment the following three lines to enable experimental sphere tesselation
+// if (rings % 2 == 0) rings++; // To ensure that the middle ring is at phi == 0 degrees
+
ring_s *ring = new ring_s[rings];
+// double offset = 0.5 * ((fragments / 2) % 2);
for (int i = 0; i < rings; i++) {
+// double phi = (M_PI * (i + offset)) / (fragments/2);
double phi = (M_PI * (i + 0.5)) / rings;
- ring[i].r = r1 * sin(phi);
+ double r = r1 * sin(phi);
ring[i].z = r1 * cos(phi);
- ring[i].fragments = get_fragments_from_r(ring[i].r, fn, fs, fa);
- ring[i].points = new point2d[ring[i].fragments];
- for (int j = 0; j < ring[i].fragments; j++) {
- phi = (M_PI*2*j) / ring[i].fragments;
- ring[i].points[j].x = ring[i].r * cos(phi);
- ring[i].points[j].y = ring[i].r * sin(phi);
- }
+ ring[i].points = new point2d[fragments];
+ generate_circle(ring[i].points, r, fragments);
}
p->append_poly();
- for (int i = 0; i < ring[0].fragments; i++)
+ for (int i = 0; i < fragments; i++)
p->append_vertex(ring[0].points[i].x, ring[0].points[i].y, ring[0].z);
for (int i = 0; i < rings-1; i++) {
ring_s *r1 = &ring[i];
ring_s *r2 = &ring[i+1];
int r1i = 0, r2i = 0;
- while (r1i < r1->fragments || r2i < r2->fragments)
+ while (r1i < fragments || r2i < fragments)
{
- if (r1i >= r1->fragments)
+ if (r1i >= fragments)
goto sphere_next_r2;
- if (r2i >= r2->fragments)
+ if (r2i >= fragments)
goto sphere_next_r1;
- if ((double)r1i / r1->fragments <
- (double)r2i / r2->fragments)
+ if ((double)r1i / fragments <
+ (double)r2i / fragments)
{
sphere_next_r1:
p->append_poly();
- int r1j = (r1i+1) % r1->fragments;
+ int r1j = (r1i+1) % fragments;
p->insert_vertex(r1->points[r1i].x, r1->points[r1i].y, r1->z);
p->insert_vertex(r1->points[r1j].x, r1->points[r1j].y, r1->z);
- p->insert_vertex(r2->points[r2i % r2->fragments].x, r2->points[r2i % r2->fragments].y, r2->z);
+ p->insert_vertex(r2->points[r2i % fragments].x, r2->points[r2i % fragments].y, r2->z);
r1i++;
} else {
sphere_next_r2:
p->append_poly();
- int r2j = (r2i+1) % r2->fragments;
+ int r2j = (r2i+1) % fragments;
p->append_vertex(r2->points[r2i].x, r2->points[r2i].y, r2->z);
p->append_vertex(r2->points[r2j].x, r2->points[r2j].y, r2->z);
- p->append_vertex(r1->points[r1i % r1->fragments].x, r1->points[r1i % r1->fragments].y, r1->z);
+ p->append_vertex(r1->points[r1i % fragments].x, r1->points[r1i % fragments].y, r1->z);
r2i++;
}
}
}
p->append_poly();
- for (int i = 0; i < ring[rings-1].fragments; i++)
+ for (int i = 0; i < fragments; i++)
p->insert_vertex(ring[rings-1].points[i].x, ring[rings-1].points[i].y, ring[rings-1].z);
delete[] ring;
@@ -361,44 +371,33 @@ sphere_next_r2:
z2 = h;
}
- struct point2d {
- double x, y;
- };
-
point2d *circle1 = new point2d[fragments];
point2d *circle2 = new point2d[fragments];
- for (int i=0; i<fragments; i++) {
- double phi = (M_PI*2*i) / fragments;
- if (r1 > 0) {
- circle1[i].x = r1*cos(phi);
- circle1[i].y = r1*sin(phi);
- } else {
- circle1[i].x = 0;
- circle1[i].y = 0;
- }
- if (r2 > 0) {
- circle2[i].x = r2*cos(phi);
- circle2[i].y = r2*sin(phi);
- } else {
- circle2[i].x = 0;
- circle2[i].y = 0;
- }
- }
+ generate_circle(circle1, r1, fragments);
+ generate_circle(circle2, r2, fragments);
for (int i=0; i<fragments; i++) {
int j = (i+1) % fragments;
- if (r1 > 0) {
+ if (r1 == r2) {
p->append_poly();
p->insert_vertex(circle1[i].x, circle1[i].y, z1);
p->insert_vertex(circle2[i].x, circle2[i].y, z2);
- p->insert_vertex(circle1[j].x, circle1[j].y, z1);
- }
- if (r2 > 0) {
- p->append_poly();
- p->insert_vertex(circle2[i].x, circle2[i].y, z2);
p->insert_vertex(circle2[j].x, circle2[j].y, z2);
p->insert_vertex(circle1[j].x, circle1[j].y, z1);
+ } else {
+ if (r1 > 0) {
+ p->append_poly();
+ p->insert_vertex(circle1[i].x, circle1[i].y, z1);
+ p->insert_vertex(circle2[i].x, circle2[i].y, z2);
+ p->insert_vertex(circle1[j].x, circle1[j].y, z1);
+ }
+ if (r2 > 0) {
+ p->append_poly();
+ p->insert_vertex(circle2[i].x, circle2[i].y, z2);
+ p->insert_vertex(circle2[j].x, circle2[j].y, z2);
+ p->insert_vertex(circle1[j].x, circle1[j].y, z1);
+ }
}
}
diff --git a/src/transform.cc b/src/transform.cc
index e841ef0..7b15a7e 100644
--- a/src/transform.cc
+++ b/src/transform.cc
@@ -91,7 +91,7 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti
argnames = QVector<QString>() << "m";
}
if (type == COLOR) {
- argnames = QVector<QString>() << "c";
+ argnames = QVector<QString>() << "c" << "alpha";
}
Context c(ctx);
@@ -227,6 +227,24 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti
if (v.type == Value::VECTOR) {
for (int i = 0; i < 4; i++)
node->m[16+i] = i < v.vec.size() ? v.vec[i]->num : 1.0;
+ } else if (v.type == Value::STRING) {
+ QString colorname = v.text;
+ QColor color;
+ color.setNamedColor(colorname);
+ if (color.isValid()) {
+ node->m[16+0] = color.redF();
+ node->m[16+1] = color.greenF();
+ node->m[16+2] = color.blueF();
+ } else {
+ PRINTF_NOCACHE("WARNING: Color name \"%s\" unknown. Please see",v.text.toUtf8().data());
+ PRINTF_NOCACHE("WARNING: http://en.wikipedia.org/wiki/Web_colors");
+ }
+ }
+ Value alpha = c.lookup_variable("alpha");
+ if (alpha.type == Value::NUMBER) {
+ node->m[16+3] = alpha.num;
+ } else {
+ node->m[16+3] = 1.0;
}
}
diff --git a/testdata/scad/convex_hull.scad b/testdata/scad/convex_hull.scad
new file mode 100644
index 0000000..3114ac5
--- /dev/null
+++ b/testdata/scad/convex_hull.scad
@@ -0,0 +1,43 @@
+// Works correctly
+module convex2dSimple() {
+ hull() {
+ translate([15,10]) circle(10);
+ circle(10);
+ }
+}
+
+// Works correctly
+module convex2dHole() {
+ hull() {
+ translate([15,10,0]) circle(10);
+ difference() {
+ circle(10);
+ circle(5);
+ }
+ }
+}
+
+// 3d not currently implemented
+module convex3dSimple() {
+ hull() {
+ translate([15,10]) cylinder(r=10);
+ cylinder(r=10);
+ }
+}
+
+// 3d not currently implemented
+module convex3dHole() {
+ hull() {
+ translate([15,10,0]) cylinder(10);
+ difference() {
+ cylinder(10);
+ cylinder(5);
+ }
+ }
+}
+
+
+convex2dHole();
+translate([40,0,0]) convex2dSimple();
+translate([0,40,0]) convex3dHole();
+translate([40,40,0]) convex3dSimple();
diff --git a/testdata/scad/minkowski.scad b/testdata/scad/minkowski.scad
index 26cd972..6d0dade 100644
--- a/testdata/scad/minkowski.scad
+++ b/testdata/scad/minkowski.scad
@@ -1 +1,67 @@
-minkowski();
+
+// Rounded box using 3d minkowski
+module roundedBox3dSimple() {
+ minkowski() {
+ cube([10,10,5]);
+ cylinder(r=5, h=5);
+ }
+}
+
+// Currently segfaults
+module roundedBox3dCut() {
+ minkowski() {
+ difference() {
+ cube([10,10,5]);
+ cube([5,5,5]);
+ }
+ cylinder(r=5, h=5);
+ }
+}
+
+// Currently segfaults
+module roundedBox3dHole() {
+ minkowski() {
+ difference() {
+ cube([10,10,5]);
+ translate([2,2,-2]) cube([6,6,10]);
+ }
+ cylinder(r=2);
+ }
+}
+
+// Works correctly
+module roundedBox2dSimple() {
+ minkowski() {
+ square([10,10]);
+ circle(r=5);
+ }
+}
+
+// Works correctly
+module roundedBox2dCut() {
+ minkowski() {
+ difference() {
+ square([10,10]);
+ square([5,5]);
+ }
+ circle(r=5);
+ }
+}
+
+// Not quite correct, result does not contain a hole, since the impl currently returns the outer boundary of the polygon_with_holes.
+module roundedBox2dHole() {
+ minkowski() {
+ difference() {
+ square([10,10]);
+ translate([2,2]) square([6,6]);
+ }
+ circle(r=2);
+ }
+}
+
+translate([-25,0,0]) roundedBox2dHole();
+translate([0,0,0]) roundedBox2dCut();
+translate([25,0,0]) roundedBox2dSimple();
+translate([-25,25,0]) roundedBox3dHole();
+translate([0,25,0]) roundedBox3dCut();
+translate([25,25,0]) roundedBox3dSimple();
diff --git a/testdata/scad/non-aff-matrix.scad b/testdata/scad/non-aff-matrix.scad
new file mode 100644
index 0000000..7dbced1
--- /dev/null
+++ b/testdata/scad/non-aff-matrix.scad
@@ -0,0 +1,6 @@
+multmatrix(m = [[1, 0, 0, 0],
+ [0, 1, 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, -0.02, 1]])
+ linear_extrude(height=20) circle(r=10);
+
diff --git a/testdata/scad/testcolornames.scad b/testdata/scad/testcolornames.scad
new file mode 100644
index 0000000..b9ad334
--- /dev/null
+++ b/testdata/scad/testcolornames.scad
@@ -0,0 +1,159 @@
+/* color samples for SVG named colors, for OpenSCAD
+Please see http://en.wikipedia.org/wiki/Web_colors
+and http://www.w3.org/TR/SVG/types.html#ColorKeywords
+for more information. */
+
+$fn=5;
+radius=0.8;
+//translate([0,0]) color("Red colors") sphere(radius);
+translate([1,0]) color("IndianRed") sphere(radius);
+translate([2,0]) color("LightCoral") sphere(radius);
+translate([3,0]) color("Salmon") sphere(radius);
+translate([4,0]) color("DarkSalmon") sphere(radius);
+translate([5,0]) color("LightSalmon") sphere(radius);
+translate([6,0]) color("Red") sphere(radius);
+translate([7,0]) color("Crimson") sphere(radius);
+translate([8,0]) color("FireBrick") sphere(radius);
+translate([9,0]) color("DarkRed") sphere(radius);
+//translate([10,0]) color("Pink colors") sphere(radius);
+translate([0,1]) color("Pink") sphere(radius);
+translate([1,1]) color("LightPink") sphere(radius);
+translate([2,1]) color("HotPink") sphere(radius);
+translate([3,1]) color("DeepPink") sphere(radius);
+translate([4,1]) color("MediumVioletRed") sphere(radius);
+translate([5,1]) color("PaleVioletRed") sphere(radius);
+//translate([6,1]) color("Orange colors") sphere(radius);
+translate([7,1]) color("LightSalmon") sphere(radius);
+translate([8,1]) color("Coral") sphere(radius);
+translate([9,1]) color("Tomato") sphere(radius);
+translate([10,1]) color("OrangeRed") sphere(radius);
+translate([0,2]) color("DarkOrange") sphere(radius);
+translate([1,2]) color("Orange") sphere(radius);
+//translate([2,2]) color("Yellow colors") sphere(radius);
+translate([3,2]) color("Gold") sphere(radius);
+translate([4,2]) color("Yellow") sphere(radius);
+translate([5,2]) color("LightYellow") sphere(radius);
+translate([6,2]) color("LemonChiffon") sphere(radius);
+translate([7,2]) color("LightGoldenrodYellow") sphere(radius);
+translate([8,2]) color("PapayaWhip") sphere(radius);
+translate([9,2]) color("Moccasin") sphere(radius);
+translate([10,2]) color("PeachPuff") sphere(radius);
+translate([0,3]) color("PaleGoldenrod") sphere(radius);
+translate([1,3]) color("Khaki") sphere(radius);
+translate([2,3]) color("DarkKhaki") sphere(radius);
+//translate([3,3]) color("Purple colors") sphere(radius);
+translate([4,3]) color("Lavender") sphere(radius);
+translate([5,3]) color("Thistle") sphere(radius);
+translate([6,3]) color("Plum") sphere(radius);
+translate([7,3]) color("Violet") sphere(radius);
+translate([8,3]) color("Orchid") sphere(radius);
+translate([9,3]) color("Fuchsia") sphere(radius);
+translate([10,3]) color("Magenta") sphere(radius);
+translate([0,4]) color("MediumOrchid") sphere(radius);
+translate([1,4]) color("MediumPurple") sphere(radius);
+translate([2,4]) color("BlueViolet") sphere(radius);
+translate([3,4]) color("DarkViolet") sphere(radius);
+translate([4,4]) color("DarkOrchid") sphere(radius);
+translate([5,4]) color("DarkMagenta") sphere(radius);
+translate([6,4]) color("Purple") sphere(radius);
+translate([7,4]) color("Indigo") sphere(radius);
+translate([8,4]) color("DarkSlateBlue") sphere(radius);
+translate([9,4]) color("SlateBlue") sphere(radius);
+translate([10,4]) color("MediumSlateBlue") sphere(radius);
+//translate([0,5]) color("Green colors") sphere(radius);
+translate([1,5]) color("GreenYellow") sphere(radius);
+translate([2,5]) color("Chartreuse") sphere(radius);
+translate([3,5]) color("LawnGreen") sphere(radius);
+translate([4,5]) color("Lime") sphere(radius);
+translate([5,5]) color("LimeGreen") sphere(radius);
+translate([6,5]) color("PaleGreen") sphere(radius);
+translate([7,5]) color("LightGreen") sphere(radius);
+translate([8,5]) color("MediumSpringGreen") sphere(radius);
+translate([9,5]) color("SpringGreen") sphere(radius);
+translate([10,5]) color("MediumSeaGreen") sphere(radius);
+translate([0,6]) color("SeaGreen") sphere(radius);
+translate([1,6]) color("ForestGreen") sphere(radius);
+translate([2,6]) color("Green") sphere(radius);
+translate([3,6]) color("DarkGreen") sphere(radius);
+translate([4,6]) color("YellowGreen") sphere(radius);
+translate([5,6]) color("OliveDrab") sphere(radius);
+translate([6,6]) color("Olive") sphere(radius);
+translate([7,6]) color("DarkOliveGreen") sphere(radius);
+translate([8,6]) color("MediumAquamarine") sphere(radius);
+translate([9,6]) color("DarkSeaGreen") sphere(radius);
+translate([10,6]) color("LightSeaGreen") sphere(radius);
+translate([0,7]) color("DarkCyan") sphere(radius);
+translate([1,7]) color("Teal") sphere(radius);
+//translate([2,7]) color("Blue/Cyan colors") sphere(radius);
+translate([3,7]) color("Aqua") sphere(radius);
+translate([4,7]) color("Cyan") sphere(radius);
+translate([5,7]) color("LightCyan") sphere(radius);
+translate([6,7]) color("PaleTurquoise") sphere(radius);
+translate([7,7]) color("Aquamarine") sphere(radius);
+translate([8,7]) color("Turquoise") sphere(radius);
+translate([9,7]) color("MediumTurquoise") sphere(radius);
+translate([10,7]) color("DarkTurquoise") sphere(radius);
+translate([0,8]) color("CadetBlue") sphere(radius);
+translate([1,8]) color("SteelBlue") sphere(radius);
+translate([2,8]) color("LightSteelBlue") sphere(radius);
+translate([3,8]) color("PowderBlue") sphere(radius);
+translate([4,8]) color("LightBlue") sphere(radius);
+translate([5,8]) color("SkyBlue") sphere(radius);
+translate([6,8]) color("LightSkyBlue") sphere(radius);
+translate([7,8]) color("DeepSkyBlue") sphere(radius);
+translate([8,8]) color("DodgerBlue") sphere(radius);
+translate([9,8]) color("CornflowerBlue") sphere(radius);
+translate([10,8]) color("RoyalBlue") sphere(radius);
+translate([0,9]) color("Blue") sphere(radius);
+translate([1,9]) color("MediumBlue") sphere(radius);
+translate([2,9]) color("DarkBlue") sphere(radius);
+translate([3,9]) color("Navy") sphere(radius);
+translate([4,9]) color("MidnightBlue") sphere(radius);
+//translate([5,9]) color("Brown colors") sphere(radius);
+translate([6,9]) color("Cornsilk") sphere(radius);
+translate([7,9]) color("BlanchedAlmond") sphere(radius);
+translate([8,9]) color("Bisque") sphere(radius);
+translate([9,9]) color("NavajoWhite") sphere(radius);
+translate([10,9]) color("Wheat") sphere(radius);
+translate([0,10]) color("BurlyWood") sphere(radius);
+translate([1,10]) color("Tan") sphere(radius);
+translate([2,10]) color("RosyBrown") sphere(radius);
+translate([3,10]) color("SandyBrown") sphere(radius);
+translate([4,10]) color("Goldenrod") sphere(radius);
+translate([5,10]) color("DarkGoldenrod") sphere(radius);
+translate([6,10]) color("Peru") sphere(radius);
+translate([7,10]) color("Chocolate") sphere(radius);
+translate([8,10]) color("SaddleBrown") sphere(radius);
+translate([9,10]) color("Sienna") sphere(radius);
+translate([10,10]) color("Brown") sphere(radius);
+translate([0,11]) color("Maroon") sphere(radius);
+//translate([1,11]) color("White colors") sphere(radius);
+translate([2,11]) color("White") sphere(radius);
+translate([3,11]) color("Snow") sphere(radius);
+translate([4,11]) color("Honeydew") sphere(radius);
+translate([5,11]) color("MintCream") sphere(radius);
+translate([6,11]) color("Azure") sphere(radius);
+translate([7,11]) color("AliceBlue") sphere(radius);
+translate([8,11]) color("GhostWhite") sphere(radius);
+translate([9,11]) color("WhiteSmoke") sphere(radius);
+translate([10,11]) color("Seashell") sphere(radius);
+translate([0,12]) color("Beige") sphere(radius);
+translate([1,12]) color("OldLace") sphere(radius);
+translate([2,12]) color("FloralWhite") sphere(radius);
+translate([3,12]) color("Ivory") sphere(radius);
+translate([4,12]) color("AntiqueWhite") sphere(radius);
+translate([5,12]) color("Linen") sphere(radius);
+translate([6,12]) color("LavenderBlush") sphere(radius);
+translate([7,12]) color("MistyRose") sphere(radius);
+//translate([8,12]) color("Gray colors") sphere(radius);
+translate([9,12]) color("Gainsboro") sphere(radius);
+translate([10,12]) color("LightGrey") sphere(radius);
+translate([0,13]) color("Silver") sphere(radius);
+translate([1,13]) color("DarkGray") sphere(radius);
+translate([2,13]) color("Gray") sphere(radius);
+translate([3,13]) color("DimGray") sphere(radius);
+translate([4,13]) color("LightSlateGray") sphere(radius);
+translate([5,13]) color("SlateGray") sphere(radius);
+translate([6,13]) color("DarkSlateGray") sphere(radius);
+translate([7,13]) color("Black") sphere(radius);
+
contact: Jan Huwald // Impressum