Merge branch 'master' into linear_extrude_argument
40
README.md
|
@ -1,4 +1,5 @@
|
|||
# What is OpenSCAD?
|
||||
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=openscad&url=http://openscad.org&title=OpenSCAD&language=&tags=github&category=software)
|
||||
|
||||
OpenSCAD is a software for creating solid 3D CAD objects. It is free software
|
||||
and available for Linux/UNIX, MS Windows and Mac OS X.
|
||||
|
@ -78,10 +79,10 @@ To build OpenSCAD, you need some libraries and tools. The version
|
|||
numbers in brackets specify the versions which have been used for
|
||||
development. Other versions may or may not work as well.
|
||||
|
||||
If you're using Ubuntu, you can install these libraries from
|
||||
aptitude. If you're using Mac, there is a build script that compiles
|
||||
the libraries from source. Follow the instructions for the platform
|
||||
you're compiling on below.
|
||||
If you're using a newer version of Ubuntu, you can install these
|
||||
libraries from aptitude. If you're using Mac, or an older Linux, there
|
||||
are build scripts that download and compile the libraries from source.
|
||||
Follow the instructions for the platform you're compiling on below.
|
||||
|
||||
* [Qt4 (4.4 - 4.7)](http://www.qt.nokia.com/)
|
||||
* [CGAL (3.6 - 3.9)](http://www.cgal.org/)
|
||||
|
@ -116,15 +117,34 @@ compilation process.
|
|||
|
||||
After that, follow the Compilation instructions below.
|
||||
|
||||
### Building for Ubuntu
|
||||
### Building for newer Ubunutu
|
||||
|
||||
If you have done this and want to contribute, fork the repo and
|
||||
contribute docs on how to build for windows!
|
||||
sudo apt-get install libqt4-dev libqt4-opengl-dev libxmu-dev cmake \
|
||||
libglew1.5-dev bison flex libeigen2-dev git-core libboost-all-dev \
|
||||
libXi-dev libcgal-dev libglut3-dev libopencsg-dev libopencsg1
|
||||
|
||||
Check your library versions against the list above. After that, follow
|
||||
the Compilation instructions below.
|
||||
|
||||
### Building for older Linux or without root access
|
||||
|
||||
First, make sure that you have compiler tools (build-essential on ubuntu).
|
||||
Then after you've cloned this git repository, run the script that sets up the
|
||||
environment variables.
|
||||
|
||||
source ./scripts/setenv-linbuild.sh
|
||||
|
||||
Then run the script to download & compile all the prerequisite libraries above:
|
||||
|
||||
./scripts/linux-build-dependencies.sh
|
||||
|
||||
After that, follow the Compilation instructions below.
|
||||
|
||||
### Building for Windows
|
||||
|
||||
If you have done this and want to contribute, fork the repo and
|
||||
contribute docs on how to build for windows!
|
||||
OpenSCAD for Windows is usually cross-compiled from Linux. If you wish to
|
||||
attempt an MSVC build, please see this site:
|
||||
http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_on_Windows
|
||||
|
||||
### Compilation
|
||||
|
||||
|
@ -137,3 +157,5 @@ Then run make. Finally you might run 'make install' as root or simply copy the
|
|||
If you had problems compiling from source, raise a new issue in the
|
||||
[issue tracker on the github page](https://github.com/openscad/openscad/issues).
|
||||
|
||||
The four subsections of this site can also be helpful:
|
||||
http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Building_OpenSCAD_from_Sources
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
;;; scad.el --- SCAD mode derived mode
|
||||
;;; scad-mode.el --- SCAD mode derived mode
|
||||
|
||||
;; Author: Len Trigg
|
||||
;; Maintainer: Len Trigg <lenbok@gmail.com>
|
||||
;; Created: March 2010
|
||||
;; Modified: November 2011
|
||||
;; Version: $Revision: 88 $
|
||||
;; Modified: 06 July 2012
|
||||
;; URL: https://raw.github.com/openscad/openscad/master/contrib/scad-mode.el
|
||||
;; Version: $Revision: 89 $
|
||||
|
||||
;; 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
|
||||
|
@ -28,7 +29,7 @@
|
|||
;;
|
||||
;; To use, insert the following into your emacs startup:
|
||||
;;
|
||||
;; (autoload 'scad-mode "scad" "Major mode for editing SCAD code." t)
|
||||
;; (autoload 'scad-mode "scad-mode" "Major mode for editing SCAD code." t)
|
||||
;; (add-to-list 'auto-mode-alist '("\\.scad$" . scad-mode))
|
||||
|
||||
;;; To Do:
|
||||
|
@ -152,8 +153,14 @@
|
|||
:syntax-table scad-mode-syntax-table
|
||||
(set (make-local-variable 'font-lock-defaults) '(scad-font-lock-keywords))
|
||||
(set (make-local-variable 'indent-line-function) 'scad-indent-line)
|
||||
;(set (make-local-variable 'imenu-generic-expression) scad-imenu-generic-expression)
|
||||
;(set (make-local-variable 'outline-regexp) scad-outline-regexp)
|
||||
;(set (make-local-variable 'imenu-generic-expression) scad-imenu-generic-expression)
|
||||
;(set (make-local-variable 'outline-regexp) scad-outline-regexp)
|
||||
;; set comment styles for scad mode
|
||||
(set (make-local-variable 'comment-start) "//")
|
||||
(set (make-local-variable 'comment-end) "")
|
||||
(set (make-local-variable 'block-comment-start) "/*")
|
||||
(set (make-local-variable 'block-comment-end) "*/")
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
@ -219,4 +226,4 @@
|
|||
(call-process scad-command nil 0 nil (buffer-file-name)))
|
||||
|
||||
(provide 'scad)
|
||||
;;; scad.el ends here
|
||||
;;; scad-mode.el ends here
|
|
@ -77,6 +77,10 @@ virtual framebuffer program like Xvnc or Xvfb. For example:
|
|||
$ Xvfb :5 -screen 0 800x600x24 &
|
||||
$ DISPLAY=:5 ctest
|
||||
|
||||
or
|
||||
|
||||
$ xvfb-run ctest
|
||||
|
||||
Some versions of Xvfb may fail, however.
|
||||
|
||||
1. Trouble finding libraries on unix
|
||||
|
@ -84,13 +88,19 @@ Some versions of Xvfb may fail, however.
|
|||
To help CMAKE find eigen2, OpenCSG, CGAL, Boost, and GLEW, you can use
|
||||
environment variables, just like for the main qmake & openscad.pro. Examples:
|
||||
|
||||
OPENSCAD_LIBRARIES=~ cmake .
|
||||
CGALDIR=~/CGAL-3.9 BOOSTDIR=~/boost-1.47.0 cmake .
|
||||
|
||||
OPENSCAD_LIBRARIES=$HOME cmake .
|
||||
CGALDIR=$HOME/CGAL-3.9 BOOSTDIR=$HOME/boost-1.47.0 cmake .
|
||||
|
||||
Valid variables are as follows:
|
||||
|
||||
BOOSTDIR, CGALDIR, EIGEN2DIR, GLEWDIR, OPENCSGDIR, OPENSCAD_LIBRARIES
|
||||
|
||||
When running, this might help find your locally built libraries (assuming
|
||||
you installed into $HOME)
|
||||
|
||||
Linux: export LD_LIBRARY_PATH=$HOME/lib:$HOME/lib64
|
||||
Mac: export DYLD_LIBRARY_PATH=$HOME/lib
|
||||
|
||||
2. Location of logs
|
||||
|
||||
Logs of test runs are found in tests/build/Testing/Temporary
|
||||
|
@ -111,7 +121,16 @@ Comparison method.
|
|||
Your version of imagemagick is old. Upgrade, or pass -DCOMPARATOR=old to
|
||||
cmake. The comparison will be of lowered reliability.
|
||||
|
||||
5. Other issues
|
||||
5. Locale errors
|
||||
|
||||
"terminate called after throwing an instance of 'std::runtime_error'
|
||||
what(): locale::facet::_S_create_c_locale name not valid"
|
||||
|
||||
Is a boost/libstdc++ bug. Fix like so:
|
||||
|
||||
$ export LC_MESSAGES=
|
||||
|
||||
6. Other issues
|
||||
|
||||
The OpenSCAD User Manual has a section on buildling. Please check there
|
||||
for updates:
|
||||
|
|
1
glew.pri
|
@ -5,6 +5,7 @@ glew {
|
|||
!isEmpty(GLEW_DIR) {
|
||||
QMAKE_INCDIR += $$GLEW_DIR/include
|
||||
QMAKE_LIBDIR += $$GLEW_DIR/lib
|
||||
QMAKE_LIBDIR += $$GLEW_DIR/lib64
|
||||
message("GLEW location: $$GLEW_DIR")
|
||||
}
|
||||
|
||||
|
|
10
openscad.pro
|
@ -81,9 +81,13 @@ win32 {
|
|||
CONFIG += qt
|
||||
QT += opengl
|
||||
|
||||
# Fedora Linux + DSO fix
|
||||
linux*:exists(/usr/lib64/libGLU*)|linux*:exists(/usr/lib/libGLU*) {
|
||||
LIBS += -lGLU
|
||||
# see http://fedoraproject.org/wiki/UnderstandingDSOLinkChange
|
||||
# and https://github.com/openscad/openscad/pull/119
|
||||
# ( QT += opengl does not automatically link glu on some DSO systems. )
|
||||
unix:!macx {
|
||||
!contains ( QMAKE_LIBS_OPENGL, "-lGLU" ) {
|
||||
QMAKE_LIBS_OPENGL += -lGLU
|
||||
}
|
||||
}
|
||||
|
||||
netbsd* {
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
#!/bin/sh -e
|
||||
#
|
||||
# This script builds all library dependencies of OpenSCAD for Linux
|
||||
#
|
||||
# This script must be run from the OpenSCAD source root directory
|
||||
#
|
||||
# Usage: linux-build-dependencies.sh
|
||||
#
|
||||
# Prerequisites:
|
||||
# - wget or curl
|
||||
# - Qt4
|
||||
# - cmake 2.8 ( force build_cmake at bottom if yours is too old )
|
||||
#
|
||||
|
||||
BASEDIR=$HOME/openscad_deps
|
||||
OPENSCADDIR=$PWD
|
||||
SRCDIR=$BASEDIR/src
|
||||
DEPLOYDIR=$BASEDIR
|
||||
NUMCPU=2 # paralell builds for some libraries
|
||||
|
||||
printUsage()
|
||||
{
|
||||
echo "Usage: $0"
|
||||
echo
|
||||
}
|
||||
|
||||
build_cmake()
|
||||
{
|
||||
version=$1
|
||||
echo "Building cmake" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf cmake-$version
|
||||
if [ ! -f cmake-$version.tar.gz ]; then
|
||||
curl -O http://www.cmake.org/files/v2.8/cmake-$version.tar.gz
|
||||
fi
|
||||
tar zxf cmake-$version.tar.gz
|
||||
cd cmake-$version
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --prefix=$DEPLOYDIR
|
||||
make -j$NUMCPU
|
||||
make install
|
||||
}
|
||||
|
||||
build_curl()
|
||||
{
|
||||
version=$1
|
||||
echo "Building curl" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf curl-$version
|
||||
if [ ! -f curl-$version.tar.bz2 ]; then
|
||||
wget http://curl.haxx.se/download/curl-$version.tar.bz2
|
||||
fi
|
||||
tar xjf curl-$version.tar.bz2
|
||||
cd curl-$version
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --prefix=$DEPLOYDIR
|
||||
make -j$NUMCPU
|
||||
make install
|
||||
}
|
||||
|
||||
build_gmp()
|
||||
{
|
||||
version=$1
|
||||
echo "Building gmp" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf gmp-$version
|
||||
if [ ! -f gmp-$version.tar.bz2 ]; then
|
||||
curl -O ftp://ftp.gmplib.org/pub/gmp-$version/gmp-$version.tar.bz2
|
||||
fi
|
||||
tar xjf gmp-$version.tar.bz2
|
||||
cd gmp-$version
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --prefix=$DEPLOYDIR --enable-cxx
|
||||
make install
|
||||
}
|
||||
|
||||
build_mpfr()
|
||||
{
|
||||
version=$1
|
||||
echo "Building mpfr" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf mpfr-$version
|
||||
if [ ! -f mpfr-$version.tar.bz2 ]; then
|
||||
curl -O http://www.mpfr.org/mpfr-$version/mpfr-$version.tar.bz2
|
||||
fi
|
||||
tar xjf mpfr-$version.tar.bz2
|
||||
cd mpfr-$version
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --prefix=$DEPLOYDIR --with-gmp=$DEPLOYDIR
|
||||
make install
|
||||
cd ..
|
||||
}
|
||||
|
||||
build_boost()
|
||||
{
|
||||
version=$1
|
||||
bversion=`echo $version | tr "." "_"`
|
||||
echo "Building boost" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf boost_$bversion
|
||||
if [ ! -f boost_$bversion.tar.bz2 ]; then
|
||||
curl -LO http://downloads.sourceforge.net/project/boost/boost/$version/boost_$bversion.tar.bz2
|
||||
fi
|
||||
tar xjf boost_$bversion.tar.bz2
|
||||
cd boost_$bversion
|
||||
# We only need certain portions of boost
|
||||
./bootstrap.sh --prefix=$DEPLOYDIR --with-libraries=thread,program_options,filesystem,system,regex
|
||||
./bjam -j$NUMCPU
|
||||
./bjam install
|
||||
}
|
||||
|
||||
build_cgal()
|
||||
{
|
||||
version=$1
|
||||
echo "Building CGAL" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf CGAL-$version
|
||||
if [ ! -f CGAL-$version.tar.gz ]; then
|
||||
#4.0
|
||||
curl -O https://gforge.inria.fr/frs/download.php/30387/CGAL-$version.tar.gz
|
||||
# 3.9 curl -O https://gforge.inria.fr/frs/download.php/29125/CGAL-$version.tar.gz
|
||||
# 3.8 curl -O https://gforge.inria.fr/frs/download.php/28500/CGAL-$version.tar.gz
|
||||
# 3.7 curl -O https://gforge.inria.fr/frs/download.php/27641/CGAL-$version.tar.gz
|
||||
fi
|
||||
tar xzf CGAL-$version.tar.gz
|
||||
cd CGAL-$version
|
||||
cmake -DCMAKE_INSTALL_PREFIX=$DEPLOYDIR -DGMP_INCLUDE_DIR=$DEPLOYDIR/include -DGMP_LIBRARIES=$DEPLOYDIR/lib/libgmp.so -DGMPXX_LIBRARIES=$DEPLOYDIR/lib/libgmpxx.so -DGMPXX_INCLUDE_DIR=$DEPLOYDIR/include -DMPFR_INCLUDE_DIR=$DEPLOYDIR/include -DMPFR_LIBRARIES=$DEPLOYDIR/lib/libmpfr.so -DWITH_CGAL_Qt3=OFF -DWITH_CGAL_Qt4=OFF -DWITH_CGAL_ImageIO=OFF -DBOOST_ROOT=$DEPLOYDIR -DCMAKE_BUILD_TYPE=Debug
|
||||
make -j$NUMCPU
|
||||
make install
|
||||
}
|
||||
|
||||
build_glew()
|
||||
{
|
||||
version=$1
|
||||
echo "Building GLEW" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf glew-$version
|
||||
if [ ! -f glew-$version.tgz ]; then
|
||||
curl -LO http://downloads.sourceforge.net/project/glew/glew/$version/glew-$version.tgz
|
||||
fi
|
||||
tar xzf glew-$version.tgz
|
||||
cd glew-$version
|
||||
mkdir -p $DEPLOYDIR/lib/pkgconfig
|
||||
|
||||
# uncomment this kludge for Fedora 64bit
|
||||
# sed -i s/"\-lXmu"/"\-L\/usr\/lib64\/libXmu.so.6"/ config/Makefile.linux
|
||||
|
||||
GLEW_DEST=$DEPLOYDIR make -j$NUMCPU
|
||||
GLEW_DEST=$DEPLOYDIR make install
|
||||
}
|
||||
|
||||
build_opencsg()
|
||||
{
|
||||
version=$1
|
||||
echo "Building OpenCSG" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf OpenCSG-$version
|
||||
if [ ! -f OpenCSG-$version.tar.gz ]; then
|
||||
curl -O http://www.opencsg.org/OpenCSG-$version.tar.gz
|
||||
fi
|
||||
tar xzf OpenCSG-$version.tar.gz
|
||||
cd OpenCSG-$version
|
||||
sed -i s/example// opencsg.pro # examples might be broken without GLUT
|
||||
|
||||
# uncomment this kludge for Fedora 64bit
|
||||
# sed -i s/"\-lXmu"/"\-L\/usr\/lib64\/libXmu.so.6"/ src/Makefile
|
||||
|
||||
qmake-qt4
|
||||
make
|
||||
install -v lib/* $DEPLOYDIR/lib
|
||||
install -v include/* $DEPLOYDIR/include
|
||||
}
|
||||
|
||||
build_eigen()
|
||||
{
|
||||
version=$1
|
||||
echo "Building eigen" $version "..."
|
||||
cd $BASEDIR/src
|
||||
rm -rf eigen-$version
|
||||
## Directory name for v2.0.17
|
||||
rm -rf eigen-eigen-b23437e61a07
|
||||
if [ ! -f eigen-$version.tar.bz2 ]; then
|
||||
curl -LO http://bitbucket.org/eigen/eigen/get/$version.tar.bz2
|
||||
mv $version.tar.bz2 eigen-$version.tar.bz2
|
||||
fi
|
||||
tar xjf eigen-$version.tar.bz2
|
||||
## File name for v2.0.17
|
||||
ln -s eigen-eigen-b23437e61a07 eigen-$version
|
||||
cd eigen-$version
|
||||
cmake -DCMAKE_INSTALL_PREFIX=$DEPLOYDIR
|
||||
make -j$NUMCPU
|
||||
make install
|
||||
}
|
||||
|
||||
if [ ! -f $OPENSCADDIR/openscad.pro ]; then
|
||||
echo "Must be run from the OpenSCAD source root directory"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -d $BASEDIR/bin ]; then
|
||||
mkdir --parents $BASEDIR/bin
|
||||
fi
|
||||
|
||||
echo "Using basedir:" $BASEDIR
|
||||
echo "Using deploydir:" $DEPLOYDIR
|
||||
echo "Using srcdir:" $SRCDIR
|
||||
echo "Number of CPUs for parallel builds:" $NUMCPU
|
||||
mkdir -p $SRCDIR $DEPLOYDIR
|
||||
|
||||
export PATH=$BASEDIR/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$DEPLOYDIR/lib:$DEPLOYDIR/lib64:$LD_LIBRARY_PATH
|
||||
export LD_RUN_PATH=$DEPLOYDIR/lib:$DEPLOYDIR/lib64:$LD_RUN_PATH
|
||||
echo "PATH modified temporarily"
|
||||
echo "LD_LIBRARY_PATH modified temporarily"
|
||||
echo "LD_RUN_PATH modified temporarily"
|
||||
|
||||
if [ ! "`command -v curl`" ]; then
|
||||
build_curl 7.26.0
|
||||
fi
|
||||
|
||||
# NB! For cmake, also update the actual download URL in the function
|
||||
if [ ! "`command -v cmake`" ]; then
|
||||
build_cmake 2.8.8
|
||||
fi
|
||||
|
||||
build_eigen 2.0.17
|
||||
build_gmp 5.0.5
|
||||
build_mpfr 3.1.1
|
||||
build_boost 1.47.0
|
||||
# NB! For CGAL, also update the actual download URL in the function
|
||||
build_cgal 4.0
|
||||
build_glew 1.7.0
|
||||
build_opencsg 1.3.2
|
||||
|
||||
echo "OpenSCAD dependencies built in " $BASEDIR
|
|
@ -186,6 +186,6 @@ case $OS in
|
|||
strip openscad-$VERSION/lib/openscad/*
|
||||
cp scripts/installer-linux.sh openscad-$VERSION/install.sh
|
||||
chmod 755 -R openscad-$VERSION/
|
||||
tar cz openscad-$VERSION > openscad-$VERSION.x86-64.tar.gz
|
||||
tar cz openscad-$VERSION > openscad-$VERSION.x86-$ARCH.tar.gz
|
||||
;;
|
||||
esac
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# setup env variables for building OpenSCAD against custom built
|
||||
# dependency libraries from linux-build-dependencies.sh
|
||||
|
||||
# run this file with 'source setenv-linbuild.sh'
|
||||
|
||||
# BASEDIR and DEPLOYDIR must be the same as in linux-build-dependencies.sh
|
||||
BASEDIR=$HOME/openscad_deps
|
||||
DEPLOYDIR=$BASEDIR
|
||||
|
||||
export PATH=$BASEDIR/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$DEPLOYDIR/lib:$DEPLOYDIR/lib64
|
||||
export LD_RUN_PATH=$DEPLOYDIR/lib:$DEPLOYDIR/lib64
|
||||
export OPENSCAD_LIBRARIES=$DEPLOYDIR
|
||||
export GLEWDIR=$DEPLOYDIR
|
||||
|
||||
echo BASEDIR: $BASEDIR
|
||||
echo DEPLOYDIR: $DEPLOYDIR
|
||||
echo PATH modified
|
||||
echo LD_LIBRARY_PATH modified
|
||||
echo LD_RUN_PATH modified
|
||||
echo OPENSCAD_LIBRARIES modified
|
||||
echo GLEWDIR modified
|
||||
|
|
@ -11,6 +11,8 @@ fi
|
|||
|
||||
if [[ $OSTYPE =~ "darwin" ]]; then
|
||||
OS=MACOSX
|
||||
elif [[ $OSTYPE == "linux-gnu" ]]; then
|
||||
OS=LINUX
|
||||
fi
|
||||
|
||||
indexfile=../openscad.github.com/index.html
|
||||
|
@ -21,6 +23,10 @@ if [ -f $indexfile ]; then
|
|||
file2=$2
|
||||
sed -i .backup -e "s/^\(.*win-snapshot-zip.*\)\(OpenSCAD-.*\.zip\)\(.*\)\(OpenSCAD-.*zip\)\(.*$\)/\\1$file1\\3$file1\\5/" $indexfile
|
||||
sed -i .backup -e "s/^\(.*win-snapshot-exe.*\)\(OpenSCAD-.*-Installer\.exe\)\(.*\)\(OpenSCAD-.*-Installer.exe\)\(.*$\)/\\1$file2\\3$file2\\5/" $indexfile
|
||||
elif [ $OS == LINUX ]; then
|
||||
file2=$2
|
||||
sed -i .backup -e "s/^\(.*linux-snapshot-32.*\)\(openscad-.*-32\.tar\.gz\)\(.*\)\(openscad-.*-32\.tar\.gz\)\(.*$\)/\\1$file1\\3$file1\\5/" $indexfile
|
||||
sed -i .backup -e "s/^\(.*linux-snapshot-64.*\)\(openscad-.*-64\.tar\.gz\)\(.*\)\(openscad-.*-64\.tar\.gz\)\(.*$\)/\\1$file2\\3$file2\\5/" $indexfile
|
||||
fi
|
||||
echo "Web page updated. Remember to commit and push openscad.github.com"
|
||||
else
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "cgalfwd.h"
|
||||
#include "memory.h"
|
||||
#include <string>
|
||||
|
||||
class CGAL_Nef_polyhedron
|
||||
{
|
||||
|
@ -18,6 +19,7 @@ public:
|
|||
CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other);
|
||||
CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other);
|
||||
CGAL_Nef_polyhedron copy() const;
|
||||
std::string dump_p2() const;
|
||||
int weight() const;
|
||||
class PolySet *convertToPolyset();
|
||||
class DxfData *convertToDxfData() const;
|
||||
|
|
|
@ -77,4 +77,29 @@ DxfData *CGAL_Nef_polyhedron::convertToDxfData() const
|
|||
return dxfdata;
|
||||
}
|
||||
|
||||
// dump the 2 dimensional nef_poly.
|
||||
std::string CGAL_Nef_polyhedron::dump_p2() const
|
||||
{
|
||||
std::stringstream out;
|
||||
CGAL_Nef_polyhedron2::Explorer explorer = this->p2->explorer();
|
||||
CGAL_Nef_polyhedron2::Explorer::Vertex_const_iterator i;
|
||||
out << "CGAL_Nef_polyhedron::p2 Vertices";
|
||||
for (i = explorer.vertices_begin(); i != explorer.vertices_end(); ++i) {
|
||||
if ( explorer.is_standard( i ) ) {
|
||||
CGAL_Nef_polyhedron2::Explorer::Point point = explorer.point( i );
|
||||
out << "\n Point x y: "
|
||||
<< CGAL::to_double(point.x()) << " "
|
||||
<< CGAL::to_double(point.y());
|
||||
} else {
|
||||
CGAL_Nef_polyhedron2::Explorer::Ray ray = explorer.ray( i );
|
||||
CGAL_Nef_polyhedron2::Explorer::Point point = ray.point( 0 );
|
||||
out << "\n Ray x y dx dy: "
|
||||
<< CGAL::to_double(point.x()) << " " << CGAL::to_double(point.y()) << " "
|
||||
<< CGAL::to_double(ray.direction().dx()) << " " << CGAL::to_double(ray.direction().dy());
|
||||
}
|
||||
}
|
||||
out << "\nCGAL_Nef_polyhedron::p2 Vertices end";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
#endif // ENABLE_CGAL
|
||||
|
|
|
@ -16,9 +16,88 @@
|
|||
#include "openscad.h" // get_fragments_from_r()
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
/*
|
||||
|
||||
This Visitor is used in the 'cut' process. Essentially, one or more of
|
||||
our 3d nef polyhedrons have been 'cut' by the xy-plane, forming a number
|
||||
of polygons that are essentially 'flat'. This Visitor object, when
|
||||
called repeatedly, collects those flat polygons together and forms a new
|
||||
2d nef polyhedron out of them. It keeps track of the 'holes' so that
|
||||
in the final resulting polygon, they are preserved properly.
|
||||
|
||||
The output polygon is stored in the output_nefpoly2d variable.
|
||||
|
||||
For more information on 3d + 2d nef polyhedrons, facets, halffacets,
|
||||
facet cycles, etc, please see these websites:
|
||||
|
||||
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_polyhedron_3-Traits---Halffacet.html
|
||||
|
||||
The halffacet iteration 'circulator' code is based on OGL_helper.h
|
||||
|
||||
Why do we throw out all 'down' half-facets? Imagine a triangle in 3d
|
||||
space - it has one 'half face' for one 'side' and another 'half face' for the
|
||||
other 'side'. If we iterated over both half-faces we would get the same vertex
|
||||
coordinates twice. Instead, we only need one side, in our case, we
|
||||
chose the 'up' side.
|
||||
*/
|
||||
class NefShellVisitor_for_cut {
|
||||
public:
|
||||
std::stringstream out;
|
||||
CGAL_Nef_polyhedron2::Boundary boundary;
|
||||
shared_ptr<CGAL_Nef_polyhedron2> tmpnef2d;
|
||||
shared_ptr<CGAL_Nef_polyhedron2> output_nefpoly2d;
|
||||
CGAL::Direction_3<CGAL_Kernel3> up;
|
||||
bool debug;
|
||||
NefShellVisitor_for_cut(bool debug=false)
|
||||
{
|
||||
output_nefpoly2d.reset( new CGAL_Nef_polyhedron2() );
|
||||
boundary = CGAL_Nef_polyhedron2::INCLUDED;
|
||||
up = CGAL::Direction_3<CGAL_Kernel3>(0,0,1);
|
||||
this->debug = debug;
|
||||
}
|
||||
std::string dump()
|
||||
{
|
||||
return out.str();
|
||||
}
|
||||
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 ) {
|
||||
if ( hfacet->plane().orthogonal_direction() != this->up ) {
|
||||
if (debug) out << "down facing half-facet. skipping\n";
|
||||
return;
|
||||
}
|
||||
|
||||
int numcontours = 0;
|
||||
CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator i;
|
||||
CGAL_forall_facet_cycles_of( i, hfacet ) {
|
||||
CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(i), c2(c1);
|
||||
std::list<CGAL_Nef_polyhedron2::Point> contour;
|
||||
CGAL_For_all( c1, c2 ) {
|
||||
CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->source()->point();
|
||||
CGAL_Nef_polyhedron2::Point point2d( point3d.x(), point3d.y() );
|
||||
contour.push_back( point2d );
|
||||
}
|
||||
tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) );
|
||||
if ( numcontours == 0 ) {
|
||||
if (debug) out << " contour is a body. make union(). " << contour.size() << " points.\n" ;
|
||||
*output_nefpoly2d += *tmpnef2d;
|
||||
} else {
|
||||
if (debug) out << " contour is a hole. make intersection(). " << contour.size() << " points.\n";
|
||||
*output_nefpoly2d *= *tmpnef2d;
|
||||
}
|
||||
numcontours++;
|
||||
} // next facet cycle (i.e. next contour)
|
||||
} // visit()
|
||||
};
|
||||
|
||||
PolySetCGALEvaluator::PolySetCGALEvaluator(CGALEvaluator &cgalevaluator)
|
||||
: PolySetEvaluator(cgalevaluator.getTree()), cgalevaluator(cgalevaluator)
|
||||
{
|
||||
this->debug = false;
|
||||
}
|
||||
|
||||
PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
|
||||
|
@ -34,80 +113,46 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
|
|||
}
|
||||
}
|
||||
if (sum.empty()) return NULL;
|
||||
if (!sum.p3->is_simple()) {
|
||||
if (!node.cut_mode) {
|
||||
PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design..");
|
||||
return new PolySet();
|
||||
}
|
||||
}
|
||||
|
||||
PolySet *ps = new PolySet();
|
||||
ps->convexity = node.convexity;
|
||||
ps->is2d = true;
|
||||
CGAL_Nef_polyhedron nef_poly;
|
||||
|
||||
// In cut mode, the model is intersected by a large but very thin box living on the
|
||||
// XY plane.
|
||||
if (node.cut_mode)
|
||||
{
|
||||
PolySet cube;
|
||||
double infval = 1e8, eps = 0.1;
|
||||
double x1 = -infval, x2 = +infval, y1 = -infval, y2 = +infval, z1 = 0, z2 = eps;
|
||||
// intersect 'sum' with the x-y plane
|
||||
CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3( 0,0,1,0 );
|
||||
*sum.p3 = sum.p3->intersection( xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY);
|
||||
|
||||
cube.append_poly(); // top
|
||||
cube.append_vertex(x1, y1, z2);
|
||||
cube.append_vertex(x2, y1, z2);
|
||||
cube.append_vertex(x2, y2, z2);
|
||||
cube.append_vertex(x1, y2, z2);
|
||||
|
||||
cube.append_poly(); // bottom
|
||||
cube.append_vertex(x1, y2, z1);
|
||||
cube.append_vertex(x2, y2, z1);
|
||||
cube.append_vertex(x2, y1, z1);
|
||||
cube.append_vertex(x1, y1, z1);
|
||||
|
||||
cube.append_poly(); // side1
|
||||
cube.append_vertex(x1, y1, z1);
|
||||
cube.append_vertex(x2, y1, z1);
|
||||
cube.append_vertex(x2, y1, z2);
|
||||
cube.append_vertex(x1, y1, z2);
|
||||
|
||||
cube.append_poly(); // side2
|
||||
cube.append_vertex(x2, y1, z1);
|
||||
cube.append_vertex(x2, y2, z1);
|
||||
cube.append_vertex(x2, y2, z2);
|
||||
cube.append_vertex(x2, y1, z2);
|
||||
|
||||
cube.append_poly(); // side3
|
||||
cube.append_vertex(x2, y2, z1);
|
||||
cube.append_vertex(x1, y2, z1);
|
||||
cube.append_vertex(x1, y2, z2);
|
||||
cube.append_vertex(x2, y2, z2);
|
||||
|
||||
cube.append_poly(); // side4
|
||||
cube.append_vertex(x1, y2, z1);
|
||||
cube.append_vertex(x1, y1, z1);
|
||||
cube.append_vertex(x1, y1, z2);
|
||||
cube.append_vertex(x1, y2, z2);
|
||||
CGAL_Nef_polyhedron Ncube = this->cgalevaluator.evaluateCGALMesh(cube);
|
||||
|
||||
sum *= Ncube;
|
||||
|
||||
// FIXME: Instead of intersecting with a thin volume, we could intersect
|
||||
// with a plane. This feels like a better solution. However, as the result
|
||||
// of such an intersection isn't simple, we cannot convert the resulting
|
||||
// Nef polyhedron to a Polyhedron using convertToPolyset() and we need
|
||||
// another way of extracting the result. kintel 20120203.
|
||||
// *sum.p3 = sum.p3->intersection(CGAL_Nef_polyhedron3::Plane_3(0, 0, 1, 0),
|
||||
// CGAL_Nef_polyhedron3::PLANE_ONLY);
|
||||
|
||||
|
||||
if (!sum.p3->is_simple()) {
|
||||
PRINT("WARNING: Body of projection(cut = true) isn't valid 2-manifold! Modify your design..");
|
||||
goto cant_project_non_simple_polyhedron;
|
||||
// Visit each polygon in sum.p3 and union/intersect into a 2d polygon (with holes)
|
||||
// For info on Volumes, Shells, Facets, and the 'visitor' pattern, please see
|
||||
// http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html
|
||||
NefShellVisitor_for_cut shell_visitor;
|
||||
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 = sum.p3->volumes_begin(); i != sum.p3->volumes_end(); ++i ) {
|
||||
for ( j = i->shells_begin(); j != i->shells_end(); ++j ) {
|
||||
sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j );
|
||||
sum.p3->visit_shell_objects( sface_handle , shell_visitor );
|
||||
}
|
||||
}
|
||||
if (debug) std::cout << "shell visitor\n" << shell_visitor.dump() << "\nshell visitor end\n";
|
||||
|
||||
PolySet *ps3 = sum.convertToPolyset();
|
||||
if (!ps3) return NULL;
|
||||
nef_poly.p2 = shell_visitor.output_nefpoly2d;
|
||||
nef_poly.dim = 2;
|
||||
if (debug) std::cout << "--\n" << nef_poly.dump_p2() << "\n";
|
||||
|
||||
// Extract polygons in the XY plane, ignoring all other polygons
|
||||
// FIXME: If the polyhedron is really thin, there might be unwanted polygons
|
||||
// in the XY plane, causing the resulting 2D polygon to be self-intersection
|
||||
// and cause a crash in CGALEvaluator::PolyReducer. The right solution is to
|
||||
// filter these polygons here. kintel 20120203.
|
||||
// FIXME: If the polyhedron is really thin, there might be unwanted polygons
|
||||
// in the XY plane, causing the resulting 2D polygon to be self-intersection
|
||||
// and cause a crash in CGALEvaluator::PolyReducer. The right solution is to
|
||||
// filter these polygons here. kintel 20120203.
|
||||
/*
|
||||
Grid2d<unsigned int> conversion_grid(GRID_COARSE);
|
||||
for (size_t i = 0; i < ps3->polygons.size(); i++) {
|
||||
for (size_t j = 0; j < ps3->polygons[i].size(); j++) {
|
||||
|
@ -129,19 +174,13 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
|
|||
}
|
||||
next_ps3_polygon_cut_mode:;
|
||||
}
|
||||
delete ps3;
|
||||
*/
|
||||
}
|
||||
// In projection mode all the triangles are projected manually into the XY plane
|
||||
else
|
||||
{
|
||||
if (!sum.p3->is_simple()) {
|
||||
PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design..");
|
||||
goto cant_project_non_simple_polyhedron;
|
||||
}
|
||||
|
||||
PolySet *ps3 = sum.convertToPolyset();
|
||||
if (!ps3) return NULL;
|
||||
CGAL_Nef_polyhedron np;
|
||||
for (size_t i = 0; i < ps3->polygons.size(); i++)
|
||||
{
|
||||
int min_x_p = -1;
|
||||
|
@ -180,22 +219,22 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node)
|
|||
plist.push_back(p);
|
||||
}
|
||||
// FIXME: Should the CGAL_Nef_polyhedron2 be cached?
|
||||
if (np.empty()) {
|
||||
np.dim = 2;
|
||||
np.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED));
|
||||
if (nef_poly.empty()) {
|
||||
nef_poly.dim = 2;
|
||||
nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED));
|
||||
}
|
||||
else {
|
||||
(*np.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED);
|
||||
(*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED);
|
||||
}
|
||||
}
|
||||
delete ps3;
|
||||
DxfData *dxf = np.convertToDxfData();
|
||||
dxf_tesselate(ps, *dxf, 0, true, false, 0);
|
||||
dxf_border_to_ps(ps, *dxf);
|
||||
delete dxf;
|
||||
}
|
||||
|
||||
cant_project_non_simple_polyhedron:
|
||||
PolySet *ps = nef_poly.convertToPolyset();
|
||||
assert( ps != NULL );
|
||||
ps->convexity = node.convexity;
|
||||
if (debug) std::cout << "--\n" << ps->dump() << "\n";
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ public:
|
|||
virtual PolySet *evaluatePolySet(const RotateExtrudeNode &node);
|
||||
virtual PolySet *evaluatePolySet(const CgaladvNode &node);
|
||||
virtual PolySet *evaluatePolySet(const RenderNode &node);
|
||||
|
||||
bool debug;
|
||||
protected:
|
||||
PolySet *extrudeDxfData(const LinearExtrudeNode &node, class DxfData &dxf);
|
||||
PolySet *rotateDxfData(const RotateExtrudeNode &node, class DxfData &dxf);
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
#include "printutils.h"
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
using namespace boost::filesystem;
|
||||
namespace fs = boost::filesystem;
|
||||
#include "boosty.h"
|
||||
|
||||
std::vector<const Context*> Context::ctx_stack;
|
||||
|
@ -179,8 +179,8 @@ AbstractNode *Context::evaluate_module(const ModuleInstantiation &inst) const
|
|||
*/
|
||||
std::string Context::getAbsolutePath(const std::string &filename) const
|
||||
{
|
||||
if (!filename.empty()) {
|
||||
return boosty::absolute(path(this->document_path) / filename).string();
|
||||
if (!filename.empty() && !boosty::is_absolute(fs::path(filename))) {
|
||||
return boosty::absolute(fs::path(this->document_path) / filename).string();
|
||||
}
|
||||
else {
|
||||
return filename;
|
||||
|
|
|
@ -5,25 +5,29 @@
|
|||
/*!
|
||||
NB! for e.g. empty intersections, this can normalize a tree to nothing and return NULL.
|
||||
*/
|
||||
shared_ptr<CSGTerm> CSGTermNormalizer::normalize(const shared_ptr<CSGTerm> &root,
|
||||
size_t limit)
|
||||
shared_ptr<CSGTerm> CSGTermNormalizer::normalize(const shared_ptr<CSGTerm> &root)
|
||||
{
|
||||
shared_ptr<CSGTerm> temp = root;
|
||||
while (1) {
|
||||
this->rootnode = temp;
|
||||
this->nodecount = 0;
|
||||
shared_ptr<CSGTerm> n = normalizePass(temp);
|
||||
if (!n) return n; // If normalized to nothing
|
||||
if (temp == n) break;
|
||||
temp = n;
|
||||
|
||||
unsigned int num = count(temp);
|
||||
#ifdef DEBUG
|
||||
PRINTB("Normalize count: %d\n", num);
|
||||
#endif
|
||||
if (num > limit) {
|
||||
PRINTB("WARNING: Normalized tree is growing past %d elements. Aborting normalization.\n", limit);
|
||||
return root;
|
||||
if (this->nodecount > this->limit) {
|
||||
PRINTB("WARNING: Normalized tree is growing past %d elements. Aborting normalization.\n", this->limit);
|
||||
// Clean up any partially evaluated terms
|
||||
shared_ptr<CSGTerm> newroot = root, tmproot;
|
||||
while (newroot != tmproot) {
|
||||
tmproot = newroot;
|
||||
newroot = collapse_null_terms(tmproot);
|
||||
}
|
||||
return newroot;
|
||||
}
|
||||
}
|
||||
this->rootnode.reset();
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
@ -42,7 +46,11 @@ shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term)
|
|||
}
|
||||
|
||||
do {
|
||||
while (term && normalize_tail(term)) { }
|
||||
while (term && match_and_replace(term)) { }
|
||||
this->nodecount++;
|
||||
if (nodecount > this->limit) {
|
||||
return shared_ptr<CSGTerm>();
|
||||
}
|
||||
if (!term || term->type == CSGTerm::TYPE_PRIMITIVE) return term;
|
||||
if (term->left) term->left = normalizePass(term->left);
|
||||
} while (term->type != CSGTerm::TYPE_UNION &&
|
||||
|
@ -51,6 +59,11 @@ shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term)
|
|||
term->right = normalizePass(term->right);
|
||||
|
||||
// FIXME: Do we need to take into account any transformation of item here?
|
||||
return collapse_null_terms(term);
|
||||
}
|
||||
|
||||
shared_ptr<CSGTerm> CSGTermNormalizer::collapse_null_terms(const shared_ptr<CSGTerm> &term)
|
||||
{
|
||||
if (!term->right) {
|
||||
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_DIFFERENCE) return term->left;
|
||||
else return term->right;
|
||||
|
@ -59,13 +72,14 @@ shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term)
|
|||
if (term->type == CSGTerm::TYPE_UNION) return term->right;
|
||||
else return term->left;
|
||||
}
|
||||
|
||||
return term;
|
||||
}
|
||||
|
||||
bool CSGTermNormalizer::normalize_tail(shared_ptr<CSGTerm> &term)
|
||||
bool CSGTermNormalizer::match_and_replace(shared_ptr<CSGTerm> &term)
|
||||
{
|
||||
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_PRIMITIVE) return false;
|
||||
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_PRIMITIVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Part A: The 'x . (y . z)' expressions
|
||||
|
||||
|
@ -149,8 +163,9 @@ bool CSGTermNormalizer::normalize_tail(shared_ptr<CSGTerm> &term)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Counts all non-leaf nodes
|
||||
unsigned int CSGTermNormalizer::count(const shared_ptr<CSGTerm> &term) const
|
||||
{
|
||||
if (!term) return 0;
|
||||
return term->type == CSGTerm::TYPE_PRIMITIVE ? 1 : 0 + count(term->left) + count(term->right);
|
||||
return term->type == CSGTerm::TYPE_PRIMITIVE ? 0 : 1 + count(term->left) + count(term->right);
|
||||
}
|
||||
|
|
|
@ -6,15 +6,20 @@
|
|||
class CSGTermNormalizer
|
||||
{
|
||||
public:
|
||||
CSGTermNormalizer() {}
|
||||
CSGTermNormalizer(size_t limit) : limit(limit) {}
|
||||
~CSGTermNormalizer() {}
|
||||
|
||||
shared_ptr<class CSGTerm> normalize(const shared_ptr<CSGTerm> &term, size_t limit);
|
||||
shared_ptr<class CSGTerm> normalize(const shared_ptr<CSGTerm> &term);
|
||||
|
||||
private:
|
||||
shared_ptr<CSGTerm> normalizePass(shared_ptr<CSGTerm> term) ;
|
||||
bool normalize_tail(shared_ptr<CSGTerm> &term);
|
||||
bool match_and_replace(shared_ptr<CSGTerm> &term);
|
||||
shared_ptr<CSGTerm> collapse_null_terms(const shared_ptr<CSGTerm> &term);
|
||||
unsigned int count(const shared_ptr<CSGTerm> &term) const;
|
||||
|
||||
size_t limit;
|
||||
size_t nodecount;
|
||||
shared_ptr<class CSGTerm> rootnode;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include <QDir>
|
||||
#include "value.h"
|
||||
|
@ -555,3 +556,27 @@ int DxfData::addPoint(double x, double y)
|
|||
return this->points.size()-1;
|
||||
}
|
||||
|
||||
std::string DxfData::dump() const
|
||||
{
|
||||
std::stringstream out;
|
||||
out << "DxfData"
|
||||
<< "\n num points: " << points.size()
|
||||
<< "\n num paths: " << paths.size()
|
||||
<< "\n num dims: " << dims.size()
|
||||
<< "\n points: ";
|
||||
for ( size_t k = 0 ; k < points.size() ; k++ ) {
|
||||
out << "\n x y: " << points[k].transpose();
|
||||
}
|
||||
out << "\n paths: ";
|
||||
for ( size_t i = 0; i < paths.size(); i++ ) {
|
||||
out << "\n path:" << i
|
||||
<< "\n is_closed: " << paths[i].is_closed
|
||||
<< "\n is_inner: " << paths[i].is_inner ;
|
||||
DxfData::Path path = paths[i];
|
||||
for ( size_t j = 0; j < path.indices.size(); j++ ) {
|
||||
out << "\n index[" << j << "]==" << path.indices[j];
|
||||
}
|
||||
}
|
||||
out << "\nDxfData end";
|
||||
return out.str();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
int addPoint(double x, double y);
|
||||
|
||||
void fixup_path_direction();
|
||||
std::string dump() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -131,6 +131,7 @@ void dxf_tesselate(PolySet *ps, DxfData &dxf, double rot, bool up, bool /* do_tr
|
|||
// ..maybe it would be better to assert here. But this would
|
||||
// break compatibility with the glu tesselator that handled such
|
||||
// cases just fine.
|
||||
PRINT( "WARNING: Duplicate vertex found during Tessellation. Render may be incorrect." );
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -600,7 +600,11 @@ void GLView::mouseMoveEvent(QMouseEvent *event)
|
|||
double mz = -(dy) * viewer_distance/1000;
|
||||
|
||||
double my = 0;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(4, 7, 0))
|
||||
if (event->buttons() & Qt::MidButton) {
|
||||
#else
|
||||
if (event->buttons() & Qt::MiddleButton) {
|
||||
#endif
|
||||
my = mz;
|
||||
mz = 0;
|
||||
// actually lock the x-position
|
||||
|
|
|
@ -118,7 +118,7 @@ use[ \t\r\n>]*"<" { BEGIN(cond_use); }
|
|||
else {
|
||||
usepath = sourcepath() / filename;
|
||||
if (!fs::exists(usepath)) {
|
||||
usepath = boosty::absolute(fs::path(get_librarydir()) / filename);
|
||||
usepath = locate_file(filename);
|
||||
}
|
||||
}
|
||||
/* Only accept regular files which exists */
|
||||
|
@ -214,7 +214,7 @@ void includefile()
|
|||
|
||||
fs::path finfo = dirinfo / filename;
|
||||
if (!exists(finfo)) {
|
||||
finfo = fs::path(get_librarydir()) / filepath / filename;
|
||||
finfo = locate_file((fs::path(filepath) / filename).string());
|
||||
}
|
||||
|
||||
filepath.clear();
|
||||
|
|
|
@ -719,9 +719,9 @@ void MainWindow::compileCSG(bool procevents)
|
|||
if (procevents)
|
||||
QApplication::processEvents();
|
||||
|
||||
CSGTermNormalizer normalizer;
|
||||
size_t normalizelimit = 2 * Preferences::inst()->getValue("advanced/openCSGLimit").toUInt();
|
||||
this->root_norm_term = normalizer.normalize(this->root_raw_term, normalizelimit);
|
||||
CSGTermNormalizer normalizer(normalizelimit);
|
||||
this->root_norm_term = normalizer.normalize(this->root_raw_term);
|
||||
if (this->root_norm_term) {
|
||||
this->root_chain = new CSGChain();
|
||||
this->root_chain->import(this->root_norm_term);
|
||||
|
@ -741,7 +741,7 @@ void MainWindow::compileCSG(bool procevents)
|
|||
|
||||
highlights_chain = new CSGChain();
|
||||
for (unsigned int i = 0; i < highlight_terms.size(); i++) {
|
||||
highlight_terms[i] = normalizer.normalize(highlight_terms[i], normalizelimit);
|
||||
highlight_terms[i] = normalizer.normalize(highlight_terms[i]);
|
||||
highlights_chain->import(highlight_terms[i]);
|
||||
}
|
||||
}
|
||||
|
@ -754,7 +754,7 @@ void MainWindow::compileCSG(bool procevents)
|
|||
|
||||
background_chain = new CSGChain();
|
||||
for (unsigned int i = 0; i < background_terms.size(); i++) {
|
||||
background_terms[i] = normalizer.normalize(background_terms[i], normalizelimit);
|
||||
background_terms[i] = normalizer.normalize(background_terms[i]);
|
||||
background_chain->import(background_terms[i]);
|
||||
}
|
||||
}
|
||||
|
@ -1359,7 +1359,7 @@ void MainWindow::actionExportSTLorOFF(bool)
|
|||
}
|
||||
|
||||
if (!this->root_N->p3->is_simple()) {
|
||||
PRINT("Object isn't a valid 2-manifold! Modify your design..");
|
||||
PRINT("Object isn't a valid 2-manifold! Modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export");
|
||||
clearCurrentOutput();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -294,6 +294,7 @@ int main(int argc, char **argv)
|
|||
PRINTB("Can't open file \"%s\" for export", csg_output_file);
|
||||
}
|
||||
else {
|
||||
fs::current_path(fparent); // Force exported filenames to be relative to document path
|
||||
fstream << tree.getString(*root_node) << "\n";
|
||||
fstream.close();
|
||||
}
|
||||
|
|
|
@ -1,27 +1,38 @@
|
|||
#include "parsersettings.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include "boosty.h"
|
||||
#include <qglobal.h> // Needed for Q_ defines - move the offending code somewhere else
|
||||
|
||||
using namespace boost::filesystem;
|
||||
#include "boosty.h"
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
std::string librarydir;
|
||||
std::vector<std::string> librarypath;
|
||||
|
||||
void set_librarydir(const std::string &libdir)
|
||||
void add_librarydir(const std::string &libdir)
|
||||
{
|
||||
librarydir = libdir;
|
||||
librarypath.push_back(libdir);
|
||||
}
|
||||
|
||||
const std::string &get_librarydir()
|
||||
/*!
|
||||
Searces for the given file in library paths and returns the full path if found.
|
||||
Returns an empty path if file cannot be found.
|
||||
*/
|
||||
std::string locate_file(const std::string &filename)
|
||||
{
|
||||
return librarydir;
|
||||
BOOST_FOREACH(const std::string &dir, librarypath) {
|
||||
fs::path usepath = fs::path(dir) / filename;
|
||||
if (fs::exists(usepath)) return usepath.string();
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
void parser_init(const std::string &applicationpath)
|
||||
{
|
||||
// FIXME: Append paths from OPENSCADPATH before adding built-in paths
|
||||
|
||||
std::string librarydir;
|
||||
path libdir(applicationpath);
|
||||
path tmpdir;
|
||||
fs::path libdir(applicationpath);
|
||||
fs::path tmpdir;
|
||||
#ifdef Q_WS_MAC
|
||||
libdir /= "../Resources"; // Libraries can be bundled
|
||||
if (!is_directory(libdir / "libraries")) libdir /= "../../..";
|
||||
|
@ -37,5 +48,5 @@ void parser_init(const std::string &applicationpath)
|
|||
if (is_directory(tmpdir = libdir / "libraries")) {
|
||||
librarydir = boosty::stringy( tmpdir );
|
||||
}
|
||||
set_librarydir(librarydir);
|
||||
if (!librarydir.empty()) add_librarydir(librarydir);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
extern int parser_error_pos;
|
||||
|
||||
void parser_init(const std::string &applicationpath);
|
||||
void set_librarydir(const std::string &libdir);
|
||||
const std::string &get_librarydir();
|
||||
void add_librarydir(const std::string &libdir);
|
||||
std::string locate_file(const std::string &filename);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -48,6 +48,36 @@ PolySet::~PolySet()
|
|||
{
|
||||
}
|
||||
|
||||
std::string PolySet::dump() const
|
||||
{
|
||||
std::stringstream out;
|
||||
out << "PolySet:"
|
||||
<< "\n dimensions:" << std::string( this->is2d ? "2" : "3" )
|
||||
<< "\n convexity:" << this->convexity
|
||||
<< "\n num polygons: " << polygons.size()
|
||||
<< "\n num borders: " << borders.size()
|
||||
<< "\n polygons data:";
|
||||
for (size_t i = 0; i < polygons.size(); i++) {
|
||||
out << "\n polygon begin:";
|
||||
const Polygon *poly = &polygons[i];
|
||||
for (size_t j = 0; j < poly->size(); j++) {
|
||||
Vector3d v = poly->at(j);
|
||||
out << "\n vertex:" << v.transpose();
|
||||
}
|
||||
}
|
||||
out << "\n borders data:";
|
||||
for (size_t i = 0; i < borders.size(); i++) {
|
||||
out << "\n border polygon begin:";
|
||||
const Polygon *poly = &borders[i];
|
||||
for (size_t j = 0; j < poly->size(); j++) {
|
||||
Vector3d v = poly->at(j);
|
||||
out << "\n vertex:" << v.transpose();
|
||||
}
|
||||
}
|
||||
out << "\nPolySet end";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void PolySet::append_poly()
|
||||
{
|
||||
polygons.push_back(Polygon());
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "grid.h"
|
||||
#include "linalg.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class PolySet
|
||||
{
|
||||
|
@ -40,6 +41,7 @@ public:
|
|||
|
||||
void render_surface(csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const;
|
||||
void render_edges(csgmode_e csgmode) const;
|
||||
std::string dump() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,3 +7,4 @@ translate([-200,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="0");
|
|||
translate([0,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="0");
|
||||
translate([200,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="noname");
|
||||
translate([0,200,0]) import(file="../../dxf/multiple-layers.dxf", layer="Layer with a pretty long name including \\ \"special\" /'\\\\ characters");
|
||||
translate([200,0,0]) import(file="@CMAKE_SOURCE_DIR@/../testdata/dxf/polygons.dxf");
|
|
@ -1,3 +1,4 @@
|
|||
import_stl("import.stl");
|
||||
translate([2,0,0]) import("import.stl");
|
||||
translate([4,0,0]) import("import_bin.stl");
|
||||
translate([0,2,0]) import("@CMAKE_SOURCE_DIR@/../testdata/scad/features/import.stl");
|
|
@ -237,7 +237,7 @@ if (NOT GLEW_INCLUDE_DIR)
|
|||
NO_DEFAULT_PATH)
|
||||
find_library(GLEW_LIBRARY
|
||||
NAMES GLEW glew
|
||||
HINTS ${GLEW_DIR}/lib
|
||||
HINTS ${GLEW_DIR}/lib ${GLEW_DIR}/lib64
|
||||
NO_DEFAULT_PATH)
|
||||
if (NOT GLEW_LIBRARY)
|
||||
find_package(GLEW REQUIRED)
|
||||
|
@ -611,10 +611,14 @@ endforeach()
|
|||
set_directory_properties(PROPERTIES TEST_INCLUDE_FILE "${CMAKE_SOURCE_DIR}/EnforceConfig.cmake")
|
||||
|
||||
# Subst files
|
||||
configure_file(${CMAKE_SOURCE_DIR}/../testdata/scad/misc/include-tests-template.scad
|
||||
configure_file(${CMAKE_SOURCE_DIR}/../testdata/scad/templates/include-tests-template.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/include-tests.scad)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/../testdata/scad/misc/use-tests-template.scad
|
||||
configure_file(${CMAKE_SOURCE_DIR}/../testdata/scad/templates/use-tests-template.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/use-tests.scad)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/../testdata/scad/templates/import_stl-tests-template.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/features/import_stl-tests.scad)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/../testdata/scad/templates/import_dxf-tests-template.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/features/import_dxf-tests.scad)
|
||||
|
||||
# Find all scad files
|
||||
file(GLOB MINIMAL_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/*.scad)
|
||||
|
|
|
@ -139,7 +139,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -112,7 +112,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -98,7 +98,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -91,7 +91,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -77,7 +77,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -258,7 +258,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
|
|||
std::string currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
@ -302,8 +302,8 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
|
|||
}
|
||||
|
||||
// CSG normalization
|
||||
CSGTermNormalizer normalizer;
|
||||
csgInfo.root_norm_term = normalizer.normalize(root_raw_term, 5000);
|
||||
CSGTermNormalizer normalizer(5000);
|
||||
csgInfo.root_norm_term = normalizer.normalize(root_raw_term);
|
||||
if (csgInfo.root_norm_term) {
|
||||
csgInfo.root_chain = new CSGChain();
|
||||
csgInfo.root_chain->import(csgInfo.root_norm_term);
|
||||
|
@ -319,7 +319,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
|
|||
|
||||
csgInfo.highlights_chain = new CSGChain();
|
||||
for (unsigned int i = 0; i < csgInfo.highlight_terms.size(); i++) {
|
||||
csgInfo.highlight_terms[i] = normalizer.normalize(csgInfo.highlight_terms[i], 5000);
|
||||
csgInfo.highlight_terms[i] = normalizer.normalize(csgInfo.highlight_terms[i]);
|
||||
csgInfo.highlights_chain->import(csgInfo.highlight_terms[i]);
|
||||
}
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ int csgtestcore(int argc, char *argv[], test_type_e test_type)
|
|||
|
||||
csgInfo.background_chain = new CSGChain();
|
||||
for (unsigned int i = 0; i < csgInfo.background_terms.size(); i++) {
|
||||
csgInfo.background_terms[i] = normalizer.normalize(csgInfo.background_terms[i], 5000);
|
||||
csgInfo.background_terms[i] = normalizer.normalize(csgInfo.background_terms[i]);
|
||||
csgInfo.background_chain->import(csgInfo.background_terms[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -84,10 +84,10 @@ int main(int argc, char **argv)
|
|||
QCoreApplication app(argc, argv);
|
||||
fs::path original_path = fs::current_path();
|
||||
|
||||
currentdir = boosty::stringy( fs::current_path() );
|
||||
currentdir = boosty::stringy(fs::current_path());
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -89,7 +89,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
|
@ -77,7 +77,7 @@ int main(int argc, char **argv)
|
|||
currentdir = boosty::stringy( fs::current_path() );
|
||||
|
||||
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
|
||||
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
add_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
|
||||
|
||||
Context root_ctx;
|
||||
register_builtin(root_ctx);
|
||||
|
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 5.6 KiB |
|
@ -21,4 +21,7 @@
|
|||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 200], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
import(file = "../../dxf/multiple-layers.dxf", layer = "Layer with a pretty long name including \\ \"special\" /'\\\\ characters", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
||||
multmatrix([[1, 0, 0, 200], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
import(file = "../../dxf/polygons.dxf", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,4 +5,7 @@
|
|||
multmatrix([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
import(file = "import_bin.stl", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
||||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
import(file = "import.stl", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 6.1 KiB |