Major overhaul of papercraft-unfold (not finished yet)
This commit is contained in:
parent
a724a3dc12
commit
7f099ab621
BIN
extensions/fablabchemnitz/papercraft/admesh/.libs/admesh
Executable file
BIN
extensions/fablabchemnitz/papercraft/admesh/.libs/admesh
Executable file
Binary file not shown.
@ -0,0 +1,41 @@
|
|||||||
|
# libadmesh.la - a libtool library file
|
||||||
|
# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14
|
||||||
|
#
|
||||||
|
# Please DO NOT delete this file!
|
||||||
|
# It is necessary for linking the library.
|
||||||
|
|
||||||
|
# The name that we can dlopen(3).
|
||||||
|
dlname='libadmesh.so.1'
|
||||||
|
|
||||||
|
# Names of this library.
|
||||||
|
library_names='libadmesh.so.1.0.0 libadmesh.so.1 libadmesh.so'
|
||||||
|
|
||||||
|
# The name of the static archive.
|
||||||
|
old_library=''
|
||||||
|
|
||||||
|
# Linker flags that cannot go in dependency_libs.
|
||||||
|
inherited_linker_flags=''
|
||||||
|
|
||||||
|
# Libraries that this one depends upon.
|
||||||
|
dependency_libs=' -lm'
|
||||||
|
|
||||||
|
# Names of additional weak libraries provided by this library
|
||||||
|
weak_library_names=''
|
||||||
|
|
||||||
|
# Version information for libadmesh.
|
||||||
|
current=1
|
||||||
|
age=0
|
||||||
|
revision=0
|
||||||
|
|
||||||
|
# Is this an already installed library?
|
||||||
|
installed=no
|
||||||
|
|
||||||
|
# Should we warn about portability when linking against -modules?
|
||||||
|
shouldnotlink=no
|
||||||
|
|
||||||
|
# Files to dlopen/dlpreopen
|
||||||
|
dlopen=''
|
||||||
|
dlpreopen=''
|
||||||
|
|
||||||
|
# Directory that this library needs to be installed in:
|
||||||
|
libdir='/usr/local/lib'
|
@ -0,0 +1,41 @@
|
|||||||
|
# libadmesh.la - a libtool library file
|
||||||
|
# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14
|
||||||
|
#
|
||||||
|
# Please DO NOT delete this file!
|
||||||
|
# It is necessary for linking the library.
|
||||||
|
|
||||||
|
# The name that we can dlopen(3).
|
||||||
|
dlname='libadmesh.so.1'
|
||||||
|
|
||||||
|
# Names of this library.
|
||||||
|
library_names='libadmesh.so.1.0.0 libadmesh.so.1 libadmesh.so'
|
||||||
|
|
||||||
|
# The name of the static archive.
|
||||||
|
old_library=''
|
||||||
|
|
||||||
|
# Linker flags that cannot go in dependency_libs.
|
||||||
|
inherited_linker_flags=''
|
||||||
|
|
||||||
|
# Libraries that this one depends upon.
|
||||||
|
dependency_libs=' -lm'
|
||||||
|
|
||||||
|
# Names of additional weak libraries provided by this library
|
||||||
|
weak_library_names=''
|
||||||
|
|
||||||
|
# Version information for libadmesh.
|
||||||
|
current=1
|
||||||
|
age=0
|
||||||
|
revision=0
|
||||||
|
|
||||||
|
# Is this an already installed library?
|
||||||
|
installed=yes
|
||||||
|
|
||||||
|
# Should we warn about portability when linking against -modules?
|
||||||
|
shouldnotlink=no
|
||||||
|
|
||||||
|
# Files to dlopen/dlpreopen
|
||||||
|
dlopen=''
|
||||||
|
dlpreopen=''
|
||||||
|
|
||||||
|
# Directory that this library needs to be installed in:
|
||||||
|
libdir='/usr/local/lib'
|
BIN
extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.so.1
Executable file
BIN
extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.so.1
Executable file
Binary file not shown.
210
extensions/fablabchemnitz/papercraft/admesh/admesh
Executable file
210
extensions/fablabchemnitz/papercraft/admesh/admesh
Executable file
@ -0,0 +1,210 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# admesh - temporary wrapper script for .libs/admesh
|
||||||
|
# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14
|
||||||
|
#
|
||||||
|
# The admesh program cannot be directly executed until all the libtool
|
||||||
|
# libraries that it depends on are installed.
|
||||||
|
#
|
||||||
|
# This wrapper script should never be moved out of the build directory.
|
||||||
|
# If it is, it will not operate correctly.
|
||||||
|
|
||||||
|
# Sed substitution that helps us do robust quoting. It backslashifies
|
||||||
|
# metacharacters that are still active within double-quoted strings.
|
||||||
|
sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
|
||||||
|
|
||||||
|
# Be Bourne compatible
|
||||||
|
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
|
||||||
|
emulate sh
|
||||||
|
NULLCMD=:
|
||||||
|
# Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
|
||||||
|
# is contrary to our usage. Disable this feature.
|
||||||
|
alias -g '${1+"$@"}'='"$@"'
|
||||||
|
setopt NO_GLOB_SUBST
|
||||||
|
else
|
||||||
|
case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
|
||||||
|
fi
|
||||||
|
BIN_SH=xpg4; export BIN_SH # for Tru64
|
||||||
|
DUALCASE=1; export DUALCASE # for MKS sh
|
||||||
|
|
||||||
|
# The HP-UX ksh and POSIX shell print the target directory to stdout
|
||||||
|
# if CDPATH is set.
|
||||||
|
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
|
||||||
|
|
||||||
|
relink_command=""
|
||||||
|
|
||||||
|
# This environment variable determines our operation mode.
|
||||||
|
if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
|
||||||
|
# install mode needs the following variables:
|
||||||
|
generated_by_libtool_version='2.4.6'
|
||||||
|
notinst_deplibs=' libadmesh.la'
|
||||||
|
else
|
||||||
|
# When we are sourced in execute mode, $file and $ECHO are already set.
|
||||||
|
if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
|
||||||
|
file="$0"
|
||||||
|
|
||||||
|
# A function that is used when there is no print builtin or printf.
|
||||||
|
func_fallback_echo ()
|
||||||
|
{
|
||||||
|
eval 'cat <<_LTECHO_EOF
|
||||||
|
$1
|
||||||
|
_LTECHO_EOF'
|
||||||
|
}
|
||||||
|
ECHO="printf %s\\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Very basic option parsing. These options are (a) specific to
|
||||||
|
# the libtool wrapper, (b) are identical between the wrapper
|
||||||
|
# /script/ and the wrapper /executable/ that is used only on
|
||||||
|
# windows platforms, and (c) all begin with the string --lt-
|
||||||
|
# (application programs are unlikely to have options that match
|
||||||
|
# this pattern).
|
||||||
|
#
|
||||||
|
# There are only two supported options: --lt-debug and
|
||||||
|
# --lt-dump-script. There is, deliberately, no --lt-help.
|
||||||
|
#
|
||||||
|
# The first argument to this parsing function should be the
|
||||||
|
# script's ./libtool value, followed by no.
|
||||||
|
lt_option_debug=
|
||||||
|
func_parse_lt_options ()
|
||||||
|
{
|
||||||
|
lt_script_arg0=$0
|
||||||
|
shift
|
||||||
|
for lt_opt
|
||||||
|
do
|
||||||
|
case "$lt_opt" in
|
||||||
|
--lt-debug) lt_option_debug=1 ;;
|
||||||
|
--lt-dump-script)
|
||||||
|
lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
|
||||||
|
test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
|
||||||
|
lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
|
||||||
|
cat "$lt_dump_D/$lt_dump_F"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--lt-*)
|
||||||
|
$ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Print the debug banner immediately:
|
||||||
|
if test -n "$lt_option_debug"; then
|
||||||
|
echo "admesh:admesh:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-14" 1>&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Used when --lt-debug. Prints its arguments to stdout
|
||||||
|
# (redirection is the responsibility of the caller)
|
||||||
|
func_lt_dump_args ()
|
||||||
|
{
|
||||||
|
lt_dump_args_N=1;
|
||||||
|
for lt_arg
|
||||||
|
do
|
||||||
|
$ECHO "admesh:admesh:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
|
||||||
|
lt_dump_args_N=`expr $lt_dump_args_N + 1`
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Core function for launching the target application
|
||||||
|
func_exec_program_core ()
|
||||||
|
{
|
||||||
|
|
||||||
|
if test -n "$lt_option_debug"; then
|
||||||
|
$ECHO "admesh:admesh:$LINENO: newargv[0]: $progdir/$program" 1>&2
|
||||||
|
func_lt_dump_args ${1+"$@"} 1>&2
|
||||||
|
fi
|
||||||
|
exec "$progdir/$program" ${1+"$@"}
|
||||||
|
|
||||||
|
$ECHO "$0: cannot exec $program $*" 1>&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# A function to encapsulate launching the target application
|
||||||
|
# Strips options in the --lt-* namespace from $@ and
|
||||||
|
# launches target application with the remaining arguments.
|
||||||
|
func_exec_program ()
|
||||||
|
{
|
||||||
|
case " $* " in
|
||||||
|
*\ --lt-*)
|
||||||
|
for lt_wr_arg
|
||||||
|
do
|
||||||
|
case $lt_wr_arg in
|
||||||
|
--lt-*) ;;
|
||||||
|
*) set x "$@" "$lt_wr_arg"; shift;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done ;;
|
||||||
|
esac
|
||||||
|
func_exec_program_core ${1+"$@"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse options
|
||||||
|
func_parse_lt_options "$0" ${1+"$@"}
|
||||||
|
|
||||||
|
# Find the directory that this script lives in.
|
||||||
|
thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
|
||||||
|
test "x$thisdir" = "x$file" && thisdir=.
|
||||||
|
|
||||||
|
# Follow symbolic links until we get to the real thisdir.
|
||||||
|
file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
|
||||||
|
while test -n "$file"; do
|
||||||
|
destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
|
||||||
|
|
||||||
|
# If there was a directory component, then change thisdir.
|
||||||
|
if test "x$destdir" != "x$file"; then
|
||||||
|
case "$destdir" in
|
||||||
|
[\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
|
||||||
|
*) thisdir="$thisdir/$destdir" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
|
||||||
|
file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
|
||||||
|
done
|
||||||
|
|
||||||
|
# Usually 'no', except on cygwin/mingw when embedded into
|
||||||
|
# the cwrapper.
|
||||||
|
WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
|
||||||
|
if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
|
||||||
|
# special case for '.'
|
||||||
|
if test "$thisdir" = "."; then
|
||||||
|
thisdir=`pwd`
|
||||||
|
fi
|
||||||
|
# remove .libs from thisdir
|
||||||
|
case "$thisdir" in
|
||||||
|
*[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
|
||||||
|
.libs ) thisdir=. ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to get the absolute directory name.
|
||||||
|
absdir=`cd "$thisdir" && pwd`
|
||||||
|
test -n "$absdir" && thisdir="$absdir"
|
||||||
|
|
||||||
|
program='admesh'
|
||||||
|
progdir="$thisdir/.libs"
|
||||||
|
|
||||||
|
|
||||||
|
if test -f "$progdir/$program"; then
|
||||||
|
# Add our own library path to LD_LIBRARY_PATH
|
||||||
|
LD_LIBRARY_PATH="/tmp/admesh/.libs:$LD_LIBRARY_PATH"
|
||||||
|
|
||||||
|
# Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
|
||||||
|
# The second colon is a workaround for a bug in BeOS R4 sed
|
||||||
|
LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH
|
||||||
|
|
||||||
|
if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
|
||||||
|
# Run the actual program with our arguments.
|
||||||
|
func_exec_program ${1+"$@"}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# The program doesn't exist.
|
||||||
|
$ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
|
||||||
|
$ECHO "This script is just a wrapper for $program." 1>&2
|
||||||
|
$ECHO "See the libtool documentation for more information." 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
BIN
extensions/fablabchemnitz/papercraft/admesh/admesh.exe
Executable file
BIN
extensions/fablabchemnitz/papercraft/admesh/admesh.exe
Executable file
Binary file not shown.
201
extensions/fablabchemnitz/papercraft/admesh/include/admesh/stl.h
Normal file
201
extensions/fablabchemnitz/papercraft/admesh/include/admesh/stl.h
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/* ADMesh -- process triangulated solid meshes
|
||||||
|
* Copyright (C) 1995, 1996 Anthony D. Martin <amartin@engr.csulb.edu>
|
||||||
|
* Copyright (C) 2013, 2014 several contributors, see AUTHORS
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Questions, comments, suggestions, etc to
|
||||||
|
* https://github.com/admesh/admesh/issues
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __admesh_stl__
|
||||||
|
#define __admesh_stl__
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define STL_MAX(A,B) ((A)>(B)? (A):(B))
|
||||||
|
#define STL_MIN(A,B) ((A)<(B)? (A):(B))
|
||||||
|
#define ABS(X) ((X) < 0 ? -(X) : (X))
|
||||||
|
|
||||||
|
#define LABEL_SIZE 80
|
||||||
|
#define NUM_FACET_SIZE 4
|
||||||
|
#define HEADER_SIZE 84
|
||||||
|
#define STL_MIN_FILE_SIZE 284
|
||||||
|
#define ASCII_LINES_PER_FACET 7
|
||||||
|
#define SIZEOF_EDGE_SORT 24
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
} stl_vertex;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
} stl_normal;
|
||||||
|
|
||||||
|
typedef char stl_extra[2];
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
stl_normal normal;
|
||||||
|
stl_vertex vertex[3];
|
||||||
|
stl_extra extra;
|
||||||
|
} stl_facet;
|
||||||
|
#define SIZEOF_STL_FACET 50
|
||||||
|
|
||||||
|
typedef enum {binary, ascii, inmemory} stl_type;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
stl_vertex p1;
|
||||||
|
stl_vertex p2;
|
||||||
|
int facet_number;
|
||||||
|
} stl_edge;
|
||||||
|
|
||||||
|
typedef struct stl_hash_edge {
|
||||||
|
unsigned key[6];
|
||||||
|
int facet_number;
|
||||||
|
int which_edge;
|
||||||
|
struct stl_hash_edge *next;
|
||||||
|
} stl_hash_edge;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int neighbor[3];
|
||||||
|
char which_vertex_not[3];
|
||||||
|
} stl_neighbors;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int vertex[3];
|
||||||
|
} v_indices_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char header[81];
|
||||||
|
stl_type type;
|
||||||
|
int number_of_facets;
|
||||||
|
stl_vertex max;
|
||||||
|
stl_vertex min;
|
||||||
|
stl_vertex size;
|
||||||
|
float bounding_diameter;
|
||||||
|
float shortest_edge;
|
||||||
|
float volume;
|
||||||
|
unsigned number_of_blocks;
|
||||||
|
int connected_edges;
|
||||||
|
int connected_facets_1_edge;
|
||||||
|
int connected_facets_2_edge;
|
||||||
|
int connected_facets_3_edge;
|
||||||
|
int facets_w_1_bad_edge;
|
||||||
|
int facets_w_2_bad_edge;
|
||||||
|
int facets_w_3_bad_edge;
|
||||||
|
int original_num_facets;
|
||||||
|
int edges_fixed;
|
||||||
|
int degenerate_facets;
|
||||||
|
int facets_removed;
|
||||||
|
int facets_added;
|
||||||
|
int facets_reversed;
|
||||||
|
int backwards_edges;
|
||||||
|
int normals_fixed;
|
||||||
|
int number_of_parts;
|
||||||
|
int malloced;
|
||||||
|
int freed;
|
||||||
|
int facets_malloced;
|
||||||
|
int collisions;
|
||||||
|
int shared_vertices;
|
||||||
|
int shared_malloced;
|
||||||
|
} stl_stats;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FILE *fp;
|
||||||
|
stl_facet *facet_start;
|
||||||
|
stl_edge *edge_start;
|
||||||
|
stl_hash_edge **heads;
|
||||||
|
stl_hash_edge *tail;
|
||||||
|
int M;
|
||||||
|
stl_neighbors *neighbors_start;
|
||||||
|
v_indices_struct *v_indices;
|
||||||
|
stl_vertex *v_shared;
|
||||||
|
stl_stats stats;
|
||||||
|
char error;
|
||||||
|
} stl_file;
|
||||||
|
|
||||||
|
|
||||||
|
extern void stl_open(stl_file *stl, char *file);
|
||||||
|
extern void stl_close(stl_file *stl);
|
||||||
|
extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file);
|
||||||
|
extern void stl_print_edges(stl_file *stl, FILE *file);
|
||||||
|
extern void stl_print_neighbors(stl_file *stl, char *file);
|
||||||
|
extern void stl_put_little_int(FILE *fp, int value_in);
|
||||||
|
extern void stl_put_little_float(FILE *fp, float value_in);
|
||||||
|
extern void stl_write_ascii(stl_file *stl, const char *file, const char *label);
|
||||||
|
extern void stl_write_binary(stl_file *stl, const char *file, const char *label);
|
||||||
|
extern void stl_write_binary_block(stl_file *stl, FILE *fp);
|
||||||
|
extern void stl_check_facets_exact(stl_file *stl);
|
||||||
|
extern void stl_check_facets_nearby(stl_file *stl, float tolerance);
|
||||||
|
extern void stl_remove_unconnected_facets(stl_file *stl);
|
||||||
|
extern void stl_write_vertex(stl_file *stl, int facet, int vertex);
|
||||||
|
extern void stl_write_facet(stl_file *stl, char *label, int facet);
|
||||||
|
extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge);
|
||||||
|
extern void stl_write_neighbor(stl_file *stl, int facet);
|
||||||
|
extern void stl_write_quad_object(stl_file *stl, char *file);
|
||||||
|
extern void stl_verify_neighbors(stl_file *stl);
|
||||||
|
extern void stl_fill_holes(stl_file *stl);
|
||||||
|
extern void stl_fix_normal_directions(stl_file *stl);
|
||||||
|
extern void stl_fix_normal_values(stl_file *stl);
|
||||||
|
extern void stl_reverse_all_facets(stl_file *stl);
|
||||||
|
extern void stl_translate(stl_file *stl, float x, float y, float z);
|
||||||
|
extern void stl_translate_relative(stl_file *stl, float x, float y, float z);
|
||||||
|
extern void stl_scale_versor(stl_file *stl, float versor[3]);
|
||||||
|
extern void stl_scale(stl_file *stl, float factor);
|
||||||
|
extern void stl_rotate_x(stl_file *stl, float angle);
|
||||||
|
extern void stl_rotate_y(stl_file *stl, float angle);
|
||||||
|
extern void stl_rotate_z(stl_file *stl, float angle);
|
||||||
|
extern void stl_mirror_xy(stl_file *stl);
|
||||||
|
extern void stl_mirror_yz(stl_file *stl);
|
||||||
|
extern void stl_mirror_xz(stl_file *stl);
|
||||||
|
extern void stl_open_merge(stl_file *stl, char *file);
|
||||||
|
extern void stl_invalidate_shared_vertices(stl_file *stl);
|
||||||
|
extern void stl_generate_shared_vertices(stl_file *stl);
|
||||||
|
extern void stl_write_obj(stl_file *stl, char *file);
|
||||||
|
extern void stl_write_off(stl_file *stl, char *file);
|
||||||
|
extern void stl_write_dxf(stl_file *stl, char *file, char *label);
|
||||||
|
extern void stl_write_vrml(stl_file *stl, char *file);
|
||||||
|
extern void stl_calculate_normal(float normal[], stl_facet *facet);
|
||||||
|
extern void stl_normalize_vector(float v[]);
|
||||||
|
extern void stl_calculate_volume(stl_file *stl);
|
||||||
|
|
||||||
|
extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag);
|
||||||
|
|
||||||
|
extern void stl_initialize(stl_file *stl);
|
||||||
|
extern void stl_count_facets(stl_file *stl, char *file);
|
||||||
|
extern void stl_allocate(stl_file *stl);
|
||||||
|
extern void stl_read(stl_file *stl, int first_facet, int first);
|
||||||
|
extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first);
|
||||||
|
extern void stl_reallocate(stl_file *stl);
|
||||||
|
extern void stl_add_facet(stl_file *stl, stl_facet *new_facet);
|
||||||
|
extern void stl_get_size(stl_file *stl);
|
||||||
|
|
||||||
|
extern void stl_clear_error(stl_file *stl);
|
||||||
|
extern int stl_get_error(stl_file *stl);
|
||||||
|
extern void stl_exit_on_error(stl_file *stl);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
BIN
extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.a
Normal file
BIN
extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.a
Normal file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.dll.a
Executable file
BIN
extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.dll.a
Executable file
Binary file not shown.
41
extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.la
Executable file
41
extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.la
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
# libadmesh.la - a libtool library file
|
||||||
|
# Generated by libtool (GNU libtool) 2.4.6
|
||||||
|
#
|
||||||
|
# Please DO NOT delete this file!
|
||||||
|
# It is necessary for linking the library.
|
||||||
|
|
||||||
|
# The name that we can dlopen(3).
|
||||||
|
dlname='../bin/libadmesh-1.dll'
|
||||||
|
|
||||||
|
# Names of this library.
|
||||||
|
library_names='libadmesh.dll.a'
|
||||||
|
|
||||||
|
# The name of the static archive.
|
||||||
|
old_library=''
|
||||||
|
|
||||||
|
# Linker flags that cannot go in dependency_libs.
|
||||||
|
inherited_linker_flags=''
|
||||||
|
|
||||||
|
# Libraries that this one depends upon.
|
||||||
|
dependency_libs=''
|
||||||
|
|
||||||
|
# Names of additional weak libraries provided by this library
|
||||||
|
weak_library_names=''
|
||||||
|
|
||||||
|
# Version information for libadmesh.
|
||||||
|
current=1
|
||||||
|
age=0
|
||||||
|
revision=0
|
||||||
|
|
||||||
|
# Is this an already installed library?
|
||||||
|
installed=yes
|
||||||
|
|
||||||
|
# Should we warn about portability when linking against -modules?
|
||||||
|
shouldnotlink=no
|
||||||
|
|
||||||
|
# Files to dlopen/dlpreopen
|
||||||
|
dlopen=''
|
||||||
|
dlpreopen=''
|
||||||
|
|
||||||
|
# Directory that this library needs to be installed in:
|
||||||
|
libdir='/usr/x86_64-w64-mingw32/sys-root/mingw/lib'
|
@ -0,0 +1,11 @@
|
|||||||
|
prefix=/usr/x86_64-w64-mingw32/sys-root/mingw
|
||||||
|
exec_prefix=/usr/x86_64-w64-mingw32/sys-root/mingw
|
||||||
|
libdir=/usr/x86_64-w64-mingw32/sys-root/mingw/lib
|
||||||
|
includedir=/usr/x86_64-w64-mingw32/sys-root/mingw/include
|
||||||
|
|
||||||
|
Name: libadmesh
|
||||||
|
Description: Library for woring with admesh
|
||||||
|
Version: 0.98.3
|
||||||
|
Libs: -L${libdir} -ladmesh
|
||||||
|
Libs.private:
|
||||||
|
Cflags: -I${includedir}
|
BIN
extensions/fablabchemnitz/papercraft/admesh/libadmesh-1.dll
Executable file
BIN
extensions/fablabchemnitz/papercraft/admesh/libadmesh-1.dll
Executable file
Binary file not shown.
@ -0,0 +1,10 @@
|
|||||||
|
newmtl cubemtl
|
||||||
|
Ns 10
|
||||||
|
Ni 1.0
|
||||||
|
d 1.0
|
||||||
|
Tf 1 1 1
|
||||||
|
illum 2
|
||||||
|
Ka 0.5 0.5 0.
|
||||||
|
Kd 0.9 0.9 0.9
|
||||||
|
Ks 0.0 0.0 0.0
|
||||||
|
map_Kd cube.png
|
@ -0,0 +1,34 @@
|
|||||||
|
mtllib cube.mtl
|
||||||
|
|
||||||
|
usemtl cubemtl
|
||||||
|
|
||||||
|
v -0.5 -0.5 -0.5
|
||||||
|
v -0.5 -0.5 0.5
|
||||||
|
v -0.5 0.5 -0.5
|
||||||
|
v -0.5 0.5 0.5
|
||||||
|
v 0.5 -0.5 -0.5
|
||||||
|
v 0.5 -0.5 0.5
|
||||||
|
v 0.5 0.5 -0.5
|
||||||
|
v 0.5 0.5 0.5
|
||||||
|
|
||||||
|
vt 0.0 0.0
|
||||||
|
vt 0.0 1.0
|
||||||
|
vt 1.0 0.0
|
||||||
|
vt 1.0 1.0
|
||||||
|
|
||||||
|
vn 1.0 0.0 0.0
|
||||||
|
vn 0.0 1.0 0.0
|
||||||
|
vn 0.0 0.0 1.0
|
||||||
|
vn -1.0 0.0 0.0
|
||||||
|
vn 0.0 -1.0 0.0
|
||||||
|
vn 0.0 0.0 -1.0
|
||||||
|
|
||||||
|
f 1/1/4 2/3/4 4/4/4 3/2/4
|
||||||
|
f 2/1/3 6/3/3 8/4/3 4/2/3
|
||||||
|
f 6/1/1 5/3/1 7/4/1 8/2/1
|
||||||
|
f 5/1/6 1/3/6 3/4/6 7/2/6
|
||||||
|
f 4/1/2 8/3/2 7/4/2 3/2/2
|
||||||
|
f 2/1/5 1/3/5 5/4/5 6/2/5
|
||||||
|
|
||||||
|
usemtl fuckyou
|
||||||
|
|
BIN
extensions/fablabchemnitz/papercraft/assets/models/cube/cube.png
Normal file
BIN
extensions/fablabchemnitz/papercraft/assets/models/cube/cube.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 149 KiB |
@ -0,0 +1,22 @@
|
|||||||
|
varying vec3 fNormal;
|
||||||
|
varying vec4 fFrontColor;
|
||||||
|
|
||||||
|
vec3 ambientLight = vec3(0.2,0.2,0.2);
|
||||||
|
vec3 directionnalLight = normalize(vec3(10,5,7));
|
||||||
|
vec3 directionnalLightFactor = vec3(0.5,0.5,0.5);
|
||||||
|
|
||||||
|
uniform sampler2D tex;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
vec3 ambientFactor = ambientLight;
|
||||||
|
vec3 lambertFactor = max(vec3(0.0,0.0,0.0), dot(directionnalLight, fNormal) * directionnalLightFactor);
|
||||||
|
vec4 noTexColor = vec4(ambientFactor + lambertFactor, 1.0);
|
||||||
|
|
||||||
|
vec4 color = texture2D(tex, gl_TexCoord[0].st);
|
||||||
|
|
||||||
|
vec4 fragColor = noTexColor * color;
|
||||||
|
gl_FragColor = fragColor * fFrontColor;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
varying vec3 fNormal;
|
||||||
|
varying vec4 fTexCoord;
|
||||||
|
varying vec4 fFrontColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
fNormal = gl_Normal;
|
||||||
|
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
|
||||||
|
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
|
||||||
|
fFrontColor = gl_Color;
|
||||||
|
|
||||||
|
}
|
0
extensions/fablabchemnitz/papercraft/d3/__init__.py
Normal file
0
extensions/fablabchemnitz/papercraft/d3/__init__.py
Normal file
27
extensions/fablabchemnitz/papercraft/d3/camera.py
Normal file
27
extensions/fablabchemnitz/papercraft/d3/camera.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from .geometry import Vector
|
||||||
|
|
||||||
|
import OpenGL.GLU as glu
|
||||||
|
|
||||||
|
class Camera:
|
||||||
|
"""Simple 3D camera
|
||||||
|
"""
|
||||||
|
def __init__(self, position = Vector(1.0,0.0,0.0), target = Vector(), up = Vector(0.0,1.0,0.0)):
|
||||||
|
"""Creates a simple camera
|
||||||
|
|
||||||
|
:param position: center of the camera
|
||||||
|
:param target: point where the camera is looking
|
||||||
|
:param up: up vector of the camera
|
||||||
|
"""
|
||||||
|
self.position = position
|
||||||
|
self.target = target
|
||||||
|
self.up = up
|
||||||
|
|
||||||
|
def look(self):
|
||||||
|
"""Sets the model view matrix of OpenGL
|
||||||
|
|
||||||
|
Simply calls the gluLookAt function
|
||||||
|
"""
|
||||||
|
glu.gluLookAt(
|
||||||
|
self.position.x, self.position.y, self.position.z,
|
||||||
|
self.target.x, self.target.y, self.target.z,
|
||||||
|
self.up.x, self.up.y, self.up.z)
|
112
extensions/fablabchemnitz/papercraft/d3/controls.py
Normal file
112
extensions/fablabchemnitz/papercraft/d3/controls.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
from .geometry import Vector
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
import math
|
||||||
|
|
||||||
|
class Controls:
|
||||||
|
"""Abstract class for controls
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
"""Apply the controls modification to the model view matrix
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update(self, time = 10):
|
||||||
|
"""Update according to the user's inputs
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TrackBallControls(Controls):
|
||||||
|
"""Trackball controls
|
||||||
|
|
||||||
|
Simple trackball controls"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Creates a TrackBallControls
|
||||||
|
|
||||||
|
The trackball is centered at the origin
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.vertex = Vector()
|
||||||
|
self.theta = 0
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
"""Apply the rotation of the current trackball
|
||||||
|
"""
|
||||||
|
gl.glRotatef(self.theta * 180 / math.pi, self.vertex.x, self.vertex.y, self.vertex.z)
|
||||||
|
|
||||||
|
def update(self, time = 10):
|
||||||
|
"""Checks the keyboard inputs and update the angle
|
||||||
|
"""
|
||||||
|
if not pygame.mouse.get_pressed()[0]:
|
||||||
|
return
|
||||||
|
|
||||||
|
coeff = 0.001
|
||||||
|
move = pygame.mouse.get_rel()
|
||||||
|
|
||||||
|
dV = Vector(move[1] * time * coeff, move[0] * time * coeff, 0)
|
||||||
|
dTheta = dV.norm2()
|
||||||
|
|
||||||
|
if abs(dTheta) < 0.00001:
|
||||||
|
return
|
||||||
|
|
||||||
|
dV.normalize()
|
||||||
|
|
||||||
|
cosT2 = math.cos(self.theta / 2)
|
||||||
|
sinT2 = math.sin(self.theta / 2)
|
||||||
|
cosDT2 = math.cos(dTheta / 2)
|
||||||
|
sinDT2 = math.sin(dTheta / 2)
|
||||||
|
|
||||||
|
A = cosT2 * sinDT2 * dV + cosDT2 * sinT2 * self.vertex + sinDT2 * sinT2 * Vector.cross_product(dV, self.vertex)
|
||||||
|
|
||||||
|
self.theta = 2 * math.acos(cosT2 * cosDT2 - sinT2 * sinDT2 * Vector.dot(dV, self.vertex))
|
||||||
|
|
||||||
|
self.vertex = A
|
||||||
|
self.vertex.normalize()
|
||||||
|
|
||||||
|
class OrbitControls(Controls):
|
||||||
|
"""Simple OrbitControls
|
||||||
|
|
||||||
|
Similar to TrackBallControls but the up vector is preserved"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Creates an OrbitControls with null angles
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.phi = 0
|
||||||
|
self.theta = 0
|
||||||
|
self.scale_log = 0
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
scale = math.exp(self.scale_log)
|
||||||
|
gl.glScalef(scale, scale, scale)
|
||||||
|
gl.glRotatef(self.theta * 180 / math.pi, 1.0, 0.0, 0.0)
|
||||||
|
gl.glRotatef(self.phi * 180 / math.pi, 0.0, 1.0, 0.0)
|
||||||
|
|
||||||
|
def apply_event(self, event):
|
||||||
|
"""Manages the wheel event
|
||||||
|
|
||||||
|
:param event: a pyevent
|
||||||
|
"""
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
# Wheel up
|
||||||
|
if event.button == 4:
|
||||||
|
self.scale_log += 0.1
|
||||||
|
# Wheel down
|
||||||
|
elif event.button == 5:
|
||||||
|
self.scale_log -= 0.1
|
||||||
|
|
||||||
|
def update(self, time = 10):
|
||||||
|
|
||||||
|
if not pygame.mouse.get_pressed()[0]:
|
||||||
|
return
|
||||||
|
|
||||||
|
move = pygame.mouse.get_rel()
|
||||||
|
self.theta += move[1] * 0.01
|
||||||
|
self.phi += move[0] * 0.01
|
||||||
|
|
||||||
|
self.theta = max(min(self.theta, math.pi / 2), -math.pi / 2)
|
108
extensions/fablabchemnitz/papercraft/d3/geometry.py
Normal file
108
extensions/fablabchemnitz/papercraft/d3/geometry.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
class Vector:
|
||||||
|
""" 3D Vector
|
||||||
|
|
||||||
|
Simple class that represents a 3D vector
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, x = 0.0, y = 0.0, z = 0.0):
|
||||||
|
"""
|
||||||
|
Creates a vector from it's coordinates
|
||||||
|
"""
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.z = z
|
||||||
|
|
||||||
|
def from_array(self, arr):
|
||||||
|
"""
|
||||||
|
Creates a vector from an array
|
||||||
|
"""
|
||||||
|
self.x = float(arr[0]) if len(arr) > 0 else None
|
||||||
|
self.y = float(arr[1]) if len(arr) > 1 else None
|
||||||
|
self.z = float(arr[2]) if len(arr) > 2 else None
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
"""
|
||||||
|
Sums two vectors
|
||||||
|
"""
|
||||||
|
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
"""
|
||||||
|
Subs two vectors
|
||||||
|
"""
|
||||||
|
return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
"""
|
||||||
|
Computes the product between a vector and a number
|
||||||
|
"""
|
||||||
|
return Vector(self.x * other, self.y * other, self.z * other)
|
||||||
|
|
||||||
|
def __truediv__(self, number):
|
||||||
|
self.x /= number
|
||||||
|
self.y /= number
|
||||||
|
self.z /= number
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
"""
|
||||||
|
Computes the product between a vector and a number
|
||||||
|
"""
|
||||||
|
return self.__mul__(other)
|
||||||
|
|
||||||
|
def norm2(self):
|
||||||
|
"""
|
||||||
|
Computes the square of the norm of a vector
|
||||||
|
"""
|
||||||
|
return self.x * self.x + self.y * self.y + self.z * self.z
|
||||||
|
|
||||||
|
def norm(self):
|
||||||
|
"""
|
||||||
|
Compute the norm of a vector
|
||||||
|
"""
|
||||||
|
return math.sqrt(self.norm2())
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
"""
|
||||||
|
Divides each coordinate of the vector by its norm
|
||||||
|
"""
|
||||||
|
norm = self.norm()
|
||||||
|
if abs(norm) > 0.0001:
|
||||||
|
self.x /= norm
|
||||||
|
self.y /= norm
|
||||||
|
self.z /= norm
|
||||||
|
|
||||||
|
def cross_product(v1, v2):
|
||||||
|
"""
|
||||||
|
Computes the cross product between the two vectors
|
||||||
|
"""
|
||||||
|
return Vector(
|
||||||
|
v1.y * v2.z - v1.z * v2.y,
|
||||||
|
v1.z * v2.x - v1.x * v2.z,
|
||||||
|
v1.x * v2.y - v1.y * v2.x)
|
||||||
|
|
||||||
|
def from_points(v1, v2):
|
||||||
|
"""
|
||||||
|
Creates a vector from two points
|
||||||
|
"""
|
||||||
|
return Vector(
|
||||||
|
v2.x - v1.x,
|
||||||
|
v2.y - v1.y,
|
||||||
|
v2.z - v1.z)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Prints the coordinates of the vector between partheses
|
||||||
|
"""
|
||||||
|
return '(' + ", ".join([str(self.x), str(self.y), str(self.z)]) + ")"
|
||||||
|
|
||||||
|
def dot(self, other):
|
||||||
|
"""
|
||||||
|
Computes the dot product of two vectors
|
||||||
|
"""
|
||||||
|
return self.x * other.x + self.y * other.y + self.z * other.z
|
||||||
|
|
||||||
|
|
334
extensions/fablabchemnitz/papercraft/d3/model/basemodel.py
Normal file
334
extensions/fablabchemnitz/papercraft/d3/model/basemodel.py
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
from math import sqrt
|
||||||
|
from ..geometry import Vector
|
||||||
|
from .mesh import Material, MeshPart
|
||||||
|
|
||||||
|
Vertex = Vector
|
||||||
|
TexCoord = Vertex
|
||||||
|
Normal = Vertex
|
||||||
|
Color = Vertex
|
||||||
|
|
||||||
|
class FaceVertex:
|
||||||
|
"""Contains the information a vertex needs in a face
|
||||||
|
|
||||||
|
In contains the index of the vertex, the index of the texture coordinate
|
||||||
|
and the index of the normal. It is None if it is not available.
|
||||||
|
:param vertex: index of the vertex
|
||||||
|
:param tex_coord: index of the texture coordinate
|
||||||
|
:param normal: index of the normal
|
||||||
|
:param color: index of the color
|
||||||
|
"""
|
||||||
|
def __init__(self, vertex = None, tex_coord = None, normal = None, color = None):
|
||||||
|
"""Initializes a FaceVertex from its indices
|
||||||
|
"""
|
||||||
|
self.vertex = vertex
|
||||||
|
self.tex_coord = tex_coord
|
||||||
|
self.normal = normal
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
def from_array(self, arr):
|
||||||
|
"""Initializes a FaceVertex from an array
|
||||||
|
|
||||||
|
:param arr: can be an array of strings, the first value will be the
|
||||||
|
vertex index, the second will be the texture coordinate index, the
|
||||||
|
third will be the normal index, and the fourth will be the color index.
|
||||||
|
"""
|
||||||
|
self.vertex = int(arr[0]) if len(arr) > 0 else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.tex_coord = int(arr[1]) if len(arr) > 1 else None
|
||||||
|
except:
|
||||||
|
self.tex_coord = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.normal = int(arr[2]) if len(arr) > 2 else None
|
||||||
|
except:
|
||||||
|
self.normal = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.color = int(arr[3]) if len(arr) > 3 else None
|
||||||
|
except:
|
||||||
|
self.color = None
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
class Face:
|
||||||
|
"""Represents a face with 3 vertices
|
||||||
|
|
||||||
|
Faces with more than 3 vertices are not supported in this class. You should
|
||||||
|
split your face first and then create the number needed of instances of
|
||||||
|
this class.
|
||||||
|
"""
|
||||||
|
def __init__(self, a = None, b = None, c = None, material = None):
|
||||||
|
"""Initializes a Face with its three FaceVertex and its Material
|
||||||
|
|
||||||
|
:param a: first FaceVertex element
|
||||||
|
:param b: second FaceVertex element
|
||||||
|
:param c: third FaceVertex element
|
||||||
|
:param material: the material to use with this face
|
||||||
|
"""
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
self.c = c
|
||||||
|
self.material = material
|
||||||
|
|
||||||
|
# Expects array of array
|
||||||
|
def from_array(self, arr):
|
||||||
|
"""Initializes a Face with an array
|
||||||
|
|
||||||
|
:param arr: should be an array of array of objects. Each array will
|
||||||
|
represent a FaceVertex
|
||||||
|
"""
|
||||||
|
self.a = FaceVertex().from_array(arr[0])
|
||||||
|
self.b = FaceVertex().from_array(arr[1])
|
||||||
|
self.c = FaceVertex().from_array(arr[2])
|
||||||
|
return self
|
||||||
|
|
||||||
|
class ModelParser:
|
||||||
|
"""Represents a 3D model
|
||||||
|
"""
|
||||||
|
def __init__(self, up_conversion = None):
|
||||||
|
"""Initializes the model
|
||||||
|
|
||||||
|
:param up_conversion: couple of characters, can be y z or z y
|
||||||
|
"""
|
||||||
|
self.up_conversion = up_conversion
|
||||||
|
self.vertices = []
|
||||||
|
self.colors = []
|
||||||
|
self.normals = []
|
||||||
|
self.tex_coords = []
|
||||||
|
self.parts = []
|
||||||
|
self.materials = []
|
||||||
|
self.current_part = None
|
||||||
|
self.path = None
|
||||||
|
|
||||||
|
def init_textures(self):
|
||||||
|
"""Initializes the textures of the parts of the model
|
||||||
|
|
||||||
|
Basically, calls glGenTexture on each texture
|
||||||
|
"""
|
||||||
|
for part in self.parts:
|
||||||
|
part.init_texture()
|
||||||
|
|
||||||
|
def add_vertex(self, vertex):
|
||||||
|
"""Adds a vertex to the current model
|
||||||
|
|
||||||
|
Will also update its bounding box, and convert the up vector if
|
||||||
|
up_conversion was specified.
|
||||||
|
|
||||||
|
:param vertex: vertex to add to the model
|
||||||
|
"""
|
||||||
|
# Apply up_conversion to the vertex
|
||||||
|
new_vertex = vertex
|
||||||
|
if self.up_conversion is not None:
|
||||||
|
if self.up_conversion[0] == 'y' and self.up_conversion[1] == 'z':
|
||||||
|
new_vertex = Vector(vertex.y, vertex.z, vertex.x)
|
||||||
|
elif self.up_conversion[0] == 'z' and self.up_conversion[1] == 'y':
|
||||||
|
new_vertex = Vector(vertex.z, vertex.x, vertex.y)
|
||||||
|
|
||||||
|
self.vertices.append(new_vertex)
|
||||||
|
|
||||||
|
def add_tex_coord(self, tex_coord):
|
||||||
|
"""Adds a texture coordinate element to the current model
|
||||||
|
|
||||||
|
:param tex_coord: tex_coord to add to the model
|
||||||
|
"""
|
||||||
|
self.tex_coords.append(tex_coord)
|
||||||
|
|
||||||
|
def add_normal(self, normal):
|
||||||
|
"""Adds a normal element to the current model
|
||||||
|
|
||||||
|
:param normal: normal to add to the model
|
||||||
|
"""
|
||||||
|
self.normals.append(normal)
|
||||||
|
|
||||||
|
def add_color(self, color):
|
||||||
|
"""Adds a color element to the current model
|
||||||
|
|
||||||
|
:param color: color to add to the model
|
||||||
|
"""
|
||||||
|
self.colors.append(color)
|
||||||
|
|
||||||
|
def add_face(self, face):
|
||||||
|
"""Adds a face to the current model
|
||||||
|
|
||||||
|
If the face has a different material than the current material, it will
|
||||||
|
create a new mesh part and update the current material.
|
||||||
|
|
||||||
|
:param face: face to add to the model
|
||||||
|
"""
|
||||||
|
if self.current_part is None or (face.material != self.current_part.material and face.material is not None):
|
||||||
|
self.current_part = MeshPart(self)
|
||||||
|
self.current_part.material = face.material if face.material is not None else Material.DEFAULT_MATERIAL
|
||||||
|
self.parts.append(self.current_part)
|
||||||
|
|
||||||
|
self.current_part.add_face(face)
|
||||||
|
|
||||||
|
def parse_file(self, path, chunk_size = 512):
|
||||||
|
"""Sets the path of the model and parse bytes by chunk
|
||||||
|
|
||||||
|
:param path: path to the file to parse
|
||||||
|
:param chunk_size: the file will be read chunk by chunk, each chunk
|
||||||
|
having chunk_size bytes
|
||||||
|
"""
|
||||||
|
self.path = path
|
||||||
|
byte_counter = 0
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
while True:
|
||||||
|
bytes = f.read(chunk_size)
|
||||||
|
if bytes == b'':
|
||||||
|
return
|
||||||
|
self.parse_bytes(bytes, byte_counter)
|
||||||
|
byte_counter += chunk_size
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Draws each part of the model with OpenGL
|
||||||
|
"""
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
|
||||||
|
for part in self.parts:
|
||||||
|
part.draw()
|
||||||
|
|
||||||
|
def generate_vbos(self):
|
||||||
|
"""Generates the VBOs of each part of the model
|
||||||
|
"""
|
||||||
|
for part in self.parts:
|
||||||
|
part.generate_vbos()
|
||||||
|
|
||||||
|
def generate_vertex_normals(self):
|
||||||
|
"""Generate the normals for each vertex of the model
|
||||||
|
|
||||||
|
A normal will be the average normal of the adjacent faces of a vertex.
|
||||||
|
"""
|
||||||
|
self.normals = [Normal() for i in self.vertices]
|
||||||
|
|
||||||
|
for part in self.parts:
|
||||||
|
for face in part.faces:
|
||||||
|
v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex])
|
||||||
|
v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex])
|
||||||
|
v1.normalize()
|
||||||
|
v2.normalize()
|
||||||
|
cross = Vertex.cross_product(v1, v2)
|
||||||
|
self.normals[face.a.vertex] += cross
|
||||||
|
self.normals[face.b.vertex] += cross
|
||||||
|
self.normals[face.c.vertex] += cross
|
||||||
|
|
||||||
|
for normal in self.normals:
|
||||||
|
normal.normalize()
|
||||||
|
|
||||||
|
for part in self.parts:
|
||||||
|
for face in part.faces:
|
||||||
|
face.a.normal = face.a.vertex
|
||||||
|
face.b.normal = face.b.vertex
|
||||||
|
face.c.normal = face.c.vertex
|
||||||
|
|
||||||
|
def generate_face_normals(self):
|
||||||
|
"""Generate the normals for each face of the model
|
||||||
|
|
||||||
|
A normal will be the normal of the face
|
||||||
|
"""
|
||||||
|
# Build array of faces
|
||||||
|
faces = sum(map(lambda x: x.faces, self.parts), [])
|
||||||
|
self.normals = [Normal()] * len(faces)
|
||||||
|
|
||||||
|
for (index, face) in enumerate(faces):
|
||||||
|
|
||||||
|
v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex])
|
||||||
|
v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex])
|
||||||
|
cross = Vertex.cross_product(v1, v2)
|
||||||
|
cross.normalize()
|
||||||
|
self.normals[index] = cross
|
||||||
|
|
||||||
|
face.a.normal = index
|
||||||
|
face.b.normal = index
|
||||||
|
face.c.normal = index
|
||||||
|
|
||||||
|
def get_material_index(self, material):
|
||||||
|
"""Finds the index of the given material
|
||||||
|
|
||||||
|
:param material: Material you want the index of
|
||||||
|
"""
|
||||||
|
return [i for (i,m) in enumerate(self.materials) if m.name == material.name][0]
|
||||||
|
|
||||||
|
class TextModelParser(ModelParser):
|
||||||
|
def parse_file(self, path):
|
||||||
|
"""Sets the path of the model and parse each line
|
||||||
|
|
||||||
|
:param path: path to the text file to parse
|
||||||
|
"""
|
||||||
|
self.path = path
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
line = line.rstrip()
|
||||||
|
if line != '':
|
||||||
|
self.parse_line(line)
|
||||||
|
|
||||||
|
|
||||||
|
class BoundingBox:
|
||||||
|
"""Represents a bounding box of a 3D model
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""Initializes the coordinates of the bounding box
|
||||||
|
"""
|
||||||
|
self.min_x = +float('inf')
|
||||||
|
self.min_y = +float('inf')
|
||||||
|
self.min_z = +float('inf')
|
||||||
|
|
||||||
|
self.max_x = -float('inf')
|
||||||
|
self.max_y = -float('inf')
|
||||||
|
self.max_z = -float('inf')
|
||||||
|
|
||||||
|
def add(self, vector):
|
||||||
|
"""Adds a vector to a bounding box
|
||||||
|
|
||||||
|
If the vector is outside the bounding box, the bounding box will be
|
||||||
|
enlarged, otherwise, nothing will happen.
|
||||||
|
|
||||||
|
:param vector: the vector that will enlarge the bounding box
|
||||||
|
"""
|
||||||
|
self.min_x = min(self.min_x, vector.x)
|
||||||
|
self.min_y = min(self.min_y, vector.y)
|
||||||
|
self.min_z = min(self.min_z, vector.z)
|
||||||
|
|
||||||
|
self.max_x = max(self.max_x, vector.x)
|
||||||
|
self.max_y = max(self.max_y, vector.y)
|
||||||
|
self.max_z = max(self.max_z, vector.z)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Returns a string that represents the bounding box
|
||||||
|
"""
|
||||||
|
return "[{},{}],[{},{}],[{},{}]".format(
|
||||||
|
self.min_x,
|
||||||
|
self.min_y,
|
||||||
|
self.min_z,
|
||||||
|
self.max_x,
|
||||||
|
self.max_y,
|
||||||
|
self.max_z)
|
||||||
|
|
||||||
|
def get_center(self):
|
||||||
|
"""Returns the center of the bounding box
|
||||||
|
"""
|
||||||
|
return Vertex(
|
||||||
|
(self.min_x + self.max_x) / 2,
|
||||||
|
(self.min_y + self.max_y) / 2,
|
||||||
|
(self.min_z + self.max_z) / 2)
|
||||||
|
|
||||||
|
def get_scale(self):
|
||||||
|
"""Returns the maximum edge of the bounding box
|
||||||
|
"""
|
||||||
|
return max(
|
||||||
|
abs(self.max_x - self.min_x),
|
||||||
|
abs(self.max_y - self.min_y),
|
||||||
|
abs(self.max_z - self.min_z))
|
||||||
|
|
||||||
|
|
||||||
|
class Exporter:
|
||||||
|
"""Represents an object that can export a model into a certain format
|
||||||
|
"""
|
||||||
|
def __init__(self, model):
|
||||||
|
"""Creates a exporter for the model
|
||||||
|
|
||||||
|
:param model: model to export
|
||||||
|
"""
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
from os.path import dirname, basename, isfile
|
||||||
|
import glob
|
||||||
|
modules = glob.glob(dirname(__file__)+"/*.py")
|
||||||
|
__all__ = [ basename(f)[:-3] for f in modules if isfile(f)]
|
208
extensions/fablabchemnitz/papercraft/d3/model/formats/obj.py
Normal file
208
extensions/fablabchemnitz/papercraft/d3/model/formats/obj.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
from ..basemodel import TextModelParser, Exporter, Vertex, TexCoord, Normal, FaceVertex, Face
|
||||||
|
from ..mesh import Material, MeshPart
|
||||||
|
from functools import reduce
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def is_obj(filename):
|
||||||
|
"""Checks that the file is a .obj file
|
||||||
|
|
||||||
|
Only checks the extension of the file
|
||||||
|
:param filename: path to the file
|
||||||
|
"""
|
||||||
|
return filename[-4:] == '.obj'
|
||||||
|
|
||||||
|
class OBJParser(TextModelParser):
|
||||||
|
"""Parser that parses a .obj file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, up_conversion = None):
|
||||||
|
super().__init__(up_conversion)
|
||||||
|
self.current_material = None
|
||||||
|
self.mtl = None
|
||||||
|
self.vertex_offset = 0
|
||||||
|
|
||||||
|
def parse_line(self, string):
|
||||||
|
"""Parses a line of .obj file
|
||||||
|
|
||||||
|
:param string: the line to parse
|
||||||
|
"""
|
||||||
|
if string == '':
|
||||||
|
return
|
||||||
|
|
||||||
|
split = string.split()
|
||||||
|
first = split[0]
|
||||||
|
split = split[1:]
|
||||||
|
|
||||||
|
if first == 'usemtl' and self.mtl is not None:
|
||||||
|
self.current_material = self.mtl[split[0]]
|
||||||
|
elif first == 'mtllib':
|
||||||
|
path = os.path.join(os.path.dirname(self.path), ' '.join(split[:]))
|
||||||
|
if os.path.isfile(path):
|
||||||
|
self.mtl = MTLParser(self)
|
||||||
|
self.mtl.parse_file(path)
|
||||||
|
else:
|
||||||
|
print('Warning : ' + path + ' not found ', file=sys.stderr)
|
||||||
|
elif first == 'v':
|
||||||
|
self.add_vertex(Vertex().from_array(split))
|
||||||
|
elif first == 'vn':
|
||||||
|
self.add_normal(Normal().from_array(split))
|
||||||
|
elif first == 'vt':
|
||||||
|
self.add_tex_coord(TexCoord().from_array(split))
|
||||||
|
elif first == 'f':
|
||||||
|
splits = list(map(lambda x: x.split('/'), split))
|
||||||
|
|
||||||
|
for i in range(len(splits)):
|
||||||
|
for j in range(len(splits[i])):
|
||||||
|
if splits[i][j] != '':
|
||||||
|
splits[i][j] = int(splits[i][j])
|
||||||
|
if splits[i][j] > 0:
|
||||||
|
splits[i][j] -= 1
|
||||||
|
else:
|
||||||
|
splits[i][j] = len(self.vertices) + splits[i][j]
|
||||||
|
|
||||||
|
# if Face3
|
||||||
|
if len(split) == 3:
|
||||||
|
face = Face().from_array(splits)
|
||||||
|
face.material = self.current_material
|
||||||
|
self.add_face(face)
|
||||||
|
|
||||||
|
# Face4 are well supported with the next stuff
|
||||||
|
# elif len(split) == 4:
|
||||||
|
# face = Face().from_array(splits[:3])
|
||||||
|
# face.material = self.current_material
|
||||||
|
# self.add_face(face)
|
||||||
|
|
||||||
|
# face = Face().from_array([splits[0], splits[2], splits[3]])
|
||||||
|
# face.material = self.current_material
|
||||||
|
# self.add_face(face)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Bweeee
|
||||||
|
# First, lets compute all the FaceVertex for each vertex
|
||||||
|
face_vertices = []
|
||||||
|
for face_vertex in splits[:]:
|
||||||
|
face_vertices.append(FaceVertex(*face_vertex))
|
||||||
|
|
||||||
|
# Then, we build the faces 0 i i+1 for each 1 <= i < len - 1
|
||||||
|
for i in range(1, len(face_vertices) - 1):
|
||||||
|
|
||||||
|
# Create face with barycenter, i and i + 1
|
||||||
|
face = Face(face_vertices[0], face_vertices[i], face_vertices[i+1])
|
||||||
|
face.material = self.current_material
|
||||||
|
self.add_face(face)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MTLParser:
|
||||||
|
"""Parser that parses a .mtl material file
|
||||||
|
"""
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""Creates a MTLParser bound to the OBJParser
|
||||||
|
|
||||||
|
:param parent: the OBJParser this MTLParser refers to
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
self.current_mtl = None
|
||||||
|
|
||||||
|
def parse_line(self, string):
|
||||||
|
"""Parses a line of .mtl file
|
||||||
|
|
||||||
|
:param string: line to parse
|
||||||
|
"""
|
||||||
|
|
||||||
|
if string == '':
|
||||||
|
return
|
||||||
|
|
||||||
|
split = string.split()
|
||||||
|
first = split[0]
|
||||||
|
split = split[1:]
|
||||||
|
|
||||||
|
if first == 'newmtl':
|
||||||
|
self.current_mtl = Material(' '.join(split[:]))
|
||||||
|
self.parent.materials.append(self.current_mtl)
|
||||||
|
elif first == 'Ka':
|
||||||
|
self.current_mtl.Ka = Vertex().from_array(split)
|
||||||
|
elif first == 'Kd':
|
||||||
|
self.current_mtl.Kd = Vertex().from_array(split)
|
||||||
|
elif first == 'Ks':
|
||||||
|
self.current_mtl.Ks = Vertex().from_array(split)
|
||||||
|
elif first == 'map_Kd':
|
||||||
|
self.current_mtl.relative_path_to_texture = ' '.join(split)
|
||||||
|
self.current_mtl.absolute_path_to_texture = os.path.join(os.path.dirname(self.parent.path), ' '.join(split))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_file(self, path):
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
line = line.rstrip()
|
||||||
|
self.parse_line(line)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
for material in self.parent.materials:
|
||||||
|
if material.name == key:
|
||||||
|
return material
|
||||||
|
|
||||||
|
|
||||||
|
class OBJExporter(Exporter):
|
||||||
|
"""Exporter to .obj format
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model):
|
||||||
|
"""Creates an exporter from the model
|
||||||
|
|
||||||
|
:param model: Model to export
|
||||||
|
"""
|
||||||
|
super().__init__(model)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Exports the model
|
||||||
|
"""
|
||||||
|
current_material = ''
|
||||||
|
string = ""
|
||||||
|
|
||||||
|
for vertex in self.model.vertices:
|
||||||
|
string += "v " + ' '.join([str(vertex.x), str(vertex.y), str(vertex.z)]) + "\n"
|
||||||
|
|
||||||
|
string += "\n"
|
||||||
|
|
||||||
|
if len(self.model.tex_coords) > 0:
|
||||||
|
for tex_coord in self.model.tex_coords:
|
||||||
|
string += "vt " + ' '.join([str(tex_coord.x), str(tex_coord.y)]) + "\n"
|
||||||
|
|
||||||
|
string += "\n"
|
||||||
|
|
||||||
|
if len(self.model.normals) > 0:
|
||||||
|
for normal in self.model.normals:
|
||||||
|
string += "vn " + ' '.join([str(normal.x), str(normal.y), str(normal.z)]) + "\n"
|
||||||
|
|
||||||
|
string += "\n"
|
||||||
|
|
||||||
|
faces = sum(map(lambda x: x.faces, self.model.parts), [])
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
if face.material is not None and face.material.name != current_material:
|
||||||
|
current_material = face.material.name
|
||||||
|
string += "usemtl " + current_material + "\n"
|
||||||
|
string += "f "
|
||||||
|
arr = []
|
||||||
|
for v in [face.a, face.b, face.c]:
|
||||||
|
sub_arr = []
|
||||||
|
sub_arr.append(str(v.vertex + 1))
|
||||||
|
if v.normal is None:
|
||||||
|
if v.tex_coord is not None:
|
||||||
|
sub_arr.append('')
|
||||||
|
sub_arr.append(str(v.tex_coord + 1))
|
||||||
|
elif v.tex_coord is not None:
|
||||||
|
sub_arr.append(str(v.tex_coord + 1))
|
||||||
|
if v.normal is not None:
|
||||||
|
sub_arr.append(str(v.normal + 1))
|
||||||
|
arr.append('/'.join(sub_arr))
|
||||||
|
|
||||||
|
string += ' '.join(arr) + '\n'
|
||||||
|
return string
|
||||||
|
|
65
extensions/fablabchemnitz/papercraft/d3/model/formats/off.py
Normal file
65
extensions/fablabchemnitz/papercraft/d3/model/formats/off.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from ..basemodel import TextModelParser, Exporter, Vertex, TexCoord, Normal, FaceVertex, Face
|
||||||
|
from ..mesh import Material, MeshPart
|
||||||
|
|
||||||
|
def is_off(filename):
|
||||||
|
"""Checks that the file is a .off file
|
||||||
|
|
||||||
|
Only checks the extension of the file
|
||||||
|
:param filename: path to the file
|
||||||
|
"""
|
||||||
|
return filename[-4:] == '.off'
|
||||||
|
|
||||||
|
class OFFParser(TextModelParser):
|
||||||
|
"""Parser that parses a .off file
|
||||||
|
"""
|
||||||
|
def __init__(self, up_conversion = None):
|
||||||
|
super().__init__(up_conversion)
|
||||||
|
self.vertex_number = None
|
||||||
|
self.face_number = None
|
||||||
|
self.edge_number = None
|
||||||
|
|
||||||
|
def parse_line(self, string):
|
||||||
|
"""Parses a line of .off file
|
||||||
|
|
||||||
|
:param string: the line to parse
|
||||||
|
"""
|
||||||
|
split = string.split()
|
||||||
|
|
||||||
|
if string == '' or string == 'OFF':
|
||||||
|
pass
|
||||||
|
elif self.vertex_number is None:
|
||||||
|
# The first will be the header
|
||||||
|
self.vertex_number = int(split[0])
|
||||||
|
self.face_number = int(split[1])
|
||||||
|
self.edge_number = int(split[2])
|
||||||
|
elif len(self.vertices) < self.vertex_number:
|
||||||
|
self.add_vertex(Vertex().from_array(split))
|
||||||
|
else:
|
||||||
|
self.add_face(Face(FaceVertex(int(split[1])), FaceVertex(int(split[2])), FaceVertex(int(split[3]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class OFFExporter(Exporter):
|
||||||
|
"""Exporter to .off format
|
||||||
|
"""
|
||||||
|
def __init__(self, model):
|
||||||
|
"""Creates an exporter from the model
|
||||||
|
|
||||||
|
:param model: Model to export
|
||||||
|
"""
|
||||||
|
super().__init__(model)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Exports the model
|
||||||
|
"""
|
||||||
|
faces = sum(map(lambda x: x.faces, self.model.parts), [])
|
||||||
|
string = "OFF\n{} {} {}".format(len(self.model.vertices), len(faces), 0) + '\n'
|
||||||
|
|
||||||
|
for vertex in self.model.vertices:
|
||||||
|
string += ' '.join([str(vertex.x), str(vertex.y), str(vertex.z)]) + '\n'
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
string += '3 ' + ' '.join([str(face.a.vertex), str(face.b.vertex), str(face.c.vertex)]) + '\n'
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
494
extensions/fablabchemnitz/papercraft/d3/model/formats/ply.py
Normal file
494
extensions/fablabchemnitz/papercraft/d3/model/formats/ply.py
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
from ..basemodel import ModelParser, TextModelParser, Exporter, Vertex, Face, Color, FaceVertex, TexCoord, Material
|
||||||
|
|
||||||
|
class UnkownTypeError(Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def is_ply(filename):
|
||||||
|
"""Checks that the file is a .ply file
|
||||||
|
|
||||||
|
Only checks the extension of the file
|
||||||
|
:param filename: path to the file
|
||||||
|
"""
|
||||||
|
return filename[-4:] == '.ply'
|
||||||
|
|
||||||
|
# List won't work with this function
|
||||||
|
def _ply_type_size(type):
|
||||||
|
"""Returns the size of a ply property
|
||||||
|
|
||||||
|
:param type: a string that is in a ply element
|
||||||
|
"""
|
||||||
|
if type == 'char' or type == 'uchar':
|
||||||
|
return 1
|
||||||
|
elif type == 'short' or type == 'ushort':
|
||||||
|
return 2
|
||||||
|
elif type == 'int' or type == 'uint':
|
||||||
|
return 4
|
||||||
|
elif type == 'float':
|
||||||
|
return 4
|
||||||
|
elif type == 'double':
|
||||||
|
return 8
|
||||||
|
else:
|
||||||
|
raise UnkownTypeError('Type ' + type + ' is unknown')
|
||||||
|
|
||||||
|
def ply_type_size(type):
|
||||||
|
"""Returns the list containing the sizes of the elements
|
||||||
|
|
||||||
|
:param type: a string that is in a ply element
|
||||||
|
"""
|
||||||
|
split = type.split()
|
||||||
|
|
||||||
|
if len(split) == 1:
|
||||||
|
return [_ply_type_size(type)]
|
||||||
|
else:
|
||||||
|
if split[0] != 'list':
|
||||||
|
print('You have multiple types but it\'s not a list...', file=sys.stderr)
|
||||||
|
sys.exit(-1)
|
||||||
|
else:
|
||||||
|
return list(map(lambda a: _ply_type_size(a), split[1:]))
|
||||||
|
|
||||||
|
|
||||||
|
def bytes_to_element(type, bytes, byteorder = 'little'):
|
||||||
|
"""Returns a python object parsed from bytes
|
||||||
|
|
||||||
|
:param type: the type of the object to parse
|
||||||
|
:param bytes: the bytes to read
|
||||||
|
:param byteorder: little or big endian
|
||||||
|
"""
|
||||||
|
if type == 'char':
|
||||||
|
return ord(struct.unpack('<b', bytes)[0])
|
||||||
|
if type == 'uchar':
|
||||||
|
return ord(struct.unpack('<c', bytes)[0])
|
||||||
|
elif type == 'short':
|
||||||
|
return struct.unpack('<h', bytes)[0]
|
||||||
|
elif type == 'ushort':
|
||||||
|
return struct.unpack('<H', bytes)[0]
|
||||||
|
elif type == 'int':
|
||||||
|
return struct.unpack('<i', bytes)[0]
|
||||||
|
elif type == 'uint':
|
||||||
|
return struct.unpack('<I', bytes)[0]
|
||||||
|
elif type == 'float':
|
||||||
|
return struct.unpack('<f', bytes)[0]
|
||||||
|
elif type == 'double':
|
||||||
|
return struct.unpack('<d', bytes)[0]
|
||||||
|
else:
|
||||||
|
raise UnkownTypeError('Type ' + type + ' is unknown')
|
||||||
|
|
||||||
|
class PLYParser(ModelParser):
|
||||||
|
"""Parser that parses a .ply file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, up_conversion = None):
|
||||||
|
super().__init__(up_conversion)
|
||||||
|
self.counter = 0
|
||||||
|
self.elements = []
|
||||||
|
self.inner_parser = PLYHeaderParser(self)
|
||||||
|
self.beginning_of_line = ''
|
||||||
|
self.header_finished = False
|
||||||
|
|
||||||
|
def parse_bytes(self, bytes, byte_counter):
|
||||||
|
"""Parses bytes of a .ply file
|
||||||
|
"""
|
||||||
|
if self.header_finished:
|
||||||
|
self.inner_parser.parse_bytes(self.beginning_of_line + bytes, byte_counter - len(self.beginning_of_line))
|
||||||
|
self.beginning_of_line = b''
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build lines for header and use PLYHeaderParser
|
||||||
|
current_line = self.beginning_of_line
|
||||||
|
for (i, c) in enumerate(bytes):
|
||||||
|
char = chr(c)
|
||||||
|
if char == '\n':
|
||||||
|
self.inner_parser.parse_line(current_line)
|
||||||
|
if current_line == 'end_header':
|
||||||
|
self.header_finished = True
|
||||||
|
self.beginning_of_line = bytes[i+1:]
|
||||||
|
return
|
||||||
|
current_line = ''
|
||||||
|
else:
|
||||||
|
current_line += chr(c)
|
||||||
|
self.beginning_of_line = current_line
|
||||||
|
|
||||||
|
class PLYHeaderParser:
|
||||||
|
"""Parser that parses the header of a .ply file
|
||||||
|
"""
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.current_element = None
|
||||||
|
self.parent = parent
|
||||||
|
self.content_parser = None
|
||||||
|
|
||||||
|
def parse_line(self, string):
|
||||||
|
split = string.split()
|
||||||
|
if string == 'ply':
|
||||||
|
return
|
||||||
|
|
||||||
|
elif split[0] == 'format':
|
||||||
|
if split[2] != '1.0':
|
||||||
|
print('Only format 1.0 is supported', file=sys.stderr)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
if split[1] == 'ascii':
|
||||||
|
self.content_parser = PLY_ASCII_ContentParser(self.parent)
|
||||||
|
elif split[1] == 'binary_little_endian':
|
||||||
|
self.content_parser = PLYLittleEndianContentParser(self.parent)
|
||||||
|
elif split[1] == 'binary_big_endian':
|
||||||
|
self.content_parser = PLYBigEndianContentParser(self.parent)
|
||||||
|
else:
|
||||||
|
print('Only ascii, binary_little_endian and binary_big_endian are supported', \
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
elif split[0] == 'element':
|
||||||
|
self.current_element = PLYElement(split[1], int(split[2]))
|
||||||
|
self.parent.elements.append(self.current_element)
|
||||||
|
|
||||||
|
elif split[0] == 'property':
|
||||||
|
self.current_element.add_property(split[-1], ' '.join(split[1:-1]))
|
||||||
|
|
||||||
|
elif split[0] == 'end_header':
|
||||||
|
self.parent.inner_parser = self.content_parser
|
||||||
|
|
||||||
|
elif split[0] == 'comment' and split[1] == 'TextureFile':
|
||||||
|
material = Material('mat' + str(len(self.parent.materials)))
|
||||||
|
self.parent.materials.append(material)
|
||||||
|
material.relative_path_to_texture = split[2]
|
||||||
|
material.absolute_path_to_texture = os.path.join(os.path.dirname(self.parent.path), split[2])
|
||||||
|
|
||||||
|
class PLYElement:
|
||||||
|
def __init__(self, name, number):
|
||||||
|
self.name = name
|
||||||
|
self.number = number
|
||||||
|
self.properties = []
|
||||||
|
|
||||||
|
def add_property(self, name, type):
|
||||||
|
self.properties.append((name, type))
|
||||||
|
|
||||||
|
class PLY_ASCII_ContentParser:
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.element_index = 0
|
||||||
|
self.counter = 0
|
||||||
|
self.current_element = None
|
||||||
|
self.beginning_of_line = ''
|
||||||
|
|
||||||
|
def parse_bytes(self, bytes, byte_counter):
|
||||||
|
current_line = self.beginning_of_line
|
||||||
|
for (i, c) in enumerate(bytes):
|
||||||
|
char = chr(c)
|
||||||
|
if char == '\n':
|
||||||
|
self.parse_line(current_line)
|
||||||
|
current_line = ''
|
||||||
|
else:
|
||||||
|
current_line += chr(c)
|
||||||
|
self.beginning_of_line = current_line
|
||||||
|
|
||||||
|
|
||||||
|
def parse_line(self, string):
|
||||||
|
|
||||||
|
if string == '':
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.current_element is None:
|
||||||
|
self.current_element = self.parent.elements[0]
|
||||||
|
|
||||||
|
split = string.split()
|
||||||
|
color = None
|
||||||
|
|
||||||
|
if self.current_element.name == 'vertex':
|
||||||
|
|
||||||
|
vertex = Vertex()
|
||||||
|
red = None
|
||||||
|
blue = None
|
||||||
|
green = None
|
||||||
|
alpha = None
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
for property in self.current_element.properties:
|
||||||
|
|
||||||
|
if property[0] == 'x':
|
||||||
|
vertex.x = float(split[offset])
|
||||||
|
elif property[0] == 'y':
|
||||||
|
vertex.y = float(split[offset])
|
||||||
|
elif property[0] == 'z':
|
||||||
|
vertex.z = float(split[offset])
|
||||||
|
elif property[0] == 'red':
|
||||||
|
red = float(split[offset]) / 255
|
||||||
|
elif property[0] == 'green':
|
||||||
|
green = float(split[offset]) / 255
|
||||||
|
elif property[0] == 'blue':
|
||||||
|
blue = float(split[offset]) / 255
|
||||||
|
elif property[0] == 'alpha':
|
||||||
|
alpha = float(split[offset]) / 255
|
||||||
|
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
self.parent.add_vertex(vertex)
|
||||||
|
|
||||||
|
if red is not None:
|
||||||
|
color = Color(red, blue, green)
|
||||||
|
self.parent.add_color(color)
|
||||||
|
|
||||||
|
elif self.current_element.name == 'face':
|
||||||
|
|
||||||
|
faceVertexArray = []
|
||||||
|
current_material = None
|
||||||
|
|
||||||
|
# Analyse element
|
||||||
|
offset = 0
|
||||||
|
for property in self.current_element.properties:
|
||||||
|
|
||||||
|
if property[0] == 'vertex_indices':
|
||||||
|
for i in range(int(split[offset])):
|
||||||
|
faceVertexArray.append(FaceVertex(int(split[i+offset+1])))
|
||||||
|
offset += int(split[0]) + 1
|
||||||
|
|
||||||
|
elif property[0] == 'texcoord':
|
||||||
|
offset += 1
|
||||||
|
for i in range(3):
|
||||||
|
# Create corresponding tex_coords
|
||||||
|
tex_coord = TexCoord().from_array(split[offset:offset+2])
|
||||||
|
offset += 2
|
||||||
|
self.parent.add_tex_coord(tex_coord)
|
||||||
|
faceVertexArray[i].tex_coord = len(self.parent.tex_coords) - 1
|
||||||
|
|
||||||
|
elif property[0] == 'texnumber':
|
||||||
|
current_material = self.parent.materials[int(split[offset])]
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
face = Face(*faceVertexArray)
|
||||||
|
face.material = current_material
|
||||||
|
self.parent.add_face(face)
|
||||||
|
|
||||||
|
self.counter += 1
|
||||||
|
|
||||||
|
if self.counter == self.current_element.number:
|
||||||
|
self.next_element()
|
||||||
|
|
||||||
|
def next_element(self):
|
||||||
|
self.element_index += 1
|
||||||
|
if self.element_index < len(self.parent.elements):
|
||||||
|
self.current_element = self.parent.elements[self.element_index]
|
||||||
|
|
||||||
|
class PLYLittleEndianContentParser:
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.previous_bytes = b''
|
||||||
|
self.element_index = 0
|
||||||
|
self.counter = 0
|
||||||
|
self.current_element = None
|
||||||
|
self.started = False
|
||||||
|
|
||||||
|
# Serves for debugging purposes
|
||||||
|
# self.current_byte = 0
|
||||||
|
|
||||||
|
def parse_bytes(self, bytes, byte_counter):
|
||||||
|
|
||||||
|
if not self.started:
|
||||||
|
# self.current_byte = byte_counter
|
||||||
|
self.started = True
|
||||||
|
|
||||||
|
if self.current_element is None:
|
||||||
|
self.current_element = self.parent.elements[0]
|
||||||
|
|
||||||
|
bytes = self.previous_bytes + bytes
|
||||||
|
current_byte_index = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
property_values = []
|
||||||
|
|
||||||
|
beginning_byte_index = current_byte_index
|
||||||
|
|
||||||
|
for property in self.current_element.properties:
|
||||||
|
|
||||||
|
size = ply_type_size(property[1])
|
||||||
|
|
||||||
|
if current_byte_index + size[0] > len(bytes):
|
||||||
|
self.previous_bytes = bytes[beginning_byte_index:]
|
||||||
|
# self.current_byte -= len(self.previous_bytes)
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(size) == 1:
|
||||||
|
|
||||||
|
size = size[0]
|
||||||
|
|
||||||
|
current_property_bytes = bytes[current_byte_index:current_byte_index+size]
|
||||||
|
property_values.append(bytes_to_element(property[1], current_property_bytes))
|
||||||
|
current_byte_index += size
|
||||||
|
# self.current_byte += size
|
||||||
|
|
||||||
|
elif len(size) == 2:
|
||||||
|
|
||||||
|
types = property[1].split()[1:]
|
||||||
|
current_property_bytes = bytes[current_byte_index:current_byte_index+size[0]]
|
||||||
|
number_of_elements = bytes_to_element(types[0], current_property_bytes)
|
||||||
|
current_byte_index += size[0]
|
||||||
|
# self.current_byte += size[0]
|
||||||
|
|
||||||
|
property_values.append([])
|
||||||
|
|
||||||
|
# Parse list
|
||||||
|
for i in range(number_of_elements):
|
||||||
|
|
||||||
|
if current_byte_index + size[1] > len(bytes):
|
||||||
|
|
||||||
|
self.previous_bytes = bytes[beginning_byte_index:]
|
||||||
|
# self.current_byte -= len(self.previous_bytes)
|
||||||
|
return
|
||||||
|
|
||||||
|
current_property_bytes = bytes[current_byte_index:current_byte_index+size[1]]
|
||||||
|
property_values[-1].append(bytes_to_element(types[1], current_property_bytes))
|
||||||
|
current_byte_index += size[1]
|
||||||
|
# self.current_byte += size[1]
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('I have not idea what this means', file=sys.stderr)
|
||||||
|
|
||||||
|
# Add element
|
||||||
|
if self.current_element.name == 'vertex':
|
||||||
|
|
||||||
|
vertex = Vertex()
|
||||||
|
red = None
|
||||||
|
green = None
|
||||||
|
blue = None
|
||||||
|
alpha = None
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
for property in self.current_element.properties:
|
||||||
|
|
||||||
|
if property[0] == 'x':
|
||||||
|
vertex.x = property_values[offset]
|
||||||
|
elif property[0] == 'y':
|
||||||
|
vertex.y = property_values[offset]
|
||||||
|
elif property[0] == 'z':
|
||||||
|
vertex.z = property_values[offset]
|
||||||
|
elif property[0] == 'red':
|
||||||
|
red = property_values[offset] / 255
|
||||||
|
elif property[0] == 'green':
|
||||||
|
green = property_values[offset] / 255
|
||||||
|
elif property[0] == 'blue':
|
||||||
|
blue = property_values[offset] / 255
|
||||||
|
elif property[0] == 'alpha':
|
||||||
|
alpha = property_values[offset] / 255
|
||||||
|
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
self.parent.add_vertex(vertex)
|
||||||
|
|
||||||
|
if red is not None:
|
||||||
|
self.parent.add_color(Color(red, blue, green))
|
||||||
|
|
||||||
|
elif self.current_element.name == 'face':
|
||||||
|
|
||||||
|
vertex_indices = []
|
||||||
|
tex_coords = []
|
||||||
|
material = None
|
||||||
|
|
||||||
|
for (i, property) in enumerate(self.current_element.properties):
|
||||||
|
|
||||||
|
if property[0] == 'vertex_indices':
|
||||||
|
vertex_indices.append(property_values[i][0])
|
||||||
|
vertex_indices.append(property_values[i][1])
|
||||||
|
vertex_indices.append(property_values[i][2])
|
||||||
|
|
||||||
|
elif property[0] == 'texcoord':
|
||||||
|
# Create texture coords
|
||||||
|
for j in range(0,6,2):
|
||||||
|
tex_coord = TexCoord(*property_values[i][j:j+2])
|
||||||
|
tex_coords.append(tex_coord)
|
||||||
|
|
||||||
|
elif property[0] == 'texnumber':
|
||||||
|
material = self.parent.materials[property_values[i]]
|
||||||
|
|
||||||
|
for tex_coord in tex_coords:
|
||||||
|
self.parent.add_tex_coord(tex_coord)
|
||||||
|
|
||||||
|
face = Face(*list(map(lambda x: FaceVertex(x), vertex_indices)))
|
||||||
|
|
||||||
|
counter = 3
|
||||||
|
if len(tex_coords) > 0:
|
||||||
|
for face_vertex in [face.a, face.b, face.c]:
|
||||||
|
face_vertex.tex_coord = len(self.parent.tex_coords) - counter
|
||||||
|
counter -= 1
|
||||||
|
|
||||||
|
if material is None and len(self.parent.materials) == 1:
|
||||||
|
material = self.parent.materials[0]
|
||||||
|
|
||||||
|
face.material = material
|
||||||
|
|
||||||
|
self.parent.add_face(face)
|
||||||
|
|
||||||
|
self.counter += 1
|
||||||
|
|
||||||
|
if self.counter == self.current_element.number:
|
||||||
|
self.next_element()
|
||||||
|
|
||||||
|
def next_element(self):
|
||||||
|
self.counter = 0
|
||||||
|
self.element_index += 1
|
||||||
|
if self.element_index < len(self.parent.elements):
|
||||||
|
self.current_element = self.parent.elements[self.element_index]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PLYBigEndianContentParser(PLYLittleEndianContentParser):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(self, parent)
|
||||||
|
|
||||||
|
def parse_bytes(self, bytes):
|
||||||
|
# Reverse bytes, and then
|
||||||
|
super().parse_bytes(self, bytes)
|
||||||
|
|
||||||
|
class PLYExporter(Exporter):
|
||||||
|
def __init__(self, model):
|
||||||
|
super().__init__(model)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
faces = sum([part.faces for part in self.model.parts], [])
|
||||||
|
|
||||||
|
# Header
|
||||||
|
string = "ply\nformat ascii 1.0\ncomment Automatically gnerated by model-converter\n"
|
||||||
|
|
||||||
|
for material in self.model.materials:
|
||||||
|
string += "comment TextureFile " + (material.relative_path_to_texture or 'None') + "\n"
|
||||||
|
|
||||||
|
# Types : vertices
|
||||||
|
string += "element vertex " + str(len(self.model.vertices)) +"\n"
|
||||||
|
string += "property float x\nproperty float y\nproperty float z\n"
|
||||||
|
|
||||||
|
# Types : faces
|
||||||
|
string += "element face " + str(len(faces)) + "\n"
|
||||||
|
string += "property list uchar int vertex_indices\n"
|
||||||
|
|
||||||
|
if len(self.model.tex_coords) > 0:
|
||||||
|
string += "property list uchar float texcoord\n"
|
||||||
|
string += "property int texnumber\n"
|
||||||
|
|
||||||
|
# End header
|
||||||
|
string += "end_header\n"
|
||||||
|
|
||||||
|
# Content of the model
|
||||||
|
for vertex in self.model.vertices:
|
||||||
|
string += str(vertex.x) + " " + str(vertex.y) + " " + str(vertex.z) + "\n"
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
string += "3 " + str(face.a.vertex) + " " + str(face.b.vertex) + " " + str(face.c.vertex)
|
||||||
|
|
||||||
|
if len(self.model.tex_coords) > 0:
|
||||||
|
string += " 6 " \
|
||||||
|
+ str(self.model.tex_coords[face.a.tex_coord].x) + " " \
|
||||||
|
+ str(self.model.tex_coords[face.a.tex_coord].y) + " " \
|
||||||
|
+ str(self.model.tex_coords[face.b.tex_coord].x) + " " \
|
||||||
|
+ str(self.model.tex_coords[face.b.tex_coord].y) + " " \
|
||||||
|
+ str(self.model.tex_coords[face.c.tex_coord].x) + " " \
|
||||||
|
+ str(self.model.tex_coords[face.c.tex_coord].y) + " " \
|
||||||
|
+ str(self.model.get_material_index(face.material))
|
||||||
|
|
||||||
|
string += "\n"
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
116
extensions/fablabchemnitz/papercraft/d3/model/formats/stl.py
Normal file
116
extensions/fablabchemnitz/papercraft/d3/model/formats/stl.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
from ..basemodel import TextModelParser, Exporter, Vertex, FaceVertex, Face
|
||||||
|
from ..mesh import MeshPart
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
def is_stl(filename):
|
||||||
|
"""Checks that the file is a .stl file
|
||||||
|
|
||||||
|
Only checks the extension of the file
|
||||||
|
:param filename: path to the file
|
||||||
|
"""
|
||||||
|
return filename[-4:] == '.stl'
|
||||||
|
|
||||||
|
class STLParser(TextModelParser):
|
||||||
|
"""Parser that parses a .stl file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, up_conversion = None):
|
||||||
|
super().__init__(up_conversion)
|
||||||
|
self.parsing_solid = False
|
||||||
|
self.parsing_face = False
|
||||||
|
self.parsing_loop = False
|
||||||
|
self.current_face = None
|
||||||
|
self.face_vertices = None
|
||||||
|
|
||||||
|
def parse_line(self, string):
|
||||||
|
"""Parses a line of .stl file
|
||||||
|
|
||||||
|
:param string: the line to parse
|
||||||
|
"""
|
||||||
|
if string == '':
|
||||||
|
return
|
||||||
|
|
||||||
|
split = string.split()
|
||||||
|
|
||||||
|
if split[0] == 'solid':
|
||||||
|
self.parsing_solid = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if split[0] == 'endsolid':
|
||||||
|
self.parsing_solid = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.parsing_solid:
|
||||||
|
|
||||||
|
if split[0] == 'facet' and split[1] == 'normal':
|
||||||
|
self.parsing_face = True
|
||||||
|
self.face_vertices = [FaceVertex(), FaceVertex(), FaceVertex()]
|
||||||
|
self.current_face = Face(*self.face_vertices)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.parsing_face:
|
||||||
|
|
||||||
|
if split[0] == 'outer' and split[1] == 'loop':
|
||||||
|
self.parsing_loop = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if split[0] == 'endloop':
|
||||||
|
self.parsing_loop = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.parsing_loop:
|
||||||
|
|
||||||
|
if split[0] == 'vertex':
|
||||||
|
current_vertex = Vertex().from_array(split[1:])
|
||||||
|
self.add_vertex(current_vertex)
|
||||||
|
self.face_vertices[0].vertex = len(self.vertices) - 1
|
||||||
|
self.face_vertices.pop(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if split[0] == 'endfacet':
|
||||||
|
self.parsing_face = False
|
||||||
|
self.add_face(self.current_face)
|
||||||
|
self.current_face = None
|
||||||
|
self.face_vertices = None
|
||||||
|
|
||||||
|
|
||||||
|
class STLExporter(Exporter):
|
||||||
|
"""Exporter to .stl format
|
||||||
|
"""
|
||||||
|
def __init__(self, model):
|
||||||
|
"""Creates an exporter from the model
|
||||||
|
|
||||||
|
:param model: Model to export
|
||||||
|
"""
|
||||||
|
super().__init__(model)
|
||||||
|
super().__init__(model)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Exports the model
|
||||||
|
"""
|
||||||
|
string = 'solid {}\n'.format(os.path.basename(self.model.path[:-4]))
|
||||||
|
|
||||||
|
self.model.generate_face_normals()
|
||||||
|
|
||||||
|
faces = sum(map(lambda x: x.faces, self.model.parts), [])
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
|
||||||
|
n = self.model.normals[face.a.normal]
|
||||||
|
v1 = self.model.vertices[face.a.vertex]
|
||||||
|
v2 = self.model.vertices[face.b.vertex]
|
||||||
|
v3 = self.model.vertices[face.c.vertex]
|
||||||
|
|
||||||
|
string += "facet normal {} {} {}\n".format(n.x, n.y, n.z)
|
||||||
|
|
||||||
|
string += "\touter loop\n"
|
||||||
|
string += "\t\tvertex {} {} {}\n".format(v1.x, v1.y, v1.z)
|
||||||
|
string += "\t\tvertex {} {} {}\n".format(v2.x, v2.y, v2.z)
|
||||||
|
string += "\t\tvertex {} {} {}\n".format(v3.x, v3.y, v3.z)
|
||||||
|
|
||||||
|
string += "\tendloop\n"
|
||||||
|
string += "endfacet\n"
|
||||||
|
|
||||||
|
string += 'endsolid {}'.format(os.path.basename(self.model.path[:-4]))
|
||||||
|
return string
|
230
extensions/fablabchemnitz/papercraft/d3/model/mesh.py
Normal file
230
extensions/fablabchemnitz/papercraft/d3/model/mesh.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
class Material:
|
||||||
|
"""Represents a material
|
||||||
|
|
||||||
|
It contains its constants and its texturess. It is also usable with OpenGL
|
||||||
|
"""
|
||||||
|
def __init__(self, name):
|
||||||
|
""" Creates an empty material
|
||||||
|
|
||||||
|
:param name: name of the material:
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.Ka = None
|
||||||
|
self.Kd = None
|
||||||
|
self.Ks = None
|
||||||
|
self.relative_path_to_texture = None
|
||||||
|
self.absolute_path_to_texture = None
|
||||||
|
self.im = None
|
||||||
|
self.id = None
|
||||||
|
|
||||||
|
def init_texture(self):
|
||||||
|
""" Initializes the OpenGL texture of the current material
|
||||||
|
|
||||||
|
To be simple, calls glGenTextures and stores the given id
|
||||||
|
"""
|
||||||
|
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
|
||||||
|
# Already initialized
|
||||||
|
if self.id is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If no map_Kd, nothing to do
|
||||||
|
if self.im is None:
|
||||||
|
|
||||||
|
if self.absolute_path_to_texture is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import PIL.Image
|
||||||
|
self.im = PIL.Image.open(self.absolute_path_to_texture)
|
||||||
|
except ImportError:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
ix, iy, image = self.im.size[0], self.im.size[1], self.im.tobytes("raw", "RGBA", 0, -1)
|
||||||
|
except:
|
||||||
|
ix, iy, image = self.im.size[0], self.im.size[1], self.im.tobytes("raw", "RGBX", 0, -1)
|
||||||
|
|
||||||
|
self.id = gl.glGenTextures(1)
|
||||||
|
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.id)
|
||||||
|
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT,1)
|
||||||
|
|
||||||
|
gl.glTexImage2D(
|
||||||
|
gl.GL_TEXTURE_2D, 0, 3, ix, iy, 0,
|
||||||
|
gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, image
|
||||||
|
)
|
||||||
|
|
||||||
|
def bind(self):
|
||||||
|
"""Binds the material to OpenGL
|
||||||
|
"""
|
||||||
|
from OpenGL import GL as gl
|
||||||
|
|
||||||
|
gl.glEnable(gl.GL_TEXTURE_2D)
|
||||||
|
gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
|
||||||
|
gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)
|
||||||
|
gl.glTexEnvf(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_2D, self.id)
|
||||||
|
|
||||||
|
def unbind(self):
|
||||||
|
"""Disables the GL_TEXTURE_2D flag of OpenGL
|
||||||
|
"""
|
||||||
|
from OpenGL import GL as gl
|
||||||
|
|
||||||
|
gl.glDisable(gl.GL_TEXTURE_2D)
|
||||||
|
|
||||||
|
Material.DEFAULT_MATERIAL=Material('')
|
||||||
|
"""Material that is used when no material is specified
|
||||||
|
"""
|
||||||
|
Material.DEFAULT_MATERIAL.Ka = 1.0
|
||||||
|
Material.DEFAULT_MATERIAL.Kd = 0.0
|
||||||
|
Material.DEFAULT_MATERIAL.Ks = 0.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
import PIL.Image
|
||||||
|
Material.DEFAULT_MATERIAL.im = PIL.Image.new("RGBA", (1,1), "white")
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MeshPart:
|
||||||
|
"""A part of a 3D model that is bound to a single material
|
||||||
|
"""
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""Creates a mesh part
|
||||||
|
|
||||||
|
:param parent: the global model with all the information
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
self.material = None
|
||||||
|
self.vertex_vbo = None
|
||||||
|
self.tex_coord_vbo = None
|
||||||
|
self.normal_vbo = None
|
||||||
|
self.color_vbo = None
|
||||||
|
self.faces = []
|
||||||
|
|
||||||
|
def init_texture(self):
|
||||||
|
"""Initializes the material of the current parent
|
||||||
|
"""
|
||||||
|
if self.material is not None:
|
||||||
|
self.material.init_texture()
|
||||||
|
|
||||||
|
def add_face(self, face):
|
||||||
|
"""Adds a face to this MeshPart
|
||||||
|
|
||||||
|
:param face: face to add
|
||||||
|
"""
|
||||||
|
self.faces.append(face)
|
||||||
|
|
||||||
|
def generate_vbos(self):
|
||||||
|
"""Generates the vbo for this MeshPart
|
||||||
|
|
||||||
|
Creates the arrays that are necessary for smooth rendering
|
||||||
|
"""
|
||||||
|
|
||||||
|
from OpenGL.arrays import vbo
|
||||||
|
from numpy import array
|
||||||
|
|
||||||
|
# Build VBO
|
||||||
|
v = []
|
||||||
|
n = []
|
||||||
|
t = []
|
||||||
|
c = []
|
||||||
|
|
||||||
|
for face in self.faces:
|
||||||
|
v1 = self.parent.vertices[face.a.vertex]
|
||||||
|
v2 = self.parent.vertices[face.b.vertex]
|
||||||
|
v3 = self.parent.vertices[face.c.vertex]
|
||||||
|
v += [[v1.x, v1.y, v1.z], [v2.x, v2.y, v2.z], [v3.x, v3.y, v3.z]]
|
||||||
|
|
||||||
|
if face.a.normal is not None:
|
||||||
|
n1 = self.parent.normals[face.a.normal]
|
||||||
|
n2 = self.parent.normals[face.b.normal]
|
||||||
|
n3 = self.parent.normals[face.c.normal]
|
||||||
|
n += [[n1.x, n1.y, n1.z], [n2.x, n2.y, n2.z], [n3.x, n3.y, n3.z]]
|
||||||
|
|
||||||
|
if face.a.tex_coord is not None:
|
||||||
|
t1 = self.parent.tex_coords[face.a.tex_coord]
|
||||||
|
t2 = self.parent.tex_coords[face.b.tex_coord]
|
||||||
|
t3 = self.parent.tex_coords[face.c.tex_coord]
|
||||||
|
t += [[t1.x, t1.y], [t2.x, t2.y], [t3.x, t3.y]]
|
||||||
|
|
||||||
|
if len(self.parent.colors) > 0: # face.a.color is not None:
|
||||||
|
c1 = self.parent.colors[face.a.vertex]
|
||||||
|
c2 = self.parent.colors[face.b.vertex]
|
||||||
|
c3 = self.parent.colors[face.c.vertex]
|
||||||
|
c += [[c1.x, c1.y, c1.z], [c2.x, c2.y, c2.z], [c3.x, c3.y, c3.z]]
|
||||||
|
|
||||||
|
self.vertex_vbo = vbo.VBO(array(v, 'f'))
|
||||||
|
|
||||||
|
if len(n) > 0:
|
||||||
|
self.normal_vbo = vbo.VBO(array(n, 'f'))
|
||||||
|
|
||||||
|
if len(t) > 0:
|
||||||
|
self.tex_coord_vbo = vbo.VBO(array(t, 'f'))
|
||||||
|
|
||||||
|
if len(c) > 0:
|
||||||
|
self.color_vbo = vbo.VBO(array(c, 'f'))
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Draws the current MeshPart
|
||||||
|
|
||||||
|
Binds the material, and draws the model
|
||||||
|
"""
|
||||||
|
if self.material is not None:
|
||||||
|
self.material.bind()
|
||||||
|
|
||||||
|
if self.vertex_vbo is not None:
|
||||||
|
self.draw_from_vbos()
|
||||||
|
else:
|
||||||
|
self.draw_from_arrays()
|
||||||
|
|
||||||
|
if self.material is not None:
|
||||||
|
self.material.unbind()
|
||||||
|
|
||||||
|
def draw_from_vbos(self):
|
||||||
|
"""Simply calls the OpenGL drawArrays function
|
||||||
|
|
||||||
|
Sets the correct vertex arrays and draws the part
|
||||||
|
"""
|
||||||
|
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
|
||||||
|
self.vertex_vbo.bind()
|
||||||
|
gl.glEnableClientState(gl.GL_VERTEX_ARRAY);
|
||||||
|
gl.glVertexPointerf(self.vertex_vbo)
|
||||||
|
self.vertex_vbo.unbind()
|
||||||
|
|
||||||
|
if self.normal_vbo is not None:
|
||||||
|
self.normal_vbo.bind()
|
||||||
|
gl.glEnableClientState(gl.GL_NORMAL_ARRAY)
|
||||||
|
gl.glNormalPointerf(self.normal_vbo)
|
||||||
|
self.normal_vbo.unbind()
|
||||||
|
|
||||||
|
if self.tex_coord_vbo is not None:
|
||||||
|
|
||||||
|
if self.material is not None:
|
||||||
|
self.material.bind()
|
||||||
|
|
||||||
|
self.tex_coord_vbo.bind()
|
||||||
|
gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY)
|
||||||
|
gl.glTexCoordPointerf(self.tex_coord_vbo)
|
||||||
|
self.tex_coord_vbo.unbind()
|
||||||
|
|
||||||
|
if self.color_vbo is not None:
|
||||||
|
self.color_vbo.bind()
|
||||||
|
gl.glEnableClientState(gl.GL_COLOR_ARRAY)
|
||||||
|
gl.glColorPointerf(self.color_vbo)
|
||||||
|
self.color_vbo.unbind()
|
||||||
|
|
||||||
|
gl.glDrawArrays(gl.GL_TRIANGLES, 0, len(self.vertex_vbo.data) * 9)
|
||||||
|
|
||||||
|
gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
|
||||||
|
gl.glDisableClientState(gl.GL_NORMAL_ARRAY)
|
||||||
|
gl.glDisableClientState(gl.GL_TEXTURE_COORD_ARRAY)
|
||||||
|
gl.glDisableClientState(gl.GL_COLOR_ARRAY)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_from_arrays(self):
|
||||||
|
pass
|
||||||
|
|
99
extensions/fablabchemnitz/papercraft/d3/model/tools.py
Normal file
99
extensions/fablabchemnitz/papercraft/d3/model/tools.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import os
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
from . import formats
|
||||||
|
from .formats import *
|
||||||
|
from .basemodel import ModelParser, Exporter
|
||||||
|
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
supported_formats = []
|
||||||
|
|
||||||
|
class ModelType:
|
||||||
|
"""Represents a type of coding of 3D object, and the module enabling
|
||||||
|
parsing and exporting
|
||||||
|
"""
|
||||||
|
def __init__(self, typename, inner_module):
|
||||||
|
"""Creates a ModelType
|
||||||
|
|
||||||
|
:param typename: the name of the 3D format
|
||||||
|
:param inner_module: the module that will parse and export the format
|
||||||
|
"""
|
||||||
|
self.typename = typename
|
||||||
|
self.inner_module = inner_module
|
||||||
|
|
||||||
|
def test_type(self, file):
|
||||||
|
"""Tests if a file has the correct type
|
||||||
|
|
||||||
|
:param file: path to the file to test
|
||||||
|
"""
|
||||||
|
return getattr(self.inner_module, 'is_' + self.typename)(file)
|
||||||
|
|
||||||
|
def create_parser(self, *args, **kwargs):
|
||||||
|
"""Creates a parser of the current type
|
||||||
|
"""
|
||||||
|
return getattr(self.inner_module, self.typename.upper() + 'Parser')(*args, **kwargs)
|
||||||
|
|
||||||
|
def create_exporter(self, *args, **kwargs):
|
||||||
|
"""Creates an exporter of the current type
|
||||||
|
"""
|
||||||
|
return getattr(self.inner_module, self.typename.upper() + 'Exporter')(*args, **kwargs)
|
||||||
|
|
||||||
|
def find_type(filename, supported_formats):
|
||||||
|
"""Find the correct type from a filename
|
||||||
|
|
||||||
|
:param filename: path to the file
|
||||||
|
:param supported_formats: list of formats that we have modules for
|
||||||
|
"""
|
||||||
|
for type in supported_formats:
|
||||||
|
if type.test_type(filename):
|
||||||
|
return type
|
||||||
|
|
||||||
|
for name in formats.__dict__:
|
||||||
|
if isinstance(formats.__dict__[name], ModuleType) and name != 'glob':
|
||||||
|
type = ModelType(name, formats.__dict__[name])
|
||||||
|
supported_formats.append(type)
|
||||||
|
|
||||||
|
def load_model(path, up_conversion = None):
|
||||||
|
"""Loads a model from a path
|
||||||
|
|
||||||
|
:param path: path to the file to load
|
||||||
|
:param up_conversion: conversion of up vectors
|
||||||
|
"""
|
||||||
|
parser = None
|
||||||
|
type = find_type(path, supported_formats)
|
||||||
|
|
||||||
|
if type is None:
|
||||||
|
raise Exception("File format not supported \"" + str(type) + "\"")
|
||||||
|
|
||||||
|
parser = type.create_parser(up_conversion)
|
||||||
|
parser.parse_file(path)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def export_model(model, path):
|
||||||
|
"""Exports a model to a path
|
||||||
|
|
||||||
|
:param model: model to export
|
||||||
|
:param path: path to save the model
|
||||||
|
"""
|
||||||
|
exporter = None
|
||||||
|
type = find_type(path, supported_formats)
|
||||||
|
|
||||||
|
if type is None:
|
||||||
|
raise Exception('File format is not supported')
|
||||||
|
|
||||||
|
exporter = type.create_exporter(model)
|
||||||
|
return exporter
|
||||||
|
|
||||||
|
def convert(input, output, up_conversion = None):
|
||||||
|
"""Converts a model
|
||||||
|
|
||||||
|
:param input: path of the input model
|
||||||
|
:param output: path to the output
|
||||||
|
:param up_conversion: convert the up vector
|
||||||
|
"""
|
||||||
|
model = load_model(input, up_conversion)
|
||||||
|
exporter = export_model(model, output)
|
||||||
|
return str(exporter)
|
||||||
|
|
53
extensions/fablabchemnitz/papercraft/d3/shader.py
Normal file
53
extensions/fablabchemnitz/papercraft/d3/shader.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import os
|
||||||
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
import OpenGL.GL as gl
|
||||||
|
import OpenGL.GL.shaders as sh
|
||||||
|
|
||||||
|
default_vertex_path = dir_path + '/../assets/shaders/shader.vert'
|
||||||
|
default_fragment_path = dir_path + '/../assets/shaders/shader.frag'
|
||||||
|
|
||||||
|
class Shader:
|
||||||
|
"""Shader
|
||||||
|
|
||||||
|
Loads, compile and binds the shader that are in the assets/shaders
|
||||||
|
directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vertex_path = default_vertex_path, fragment_path = default_fragment_path):
|
||||||
|
"""Creates a shader object, and compile it
|
||||||
|
|
||||||
|
:param vertex_path: path to the vertex shader
|
||||||
|
:param fragment_path: path to the fragment shader
|
||||||
|
"""
|
||||||
|
with open(vertex_path) as f:
|
||||||
|
self.vertex_src = f.read()
|
||||||
|
|
||||||
|
with open(fragment_path) as f:
|
||||||
|
self.fragment_src = f.read()
|
||||||
|
|
||||||
|
self.compile_shaders()
|
||||||
|
self.compile_program()
|
||||||
|
|
||||||
|
def compile_shaders(self):
|
||||||
|
""" Compiles the shader
|
||||||
|
"""
|
||||||
|
self.vertex_shader = sh.compileShader(self.vertex_src, gl.GL_VERTEX_SHADER)
|
||||||
|
self.fragment_shader = sh.compileShader(self.fragment_src, gl.GL_FRAGMENT_SHADER)
|
||||||
|
|
||||||
|
def compile_program(self):
|
||||||
|
"""Compile the shader program
|
||||||
|
|
||||||
|
The shaders must be compiled
|
||||||
|
"""
|
||||||
|
self.program = sh.compileProgram(self.vertex_shader, self.fragment_shader)
|
||||||
|
|
||||||
|
def bind(self):
|
||||||
|
"""Bind the current shader to the OpenGL context
|
||||||
|
"""
|
||||||
|
gl.glUseProgram(self.program)
|
||||||
|
|
||||||
|
def unbind(self):
|
||||||
|
"""Reset OpenGL shader to 0
|
||||||
|
"""
|
||||||
|
gl.glUseProgram(0)
|
BIN
extensions/fablabchemnitz/papercraft/fstl/fstl
Executable file
BIN
extensions/fablabchemnitz/papercraft/fstl/fstl
Executable file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/fstl.exe
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/fstl.exe
Normal file
Binary file not shown.
46
extensions/fablabchemnitz/papercraft/papercraft_unfold.inx
Normal file
46
extensions/fablabchemnitz/papercraft/papercraft_unfold.inx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Papercraft Unfold</name>
|
||||||
|
<id>fablabchemnitz.de.papercraft_unfold</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="general" gui-text="Input / General">
|
||||||
|
<param name="inputfile" type="path" gui-text="Input File" gui-description="The model to unfold" filetypes="amf,gcode,jscad,js,json,obj,off,ply,scad,stl"
|
||||||
|
mode="file">/your/beautiful/3dmodel/file</param>
|
||||||
|
<param name="resizetoimport" type="bool" gui-text="Resize the canvas to the imported drawing's bounding box">true</param>
|
||||||
|
<param name="extraborder" type="float" precision="3" gui-text="Add extra border around fitted canvas">0.0</param>
|
||||||
|
<param name="extraborder_units" type="optiongroup" appearance="combo" gui-text="Border offset units">
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="in">in</option>
|
||||||
|
<option value="pt">pt</option>
|
||||||
|
<option value="px">px</option>
|
||||||
|
</param>
|
||||||
|
<param name="show_fstl" type="bool" gui-text="Show converted (and fixed) STL in fstl Viewer">true</param>
|
||||||
|
</page>
|
||||||
|
<page name="meshfixing" gui-text="Mesh Fixing">
|
||||||
|
<param name="fixmesh" type="bool" gui-text="Try to repair meshes before flattening using ADMmesh (recommended)">true</param>
|
||||||
|
<separator/>
|
||||||
|
<param name="exact" type="bool" gui-text="Only check for perfectly matched edges">true</param>
|
||||||
|
<param name="nearby" type="bool" gui-text="Find and connect nearby facets. Correct bad facets">true</param>
|
||||||
|
<param name="tolerance" type="float" min="0.0" max="10000.0" precision="4" gui-text="Initial tolerance to use for nearby check">0.0</param>
|
||||||
|
<param name="iterations" type="int" min="1" max="1000" gui-text="Number of iterations for nearby check">1</param>
|
||||||
|
<param name="increment" type="float" min="0.0" max="10000.0" precision="4" gui-text="Amount to increment tolerance after iteration">0.0</param>
|
||||||
|
<param name="remove_unconnected" type="bool" gui-text="Remove facets that have 0 neighbors">true</param>
|
||||||
|
<param name="fill_holes" type="bool" gui-text="Add facets to fill holes">true</param>
|
||||||
|
<param name="normal_directions" type="bool" gui-text="Check and fix direction of normals (ie cw, ccw)">true</param>
|
||||||
|
<param name="reverse_all" type="bool" gui-text="Reverse the directions of all facets and normals">true</param>
|
||||||
|
<param name="normal_values" type="bool" gui-text="Check and fix normal values">true</param>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect needs-live-preview="true">
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Import/Export/Transfer"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">papercraft_unfold.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
255
extensions/fablabchemnitz/papercraft/papercraft_unfold.py
Normal file
255
extensions/fablabchemnitz/papercraft/papercraft_unfold.py
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import inkex
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
#specific imports for model-converter-python
|
||||||
|
import functools as fc
|
||||||
|
import d3.model.tools as mt
|
||||||
|
from d3.model.basemodel import Vector
|
||||||
|
|
||||||
|
"""
|
||||||
|
Extension for InkScape 1.0
|
||||||
|
|
||||||
|
Import any DWG or DXF file using ODA File Converter, sk1 UniConvertor, ezdxf and more tools.
|
||||||
|
|
||||||
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
|
Date: 08.09.2020
|
||||||
|
Last patch: 08.09.2020
|
||||||
|
License: GNU GPL v3
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
|
||||||
|
This tool converts a lot of different formats into STL Format. The STL then gets unfolded (flattened) to make a papercraft model. You can select between different engines. The used tools are
|
||||||
|
- converters
|
||||||
|
- openjscad
|
||||||
|
- model-converter-python
|
||||||
|
- repairers
|
||||||
|
- ADMesh
|
||||||
|
- unfolders
|
||||||
|
- osresearch/papercraft
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
openjscad [-v] <file> [-of <format>] [-o <output>]
|
||||||
|
<file> : input file (Supported types: .jscad, .js, .scad, .stl, .amf, .obj, .gcode, .svg, .json)
|
||||||
|
<output>: output file (Supported types: .jscad, .stl, .amf, .dxf, .svg, .json)
|
||||||
|
<format>: 'jscad', 'stla' (STL ASCII, default), 'stlb' (STL Binary), 'amf', 'dxf', 'svg', 'json'
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
ADMesh version 0.99.0dev
|
||||||
|
Copyright (C) 1995, 1996 Anthony D. Martin
|
||||||
|
Usage: /usr/share/inkscape/extensions/mightyscape-1.X/extensions/fablabchemnitz/papercraft/.libs/admesh [OPTION]... file
|
||||||
|
|
||||||
|
--x-rotate=angle Rotate CCW about x-axis by angle degrees
|
||||||
|
--y-rotate=angle Rotate CCW about y-axis by angle degrees
|
||||||
|
--z-rotate=angle Rotate CCW about z-axis by angle degrees
|
||||||
|
--xy-mirror Mirror about the xy plane
|
||||||
|
--yz-mirror Mirror about the yz plane
|
||||||
|
--xz-mirror Mirror about the xz plane
|
||||||
|
--scale=factor Scale the file by factor (multiply by factor)
|
||||||
|
--scale-xyz=x,y,z Scale the file by a non uniform factor
|
||||||
|
--translate=x,y,z Translate the file to x, y, and z
|
||||||
|
--merge=name Merge file called name with input file
|
||||||
|
-e, --exact Only check for perfectly matched edges
|
||||||
|
-n, --nearby Find and connect nearby facets. Correct bad facets
|
||||||
|
-t, --tolerance=tol Initial tolerance to use for nearby check = tol
|
||||||
|
-i, --iterations=i Number of iterations for nearby check = i
|
||||||
|
-m, --increment=inc Amount to increment tolerance after iteration=inc
|
||||||
|
-u, --remove-unconnected Remove facets that have 0 neighbors
|
||||||
|
-f, --fill-holes Add facets to fill holes
|
||||||
|
-d, --normal-directions Check and fix direction of normals(ie cw, ccw)
|
||||||
|
--reverse-all Reverse the directions of all facets and normals
|
||||||
|
-v, --normal-values Check and fix normal values
|
||||||
|
-c, --no-check Don't do any check on input file
|
||||||
|
-b, --write-binary-stl=name Output a binary STL file called name
|
||||||
|
-a, --write-ascii-stl=name Output an ascii STL file called name
|
||||||
|
--write-off=name Output a Geomview OFF format file called name
|
||||||
|
--write-dxf=name Output a DXF format file called name
|
||||||
|
--write-vrml=name Output a VRML format file called name
|
||||||
|
--help Display this help and exit
|
||||||
|
--version Output version information and exit
|
||||||
|
|
||||||
|
The functions are executed in the same order as the options shown here.
|
||||||
|
So check here to find what happens if, for example, --translate and --merge
|
||||||
|
options are specified together. The order of the options specified on the
|
||||||
|
command line is not important.
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
|
||||||
|
Module licenses
|
||||||
|
- papercraft (https://github.com/osresearch/papercraft) - GPL v2 License
|
||||||
|
- openjscad (https://github.com/jscad/OpenJSCAD.org) - MIT License and other, not bundled. Install it separately
|
||||||
|
- model-converter (https://github.com/tforgione/model-converter-python) - MIT License
|
||||||
|
- admesh (https://github.com/admesh/admesh) - GPL License
|
||||||
|
|
||||||
|
#TODO
|
||||||
|
- admesh Parameter als extra Tab in InkScape
|
||||||
|
- fix canvas resize
|
||||||
|
- Windows Executables
|
||||||
|
- Doku Seite mit HowTo Compile / install openjscad + admesh
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Unfold(inkex.Effect):
|
||||||
|
def __init__(self):
|
||||||
|
inkex.Effect.__init__(self)
|
||||||
|
self.arg_parser.add_argument("--tab")
|
||||||
|
self.arg_parser.add_argument("--inputfile")
|
||||||
|
self.arg_parser.add_argument("--resizetoimport", type=inkex.Boolean, default=True, help="Resize the canvas to the imported drawing's bounding box")
|
||||||
|
self.arg_parser.add_argument("--extraborder", type=float, default=0.0)
|
||||||
|
self.arg_parser.add_argument("--extraborder_units")
|
||||||
|
self.arg_parser.add_argument("--show_fstl", type=inkex.Boolean, default=True, help="Show converted (and fixed) STL in fstl Viewer")
|
||||||
|
self.arg_parser.add_argument("--fixmesh", type=inkex.Boolean, default=True)
|
||||||
|
self.arg_parser.add_argument("--exact", type=inkex.Boolean, default=True, help="Only check for perfectly matched edges")
|
||||||
|
self.arg_parser.add_argument("--nearby", type=inkex.Boolean, default=True, help="Find and connect nearby facets. Correct bad facets")
|
||||||
|
self.arg_parser.add_argument("--tolerance", type=float, default=0.0, help="Initial tolerance to use for nearby check")
|
||||||
|
self.arg_parser.add_argument("--iterations", type=int, default=1, help="Number of iterations for nearby check")
|
||||||
|
self.arg_parser.add_argument("--increment", type=float, default=0.0, help="Amount to increment tolerance after iteration")
|
||||||
|
self.arg_parser.add_argument("--remove_unconnected", type=inkex.Boolean, default=True, help="Remove facets that have 0 neighbors")
|
||||||
|
self.arg_parser.add_argument("--fill_holes", type=inkex.Boolean, default=True, help="Add facets to fill holes")
|
||||||
|
self.arg_parser.add_argument("--normal_directions", type=inkex.Boolean, default=True, help="Check and fix direction of normals (ie cw, ccw)")
|
||||||
|
self.arg_parser.add_argument("--reverse_all", type=inkex.Boolean, default=True, help="Reverse the directions of all facets and normals")
|
||||||
|
self.arg_parser.add_argument("--normal_values", type=inkex.Boolean, default=True, help="Check and fix normal values")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
import_formats_model_converter=[".obj", ".off", ".ply"] #we could also handle stl but openscad does the better job
|
||||||
|
import_formats_openjscad=[".jscad", ".js", ".scad", ".stl", ".amf", ".gcode", ".json"] #we could also handle obj but model converter does the better job
|
||||||
|
|
||||||
|
inputfile = self.options.inputfile
|
||||||
|
if not os.path.exists(inputfile):
|
||||||
|
inkex.utils.debug("The input file does not exist. Please select a proper file and try again.")
|
||||||
|
exit(1)
|
||||||
|
inputfile_format = (os.path.splitext(os.path.basename(inputfile))[1]).lower()
|
||||||
|
if inputfile_format not in import_formats_model_converter and inputfile_format not in import_formats_openjscad:
|
||||||
|
inkex.utils.debug("The input file format cannot be converted to a required format for flattening.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
converted_inputfile = os.path.join(tempfile.gettempdir(), os.path.splitext(os.path.basename(inputfile))[0] + ".stl")
|
||||||
|
if os.path.exists(converted_inputfile):
|
||||||
|
os.remove(converted_inputfile) #remove previously generated conversion file
|
||||||
|
|
||||||
|
if inputfile_format in import_formats_model_converter:
|
||||||
|
up_conversion = None
|
||||||
|
with open(converted_inputfile, 'w') as f:
|
||||||
|
f.write(mt.convert(inputfile, converted_inputfile, up_conversion))
|
||||||
|
if inputfile_format in import_formats_openjscad:
|
||||||
|
cmd = "openjscad \"" + inputfile + "\" -of stlb -o \"" + converted_inputfile + "\""
|
||||||
|
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
p.wait()
|
||||||
|
if p.returncode != 0 or len(stderr) > 0:
|
||||||
|
inkex.utils.debug("openjscad conversion failed: %d %s %s" % (p.returncode, stdout, stderr))
|
||||||
|
|
||||||
|
#we do this to convert possibly previously generated ASCII STL from model converter to binary STL to ensure always binary STLs
|
||||||
|
cmd = "openjscad \"" + converted_inputfile + "\" -of stlb -o \"" + converted_inputfile + "\""
|
||||||
|
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
p.wait()
|
||||||
|
if p.returncode != 0 or len(stderr) > 0:
|
||||||
|
inkex.utils.debug("openjscad conversion failed: %d %s %s" % (p.returncode, stdout, stderr))
|
||||||
|
|
||||||
|
if not os.path.exists(converted_inputfile):
|
||||||
|
inkex.utils.debug("Cannot find conversion output. Unable to continue")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Run ADMesh mesh fixer to overwrite the STL with fixed output (binary output too)
|
||||||
|
if self.options.fixmesh == True:
|
||||||
|
if os.name=="nt":
|
||||||
|
cmd = "admesh\\admesh.exe "
|
||||||
|
else:
|
||||||
|
cmd = "./admesh/admesh "
|
||||||
|
if self.options.exact == True: cmd += "--exact "
|
||||||
|
if self.options.nearby == True: cmd += "--nearby "
|
||||||
|
if self.options.tolerance > 0.0: cmd += "--tolerance " + str(self.options.tolerance) + " "
|
||||||
|
if self.options.iterations > 1: cmd += "--iterations " + str(self.options.iterations) + " "
|
||||||
|
if self.options.increment > 0.0: cmd += "--increment " + str(self.options.increment) + " "
|
||||||
|
if self.options.remove_unconnected == True: cmd += "--remove-unconnected "
|
||||||
|
if self.options.normal_directions == True: cmd += "--normal-directions "
|
||||||
|
if self.options.fill_holes == True: cmd += "--fill-holes "
|
||||||
|
if self.options.reverse_all == True: cmd += "--reverse-all "
|
||||||
|
if self.options.normal_values == True: cmd += "--normal-values "
|
||||||
|
cmd += "\"" + converted_inputfile + "\" "
|
||||||
|
cmd += "-b \"" + converted_inputfile + "\""
|
||||||
|
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
p.wait()
|
||||||
|
if p.returncode != 0:
|
||||||
|
inkex.utils.debug("admesh failed: %d %s %s" % (p.returncode, stdout, stderr))
|
||||||
|
|
||||||
|
# Run papercraft flattening
|
||||||
|
converted_flattenfile = os.path.join(tempfile.gettempdir(), os.path.splitext(os.path.basename(inputfile))[0] + ".svg")
|
||||||
|
if os.path.exists(converted_flattenfile):
|
||||||
|
os.remove(converted_flattenfile) #remove previously generated conversion file
|
||||||
|
if os.name=="nt":
|
||||||
|
cmd = "unfold\\unfold.exe" + " < \"" + converted_inputfile + "\" > \"" + converted_flattenfile + "\""
|
||||||
|
else:
|
||||||
|
cmd = "./unfold/unfold" + " < \"" + converted_inputfile + "\" > \"" + converted_flattenfile + "\""
|
||||||
|
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
p.wait()
|
||||||
|
if p.returncode != 0:
|
||||||
|
inkex.utils.debug("osresearch/papercraft unfold failed: %d %s %s" % (p.returncode, stdout, stderr))
|
||||||
|
|
||||||
|
# Open converted output in fstl
|
||||||
|
if self.options.show_fstl == True:
|
||||||
|
if os.name=="nt":
|
||||||
|
cmd = "fstl\\fstl.exe \"" + converted_inputfile + "\""
|
||||||
|
else:
|
||||||
|
cmd = "./fstl/fstl \"" + converted_inputfile + "\""
|
||||||
|
p = Popen(cmd, shell=True)
|
||||||
|
p.wait()
|
||||||
|
|
||||||
|
# Write the generated SVG into InkScape's canvas
|
||||||
|
try:
|
||||||
|
stream = open(converted_flattenfile, 'r')
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
inkex.utils.debug("There was no SVG output generated. Cannot continue")
|
||||||
|
exit(1)
|
||||||
|
p = etree.XMLParser(huge_tree=True)
|
||||||
|
doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)).getroot()
|
||||||
|
stream.close()
|
||||||
|
doc.set('id', self.svg.get_unique_id('papercraft_unfold'))
|
||||||
|
self.document.getroot().append(doc)
|
||||||
|
|
||||||
|
#adjust viewport and width/height to have the import at the center of the canvas - unstable at the moment.
|
||||||
|
if self.options.resizetoimport:
|
||||||
|
elements = []
|
||||||
|
for child in doc.getchildren():
|
||||||
|
#if child.tag == inkex.addNS('g','svg'):
|
||||||
|
elements.append(child)
|
||||||
|
|
||||||
|
#build sum of bounding boxes and ignore errors for faulty elements (sum function often fails for that usecase!)
|
||||||
|
bbox = None
|
||||||
|
try:
|
||||||
|
bbox = elements[0].bounding_box() #init with the first bounding box of the tree (and hope that it is not a faulty one)
|
||||||
|
except Exception as e:
|
||||||
|
#inkex.utils.debug(str(e))
|
||||||
|
pass
|
||||||
|
count = 0
|
||||||
|
for element in elements:
|
||||||
|
if count != 0: #skip the first
|
||||||
|
try:
|
||||||
|
#bbox.add(element.bounding_box())
|
||||||
|
bbox += element.bounding_box()
|
||||||
|
except Exception as e:
|
||||||
|
#inkex.utils.debug(str(e))
|
||||||
|
pass
|
||||||
|
count += 1 #some stupid counter
|
||||||
|
if bbox is not None:
|
||||||
|
root = self.svg.getElement('//svg:svg');
|
||||||
|
offset = self.svg.unittouu(str(self.options.extraborder) + self.options.extraborder_units)
|
||||||
|
root.set('viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset))
|
||||||
|
root.set('width', bbox.width + 2 * offset)
|
||||||
|
root.set('height', bbox.height + 2 * offset)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Unfold().run()
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
|
||||||
<name>Papercraft Unfold ASCII</name>
|
|
||||||
<id>fablabchemnitz.de.papercraft_unfold_ascii</id>
|
|
||||||
<input>
|
|
||||||
<extension>.stl</extension>
|
|
||||||
<mimetype>application/sla</mimetype>
|
|
||||||
<filetypename>Unfoldable Stereolitography File ASCII (*.stl)</filetypename>
|
|
||||||
<filetypetooltip>Unfold STL Files</filetypetooltip>
|
|
||||||
</input>
|
|
||||||
<script>
|
|
||||||
<command location="inx" interpreter="python">papercraft_unfold_ascii.py</command>
|
|
||||||
</script>
|
|
||||||
</inkscape-extension>
|
|
@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import inkex
|
|
||||||
import subprocess
|
|
||||||
from subprocess import Popen
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
class Unfold(inkex.Effect):
|
|
||||||
def __init__(self):
|
|
||||||
inkex.Effect.__init__(self)
|
|
||||||
stl_filename = sys.argv[1]
|
|
||||||
#inkex.utils.debug("stl_filename: "+stl_filename)
|
|
||||||
if os.name=="nt":
|
|
||||||
outname = "papercraft_unfold_output.svg"
|
|
||||||
#remove old file if existent
|
|
||||||
if os.path.exists(outname):
|
|
||||||
os.remove(outname)
|
|
||||||
if os.path.exists("unfold.exe.stackdump"):
|
|
||||||
os.remove("unfold.exe.stackdump")
|
|
||||||
#convert the STL to have a binary one and wait until conversion finished before running papercraft
|
|
||||||
cmd = os.getcwd() + "\\papercraft\\STLConverter.exe" + " \"" + stl_filename + "\""
|
|
||||||
p = Popen(cmd, shell=True)
|
|
||||||
#inkex.utils.debug(cmd)
|
|
||||||
#inkex.utils.debug("os.getcwd(): "+os.getcwd())
|
|
||||||
#inkex.utils.debug(os.path.splitext(stl_filename)[0])
|
|
||||||
p.wait()
|
|
||||||
cmd2 = os.getcwd() + "\\papercraft\\unfold.exe" + " < \"" + os.path.splitext(stl_filename)[0] + "-binary.stl\" > " + outname
|
|
||||||
#inkex.utils.debug("cmd2: "+cmd2)
|
|
||||||
p2 = Popen(cmd2, shell=True)
|
|
||||||
#inkex.utils.debug(p2.communicate())
|
|
||||||
p2.wait()
|
|
||||||
if p2.returncode == 0:
|
|
||||||
#inkex.utils.debug("OK")
|
|
||||||
doc = etree.parse(os.getcwd() + "\\" + outname)
|
|
||||||
doc.write(sys.stdout.buffer)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gc = Unfold()
|
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
|
||||||
<name>Papercraft Unfold Binary</name>
|
|
||||||
<id>fablabchemnitz.de.papercraft_unfold_binary</id>
|
|
||||||
<input>
|
|
||||||
<extension>.stl</extension>
|
|
||||||
<mimetype>application/sla</mimetype>
|
|
||||||
<filetypename>Unfoldable Stereolitography File Binary (*.stl)</filetypename>
|
|
||||||
<filetypetooltip>Unfold STL Files</filetypetooltip>
|
|
||||||
</input>
|
|
||||||
<script>
|
|
||||||
<command location="inx" interpreter="python">papercraft_unfold_binary.py</command>
|
|
||||||
</script>
|
|
||||||
</inkscape-extension>
|
|
@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import inkex
|
|
||||||
import subprocess
|
|
||||||
from subprocess import Popen
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
class Unfold(inkex.Effect):
|
|
||||||
def __init__(self):
|
|
||||||
inkex.Effect.__init__(self)
|
|
||||||
stl_filename = sys.argv[1]
|
|
||||||
#inkex.utils.debug("stl_filename: "+stl_filename)
|
|
||||||
if os.name=="nt":
|
|
||||||
outname = "papercraft_unfold_output.svg"
|
|
||||||
#remove old file if existent
|
|
||||||
if os.path.exists(outname):
|
|
||||||
os.remove(outname)
|
|
||||||
if os.path.exists("unfold.exe.stackdump"):
|
|
||||||
os.remove("unfold.exe.stackdump")
|
|
||||||
#inkex.utils.debug("os.getcwd(): "+os.getcwd())
|
|
||||||
#inkex.utils.debug(os.path.splitext(stl_filename)[0])
|
|
||||||
cmd = os.getcwd() + "\\papercraft\\unfold.exe" + " < \"" + stl_filename + "\" > " + outname
|
|
||||||
#inkex.utils.debug("cmd: "+cmd)
|
|
||||||
p = Popen(cmd, shell=True)
|
|
||||||
#inkex.utils.debug(p.communicate())
|
|
||||||
p.wait()
|
|
||||||
if p.returncode == 0:
|
|
||||||
#inkex.utils.debug("OK")
|
|
||||||
doc = etree.parse(os.getcwd() + "\\" + outname)
|
|
||||||
#inkex.utils.debug(etree.tostring(doc))
|
|
||||||
doc.write(sys.stdout.buffer)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gc = Unfold()
|
|
BIN
extensions/fablabchemnitz/papercraft/unfold/unfold
Executable file
BIN
extensions/fablabchemnitz/papercraft/unfold/unfold
Executable file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user