diff --git a/extensions/fablabchemnitz/papercraft/admesh/.libs/admesh b/extensions/fablabchemnitz/papercraft/admesh/.libs/admesh new file mode 100755 index 00000000..f673dbd4 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/admesh/.libs/admesh differ diff --git a/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.la b/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.la new file mode 100644 index 00000000..a9d16ebe --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.la @@ -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' diff --git a/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.lai b/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.lai new file mode 100644 index 00000000..1944c188 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.lai @@ -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' diff --git a/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.so.1 b/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.so.1 new file mode 100755 index 00000000..7c734945 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/admesh/.libs/libadmesh.so.1 differ diff --git a/extensions/fablabchemnitz/papercraft/admesh/admesh b/extensions/fablabchemnitz/papercraft/admesh/admesh new file mode 100755 index 00000000..396e1adb --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/admesh/admesh @@ -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 diff --git a/extensions/fablabchemnitz/papercraft/admesh/admesh.exe b/extensions/fablabchemnitz/papercraft/admesh/admesh.exe new file mode 100755 index 00000000..ea56453d Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/admesh/admesh.exe differ diff --git a/extensions/fablabchemnitz/papercraft/admesh/include/admesh/stl.h b/extensions/fablabchemnitz/papercraft/admesh/include/admesh/stl.h new file mode 100644 index 00000000..25fe770a --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/admesh/include/admesh/stl.h @@ -0,0 +1,201 @@ +/* ADMesh -- process triangulated solid meshes + * Copyright (C) 1995, 1996 Anthony D. Martin + * 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 + +#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 diff --git a/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.a b/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.a new file mode 100644 index 00000000..601cd204 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.a differ diff --git a/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.dll.a b/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.dll.a new file mode 100755 index 00000000..6a52c063 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.dll.a differ diff --git a/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.la b/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.la new file mode 100755 index 00000000..d7de0992 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/admesh/lib/libadmesh.la @@ -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' diff --git a/extensions/fablabchemnitz/papercraft/admesh/lib/pkgconfig/libadmesh.pc b/extensions/fablabchemnitz/papercraft/admesh/lib/pkgconfig/libadmesh.pc new file mode 100644 index 00000000..7a2fbe46 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/admesh/lib/pkgconfig/libadmesh.pc @@ -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} diff --git a/extensions/fablabchemnitz/papercraft/admesh/libadmesh-1.dll b/extensions/fablabchemnitz/papercraft/admesh/libadmesh-1.dll new file mode 100755 index 00000000..d64fdd4e Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/admesh/libadmesh-1.dll differ diff --git a/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.mtl b/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.mtl new file mode 100644 index 00000000..5a4b37fe --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.mtl @@ -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 diff --git a/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.obj b/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.obj new file mode 100644 index 00000000..d105fe83 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.obj @@ -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 + diff --git a/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.png b/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.png new file mode 100644 index 00000000..b1928b00 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/assets/models/cube/cube.png differ diff --git a/extensions/fablabchemnitz/papercraft/assets/shaders/shader.frag b/extensions/fablabchemnitz/papercraft/assets/shaders/shader.frag new file mode 100644 index 00000000..b8500344 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/assets/shaders/shader.frag @@ -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; + +} + diff --git a/extensions/fablabchemnitz/papercraft/assets/shaders/shader.vert b/extensions/fablabchemnitz/papercraft/assets/shaders/shader.vert new file mode 100644 index 00000000..a50f4ef6 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/assets/shaders/shader.vert @@ -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; + +} diff --git a/extensions/fablabchemnitz/papercraft/d3/__init__.py b/extensions/fablabchemnitz/papercraft/d3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/extensions/fablabchemnitz/papercraft/d3/camera.py b/extensions/fablabchemnitz/papercraft/d3/camera.py new file mode 100644 index 00000000..9eeecc1f --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/camera.py @@ -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) diff --git a/extensions/fablabchemnitz/papercraft/d3/controls.py b/extensions/fablabchemnitz/papercraft/d3/controls.py new file mode 100644 index 00000000..bff9be15 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/controls.py @@ -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) diff --git a/extensions/fablabchemnitz/papercraft/d3/geometry.py b/extensions/fablabchemnitz/papercraft/d3/geometry.py new file mode 100644 index 00000000..2e90c9cc --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/geometry.py @@ -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 + + diff --git a/extensions/fablabchemnitz/papercraft/d3/model/__init__.py b/extensions/fablabchemnitz/papercraft/d3/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/extensions/fablabchemnitz/papercraft/d3/model/basemodel.py b/extensions/fablabchemnitz/papercraft/d3/model/basemodel.py new file mode 100644 index 00000000..f9ee647a --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/basemodel.py @@ -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 + + diff --git a/extensions/fablabchemnitz/papercraft/d3/model/formats/__init__.py b/extensions/fablabchemnitz/papercraft/d3/model/formats/__init__.py new file mode 100644 index 00000000..ff5b8f08 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/formats/__init__.py @@ -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)] diff --git a/extensions/fablabchemnitz/papercraft/d3/model/formats/obj.py b/extensions/fablabchemnitz/papercraft/d3/model/formats/obj.py new file mode 100644 index 00000000..26ec05d6 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/formats/obj.py @@ -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 + diff --git a/extensions/fablabchemnitz/papercraft/d3/model/formats/off.py b/extensions/fablabchemnitz/papercraft/d3/model/formats/off.py new file mode 100644 index 00000000..9bf08824 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/formats/off.py @@ -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 + diff --git a/extensions/fablabchemnitz/papercraft/d3/model/formats/ply.py b/extensions/fablabchemnitz/papercraft/d3/model/formats/ply.py new file mode 100644 index 00000000..8e32539a --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/formats/ply.py @@ -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(' 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 + diff --git a/extensions/fablabchemnitz/papercraft/d3/model/formats/stl.py b/extensions/fablabchemnitz/papercraft/d3/model/formats/stl.py new file mode 100644 index 00000000..7cb367ad --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/formats/stl.py @@ -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 diff --git a/extensions/fablabchemnitz/papercraft/d3/model/mesh.py b/extensions/fablabchemnitz/papercraft/d3/model/mesh.py new file mode 100644 index 00000000..c47f36f1 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/mesh.py @@ -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 + diff --git a/extensions/fablabchemnitz/papercraft/d3/model/tools.py b/extensions/fablabchemnitz/papercraft/d3/model/tools.py new file mode 100644 index 00000000..2b3ef066 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/model/tools.py @@ -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) + diff --git a/extensions/fablabchemnitz/papercraft/d3/shader.py b/extensions/fablabchemnitz/papercraft/d3/shader.py new file mode 100644 index 00000000..e55cc7e8 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/d3/shader.py @@ -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) diff --git a/extensions/fablabchemnitz/papercraft/fstl/fstl b/extensions/fablabchemnitz/papercraft/fstl/fstl new file mode 100755 index 00000000..e2632a56 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/fstl/fstl differ diff --git a/extensions/fablabchemnitz/papercraft/fstl/fstl.exe b/extensions/fablabchemnitz/papercraft/fstl/fstl.exe new file mode 100644 index 00000000..d4e02613 Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/fstl/fstl.exe differ diff --git a/extensions/fablabchemnitz/papercraft/papercraft_unfold.inx b/extensions/fablabchemnitz/papercraft/papercraft_unfold.inx new file mode 100644 index 00000000..ed7ec448 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/papercraft_unfold.inx @@ -0,0 +1,46 @@ + + + Papercraft Unfold + fablabchemnitz.de.papercraft_unfold + + + /your/beautiful/3dmodel/file + true + 0.0 + + + + + + + + true + + + true + + true + true + 0.0 + 1 + 0.0 + true + true + true + true + true + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/papercraft/papercraft_unfold.py b/extensions/fablabchemnitz/papercraft/papercraft_unfold.py new file mode 100644 index 00000000..7f51b6f6 --- /dev/null +++ b/extensions/fablabchemnitz/papercraft/papercraft_unfold.py @@ -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] [-of ] [-o ] + : input file (Supported types: .jscad, .js, .scad, .stl, .amf, .obj, .gcode, .svg, .json) + : output file (Supported types: .jscad, .stl, .amf, .dxf, .svg, .json) + : '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() \ No newline at end of file diff --git a/extensions/fablabchemnitz/papercraft/papercraft_unfold_ascii.inx b/extensions/fablabchemnitz/papercraft/papercraft_unfold_ascii.inx deleted file mode 100644 index 1dad253a..00000000 --- a/extensions/fablabchemnitz/papercraft/papercraft_unfold_ascii.inx +++ /dev/null @@ -1,14 +0,0 @@ - - - Papercraft Unfold ASCII - fablabchemnitz.de.papercraft_unfold_ascii - - .stl - application/sla - Unfoldable Stereolitography File ASCII (*.stl) - Unfold STL Files - - - \ No newline at end of file diff --git a/extensions/fablabchemnitz/papercraft/papercraft_unfold_ascii.py b/extensions/fablabchemnitz/papercraft/papercraft_unfold_ascii.py deleted file mode 100644 index edad2857..00000000 --- a/extensions/fablabchemnitz/papercraft/papercraft_unfold_ascii.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/extensions/fablabchemnitz/papercraft/papercraft_unfold_binary.inx b/extensions/fablabchemnitz/papercraft/papercraft_unfold_binary.inx deleted file mode 100644 index bfc99b85..00000000 --- a/extensions/fablabchemnitz/papercraft/papercraft_unfold_binary.inx +++ /dev/null @@ -1,14 +0,0 @@ - - - Papercraft Unfold Binary - fablabchemnitz.de.papercraft_unfold_binary - - .stl - application/sla - Unfoldable Stereolitography File Binary (*.stl) - Unfold STL Files - - - \ No newline at end of file diff --git a/extensions/fablabchemnitz/papercraft/papercraft_unfold_binary.py b/extensions/fablabchemnitz/papercraft/papercraft_unfold_binary.py deleted file mode 100644 index e32e15d7..00000000 --- a/extensions/fablabchemnitz/papercraft/papercraft_unfold_binary.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/extensions/fablabchemnitz/papercraft/windows/cygwin1.dll b/extensions/fablabchemnitz/papercraft/unfold/cygwin1.dll similarity index 100% rename from extensions/fablabchemnitz/papercraft/windows/cygwin1.dll rename to extensions/fablabchemnitz/papercraft/unfold/cygwin1.dll diff --git a/extensions/fablabchemnitz/papercraft/unfold/unfold b/extensions/fablabchemnitz/papercraft/unfold/unfold new file mode 100755 index 00000000..e0afb53d Binary files /dev/null and b/extensions/fablabchemnitz/papercraft/unfold/unfold differ diff --git a/extensions/fablabchemnitz/papercraft/windows/unfold.exe b/extensions/fablabchemnitz/papercraft/unfold/unfold.exe similarity index 100% rename from extensions/fablabchemnitz/papercraft/windows/unfold.exe rename to extensions/fablabchemnitz/papercraft/unfold/unfold.exe diff --git a/extensions/fablabchemnitz/papercraft/windows/STLConverter.exe b/extensions/fablabchemnitz/papercraft/windows/STLConverter.exe deleted file mode 100644 index c8afaf08..00000000 Binary files a/extensions/fablabchemnitz/papercraft/windows/STLConverter.exe and /dev/null differ