Some cleanings
This commit is contained in:
parent
6c9508acf8
commit
c2b48e521a
Binary file not shown.
@ -1,41 +0,0 @@
|
|||||||
# 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'
|
|
@ -1,41 +0,0 @@
|
|||||||
# 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'
|
|
Binary file not shown.
@ -1,210 +0,0 @@
|
|||||||
#! /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
|
|
Binary file not shown.
@ -1,201 +0,0 @@
|
|||||||
/* 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
|
|
Binary file not shown.
Binary file not shown.
@ -1,41 +0,0 @@
|
|||||||
# 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'
|
|
@ -1,11 +0,0 @@
|
|||||||
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}
|
|
Binary file not shown.
@ -1,10 +0,0 @@
|
|||||||
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
|
|
@ -1,34 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 149 KiB |
@ -1,22 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
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)
|
|
@ -1,112 +0,0 @@
|
|||||||
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)
|
|
@ -1,108 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
|
|
@ -1,334 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
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)]
|
|
@ -1,208 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
@ -1,494 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
|||||||
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
|
|
@ -1,230 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
|||||||
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)
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring
generated
vendored
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
"$basedir/node" "$basedir/../astring/bin/astring" "$@"
|
|
||||||
ret=$?
|
|
||||||
else
|
|
||||||
node "$basedir/../astring/bin/astring" "$@"
|
|
||||||
ret=$?
|
|
||||||
fi
|
|
||||||
exit $ret
|
|
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring.cmd
generated
vendored
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring.cmd
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
@ECHO off
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
"%_prog%" "%dp0%\..\astring\bin\astring" %*
|
|
||||||
ENDLOCAL
|
|
||||||
EXIT /b %errorlevel%
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring.ps1
generated
vendored
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring.ps1
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
& "$basedir/node$exe" "$basedir/../astring/bin/astring" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../astring/bin/astring" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse
generated
vendored
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
"$basedir/node" "$basedir/../esprima/bin/esparse.js" "$@"
|
|
||||||
ret=$?
|
|
||||||
else
|
|
||||||
node "$basedir/../esprima/bin/esparse.js" "$@"
|
|
||||||
ret=$?
|
|
||||||
fi
|
|
||||||
exit $ret
|
|
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse.cmd
generated
vendored
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse.cmd
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
@ECHO off
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
"%_prog%" "%dp0%\..\esprima\bin\esparse.js" %*
|
|
||||||
ENDLOCAL
|
|
||||||
EXIT /b %errorlevel%
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse.ps1
generated
vendored
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse.ps1
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
& "$basedir/node$exe" "$basedir/../esprima/bin/esparse.js" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../esprima/bin/esparse.js" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate
generated
vendored
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
"$basedir/node" "$basedir/../esprima/bin/esvalidate.js" "$@"
|
|
||||||
ret=$?
|
|
||||||
else
|
|
||||||
node "$basedir/../esprima/bin/esvalidate.js" "$@"
|
|
||||||
ret=$?
|
|
||||||
fi
|
|
||||||
exit $ret
|
|
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate.cmd
generated
vendored
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate.cmd
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
@ECHO off
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
"%_prog%" "%dp0%\..\esprima\bin\esvalidate.js" %*
|
|
||||||
ENDLOCAL
|
|
||||||
EXIT /b %errorlevel%
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate.ps1
generated
vendored
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate.ps1
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
& "$basedir/node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../esprima/bin/esvalidate.js" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
BIN
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/node.exe
generated
vendored
BIN
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/node.exe
generated
vendored
Binary file not shown.
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad
generated
vendored
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
"$basedir/node" "$basedir/../@jscad/openjscad/src/cli/cli.js" "$@"
|
|
||||||
ret=$?
|
|
||||||
else
|
|
||||||
node "$basedir/../@jscad/openjscad/src/cli/cli.js" "$@"
|
|
||||||
ret=$?
|
|
||||||
fi
|
|
||||||
exit $ret
|
|
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad.cmd
generated
vendored
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad.cmd
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
@ECHO off
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
"%_prog%" "%dp0%\..\@jscad\openjscad\src\cli\cli.js" %*
|
|
||||||
ENDLOCAL
|
|
||||||
EXIT /b %errorlevel%
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad.ps1
generated
vendored
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad.ps1
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
& "$basedir/node$exe" "$basedir/../@jscad/openjscad/src/cli/cli.js" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../@jscad/openjscad/src/cli/cli.js" $args
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
@ -1,28 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
||||||
|
|
||||||
<a name="0.0.4"></a>
|
|
||||||
## [0.0.4](https://github.com/jscad/io/compare/@jscad/amf-deserializer@0.0.3...@jscad/amf-deserializer@0.0.4) (2017-11-04)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-deserializer
|
|
||||||
|
|
||||||
<a name="0.0.3"></a>
|
|
||||||
## [0.0.3](https://github.com/jscad/io/compare/@jscad/amf-deserializer@0.0.2...@jscad/amf-deserializer@0.0.3) (2017-10-10)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-deserializer
|
|
||||||
|
|
||||||
<a name="0.0.2"></a>
|
|
||||||
## 0.0.2 (2017-10-10)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-deserializer
|
|
@ -1,50 +0,0 @@
|
|||||||
## @jscad/amf-deserializer
|
|
||||||
|
|
||||||
> amf deserializer for the jscad project
|
|
||||||
|
|
||||||
[![npm version](https://badge.fury.io/js/%40jscad%2Famf-deserializer.svg)](https://badge.fury.io/js/%40jscad%2Famf-deserializer)
|
|
||||||
[![Build Status](https://travis-ci.org/jscad/io.svg)](https://travis-ci.org/jscad/amf-deserializer)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This deserializer converts raw amf data to jscad code (that can be evaluated to CSG/CAG).
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Contribute](#contribute)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install @jscad/amf-deserializer
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const amfDeSerializer = require('@jscad/amf-deserializer')
|
|
||||||
|
|
||||||
const rawData = fs.readFileSync('PATH/TO/file.amf')
|
|
||||||
const csgData = amfDeSerializer(rawData)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
For questions about the API, please contact the [User Group](https://plus.google.com/communities/114958480887231067224)
|
|
||||||
|
|
||||||
PRs accepted.
|
|
||||||
|
|
||||||
Small Note: If editing this README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[The MIT License (MIT)](./LICENSE)
|
|
||||||
(unless specified otherwise)
|
|
728
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/index.js
generated
vendored
728
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/index.js
generated
vendored
@ -1,728 +0,0 @@
|
|||||||
/*
|
|
||||||
## License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Z3 Development https://github.com/z3dev
|
|
||||||
Copyright (c) 2013-2016 by Rene K. Mueller <spiritdude@gmail.com>
|
|
||||||
Copyright (c) 2016 by Z3D Development
|
|
||||||
|
|
||||||
All code released under MIT license
|
|
||||||
|
|
||||||
History:
|
|
||||||
2016/06/27: 0.5.1: rewrote using SAX XML parser, enhanced for multiple objects, materials, units by Z3Dev
|
|
||||||
2013/04/11: 0.018: added alpha support to AMF export
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// //////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// AMF is a language for describing three-dimensional graphics in XML
|
|
||||||
// See http://www.astm.org/Standards/ISOASTM52915.htm
|
|
||||||
// See http://amf.wikispaces.com/
|
|
||||||
//
|
|
||||||
// //////////////////////////////////////////
|
|
||||||
const sax = require('sax')
|
|
||||||
|
|
||||||
const inchMM = (1 / 0.039370) // used for scaling AMF (inch) to CAG coordinates(MM)
|
|
||||||
|
|
||||||
// processing controls
|
|
||||||
sax.SAXParser.prototype.amfLast = null // last object found
|
|
||||||
sax.SAXParser.prototype.amfDefinition = 0 // definitions beinging created
|
|
||||||
// 0-AMF,1-object,2-material,3-texture,4-constellation,5-metadata
|
|
||||||
// high level elements / definitions
|
|
||||||
sax.SAXParser.prototype.amfObjects = [] // list of objects
|
|
||||||
sax.SAXParser.prototype.amfMaterials = [] // list of materials
|
|
||||||
sax.SAXParser.prototype.amfTextures = [] // list of textures
|
|
||||||
sax.SAXParser.prototype.amfConstels = [] // list of constellations
|
|
||||||
sax.SAXParser.prototype.amfMetadata = [] // list of metadata
|
|
||||||
|
|
||||||
sax.SAXParser.prototype.amfObj = null // amf in object form
|
|
||||||
|
|
||||||
function amfAmf (element) {
|
|
||||||
// default SVG with no viewport
|
|
||||||
var obj = {type: 'amf', unit: 'mm', scale: 1.0}
|
|
||||||
|
|
||||||
if ('UNIT' in element) { obj.unit = element.UNIT.toLowerCase() }
|
|
||||||
// set scaling
|
|
||||||
switch (obj.unit.toLowerCase()) {
|
|
||||||
case 'inch':
|
|
||||||
obj.scale = inchMM
|
|
||||||
break
|
|
||||||
case 'foot':
|
|
||||||
obj.scale = inchMM * 12.0
|
|
||||||
break
|
|
||||||
case 'meter':
|
|
||||||
obj.scale = 1000.0
|
|
||||||
break
|
|
||||||
case 'micron':
|
|
||||||
obj.scale = 0.001
|
|
||||||
break
|
|
||||||
case 'millimeter':
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
sax.SAXParser.prototype.amfObject = function (element) {
|
|
||||||
var obj = {type: 'object', id: 'JSCAD' + (this.amfObjects.length)} // default ID
|
|
||||||
|
|
||||||
if ('ID' in element) { obj.id = element.ID }
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfMesh (element) {
|
|
||||||
var obj = {type: 'mesh'}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: TBD Vertices can have a color, which is used to interpolate a face color (from the 3 vertices)
|
|
||||||
function amfVertices (element) {
|
|
||||||
var obj = {type: 'vertices'}
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfCoordinates (element) {
|
|
||||||
var obj = {type: 'coordinates'}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
function amfNormal (element) {
|
|
||||||
var obj = {type: 'normal'}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
function amfX (element) {
|
|
||||||
return {type: 'x', value: '0'}
|
|
||||||
}
|
|
||||||
function amfY (element) {
|
|
||||||
return {type: 'y', value: '0'}
|
|
||||||
}
|
|
||||||
function amfZ (element) {
|
|
||||||
return {type: 'z', value: '0'}
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfVolume (element) {
|
|
||||||
var obj = {type: 'volume'}
|
|
||||||
|
|
||||||
if ('MATERIALID' in element) { obj.materialid = element.MATERIALID }
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfTriangle (element) {
|
|
||||||
var obj = {type: 'triangle'}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
function amfV1 (element) {
|
|
||||||
return {type: 'v1', value: '0'}
|
|
||||||
}
|
|
||||||
function amfV2 (element) {
|
|
||||||
return {type: 'v2', value: '0'}
|
|
||||||
}
|
|
||||||
function amfV3 (element) {
|
|
||||||
return {type: 'v3', value: '0'}
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfVertex (element) {
|
|
||||||
var obj = {type: 'vertex'}
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfEdge (element) {
|
|
||||||
var obj = {type: 'edge'}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfMetadata (element) {
|
|
||||||
var obj = {type: 'metadata'}
|
|
||||||
|
|
||||||
if ('TYPE' in element) { obj.mtype = element.TYPE }
|
|
||||||
if ('ID' in element) { obj.id = element.ID }
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfMaterial (element) {
|
|
||||||
var obj = {type: 'material'}
|
|
||||||
|
|
||||||
if ('ID' in element) { obj.id = element.ID }
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfColor (element) {
|
|
||||||
var obj = {type: 'color'}
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
function amfR (element) {
|
|
||||||
return {type: 'r', value: '1'}
|
|
||||||
}
|
|
||||||
function amfG (element) {
|
|
||||||
return {type: 'g', value: '1'}
|
|
||||||
}
|
|
||||||
function amfB (element) {
|
|
||||||
return {type: 'b', value: '1'}
|
|
||||||
}
|
|
||||||
function amfA (element) {
|
|
||||||
return {type: 'a', value: '1'}
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfMap (element) {
|
|
||||||
var obj = {type: 'map'}
|
|
||||||
|
|
||||||
if ('GTEXID' in element) { obj.gtexid = element.GTEXID }
|
|
||||||
if ('BTEXID' in element) { obj.btexid = element.BTEXID }
|
|
||||||
if ('RTEXID' in element) { obj.rtexid = element.RTEXID }
|
|
||||||
|
|
||||||
obj.objects = []
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function amfU1 (element) {
|
|
||||||
return {type: 'u1', value: '0'}
|
|
||||||
}
|
|
||||||
function amfU2 (element) {
|
|
||||||
return {type: 'u2', value: '0'}
|
|
||||||
}
|
|
||||||
function amfU3 (element) {
|
|
||||||
return {type: 'u3', value: '0'}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAmfParser (src, pxPmm) {
|
|
||||||
// create a parser for the XML
|
|
||||||
var parser = sax.parser(false, {trim: true, lowercase: false, position: true})
|
|
||||||
|
|
||||||
parser.onerror = function (e) {
|
|
||||||
console.log('error: line ' + e.line + ', column ' + e.column + ', bad character [' + e.c + ']')
|
|
||||||
}
|
|
||||||
parser.onopentag = function (node) {
|
|
||||||
// console.log('opentag: '+node.name+' at line '+this.line+' position '+this.column);
|
|
||||||
// for (x in node.attributes) {
|
|
||||||
// console.log(' '+x+'='+node.attributes[x]);
|
|
||||||
// }
|
|
||||||
// case 'VTEX1':
|
|
||||||
// case 'VTEX2':
|
|
||||||
// case 'VTEX3':
|
|
||||||
|
|
||||||
var obj = null
|
|
||||||
switch (node.name) {
|
|
||||||
// top level elements
|
|
||||||
case 'AMF':
|
|
||||||
obj = amfAmf(node.attributes)
|
|
||||||
break
|
|
||||||
case 'OBJECT':
|
|
||||||
obj = this.amfObject(node.attributes)
|
|
||||||
if (this.amfDefinition === 0) this.amfDefinition = 1 // OBJECT processing
|
|
||||||
break
|
|
||||||
case 'MESH':
|
|
||||||
obj = amfMesh(node.attributes)
|
|
||||||
break
|
|
||||||
case 'VERTICES':
|
|
||||||
obj = amfVertices(node.attributes)
|
|
||||||
break
|
|
||||||
case 'VERTEX':
|
|
||||||
obj = amfVertex(node.attributes)
|
|
||||||
break
|
|
||||||
case 'EDGE':
|
|
||||||
obj = amfEdge(node.attributes)
|
|
||||||
break
|
|
||||||
case 'VOLUME':
|
|
||||||
obj = amfVolume(node.attributes)
|
|
||||||
break
|
|
||||||
case 'MATERIAL':
|
|
||||||
obj = amfMaterial(node.attributes)
|
|
||||||
if (this.amfDefinition === 0) this.amfDefinition = 2 // MATERIAL processing
|
|
||||||
break
|
|
||||||
case 'COMPOSITE':
|
|
||||||
break
|
|
||||||
case 'TEXTURE':
|
|
||||||
if (this.amfDefinition === 0) this.amfDefinition = 3 // TEXTURE processing
|
|
||||||
break
|
|
||||||
case 'CONSTELLATION':
|
|
||||||
if (this.amfDefinition === 0) this.amfDefinition = 4 // CONSTELLATION processing
|
|
||||||
break
|
|
||||||
case 'METADATA':
|
|
||||||
obj = amfMetadata(node.attributes)
|
|
||||||
if (this.amfDefinition === 0) this.amfDefinition = 5 // METADATA processing
|
|
||||||
break
|
|
||||||
// coordinate elements
|
|
||||||
case 'COORDINATES':
|
|
||||||
obj = amfCoordinates(node.attributes)
|
|
||||||
break
|
|
||||||
case 'NORMAL':
|
|
||||||
obj = amfNormal(node.attributes)
|
|
||||||
break
|
|
||||||
case 'X':
|
|
||||||
case 'NX':
|
|
||||||
obj = amfX(node.attributes)
|
|
||||||
break
|
|
||||||
case 'Y':
|
|
||||||
case 'NY':
|
|
||||||
obj = amfY(node.attributes)
|
|
||||||
break
|
|
||||||
case 'Z':
|
|
||||||
case 'NZ':
|
|
||||||
obj = amfZ(node.attributes)
|
|
||||||
break
|
|
||||||
// triangle elements
|
|
||||||
case 'TRIANGLE':
|
|
||||||
obj = amfTriangle(node.attributes)
|
|
||||||
break
|
|
||||||
case 'V1':
|
|
||||||
case 'VTEX1':
|
|
||||||
obj = amfV1(node.attributes)
|
|
||||||
break
|
|
||||||
case 'V2':
|
|
||||||
case 'VTEX2':
|
|
||||||
obj = amfV2(node.attributes)
|
|
||||||
break
|
|
||||||
case 'V3':
|
|
||||||
case 'VTEX3':
|
|
||||||
obj = amfV3(node.attributes)
|
|
||||||
break
|
|
||||||
// color elements
|
|
||||||
case 'COLOR':
|
|
||||||
obj = amfColor(node.attributes)
|
|
||||||
break
|
|
||||||
case 'R':
|
|
||||||
obj = amfR(node.attributes)
|
|
||||||
break
|
|
||||||
case 'G':
|
|
||||||
obj = amfG(node.attributes)
|
|
||||||
break
|
|
||||||
case 'B':
|
|
||||||
obj = amfB(node.attributes)
|
|
||||||
break
|
|
||||||
case 'A':
|
|
||||||
obj = amfA(node.attributes)
|
|
||||||
break
|
|
||||||
// map elements
|
|
||||||
case 'MAP':
|
|
||||||
case 'TEXMAP':
|
|
||||||
obj = amfMap(node.attributes)
|
|
||||||
break
|
|
||||||
case 'U1':
|
|
||||||
case 'UTEX1':
|
|
||||||
case 'WTEX1':
|
|
||||||
obj = amfU1(node.attributes)
|
|
||||||
break
|
|
||||||
case 'U2':
|
|
||||||
case 'UTEX2':
|
|
||||||
case 'WTEX2':
|
|
||||||
obj = amfU2(node.attributes)
|
|
||||||
break
|
|
||||||
case 'U3':
|
|
||||||
case 'UTEX3':
|
|
||||||
case 'WTEX3':
|
|
||||||
obj = amfU3(node.attributes)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
// console.log('opentag: '+node.name+' at line '+this.line+' position '+this.column);
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj !== null) {
|
|
||||||
// console.log('definitinon '+this.amfDefinition);
|
|
||||||
switch (this.amfDefinition) {
|
|
||||||
case 0: // definition of AMF
|
|
||||||
if ('objects' in obj) {
|
|
||||||
// console.log('push object ['+obj.type+']');
|
|
||||||
this.amfObjects.push(obj)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 1: // definition of OBJECT
|
|
||||||
if (this.amfObjects.length > 0) {
|
|
||||||
var group = this.amfObjects.pop()
|
|
||||||
// add the object to the active group if necessary
|
|
||||||
if ('objects' in group) {
|
|
||||||
// console.log('object '+group.type+' adding ['+obj.type+']');
|
|
||||||
// console.log(JSON.stringify(obj));
|
|
||||||
group.objects.push(obj)
|
|
||||||
}
|
|
||||||
this.amfObjects.push(group)
|
|
||||||
// and push this object as a group object if necessary
|
|
||||||
if ('objects' in obj) {
|
|
||||||
// console.log('object group ['+obj.type+']');
|
|
||||||
this.amfObjects.push(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 2: // definition of MATERIAL
|
|
||||||
if (obj.type === 'material') {
|
|
||||||
// console.log('push material ['+obj.type+']');
|
|
||||||
this.amfMaterials.push(obj)
|
|
||||||
} else {
|
|
||||||
if (this.amfMaterials.length > 0) {
|
|
||||||
let group = this.amfMaterials.pop()
|
|
||||||
// add the object to the active group if necessary
|
|
||||||
if ('objects' in group) {
|
|
||||||
// console.log('material '+group.type+' adding ['+obj.type+']');
|
|
||||||
// console.log(JSON.stringify(obj));
|
|
||||||
group.objects.push(obj)
|
|
||||||
}
|
|
||||||
this.amfMaterials.push(group)
|
|
||||||
// and push this object as a group object if necessary
|
|
||||||
if ('objects' in obj) {
|
|
||||||
// console.log('push material ['+obj.type+']');
|
|
||||||
this.amfMaterials.push(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 3: // definition of TEXTURE
|
|
||||||
break
|
|
||||||
case 4: // definition of CONSTELLATION
|
|
||||||
break
|
|
||||||
case 5: // definition of METADATA
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
console.log('ERROR: invalid AMF definition')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
this.amfLast = obj // retain this object in order to add values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.onclosetag = function (node) {
|
|
||||||
// console.log('onclosetag: '+this.amfDefinition);
|
|
||||||
switch (node) {
|
|
||||||
// list those which have objects
|
|
||||||
case 'AMF':
|
|
||||||
case 'OBJECT':
|
|
||||||
case 'MESH':
|
|
||||||
case 'VERTICES':
|
|
||||||
case 'VERTEX':
|
|
||||||
case 'EDGE':
|
|
||||||
case 'COORDINATES':
|
|
||||||
case 'NORMAL':
|
|
||||||
case 'VOLUME':
|
|
||||||
case 'TRIANGLE':
|
|
||||||
case 'MATERIAL':
|
|
||||||
case 'COLOR':
|
|
||||||
case 'MAP':
|
|
||||||
case 'TEXMAP':
|
|
||||||
break
|
|
||||||
case 'TEXTURE':
|
|
||||||
if (this.amfDefinition === 3) { this.amfDefinition = 0 } // resume processing
|
|
||||||
return
|
|
||||||
case 'CONSTELLATION':
|
|
||||||
if (this.amfDefinition === 4) { this.amfDefinition = 0 } // resume processing
|
|
||||||
return
|
|
||||||
case 'METADATA':
|
|
||||||
if (this.amfDefinition === 5) { this.amfDefinition = 0 } // resume processing
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// console.log('closetag: '+node);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = null
|
|
||||||
switch (this.amfDefinition) {
|
|
||||||
case 0: // definition of AMF
|
|
||||||
case 1: // definition of OBJECT
|
|
||||||
if (this.amfObjects.length > 0) {
|
|
||||||
obj = this.amfObjects.pop()
|
|
||||||
// console.log('pop object ['+obj.type+']');
|
|
||||||
if (obj.type === 'object') {
|
|
||||||
this.amfDefinition = 0 // AMF processing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check for completeness
|
|
||||||
if (this.amfObjects.length === 0) {
|
|
||||||
this.amfObj = obj
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 2: // definition of MATERIAL
|
|
||||||
if (this.amfMaterials.length > 0) {
|
|
||||||
obj = this.amfMaterials.pop()
|
|
||||||
// console.log('pop material ['+obj.type+']');
|
|
||||||
if (obj.type === 'material') {
|
|
||||||
this.amfMaterials.push(obj) // keep a list of materials
|
|
||||||
this.amfDefinition = 0 // AMF processing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 3: // definition of TEXTURE
|
|
||||||
this.amfDefinition = 0 // AMF processing
|
|
||||||
break
|
|
||||||
case 4: // definition of CONSTELLATION
|
|
||||||
this.amfDefinition = 0 // AMF processing
|
|
||||||
break
|
|
||||||
case 5: // definition of METADATA
|
|
||||||
this.amfDefinition = 0 // AMF processing
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.ontext = function (value) {
|
|
||||||
if (value !== null) {
|
|
||||||
if (this.amfLast && this.amfDefinition !== 0) {
|
|
||||||
this.amfLast.value = value
|
|
||||||
// console.log(JSON.stringify(this.amfLast));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.onend = function () {
|
|
||||||
// console.log('AMF parsing completed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the parser
|
|
||||||
parser.write(src).close()
|
|
||||||
|
|
||||||
return parser
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// convert the internal repreentation into JSCAD code
|
|
||||||
//
|
|
||||||
function codify (amf, data) {
|
|
||||||
if (amf.type !== 'amf' || (!amf.objects)) throw new Error('AMF malformed')
|
|
||||||
|
|
||||||
let code = ''
|
|
||||||
|
|
||||||
// hack due to lack of this in array map()
|
|
||||||
var objects = amf.objects
|
|
||||||
var materials = data.amfMaterials
|
|
||||||
var lastmaterial = null
|
|
||||||
function findMaterial (id) {
|
|
||||||
if (lastmaterial && lastmaterial.id === id) return lastmaterial
|
|
||||||
for (let i = 0; i < materials.length; i++) {
|
|
||||||
if (materials[i].id && materials[i].id === id) {
|
|
||||||
lastmaterial = materials[i]
|
|
||||||
return lastmaterial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
function getValue (objects, type) {
|
|
||||||
for (let i = 0; i < objects.length; i++) {
|
|
||||||
if (objects[i].type === type) return objects[i].value
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
function getColor (objects) {
|
|
||||||
for (let i = 0; i < objects.length; i++) {
|
|
||||||
var obj = objects[i]
|
|
||||||
if (obj.type === 'color') {
|
|
||||||
var r = parseFloat(getValue(obj.objects, 'r'))
|
|
||||||
var g = parseFloat(getValue(obj.objects, 'g'))
|
|
||||||
var b = parseFloat(getValue(obj.objects, 'b'))
|
|
||||||
var a = parseFloat(getValue(obj.objects, 'a'))
|
|
||||||
if (Number.isNaN(r)) r = 1.0 // AMF default color
|
|
||||||
if (Number.isNaN(g)) g = 1.0
|
|
||||||
if (Number.isNaN(b)) b = 1.0
|
|
||||||
if (Number.isNaN(a)) a = 1.0
|
|
||||||
return [r, g, b, a]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
function findColorByMaterial (id) {
|
|
||||||
var m = findMaterial(id)
|
|
||||||
if (m) {
|
|
||||||
return getColor(m.objects)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert high level definitions
|
|
||||||
function createDefinition (obj, didx) {
|
|
||||||
// console.log(materials.length);
|
|
||||||
switch (obj.type) {
|
|
||||||
case 'object':
|
|
||||||
createObject(obj, didx)
|
|
||||||
break
|
|
||||||
case 'metadata':
|
|
||||||
break
|
|
||||||
case 'material':
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
console.log('Warning: unknown definition: ' + obj.type)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// convert all objects to CSG based code
|
|
||||||
function createObject (obj, oidx) {
|
|
||||||
var vertices = [] // [x,y,z]
|
|
||||||
var faces = [] // [v1,v2,v3]
|
|
||||||
var colors = [] // [r,g,b,a]
|
|
||||||
|
|
||||||
function addCoord (coord, cidx) {
|
|
||||||
if (coord.type === 'coordinates') {
|
|
||||||
var x = parseFloat(getValue(coord.objects, 'x'))
|
|
||||||
var y = parseFloat(getValue(coord.objects, 'y'))
|
|
||||||
var z = parseFloat(getValue(coord.objects, 'z'))
|
|
||||||
// console.log('['+x+','+y+','+z+']');
|
|
||||||
vertices.push([x, y, z])
|
|
||||||
}
|
|
||||||
// normal is possible
|
|
||||||
}
|
|
||||||
function addVertex (vertex, vidx) {
|
|
||||||
// console.log(vertex.type);
|
|
||||||
if (vertex.type === 'vertex') {
|
|
||||||
vertex.objects.map(addCoord)
|
|
||||||
}
|
|
||||||
// edge is possible
|
|
||||||
}
|
|
||||||
function addTriangle (tri, tidx) {
|
|
||||||
if (tri.type === 'triangle') {
|
|
||||||
var v1 = parseInt(getValue(tri.objects, 'v1'))
|
|
||||||
var v2 = parseInt(getValue(tri.objects, 'v2'))
|
|
||||||
var v3 = parseInt(getValue(tri.objects, 'v3'))
|
|
||||||
// console.log('['+v1+','+v2+','+v3+']');
|
|
||||||
faces.push([v1, v2, v3]) // HINT: reverse order for polyhedron()
|
|
||||||
var c = getColor(tri.objects)
|
|
||||||
if (c) {
|
|
||||||
colors.push(c)
|
|
||||||
} else {
|
|
||||||
colors.push(tricolor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tricolor = null // for found colors
|
|
||||||
function addPart (part, pidx) {
|
|
||||||
// console.log(part.type);
|
|
||||||
switch (part.type) {
|
|
||||||
case 'vertices':
|
|
||||||
part.objects.map(addVertex, data)
|
|
||||||
break
|
|
||||||
case 'volume':
|
|
||||||
tricolor = getColor(part.objects)
|
|
||||||
if (part.materialid) {
|
|
||||||
// convert material to color
|
|
||||||
tricolor = findColorByMaterial(part.materialid)
|
|
||||||
}
|
|
||||||
part.objects.map(addTriangle, data)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function addMesh (mesh, midx) {
|
|
||||||
// console.log(mesh.type);
|
|
||||||
if (mesh.type === 'mesh') {
|
|
||||||
mesh.objects.map(addPart, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.objects.length > 0) {
|
|
||||||
obj.objects.map(addMesh, data)
|
|
||||||
|
|
||||||
var fcount = faces.length
|
|
||||||
var vcount = vertices.length
|
|
||||||
|
|
||||||
code += '// Object ' + obj.id + '\n'
|
|
||||||
code += '// faces : ' + fcount + '\n'
|
|
||||||
code += '// vertices: ' + vcount + '\n'
|
|
||||||
code += 'function createObject' + obj.id + '() {\n'
|
|
||||||
code += ' var polys = [];\n'
|
|
||||||
|
|
||||||
// convert the results into function calls
|
|
||||||
for (var i = 0; i < fcount; i++) {
|
|
||||||
code += ' polys.push(\n'
|
|
||||||
code += ' PP([\n'
|
|
||||||
for (var j = 0; j < faces[i].length; j++) {
|
|
||||||
if (faces[i][j] < 0 || faces[i][j] >= vcount) {
|
|
||||||
// if (err.length === '') err += 'bad index for vertice (out of range)'
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (j) code += ',\n'
|
|
||||||
code += ' VV(' + vertices[faces[i][j]] + ')'
|
|
||||||
}
|
|
||||||
code += '])'
|
|
||||||
if (colors[i]) {
|
|
||||||
var c = colors[i]
|
|
||||||
code += '.setColor([' + c[0] + ',' + c[1] + ',' + c[2] + ',' + c[3] + '])'
|
|
||||||
}
|
|
||||||
code += ');\n'
|
|
||||||
}
|
|
||||||
code += ' return CSG.fromPolygons(polys);\n'
|
|
||||||
code += '}\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start everthing
|
|
||||||
code = '// Objects : ' + objects.length + '\n'
|
|
||||||
code += '// Materials: ' + materials.length + '\n'
|
|
||||||
code += '\n'
|
|
||||||
code += '// helper functions\n'
|
|
||||||
if (amf.scale !== 1.0) {
|
|
||||||
code += 'var SCALE = ' + amf.scale + '; // scaling units (' + amf.unit + ')\n'
|
|
||||||
code += 'var VV = function(x,y,z) { return new CSG.Vertex(new CSG.Vector3D(x*SCALE,y*SCALE,z*SCALE)); };\n'
|
|
||||||
} else {
|
|
||||||
code += 'var VV = function(x,y,z) { return new CSG.Vertex(new CSG.Vector3D(x,y,z)); };\n'
|
|
||||||
}
|
|
||||||
code += 'var PP = function(a) { return new CSG.Polygon(a); };\n'
|
|
||||||
code += '\n'
|
|
||||||
code += 'function main() {\n'
|
|
||||||
code += ' var csgs = [];\n'
|
|
||||||
for (let i = 0; i < objects.length; i++) {
|
|
||||||
var obj = objects[i]
|
|
||||||
if (obj.type === 'object') {
|
|
||||||
code += ' csgs.push(createObject' + obj.id + '());\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code += ' return union(csgs);\n'
|
|
||||||
code += '}\n'
|
|
||||||
code += '\n'
|
|
||||||
|
|
||||||
objects.map(createDefinition, data)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// deserialize the given AMF source and return a JSCAD script
|
|
||||||
//
|
|
||||||
// fn (optional) original filename of AMF source
|
|
||||||
// options (optional) anonymous object with:
|
|
||||||
// pxPmm: pixels per milimeter for calcuations
|
|
||||||
// FIXME: add openjscad version in a cleaner manner ?
|
|
||||||
function deserialize (src, fn, options) {
|
|
||||||
fn = fn || 'amf'
|
|
||||||
const defaults = {version: '0.0.0'}
|
|
||||||
options = Object.assign({}, defaults, options)
|
|
||||||
const {version} = options
|
|
||||||
|
|
||||||
// parse the AMF source
|
|
||||||
const parser = createAmfParser(src)
|
|
||||||
// convert the internal objects to JSCAD code
|
|
||||||
var code = ''
|
|
||||||
code += '//\n'
|
|
||||||
code += '// producer: OpenJSCAD.org ' + version + ' AMF Importer\n'
|
|
||||||
code += '// date: ' + (new Date()) + '\n'
|
|
||||||
code += '// source: ' + fn + '\n'
|
|
||||||
code += '//\n'
|
|
||||||
if (parser.amfObj !== null) {
|
|
||||||
// console.log(JSON.stringify(parser.amfObj))
|
|
||||||
// console.log(JSON.stringify(parser.amfMaterials))
|
|
||||||
code += codify(parser.amfObj, parser)
|
|
||||||
} else {
|
|
||||||
console.log('Warning: AMF parsing failed')
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
deserialize
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
{
|
|
||||||
"_from": "@jscad/amf-deserializer@^0.0.4",
|
|
||||||
"_id": "@jscad/amf-deserializer@0.0.4",
|
|
||||||
"_inBundle": false,
|
|
||||||
"_integrity": "sha1-BB3uKQrNpBEPs7m/hxoepUIDTCs=",
|
|
||||||
"_location": "/@jscad/amf-deserializer",
|
|
||||||
"_phantomChildren": {},
|
|
||||||
"_requested": {
|
|
||||||
"type": "range",
|
|
||||||
"registry": true,
|
|
||||||
"raw": "@jscad/amf-deserializer@^0.0.4",
|
|
||||||
"name": "@jscad/amf-deserializer",
|
|
||||||
"escapedName": "@jscad%2famf-deserializer",
|
|
||||||
"scope": "@jscad",
|
|
||||||
"rawSpec": "^0.0.4",
|
|
||||||
"saveSpec": null,
|
|
||||||
"fetchSpec": "^0.0.4"
|
|
||||||
},
|
|
||||||
"_requiredBy": [
|
|
||||||
"/@jscad/io"
|
|
||||||
],
|
|
||||||
"_resolved": "https://registry.npmjs.org/@jscad/amf-deserializer/-/amf-deserializer-0.0.4.tgz",
|
|
||||||
"_shasum": "041dee290acda4110fb3b9bf871a1ea542034c2b",
|
|
||||||
"_spec": "@jscad/amf-deserializer@^0.0.4",
|
|
||||||
"_where": "C:\\Users\\tomate\\node_modules\\@jscad\\io",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/jscad/io/issues"
|
|
||||||
},
|
|
||||||
"bundleDependencies": false,
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "Rene K. Mueller",
|
|
||||||
"url": "http://renekmueller.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "z3dev",
|
|
||||||
"url": "http://www.z3d.jp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Mark 'kaosat-dev' Moissette",
|
|
||||||
"url": "http://kaosat.net"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"sax": "^1.2.1"
|
|
||||||
},
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "Amf deserializer for jscad project",
|
|
||||||
"homepage": "https://github.com/jscad/io#readme",
|
|
||||||
"keywords": [
|
|
||||||
"openjscad",
|
|
||||||
"jscad",
|
|
||||||
"csg",
|
|
||||||
"deserializer",
|
|
||||||
"amf"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "index.js",
|
|
||||||
"name": "@jscad/amf-deserializer",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/jscad/io.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"release-major": "git checkout master && npm version major && git commit -a -m 'chore(dist): built dist/'; git push origin master --tags ",
|
|
||||||
"release-minor": "git checkout master && npm version minor && git commit -a -m 'chore(dist): built dist/'; git push origin master --tags ",
|
|
||||||
"release-patch": "git checkout master && npm version patch && git commit -a -m 'chore(dist): built dist/'; git push origin master --tags ",
|
|
||||||
"test": "ava './test.js' --verbose --timeout 10000"
|
|
||||||
},
|
|
||||||
"version": "0.0.4"
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
const test = require('ava')
|
|
||||||
const deserializer = require('./index.js')
|
|
||||||
|
|
||||||
test.todo('add some actual tests later')
|
|
@ -1,36 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
||||||
|
|
||||||
<a name="0.0.5"></a>
|
|
||||||
## [0.0.5](https://github.com/jscad/io/compare/@jscad/amf-serializer@0.0.4...@jscad/amf-serializer@0.0.5) (2017-11-04)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-serializer
|
|
||||||
|
|
||||||
<a name="0.0.4"></a>
|
|
||||||
## [0.0.4](https://github.com/jscad/io/compare/@jscad/amf-serializer@0.0.3...@jscad/amf-serializer@0.0.4) (2017-10-30)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-serializer
|
|
||||||
|
|
||||||
<a name="0.0.3"></a>
|
|
||||||
## [0.0.3](https://github.com/jscad/io/compare/@jscad/amf-serializer@0.0.2...@jscad/amf-serializer@0.0.3) (2017-10-10)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-serializer
|
|
||||||
|
|
||||||
<a name="0.0.2"></a>
|
|
||||||
## 0.0.2 (2017-10-10)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @jscad/amf-serializer
|
|
@ -1,54 +0,0 @@
|
|||||||
## @jscad/amf-serializer
|
|
||||||
|
|
||||||
> amf serializer for the jscad project (from CSG)
|
|
||||||
|
|
||||||
[![npm version](https://badge.fury.io/js/%40jscad%2Famf-serializer.svg)](https://badge.fury.io/js/%40jscad%2Famf-serializer)
|
|
||||||
[![Build Status](https://travis-ci.org/jscad/io.svg)](https://travis-ci.org/jscad/amf-serializer)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This serializer outputs a 'blobable' array of data (from a CSG object)
|
|
||||||
ie an array that can either be passed directly to a Blob (`new Blob(blobable)`)
|
|
||||||
or converted to a Node.js buffer.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Contribute](#contribute)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install @jscad/amf-serializer
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const amfSerializer = require('@jscad/amf-serializer')
|
|
||||||
|
|
||||||
const rawData = amfSerializer(CSGObject)
|
|
||||||
|
|
||||||
//in browser (with browserify etc)
|
|
||||||
const blob = new Blob(rawData)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
For questions about the API, please contact the [User Group](https://plus.google.com/communities/114958480887231067224)
|
|
||||||
|
|
||||||
PRs accepted.
|
|
||||||
|
|
||||||
Small Note: If editing this README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[The MIT License (MIT)](./LICENSE)
|
|
||||||
(unless specified otherwise)
|
|
@ -1,70 +0,0 @@
|
|||||||
const { ensureManifoldness } = require('@jscad/io-utils')
|
|
||||||
const mimeType = 'application/amf+xml'
|
|
||||||
|
|
||||||
function serialize (CSG, m) {
|
|
||||||
CSG = ensureManifoldness(CSG)
|
|
||||||
var result = '<?xml version="1.0" encoding="UTF-8"?>\n<amf' + (m && m.unit ? ' unit="+m.unit"' : '') + '>\n'
|
|
||||||
for (var k in m) {
|
|
||||||
result += '<metadata type="' + k + '">' + m[k] + '</metadata>\n'
|
|
||||||
}
|
|
||||||
result += '<object id="0">\n<mesh>\n<vertices>\n'
|
|
||||||
|
|
||||||
CSG.polygons.map(function (p) { // first we dump all vertices of all polygons
|
|
||||||
for (var i = 0; i < p.vertices.length; i++) {
|
|
||||||
result += CSGVertextoAMFString(p.vertices[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
result += '</vertices>\n'
|
|
||||||
|
|
||||||
var n = 0
|
|
||||||
CSG.polygons.map(function (p) { // then we dump all polygons
|
|
||||||
result += '<volume>\n'
|
|
||||||
if (p.vertices.length < 3) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var color = null
|
|
||||||
if (p.shared && p.shared.color) {
|
|
||||||
color = p.shared.color
|
|
||||||
} else if (p.color) {
|
|
||||||
color = p.color
|
|
||||||
}
|
|
||||||
if (color != null) {
|
|
||||||
if (color.length < 4) color.push(1.0)
|
|
||||||
result += '<color><r>' + color[0] + '</r><g>' + color[1] + '</g><b>' + color[2] + '</b><a>' + color[3] + '</a></color>'
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < p.vertices.length - 2; i++) { // making sure they are all triangles (triangular polygons)
|
|
||||||
result += '<triangle>'
|
|
||||||
result += '<v1>' + (n) + '</v1>'
|
|
||||||
result += '<v2>' + (n + i + 1) + '</v2>'
|
|
||||||
result += '<v3>' + (n + i + 2) + '</v3>'
|
|
||||||
result += '</triangle>\n'
|
|
||||||
}
|
|
||||||
n += p.vertices.length
|
|
||||||
result += '</volume>\n'
|
|
||||||
})
|
|
||||||
result += '</mesh>\n</object>\n'
|
|
||||||
result += '</amf>\n'
|
|
||||||
return [result]
|
|
||||||
}
|
|
||||||
|
|
||||||
function CSGVectortoAMFString (v) {
|
|
||||||
return '<x>' + v._x + '</x><y>' + v._y + '</y><z>' + v._z + '</z>'
|
|
||||||
}
|
|
||||||
|
|
||||||
function CSGVertextoAMFString (vertex) {
|
|
||||||
return '<vertex><coordinates>' + CSGVectortoAMFString(vertex.pos) + '</coordinates></vertex>\n'
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
CSG.Vector3D.prototype.toAMFString = function () {
|
|
||||||
return '<x>' + this._x + '</x><y>' + this._y + '</y><z>' + this._z + '</z>'
|
|
||||||
}
|
|
||||||
|
|
||||||
CSG.Vertex.prototype.toAMFString = function () {
|
|
||||||
return '<vertex><coordinates>' + this.pos.toAMFString() + '</coordinates></vertex>\n'
|
|
||||||
} */
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
serialize,
|
|
||||||
mimeType
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
"_from": "@jscad/amf-serializer@^0.0.5",
|
|
||||||
"_id": "@jscad/amf-serializer@0.0.5",
|
|
||||||
"_inBundle": false,
|
|
||||||
"_integrity": "sha1-3A+Hx7LG15/F9rZnfwxoag0/Bg4=",
|
|
||||||
"_location": "/@jscad/amf-serializer",
|
|
||||||
"_phantomChildren": {},
|
|
||||||
"_requested": {
|
|
||||||
"type": "range",
|
|
||||||
"registry": true,
|
|
||||||
"raw": "@jscad/amf-serializer@^0.0.5",
|
|
||||||
"name": "@jscad/amf-serializer",
|
|
||||||
"escapedName": "@jscad%2famf-serializer",
|
|
||||||
"scope": "@jscad",
|
|
||||||
"rawSpec": "^0.0.5",
|
|
||||||
"saveSpec": null,
|
|
||||||
"fetchSpec": "^0.0.5"
|
|
||||||
},
|
|
||||||
"_requiredBy": [
|
|
||||||
"/@jscad/io"
|
|
||||||
],
|
|
||||||
"_resolved": "https://registry.npmjs.org/@jscad/amf-serializer/-/amf-serializer-0.0.5.tgz",
|
|
||||||
"_shasum": "dc0f87c7b2c6d79fc5f6b6677f0c686a0d3f060e",
|
|
||||||
"_spec": "@jscad/amf-serializer@^0.0.5",
|
|
||||||
"_where": "C:\\Users\\tomate\\node_modules\\@jscad\\io",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/jscad/io/issues"
|
|
||||||
},
|
|
||||||
"bundleDependencies": false,
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "Rene K. Mueller",
|
|
||||||
"url": "http://renekmueller.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "z3dev",
|
|
||||||
"url": "http://www.z3d.jp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Mark 'kaosat-dev' Moissette",
|
|
||||||
"url": "http://kaosat.net"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@jscad/io-utils": "^0.1.2",
|
|
||||||
"sax": "^1.2.1",
|
|
||||||
"xmldom": "^0.1.27"
|
|
||||||
},
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "Amf serializer for jscad project",
|
|
||||||
"devDependencies": {
|
|
||||||
"@jscad/csg": "0.3.6"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/jscad/io#readme",
|
|
||||||
"keywords": [
|
|
||||||
"openjscad",
|
|
||||||
"jscad",
|
|
||||||
"csg",
|
|
||||||
"serializer",
|
|
||||||
"amf"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "index.js",
|
|
||||||
"name": "@jscad/amf-serializer",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/jscad/io.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"release-major": "git checkout master && npm version major && git commit -a -m 'chore(dist): built dist/'; git push origin master --tags ",
|
|
||||||
"release-minor": "git checkout master && npm version minor && git commit -a -m 'chore(dist): built dist/'; git push origin master --tags ",
|
|
||||||
"release-patch": "git checkout master && npm version patch && git commit -a -m 'chore(dist): built dist/'; git push origin master --tags ",
|
|
||||||
"test": "ava './test.js' --verbose --timeout 10000"
|
|
||||||
},
|
|
||||||
"version": "0.0.5"
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
const test = require('ava')
|
|
||||||
const {CSG} = require('@jscad/csg')
|
|
||||||
const serializer = require('./index.js')
|
|
||||||
|
|
||||||
test('serialize csg to amf', function (t) {
|
|
||||||
const input = new CSG.cube()
|
|
||||||
const expected = [ '<?xml version="1.0" encoding="UTF-8"?>\n<amf>\n<object id="0">\n<mesh>\n<vertices>\n<vertex><coordinates><x>-1</x><y>-1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>-1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>-1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>-1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>-1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>-1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>-1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>-1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>-1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>-1</y><z>-1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>-1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>-1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>1</x><y>1</y><z>1</z></coordinates></vertex>\n<vertex><coordinates><x>-1</x><y>1</y><z>1</z></coordinates></vertex>\n</vertices>\n<volume>\n<triangle><v1>0</v1><v2>1</v2><v3>2</v3></triangle>\n<triangle><v1>0</v1><v2>2</v2><v3>3</v3></triangle>\n</volume>\n<volume>\n<triangle><v1>4</v1><v2>5</v2><v3>6</v3></triangle>\n<triangle><v1>4</v1><v2>6</v2><v3>7</v3></triangle>\n</volume>\n<volume>\n<triangle><v1>8</v1><v2>9</v2><v3>10</v3></triangle>\n<triangle><v1>8</v1><v2>10</v2><v3>11</v3></triangle>\n</volume>\n<volume>\n<triangle><v1>12</v1><v2>13</v2><v3>14</v3></triangle>\n<triangle><v1>12</v1><v2>14</v2><v3>15</v3></triangle>\n</volume>\n<volume>\n<triangle><v1>16</v1><v2>17</v2><v3>18</v3></triangle>\n<triangle><v1>16</v1><v2>18</v2><v3>19</v3></triangle>\n</volume>\n<volume>\n<triangle><v1>20</v1><v2>21</v2><v3>22</v3></triangle>\n<triangle><v1>20</v1><v2>22</v2><v3>23</v3></triangle>\n</volume>\n</mesh>\n</object>\n</amf>\n' ]
|
|
||||||
const observed = serializer.serialize(input)
|
|
||||||
|
|
||||||
t.deepEqual(observed, expected)
|
|
||||||
})
|
|
142
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/CHANGELOG.md
generated
vendored
142
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/CHANGELOG.md
generated
vendored
@ -1,142 +0,0 @@
|
|||||||
<a name="0.3.6"></a>
|
|
||||||
## [0.3.6](https://github.com/jscad/csg.js/compare/v0.3.5...v0.3.6) (2017-11-03)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.3.5"></a>
|
|
||||||
## [0.3.5](https://github.com/jscad/csg.js/compare/v0.3.4...v0.3.5) (2017-11-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **fixTJunctions:** fixes issues with fixTJunctions ([#63](https://github.com/jscad/csg.js/issues/63)) ([78c5102](https://github.com/jscad/csg.js/commit/78c5102))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.3.4"></a>
|
|
||||||
## [0.3.4](https://github.com/jscad/csg.js/compare/v0.3.3...v0.3.4) (2017-11-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **ConnectorsList:** add back missing ConnectorsList ([#62](https://github.com/jscad/csg.js/issues/62)) ([cfc1c7e](https://github.com/jscad/csg.js/commit/cfc1c7e))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.3.3"></a>
|
|
||||||
## [0.3.3](https://github.com/jscad/csg.js/compare/v0.3.2...v0.3.3) (2017-11-01)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.3.2"></a>
|
|
||||||
## [0.3.2](https://github.com/jscad/csg.js/compare/v0.3.1...v0.3.2) (2017-10-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **path2D:** added missing innerToCAG method to path2D proto & added tests & docs ([#52](https://github.com/jscad/csg.js/issues/52)) ([4a5e37e](https://github.com/jscad/csg.js/commit/4a5e37e))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.3.1"></a>
|
|
||||||
## [0.3.1](https://github.com/jscad/csg.js/compare/v0.3.0...v0.3.1) (2017-06-11)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.3.0"></a>
|
|
||||||
# [0.3.0](https://github.com/jscad/csg.js/compare/v0.2.4...v0.3.0) (2017-05-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **CAG.toPoints:** CAG Enhancement for toPoints() ([d023243](https://github.com/jscad/csg.js/commit/d023243))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.2.4"></a>
|
|
||||||
## [0.2.4](https://github.com/jscad/csg.js/compare/v0.2.3...v0.2.4) (2017-05-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **CAG:** reverted back CAG to default to canonicalized = false ([68a0558](https://github.com/jscad/csg.js/commit/68a0558))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.2.3"></a>
|
|
||||||
## [0.2.3](https://github.com/jscad/csg.js/compare/v0.2.2...v0.2.3) (2017-05-19)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.2.2"></a>
|
|
||||||
## [0.2.2](https://github.com/jscad/csg.js/compare/v0.2.1...v0.2.2) (2017-05-19)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.2.1"></a>
|
|
||||||
## [0.2.1](https://github.com/jscad/csg.js/compare/v0.2.0...v0.2.1) (2017-04-27)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.2.0"></a>
|
|
||||||
# [0.2.0](https://github.com/jscad/csg.js/compare/v0.1.4...v0.2.0) (2017-04-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **asserts:** fixed issue with asserts in latest ava ([1210f66](https://github.com/jscad/csg.js/commit/1210f66))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.1.4"></a>
|
|
||||||
## [0.1.4](https://github.com/jscad/csg.js/compare/v0.1.3...v0.1.4) (2017-01-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **README:** more attempts at fixing README on npm ... ([a5ae096](https://github.com/jscad/csg.js/commit/a5ae096))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.1.3"></a>
|
|
||||||
## [0.1.3](https://github.com/jscad/csg.js/compare/v0.1.2...v0.1.3) (2017-01-27)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.1.2"></a>
|
|
||||||
## [0.1.2](https://github.com/jscad/csg.js/compare/v0.1.1...v0.1.2) (2017-01-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **typo:** fixed typo in package name ([4f2aec6](https://github.com/jscad/csg.js/commit/4f2aec6))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.1.1"></a>
|
|
||||||
## [0.1.1](https://github.com/jscad/csg.js/compare/v0.1.0...v0.1.1) (2017-01-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **babelrc:** added missing babelrc ([94f9684](https://github.com/jscad/csg.js/commit/94f9684))
|
|
||||||
* **package:** removed part of package relevant to 'builds' ([7bf4815](https://github.com/jscad/csg.js/commit/7bf4815))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.1.0"></a>
|
|
||||||
# [0.1.0](https://github.com/jscad/csg.js/compare/af6453c...v0.1.0) (2017-01-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* bools fail if cylinder resolution not integer. Solution: parse all resolution as int ([af6453c](https://github.com/jscad/csg.js/commit/af6453c))
|
|
||||||
* **package:** fixed package name ([cbba148](https://github.com/jscad/csg.js/commit/cbba148))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **csg.js:** updated csg.js based on recent changes in OpenjSCAD.org ([db1d133](https://github.com/jscad/csg.js/commit/db1d133))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
73
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/CONTRIBUTING.md
generated
vendored
73
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/CONTRIBUTING.md
generated
vendored
@ -1,73 +0,0 @@
|
|||||||
## csg.js
|
|
||||||
|
|
||||||
## Constructive Solid Geometry Library - Contributing Guide
|
|
||||||
|
|
||||||
This library is part of the JSCAD Organization, and is maintained by a group of volunteers. We welcome and encourage anyone to pitch in but please take a moment to read the following guidelines.
|
|
||||||
|
|
||||||
* If you want to submit a bug report please make sure to follow the [Reporting Issues](https://github.com/jscad/csg.js/wiki/Reporting-Issues) guide. Bug reports are accepted as [Issues](https://github.com/jscad/csg.js/issues/) via GitHub.
|
|
||||||
|
|
||||||
* If you want to submit a change or a patch, please read the contents below on how to make changes. New contributions are accepted as [Pull Requests](https://github.com/jscad/csg.js/pulls/) via GithHub.
|
|
||||||
|
|
||||||
* We only accept bug reports and pull requests on **GitHub**.
|
|
||||||
|
|
||||||
* If you have a question about how to use CSG.js, then please start a conversation at the [OpenJSCAD.org User Group](https://plus.google.com/communities/114958480887231067224). You might find the answer in the [OpenJSCAD.org User Guide](https://github.com/Spiritdude/OpenJSCAD.org/wiki/User-Guide).
|
|
||||||
|
|
||||||
* If you have a change or new feature in mind, please start a conversation with the [Core Developers](https://plus.google.com/communities/114958480887231067224) and start contributing changes.
|
|
||||||
|
|
||||||
Thanks!
|
|
||||||
|
|
||||||
The JSCAD Organization
|
|
||||||
|
|
||||||
## Making Changes
|
|
||||||
|
|
||||||
First, we suggest that you fork this GIT repository. This will keep your changes separate from the fast lane. And make pull requests easier.
|
|
||||||
|
|
||||||
Once forked, clone your copy of the CSG library.
|
|
||||||
```
|
|
||||||
git clone https://github.com/myusername/csg.js.git
|
|
||||||
cd csg.js
|
|
||||||
```
|
|
||||||
|
|
||||||
**We suggest downloading NPM. This will allow you to generate API documents, and run test suites.**
|
|
||||||
|
|
||||||
Once you have NPM, install all the dependencies for development.
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
And, run the test cases to verify the library is functional.
|
|
||||||
```
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
And, create the API documentation.
|
|
||||||
```
|
|
||||||
npm run build-docs
|
|
||||||
```
|
|
||||||
|
|
||||||
The project is structured as:
|
|
||||||
- csg.js : library source code
|
|
||||||
- test/*.js : various test suites
|
|
||||||
- helpers/*.js : various helper classes for testing
|
|
||||||
- objects/* : objects created by test suites
|
|
||||||
- doc/* : generated API documentation
|
|
||||||
|
|
||||||
You can now make changes to the library, as well as the test suites.
|
|
||||||
|
|
||||||
If you intend to contribute changes back to JSCAD then please be sure to create test cases.
|
|
||||||
|
|
||||||
Done? Great! Push the changes back to the fork.
|
|
||||||
```
|
|
||||||
git commit changed-file
|
|
||||||
git add test/new-test-suite.js
|
|
||||||
git commit test/new-test-suite.js
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
Finally, you can review your changes via GitHub, and create a pull request.
|
|
||||||
|
|
||||||
TIPS for successful pull requests:
|
|
||||||
- Commit often, and comment well
|
|
||||||
- Follow the [JavaScript Standard Style](https://github.com/feross/standard)
|
|
||||||
- Create test cases for all changes
|
|
||||||
- Verify that all tests suites pass
|
|
||||||
|
|
||||||
WOW! Thanks for the cool code.
|
|
||||||
|
|
22
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/LICENSE
generated
vendored
22
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/LICENSE
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2017 @jscad
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
67
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/README.md
generated
vendored
67
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/README.md
generated
vendored
@ -1,67 +0,0 @@
|
|||||||
## csg.js
|
|
||||||
|
|
||||||
## Constructive Solid Geometry (CSG) Library
|
|
||||||
|
|
||||||
[![GitHub version](https://badge.fury.io/gh/jscad%2Fcsg.js.svg)](https://badge.fury.io/gh/jscad%2Fcsg.js)
|
|
||||||
[![Build Status](https://travis-ci.org/jscad/csg.js.svg)](https://travis-ci.org/jscad/csg.js)
|
|
||||||
|
|
||||||
> Solid modelling library (2d & 3d)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Constructive Solid Geometry (CSG) is a modelling technique that uses Boolean operations like union and intersection to combine 3D solids. This library implements CSG operations on meshes elegantly and concisely using BSP trees, and is meant to serve as an easily understandable implementation of the algorithm.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [API](#api)
|
|
||||||
- [Contribute](#contribute)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install @jscad/csg
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
- as Node module :
|
|
||||||
|
|
||||||
```
|
|
||||||
const csg = require('@jscad/csg')
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
The API documentation can be found [here](./docs/api.md).
|
|
||||||
|
|
||||||
Also see the [OpenJsCad User Guide](https://en.wikibooks.org/wiki/OpenJSCAD_User_Guide).
|
|
||||||
|
|
||||||
For questions about the API, please contact the [User Group](https://plus.google.com/communities/114958480887231067224)
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
This library is part of the JSCAD Organization, and is maintained by a group of volunteers. We welcome and encourage anyone to pitch in but please take a moment to read the following guidelines.
|
|
||||||
|
|
||||||
* If you want to submit a bug report please make sure to follow the [Reporting Issues](https://github.com/jscad/csg.js/wiki/Reporting-Issues) guide. Bug reports are accepted as [Issues](https://github.com/jscad/csg.js/issues/) via GitHub.
|
|
||||||
|
|
||||||
* If you want to submit a change or a patch, please see the [Contributing guidelines](https://github.com/jscad/csg.js/blob/master/CONTRIBUTING.md). New contributions are accepted as [Pull Requests](https://github.com/jscad/csg.js/pulls/) via GithHub.
|
|
||||||
|
|
||||||
* We only accept bug reports and pull requests on **GitHub**.
|
|
||||||
|
|
||||||
* If you have a question about how to use CSG.js, then please start a conversation at the [OpenJSCAD.org User Group](https://plus.google.com/communities/114958480887231067224). You might find the answer in the [OpenJSCAD.org User Guide](https://github.com/Spiritdude/OpenJSCAD.org/wiki/User-Guide).
|
|
||||||
|
|
||||||
* If you have a change or new feature in mind, please start a conversation with the [Core Developers](https://plus.google.com/communities/114958480887231067224) and start contributing changes.
|
|
||||||
|
|
||||||
Small Note: If editing this README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
||||||
|
|
||||||
## Copyrights
|
|
||||||
|
|
||||||
Some copyrights apply. Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl), under the MIT license. Copyright (c) 2011 Evan Wallace (http://madebyevan.com/)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[The MIT License (MIT)](https://github.com/jscad/csg.js/blob/master/LICENSE)
|
|
||||||
(unless specified otherwise)
|
|
189
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/csg.js
generated
vendored
189
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/csg.js
generated
vendored
@ -1,189 +0,0 @@
|
|||||||
/*
|
|
||||||
## License
|
|
||||||
|
|
||||||
Copyright (c) 2014 bebbi (elghatta@gmail.com)
|
|
||||||
Copyright (c) 2013 Eduard Bespalov (edwbes@gmail.com)
|
|
||||||
Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl)
|
|
||||||
Copyright (c) 2011 Evan Wallace (http://evanw.github.com/csg.js/)
|
|
||||||
Copyright (c) 2012 Alexandre Girard (https://github.com/alx)
|
|
||||||
|
|
||||||
All code released under MIT license
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
For an overview of the CSG process see the original csg.js code:
|
|
||||||
http://evanw.github.com/csg.js/
|
|
||||||
|
|
||||||
CSG operations through BSP trees suffer from one problem: heavy fragmentation
|
|
||||||
of polygons. If two CSG solids of n polygons are unified, the resulting solid may have
|
|
||||||
in the order of n*n polygons, because each polygon is split by the planes of all other
|
|
||||||
polygons. After a few operations the number of polygons explodes.
|
|
||||||
|
|
||||||
This version of CSG.js solves the problem in 3 ways:
|
|
||||||
|
|
||||||
1. Every polygon split is recorded in a tree (CSG.PolygonTreeNode). This is a separate
|
|
||||||
tree, not to be confused with the CSG tree. If a polygon is split into two parts but in
|
|
||||||
the end both fragments have not been discarded by the CSG operation, we can retrieve
|
|
||||||
the original unsplit polygon from the tree, instead of the two fragments.
|
|
||||||
|
|
||||||
This does not completely solve the issue though: if a polygon is split multiple times
|
|
||||||
the number of fragments depends on the order of subsequent splits, and we might still
|
|
||||||
end up with unncessary splits:
|
|
||||||
Suppose a polygon is first split into A and B, and then into A1, B1, A2, B2. Suppose B2 is
|
|
||||||
discarded. We will end up with 2 polygons: A and B1. Depending on the actual split boundaries
|
|
||||||
we could still have joined A and B1 into one polygon. Therefore a second approach is used as well:
|
|
||||||
|
|
||||||
2. After CSG operations all coplanar polygon fragments are joined by a retesselating
|
|
||||||
operation. See CSG.reTesselated(). Retesselation is done through a
|
|
||||||
linear sweep over the polygon surface. The sweep line passes over the y coordinates
|
|
||||||
of all vertices in the polygon. Polygons are split at each sweep line, and the fragments
|
|
||||||
are joined horizontally and vertically into larger polygons (making sure that we
|
|
||||||
will end up with convex polygons).
|
|
||||||
This still doesn't solve the problem completely: due to floating point imprecisions
|
|
||||||
we may end up with small gaps between polygons, and polygons may not be exactly coplanar
|
|
||||||
anymore, and as a result the retesselation algorithm may fail to join those polygons.
|
|
||||||
Therefore:
|
|
||||||
|
|
||||||
3. A canonicalization algorithm is implemented: it looks for vertices that have
|
|
||||||
approximately the same coordinates (with a certain tolerance, say 1e-5) and replaces
|
|
||||||
them with the same vertex. If polygons share a vertex they will actually point to the
|
|
||||||
same CSG.Vertex instance. The same is done for polygon planes. See CSG.canonicalized().
|
|
||||||
|
|
||||||
Performance improvements to the original CSG.js:
|
|
||||||
|
|
||||||
Replaced the flip() and invert() methods by flipped() and inverted() which don't
|
|
||||||
modify the source object. This allows to get rid of all clone() calls, so that
|
|
||||||
multiple polygons can refer to the same CSG.Plane instance etc.
|
|
||||||
|
|
||||||
The original union() used an extra invert(), clipTo(), invert() sequence just to remove the
|
|
||||||
coplanar front faces from b; this is now combined in a single b.clipTo(a, true) call.
|
|
||||||
|
|
||||||
Detection whether a polygon is in front or in back of a plane: for each polygon
|
|
||||||
we are caching the coordinates of the bounding sphere. If the bounding sphere is
|
|
||||||
in front or in back of the plane we don't have to check the individual vertices
|
|
||||||
anymore.
|
|
||||||
|
|
||||||
Other additions to the original CSG.js:
|
|
||||||
|
|
||||||
CSG.Vector class has been renamed into CSG.Vector3D
|
|
||||||
|
|
||||||
Classes for 3D lines, 2D vectors, 2D lines, and methods to find the intersection of
|
|
||||||
a line and a plane etc.
|
|
||||||
|
|
||||||
Transformations: CSG.transform(), CSG.translate(), CSG.rotate(), CSG.scale()
|
|
||||||
|
|
||||||
Expanding or contracting a solid: CSG.expand() and CSG.contract(). Creates nice
|
|
||||||
smooth corners.
|
|
||||||
|
|
||||||
The vertex normal has been removed since it complicates retesselation. It's not needed
|
|
||||||
for solid CAD anyway.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const {addTransformationMethodsToPrototype, addCenteringToPrototype} = require('./src/mutators')
|
|
||||||
let CSG = require('./src/CSG')
|
|
||||||
let CAG = require('./src/CAG')
|
|
||||||
|
|
||||||
// FIXME: how many are actual usefull to be exposed as API ?? looks like a code smell
|
|
||||||
const { _CSGDEBUG,
|
|
||||||
defaultResolution2D,
|
|
||||||
defaultResolution3D,
|
|
||||||
EPS,
|
|
||||||
angleEPS,
|
|
||||||
areaEPS,
|
|
||||||
all,
|
|
||||||
top,
|
|
||||||
bottom,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
front,
|
|
||||||
back,
|
|
||||||
staticTag,
|
|
||||||
getTag} = require('./src/constants')
|
|
||||||
|
|
||||||
CSG._CSGDEBUG = _CSGDEBUG
|
|
||||||
CSG.defaultResolution2D = defaultResolution2D
|
|
||||||
CSG.defaultResolution3D = defaultResolution3D
|
|
||||||
CSG.EPS = EPS
|
|
||||||
CSG.angleEPS = angleEPS
|
|
||||||
CSG.areaEPS = areaEPS
|
|
||||||
CSG.all = all
|
|
||||||
CSG.top = top
|
|
||||||
CSG.bottom = bottom
|
|
||||||
CSG.left = left
|
|
||||||
CSG.right = right
|
|
||||||
CSG.front = front
|
|
||||||
CSG.back = back
|
|
||||||
CSG.staticTag = staticTag
|
|
||||||
CSG.getTag = getTag
|
|
||||||
|
|
||||||
// eek ! all this is kept for backwards compatibility...for now
|
|
||||||
CSG.Vector2D = require('./src/math/Vector2')
|
|
||||||
CSG.Vector3D = require('./src/math/Vector3')
|
|
||||||
CSG.Vertex = require('./src/math/Vertex3')
|
|
||||||
CAG.Vertex = require('./src/math/Vertex2')
|
|
||||||
CSG.Plane = require('./src/math/Plane')
|
|
||||||
CSG.Polygon = require('./src/math/Polygon3')
|
|
||||||
CSG.Polygon2D = require('./src/math/Polygon2')
|
|
||||||
CSG.Line2D = require('./src/math/Line2')
|
|
||||||
CSG.Line3D = require('./src/math/Line3')
|
|
||||||
CSG.Path2D = require('./src/math/Path2')
|
|
||||||
CSG.OrthoNormalBasis = require('./src/math/OrthoNormalBasis')
|
|
||||||
CSG.Matrix4x4 = require('./src/math/Matrix4')
|
|
||||||
|
|
||||||
CAG.Side = require('./src/math/Side')
|
|
||||||
|
|
||||||
CSG.Connector = require('./src/connectors').Connector
|
|
||||||
CSG.ConnectorList = require('./src/connectors').ConnectorList
|
|
||||||
CSG.Properties = require('./src/Properties')
|
|
||||||
|
|
||||||
const {circle, ellipse, rectangle, roundedRectangle} = require('./src/primitives2d')
|
|
||||||
const {sphere, cube, roundedCube, cylinder, roundedCylinder, cylinderElliptic, polyhedron} = require('./src/primitives3d')
|
|
||||||
|
|
||||||
CSG.sphere = sphere
|
|
||||||
CSG.cube = cube
|
|
||||||
CSG.roundedCube = roundedCube
|
|
||||||
CSG.cylinder = cylinder
|
|
||||||
CSG.roundedCylinder = roundedCylinder
|
|
||||||
CSG.cylinderElliptic = cylinderElliptic
|
|
||||||
CSG.polyhedron = polyhedron
|
|
||||||
|
|
||||||
CAG.circle = circle
|
|
||||||
CAG.ellipse = ellipse
|
|
||||||
CAG.rectangle = rectangle
|
|
||||||
CAG.roundedRectangle = roundedRectangle
|
|
||||||
|
|
||||||
//
|
|
||||||
const {fromCompactBinary, fromObject, fromSlices} = require('./src/CSGFactories')
|
|
||||||
CSG.fromCompactBinary = fromCompactBinary
|
|
||||||
CSG.fromObject = fromObject
|
|
||||||
CSG.fromSlices = fromSlices
|
|
||||||
|
|
||||||
CSG.toPointCloud = require('./src/debugHelpers').toPointCloud
|
|
||||||
|
|
||||||
const CAGMakers = require('./src/CAGFactories')
|
|
||||||
CAG.fromObject = CAGMakers.fromObject
|
|
||||||
CAG.fromPointsNoCheck = CAGMakers.fromPointsNoCheck
|
|
||||||
CAG.fromPath2 = CAGMakers.fromPath2
|
|
||||||
|
|
||||||
// ////////////////////////////////////
|
|
||||||
addTransformationMethodsToPrototype(CSG.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Vector2D.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Vector3D.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Vertex.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Plane.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Polygon.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Line2D.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Line3D.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Path2D.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.OrthoNormalBasis.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CSG.Connector.prototype)
|
|
||||||
|
|
||||||
addTransformationMethodsToPrototype(CAG.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CAG.Side.prototype)
|
|
||||||
addTransformationMethodsToPrototype(CAG.Vertex.prototype)
|
|
||||||
|
|
||||||
addCenteringToPrototype(CSG.prototype, ['x', 'y', 'z'])
|
|
||||||
addCenteringToPrototype(CAG.prototype, ['x', 'y'])
|
|
||||||
|
|
||||||
module.exports = {CSG, CAG}
|
|
1089
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/docs/api.md
generated
vendored
1089
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/docs/api.md
generated
vendored
File diff suppressed because it is too large
Load Diff
108
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/package.json
generated
vendored
108
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/package.json
generated
vendored
@ -1,108 +0,0 @@
|
|||||||
{
|
|
||||||
"_from": "@jscad/csg@0.3.6",
|
|
||||||
"_id": "@jscad/csg@0.3.6",
|
|
||||||
"_inBundle": false,
|
|
||||||
"_integrity": "sha512-GoUXhTwO0L+Cxba8VfFFLGm+ECd0xZKoIAPpeUYsDiCVsIZ6XK+3GLgBgn5waeumQNh+H6WsHjMMRKXBrDBreA==",
|
|
||||||
"_location": "/@jscad/csg",
|
|
||||||
"_phantomChildren": {},
|
|
||||||
"_requested": {
|
|
||||||
"type": "version",
|
|
||||||
"registry": true,
|
|
||||||
"raw": "@jscad/csg@0.3.6",
|
|
||||||
"name": "@jscad/csg",
|
|
||||||
"escapedName": "@jscad%2fcsg",
|
|
||||||
"scope": "@jscad",
|
|
||||||
"rawSpec": "0.3.6",
|
|
||||||
"saveSpec": null,
|
|
||||||
"fetchSpec": "0.3.6"
|
|
||||||
},
|
|
||||||
"_requiredBy": [
|
|
||||||
"/@jscad/json-deserializer",
|
|
||||||
"/@jscad/json-serializer",
|
|
||||||
"/@jscad/openjscad",
|
|
||||||
"/@jscad/stl-deserializer",
|
|
||||||
"/@jscad/svg-deserializer",
|
|
||||||
"/@jscad/svg-serializer"
|
|
||||||
],
|
|
||||||
"_resolved": "https://registry.npmjs.org/@jscad/csg/-/csg-0.3.6.tgz",
|
|
||||||
"_shasum": "4fc13667c4130b3010328d7587fe5d9328712ced",
|
|
||||||
"_spec": "@jscad/csg@0.3.6",
|
|
||||||
"_where": "C:\\Users\\tomate\\node_modules\\@jscad\\openjscad",
|
|
||||||
"ava": {
|
|
||||||
"require": [
|
|
||||||
"babel-register"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/jscad/csg.js/issues"
|
|
||||||
},
|
|
||||||
"bundleDependencies": false,
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "Alexandre Girard",
|
|
||||||
"url": "https://github.com/alx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Evan Wallace",
|
|
||||||
"url": "http://evanw.github.com/csg.js/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Joost Nieuwenhuijse",
|
|
||||||
"email": "joost@newhouse.nl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Eduard Bespalov",
|
|
||||||
"url": "http://evanw.github.com/csg.js/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bebbi",
|
|
||||||
"email": "elghatta@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Spiritdude Rene K Mueller",
|
|
||||||
"url": "http://renekmueller.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jeff Gay",
|
|
||||||
"url": "http://www.z3d.jp"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {},
|
|
||||||
"deprecated": false,
|
|
||||||
"description": "Constructive Solid Geometry (CSG) Library",
|
|
||||||
"devDependencies": {
|
|
||||||
"ava": "^0.23.0",
|
|
||||||
"conventional-changelog-cli": "^1.3.4",
|
|
||||||
"jsdoc": "^3.4.3",
|
|
||||||
"jsdoc-to-markdown": "^3.0.0",
|
|
||||||
"nyc": "^10.3.2"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/jscad/csg.js#readme",
|
|
||||||
"keywords": [
|
|
||||||
"csg",
|
|
||||||
"parametric",
|
|
||||||
"modeling",
|
|
||||||
"openjscad",
|
|
||||||
"jscad"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "csg.js",
|
|
||||||
"name": "@jscad/csg",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/jscad/csg.js.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build-docs": "jsdoc -c jsdoc.json",
|
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
|
||||||
"docs": "jsdoc2md --files 'src/**/*.js' > docs/api.md",
|
|
||||||
"postversion": "git push origin master && git push origin master --tags",
|
|
||||||
"preversion": "npm test",
|
|
||||||
"release-major": "git checkout master && git pull origin master && npm version major",
|
|
||||||
"release-minor": "git checkout master && git pull origin master && npm version minor",
|
|
||||||
"release-patch": "git checkout master && git pull origin master && npm version patch",
|
|
||||||
"test": "nyc ava 'test' --concurrency 3 --verbose --timeout 40000",
|
|
||||||
"version": "npm run changelog && npm run docs && git add -A "
|
|
||||||
},
|
|
||||||
"version": "0.3.6"
|
|
||||||
}
|
|
816
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CAG.js
generated
vendored
816
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CAG.js
generated
vendored
@ -1,816 +0,0 @@
|
|||||||
const {EPS, angleEPS, areaEPS, defaultResolution3D} = require('./constants')
|
|
||||||
const {Connector} = require('./connectors')
|
|
||||||
const OrthoNormalBasis = require('./math/OrthoNormalBasis')
|
|
||||||
const Vertex2D = require('./math/Vertex2')
|
|
||||||
const Vertex3D = require('./math/Vertex3')
|
|
||||||
const Vector2D = require('./math/Vector2')
|
|
||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Polygon = require('./math/Polygon3')
|
|
||||||
const Path2D = require('./math/Path2')
|
|
||||||
const Side = require('./math/Side')
|
|
||||||
const {linesIntersect} = require('./math/lineUtils')
|
|
||||||
const {parseOptionAs3DVector, parseOptionAsBool, parseOptionAsFloat, parseOptionAsInt} = require('./optionParsers')
|
|
||||||
const FuzzyCAGFactory = require('./FuzzyFactory2d')
|
|
||||||
/**
|
|
||||||
* Class CAG
|
|
||||||
* Holds a solid area geometry like CSG but 2D.
|
|
||||||
* Each area consists of a number of sides.
|
|
||||||
* Each side is a line between 2 points.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
let CAG = function () {
|
|
||||||
this.sides = []
|
|
||||||
this.isCanonicalized = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a CAG from a list of `Side` instances.
|
|
||||||
* @param {Side[]} sides - list of sides
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
CAG.fromSides = function (sides) {
|
|
||||||
let cag = new CAG()
|
|
||||||
cag.sides = sides
|
|
||||||
return cag
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Converts a CSG to a The CSG must consist of polygons with only z coordinates +1 and -1
|
|
||||||
// as constructed by _toCSGWall(-1, 1). This is so we can use the 3D union(), intersect() etc
|
|
||||||
CAG.fromFakeCSG = function (csg) {
|
|
||||||
let sides = csg.polygons.map(function (p) {
|
|
||||||
return Side._fromFakePolygon(p)
|
|
||||||
})
|
|
||||||
.filter(function (s) {
|
|
||||||
return s !== null
|
|
||||||
})
|
|
||||||
return CAG.fromSides(sides)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a CAG from a list of points (a polygon).
|
|
||||||
* The rotation direction of the points is not relevant.
|
|
||||||
* The points can define a convex or a concave polygon.
|
|
||||||
* The polygon must not self intersect.
|
|
||||||
* @param {points[]} points - list of points in 2D space
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
CAG.fromPoints = function (points) {
|
|
||||||
let numpoints = points.length
|
|
||||||
if (numpoints < 3) throw new Error('CAG shape needs at least 3 points')
|
|
||||||
let sides = []
|
|
||||||
let prevpoint = new Vector2D(points[numpoints - 1])
|
|
||||||
let prevvertex = new Vertex2D(prevpoint)
|
|
||||||
points.map(function (p) {
|
|
||||||
let point = new Vector2D(p)
|
|
||||||
let vertex = new Vertex2D(point)
|
|
||||||
let side = new Side(prevvertex, vertex)
|
|
||||||
sides.push(side)
|
|
||||||
prevvertex = vertex
|
|
||||||
})
|
|
||||||
let result = CAG.fromSides(sides)
|
|
||||||
if (result.isSelfIntersecting()) {
|
|
||||||
throw new Error('Polygon is self intersecting!')
|
|
||||||
}
|
|
||||||
let area = result.area()
|
|
||||||
if (Math.abs(area) < areaEPS) {
|
|
||||||
throw new Error('Degenerate polygon!')
|
|
||||||
}
|
|
||||||
if (area < 0) {
|
|
||||||
result = result.flipped()
|
|
||||||
}
|
|
||||||
result = result.canonicalized()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const CAGFromCAGFuzzyFactory = function (factory, sourcecag) {
|
|
||||||
let _this = factory
|
|
||||||
let newsides = sourcecag.sides.map(function (side) {
|
|
||||||
return _this.getSide(side)
|
|
||||||
})
|
|
||||||
// remove bad sides (mostly a user input issue)
|
|
||||||
.filter(function (side) {
|
|
||||||
return side.length() > EPS
|
|
||||||
})
|
|
||||||
return CAG.fromSides(newsides)
|
|
||||||
}
|
|
||||||
|
|
||||||
CAG.prototype = {
|
|
||||||
toString: function () {
|
|
||||||
let result = 'CAG (' + this.sides.length + ' sides):\n'
|
|
||||||
this.sides.map(function (side) {
|
|
||||||
result += ' ' + side.toString() + '\n'
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
_toCSGWall: function (z0, z1) {
|
|
||||||
const CSG = require('./CSG') // FIXME: circular dependencies CAG=>CSG=>CAG
|
|
||||||
let polygons = this.sides.map(function (side) {
|
|
||||||
return side.toPolygon3D(z0, z1)
|
|
||||||
})
|
|
||||||
return CSG.fromPolygons(polygons)
|
|
||||||
},
|
|
||||||
|
|
||||||
_toVector3DPairs: function (m) {
|
|
||||||
// transform m
|
|
||||||
let pairs = this.sides.map(function (side) {
|
|
||||||
let p0 = side.vertex0.pos
|
|
||||||
let p1 = side.vertex1.pos
|
|
||||||
return [Vector3D.Create(p0.x, p0.y, 0),
|
|
||||||
Vector3D.Create(p1.x, p1.y, 0)]
|
|
||||||
})
|
|
||||||
if (typeof m !== 'undefined') {
|
|
||||||
pairs = pairs.map(function (pair) {
|
|
||||||
return pair.map(function (v) {
|
|
||||||
return v.transform(m)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return pairs
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* transform a cag into the polygons of a corresponding 3d plane, positioned per options
|
|
||||||
* Accepts a connector for plane positioning, or optionally
|
|
||||||
* single translation, axisVector, normalVector arguments
|
|
||||||
* (toConnector has precedence over single arguments if provided)
|
|
||||||
*/
|
|
||||||
_toPlanePolygons: function (options) {
|
|
||||||
const CSG = require('./CSG') // FIXME: circular dependencies CAG=>CSG=>CAG
|
|
||||||
let flipped = options.flipped || false
|
|
||||||
// reference connector for transformation
|
|
||||||
let origin = [0, 0, 0]
|
|
||||||
let defaultAxis = [0, 0, 1]
|
|
||||||
let defaultNormal = [0, 1, 0]
|
|
||||||
let thisConnector = new Connector(origin, defaultAxis, defaultNormal)
|
|
||||||
// translated connector per options
|
|
||||||
let translation = options.translation || origin
|
|
||||||
let axisVector = options.axisVector || defaultAxis
|
|
||||||
let normalVector = options.normalVector || defaultNormal
|
|
||||||
// will override above if options has toConnector
|
|
||||||
let toConnector = options.toConnector ||
|
|
||||||
new Connector(translation, axisVector, normalVector)
|
|
||||||
// resulting transform
|
|
||||||
let m = thisConnector.getTransformationTo(toConnector, false, 0)
|
|
||||||
// create plane as a (partial non-closed) CSG in XY plane
|
|
||||||
let bounds = this.getBounds()
|
|
||||||
bounds[0] = bounds[0].minus(new Vector2D(1, 1))
|
|
||||||
bounds[1] = bounds[1].plus(new Vector2D(1, 1))
|
|
||||||
let csgshell = this._toCSGWall(-1, 1)
|
|
||||||
let csgplane = CSG.fromPolygons([new Polygon([
|
|
||||||
new Vertex3D(new Vector3D(bounds[0].x, bounds[0].y, 0)),
|
|
||||||
new Vertex3D(new Vector3D(bounds[1].x, bounds[0].y, 0)),
|
|
||||||
new Vertex3D(new Vector3D(bounds[1].x, bounds[1].y, 0)),
|
|
||||||
new Vertex3D(new Vector3D(bounds[0].x, bounds[1].y, 0))
|
|
||||||
])])
|
|
||||||
if (flipped) {
|
|
||||||
csgplane = csgplane.invert()
|
|
||||||
}
|
|
||||||
// intersectSub -> prevent premature retesselate/canonicalize
|
|
||||||
csgplane = csgplane.intersectSub(csgshell)
|
|
||||||
// only keep the polygons in the z plane:
|
|
||||||
let polys = csgplane.polygons.filter(function (polygon) {
|
|
||||||
return Math.abs(polygon.plane.normal.z) > 0.99
|
|
||||||
})
|
|
||||||
// finally, position the plane per passed transformations
|
|
||||||
return polys.map(function (poly) {
|
|
||||||
return poly.transform(m)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* given 2 connectors, this returns all polygons of a "wall" between 2
|
|
||||||
* copies of this cag, positioned in 3d space as "bottom" and
|
|
||||||
* "top" plane per connectors toConnector1, and toConnector2, respectively
|
|
||||||
*/
|
|
||||||
_toWallPolygons: function (options) {
|
|
||||||
// normals are going to be correct as long as toConn2.point - toConn1.point
|
|
||||||
// points into cag normal direction (check in caller)
|
|
||||||
// arguments: options.toConnector1, options.toConnector2, options.cag
|
|
||||||
// walls go from toConnector1 to toConnector2
|
|
||||||
// optionally, target cag to point to - cag needs to have same number of sides as this!
|
|
||||||
let origin = [0, 0, 0]
|
|
||||||
let defaultAxis = [0, 0, 1]
|
|
||||||
let defaultNormal = [0, 1, 0]
|
|
||||||
let thisConnector = new Connector(origin, defaultAxis, defaultNormal)
|
|
||||||
// arguments:
|
|
||||||
let toConnector1 = options.toConnector1
|
|
||||||
// let toConnector2 = new Connector([0, 0, -30], defaultAxis, defaultNormal);
|
|
||||||
let toConnector2 = options.toConnector2
|
|
||||||
if (!(toConnector1 instanceof Connector && toConnector2 instanceof Connector)) {
|
|
||||||
throw new Error('could not parse Connector arguments toConnector1 or toConnector2')
|
|
||||||
}
|
|
||||||
if (options.cag) {
|
|
||||||
if (options.cag.sides.length !== this.sides.length) {
|
|
||||||
throw new Error('target cag needs same sides count as start cag')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// target cag is same as this unless specified
|
|
||||||
let toCag = options.cag || this
|
|
||||||
let m1 = thisConnector.getTransformationTo(toConnector1, false, 0)
|
|
||||||
let m2 = thisConnector.getTransformationTo(toConnector2, false, 0)
|
|
||||||
let vps1 = this._toVector3DPairs(m1)
|
|
||||||
let vps2 = toCag._toVector3DPairs(m2)
|
|
||||||
|
|
||||||
let polygons = []
|
|
||||||
vps1.forEach(function (vp1, i) {
|
|
||||||
polygons.push(new Polygon([
|
|
||||||
new Vertex3D(vps2[i][1]), new Vertex3D(vps2[i][0]), new Vertex3D(vp1[0])]))
|
|
||||||
polygons.push(new Polygon([
|
|
||||||
new Vertex3D(vps2[i][1]), new Vertex3D(vp1[0]), new Vertex3D(vp1[1])]))
|
|
||||||
})
|
|
||||||
return polygons
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert to a list of points.
|
|
||||||
* @return {points[]} list of points in 2D space
|
|
||||||
*/
|
|
||||||
toPoints: function () {
|
|
||||||
let points = this.sides.map(function (side) {
|
|
||||||
let v0 = side.vertex0
|
|
||||||
// let v1 = side.vertex1
|
|
||||||
return v0.pos
|
|
||||||
})
|
|
||||||
// due to the logic of CAG.fromPoints()
|
|
||||||
// move the first point to the last
|
|
||||||
if (points.length > 0) {
|
|
||||||
points.push(points.shift())
|
|
||||||
}
|
|
||||||
return points
|
|
||||||
},
|
|
||||||
|
|
||||||
union: function (cag) {
|
|
||||||
let cags
|
|
||||||
if (cag instanceof Array) {
|
|
||||||
cags = cag
|
|
||||||
} else {
|
|
||||||
cags = [cag]
|
|
||||||
}
|
|
||||||
let r = this._toCSGWall(-1, 1)
|
|
||||||
r = r.union(
|
|
||||||
cags.map(function (cag) {
|
|
||||||
return cag._toCSGWall(-1, 1).reTesselated()
|
|
||||||
}), false, false)
|
|
||||||
return CAG.fromFakeCSG(r).canonicalized()
|
|
||||||
},
|
|
||||||
|
|
||||||
subtract: function (cag) {
|
|
||||||
let cags
|
|
||||||
if (cag instanceof Array) {
|
|
||||||
cags = cag
|
|
||||||
} else {
|
|
||||||
cags = [cag]
|
|
||||||
}
|
|
||||||
let r = this._toCSGWall(-1, 1)
|
|
||||||
cags.map(function (cag) {
|
|
||||||
r = r.subtractSub(cag._toCSGWall(-1, 1), false, false)
|
|
||||||
})
|
|
||||||
r = r.reTesselated()
|
|
||||||
r = r.canonicalized()
|
|
||||||
r = CAG.fromFakeCSG(r)
|
|
||||||
r = r.canonicalized()
|
|
||||||
return r
|
|
||||||
},
|
|
||||||
|
|
||||||
intersect: function (cag) {
|
|
||||||
let cags
|
|
||||||
if (cag instanceof Array) {
|
|
||||||
cags = cag
|
|
||||||
} else {
|
|
||||||
cags = [cag]
|
|
||||||
}
|
|
||||||
let r = this._toCSGWall(-1, 1)
|
|
||||||
cags.map(function (cag) {
|
|
||||||
r = r.intersectSub(cag._toCSGWall(-1, 1), false, false)
|
|
||||||
})
|
|
||||||
r = r.reTesselated()
|
|
||||||
r = r.canonicalized()
|
|
||||||
r = CAG.fromFakeCSG(r)
|
|
||||||
r = r.canonicalized()
|
|
||||||
return r
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let ismirror = matrix4x4.isMirroring()
|
|
||||||
let newsides = this.sides.map(function (side) {
|
|
||||||
return side.transform(matrix4x4)
|
|
||||||
})
|
|
||||||
let result = CAG.fromSides(newsides)
|
|
||||||
if (ismirror) {
|
|
||||||
result = result.flipped()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// see http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ :
|
|
||||||
// Area of the polygon. For a counter clockwise rotating polygon the area is positive, otherwise negative
|
|
||||||
// Note(bebbi): this looks wrong. See polygon getArea()
|
|
||||||
area: function () {
|
|
||||||
let polygonArea = 0
|
|
||||||
this.sides.map(function (side) {
|
|
||||||
polygonArea += side.vertex0.pos.cross(side.vertex1.pos)
|
|
||||||
})
|
|
||||||
polygonArea *= 0.5
|
|
||||||
return polygonArea
|
|
||||||
},
|
|
||||||
|
|
||||||
flipped: function () {
|
|
||||||
let newsides = this.sides.map(function (side) {
|
|
||||||
return side.flipped()
|
|
||||||
})
|
|
||||||
newsides.reverse()
|
|
||||||
return CAG.fromSides(newsides)
|
|
||||||
},
|
|
||||||
|
|
||||||
getBounds: function () {
|
|
||||||
let minpoint
|
|
||||||
if (this.sides.length === 0) {
|
|
||||||
minpoint = new Vector2D(0, 0)
|
|
||||||
} else {
|
|
||||||
minpoint = this.sides[0].vertex0.pos
|
|
||||||
}
|
|
||||||
let maxpoint = minpoint
|
|
||||||
this.sides.map(function (side) {
|
|
||||||
minpoint = minpoint.min(side.vertex0.pos)
|
|
||||||
minpoint = minpoint.min(side.vertex1.pos)
|
|
||||||
maxpoint = maxpoint.max(side.vertex0.pos)
|
|
||||||
maxpoint = maxpoint.max(side.vertex1.pos)
|
|
||||||
})
|
|
||||||
return [minpoint, maxpoint]
|
|
||||||
},
|
|
||||||
|
|
||||||
isSelfIntersecting: function (debug) {
|
|
||||||
let numsides = this.sides.length
|
|
||||||
for (let i = 0; i < numsides; i++) {
|
|
||||||
let side0 = this.sides[i]
|
|
||||||
for (let ii = i + 1; ii < numsides; ii++) {
|
|
||||||
let side1 = this.sides[ii]
|
|
||||||
if (linesIntersect(side0.vertex0.pos, side0.vertex1.pos, side1.vertex0.pos, side1.vertex1.pos)) {
|
|
||||||
if (debug) { console.log('side ' + i + ': ' + side0); console.log('side ' + ii + ': ' + side1) }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
|
|
||||||
expandedShell: function (radius, resolution) {
|
|
||||||
resolution = resolution || 8
|
|
||||||
if (resolution < 4) resolution = 4
|
|
||||||
let cags = []
|
|
||||||
let pointmap = {}
|
|
||||||
let cag = this.canonicalized()
|
|
||||||
cag.sides.map(function (side) {
|
|
||||||
let d = side.vertex1.pos.minus(side.vertex0.pos)
|
|
||||||
let dl = d.length()
|
|
||||||
if (dl > EPS) {
|
|
||||||
d = d.times(1.0 / dl)
|
|
||||||
let normal = d.normal().times(radius)
|
|
||||||
let shellpoints = [
|
|
||||||
side.vertex1.pos.plus(normal),
|
|
||||||
side.vertex1.pos.minus(normal),
|
|
||||||
side.vertex0.pos.minus(normal),
|
|
||||||
side.vertex0.pos.plus(normal)
|
|
||||||
]
|
|
||||||
// let newcag = CAG.fromPointsNoCheck(shellpoints);
|
|
||||||
let newcag = CAG.fromPoints(shellpoints)
|
|
||||||
cags.push(newcag)
|
|
||||||
for (let step = 0; step < 2; step++) {
|
|
||||||
let p1 = (step === 0) ? side.vertex0.pos : side.vertex1.pos
|
|
||||||
let p2 = (step === 0) ? side.vertex1.pos : side.vertex0.pos
|
|
||||||
let tag = p1.x + ' ' + p1.y
|
|
||||||
if (!(tag in pointmap)) {
|
|
||||||
pointmap[tag] = []
|
|
||||||
}
|
|
||||||
pointmap[tag].push({
|
|
||||||
'p1': p1,
|
|
||||||
'p2': p2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
for (let tag in pointmap) {
|
|
||||||
let m = pointmap[tag]
|
|
||||||
let angle1, angle2
|
|
||||||
let pcenter = m[0].p1
|
|
||||||
if (m.length === 2) {
|
|
||||||
let end1 = m[0].p2
|
|
||||||
let end2 = m[1].p2
|
|
||||||
angle1 = end1.minus(pcenter).angleDegrees()
|
|
||||||
angle2 = end2.minus(pcenter).angleDegrees()
|
|
||||||
if (angle2 < angle1) angle2 += 360
|
|
||||||
if (angle2 >= (angle1 + 360)) angle2 -= 360
|
|
||||||
if (angle2 < angle1 + 180) {
|
|
||||||
let t = angle2
|
|
||||||
angle2 = angle1 + 360
|
|
||||||
angle1 = t
|
|
||||||
}
|
|
||||||
angle1 += 90
|
|
||||||
angle2 -= 90
|
|
||||||
} else {
|
|
||||||
angle1 = 0
|
|
||||||
angle2 = 360
|
|
||||||
}
|
|
||||||
let fullcircle = (angle2 > angle1 + 359.999)
|
|
||||||
if (fullcircle) {
|
|
||||||
angle1 = 0
|
|
||||||
angle2 = 360
|
|
||||||
}
|
|
||||||
if (angle2 > (angle1 + angleEPS)) {
|
|
||||||
let points = []
|
|
||||||
if (!fullcircle) {
|
|
||||||
points.push(pcenter)
|
|
||||||
}
|
|
||||||
let numsteps = Math.round(resolution * (angle2 - angle1) / 360)
|
|
||||||
if (numsteps < 1) numsteps = 1
|
|
||||||
for (let step = 0; step <= numsteps; step++) {
|
|
||||||
let angle = angle1 + step / numsteps * (angle2 - angle1)
|
|
||||||
if (step === numsteps) angle = angle2 // prevent rounding errors
|
|
||||||
let point = pcenter.plus(Vector2D.fromAngleDegrees(angle).times(radius))
|
|
||||||
if ((!fullcircle) || (step > 0)) {
|
|
||||||
points.push(point)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let newcag = CAG.fromPointsNoCheck(points)
|
|
||||||
cags.push(newcag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = new CAG()
|
|
||||||
result = result.union(cags)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
expand: function (radius, resolution) {
|
|
||||||
let result = this.union(this.expandedShell(radius, resolution))
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
contract: function (radius, resolution) {
|
|
||||||
let result = this.subtract(this.expandedShell(radius, resolution))
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// extrude the CAG in a certain plane.
|
|
||||||
// Giving just a plane is not enough, multiple different extrusions in the same plane would be possible
|
|
||||||
// by rotating around the plane's origin. An additional right-hand vector should be specified as well,
|
|
||||||
// and this is exactly a OrthoNormalBasis.
|
|
||||||
//
|
|
||||||
// orthonormalbasis: characterizes the plane in which to extrude
|
|
||||||
// depth: thickness of the extruded shape. Extrusion is done upwards from the plane
|
|
||||||
// (unless symmetrical option is set, see below)
|
|
||||||
// options:
|
|
||||||
// {symmetrical: true} // extrude symmetrically in two directions about the plane
|
|
||||||
extrudeInOrthonormalBasis: function (orthonormalbasis, depth, options) {
|
|
||||||
// first extrude in the regular Z plane:
|
|
||||||
if (!(orthonormalbasis instanceof OrthoNormalBasis)) {
|
|
||||||
throw new Error('extrudeInPlane: the first parameter should be a OrthoNormalBasis')
|
|
||||||
}
|
|
||||||
let extruded = this.extrude({
|
|
||||||
offset: [0, 0, depth]
|
|
||||||
})
|
|
||||||
if (parseOptionAsBool(options, 'symmetrical', false)) {
|
|
||||||
extruded = extruded.translate([0, 0, -depth / 2])
|
|
||||||
}
|
|
||||||
let matrix = orthonormalbasis.getInverseProjectionMatrix()
|
|
||||||
extruded = extruded.transform(matrix)
|
|
||||||
return extruded
|
|
||||||
},
|
|
||||||
|
|
||||||
// Extrude in a standard cartesian plane, specified by two axis identifiers. Each identifier can be
|
|
||||||
// one of ["X","Y","Z","-X","-Y","-Z"]
|
|
||||||
// The 2d x axis will map to the first given 3D axis, the 2d y axis will map to the second.
|
|
||||||
// See OrthoNormalBasis.GetCartesian for details.
|
|
||||||
// options:
|
|
||||||
// {symmetrical: true} // extrude symmetrically in two directions about the plane
|
|
||||||
extrudeInPlane: function (axis1, axis2, depth, options) {
|
|
||||||
return this.extrudeInOrthonormalBasis(OrthoNormalBasis.GetCartesian(axis1, axis2), depth, options)
|
|
||||||
},
|
|
||||||
|
|
||||||
// extruded=cag.extrude({offset: [0,0,10], twistangle: 360, twiststeps: 100});
|
|
||||||
// linear extrusion of 2D shape, with optional twist
|
|
||||||
// The 2d shape is placed in in z=0 plane and extruded into direction <offset> (a Vector3D)
|
|
||||||
// The final face is rotated <twistangle> degrees. Rotation is done around the origin of the 2d shape (i.e. x=0, y=0)
|
|
||||||
// twiststeps determines the resolution of the twist (should be >= 1)
|
|
||||||
// returns a CSG object
|
|
||||||
extrude: function (options) {
|
|
||||||
const CSG = require('./CSG') // FIXME: circular dependencies CAG=>CSG=>CAG
|
|
||||||
if (this.sides.length === 0) {
|
|
||||||
// empty!
|
|
||||||
return new CSG()
|
|
||||||
}
|
|
||||||
let offsetVector = parseOptionAs3DVector(options, 'offset', [0, 0, 1])
|
|
||||||
let twistangle = parseOptionAsFloat(options, 'twistangle', 0)
|
|
||||||
let twiststeps = parseOptionAsInt(options, 'twiststeps', defaultResolution3D)
|
|
||||||
if (offsetVector.z === 0) {
|
|
||||||
throw new Error('offset cannot be orthogonal to Z axis')
|
|
||||||
}
|
|
||||||
if (twistangle === 0 || twiststeps < 1) {
|
|
||||||
twiststeps = 1
|
|
||||||
}
|
|
||||||
let normalVector = Vector3D.Create(0, 1, 0)
|
|
||||||
|
|
||||||
let polygons = []
|
|
||||||
// bottom and top
|
|
||||||
polygons = polygons.concat(this._toPlanePolygons({
|
|
||||||
translation: [0, 0, 0],
|
|
||||||
normalVector: normalVector,
|
|
||||||
flipped: !(offsetVector.z < 0)}
|
|
||||||
))
|
|
||||||
polygons = polygons.concat(this._toPlanePolygons({
|
|
||||||
translation: offsetVector,
|
|
||||||
normalVector: normalVector.rotateZ(twistangle),
|
|
||||||
flipped: offsetVector.z < 0}))
|
|
||||||
// walls
|
|
||||||
for (let i = 0; i < twiststeps; i++) {
|
|
||||||
let c1 = new Connector(offsetVector.times(i / twiststeps), [0, 0, offsetVector.z],
|
|
||||||
normalVector.rotateZ(i * twistangle / twiststeps))
|
|
||||||
let c2 = new Connector(offsetVector.times((i + 1) / twiststeps), [0, 0, offsetVector.z],
|
|
||||||
normalVector.rotateZ((i + 1) * twistangle / twiststeps))
|
|
||||||
polygons = polygons.concat(this._toWallPolygons({toConnector1: c1, toConnector2: c2}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return CSG.fromPolygons(polygons)
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Extrude to into a 3D solid by rotating the origin around the Y axis.
|
|
||||||
* (and turning everything into XY plane)
|
|
||||||
* @param {Object} options - options for construction
|
|
||||||
* @param {Number} [options.angle=360] - angle of rotation
|
|
||||||
* @param {Number} [options.resolution=defaultResolution3D] - number of polygons per 360 degree revolution
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*/
|
|
||||||
rotateExtrude: function (options) { // FIXME options should be optional
|
|
||||||
const CSG = require('./CSG') // FIXME: circular dependencies CAG=>CSG=>CAG
|
|
||||||
let alpha = parseOptionAsFloat(options, 'angle', 360)
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
|
||||||
|
|
||||||
alpha = alpha > 360 ? alpha % 360 : alpha
|
|
||||||
let origin = [0, 0, 0]
|
|
||||||
let axisV = Vector3D.Create(0, 1, 0)
|
|
||||||
let normalV = [0, 0, 1]
|
|
||||||
let polygons = []
|
|
||||||
// planes only needed if alpha > 0
|
|
||||||
let connS = new Connector(origin, axisV, normalV)
|
|
||||||
if (alpha > 0 && alpha < 360) {
|
|
||||||
// we need to rotate negative to satisfy wall function condition of
|
|
||||||
// building in the direction of axis vector
|
|
||||||
let connE = new Connector(origin, axisV.rotateZ(-alpha), normalV)
|
|
||||||
polygons = polygons.concat(
|
|
||||||
this._toPlanePolygons({toConnector: connS, flipped: true}))
|
|
||||||
polygons = polygons.concat(
|
|
||||||
this._toPlanePolygons({toConnector: connE}))
|
|
||||||
}
|
|
||||||
let connT1 = connS
|
|
||||||
let connT2
|
|
||||||
let step = alpha / resolution
|
|
||||||
for (let a = step; a <= alpha + EPS; a += step) { // FIXME Should this be angelEPS?
|
|
||||||
connT2 = new Connector(origin, axisV.rotateZ(-a), normalV)
|
|
||||||
polygons = polygons.concat(this._toWallPolygons(
|
|
||||||
{toConnector1: connT1, toConnector2: connT2}))
|
|
||||||
connT1 = connT2
|
|
||||||
}
|
|
||||||
return CSG.fromPolygons(polygons).reTesselated()
|
|
||||||
},
|
|
||||||
|
|
||||||
// check if we are a valid CAG (for debugging)
|
|
||||||
// NOTE(bebbi) uneven side count doesn't work because rounding with EPS isn't taken into account
|
|
||||||
check: function () {
|
|
||||||
let errors = []
|
|
||||||
if (this.isSelfIntersecting(true)) {
|
|
||||||
errors.push('Self intersects')
|
|
||||||
}
|
|
||||||
let pointcount = {}
|
|
||||||
this.sides.map(function (side) {
|
|
||||||
function mappoint (p) {
|
|
||||||
let tag = p.x + ' ' + p.y
|
|
||||||
if (!(tag in pointcount)) pointcount[tag] = 0
|
|
||||||
pointcount[tag] ++
|
|
||||||
}
|
|
||||||
mappoint(side.vertex0.pos)
|
|
||||||
mappoint(side.vertex1.pos)
|
|
||||||
})
|
|
||||||
for (let tag in pointcount) {
|
|
||||||
let count = pointcount[tag]
|
|
||||||
if (count & 1) {
|
|
||||||
errors.push('Uneven number of sides (' + count + ') for point ' + tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let area = this.area()
|
|
||||||
if (area < areaEPS) {
|
|
||||||
errors.push('Area is ' + area)
|
|
||||||
}
|
|
||||||
if (errors.length > 0) {
|
|
||||||
let ertxt = ''
|
|
||||||
errors.map(function (err) {
|
|
||||||
ertxt += err + '\n'
|
|
||||||
})
|
|
||||||
throw new Error(ertxt)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
canonicalized: function () {
|
|
||||||
if (this.isCanonicalized) {
|
|
||||||
return this
|
|
||||||
} else {
|
|
||||||
let factory = new FuzzyCAGFactory()
|
|
||||||
let result = CAGFromCAGFuzzyFactory(factory, this)
|
|
||||||
result.isCanonicalized = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Convert to compact binary form.
|
|
||||||
* See CAG.fromCompactBinary.
|
|
||||||
* @return {CompactBinary}
|
|
||||||
*/
|
|
||||||
toCompactBinary: function () {
|
|
||||||
let cag = this.canonicalized()
|
|
||||||
let numsides = cag.sides.length
|
|
||||||
let vertexmap = {}
|
|
||||||
let vertices = []
|
|
||||||
let numvertices = 0
|
|
||||||
let sideVertexIndices = new Uint32Array(2 * numsides)
|
|
||||||
let sidevertexindicesindex = 0
|
|
||||||
cag.sides.map(function (side) {
|
|
||||||
[side.vertex0, side.vertex1].map(function (v) {
|
|
||||||
let vertextag = v.getTag()
|
|
||||||
let vertexindex
|
|
||||||
if (!(vertextag in vertexmap)) {
|
|
||||||
vertexindex = numvertices++
|
|
||||||
vertexmap[vertextag] = vertexindex
|
|
||||||
vertices.push(v)
|
|
||||||
} else {
|
|
||||||
vertexindex = vertexmap[vertextag]
|
|
||||||
}
|
|
||||||
sideVertexIndices[sidevertexindicesindex++] = vertexindex
|
|
||||||
})
|
|
||||||
})
|
|
||||||
let vertexData = new Float64Array(numvertices * 2)
|
|
||||||
let verticesArrayIndex = 0
|
|
||||||
vertices.map(function (v) {
|
|
||||||
let pos = v.pos
|
|
||||||
vertexData[verticesArrayIndex++] = pos._x
|
|
||||||
vertexData[verticesArrayIndex++] = pos._y
|
|
||||||
})
|
|
||||||
let result = {
|
|
||||||
'class': 'CAG',
|
|
||||||
sideVertexIndices: sideVertexIndices,
|
|
||||||
vertexData: vertexData
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
getOutlinePaths: function () {
|
|
||||||
let cag = this.canonicalized()
|
|
||||||
let sideTagToSideMap = {}
|
|
||||||
let startVertexTagToSideTagMap = {}
|
|
||||||
cag.sides.map(function (side) {
|
|
||||||
let sidetag = side.getTag()
|
|
||||||
sideTagToSideMap[sidetag] = side
|
|
||||||
let startvertextag = side.vertex0.getTag()
|
|
||||||
if (!(startvertextag in startVertexTagToSideTagMap)) {
|
|
||||||
startVertexTagToSideTagMap[startvertextag] = []
|
|
||||||
}
|
|
||||||
startVertexTagToSideTagMap[startvertextag].push(sidetag)
|
|
||||||
})
|
|
||||||
let paths = []
|
|
||||||
while (true) {
|
|
||||||
let startsidetag = null
|
|
||||||
for (let aVertexTag in startVertexTagToSideTagMap) {
|
|
||||||
let sidesForThisVertex = startVertexTagToSideTagMap[aVertexTag]
|
|
||||||
startsidetag = sidesForThisVertex[0]
|
|
||||||
sidesForThisVertex.splice(0, 1)
|
|
||||||
if (sidesForThisVertex.length === 0) {
|
|
||||||
delete startVertexTagToSideTagMap[aVertexTag]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (startsidetag === null) break // we've had all sides
|
|
||||||
let connectedVertexPoints = []
|
|
||||||
let sidetag = startsidetag
|
|
||||||
let thisside = sideTagToSideMap[sidetag]
|
|
||||||
let startvertextag = thisside.vertex0.getTag()
|
|
||||||
while (true) {
|
|
||||||
connectedVertexPoints.push(thisside.vertex0.pos)
|
|
||||||
let nextvertextag = thisside.vertex1.getTag()
|
|
||||||
if (nextvertextag === startvertextag) break // we've closed the polygon
|
|
||||||
if (!(nextvertextag in startVertexTagToSideTagMap)) {
|
|
||||||
throw new Error('Area is not closed!')
|
|
||||||
}
|
|
||||||
let nextpossiblesidetags = startVertexTagToSideTagMap[nextvertextag]
|
|
||||||
let nextsideindex = -1
|
|
||||||
if (nextpossiblesidetags.length === 1) {
|
|
||||||
nextsideindex = 0
|
|
||||||
} else {
|
|
||||||
// more than one side starting at the same vertex. This means we have
|
|
||||||
// two shapes touching at the same corner
|
|
||||||
let bestangle = null
|
|
||||||
let thisangle = thisside.direction().angleDegrees()
|
|
||||||
for (let sideindex = 0; sideindex < nextpossiblesidetags.length; sideindex++) {
|
|
||||||
let nextpossiblesidetag = nextpossiblesidetags[sideindex]
|
|
||||||
let possibleside = sideTagToSideMap[nextpossiblesidetag]
|
|
||||||
let angle = possibleside.direction().angleDegrees()
|
|
||||||
let angledif = angle - thisangle
|
|
||||||
if (angledif < -180) angledif += 360
|
|
||||||
if (angledif >= 180) angledif -= 360
|
|
||||||
if ((nextsideindex < 0) || (angledif > bestangle)) {
|
|
||||||
nextsideindex = sideindex
|
|
||||||
bestangle = angledif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let nextsidetag = nextpossiblesidetags[nextsideindex]
|
|
||||||
nextpossiblesidetags.splice(nextsideindex, 1)
|
|
||||||
if (nextpossiblesidetags.length === 0) {
|
|
||||||
delete startVertexTagToSideTagMap[nextvertextag]
|
|
||||||
}
|
|
||||||
thisside = sideTagToSideMap[nextsidetag]
|
|
||||||
} // inner loop
|
|
||||||
// due to the logic of CAG.fromPoints()
|
|
||||||
// move the first point to the last
|
|
||||||
if (connectedVertexPoints.length > 0) {
|
|
||||||
connectedVertexPoints.push(connectedVertexPoints.shift())
|
|
||||||
}
|
|
||||||
let path = new Path2D(connectedVertexPoints, true)
|
|
||||||
paths.push(path)
|
|
||||||
} // outer loop
|
|
||||||
return paths
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
cag = cag.overCutInsideCorners(cutterradius);
|
|
||||||
|
|
||||||
Using a CNC router it's impossible to cut out a true sharp inside corner. The inside corner
|
|
||||||
will be rounded due to the radius of the cutter. This function compensates for this by creating
|
|
||||||
an extra cutout at each inner corner so that the actual cut out shape will be at least as large
|
|
||||||
as needed.
|
|
||||||
*/
|
|
||||||
overCutInsideCorners: function (cutterradius) {
|
|
||||||
let cag = this.canonicalized()
|
|
||||||
// for each vertex determine the 'incoming' side and 'outgoing' side:
|
|
||||||
let pointmap = {} // tag => {pos: coord, from: [], to: []}
|
|
||||||
cag.sides.map(function (side) {
|
|
||||||
if (!(side.vertex0.getTag() in pointmap)) {
|
|
||||||
pointmap[side.vertex0.getTag()] = {
|
|
||||||
pos: side.vertex0.pos,
|
|
||||||
from: [],
|
|
||||||
to: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pointmap[side.vertex0.getTag()].to.push(side.vertex1.pos)
|
|
||||||
if (!(side.vertex1.getTag() in pointmap)) {
|
|
||||||
pointmap[side.vertex1.getTag()] = {
|
|
||||||
pos: side.vertex1.pos,
|
|
||||||
from: [],
|
|
||||||
to: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pointmap[side.vertex1.getTag()].from.push(side.vertex0.pos)
|
|
||||||
})
|
|
||||||
// overcut all sharp corners:
|
|
||||||
let cutouts = []
|
|
||||||
for (let pointtag in pointmap) {
|
|
||||||
let pointobj = pointmap[pointtag]
|
|
||||||
if ((pointobj.from.length === 1) && (pointobj.to.length === 1)) {
|
|
||||||
// ok, 1 incoming side and 1 outgoing side:
|
|
||||||
let fromcoord = pointobj.from[0]
|
|
||||||
let pointcoord = pointobj.pos
|
|
||||||
let tocoord = pointobj.to[0]
|
|
||||||
let v1 = pointcoord.minus(fromcoord).unit()
|
|
||||||
let v2 = tocoord.minus(pointcoord).unit()
|
|
||||||
let crossproduct = v1.cross(v2)
|
|
||||||
let isInnerCorner = (crossproduct < 0.001)
|
|
||||||
if (isInnerCorner) {
|
|
||||||
// yes it's a sharp corner:
|
|
||||||
let alpha = v2.angleRadians() - v1.angleRadians() + Math.PI
|
|
||||||
if (alpha < 0) {
|
|
||||||
alpha += 2 * Math.PI
|
|
||||||
} else if (alpha >= 2 * Math.PI) {
|
|
||||||
alpha -= 2 * Math.PI
|
|
||||||
}
|
|
||||||
let midvector = v2.minus(v1).unit()
|
|
||||||
let circlesegmentangle = 30 / 180 * Math.PI // resolution of the circle: segments of 30 degrees
|
|
||||||
// we need to increase the radius slightly so that our imperfect circle will contain a perfect circle of cutterradius
|
|
||||||
let radiuscorrected = cutterradius / Math.cos(circlesegmentangle / 2)
|
|
||||||
let circlecenter = pointcoord.plus(midvector.times(radiuscorrected))
|
|
||||||
// we don't need to create a full circle; a pie is enough. Find the angles for the pie:
|
|
||||||
let startangle = alpha + midvector.angleRadians()
|
|
||||||
let deltaangle = 2 * (Math.PI - alpha)
|
|
||||||
let numsteps = 2 * Math.ceil(deltaangle / circlesegmentangle / 2) // should be even
|
|
||||||
// build the pie:
|
|
||||||
let points = [circlecenter]
|
|
||||||
for (let i = 0; i <= numsteps; i++) {
|
|
||||||
let angle = startangle + i / numsteps * deltaangle
|
|
||||||
let p = Vector2D.fromAngleRadians(angle).times(radiuscorrected).plus(circlecenter)
|
|
||||||
points.push(p)
|
|
||||||
}
|
|
||||||
cutouts.push(CAG.fromPoints(points))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = cag.subtract(cutouts)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CAG
|
|
@ -1,58 +0,0 @@
|
|||||||
const CAG = require('./CAG')
|
|
||||||
const Side = require('./math/Side')
|
|
||||||
const Vector2D = require('./math/Vector2')
|
|
||||||
const Vertex = require('./math/Vertex2')
|
|
||||||
const Path2 = require('./math/Path2')
|
|
||||||
|
|
||||||
/** Reconstruct a CAG from an object with identical property names.
|
|
||||||
* @param {Object} obj - anonymous object, typically from JSON
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
const fromObject = function (obj) {
|
|
||||||
let sides = obj.sides.map(function (s) {
|
|
||||||
return Side.fromObject(s)
|
|
||||||
})
|
|
||||||
let cag = CAG.fromSides(sides)
|
|
||||||
cag.isCanonicalized = obj.isCanonicalized
|
|
||||||
return cag
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a CAG from a list of points (a polygon).
|
|
||||||
* Like fromPoints() but does not check if the result is a valid polygon.
|
|
||||||
* The points MUST rotate counter clockwise.
|
|
||||||
* The points can define a convex or a concave polygon.
|
|
||||||
* The polygon must not self intersect.
|
|
||||||
* @param {points[]} points - list of points in 2D space
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
const fromPointsNoCheck = function (points) {
|
|
||||||
let sides = []
|
|
||||||
let prevpoint = new Vector2D(points[points.length - 1])
|
|
||||||
let prevvertex = new Vertex(prevpoint)
|
|
||||||
points.map(function (p) {
|
|
||||||
let point = new Vector2D(p)
|
|
||||||
let vertex = new Vertex(point)
|
|
||||||
let side = new Side(prevvertex, vertex)
|
|
||||||
sides.push(side)
|
|
||||||
prevvertex = vertex
|
|
||||||
})
|
|
||||||
return CAG.fromSides(sides)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a CAG from a 2d-path (a closed sequence of points).
|
|
||||||
* Like fromPoints() but does not check if the result is a valid polygon.
|
|
||||||
* @param {path} Path2 - a Path2 path
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
const fromPath2 = function (path) {
|
|
||||||
if (!path.isClosed()) throw new Error('The path should be closed!')
|
|
||||||
return CAG.fromPoints(path.getPoints())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fromObject,
|
|
||||||
fromPointsNoCheck,
|
|
||||||
fromPath2
|
|
||||||
//fromFakeCSG
|
|
||||||
}
|
|
969
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CSG.js
generated
vendored
969
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CSG.js
generated
vendored
@ -1,969 +0,0 @@
|
|||||||
const {fnNumberSort} = require('./utils')
|
|
||||||
const FuzzyCSGFactory = require('./FuzzyFactory3d')
|
|
||||||
const Tree = require('./trees')
|
|
||||||
const {EPS} = require('./constants')
|
|
||||||
const {reTesselateCoplanarPolygons} = require('./math/polygonUtils')
|
|
||||||
const Polygon = require('./math/Polygon3')
|
|
||||||
const Plane = require('./math/Plane')
|
|
||||||
const Vertex = require('./math/Vertex3')
|
|
||||||
const Vector2D = require('./math/Vector2')
|
|
||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Matrix4x4 = require('./math/Matrix4')
|
|
||||||
const OrthoNormalBasis = require('./math/OrthoNormalBasis')
|
|
||||||
|
|
||||||
const CAG = require('./CAG') // FIXME: circular dependency !
|
|
||||||
|
|
||||||
const Properties = require('./Properties')
|
|
||||||
const {Connector} = require('./connectors')
|
|
||||||
const fixTJunctions = require('./utils/fixTJunctions')
|
|
||||||
// let {fromPolygons} = require('./CSGMakers') // FIXME: circular dependency !
|
|
||||||
|
|
||||||
/** Class CSG
|
|
||||||
* Holds a binary space partition tree representing a 3D solid. Two solids can
|
|
||||||
* be combined using the `union()`, `subtract()`, and `intersect()` methods.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
let CSG = function () {
|
|
||||||
this.polygons = []
|
|
||||||
this.properties = new Properties()
|
|
||||||
this.isCanonicalized = true
|
|
||||||
this.isRetesselated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
CSG.prototype = {
|
|
||||||
/** @return {Polygon[]} The list of polygons. */
|
|
||||||
toPolygons: function () {
|
|
||||||
return this.polygons
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new CSG solid representing the space in either this solid or
|
|
||||||
* in the given solids. Neither this solid nor the given solids are modified.
|
|
||||||
* @param {CSG[]} csg - list of CSG objects
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
* @example
|
|
||||||
* let C = A.union(B)
|
|
||||||
* @example
|
|
||||||
* +-------+ +-------+
|
|
||||||
* | | | |
|
|
||||||
* | A | | |
|
|
||||||
* | +--+----+ = | +----+
|
|
||||||
* +----+--+ | +----+ |
|
|
||||||
* | B | | |
|
|
||||||
* | | | |
|
|
||||||
* +-------+ +-------+
|
|
||||||
*/
|
|
||||||
union: function (csg) {
|
|
||||||
let csgs
|
|
||||||
if (csg instanceof Array) {
|
|
||||||
csgs = csg.slice(0)
|
|
||||||
csgs.push(this)
|
|
||||||
} else {
|
|
||||||
csgs = [this, csg]
|
|
||||||
}
|
|
||||||
|
|
||||||
let i
|
|
||||||
// combine csg pairs in a way that forms a balanced binary tree pattern
|
|
||||||
for (i = 1; i < csgs.length; i += 2) {
|
|
||||||
csgs.push(csgs[i - 1].unionSub(csgs[i]))
|
|
||||||
}
|
|
||||||
return csgs[i - 1].reTesselated().canonicalized()
|
|
||||||
},
|
|
||||||
|
|
||||||
unionSub: function (csg, retesselate, canonicalize) {
|
|
||||||
if (!this.mayOverlap(csg)) {
|
|
||||||
return this.unionForNonIntersecting(csg)
|
|
||||||
} else {
|
|
||||||
let a = new Tree(this.polygons)
|
|
||||||
let b = new Tree(csg.polygons)
|
|
||||||
a.clipTo(b, false)
|
|
||||||
|
|
||||||
// b.clipTo(a, true); // ERROR: this doesn't work
|
|
||||||
b.clipTo(a)
|
|
||||||
b.invert()
|
|
||||||
b.clipTo(a)
|
|
||||||
b.invert()
|
|
||||||
|
|
||||||
let newpolygons = a.allPolygons().concat(b.allPolygons())
|
|
||||||
let result = CSG.fromPolygons(newpolygons)
|
|
||||||
result.properties = this.properties._merge(csg.properties)
|
|
||||||
if (retesselate) result = result.reTesselated()
|
|
||||||
if (canonicalize) result = result.canonicalized()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Like union, but when we know that the two solids are not intersecting
|
|
||||||
// Do not use if you are not completely sure that the solids do not intersect!
|
|
||||||
unionForNonIntersecting: function (csg) {
|
|
||||||
let newpolygons = this.polygons.concat(csg.polygons)
|
|
||||||
let result = CSG.fromPolygons(newpolygons)
|
|
||||||
result.properties = this.properties._merge(csg.properties)
|
|
||||||
result.isCanonicalized = this.isCanonicalized && csg.isCanonicalized
|
|
||||||
result.isRetesselated = this.isRetesselated && csg.isRetesselated
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new CSG solid representing space in this solid but
|
|
||||||
* not in the given solids. Neither this solid nor the given solids are modified.
|
|
||||||
* @param {CSG[]} csg - list of CSG objects
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
* @example
|
|
||||||
* let C = A.subtract(B)
|
|
||||||
* @example
|
|
||||||
* +-------+ +-------+
|
|
||||||
* | | | |
|
|
||||||
* | A | | |
|
|
||||||
* | +--+----+ = | +--+
|
|
||||||
* +----+--+ | +----+
|
|
||||||
* | B |
|
|
||||||
* | |
|
|
||||||
* +-------+
|
|
||||||
*/
|
|
||||||
subtract: function (csg) {
|
|
||||||
let csgs
|
|
||||||
if (csg instanceof Array) {
|
|
||||||
csgs = csg
|
|
||||||
} else {
|
|
||||||
csgs = [csg]
|
|
||||||
}
|
|
||||||
let result = this
|
|
||||||
for (let i = 0; i < csgs.length; i++) {
|
|
||||||
let islast = (i === (csgs.length - 1))
|
|
||||||
result = result.subtractSub(csgs[i], islast, islast)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
subtractSub: function (csg, retesselate, canonicalize) {
|
|
||||||
let a = new Tree(this.polygons)
|
|
||||||
let b = new Tree(csg.polygons)
|
|
||||||
a.invert()
|
|
||||||
a.clipTo(b)
|
|
||||||
b.clipTo(a, true)
|
|
||||||
a.addPolygons(b.allPolygons())
|
|
||||||
a.invert()
|
|
||||||
let result = CSG.fromPolygons(a.allPolygons())
|
|
||||||
result.properties = this.properties._merge(csg.properties)
|
|
||||||
if (retesselate) result = result.reTesselated()
|
|
||||||
if (canonicalize) result = result.canonicalized()
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new CSG solid representing space in both this solid and
|
|
||||||
* in the given solids. Neither this solid nor the given solids are modified.
|
|
||||||
* @param {CSG[]} csg - list of CSG objects
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
* @example
|
|
||||||
* let C = A.intersect(B)
|
|
||||||
* @example
|
|
||||||
* +-------+
|
|
||||||
* | |
|
|
||||||
* | A |
|
|
||||||
* | +--+----+ = +--+
|
|
||||||
* +----+--+ | +--+
|
|
||||||
* | B |
|
|
||||||
* | |
|
|
||||||
* +-------+
|
|
||||||
*/
|
|
||||||
intersect: function (csg) {
|
|
||||||
let csgs
|
|
||||||
if (csg instanceof Array) {
|
|
||||||
csgs = csg
|
|
||||||
} else {
|
|
||||||
csgs = [csg]
|
|
||||||
}
|
|
||||||
let result = this
|
|
||||||
for (let i = 0; i < csgs.length; i++) {
|
|
||||||
let islast = (i === (csgs.length - 1))
|
|
||||||
result = result.intersectSub(csgs[i], islast, islast)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
intersectSub: function (csg, retesselate, canonicalize) {
|
|
||||||
let a = new Tree(this.polygons)
|
|
||||||
let b = new Tree(csg.polygons)
|
|
||||||
a.invert()
|
|
||||||
b.clipTo(a)
|
|
||||||
b.invert()
|
|
||||||
a.clipTo(b)
|
|
||||||
b.clipTo(a)
|
|
||||||
a.addPolygons(b.allPolygons())
|
|
||||||
a.invert()
|
|
||||||
let result = CSG.fromPolygons(a.allPolygons())
|
|
||||||
result.properties = this.properties._merge(csg.properties)
|
|
||||||
if (retesselate) result = result.reTesselated()
|
|
||||||
if (canonicalize) result = result.canonicalized()
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new CSG solid with solid and empty space switched.
|
|
||||||
* This solid is not modified.
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
* @example
|
|
||||||
* let B = A.invert()
|
|
||||||
*/
|
|
||||||
invert: function () {
|
|
||||||
let flippedpolygons = this.polygons.map(function (p) {
|
|
||||||
return p.flipped()
|
|
||||||
})
|
|
||||||
return CSG.fromPolygons(flippedpolygons)
|
|
||||||
// TODO: flip properties?
|
|
||||||
},
|
|
||||||
|
|
||||||
// Affine transformation of CSG object. Returns a new CSG object
|
|
||||||
transform1: function (matrix4x4) {
|
|
||||||
let newpolygons = this.polygons.map(function (p) {
|
|
||||||
return p.transform(matrix4x4)
|
|
||||||
})
|
|
||||||
let result = CSG.fromPolygons(newpolygons)
|
|
||||||
result.properties = this.properties._transform(matrix4x4)
|
|
||||||
result.isRetesselated = this.isRetesselated
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new CSG solid that is transformed using the given Matrix.
|
|
||||||
* Several matrix transformations can be combined before transforming this solid.
|
|
||||||
* @param {CSG.Matrix4x4} matrix4x4 - matrix to be applied
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
* @example
|
|
||||||
* var m = new CSG.Matrix4x4()
|
|
||||||
* m = m.multiply(CSG.Matrix4x4.rotationX(40))
|
|
||||||
* m = m.multiply(CSG.Matrix4x4.translation([-.5, 0, 0]))
|
|
||||||
* let B = A.transform(m)
|
|
||||||
*/
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let ismirror = matrix4x4.isMirroring()
|
|
||||||
let transformedvertices = {}
|
|
||||||
let transformedplanes = {}
|
|
||||||
let newpolygons = this.polygons.map(function (p) {
|
|
||||||
let newplane
|
|
||||||
let plane = p.plane
|
|
||||||
let planetag = plane.getTag()
|
|
||||||
if (planetag in transformedplanes) {
|
|
||||||
newplane = transformedplanes[planetag]
|
|
||||||
} else {
|
|
||||||
newplane = plane.transform(matrix4x4)
|
|
||||||
transformedplanes[planetag] = newplane
|
|
||||||
}
|
|
||||||
let newvertices = p.vertices.map(function (v) {
|
|
||||||
let newvertex
|
|
||||||
let vertextag = v.getTag()
|
|
||||||
if (vertextag in transformedvertices) {
|
|
||||||
newvertex = transformedvertices[vertextag]
|
|
||||||
} else {
|
|
||||||
newvertex = v.transform(matrix4x4)
|
|
||||||
transformedvertices[vertextag] = newvertex
|
|
||||||
}
|
|
||||||
return newvertex
|
|
||||||
})
|
|
||||||
if (ismirror) newvertices.reverse()
|
|
||||||
return new Polygon(newvertices, p.shared, newplane)
|
|
||||||
})
|
|
||||||
let result = CSG.fromPolygons(newpolygons)
|
|
||||||
result.properties = this.properties._transform(matrix4x4)
|
|
||||||
result.isRetesselated = this.isRetesselated
|
|
||||||
result.isCanonicalized = this.isCanonicalized
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () {
|
|
||||||
let result = 'CSG solid:\n'
|
|
||||||
this.polygons.map(function (p) {
|
|
||||||
result += p.toString()
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Expand the solid
|
|
||||||
// resolution: number of points per 360 degree for the rounded corners
|
|
||||||
expand: function (radius, resolution) {
|
|
||||||
let result = this.expandedShell(radius, resolution, true)
|
|
||||||
result = result.reTesselated()
|
|
||||||
result.properties = this.properties // keep original properties
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Contract the solid
|
|
||||||
// resolution: number of points per 360 degree for the rounded corners
|
|
||||||
contract: function (radius, resolution) {
|
|
||||||
let expandedshell = this.expandedShell(radius, resolution, false)
|
|
||||||
let result = this.subtract(expandedshell)
|
|
||||||
result = result.reTesselated()
|
|
||||||
result.properties = this.properties // keep original properties
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// cut the solid at a plane, and stretch the cross-section found along plane normal
|
|
||||||
stretchAtPlane: function (normal, point, length) {
|
|
||||||
let plane = Plane.fromNormalAndPoint(normal, point)
|
|
||||||
let onb = new OrthoNormalBasis(plane)
|
|
||||||
let crosssect = this.sectionCut(onb)
|
|
||||||
let midpiece = crosssect.extrudeInOrthonormalBasis(onb, length)
|
|
||||||
let piece1 = this.cutByPlane(plane)
|
|
||||||
let piece2 = this.cutByPlane(plane.flipped())
|
|
||||||
let result = piece1.union([midpiece, piece2.translate(plane.normal.times(length))])
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create the expanded shell of the solid:
|
|
||||||
// All faces are extruded to get a thickness of 2*radius
|
|
||||||
// Cylinders are constructed around every side
|
|
||||||
// Spheres are placed on every vertex
|
|
||||||
// unionWithThis: if true, the resulting solid will be united with 'this' solid;
|
|
||||||
// the result is a true expansion of the solid
|
|
||||||
// If false, returns only the shell
|
|
||||||
expandedShell: function (radius, resolution, unionWithThis) {
|
|
||||||
// const {sphere} = require('./primitives3d') // FIXME: circular dependency !
|
|
||||||
let csg = this.reTesselated()
|
|
||||||
let result
|
|
||||||
if (unionWithThis) {
|
|
||||||
result = csg
|
|
||||||
} else {
|
|
||||||
result = new CSG()
|
|
||||||
}
|
|
||||||
|
|
||||||
// first extrude all polygons:
|
|
||||||
csg.polygons.map(function (polygon) {
|
|
||||||
let extrudevector = polygon.plane.normal.unit().times(2 * radius)
|
|
||||||
let translatedpolygon = polygon.translate(extrudevector.times(-0.5))
|
|
||||||
let extrudedface = translatedpolygon.extrude(extrudevector)
|
|
||||||
result = result.unionSub(extrudedface, false, false)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Make a list of all unique vertex pairs (i.e. all sides of the solid)
|
|
||||||
// For each vertex pair we collect the following:
|
|
||||||
// v1: first coordinate
|
|
||||||
// v2: second coordinate
|
|
||||||
// planenormals: array of normal vectors of all planes touching this side
|
|
||||||
let vertexpairs = {} // map of 'vertex pair tag' to {v1, v2, planenormals}
|
|
||||||
csg.polygons.map(function (polygon) {
|
|
||||||
let numvertices = polygon.vertices.length
|
|
||||||
let prevvertex = polygon.vertices[numvertices - 1]
|
|
||||||
let prevvertextag = prevvertex.getTag()
|
|
||||||
for (let i = 0; i < numvertices; i++) {
|
|
||||||
let vertex = polygon.vertices[i]
|
|
||||||
let vertextag = vertex.getTag()
|
|
||||||
let vertextagpair
|
|
||||||
if (vertextag < prevvertextag) {
|
|
||||||
vertextagpair = vertextag + '-' + prevvertextag
|
|
||||||
} else {
|
|
||||||
vertextagpair = prevvertextag + '-' + vertextag
|
|
||||||
}
|
|
||||||
let obj
|
|
||||||
if (vertextagpair in vertexpairs) {
|
|
||||||
obj = vertexpairs[vertextagpair]
|
|
||||||
} else {
|
|
||||||
obj = {
|
|
||||||
v1: prevvertex,
|
|
||||||
v2: vertex,
|
|
||||||
planenormals: []
|
|
||||||
}
|
|
||||||
vertexpairs[vertextagpair] = obj
|
|
||||||
}
|
|
||||||
obj.planenormals.push(polygon.plane.normal)
|
|
||||||
|
|
||||||
prevvertextag = vertextag
|
|
||||||
prevvertex = vertex
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// now construct a cylinder on every side
|
|
||||||
// The cylinder is always an approximation of a true cylinder: it will have <resolution> polygons
|
|
||||||
// around the sides. We will make sure though that the cylinder will have an edge at every
|
|
||||||
// face that touches this side. This ensures that we will get a smooth fill even
|
|
||||||
// if two edges are at, say, 10 degrees and the resolution is low.
|
|
||||||
// Note: the result is not retesselated yet but it really should be!
|
|
||||||
for (let vertextagpair in vertexpairs) {
|
|
||||||
let vertexpair = vertexpairs[vertextagpair]
|
|
||||||
let startpoint = vertexpair.v1.pos
|
|
||||||
let endpoint = vertexpair.v2.pos
|
|
||||||
// our x,y and z vectors:
|
|
||||||
let zbase = endpoint.minus(startpoint).unit()
|
|
||||||
let xbase = vertexpair.planenormals[0].unit()
|
|
||||||
let ybase = xbase.cross(zbase)
|
|
||||||
|
|
||||||
// make a list of angles that the cylinder should traverse:
|
|
||||||
let angles = []
|
|
||||||
|
|
||||||
// first of all equally spaced around the cylinder:
|
|
||||||
for (let i = 0; i < resolution; i++) {
|
|
||||||
angles.push(i * Math.PI * 2 / resolution)
|
|
||||||
}
|
|
||||||
|
|
||||||
// and also at every normal of all touching planes:
|
|
||||||
for (let i = 0, iMax = vertexpair.planenormals.length; i < iMax; i++) {
|
|
||||||
let planenormal = vertexpair.planenormals[i]
|
|
||||||
let si = ybase.dot(planenormal)
|
|
||||||
let co = xbase.dot(planenormal)
|
|
||||||
let angle = Math.atan2(si, co)
|
|
||||||
|
|
||||||
if (angle < 0) angle += Math.PI * 2
|
|
||||||
angles.push(angle)
|
|
||||||
angle = Math.atan2(-si, -co)
|
|
||||||
if (angle < 0) angle += Math.PI * 2
|
|
||||||
angles.push(angle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this will result in some duplicate angles but we will get rid of those later.
|
|
||||||
// Sort:
|
|
||||||
angles = angles.sort(fnNumberSort)
|
|
||||||
|
|
||||||
// Now construct the cylinder by traversing all angles:
|
|
||||||
let numangles = angles.length
|
|
||||||
let prevp1
|
|
||||||
let prevp2
|
|
||||||
let startfacevertices = []
|
|
||||||
let endfacevertices = []
|
|
||||||
let polygons = []
|
|
||||||
for (let i = -1; i < numangles; i++) {
|
|
||||||
let angle = angles[(i < 0) ? (i + numangles) : i]
|
|
||||||
let si = Math.sin(angle)
|
|
||||||
let co = Math.cos(angle)
|
|
||||||
let p = xbase.times(co * radius).plus(ybase.times(si * radius))
|
|
||||||
let p1 = startpoint.plus(p)
|
|
||||||
let p2 = endpoint.plus(p)
|
|
||||||
let skip = false
|
|
||||||
if (i >= 0) {
|
|
||||||
if (p1.distanceTo(prevp1) < EPS) {
|
|
||||||
skip = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!skip) {
|
|
||||||
if (i >= 0) {
|
|
||||||
startfacevertices.push(new Vertex(p1))
|
|
||||||
endfacevertices.push(new Vertex(p2))
|
|
||||||
let polygonvertices = [
|
|
||||||
new Vertex(prevp2),
|
|
||||||
new Vertex(p2),
|
|
||||||
new Vertex(p1),
|
|
||||||
new Vertex(prevp1)
|
|
||||||
]
|
|
||||||
let polygon = new Polygon(polygonvertices)
|
|
||||||
polygons.push(polygon)
|
|
||||||
}
|
|
||||||
prevp1 = p1
|
|
||||||
prevp2 = p2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endfacevertices.reverse()
|
|
||||||
polygons.push(new Polygon(startfacevertices))
|
|
||||||
polygons.push(new Polygon(endfacevertices))
|
|
||||||
let cylinder = CSG.fromPolygons(polygons)
|
|
||||||
result = result.unionSub(cylinder, false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a list of all unique vertices
|
|
||||||
// For each vertex we also collect the list of normals of the planes touching the vertices
|
|
||||||
let vertexmap = {}
|
|
||||||
csg.polygons.map(function (polygon) {
|
|
||||||
polygon.vertices.map(function (vertex) {
|
|
||||||
let vertextag = vertex.getTag()
|
|
||||||
let obj
|
|
||||||
if (vertextag in vertexmap) {
|
|
||||||
obj = vertexmap[vertextag]
|
|
||||||
} else {
|
|
||||||
obj = {
|
|
||||||
pos: vertex.pos,
|
|
||||||
normals: []
|
|
||||||
}
|
|
||||||
vertexmap[vertextag] = obj
|
|
||||||
}
|
|
||||||
obj.normals.push(polygon.plane.normal)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// and build spheres at each vertex
|
|
||||||
// We will try to set the x and z axis to the normals of 2 planes
|
|
||||||
// This will ensure that our sphere tesselation somewhat matches 2 planes
|
|
||||||
for (let vertextag in vertexmap) {
|
|
||||||
let vertexobj = vertexmap[vertextag]
|
|
||||||
// use the first normal to be the x axis of our sphere:
|
|
||||||
let xaxis = vertexobj.normals[0].unit()
|
|
||||||
// and find a suitable z axis. We will use the normal which is most perpendicular to the x axis:
|
|
||||||
let bestzaxis = null
|
|
||||||
let bestzaxisorthogonality = 0
|
|
||||||
for (let i = 1; i < vertexobj.normals.length; i++) {
|
|
||||||
let normal = vertexobj.normals[i].unit()
|
|
||||||
let cross = xaxis.cross(normal)
|
|
||||||
let crosslength = cross.length()
|
|
||||||
if (crosslength > 0.05) {
|
|
||||||
if (crosslength > bestzaxisorthogonality) {
|
|
||||||
bestzaxisorthogonality = crosslength
|
|
||||||
bestzaxis = normal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bestzaxis) {
|
|
||||||
bestzaxis = xaxis.randomNonParallelVector()
|
|
||||||
}
|
|
||||||
let yaxis = xaxis.cross(bestzaxis).unit()
|
|
||||||
let zaxis = yaxis.cross(xaxis)
|
|
||||||
let _sphere = CSG.sphere({
|
|
||||||
center: vertexobj.pos,
|
|
||||||
radius: radius,
|
|
||||||
resolution: resolution,
|
|
||||||
axes: [xaxis, yaxis, zaxis]
|
|
||||||
})
|
|
||||||
result = result.unionSub(_sphere, false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
canonicalized: function () {
|
|
||||||
if (this.isCanonicalized) {
|
|
||||||
return this
|
|
||||||
} else {
|
|
||||||
let factory = new FuzzyCSGFactory()
|
|
||||||
let result = CSGFromCSGFuzzyFactory(factory, this)
|
|
||||||
result.isCanonicalized = true
|
|
||||||
result.isRetesselated = this.isRetesselated
|
|
||||||
result.properties = this.properties // keep original properties
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
reTesselated: function () {
|
|
||||||
if (this.isRetesselated) {
|
|
||||||
return this
|
|
||||||
} else {
|
|
||||||
let csg = this
|
|
||||||
let polygonsPerPlane = {}
|
|
||||||
let isCanonicalized = csg.isCanonicalized
|
|
||||||
let fuzzyfactory = new FuzzyCSGFactory()
|
|
||||||
csg.polygons.map(function (polygon) {
|
|
||||||
let plane = polygon.plane
|
|
||||||
let shared = polygon.shared
|
|
||||||
if (!isCanonicalized) {
|
|
||||||
// in order to identify to polygons having the same plane, we need to canonicalize the planes
|
|
||||||
// We don't have to do a full canonizalization (including vertices), to save time only do the planes and the shared data:
|
|
||||||
plane = fuzzyfactory.getPlane(plane)
|
|
||||||
shared = fuzzyfactory.getPolygonShared(shared)
|
|
||||||
}
|
|
||||||
let tag = plane.getTag() + '/' + shared.getTag()
|
|
||||||
if (!(tag in polygonsPerPlane)) {
|
|
||||||
polygonsPerPlane[tag] = [polygon]
|
|
||||||
} else {
|
|
||||||
polygonsPerPlane[tag].push(polygon)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let destpolygons = []
|
|
||||||
for (let planetag in polygonsPerPlane) {
|
|
||||||
let sourcepolygons = polygonsPerPlane[planetag]
|
|
||||||
if (sourcepolygons.length < 2) {
|
|
||||||
destpolygons = destpolygons.concat(sourcepolygons)
|
|
||||||
} else {
|
|
||||||
let retesselayedpolygons = []
|
|
||||||
reTesselateCoplanarPolygons(sourcepolygons, retesselayedpolygons)
|
|
||||||
destpolygons = destpolygons.concat(retesselayedpolygons)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = CSG.fromPolygons(destpolygons)
|
|
||||||
result.isRetesselated = true
|
|
||||||
// result = result.canonicalized();
|
|
||||||
result.properties = this.properties // keep original properties
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of Vector3D, providing minimum coordinates and maximum coordinates
|
|
||||||
* of this solid.
|
|
||||||
* @returns {Vector3D[]}
|
|
||||||
* @example
|
|
||||||
* let bounds = A.getBounds()
|
|
||||||
* let minX = bounds[0].x
|
|
||||||
*/
|
|
||||||
getBounds: function () {
|
|
||||||
if (!this.cachedBoundingBox) {
|
|
||||||
let minpoint = new Vector3D(0, 0, 0)
|
|
||||||
let maxpoint = new Vector3D(0, 0, 0)
|
|
||||||
let polygons = this.polygons
|
|
||||||
let numpolygons = polygons.length
|
|
||||||
for (let i = 0; i < numpolygons; i++) {
|
|
||||||
let polygon = polygons[i]
|
|
||||||
let bounds = polygon.boundingBox()
|
|
||||||
if (i === 0) {
|
|
||||||
minpoint = bounds[0]
|
|
||||||
maxpoint = bounds[1]
|
|
||||||
} else {
|
|
||||||
minpoint = minpoint.min(bounds[0])
|
|
||||||
maxpoint = maxpoint.max(bounds[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.cachedBoundingBox = [minpoint, maxpoint]
|
|
||||||
}
|
|
||||||
return this.cachedBoundingBox
|
|
||||||
},
|
|
||||||
|
|
||||||
// returns true if there is a possibility that the two solids overlap
|
|
||||||
// returns false if we can be sure that they do not overlap
|
|
||||||
mayOverlap: function (csg) {
|
|
||||||
if ((this.polygons.length === 0) || (csg.polygons.length === 0)) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
let mybounds = this.getBounds()
|
|
||||||
let otherbounds = csg.getBounds()
|
|
||||||
if (mybounds[1].x < otherbounds[0].x) return false
|
|
||||||
if (mybounds[0].x > otherbounds[1].x) return false
|
|
||||||
if (mybounds[1].y < otherbounds[0].y) return false
|
|
||||||
if (mybounds[0].y > otherbounds[1].y) return false
|
|
||||||
if (mybounds[1].z < otherbounds[0].z) return false
|
|
||||||
if (mybounds[0].z > otherbounds[1].z) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Cut the solid by a plane. Returns the solid on the back side of the plane
|
|
||||||
cutByPlane: function (plane) {
|
|
||||||
if (this.polygons.length === 0) {
|
|
||||||
return new CSG()
|
|
||||||
}
|
|
||||||
// Ideally we would like to do an intersection with a polygon of inifinite size
|
|
||||||
// but this is not supported by our implementation. As a workaround, we will create
|
|
||||||
// a cube, with one face on the plane, and a size larger enough so that the entire
|
|
||||||
// solid fits in the cube.
|
|
||||||
// find the max distance of any vertex to the center of the plane:
|
|
||||||
let planecenter = plane.normal.times(plane.w)
|
|
||||||
let maxdistance = 0
|
|
||||||
this.polygons.map(function (polygon) {
|
|
||||||
polygon.vertices.map(function (vertex) {
|
|
||||||
let distance = vertex.pos.distanceToSquared(planecenter)
|
|
||||||
if (distance > maxdistance) maxdistance = distance
|
|
||||||
})
|
|
||||||
})
|
|
||||||
maxdistance = Math.sqrt(maxdistance)
|
|
||||||
maxdistance *= 1.01 // make sure it's really larger
|
|
||||||
// Now build a polygon on the plane, at any point farther than maxdistance from the plane center:
|
|
||||||
let vertices = []
|
|
||||||
let orthobasis = new OrthoNormalBasis(plane)
|
|
||||||
vertices.push(new Vertex(orthobasis.to3D(new Vector2D(maxdistance, -maxdistance))))
|
|
||||||
vertices.push(new Vertex(orthobasis.to3D(new Vector2D(-maxdistance, -maxdistance))))
|
|
||||||
vertices.push(new Vertex(orthobasis.to3D(new Vector2D(-maxdistance, maxdistance))))
|
|
||||||
vertices.push(new Vertex(orthobasis.to3D(new Vector2D(maxdistance, maxdistance))))
|
|
||||||
let polygon = new Polygon(vertices, null, plane.flipped())
|
|
||||||
|
|
||||||
// and extrude the polygon into a cube, backwards of the plane:
|
|
||||||
let cube = polygon.extrude(plane.normal.times(-maxdistance))
|
|
||||||
|
|
||||||
// Now we can do the intersection:
|
|
||||||
let result = this.intersect(cube)
|
|
||||||
result.properties = this.properties // keep original properties
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Connect a solid to another solid, such that two Connectors become connected
|
|
||||||
// myConnector: a Connector of this solid
|
|
||||||
// otherConnector: a Connector to which myConnector should be connected
|
|
||||||
// mirror: false: the 'axis' vectors of the connectors should point in the same direction
|
|
||||||
// true: the 'axis' vectors of the connectors should point in opposite direction
|
|
||||||
// normalrotation: degrees of rotation between the 'normal' vectors of the two
|
|
||||||
// connectors
|
|
||||||
connectTo: function (myConnector, otherConnector, mirror, normalrotation) {
|
|
||||||
let matrix = myConnector.getTransformationTo(otherConnector, mirror, normalrotation)
|
|
||||||
return this.transform(matrix)
|
|
||||||
},
|
|
||||||
|
|
||||||
// set the .shared property of all polygons
|
|
||||||
// Returns a new CSG solid, the original is unmodified!
|
|
||||||
setShared: function (shared) {
|
|
||||||
let polygons = this.polygons.map(function (p) {
|
|
||||||
return new Polygon(p.vertices, shared, p.plane)
|
|
||||||
})
|
|
||||||
let result = CSG.fromPolygons(polygons)
|
|
||||||
result.properties = this.properties // keep original properties
|
|
||||||
result.isRetesselated = this.isRetesselated
|
|
||||||
result.isCanonicalized = this.isCanonicalized
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
setColor: function (args) {
|
|
||||||
let newshared = Polygon.Shared.fromColor.apply(this, arguments)
|
|
||||||
return this.setShared(newshared)
|
|
||||||
},
|
|
||||||
|
|
||||||
toCompactBinary: function () {
|
|
||||||
let csg = this.canonicalized(),
|
|
||||||
numpolygons = csg.polygons.length,
|
|
||||||
numpolygonvertices = 0,
|
|
||||||
numvertices = 0,
|
|
||||||
vertexmap = {},
|
|
||||||
vertices = [],
|
|
||||||
numplanes = 0,
|
|
||||||
planemap = {},
|
|
||||||
polygonindex = 0,
|
|
||||||
planes = [],
|
|
||||||
shareds = [],
|
|
||||||
sharedmap = {},
|
|
||||||
numshared = 0
|
|
||||||
// for (let i = 0, iMax = csg.polygons.length; i < iMax; i++) {
|
|
||||||
// let p = csg.polygons[i];
|
|
||||||
// for (let j = 0, jMax = p.length; j < jMax; j++) {
|
|
||||||
// ++numpolygonvertices;
|
|
||||||
// let vertextag = p[j].getTag();
|
|
||||||
// if(!(vertextag in vertexmap)) {
|
|
||||||
// vertexmap[vertextag] = numvertices++;
|
|
||||||
// vertices.push(p[j]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
csg.polygons.map(function (p) {
|
|
||||||
p.vertices.map(function (v) {
|
|
||||||
++numpolygonvertices
|
|
||||||
let vertextag = v.getTag()
|
|
||||||
if (!(vertextag in vertexmap)) {
|
|
||||||
vertexmap[vertextag] = numvertices++
|
|
||||||
vertices.push(v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let planetag = p.plane.getTag()
|
|
||||||
if (!(planetag in planemap)) {
|
|
||||||
planemap[planetag] = numplanes++
|
|
||||||
planes.push(p.plane)
|
|
||||||
}
|
|
||||||
let sharedtag = p.shared.getTag()
|
|
||||||
if (!(sharedtag in sharedmap)) {
|
|
||||||
sharedmap[sharedtag] = numshared++
|
|
||||||
shareds.push(p.shared)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let numVerticesPerPolygon = new Uint32Array(numpolygons)
|
|
||||||
let polygonSharedIndexes = new Uint32Array(numpolygons)
|
|
||||||
let polygonVertices = new Uint32Array(numpolygonvertices)
|
|
||||||
let polygonPlaneIndexes = new Uint32Array(numpolygons)
|
|
||||||
let vertexData = new Float64Array(numvertices * 3)
|
|
||||||
let planeData = new Float64Array(numplanes * 4)
|
|
||||||
let polygonVerticesIndex = 0
|
|
||||||
for (let polygonindex = 0; polygonindex < numpolygons; ++polygonindex) {
|
|
||||||
let p = csg.polygons[polygonindex]
|
|
||||||
numVerticesPerPolygon[polygonindex] = p.vertices.length
|
|
||||||
p.vertices.map(function (v) {
|
|
||||||
let vertextag = v.getTag()
|
|
||||||
let vertexindex = vertexmap[vertextag]
|
|
||||||
polygonVertices[polygonVerticesIndex++] = vertexindex
|
|
||||||
})
|
|
||||||
let planetag = p.plane.getTag()
|
|
||||||
let planeindex = planemap[planetag]
|
|
||||||
polygonPlaneIndexes[polygonindex] = planeindex
|
|
||||||
let sharedtag = p.shared.getTag()
|
|
||||||
let sharedindex = sharedmap[sharedtag]
|
|
||||||
polygonSharedIndexes[polygonindex] = sharedindex
|
|
||||||
}
|
|
||||||
let verticesArrayIndex = 0
|
|
||||||
vertices.map(function (v) {
|
|
||||||
let pos = v.pos
|
|
||||||
vertexData[verticesArrayIndex++] = pos._x
|
|
||||||
vertexData[verticesArrayIndex++] = pos._y
|
|
||||||
vertexData[verticesArrayIndex++] = pos._z
|
|
||||||
})
|
|
||||||
let planesArrayIndex = 0
|
|
||||||
planes.map(function (p) {
|
|
||||||
let normal = p.normal
|
|
||||||
planeData[planesArrayIndex++] = normal._x
|
|
||||||
planeData[planesArrayIndex++] = normal._y
|
|
||||||
planeData[planesArrayIndex++] = normal._z
|
|
||||||
planeData[planesArrayIndex++] = p.w
|
|
||||||
})
|
|
||||||
let result = {
|
|
||||||
'class': 'CSG',
|
|
||||||
numPolygons: numpolygons,
|
|
||||||
numVerticesPerPolygon: numVerticesPerPolygon,
|
|
||||||
polygonPlaneIndexes: polygonPlaneIndexes,
|
|
||||||
polygonSharedIndexes: polygonSharedIndexes,
|
|
||||||
polygonVertices: polygonVertices,
|
|
||||||
vertexData: vertexData,
|
|
||||||
planeData: planeData,
|
|
||||||
shared: shareds
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get the transformation that transforms this CSG such that it is lying on the z=0 plane,
|
|
||||||
// as flat as possible (i.e. the least z-height).
|
|
||||||
// So that it is in an orientation suitable for CNC milling
|
|
||||||
getTransformationAndInverseTransformationToFlatLying: function () {
|
|
||||||
if (this.polygons.length === 0) {
|
|
||||||
let m = new Matrix4x4() // unity
|
|
||||||
return [m, m]
|
|
||||||
} else {
|
|
||||||
// get a list of unique planes in the CSG:
|
|
||||||
let csg = this.canonicalized()
|
|
||||||
let planemap = {}
|
|
||||||
csg.polygons.map(function (polygon) {
|
|
||||||
planemap[polygon.plane.getTag()] = polygon.plane
|
|
||||||
})
|
|
||||||
// try each plane in the CSG and find the plane that, when we align it flat onto z=0,
|
|
||||||
// gives the least height in z-direction.
|
|
||||||
// If two planes give the same height, pick the plane that originally had a normal closest
|
|
||||||
// to [0,0,-1].
|
|
||||||
let xvector = new Vector3D(1, 0, 0)
|
|
||||||
let yvector = new Vector3D(0, 1, 0)
|
|
||||||
let zvector = new Vector3D(0, 0, 1)
|
|
||||||
let z0connectorx = new Connector([0, 0, 0], [0, 0, -1], xvector)
|
|
||||||
let z0connectory = new Connector([0, 0, 0], [0, 0, -1], yvector)
|
|
||||||
let isfirst = true
|
|
||||||
let minheight = 0
|
|
||||||
let maxdotz = 0
|
|
||||||
let besttransformation, bestinversetransformation
|
|
||||||
for (let planetag in planemap) {
|
|
||||||
let plane = planemap[planetag]
|
|
||||||
let pointonplane = plane.normal.times(plane.w)
|
|
||||||
let transformation, inversetransformation
|
|
||||||
// We need a normal vecrtor for the transformation
|
|
||||||
// determine which is more perpendicular to the plane normal: x or y?
|
|
||||||
// we will align this as much as possible to the x or y axis vector
|
|
||||||
let xorthogonality = plane.normal.cross(xvector).length()
|
|
||||||
let yorthogonality = plane.normal.cross(yvector).length()
|
|
||||||
if (xorthogonality > yorthogonality) {
|
|
||||||
// x is better:
|
|
||||||
let planeconnector = new Connector(pointonplane, plane.normal, xvector)
|
|
||||||
transformation = planeconnector.getTransformationTo(z0connectorx, false, 0)
|
|
||||||
inversetransformation = z0connectorx.getTransformationTo(planeconnector, false, 0)
|
|
||||||
} else {
|
|
||||||
// y is better:
|
|
||||||
let planeconnector = new Connector(pointonplane, plane.normal, yvector)
|
|
||||||
transformation = planeconnector.getTransformationTo(z0connectory, false, 0)
|
|
||||||
inversetransformation = z0connectory.getTransformationTo(planeconnector, false, 0)
|
|
||||||
}
|
|
||||||
let transformedcsg = csg.transform(transformation)
|
|
||||||
let dotz = -plane.normal.dot(zvector)
|
|
||||||
let bounds = transformedcsg.getBounds()
|
|
||||||
let zheight = bounds[1].z - bounds[0].z
|
|
||||||
let isbetter = isfirst
|
|
||||||
if (!isbetter) {
|
|
||||||
if (zheight < minheight) {
|
|
||||||
isbetter = true
|
|
||||||
} else if (zheight === minheight) {
|
|
||||||
if (dotz > maxdotz) isbetter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isbetter) {
|
|
||||||
// translate the transformation around the z-axis and onto the z plane:
|
|
||||||
let translation = new Vector3D([-0.5 * (bounds[1].x + bounds[0].x), -0.5 * (bounds[1].y + bounds[0].y), -bounds[0].z])
|
|
||||||
transformation = transformation.multiply(Matrix4x4.translation(translation))
|
|
||||||
inversetransformation = Matrix4x4.translation(translation.negated()).multiply(inversetransformation)
|
|
||||||
minheight = zheight
|
|
||||||
maxdotz = dotz
|
|
||||||
besttransformation = transformation
|
|
||||||
bestinversetransformation = inversetransformation
|
|
||||||
}
|
|
||||||
isfirst = false
|
|
||||||
}
|
|
||||||
return [besttransformation, bestinversetransformation]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getTransformationToFlatLying: function () {
|
|
||||||
let result = this.getTransformationAndInverseTransformationToFlatLying()
|
|
||||||
return result[0]
|
|
||||||
},
|
|
||||||
|
|
||||||
lieFlat: function () {
|
|
||||||
let transformation = this.getTransformationToFlatLying()
|
|
||||||
return this.transform(transformation)
|
|
||||||
},
|
|
||||||
|
|
||||||
// project the 3D CSG onto a plane
|
|
||||||
// This returns a 2D CAG with the 'shadow' shape of the 3D solid when projected onto the
|
|
||||||
// plane represented by the orthonormal basis
|
|
||||||
projectToOrthoNormalBasis: function (orthobasis) {
|
|
||||||
let cags = []
|
|
||||||
this.polygons.filter(function (p) {
|
|
||||||
// only return polys in plane, others may disturb result
|
|
||||||
return p.plane.normal.minus(orthobasis.plane.normal).lengthSquared() < (EPS * EPS)
|
|
||||||
})
|
|
||||||
.map(function (polygon) {
|
|
||||||
let cag = polygon.projectToOrthoNormalBasis(orthobasis)
|
|
||||||
if (cag.sides.length > 0) {
|
|
||||||
cags.push(cag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let result = new CAG().union(cags)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
sectionCut: function (orthobasis) {
|
|
||||||
let plane1 = orthobasis.plane
|
|
||||||
let plane2 = orthobasis.plane.flipped()
|
|
||||||
plane1 = new Plane(plane1.normal, plane1.w)
|
|
||||||
plane2 = new Plane(plane2.normal, plane2.w + (5 * EPS))
|
|
||||||
let cut3d = this.cutByPlane(plane1)
|
|
||||||
cut3d = cut3d.cutByPlane(plane2)
|
|
||||||
return cut3d.projectToOrthoNormalBasis(orthobasis)
|
|
||||||
},
|
|
||||||
|
|
||||||
fixTJunctions: function () {
|
|
||||||
return fixTJunctions(CSG.fromPolygons, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
toTriangles: function () {
|
|
||||||
let polygons = []
|
|
||||||
this.polygons.forEach(function (poly) {
|
|
||||||
let firstVertex = poly.vertices[0]
|
|
||||||
for (let i = poly.vertices.length - 3; i >= 0; i--) {
|
|
||||||
polygons.push(new Polygon([
|
|
||||||
firstVertex, poly.vertices[i + 1], poly.vertices[i + 2]
|
|
||||||
],
|
|
||||||
poly.shared, poly.plane))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return polygons
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of values for the requested features of this solid.
|
|
||||||
* Supported Features: 'volume', 'area'
|
|
||||||
* @param {String[]} features - list of features to calculate
|
|
||||||
* @returns {Float[]} values
|
|
||||||
* @example
|
|
||||||
* let volume = A.getFeatures('volume')
|
|
||||||
* let values = A.getFeatures('area','volume')
|
|
||||||
*/
|
|
||||||
getFeatures: function (features) {
|
|
||||||
if (!(features instanceof Array)) {
|
|
||||||
features = [features]
|
|
||||||
}
|
|
||||||
let result = this.toTriangles().map(function (triPoly) {
|
|
||||||
return triPoly.getTetraFeatures(features)
|
|
||||||
})
|
|
||||||
.reduce(function (pv, v) {
|
|
||||||
return v.map(function (feat, i) {
|
|
||||||
return feat + (pv === 0 ? 0 : pv[i])
|
|
||||||
})
|
|
||||||
}, 0)
|
|
||||||
return (result.length === 1) ? result[0] : result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a CSG solid from a list of `Polygon` instances.
|
|
||||||
* @param {Polygon[]} polygons - list of polygons
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
*/
|
|
||||||
CSG.fromPolygons = function fromPolygons (polygons) {
|
|
||||||
let csg = new CSG()
|
|
||||||
csg.polygons = polygons
|
|
||||||
csg.isCanonicalized = false
|
|
||||||
csg.isRetesselated = false
|
|
||||||
return csg
|
|
||||||
}
|
|
||||||
|
|
||||||
const CSGFromCSGFuzzyFactory = function (factory, sourcecsg) {
|
|
||||||
let _this = factory
|
|
||||||
let newpolygons = []
|
|
||||||
sourcecsg.polygons.forEach(function (polygon) {
|
|
||||||
let newpolygon = _this.getPolygon(polygon)
|
|
||||||
// see getPolygon above: we may get a polygon with no vertices, discard it:
|
|
||||||
if (newpolygon.vertices.length >= 3) {
|
|
||||||
newpolygons.push(newpolygon)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return CSG.fromPolygons(newpolygons)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CSG
|
|
111
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CSGFactories.js
generated
vendored
111
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CSGFactories.js
generated
vendored
@ -1,111 +0,0 @@
|
|||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Vertex = require('./math/Vertex3')
|
|
||||||
const Plane = require('./math/Plane')
|
|
||||||
const Polygon2D = require('./math/Polygon2')
|
|
||||||
const Polygon3D = require('./math/Polygon3')
|
|
||||||
|
|
||||||
/** Construct a CSG solid from a list of pre-generated slices.
|
|
||||||
* See Polygon.prototype.solidFromSlices() for details.
|
|
||||||
* @param {Object} options - options passed to solidFromSlices()
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
*/
|
|
||||||
function fromSlices (options) {
|
|
||||||
return (new Polygon2D.createFromPoints([
|
|
||||||
[0, 0, 0],
|
|
||||||
[1, 0, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 0]
|
|
||||||
])).solidFromSlices(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reconstruct a CSG solid from an object with identical property names.
|
|
||||||
* @param {Object} obj - anonymous object, typically from JSON
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
*/
|
|
||||||
function fromObject (obj) {
|
|
||||||
const CSG = require('./CSG')
|
|
||||||
let polygons = obj.polygons.map(function (p) {
|
|
||||||
return Polygon3D.fromObject(p)
|
|
||||||
})
|
|
||||||
let csg = CSG.fromPolygons(polygons)
|
|
||||||
csg.isCanonicalized = obj.isCanonicalized
|
|
||||||
csg.isRetesselated = obj.isRetesselated
|
|
||||||
return csg
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reconstruct a CSG from the output of toCompactBinary().
|
|
||||||
* @param {CompactBinary} bin - see toCompactBinary().
|
|
||||||
* @returns {CSG} new CSG object
|
|
||||||
*/
|
|
||||||
function fromCompactBinary (bin) {
|
|
||||||
const CSG = require('./CSG') // FIXME: circular dependency ??
|
|
||||||
|
|
||||||
if (bin['class'] !== 'CSG') throw new Error('Not a CSG')
|
|
||||||
let planes = []
|
|
||||||
let planeData = bin.planeData
|
|
||||||
let numplanes = planeData.length / 4
|
|
||||||
let arrayindex = 0
|
|
||||||
let x, y, z, w, normal, plane
|
|
||||||
for (let planeindex = 0; planeindex < numplanes; planeindex++) {
|
|
||||||
x = planeData[arrayindex++]
|
|
||||||
y = planeData[arrayindex++]
|
|
||||||
z = planeData[arrayindex++]
|
|
||||||
w = planeData[arrayindex++]
|
|
||||||
normal = Vector3D.Create(x, y, z)
|
|
||||||
plane = new Plane(normal, w)
|
|
||||||
planes.push(plane)
|
|
||||||
}
|
|
||||||
|
|
||||||
let vertices = []
|
|
||||||
const vertexData = bin.vertexData
|
|
||||||
const numvertices = vertexData.length / 3
|
|
||||||
let pos
|
|
||||||
let vertex
|
|
||||||
arrayindex = 0
|
|
||||||
for (let vertexindex = 0; vertexindex < numvertices; vertexindex++) {
|
|
||||||
x = vertexData[arrayindex++]
|
|
||||||
y = vertexData[arrayindex++]
|
|
||||||
z = vertexData[arrayindex++]
|
|
||||||
pos = Vector3D.Create(x, y, z)
|
|
||||||
vertex = new Vertex(pos)
|
|
||||||
vertices.push(vertex)
|
|
||||||
}
|
|
||||||
|
|
||||||
let shareds = bin.shared.map(function (shared) {
|
|
||||||
return Polygon3D.Shared.fromObject(shared)
|
|
||||||
})
|
|
||||||
|
|
||||||
let polygons = []
|
|
||||||
let numpolygons = bin.numPolygons
|
|
||||||
let numVerticesPerPolygon = bin.numVerticesPerPolygon
|
|
||||||
let polygonVertices = bin.polygonVertices
|
|
||||||
let polygonPlaneIndexes = bin.polygonPlaneIndexes
|
|
||||||
let polygonSharedIndexes = bin.polygonSharedIndexes
|
|
||||||
let numpolygonvertices
|
|
||||||
let polygonvertices
|
|
||||||
let shared
|
|
||||||
let polygon // already defined plane,
|
|
||||||
arrayindex = 0
|
|
||||||
for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
|
|
||||||
numpolygonvertices = numVerticesPerPolygon[polygonindex]
|
|
||||||
polygonvertices = []
|
|
||||||
for (let i = 0; i < numpolygonvertices; i++) {
|
|
||||||
polygonvertices.push(vertices[polygonVertices[arrayindex++]])
|
|
||||||
}
|
|
||||||
plane = planes[polygonPlaneIndexes[polygonindex]]
|
|
||||||
shared = shareds[polygonSharedIndexes[polygonindex]]
|
|
||||||
polygon = new Polygon3D(polygonvertices, shared, plane)
|
|
||||||
polygons.push(polygon)
|
|
||||||
}
|
|
||||||
let csg = CSG.fromPolygons(polygons)
|
|
||||||
csg.isCanonicalized = true
|
|
||||||
csg.isRetesselated = true
|
|
||||||
return csg
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
//fromPolygons,
|
|
||||||
fromSlices,
|
|
||||||
fromObject,
|
|
||||||
fromCompactBinary
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
// //////////////////////////////
|
|
||||||
// ## class fuzzyFactory
|
|
||||||
// This class acts as a factory for objects. We can search for an object with approximately
|
|
||||||
// the desired properties (say a rectangle with width 2 and height 1)
|
|
||||||
// The lookupOrCreate() method looks for an existing object (for example it may find an existing rectangle
|
|
||||||
// with width 2.0001 and height 0.999. If no object is found, the user supplied callback is
|
|
||||||
// called, which should generate a new object. The new object is inserted into the database
|
|
||||||
// so it can be found by future lookupOrCreate() calls.
|
|
||||||
// Constructor:
|
|
||||||
// numdimensions: the number of parameters for each object
|
|
||||||
// for example for a 2D rectangle this would be 2
|
|
||||||
// tolerance: The maximum difference for each parameter allowed to be considered a match
|
|
||||||
const FuzzyFactory = function (numdimensions, tolerance) {
|
|
||||||
this.lookuptable = {}
|
|
||||||
this.multiplier = 1.0 / tolerance
|
|
||||||
}
|
|
||||||
|
|
||||||
FuzzyFactory.prototype = {
|
|
||||||
// let obj = f.lookupOrCreate([el1, el2, el3], function(elements) {/* create the new object */});
|
|
||||||
// Performs a fuzzy lookup of the object with the specified elements.
|
|
||||||
// If found, returns the existing object
|
|
||||||
// If not found, calls the supplied callback function which should create a new object with
|
|
||||||
// the specified properties. This object is inserted in the lookup database.
|
|
||||||
lookupOrCreate: function (els, creatorCallback) {
|
|
||||||
let hash = ''
|
|
||||||
let multiplier = this.multiplier
|
|
||||||
els.forEach(function (el) {
|
|
||||||
let valueQuantized = Math.round(el * multiplier)
|
|
||||||
hash += valueQuantized + '/'
|
|
||||||
})
|
|
||||||
if (hash in this.lookuptable) {
|
|
||||||
return this.lookuptable[hash]
|
|
||||||
} else {
|
|
||||||
let object = creatorCallback(els)
|
|
||||||
let hashparts = els.map(function (el) {
|
|
||||||
let q0 = Math.floor(el * multiplier)
|
|
||||||
let q1 = q0 + 1
|
|
||||||
return ['' + q0 + '/', '' + q1 + '/']
|
|
||||||
})
|
|
||||||
let numelements = els.length
|
|
||||||
let numhashes = 1 << numelements
|
|
||||||
for (let hashmask = 0; hashmask < numhashes; ++hashmask) {
|
|
||||||
let hashmaskShifted = hashmask
|
|
||||||
hash = ''
|
|
||||||
hashparts.forEach(function (hashpart) {
|
|
||||||
hash += hashpart[hashmaskShifted & 1]
|
|
||||||
hashmaskShifted >>= 1
|
|
||||||
})
|
|
||||||
this.lookuptable[hash] = object
|
|
||||||
}
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = FuzzyFactory
|
|
@ -1,25 +0,0 @@
|
|||||||
const FuzzyFactory = require('./FuzzyFactory')
|
|
||||||
const {EPS} = require('./constants')
|
|
||||||
const Side = require('./math/Side')
|
|
||||||
|
|
||||||
const FuzzyCAGFactory = function () {
|
|
||||||
this.vertexfactory = new FuzzyFactory(2, EPS)
|
|
||||||
}
|
|
||||||
|
|
||||||
FuzzyCAGFactory.prototype = {
|
|
||||||
getVertex: function (sourcevertex) {
|
|
||||||
let elements = [sourcevertex.pos._x, sourcevertex.pos._y]
|
|
||||||
let result = this.vertexfactory.lookupOrCreate(elements, function (els) {
|
|
||||||
return sourcevertex
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
getSide: function (sourceside) {
|
|
||||||
let vertex0 = this.getVertex(sourceside.vertex0)
|
|
||||||
let vertex1 = this.getVertex(sourceside.vertex1)
|
|
||||||
return new Side(vertex0, vertex1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = FuzzyCAGFactory
|
|
@ -1,68 +0,0 @@
|
|||||||
const {EPS} = require('./constants')
|
|
||||||
const Polygon = require('./math/Polygon3')
|
|
||||||
const FuzzyFactory = require('./FuzzyFactory')
|
|
||||||
|
|
||||||
// ////////////////////////////////////
|
|
||||||
const FuzzyCSGFactory = function () {
|
|
||||||
this.vertexfactory = new FuzzyFactory(3, EPS)
|
|
||||||
this.planefactory = new FuzzyFactory(4, EPS)
|
|
||||||
this.polygonsharedfactory = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
FuzzyCSGFactory.prototype = {
|
|
||||||
getPolygonShared: function (sourceshared) {
|
|
||||||
let hash = sourceshared.getHash()
|
|
||||||
if (hash in this.polygonsharedfactory) {
|
|
||||||
return this.polygonsharedfactory[hash]
|
|
||||||
} else {
|
|
||||||
this.polygonsharedfactory[hash] = sourceshared
|
|
||||||
return sourceshared
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getVertex: function (sourcevertex) {
|
|
||||||
let elements = [sourcevertex.pos._x, sourcevertex.pos._y, sourcevertex.pos._z]
|
|
||||||
let result = this.vertexfactory.lookupOrCreate(elements, function (els) {
|
|
||||||
return sourcevertex
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
getPlane: function (sourceplane) {
|
|
||||||
let elements = [sourceplane.normal._x, sourceplane.normal._y, sourceplane.normal._z, sourceplane.w]
|
|
||||||
let result = this.planefactory.lookupOrCreate(elements, function (els) {
|
|
||||||
return sourceplane
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
getPolygon: function (sourcepolygon) {
|
|
||||||
let newplane = this.getPlane(sourcepolygon.plane)
|
|
||||||
let newshared = this.getPolygonShared(sourcepolygon.shared)
|
|
||||||
let _this = this
|
|
||||||
let newvertices = sourcepolygon.vertices.map(function (vertex) {
|
|
||||||
return _this.getVertex(vertex)
|
|
||||||
})
|
|
||||||
// two vertices that were originally very close may now have become
|
|
||||||
// truly identical (referring to the same Vertex object).
|
|
||||||
// Remove duplicate vertices:
|
|
||||||
let newverticesDedup = []
|
|
||||||
if (newvertices.length > 0) {
|
|
||||||
let prevvertextag = newvertices[newvertices.length - 1].getTag()
|
|
||||||
newvertices.forEach(function (vertex) {
|
|
||||||
let vertextag = vertex.getTag()
|
|
||||||
if (vertextag !== prevvertextag) {
|
|
||||||
newverticesDedup.push(vertex)
|
|
||||||
}
|
|
||||||
prevvertextag = vertextag
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// If it's degenerate, remove all vertices:
|
|
||||||
if (newverticesDedup.length < 3) {
|
|
||||||
newverticesDedup = []
|
|
||||||
}
|
|
||||||
return new Polygon(newverticesDedup, newshared, newplane)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = FuzzyCSGFactory
|
|
82
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/Properties.js
generated
vendored
82
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/Properties.js
generated
vendored
@ -1,82 +0,0 @@
|
|||||||
// ////////////////////////////////////
|
|
||||||
// # Class Properties
|
|
||||||
// This class is used to store properties of a solid
|
|
||||||
// A property can for example be a Vertex, a Plane or a Line3D
|
|
||||||
// Whenever an affine transform is applied to the CSG solid, all its properties are
|
|
||||||
// transformed as well.
|
|
||||||
// The properties can be stored in a complex nested structure (using arrays and objects)
|
|
||||||
const Properties = function () {}
|
|
||||||
|
|
||||||
Properties.prototype = {
|
|
||||||
_transform: function (matrix4x4) {
|
|
||||||
let result = new Properties()
|
|
||||||
Properties.transformObj(this, result, matrix4x4)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
_merge: function (otherproperties) {
|
|
||||||
let result = new Properties()
|
|
||||||
Properties.cloneObj(this, result)
|
|
||||||
Properties.addFrom(result, otherproperties)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties.transformObj = function (source, result, matrix4x4) {
|
|
||||||
for (let propertyname in source) {
|
|
||||||
if (propertyname === '_transform') continue
|
|
||||||
if (propertyname === '_merge') continue
|
|
||||||
let propertyvalue = source[propertyname]
|
|
||||||
let transformed = propertyvalue
|
|
||||||
if (typeof (propertyvalue) === 'object') {
|
|
||||||
if (('transform' in propertyvalue) && (typeof (propertyvalue.transform) === 'function')) {
|
|
||||||
transformed = propertyvalue.transform(matrix4x4)
|
|
||||||
} else if (propertyvalue instanceof Array) {
|
|
||||||
transformed = []
|
|
||||||
Properties.transformObj(propertyvalue, transformed, matrix4x4)
|
|
||||||
} else if (propertyvalue instanceof Properties) {
|
|
||||||
transformed = new Properties()
|
|
||||||
Properties.transformObj(propertyvalue, transformed, matrix4x4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result[propertyname] = transformed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties.cloneObj = function (source, result) {
|
|
||||||
for (let propertyname in source) {
|
|
||||||
if (propertyname === '_transform') continue
|
|
||||||
if (propertyname === '_merge') continue
|
|
||||||
let propertyvalue = source[propertyname]
|
|
||||||
let cloned = propertyvalue
|
|
||||||
if (typeof (propertyvalue) === 'object') {
|
|
||||||
if (propertyvalue instanceof Array) {
|
|
||||||
cloned = []
|
|
||||||
for (let i = 0; i < propertyvalue.length; i++) {
|
|
||||||
cloned.push(propertyvalue[i])
|
|
||||||
}
|
|
||||||
} else if (propertyvalue instanceof Properties) {
|
|
||||||
cloned = new Properties()
|
|
||||||
Properties.cloneObj(propertyvalue, cloned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result[propertyname] = cloned
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties.addFrom = function (result, otherproperties) {
|
|
||||||
for (let propertyname in otherproperties) {
|
|
||||||
if (propertyname === '_transform') continue
|
|
||||||
if (propertyname === '_merge') continue
|
|
||||||
if ((propertyname in result) &&
|
|
||||||
(typeof (result[propertyname]) === 'object') &&
|
|
||||||
(result[propertyname] instanceof Properties) &&
|
|
||||||
(typeof (otherproperties[propertyname]) === 'object') &&
|
|
||||||
(otherproperties[propertyname] instanceof Properties)) {
|
|
||||||
Properties.addFrom(result[propertyname], otherproperties[propertyname])
|
|
||||||
} else if (!(propertyname in result)) {
|
|
||||||
result[propertyname] = otherproperties[propertyname]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Properties
|
|
220
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/connectors.js
generated
vendored
220
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/connectors.js
generated
vendored
@ -1,220 +0,0 @@
|
|||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Line3D = require('./math/Line3')
|
|
||||||
const Matrix4x4 = require('./math/Matrix4')
|
|
||||||
const OrthoNormalBasis = require('./math/OrthoNormalBasis')
|
|
||||||
const Plane = require('./math/Plane')
|
|
||||||
|
|
||||||
// # class Connector
|
|
||||||
// A connector allows to attach two objects at predefined positions
|
|
||||||
// For example a servo motor and a servo horn:
|
|
||||||
// Both can have a Connector called 'shaft'
|
|
||||||
// The horn can be moved and rotated such that the two connectors match
|
|
||||||
// and the horn is attached to the servo motor at the proper position.
|
|
||||||
// Connectors are stored in the properties of a CSG solid so they are
|
|
||||||
// ge the same transformations applied as the solid
|
|
||||||
const Connector = function (point, axisvector, normalvector) {
|
|
||||||
this.point = new Vector3D(point)
|
|
||||||
this.axisvector = new Vector3D(axisvector).unit()
|
|
||||||
this.normalvector = new Vector3D(normalvector).unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
Connector.prototype = {
|
|
||||||
normalized: function () {
|
|
||||||
let axisvector = this.axisvector.unit()
|
|
||||||
// make the normal vector truly normal:
|
|
||||||
let n = this.normalvector.cross(axisvector).unit()
|
|
||||||
let normalvector = axisvector.cross(n)
|
|
||||||
return new Connector(this.point, axisvector, normalvector)
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let point = this.point.multiply4x4(matrix4x4)
|
|
||||||
let axisvector = this.point.plus(this.axisvector).multiply4x4(matrix4x4).minus(point)
|
|
||||||
let normalvector = this.point.plus(this.normalvector).multiply4x4(matrix4x4).minus(point)
|
|
||||||
return new Connector(point, axisvector, normalvector)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get the transformation matrix to connect this Connector to another connector
|
|
||||||
// other: a Connector to which this connector should be connected
|
|
||||||
// mirror: false: the 'axis' vectors of the connectors should point in the same direction
|
|
||||||
// true: the 'axis' vectors of the connectors should point in opposite direction
|
|
||||||
// normalrotation: degrees of rotation between the 'normal' vectors of the two
|
|
||||||
// connectors
|
|
||||||
getTransformationTo: function (other, mirror, normalrotation) {
|
|
||||||
mirror = mirror ? true : false
|
|
||||||
normalrotation = normalrotation ? Number(normalrotation) : 0
|
|
||||||
let us = this.normalized()
|
|
||||||
other = other.normalized()
|
|
||||||
// shift to the origin:
|
|
||||||
let transformation = Matrix4x4.translation(this.point.negated())
|
|
||||||
// construct the plane crossing through the origin and the two axes:
|
|
||||||
let axesplane = Plane.anyPlaneFromVector3Ds(
|
|
||||||
new Vector3D(0, 0, 0), us.axisvector, other.axisvector)
|
|
||||||
let axesbasis = new OrthoNormalBasis(axesplane)
|
|
||||||
let angle1 = axesbasis.to2D(us.axisvector).angle()
|
|
||||||
let angle2 = axesbasis.to2D(other.axisvector).angle()
|
|
||||||
let rotation = 180.0 * (angle2 - angle1) / Math.PI
|
|
||||||
if (mirror) rotation += 180.0
|
|
||||||
transformation = transformation.multiply(axesbasis.getProjectionMatrix())
|
|
||||||
transformation = transformation.multiply(Matrix4x4.rotationZ(rotation))
|
|
||||||
transformation = transformation.multiply(axesbasis.getInverseProjectionMatrix())
|
|
||||||
let usAxesAligned = us.transform(transformation)
|
|
||||||
// Now we have done the transformation for aligning the axes.
|
|
||||||
// We still need to align the normals:
|
|
||||||
let normalsplane = Plane.fromNormalAndPoint(other.axisvector, new Vector3D(0, 0, 0))
|
|
||||||
let normalsbasis = new OrthoNormalBasis(normalsplane)
|
|
||||||
angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle()
|
|
||||||
angle2 = normalsbasis.to2D(other.normalvector).angle()
|
|
||||||
rotation = 180.0 * (angle2 - angle1) / Math.PI
|
|
||||||
rotation += normalrotation
|
|
||||||
transformation = transformation.multiply(normalsbasis.getProjectionMatrix())
|
|
||||||
transformation = transformation.multiply(Matrix4x4.rotationZ(rotation))
|
|
||||||
transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix())
|
|
||||||
// and translate to the destination point:
|
|
||||||
transformation = transformation.multiply(Matrix4x4.translation(other.point))
|
|
||||||
// let usAligned = us.transform(transformation);
|
|
||||||
return transformation
|
|
||||||
},
|
|
||||||
|
|
||||||
axisLine: function () {
|
|
||||||
return new Line3D(this.point, this.axisvector)
|
|
||||||
},
|
|
||||||
|
|
||||||
// creates a new Connector, with the connection point moved in the direction of the axisvector
|
|
||||||
extend: function (distance) {
|
|
||||||
let newpoint = this.point.plus(this.axisvector.unit().times(distance))
|
|
||||||
return new Connector(newpoint, this.axisvector, this.normalvector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConnectorList = function (connectors) {
|
|
||||||
this.connectors_ = connectors ? connectors.slice() : []
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectorList.defaultNormal = [0, 0, 1]
|
|
||||||
|
|
||||||
ConnectorList.fromPath2D = function (path2D, arg1, arg2) {
|
|
||||||
if (arguments.length === 3) {
|
|
||||||
return ConnectorList._fromPath2DTangents(path2D, arg1, arg2)
|
|
||||||
} else if (arguments.length === 2) {
|
|
||||||
return ConnectorList._fromPath2DExplicit(path2D, arg1)
|
|
||||||
} else {
|
|
||||||
throw new Error('call with path2D and either 2 direction vectors, or a function returning direction vectors')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* calculate the connector axisvectors by calculating the "tangent" for path2D.
|
|
||||||
* This is undefined for start and end points, so axis for these have to be manually
|
|
||||||
* provided.
|
|
||||||
*/
|
|
||||||
ConnectorList._fromPath2DTangents = function (path2D, start, end) {
|
|
||||||
// path2D
|
|
||||||
let axis
|
|
||||||
let pathLen = path2D.points.length
|
|
||||||
let result = new ConnectorList([new Connector(path2D.points[0],
|
|
||||||
start, ConnectorList.defaultNormal)])
|
|
||||||
// middle points
|
|
||||||
path2D.points.slice(1, pathLen - 1).forEach(function (p2, i) {
|
|
||||||
axis = path2D.points[i + 2].minus(path2D.points[i]).toVector3D(0)
|
|
||||||
result.appendConnector(new Connector(p2.toVector3D(0), axis,
|
|
||||||
ConnectorList.defaultNormal))
|
|
||||||
}, this)
|
|
||||||
result.appendConnector(new Connector(path2D.points[pathLen - 1], end,
|
|
||||||
ConnectorList.defaultNormal))
|
|
||||||
result.closed = path2D.closed
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* angleIsh: either a static angle, or a function(point) returning an angle
|
|
||||||
*/
|
|
||||||
ConnectorList._fromPath2DExplicit = function (path2D, angleIsh) {
|
|
||||||
function getAngle (angleIsh, pt, i) {
|
|
||||||
if (typeof angleIsh === 'function') {
|
|
||||||
angleIsh = angleIsh(pt, i)
|
|
||||||
}
|
|
||||||
return angleIsh
|
|
||||||
}
|
|
||||||
let result = new ConnectorList(
|
|
||||||
path2D.points.map(function (p2, i) {
|
|
||||||
return new Connector(p2.toVector3D(0),
|
|
||||||
Vector3D.Create(1, 0, 0).rotateZ(getAngle(angleIsh, p2, i)),
|
|
||||||
ConnectorList.defaultNormal)
|
|
||||||
}, this)
|
|
||||||
)
|
|
||||||
result.closed = path2D.closed
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectorList.prototype = {
|
|
||||||
setClosed: function (closed) {
|
|
||||||
this.closed = !!closed // FIXME: what the hell ?
|
|
||||||
},
|
|
||||||
appendConnector: function (conn) {
|
|
||||||
this.connectors_.push(conn)
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
* arguments: cagish: a cag or a function(connector) returning a cag
|
|
||||||
* closed: whether the 3d path defined by connectors location
|
|
||||||
* should be closed or stay open
|
|
||||||
* Note: don't duplicate connectors in the path
|
|
||||||
* TODO: consider an option "maySelfIntersect" to close & force union all single segments
|
|
||||||
*/
|
|
||||||
followWith: function (cagish) {
|
|
||||||
const CSG = require('./CSG') // FIXME , circular dependency connectors => CSG => connectors
|
|
||||||
|
|
||||||
this.verify()
|
|
||||||
function getCag (cagish, connector) {
|
|
||||||
if (typeof cagish === 'function') {
|
|
||||||
cagish = cagish(connector.point, connector.axisvector, connector.normalvector)
|
|
||||||
}
|
|
||||||
return cagish
|
|
||||||
}
|
|
||||||
|
|
||||||
let polygons = []
|
|
||||||
let currCag
|
|
||||||
let prevConnector = this.connectors_[this.connectors_.length - 1]
|
|
||||||
let prevCag = getCag(cagish, prevConnector)
|
|
||||||
// add walls
|
|
||||||
this.connectors_.forEach(function (connector, notFirst) {
|
|
||||||
currCag = getCag(cagish, connector)
|
|
||||||
if (notFirst || this.closed) {
|
|
||||||
polygons.push.apply(polygons, prevCag._toWallPolygons({
|
|
||||||
toConnector1: prevConnector, toConnector2: connector, cag: currCag}))
|
|
||||||
} else {
|
|
||||||
// it is the first, and shape not closed -> build start wall
|
|
||||||
polygons.push.apply(polygons,
|
|
||||||
currCag._toPlanePolygons({toConnector: connector, flipped: true}))
|
|
||||||
}
|
|
||||||
if (notFirst === this.connectors_.length - 1 && !this.closed) {
|
|
||||||
// build end wall
|
|
||||||
polygons.push.apply(polygons,
|
|
||||||
currCag._toPlanePolygons({toConnector: connector}))
|
|
||||||
}
|
|
||||||
prevCag = currCag
|
|
||||||
prevConnector = connector
|
|
||||||
}, this)
|
|
||||||
return CSG.fromPolygons(polygons).reTesselated().canonicalized()
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
* general idea behind these checks: connectors need to have smooth transition from one to another
|
|
||||||
* TODO: add a check that 2 follow-on CAGs are not intersecting
|
|
||||||
*/
|
|
||||||
verify: function () {
|
|
||||||
let connI
|
|
||||||
let connI1
|
|
||||||
for (let i = 0; i < this.connectors_.length - 1; i++) {
|
|
||||||
connI = this.connectors_[i]
|
|
||||||
connI1 = this.connectors_[i + 1]
|
|
||||||
if (connI1.point.minus(connI.point).dot(connI.axisvector) <= 0) {
|
|
||||||
throw new Error('Invalid ConnectorList. Each connectors position needs to be within a <90deg range of previous connectors axisvector')
|
|
||||||
}
|
|
||||||
if (connI.axisvector.dot(connI1.axisvector) <= 0) {
|
|
||||||
throw new Error('invalid ConnectorList. No neighboring connectors axisvectors may span a >=90deg angle')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {Connector, ConnectorList}
|
|
55
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/constants.js
generated
vendored
55
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/constants.js
generated
vendored
@ -1,55 +0,0 @@
|
|||||||
const _CSGDEBUG = false
|
|
||||||
|
|
||||||
/** Number of polygons per 360 degree revolution for 2D objects.
|
|
||||||
* @default
|
|
||||||
*/
|
|
||||||
const defaultResolution2D = 32 // FIXME this seems excessive
|
|
||||||
/** Number of polygons per 360 degree revolution for 3D objects.
|
|
||||||
* @default
|
|
||||||
*/
|
|
||||||
const defaultResolution3D = 12
|
|
||||||
|
|
||||||
/** Epsilon used during determination of near zero distances.
|
|
||||||
* @default
|
|
||||||
*/
|
|
||||||
const EPS = 1e-5
|
|
||||||
|
|
||||||
/** Epsilon used during determination of near zero areas.
|
|
||||||
* @default
|
|
||||||
*/
|
|
||||||
const angleEPS = 0.10
|
|
||||||
|
|
||||||
/** Epsilon used during determination of near zero areas.
|
|
||||||
* This is the minimal area of a minimal polygon.
|
|
||||||
* @default
|
|
||||||
*/
|
|
||||||
const areaEPS = 0.50 * EPS * EPS * Math.sin(angleEPS)
|
|
||||||
|
|
||||||
const all = 0
|
|
||||||
const top = 1
|
|
||||||
const bottom = 2
|
|
||||||
const left = 3
|
|
||||||
const right = 4
|
|
||||||
const front = 5
|
|
||||||
const back = 6
|
|
||||||
// Tag factory: we can request a unique tag through CSG.getTag()
|
|
||||||
let staticTag = 1
|
|
||||||
const getTag = () => staticTag++
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
_CSGDEBUG,
|
|
||||||
defaultResolution2D,
|
|
||||||
defaultResolution3D,
|
|
||||||
EPS,
|
|
||||||
angleEPS,
|
|
||||||
areaEPS,
|
|
||||||
all,
|
|
||||||
top,
|
|
||||||
bottom,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
front,
|
|
||||||
back,
|
|
||||||
staticTag,
|
|
||||||
getTag
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
const CSG = require('./CSG')
|
|
||||||
const {cube} = require('./primitives3d')
|
|
||||||
|
|
||||||
// For debugging
|
|
||||||
// Creates a new solid with a tiny cube at every vertex of the source solid
|
|
||||||
// this is seperated from the CSG class itself because of the dependency on cube
|
|
||||||
const toPointCloud = function (csg, cuberadius) {
|
|
||||||
csg = csg.reTesselated()
|
|
||||||
|
|
||||||
let result = new CSG()
|
|
||||||
|
|
||||||
// make a list of all unique vertices
|
|
||||||
// For each vertex we also collect the list of normals of the planes touching the vertices
|
|
||||||
let vertexmap = {}
|
|
||||||
csg.polygons.map(function (polygon) {
|
|
||||||
polygon.vertices.map(function (vertex) {
|
|
||||||
vertexmap[vertex.getTag()] = vertex.pos
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
for (let vertextag in vertexmap) {
|
|
||||||
let pos = vertexmap[vertextag]
|
|
||||||
let _cube = cube({
|
|
||||||
center: pos,
|
|
||||||
radius: cuberadius
|
|
||||||
})
|
|
||||||
result = result.unionSub(_cube, false, false)
|
|
||||||
}
|
|
||||||
result = result.reTesselated()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {toPointCloud}
|
|
90
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Line2.js
generated
vendored
90
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Line2.js
generated
vendored
@ -1,90 +0,0 @@
|
|||||||
const Vector2D = require('./Vector2')
|
|
||||||
const {solve2Linear} = require('../utils')
|
|
||||||
|
|
||||||
/** class Line2D
|
|
||||||
* Represents a directional line in 2D space
|
|
||||||
* A line is parametrized by its normal vector (perpendicular to the line, rotated 90 degrees counter clockwise)
|
|
||||||
* and w. The line passes through the point <normal>.times(w).
|
|
||||||
* Equation: p is on line if normal.dot(p)==w
|
|
||||||
* @param {Vector2D} normal normal must be a unit vector!
|
|
||||||
* @returns {Line2D}
|
|
||||||
*/
|
|
||||||
const Line2D = function (normal, w) {
|
|
||||||
normal = new Vector2D(normal)
|
|
||||||
w = parseFloat(w)
|
|
||||||
let l = normal.length()
|
|
||||||
// normalize:
|
|
||||||
w *= l
|
|
||||||
normal = normal.times(1.0 / l)
|
|
||||||
this.normal = normal
|
|
||||||
this.w = w
|
|
||||||
}
|
|
||||||
|
|
||||||
Line2D.fromPoints = function (p1, p2) {
|
|
||||||
p1 = new Vector2D(p1)
|
|
||||||
p2 = new Vector2D(p2)
|
|
||||||
let direction = p2.minus(p1)
|
|
||||||
let normal = direction.normal().negated().unit()
|
|
||||||
let w = p1.dot(normal)
|
|
||||||
return new Line2D(normal, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
Line2D.prototype = {
|
|
||||||
// same line but opposite direction:
|
|
||||||
reverse: function () {
|
|
||||||
return new Line2D(this.normal.negated(), -this.w)
|
|
||||||
},
|
|
||||||
|
|
||||||
equals: function (l) {
|
|
||||||
return (l.normal.equals(this.normal) && (l.w === this.w))
|
|
||||||
},
|
|
||||||
|
|
||||||
origin: function () {
|
|
||||||
return this.normal.times(this.w)
|
|
||||||
},
|
|
||||||
|
|
||||||
direction: function () {
|
|
||||||
return this.normal.normal()
|
|
||||||
},
|
|
||||||
|
|
||||||
xAtY: function (y) {
|
|
||||||
// (py == y) && (normal * p == w)
|
|
||||||
// -> px = (w - normal._y * y) / normal.x
|
|
||||||
let x = (this.w - this.normal._y * y) / this.normal.x
|
|
||||||
return x
|
|
||||||
},
|
|
||||||
|
|
||||||
absDistanceToPoint: function (point) {
|
|
||||||
point = new Vector2D(point)
|
|
||||||
let pointProjected = point.dot(this.normal)
|
|
||||||
let distance = Math.abs(pointProjected - this.w)
|
|
||||||
return distance
|
|
||||||
},
|
|
||||||
/* FIXME: has error - origin is not defined, the method is never used
|
|
||||||
closestPoint: function(point) {
|
|
||||||
point = new Vector2D(point);
|
|
||||||
let vector = point.dot(this.direction());
|
|
||||||
return origin.plus(vector);
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// intersection between two lines, returns point as Vector2D
|
|
||||||
intersectWithLine: function (line2d) {
|
|
||||||
let point = solve2Linear(this.normal.x, this.normal.y, line2d.normal.x, line2d.normal.y, this.w, line2d.w)
|
|
||||||
point = new Vector2D(point) // make vector2d
|
|
||||||
return point
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let origin = new Vector2D(0, 0)
|
|
||||||
let pointOnPlane = this.normal.times(this.w)
|
|
||||||
let neworigin = origin.multiply4x4(matrix4x4)
|
|
||||||
let neworiginPlusNormal = this.normal.multiply4x4(matrix4x4)
|
|
||||||
let newnormal = neworiginPlusNormal.minus(neworigin)
|
|
||||||
let newpointOnPlane = pointOnPlane.multiply4x4(matrix4x4)
|
|
||||||
let neww = newnormal.dot(newpointOnPlane)
|
|
||||||
return new Line2D(newnormal, neww)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Line2D
|
|
100
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Line3.js
generated
vendored
100
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Line3.js
generated
vendored
@ -1,100 +0,0 @@
|
|||||||
const Vector3D = require('./Vector3')
|
|
||||||
const {EPS} = require('../constants')
|
|
||||||
const {solve2Linear} = require('../utils')
|
|
||||||
|
|
||||||
// # class Line3D
|
|
||||||
// Represents a line in 3D space
|
|
||||||
// direction must be a unit vector
|
|
||||||
// point is a random point on the line
|
|
||||||
const Line3D = function (point, direction) {
|
|
||||||
point = new Vector3D(point)
|
|
||||||
direction = new Vector3D(direction)
|
|
||||||
this.point = point
|
|
||||||
this.direction = direction.unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
Line3D.fromPoints = function (p1, p2) {
|
|
||||||
p1 = new Vector3D(p1)
|
|
||||||
p2 = new Vector3D(p2)
|
|
||||||
let direction = p2.minus(p1)
|
|
||||||
return new Line3D(p1, direction)
|
|
||||||
}
|
|
||||||
|
|
||||||
Line3D.fromPlanes = function (p1, p2) {
|
|
||||||
let direction = p1.normal.cross(p2.normal)
|
|
||||||
let l = direction.length()
|
|
||||||
if (l < EPS) {
|
|
||||||
throw new Error('Parallel planes')
|
|
||||||
}
|
|
||||||
direction = direction.times(1.0 / l)
|
|
||||||
|
|
||||||
let mabsx = Math.abs(direction.x)
|
|
||||||
let mabsy = Math.abs(direction.y)
|
|
||||||
let mabsz = Math.abs(direction.z)
|
|
||||||
let origin
|
|
||||||
if ((mabsx >= mabsy) && (mabsx >= mabsz)) {
|
|
||||||
// direction vector is mostly pointing towards x
|
|
||||||
// find a point p for which x is zero:
|
|
||||||
let r = solve2Linear(p1.normal.y, p1.normal.z, p2.normal.y, p2.normal.z, p1.w, p2.w)
|
|
||||||
origin = new Vector3D(0, r[0], r[1])
|
|
||||||
} else if ((mabsy >= mabsx) && (mabsy >= mabsz)) {
|
|
||||||
// find a point p for which y is zero:
|
|
||||||
let r = solve2Linear(p1.normal.x, p1.normal.z, p2.normal.x, p2.normal.z, p1.w, p2.w)
|
|
||||||
origin = new Vector3D(r[0], 0, r[1])
|
|
||||||
} else {
|
|
||||||
// find a point p for which z is zero:
|
|
||||||
let r = solve2Linear(p1.normal.x, p1.normal.y, p2.normal.x, p2.normal.y, p1.w, p2.w)
|
|
||||||
origin = new Vector3D(r[0], r[1], 0)
|
|
||||||
}
|
|
||||||
return new Line3D(origin, direction)
|
|
||||||
}
|
|
||||||
|
|
||||||
Line3D.prototype = {
|
|
||||||
intersectWithPlane: function (plane) {
|
|
||||||
// plane: plane.normal * p = plane.w
|
|
||||||
// line: p=line.point + labda * line.direction
|
|
||||||
let labda = (plane.w - plane.normal.dot(this.point)) / plane.normal.dot(this.direction)
|
|
||||||
let point = this.point.plus(this.direction.times(labda))
|
|
||||||
return point
|
|
||||||
},
|
|
||||||
|
|
||||||
clone: function (line) {
|
|
||||||
return new Line3D(this.point.clone(), this.direction.clone())
|
|
||||||
},
|
|
||||||
|
|
||||||
reverse: function () {
|
|
||||||
return new Line3D(this.point.clone(), this.direction.negated())
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let newpoint = this.point.multiply4x4(matrix4x4)
|
|
||||||
let pointPlusDirection = this.point.plus(this.direction)
|
|
||||||
let newPointPlusDirection = pointPlusDirection.multiply4x4(matrix4x4)
|
|
||||||
let newdirection = newPointPlusDirection.minus(newpoint)
|
|
||||||
return new Line3D(newpoint, newdirection)
|
|
||||||
},
|
|
||||||
|
|
||||||
closestPointOnLine: function (point) {
|
|
||||||
point = new Vector3D(point)
|
|
||||||
let t = point.minus(this.point).dot(this.direction) / this.direction.dot(this.direction)
|
|
||||||
let closestpoint = this.point.plus(this.direction.times(t))
|
|
||||||
return closestpoint
|
|
||||||
},
|
|
||||||
|
|
||||||
distanceToPoint: function (point) {
|
|
||||||
point = new Vector3D(point)
|
|
||||||
let closestpoint = this.closestPointOnLine(point)
|
|
||||||
let distancevector = point.minus(closestpoint)
|
|
||||||
let distance = distancevector.length()
|
|
||||||
return distance
|
|
||||||
},
|
|
||||||
|
|
||||||
equals: function (line3d) {
|
|
||||||
if (!this.direction.equals(line3d.direction)) return false
|
|
||||||
let distance = this.distanceToPoint(line3d.point)
|
|
||||||
if (distance > EPS) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Line3D
|
|
284
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Matrix4.js
generated
vendored
284
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Matrix4.js
generated
vendored
@ -1,284 +0,0 @@
|
|||||||
const Vector3D = require('./Vector3')
|
|
||||||
const Vector2D = require('./Vector2')
|
|
||||||
const OrthoNormalBasis = require('./OrthoNormalBasis')
|
|
||||||
const Plane = require('./Plane')
|
|
||||||
|
|
||||||
// # class Matrix4x4:
|
|
||||||
// Represents a 4x4 matrix. Elements are specified in row order
|
|
||||||
const Matrix4x4 = function (elements) {
|
|
||||||
if (arguments.length >= 1) {
|
|
||||||
this.elements = elements
|
|
||||||
} else {
|
|
||||||
// if no arguments passed: create unity matrix
|
|
||||||
this.elements = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Matrix4x4.prototype = {
|
|
||||||
plus: function (m) {
|
|
||||||
var r = []
|
|
||||||
for (var i = 0; i < 16; i++) {
|
|
||||||
r[i] = this.elements[i] + m.elements[i]
|
|
||||||
}
|
|
||||||
return new Matrix4x4(r)
|
|
||||||
},
|
|
||||||
|
|
||||||
minus: function (m) {
|
|
||||||
var r = []
|
|
||||||
for (var i = 0; i < 16; i++) {
|
|
||||||
r[i] = this.elements[i] - m.elements[i]
|
|
||||||
}
|
|
||||||
return new Matrix4x4(r)
|
|
||||||
},
|
|
||||||
|
|
||||||
// right multiply by another 4x4 matrix:
|
|
||||||
multiply: function (m) {
|
|
||||||
// cache elements in local variables, for speedup:
|
|
||||||
var this0 = this.elements[0]
|
|
||||||
var this1 = this.elements[1]
|
|
||||||
var this2 = this.elements[2]
|
|
||||||
var this3 = this.elements[3]
|
|
||||||
var this4 = this.elements[4]
|
|
||||||
var this5 = this.elements[5]
|
|
||||||
var this6 = this.elements[6]
|
|
||||||
var this7 = this.elements[7]
|
|
||||||
var this8 = this.elements[8]
|
|
||||||
var this9 = this.elements[9]
|
|
||||||
var this10 = this.elements[10]
|
|
||||||
var this11 = this.elements[11]
|
|
||||||
var this12 = this.elements[12]
|
|
||||||
var this13 = this.elements[13]
|
|
||||||
var this14 = this.elements[14]
|
|
||||||
var this15 = this.elements[15]
|
|
||||||
var m0 = m.elements[0]
|
|
||||||
var m1 = m.elements[1]
|
|
||||||
var m2 = m.elements[2]
|
|
||||||
var m3 = m.elements[3]
|
|
||||||
var m4 = m.elements[4]
|
|
||||||
var m5 = m.elements[5]
|
|
||||||
var m6 = m.elements[6]
|
|
||||||
var m7 = m.elements[7]
|
|
||||||
var m8 = m.elements[8]
|
|
||||||
var m9 = m.elements[9]
|
|
||||||
var m10 = m.elements[10]
|
|
||||||
var m11 = m.elements[11]
|
|
||||||
var m12 = m.elements[12]
|
|
||||||
var m13 = m.elements[13]
|
|
||||||
var m14 = m.elements[14]
|
|
||||||
var m15 = m.elements[15]
|
|
||||||
|
|
||||||
var result = []
|
|
||||||
result[0] = this0 * m0 + this1 * m4 + this2 * m8 + this3 * m12
|
|
||||||
result[1] = this0 * m1 + this1 * m5 + this2 * m9 + this3 * m13
|
|
||||||
result[2] = this0 * m2 + this1 * m6 + this2 * m10 + this3 * m14
|
|
||||||
result[3] = this0 * m3 + this1 * m7 + this2 * m11 + this3 * m15
|
|
||||||
result[4] = this4 * m0 + this5 * m4 + this6 * m8 + this7 * m12
|
|
||||||
result[5] = this4 * m1 + this5 * m5 + this6 * m9 + this7 * m13
|
|
||||||
result[6] = this4 * m2 + this5 * m6 + this6 * m10 + this7 * m14
|
|
||||||
result[7] = this4 * m3 + this5 * m7 + this6 * m11 + this7 * m15
|
|
||||||
result[8] = this8 * m0 + this9 * m4 + this10 * m8 + this11 * m12
|
|
||||||
result[9] = this8 * m1 + this9 * m5 + this10 * m9 + this11 * m13
|
|
||||||
result[10] = this8 * m2 + this9 * m6 + this10 * m10 + this11 * m14
|
|
||||||
result[11] = this8 * m3 + this9 * m7 + this10 * m11 + this11 * m15
|
|
||||||
result[12] = this12 * m0 + this13 * m4 + this14 * m8 + this15 * m12
|
|
||||||
result[13] = this12 * m1 + this13 * m5 + this14 * m9 + this15 * m13
|
|
||||||
result[14] = this12 * m2 + this13 * m6 + this14 * m10 + this15 * m14
|
|
||||||
result[15] = this12 * m3 + this13 * m7 + this14 * m11 + this15 * m15
|
|
||||||
return new Matrix4x4(result)
|
|
||||||
},
|
|
||||||
|
|
||||||
clone: function () {
|
|
||||||
var elements = this.elements.map(function (p) {
|
|
||||||
return p
|
|
||||||
})
|
|
||||||
return new Matrix4x4(elements)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Right multiply the matrix by a Vector3D (interpreted as 3 row, 1 column)
|
|
||||||
// (result = M*v)
|
|
||||||
// Fourth element is taken as 1
|
|
||||||
rightMultiply1x3Vector: function (v) {
|
|
||||||
var v0 = v._x
|
|
||||||
var v1 = v._y
|
|
||||||
var v2 = v._z
|
|
||||||
var v3 = 1
|
|
||||||
var x = v0 * this.elements[0] + v1 * this.elements[1] + v2 * this.elements[2] + v3 * this.elements[3]
|
|
||||||
var y = v0 * this.elements[4] + v1 * this.elements[5] + v2 * this.elements[6] + v3 * this.elements[7]
|
|
||||||
var z = v0 * this.elements[8] + v1 * this.elements[9] + v2 * this.elements[10] + v3 * this.elements[11]
|
|
||||||
var w = v0 * this.elements[12] + v1 * this.elements[13] + v2 * this.elements[14] + v3 * this.elements[15]
|
|
||||||
// scale such that fourth element becomes 1:
|
|
||||||
if (w !== 1) {
|
|
||||||
var invw = 1.0 / w
|
|
||||||
x *= invw
|
|
||||||
y *= invw
|
|
||||||
z *= invw
|
|
||||||
}
|
|
||||||
return new Vector3D(x, y, z)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Multiply a Vector3D (interpreted as 3 column, 1 row) by this matrix
|
|
||||||
// (result = v*M)
|
|
||||||
// Fourth element is taken as 1
|
|
||||||
leftMultiply1x3Vector: function (v) {
|
|
||||||
var v0 = v._x
|
|
||||||
var v1 = v._y
|
|
||||||
var v2 = v._z
|
|
||||||
var v3 = 1
|
|
||||||
var x = v0 * this.elements[0] + v1 * this.elements[4] + v2 * this.elements[8] + v3 * this.elements[12]
|
|
||||||
var y = v0 * this.elements[1] + v1 * this.elements[5] + v2 * this.elements[9] + v3 * this.elements[13]
|
|
||||||
var z = v0 * this.elements[2] + v1 * this.elements[6] + v2 * this.elements[10] + v3 * this.elements[14]
|
|
||||||
var w = v0 * this.elements[3] + v1 * this.elements[7] + v2 * this.elements[11] + v3 * this.elements[15]
|
|
||||||
// scale such that fourth element becomes 1:
|
|
||||||
if (w !== 1) {
|
|
||||||
var invw = 1.0 / w
|
|
||||||
x *= invw
|
|
||||||
y *= invw
|
|
||||||
z *= invw
|
|
||||||
}
|
|
||||||
return new Vector3D(x, y, z)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Right multiply the matrix by a Vector2D (interpreted as 2 row, 1 column)
|
|
||||||
// (result = M*v)
|
|
||||||
// Fourth element is taken as 1
|
|
||||||
rightMultiply1x2Vector: function (v) {
|
|
||||||
var v0 = v.x
|
|
||||||
var v1 = v.y
|
|
||||||
var v2 = 0
|
|
||||||
var v3 = 1
|
|
||||||
var x = v0 * this.elements[0] + v1 * this.elements[1] + v2 * this.elements[2] + v3 * this.elements[3]
|
|
||||||
var y = v0 * this.elements[4] + v1 * this.elements[5] + v2 * this.elements[6] + v3 * this.elements[7]
|
|
||||||
var z = v0 * this.elements[8] + v1 * this.elements[9] + v2 * this.elements[10] + v3 * this.elements[11]
|
|
||||||
var w = v0 * this.elements[12] + v1 * this.elements[13] + v2 * this.elements[14] + v3 * this.elements[15]
|
|
||||||
// scale such that fourth element becomes 1:
|
|
||||||
if (w !== 1) {
|
|
||||||
var invw = 1.0 / w
|
|
||||||
x *= invw
|
|
||||||
y *= invw
|
|
||||||
z *= invw
|
|
||||||
}
|
|
||||||
return new Vector2D(x, y)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Multiply a Vector2D (interpreted as 2 column, 1 row) by this matrix
|
|
||||||
// (result = v*M)
|
|
||||||
// Fourth element is taken as 1
|
|
||||||
leftMultiply1x2Vector: function (v) {
|
|
||||||
var v0 = v.x
|
|
||||||
var v1 = v.y
|
|
||||||
var v2 = 0
|
|
||||||
var v3 = 1
|
|
||||||
var x = v0 * this.elements[0] + v1 * this.elements[4] + v2 * this.elements[8] + v3 * this.elements[12]
|
|
||||||
var y = v0 * this.elements[1] + v1 * this.elements[5] + v2 * this.elements[9] + v3 * this.elements[13]
|
|
||||||
var z = v0 * this.elements[2] + v1 * this.elements[6] + v2 * this.elements[10] + v3 * this.elements[14]
|
|
||||||
var w = v0 * this.elements[3] + v1 * this.elements[7] + v2 * this.elements[11] + v3 * this.elements[15]
|
|
||||||
// scale such that fourth element becomes 1:
|
|
||||||
if (w !== 1) {
|
|
||||||
var invw = 1.0 / w
|
|
||||||
x *= invw
|
|
||||||
y *= invw
|
|
||||||
z *= invw
|
|
||||||
}
|
|
||||||
return new Vector2D(x, y)
|
|
||||||
},
|
|
||||||
|
|
||||||
// determine whether this matrix is a mirroring transformation
|
|
||||||
isMirroring: function () {
|
|
||||||
var u = new Vector3D(this.elements[0], this.elements[4], this.elements[8])
|
|
||||||
var v = new Vector3D(this.elements[1], this.elements[5], this.elements[9])
|
|
||||||
var w = new Vector3D(this.elements[2], this.elements[6], this.elements[10])
|
|
||||||
|
|
||||||
// for a true orthogonal, non-mirrored base, u.cross(v) == w
|
|
||||||
// If they have an opposite direction then we are mirroring
|
|
||||||
var mirrorvalue = u.cross(v).dot(w)
|
|
||||||
var ismirror = (mirrorvalue < 0)
|
|
||||||
return ismirror
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the unity matrix
|
|
||||||
Matrix4x4.unity = function () {
|
|
||||||
return new Matrix4x4()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a rotation matrix for rotating around the x axis
|
|
||||||
Matrix4x4.rotationX = function (degrees) {
|
|
||||||
var radians = degrees * Math.PI * (1.0 / 180.0)
|
|
||||||
var cos = Math.cos(radians)
|
|
||||||
var sin = Math.sin(radians)
|
|
||||||
var els = [
|
|
||||||
1, 0, 0, 0, 0, cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1
|
|
||||||
]
|
|
||||||
return new Matrix4x4(els)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a rotation matrix for rotating around the y axis
|
|
||||||
Matrix4x4.rotationY = function (degrees) {
|
|
||||||
var radians = degrees * Math.PI * (1.0 / 180.0)
|
|
||||||
var cos = Math.cos(radians)
|
|
||||||
var sin = Math.sin(radians)
|
|
||||||
var els = [
|
|
||||||
cos, 0, -sin, 0, 0, 1, 0, 0, sin, 0, cos, 0, 0, 0, 0, 1
|
|
||||||
]
|
|
||||||
return new Matrix4x4(els)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a rotation matrix for rotating around the z axis
|
|
||||||
Matrix4x4.rotationZ = function (degrees) {
|
|
||||||
var radians = degrees * Math.PI * (1.0 / 180.0)
|
|
||||||
var cos = Math.cos(radians)
|
|
||||||
var sin = Math.sin(radians)
|
|
||||||
var els = [
|
|
||||||
cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1
|
|
||||||
]
|
|
||||||
return new Matrix4x4(els)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matrix for rotation about arbitrary point and axis
|
|
||||||
Matrix4x4.rotation = function (rotationCenter, rotationAxis, degrees) {
|
|
||||||
rotationCenter = new Vector3D(rotationCenter)
|
|
||||||
rotationAxis = new Vector3D(rotationAxis)
|
|
||||||
var rotationPlane = Plane.fromNormalAndPoint(rotationAxis, rotationCenter)
|
|
||||||
var orthobasis = new OrthoNormalBasis(rotationPlane)
|
|
||||||
var transformation = Matrix4x4.translation(rotationCenter.negated())
|
|
||||||
transformation = transformation.multiply(orthobasis.getProjectionMatrix())
|
|
||||||
transformation = transformation.multiply(Matrix4x4.rotationZ(degrees))
|
|
||||||
transformation = transformation.multiply(orthobasis.getInverseProjectionMatrix())
|
|
||||||
transformation = transformation.multiply(Matrix4x4.translation(rotationCenter))
|
|
||||||
return transformation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an affine matrix for translation:
|
|
||||||
Matrix4x4.translation = function (v) {
|
|
||||||
// parse as Vector3D, so we can pass an array or a Vector3D
|
|
||||||
var vec = new Vector3D(v)
|
|
||||||
var els = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, vec.x, vec.y, vec.z, 1]
|
|
||||||
return new Matrix4x4(els)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an affine matrix for mirroring into an arbitrary plane:
|
|
||||||
Matrix4x4.mirroring = function (plane) {
|
|
||||||
var nx = plane.normal.x
|
|
||||||
var ny = plane.normal.y
|
|
||||||
var nz = plane.normal.z
|
|
||||||
var w = plane.w
|
|
||||||
var els = [
|
|
||||||
(1.0 - 2.0 * nx * nx), (-2.0 * ny * nx), (-2.0 * nz * nx), 0,
|
|
||||||
(-2.0 * nx * ny), (1.0 - 2.0 * ny * ny), (-2.0 * nz * ny), 0,
|
|
||||||
(-2.0 * nx * nz), (-2.0 * ny * nz), (1.0 - 2.0 * nz * nz), 0,
|
|
||||||
(2.0 * nx * w), (2.0 * ny * w), (2.0 * nz * w), 1
|
|
||||||
]
|
|
||||||
return new Matrix4x4(els)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an affine matrix for scaling:
|
|
||||||
Matrix4x4.scaling = function (v) {
|
|
||||||
// parse as Vector3D, so we can pass an array or a Vector3D
|
|
||||||
var vec = new Vector3D(v)
|
|
||||||
var els = [
|
|
||||||
vec.x, 0, 0, 0, 0, vec.y, 0, 0, 0, 0, vec.z, 0, 0, 0, 0, 1
|
|
||||||
]
|
|
||||||
return new Matrix4x4(els)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Matrix4x4
|
|
@ -1,202 +0,0 @@
|
|||||||
const Vector2D = require('./Vector2')
|
|
||||||
const Vector3D = require('./Vector3')
|
|
||||||
const Line2D = require('./Line2')
|
|
||||||
const Line3D = require('./Line3')
|
|
||||||
const Plane = require('./Plane')
|
|
||||||
|
|
||||||
// # class OrthoNormalBasis
|
|
||||||
// Reprojects points on a 3D plane onto a 2D plane
|
|
||||||
// or from a 2D plane back onto the 3D plane
|
|
||||||
const OrthoNormalBasis = function (plane, rightvector) {
|
|
||||||
if (arguments.length < 2) {
|
|
||||||
// choose an arbitrary right hand vector, making sure it is somewhat orthogonal to the plane normal:
|
|
||||||
rightvector = plane.normal.randomNonParallelVector()
|
|
||||||
} else {
|
|
||||||
rightvector = new Vector3D(rightvector)
|
|
||||||
}
|
|
||||||
this.v = plane.normal.cross(rightvector).unit()
|
|
||||||
this.u = this.v.cross(plane.normal)
|
|
||||||
this.plane = plane
|
|
||||||
this.planeorigin = plane.normal.times(plane.w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an orthonormal basis for the standard XYZ planes.
|
|
||||||
// Parameters: the names of two 3D axes. The 2d x axis will map to the first given 3D axis, the 2d y
|
|
||||||
// axis will map to the second.
|
|
||||||
// Prepend the axis with a "-" to invert the direction of this axis.
|
|
||||||
// For example: OrthoNormalBasis.GetCartesian("-Y","Z")
|
|
||||||
// will return an orthonormal basis where the 2d X axis maps to the 3D inverted Y axis, and
|
|
||||||
// the 2d Y axis maps to the 3D Z axis.
|
|
||||||
OrthoNormalBasis.GetCartesian = function (xaxisid, yaxisid) {
|
|
||||||
let axisid = xaxisid + '/' + yaxisid
|
|
||||||
let planenormal, rightvector
|
|
||||||
if (axisid === 'X/Y') {
|
|
||||||
planenormal = [0, 0, 1]
|
|
||||||
rightvector = [1, 0, 0]
|
|
||||||
} else if (axisid === 'Y/-X') {
|
|
||||||
planenormal = [0, 0, 1]
|
|
||||||
rightvector = [0, 1, 0]
|
|
||||||
} else if (axisid === '-X/-Y') {
|
|
||||||
planenormal = [0, 0, 1]
|
|
||||||
rightvector = [-1, 0, 0]
|
|
||||||
} else if (axisid === '-Y/X') {
|
|
||||||
planenormal = [0, 0, 1]
|
|
||||||
rightvector = [0, -1, 0]
|
|
||||||
} else if (axisid === '-X/Y') {
|
|
||||||
planenormal = [0, 0, -1]
|
|
||||||
rightvector = [-1, 0, 0]
|
|
||||||
} else if (axisid === '-Y/-X') {
|
|
||||||
planenormal = [0, 0, -1]
|
|
||||||
rightvector = [0, -1, 0]
|
|
||||||
} else if (axisid === 'X/-Y') {
|
|
||||||
planenormal = [0, 0, -1]
|
|
||||||
rightvector = [1, 0, 0]
|
|
||||||
} else if (axisid === 'Y/X') {
|
|
||||||
planenormal = [0, 0, -1]
|
|
||||||
rightvector = [0, 1, 0]
|
|
||||||
} else if (axisid === 'X/Z') {
|
|
||||||
planenormal = [0, -1, 0]
|
|
||||||
rightvector = [1, 0, 0]
|
|
||||||
} else if (axisid === 'Z/-X') {
|
|
||||||
planenormal = [0, -1, 0]
|
|
||||||
rightvector = [0, 0, 1]
|
|
||||||
} else if (axisid === '-X/-Z') {
|
|
||||||
planenormal = [0, -1, 0]
|
|
||||||
rightvector = [-1, 0, 0]
|
|
||||||
} else if (axisid === '-Z/X') {
|
|
||||||
planenormal = [0, -1, 0]
|
|
||||||
rightvector = [0, 0, -1]
|
|
||||||
} else if (axisid === '-X/Z') {
|
|
||||||
planenormal = [0, 1, 0]
|
|
||||||
rightvector = [-1, 0, 0]
|
|
||||||
} else if (axisid === '-Z/-X') {
|
|
||||||
planenormal = [0, 1, 0]
|
|
||||||
rightvector = [0, 0, -1]
|
|
||||||
} else if (axisid === 'X/-Z') {
|
|
||||||
planenormal = [0, 1, 0]
|
|
||||||
rightvector = [1, 0, 0]
|
|
||||||
} else if (axisid === 'Z/X') {
|
|
||||||
planenormal = [0, 1, 0]
|
|
||||||
rightvector = [0, 0, 1]
|
|
||||||
} else if (axisid === 'Y/Z') {
|
|
||||||
planenormal = [1, 0, 0]
|
|
||||||
rightvector = [0, 1, 0]
|
|
||||||
} else if (axisid === 'Z/-Y') {
|
|
||||||
planenormal = [1, 0, 0]
|
|
||||||
rightvector = [0, 0, 1]
|
|
||||||
} else if (axisid === '-Y/-Z') {
|
|
||||||
planenormal = [1, 0, 0]
|
|
||||||
rightvector = [0, -1, 0]
|
|
||||||
} else if (axisid === '-Z/Y') {
|
|
||||||
planenormal = [1, 0, 0]
|
|
||||||
rightvector = [0, 0, -1]
|
|
||||||
} else if (axisid === '-Y/Z') {
|
|
||||||
planenormal = [-1, 0, 0]
|
|
||||||
rightvector = [0, -1, 0]
|
|
||||||
} else if (axisid === '-Z/-Y') {
|
|
||||||
planenormal = [-1, 0, 0]
|
|
||||||
rightvector = [0, 0, -1]
|
|
||||||
} else if (axisid === 'Y/-Z') {
|
|
||||||
planenormal = [-1, 0, 0]
|
|
||||||
rightvector = [0, 1, 0]
|
|
||||||
} else if (axisid === 'Z/Y') {
|
|
||||||
planenormal = [-1, 0, 0]
|
|
||||||
rightvector = [0, 0, 1]
|
|
||||||
} else {
|
|
||||||
throw new Error('OrthoNormalBasis.GetCartesian: invalid combination of axis identifiers. Should pass two string arguments from [X,Y,Z,-X,-Y,-Z], being two different axes.')
|
|
||||||
}
|
|
||||||
return new OrthoNormalBasis(new Plane(new Vector3D(planenormal), 0), new Vector3D(rightvector))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// test code for OrthoNormalBasis.GetCartesian()
|
|
||||||
OrthoNormalBasis.GetCartesian_Test=function() {
|
|
||||||
let axisnames=["X","Y","Z","-X","-Y","-Z"];
|
|
||||||
let axisvectors=[[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0], [0,0,-1]];
|
|
||||||
for(let axis1=0; axis1 < 3; axis1++) {
|
|
||||||
for(let axis1inverted=0; axis1inverted < 2; axis1inverted++) {
|
|
||||||
let axis1name=axisnames[axis1+3*axis1inverted];
|
|
||||||
let axis1vector=axisvectors[axis1+3*axis1inverted];
|
|
||||||
for(let axis2=0; axis2 < 3; axis2++) {
|
|
||||||
if(axis2 != axis1) {
|
|
||||||
for(let axis2inverted=0; axis2inverted < 2; axis2inverted++) {
|
|
||||||
let axis2name=axisnames[axis2+3*axis2inverted];
|
|
||||||
let axis2vector=axisvectors[axis2+3*axis2inverted];
|
|
||||||
let orthobasis=OrthoNormalBasis.GetCartesian(axis1name, axis2name);
|
|
||||||
let test1=orthobasis.to3D(new Vector2D([1,0]));
|
|
||||||
let test2=orthobasis.to3D(new Vector2D([0,1]));
|
|
||||||
let expected1=new Vector3D(axis1vector);
|
|
||||||
let expected2=new Vector3D(axis2vector);
|
|
||||||
let d1=test1.distanceTo(expected1);
|
|
||||||
let d2=test2.distanceTo(expected2);
|
|
||||||
if( (d1 > 0.01) || (d2 > 0.01) ) {
|
|
||||||
throw new Error("Wrong!");
|
|
||||||
}}}}}}
|
|
||||||
throw new Error("OK");
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
// The z=0 plane, with the 3D x and y vectors mapped to the 2D x and y vector
|
|
||||||
OrthoNormalBasis.Z0Plane = function () {
|
|
||||||
let plane = new Plane(new Vector3D([0, 0, 1]), 0)
|
|
||||||
return new OrthoNormalBasis(plane, new Vector3D([1, 0, 0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
OrthoNormalBasis.prototype = {
|
|
||||||
getProjectionMatrix: function () {
|
|
||||||
const Matrix4x4 = require('./Matrix4') // FIXME: circular dependencies Matrix=>OrthoNormalBasis => Matrix
|
|
||||||
return new Matrix4x4([
|
|
||||||
this.u.x, this.v.x, this.plane.normal.x, 0,
|
|
||||||
this.u.y, this.v.y, this.plane.normal.y, 0,
|
|
||||||
this.u.z, this.v.z, this.plane.normal.z, 0,
|
|
||||||
0, 0, -this.plane.w, 1
|
|
||||||
])
|
|
||||||
},
|
|
||||||
|
|
||||||
getInverseProjectionMatrix: function () {
|
|
||||||
const Matrix4x4 = require('./Matrix4') // FIXME: circular dependencies Matrix=>OrthoNormalBasis => Matrix
|
|
||||||
let p = this.plane.normal.times(this.plane.w)
|
|
||||||
return new Matrix4x4([
|
|
||||||
this.u.x, this.u.y, this.u.z, 0,
|
|
||||||
this.v.x, this.v.y, this.v.z, 0,
|
|
||||||
this.plane.normal.x, this.plane.normal.y, this.plane.normal.z, 0,
|
|
||||||
p.x, p.y, p.z, 1
|
|
||||||
])
|
|
||||||
},
|
|
||||||
|
|
||||||
to2D: function (vec3) {
|
|
||||||
return new Vector2D(vec3.dot(this.u), vec3.dot(this.v))
|
|
||||||
},
|
|
||||||
|
|
||||||
to3D: function (vec2) {
|
|
||||||
return this.planeorigin.plus(this.u.times(vec2.x)).plus(this.v.times(vec2.y))
|
|
||||||
},
|
|
||||||
|
|
||||||
line3Dto2D: function (line3d) {
|
|
||||||
let a = line3d.point
|
|
||||||
let b = line3d.direction.plus(a)
|
|
||||||
let a2d = this.to2D(a)
|
|
||||||
let b2d = this.to2D(b)
|
|
||||||
return Line2D.fromPoints(a2d, b2d)
|
|
||||||
},
|
|
||||||
|
|
||||||
line2Dto3D: function (line2d) {
|
|
||||||
let a = line2d.origin()
|
|
||||||
let b = line2d.direction().plus(a)
|
|
||||||
let a3d = this.to3D(a)
|
|
||||||
let b3d = this.to3D(b)
|
|
||||||
return Line3D.fromPoints(a3d, b3d)
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
// todo: this may not work properly in case of mirroring
|
|
||||||
let newplane = this.plane.transform(matrix4x4)
|
|
||||||
let rightpointTransformed = this.u.transform(matrix4x4)
|
|
||||||
let originTransformed = new Vector3D(0, 0, 0).transform(matrix4x4)
|
|
||||||
let newrighthandvector = rightpointTransformed.minus(originTransformed)
|
|
||||||
let newbasis = new OrthoNormalBasis(newplane, newrighthandvector)
|
|
||||||
return newbasis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = OrthoNormalBasis
|
|
472
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Path2.js
generated
vendored
472
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Path2.js
generated
vendored
@ -1,472 +0,0 @@
|
|||||||
const Vector2D = require('./Vector2')
|
|
||||||
const {EPS, angleEPS} = require('../constants')
|
|
||||||
const {parseOptionAs2DVector, parseOptionAsFloat, parseOptionAsInt, parseOptionAsBool} = require('../optionParsers')
|
|
||||||
const {defaultResolution2D} = require('../constants')
|
|
||||||
const Vertex = require('./Vertex2')
|
|
||||||
const Side = require('./Side')
|
|
||||||
|
|
||||||
/** Class Path2D
|
|
||||||
* Represents a series of points, connected by infinitely thin lines.
|
|
||||||
* A path can be open or closed, i.e. additional line between first and last points.
|
|
||||||
* The difference between Path2D and CAG is that a path is a 'thin' line, whereas a CAG is an enclosed area.
|
|
||||||
* @constructor
|
|
||||||
* @param {Vector2D[]} [points=[]] - list of points
|
|
||||||
* @param {boolean} [closed=false] - closer of path
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* new CSG.Path2D()
|
|
||||||
* new CSG.Path2D([[10,10], [-10,10], [-10,-10], [10,-10]], true) // closed
|
|
||||||
*/
|
|
||||||
const Path2D = function (points, closed) {
|
|
||||||
closed = !!closed
|
|
||||||
points = points || []
|
|
||||||
// re-parse the points into Vector2D
|
|
||||||
// and remove any duplicate points
|
|
||||||
let prevpoint = null
|
|
||||||
if (closed && (points.length > 0)) {
|
|
||||||
prevpoint = new Vector2D(points[points.length - 1])
|
|
||||||
}
|
|
||||||
let newpoints = []
|
|
||||||
points.map(function (point) {
|
|
||||||
point = new Vector2D(point)
|
|
||||||
let skip = false
|
|
||||||
if (prevpoint !== null) {
|
|
||||||
let distance = point.distanceTo(prevpoint)
|
|
||||||
skip = distance < EPS
|
|
||||||
}
|
|
||||||
if (!skip) newpoints.push(point)
|
|
||||||
prevpoint = point
|
|
||||||
})
|
|
||||||
this.points = newpoints
|
|
||||||
this.closed = closed
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct an arc.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector2D} [options.center=[0,0]] - center of circle
|
|
||||||
* @param {Number} [options.radius=1] - radius of circle
|
|
||||||
* @param {Number} [options.startangle=0] - starting angle of the arc, in degrees
|
|
||||||
* @param {Number} [options.endangle=360] - ending angle of the arc, in degrees
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of sides per 360 rotation
|
|
||||||
* @param {Boolean} [options.maketangent=false] - adds line segments at both ends of the arc to ensure that the gradients at the edges are tangent
|
|
||||||
* @returns {Path2D} new Path2D object (not closed)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let path = CSG.Path2D.arc({
|
|
||||||
* center: [5, 5],
|
|
||||||
* radius: 10,
|
|
||||||
* startangle: 90,
|
|
||||||
* endangle: 180,
|
|
||||||
* resolution: 36,
|
|
||||||
* maketangent: true
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
Path2D.arc = function (options) {
|
|
||||||
let center = parseOptionAs2DVector(options, 'center', 0)
|
|
||||||
let radius = parseOptionAsFloat(options, 'radius', 1)
|
|
||||||
let startangle = parseOptionAsFloat(options, 'startangle', 0)
|
|
||||||
let endangle = parseOptionAsFloat(options, 'endangle', 360)
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
|
||||||
let maketangent = parseOptionAsBool(options, 'maketangent', false)
|
|
||||||
// no need to make multiple turns:
|
|
||||||
while (endangle - startangle >= 720) {
|
|
||||||
endangle -= 360
|
|
||||||
}
|
|
||||||
while (endangle - startangle <= -720) {
|
|
||||||
endangle += 360
|
|
||||||
}
|
|
||||||
let points = []
|
|
||||||
let point
|
|
||||||
let absangledif = Math.abs(endangle - startangle)
|
|
||||||
if (absangledif < angleEPS) {
|
|
||||||
point = Vector2D.fromAngle(startangle / 180.0 * Math.PI).times(radius)
|
|
||||||
points.push(point.plus(center))
|
|
||||||
} else {
|
|
||||||
let numsteps = Math.floor(resolution * absangledif / 360) + 1
|
|
||||||
let edgestepsize = numsteps * 0.5 / absangledif // step size for half a degree
|
|
||||||
if (edgestepsize > 0.25) edgestepsize = 0.25
|
|
||||||
let numstepsMod = maketangent ? (numsteps + 2) : numsteps
|
|
||||||
for (let i = 0; i <= numstepsMod; i++) {
|
|
||||||
let step = i
|
|
||||||
if (maketangent) {
|
|
||||||
step = (i - 1) * (numsteps - 2 * edgestepsize) / numsteps + edgestepsize
|
|
||||||
if (step < 0) step = 0
|
|
||||||
if (step > numsteps) step = numsteps
|
|
||||||
}
|
|
||||||
let angle = startangle + step * (endangle - startangle) / numsteps
|
|
||||||
point = Vector2D.fromAngle(angle / 180.0 * Math.PI).times(radius)
|
|
||||||
points.push(point.plus(center))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Path2D(points, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Path2D.prototype = {
|
|
||||||
concat: function (otherpath) {
|
|
||||||
if (this.closed || otherpath.closed) {
|
|
||||||
throw new Error('Paths must not be closed')
|
|
||||||
}
|
|
||||||
let newpoints = this.points.concat(otherpath.points)
|
|
||||||
return new Path2D(newpoints)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the points that make up the path.
|
|
||||||
* note that this is current internal list of points, not an immutable copy.
|
|
||||||
* @returns {Vector2[]} array of points the make up the path
|
|
||||||
*/
|
|
||||||
getPoints: function() {
|
|
||||||
return this.points;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append an point to the end of the path.
|
|
||||||
* @param {Vector2D} point - point to append
|
|
||||||
* @returns {Path2D} new Path2D object (not closed)
|
|
||||||
*/
|
|
||||||
appendPoint: function (point) {
|
|
||||||
if (this.closed) {
|
|
||||||
throw new Error('Path must not be closed')
|
|
||||||
}
|
|
||||||
point = new Vector2D(point) // cast to Vector2D
|
|
||||||
let newpoints = this.points.concat([point])
|
|
||||||
return new Path2D(newpoints)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a list of points to the end of the path.
|
|
||||||
* @param {Vector2D[]} points - points to append
|
|
||||||
* @returns {Path2D} new Path2D object (not closed)
|
|
||||||
*/
|
|
||||||
appendPoints: function (points) {
|
|
||||||
if (this.closed) {
|
|
||||||
throw new Error('Path must not be closed')
|
|
||||||
}
|
|
||||||
let newpoints = this.points
|
|
||||||
points.forEach(function (point) {
|
|
||||||
newpoints.push(new Vector2D(point)) // cast to Vector2D
|
|
||||||
})
|
|
||||||
return new Path2D(newpoints)
|
|
||||||
},
|
|
||||||
|
|
||||||
close: function () {
|
|
||||||
return new Path2D(this.points, true)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the path is a closed or not.
|
|
||||||
* @returns {Boolean} true when the path is closed, otherwise false
|
|
||||||
*/
|
|
||||||
isClosed: function() {
|
|
||||||
return this.closed
|
|
||||||
},
|
|
||||||
|
|
||||||
// Extrude the path by following it with a rectangle (upright, perpendicular to the path direction)
|
|
||||||
// Returns a CSG solid
|
|
||||||
// width: width of the extrusion, in the z=0 plane
|
|
||||||
// height: height of the extrusion in the z direction
|
|
||||||
// resolution: number of segments per 360 degrees for the curve in a corner
|
|
||||||
rectangularExtrude: function (width, height, resolution) {
|
|
||||||
let cag = this.expandToCAG(width / 2, resolution)
|
|
||||||
let result = cag.extrude({
|
|
||||||
offset: [0, 0, height]
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Expand the path to a CAG
|
|
||||||
// This traces the path with a circle with radius pathradius
|
|
||||||
expandToCAG: function (pathradius, resolution) {
|
|
||||||
const CAG = require('../CAG') // FIXME: cyclic dependencies CAG => PATH2 => CAG
|
|
||||||
let sides = []
|
|
||||||
let numpoints = this.points.length
|
|
||||||
let startindex = 0
|
|
||||||
if (this.closed && (numpoints > 2)) startindex = -1
|
|
||||||
let prevvertex
|
|
||||||
for (let i = startindex; i < numpoints; i++) {
|
|
||||||
let pointindex = i
|
|
||||||
if (pointindex < 0) pointindex = numpoints - 1
|
|
||||||
let point = this.points[pointindex]
|
|
||||||
let vertex = new Vertex(point)
|
|
||||||
if (i > startindex) {
|
|
||||||
let side = new Side(prevvertex, vertex)
|
|
||||||
sides.push(side)
|
|
||||||
}
|
|
||||||
prevvertex = vertex
|
|
||||||
}
|
|
||||||
let shellcag = CAG.fromSides(sides)
|
|
||||||
let expanded = shellcag.expandedShell(pathradius, resolution)
|
|
||||||
return expanded
|
|
||||||
},
|
|
||||||
|
|
||||||
innerToCAG: function() {
|
|
||||||
const CAG = require('../CAG') // FIXME: cyclic dependencies CAG => PATH2 => CAG
|
|
||||||
if (!this.closed) throw new Error("The path should be closed!");
|
|
||||||
return CAG.fromPoints(this.points);
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let newpoints = this.points.map(function (point) {
|
|
||||||
return point.multiply4x4(matrix4x4)
|
|
||||||
})
|
|
||||||
return new Path2D(newpoints, this.closed)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a Bezier curve to the end of the path, using the control points to transition the curve through start and end points.
|
|
||||||
* <br>
|
|
||||||
* The Bézier curve starts at the last point in the path,
|
|
||||||
* and ends at the last given control point. Other control points are intermediate control points.
|
|
||||||
* <br>
|
|
||||||
* The first control point may be null to ensure a smooth transition occurs. In this case,
|
|
||||||
* the second to last control point of the path is mirrored into the control points of the Bezier curve.
|
|
||||||
* In other words, the trailing gradient of the path matches the new gradient of the curve.
|
|
||||||
* @param {Vector2D[]} controlpoints - list of control points
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of sides per 360 rotation
|
|
||||||
* @returns {Path2D} new Path2D object (not closed)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let p5 = new CSG.Path2D([[10,-20]],false);
|
|
||||||
* p5 = p5.appendBezier([[10,-10],[25,-10],[25,-20]]);
|
|
||||||
* p5 = p5.appendBezier([[25,-30],[40,-30],[40,-20]]);
|
|
||||||
*/
|
|
||||||
appendBezier: function (controlpoints, options) {
|
|
||||||
if (arguments.length < 2) {
|
|
||||||
options = {}
|
|
||||||
}
|
|
||||||
if (this.closed) {
|
|
||||||
throw new Error('Path must not be closed')
|
|
||||||
}
|
|
||||||
if (!(controlpoints instanceof Array)) {
|
|
||||||
throw new Error('appendBezier: should pass an array of control points')
|
|
||||||
}
|
|
||||||
if (controlpoints.length < 1) {
|
|
||||||
throw new Error('appendBezier: need at least 1 control point')
|
|
||||||
}
|
|
||||||
if (this.points.length < 1) {
|
|
||||||
throw new Error('appendBezier: path must already contain a point (the endpoint of the path is used as the starting point for the bezier curve)')
|
|
||||||
}
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
|
||||||
if (resolution < 4) resolution = 4
|
|
||||||
let factorials = []
|
|
||||||
let controlpointsParsed = []
|
|
||||||
controlpointsParsed.push(this.points[this.points.length - 1]) // start at the previous end point
|
|
||||||
for (let i = 0; i < controlpoints.length; ++i) {
|
|
||||||
let p = controlpoints[i]
|
|
||||||
if (p === null) {
|
|
||||||
// we can pass null as the first control point. In that case a smooth gradient is ensured:
|
|
||||||
if (i !== 0) {
|
|
||||||
throw new Error('appendBezier: null can only be passed as the first control point')
|
|
||||||
}
|
|
||||||
if (controlpoints.length < 2) {
|
|
||||||
throw new Error('appendBezier: null can only be passed if there is at least one more control point')
|
|
||||||
}
|
|
||||||
let lastBezierControlPoint
|
|
||||||
if ('lastBezierControlPoint' in this) {
|
|
||||||
lastBezierControlPoint = this.lastBezierControlPoint
|
|
||||||
} else {
|
|
||||||
if (this.points.length < 2) {
|
|
||||||
throw new Error('appendBezier: null is passed as a control point but this requires a previous bezier curve or at least two points in the existing path')
|
|
||||||
}
|
|
||||||
lastBezierControlPoint = this.points[this.points.length - 2]
|
|
||||||
}
|
|
||||||
// mirror the last bezier control point:
|
|
||||||
p = this.points[this.points.length - 1].times(2).minus(lastBezierControlPoint)
|
|
||||||
} else {
|
|
||||||
p = new Vector2D(p) // cast to Vector2D
|
|
||||||
}
|
|
||||||
controlpointsParsed.push(p)
|
|
||||||
}
|
|
||||||
let bezierOrder = controlpointsParsed.length - 1
|
|
||||||
let fact = 1
|
|
||||||
for (let i = 0; i <= bezierOrder; ++i) {
|
|
||||||
if (i > 0) fact *= i
|
|
||||||
factorials.push(fact)
|
|
||||||
}
|
|
||||||
let binomials = []
|
|
||||||
for (let i = 0; i <= bezierOrder; ++i) {
|
|
||||||
let binomial = factorials[bezierOrder] / (factorials[i] * factorials[bezierOrder - i])
|
|
||||||
binomials.push(binomial)
|
|
||||||
}
|
|
||||||
let getPointForT = function (t) {
|
|
||||||
let t_k = 1 // = pow(t,k)
|
|
||||||
let one_minus_t_n_minus_k = Math.pow(1 - t, bezierOrder) // = pow( 1-t, bezierOrder - k)
|
|
||||||
let inv_1_minus_t = (t !== 1) ? (1 / (1 - t)) : 1
|
|
||||||
let point = new Vector2D(0, 0)
|
|
||||||
for (let k = 0; k <= bezierOrder; ++k) {
|
|
||||||
if (k === bezierOrder) one_minus_t_n_minus_k = 1
|
|
||||||
let bernstein_coefficient = binomials[k] * t_k * one_minus_t_n_minus_k
|
|
||||||
point = point.plus(controlpointsParsed[k].times(bernstein_coefficient))
|
|
||||||
t_k *= t
|
|
||||||
one_minus_t_n_minus_k *= inv_1_minus_t
|
|
||||||
}
|
|
||||||
return point
|
|
||||||
}
|
|
||||||
let newpoints = []
|
|
||||||
let newpoints_t = []
|
|
||||||
let numsteps = bezierOrder + 1
|
|
||||||
for (let i = 0; i < numsteps; ++i) {
|
|
||||||
let t = i / (numsteps - 1)
|
|
||||||
let point = getPointForT(t)
|
|
||||||
newpoints.push(point)
|
|
||||||
newpoints_t.push(t)
|
|
||||||
}
|
|
||||||
// subdivide each segment until the angle at each vertex becomes small enough:
|
|
||||||
let subdivideBase = 1
|
|
||||||
let maxangle = Math.PI * 2 / resolution // segments may have differ no more in angle than this
|
|
||||||
let maxsinangle = Math.sin(maxangle)
|
|
||||||
while (subdivideBase < newpoints.length - 1) {
|
|
||||||
let dir1 = newpoints[subdivideBase].minus(newpoints[subdivideBase - 1]).unit()
|
|
||||||
let dir2 = newpoints[subdivideBase + 1].minus(newpoints[subdivideBase]).unit()
|
|
||||||
let sinangle = dir1.cross(dir2) // this is the sine of the angle
|
|
||||||
if (Math.abs(sinangle) > maxsinangle) {
|
|
||||||
// angle is too big, we need to subdivide
|
|
||||||
let t0 = newpoints_t[subdivideBase - 1]
|
|
||||||
let t1 = newpoints_t[subdivideBase + 1]
|
|
||||||
let t0_new = t0 + (t1 - t0) * 1 / 3
|
|
||||||
let t1_new = t0 + (t1 - t0) * 2 / 3
|
|
||||||
let point0_new = getPointForT(t0_new)
|
|
||||||
let point1_new = getPointForT(t1_new)
|
|
||||||
// remove the point at subdivideBase and replace with 2 new points:
|
|
||||||
newpoints.splice(subdivideBase, 1, point0_new, point1_new)
|
|
||||||
newpoints_t.splice(subdivideBase, 1, t0_new, t1_new)
|
|
||||||
// re - evaluate the angles, starting at the previous junction since it has changed:
|
|
||||||
subdivideBase--
|
|
||||||
if (subdivideBase < 1) subdivideBase = 1
|
|
||||||
} else {
|
|
||||||
++subdivideBase
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// append to the previous points, but skip the first new point because it is identical to the last point:
|
|
||||||
newpoints = this.points.concat(newpoints.slice(1))
|
|
||||||
let result = new Path2D(newpoints)
|
|
||||||
result.lastBezierControlPoint = controlpointsParsed[controlpointsParsed.length - 2]
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append an arc to the end of the path.
|
|
||||||
* This implementation follows the SVG arc specs. For the details see
|
|
||||||
* http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
|
|
||||||
* @param {Vector2D} endpoint - end point of arc
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Number} [options.radius=0] - radius of arc (X and Y), see also xradius and yradius
|
|
||||||
* @param {Number} [options.xradius=0] - X radius of arc, see also radius
|
|
||||||
* @param {Number} [options.yradius=0] - Y radius of arc, see also radius
|
|
||||||
* @param {Number} [options.xaxisrotation=0] - rotation (in degrees) of the X axis of the arc with respect to the X axis of the coordinate system
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of sides per 360 rotation
|
|
||||||
* @param {Boolean} [options.clockwise=false] - draw an arc clockwise with respect to the center point
|
|
||||||
* @param {Boolean} [options.large=false] - draw an arc longer than 180 degrees
|
|
||||||
* @returns {Path2D} new Path2D object (not closed)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let p1 = new CSG.Path2D([[27.5,-22.96875]],false);
|
|
||||||
* p1 = p1.appendPoint([27.5,-3.28125]);
|
|
||||||
* p1 = p1.appendArc([12.5,-22.96875],{xradius: 15,yradius: -19.6875,xaxisrotation: 0,clockwise: false,large: false});
|
|
||||||
* p1 = p1.close();
|
|
||||||
*/
|
|
||||||
appendArc: function (endpoint, options) {
|
|
||||||
let decimals = 100000
|
|
||||||
if (arguments.length < 2) {
|
|
||||||
options = {}
|
|
||||||
}
|
|
||||||
if (this.closed) {
|
|
||||||
throw new Error('Path must not be closed')
|
|
||||||
}
|
|
||||||
if (this.points.length < 1) {
|
|
||||||
throw new Error('appendArc: path must already contain a point (the endpoint of the path is used as the starting point for the arc)')
|
|
||||||
}
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
|
||||||
if (resolution < 4) resolution = 4
|
|
||||||
let xradius, yradius
|
|
||||||
if (('xradius' in options) || ('yradius' in options)) {
|
|
||||||
if ('radius' in options) {
|
|
||||||
throw new Error('Should either give an xradius and yradius parameter, or a radius parameter')
|
|
||||||
}
|
|
||||||
xradius = parseOptionAsFloat(options, 'xradius', 0)
|
|
||||||
yradius = parseOptionAsFloat(options, 'yradius', 0)
|
|
||||||
} else {
|
|
||||||
xradius = parseOptionAsFloat(options, 'radius', 0)
|
|
||||||
yradius = xradius
|
|
||||||
}
|
|
||||||
let xaxisrotation = parseOptionAsFloat(options, 'xaxisrotation', 0)
|
|
||||||
let clockwise = parseOptionAsBool(options, 'clockwise', false)
|
|
||||||
let largearc = parseOptionAsBool(options, 'large', false)
|
|
||||||
let startpoint = this.points[this.points.length - 1]
|
|
||||||
endpoint = new Vector2D(endpoint)
|
|
||||||
// round to precision in order to have determinate calculations
|
|
||||||
xradius = Math.round(xradius * decimals) / decimals
|
|
||||||
yradius = Math.round(yradius * decimals) / decimals
|
|
||||||
endpoint = new Vector2D(Math.round(endpoint.x * decimals) / decimals, Math.round(endpoint.y * decimals) / decimals)
|
|
||||||
|
|
||||||
let sweepFlag = !clockwise
|
|
||||||
let newpoints = []
|
|
||||||
if ((xradius === 0) || (yradius === 0)) {
|
|
||||||
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes:
|
|
||||||
// If rx = 0 or ry = 0, then treat this as a straight line from (x1, y1) to (x2, y2) and stop
|
|
||||||
newpoints.push(endpoint)
|
|
||||||
} else {
|
|
||||||
xradius = Math.abs(xradius)
|
|
||||||
yradius = Math.abs(yradius)
|
|
||||||
|
|
||||||
// see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes :
|
|
||||||
let phi = xaxisrotation * Math.PI / 180.0
|
|
||||||
let cosphi = Math.cos(phi)
|
|
||||||
let sinphi = Math.sin(phi)
|
|
||||||
let minushalfdistance = startpoint.minus(endpoint).times(0.5)
|
|
||||||
// F.6.5.1:
|
|
||||||
// round to precision in order to have determinate calculations
|
|
||||||
let x = Math.round((cosphi * minushalfdistance.x + sinphi * minushalfdistance.y) * decimals) / decimals
|
|
||||||
let y = Math.round((-sinphi * minushalfdistance.x + cosphi * minushalfdistance.y) * decimals) / decimals
|
|
||||||
let startTranslated = new Vector2D(x, y)
|
|
||||||
// F.6.6.2:
|
|
||||||
let biglambda = (startTranslated.x * startTranslated.x) / (xradius * xradius) + (startTranslated.y * startTranslated.y) / (yradius * yradius)
|
|
||||||
if (biglambda > 1.0) {
|
|
||||||
// F.6.6.3:
|
|
||||||
let sqrtbiglambda = Math.sqrt(biglambda)
|
|
||||||
xradius *= sqrtbiglambda
|
|
||||||
yradius *= sqrtbiglambda
|
|
||||||
// round to precision in order to have determinate calculations
|
|
||||||
xradius = Math.round(xradius * decimals) / decimals
|
|
||||||
yradius = Math.round(yradius * decimals) / decimals
|
|
||||||
}
|
|
||||||
// F.6.5.2:
|
|
||||||
let multiplier1 = Math.sqrt((xradius * xradius * yradius * yradius - xradius * xradius * startTranslated.y * startTranslated.y - yradius * yradius * startTranslated.x * startTranslated.x) / (xradius * xradius * startTranslated.y * startTranslated.y + yradius * yradius * startTranslated.x * startTranslated.x))
|
|
||||||
if (sweepFlag === largearc) multiplier1 = -multiplier1
|
|
||||||
let centerTranslated = new Vector2D(xradius * startTranslated.y / yradius, -yradius * startTranslated.x / xradius).times(multiplier1)
|
|
||||||
// F.6.5.3:
|
|
||||||
let center = new Vector2D(cosphi * centerTranslated.x - sinphi * centerTranslated.y, sinphi * centerTranslated.x + cosphi * centerTranslated.y).plus((startpoint.plus(endpoint)).times(0.5))
|
|
||||||
// F.6.5.5:
|
|
||||||
let vec1 = new Vector2D((startTranslated.x - centerTranslated.x) / xradius, (startTranslated.y - centerTranslated.y) / yradius)
|
|
||||||
let vec2 = new Vector2D((-startTranslated.x - centerTranslated.x) / xradius, (-startTranslated.y - centerTranslated.y) / yradius)
|
|
||||||
let theta1 = vec1.angleRadians()
|
|
||||||
let theta2 = vec2.angleRadians()
|
|
||||||
let deltatheta = theta2 - theta1
|
|
||||||
deltatheta = deltatheta % (2 * Math.PI)
|
|
||||||
if ((!sweepFlag) && (deltatheta > 0)) {
|
|
||||||
deltatheta -= 2 * Math.PI
|
|
||||||
} else if ((sweepFlag) && (deltatheta < 0)) {
|
|
||||||
deltatheta += 2 * Math.PI
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok, we have the center point and angle range (from theta1, deltatheta radians) so we can create the ellipse
|
|
||||||
let numsteps = Math.ceil(Math.abs(deltatheta) / (2 * Math.PI) * resolution) + 1
|
|
||||||
if (numsteps < 1) numsteps = 1
|
|
||||||
for (let step = 1; step <= numsteps; step++) {
|
|
||||||
let theta = theta1 + step / numsteps * deltatheta
|
|
||||||
let costheta = Math.cos(theta)
|
|
||||||
let sintheta = Math.sin(theta)
|
|
||||||
// F.6.3.1:
|
|
||||||
let point = new Vector2D(cosphi * xradius * costheta - sinphi * yradius * sintheta, sinphi * xradius * costheta + cosphi * yradius * sintheta).plus(center)
|
|
||||||
newpoints.push(point)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newpoints = this.points.concat(newpoints)
|
|
||||||
let result = new Path2D(newpoints)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Path2D
|
|
140
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Plane.js
generated
vendored
140
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Plane.js
generated
vendored
@ -1,140 +0,0 @@
|
|||||||
const Vector3D = require('./Vector3')
|
|
||||||
const Line3D = require('./Line3')
|
|
||||||
const {EPS, getTag} = require('../constants')
|
|
||||||
|
|
||||||
// # class Plane
|
|
||||||
// Represents a plane in 3D space.
|
|
||||||
const Plane = function (normal, w) {
|
|
||||||
this.normal = normal
|
|
||||||
this.w = w
|
|
||||||
}
|
|
||||||
|
|
||||||
// create from an untyped object with identical property names:
|
|
||||||
Plane.fromObject = function (obj) {
|
|
||||||
let normal = new Vector3D(obj.normal)
|
|
||||||
let w = parseFloat(obj.w)
|
|
||||||
return new Plane(normal, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
Plane.fromVector3Ds = function (a, b, c) {
|
|
||||||
let n = b.minus(a).cross(c.minus(a)).unit()
|
|
||||||
return new Plane(n, n.dot(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
// like fromVector3Ds, but allow the vectors to be on one point or one line
|
|
||||||
// in such a case a random plane through the given points is constructed
|
|
||||||
Plane.anyPlaneFromVector3Ds = function (a, b, c) {
|
|
||||||
let v1 = b.minus(a)
|
|
||||||
let v2 = c.minus(a)
|
|
||||||
if (v1.length() < EPS) {
|
|
||||||
v1 = v2.randomNonParallelVector()
|
|
||||||
}
|
|
||||||
if (v2.length() < EPS) {
|
|
||||||
v2 = v1.randomNonParallelVector()
|
|
||||||
}
|
|
||||||
let normal = v1.cross(v2)
|
|
||||||
if (normal.length() < EPS) {
|
|
||||||
// this would mean that v1 == v2.negated()
|
|
||||||
v2 = v1.randomNonParallelVector()
|
|
||||||
normal = v1.cross(v2)
|
|
||||||
}
|
|
||||||
normal = normal.unit()
|
|
||||||
return new Plane(normal, normal.dot(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
Plane.fromPoints = function (a, b, c) {
|
|
||||||
a = new Vector3D(a)
|
|
||||||
b = new Vector3D(b)
|
|
||||||
c = new Vector3D(c)
|
|
||||||
return Plane.fromVector3Ds(a, b, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
Plane.fromNormalAndPoint = function (normal, point) {
|
|
||||||
normal = new Vector3D(normal)
|
|
||||||
point = new Vector3D(point)
|
|
||||||
normal = normal.unit()
|
|
||||||
let w = point.dot(normal)
|
|
||||||
return new Plane(normal, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
Plane.prototype = {
|
|
||||||
flipped: function () {
|
|
||||||
return new Plane(this.normal.negated(), -this.w)
|
|
||||||
},
|
|
||||||
|
|
||||||
getTag: function () {
|
|
||||||
let result = this.tag
|
|
||||||
if (!result) {
|
|
||||||
result = getTag()
|
|
||||||
this.tag = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
equals: function (n) {
|
|
||||||
return this.normal.equals(n.normal) && this.w === n.w
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let ismirror = matrix4x4.isMirroring()
|
|
||||||
// get two vectors in the plane:
|
|
||||||
let r = this.normal.randomNonParallelVector()
|
|
||||||
let u = this.normal.cross(r)
|
|
||||||
let v = this.normal.cross(u)
|
|
||||||
// get 3 points in the plane:
|
|
||||||
let point1 = this.normal.times(this.w)
|
|
||||||
let point2 = point1.plus(u)
|
|
||||||
let point3 = point1.plus(v)
|
|
||||||
// transform the points:
|
|
||||||
point1 = point1.multiply4x4(matrix4x4)
|
|
||||||
point2 = point2.multiply4x4(matrix4x4)
|
|
||||||
point3 = point3.multiply4x4(matrix4x4)
|
|
||||||
// and create a new plane from the transformed points:
|
|
||||||
let newplane = Plane.fromVector3Ds(point1, point2, point3)
|
|
||||||
if (ismirror) {
|
|
||||||
// the transform is mirroring
|
|
||||||
// We should mirror the plane:
|
|
||||||
newplane = newplane.flipped()
|
|
||||||
}
|
|
||||||
return newplane
|
|
||||||
},
|
|
||||||
|
|
||||||
// robust splitting of a line by a plane
|
|
||||||
// will work even if the line is parallel to the plane
|
|
||||||
splitLineBetweenPoints: function (p1, p2) {
|
|
||||||
let direction = p2.minus(p1)
|
|
||||||
let labda = (this.w - this.normal.dot(p1)) / this.normal.dot(direction)
|
|
||||||
if (isNaN(labda)) labda = 0
|
|
||||||
if (labda > 1) labda = 1
|
|
||||||
if (labda < 0) labda = 0
|
|
||||||
let result = p1.plus(direction.times(labda))
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// returns Vector3D
|
|
||||||
intersectWithLine: function (line3d) {
|
|
||||||
return line3d.intersectWithPlane(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
// intersection of two planes
|
|
||||||
intersectWithPlane: function (plane) {
|
|
||||||
return Line3D.fromPlanes(this, plane)
|
|
||||||
},
|
|
||||||
|
|
||||||
signedDistanceToPoint: function (point) {
|
|
||||||
let t = this.normal.dot(point) - this.w
|
|
||||||
return t
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () {
|
|
||||||
return '[normal: ' + this.normal.toString() + ', w: ' + this.w + ']'
|
|
||||||
},
|
|
||||||
|
|
||||||
mirrorPoint: function (point3d) {
|
|
||||||
let distance = this.signedDistanceToPoint(point3d)
|
|
||||||
let mirrored = point3d.minus(this.normal.times(distance * 2.0))
|
|
||||||
return mirrored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Plane
|
|
@ -1,19 +0,0 @@
|
|||||||
const CAG = require('../CAG')
|
|
||||||
|
|
||||||
/*
|
|
||||||
2D polygons are now supported through the CAG class.
|
|
||||||
With many improvements (see documentation):
|
|
||||||
- shapes do no longer have to be convex
|
|
||||||
- union/intersect/subtract is supported
|
|
||||||
- expand / contract are supported
|
|
||||||
|
|
||||||
But we'll keep CSG.Polygon2D as a stub for backwards compatibility
|
|
||||||
*/
|
|
||||||
function Polygon2D (points) {
|
|
||||||
const cag = CAG.fromPoints(points)
|
|
||||||
this.sides = cag.sides
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon2D.prototype = CAG.prototype
|
|
||||||
|
|
||||||
module.exports = Polygon2D
|
|
575
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Polygon3.js
generated
vendored
575
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Polygon3.js
generated
vendored
@ -1,575 +0,0 @@
|
|||||||
const Vector3D = require('./Vector3')
|
|
||||||
const Vertex = require('./Vertex3')
|
|
||||||
const Matrix4x4 = require('./Matrix4')
|
|
||||||
const {_CSGDEBUG, EPS, getTag, areaEPS} = require('../constants')
|
|
||||||
const {fnSortByIndex} = require('../utils')
|
|
||||||
|
|
||||||
/** Class Polygon
|
|
||||||
* Represents a convex polygon. The vertices used to initialize a polygon must
|
|
||||||
* be coplanar and form a convex loop. They do not have to be `Vertex`
|
|
||||||
* instances but they must behave similarly (duck typing can be used for
|
|
||||||
* customization).
|
|
||||||
* <br>
|
|
||||||
* Each convex polygon has a `shared` property, which is shared between all
|
|
||||||
* polygons that are clones of each other or were split from the same polygon.
|
|
||||||
* This can be used to define per-polygon properties (such as surface color).
|
|
||||||
* <br>
|
|
||||||
* The plane of the polygon is calculated from the vertex coordinates if not provided.
|
|
||||||
* The plane can alternatively be passed as the third argument to avoid calculations.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Vertex[]} vertices - list of vertices
|
|
||||||
* @param {Polygon.Shared} [shared=defaultShared] - shared property to apply
|
|
||||||
* @param {Plane} [plane] - plane of the polygon
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const vertices = [
|
|
||||||
* new CSG.Vertex(new CSG.Vector3D([0, 0, 0])),
|
|
||||||
* new CSG.Vertex(new CSG.Vector3D([0, 10, 0])),
|
|
||||||
* new CSG.Vertex(new CSG.Vector3D([0, 10, 10]))
|
|
||||||
* ]
|
|
||||||
* let observed = new Polygon(vertices)
|
|
||||||
*/
|
|
||||||
let Polygon = function (vertices, shared, plane) {
|
|
||||||
this.vertices = vertices
|
|
||||||
if (!shared) shared = Polygon.defaultShared
|
|
||||||
this.shared = shared
|
|
||||||
// let numvertices = vertices.length;
|
|
||||||
|
|
||||||
if (arguments.length >= 3) {
|
|
||||||
this.plane = plane
|
|
||||||
} else {
|
|
||||||
const Plane = require('./Plane') // FIXME: circular dependencies
|
|
||||||
this.plane = Plane.fromVector3Ds(vertices[0].pos, vertices[1].pos, vertices[2].pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_CSGDEBUG) {
|
|
||||||
if (!this.checkIfConvex()) {
|
|
||||||
throw new Error('Not convex!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create from an untyped object with identical property names:
|
|
||||||
Polygon.fromObject = function (obj) {
|
|
||||||
const Plane = require('./Plane') // FIXME: circular dependencies
|
|
||||||
let vertices = obj.vertices.map(function (v) {
|
|
||||||
return Vertex.fromObject(v)
|
|
||||||
})
|
|
||||||
let shared = Polygon.Shared.fromObject(obj.shared)
|
|
||||||
let plane = Plane.fromObject(obj.plane)
|
|
||||||
return new Polygon(vertices, shared, plane)
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon.prototype = {
|
|
||||||
/** Check whether the polygon is convex. (it should be, otherwise we will get unexpected results)
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
checkIfConvex: function () {
|
|
||||||
return Polygon.verticesConvex(this.vertices, this.plane.normal)
|
|
||||||
},
|
|
||||||
|
|
||||||
// FIXME what? why does this return this, and not a new polygon?
|
|
||||||
// FIXME is this used?
|
|
||||||
setColor: function (args) {
|
|
||||||
let newshared = Polygon.Shared.fromColor.apply(this, arguments)
|
|
||||||
this.shared = newshared
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
getSignedVolume: function () {
|
|
||||||
let signedVolume = 0
|
|
||||||
for (let i = 0; i < this.vertices.length - 2; i++) {
|
|
||||||
signedVolume += this.vertices[0].pos.dot(this.vertices[i + 1].pos
|
|
||||||
.cross(this.vertices[i + 2].pos))
|
|
||||||
}
|
|
||||||
signedVolume /= 6
|
|
||||||
return signedVolume
|
|
||||||
},
|
|
||||||
|
|
||||||
// Note: could calculate vectors only once to speed up
|
|
||||||
getArea: function () {
|
|
||||||
let polygonArea = 0
|
|
||||||
for (let i = 0; i < this.vertices.length - 2; i++) {
|
|
||||||
polygonArea += this.vertices[i + 1].pos.minus(this.vertices[0].pos)
|
|
||||||
.cross(this.vertices[i + 2].pos.minus(this.vertices[i + 1].pos)).length()
|
|
||||||
}
|
|
||||||
polygonArea /= 2
|
|
||||||
return polygonArea
|
|
||||||
},
|
|
||||||
|
|
||||||
// accepts array of features to calculate
|
|
||||||
// returns array of results
|
|
||||||
getTetraFeatures: function (features) {
|
|
||||||
let result = []
|
|
||||||
features.forEach(function (feature) {
|
|
||||||
if (feature === 'volume') {
|
|
||||||
result.push(this.getSignedVolume())
|
|
||||||
} else if (feature === 'area') {
|
|
||||||
result.push(this.getArea())
|
|
||||||
}
|
|
||||||
}, this)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Extrude a polygon into the direction offsetvector
|
|
||||||
// Returns a CSG object
|
|
||||||
extrude: function (offsetvector) {
|
|
||||||
const CSG = require('../CSG') // because of circular dependencies
|
|
||||||
|
|
||||||
let newpolygons = []
|
|
||||||
|
|
||||||
let polygon1 = this
|
|
||||||
let direction = polygon1.plane.normal.dot(offsetvector)
|
|
||||||
if (direction > 0) {
|
|
||||||
polygon1 = polygon1.flipped()
|
|
||||||
}
|
|
||||||
newpolygons.push(polygon1)
|
|
||||||
let polygon2 = polygon1.translate(offsetvector)
|
|
||||||
let numvertices = this.vertices.length
|
|
||||||
for (let i = 0; i < numvertices; i++) {
|
|
||||||
let sidefacepoints = []
|
|
||||||
let nexti = (i < (numvertices - 1)) ? i + 1 : 0
|
|
||||||
sidefacepoints.push(polygon1.vertices[i].pos)
|
|
||||||
sidefacepoints.push(polygon2.vertices[i].pos)
|
|
||||||
sidefacepoints.push(polygon2.vertices[nexti].pos)
|
|
||||||
sidefacepoints.push(polygon1.vertices[nexti].pos)
|
|
||||||
let sidefacepolygon = Polygon.createFromPoints(sidefacepoints, this.shared)
|
|
||||||
newpolygons.push(sidefacepolygon)
|
|
||||||
}
|
|
||||||
polygon2 = polygon2.flipped()
|
|
||||||
newpolygons.push(polygon2)
|
|
||||||
return CSG.fromPolygons(newpolygons)
|
|
||||||
},
|
|
||||||
|
|
||||||
translate: function (offset) {
|
|
||||||
return this.transform(Matrix4x4.translation(offset))
|
|
||||||
},
|
|
||||||
|
|
||||||
// returns an array with a Vector3D (center point) and a radius
|
|
||||||
boundingSphere: function () {
|
|
||||||
if (!this.cachedBoundingSphere) {
|
|
||||||
let box = this.boundingBox()
|
|
||||||
let middle = box[0].plus(box[1]).times(0.5)
|
|
||||||
let radius3 = box[1].minus(middle)
|
|
||||||
let radius = radius3.length()
|
|
||||||
this.cachedBoundingSphere = [middle, radius]
|
|
||||||
}
|
|
||||||
return this.cachedBoundingSphere
|
|
||||||
},
|
|
||||||
|
|
||||||
// returns an array of two Vector3Ds (minimum coordinates and maximum coordinates)
|
|
||||||
boundingBox: function () {
|
|
||||||
if (!this.cachedBoundingBox) {
|
|
||||||
let minpoint, maxpoint
|
|
||||||
let vertices = this.vertices
|
|
||||||
let numvertices = vertices.length
|
|
||||||
if (numvertices === 0) {
|
|
||||||
minpoint = new Vector3D(0, 0, 0)
|
|
||||||
} else {
|
|
||||||
minpoint = vertices[0].pos
|
|
||||||
}
|
|
||||||
maxpoint = minpoint
|
|
||||||
for (let i = 1; i < numvertices; i++) {
|
|
||||||
let point = vertices[i].pos
|
|
||||||
minpoint = minpoint.min(point)
|
|
||||||
maxpoint = maxpoint.max(point)
|
|
||||||
}
|
|
||||||
this.cachedBoundingBox = [minpoint, maxpoint]
|
|
||||||
}
|
|
||||||
return this.cachedBoundingBox
|
|
||||||
},
|
|
||||||
|
|
||||||
flipped: function () {
|
|
||||||
let newvertices = this.vertices.map(function (v) {
|
|
||||||
return v.flipped()
|
|
||||||
})
|
|
||||||
newvertices.reverse()
|
|
||||||
let newplane = this.plane.flipped()
|
|
||||||
return new Polygon(newvertices, this.shared, newplane)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Affine transformation of polygon. Returns a new Polygon
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
let newvertices = this.vertices.map(function (v) {
|
|
||||||
return v.transform(matrix4x4)
|
|
||||||
})
|
|
||||||
let newplane = this.plane.transform(matrix4x4)
|
|
||||||
if (matrix4x4.isMirroring()) {
|
|
||||||
// need to reverse the vertex order
|
|
||||||
// in order to preserve the inside/outside orientation:
|
|
||||||
newvertices.reverse()
|
|
||||||
}
|
|
||||||
return new Polygon(newvertices, this.shared, newplane)
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () {
|
|
||||||
let result = 'Polygon plane: ' + this.plane.toString() + '\n'
|
|
||||||
this.vertices.map(function (vertex) {
|
|
||||||
result += ' ' + vertex.toString() + '\n'
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// project the 3D polygon onto a plane
|
|
||||||
projectToOrthoNormalBasis: function (orthobasis) {
|
|
||||||
const CAG = require('../CAG')
|
|
||||||
const {fromPointsNoCheck} = require('../CAGFactories') // circular dependencies
|
|
||||||
let points2d = this.vertices.map(function (vertex) {
|
|
||||||
return orthobasis.to2D(vertex.pos)
|
|
||||||
})
|
|
||||||
|
|
||||||
let result = fromPointsNoCheck(points2d)
|
|
||||||
let area = result.area()
|
|
||||||
if (Math.abs(area) < areaEPS) {
|
|
||||||
// the polygon was perpendicular to the orthnormal plane. The resulting 2D polygon would be degenerate
|
|
||||||
// return an empty area instead:
|
|
||||||
result = new CAG()
|
|
||||||
} else if (area < 0) {
|
|
||||||
result = result.flipped()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
//FIXME: WHY is this for 3D polygons and not for 2D shapes ?
|
|
||||||
/**
|
|
||||||
* Creates solid from slices (Polygon) by generating walls
|
|
||||||
* @param {Object} options Solid generating options
|
|
||||||
* - numslices {Number} Number of slices to be generated
|
|
||||||
* - callback(t, slice) {Function} Callback function generating slices.
|
|
||||||
* arguments: t = [0..1], slice = [0..numslices - 1]
|
|
||||||
* return: Polygon or null to skip
|
|
||||||
* - loop {Boolean} no flats, only walls, it's used to generate solids like a tor
|
|
||||||
*/
|
|
||||||
solidFromSlices: function (options) {
|
|
||||||
const CSG = require('../CSG')
|
|
||||||
|
|
||||||
let polygons = [],
|
|
||||||
csg = null,
|
|
||||||
prev = null,
|
|
||||||
bottom = null,
|
|
||||||
top = null,
|
|
||||||
numSlices = 2,
|
|
||||||
bLoop = false,
|
|
||||||
fnCallback,
|
|
||||||
flipped = null
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
bLoop = Boolean(options['loop'])
|
|
||||||
|
|
||||||
if (options.numslices) { numSlices = options.numslices }
|
|
||||||
|
|
||||||
if (options.callback) {
|
|
||||||
fnCallback = options.callback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fnCallback) {
|
|
||||||
let square = new Polygon.createFromPoints([
|
|
||||||
[0, 0, 0],
|
|
||||||
[1, 0, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 0]
|
|
||||||
])
|
|
||||||
fnCallback = function (t, slice) {
|
|
||||||
return t === 0 || t === 1 ? square.translate([0, 0, t]) : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0, iMax = numSlices - 1; i <= iMax; i++) {
|
|
||||||
csg = fnCallback.call(this, i / iMax, i)
|
|
||||||
if (csg) {
|
|
||||||
if (!(csg instanceof Polygon)) {
|
|
||||||
throw new Error('Polygon.solidFromSlices callback error: Polygon expected')
|
|
||||||
}
|
|
||||||
csg.checkIfConvex()
|
|
||||||
|
|
||||||
if (prev) { // generate walls
|
|
||||||
if (flipped === null) { // not generated yet
|
|
||||||
flipped = prev.plane.signedDistanceToPoint(csg.vertices[0].pos) < 0
|
|
||||||
}
|
|
||||||
this._addWalls(polygons, prev, csg, flipped)
|
|
||||||
} else { // the first - will be a bottom
|
|
||||||
bottom = csg
|
|
||||||
}
|
|
||||||
prev = csg
|
|
||||||
} // callback can return null to skip that slice
|
|
||||||
}
|
|
||||||
top = csg
|
|
||||||
|
|
||||||
if (bLoop) {
|
|
||||||
let bSameTopBottom = bottom.vertices.length === top.vertices.length &&
|
|
||||||
bottom.vertices.every(function (v, index) {
|
|
||||||
return v.pos.equals(top.vertices[index].pos)
|
|
||||||
})
|
|
||||||
// if top and bottom are not the same -
|
|
||||||
// generate walls between them
|
|
||||||
if (!bSameTopBottom) {
|
|
||||||
this._addWalls(polygons, top, bottom, flipped)
|
|
||||||
} // else - already generated
|
|
||||||
} else {
|
|
||||||
// save top and bottom
|
|
||||||
// TODO: flip if necessary
|
|
||||||
polygons.unshift(flipped ? bottom : bottom.flipped())
|
|
||||||
polygons.push(flipped ? top.flipped() : top)
|
|
||||||
}
|
|
||||||
return CSG.fromPolygons(polygons)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param walls Array of wall polygons
|
|
||||||
* @param bottom Bottom polygon
|
|
||||||
* @param top Top polygon
|
|
||||||
*/
|
|
||||||
_addWalls: function (walls, bottom, top, bFlipped) {
|
|
||||||
let bottomPoints = bottom.vertices.slice(0) // make a copy
|
|
||||||
let topPoints = top.vertices.slice(0) // make a copy
|
|
||||||
let color = top.shared || null
|
|
||||||
|
|
||||||
// check if bottom perimeter is closed
|
|
||||||
if (!bottomPoints[0].pos.equals(bottomPoints[bottomPoints.length - 1].pos)) {
|
|
||||||
bottomPoints.push(bottomPoints[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if top perimeter is closed
|
|
||||||
if (!topPoints[0].pos.equals(topPoints[topPoints.length - 1].pos)) {
|
|
||||||
topPoints.push(topPoints[0])
|
|
||||||
}
|
|
||||||
if (bFlipped) {
|
|
||||||
bottomPoints = bottomPoints.reverse()
|
|
||||||
topPoints = topPoints.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
let iTopLen = topPoints.length - 1
|
|
||||||
let iBotLen = bottomPoints.length - 1
|
|
||||||
let iExtra = iTopLen - iBotLen// how many extra triangles we need
|
|
||||||
let bMoreTops = iExtra > 0
|
|
||||||
let bMoreBottoms = iExtra < 0
|
|
||||||
|
|
||||||
let aMin = [] // indexes to start extra triangles (polygon with minimal square)
|
|
||||||
// init - we need exactly /iExtra/ small triangles
|
|
||||||
for (let i = Math.abs(iExtra); i > 0; i--) {
|
|
||||||
aMin.push({
|
|
||||||
len: Infinity,
|
|
||||||
index: -1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let len
|
|
||||||
if (bMoreBottoms) {
|
|
||||||
for (let i = 0; i < iBotLen; i++) {
|
|
||||||
len = bottomPoints[i].pos.distanceToSquared(bottomPoints[i + 1].pos)
|
|
||||||
// find the element to replace
|
|
||||||
for (let j = aMin.length - 1; j >= 0; j--) {
|
|
||||||
if (aMin[j].len > len) {
|
|
||||||
aMin[j].len = len
|
|
||||||
aMin.index = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} // for
|
|
||||||
}
|
|
||||||
} else if (bMoreTops) {
|
|
||||||
for (let i = 0; i < iTopLen; i++) {
|
|
||||||
len = topPoints[i].pos.distanceToSquared(topPoints[i + 1].pos)
|
|
||||||
// find the element to replace
|
|
||||||
for (let j = aMin.length - 1; j >= 0; j--) {
|
|
||||||
if (aMin[j].len > len) {
|
|
||||||
aMin[j].len = len
|
|
||||||
aMin.index = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} // for
|
|
||||||
}
|
|
||||||
} // if
|
|
||||||
// sort by index
|
|
||||||
aMin.sort(fnSortByIndex)
|
|
||||||
let getTriangle = function addWallsPutTriangle (pointA, pointB, pointC, color) {
|
|
||||||
return new Polygon([pointA, pointB, pointC], color)
|
|
||||||
// return bFlipped ? triangle.flipped() : triangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bpoint = bottomPoints[0]
|
|
||||||
let tpoint = topPoints[0]
|
|
||||||
let secondPoint
|
|
||||||
let nBotFacet
|
|
||||||
let nTopFacet // length of triangle facet side
|
|
||||||
for (let iB = 0, iT = 0, iMax = iTopLen + iBotLen; iB + iT < iMax;) {
|
|
||||||
if (aMin.length) {
|
|
||||||
if (bMoreTops && iT === aMin[0].index) { // one vertex is on the bottom, 2 - on the top
|
|
||||||
secondPoint = topPoints[++iT]
|
|
||||||
// console.log('<<< extra top: ' + secondPoint + ', ' + tpoint + ', bottom: ' + bpoint);
|
|
||||||
walls.push(getTriangle(
|
|
||||||
secondPoint, tpoint, bpoint, color
|
|
||||||
))
|
|
||||||
tpoint = secondPoint
|
|
||||||
aMin.shift()
|
|
||||||
continue
|
|
||||||
} else if (bMoreBottoms && iB === aMin[0].index) {
|
|
||||||
secondPoint = bottomPoints[++iB]
|
|
||||||
walls.push(getTriangle(
|
|
||||||
tpoint, bpoint, secondPoint, color
|
|
||||||
))
|
|
||||||
bpoint = secondPoint
|
|
||||||
aMin.shift()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// choose the shortest path
|
|
||||||
if (iB < iBotLen) { // one vertex is on the top, 2 - on the bottom
|
|
||||||
nBotFacet = tpoint.pos.distanceToSquared(bottomPoints[iB + 1].pos)
|
|
||||||
} else {
|
|
||||||
nBotFacet = Infinity
|
|
||||||
}
|
|
||||||
if (iT < iTopLen) { // one vertex is on the bottom, 2 - on the top
|
|
||||||
nTopFacet = bpoint.pos.distanceToSquared(topPoints[iT + 1].pos)
|
|
||||||
} else {
|
|
||||||
nTopFacet = Infinity
|
|
||||||
}
|
|
||||||
if (nBotFacet <= nTopFacet) {
|
|
||||||
secondPoint = bottomPoints[++iB]
|
|
||||||
walls.push(getTriangle(
|
|
||||||
tpoint, bpoint, secondPoint, color
|
|
||||||
))
|
|
||||||
bpoint = secondPoint
|
|
||||||
} else if (iT < iTopLen) { // nTopFacet < Infinity
|
|
||||||
secondPoint = topPoints[++iT]
|
|
||||||
// console.log('<<< top: ' + secondPoint + ', ' + tpoint + ', bottom: ' + bpoint);
|
|
||||||
walls.push(getTriangle(
|
|
||||||
secondPoint, tpoint, bpoint, color
|
|
||||||
))
|
|
||||||
tpoint = secondPoint
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return walls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon.verticesConvex = function (vertices, planenormal) {
|
|
||||||
let numvertices = vertices.length
|
|
||||||
if (numvertices > 2) {
|
|
||||||
let prevprevpos = vertices[numvertices - 2].pos
|
|
||||||
let prevpos = vertices[numvertices - 1].pos
|
|
||||||
for (let i = 0; i < numvertices; i++) {
|
|
||||||
let pos = vertices[i].pos
|
|
||||||
if (!Polygon.isConvexPoint(prevprevpos, prevpos, pos, planenormal)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
prevprevpos = prevpos
|
|
||||||
prevpos = pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a polygon from the given points.
|
|
||||||
*
|
|
||||||
* @param {Array[]} points - list of points
|
|
||||||
* @param {Polygon.Shared} [shared=defaultShared] - shared property to apply
|
|
||||||
* @param {Plane} [plane] - plane of the polygon
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const points = [
|
|
||||||
* [0, 0, 0],
|
|
||||||
* [0, 10, 0],
|
|
||||||
* [0, 10, 10]
|
|
||||||
* ]
|
|
||||||
* let observed = CSG.Polygon.createFromPoints(points)
|
|
||||||
*/
|
|
||||||
Polygon.createFromPoints = function (points, shared, plane) {
|
|
||||||
let vertices = []
|
|
||||||
points.map(function (p) {
|
|
||||||
let vec = new Vector3D(p)
|
|
||||||
let vertex = new Vertex(vec)
|
|
||||||
vertices.push(vertex)
|
|
||||||
})
|
|
||||||
let polygon
|
|
||||||
if (arguments.length < 3) {
|
|
||||||
polygon = new Polygon(vertices, shared)
|
|
||||||
} else {
|
|
||||||
polygon = new Polygon(vertices, shared, plane)
|
|
||||||
}
|
|
||||||
return polygon
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate whether three points form a convex corner
|
|
||||||
// prevpoint, point, nextpoint: the 3 coordinates (Vector3D instances)
|
|
||||||
// normal: the normal vector of the plane
|
|
||||||
Polygon.isConvexPoint = function (prevpoint, point, nextpoint, normal) {
|
|
||||||
let crossproduct = point.minus(prevpoint).cross(nextpoint.minus(point))
|
|
||||||
let crossdotnormal = crossproduct.dot(normal)
|
|
||||||
return (crossdotnormal >= 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon.isStrictlyConvexPoint = function (prevpoint, point, nextpoint, normal) {
|
|
||||||
let crossproduct = point.minus(prevpoint).cross(nextpoint.minus(point))
|
|
||||||
let crossdotnormal = crossproduct.dot(normal)
|
|
||||||
return (crossdotnormal >= EPS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Class Polygon.Shared
|
|
||||||
* Holds the shared properties for each polygon (Currently only color).
|
|
||||||
* @constructor
|
|
||||||
* @param {Array[]} color - array containing RGBA values, or null
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let shared = new CSG.Polygon.Shared([0, 0, 0, 1])
|
|
||||||
*/
|
|
||||||
Polygon.Shared = function (color) {
|
|
||||||
if (color !== null) {
|
|
||||||
if (color.length !== 4) {
|
|
||||||
throw new Error('Expecting 4 element array')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.color = color
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon.Shared.fromObject = function (obj) {
|
|
||||||
return new Polygon.Shared(obj.color)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create Polygon.Shared from color values.
|
|
||||||
* @param {number} r - value of RED component
|
|
||||||
* @param {number} g - value of GREEN component
|
|
||||||
* @param {number} b - value of BLUE component
|
|
||||||
* @param {number} [a] - value of ALPHA component
|
|
||||||
* @param {Array[]} [color] - OR array containing RGB values (optional Alpha)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let s1 = Polygon.Shared.fromColor(0,0,0)
|
|
||||||
* let s2 = Polygon.Shared.fromColor([0,0,0,1])
|
|
||||||
*/
|
|
||||||
Polygon.Shared.fromColor = function (args) {
|
|
||||||
let color
|
|
||||||
if (arguments.length === 1) {
|
|
||||||
color = arguments[0].slice() // make deep copy
|
|
||||||
} else {
|
|
||||||
color = []
|
|
||||||
for (let i = 0; i < arguments.length; i++) {
|
|
||||||
color.push(arguments[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (color.length === 3) {
|
|
||||||
color.push(1)
|
|
||||||
} else if (color.length !== 4) {
|
|
||||||
throw new Error('setColor expects either an array with 3 or 4 elements, or 3 or 4 parameters.')
|
|
||||||
}
|
|
||||||
return new Polygon.Shared(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon.Shared.prototype = {
|
|
||||||
getTag: function () {
|
|
||||||
let result = this.tag
|
|
||||||
if (!result) {
|
|
||||||
result = getTag()
|
|
||||||
this.tag = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
// get a string uniquely identifying this object
|
|
||||||
getHash: function () {
|
|
||||||
if (!this.color) return 'null'
|
|
||||||
return this.color.join('/')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Polygon.defaultShared = new Polygon.Shared(null)
|
|
||||||
|
|
||||||
module.exports = Polygon
|
|
102
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Side.js
generated
vendored
102
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Side.js
generated
vendored
@ -1,102 +0,0 @@
|
|||||||
const Vector2D = require('./Vector2')
|
|
||||||
const Vertex = require('./Vertex2')
|
|
||||||
const Vertex3 = require('./Vertex3')
|
|
||||||
const Polygon = require('./Polygon3')
|
|
||||||
const {getTag} = require('../constants')
|
|
||||||
|
|
||||||
const Side = function (vertex0, vertex1) {
|
|
||||||
if (!(vertex0 instanceof Vertex)) throw new Error('Assertion failed')
|
|
||||||
if (!(vertex1 instanceof Vertex)) throw new Error('Assertion failed')
|
|
||||||
this.vertex0 = vertex0
|
|
||||||
this.vertex1 = vertex1
|
|
||||||
}
|
|
||||||
|
|
||||||
Side.fromObject = function (obj) {
|
|
||||||
var vertex0 = Vertex.fromObject(obj.vertex0)
|
|
||||||
var vertex1 = Vertex.fromObject(obj.vertex1)
|
|
||||||
return new Side(vertex0, vertex1)
|
|
||||||
}
|
|
||||||
|
|
||||||
Side._fromFakePolygon = function (polygon) {
|
|
||||||
// this can happen based on union, seems to be residuals -
|
|
||||||
// return null and handle in caller
|
|
||||||
if (polygon.vertices.length < 4) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
var vert1Indices = []
|
|
||||||
var pts2d = polygon.vertices.filter(function (v, i) {
|
|
||||||
if (v.pos.z > 0) {
|
|
||||||
vert1Indices.push(i)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
.map(function (v) {
|
|
||||||
return new Vector2D(v.pos.x, v.pos.y)
|
|
||||||
})
|
|
||||||
if (pts2d.length !== 2) {
|
|
||||||
throw new Error('Assertion failed: _fromFakePolygon: not enough points found')
|
|
||||||
}
|
|
||||||
var d = vert1Indices[1] - vert1Indices[0]
|
|
||||||
if (d === 1 || d === 3) {
|
|
||||||
if (d === 1) {
|
|
||||||
pts2d.reverse()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Assertion failed: _fromFakePolygon: unknown index ordering')
|
|
||||||
}
|
|
||||||
var result = new Side(new Vertex(pts2d[0]), new Vertex(pts2d[1]))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Side.prototype = {
|
|
||||||
toString: function () {
|
|
||||||
return this.vertex0 + ' -> ' + this.vertex1
|
|
||||||
},
|
|
||||||
|
|
||||||
toPolygon3D: function (z0, z1) {
|
|
||||||
//console.log(this.vertex0.pos)
|
|
||||||
const vertices = [
|
|
||||||
new Vertex3(this.vertex0.pos.toVector3D(z0)),
|
|
||||||
new Vertex3(this.vertex1.pos.toVector3D(z0)),
|
|
||||||
new Vertex3(this.vertex1.pos.toVector3D(z1)),
|
|
||||||
new Vertex3(this.vertex0.pos.toVector3D(z1))
|
|
||||||
]
|
|
||||||
return new Polygon(vertices)
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
var newp1 = this.vertex0.pos.transform(matrix4x4)
|
|
||||||
var newp2 = this.vertex1.pos.transform(matrix4x4)
|
|
||||||
return new Side(new Vertex(newp1), new Vertex(newp2))
|
|
||||||
},
|
|
||||||
|
|
||||||
flipped: function () {
|
|
||||||
return new Side(this.vertex1, this.vertex0)
|
|
||||||
},
|
|
||||||
|
|
||||||
direction: function () {
|
|
||||||
return this.vertex1.pos.minus(this.vertex0.pos)
|
|
||||||
},
|
|
||||||
|
|
||||||
getTag: function () {
|
|
||||||
var result = this.tag
|
|
||||||
if (!result) {
|
|
||||||
result = getTag()
|
|
||||||
this.tag = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
lengthSquared: function () {
|
|
||||||
let x = this.vertex1.pos.x - this.vertex0.pos.x
|
|
||||||
let y = this.vertex1.pos.y - this.vertex0.pos.y
|
|
||||||
return x * x + y * y
|
|
||||||
},
|
|
||||||
|
|
||||||
length: function () {
|
|
||||||
return Math.sqrt(this.lengthSquared())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Side
|
|
196
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vector2.js
generated
vendored
196
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vector2.js
generated
vendored
@ -1,196 +0,0 @@
|
|||||||
const {IsFloat} = require('../utils')
|
|
||||||
|
|
||||||
/** Class Vector2D
|
|
||||||
* Represents a 2D vector with X, Y coordinates
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* new CSG.Vector2D(1, 2);
|
|
||||||
* new CSG.Vector3D([1, 2]);
|
|
||||||
* new CSG.Vector3D({ x: 1, y: 2});
|
|
||||||
*/
|
|
||||||
const Vector2D = function (x, y) {
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
this._x = parseFloat(x)
|
|
||||||
this._y = parseFloat(y)
|
|
||||||
} else {
|
|
||||||
var ok = true
|
|
||||||
if (arguments.length === 1) {
|
|
||||||
if (typeof (x) === 'object') {
|
|
||||||
if (x instanceof Vector2D) {
|
|
||||||
this._x = x._x
|
|
||||||
this._y = x._y
|
|
||||||
} else if (x instanceof Array) {
|
|
||||||
this._x = parseFloat(x[0])
|
|
||||||
this._y = parseFloat(x[1])
|
|
||||||
} else if (('x' in x) && ('y' in x)) {
|
|
||||||
this._x = parseFloat(x.x)
|
|
||||||
this._y = parseFloat(x.y)
|
|
||||||
} else ok = false
|
|
||||||
} else {
|
|
||||||
var v = parseFloat(x)
|
|
||||||
this._x = v
|
|
||||||
this._y = v
|
|
||||||
}
|
|
||||||
} else ok = false
|
|
||||||
if (ok) {
|
|
||||||
if ((!IsFloat(this._x)) || (!IsFloat(this._y))) ok = false
|
|
||||||
}
|
|
||||||
if (!ok) {
|
|
||||||
throw new Error('wrong arguments')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2D.fromAngle = function (radians) {
|
|
||||||
return Vector2D.fromAngleRadians(radians)
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2D.fromAngleDegrees = function (degrees) {
|
|
||||||
var radians = Math.PI * degrees / 180
|
|
||||||
return Vector2D.fromAngleRadians(radians)
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2D.fromAngleRadians = function (radians) {
|
|
||||||
return Vector2D.Create(Math.cos(radians), Math.sin(radians))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This does the same as new Vector2D(x,y) but it doesn't go through the constructor
|
|
||||||
// and the parameters are not validated. Is much faster.
|
|
||||||
Vector2D.Create = function (x, y) {
|
|
||||||
var result = Object.create(Vector2D.prototype)
|
|
||||||
result._x = x
|
|
||||||
result._y = y
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2D.prototype = {
|
|
||||||
get x () {
|
|
||||||
return this._x
|
|
||||||
},
|
|
||||||
get y () {
|
|
||||||
return this._y
|
|
||||||
},
|
|
||||||
|
|
||||||
set x (v) {
|
|
||||||
throw new Error('Vector2D is immutable')
|
|
||||||
},
|
|
||||||
set y (v) {
|
|
||||||
throw new Error('Vector2D is immutable')
|
|
||||||
},
|
|
||||||
|
|
||||||
// extend to a 3D vector by adding a z coordinate:
|
|
||||||
toVector3D: function (z) {
|
|
||||||
const Vector3D = require('./Vector3') // FIXME: circular dependencies Vector2 => Vector3 => Vector2
|
|
||||||
return new Vector3D(this._x, this._y, z)
|
|
||||||
},
|
|
||||||
|
|
||||||
equals: function (a) {
|
|
||||||
return (this._x === a._x) && (this._y === a._y)
|
|
||||||
},
|
|
||||||
|
|
||||||
clone: function () {
|
|
||||||
return Vector2D.Create(this._x, this._y)
|
|
||||||
},
|
|
||||||
|
|
||||||
negated: function () {
|
|
||||||
return Vector2D.Create(-this._x, -this._y)
|
|
||||||
},
|
|
||||||
|
|
||||||
plus: function (a) {
|
|
||||||
return Vector2D.Create(this._x + a._x, this._y + a._y)
|
|
||||||
},
|
|
||||||
|
|
||||||
minus: function (a) {
|
|
||||||
return Vector2D.Create(this._x - a._x, this._y - a._y)
|
|
||||||
},
|
|
||||||
|
|
||||||
times: function (a) {
|
|
||||||
return Vector2D.Create(this._x * a, this._y * a)
|
|
||||||
},
|
|
||||||
|
|
||||||
dividedBy: function (a) {
|
|
||||||
return Vector2D.Create(this._x / a, this._y / a)
|
|
||||||
},
|
|
||||||
|
|
||||||
dot: function (a) {
|
|
||||||
return this._x * a._x + this._y * a._y
|
|
||||||
},
|
|
||||||
|
|
||||||
lerp: function (a, t) {
|
|
||||||
return this.plus(a.minus(this).times(t))
|
|
||||||
},
|
|
||||||
|
|
||||||
length: function () {
|
|
||||||
return Math.sqrt(this.dot(this))
|
|
||||||
},
|
|
||||||
|
|
||||||
distanceTo: function (a) {
|
|
||||||
return this.minus(a).length()
|
|
||||||
},
|
|
||||||
|
|
||||||
distanceToSquared: function (a) {
|
|
||||||
return this.minus(a).lengthSquared()
|
|
||||||
},
|
|
||||||
|
|
||||||
lengthSquared: function () {
|
|
||||||
return this.dot(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
unit: function () {
|
|
||||||
return this.dividedBy(this.length())
|
|
||||||
},
|
|
||||||
|
|
||||||
cross: function (a) {
|
|
||||||
return this._x * a._y - this._y * a._x
|
|
||||||
},
|
|
||||||
|
|
||||||
// returns the vector rotated by 90 degrees clockwise
|
|
||||||
normal: function () {
|
|
||||||
return Vector2D.Create(this._y, -this._x)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Right multiply by a 4x4 matrix (the vector is interpreted as a row vector)
|
|
||||||
// Returns a new Vector2D
|
|
||||||
multiply4x4: function (matrix4x4) {
|
|
||||||
return matrix4x4.leftMultiply1x2Vector(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
return matrix4x4.leftMultiply1x2Vector(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
angle: function () {
|
|
||||||
return this.angleRadians()
|
|
||||||
},
|
|
||||||
|
|
||||||
angleDegrees: function () {
|
|
||||||
var radians = this.angleRadians()
|
|
||||||
return 180 * radians / Math.PI
|
|
||||||
},
|
|
||||||
|
|
||||||
angleRadians: function () {
|
|
||||||
// y=sin, x=cos
|
|
||||||
return Math.atan2(this._y, this._x)
|
|
||||||
},
|
|
||||||
|
|
||||||
min: function (p) {
|
|
||||||
return Vector2D.Create(
|
|
||||||
Math.min(this._x, p._x), Math.min(this._y, p._y))
|
|
||||||
},
|
|
||||||
|
|
||||||
max: function (p) {
|
|
||||||
return Vector2D.Create(
|
|
||||||
Math.max(this._x, p._x), Math.max(this._y, p._y))
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () {
|
|
||||||
return '(' + this._x.toFixed(5) + ', ' + this._y.toFixed(5) + ')'
|
|
||||||
},
|
|
||||||
|
|
||||||
abs: function () {
|
|
||||||
return Vector2D.Create(Math.abs(this._x), Math.abs(this._y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Vector2D
|
|
213
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vector3.js
generated
vendored
213
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vector3.js
generated
vendored
@ -1,213 +0,0 @@
|
|||||||
const {IsFloat} = require('../utils')
|
|
||||||
const Vector2D = require('./Vector2')
|
|
||||||
|
|
||||||
/** Class Vector3D
|
|
||||||
* Represents a 3D vector with X, Y, Z coordinates.
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* new CSG.Vector3D(1, 2, 3);
|
|
||||||
* new CSG.Vector3D([1, 2, 3]);
|
|
||||||
* new CSG.Vector3D({ x: 1, y: 2, z: 3 });
|
|
||||||
* new CSG.Vector3D(1, 2); // assumes z=0
|
|
||||||
* new CSG.Vector3D([1, 2]); // assumes z=0
|
|
||||||
*/
|
|
||||||
const Vector3D = function (x, y, z) {
|
|
||||||
if (arguments.length === 3) {
|
|
||||||
this._x = parseFloat(x)
|
|
||||||
this._y = parseFloat(y)
|
|
||||||
this._z = parseFloat(z)
|
|
||||||
} else if (arguments.length === 2) {
|
|
||||||
this._x = parseFloat(x)
|
|
||||||
this._y = parseFloat(y)
|
|
||||||
this._z = 0
|
|
||||||
} else {
|
|
||||||
var ok = true
|
|
||||||
if (arguments.length === 1) {
|
|
||||||
if (typeof (x) === 'object') {
|
|
||||||
if (x instanceof Vector3D) {
|
|
||||||
this._x = x._x
|
|
||||||
this._y = x._y
|
|
||||||
this._z = x._z
|
|
||||||
} else if (x instanceof Vector2D) {
|
|
||||||
this._x = x._x
|
|
||||||
this._y = x._y
|
|
||||||
this._z = 0
|
|
||||||
} else if (x instanceof Array) {
|
|
||||||
if ((x.length < 2) || (x.length > 3)) {
|
|
||||||
ok = false
|
|
||||||
} else {
|
|
||||||
this._x = parseFloat(x[0])
|
|
||||||
this._y = parseFloat(x[1])
|
|
||||||
if (x.length === 3) {
|
|
||||||
this._z = parseFloat(x[2])
|
|
||||||
} else {
|
|
||||||
this._z = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (('x' in x) && ('y' in x)) {
|
|
||||||
this._x = parseFloat(x.x)
|
|
||||||
this._y = parseFloat(x.y)
|
|
||||||
if ('z' in x) {
|
|
||||||
this._z = parseFloat(x.z)
|
|
||||||
} else {
|
|
||||||
this._z = 0
|
|
||||||
}
|
|
||||||
} else if (('_x' in x) && ('_y' in x)) {
|
|
||||||
this._x = parseFloat(x._x)
|
|
||||||
this._y = parseFloat(x._y)
|
|
||||||
if ('_z' in x) {
|
|
||||||
this._z = parseFloat(x._z)
|
|
||||||
} else {
|
|
||||||
this._z = 0
|
|
||||||
}
|
|
||||||
} else ok = false
|
|
||||||
} else {
|
|
||||||
var v = parseFloat(x)
|
|
||||||
this._x = v
|
|
||||||
this._y = v
|
|
||||||
this._z = v
|
|
||||||
}
|
|
||||||
} else ok = false
|
|
||||||
if (ok) {
|
|
||||||
if ((!IsFloat(this._x)) || (!IsFloat(this._y)) || (!IsFloat(this._z))) ok = false
|
|
||||||
} else {
|
|
||||||
throw new Error('wrong arguments')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This does the same as new Vector3D(x,y,z) but it doesn't go through the constructor
|
|
||||||
// and the parameters are not validated. Is much faster.
|
|
||||||
Vector3D.Create = function (x, y, z) {
|
|
||||||
var result = Object.create(Vector3D.prototype)
|
|
||||||
result._x = x
|
|
||||||
result._y = y
|
|
||||||
result._z = z
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3D.prototype = {
|
|
||||||
get x () {
|
|
||||||
return this._x
|
|
||||||
},
|
|
||||||
get y () {
|
|
||||||
return this._y
|
|
||||||
},
|
|
||||||
get z () {
|
|
||||||
return this._z
|
|
||||||
},
|
|
||||||
|
|
||||||
set x (v) {
|
|
||||||
throw new Error('Vector3D is immutable')
|
|
||||||
},
|
|
||||||
set y (v) {
|
|
||||||
throw new Error('Vector3D is immutable')
|
|
||||||
},
|
|
||||||
set z (v) {
|
|
||||||
throw new Error('Vector3D is immutable')
|
|
||||||
},
|
|
||||||
|
|
||||||
clone: function () {
|
|
||||||
return Vector3D.Create(this._x, this._y, this._z)
|
|
||||||
},
|
|
||||||
|
|
||||||
negated: function () {
|
|
||||||
return Vector3D.Create(-this._x, -this._y, -this._z)
|
|
||||||
},
|
|
||||||
|
|
||||||
abs: function () {
|
|
||||||
return Vector3D.Create(Math.abs(this._x), Math.abs(this._y), Math.abs(this._z))
|
|
||||||
},
|
|
||||||
|
|
||||||
plus: function (a) {
|
|
||||||
return Vector3D.Create(this._x + a._x, this._y + a._y, this._z + a._z)
|
|
||||||
},
|
|
||||||
|
|
||||||
minus: function (a) {
|
|
||||||
return Vector3D.Create(this._x - a._x, this._y - a._y, this._z - a._z)
|
|
||||||
},
|
|
||||||
|
|
||||||
times: function (a) {
|
|
||||||
return Vector3D.Create(this._x * a, this._y * a, this._z * a)
|
|
||||||
},
|
|
||||||
|
|
||||||
dividedBy: function (a) {
|
|
||||||
return Vector3D.Create(this._x / a, this._y / a, this._z / a)
|
|
||||||
},
|
|
||||||
|
|
||||||
dot: function (a) {
|
|
||||||
return this._x * a._x + this._y * a._y + this._z * a._z
|
|
||||||
},
|
|
||||||
|
|
||||||
lerp: function (a, t) {
|
|
||||||
return this.plus(a.minus(this).times(t))
|
|
||||||
},
|
|
||||||
|
|
||||||
lengthSquared: function () {
|
|
||||||
return this.dot(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
length: function () {
|
|
||||||
return Math.sqrt(this.lengthSquared())
|
|
||||||
},
|
|
||||||
|
|
||||||
unit: function () {
|
|
||||||
return this.dividedBy(this.length())
|
|
||||||
},
|
|
||||||
|
|
||||||
cross: function (a) {
|
|
||||||
return Vector3D.Create(
|
|
||||||
this._y * a._z - this._z * a._y, this._z * a._x - this._x * a._z, this._x * a._y - this._y * a._x)
|
|
||||||
},
|
|
||||||
|
|
||||||
distanceTo: function (a) {
|
|
||||||
return this.minus(a).length()
|
|
||||||
},
|
|
||||||
|
|
||||||
distanceToSquared: function (a) {
|
|
||||||
return this.minus(a).lengthSquared()
|
|
||||||
},
|
|
||||||
|
|
||||||
equals: function (a) {
|
|
||||||
return (this._x === a._x) && (this._y === a._y) && (this._z === a._z)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Right multiply by a 4x4 matrix (the vector is interpreted as a row vector)
|
|
||||||
// Returns a new Vector3D
|
|
||||||
multiply4x4: function (matrix4x4) {
|
|
||||||
return matrix4x4.leftMultiply1x3Vector(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
return matrix4x4.leftMultiply1x3Vector(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () {
|
|
||||||
return '(' + this._x.toFixed(5) + ', ' + this._y.toFixed(5) + ', ' + this._z.toFixed(5) + ')'
|
|
||||||
},
|
|
||||||
|
|
||||||
// find a vector that is somewhat perpendicular to this one
|
|
||||||
randomNonParallelVector: function () {
|
|
||||||
var abs = this.abs()
|
|
||||||
if ((abs._x <= abs._y) && (abs._x <= abs._z)) {
|
|
||||||
return Vector3D.Create(1, 0, 0)
|
|
||||||
} else if ((abs._y <= abs._x) && (abs._y <= abs._z)) {
|
|
||||||
return Vector3D.Create(0, 1, 0)
|
|
||||||
} else {
|
|
||||||
return Vector3D.Create(0, 0, 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
min: function (p) {
|
|
||||||
return Vector3D.Create(
|
|
||||||
Math.min(this._x, p._x), Math.min(this._y, p._y), Math.min(this._z, p._z))
|
|
||||||
},
|
|
||||||
|
|
||||||
max: function (p) {
|
|
||||||
return Vector3D.Create(
|
|
||||||
Math.max(this._x, p._x), Math.max(this._y, p._y), Math.max(this._z, p._z))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Vector3D
|
|
@ -1,26 +0,0 @@
|
|||||||
const Vector2D = require('./Vector2')
|
|
||||||
const {getTag} = require('../constants')
|
|
||||||
|
|
||||||
const Vertex = function (pos) {
|
|
||||||
this.pos = pos
|
|
||||||
}
|
|
||||||
|
|
||||||
Vertex.fromObject = function (obj) {
|
|
||||||
return new Vertex(new Vector2D(obj.pos._x, obj.pos._y))
|
|
||||||
}
|
|
||||||
|
|
||||||
Vertex.prototype = {
|
|
||||||
toString: function () {
|
|
||||||
return '(' + this.pos.x.toFixed(5) + ',' + this.pos.y.toFixed(5) + ')'
|
|
||||||
},
|
|
||||||
getTag: function () {
|
|
||||||
var result = this.tag
|
|
||||||
if (!result) {
|
|
||||||
result = getTag()
|
|
||||||
this.tag = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Vertex
|
|
@ -1,56 +0,0 @@
|
|||||||
const Vector3D = require('./Vector3')
|
|
||||||
const {getTag} = require('../constants')
|
|
||||||
|
|
||||||
// # class Vertex
|
|
||||||
// Represents a vertex of a polygon. Use your own vertex class instead of this
|
|
||||||
// one to provide additional features like texture coordinates and vertex
|
|
||||||
// colors. Custom vertex classes need to provide a `pos` property
|
|
||||||
// `flipped()`, and `interpolate()` methods that behave analogous to the ones
|
|
||||||
// FIXME: And a lot MORE (see plane.fromVector3Ds for ex) ! This is fragile code
|
|
||||||
// defined by `Vertex`.
|
|
||||||
const Vertex = function (pos) {
|
|
||||||
this.pos = pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// create from an untyped object with identical property names:
|
|
||||||
Vertex.fromObject = function (obj) {
|
|
||||||
var pos = new Vector3D(obj.pos)
|
|
||||||
return new Vertex(pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
Vertex.prototype = {
|
|
||||||
// Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the
|
|
||||||
// orientation of a polygon is flipped.
|
|
||||||
flipped: function () {
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
|
|
||||||
getTag: function () {
|
|
||||||
var result = this.tag
|
|
||||||
if (!result) {
|
|
||||||
result = getTag()
|
|
||||||
this.tag = result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create a new vertex between this vertex and `other` by linearly
|
|
||||||
// interpolating all properties using a parameter of `t`. Subclasses should
|
|
||||||
// override this to interpolate additional properties.
|
|
||||||
interpolate: function (other, t) {
|
|
||||||
var newpos = this.pos.lerp(other.pos, t)
|
|
||||||
return new Vertex(newpos)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Affine transformation of vertex. Returns a new Vertex
|
|
||||||
transform: function (matrix4x4) {
|
|
||||||
var newpos = this.pos.multiply4x4(matrix4x4)
|
|
||||||
return new Vertex(newpos)
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function () {
|
|
||||||
return this.pos.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Vertex
|
|
@ -1,25 +0,0 @@
|
|||||||
const {EPS} = require('../constants')
|
|
||||||
const {solve2Linear} = require('../utils')
|
|
||||||
|
|
||||||
// see if the line between p0start and p0end intersects with the line between p1start and p1end
|
|
||||||
// returns true if the lines strictly intersect, the end points are not counted!
|
|
||||||
const linesIntersect = function (p0start, p0end, p1start, p1end) {
|
|
||||||
if (p0end.equals(p1start) || p1end.equals(p0start)) {
|
|
||||||
let d = p1end.minus(p1start).unit().plus(p0end.minus(p0start).unit()).length()
|
|
||||||
if (d < EPS) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let d0 = p0end.minus(p0start)
|
|
||||||
let d1 = p1end.minus(p1start)
|
|
||||||
// FIXME These epsilons need review and testing
|
|
||||||
if (Math.abs(d0.cross(d1)) < 1e-9) return false // lines are parallel
|
|
||||||
let alphas = solve2Linear(-d0.x, d1.x, -d0.y, d1.y, p0start.x - p1start.x, p0start.y - p1start.y)
|
|
||||||
if ((alphas[0] > 1e-6) && (alphas[0] < 0.999999) && (alphas[1] > 1e-5) && (alphas[1] < 0.999999)) return true
|
|
||||||
// if( (alphas[0] >= 0) && (alphas[0] <= 1) && (alphas[1] >= 0) && (alphas[1] <= 1) ) return true;
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {linesIntersect}
|
|
@ -1,342 +0,0 @@
|
|||||||
const {EPS} = require('../constants')
|
|
||||||
const OrthoNormalBasis = require('./OrthoNormalBasis')
|
|
||||||
const {interpolateBetween2DPointsForY, insertSorted, fnNumberSort} = require('../utils')
|
|
||||||
const Vertex = require('./Vertex3')
|
|
||||||
const Vector2D = require('./Vector2')
|
|
||||||
const Line2D = require('./Line2')
|
|
||||||
const Polygon = require('./Polygon3')
|
|
||||||
|
|
||||||
// Retesselation function for a set of coplanar polygons. See the introduction at the top of
|
|
||||||
// this file.
|
|
||||||
const reTesselateCoplanarPolygons = function (sourcepolygons, destpolygons) {
|
|
||||||
let numpolygons = sourcepolygons.length
|
|
||||||
if (numpolygons > 0) {
|
|
||||||
let plane = sourcepolygons[0].plane
|
|
||||||
let shared = sourcepolygons[0].shared
|
|
||||||
let orthobasis = new OrthoNormalBasis(plane)
|
|
||||||
let polygonvertices2d = [] // array of array of Vector2D
|
|
||||||
let polygontopvertexindexes = [] // array of indexes of topmost vertex per polygon
|
|
||||||
let topy2polygonindexes = {}
|
|
||||||
let ycoordinatetopolygonindexes = {}
|
|
||||||
|
|
||||||
let xcoordinatebins = {}
|
|
||||||
let ycoordinatebins = {}
|
|
||||||
|
|
||||||
// convert all polygon vertices to 2D
|
|
||||||
// Make a list of all encountered y coordinates
|
|
||||||
// And build a map of all polygons that have a vertex at a certain y coordinate:
|
|
||||||
let ycoordinateBinningFactor = 1.0 / EPS * 10
|
|
||||||
for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
|
|
||||||
let poly3d = sourcepolygons[polygonindex]
|
|
||||||
let vertices2d = []
|
|
||||||
let numvertices = poly3d.vertices.length
|
|
||||||
let minindex = -1
|
|
||||||
if (numvertices > 0) {
|
|
||||||
let miny, maxy, maxindex
|
|
||||||
for (let i = 0; i < numvertices; i++) {
|
|
||||||
let pos2d = orthobasis.to2D(poly3d.vertices[i].pos)
|
|
||||||
// perform binning of y coordinates: If we have multiple vertices very
|
|
||||||
// close to each other, give them the same y coordinate:
|
|
||||||
let ycoordinatebin = Math.floor(pos2d.y * ycoordinateBinningFactor)
|
|
||||||
let newy
|
|
||||||
if (ycoordinatebin in ycoordinatebins) {
|
|
||||||
newy = ycoordinatebins[ycoordinatebin]
|
|
||||||
} else if (ycoordinatebin + 1 in ycoordinatebins) {
|
|
||||||
newy = ycoordinatebins[ycoordinatebin + 1]
|
|
||||||
} else if (ycoordinatebin - 1 in ycoordinatebins) {
|
|
||||||
newy = ycoordinatebins[ycoordinatebin - 1]
|
|
||||||
} else {
|
|
||||||
newy = pos2d.y
|
|
||||||
ycoordinatebins[ycoordinatebin] = pos2d.y
|
|
||||||
}
|
|
||||||
pos2d = Vector2D.Create(pos2d.x, newy)
|
|
||||||
vertices2d.push(pos2d)
|
|
||||||
let y = pos2d.y
|
|
||||||
if ((i === 0) || (y < miny)) {
|
|
||||||
miny = y
|
|
||||||
minindex = i
|
|
||||||
}
|
|
||||||
if ((i === 0) || (y > maxy)) {
|
|
||||||
maxy = y
|
|
||||||
maxindex = i
|
|
||||||
}
|
|
||||||
if (!(y in ycoordinatetopolygonindexes)) {
|
|
||||||
ycoordinatetopolygonindexes[y] = {}
|
|
||||||
}
|
|
||||||
ycoordinatetopolygonindexes[y][polygonindex] = true
|
|
||||||
}
|
|
||||||
if (miny >= maxy) {
|
|
||||||
// degenerate polygon, all vertices have same y coordinate. Just ignore it from now:
|
|
||||||
vertices2d = []
|
|
||||||
numvertices = 0
|
|
||||||
minindex = -1
|
|
||||||
} else {
|
|
||||||
if (!(miny in topy2polygonindexes)) {
|
|
||||||
topy2polygonindexes[miny] = []
|
|
||||||
}
|
|
||||||
topy2polygonindexes[miny].push(polygonindex)
|
|
||||||
}
|
|
||||||
} // if(numvertices > 0)
|
|
||||||
// reverse the vertex order:
|
|
||||||
vertices2d.reverse()
|
|
||||||
minindex = numvertices - minindex - 1
|
|
||||||
polygonvertices2d.push(vertices2d)
|
|
||||||
polygontopvertexindexes.push(minindex)
|
|
||||||
}
|
|
||||||
let ycoordinates = []
|
|
||||||
for (let ycoordinate in ycoordinatetopolygonindexes) ycoordinates.push(ycoordinate)
|
|
||||||
ycoordinates.sort(fnNumberSort)
|
|
||||||
|
|
||||||
// Now we will iterate over all y coordinates, from lowest to highest y coordinate
|
|
||||||
// activepolygons: source polygons that are 'active', i.e. intersect with our y coordinate
|
|
||||||
// Is sorted so the polygons are in left to right order
|
|
||||||
// Each element in activepolygons has these properties:
|
|
||||||
// polygonindex: the index of the source polygon (i.e. an index into the sourcepolygons
|
|
||||||
// and polygonvertices2d arrays)
|
|
||||||
// leftvertexindex: the index of the vertex at the left side of the polygon (lowest x)
|
|
||||||
// that is at or just above the current y coordinate
|
|
||||||
// rightvertexindex: dito at right hand side of polygon
|
|
||||||
// topleft, bottomleft: coordinates of the left side of the polygon crossing the current y coordinate
|
|
||||||
// topright, bottomright: coordinates of the right hand side of the polygon crossing the current y coordinate
|
|
||||||
let activepolygons = []
|
|
||||||
let prevoutpolygonrow = []
|
|
||||||
for (let yindex = 0; yindex < ycoordinates.length; yindex++) {
|
|
||||||
let newoutpolygonrow = []
|
|
||||||
let ycoordinate_as_string = ycoordinates[yindex]
|
|
||||||
let ycoordinate = Number(ycoordinate_as_string)
|
|
||||||
|
|
||||||
// update activepolygons for this y coordinate:
|
|
||||||
// - Remove any polygons that end at this y coordinate
|
|
||||||
// - update leftvertexindex and rightvertexindex (which point to the current vertex index
|
|
||||||
// at the the left and right side of the polygon
|
|
||||||
// Iterate over all polygons that have a corner at this y coordinate:
|
|
||||||
let polygonindexeswithcorner = ycoordinatetopolygonindexes[ycoordinate_as_string]
|
|
||||||
for (let activepolygonindex = 0; activepolygonindex < activepolygons.length; ++activepolygonindex) {
|
|
||||||
let activepolygon = activepolygons[activepolygonindex]
|
|
||||||
let polygonindex = activepolygon.polygonindex
|
|
||||||
if (polygonindexeswithcorner[polygonindex]) {
|
|
||||||
// this active polygon has a corner at this y coordinate:
|
|
||||||
let vertices2d = polygonvertices2d[polygonindex]
|
|
||||||
let numvertices = vertices2d.length
|
|
||||||
let newleftvertexindex = activepolygon.leftvertexindex
|
|
||||||
let newrightvertexindex = activepolygon.rightvertexindex
|
|
||||||
// See if we need to increase leftvertexindex or decrease rightvertexindex:
|
|
||||||
while (true) {
|
|
||||||
let nextleftvertexindex = newleftvertexindex + 1
|
|
||||||
if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0
|
|
||||||
if (vertices2d[nextleftvertexindex].y !== ycoordinate) break
|
|
||||||
newleftvertexindex = nextleftvertexindex
|
|
||||||
}
|
|
||||||
let nextrightvertexindex = newrightvertexindex - 1
|
|
||||||
if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1
|
|
||||||
if (vertices2d[nextrightvertexindex].y === ycoordinate) {
|
|
||||||
newrightvertexindex = nextrightvertexindex
|
|
||||||
}
|
|
||||||
if ((newleftvertexindex !== activepolygon.leftvertexindex) && (newleftvertexindex === newrightvertexindex)) {
|
|
||||||
// We have increased leftvertexindex or decreased rightvertexindex, and now they point to the same vertex
|
|
||||||
// This means that this is the bottom point of the polygon. We'll remove it:
|
|
||||||
activepolygons.splice(activepolygonindex, 1)
|
|
||||||
--activepolygonindex
|
|
||||||
} else {
|
|
||||||
activepolygon.leftvertexindex = newleftvertexindex
|
|
||||||
activepolygon.rightvertexindex = newrightvertexindex
|
|
||||||
activepolygon.topleft = vertices2d[newleftvertexindex]
|
|
||||||
activepolygon.topright = vertices2d[newrightvertexindex]
|
|
||||||
let nextleftvertexindex = newleftvertexindex + 1
|
|
||||||
if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0
|
|
||||||
activepolygon.bottomleft = vertices2d[nextleftvertexindex]
|
|
||||||
let nextrightvertexindex = newrightvertexindex - 1
|
|
||||||
if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1
|
|
||||||
activepolygon.bottomright = vertices2d[nextrightvertexindex]
|
|
||||||
}
|
|
||||||
} // if polygon has corner here
|
|
||||||
} // for activepolygonindex
|
|
||||||
let nextycoordinate
|
|
||||||
if (yindex >= ycoordinates.length - 1) {
|
|
||||||
// last row, all polygons must be finished here:
|
|
||||||
activepolygons = []
|
|
||||||
nextycoordinate = null
|
|
||||||
} else // yindex < ycoordinates.length-1
|
|
||||||
{
|
|
||||||
nextycoordinate = Number(ycoordinates[yindex + 1])
|
|
||||||
let middleycoordinate = 0.5 * (ycoordinate + nextycoordinate)
|
|
||||||
// update activepolygons by adding any polygons that start here:
|
|
||||||
let startingpolygonindexes = topy2polygonindexes[ycoordinate_as_string]
|
|
||||||
for (let polygonindex_key in startingpolygonindexes) {
|
|
||||||
let polygonindex = startingpolygonindexes[polygonindex_key]
|
|
||||||
let vertices2d = polygonvertices2d[polygonindex]
|
|
||||||
let numvertices = vertices2d.length
|
|
||||||
let topvertexindex = polygontopvertexindexes[polygonindex]
|
|
||||||
// the top of the polygon may be a horizontal line. In that case topvertexindex can point to any point on this line.
|
|
||||||
// Find the left and right topmost vertices which have the current y coordinate:
|
|
||||||
let topleftvertexindex = topvertexindex
|
|
||||||
while (true) {
|
|
||||||
let i = topleftvertexindex + 1
|
|
||||||
if (i >= numvertices) i = 0
|
|
||||||
if (vertices2d[i].y !== ycoordinate) break
|
|
||||||
if (i === topvertexindex) break // should not happen, but just to prevent endless loops
|
|
||||||
topleftvertexindex = i
|
|
||||||
}
|
|
||||||
let toprightvertexindex = topvertexindex
|
|
||||||
while (true) {
|
|
||||||
let i = toprightvertexindex - 1
|
|
||||||
if (i < 0) i = numvertices - 1
|
|
||||||
if (vertices2d[i].y !== ycoordinate) break
|
|
||||||
if (i === topleftvertexindex) break // should not happen, but just to prevent endless loops
|
|
||||||
toprightvertexindex = i
|
|
||||||
}
|
|
||||||
let nextleftvertexindex = topleftvertexindex + 1
|
|
||||||
if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0
|
|
||||||
let nextrightvertexindex = toprightvertexindex - 1
|
|
||||||
if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1
|
|
||||||
let newactivepolygon = {
|
|
||||||
polygonindex: polygonindex,
|
|
||||||
leftvertexindex: topleftvertexindex,
|
|
||||||
rightvertexindex: toprightvertexindex,
|
|
||||||
topleft: vertices2d[topleftvertexindex],
|
|
||||||
topright: vertices2d[toprightvertexindex],
|
|
||||||
bottomleft: vertices2d[nextleftvertexindex],
|
|
||||||
bottomright: vertices2d[nextrightvertexindex]
|
|
||||||
}
|
|
||||||
insertSorted(activepolygons, newactivepolygon, function (el1, el2) {
|
|
||||||
let x1 = interpolateBetween2DPointsForY(
|
|
||||||
el1.topleft, el1.bottomleft, middleycoordinate)
|
|
||||||
let x2 = interpolateBetween2DPointsForY(
|
|
||||||
el2.topleft, el2.bottomleft, middleycoordinate)
|
|
||||||
if (x1 > x2) return 1
|
|
||||||
if (x1 < x2) return -1
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
} // for(let polygonindex in startingpolygonindexes)
|
|
||||||
} // yindex < ycoordinates.length-1
|
|
||||||
// if( (yindex === ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) )
|
|
||||||
if (true) {
|
|
||||||
// Now activepolygons is up to date
|
|
||||||
// Build the output polygons for the next row in newoutpolygonrow:
|
|
||||||
for (let activepolygonKey in activepolygons) {
|
|
||||||
let activepolygon = activepolygons[activepolygonKey]
|
|
||||||
let polygonindex = activepolygon.polygonindex
|
|
||||||
let vertices2d = polygonvertices2d[polygonindex]
|
|
||||||
let numvertices = vertices2d.length
|
|
||||||
|
|
||||||
let x = interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, ycoordinate)
|
|
||||||
let topleft = Vector2D.Create(x, ycoordinate)
|
|
||||||
x = interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, ycoordinate)
|
|
||||||
let topright = Vector2D.Create(x, ycoordinate)
|
|
||||||
x = interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, nextycoordinate)
|
|
||||||
let bottomleft = Vector2D.Create(x, nextycoordinate)
|
|
||||||
x = interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, nextycoordinate)
|
|
||||||
let bottomright = Vector2D.Create(x, nextycoordinate)
|
|
||||||
let outpolygon = {
|
|
||||||
topleft: topleft,
|
|
||||||
topright: topright,
|
|
||||||
bottomleft: bottomleft,
|
|
||||||
bottomright: bottomright,
|
|
||||||
leftline: Line2D.fromPoints(topleft, bottomleft),
|
|
||||||
rightline: Line2D.fromPoints(bottomright, topright)
|
|
||||||
}
|
|
||||||
if (newoutpolygonrow.length > 0) {
|
|
||||||
let prevoutpolygon = newoutpolygonrow[newoutpolygonrow.length - 1]
|
|
||||||
let d1 = outpolygon.topleft.distanceTo(prevoutpolygon.topright)
|
|
||||||
let d2 = outpolygon.bottomleft.distanceTo(prevoutpolygon.bottomright)
|
|
||||||
if ((d1 < EPS) && (d2 < EPS)) {
|
|
||||||
// we can join this polygon with the one to the left:
|
|
||||||
outpolygon.topleft = prevoutpolygon.topleft
|
|
||||||
outpolygon.leftline = prevoutpolygon.leftline
|
|
||||||
outpolygon.bottomleft = prevoutpolygon.bottomleft
|
|
||||||
newoutpolygonrow.splice(newoutpolygonrow.length - 1, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newoutpolygonrow.push(outpolygon)
|
|
||||||
} // for(activepolygon in activepolygons)
|
|
||||||
if (yindex > 0) {
|
|
||||||
// try to match the new polygons against the previous row:
|
|
||||||
let prevcontinuedindexes = {}
|
|
||||||
let matchedindexes = {}
|
|
||||||
for (let i = 0; i < newoutpolygonrow.length; i++) {
|
|
||||||
let thispolygon = newoutpolygonrow[i]
|
|
||||||
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
||||||
if (!matchedindexes[ii]) // not already processed?
|
|
||||||
{
|
|
||||||
// We have a match if the sidelines are equal or if the top coordinates
|
|
||||||
// are on the sidelines of the previous polygon
|
|
||||||
let prevpolygon = prevoutpolygonrow[ii]
|
|
||||||
if (prevpolygon.bottomleft.distanceTo(thispolygon.topleft) < EPS) {
|
|
||||||
if (prevpolygon.bottomright.distanceTo(thispolygon.topright) < EPS) {
|
|
||||||
// Yes, the top of this polygon matches the bottom of the previous:
|
|
||||||
matchedindexes[ii] = true
|
|
||||||
// Now check if the joined polygon would remain convex:
|
|
||||||
let d1 = thispolygon.leftline.direction().x - prevpolygon.leftline.direction().x
|
|
||||||
let d2 = thispolygon.rightline.direction().x - prevpolygon.rightline.direction().x
|
|
||||||
let leftlinecontinues = Math.abs(d1) < EPS
|
|
||||||
let rightlinecontinues = Math.abs(d2) < EPS
|
|
||||||
let leftlineisconvex = leftlinecontinues || (d1 >= 0)
|
|
||||||
let rightlineisconvex = rightlinecontinues || (d2 >= 0)
|
|
||||||
if (leftlineisconvex && rightlineisconvex) {
|
|
||||||
// yes, both sides have convex corners:
|
|
||||||
// This polygon will continue the previous polygon
|
|
||||||
thispolygon.outpolygon = prevpolygon.outpolygon
|
|
||||||
thispolygon.leftlinecontinues = leftlinecontinues
|
|
||||||
thispolygon.rightlinecontinues = rightlinecontinues
|
|
||||||
prevcontinuedindexes[ii] = true
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // if(!prevcontinuedindexes[ii])
|
|
||||||
} // for ii
|
|
||||||
} // for i
|
|
||||||
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
||||||
if (!prevcontinuedindexes[ii]) {
|
|
||||||
// polygon ends here
|
|
||||||
// Finish the polygon with the last point(s):
|
|
||||||
let prevpolygon = prevoutpolygonrow[ii]
|
|
||||||
prevpolygon.outpolygon.rightpoints.push(prevpolygon.bottomright)
|
|
||||||
if (prevpolygon.bottomright.distanceTo(prevpolygon.bottomleft) > EPS) {
|
|
||||||
// polygon ends with a horizontal line:
|
|
||||||
prevpolygon.outpolygon.leftpoints.push(prevpolygon.bottomleft)
|
|
||||||
}
|
|
||||||
// reverse the left half so we get a counterclockwise circle:
|
|
||||||
prevpolygon.outpolygon.leftpoints.reverse()
|
|
||||||
let points2d = prevpolygon.outpolygon.rightpoints.concat(prevpolygon.outpolygon.leftpoints)
|
|
||||||
let vertices3d = []
|
|
||||||
points2d.map(function (point2d) {
|
|
||||||
let point3d = orthobasis.to3D(point2d)
|
|
||||||
let vertex3d = new Vertex(point3d)
|
|
||||||
vertices3d.push(vertex3d)
|
|
||||||
})
|
|
||||||
let polygon = new Polygon(vertices3d, shared, plane)
|
|
||||||
destpolygons.push(polygon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // if(yindex > 0)
|
|
||||||
for (let i = 0; i < newoutpolygonrow.length; i++) {
|
|
||||||
let thispolygon = newoutpolygonrow[i]
|
|
||||||
if (!thispolygon.outpolygon) {
|
|
||||||
// polygon starts here:
|
|
||||||
thispolygon.outpolygon = {
|
|
||||||
leftpoints: [],
|
|
||||||
rightpoints: []
|
|
||||||
}
|
|
||||||
thispolygon.outpolygon.leftpoints.push(thispolygon.topleft)
|
|
||||||
if (thispolygon.topleft.distanceTo(thispolygon.topright) > EPS) {
|
|
||||||
// we have a horizontal line at the top:
|
|
||||||
thispolygon.outpolygon.rightpoints.push(thispolygon.topright)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// continuation of a previous row
|
|
||||||
if (!thispolygon.leftlinecontinues) {
|
|
||||||
thispolygon.outpolygon.leftpoints.push(thispolygon.topleft)
|
|
||||||
}
|
|
||||||
if (!thispolygon.rightlinecontinues) {
|
|
||||||
thispolygon.outpolygon.rightpoints.push(thispolygon.topright)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevoutpolygonrow = newoutpolygonrow
|
|
||||||
}
|
|
||||||
} // for yindex
|
|
||||||
} // if(numpolygons > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {reTesselateCoplanarPolygons}
|
|
81
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/mutators.js
generated
vendored
81
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/mutators.js
generated
vendored
@ -1,81 +0,0 @@
|
|||||||
const Matrix4x4 = require('./math/Matrix4')
|
|
||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Plane = require('./math/Plane')
|
|
||||||
|
|
||||||
// Add several convenience methods to the classes that support a transform() method:
|
|
||||||
const addTransformationMethodsToPrototype = function (prot) {
|
|
||||||
prot.mirrored = function (plane) {
|
|
||||||
return this.transform(Matrix4x4.mirroring(plane))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.mirroredX = function () {
|
|
||||||
let plane = new Plane(Vector3D.Create(1, 0, 0), 0)
|
|
||||||
return this.mirrored(plane)
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.mirroredY = function () {
|
|
||||||
let plane = new Plane(Vector3D.Create(0, 1, 0), 0)
|
|
||||||
return this.mirrored(plane)
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.mirroredZ = function () {
|
|
||||||
let plane = new Plane(Vector3D.Create(0, 0, 1), 0)
|
|
||||||
return this.mirrored(plane)
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.translate = function (v) {
|
|
||||||
return this.transform(Matrix4x4.translation(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.scale = function (f) {
|
|
||||||
return this.transform(Matrix4x4.scaling(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.rotateX = function (deg) {
|
|
||||||
return this.transform(Matrix4x4.rotationX(deg))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.rotateY = function (deg) {
|
|
||||||
return this.transform(Matrix4x4.rotationY(deg))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.rotateZ = function (deg) {
|
|
||||||
return this.transform(Matrix4x4.rotationZ(deg))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.rotate = function (rotationCenter, rotationAxis, degrees) {
|
|
||||||
return this.transform(Matrix4x4.rotation(rotationCenter, rotationAxis, degrees))
|
|
||||||
}
|
|
||||||
|
|
||||||
prot.rotateEulerAngles = function (alpha, beta, gamma, position) {
|
|
||||||
position = position || [0, 0, 0]
|
|
||||||
|
|
||||||
let Rz1 = Matrix4x4.rotationZ(alpha)
|
|
||||||
let Rx = Matrix4x4.rotationX(beta)
|
|
||||||
let Rz2 = Matrix4x4.rotationZ(gamma)
|
|
||||||
let T = Matrix4x4.translation(new Vector3D(position))
|
|
||||||
|
|
||||||
return this.transform(Rz2.multiply(Rx).multiply(Rz1).multiply(T))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: consider generalization and adding to addTransformationMethodsToPrototype
|
|
||||||
const addCenteringToPrototype = function (prot, axes) {
|
|
||||||
prot.center = function (cAxes) {
|
|
||||||
cAxes = Array.prototype.map.call(arguments, function (a) {
|
|
||||||
return a // .toLowerCase();
|
|
||||||
})
|
|
||||||
// no args: center on all axes
|
|
||||||
if (!cAxes.length) {
|
|
||||||
cAxes = axes.slice()
|
|
||||||
}
|
|
||||||
let b = this.getBounds()
|
|
||||||
return this.translate(axes.map(function (a) {
|
|
||||||
return cAxes.indexOf(a) > -1 ? -(b[0][a] + b[1][a]) / 2 : 0
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = {
|
|
||||||
addTransformationMethodsToPrototype,
|
|
||||||
addCenteringToPrototype
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Vector2D = require('./math/Vector2')
|
|
||||||
|
|
||||||
// Parse an option from the options object
|
|
||||||
// If the option is not present, return the default value
|
|
||||||
const parseOption = function (options, optionname, defaultvalue) {
|
|
||||||
var result = defaultvalue
|
|
||||||
if (options && optionname in options) {
|
|
||||||
result = options[optionname]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse an option and force into a Vector3D. If a scalar is passed it is converted
|
|
||||||
// into a vector with equal x,y,z
|
|
||||||
const parseOptionAs3DVector = function (options, optionname, defaultvalue) {
|
|
||||||
var result = parseOption(options, optionname, defaultvalue)
|
|
||||||
result = new Vector3D(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseOptionAs3DVectorList = function (options, optionname, defaultvalue) {
|
|
||||||
var result = parseOption(options, optionname, defaultvalue)
|
|
||||||
return result.map(function (res) {
|
|
||||||
return new Vector3D(res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse an option and force into a Vector2D. If a scalar is passed it is converted
|
|
||||||
// into a vector with equal x,y
|
|
||||||
const parseOptionAs2DVector = function (options, optionname, defaultvalue) {
|
|
||||||
var result = parseOption(options, optionname, defaultvalue)
|
|
||||||
result = new Vector2D(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseOptionAsFloat = function (options, optionname, defaultvalue) {
|
|
||||||
var result = parseOption(options, optionname, defaultvalue)
|
|
||||||
if (typeof (result) === 'string') {
|
|
||||||
result = Number(result)
|
|
||||||
}
|
|
||||||
if (isNaN(result) || typeof (result) !== 'number') {
|
|
||||||
throw new Error('Parameter ' + optionname + ' should be a number')
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseOptionAsInt = function (options, optionname, defaultvalue) {
|
|
||||||
var result = parseOption(options, optionname, defaultvalue)
|
|
||||||
result = Number(Math.floor(result))
|
|
||||||
if (isNaN(result)) {
|
|
||||||
throw new Error('Parameter ' + optionname + ' should be a number')
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseOptionAsBool = function (options, optionname, defaultvalue) {
|
|
||||||
var result = parseOption(options, optionname, defaultvalue)
|
|
||||||
if (typeof (result) === 'string') {
|
|
||||||
if (result === 'true') result = true
|
|
||||||
else if (result === 'false') result = false
|
|
||||||
else if (result === 0) result = false
|
|
||||||
}
|
|
||||||
result = !!result
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
parseOption,
|
|
||||||
parseOptionAsInt,
|
|
||||||
parseOptionAsFloat,
|
|
||||||
parseOptionAsBool,
|
|
||||||
parseOptionAs3DVector,
|
|
||||||
parseOptionAs2DVector,
|
|
||||||
parseOptionAs3DVectorList
|
|
||||||
}
|
|
184
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/primitives2d.js
generated
vendored
184
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/primitives2d.js
generated
vendored
@ -1,184 +0,0 @@
|
|||||||
const CAG = require('./CAG')
|
|
||||||
const {parseOptionAs2DVector, parseOptionAsFloat, parseOptionAsInt} = require('./optionParsers')
|
|
||||||
const {defaultResolution2D} = require('./constants')
|
|
||||||
const Vector2D = require('./math/Vector2')
|
|
||||||
const Path2D = require('./math/Path2')
|
|
||||||
const {fromCompactBinary} = require('./CAGFactories')
|
|
||||||
|
|
||||||
/** Construct a circle.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector2D} [options.center=[0,0]] - center of circle
|
|
||||||
* @param {Number} [options.radius=1] - radius of circle
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of sides per 360 rotation
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
const circle = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
let center = parseOptionAs2DVector(options, 'center', [0, 0])
|
|
||||||
let radius = parseOptionAsFloat(options, 'radius', 1)
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
|
||||||
let points = []
|
|
||||||
for (let i = 0; i < resolution; i++) {
|
|
||||||
let radians = 2 * Math.PI * i / resolution
|
|
||||||
let point = Vector2D.fromAngleRadians(radians).times(radius).plus(center)
|
|
||||||
points.push(point)
|
|
||||||
}
|
|
||||||
return CAG.fromPoints(points)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct an ellispe.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector2D} [options.center=[0,0]] - center of ellipse
|
|
||||||
* @param {Vector2D} [options.radius=[1,1]] - radius of ellipse, width and height
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of sides per 360 rotation
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
const ellipse = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
let c = parseOptionAs2DVector(options, 'center', [0, 0])
|
|
||||||
let r = parseOptionAs2DVector(options, 'radius', [1, 1])
|
|
||||||
r = r.abs() // negative radii make no sense
|
|
||||||
let res = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
|
||||||
|
|
||||||
let e2 = new Path2D([[c.x, c.y + r.y]])
|
|
||||||
e2 = e2.appendArc([c.x, c.y - r.y], {
|
|
||||||
xradius: r.x,
|
|
||||||
yradius: r.y,
|
|
||||||
xaxisrotation: 0,
|
|
||||||
resolution: res,
|
|
||||||
clockwise: true,
|
|
||||||
large: false
|
|
||||||
})
|
|
||||||
e2 = e2.appendArc([c.x, c.y + r.y], {
|
|
||||||
xradius: r.x,
|
|
||||||
yradius: r.y,
|
|
||||||
xaxisrotation: 0,
|
|
||||||
resolution: res,
|
|
||||||
clockwise: true,
|
|
||||||
large: false
|
|
||||||
})
|
|
||||||
e2 = e2.close()
|
|
||||||
return CAG.fromPath2(e2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a rectangle.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector2D} [options.center=[0,0]] - center of rectangle
|
|
||||||
* @param {Vector2D} [options.radius=[1,1]] - radius of rectangle, width and height
|
|
||||||
* @param {Vector2D} [options.corner1=[0,0]] - bottom left corner of rectangle (alternate)
|
|
||||||
* @param {Vector2D} [options.corner2=[0,0]] - upper right corner of rectangle (alternate)
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
const rectangle = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
let c, r
|
|
||||||
if (('corner1' in options) || ('corner2' in options)) {
|
|
||||||
if (('center' in options) || ('radius' in options)) {
|
|
||||||
throw new Error('rectangle: should either give a radius and center parameter, or a corner1 and corner2 parameter')
|
|
||||||
}
|
|
||||||
let corner1 = parseOptionAs2DVector(options, 'corner1', [0, 0])
|
|
||||||
let corner2 = parseOptionAs2DVector(options, 'corner2', [1, 1])
|
|
||||||
c = corner1.plus(corner2).times(0.5)
|
|
||||||
r = corner2.minus(corner1).times(0.5)
|
|
||||||
} else {
|
|
||||||
c = parseOptionAs2DVector(options, 'center', [0, 0])
|
|
||||||
r = parseOptionAs2DVector(options, 'radius', [1, 1])
|
|
||||||
}
|
|
||||||
r = r.abs() // negative radii make no sense
|
|
||||||
let rswap = new Vector2D(r.x, -r.y)
|
|
||||||
let points = [
|
|
||||||
c.plus(r), c.plus(rswap), c.minus(r), c.minus(rswap)
|
|
||||||
]
|
|
||||||
return CAG.fromPoints(points)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a rounded rectangle.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector2D} [options.center=[0,0]] - center of rounded rectangle
|
|
||||||
* @param {Vector2D} [options.radius=[1,1]] - radius of rounded rectangle, width and height
|
|
||||||
* @param {Vector2D} [options.corner1=[0,0]] - bottom left corner of rounded rectangle (alternate)
|
|
||||||
* @param {Vector2D} [options.corner2=[0,0]] - upper right corner of rounded rectangle (alternate)
|
|
||||||
* @param {Number} [options.roundradius=0.2] - round radius of corners
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of sides per 360 rotation
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let r = roundedRectangle({
|
|
||||||
* center: [0, 0],
|
|
||||||
* radius: [5, 10],
|
|
||||||
* roundradius: 2,
|
|
||||||
* resolution: 36,
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const roundedRectangle = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
let center, radius
|
|
||||||
if (('corner1' in options) || ('corner2' in options)) {
|
|
||||||
if (('center' in options) || ('radius' in options)) {
|
|
||||||
throw new Error('roundedRectangle: should either give a radius and center parameter, or a corner1 and corner2 parameter')
|
|
||||||
}
|
|
||||||
let corner1 = parseOptionAs2DVector(options, 'corner1', [0, 0])
|
|
||||||
let corner2 = parseOptionAs2DVector(options, 'corner2', [1, 1])
|
|
||||||
center = corner1.plus(corner2).times(0.5)
|
|
||||||
radius = corner2.minus(corner1).times(0.5)
|
|
||||||
} else {
|
|
||||||
center = parseOptionAs2DVector(options, 'center', [0, 0])
|
|
||||||
radius = parseOptionAs2DVector(options, 'radius', [1, 1])
|
|
||||||
}
|
|
||||||
radius = radius.abs() // negative radii make no sense
|
|
||||||
let roundradius = parseOptionAsFloat(options, 'roundradius', 0.2)
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution2D)
|
|
||||||
let maxroundradius = Math.min(radius.x, radius.y)
|
|
||||||
maxroundradius -= 0.1
|
|
||||||
roundradius = Math.min(roundradius, maxroundradius)
|
|
||||||
roundradius = Math.max(0, roundradius)
|
|
||||||
radius = new Vector2D(radius.x - roundradius, radius.y - roundradius)
|
|
||||||
let rect = CAG.rectangle({
|
|
||||||
center: center,
|
|
||||||
radius: radius
|
|
||||||
})
|
|
||||||
if (roundradius > 0) {
|
|
||||||
rect = rect.expand(roundradius, resolution)
|
|
||||||
}
|
|
||||||
return rect
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reconstruct a CAG from the output of toCompactBinary().
|
|
||||||
* @param {CompactBinary} bin - see toCompactBinary()
|
|
||||||
* @returns {CAG} new CAG object
|
|
||||||
*/
|
|
||||||
CAG.fromCompactBinary = function (bin) {
|
|
||||||
if (bin['class'] !== 'CAG') throw new Error('Not a CAG')
|
|
||||||
let vertices = []
|
|
||||||
let vertexData = bin.vertexData
|
|
||||||
let numvertices = vertexData.length / 2
|
|
||||||
let arrayindex = 0
|
|
||||||
for (let vertexindex = 0; vertexindex < numvertices; vertexindex++) {
|
|
||||||
let x = vertexData[arrayindex++]
|
|
||||||
let y = vertexData[arrayindex++]
|
|
||||||
let pos = new Vector2D(x, y)
|
|
||||||
let vertex = new CAG.Vertex(pos)
|
|
||||||
vertices.push(vertex)
|
|
||||||
}
|
|
||||||
|
|
||||||
let sides = []
|
|
||||||
let numsides = bin.sideVertexIndices.length / 2
|
|
||||||
arrayindex = 0
|
|
||||||
for (let sideindex = 0; sideindex < numsides; sideindex++) {
|
|
||||||
let vertexindex0 = bin.sideVertexIndices[arrayindex++]
|
|
||||||
let vertexindex1 = bin.sideVertexIndices[arrayindex++]
|
|
||||||
let side = new CAG.Side(vertices[vertexindex0], vertices[vertexindex1])
|
|
||||||
sides.push(side)
|
|
||||||
}
|
|
||||||
let cag = CAG.fromSides(sides)
|
|
||||||
cag.isCanonicalized = true
|
|
||||||
return cag
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
circle,
|
|
||||||
ellipse,
|
|
||||||
rectangle,
|
|
||||||
roundedRectangle,
|
|
||||||
fromCompactBinary
|
|
||||||
}
|
|
548
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/primitives3d.js
generated
vendored
548
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/primitives3d.js
generated
vendored
@ -1,548 +0,0 @@
|
|||||||
const CSG = require('./CSG')
|
|
||||||
const {parseOption, parseOptionAs3DVector, parseOptionAs2DVector, parseOptionAs3DVectorList, parseOptionAsFloat, parseOptionAsInt} = require('./optionParsers')
|
|
||||||
const {defaultResolution3D, defaultResolution2D, EPS} = require('./constants')
|
|
||||||
const Vector3D = require('./math/Vector3')
|
|
||||||
const Vertex = require('./math/Vertex3')
|
|
||||||
const Polygon = require('./math/Polygon3')
|
|
||||||
const {Connector} = require('./connectors')
|
|
||||||
const Properties = require('./Properties')
|
|
||||||
|
|
||||||
/** Construct an axis-aligned solid cuboid.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector3D} [options.center=[0,0,0]] - center of cube
|
|
||||||
* @param {Vector3D} [options.radius=[1,1,1]] - radius of cube, single scalar also possible
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let cube = CSG.cube({
|
|
||||||
* center: [5, 5, 5],
|
|
||||||
* radius: 5, // scalar radius
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const cube = function (options) {
|
|
||||||
let c
|
|
||||||
let r
|
|
||||||
let corner1
|
|
||||||
let corner2
|
|
||||||
options = options || {}
|
|
||||||
if (('corner1' in options) || ('corner2' in options)) {
|
|
||||||
if (('center' in options) || ('radius' in options)) {
|
|
||||||
throw new Error('cube: should either give a radius and center parameter, or a corner1 and corner2 parameter')
|
|
||||||
}
|
|
||||||
corner1 = parseOptionAs3DVector(options, 'corner1', [0, 0, 0])
|
|
||||||
corner2 = parseOptionAs3DVector(options, 'corner2', [1, 1, 1])
|
|
||||||
c = corner1.plus(corner2).times(0.5)
|
|
||||||
r = corner2.minus(corner1).times(0.5)
|
|
||||||
} else {
|
|
||||||
c = parseOptionAs3DVector(options, 'center', [0, 0, 0])
|
|
||||||
r = parseOptionAs3DVector(options, 'radius', [1, 1, 1])
|
|
||||||
}
|
|
||||||
r = r.abs() // negative radii make no sense
|
|
||||||
let result = CSG.fromPolygons([
|
|
||||||
[
|
|
||||||
[0, 4, 6, 2],
|
|
||||||
[-1, 0, 0]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 3, 7, 5],
|
|
||||||
[+1, 0, 0]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 5, 4],
|
|
||||||
[0, -1, 0]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[2, 6, 7, 3],
|
|
||||||
[0, +1, 0]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 2, 3, 1],
|
|
||||||
[0, 0, -1]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[4, 5, 7, 6],
|
|
||||||
[0, 0, +1]
|
|
||||||
]
|
|
||||||
].map(function (info) {
|
|
||||||
let vertices = info[0].map(function (i) {
|
|
||||||
let pos = new Vector3D(
|
|
||||||
c.x + r.x * (2 * !!(i & 1) - 1), c.y + r.y * (2 * !!(i & 2) - 1), c.z + r.z * (2 * !!(i & 4) - 1))
|
|
||||||
return new Vertex(pos)
|
|
||||||
})
|
|
||||||
return new Polygon(vertices, null /* , plane */)
|
|
||||||
}))
|
|
||||||
result.properties.cube = new Properties()
|
|
||||||
result.properties.cube.center = new Vector3D(c)
|
|
||||||
// add 6 connectors, at the centers of each face:
|
|
||||||
result.properties.cube.facecenters = [
|
|
||||||
new Connector(new Vector3D([r.x, 0, 0]).plus(c), [1, 0, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([-r.x, 0, 0]).plus(c), [-1, 0, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([0, r.y, 0]).plus(c), [0, 1, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([0, -r.y, 0]).plus(c), [0, -1, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([0, 0, r.z]).plus(c), [0, 0, 1], [1, 0, 0]),
|
|
||||||
new Connector(new Vector3D([0, 0, -r.z]).plus(c), [0, 0, -1], [1, 0, 0])
|
|
||||||
]
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a solid sphere
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector3D} [options.center=[0,0,0]] - center of sphere
|
|
||||||
* @param {Number} [options.radius=1] - radius of sphere
|
|
||||||
* @param {Number} [options.resolution=defaultResolution3D] - number of polygons per 360 degree revolution
|
|
||||||
* @param {Array} [options.axes] - an array with 3 vectors for the x, y and z base vectors
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let sphere = CSG.sphere({
|
|
||||||
* center: [0, 0, 0],
|
|
||||||
* radius: 2,
|
|
||||||
* resolution: 32,
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const sphere = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
let center = parseOptionAs3DVector(options, 'center', [0, 0, 0])
|
|
||||||
let radius = parseOptionAsFloat(options, 'radius', 1)
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
|
||||||
let xvector, yvector, zvector
|
|
||||||
if ('axes' in options) {
|
|
||||||
xvector = options.axes[0].unit().times(radius)
|
|
||||||
yvector = options.axes[1].unit().times(radius)
|
|
||||||
zvector = options.axes[2].unit().times(radius)
|
|
||||||
} else {
|
|
||||||
xvector = new Vector3D([1, 0, 0]).times(radius)
|
|
||||||
yvector = new Vector3D([0, -1, 0]).times(radius)
|
|
||||||
zvector = new Vector3D([0, 0, 1]).times(radius)
|
|
||||||
}
|
|
||||||
if (resolution < 4) resolution = 4
|
|
||||||
let qresolution = Math.round(resolution / 4)
|
|
||||||
let prevcylinderpoint
|
|
||||||
let polygons = []
|
|
||||||
for (let slice1 = 0; slice1 <= resolution; slice1++) {
|
|
||||||
let angle = Math.PI * 2.0 * slice1 / resolution
|
|
||||||
let cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle)))
|
|
||||||
if (slice1 > 0) {
|
|
||||||
// cylinder vertices:
|
|
||||||
let vertices = []
|
|
||||||
let prevcospitch, prevsinpitch
|
|
||||||
for (let slice2 = 0; slice2 <= qresolution; slice2++) {
|
|
||||||
let pitch = 0.5 * Math.PI * slice2 / qresolution
|
|
||||||
let cospitch = Math.cos(pitch)
|
|
||||||
let sinpitch = Math.sin(pitch)
|
|
||||||
if (slice2 > 0) {
|
|
||||||
vertices = []
|
|
||||||
vertices.push(new Vertex(center.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
|
||||||
vertices.push(new Vertex(center.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
|
||||||
if (slice2 < qresolution) {
|
|
||||||
vertices.push(new Vertex(center.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
|
||||||
}
|
|
||||||
vertices.push(new Vertex(center.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
|
||||||
polygons.push(new Polygon(vertices))
|
|
||||||
vertices = []
|
|
||||||
vertices.push(new Vertex(center.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
|
||||||
vertices.push(new Vertex(center.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
|
||||||
if (slice2 < qresolution) {
|
|
||||||
vertices.push(new Vertex(center.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
|
||||||
}
|
|
||||||
vertices.push(new Vertex(center.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
|
||||||
vertices.reverse()
|
|
||||||
polygons.push(new Polygon(vertices))
|
|
||||||
}
|
|
||||||
prevcospitch = cospitch
|
|
||||||
prevsinpitch = sinpitch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevcylinderpoint = cylinderpoint
|
|
||||||
}
|
|
||||||
let result = CSG.fromPolygons(polygons)
|
|
||||||
result.properties.sphere = new Properties()
|
|
||||||
result.properties.sphere.center = new Vector3D(center)
|
|
||||||
result.properties.sphere.facepoint = center.plus(xvector)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a solid cylinder.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector} [options.start=[0,-1,0]] - start point of cylinder
|
|
||||||
* @param {Vector} [options.end=[0,1,0]] - end point of cylinder
|
|
||||||
* @param {Number} [options.radius=1] - radius of cylinder, must be scalar
|
|
||||||
* @param {Number} [options.resolution=defaultResolution3D] - number of polygons per 360 degree revolution
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let cylinder = CSG.cylinder({
|
|
||||||
* start: [0, -10, 0],
|
|
||||||
* end: [0, 10, 0],
|
|
||||||
* radius: 10,
|
|
||||||
* resolution: 16
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const cylinder = function (options) {
|
|
||||||
let s = parseOptionAs3DVector(options, 'start', [0, -1, 0])
|
|
||||||
let e = parseOptionAs3DVector(options, 'end', [0, 1, 0])
|
|
||||||
let r = parseOptionAsFloat(options, 'radius', 1)
|
|
||||||
let rEnd = parseOptionAsFloat(options, 'radiusEnd', r)
|
|
||||||
let rStart = parseOptionAsFloat(options, 'radiusStart', r)
|
|
||||||
let alpha = parseOptionAsFloat(options, 'sectorAngle', 360)
|
|
||||||
alpha = alpha > 360 ? alpha % 360 : alpha
|
|
||||||
|
|
||||||
if ((rEnd < 0) || (rStart < 0)) {
|
|
||||||
throw new Error('Radius should be non-negative')
|
|
||||||
}
|
|
||||||
if ((rEnd === 0) && (rStart === 0)) {
|
|
||||||
throw new Error('Either radiusStart or radiusEnd should be positive')
|
|
||||||
}
|
|
||||||
|
|
||||||
let slices = parseOptionAsInt(options, 'resolution', defaultResolution2D) // FIXME is this 3D?
|
|
||||||
let ray = e.minus(s)
|
|
||||||
let axisZ = ray.unit() //, isY = (Math.abs(axisZ.y) > 0.5);
|
|
||||||
let axisX = axisZ.randomNonParallelVector().unit()
|
|
||||||
|
|
||||||
// let axisX = new Vector3D(isY, !isY, 0).cross(axisZ).unit();
|
|
||||||
let axisY = axisX.cross(axisZ).unit()
|
|
||||||
let start = new Vertex(s)
|
|
||||||
let end = new Vertex(e)
|
|
||||||
let polygons = []
|
|
||||||
|
|
||||||
function point (stack, slice, radius) {
|
|
||||||
let angle = slice * Math.PI * alpha / 180
|
|
||||||
let out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle)))
|
|
||||||
let pos = s.plus(ray.times(stack)).plus(out.times(radius))
|
|
||||||
return new Vertex(pos)
|
|
||||||
}
|
|
||||||
if (alpha > 0) {
|
|
||||||
for (let i = 0; i < slices; i++) {
|
|
||||||
let t0 = i / slices
|
|
||||||
let t1 = (i + 1) / slices
|
|
||||||
if (rEnd === rStart) {
|
|
||||||
polygons.push(new Polygon([start, point(0, t0, rEnd), point(0, t1, rEnd)]))
|
|
||||||
polygons.push(new Polygon([point(0, t1, rEnd), point(0, t0, rEnd), point(1, t0, rEnd), point(1, t1, rEnd)]))
|
|
||||||
polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
|
||||||
} else {
|
|
||||||
if (rStart > 0) {
|
|
||||||
polygons.push(new Polygon([start, point(0, t0, rStart), point(0, t1, rStart)]))
|
|
||||||
polygons.push(new Polygon([point(0, t0, rStart), point(1, t0, rEnd), point(0, t1, rStart)]))
|
|
||||||
}
|
|
||||||
if (rEnd > 0) {
|
|
||||||
polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
|
||||||
polygons.push(new Polygon([point(1, t0, rEnd), point(1, t1, rEnd), point(0, t1, rStart)]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (alpha < 360) {
|
|
||||||
polygons.push(new Polygon([start, end, point(0, 0, rStart)]))
|
|
||||||
polygons.push(new Polygon([point(0, 0, rStart), end, point(1, 0, rEnd)]))
|
|
||||||
polygons.push(new Polygon([start, point(0, 1, rStart), end]))
|
|
||||||
polygons.push(new Polygon([point(0, 1, rStart), point(1, 1, rEnd), end]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = CSG.fromPolygons(polygons)
|
|
||||||
result.properties.cylinder = new Properties()
|
|
||||||
result.properties.cylinder.start = new Connector(s, axisZ.negated(), axisX)
|
|
||||||
result.properties.cylinder.end = new Connector(e, axisZ, axisX)
|
|
||||||
let cylCenter = s.plus(ray.times(0.5))
|
|
||||||
let fptVec = axisX.rotate(s, axisZ, -alpha / 2).times((rStart + rEnd) / 2)
|
|
||||||
let fptVec90 = fptVec.cross(axisZ)
|
|
||||||
// note this one is NOT a face normal for a cone. - It's horizontal from cyl perspective
|
|
||||||
result.properties.cylinder.facepointH = new Connector(cylCenter.plus(fptVec), fptVec, axisZ)
|
|
||||||
result.properties.cylinder.facepointH90 = new Connector(cylCenter.plus(fptVec90), fptVec90, axisZ)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a cylinder with rounded ends.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector3D} [options.start=[0,-1,0]] - start point of cylinder
|
|
||||||
* @param {Vector3D} [options.end=[0,1,0]] - end point of cylinder
|
|
||||||
* @param {Number} [options.radius=1] - radius of rounded ends, must be scalar
|
|
||||||
* @param {Vector3D} [options.normal] - vector determining the starting angle for tesselation. Should be non-parallel to start.minus(end)
|
|
||||||
* @param {Number} [options.resolution=defaultResolution3D] - number of polygons per 360 degree revolution
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let cylinder = CSG.roundedCylinder({
|
|
||||||
* start: [0, -10, 0],
|
|
||||||
* end: [0, 10, 0],
|
|
||||||
* radius: 2,
|
|
||||||
* resolution: 16
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const roundedCylinder = function (options) {
|
|
||||||
let p1 = parseOptionAs3DVector(options, 'start', [0, -1, 0])
|
|
||||||
let p2 = parseOptionAs3DVector(options, 'end', [0, 1, 0])
|
|
||||||
let radius = parseOptionAsFloat(options, 'radius', 1)
|
|
||||||
let direction = p2.minus(p1)
|
|
||||||
let defaultnormal
|
|
||||||
if (Math.abs(direction.x) > Math.abs(direction.y)) {
|
|
||||||
defaultnormal = new Vector3D(0, 1, 0)
|
|
||||||
} else {
|
|
||||||
defaultnormal = new Vector3D(1, 0, 0)
|
|
||||||
}
|
|
||||||
let normal = parseOptionAs3DVector(options, 'normal', defaultnormal)
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
|
||||||
if (resolution < 4) resolution = 4
|
|
||||||
let polygons = []
|
|
||||||
let qresolution = Math.floor(0.25 * resolution)
|
|
||||||
let length = direction.length()
|
|
||||||
if (length < EPS) {
|
|
||||||
return sphere({
|
|
||||||
center: p1,
|
|
||||||
radius: radius,
|
|
||||||
resolution: resolution
|
|
||||||
})
|
|
||||||
}
|
|
||||||
let zvector = direction.unit().times(radius)
|
|
||||||
let xvector = zvector.cross(normal).unit().times(radius)
|
|
||||||
let yvector = xvector.cross(zvector).unit().times(radius)
|
|
||||||
let prevcylinderpoint
|
|
||||||
for (let slice1 = 0; slice1 <= resolution; slice1++) {
|
|
||||||
let angle = Math.PI * 2.0 * slice1 / resolution
|
|
||||||
let cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle)))
|
|
||||||
if (slice1 > 0) {
|
|
||||||
// cylinder vertices:
|
|
||||||
let vertices = []
|
|
||||||
vertices.push(new Vertex(p1.plus(cylinderpoint)))
|
|
||||||
vertices.push(new Vertex(p1.plus(prevcylinderpoint)))
|
|
||||||
vertices.push(new Vertex(p2.plus(prevcylinderpoint)))
|
|
||||||
vertices.push(new Vertex(p2.plus(cylinderpoint)))
|
|
||||||
polygons.push(new Polygon(vertices))
|
|
||||||
let prevcospitch, prevsinpitch
|
|
||||||
for (let slice2 = 0; slice2 <= qresolution; slice2++) {
|
|
||||||
let pitch = 0.5 * Math.PI * slice2 / qresolution
|
|
||||||
// let pitch = Math.asin(slice2/qresolution);
|
|
||||||
let cospitch = Math.cos(pitch)
|
|
||||||
let sinpitch = Math.sin(pitch)
|
|
||||||
if (slice2 > 0) {
|
|
||||||
vertices = []
|
|
||||||
vertices.push(new Vertex(p1.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
|
||||||
vertices.push(new Vertex(p1.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))))
|
|
||||||
if (slice2 < qresolution) {
|
|
||||||
vertices.push(new Vertex(p1.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
|
||||||
}
|
|
||||||
vertices.push(new Vertex(p1.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))))
|
|
||||||
polygons.push(new Polygon(vertices))
|
|
||||||
vertices = []
|
|
||||||
vertices.push(new Vertex(p2.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
|
||||||
vertices.push(new Vertex(p2.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))))
|
|
||||||
if (slice2 < qresolution) {
|
|
||||||
vertices.push(new Vertex(p2.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
|
||||||
}
|
|
||||||
vertices.push(new Vertex(p2.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))))
|
|
||||||
vertices.reverse()
|
|
||||||
polygons.push(new Polygon(vertices))
|
|
||||||
}
|
|
||||||
prevcospitch = cospitch
|
|
||||||
prevsinpitch = sinpitch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevcylinderpoint = cylinderpoint
|
|
||||||
}
|
|
||||||
let result = CSG.fromPolygons(polygons)
|
|
||||||
let ray = zvector.unit()
|
|
||||||
let axisX = xvector.unit()
|
|
||||||
result.properties.roundedCylinder = new Properties()
|
|
||||||
result.properties.roundedCylinder.start = new Connector(p1, ray.negated(), axisX)
|
|
||||||
result.properties.roundedCylinder.end = new Connector(p2, ray, axisX)
|
|
||||||
result.properties.roundedCylinder.facepoint = p1.plus(xvector)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct an elliptic cylinder.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector3D} [options.start=[0,-1,0]] - start point of cylinder
|
|
||||||
* @param {Vector3D} [options.end=[0,1,0]] - end point of cylinder
|
|
||||||
* @param {Vector2D} [options.radius=[1,1]] - radius of rounded ends, must be two dimensional array
|
|
||||||
* @param {Vector2D} [options.radiusStart=[1,1]] - OPTIONAL radius of rounded start, must be two dimensional array
|
|
||||||
* @param {Vector2D} [options.radiusEnd=[1,1]] - OPTIONAL radius of rounded end, must be two dimensional array
|
|
||||||
* @param {Number} [options.resolution=defaultResolution2D] - number of polygons per 360 degree revolution
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let cylinder = CSG.cylinderElliptic({
|
|
||||||
* start: [0, -10, 0],
|
|
||||||
* end: [0, 10, 0],
|
|
||||||
* radiusStart: [10,5],
|
|
||||||
* radiusEnd: [8,3],
|
|
||||||
* resolution: 16
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
|
|
||||||
const cylinderElliptic = function (options) {
|
|
||||||
let s = parseOptionAs3DVector(options, 'start', [0, -1, 0])
|
|
||||||
let e = parseOptionAs3DVector(options, 'end', [0, 1, 0])
|
|
||||||
let r = parseOptionAs2DVector(options, 'radius', [1, 1])
|
|
||||||
let rEnd = parseOptionAs2DVector(options, 'radiusEnd', r)
|
|
||||||
let rStart = parseOptionAs2DVector(options, 'radiusStart', r)
|
|
||||||
|
|
||||||
if ((rEnd._x < 0) || (rStart._x < 0) || (rEnd._y < 0) || (rStart._y < 0)) {
|
|
||||||
throw new Error('Radius should be non-negative')
|
|
||||||
}
|
|
||||||
if ((rEnd._x === 0 || rEnd._y === 0) && (rStart._x === 0 || rStart._y === 0)) {
|
|
||||||
throw new Error('Either radiusStart or radiusEnd should be positive')
|
|
||||||
}
|
|
||||||
|
|
||||||
let slices = parseOptionAsInt(options, 'resolution', defaultResolution2D) // FIXME is this correct?
|
|
||||||
let ray = e.minus(s)
|
|
||||||
let axisZ = ray.unit() //, isY = (Math.abs(axisZ.y) > 0.5);
|
|
||||||
let axisX = axisZ.randomNonParallelVector().unit()
|
|
||||||
|
|
||||||
// let axisX = new Vector3D(isY, !isY, 0).cross(axisZ).unit();
|
|
||||||
let axisY = axisX.cross(axisZ).unit()
|
|
||||||
let start = new Vertex(s)
|
|
||||||
let end = new Vertex(e)
|
|
||||||
let polygons = []
|
|
||||||
|
|
||||||
function point (stack, slice, radius) {
|
|
||||||
let angle = slice * Math.PI * 2
|
|
||||||
let out = axisX.times(radius._x * Math.cos(angle)).plus(axisY.times(radius._y * Math.sin(angle)))
|
|
||||||
let pos = s.plus(ray.times(stack)).plus(out)
|
|
||||||
return new Vertex(pos)
|
|
||||||
}
|
|
||||||
for (let i = 0; i < slices; i++) {
|
|
||||||
let t0 = i / slices
|
|
||||||
let t1 = (i + 1) / slices
|
|
||||||
|
|
||||||
if (rEnd._x === rStart._x && rEnd._y === rStart._y) {
|
|
||||||
polygons.push(new Polygon([start, point(0, t0, rEnd), point(0, t1, rEnd)]))
|
|
||||||
polygons.push(new Polygon([point(0, t1, rEnd), point(0, t0, rEnd), point(1, t0, rEnd), point(1, t1, rEnd)]))
|
|
||||||
polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
|
||||||
} else {
|
|
||||||
if (rStart._x > 0) {
|
|
||||||
polygons.push(new Polygon([start, point(0, t0, rStart), point(0, t1, rStart)]))
|
|
||||||
polygons.push(new Polygon([point(0, t0, rStart), point(1, t0, rEnd), point(0, t1, rStart)]))
|
|
||||||
}
|
|
||||||
if (rEnd._x > 0) {
|
|
||||||
polygons.push(new Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]))
|
|
||||||
polygons.push(new Polygon([point(1, t0, rEnd), point(1, t1, rEnd), point(0, t1, rStart)]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = CSG.fromPolygons(polygons)
|
|
||||||
result.properties.cylinder = new Properties()
|
|
||||||
result.properties.cylinder.start = new Connector(s, axisZ.negated(), axisX)
|
|
||||||
result.properties.cylinder.end = new Connector(e, axisZ, axisX)
|
|
||||||
result.properties.cylinder.facepoint = s.plus(axisX.times(rStart))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct an axis-aligned solid rounded cuboid.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @param {Vector3D} [options.center=[0,0,0]] - center of rounded cube
|
|
||||||
* @param {Vector3D} [options.radius=[1,1,1]] - radius of rounded cube, single scalar is possible
|
|
||||||
* @param {Number} [options.roundradius=0.2] - radius of rounded edges
|
|
||||||
* @param {Number} [options.resolution=defaultResolution3D] - number of polygons per 360 degree revolution
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* let cube = CSG.roundedCube({
|
|
||||||
* center: [2, 0, 2],
|
|
||||||
* radius: 15,
|
|
||||||
* roundradius: 2,
|
|
||||||
* resolution: 36,
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
const roundedCube = function (options) {
|
|
||||||
let minRR = 1e-2 // minroundradius 1e-3 gives rounding errors already
|
|
||||||
let center
|
|
||||||
let cuberadius
|
|
||||||
let corner1
|
|
||||||
let corner2
|
|
||||||
options = options || {}
|
|
||||||
if (('corner1' in options) || ('corner2' in options)) {
|
|
||||||
if (('center' in options) || ('radius' in options)) {
|
|
||||||
throw new Error('roundedCube: should either give a radius and center parameter, or a corner1 and corner2 parameter')
|
|
||||||
}
|
|
||||||
corner1 = parseOptionAs3DVector(options, 'corner1', [0, 0, 0])
|
|
||||||
corner2 = parseOptionAs3DVector(options, 'corner2', [1, 1, 1])
|
|
||||||
center = corner1.plus(corner2).times(0.5)
|
|
||||||
cuberadius = corner2.minus(corner1).times(0.5)
|
|
||||||
} else {
|
|
||||||
center = parseOptionAs3DVector(options, 'center', [0, 0, 0])
|
|
||||||
cuberadius = parseOptionAs3DVector(options, 'radius', [1, 1, 1])
|
|
||||||
}
|
|
||||||
cuberadius = cuberadius.abs() // negative radii make no sense
|
|
||||||
let resolution = parseOptionAsInt(options, 'resolution', defaultResolution3D)
|
|
||||||
if (resolution < 4) resolution = 4
|
|
||||||
if (resolution % 2 === 1 && resolution < 8) resolution = 8 // avoid ugly
|
|
||||||
let roundradius = parseOptionAs3DVector(options, 'roundradius', [0.2, 0.2, 0.2])
|
|
||||||
// slight hack for now - total radius stays ok
|
|
||||||
roundradius = Vector3D.Create(Math.max(roundradius.x, minRR), Math.max(roundradius.y, minRR), Math.max(roundradius.z, minRR))
|
|
||||||
let innerradius = cuberadius.minus(roundradius)
|
|
||||||
if (innerradius.x < 0 || innerradius.y < 0 || innerradius.z < 0) {
|
|
||||||
throw new Error('roundradius <= radius!')
|
|
||||||
}
|
|
||||||
let res = sphere({radius: 1, resolution: resolution})
|
|
||||||
res = res.scale(roundradius)
|
|
||||||
innerradius.x > EPS && (res = res.stretchAtPlane([1, 0, 0], [0, 0, 0], 2 * innerradius.x))
|
|
||||||
innerradius.y > EPS && (res = res.stretchAtPlane([0, 1, 0], [0, 0, 0], 2 * innerradius.y))
|
|
||||||
innerradius.z > EPS && (res = res.stretchAtPlane([0, 0, 1], [0, 0, 0], 2 * innerradius.z))
|
|
||||||
res = res.translate([-innerradius.x + center.x, -innerradius.y + center.y, -innerradius.z + center.z])
|
|
||||||
res = res.reTesselated()
|
|
||||||
res.properties.roundedCube = new Properties()
|
|
||||||
res.properties.roundedCube.center = new Vertex(center)
|
|
||||||
res.properties.roundedCube.facecenters = [
|
|
||||||
new Connector(new Vector3D([cuberadius.x, 0, 0]).plus(center), [1, 0, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([-cuberadius.x, 0, 0]).plus(center), [-1, 0, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([0, cuberadius.y, 0]).plus(center), [0, 1, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([0, -cuberadius.y, 0]).plus(center), [0, -1, 0], [0, 0, 1]),
|
|
||||||
new Connector(new Vector3D([0, 0, cuberadius.z]).plus(center), [0, 0, 1], [1, 0, 0]),
|
|
||||||
new Connector(new Vector3D([0, 0, -cuberadius.z]).plus(center), [0, 0, -1], [1, 0, 0])
|
|
||||||
]
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a polyhedron using Openscad style arguments.
|
|
||||||
* Define face vertices clockwise looking from outside.
|
|
||||||
* @param {Object} [options] - options for construction
|
|
||||||
* @returns {CSG} new 3D solid
|
|
||||||
*/
|
|
||||||
const polyhedron = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
if (('points' in options) !== ('faces' in options)) {
|
|
||||||
throw new Error("polyhedron needs 'points' and 'faces' arrays")
|
|
||||||
}
|
|
||||||
let vertices = parseOptionAs3DVectorList(options, 'points', [
|
|
||||||
[1, 1, 0],
|
|
||||||
[1, -1, 0],
|
|
||||||
[-1, -1, 0],
|
|
||||||
[-1, 1, 0],
|
|
||||||
[0, 0, 1]
|
|
||||||
])
|
|
||||||
.map(function (pt) {
|
|
||||||
return new Vertex(pt)
|
|
||||||
})
|
|
||||||
let faces = parseOption(options, 'faces', [
|
|
||||||
[0, 1, 4],
|
|
||||||
[1, 2, 4],
|
|
||||||
[2, 3, 4],
|
|
||||||
[3, 0, 4],
|
|
||||||
[1, 0, 3],
|
|
||||||
[2, 1, 3]
|
|
||||||
])
|
|
||||||
// Openscad convention defines inward normals - so we have to invert here
|
|
||||||
faces.forEach(function (face) {
|
|
||||||
face.reverse()
|
|
||||||
})
|
|
||||||
let polygons = faces.map(function (face) {
|
|
||||||
return new Polygon(face.map(function (idx) {
|
|
||||||
return vertices[idx]
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: facecenters as connectors? probably overkill. Maybe centroid
|
|
||||||
// the re-tesselation here happens because it's so easy for a user to
|
|
||||||
// create parametrized polyhedrons that end up with 1-2 dimensional polygons.
|
|
||||||
// These will create infinite loops at CSG.Tree()
|
|
||||||
return CSG.fromPolygons(polygons).reTesselated()
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
cube,
|
|
||||||
sphere,
|
|
||||||
roundedCube,
|
|
||||||
cylinder,
|
|
||||||
roundedCylinder,
|
|
||||||
cylinderElliptic,
|
|
||||||
polyhedron
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user