Added option for labels/no labels (Papercraft Unfold)
This commit is contained in:
parent
b80abd0d85
commit
7c536aa80b
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5Core.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5Core.dll
Normal file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5Gui.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5Gui.dll
Normal file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5OpenGL.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5OpenGL.dll
Normal file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5Widgets.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/Qt5Widgets.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/libEGL.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/libEGL.dll
Normal file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/libGLESv2.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/libGLESv2.dll
Normal file
Binary file not shown.
BIN
extensions/fablabchemnitz/papercraft/fstl/platforms/qwindows.dll
Normal file
BIN
extensions/fablabchemnitz/papercraft/fstl/platforms/qwindows.dll
Normal file
Binary file not shown.
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring
generated
vendored
Normal file
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/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
Normal file
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring.cmd
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@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
Normal file
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/astring.ps1
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/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
Normal file
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/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
Normal file
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse.cmd
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@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
Normal file
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esparse.ps1
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/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
Normal file
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/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
Normal file
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate.cmd
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@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
Normal file
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/esvalidate.ps1
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/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
Normal file
BIN
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/node.exe
generated
vendored
Normal file
Binary file not shown.
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad
generated
vendored
Normal file
15
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/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
Normal file
17
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad.cmd
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@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
Normal file
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/.bin/openjscad.ps1
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/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
|
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/CHANGELOG.md
generated
vendored
Normal file
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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
|
50
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/README.md
generated
vendored
Normal file
50
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/README.md
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
## @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
Normal file
728
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/index.js
generated
vendored
Normal file
@ -0,0 +1,728 @@
|
|||||||
|
/*
|
||||||
|
## 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
|
||||||
|
}
|
71
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/package.json
generated
vendored
Normal file
71
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/package.json
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"_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"
|
||||||
|
}
|
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/test.js
generated
vendored
Normal file
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-deserializer/test.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const deserializer = require('./index.js')
|
||||||
|
|
||||||
|
test.todo('add some actual tests later')
|
36
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/CHANGELOG.md
generated
vendored
Normal file
36
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# 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
|
54
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/README.md
generated
vendored
Normal file
54
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/README.md
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
## @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)
|
70
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/index.js
generated
vendored
Normal file
70
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/index.js
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
}
|
76
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/package.json
generated
vendored
Normal file
76
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/package.json
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"_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"
|
||||||
|
}
|
11
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/test.js
generated
vendored
Normal file
11
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/amf-serializer/test.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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
Normal file
142
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<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
Normal file
73
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
## 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
Normal file
22
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
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
Normal file
67
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/README.md
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
## 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
Normal file
189
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/csg.js
generated
vendored
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
## 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
Normal file
1089
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/docs/api.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
108
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/package.json
generated
vendored
Normal file
108
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/package.json
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"_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
Normal file
816
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CAG.js
generated
vendored
Normal file
@ -0,0 +1,816 @@
|
|||||||
|
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
|
58
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CAGFactories.js
generated
vendored
Normal file
58
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CAGFactories.js
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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
Normal file
969
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CSG.js
generated
vendored
Normal file
@ -0,0 +1,969 @@
|
|||||||
|
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
Normal file
111
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/CSGFactories.js
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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
|
||||||
|
}
|
56
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/FuzzyFactory.js
generated
vendored
Normal file
56
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/FuzzyFactory.js
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// //////////////////////////////
|
||||||
|
// ## 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
|
25
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/FuzzyFactory2d.js
generated
vendored
Normal file
25
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/FuzzyFactory2d.js
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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
|
68
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/FuzzyFactory3d.js
generated
vendored
Normal file
68
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/FuzzyFactory3d.js
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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
Normal file
82
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/Properties.js
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// ////////////////////////////////////
|
||||||
|
// # 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
Normal file
220
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/connectors.js
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
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
Normal file
55
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/constants.js
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
}
|
33
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/debugHelpers.js
generated
vendored
Normal file
33
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/debugHelpers.js
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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
Normal file
90
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Line2.js
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
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
Normal file
100
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Line3.js
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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
Normal file
284
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Matrix4.js
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
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
|
202
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/OrthoNormalBasis.js
generated
vendored
Normal file
202
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/OrthoNormalBasis.js
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
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
Normal file
472
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Path2.js
generated
vendored
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
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
Normal file
140
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Plane.js
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
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
|
19
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Polygon2.js
generated
vendored
Normal file
19
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Polygon2.js
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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
Normal file
575
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Polygon3.js
generated
vendored
Normal file
@ -0,0 +1,575 @@
|
|||||||
|
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
Normal file
102
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Side.js
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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
Normal file
196
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vector2.js
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
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
Normal file
213
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vector3.js
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
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
|
26
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vertex2.js
generated
vendored
Normal file
26
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vertex2.js
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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
|
56
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vertex3.js
generated
vendored
Normal file
56
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/Vertex3.js
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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
|
25
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/lineUtils.js
generated
vendored
Normal file
25
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/lineUtils.js
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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}
|
342
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/polygonUtils.js
generated
vendored
Normal file
342
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/math/polygonUtils.js
generated
vendored
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
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
Normal file
81
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/mutators.js
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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
|
||||||
|
}
|
76
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/optionParsers.js
generated
vendored
Normal file
76
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/optionParsers.js
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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
Normal file
184
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/primitives2d.js
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
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
Normal file
548
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/primitives3d.js
generated
vendored
Normal file
@ -0,0 +1,548 @@
|
|||||||
|
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
|
||||||
|
}
|
510
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/trees.js
generated
vendored
Normal file
510
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/trees.js
generated
vendored
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
const {_CSGDEBUG, EPS} = require('./constants')
|
||||||
|
const Vertex = require('./math/Vertex3')
|
||||||
|
const Polygon = require('./math/Polygon3')
|
||||||
|
|
||||||
|
// Returns object:
|
||||||
|
// .type:
|
||||||
|
// 0: coplanar-front
|
||||||
|
// 1: coplanar-back
|
||||||
|
// 2: front
|
||||||
|
// 3: back
|
||||||
|
// 4: spanning
|
||||||
|
// In case the polygon is spanning, returns:
|
||||||
|
// .front: a Polygon of the front part
|
||||||
|
// .back: a Polygon of the back part
|
||||||
|
function splitPolygonByPlane (plane, polygon) {
|
||||||
|
let result = {
|
||||||
|
type: null,
|
||||||
|
front: null,
|
||||||
|
back: null
|
||||||
|
}
|
||||||
|
// cache in local lets (speedup):
|
||||||
|
let planenormal = plane.normal
|
||||||
|
let vertices = polygon.vertices
|
||||||
|
let numvertices = vertices.length
|
||||||
|
if (polygon.plane.equals(plane)) {
|
||||||
|
result.type = 0
|
||||||
|
} else {
|
||||||
|
let thisw = plane.w
|
||||||
|
let hasfront = false
|
||||||
|
let hasback = false
|
||||||
|
let vertexIsBack = []
|
||||||
|
let MINEPS = -EPS
|
||||||
|
for (let i = 0; i < numvertices; i++) {
|
||||||
|
let t = planenormal.dot(vertices[i].pos) - thisw
|
||||||
|
let isback = (t < 0)
|
||||||
|
vertexIsBack.push(isback)
|
||||||
|
if (t > EPS) hasfront = true
|
||||||
|
if (t < MINEPS) hasback = true
|
||||||
|
}
|
||||||
|
if ((!hasfront) && (!hasback)) {
|
||||||
|
// all points coplanar
|
||||||
|
let t = planenormal.dot(polygon.plane.normal)
|
||||||
|
result.type = (t >= 0) ? 0 : 1
|
||||||
|
} else if (!hasback) {
|
||||||
|
result.type = 2
|
||||||
|
} else if (!hasfront) {
|
||||||
|
result.type = 3
|
||||||
|
} else {
|
||||||
|
// spanning
|
||||||
|
result.type = 4
|
||||||
|
let frontvertices = []
|
||||||
|
let backvertices = []
|
||||||
|
let isback = vertexIsBack[0]
|
||||||
|
for (let vertexindex = 0; vertexindex < numvertices; vertexindex++) {
|
||||||
|
let vertex = vertices[vertexindex]
|
||||||
|
let nextvertexindex = vertexindex + 1
|
||||||
|
if (nextvertexindex >= numvertices) nextvertexindex = 0
|
||||||
|
let nextisback = vertexIsBack[nextvertexindex]
|
||||||
|
if (isback === nextisback) {
|
||||||
|
// line segment is on one side of the plane:
|
||||||
|
if (isback) {
|
||||||
|
backvertices.push(vertex)
|
||||||
|
} else {
|
||||||
|
frontvertices.push(vertex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// line segment intersects plane:
|
||||||
|
let point = vertex.pos
|
||||||
|
let nextpoint = vertices[nextvertexindex].pos
|
||||||
|
let intersectionpoint = plane.splitLineBetweenPoints(point, nextpoint)
|
||||||
|
let intersectionvertex = new Vertex(intersectionpoint)
|
||||||
|
if (isback) {
|
||||||
|
backvertices.push(vertex)
|
||||||
|
backvertices.push(intersectionvertex)
|
||||||
|
frontvertices.push(intersectionvertex)
|
||||||
|
} else {
|
||||||
|
frontvertices.push(vertex)
|
||||||
|
frontvertices.push(intersectionvertex)
|
||||||
|
backvertices.push(intersectionvertex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isback = nextisback
|
||||||
|
} // for vertexindex
|
||||||
|
// remove duplicate vertices:
|
||||||
|
let EPS_SQUARED = EPS * EPS
|
||||||
|
if (backvertices.length >= 3) {
|
||||||
|
let prevvertex = backvertices[backvertices.length - 1]
|
||||||
|
for (let vertexindex = 0; vertexindex < backvertices.length; vertexindex++) {
|
||||||
|
let vertex = backvertices[vertexindex]
|
||||||
|
if (vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) {
|
||||||
|
backvertices.splice(vertexindex, 1)
|
||||||
|
vertexindex--
|
||||||
|
}
|
||||||
|
prevvertex = vertex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (frontvertices.length >= 3) {
|
||||||
|
let prevvertex = frontvertices[frontvertices.length - 1]
|
||||||
|
for (let vertexindex = 0; vertexindex < frontvertices.length; vertexindex++) {
|
||||||
|
let vertex = frontvertices[vertexindex]
|
||||||
|
if (vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) {
|
||||||
|
frontvertices.splice(vertexindex, 1)
|
||||||
|
vertexindex--
|
||||||
|
}
|
||||||
|
prevvertex = vertex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (frontvertices.length >= 3) {
|
||||||
|
result.front = new Polygon(frontvertices, polygon.shared, polygon.plane)
|
||||||
|
}
|
||||||
|
if (backvertices.length >= 3) {
|
||||||
|
result.back = new Polygon(backvertices, polygon.shared, polygon.plane)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// # class PolygonTreeNode
|
||||||
|
// This class manages hierarchical splits of polygons
|
||||||
|
// At the top is a root node which doesn hold a polygon, only child PolygonTreeNodes
|
||||||
|
// Below that are zero or more 'top' nodes; each holds a polygon. The polygons can be in different planes
|
||||||
|
// splitByPlane() splits a node by a plane. If the plane intersects the polygon, two new child nodes
|
||||||
|
// are created holding the splitted polygon.
|
||||||
|
// getPolygons() retrieves the polygon from the tree. If for PolygonTreeNode the polygon is split but
|
||||||
|
// the two split parts (child nodes) are still intact, then the unsplit polygon is returned.
|
||||||
|
// This ensures that we can safely split a polygon into many fragments. If the fragments are untouched,
|
||||||
|
// getPolygons() will return the original unsplit polygon instead of the fragments.
|
||||||
|
// remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated
|
||||||
|
// since they are no longer intact.
|
||||||
|
// constructor creates the root node:
|
||||||
|
const PolygonTreeNode = function () {
|
||||||
|
this.parent = null
|
||||||
|
this.children = []
|
||||||
|
this.polygon = null
|
||||||
|
this.removed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
PolygonTreeNode.prototype = {
|
||||||
|
// fill the tree with polygons. Should be called on the root node only; child nodes must
|
||||||
|
// always be a derivate (split) of the parent node.
|
||||||
|
addPolygons: function (polygons) {
|
||||||
|
if (!this.isRootNode())
|
||||||
|
// new polygons can only be added to root node; children can only be splitted polygons
|
||||||
|
{
|
||||||
|
throw new Error('Assertion failed')
|
||||||
|
}
|
||||||
|
let _this = this
|
||||||
|
polygons.map(function (polygon) {
|
||||||
|
_this.addChild(polygon)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// remove a node
|
||||||
|
// - the siblings become toplevel nodes
|
||||||
|
// - the parent is removed recursively
|
||||||
|
remove: function () {
|
||||||
|
if (!this.removed) {
|
||||||
|
this.removed = true
|
||||||
|
|
||||||
|
if (_CSGDEBUG) {
|
||||||
|
if (this.isRootNode()) throw new Error('Assertion failed') // can't remove root node
|
||||||
|
if (this.children.length) throw new Error('Assertion failed') // we shouldn't remove nodes with children
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove ourselves from the parent's children list:
|
||||||
|
let parentschildren = this.parent.children
|
||||||
|
let i = parentschildren.indexOf(this)
|
||||||
|
if (i < 0) throw new Error('Assertion failed')
|
||||||
|
parentschildren.splice(i, 1)
|
||||||
|
|
||||||
|
// invalidate the parent's polygon, and of all parents above it:
|
||||||
|
this.parent.recursivelyInvalidatePolygon()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isRemoved: function () {
|
||||||
|
return this.removed
|
||||||
|
},
|
||||||
|
|
||||||
|
isRootNode: function () {
|
||||||
|
return !this.parent
|
||||||
|
},
|
||||||
|
|
||||||
|
// invert all polygons in the tree. Call on the root node
|
||||||
|
invert: function () {
|
||||||
|
if (!this.isRootNode()) throw new Error('Assertion failed') // can only call this on the root node
|
||||||
|
this.invertSub()
|
||||||
|
},
|
||||||
|
|
||||||
|
getPolygon: function () {
|
||||||
|
if (!this.polygon) throw new Error('Assertion failed') // doesn't have a polygon, which means that it has been broken down
|
||||||
|
return this.polygon
|
||||||
|
},
|
||||||
|
|
||||||
|
getPolygons: function (result) {
|
||||||
|
let children = [this]
|
||||||
|
let queue = [children]
|
||||||
|
let i, j, l, node
|
||||||
|
for (i = 0; i < queue.length; ++i) { // queue size can change in loop, don't cache length
|
||||||
|
children = queue[i]
|
||||||
|
for (j = 0, l = children.length; j < l; j++) { // ok to cache length
|
||||||
|
node = children[j]
|
||||||
|
if (node.polygon) {
|
||||||
|
// the polygon hasn't been broken yet. We can ignore the children and return our polygon:
|
||||||
|
result.push(node.polygon)
|
||||||
|
} else {
|
||||||
|
// our polygon has been split up and broken, so gather all subpolygons from the children
|
||||||
|
queue.push(node.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// split the node by a plane; add the resulting nodes to the frontnodes and backnodes array
|
||||||
|
// If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays
|
||||||
|
// If the plane does intersect the polygon, two new child nodes are created for the front and back fragments,
|
||||||
|
// and added to both arrays.
|
||||||
|
splitByPlane: function (plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes) {
|
||||||
|
if (this.children.length) {
|
||||||
|
let queue = [this.children]
|
||||||
|
let i
|
||||||
|
let j
|
||||||
|
let l
|
||||||
|
let node
|
||||||
|
let nodes
|
||||||
|
for (i = 0; i < queue.length; i++) { // queue.length can increase, do not cache
|
||||||
|
nodes = queue[i]
|
||||||
|
for (j = 0, l = nodes.length; j < l; j++) { // ok to cache length
|
||||||
|
node = nodes[j]
|
||||||
|
if (node.children.length) {
|
||||||
|
queue.push(node.children)
|
||||||
|
} else {
|
||||||
|
// no children. Split the polygon:
|
||||||
|
node._splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// only to be called for nodes with no children
|
||||||
|
_splitByPlane: function (plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes) {
|
||||||
|
let polygon = this.polygon
|
||||||
|
if (polygon) {
|
||||||
|
let bound = polygon.boundingSphere()
|
||||||
|
let sphereradius = bound[1] + EPS // FIXME Why add imprecision?
|
||||||
|
let planenormal = plane.normal
|
||||||
|
let spherecenter = bound[0]
|
||||||
|
let d = planenormal.dot(spherecenter) - plane.w
|
||||||
|
if (d > sphereradius) {
|
||||||
|
frontnodes.push(this)
|
||||||
|
} else if (d < -sphereradius) {
|
||||||
|
backnodes.push(this)
|
||||||
|
} else {
|
||||||
|
let splitresult = splitPolygonByPlane(plane, polygon)
|
||||||
|
switch (splitresult.type) {
|
||||||
|
case 0:
|
||||||
|
// coplanar front:
|
||||||
|
coplanarfrontnodes.push(this)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// coplanar back:
|
||||||
|
coplanarbacknodes.push(this)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// front:
|
||||||
|
frontnodes.push(this)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
// back:
|
||||||
|
backnodes.push(this)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
// spanning:
|
||||||
|
if (splitresult.front) {
|
||||||
|
let frontnode = this.addChild(splitresult.front)
|
||||||
|
frontnodes.push(frontnode)
|
||||||
|
}
|
||||||
|
if (splitresult.back) {
|
||||||
|
let backnode = this.addChild(splitresult.back)
|
||||||
|
backnodes.push(backnode)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// PRIVATE methods from here:
|
||||||
|
// add child to a node
|
||||||
|
// this should be called whenever the polygon is split
|
||||||
|
// a child should be created for every fragment of the split polygon
|
||||||
|
// returns the newly created child
|
||||||
|
addChild: function (polygon) {
|
||||||
|
let newchild = new PolygonTreeNode()
|
||||||
|
newchild.parent = this
|
||||||
|
newchild.polygon = polygon
|
||||||
|
this.children.push(newchild)
|
||||||
|
return newchild
|
||||||
|
},
|
||||||
|
|
||||||
|
invertSub: function () {
|
||||||
|
let children = [this]
|
||||||
|
let queue = [children]
|
||||||
|
let i, j, l, node
|
||||||
|
for (i = 0; i < queue.length; i++) {
|
||||||
|
children = queue[i]
|
||||||
|
for (j = 0, l = children.length; j < l; j++) {
|
||||||
|
node = children[j]
|
||||||
|
if (node.polygon) {
|
||||||
|
node.polygon = node.polygon.flipped()
|
||||||
|
}
|
||||||
|
queue.push(node.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
recursivelyInvalidatePolygon: function () {
|
||||||
|
let node = this
|
||||||
|
while (node.polygon) {
|
||||||
|
node.polygon = null
|
||||||
|
if (node.parent) {
|
||||||
|
node = node.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// # class Tree
|
||||||
|
// This is the root of a BSP tree
|
||||||
|
// We are using this separate class for the root of the tree, to hold the PolygonTreeNode root
|
||||||
|
// The actual tree is kept in this.rootnode
|
||||||
|
const Tree = function (polygons) {
|
||||||
|
this.polygonTree = new PolygonTreeNode()
|
||||||
|
this.rootnode = new Node(null)
|
||||||
|
if (polygons) this.addPolygons(polygons)
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.prototype = {
|
||||||
|
invert: function () {
|
||||||
|
this.polygonTree.invert()
|
||||||
|
this.rootnode.invert()
|
||||||
|
},
|
||||||
|
|
||||||
|
// Remove all polygons in this BSP tree that are inside the other BSP tree
|
||||||
|
// `tree`.
|
||||||
|
clipTo: function (tree, alsoRemovecoplanarFront) {
|
||||||
|
alsoRemovecoplanarFront = alsoRemovecoplanarFront ? true : false
|
||||||
|
this.rootnode.clipTo(tree, alsoRemovecoplanarFront)
|
||||||
|
},
|
||||||
|
|
||||||
|
allPolygons: function () {
|
||||||
|
let result = []
|
||||||
|
this.polygonTree.getPolygons(result)
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
addPolygons: function (polygons) {
|
||||||
|
let _this = this
|
||||||
|
let polygontreenodes = polygons.map(function (p) {
|
||||||
|
return _this.polygonTree.addChild(p)
|
||||||
|
})
|
||||||
|
this.rootnode.addPolygonTreeNodes(polygontreenodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// # class Node
|
||||||
|
// Holds a node in a BSP tree. A BSP tree is built from a collection of polygons
|
||||||
|
// by picking a polygon to split along.
|
||||||
|
// Polygons are not stored directly in the tree, but in PolygonTreeNodes, stored in
|
||||||
|
// this.polygontreenodes. Those PolygonTreeNodes are children of the owning
|
||||||
|
// Tree.polygonTree
|
||||||
|
// This is not a leafy BSP tree since there is
|
||||||
|
// no distinction between internal and leaf nodes.
|
||||||
|
const Node = function (parent) {
|
||||||
|
this.plane = null
|
||||||
|
this.front = null
|
||||||
|
this.back = null
|
||||||
|
this.polygontreenodes = []
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
Node.prototype = {
|
||||||
|
// Convert solid space to empty space and empty space to solid space.
|
||||||
|
invert: function () {
|
||||||
|
let queue = [this]
|
||||||
|
let node
|
||||||
|
for (let i = 0; i < queue.length; i++) {
|
||||||
|
node = queue[i]
|
||||||
|
if (node.plane) node.plane = node.plane.flipped()
|
||||||
|
if (node.front) queue.push(node.front)
|
||||||
|
if (node.back) queue.push(node.back)
|
||||||
|
let temp = node.front
|
||||||
|
node.front = node.back
|
||||||
|
node.back = temp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// clip polygontreenodes to our plane
|
||||||
|
// calls remove() for all clipped PolygonTreeNodes
|
||||||
|
clipPolygons: function (polygontreenodes, alsoRemovecoplanarFront) {
|
||||||
|
let args = {'node': this, 'polygontreenodes': polygontreenodes}
|
||||||
|
let node
|
||||||
|
let stack = []
|
||||||
|
|
||||||
|
do {
|
||||||
|
node = args.node
|
||||||
|
polygontreenodes = args.polygontreenodes
|
||||||
|
|
||||||
|
// begin "function"
|
||||||
|
if (node.plane) {
|
||||||
|
let backnodes = []
|
||||||
|
let frontnodes = []
|
||||||
|
let coplanarfrontnodes = alsoRemovecoplanarFront ? backnodes : frontnodes
|
||||||
|
let plane = node.plane
|
||||||
|
let numpolygontreenodes = polygontreenodes.length
|
||||||
|
for (let i = 0; i < numpolygontreenodes; i++) {
|
||||||
|
let node1 = polygontreenodes[i]
|
||||||
|
if (!node1.isRemoved()) {
|
||||||
|
node1.splitByPlane(plane, coplanarfrontnodes, backnodes, frontnodes, backnodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.front && (frontnodes.length > 0)) {
|
||||||
|
stack.push({'node': node.front, 'polygontreenodes': frontnodes})
|
||||||
|
}
|
||||||
|
let numbacknodes = backnodes.length
|
||||||
|
if (node.back && (numbacknodes > 0)) {
|
||||||
|
stack.push({'node': node.back, 'polygontreenodes': backnodes})
|
||||||
|
} else {
|
||||||
|
// there's nothing behind this plane. Delete the nodes behind this plane:
|
||||||
|
for (let i = 0; i < numbacknodes; i++) {
|
||||||
|
backnodes[i].remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args = stack.pop()
|
||||||
|
} while (typeof (args) !== 'undefined')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Remove all polygons in this BSP tree that are inside the other BSP tree
|
||||||
|
// `tree`.
|
||||||
|
clipTo: function (tree, alsoRemovecoplanarFront) {
|
||||||
|
let node = this
|
||||||
|
let stack = []
|
||||||
|
do {
|
||||||
|
if (node.polygontreenodes.length > 0) {
|
||||||
|
tree.rootnode.clipPolygons(node.polygontreenodes, alsoRemovecoplanarFront)
|
||||||
|
}
|
||||||
|
if (node.front) stack.push(node.front)
|
||||||
|
if (node.back) stack.push(node.back)
|
||||||
|
node = stack.pop()
|
||||||
|
} while (typeof (node) !== 'undefined')
|
||||||
|
},
|
||||||
|
|
||||||
|
addPolygonTreeNodes: function (polygontreenodes) {
|
||||||
|
let args = {'node': this, 'polygontreenodes': polygontreenodes}
|
||||||
|
let node
|
||||||
|
let stack = []
|
||||||
|
do {
|
||||||
|
node = args.node
|
||||||
|
polygontreenodes = args.polygontreenodes
|
||||||
|
|
||||||
|
if (polygontreenodes.length === 0) {
|
||||||
|
args = stack.pop()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let _this = node
|
||||||
|
if (!node.plane) {
|
||||||
|
let bestplane = polygontreenodes[0].getPolygon().plane
|
||||||
|
node.plane = bestplane
|
||||||
|
}
|
||||||
|
let frontnodes = []
|
||||||
|
let backnodes = []
|
||||||
|
|
||||||
|
for (let i = 0, n = polygontreenodes.length; i < n; ++i) {
|
||||||
|
polygontreenodes[i].splitByPlane(_this.plane, _this.polygontreenodes, backnodes, frontnodes, backnodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frontnodes.length > 0) {
|
||||||
|
if (!node.front) node.front = new Node(node)
|
||||||
|
stack.push({'node': node.front, 'polygontreenodes': frontnodes})
|
||||||
|
}
|
||||||
|
if (backnodes.length > 0) {
|
||||||
|
if (!node.back) node.back = new Node(node)
|
||||||
|
stack.push({'node': node.back, 'polygontreenodes': backnodes})
|
||||||
|
}
|
||||||
|
|
||||||
|
args = stack.pop()
|
||||||
|
} while (typeof (args) !== 'undefined')
|
||||||
|
},
|
||||||
|
|
||||||
|
getParentPlaneNormals: function (normals, maxdepth) {
|
||||||
|
if (maxdepth > 0) {
|
||||||
|
if (this.parent) {
|
||||||
|
normals.push(this.parent.plane.normal)
|
||||||
|
this.parent.getParentPlaneNormals(normals, maxdepth - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Tree
|
71
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/utils.js
generated
vendored
Normal file
71
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/utils.js
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
function fnNumberSort (a, b) {
|
||||||
|
return a - b
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnSortByIndex (a, b) {
|
||||||
|
return a.index - b.index
|
||||||
|
}
|
||||||
|
|
||||||
|
const IsFloat = function (n) {
|
||||||
|
return (!isNaN(n)) || (n === Infinity) || (n === -Infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
const solve2Linear = function (a, b, c, d, u, v) {
|
||||||
|
let det = a * d - b * c
|
||||||
|
let invdet = 1.0 / det
|
||||||
|
let x = u * d - b * v
|
||||||
|
let y = -u * c + a * v
|
||||||
|
x *= invdet
|
||||||
|
y *= invdet
|
||||||
|
return [x, y]
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertSorted (array, element, comparefunc) {
|
||||||
|
let leftbound = 0
|
||||||
|
let rightbound = array.length
|
||||||
|
while (rightbound > leftbound) {
|
||||||
|
let testindex = Math.floor((leftbound + rightbound) / 2)
|
||||||
|
let testelement = array[testindex]
|
||||||
|
let compareresult = comparefunc(element, testelement)
|
||||||
|
if (compareresult > 0) // element > testelement
|
||||||
|
{
|
||||||
|
leftbound = testindex + 1
|
||||||
|
} else {
|
||||||
|
rightbound = testindex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
array.splice(leftbound, 0, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the x coordinate of a point with a certain y coordinate, interpolated between two
|
||||||
|
// points (CSG.Vector2D).
|
||||||
|
// Interpolation is robust even if the points have the same y coordinate
|
||||||
|
const interpolateBetween2DPointsForY = function (point1, point2, y) {
|
||||||
|
let f1 = y - point1.y
|
||||||
|
let f2 = point2.y - point1.y
|
||||||
|
if (f2 < 0) {
|
||||||
|
f1 = -f1
|
||||||
|
f2 = -f2
|
||||||
|
}
|
||||||
|
let t
|
||||||
|
if (f1 <= 0) {
|
||||||
|
t = 0.0
|
||||||
|
} else if (f1 >= f2) {
|
||||||
|
t = 1.0
|
||||||
|
} else if (f2 < 1e-10) { // FIXME Should this be CSG.EPS?
|
||||||
|
t = 0.5
|
||||||
|
} else {
|
||||||
|
t = f1 / f2
|
||||||
|
}
|
||||||
|
let result = point1.x + t * (point2.x - point1.x)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fnNumberSort,
|
||||||
|
fnSortByIndex,
|
||||||
|
IsFloat,
|
||||||
|
solve2Linear,
|
||||||
|
insertSorted,
|
||||||
|
interpolateBetween2DPointsForY
|
||||||
|
}
|
316
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/utils/fixTJunctions.js
generated
vendored
Normal file
316
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/utils/fixTJunctions.js
generated
vendored
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
const {EPS} = require('../constants')
|
||||||
|
const Polygon = require('../math/Polygon3')
|
||||||
|
const Plane = require('../math/Plane')
|
||||||
|
|
||||||
|
function addSide (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, vertex1, polygonindex) {
|
||||||
|
let starttag = vertex0.getTag()
|
||||||
|
let endtag = vertex1.getTag()
|
||||||
|
if (starttag === endtag) throw new Error('Assertion failed')
|
||||||
|
let newsidetag = starttag + '/' + endtag
|
||||||
|
let reversesidetag = endtag + '/' + starttag
|
||||||
|
if (reversesidetag in sidemap) {
|
||||||
|
// we have a matching reverse oriented side.
|
||||||
|
// Instead of adding the new side, cancel out the reverse side:
|
||||||
|
// console.log("addSide("+newsidetag+") has reverse side:");
|
||||||
|
deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, vertex1, vertex0, null)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
// console.log("addSide("+newsidetag+")");
|
||||||
|
let newsideobj = {
|
||||||
|
vertex0: vertex0,
|
||||||
|
vertex1: vertex1,
|
||||||
|
polygonindex: polygonindex
|
||||||
|
}
|
||||||
|
if (!(newsidetag in sidemap)) {
|
||||||
|
sidemap[newsidetag] = [newsideobj]
|
||||||
|
} else {
|
||||||
|
sidemap[newsidetag].push(newsideobj)
|
||||||
|
}
|
||||||
|
if (starttag in vertextag2sidestart) {
|
||||||
|
vertextag2sidestart[starttag].push(newsidetag)
|
||||||
|
} else {
|
||||||
|
vertextag2sidestart[starttag] = [newsidetag]
|
||||||
|
}
|
||||||
|
if (endtag in vertextag2sideend) {
|
||||||
|
vertextag2sideend[endtag].push(newsidetag)
|
||||||
|
} else {
|
||||||
|
vertextag2sideend[endtag] = [newsidetag]
|
||||||
|
}
|
||||||
|
return newsidetag
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSide (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, vertex1, polygonindex) {
|
||||||
|
let starttag = vertex0.getTag()
|
||||||
|
let endtag = vertex1.getTag()
|
||||||
|
let sidetag = starttag + '/' + endtag
|
||||||
|
// console.log("deleteSide("+sidetag+")");
|
||||||
|
if (!(sidetag in sidemap)) throw new Error('Assertion failed')
|
||||||
|
let idx = -1
|
||||||
|
let sideobjs = sidemap[sidetag]
|
||||||
|
for (let i = 0; i < sideobjs.length; i++) {
|
||||||
|
let sideobj = sideobjs[i]
|
||||||
|
if (sideobj.vertex0 !== vertex0) continue
|
||||||
|
if (sideobj.vertex1 !== vertex1) continue
|
||||||
|
if (polygonindex !== null) {
|
||||||
|
if (sideobj.polygonindex !== polygonindex) continue
|
||||||
|
}
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (idx < 0) throw new Error('Assertion failed')
|
||||||
|
sideobjs.splice(idx, 1)
|
||||||
|
if (sideobjs.length === 0) {
|
||||||
|
delete sidemap[sidetag]
|
||||||
|
}
|
||||||
|
idx = vertextag2sidestart[starttag].indexOf(sidetag)
|
||||||
|
if (idx < 0) throw new Error('Assertion failed')
|
||||||
|
vertextag2sidestart[starttag].splice(idx, 1)
|
||||||
|
if (vertextag2sidestart[starttag].length === 0) {
|
||||||
|
delete vertextag2sidestart[starttag]
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = vertextag2sideend[endtag].indexOf(sidetag)
|
||||||
|
if (idx < 0) throw new Error('Assertion failed')
|
||||||
|
vertextag2sideend[endtag].splice(idx, 1)
|
||||||
|
if (vertextag2sideend[endtag].length === 0) {
|
||||||
|
delete vertextag2sideend[endtag]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fixTJunctions:
|
||||||
|
|
||||||
|
Suppose we have two polygons ACDB and EDGF:
|
||||||
|
|
||||||
|
A-----B
|
||||||
|
| |
|
||||||
|
| E--F
|
||||||
|
| | |
|
||||||
|
C-----D--G
|
||||||
|
|
||||||
|
Note that vertex E forms a T-junction on the side BD. In this case some STL slicers will complain
|
||||||
|
that the solid is not watertight. This is because the watertightness check is done by checking if
|
||||||
|
each side DE is matched by another side ED.
|
||||||
|
|
||||||
|
This function will return a new solid with ACDB replaced by ACDEB
|
||||||
|
|
||||||
|
Note that this can create polygons that are slightly non-convex (due to rounding errors). Therefore the result should
|
||||||
|
not be used for further CSG operations!
|
||||||
|
*/
|
||||||
|
const fixTJunctions = function (fromPolygons, csg) {
|
||||||
|
csg = csg.canonicalized()
|
||||||
|
let sidemap = {}
|
||||||
|
|
||||||
|
// STEP 1
|
||||||
|
for (let polygonindex = 0; polygonindex < csg.polygons.length; polygonindex++) {
|
||||||
|
let polygon = csg.polygons[polygonindex]
|
||||||
|
let numvertices = polygon.vertices.length
|
||||||
|
// should be true
|
||||||
|
if (numvertices >= 3) {
|
||||||
|
let vertex = polygon.vertices[0]
|
||||||
|
let vertextag = vertex.getTag()
|
||||||
|
for (let vertexindex = 0; vertexindex < numvertices; vertexindex++) {
|
||||||
|
let nextvertexindex = vertexindex + 1
|
||||||
|
if (nextvertexindex === numvertices) nextvertexindex = 0
|
||||||
|
let nextvertex = polygon.vertices[nextvertexindex]
|
||||||
|
let nextvertextag = nextvertex.getTag()
|
||||||
|
let sidetag = vertextag + '/' + nextvertextag
|
||||||
|
let reversesidetag = nextvertextag + '/' + vertextag
|
||||||
|
if (reversesidetag in sidemap) {
|
||||||
|
// this side matches the same side in another polygon. Remove from sidemap:
|
||||||
|
let ar = sidemap[reversesidetag]
|
||||||
|
ar.splice(-1, 1)
|
||||||
|
if (ar.length === 0) {
|
||||||
|
delete sidemap[reversesidetag]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let sideobj = {
|
||||||
|
vertex0: vertex,
|
||||||
|
vertex1: nextvertex,
|
||||||
|
polygonindex: polygonindex
|
||||||
|
}
|
||||||
|
if (!(sidetag in sidemap)) {
|
||||||
|
sidemap[sidetag] = [sideobj]
|
||||||
|
} else {
|
||||||
|
sidemap[sidetag].push(sideobj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vertex = nextvertex
|
||||||
|
vertextag = nextvertextag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// STEP 2
|
||||||
|
// now sidemap contains 'unmatched' sides
|
||||||
|
// i.e. side AB in one polygon does not have a matching side BA in another polygon
|
||||||
|
let vertextag2sidestart = {}
|
||||||
|
let vertextag2sideend = {}
|
||||||
|
let sidestocheck = {}
|
||||||
|
let sidemapisempty = true
|
||||||
|
for (let sidetag in sidemap) {
|
||||||
|
sidemapisempty = false
|
||||||
|
sidestocheck[sidetag] = true
|
||||||
|
sidemap[sidetag].map(function (sideobj) {
|
||||||
|
let starttag = sideobj.vertex0.getTag()
|
||||||
|
let endtag = sideobj.vertex1.getTag()
|
||||||
|
if (starttag in vertextag2sidestart) {
|
||||||
|
vertextag2sidestart[starttag].push(sidetag)
|
||||||
|
} else {
|
||||||
|
vertextag2sidestart[starttag] = [sidetag]
|
||||||
|
}
|
||||||
|
if (endtag in vertextag2sideend) {
|
||||||
|
vertextag2sideend[endtag].push(sidetag)
|
||||||
|
} else {
|
||||||
|
vertextag2sideend[endtag] = [sidetag]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3 : if sidemap is not empty
|
||||||
|
if (!sidemapisempty) {
|
||||||
|
// make a copy of the polygons array, since we are going to modify it:
|
||||||
|
let polygons = csg.polygons.slice(0)
|
||||||
|
while (true) {
|
||||||
|
let sidemapisempty = true
|
||||||
|
for (let sidetag in sidemap) {
|
||||||
|
sidemapisempty = false
|
||||||
|
sidestocheck[sidetag] = true
|
||||||
|
}
|
||||||
|
if (sidemapisempty) break
|
||||||
|
let donesomething = false
|
||||||
|
while (true) {
|
||||||
|
let sidetagtocheck = null
|
||||||
|
for (let sidetag in sidestocheck) {
|
||||||
|
sidetagtocheck = sidetag
|
||||||
|
break // FIXME : say what now ?
|
||||||
|
}
|
||||||
|
if (sidetagtocheck === null) break // sidestocheck is empty, we're done!
|
||||||
|
let donewithside = true
|
||||||
|
if (sidetagtocheck in sidemap) {
|
||||||
|
let sideobjs = sidemap[sidetagtocheck]
|
||||||
|
if (sideobjs.length === 0) throw new Error('Assertion failed')
|
||||||
|
let sideobj = sideobjs[0]
|
||||||
|
for (let directionindex = 0; directionindex < 2; directionindex++) {
|
||||||
|
let startvertex = (directionindex === 0) ? sideobj.vertex0 : sideobj.vertex1
|
||||||
|
let endvertex = (directionindex === 0) ? sideobj.vertex1 : sideobj.vertex0
|
||||||
|
let startvertextag = startvertex.getTag()
|
||||||
|
let endvertextag = endvertex.getTag()
|
||||||
|
let matchingsides = []
|
||||||
|
if (directionindex === 0) {
|
||||||
|
if (startvertextag in vertextag2sideend) {
|
||||||
|
matchingsides = vertextag2sideend[startvertextag]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (startvertextag in vertextag2sidestart) {
|
||||||
|
matchingsides = vertextag2sidestart[startvertextag]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let matchingsideindex = 0; matchingsideindex < matchingsides.length; matchingsideindex++) {
|
||||||
|
let matchingsidetag = matchingsides[matchingsideindex]
|
||||||
|
let matchingside = sidemap[matchingsidetag][0]
|
||||||
|
let matchingsidestartvertex = (directionindex === 0) ? matchingside.vertex0 : matchingside.vertex1
|
||||||
|
let matchingsideendvertex = (directionindex === 0) ? matchingside.vertex1 : matchingside.vertex0
|
||||||
|
let matchingsidestartvertextag = matchingsidestartvertex.getTag()
|
||||||
|
let matchingsideendvertextag = matchingsideendvertex.getTag()
|
||||||
|
if (matchingsideendvertextag !== startvertextag) throw new Error('Assertion failed')
|
||||||
|
if (matchingsidestartvertextag === endvertextag) {
|
||||||
|
// matchingside cancels sidetagtocheck
|
||||||
|
deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, startvertex, endvertex, null)
|
||||||
|
deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, endvertex, startvertex, null)
|
||||||
|
donewithside = false
|
||||||
|
directionindex = 2 // skip reverse direction check
|
||||||
|
donesomething = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
let startpos = startvertex.pos
|
||||||
|
let endpos = endvertex.pos
|
||||||
|
let checkpos = matchingsidestartvertex.pos
|
||||||
|
let direction = checkpos.minus(startpos)
|
||||||
|
// Now we need to check if endpos is on the line startpos-checkpos:
|
||||||
|
let t = endpos.minus(startpos).dot(direction) / direction.dot(direction)
|
||||||
|
if ((t > 0) && (t < 1)) {
|
||||||
|
let closestpoint = startpos.plus(direction.times(t))
|
||||||
|
let distancesquared = closestpoint.distanceToSquared(endpos)
|
||||||
|
if (distancesquared < (EPS * EPS)) {
|
||||||
|
// Yes it's a t-junction! We need to split matchingside in two:
|
||||||
|
let polygonindex = matchingside.polygonindex
|
||||||
|
let polygon = polygons[polygonindex]
|
||||||
|
// find the index of startvertextag in polygon:
|
||||||
|
let insertionvertextag = matchingside.vertex1.getTag()
|
||||||
|
let insertionvertextagindex = -1
|
||||||
|
for (let i = 0; i < polygon.vertices.length; i++) {
|
||||||
|
if (polygon.vertices[i].getTag() === insertionvertextag) {
|
||||||
|
insertionvertextagindex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insertionvertextagindex < 0) throw new Error('Assertion failed')
|
||||||
|
// split the side by inserting the vertex:
|
||||||
|
let newvertices = polygon.vertices.slice(0)
|
||||||
|
newvertices.splice(insertionvertextagindex, 0, endvertex)
|
||||||
|
let newpolygon = new Polygon(newvertices, polygon.shared /* polygon.plane */)
|
||||||
|
|
||||||
|
// calculate plane with differents point
|
||||||
|
if (isNaN(newpolygon.plane.w)) {
|
||||||
|
let found = false
|
||||||
|
let loop = function (callback) {
|
||||||
|
newpolygon.vertices.forEach(function (item) {
|
||||||
|
if (found) return
|
||||||
|
callback(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loop(function (a) {
|
||||||
|
loop(function (b) {
|
||||||
|
loop(function (c) {
|
||||||
|
newpolygon.plane = Plane.fromPoints(a.pos, b.pos, c.pos)
|
||||||
|
if (!isNaN(newpolygon.plane.w)) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
polygons[polygonindex] = newpolygon
|
||||||
|
// remove the original sides from our maps
|
||||||
|
// deleteSide(sideobj.vertex0, sideobj.vertex1, null)
|
||||||
|
deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, matchingside.vertex1, polygonindex)
|
||||||
|
let newsidetag1 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, endvertex, polygonindex)
|
||||||
|
let newsidetag2 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, endvertex, matchingside.vertex1, polygonindex)
|
||||||
|
if (newsidetag1 !== null) sidestocheck[newsidetag1] = true
|
||||||
|
if (newsidetag2 !== null) sidestocheck[newsidetag2] = true
|
||||||
|
donewithside = false
|
||||||
|
directionindex = 2 // skip reverse direction check
|
||||||
|
donesomething = true
|
||||||
|
break
|
||||||
|
} // if(distancesquared < 1e-10)
|
||||||
|
} // if( (t > 0) && (t < 1) )
|
||||||
|
} // if(endingstidestartvertextag === endvertextag)
|
||||||
|
} // for matchingsideindex
|
||||||
|
} // for directionindex
|
||||||
|
} // if(sidetagtocheck in sidemap)
|
||||||
|
if (donewithside) {
|
||||||
|
delete sidestocheck[sidetagtocheck]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!donesomething) break
|
||||||
|
}
|
||||||
|
let newcsg = fromPolygons(polygons)
|
||||||
|
newcsg.properties = csg.properties
|
||||||
|
newcsg.isCanonicalized = true
|
||||||
|
newcsg.isRetesselated = true
|
||||||
|
csg = newcsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME : what is even the point of this ???
|
||||||
|
/* sidemapisempty = true
|
||||||
|
for (let sidetag in sidemap) {
|
||||||
|
sidemapisempty = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return csg
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fixTJunctions
|
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/CHANGELOG.md
generated
vendored
Normal file
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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/dxf-serializer@0.0.3...@jscad/dxf-serializer@0.0.4) (2017-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/dxf-serializer
|
||||||
|
|
||||||
|
<a name="0.0.3"></a>
|
||||||
|
## [0.0.3](https://github.com/jscad/io/compare/@jscad/dxf-serializer@0.0.2...@jscad/dxf-serializer@0.0.3) (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/dxf-serializer
|
||||||
|
|
||||||
|
<a name="0.0.2"></a>
|
||||||
|
## 0.0.2 (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/dxf-serializer
|
54
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/README.md
generated
vendored
Normal file
54
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/README.md
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
## @jscad/dxf-serializer
|
||||||
|
|
||||||
|
> dxf serializer for the jscad project (from CSG)
|
||||||
|
|
||||||
|
[![npm version](https://badge.fury.io/js/%40jscad%2Fdxf-serializer.svg)](https://badge.fury.io/js/%40jscad%2Fdxf-serializer)
|
||||||
|
[![Build Status](https://travis-ci.org/jscad/io.svg)](https://travis-ci.org/jscad/dxf-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/dxf-serializer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const dxfSerializer = require('@jscad/dxf-serializer')
|
||||||
|
|
||||||
|
const rawData = dxfSerializer(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)
|
42
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/index.js
generated
vendored
Normal file
42
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/index.js
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const mimeType = 'application/dxf'
|
||||||
|
|
||||||
|
function serialize (cagObject) {
|
||||||
|
var paths = cagObject.getOutlinePaths()
|
||||||
|
return PathsToDxf(paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PathsToDxf (paths) {
|
||||||
|
var str = '999\nDXF generated by OpenJsCad\n'
|
||||||
|
str += ' 0\nSECTION\n 2\nHEADER\n'
|
||||||
|
str += ' 0\nENDSEC\n'
|
||||||
|
str += ' 0\nSECTION\n 2\nTABLES\n'
|
||||||
|
str += ' 0\nTABLE\n 2\nLTYPE\n 70\n1\n'
|
||||||
|
str += ' 0\nLTYPE\n 2\nCONTINUOUS\n 3\nSolid Line\n 72\n65\n 73\n0\n 40\n0.0\n'
|
||||||
|
str += ' 0\nENDTAB\n'
|
||||||
|
str += ' 0\nTABLE\n 2\nLAYER\n 70\n1\n'
|
||||||
|
str += ' 0\nLAYER\n 2\nOpenJsCad\n 62\n7\n 6\ncontinuous\n'
|
||||||
|
str += ' 0\nENDTAB\n'
|
||||||
|
str += ' 0\nTABLE\n 2\nSTYLE\n 70\n0\n 0\nENDTAB\n'
|
||||||
|
str += ' 0\nTABLE\n 2\nVIEW\n 70\n0\n 0\nENDTAB\n'
|
||||||
|
str += ' 0\nENDSEC\n'
|
||||||
|
str += ' 0\nSECTION\n 2\nBLOCKS\n'
|
||||||
|
str += ' 0\nENDSEC\n'
|
||||||
|
str += ' 0\nSECTION\n 2\nENTITIES\n'
|
||||||
|
paths.map(function (path) {
|
||||||
|
var numpointsClosed = path.points.length + (path.closed ? 1 : 0)
|
||||||
|
str += ' 0\nLWPOLYLINE\n 8\nOpenJsCad\n 90\n' + numpointsClosed + '\n 70\n' + (path.closed ? 1 : 0) + '\n'
|
||||||
|
for (var pointindex = 0; pointindex < numpointsClosed; pointindex++) {
|
||||||
|
var pointindexwrapped = pointindex
|
||||||
|
if (pointindexwrapped >= path.points.length) pointindexwrapped -= path.points.length
|
||||||
|
var point = path.points[pointindexwrapped]
|
||||||
|
str += ' 10\n' + point.x + '\n 20\n' + point.y + '\n 30\n0.0\n'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
str += ' 0\nENDSEC\n 0\nEOF\n'
|
||||||
|
return [str]
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
serialize,
|
||||||
|
mimeType
|
||||||
|
}
|
68
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/package.json
generated
vendored
Normal file
68
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/package.json
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"_from": "@jscad/dxf-serializer@^0.0.4",
|
||||||
|
"_id": "@jscad/dxf-serializer@0.0.4",
|
||||||
|
"_inBundle": false,
|
||||||
|
"_integrity": "sha1-P7O3a2HmqYfiUIJ8SWNPAHNvasg=",
|
||||||
|
"_location": "/@jscad/dxf-serializer",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"type": "range",
|
||||||
|
"registry": true,
|
||||||
|
"raw": "@jscad/dxf-serializer@^0.0.4",
|
||||||
|
"name": "@jscad/dxf-serializer",
|
||||||
|
"escapedName": "@jscad%2fdxf-serializer",
|
||||||
|
"scope": "@jscad",
|
||||||
|
"rawSpec": "^0.0.4",
|
||||||
|
"saveSpec": null,
|
||||||
|
"fetchSpec": "^0.0.4"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/@jscad/io"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/@jscad/dxf-serializer/-/dxf-serializer-0.0.4.tgz",
|
||||||
|
"_shasum": "3fb3b76b61e6a987e250827c49634f00736f6ac8",
|
||||||
|
"_spec": "@jscad/dxf-serializer@^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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "Dxf serializer for jscad project",
|
||||||
|
"homepage": "https://github.com/jscad/io#readme",
|
||||||
|
"keywords": [
|
||||||
|
"openjscad",
|
||||||
|
"jscad",
|
||||||
|
"csg",
|
||||||
|
"serializer",
|
||||||
|
"dxf"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "@jscad/dxf-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.4"
|
||||||
|
}
|
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/test.js
generated
vendored
Normal file
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/dxf-serializer/test.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const serializer = require('./index.js')
|
||||||
|
|
||||||
|
test.todo('add some actual tests later')
|
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/CHANGELOG.md
generated
vendored
Normal file
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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/gcode-deserializer@0.0.3...@jscad/gcode-deserializer@0.0.4) (2017-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/gcode-deserializer
|
||||||
|
|
||||||
|
<a name="0.0.3"></a>
|
||||||
|
## [0.0.3](https://github.com/jscad/io/compare/@jscad/gcode-deserializer@0.0.2...@jscad/gcode-deserializer@0.0.3) (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/gcode-deserializer
|
||||||
|
|
||||||
|
<a name="0.0.2"></a>
|
||||||
|
## 0.0.2 (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/gcode-deserializer
|
50
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/README.md
generated
vendored
Normal file
50
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/README.md
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
## @jscad/gcode-deserializer
|
||||||
|
|
||||||
|
> gcode deserializer for the jscad project
|
||||||
|
|
||||||
|
[![npm version](https://badge.fury.io/js/%40jscad%2Fgcode-deserializer.svg)](https://badge.fury.io/js/%40jscad%2Fgcode-deserializer)
|
||||||
|
[![Build Status](https://travis-ci.org/jscad/io.svg)](https://travis-ci.org/jscad/gcode-deserializer)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This deserializer converts raw gcode 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/gcode-deserializer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const gcodeDeSerializer = require('@jscad/gcode-deserializer')
|
||||||
|
|
||||||
|
const rawData = fs.readFileSync('PATH/TO/file.gcode')
|
||||||
|
const csgData = gcodeDeSerializer(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)
|
103
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/index.js
generated
vendored
Normal file
103
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/index.js
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
function deserialize (gcode, fn, options) {
|
||||||
|
// http://reprap.org/wiki/G-code
|
||||||
|
const defaults = {version: '0.0.0'}
|
||||||
|
options = Object.assign({}, defaults, options)
|
||||||
|
const {version} = options
|
||||||
|
// just as experiment ...
|
||||||
|
var l = gcode.split(/[\n]/) // for now just GCODE ASCII
|
||||||
|
var srci = ''
|
||||||
|
var d = 0
|
||||||
|
var pos = []
|
||||||
|
var lpos = []
|
||||||
|
var le = 0
|
||||||
|
var p = []
|
||||||
|
var origin = [-100, -100]
|
||||||
|
var layers = 0
|
||||||
|
var lh = 0.35
|
||||||
|
var lz = 0
|
||||||
|
var ld = 0
|
||||||
|
|
||||||
|
for (var i = 0; i < l.length; i++) {
|
||||||
|
var val = ''
|
||||||
|
var k
|
||||||
|
var e = 0
|
||||||
|
if (l[i].match(/^\s*;/)) { continue }
|
||||||
|
var c = l[i].split(/\s+/)
|
||||||
|
for (var j = 0; j < c.length; j++) {
|
||||||
|
if (c[j].match(/G(\d+)/)) {
|
||||||
|
var n = parseInt(RegExp.$1)
|
||||||
|
if (n === 1) d++
|
||||||
|
if (n === 90) pos.type = 'abs'
|
||||||
|
if (n === 91) pos.type = 'rel'
|
||||||
|
} else if (c[j].match(/M(\d+)/)) {
|
||||||
|
let n = parseInt(RegExp.$1)
|
||||||
|
if (n === 104 || n === 109) { k = 'temp' }
|
||||||
|
} else if (c[j].match(/S([\d\.]+)/)) {
|
||||||
|
var v = parseInt(RegExp.$1)
|
||||||
|
if (k !== undefined) {
|
||||||
|
val[k] = v
|
||||||
|
}
|
||||||
|
} else if (c[j].match(/([XYZE])([\-\d\.]+)/)) {
|
||||||
|
var a = RegExp.$1
|
||||||
|
let v = parseFloat(RegExp.$2)
|
||||||
|
if (pos.type === 'abs') {
|
||||||
|
if (d) pos[a] = v
|
||||||
|
} else {
|
||||||
|
if (d) pos[a] += v
|
||||||
|
}
|
||||||
|
// console.log(d,a,pos.E,lpos.E);
|
||||||
|
if (d && a === 'E' && lpos.E === undefined) {
|
||||||
|
lpos.E = pos.E
|
||||||
|
}
|
||||||
|
if (d && a === 'E' && (pos.E - lpos.E) > 0) {
|
||||||
|
// console.log(pos.E,lpos.E);
|
||||||
|
e++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (d && pos.X && pos.Y) {
|
||||||
|
if (e) {
|
||||||
|
if (!le && lpos.X && lpos.Y) {
|
||||||
|
// console.log(lpos.X,lpos.Y);
|
||||||
|
p.push('[' + (lpos.X + origin[0]) + ',' + (lpos.Y + origin[1]) + ']')
|
||||||
|
}
|
||||||
|
p.push('[' + (pos.X + origin[0]) + ',' + (pos.Y + origin[1]) + ']')
|
||||||
|
}
|
||||||
|
if (!e && le && p.length > 1) {
|
||||||
|
if (srci.length) srci += ',\n\t\t'
|
||||||
|
if (pos.Z !== lz) {
|
||||||
|
lh = pos.Z - lz
|
||||||
|
layers++
|
||||||
|
}
|
||||||
|
srci += 'EX([' + p.join(', ') + '],{w: ' + lh * 1.1 + ', h:' + lh * 1.02 + ', fn:1, closed: false}).translate([0,0,' + pos['Z'] + '])'
|
||||||
|
p = []
|
||||||
|
lz = pos.Z
|
||||||
|
// if(layers>2)
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
le = e
|
||||||
|
lpos.X = pos.X
|
||||||
|
lpos.Y = pos.Y
|
||||||
|
lpos.Z = pos.Z
|
||||||
|
lpos.E = pos.E
|
||||||
|
}
|
||||||
|
ld = d
|
||||||
|
}
|
||||||
|
|
||||||
|
var src = ''
|
||||||
|
src += '// producer: OpenJSCAD Compatibility (' + version + ') GCode Importer\n'
|
||||||
|
src += '// date: ' + (new Date()) + '\n'
|
||||||
|
src += '// source: ' + fn + '\n'
|
||||||
|
src += '\n'
|
||||||
|
// if(err) src += "// WARNING: import errors: "+err+" (some triangles might be misaligned or missing)\n";
|
||||||
|
src += '// layers: ' + layers + '\n'
|
||||||
|
src += 'function main() {\n\tvar EX = function(p,opt) { return rectangular_extrude(p,opt); }\n\treturn ['
|
||||||
|
src += srci
|
||||||
|
src += '\n\t];\n}\n'
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deserialize
|
||||||
|
}
|
68
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/package.json
generated
vendored
Normal file
68
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/package.json
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"_from": "@jscad/gcode-deserializer@^0.0.4",
|
||||||
|
"_id": "@jscad/gcode-deserializer@0.0.4",
|
||||||
|
"_inBundle": false,
|
||||||
|
"_integrity": "sha1-pdk+zvhGvzCllvUCHXs26mKhOic=",
|
||||||
|
"_location": "/@jscad/gcode-deserializer",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"type": "range",
|
||||||
|
"registry": true,
|
||||||
|
"raw": "@jscad/gcode-deserializer@^0.0.4",
|
||||||
|
"name": "@jscad/gcode-deserializer",
|
||||||
|
"escapedName": "@jscad%2fgcode-deserializer",
|
||||||
|
"scope": "@jscad",
|
||||||
|
"rawSpec": "^0.0.4",
|
||||||
|
"saveSpec": null,
|
||||||
|
"fetchSpec": "^0.0.4"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/@jscad/io"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/@jscad/gcode-deserializer/-/gcode-deserializer-0.0.4.tgz",
|
||||||
|
"_shasum": "a5d93ecef846bf30a596f5021d7b36ea62a13a27",
|
||||||
|
"_spec": "@jscad/gcode-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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "Gcode deserializer for jscad project",
|
||||||
|
"homepage": "https://github.com/jscad/io#readme",
|
||||||
|
"keywords": [
|
||||||
|
"openjscad",
|
||||||
|
"jscad",
|
||||||
|
"csg",
|
||||||
|
"deserializer",
|
||||||
|
"gcode"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "@jscad/gcode-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"
|
||||||
|
}
|
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/test.js
generated
vendored
Normal file
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/gcode-deserializer/test.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const deserializer = require('./index.js')
|
||||||
|
|
||||||
|
test.todo('add some actual tests later')
|
124
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/BinaryReader.js
generated
vendored
Normal file
124
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/BinaryReader.js
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// BinaryReader
|
||||||
|
// Refactored by Vjeux <vjeuxx@gmail.com>
|
||||||
|
// http://blog.vjeux.com/2010/javascript/javascript-binary-reader.html
|
||||||
|
|
||||||
|
// Original
|
||||||
|
// + Jonas Raoni Soares Silva
|
||||||
|
// @ http://jsfromhell.com/classes/binary-deserializer [rev. #1]
|
||||||
|
|
||||||
|
function BinaryReader (data) {
|
||||||
|
this._buffer = data
|
||||||
|
this._pos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryReader.prototype = {
|
||||||
|
/* Public */
|
||||||
|
readInt8: function () { return this._decodeInt(8, true) },
|
||||||
|
readUInt8: function () { return this._decodeInt(8, false) },
|
||||||
|
readInt16: function () { return this._decodeInt(16, true) },
|
||||||
|
readUInt16: function () { return this._decodeInt(16, false) },
|
||||||
|
readInt32: function () { return this._decodeInt(32, true) },
|
||||||
|
readUInt32: function () { return this._decodeInt(32, false) },
|
||||||
|
|
||||||
|
readFloat: function () { return this._decodeFloat(23, 8) },
|
||||||
|
readDouble: function () { return this._decodeFloat(52, 11) },
|
||||||
|
|
||||||
|
readChar: function () { return this.readString(1) },
|
||||||
|
readString: function (length) {
|
||||||
|
this._checkSize(length * 8)
|
||||||
|
var result = this._buffer.substr(this._pos, length)
|
||||||
|
this._pos += length
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
seek: function (pos) {
|
||||||
|
this._pos = pos
|
||||||
|
this._checkSize(0)
|
||||||
|
},
|
||||||
|
|
||||||
|
getPosition: function () {
|
||||||
|
return this._pos
|
||||||
|
},
|
||||||
|
|
||||||
|
getSize: function () {
|
||||||
|
return this._buffer.length
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
_decodeFloat: function (precisionBits, exponentBits) {
|
||||||
|
var length = precisionBits + exponentBits + 1
|
||||||
|
var size = length >> 3
|
||||||
|
this._checkSize(length)
|
||||||
|
|
||||||
|
var bias = Math.pow(2, exponentBits - 1) - 1
|
||||||
|
var signal = this._readBits(precisionBits + exponentBits, 1, size)
|
||||||
|
var exponent = this._readBits(precisionBits, exponentBits, size)
|
||||||
|
var significand = 0
|
||||||
|
var divisor = 2
|
||||||
|
var curByte = 0 // length + (-precisionBits >> 3) - 1;
|
||||||
|
do {
|
||||||
|
var byteValue = this._readByte(++curByte, size)
|
||||||
|
var startBit = precisionBits % 8 || 8
|
||||||
|
var mask = 1 << startBit
|
||||||
|
while (mask >>= 1) {
|
||||||
|
if (byteValue & mask) {
|
||||||
|
significand += 1 / divisor
|
||||||
|
}
|
||||||
|
divisor *= 2
|
||||||
|
}
|
||||||
|
} while (precisionBits -= startBit)
|
||||||
|
|
||||||
|
this._pos += size
|
||||||
|
|
||||||
|
return exponent === (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
|
||||||
|
: (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
|
||||||
|
: Math.pow(2, exponent - bias) * (1 + significand) : 0)
|
||||||
|
},
|
||||||
|
|
||||||
|
_decodeInt: function (bits, signed) {
|
||||||
|
var x = this._readBits(0, bits, bits / 8)
|
||||||
|
var max = Math.pow(2, bits)
|
||||||
|
var result = signed && x >= max / 2 ? x - max : x
|
||||||
|
|
||||||
|
this._pos += bits / 8
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
// shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
|
||||||
|
_shl: function (a, b) {
|
||||||
|
for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) === 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
|
||||||
|
_readByte: function (i, size) {
|
||||||
|
return this._buffer.charCodeAt(this._pos + size - i - 1) & 0xff
|
||||||
|
},
|
||||||
|
|
||||||
|
_readBits: function (start, length, size) {
|
||||||
|
var offsetLeft = (start + length) % 8
|
||||||
|
var offsetRight = start % 8
|
||||||
|
var curByte = size - (start >> 3) - 1
|
||||||
|
var lastByte = size + (-(start + length) >> 3)
|
||||||
|
var diff = curByte - lastByte
|
||||||
|
|
||||||
|
var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1)
|
||||||
|
|
||||||
|
if (diff && offsetLeft) {
|
||||||
|
sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight
|
||||||
|
}
|
||||||
|
|
||||||
|
while (diff) {
|
||||||
|
sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum
|
||||||
|
},
|
||||||
|
|
||||||
|
_checkSize: function (neededBits) {
|
||||||
|
if (!(this._pos + Math.ceil(neededBits / 8) < this._buffer.length)) {
|
||||||
|
// throw new Error("Index out of bound");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BinaryReader
|
58
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/CHANGELOG.md
generated
vendored
Normal file
58
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# 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.1.3"></a>
|
||||||
|
## [0.1.3](https://github.com/jscad/io/compare/@jscad/io-utils@0.1.2...@jscad/io-utils@0.1.3) (2018-09-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **svg deserializer:** fixed svg-deserializer to work with Inkscape files ([#72](https://github.com/jscad/io/issues/72)) ([f35ea5e](https://github.com/jscad/io/commit/f35ea5e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.1.2"></a>
|
||||||
|
## [0.1.2](https://github.com/jscad/io/compare/@jscad/io-utils@0.1.1...@jscad/io-utils@0.1.2) (2017-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io-utils
|
||||||
|
|
||||||
|
<a name="0.1.1"></a>
|
||||||
|
## [0.1.1](https://github.com/jscad/io/compare/@jscad/io-utils@0.1.0...@jscad/io-utils@0.1.1) (2017-10-30)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io-utils
|
||||||
|
|
||||||
|
<a name="0.1.0"></a>
|
||||||
|
# [0.1.0](https://github.com/jscad/io/compare/@jscad/io-utils@0.0.4...@jscad/io-utils@0.1.0) (2017-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **stl-deserializer:** add ability to deserialize stl to csg ([#32](https://github.com/jscad/io/issues/32)) ([a90dcf4](https://github.com/jscad/io/commit/a90dcf4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.0.4"></a>
|
||||||
|
## [0.0.4](https://github.com/jscad/io/compare/@jscad/io-utils@0.0.3...@jscad/io-utils@0.0.4) (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io-utils
|
||||||
|
|
||||||
|
<a name="0.0.3"></a>
|
||||||
|
## 0.0.3 (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io-utils
|
57
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/README.md
generated
vendored
Normal file
57
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/README.md
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
## @jscad/io-utils
|
||||||
|
|
||||||
|
> input/output handling utilities
|
||||||
|
|
||||||
|
[![npm version](https://badge.fury.io/js/%40jscad%2Fio-utils.svg)](https://badge.fury.io/js/%40jscad%2Fio-utils)
|
||||||
|
[![Build Status](https://travis-ci.org/jscad/io.svg)](https://travis-ci.org/jscad/io-utils)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This contains following utilities:
|
||||||
|
|
||||||
|
- makeBlob : converts arrays of raw data output by the various serializers into
|
||||||
|
a Blob that can also be converted to Node.js buffer
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Contribute](#contribute)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install @jscad/io-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const {makeBlob} = require('@jscad/io-utils')
|
||||||
|
const stlSerializer = require('@jscad/stl-serializer')
|
||||||
|
|
||||||
|
const rawData = stlSerializer(CSGObject)
|
||||||
|
|
||||||
|
const blob = new makeBlob(rawData)
|
||||||
|
//get a Node.js buffer
|
||||||
|
const buffer = blob.asBuffer()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 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)
|
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/ensureManifoldness.js
generated
vendored
Normal file
18
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/ensureManifoldness.js
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* wrapper around internal csg methods (in case they change) to make sure
|
||||||
|
* it resuts in a manifold mesh
|
||||||
|
* @constructor
|
||||||
|
* @param {string} title - The title of the book.
|
||||||
|
* @return {csg}
|
||||||
|
*/
|
||||||
|
function ensureManifoldness (input) {
|
||||||
|
const transform = input => {
|
||||||
|
input = 'reTesselated' in input ? input.reTesselated() : input
|
||||||
|
input = 'fixTJunctions' in input ? input.fixTJunctions() : input // fixTJunctions also calls this.canonicalized() so no need to do it twice
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.constructor !== Array ? transform(input) : input.map(transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ensureManifoldness
|
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/index.js
generated
vendored
Normal file
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/index.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const makeBlob = require('./makeBlob')
|
||||||
|
const BinaryReader = require('./BinaryReader')
|
||||||
|
const ensureManifoldness = require('./ensureManifoldness')
|
||||||
|
module.exports = {makeBlob, BinaryReader, ensureManifoldness}
|
114
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/makeBlob.js
generated
vendored
Normal file
114
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/makeBlob.js
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Blob.js
|
||||||
|
* See https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||||
|
*
|
||||||
|
* Node and Browserify Compatible
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 by Z3 Dev (@zdev/www.z3dev.jp)
|
||||||
|
* License: MIT License
|
||||||
|
*
|
||||||
|
* This implementation uses the Buffer class for all storage.
|
||||||
|
* See https://nodejs.org/api/buffer.html
|
||||||
|
*
|
||||||
|
* URL.createObjectURL(blob)
|
||||||
|
*
|
||||||
|
* History:
|
||||||
|
* 2015/07/02: 0.0.1: contributed to OpenJSCAD.org CLI openjscad
|
||||||
|
*/
|
||||||
|
|
||||||
|
function makeBlob (contents, options) {
|
||||||
|
const blob = typeof window !== 'undefined' ? window.Blob : Blob
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
|
||||||
|
function Blob (contents, options) {
|
||||||
|
// make the optional options non-optional
|
||||||
|
options = options || {}
|
||||||
|
// number of bytes
|
||||||
|
this.size = 0 // contents, not allocation
|
||||||
|
// media type
|
||||||
|
this.type = ''
|
||||||
|
// readability state (CLOSED: true, OPENED: false)
|
||||||
|
this.isClosed = false
|
||||||
|
// encoding of given strings
|
||||||
|
this.encoding = 'utf8'
|
||||||
|
// storage
|
||||||
|
this.buffer = null
|
||||||
|
this.length = 32e+6 // allocation, not contents
|
||||||
|
|
||||||
|
if (!contents) return
|
||||||
|
if (!Array.isArray(contents)) return
|
||||||
|
|
||||||
|
// process options if any
|
||||||
|
if (options.type) {
|
||||||
|
// TBD if type contains any chars outside range U+0020 to U+007E, then set type to the empty string
|
||||||
|
// Convert every character in type to lowercase
|
||||||
|
this.type = options.type.toLowerCase()
|
||||||
|
}
|
||||||
|
if (options.endings) {
|
||||||
|
// convert the EOL on strings
|
||||||
|
}
|
||||||
|
if (options.encoding) {
|
||||||
|
this.encoding = options.encoding.toLowerCase()
|
||||||
|
}
|
||||||
|
if (options.length) {
|
||||||
|
this.length = options.length
|
||||||
|
}
|
||||||
|
|
||||||
|
let wbytes
|
||||||
|
let object
|
||||||
|
// convert the contents (String, ArrayBufferView, ArrayBuffer, Blob)
|
||||||
|
this.buffer = Buffer.alloc(this.length) // new Buffer(this.length)
|
||||||
|
var index = 0
|
||||||
|
for (index = 0; index < contents.length; index++) {
|
||||||
|
switch (typeof (contents[index])) {
|
||||||
|
case 'string':
|
||||||
|
wbytes = this.buffer.write(contents[index], this.size, this.encoding)
|
||||||
|
this.size = this.size + wbytes
|
||||||
|
break
|
||||||
|
case 'object':
|
||||||
|
object = contents[index] // this should be a reference to an object
|
||||||
|
if (Buffer.isBuffer(object)) {
|
||||||
|
}
|
||||||
|
if (object instanceof ArrayBuffer) {
|
||||||
|
var view = new DataView(object)
|
||||||
|
var bindex = 0
|
||||||
|
for (bindex = 0; bindex < object.byteLength; bindex++) {
|
||||||
|
var xbyte = view.getUint8(bindex)
|
||||||
|
wbytes = this.buffer.writeUInt8(xbyte, this.size, false)
|
||||||
|
this.size++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
Blob.prototype = {
|
||||||
|
asBuffer: function () {
|
||||||
|
return this.buffer.slice(0, this.size)
|
||||||
|
},
|
||||||
|
|
||||||
|
slice: function (start, end, type) {
|
||||||
|
start = start || 0
|
||||||
|
end = end || this.size
|
||||||
|
type = type || ''
|
||||||
|
return new Blob()
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function () {
|
||||||
|
// if state of context objext is already CLOSED then return
|
||||||
|
if (this.isClosed) return
|
||||||
|
// set the readbility state of the context object to CLOSED and remove storage
|
||||||
|
this.isClosed = true
|
||||||
|
},
|
||||||
|
|
||||||
|
toString: function () {
|
||||||
|
return 'blob blob blob'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = makeBlob
|
79
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/package.json
generated
vendored
Normal file
79
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/package.json
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"_from": "@jscad/io-utils@^0.1.2",
|
||||||
|
"_id": "@jscad/io-utils@0.1.3",
|
||||||
|
"_inBundle": false,
|
||||||
|
"_integrity": "sha512-aCVUZovewI4njtEJ1fvjhasqd8EHIzQaPb88MtFWf/ff1NdGOu/vRH3yGG8vu0NZektGEDCKp028g2nTBL7aFg==",
|
||||||
|
"_location": "/@jscad/io-utils",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"type": "range",
|
||||||
|
"registry": true,
|
||||||
|
"raw": "@jscad/io-utils@^0.1.2",
|
||||||
|
"name": "@jscad/io-utils",
|
||||||
|
"escapedName": "@jscad%2fio-utils",
|
||||||
|
"scope": "@jscad",
|
||||||
|
"rawSpec": "^0.1.2",
|
||||||
|
"saveSpec": null,
|
||||||
|
"fetchSpec": "^0.1.2"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/@jscad/amf-serializer",
|
||||||
|
"/@jscad/io",
|
||||||
|
"/@jscad/json-serializer",
|
||||||
|
"/@jscad/stl-deserializer",
|
||||||
|
"/@jscad/stl-serializer",
|
||||||
|
"/@jscad/x3d-serializer"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/@jscad/io-utils/-/io-utils-0.1.3.tgz",
|
||||||
|
"_shasum": "ddc4ea914606c085b64ed20919a1973b3f47ac51",
|
||||||
|
"_spec": "@jscad/io-utils@^0.1.2",
|
||||||
|
"_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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "Utilities for input , output and formats handling for jscad project",
|
||||||
|
"devDependencies": {
|
||||||
|
"@jscad/csg": "0.7.0"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jscad/io#readme",
|
||||||
|
"keywords": [
|
||||||
|
"openscad",
|
||||||
|
"openjscad",
|
||||||
|
"jscad",
|
||||||
|
"cad",
|
||||||
|
"io",
|
||||||
|
"formats",
|
||||||
|
"serializer",
|
||||||
|
"deserializer"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "@jscad/io-utils",
|
||||||
|
"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.1.3"
|
||||||
|
}
|
34
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/test.js
generated
vendored
Normal file
34
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io-utils/test.js
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const {CSG} = require('@jscad/csg')
|
||||||
|
const ensureManifoldness = require('./ensureManifoldness.js')
|
||||||
|
|
||||||
|
// NOTE : this test is BAD WAY, and too dependent on the current implementation , we need actual checks
|
||||||
|
// such as https://gamedev.stackexchange.com/questions/61878/how-check-if-an-arbitrary-given-mesh-is-a-single-closed-mesh
|
||||||
|
// https://stackoverflow.com/questions/761026/is-a-closed-polygonal-mesh-flipped
|
||||||
|
// https://blender.stackexchange.com/questions/20956/is-there-a-way-to-check-a-mesh-for-problems
|
||||||
|
// https://pypi.python.org/pypi/trimesh/2.2.8
|
||||||
|
test('ensureManifoldness of csg objects (single input)', function (t) {
|
||||||
|
const input = new CSG.cube()
|
||||||
|
t.deepEqual(input.isCanonicalized, false)
|
||||||
|
t.deepEqual(input.isRetesselated, false)
|
||||||
|
const observed = ensureManifoldness(input)
|
||||||
|
t.deepEqual(observed.isCanonicalized, true)
|
||||||
|
t.deepEqual(observed.isRetesselated, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ensureManifoldness of csg objects (array of inputs)', function (t) {
|
||||||
|
const input = [
|
||||||
|
new CSG.cube(),
|
||||||
|
new CSG.sphere(),
|
||||||
|
new CSG.cube()
|
||||||
|
]
|
||||||
|
input.map(x => {
|
||||||
|
t.deepEqual(x.isCanonicalized, false)
|
||||||
|
t.deepEqual(x.isRetesselated, false)
|
||||||
|
})
|
||||||
|
const observed = ensureManifoldness(input)
|
||||||
|
observed.map(x => {
|
||||||
|
t.deepEqual(x.isCanonicalized, true)
|
||||||
|
t.deepEqual(x.isRetesselated, true)
|
||||||
|
})
|
||||||
|
})
|
52
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/CHANGELOG.md
generated
vendored
Normal file
52
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# 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.3.7"></a>
|
||||||
|
## [0.3.7](https://github.com/jscad/io/compare/@jscad/io@0.3.6...@jscad/io@0.3.7) (2017-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io
|
||||||
|
|
||||||
|
<a name="0.3.6"></a>
|
||||||
|
## [0.3.6](https://github.com/jscad/io/compare/@jscad/io@0.3.5...@jscad/io@0.3.6) (2017-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io
|
||||||
|
|
||||||
|
<a name="0.3.5"></a>
|
||||||
|
## [0.3.5](https://github.com/jscad/io/compare/@jscad/io@0.3.4...@jscad/io@0.3.5) (2017-10-30)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io
|
||||||
|
|
||||||
|
<a name="0.3.4"></a>
|
||||||
|
## [0.3.4](https://github.com/jscad/io/compare/@jscad/io@0.3.3...@jscad/io@0.3.4) (2017-10-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io
|
||||||
|
|
||||||
|
<a name="0.3.3"></a>
|
||||||
|
## [0.3.3](https://github.com/jscad/io/compare/@jscad/io@0.3.2...@jscad/io@0.3.3) (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/io
|
||||||
|
|
||||||
|
<a name="0.3.2"></a>
|
||||||
|
## 0.3.2 (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **io:** fixed bad version numbers for svg de/serializers ([#28](https://github.com/jscad/io/issues/28)) ([60d239c](https://github.com/jscad/io/commit/60d239c))
|
73
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/README.md
generated
vendored
Normal file
73
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/README.md
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
## io
|
||||||
|
|
||||||
|
## input output formats handling for the jscad project
|
||||||
|
|
||||||
|
[![npm version](https://badge.fury.io/js/%40jscad%2Fio.svg)](https://badge.fury.io/js/%40jscad%2Fio)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This package is a metapackage and includes all the input/output format handling for the jscad projects, and can also be used separately.
|
||||||
|
|
||||||
|
### Inputs / deserializers
|
||||||
|
|
||||||
|
ie: file data => jscad code (that can be evaluated to CSG/CAG)
|
||||||
|
> note : currently serializers & deserializers are NOT symetrical as deserializers
|
||||||
|
do not generate CSG/CAG objects
|
||||||
|
|
||||||
|
Following formats are supported as inputs
|
||||||
|
- [AMF](https://github.com/jscad/io/blob/master/packages/amf-deserializer)
|
||||||
|
- [gcode](https://github.com/jscad/io/blob/master/packages/gcode-deserializer)
|
||||||
|
- [JSON](https://github.com/jscad/io/blob/master/packages/json-deserializer)
|
||||||
|
- [OBJ](https://github.com/jscad/io/blob/master/packages/obj-deserializer)
|
||||||
|
- [STL (binary, ASCII)](https://github.com/jscad/io/blob/master/packages/stl-deserializer)
|
||||||
|
- [SVG](https://github.com/jscad/io/blob/master/packages/svg-deserializer)
|
||||||
|
|
||||||
|
### Outputs/ serializers
|
||||||
|
|
||||||
|
ie: CSG/CAG => blob
|
||||||
|
|
||||||
|
Following formats are supported as outputs
|
||||||
|
- [AMF](https://github.com/jscad/io/blob/master/packages/amf-serializer)
|
||||||
|
- [DXF](https://github.com/jscad/io/blob/master/packages/dxf-serializer)
|
||||||
|
- [JSON](https://github.com/jscad/io/blob/master/packages/json-serializer)
|
||||||
|
- [STL (binary, ASCII)](https://github.com/jscad/io/blob/master/packages/stl-serializer)
|
||||||
|
- [SVG](https://github.com/jscad/io/blob/master/packages/svg-serializer)
|
||||||
|
- [X3D](https://github.com/jscad/io/blob/master/packages/x3d-serializer)
|
||||||
|
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Contribute](#contribute)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install @jscad/io
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- as Node module :
|
||||||
|
|
||||||
|
```
|
||||||
|
const io = require('@jscad/io')
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 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)](https://github.com/jscad/io/blob/master/LICENSE)
|
||||||
|
(unless specified otherwise)
|
51
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/index.js
generated
vendored
Normal file
51
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/index.js
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const {makeBlob} = require('@jscad/io-utils')
|
||||||
|
|
||||||
|
const amfSerializer = require('@jscad/amf-serializer')
|
||||||
|
const dxfSerializer = require('@jscad/dxf-serializer')
|
||||||
|
const jsonSerializer = require('@jscad/json-serializer')
|
||||||
|
const stlSerializer = require('@jscad/stl-serializer')
|
||||||
|
const svgSerializer = require('@jscad/svg-serializer')
|
||||||
|
const x3dSerializer = require('@jscad/x3d-serializer')
|
||||||
|
|
||||||
|
const amfDeSerializer = require('@jscad/amf-deserializer')
|
||||||
|
const gcodeDeSerializer = require('@jscad/gcode-deserializer')
|
||||||
|
const jsonDeSerializer = require('@jscad/json-deserializer')
|
||||||
|
const objDeSerializer = require('@jscad/obj-deserializer')
|
||||||
|
const stlDeSerializer = require('@jscad/stl-deserializer')
|
||||||
|
const svgDeSerializer = require('@jscad/svg-deserializer')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
makeBlob,
|
||||||
|
amfSerializer,
|
||||||
|
dxfSerializer,
|
||||||
|
jsonSerializer,
|
||||||
|
stlSerializer,
|
||||||
|
svgSerializer,
|
||||||
|
x3dSerializer,
|
||||||
|
|
||||||
|
amfDeSerializer,
|
||||||
|
gcodeDeSerializer,
|
||||||
|
jsonDeSerializer,
|
||||||
|
objDeSerializer,
|
||||||
|
stlDeSerializer,
|
||||||
|
svgDeSerializer
|
||||||
|
}
|
||||||
|
/* export {makeBlob} from './utils/Blob'
|
||||||
|
|
||||||
|
import * as CAGToDxf from './serializers/CAGToDxf'
|
||||||
|
import * as CAGToJson from './serializers/CAGToJson'
|
||||||
|
import * as CAGToSvg from './serializers/CAGToSvg'
|
||||||
|
import * as CSGToAMF from './serializers/CSGToAMF'
|
||||||
|
import * as CSGToJson from './serializers/CSGToJson'
|
||||||
|
import * as CSGToStla from './serializers/CSGToStla'
|
||||||
|
import * as CSGToStlb from './serializers/CSGToStlb'
|
||||||
|
import * as CSGToX3D from './serializers/CSGToX3D'
|
||||||
|
|
||||||
|
export {CAGToDxf, CAGToJson, CAGToSvg, CSGToAMF, CSGToJson, CSGToStla, CSGToStlb, CSGToX3D}
|
||||||
|
|
||||||
|
export {parseAMF} from './deserializers/parseAMF'
|
||||||
|
export {parseGCode} from './deserializers/parseGCode'
|
||||||
|
export {parseJSON} from './deserializers/parseJSON'
|
||||||
|
export {parseOBJ} from './deserializers/parseOBJ'
|
||||||
|
export {parseSTL} from './deserializers/parseSTL'
|
||||||
|
export {parseSVG} from './deserializers/parseSVG' */
|
89
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/package.json
generated
vendored
Normal file
89
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/io/package.json
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"_from": "@jscad/io@0.3.7",
|
||||||
|
"_id": "@jscad/io@0.3.7",
|
||||||
|
"_inBundle": false,
|
||||||
|
"_integrity": "sha1-CNWCv/0juTdopkvYo3Qv3De86u4=",
|
||||||
|
"_location": "/@jscad/io",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"type": "version",
|
||||||
|
"registry": true,
|
||||||
|
"raw": "@jscad/io@0.3.7",
|
||||||
|
"name": "@jscad/io",
|
||||||
|
"escapedName": "@jscad%2fio",
|
||||||
|
"scope": "@jscad",
|
||||||
|
"rawSpec": "0.3.7",
|
||||||
|
"saveSpec": null,
|
||||||
|
"fetchSpec": "0.3.7"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/@jscad/openjscad"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/@jscad/io/-/io-0.3.7.tgz",
|
||||||
|
"_shasum": "08d582bffd23b93768a64bd8a3742fdc37bceaee",
|
||||||
|
"_spec": "@jscad/io@0.3.7",
|
||||||
|
"_where": "C:\\Users\\tomate\\node_modules\\@jscad\\openjscad",
|
||||||
|
"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/amf-deserializer": "^0.0.4",
|
||||||
|
"@jscad/amf-serializer": "^0.0.5",
|
||||||
|
"@jscad/dxf-serializer": "^0.0.4",
|
||||||
|
"@jscad/gcode-deserializer": "^0.0.4",
|
||||||
|
"@jscad/io-utils": "^0.1.2",
|
||||||
|
"@jscad/json-deserializer": "^0.0.4",
|
||||||
|
"@jscad/json-serializer": "^0.0.5",
|
||||||
|
"@jscad/obj-deserializer": "^0.0.4",
|
||||||
|
"@jscad/stl-deserializer": "^0.1.2",
|
||||||
|
"@jscad/stl-serializer": "^0.0.6",
|
||||||
|
"@jscad/svg-deserializer": "^0.2.3",
|
||||||
|
"@jscad/svg-serializer": "^0.0.4",
|
||||||
|
"@jscad/x3d-serializer": "^0.0.6"
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "Meta package for input , output and formats handling for jscad project",
|
||||||
|
"homepage": "https://github.com/jscad/io#readme",
|
||||||
|
"keywords": [
|
||||||
|
"openscad",
|
||||||
|
"openjscad",
|
||||||
|
"jscad",
|
||||||
|
"parametric",
|
||||||
|
"modeling",
|
||||||
|
"cad",
|
||||||
|
"io",
|
||||||
|
"formats",
|
||||||
|
"parser",
|
||||||
|
"writer",
|
||||||
|
"serializer",
|
||||||
|
"deserializer"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "@jscad/io",
|
||||||
|
"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 "
|
||||||
|
},
|
||||||
|
"version": "0.3.7"
|
||||||
|
}
|
24
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/CAGFromJson.js
generated
vendored
Normal file
24
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/CAGFromJson.js
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// import { CSG, CAG } from '@jscad/csg'
|
||||||
|
const {CSG, CAG} = require('@jscad/csg')
|
||||||
|
|
||||||
|
// convert the given (anonymous JSON) object into CAG
|
||||||
|
// Note: Any issues during conversion will result in exceptions
|
||||||
|
function deserialize (o) {
|
||||||
|
// verify the object IS convertable
|
||||||
|
if (o.type === 'cag') {
|
||||||
|
Object.setPrototypeOf(o, CAG.prototype)
|
||||||
|
o.sides.map(function (side) {
|
||||||
|
Object.setPrototypeOf(side, CAG.Side.prototype)
|
||||||
|
Object.setPrototypeOf(side.vertex0, CAG.Vertex.prototype)
|
||||||
|
Object.setPrototypeOf(side.vertex1, CAG.Vertex.prototype)
|
||||||
|
Object.setPrototypeOf(side.vertex0.pos, CSG.Vector2D.prototype)
|
||||||
|
Object.setPrototypeOf(side.vertex1.pos, CSG.Vector2D.prototype)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deserialize
|
||||||
|
}
|
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/CHANGELOG.md
generated
vendored
Normal file
28
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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/json-deserializer@0.0.3...@jscad/json-deserializer@0.0.4) (2017-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/json-deserializer
|
||||||
|
|
||||||
|
<a name="0.0.3"></a>
|
||||||
|
## [0.0.3](https://github.com/jscad/io/compare/@jscad/json-deserializer@0.0.2...@jscad/json-deserializer@0.0.3) (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/json-deserializer
|
||||||
|
|
||||||
|
<a name="0.0.2"></a>
|
||||||
|
## 0.0.2 (2017-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @jscad/json-deserializer
|
26
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/CSGFromJson.js
generated
vendored
Normal file
26
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/CSGFromJson.js
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const {CSG} = require('@jscad/csg')
|
||||||
|
|
||||||
|
// convert the given (anonymous JSON) object into CSG
|
||||||
|
// Note: Any issues during conversion will result in exceptions
|
||||||
|
function parse (o) {
|
||||||
|
// verify the object IS convertable
|
||||||
|
if (o.type === 'csg') {
|
||||||
|
Object.setPrototypeOf(o, CSG.prototype)
|
||||||
|
o.polygons.map(function (p) {
|
||||||
|
Object.setPrototypeOf(p, CSG.Polygon.prototype)
|
||||||
|
p.vertices.map(function (v) {
|
||||||
|
Object.setPrototypeOf(v, CSG.Vertex.prototype)
|
||||||
|
Object.setPrototypeOf(v.pos, CSG.Vector3D.prototype)
|
||||||
|
})
|
||||||
|
Object.setPrototypeOf(p.shared, CSG.Polygon.Shared.prototype)
|
||||||
|
Object.setPrototypeOf(p.plane, CSG.Plane.prototype)
|
||||||
|
Object.setPrototypeOf(p.plane.normal, CSG.Vector3D.prototype)
|
||||||
|
})
|
||||||
|
o.properties = new CSG.Properties()
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parse
|
||||||
|
}
|
50
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/README.md
generated
vendored
Normal file
50
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/README.md
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
## @jscad/json-deserializer
|
||||||
|
|
||||||
|
> json deserializer for the jscad project
|
||||||
|
|
||||||
|
[![npm version](https://badge.fury.io/js/%40jscad%2Fjson-deserializer.svg)](https://badge.fury.io/js/%40jscad%2Fjson-deserializer)
|
||||||
|
[![Build Status](https://travis-ci.org/jscad/io.svg)](https://travis-ci.org/jscad/json-deserializer)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This deserializer converts raw json 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/json-deserializer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const jsonDeSerializer = require('@jscad/json-deserializer')
|
||||||
|
|
||||||
|
const rawData = fs.readFileSync('PATH/TO/file.json')
|
||||||
|
const csgData = jsonDeSerializer(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)
|
107
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/index.js
generated
vendored
Normal file
107
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/index.js
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Z3 Development https://github.com/z3dev
|
||||||
|
|
||||||
|
All code released under MIT license
|
||||||
|
|
||||||
|
History:
|
||||||
|
2016/10/15: 0.5.2: initial version
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
1) All functions extend other objects in order to maintain namespaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import { CSG } from '@jscad/csg'
|
||||||
|
const {CSG, CAG} = require('@jscad/csg')
|
||||||
|
|
||||||
|
// //////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// JSON (JavaScript Object Notation) is a lightweight data-interchange format
|
||||||
|
// See http://json.org/
|
||||||
|
//
|
||||||
|
// //////////////////////////////////////////
|
||||||
|
|
||||||
|
function toSourceCSGVertex (ver) {
|
||||||
|
return 'new CSG.Vertex(new CSG.Vector3D(' + ver._x + ',' + ver._y + ',' + ver._z + '))'
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the give CSG object to JSCAD source
|
||||||
|
function toSourceCSG (csg) {
|
||||||
|
var code = ' var polygons = [];\n'
|
||||||
|
csg.polygons.map(function (p) {
|
||||||
|
code += ' poly = new CSG.Polygon([\n'
|
||||||
|
for (var i = 0; i < p.vertices.length; i++) {
|
||||||
|
code += ' ' + toSourceCSGVertex(p.vertices[i].pos) + ',\n'
|
||||||
|
}
|
||||||
|
code += ' ])'
|
||||||
|
if (p.shared && p.shared.color && p.shared.color.length) {
|
||||||
|
code += '.setColor(' + JSON.stringify(p.shared.color) + ');\n'
|
||||||
|
} else {
|
||||||
|
code += ';\n'
|
||||||
|
}
|
||||||
|
code += ' polygons.push(poly);\n'
|
||||||
|
})
|
||||||
|
code += ' return CSG.fromPolygons(polygons);\n'
|
||||||
|
return code
|
||||||
|
};
|
||||||
|
|
||||||
|
function toSourceCAGVertex (ver) {
|
||||||
|
return 'new CAG.Vertex(new CSG.Vector2D(' + ver.pos._x + ',' + ver.pos._y + '))'
|
||||||
|
};
|
||||||
|
function toSourceSide (side) {
|
||||||
|
return 'new CAG.Side(' + toSourceCAGVertex(side.vertex0) + ',' + toSourceCAGVertex(side.vertex1) + ')'
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert the give CAG object to JSCAD source
|
||||||
|
function toSourceCAG (cag) {
|
||||||
|
var code = ' var sides = [];\n'
|
||||||
|
cag.sides.map(function (s) {
|
||||||
|
code += ' sides.push(' + toSourceSide(s) + ');\n'
|
||||||
|
})
|
||||||
|
code += ' return CAG.fromSides(sides);\n'
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert an anonymous CSG/CAG object to JSCAD source
|
||||||
|
function toSource (obj) {
|
||||||
|
if (obj.type && obj.type === 'csg') {
|
||||||
|
var csg = CSG.fromObject(obj)
|
||||||
|
return toSourceCSG(csg)
|
||||||
|
}
|
||||||
|
if (obj.type && obj.type === 'cag') {
|
||||||
|
var cag = CAG.fromObject(obj)
|
||||||
|
return toSourceCAG(cag)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// deserialize the given JSON source and return a JSCAD script
|
||||||
|
//
|
||||||
|
// fn (optional) original filename of JSON source
|
||||||
|
//
|
||||||
|
function deserialize (src, fn, options) {
|
||||||
|
fn = fn || 'amf'
|
||||||
|
const defaults = {version: '0.0.0'}
|
||||||
|
options = Object.assign({}, defaults, options)
|
||||||
|
const {version} = options
|
||||||
|
|
||||||
|
// convert the JSON into an anonymous object
|
||||||
|
var obj = JSON.parse(src)
|
||||||
|
// convert the internal objects to JSCAD code
|
||||||
|
var code = ''
|
||||||
|
code += '//\n'
|
||||||
|
code += '// producer: OpenJSCAD.org ' + version + ' JSON Importer\n'
|
||||||
|
code += '// date: ' + (new Date()) + '\n'
|
||||||
|
code += '// source: ' + fn + '\n'
|
||||||
|
code += '//\n'
|
||||||
|
code += 'function main() {\n'
|
||||||
|
code += toSource(obj)
|
||||||
|
code += '};\n'
|
||||||
|
return code
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deserialize
|
||||||
|
}
|
71
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/package.json
generated
vendored
Normal file
71
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/package.json
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"_from": "@jscad/json-deserializer@^0.0.4",
|
||||||
|
"_id": "@jscad/json-deserializer@0.0.4",
|
||||||
|
"_inBundle": false,
|
||||||
|
"_integrity": "sha1-ffpQCUUoh5LgTlcaDCTxXFXuurA=",
|
||||||
|
"_location": "/@jscad/json-deserializer",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"type": "range",
|
||||||
|
"registry": true,
|
||||||
|
"raw": "@jscad/json-deserializer@^0.0.4",
|
||||||
|
"name": "@jscad/json-deserializer",
|
||||||
|
"escapedName": "@jscad%2fjson-deserializer",
|
||||||
|
"scope": "@jscad",
|
||||||
|
"rawSpec": "^0.0.4",
|
||||||
|
"saveSpec": null,
|
||||||
|
"fetchSpec": "^0.0.4"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"/@jscad/io"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/@jscad/json-deserializer/-/json-deserializer-0.0.4.tgz",
|
||||||
|
"_shasum": "7dfa500945288792e04e571a0c24f15c55eebab0",
|
||||||
|
"_spec": "@jscad/json-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": {
|
||||||
|
"@jscad/csg": "0.3.6"
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "Json deserializer for jscad project",
|
||||||
|
"homepage": "https://github.com/jscad/io#readme",
|
||||||
|
"keywords": [
|
||||||
|
"openjscad",
|
||||||
|
"jscad",
|
||||||
|
"csg",
|
||||||
|
"deserializer",
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "@jscad/json-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 20000"
|
||||||
|
},
|
||||||
|
"version": "0.0.4"
|
||||||
|
}
|
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/test.js
generated
vendored
Normal file
4
extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/json-deserializer/test.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const deserializer = require('./index.js')
|
||||||
|
|
||||||
|
test.todo('add some actual tests later')
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user