Compare commits
253 Commits
fix-macos-
...
master
Author | SHA1 | Date |
---|---|---|
ruevs | 2afabadfa5 | |
Kyle Dickerson | 40a1fdc6af | |
ruevs | cc64fed2d0 | |
John Ingle | 71ad5cbfab | |
mnml_ | 8f40d8f4c2 | |
Koen Schmeets | 569ac27dd7 | |
Koen Schmeets | d0a201e0cb | |
Koen Schmeets | 656afaa12d | |
Koen Schmeets | a17ee176de | |
ruevs | 7310659a24 | |
NotSure | 7d379e7f3b | |
NotSure | f25bf39fa7 | |
Adam Novak | ddeab11615 | |
phkahler | 1f8b3f2589 | |
phkahler | 1a71ca745a | |
Rylie Pavlik | 486e43a450 | |
Dhruv Gramopadhye | 3e5f9834d7 | |
Dhruv Gramopadhye | 94618d4ce1 | |
ruevs | 727b0153f6 | |
phkahler | 87d0e097bb | |
ruevs | d88ed3f2ee | |
ruevs | 6f7e45ba9f | |
ruevs | d5e8a8267c | |
ruevs | 5edc1ec0fb | |
ruevs | 70382660c8 | |
phkahler | e7c0c1665f | |
ruevs | 0d26ca17f7 | |
ruevs | 9edf2bcc34 | |
phkahler | f399997976 | |
phkahler | 1963a836ef | |
Victor Kustov | 1919a18916 | |
Koen Schmeets | e6565c8118 | |
vthriller | beb473b94d | |
Tomáš Hübelbauer | 36ecb85bb3 | |
Yusuf Yavuzyigit | 88e4d08324 | |
Yusuf Yavuzyigit | 95b00bd888 | |
Paul Kahler | 0e2fd2e6d9 | |
phkahler | b34d9a2f11 | |
ruevs | f0912d42b1 | |
Matteo Scalia | 3d04d4cc04 | |
alufers | 7ccb0ffac4 | |
phkahler | fd25424ab5 | |
phkahler | 604335f1c9 | |
liuxilu | 2e2aceb5ed | |
Alexandre Prokoudine | aa0d25e6ae | |
ruevs | 659215d84c | |
ruevs | 7161a2ab38 | |
ruevs | b69d565e9d | |
ruevs | 9e043c66ca | |
ruevs | dab6e173ee | |
ruevs | d63047d089 | |
ruevs | 0305f9a56c | |
ruevs | 69ded9721f | |
ruevs | 25b5977962 | |
Blockers | d6e970918f | |
robnee | 3c91bf7ca4 | |
phkahler | 302aebfd1a | |
phkahler | e4fcb7de08 | |
phkahler | 0c28adc69e | |
Koen Schmeets | 8117a21d59 | |
phkahler | 9ee9aa7609 | |
ruevs | b556daaf74 | |
ruevs | 3f11bbe47a | |
lomatus | 10c6c09541 | |
ruevs | 9282b403dd | |
ruevs | 60cd95d608 | |
77maxikov | a0219b2228 | |
phkahler | 4a34253a37 | |
ruevs | b4be656f25 | |
77maxikov | 7b6a85da5a | |
ruevs | 6487fb890e | |
ruevs | bacc0b66bd | |
Paul Kahler | 3ee3561153 | |
Paul Kahler | a1be8a8d6a | |
ruevs | f22ebf2a54 | |
phkahler | 20e3d15f90 | |
Koen Schmeets | 10cb310f18 | |
Koen Schmeets | 1827d154c8 | |
phkahler | a71e4bef81 | |
phkahler | 3833dd0246 | |
ruevs | 3609f8a7e9 | |
ruevs | aee47a42c6 | |
ruevs | adb2768154 | |
phkahler | 7d5eaffa89 | |
phkahler | 105a350ccd | |
phkahler | 0db1f6bacd | |
phkahler | a5809891d6 | |
77maxikov | 3d3d5c789d | |
ruevs | 50cbecbe72 | |
ruevs | 6fc84ae2ce | |
ruevs | c53c592dbe | |
ruevs | 31ac8083ae | |
verylowfreq | 1603402df2 | |
verylowfreq | 4981570844 | |
verylowfreq | b5cde57bb6 | |
verylowfreq | cf597277fa | |
verylowfreq | 64948c4526 | |
ruevs | 56b9d36030 | |
Eldritch Cheese | 4fc0141a5e | |
TristeFigure | d50e2b2a43 | |
Val Lorentz | 5c899617b4 | |
Val Lorentz | d1d7ae690b | |
Val Lorentz | fc55990f21 | |
Val Lorentz | 014dd43cf4 | |
Val Lorentz | 08c787f749 | |
Adam Novak | 3dc4d0e640 | |
Loïc Bartoletti | 6b9e7b2eec | |
Val Lorentz | 574fc0190a | |
Val Lorentz | a873cee637 | |
Val Lorentz | f9a7a96108 | |
ruevs | bce25bb0e2 | |
verylowfreq | f7415048a5 | |
whitequark | 5ca6d04e02 | |
Adam Strzelecki | cf4defcd47 | |
Maximilian Federle | c65e31bece | |
ruevs | bc4244e099 | |
Ryan Pavlik | ce6c4ddeb5 | |
Ryan Pavlik | 29263a8d41 | |
Ryan Pavlik | 6951c71785 | |
tinywrkb | e6e217b7df | |
tinywrkb | 1c5db4d564 | |
tinywrkb | 2d19afaaef | |
tinywrkb | 63e420ed11 | |
tinywrkb | c7dbd54e01 | |
tinywrkb | 2fdcf228ff | |
tinywrkb | d5f4e6f200 | |
tinywrkb | 356e6759b3 | |
tinywrkb | 8314d74c59 | |
tinywrkb | fef5cc4e4b | |
tinywrkb | 4a210bdf75 | |
Ryan Pavlik | c4522dbd0d | |
Ryan Pavlik | c2f65cac12 | |
Ryan Pavlik | b5333608e9 | |
ruevs | d6e1b23006 | |
strzinek | 3b133bfd6d | |
verylowfreq | c20c4dab30 | |
ruevs | 1e1e655848 | |
robnee | b48ce240c7 | |
phkahler | be3489e3a0 | |
phkahler | 3fe6349563 | |
Paul Kahler | 00418824f9 | |
Ryan Pavlik | 70bde63cb3 | |
Ryan Pavlik | ec1c2289e5 | |
Paul Kahler | dfbefa60f5 | |
Paul Kahler | a4416a4cae | |
Paul Kahler | 0c09d9df0c | |
phkahler | ab00823acc | |
carelinus | b334996c49 | |
Maximilian Federle | 6a6fffed0e | |
Paul Kahler | 0cec15c97d | |
Paul Kahler | 2e4a4dee52 | |
Paul Kahler | 7e2aba466a | |
dustinhartlyn | fc16cdb370 | |
Koen Schmeets | b429141c28 | |
phkahler | c5ea9a44e1 | |
ruevs | 2383a39636 | |
ruevs | d8b5281fc9 | |
Maximilian Federle | 43795f5dac | |
ruevs | 026f936989 | |
phkahler | 465506670c | |
Mustafa Halil | 7fa72500c8 | |
ruevs | a1b2db5e18 | |
phkahler | 61cc28f8b2 | |
ruevs | 859df9f43d | |
phkahler | b399d9a1ec | |
ruevs | 5cb299b2be | |
ruevs | f94fc89587 | |
ruevs | bb7a6cbbba | |
herrgahr | f1e47e6554 | |
Ryan Pavlik | 5efc148074 | |
ruevs | 79a6463856 | |
ruevs | 3eaa99cb25 | |
Ryan Pavlik | 3d482f0d52 | |
ruevs | 9ba7ab5544 | |
ruevs | 3136493a6a | |
ruevs | 892477ee43 | |
ruevs | cc4307a2a9 | |
luzpaz | ae4337f066 | |
Maximilian Federle | 39ca23f38e | |
Maximilian Federle | 34efb6de77 | |
Koen Schmeets | b71c728262 | |
Koen Schmeets | 8f049c5a14 | |
phkahler | 74d7db879e | |
phkahler | 5315a69a1e | |
Ryan Pavlik | 18dc8ee12c | |
Ryan Pavlik | 8ab70c2c8d | |
Ryan Pavlik | 6edeb66e3d | |
Ryan Pavlik | 42f5d3ab0d | |
Koen Schmeets | 2521dcadc4 | |
Ryan Pavlik | 605b48e6c0 | |
EvilSpirit | c0f075671b | |
Koen Schmeets | 4ad5d42a24 | |
Koen Schmeets | c66e6cbacc | |
Ryan Pavlik | dca5ce607d | |
Ryan Pavlik | ac91809a96 | |
EvilSpirit | e528fabbe4 | |
EvilSpirit | aec46b608e | |
EvilSpirit | 3ba40230dd | |
EvilSpirit | 2f31673708 | |
EvilSpirit | 4d58f95b43 | |
EvilSpirit | 7f86a78472 | |
EvilSpirit | 708a08f04b | |
Ryan Pavlik | 974175dfbc | |
Ryan Pavlik | 006539b945 | |
Ryan Pavlik | a1e18b83cb | |
Ryan Pavlik | 6d40eface2 | |
Ryan Pavlik | 71c6492d6d | |
Ryan Pavlik | b86e0dec84 | |
ruevs | e07b082eb8 | |
ruevs | 2450010bbf | |
Tom Sutcliffe | 91db627a81 | |
phkahler | 85f6ec4144 | |
ruevs | df3ef2ab0e | |
Maximilian Federle | eb17248bd5 | |
Simon Wells | 2a722c16b8 | |
Simon Wells | 267c002975 | |
MX_Master | a45e84a2ff | |
OlesyaGerasimenko | 2cd0ee4b33 | |
phkahler | bb1938903b | |
andesfreedesign | 4afa810173 | |
Maximilian Federle | 6bc63e92b0 | |
phkahler | 0eab7f783b | |
tomsci | 8cfe1d4bd7 | |
phkahler | 4bf9df2385 | |
tomsci | e1b0784b31 | |
tomsci | 31a709e2c8 | |
Tom Sutcliffe | 7e823df94a | |
Tom Sutcliffe | f71c527e23 | |
phkahler | f47cf65f41 | |
Tom Sutcliffe | b87987922f | |
Tom Sutcliffe | 4db3e90b81 | |
Koen Schmeets | 0a3504c30a | |
Tom Sutcliffe | 5edb2eebf6 | |
phkahler | 3e595002fe | |
Tom Sutcliffe | e86eb65985 | |
Tom Sutcliffe | 959cf5ba75 | |
Tom Sutcliffe | 41e3668f89 | |
Tom Sutcliffe | 2fb6119de8 | |
Tom Sutcliffe | 645febfcd6 | |
Tom Sutcliffe | c19bd8cc99 | |
Tom Sutcliffe | b65a0be3d6 | |
Tom Sutcliffe | 56719415de | |
phkahler | 1b8e1dec65 | |
phkahler | 06a1f8031d | |
phkahler | f6bb0a2d35 | |
phkahler | 2afd6103d9 | |
phkahler | a97b77c1e5 | |
luz paz | 37da0f3341 | |
app4soft | ddb76324af | |
phkahler | 002b12484e | |
Eric Chan | 37de364257 | |
Maxipaille | 4308dc136b | |
Olivier JANIN | 3ccf7845f5 |
|
@ -1,6 +1,6 @@
|
||||||
### System information
|
### System information
|
||||||
|
|
||||||
- **SolveSpace version:** <!--e.g. 3.0~3dd2fc00; go to Help → About...-->
|
- **SolveSpace version:** <!--e.g. 3.1~70bde63c; go to Help → About… / SolveSpace → About SolveSpace (macOS)-->
|
||||||
- **Operating system:** <!--e.g. Debian testing-->
|
- **Operating system:** <!--e.g. Debian testing-->
|
||||||
|
|
||||||
### Expected behavior
|
### Expected behavior
|
||||||
|
|
|
@ -14,13 +14,13 @@ CMAKE_GENERATOR="Unix Makefiles"
|
||||||
CMAKE_PREFIX_PATH=""
|
CMAKE_PREFIX_PATH=""
|
||||||
if [ "$2" = "arm64" ]; then
|
if [ "$2" = "arm64" ]; then
|
||||||
OSX_ARCHITECTURE="arm64"
|
OSX_ARCHITECTURE="arm64"
|
||||||
CMAKE_PREFIX_PATH="/tmp/libomp-arm64/libomp/11.0.1"
|
CMAKE_PREFIX_PATH=$(find /tmp/libomp-arm64/libomp -depth 1)
|
||||||
git apply cmake/libpng-macos-arm64.patch || echo "Could not apply patch, probably already patched..."
|
git apply cmake/libpng-macos-arm64.patch || echo "Could not apply patch, probably already patched..."
|
||||||
mkdir build-arm64 || true
|
mkdir build-arm64 || true
|
||||||
cd build-arm64
|
cd build-arm64
|
||||||
elif [ "$2" = "x86_64" ]; then
|
elif [ "$2" = "x86_64" ]; then
|
||||||
OSX_ARCHITECTURE="x86_64"
|
OSX_ARCHITECTURE="x86_64"
|
||||||
CMAKE_PREFIX_PATH="/tmp/libomp-x86_64/libomp/11.0.1"
|
CMAKE_PREFIX_PATH=$(find /tmp/libomp-x86_64/libomp -depth 1)
|
||||||
mkdir build || true
|
mkdir build || true
|
||||||
cd build
|
cd build
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
#!/bin/sh -xe
|
#!/bin/sh -xe
|
||||||
|
|
||||||
if [ "$1" = "ci" ]; then
|
if [ "$1" = "ci" ]; then
|
||||||
curl -L https://bintray.com/homebrew/bottles/download_file?file_path=libomp-11.0.1.arm64_big_sur.bottle.tar.gz --output /tmp/libomp-arm64.tar.gz
|
armloc=$(brew fetch --bottle-tag=arm64_ventura libomp | grep -i downloaded | grep tar.gz | cut -f2 -d:)
|
||||||
|
x64loc=$(brew fetch --bottle-tag=ventura libomp | grep -i downloaded | grep tar.gz | cut -f2 -d:)
|
||||||
|
cp $armloc /tmp/libomp-arm64.tar.gz
|
||||||
mkdir /tmp/libomp-arm64 || true
|
mkdir /tmp/libomp-arm64 || true
|
||||||
tar -xzvf /tmp/libomp-arm64.tar.gz -C /tmp/libomp-arm64
|
tar -xzvf /tmp/libomp-arm64.tar.gz -C /tmp/libomp-arm64
|
||||||
curl -L https://bintray.com/homebrew/bottles/download_file?file_path=libomp-11.0.1.big_sur.bottle.tar.gz --output /tmp/libomp-x86_64.tar.gz
|
cp $x64loc /tmp/libomp-x86_64.tar.gz
|
||||||
mkdir /tmp/libomp-x86_64 || true
|
mkdir /tmp/libomp-x86_64 || true
|
||||||
tar -xzvf /tmp/libomp-x86_64.tar.gz -C /tmp/libomp-x86_64
|
tar -xzvf /tmp/libomp-x86_64.tar.gz -C /tmp/libomp-x86_64
|
||||||
else
|
else
|
||||||
brew install libomp
|
brew install libomp
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git submodule update --init extlib/cairo extlib/freetype extlib/libdxfrw extlib/libpng extlib/mimalloc extlib/pixman extlib/zlib
|
git submodule update --init extlib/cairo extlib/freetype extlib/libdxfrw extlib/libpng extlib/mimalloc extlib/pixman extlib/zlib extlib/eigen
|
||||||
|
|
|
@ -7,4 +7,4 @@ sudo apt-get install -q -y \
|
||||||
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev libgl-dev \
|
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev libgl-dev \
|
||||||
libgl-dev libglu-dev libspnav-dev
|
libgl-dev libglu-dev libspnav-dev
|
||||||
|
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
||||||
|
|
|
@ -58,34 +58,29 @@ hdiutil create -srcfolder "${app}" "${dmg}"
|
||||||
# sign the .dmg
|
# sign the .dmg
|
||||||
codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${dmg}"
|
codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${dmg}"
|
||||||
|
|
||||||
# notarize and store request uuid in variable
|
if ! command -v xcrun >/dev/null || ! xcrun --find notarytool >/dev/null; then
|
||||||
notarize_uuid=$(xcrun altool --notarize-app --primary-bundle-id "${bundle_id}" --username "${MACOS_APPSTORE_USERNAME}" --password "${MACOS_APPSTORE_APP_PASSWORD}" --file "${dmg}" 2>&1 | grep RequestUUID | awk '{print $3'})
|
echo "Notarytool is not present in the system. Notarization has failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo $notarize_uuid
|
# Submit the package for notarization
|
||||||
|
notarization_output=$(
|
||||||
|
xcrun notarytool submit "${dmg}" \
|
||||||
|
--apple-id "hello@koenschmeets.nl" \
|
||||||
|
--password "${MACOS_APPSTORE_APP_PASSWORD}" \
|
||||||
|
--team-id "8X77K9NDG3" \
|
||||||
|
--wait 2>&1)
|
||||||
|
|
||||||
# wait a bit so we don't get errors during checking
|
if [ $? -eq 0 ]; then
|
||||||
sleep 10
|
# Extract the operation ID from the output
|
||||||
|
operation_id=$(echo "$notarization_output" | awk '/RequestUUID/ {print $NF}')
|
||||||
success=0
|
echo "Notarization submitted. Operation ID: $operation_id"
|
||||||
for (( ; ; ))
|
exit 0
|
||||||
do
|
|
||||||
echo "Checking progress..."
|
|
||||||
progress=$(xcrun altool --notarization-info "${notarize_uuid}" -u "${MACOS_APPSTORE_USERNAME}" -p "${MACOS_APPSTORE_APP_PASSWORD}" 2>&1)
|
|
||||||
# echo "${progress}"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ] || [[ "${progress}" =~ "Invalid" ]] ; then
|
|
||||||
echo "Error with notarization. Exiting"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${progress}" =~ "success" ]]; then
|
|
||||||
success=1
|
|
||||||
break
|
|
||||||
else
|
else
|
||||||
echo "Not completed yet. Sleeping for 10 seconds"
|
echo "Notarization failed. Error: $notarization_output"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
sleep 10
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
# staple
|
# staple
|
||||||
xcrun stapler staple "${dmg}"
|
xcrun stapler staple "${dmg}"
|
|
@ -12,17 +12,19 @@ jobs:
|
||||||
cancel_previous_runs:
|
cancel_previous_runs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Cancel Previous Runs
|
name: Cancel Previous Runs
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: styfle/cancel-workflow-action@0.8.0
|
- uses: styfle/cancel-workflow-action
|
||||||
with:
|
with:
|
||||||
access_token: ${{ github.token }}
|
access_token: ${{ github.token }}
|
||||||
|
|
||||||
test_ubuntu:
|
test_ubuntu:
|
||||||
needs: [cancel_previous_runs]
|
needs: [cancel_previous_runs]
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
name: Test Ubuntu
|
name: Test Ubuntu
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-ubuntu.sh
|
run: .github/scripts/install-ubuntu.sh
|
||||||
- name: Build & Test
|
- name: Build & Test
|
||||||
|
@ -33,7 +35,7 @@ jobs:
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
name: Test Windows
|
name: Test Windows
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-windows.sh
|
run: .github/scripts/install-windows.sh
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -43,10 +45,10 @@ jobs:
|
||||||
|
|
||||||
test_macos:
|
test_macos:
|
||||||
needs: [cancel_previous_runs]
|
needs: [cancel_previous_runs]
|
||||||
runs-on: macos-10.15
|
runs-on: macos-latest
|
||||||
name: Test macOS
|
name: Test macOS
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-macos.sh ci
|
run: .github/scripts/install-macos.sh ci
|
||||||
- name: Build & Test
|
- name: Build & Test
|
||||||
|
@ -57,7 +59,7 @@ jobs:
|
||||||
name: Build Release Windows
|
name: Build Release Windows
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-windows.sh
|
run: .github/scripts/install-windows.sh
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -65,7 +67,7 @@ jobs:
|
||||||
run: .github/scripts/build-windows.sh release
|
run: .github/scripts/build-windows.sh release
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows
|
name: windows
|
||||||
path: build/bin/RelWithDebInfo/solvespace.exe
|
path: build/bin/RelWithDebInfo/solvespace.exe
|
||||||
|
@ -75,7 +77,7 @@ jobs:
|
||||||
name: Build Release Windows (OpenMP)
|
name: Build Release Windows (OpenMP)
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-windows.sh
|
run: .github/scripts/install-windows.sh
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -83,7 +85,7 @@ jobs:
|
||||||
run: .github/scripts/build-windows.sh release openmp
|
run: .github/scripts/build-windows.sh release openmp
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-openmp
|
name: windows-openmp
|
||||||
path: build/bin/RelWithDebInfo/solvespace-openmp.exe
|
path: build/bin/RelWithDebInfo/solvespace-openmp.exe
|
||||||
|
@ -91,9 +93,9 @@ jobs:
|
||||||
build_release_macos:
|
build_release_macos:
|
||||||
needs: [test_ubuntu, test_windows, test_macos]
|
needs: [test_ubuntu, test_windows, test_macos]
|
||||||
name: Build Release macOS
|
name: Build Release macOS
|
||||||
runs-on: macos-10.15
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-macos.sh ci
|
run: .github/scripts/install-macos.sh ci
|
||||||
- name: Build & Test
|
- name: Build & Test
|
||||||
|
@ -107,121 +109,56 @@ jobs:
|
||||||
MACOS_APPSTORE_USERNAME: ${{ secrets.MACOS_APPSTORE_USERNAME }}
|
MACOS_APPSTORE_USERNAME: ${{ secrets.MACOS_APPSTORE_USERNAME }}
|
||||||
MACOS_DEVELOPER_ID: ${{ secrets.MACOS_DEVELOPER_ID }}
|
MACOS_DEVELOPER_ID: ${{ secrets.MACOS_DEVELOPER_ID }}
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos
|
name: macos
|
||||||
path: build/bin/SolveSpace.dmg
|
path: build/bin/SolveSpace.dmg
|
||||||
|
|
||||||
deploy_snap_amd64:
|
# deploy_snap_amd64:
|
||||||
needs: [test_ubuntu, test_windows, test_macos]
|
# needs: [test_ubuntu, test_windows, test_macos]
|
||||||
name: Deploy AMD64 Snap
|
# name: Deploy AMD64 Snap
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v2
|
# - uses: actions/checkout@v4
|
||||||
- name: Set Up Source
|
# - name: Fetch Tags
|
||||||
run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
|
# run: git fetch --force --tags
|
||||||
- name: Build Snap
|
# - name: Set Up Source
|
||||||
id: build
|
# run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
|
||||||
uses: diddlesnaps/snapcraft-multiarch-action@v1
|
# - name: Build Snap
|
||||||
with:
|
# uses: snapcore/action-build@v1
|
||||||
path: pkg/snap
|
# id: build
|
||||||
- name: Upload & Release to Edge
|
# with:
|
||||||
if: github.event_name == 'push'
|
# path: pkg/snap
|
||||||
uses: snapcore/action-publish@v1
|
# - name: Upload & Release to Edge
|
||||||
with:
|
# if: github.event_name == 'push'
|
||||||
store_login: ${{ secrets.SNAPSTORE_LOGIN }}
|
# uses: snapcore/action-publish@v1
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
# env:
|
||||||
release: edge
|
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPSTORE_LOGIN }}
|
||||||
- name: Upload & Release to Beta + Edge
|
# with:
|
||||||
if: github.event_name == 'release'
|
# snap: ${{ steps.build.outputs.snap }}
|
||||||
uses: snapcore/action-publish@v1
|
# release: edge
|
||||||
with:
|
# - name: Upload & Release to Beta + Edge
|
||||||
store_login: ${{ secrets.SNAPSTORE_LOGIN }}
|
# if: github.event_name == 'release'
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
# uses: snapcore/action-publish@v1
|
||||||
release: edge,beta
|
# with:
|
||||||
|
# store_login: ${{ secrets.SNAPSTORE_LOGIN }}
|
||||||
deploy_snap_arm64:
|
# snap: ${{ steps.build.outputs.snap }}
|
||||||
needs: [test_ubuntu, test_windows, test_macos]
|
# release: edge,beta
|
||||||
name: Deploy ARM64 Snap
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: docker/setup-qemu-action@v1
|
|
||||||
with:
|
|
||||||
image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set Up Source
|
|
||||||
run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
|
|
||||||
- name: Build Snap
|
|
||||||
id: build
|
|
||||||
uses: diddlesnaps/snapcraft-multiarch-action@v1
|
|
||||||
with:
|
|
||||||
path: pkg/snap
|
|
||||||
architecture: arm64
|
|
||||||
- name: Upload & Release to Edge
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: snapcore/action-publish@v1
|
|
||||||
with:
|
|
||||||
store_login: ${{ secrets.SNAPSTORE_LOGIN }}
|
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
|
||||||
release: edge
|
|
||||||
- name: Upload & Release to Beta + Edge
|
|
||||||
if: github.event_name == 'release'
|
|
||||||
uses: snapcore/action-publish@v1
|
|
||||||
with:
|
|
||||||
store_login: ${{ secrets.SNAPSTORE_LOGIN }}
|
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
|
||||||
release: edge,beta
|
|
||||||
|
|
||||||
update_edge_release:
|
|
||||||
name: Update Edge Release
|
|
||||||
needs: [build_release_windows, build_release_windows_openmp, build_release_macos]
|
|
||||||
if: github.event_name == 'push' && !cancelled()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
steps:
|
|
||||||
- name: Delete Old Edge Release
|
|
||||||
uses: dev-drprasad/delete-tag-and-release@v0.2.0
|
|
||||||
with:
|
|
||||||
delete_release: true
|
|
||||||
tag_name: edge
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Wait
|
|
||||||
shell: bash
|
|
||||||
run: sleep 60
|
|
||||||
- name: Create New Edge Release
|
|
||||||
id: create_release
|
|
||||||
uses: softprops/action-gh-release@35d938cf01f60fbe522917c81be1e892074f6ad6
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: edge
|
|
||||||
name: Edge
|
|
||||||
prerelease: true
|
|
||||||
draft: false
|
|
||||||
body: ${{ github.event.head_commit.message }}
|
|
||||||
|
|
||||||
upload_release_assets:
|
upload_release_assets:
|
||||||
name: Upload Release Assets
|
name: Upload Release Assets
|
||||||
needs: [build_release_windows, build_release_windows_openmp, build_release_macos, update_edge_release]
|
needs: [build_release_windows, build_release_windows_openmp, build_release_macos]
|
||||||
if: "!cancelled()"
|
if: "!cancelled() && github.event_name == 'release'"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download All Workflow Artifacts
|
- name: Download All Workflow Artifacts
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v4
|
||||||
- name: Get Release Upload URL
|
- name: Get Release Upload URL
|
||||||
id: get_upload_url
|
id: get_upload_url
|
||||||
env:
|
env:
|
||||||
event_name: ${{ github.event_name }}
|
|
||||||
event: ${{ toJson(github.event) }}
|
event: ${{ toJson(github.event) }}
|
||||||
edge_upload_url: ${{ needs.update_edge_release.outputs.upload_url }}
|
|
||||||
run: |
|
run: |
|
||||||
if [ "$event_name" = "release" ]; then
|
|
||||||
upload_url=$(echo "$event" | jq -r ".release.upload_url")
|
upload_url=$(echo "$event" | jq -r ".release.upload_url")
|
||||||
else
|
|
||||||
upload_url="$edge_upload_url"
|
|
||||||
fi
|
|
||||||
echo "::set-output name=upload_url::$upload_url"
|
echo "::set-output name=upload_url::$upload_url"
|
||||||
echo "Upload URL: $upload_url"
|
echo "Upload URL: $upload_url"
|
||||||
- name: Upload solvespace.exe
|
- name: Upload solvespace.exe
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
name: Source Tarball
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create_tarball:
|
||||||
|
name: Create & Upload Tarball
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Pack Tarball
|
||||||
|
id: pack_tarball
|
||||||
|
run: |
|
||||||
|
version="${GITHUB_REF#refs/tags/v}"
|
||||||
|
dir_name="solvespace-${version}"
|
||||||
|
archive_name="${dir_name}.tar.xz"
|
||||||
|
archive_path="${HOME}/${archive_name}"
|
||||||
|
commit_sha="$GITHUB_SHA"
|
||||||
|
|
||||||
|
sed -e 's/^\(include(GetGitCommitHash)\)/#\1/' \
|
||||||
|
-e 's/^# \(set(GIT_COMMIT_HASH\).*/\1 '"$commit_sha"')/' \
|
||||||
|
-i CMakeLists.txt
|
||||||
|
|
||||||
|
echo "::set-output name=archive_name::${archive_name}"
|
||||||
|
echo "::set-output name=archive_path::${archive_path}"
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
tar \
|
||||||
|
--exclude-vcs \
|
||||||
|
--transform "s:^solvespace:${dir_name}:" \
|
||||||
|
-cvaf \
|
||||||
|
${archive_path} \
|
||||||
|
solvespace
|
||||||
|
- name: Get Release Upload URL
|
||||||
|
id: get_upload_url
|
||||||
|
env:
|
||||||
|
event: ${{ toJson(github.event) }}
|
||||||
|
run: |
|
||||||
|
upload_url=$(echo "$event" | jq -r ".release.upload_url")
|
||||||
|
echo "::set-output name=upload_url::$upload_url"
|
||||||
|
echo "Upload URL: $upload_url"
|
||||||
|
- name: Upload Tarball
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
|
||||||
|
asset_path: ${{ steps.pack_tarball.outputs.archive_path }}
|
||||||
|
asset_name: ${{ steps.pack_tarball.outputs.archive_name }}
|
||||||
|
asset_content_type: binary/octet-stream
|
|
@ -12,10 +12,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_ubuntu:
|
test_ubuntu:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
name: Test Ubuntu
|
name: Test Ubuntu
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-ubuntu.sh
|
run: .github/scripts/install-ubuntu.sh
|
||||||
- name: Build & Test
|
- name: Build & Test
|
||||||
|
@ -25,7 +25,7 @@ jobs:
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
name: Test Windows
|
name: Test Windows
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-windows.sh
|
run: .github/scripts/install-windows.sh
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -34,11 +34,28 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
test_macos:
|
test_macos:
|
||||||
runs-on: macos-10.15
|
runs-on: macos-latest
|
||||||
name: Test macOS
|
name: Test macOS
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: .github/scripts/install-macos.sh ci
|
run: .github/scripts/install-macos.sh ci
|
||||||
- name: Build & Test
|
- name: Build & Test
|
||||||
run: .github/scripts/build-macos.sh debug arm64 && .github/scripts/build-macos.sh debug x86_64
|
run: .github/scripts/build-macos.sh debug arm64 && .github/scripts/build-macos.sh debug x86_64
|
||||||
|
|
||||||
|
test_flatpak:
|
||||||
|
name: Test Flatpak x86_64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: bilelmoussaoui/flatpak-github-actions:freedesktop-21.08
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
|
with:
|
||||||
|
bundle: "solvespace.flatpak"
|
||||||
|
manifest-path: "pkg/flatpak/com.solvespace.SolveSpace.json"
|
||||||
|
cache-key: flatpak-builder-${{ github.sha }}
|
||||||
|
|
|
@ -14,3 +14,8 @@
|
||||||
/obj-*/
|
/obj-*/
|
||||||
/*.slvs
|
/*.slvs
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
out/
|
||||||
|
.vs/
|
||||||
|
CMakeSettings.json
|
||||||
|
|
|
@ -23,3 +23,6 @@
|
||||||
[submodule "extlib/mimalloc"]
|
[submodule "extlib/mimalloc"]
|
||||||
path = extlib/mimalloc
|
path = extlib/mimalloc
|
||||||
url = https://github.com/microsoft/mimalloc
|
url = https://github.com/microsoft/mimalloc
|
||||||
|
[submodule "extlib/eigen"]
|
||||||
|
path = extlib/eigen
|
||||||
|
url = https://gitlab.com/libeigen/eigen.git
|
||||||
|
|
92
CHANGELOG.md
92
CHANGELOG.md
|
@ -1,6 +1,98 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
3.x - development
|
||||||
|
---
|
||||||
|
|
||||||
|
Geometric Modelling Kernel (NURBS)
|
||||||
|
|
||||||
|
* Improve the difference boolean operations.
|
||||||
|
|
||||||
|
Constraints (new and improved):
|
||||||
|
|
||||||
|
* Add Parallel and Perpendicular constraints for 2 faces.
|
||||||
|
* The equal angle constraint is moved to the `N` shortcut and menu item to allow equal length (`Q`) to be applied to three or four lines.
|
||||||
|
|
||||||
|
Allow these constraints to be applied to more entities at once:
|
||||||
|
* More than two line Segments - equal length.
|
||||||
|
* More than two Arcs and/or circles - equal diameter/radius.
|
||||||
|
* Any number of Lines - horizontal or vertical.
|
||||||
|
* More than two points - horizontal or vertical.
|
||||||
|
* Point on face can be applied to a point and 1-3 faces at once.
|
||||||
|
* More than two points coincident.
|
||||||
|
|
||||||
|
Sketching
|
||||||
|
|
||||||
|
* `Image` sketch elements are not copied in 3d groups (extrude, lathe, revolve, helix) by default. `Toggle Construction` for an image to get the old behavior.
|
||||||
|
|
||||||
|
Translations (now in 10 languages!)
|
||||||
|
* Added Czech cs_CZ.
|
||||||
|
* Added Japanese ja_JP.
|
||||||
|
* Update translation for French fr_FR, Russian ru_RU and Chinese zh_CN.
|
||||||
|
|
||||||
|
Other User interface changes:
|
||||||
|
* `CTRL+Tab` hides/shows the toolbar.
|
||||||
|
* Marquee selection of line segments is now precise.
|
||||||
|
* Speed up the animation when moving the view, for example when pressing `F2` or `F3`.
|
||||||
|
* Pressing ESC while drawing a sketch entity now deletes the entity rather than completing it.
|
||||||
|
* `CTRL+Shift+S` shortcut for "Save As..."
|
||||||
|
* New option "use camera mouse navigation" for camera (instead of the default model) rotation navigation.
|
||||||
|
* Sketches can be displayed with only dimensions visible (the button controlling visibility of constraints in the Property Browser has a new state).
|
||||||
|
* More entity types described in the text screens.
|
||||||
|
|
||||||
|
Other
|
||||||
|
|
||||||
|
* Merged and improved the experimental Web version (Emscripten port).
|
||||||
|
* Better Flatpack support.
|
||||||
|
* Several bug fixes and usability improvements.
|
||||||
|
* Allow 32 bit SolveSpace to access up to 4GB of RAM to allow working on larger projects.
|
||||||
|
|
||||||
|
3.1
|
||||||
|
---
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
* Arcs length ratio and difference.
|
||||||
|
* Arc & Line length ratio and difference.
|
||||||
|
* Allow comments to be associated with point entities.
|
||||||
|
|
||||||
|
Sketching:
|
||||||
|
|
||||||
|
* Support for pan, zoom and rotate trackpad gestures on macOS
|
||||||
|
* Add "exploded view" to sketches via "\\" key. Shows sketch elements separated
|
||||||
|
by a configurable distance perpendicular to the sketch plane.
|
||||||
|
* Added Feet-Inches as a unit of measure. Inputs are still in inches.
|
||||||
|
But the display shows feet, inches, and fraction of an inch.
|
||||||
|
* Added an optional "pitch" parameter to helix extrusions (in the text window)
|
||||||
|
* Allow use of Point & Normal to define "sketch-in-new-workplane".
|
||||||
|
* Update "Property Browser" live while dragging the sketch.
|
||||||
|
|
||||||
|
MISC:
|
||||||
|
|
||||||
|
* Add a link to the GitHub commit from which SolveSpace was built in the Help
|
||||||
|
menu.
|
||||||
|
* Make all points, vectors and normals shown in the Property Browser into
|
||||||
|
active links. This makes them explorable and selectable.
|
||||||
|
* Load 16bit PNG images correctly by re-scaling to 8bit.
|
||||||
|
* Fixed hang when trying to display characters missing from the embedded font.
|
||||||
|
* The main window vertical size can be as small as the toolbar.
|
||||||
|
* Configurable "SafeHeight" parameter instead of the fixed 5mm for G-code export.
|
||||||
|
* Add Spanish / Argentina translation.
|
||||||
|
* Move "perspective factor", "lighting direction" and "explode distance" from
|
||||||
|
the "configuration" screen to the "view" screen.
|
||||||
|
* Add a "∆" suffix to groups which have "force to triangle mesh" ticked
|
||||||
|
* Gray the group name in the text window for groups with suppressed solid model.
|
||||||
|
* Added the ability to Link STL files.
|
||||||
|
* When linking circuit boards (IDF .emn files) show keepout regions as construction entities.
|
||||||
|
|
||||||
|
Performance:
|
||||||
|
|
||||||
|
* Speed up sketches with many constraints by roughly 8x by using the Eigen
|
||||||
|
library in the solver. The maximum unknowns increased from 1024 to 2048.
|
||||||
|
* Add a "suppress dof calculation" setting to groups - increases performance for
|
||||||
|
complex sketches.
|
||||||
|
* More changes to the ID list implementation.
|
||||||
|
|
||||||
3.0
|
3.0
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
|
||||||
" mkdir build && cd build && cmake ..")
|
" mkdir build && cd build && cmake ..")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
list(APPEND CMAKE_MODULE_PATH
|
||||||
"${CMAKE_SOURCE_DIR}/cmake/")
|
"${CMAKE_SOURCE_DIR}/cmake/")
|
||||||
|
|
||||||
cmake_policy(SET CMP0048 OLD)
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||||
|
|
||||||
|
@ -25,6 +24,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
# Docs say this must be set before the first project() call
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12" CACHE STRING "macOS minimum supported version")
|
||||||
|
endif()
|
||||||
|
|
||||||
# project
|
# project
|
||||||
|
|
||||||
# NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds
|
# NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds
|
||||||
|
@ -34,10 +38,10 @@ include(GetGitCommitHash)
|
||||||
# and instead uncomment the following, adding the complete git hash of the checkout you are using:
|
# and instead uncomment the following, adding the complete git hash of the checkout you are using:
|
||||||
# set(GIT_COMMIT_HASH 0000000000000000000000000000000000000000)
|
# set(GIT_COMMIT_HASH 0000000000000000000000000000000000000000)
|
||||||
|
|
||||||
set(solvespace_VERSION_MAJOR 3)
|
|
||||||
set(solvespace_VERSION_MINOR 0)
|
|
||||||
string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH)
|
string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH)
|
||||||
project(solvespace LANGUAGES C CXX ASM)
|
project(solvespace
|
||||||
|
VERSION 3.1
|
||||||
|
LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
set(ENABLE_GUI ON CACHE BOOL
|
set(ENABLE_GUI ON CACHE BOOL
|
||||||
"Whether the graphical interface is enabled")
|
"Whether the graphical interface is enabled")
|
||||||
|
@ -53,6 +57,9 @@ set(ENABLE_OPENMP OFF CACHE BOOL
|
||||||
"Whether geometric operations will be parallelized using OpenMP")
|
"Whether geometric operations will be parallelized using OpenMP")
|
||||||
set(ENABLE_LTO OFF CACHE BOOL
|
set(ENABLE_LTO OFF CACHE BOOL
|
||||||
"Whether interprocedural (global) optimizations are enabled")
|
"Whether interprocedural (global) optimizations are enabled")
|
||||||
|
option(FORCE_VENDORED_Eigen3
|
||||||
|
"Whether we should use our bundled Eigen even in the presence of a system copy"
|
||||||
|
OFF)
|
||||||
|
|
||||||
set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)")
|
set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)")
|
||||||
|
|
||||||
|
@ -84,6 +91,10 @@ endif()
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++")
|
||||||
|
# Link 32 bit SolveSpace with --large-address-aware which allows it to access
|
||||||
|
# up to 3GB on a properly configured 32 bit Windows and up to 4GB on 64 bit.
|
||||||
|
# See https://msdn.microsoft.com/en-us/library/aa366778
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--large-address-aware")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Ensure that all platforms use 64-bit IEEE floating point operations for consistency;
|
# Ensure that all platforms use 64-bit IEEE floating point operations for consistency;
|
||||||
|
@ -109,7 +120,12 @@ endif()
|
||||||
if(ENABLE_OPENMP)
|
if(ENABLE_OPENMP)
|
||||||
find_package( OpenMP REQUIRED )
|
find_package( OpenMP REQUIRED )
|
||||||
if(OPENMP_FOUND)
|
if(OPENMP_FOUND)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
|
add_library(slvs_openmp INTERFACE)
|
||||||
|
target_compile_options(slvs_openmp INTERFACE ${OpenMP_CXX_FLAGS})
|
||||||
|
target_link_libraries(slvs_openmp INTERFACE
|
||||||
|
${OpenMP_CXX_LIBRARIES})
|
||||||
|
target_include_directories(slvs_openmp INTERFACE SYSTEM
|
||||||
|
${OpenMP_CXX_INCLUDE_DIRS})
|
||||||
message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} )
|
message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} )
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
@ -170,6 +186,10 @@ if(APPLE)
|
||||||
set(CMAKE_FIND_FRAMEWORK LAST)
|
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(M_LIBRARY "" CACHE STRING "libm (not necessary)" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
message(STATUS "Using in-tree libdxfrw")
|
message(STATUS "Using in-tree libdxfrw")
|
||||||
add_subdirectory(extlib/libdxfrw)
|
add_subdirectory(extlib/libdxfrw)
|
||||||
|
|
||||||
|
@ -181,7 +201,22 @@ set(MI_BUILD_TESTS OFF CACHE BOOL "")
|
||||||
add_subdirectory(extlib/mimalloc EXCLUDE_FROM_ALL)
|
add_subdirectory(extlib/mimalloc EXCLUDE_FROM_ALL)
|
||||||
set(MIMALLOC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/mimalloc/include)
|
set(MIMALLOC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/mimalloc/include)
|
||||||
|
|
||||||
if(WIN32 OR APPLE)
|
if(NOT FORCE_VENDORED_Eigen3)
|
||||||
|
find_package(Eigen3 CONFIG)
|
||||||
|
endif()
|
||||||
|
if(FORCE_VENDORED_Eigen3 OR NOT EIGEN3_INCLUDE_DIRS)
|
||||||
|
message(STATUS "Using in-tree Eigen")
|
||||||
|
set(EIGEN3_FOUND YES)
|
||||||
|
set(EIGEN3_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/eigen)
|
||||||
|
else()
|
||||||
|
message(STATUS "Using system Eigen: ${EIGEN3_INCLUDE_DIRS}")
|
||||||
|
endif()
|
||||||
|
if(NOT EXISTS "${EIGEN3_INCLUDE_DIRS}")
|
||||||
|
message(FATAL_ERROR "Eigen 3 not found on system or in-tree")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
if(WIN32 OR APPLE OR EMSCRIPTEN)
|
||||||
# On Win32 and macOS we use vendored packages, since there is little to no benefit
|
# On Win32 and macOS we use vendored packages, since there is little to no benefit
|
||||||
# to trying to find system versions. In particular, trying to link to libraries from
|
# to trying to find system versions. In particular, trying to link to libraries from
|
||||||
# Homebrew or macOS system libraries into the .app file is highly likely to result
|
# Homebrew or macOS system libraries into the .app file is highly likely to result
|
||||||
|
@ -242,7 +277,7 @@ else()
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
find_package(Freetype REQUIRED)
|
find_package(Freetype REQUIRED)
|
||||||
pkg_check_modules(CAIRO REQUIRED cairo)
|
find_package(Cairo REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# GUI dependencies
|
# GUI dependencies
|
||||||
|
@ -276,7 +311,8 @@ if(ENABLE_GUI)
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
||||||
set(util_LIBRARIES ${APPKIT_LIBRARY})
|
elseif(EMSCRIPTEN)
|
||||||
|
# Everything is built in
|
||||||
else()
|
else()
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
find_package(SpaceWare)
|
find_package(SpaceWare)
|
||||||
|
@ -347,9 +383,19 @@ if(MSVC)
|
||||||
# Same for the (C99) __func__ special variable; we use it only in C++ code.
|
# Same for the (C99) __func__ special variable; we use it only in C++ code.
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__func__=__FUNCTION__")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__func__=__FUNCTION__")
|
||||||
|
|
||||||
|
# Multi-processor Compilation
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||||
|
|
||||||
# We rely on these /we flags. They correspond to the GNU-style flags below as
|
# We rely on these /we flags. They correspond to the GNU-style flags below as
|
||||||
# follows: /w4062=-Wswitch
|
# follows: /w4062=-Wswitch
|
||||||
set(WARNING_FLAGS "${WARNING_FLAGS} /we4062")
|
set(WARNING_FLAGS "${WARNING_FLAGS} /we4062")
|
||||||
|
|
||||||
|
# Link 32 bit SolveSpace with /LARGEADDRESSAWARE which allows it to access
|
||||||
|
# up to 3GB on a properly configured 32 bit Windows and up to 4GB on 64 bit.
|
||||||
|
# See https://msdn.microsoft.com/en-us/library/aa366778
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
|
301
README.md
301
README.md
|
@ -1,172 +1,242 @@
|
||||||
|
# SolveSpace
|
||||||
|
|
||||||
<img src="res/freedesktop/solvespace-scalable.svg" width="70" height="70" alt="SolveSpace Logo" align="left">
|
<img src="res/freedesktop/solvespace-scalable.svg" width="70" height="70" alt="SolveSpace Logo" align="left">
|
||||||
|
|
||||||
SolveSpace
|
|
||||||
==========
|
|
||||||
[![Build Status](https://github.com/solvespace/solvespace/workflows/CD/badge.svg)](https://github.com/solvespace/solvespace/actions)
|
[![Build Status](https://github.com/solvespace/solvespace/workflows/CD/badge.svg)](https://github.com/solvespace/solvespace/actions)
|
||||||
[![solvespace](https://snapcraft.io/solvespace/badge.svg)](https://snapcraft.io/solvespace)
|
[![solvespace](https://snapcraft.io/solvespace/badge.svg)](https://snapcraft.io/solvespace)
|
||||||
[![solvespace](https://snapcraft.io/solvespace/trending.svg?name=0)](https://snapcraft.io/solvespace)
|
[![solvespace](https://snapcraft.io/solvespace/trending.svg?name=0)](https://snapcraft.io/solvespace)
|
||||||
|
|
||||||
This repository contains the source code of [SolveSpace][], a parametric
|
This repository contains the source code of [SolveSpace][], a parametric
|
||||||
2d/3d CAD.
|
2d/3d CAD tool.
|
||||||
|
|
||||||
[solvespace]: http://solvespace.com
|
[solvespace]: https://solvespace.com
|
||||||
|
|
||||||
Community
|
## Community
|
||||||
---------
|
|
||||||
|
|
||||||
The official SolveSpace [website][sswebsite] has [tutorials][sstutorial],
|
The official SolveSpace [website][sswebsite] has [tutorials][sstutorial],
|
||||||
[reference manual][ssref] and a [forum][ssforum]; there is also an official
|
[reference manual][ssref] and a [forum][ssforum]; there is also an official
|
||||||
IRC channel [#solvespace at irc.freenode.net][ssirc].
|
IRC channel [#solvespace at web.libera.chat][ssirc].
|
||||||
|
|
||||||
[sswebsite]: http://solvespace.com/
|
[sswebsite]: http://solvespace.com/
|
||||||
[ssref]: http://solvespace.com/ref.pl
|
[ssref]: http://solvespace.com/ref.pl
|
||||||
[sstutorial]: http://solvespace.com/tutorial.pl
|
[sstutorial]: http://solvespace.com/tutorial.pl
|
||||||
[ssforum]: http://solvespace.com/forum.pl
|
[ssforum]: http://solvespace.com/forum.pl
|
||||||
[ssirc]: https://webchat.freenode.net/?channels=solvespace
|
[ssirc]: https://web.libera.chat/#solvespace
|
||||||
|
|
||||||
Installation
|
## Installation
|
||||||
------------
|
|
||||||
|
|
||||||
### Via official binary packages
|
### Via Official Packages
|
||||||
|
|
||||||
_Official_ release binary packages for macOS (>=10.6 64-bit) and Windows (>=Vista 32-bit) are
|
_Official_ release packages for macOS (>=10.6 64-bit) and Windows
|
||||||
available via [GitHub releases][rel]. These packages are automatically built by
|
(>=Vista 32-bit) are available via [GitHub releases][rel]. These packages are
|
||||||
the SolveSpace maintainers for each stable release.
|
automatically built by the SolveSpace maintainers for each stable release.
|
||||||
|
|
||||||
[rel]: https://github.com/solvespace/solvespace/releases
|
[rel]: https://github.com/solvespace/solvespace/releases
|
||||||
|
|
||||||
|
### Via Flathub
|
||||||
|
|
||||||
|
Official releases can be installed as a Flatpak from Flathub.
|
||||||
|
|
||||||
|
[Get SolveSpace from Flathub](https://flathub.org/apps/details/com.solvespace.SolveSpace)
|
||||||
|
|
||||||
|
These should work on any Linux distribution that supports Flatpak.
|
||||||
|
|
||||||
### Via Snap Store
|
### Via Snap Store
|
||||||
|
|
||||||
Builds from master are automatically released to the `edge` channel in the Snap Store. Those packages contain the latest improvements, but receive less testing than release builds.
|
Official releases can be installed from the `stable` channel.
|
||||||
|
|
||||||
Future official releases will appear in the `stable` channel.
|
Builds from master are automatically released to the `edge` channel in the Snap
|
||||||
|
Store. Those packages contain the latest improvements, but receive less testing
|
||||||
|
than release builds.
|
||||||
|
|
||||||
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/solvespace)
|
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/solvespace)
|
||||||
|
|
||||||
Or install from a terminal:
|
Or install from a terminal:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
snap install --edge solvespace
|
# for the latest stable release:
|
||||||
|
snap install solvespace
|
||||||
|
|
||||||
|
# for the bleeding edge builds from master:
|
||||||
|
snap install solvespace --edge
|
||||||
```
|
```
|
||||||
|
|
||||||
### Via third-party binary packages
|
### Via automated edge builds
|
||||||
|
|
||||||
_Third-party_ nightly binary packages for Debian and Ubuntu are available
|
> :warning: **Edge builds might be unstable or contain severe bugs!**
|
||||||
via [notesalexp.org][notesalexp]. These packages are automatically built from non-released
|
> They are intended for experienced users to test new features or verify bugfixes.
|
||||||
source code. The SolveSpace maintainers do not control the contents of these packages
|
|
||||||
and cannot guarantee their functionality.
|
|
||||||
|
|
||||||
[notesalexp]: https://notesalexp.org/packages/en/source/solvespace/
|
Cutting edge builds from the latest master commit are available as zip archives
|
||||||
|
from the following links:
|
||||||
|
|
||||||
|
- [macOS](https://nightly.link/solvespace/solvespace/workflows/cd/master/macos.zip)
|
||||||
|
- [Windows](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows.zip)
|
||||||
|
- [Windows with OpenMP enabled](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows-openmp.zip)
|
||||||
|
|
||||||
|
Extract the downloaded archive and install or execute the contained file as is
|
||||||
|
appropriate for your platform.
|
||||||
|
|
||||||
### Via source code
|
### Via source code
|
||||||
|
|
||||||
See below.
|
Irrespective of the OS used, before building, check out the project and the
|
||||||
|
necessary submodules:
|
||||||
|
|
||||||
Building on Linux
|
```sh
|
||||||
-----------------
|
git clone https://github.com/solvespace/solvespace
|
||||||
|
cd solvespace
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
You will need `git`. See the platform specific instructions below to install it.
|
||||||
|
|
||||||
|
## Building on Linux
|
||||||
|
|
||||||
### Building for Linux
|
### Building for Linux
|
||||||
|
|
||||||
You will need the usual build tools, CMake, zlib, libpng, cairo, freetype.
|
You will need the usual build tools, CMake, zlib, libpng, cairo, freetype. To
|
||||||
To build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later), pangomm 1.4,
|
build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later),
|
||||||
OpenGL and OpenGL GLU, and optionally, the Space Navigator client library.
|
pangomm 1.4, OpenGL and OpenGL GLU, and optionally, the Space Navigator client
|
||||||
On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
library. On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
||||||
|
|
||||||
sudo apt install git build-essential cmake zlib1g-dev libpng-dev \
|
```sh
|
||||||
|
sudo apt install git build-essential cmake zlib1g-dev libpng-dev \
|
||||||
libcairo2-dev libfreetype6-dev libjson-c-dev \
|
libcairo2-dev libfreetype6-dev libjson-c-dev \
|
||||||
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev \
|
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev \
|
||||||
libgl-dev libglu-dev libspnav-dev
|
libgl-dev libglu-dev libspnav-dev
|
||||||
|
```
|
||||||
|
|
||||||
On a Redhat derivative (e.g. Fedora) the dependencies can be installed with:
|
On a RedHat derivative (e.g. Fedora) the dependencies can be installed with:
|
||||||
|
|
||||||
sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \
|
```sh
|
||||||
|
sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \
|
||||||
cairo-devel freetype-devel json-c-devel \
|
cairo-devel freetype-devel json-c-devel \
|
||||||
fontconfig-devel gtkmm30-devel pangomm-devel \
|
fontconfig-devel gtkmm30-devel pangomm-devel \
|
||||||
mesa-libGL-devel mesa-libGLU-devel libspnav-devel
|
mesa-libGL-devel mesa-libGLU-devel libspnav-devel
|
||||||
|
```
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
mkdir build
|
```sh
|
||||||
cd build
|
mkdir build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
cd build
|
||||||
make
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||||
sudo make install
|
make
|
||||||
|
|
||||||
Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the
|
# Optionally
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
Link Time Optimization is supported by adding `-DENABLE_LTO=ON` to cmake at the
|
||||||
expense of longer build time.
|
expense of longer build time.
|
||||||
|
|
||||||
The graphical interface is built as `build/bin/solvespace`, and the command-line interface
|
The graphical interface is built as `build/bin/solvespace`, and the command-line
|
||||||
is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
interface is built as `build/bin/solvespace-cli`. It is possible to build only
|
||||||
|
the command-line interface by passing the `-DENABLE_GUI=OFF` flag to the cmake
|
||||||
|
invocation.
|
||||||
|
|
||||||
### Building for Windows
|
### Building for Windows
|
||||||
|
|
||||||
Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed to work.
|
Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed
|
||||||
|
to work.
|
||||||
|
|
||||||
You will need the usual build tools, CMake, a Windows cross-compiler, and flatc. On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
You will need the usual build tools, CMake, and a Windows cross-compiler. On a
|
||||||
|
Debian derivative (e.g. Ubuntu) these can be installed with:
|
||||||
|
|
||||||
apt-get install git build-essential cmake mingw-w64
|
```sh
|
||||||
|
apt-get install git build-essential cmake mingw-w64
|
||||||
|
```
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init
|
|
||||||
|
|
||||||
Build 64-bit SolveSpace with the following:
|
Build 64-bit SolveSpace with the following:
|
||||||
|
|
||||||
mkdir build
|
```sh
|
||||||
cd build
|
mkdir build
|
||||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \
|
cd build
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \
|
||||||
-DFLATC=$(which flatc)
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
make
|
make
|
||||||
|
```
|
||||||
|
|
||||||
The graphical interface is built as `build/bin/solvespace.exe`, and the command-line interface
|
The graphical interface is built as `build/bin/solvespace.exe`, and the
|
||||||
is built as `build/bin/solvespace-cli.exe`.
|
command-line interface is built as `build/bin/solvespace-cli.exe`.
|
||||||
|
|
||||||
Space Navigator support will not be available.
|
Space Navigator support will not be available.
|
||||||
|
|
||||||
If using Ubuntu to cross-compile, Ubuntu 17.10 or newer (or, alternatively, MinGW from the Ubuntu
|
### Building for web (very experimental)
|
||||||
17.10 repositories) is required.
|
|
||||||
|
|
||||||
Building on macOS
|
**Please note that this port contains many critical bugs and unimplemented core functions.**
|
||||||
-----------------
|
|
||||||
|
|
||||||
You will need git, XCode tools and CMake. Git and CMake can be installed
|
You will need the usual build tools, cmake and [Emscripten][]. On a Debian derivative (e.g. Ubuntu) dependencies other than Emscripten can be installed with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get install git build-essential cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
First, install and prepare `emsdk`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/emscripten-core/emsdk
|
||||||
|
cd emsdk
|
||||||
|
./emsdk install latest
|
||||||
|
./emsdk activate latest
|
||||||
|
source ./emsdk_env.sh
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_LTO="ON" -DENABLE_TESTS="OFF" -DENABLE_CLI="OFF" -DENABLE_COVERAGE="OFF"
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
The graphical interface is built as multiple files in the `build/bin` directory with names
|
||||||
|
starting with `solvespace`. It can be run locally with `emrun build/bin/solvespace.html`.
|
||||||
|
|
||||||
|
The command-line interface is not available.
|
||||||
|
|
||||||
|
[emscripten]: https://emscripten.org/
|
||||||
|
|
||||||
|
## Building on macOS
|
||||||
|
|
||||||
|
You will need git, XCode tools, CMake and libomp. Git, CMake and libomp can be installed
|
||||||
via [Homebrew][]:
|
via [Homebrew][]:
|
||||||
|
|
||||||
brew install git cmake
|
```sh
|
||||||
|
brew install git cmake libomp
|
||||||
|
```
|
||||||
|
|
||||||
XCode has to be installed via AppStore or [the Apple website][appledeveloper];
|
XCode has to be installed via AppStore or [the Apple website][appledeveloper];
|
||||||
it requires a free Apple ID.
|
it requires a free Apple ID.
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init
|
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
mkdir build
|
```sh
|
||||||
cd build
|
mkdir build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
cd build
|
||||||
make
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the
|
Link Time Optimization is supported by adding `-DENABLE_LTO=ON` to cmake at the
|
||||||
expense of longer build time.
|
expense of longer build time.
|
||||||
|
|
||||||
Alternatively, generate an XCode project, open it, and build the "Release" scheme:
|
Alternatively, generate an XCode project, open it, and build the "Release" scheme:
|
||||||
|
|
||||||
mkdir build
|
```sh
|
||||||
cd build
|
mkdir build
|
||||||
cmake .. -G Xcode
|
cd build
|
||||||
|
cmake .. -G Xcode
|
||||||
|
```
|
||||||
|
|
||||||
The application is built in `build/bin/SolveSpace.app`, the graphical interface executable
|
The application is built in `build/bin/SolveSpace.app`, the graphical interface executable
|
||||||
is `build/bin/SolveSpace.app/Contents/MacOS/SolveSpace`, and the command-line interface executable
|
is `build/bin/SolveSpace.app/Contents/MacOS/SolveSpace`, and the command-line interface executable
|
||||||
|
@ -175,26 +245,26 @@ is `build/bin/SolveSpace.app/Contents/MacOS/solvespace-cli`.
|
||||||
[homebrew]: https://brew.sh/
|
[homebrew]: https://brew.sh/
|
||||||
[appledeveloper]: https://developer.apple.com/download/
|
[appledeveloper]: https://developer.apple.com/download/
|
||||||
|
|
||||||
Building on OpenBSD
|
## Building on OpenBSD
|
||||||
-------------------
|
|
||||||
|
|
||||||
You will need git, cmake, libexecinfo, libpng, gtk3mm and pangomm.
|
You will need git, cmake, libexecinfo, libpng, gtk3mm and pangomm.
|
||||||
These can be installed from the ports tree:
|
These can be installed from the ports tree:
|
||||||
|
|
||||||
pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm
|
```sh
|
||||||
|
pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm
|
||||||
|
```
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
mkdir build
|
```sh
|
||||||
cd build
|
mkdir build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
cd build
|
||||||
make
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
Unfortunately, on OpenBSD, the produced executables are not filesystem location independent
|
Unfortunately, on OpenBSD, the produced executables are not filesystem location independent
|
||||||
and must be installed before use. By default, the graphical interface is installed to
|
and must be installed before use. By default, the graphical interface is installed to
|
||||||
|
@ -202,33 +272,35 @@ and must be installed before use. By default, the graphical interface is install
|
||||||
`/usr/local/bin/solvespace-cli`. It is possible to build only the command-line interface
|
`/usr/local/bin/solvespace-cli`. It is possible to build only the command-line interface
|
||||||
by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
||||||
|
|
||||||
Building on Windows
|
## Building on Windows
|
||||||
-------------------
|
|
||||||
|
|
||||||
You will need [git][gitwin], [cmake][cmakewin] and a C++ compiler
|
You will need [git][gitwin], [cmake][cmakewin] and a C++ compiler
|
||||||
(either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015
|
(either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015
|
||||||
or later is required.
|
or later is required.
|
||||||
|
If gawk is in your path be sure it is a proper Windows port that can handle CL LF line endings.
|
||||||
|
If not CMake may fail in libpng due to some awk scripts - issue #1228.
|
||||||
|
|
||||||
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
### Building with Visual Studio IDE
|
### Building with Visual Studio IDE
|
||||||
|
|
||||||
Check out the git submodules. Create a directory `build` in
|
Create a directory `build` in
|
||||||
the source tree and point cmake-gui to the source tree and that directory.
|
the source tree and point cmake-gui to the source tree and that directory.
|
||||||
Press "Configure" and "Generate", then open `build\solvespace.sln` with
|
Press "Configure" and "Generate", then open `build\solvespace.sln` with
|
||||||
Visual C++ and build it.
|
Visual C++ and build it.
|
||||||
|
|
||||||
### Building with Visual Studio in a command prompt
|
### Building with Visual Studio in a command prompt
|
||||||
|
|
||||||
First, ensure that git and cl (the Visual C++ compiler driver) are in your
|
First, ensure that `git` and `cl` (the Visual C++ compiler driver) are in your
|
||||||
`%PATH%`; the latter is usually done by invoking `vcvarsall.bat` from your
|
`%PATH%`; the latter is usually done by invoking `vcvarsall.bat` from your
|
||||||
Visual Studio install. Then, run the following in cmd or PowerShell:
|
Visual Studio install. Then, run the following in cmd or PowerShell:
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
```bat
|
||||||
cd solvespace
|
mkdir build
|
||||||
git submodule update --init
|
cd build
|
||||||
mkdir build
|
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||||
cd build
|
nmake
|
||||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
```
|
||||||
nmake
|
|
||||||
|
|
||||||
### Building with MinGW
|
### Building with MinGW
|
||||||
|
|
||||||
|
@ -238,25 +310,22 @@ Space Navigator support will be disabled.
|
||||||
First, ensure that git and gcc are in your `$PATH`. Then, run the following
|
First, ensure that git and gcc are in your `$PATH`. Then, run the following
|
||||||
in bash:
|
in bash:
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
```sh
|
||||||
cd solvespace
|
mkdir build
|
||||||
git submodule update --init
|
cd build
|
||||||
mkdir build
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
cd build
|
make
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
```
|
||||||
make
|
|
||||||
|
|
||||||
[gitwin]: https://git-scm.com/download/win
|
[gitwin]: https://git-scm.com/download/win
|
||||||
[cmakewin]: http://www.cmake.org/download/#latest
|
[cmakewin]: http://www.cmake.org/download/#latest
|
||||||
[mingw]: http://www.mingw.org/
|
[mingw]: http://www.mingw.org/
|
||||||
|
|
||||||
Contributing
|
## Contributing
|
||||||
------------
|
|
||||||
|
|
||||||
See the [guide for contributors](CONTRIBUTING.md) for the best way to file issues, contribute code,
|
See the [guide for contributors](CONTRIBUTING.md) for the best way to file issues, contribute code,
|
||||||
and debug SolveSpace.
|
and debug SolveSpace.
|
||||||
|
|
||||||
License
|
## License
|
||||||
-------
|
|
||||||
|
|
||||||
SolveSpace is distributed under the terms of the [GPL v3](COPYING.txt) or later.
|
SolveSpace is distributed under the terms of the [GPL v3](COPYING.txt) or later.
|
||||||
|
|
|
@ -4,12 +4,12 @@ function(disable_warnings)
|
||||||
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE)
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE)
|
||||||
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0" PARENT_SCOPE)
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0 /MP" PARENT_SCOPE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE)
|
||||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0" PARENT_SCOPE)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0 /MP" PARENT_SCOPE)
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
# - Try to find Cairo
|
||||||
|
# Once done, this will define
|
||||||
|
#
|
||||||
|
# CAIRO_FOUND - system has Cairo
|
||||||
|
# CAIRO_INCLUDE_DIRS - the Cairo include directories
|
||||||
|
# CAIRO_LIBRARIES - link these to use Cairo
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
|
||||||
|
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
|
||||||
|
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_check_modules(PC_CAIRO QUIET cairo)
|
||||||
|
|
||||||
|
find_path(CAIRO_INCLUDE_DIRS
|
||||||
|
NAMES cairo.h
|
||||||
|
HINTS ${PC_CAIRO_INCLUDEDIR}
|
||||||
|
${PC_CAIRO_INCLUDE_DIRS}
|
||||||
|
PATH_SUFFIXES cairo
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(CAIRO_LIBRARIES
|
||||||
|
NAMES cairo
|
||||||
|
HINTS ${PC_CAIRO_LIBDIR}
|
||||||
|
${PC_CAIRO_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (CAIRO_INCLUDE_DIRS)
|
||||||
|
if (EXISTS "${CAIRO_INCLUDE_DIRS}/cairo-version.h")
|
||||||
|
file(READ "${CAIRO_INCLUDE_DIRS}/cairo-version.h" CAIRO_VERSION_CONTENT)
|
||||||
|
|
||||||
|
string(REGEX MATCH "#define +CAIRO_VERSION_MAJOR +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}")
|
||||||
|
set(CAIRO_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
string(REGEX MATCH "#define +CAIRO_VERSION_MINOR +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}")
|
||||||
|
set(CAIRO_VERSION_MINOR "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
string(REGEX MATCH "#define +CAIRO_VERSION_MICRO +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}")
|
||||||
|
set(CAIRO_VERSION_MICRO "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
set(CAIRO_VERSION "${CAIRO_VERSION_MAJOR}.${CAIRO_VERSION_MINOR}.${CAIRO_VERSION_MICRO}")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${Cairo_FIND_VERSION}" VERSION_GREATER "${CAIRO_VERSION}")
|
||||||
|
message(FATAL_ERROR "Required version (" ${Cairo_FIND_VERSION} ") is higher than found version (" ${CAIRO_VERSION} ")")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cairo REQUIRED_VARS CAIRO_INCLUDE_DIRS CAIRO_LIBRARIES
|
||||||
|
VERSION_VAR CAIRO_VERSION)
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
CAIRO_INCLUDE_DIRS
|
||||||
|
CAIRO_LIBRARIES
|
||||||
|
)
|
|
@ -16,7 +16,7 @@ if(UNIX)
|
||||||
|
|
||||||
# Support the REQUIRED and QUIET arguments, and set SPACEWARE_FOUND if found.
|
# Support the REQUIRED and QUIET arguments, and set SPACEWARE_FOUND if found.
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SPACEWARE DEFAULT_MSG
|
find_package_handle_standard_args(SpaceWare DEFAULT_MSG
|
||||||
SPACEWARE_LIBRARY SPACEWARE_INCLUDE_DIR)
|
SPACEWARE_LIBRARY SPACEWARE_INCLUDE_DIR)
|
||||||
|
|
||||||
if(SPACEWARE_FOUND)
|
if(SPACEWARE_FOUND)
|
||||||
|
|
|
@ -15,11 +15,11 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}</string>
|
<string>${PROJECT_VERSION}~${solvespace_GIT_HASH}</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}</string>
|
<string>${PROJECT_VERSION}</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© 2008-2016 Jonathan Westhues and other authors</string>
|
<string>© 2008-2024 Jonathan Westhues and other authors</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
set(EMSCRIPTEN 1)
|
||||||
|
|
||||||
|
set(CMAKE_C_OUTPUT_EXTENSION ".o")
|
||||||
|
set(CMAKE_CXX_OUTPUT_EXTENSION ".o")
|
||||||
|
set(CMAKE_EXECUTABLE_SUFFIX ".html")
|
||||||
|
|
||||||
|
set(CMAKE_SIZEOF_VOID_P 4)
|
||||||
|
|
||||||
|
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
|
||||||
|
|
||||||
|
# FIXME(emscripten): Suppress non-c-typedef-for-linkage warnings in solvespace.h
|
||||||
|
add_compile_options(-Wno-non-c-typedef-for-linkage)
|
||||||
|
add_link_options(-s EXPORTED_RUNTIME_METHODS=[allocate])
|
||||||
|
|
||||||
|
# Enable optimization. Workaround for "too many locals" error when runs on browser.
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL Release)
|
||||||
|
add_compile_options(-O2)
|
||||||
|
else()
|
||||||
|
add_compile_options(-O1)
|
||||||
|
endif()
|
|
@ -0,0 +1,8 @@
|
||||||
|
set(CMAKE_SYSTEM_NAME Emscripten)
|
||||||
|
|
||||||
|
set(TRIPLE asmjs-unknown-emscripten)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER emcc)
|
||||||
|
set(CMAKE_CXX_COMPILER em++)
|
||||||
|
|
||||||
|
set(M_LIBRARY m)
|
|
@ -4,3 +4,7 @@ if(MSVC)
|
||||||
set(CMAKE_C_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
set(CMAKE_C_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
||||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG_INIT "-g4")
|
||||||
|
endif()
|
||||||
|
|
|
@ -4,3 +4,7 @@ if(MSVC)
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
set(CMAKE_CXX_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
||||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g4")
|
||||||
|
endif()
|
||||||
|
|
|
@ -9,25 +9,25 @@ by pointers from the entity (font, extra points, etc...)
|
||||||
|
|
||||||
Entities in a sketch are kept in a global array (IdList) referenced by a unique
|
Entities in a sketch are kept in a global array (IdList) referenced by a unique
|
||||||
Id (handle) and can be looked up by Id in log(n) time via binary search. In
|
Id (handle) and can be looked up by Id in log(n) time via binary search. In
|
||||||
order to use binary seach the array must be kept in order sorted by Id. One
|
order to use binary search the array must be kept in order sorted by Id. One
|
||||||
problem is that insertion takes O(n) time because half the list (on average)
|
problem is that insertion takes O(n) time because half the list (on average)
|
||||||
must be shifted to make room for a new item.
|
must be shifted to make room for a new item.
|
||||||
|
|
||||||
The IdList class is a template and is used for more than entites.
|
The IdList class is a template and is used for more than entities.
|
||||||
|
|
||||||
EntityMap:
|
EntityMap:
|
||||||
==========
|
==========
|
||||||
Another important structure is the EntityMap and EntityKey defined in sketch.h
|
Another important structure is the EntityMap and EntityKey defined in sketch.h
|
||||||
This is what allows SovleSpace to update groups when earlier groups in the
|
This is what allows SovleSpace to update groups when earlier groups in the
|
||||||
sketch are changed. If a rectangle is extruded to a box and items are
|
sketch are changed. If a rectangle is extruded to a box and items are
|
||||||
constrained to entites on that box, the user can go back to the sketch and
|
constrained to entities on that box, the user can go back to the sketch and
|
||||||
modify it. Entites can be added, modified an even deleted. So long as the
|
modify it. Entities can be added, modified an even deleted. So long as the
|
||||||
entites that are later used to build upon are kept the later extrude group will
|
entities that are later used to build upon are kept the later extrude group will
|
||||||
pick up the changes from the 2D sketch and anything build on it will remain.
|
pick up the changes from the 2D sketch and anything build on it will remain.
|
||||||
|
|
||||||
The way this works is that each group has a member called remap, which is one of
|
The way this works is that each group has a member called remap, which is one of
|
||||||
these maps. This is where my understanding is fuzzy. At the end of Group.cpp is
|
these maps. This is where my understanding is fuzzy. At the end of Group.cpp is
|
||||||
a function called Group::CopyEntity() which is used to make new sketch entites
|
a function called Group::CopyEntity() which is used to make new sketch entities
|
||||||
when a group is created. These are generally copies of entities in the previous
|
when a group is created. These are generally copies of entities in the previous
|
||||||
group, but there are exceptions. A point will be used to generate a line when
|
group, but there are exceptions. A point will be used to generate a line when
|
||||||
extruding a 2D sketch. A point will also be "copied" to a circle for a Lathe
|
extruding a 2D sketch. A point will also be "copied" to a circle for a Lathe
|
||||||
|
@ -35,7 +35,7 @@ group. For this reason, the entity key is derived by combining its previous key
|
||||||
with something often called the CopyNumber or just remap (unfortunate).
|
with something often called the CopyNumber or just remap (unfortunate).
|
||||||
|
|
||||||
When a group is regenerated (the first time, or after a previous one is
|
When a group is regenerated (the first time, or after a previous one is
|
||||||
modified) entites are copied from the old group to the new one. For Step
|
modified) entities are copied from the old group to the new one. For Step
|
||||||
Translating and Rotating there may be many copies, and the copy number is
|
Translating and Rotating there may be many copies, and the copy number is
|
||||||
literally N for the Nth copy except for the last one which gets an enum - it is
|
literally N for the Nth copy except for the last one which gets an enum - it is
|
||||||
common to constrain the last item, so it gets a large unique number so that
|
common to constrain the last item, so it gets a large unique number so that
|
||||||
|
@ -45,5 +45,5 @@ Remap that was created the same way. This is how constructions are preserved
|
||||||
across underlying changes.
|
across underlying changes.
|
||||||
|
|
||||||
There are some hard limits used in the hash table for the remap mechanism which
|
There are some hard limits used in the hash table for the remap mechanism which
|
||||||
limit the number of entites in a group (but not the global sketch).
|
limit the number of entities in a group (but not the global sketch).
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ POINT_N_ROT_TRANS: Rotates a point via quaternion param[3],param[4],param[5],par
|
||||||
|
|
||||||
POINT_N_COPY: A non-transformed copy of a point - numeric copy?
|
POINT_N_COPY: A non-transformed copy of a point - numeric copy?
|
||||||
|
|
||||||
POINT_N_ROT_AA: A point rotated arount point param[0],param[1],param[2] Where the
|
POINT_N_ROT_AA: A point rotated around point param[0],param[1],param[2] Where the
|
||||||
angle is given by param[3]*timesApplied (times 2?) and the axis
|
angle is given by param[3]*timesApplied (times 2?) and the axis
|
||||||
of rotation defined by param[4],param[5],param[6]
|
of rotation defined by param[4],param[5],param[6]
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ the entity itself.
|
||||||
The ForceTo() functions are shortcuts for using the solver. They are passed the
|
The ForceTo() functions are shortcuts for using the solver. They are passed the
|
||||||
desired location of a point (or orientation of a normal...) and have the opportunity
|
desired location of a point (or orientation of a normal...) and have the opportunity
|
||||||
to back-calculate what the group parameters should be to place it there. This is
|
to back-calculate what the group parameters should be to place it there. This is
|
||||||
used for mouse dragging of copied entites. It is notable that the constraints will
|
used for mouse dragging of copied entities. It is notable that the constraints will
|
||||||
still be applied afterward, but this is a good shortcut.
|
still be applied afterward, but this is a good shortcut.
|
||||||
|
|
||||||
When creating a new entity transformation, the first thing to do is define the
|
When creating a new entity transformation, the first thing to do is define the
|
||||||
|
|
|
@ -6,3 +6,8 @@ add_executable(CDemo
|
||||||
|
|
||||||
target_link_libraries(CDemo
|
target_link_libraries(CDemo
|
||||||
slvs)
|
slvs)
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set_target_properties(CDemo PROPERTIES
|
||||||
|
LINK_FLAGS "-s TOTAL_MEMORY=134217728")
|
||||||
|
endif()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3147391d946bb4b6c68edd901f2add6ac1f31f8c
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4e643b6d3178e0ea2a093b7e14fe621631a91e4b
|
Subproject commit f819dbb4e4813fab464aee16770f39f11476bfea
|
|
@ -113,6 +113,10 @@ typedef struct {
|
||||||
#define SLVS_C_WHERE_DRAGGED 100031
|
#define SLVS_C_WHERE_DRAGGED 100031
|
||||||
#define SLVS_C_CURVE_CURVE_TANGENT 100032
|
#define SLVS_C_CURVE_CURVE_TANGENT 100032
|
||||||
#define SLVS_C_LENGTH_DIFFERENCE 100033
|
#define SLVS_C_LENGTH_DIFFERENCE 100033
|
||||||
|
#define SLVS_C_ARC_ARC_LEN_RATIO 100034
|
||||||
|
#define SLVS_C_ARC_LINE_LEN_RATIO 100035
|
||||||
|
#define SLVS_C_ARC_ARC_DIFFERENCE 100036
|
||||||
|
#define SLVS_C_ARC_LINE_DIFFERENCE 100037
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Slvs_hConstraint h;
|
Slvs_hConstraint h;
|
||||||
|
|
|
@ -1,69 +1,97 @@
|
||||||
{
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/TingPing/flatpak-manifest-schema/master/flatpak-manifest.schema",
|
||||||
"app-id": "com.solvespace.SolveSpace",
|
"app-id": "com.solvespace.SolveSpace",
|
||||||
"runtime": "org.freedesktop.Platform",
|
"runtime": "org.freedesktop.Platform",
|
||||||
"runtime-version": "20.08",
|
"runtime-version": "21.08",
|
||||||
"sdk": "org.freedesktop.Sdk",
|
"sdk": "org.freedesktop.Sdk",
|
||||||
"finish-args": [
|
"finish-args": [
|
||||||
/* Access to display server and OpenGL */
|
"--device=dri",
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland"
|
||||||
"--device=dri",
|
|
||||||
/* Access to save files */
|
|
||||||
"--filesystem=home"
|
|
||||||
],
|
],
|
||||||
"cleanup": [
|
"cleanup": [
|
||||||
"/include",
|
"/include",
|
||||||
"/lib/*/include",
|
"/lib/cmake",
|
||||||
"*.a",
|
"/lib/pkgconfig",
|
||||||
"*.la",
|
|
||||||
"*.m4",
|
|
||||||
"/lib/libslvs*.so*",
|
|
||||||
"/lib/libglibmm_generate_extra_defs*.so*",
|
|
||||||
"/share/pkgconfig",
|
|
||||||
"*.pc",
|
|
||||||
"/share/man",
|
|
||||||
"/share/doc",
|
|
||||||
"/share/aclocal",
|
"/share/aclocal",
|
||||||
/* mm-common junk */
|
"/share/pkgconfig",
|
||||||
"/bin/mm-common-prepare",
|
"*.la"
|
||||||
"/share/mm-common"
|
|
||||||
],
|
],
|
||||||
"command": "solvespace",
|
"command": "solvespace",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "mm-common",
|
"name": "mm-common",
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.2.tar.xz",
|
|
||||||
"sha256": "a2a99f3fa943cf662f189163ed39a2cfc19a428d906dd4f92b387d3659d1641d"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sigc++",
|
|
||||||
"config-opts": [
|
|
||||||
"--disable-documentation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://download.gnome.org/sources/libsigc++/2.10/libsigc%2B%2B-2.10.6.tar.xz",
|
|
||||||
"sha256": "dda176dc4681bda9d5a2ac1bc55273bdd381662b7a6d49e918267d13e8774e1b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "glibmm",
|
|
||||||
"config-opts": [],
|
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.gnome.org/sources/glibmm/2.64/glibmm-2.64.5.tar.xz",
|
"url": "https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.4.tar.xz",
|
||||||
"sha256": "508fc86e2c9141198aa16c225b16fd6b911917c0d3817602652844d0973ea386"
|
"sha256": "e954c09b4309a7ef93e13b69260acdc5738c907477eb381b78bb1e414ee6dbd8",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "mm-common",
|
||||||
|
"stable-only": true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/bin",
|
||||||
|
"/share/doc",
|
||||||
|
"/share/man",
|
||||||
|
"/share/mm-common"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sigc++",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dbuild-examples=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/libsigc++/2.10/libsigc++-2.10.8.tar.xz",
|
||||||
|
"sha256": "235a40bec7346c7b82b6a8caae0456353dc06e71f14bc414bcc858af1838719a",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "libsigc++",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/sigc++-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "glibmm",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dbuild-examples=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/glibmm/2.66/glibmm-2.66.4.tar.xz",
|
||||||
|
"sha256": "199ace5682d81b15a1d565480b4a950682f2db6402c8aa5dd7217d71edff81d5",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "glibmm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "2.68.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/giomm-*",
|
||||||
|
"/lib/glibmm-*",
|
||||||
|
"/lib/libglibmm_generate_extra_defs-*.so*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -74,76 +102,152 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/cairomm/1.12/cairomm-1.12.0.tar.xz",
|
"url": "https://download.gnome.org/sources/cairomm/1.12/cairomm-1.12.0.tar.xz",
|
||||||
"sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6"
|
"sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "cairomm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "1.16.0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/cairomm-*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pangomm",
|
"name": "pangomm",
|
||||||
"config-opts": [
|
|
||||||
"--disable-documentation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/pangomm/2.40/pangomm-2.40.2.tar.xz",
|
|
||||||
"sha256": "0a97aa72513db9088ca3034af923484108746dba146e98ed76842cf858322d05"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "atkmm",
|
|
||||||
"config-opts": [
|
|
||||||
"--disable-documentation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/atkmm/2.28/atkmm-2.28.0.tar.xz",
|
|
||||||
"sha256": "4c4cfc917fd42d3879ce997b463428d6982affa0fb660cafcc0bc2d9afcedd3a"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gtkmm",
|
|
||||||
"config-opts": [],
|
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.4.tar.xz",
|
"url": "https://download.gnome.org/sources/pangomm/2.46/pangomm-2.46.2.tar.xz",
|
||||||
"sha256": "9beb71c3e90cfcfb790396b51e3f5e7169966751efd4f3ef9697114be3be6743"
|
"sha256": "57442ab4dc043877bfe3839915731ab2d693fc6634a71614422fb530c9eaa6f4",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "pangomm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "2.48.0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/pangomm-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atkmm",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/atkmm/2.28/atkmm-2.28.2.tar.xz",
|
||||||
|
"sha256": "a0bb49765ceccc293ab2c6735ba100431807d384ffa14c2ebd30e07993fd2fa4",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "atkmm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "2.30.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/atkmm-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gtkmm",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dbuild-demos=false",
|
||||||
|
"-Dbuild-tests=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.6.tar.xz",
|
||||||
|
"sha256": "4b3e142e944e1633bba008900605c341a93cfd755a7fa2a00b05d041341f11d6",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "gtkmm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/gdkmm-*",
|
||||||
|
"/lib/gtkmm-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eigen",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz",
|
||||||
|
"sha256": "8586084f71f9bde545ee7fa6d00288b264a2b7ac3607b974e54d13e7162c1c72",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "anitya",
|
||||||
|
"project-id": 13751,
|
||||||
|
"stable-only": true,
|
||||||
|
"url-template": "https://gitlab.com/libeigen/eigen/-/archive/$version/eigen-$version.tar.gz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "libjson-c",
|
"name": "libjson-c",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"config-opts": [
|
||||||
|
"-DBUILD_STATIC_LIBS=OFF",
|
||||||
|
"-DENABLE_THREADING=ON"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
/* 0.15-nodoc doesn't build */
|
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.13.1-nodoc.tar.gz",
|
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.16.tar.gz",
|
||||||
"sha256": "94a26340c0785fcff4f46ff38609cf84ebcd670df0c8efd75d039cc951d80132"
|
"sha256": "8e45ac8f96ec7791eaf3bb7ee50e9c2100bbbc87b8d0f1d030c5ba8a0288d96b",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "anitya",
|
||||||
|
"project-id": 1477,
|
||||||
|
"stable-only": true,
|
||||||
|
"url-template": "https://s3.amazonaws.com/json-c_releases/releases/json-c-$version.tar.gz"
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
"buildsystem": "cmake",
|
]
|
||||||
"builddir": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "SolveSpace",
|
"name": "solvespace",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"config-opts": [
|
||||||
|
"-DFLATPAK=ON",
|
||||||
|
"-DENABLE_TESTS=OFF"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "dir",
|
"type": "dir",
|
||||||
"path": "../.."
|
"path": "../.."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buildsystem": "cmake",
|
"cleanup": [
|
||||||
"builddir": true,
|
"/lib/libslvs*.so*"
|
||||||
"config-opts": [
|
|
||||||
"-DFLATPAK=ON",
|
|
||||||
"-DENABLE_CLI=OFF",
|
|
||||||
"-DENABLE_TESTS=OFF"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: solvespace
|
name: solvespace
|
||||||
base: core18
|
base: core22
|
||||||
summary: Parametric 2d/3d CAD
|
summary: Parametric 2d/3d CAD
|
||||||
adopt-info: solvespace
|
adopt-info: solvespace
|
||||||
description: |
|
description: |
|
||||||
|
@ -14,6 +14,8 @@ description: |
|
||||||
|
|
||||||
confinement: strict
|
confinement: strict
|
||||||
license: GPL-3.0
|
license: GPL-3.0
|
||||||
|
compression: lzo
|
||||||
|
grade: stable
|
||||||
|
|
||||||
layout:
|
layout:
|
||||||
/usr/share/solvespace:
|
/usr/share/solvespace:
|
||||||
|
@ -23,13 +25,13 @@ apps:
|
||||||
solvespace:
|
solvespace:
|
||||||
command: usr/bin/solvespace
|
command: usr/bin/solvespace
|
||||||
desktop: solvespace.desktop
|
desktop: solvespace.desktop
|
||||||
extensions: [gnome-3-34]
|
extensions: [gnome]
|
||||||
plugs: [opengl, unity7, home, removable-media, gsettings, network]
|
plugs: [opengl, unity7, home, removable-media, gsettings, network]
|
||||||
environment:
|
environment:
|
||||||
__EGL_VENDOR_LIBRARY_DIRS: $SNAP/gnome-platform/usr/share/glvnd/egl_vendor.d:$SNAP/usr/share/glvnd/egl_vendor.d
|
GTK_USE_PORTAL: "0"
|
||||||
cli:
|
cli:
|
||||||
command: usr/bin/solvespace-cli
|
command: usr/bin/solvespace-cli
|
||||||
extensions: [gnome-3-34]
|
extensions: [gnome]
|
||||||
plugs: [home, removable-media, network]
|
plugs: [home, removable-media, network]
|
||||||
|
|
||||||
parts:
|
parts:
|
||||||
|
@ -38,15 +40,15 @@ parts:
|
||||||
source: ./solvespace-snap-src
|
source: ./solvespace-snap-src
|
||||||
source-type: local
|
source-type: local
|
||||||
override-pull: |
|
override-pull: |
|
||||||
snapcraftctl pull
|
craftctl default
|
||||||
version_major=$(grep "solvespace_VERSION_MAJOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2)
|
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
||||||
version_minor=$(grep "solvespace_VERSION_MINOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2)
|
override-build: |
|
||||||
version="$version_major.$version_minor~$(git rev-parse --short=8 HEAD)"
|
craftctl default
|
||||||
snapcraftctl set-version "$version"
|
project_version=$(grep CMAKE_PROJECT_VERSION:STATIC CMakeCache.txt | cut -d "=" -f2)
|
||||||
git describe --exact-match HEAD && grade="stable" || grade="devel"
|
cd $CRAFT_PART_SRC
|
||||||
snapcraftctl set-grade "$grade"
|
version="$project_version~$(git rev-parse --short=8 HEAD)"
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
craftctl set version="$version"
|
||||||
configflags:
|
cmake-parameters:
|
||||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
- -DCMAKE_BUILD_TYPE=Release
|
- -DCMAKE_BUILD_TYPE=Release
|
||||||
- -DENABLE_TESTS=OFF
|
- -DENABLE_TESTS=OFF
|
||||||
|
@ -56,6 +58,7 @@ parts:
|
||||||
build-packages:
|
build-packages:
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
- libpng-dev
|
- libpng-dev
|
||||||
|
- libcairo2-dev
|
||||||
- libfreetype6-dev
|
- libfreetype6-dev
|
||||||
- libjson-c-dev
|
- libjson-c-dev
|
||||||
- libgl-dev
|
- libgl-dev
|
||||||
|
@ -63,6 +66,7 @@ parts:
|
||||||
- libspnav-dev
|
- libspnav-dev
|
||||||
- git
|
- git
|
||||||
- g++
|
- g++
|
||||||
|
- libc6-dev
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libspnav0
|
- libspnav0
|
||||||
- libsigc++-2.0-0v5
|
- libsigc++-2.0-0v5
|
||||||
|
@ -70,11 +74,14 @@ parts:
|
||||||
cleanup:
|
cleanup:
|
||||||
after: [solvespace]
|
after: [solvespace]
|
||||||
plugin: nil
|
plugin: nil
|
||||||
build-snaps: [core18, gnome-3-34-1804]
|
build-snaps: [gnome-42-2204]
|
||||||
override-prime: |
|
override-prime: |
|
||||||
# Remove all files from snap that are already included in the base snap or in
|
|
||||||
# any connected content snaps
|
|
||||||
set -eux
|
set -eux
|
||||||
for snap in "core18" "gnome-3-34-1804"; do # List all content-snaps and base snaps you're using here
|
for snap in "gnome-42-2204"; do # List all content-snaps you're using here
|
||||||
cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" \;
|
cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$CRAFT_PRIME/{}" "$CRAFT_PRIME/usr/{}" \;
|
||||||
done
|
done
|
||||||
|
for cruft in bug lintian man; do
|
||||||
|
rm -rf $CRAFT_PRIME/usr/share/$cruft
|
||||||
|
done
|
||||||
|
find $CRAFT_PRIME/usr/share/doc/ -type f -not -name 'copyright' -delete
|
||||||
|
find $CRAFT_PRIME/usr/share -type d -empty -delete
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# First, set up registration functions for the kinds of resources we handle.
|
# First, set up registration functions for the kinds of resources we handle.
|
||||||
set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/)
|
set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/)
|
||||||
set(resource_list)
|
set(resource_list)
|
||||||
|
set(resource_names)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc)
|
${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc)
|
||||||
|
@ -83,6 +84,23 @@ elseif(APPLE)
|
||||||
DEPENDS ${source}
|
DEPENDS ${source}
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
set(resource_dir ${CMAKE_BINARY_DIR}/src/res)
|
||||||
|
|
||||||
|
function(add_resource name)
|
||||||
|
set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name})
|
||||||
|
set(target ${resource_dir}/${name})
|
||||||
|
set(resource_list "${resource_list};${target}" PARENT_SCOPE)
|
||||||
|
set(resource_names "${resource_names};res/${name}" PARENT_SCOPE)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${target}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${resource_dir}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target}
|
||||||
|
COMMENT "Copying resource ${name}"
|
||||||
|
DEPENDS ${source}
|
||||||
|
VERBATIM)
|
||||||
|
endfunction()
|
||||||
else() # Unix
|
else() # Unix
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
@ -112,6 +130,7 @@ function(add_resources)
|
||||||
foreach(name ${ARGN})
|
foreach(name ${ARGN})
|
||||||
add_resource(${name})
|
add_resource(${name})
|
||||||
set(resource_list "${resource_list}" PARENT_SCOPE)
|
set(resource_list "${resource_list}" PARENT_SCOPE)
|
||||||
|
set(resource_names "${resource_names}" PARENT_SCOPE)
|
||||||
endforeach()
|
endforeach()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
@ -249,6 +268,8 @@ add_resources(
|
||||||
icons/graphics-window/trim.png
|
icons/graphics-window/trim.png
|
||||||
icons/graphics-window/vert.png
|
icons/graphics-window/vert.png
|
||||||
icons/text-window/constraint.png
|
icons/text-window/constraint.png
|
||||||
|
icons/text-window/constraint-dimo.png
|
||||||
|
icons/text-window/constraint-wo.png
|
||||||
icons/text-window/construction.png
|
icons/text-window/construction.png
|
||||||
icons/text-window/edges.png
|
icons/text-window/edges.png
|
||||||
icons/text-window/faces.png
|
icons/text-window/faces.png
|
||||||
|
@ -262,13 +283,16 @@ add_resources(
|
||||||
icons/text-window/shaded.png
|
icons/text-window/shaded.png
|
||||||
icons/text-window/workplane.png
|
icons/text-window/workplane.png
|
||||||
locales.txt
|
locales.txt
|
||||||
|
locales/cs_CZ.po
|
||||||
locales/de_DE.po
|
locales/de_DE.po
|
||||||
locales/en_US.po
|
locales/en_US.po
|
||||||
locales/fr_FR.po
|
locales/fr_FR.po
|
||||||
locales/uk_UA.po
|
locales/uk_UA.po
|
||||||
|
locales/es_AR.po
|
||||||
locales/tr_TR.po
|
locales/tr_TR.po
|
||||||
locales/ru_RU.po
|
locales/ru_RU.po
|
||||||
locales/zh_CN.po
|
locales/zh_CN.po
|
||||||
|
locales/ja_JP.po
|
||||||
fonts/unifont.hex.gz
|
fonts/unifont.hex.gz
|
||||||
fonts/private/0-check-false.png
|
fonts/private/0-check-false.png
|
||||||
fonts/private/1-check-true.png
|
fonts/private/1-check-true.png
|
||||||
|
@ -303,4 +327,6 @@ add_custom_target(resources
|
||||||
DEPENDS ${resource_list})
|
DEPENDS ${resource_list})
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file})
|
set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file})
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
set_property(TARGET resources PROPERTY NAMES ${resource_names})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include:
|
SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Modeling 3d parts — draw with extrudes, revolves, and Boolean operations</li>
|
<li>Modeling 3d parts — draw with extrudes, revolves/helix, and Boolean operations</li>
|
||||||
<li>Modeling 2d parts — draw the part as a single section, and export; use 3d assembly to verify fit</li>
|
<li>Modeling 2d parts — draw the part as a single section, and export; use 3d assembly to verify fit</li>
|
||||||
<li>Modeling 3d-printed parts — export the STL or other triangle mesh expected by most slicers</li>
|
<li>Modeling 3d-printed parts — export the STL or other triangle mesh expected by most slicers</li>
|
||||||
<li>Preparing 2D CAM data — export 2d vector art for a waterjet machine or laser cutter</li>
|
<li>Preparing 2D CAM data — export 2d vector art for a waterjet machine or laser cutter</li>
|
||||||
|
@ -31,6 +31,34 @@
|
||||||
<url type="bugtracker">https://github.com/solvespace/solvespace/issues</url>
|
<url type="bugtracker">https://github.com/solvespace/solvespace/issues</url>
|
||||||
|
|
||||||
<launchable type="desktop-id">@DESKTOP_FILE_NAME@</launchable>
|
<launchable type="desktop-id">@DESKTOP_FILE_NAME@</launchable>
|
||||||
|
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Main window with an empty document</caption>
|
||||||
|
<image>https://solvespace.com/pics/window-linux-main.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Property Browser with an empty document</caption>
|
||||||
|
<image>https://solvespace.com/pics/window-linux-property-browser.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Viewing and editing constraints on a model</caption>
|
||||||
|
<image>https://solvespace.com/pics/front-page-pic.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>3D view of a stand made from notched angle iron, from the "ex-stand" project</caption>
|
||||||
|
<image>https://solvespace.com/pics/ex-stand-detail.jpg</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Dimensioning a 2D sketch for a case for a printed circuit board, from the "ex-case" project</caption>
|
||||||
|
<image>https://solvespace.com/pics/ex-case-outline.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Showing tracing of Chebyshev's linkage, from the "ex-chebyshev" project</caption>
|
||||||
|
<image>https://solvespace.com/pics/ex-chebyshev.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
|
||||||
<provides>
|
<provides>
|
||||||
<mediatype>application/x-solvespace</mediatype>
|
<mediatype>application/x-solvespace</mediatype>
|
||||||
</provides>
|
</provides>
|
||||||
|
@ -38,6 +66,19 @@
|
||||||
<content_rating type="oars-1.0" />
|
<content_rating type="oars-1.0" />
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="3.1" date="2022-06-01" type="stable">
|
||||||
|
<description>
|
||||||
|
<p>Major new stable release. Includes new arc and line length ratio and difference
|
||||||
|
constraints, comments associated with point entities. Adds "exploded view" to sketches,
|
||||||
|
and support for displaying measurements in "feet-inches". Adds a pitch parameter to
|
||||||
|
helix extrusions. Allows use of Point and Normal to define a new workplane to sketch in.
|
||||||
|
Adds live updating of Property Browser while dragging the sketch, and active links for
|
||||||
|
all points, normals, and vectors in the Property Browser. Adds the ability to link STL
|
||||||
|
files into a model. Includes a variety of UI improvements. Speeds up complex sketches
|
||||||
|
by up to 8x and doubles the maximum unknowns.</p>
|
||||||
|
</description>
|
||||||
|
<url>https://github.com/solvespace/solvespace/releases/tag/v3.0</url>
|
||||||
|
</release>
|
||||||
<release version="3.0" date="2021-04-18" type="stable">
|
<release version="3.0" date="2021-04-18" type="stable">
|
||||||
<description>
|
<description>
|
||||||
<p>Major new stable release. Includes new intersection boolean operation,
|
<p>Major new stable release. Includes new intersection boolean operation,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=SolveSpace
|
Name=SolveSpace
|
||||||
Comment=A parametric 2d/3d CAD
|
Comment=A parametric 2d/3d CAD
|
||||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
|
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f
|
||||||
MimeType=application/x-solvespace
|
MimeType=application/x-solvespace
|
||||||
Icon=com.solvespace.SolveSpace
|
Icon=com.solvespace.SolveSpace
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=SolveSpace
|
Name=SolveSpace
|
||||||
Comment=A parametric 2d/3d CAD
|
Comment=A parametric 2d/3d CAD
|
||||||
Exec=solvespace
|
Exec=solvespace %f
|
||||||
MimeType=application/x-solvespace
|
MimeType=application/x-solvespace
|
||||||
Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg
|
Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=SolveSpace
|
Name=SolveSpace
|
||||||
Comment=A parametric 2d/3d CAD
|
Comment=A parametric 2d/3d CAD
|
||||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
|
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f
|
||||||
MimeType=application/x-solvespace
|
MimeType=application/x-solvespace
|
||||||
Icon=solvespace
|
Icon=solvespace
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 194 B |
Binary file not shown.
After Width: | Height: | Size: 284 B |
|
@ -1,9 +1,12 @@
|
||||||
# This file lists the ISO locale codes (ISO 639-1/ISO 3166-1), Windows LCIDs,
|
# This file lists the ISO locale codes (ISO 639-1/ISO 3166-1), Windows LCIDs,
|
||||||
# and human-readable names for every culture supported by SolveSpace.
|
# and human-readable names for every culture supported by SolveSpace.
|
||||||
|
cs-CZ,1029,Česky
|
||||||
de-DE,0407,Deutsch
|
de-DE,0407,Deutsch
|
||||||
en-US,0409,English (US)
|
en-US,0409,English (US)
|
||||||
fr-FR,040C,Français
|
fr-FR,040C,Français
|
||||||
|
es-AR,2C0A,español (AR)
|
||||||
ru-RU,0419,Русский
|
ru-RU,0419,Русский
|
||||||
tr-TR,041F,Türkçe
|
tr-TR,041F,Türkçe
|
||||||
uk-UA,0422,Українська
|
uk-UA,0422,Українська
|
||||||
zh-CN,0804,简体中文
|
zh-CN,0804,简体中文
|
||||||
|
ja-JP,0411,日本語
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1431
res/locales/fr_FR.po
1431
res/locales/fr_FR.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1220
res/locales/ru_RU.po
1220
res/locales/ru_RU.po
File diff suppressed because it is too large
Load Diff
1262
res/locales/tr_TR.po
1262
res/locales/tr_TR.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1721
res/locales/zh_CN.po
1721
res/locales/zh_CN.po
File diff suppressed because it is too large
Load Diff
708
res/messages.pot
708
res/messages.pot
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
1 VERSIONINFO
|
1 VERSIONINFO
|
||||||
FILEVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0
|
FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},0,0
|
||||||
PRODUCTVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0
|
PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},0,0
|
||||||
FILEFLAGSMASK 0
|
FILEFLAGSMASK 0
|
||||||
FILEFLAGS 0
|
FILEFLAGS 0
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
|
@ -13,12 +13,12 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "The SolveSpace authors"
|
VALUE "CompanyName", "The SolveSpace authors"
|
||||||
VALUE "ProductName", "SolveSpace"
|
VALUE "ProductName", "SolveSpace"
|
||||||
VALUE "ProductVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}"
|
VALUE "ProductVersion", "${PROJECT_VERSION}~${solvespace_GIT_HASH}"
|
||||||
VALUE "FileDescription", "SolveSpace, a parametric 2d/3d CAD"
|
VALUE "FileDescription", "SolveSpace, a parametric 2d/3d CAD"
|
||||||
VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}"
|
VALUE "FileVersion", "${PROJECT_VERSION}~${solvespace_GIT_HASH}"
|
||||||
VALUE "OriginalFilename", "solvespace.exe"
|
VALUE "OriginalFilename", "solvespace.exe"
|
||||||
VALUE "InternalName", "solvespace"
|
VALUE "InternalName", "solvespace"
|
||||||
VALUE "LegalCopyright", "(c) 2008-2021 Jonathan Westhues and other authors"
|
VALUE "LegalCopyright", "(c) 2008-2024 Jonathan Westhues and other authors"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|
|
@ -19,50 +19,78 @@ endif()
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||||
|
|
||||||
|
# solvespace dependencies
|
||||||
|
add_library(slvs_deps INTERFACE)
|
||||||
|
target_include_directories(slvs_deps INTERFACE SYSTEM
|
||||||
|
${OPENGL_INCLUDE_DIR}
|
||||||
|
${ZLIB_INCLUDE_DIR}
|
||||||
|
${PNG_PNG_INCLUDE_DIR}
|
||||||
|
${FREETYPE_INCLUDE_DIRS}
|
||||||
|
${CAIRO_INCLUDE_DIRS}
|
||||||
|
${MIMALLOC_INCLUDE_DIR}
|
||||||
|
${EIGEN3_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(slvs_deps INTERFACE
|
||||||
|
dxfrw
|
||||||
|
${ZLIB_LIBRARY}
|
||||||
|
${PNG_LIBRARY}
|
||||||
|
${FREETYPE_LIBRARY}
|
||||||
|
${CAIRO_LIBRARIES}
|
||||||
|
mimalloc-static)
|
||||||
|
|
||||||
|
if(Backtrace_FOUND)
|
||||||
|
target_include_directories(slvs_deps INTERFACE SYSTEM
|
||||||
|
${Backtrace_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(slvs_deps INTERFACE
|
||||||
|
${Backtrace_LIBRARY})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(SPACEWARE_FOUND)
|
||||||
|
target_include_directories(slvs_deps INTERFACE SYSTEM
|
||||||
|
${SPACEWARE_INCLUDE_DIR})
|
||||||
|
target_link_libraries(slvs_deps INTERFACE
|
||||||
|
${SPACEWARE_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_OPENMP)
|
||||||
|
target_link_libraries(slvs_deps INTERFACE slvs_openmp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_options(slvs_deps
|
||||||
|
INTERFACE ${COVERAGE_FLAGS})
|
||||||
|
|
||||||
# platform utilities
|
# platform utilities
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(util_LIBRARIES
|
target_link_libraries(slvs_deps INTERFACE
|
||||||
${APPKIT_LIBRARY})
|
${APPKIT_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# libslvs
|
# libslvs
|
||||||
|
add_library(slvs SHARED
|
||||||
set(libslvs_SOURCES
|
solvespace.h
|
||||||
|
platform/platform.h
|
||||||
util.cpp
|
util.cpp
|
||||||
entity.cpp
|
entity.cpp
|
||||||
expr.cpp
|
expr.cpp
|
||||||
constraint.cpp
|
constraint.cpp
|
||||||
constrainteq.cpp
|
constrainteq.cpp
|
||||||
system.cpp
|
system.cpp
|
||||||
platform/platform.cpp)
|
platform/platform.cpp
|
||||||
|
|
||||||
set(libslvs_HEADERS
|
|
||||||
solvespace.h
|
|
||||||
platform/platform.h)
|
|
||||||
|
|
||||||
add_library(slvs SHARED
|
|
||||||
${libslvs_SOURCES}
|
|
||||||
${libslvs_HEADERS}
|
|
||||||
${util_SOURCES}
|
|
||||||
lib.cpp)
|
lib.cpp)
|
||||||
|
|
||||||
target_compile_definitions(slvs
|
target_compile_definitions(slvs
|
||||||
PRIVATE -DLIBRARY)
|
PRIVATE -DLIBRARY)
|
||||||
|
|
||||||
target_include_directories(slvs
|
target_include_directories(slvs
|
||||||
PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
PUBLIC
|
||||||
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
${EIGEN3_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_libraries(slvs
|
target_link_libraries(slvs PRIVATE slvs_deps)
|
||||||
${util_LIBRARIES}
|
|
||||||
mimalloc-static)
|
|
||||||
|
|
||||||
add_dependencies(slvs
|
|
||||||
mimalloc-static)
|
|
||||||
|
|
||||||
set_target_properties(slvs PROPERTIES
|
set_target_properties(slvs PROPERTIES
|
||||||
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h
|
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h
|
||||||
VERSION ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}
|
VERSION ${PROJECT_VERSION}
|
||||||
SOVERSION 1)
|
SOVERSION 1)
|
||||||
|
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
|
@ -72,77 +100,18 @@ if(NOT WIN32)
|
||||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# solvespace dependencies
|
|
||||||
|
|
||||||
include_directories(
|
|
||||||
${OPENGL_INCLUDE_DIR}
|
|
||||||
${ZLIB_INCLUDE_DIR}
|
|
||||||
${PNG_PNG_INCLUDE_DIR}
|
|
||||||
${FREETYPE_INCLUDE_DIRS}
|
|
||||||
${CAIRO_INCLUDE_DIRS}
|
|
||||||
${MIMALLOC_INCLUDE_DIR}
|
|
||||||
${OpenMP_CXX_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
if(Backtrace_FOUND)
|
|
||||||
include_directories(
|
|
||||||
${Backtrace_INCLUDE_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(SPACEWARE_FOUND)
|
|
||||||
include_directories(
|
|
||||||
${SPACEWARE_INCLUDE_DIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(OPENGL STREQUAL 3)
|
|
||||||
set(gl_SOURCES
|
|
||||||
render/gl3shader.cpp
|
|
||||||
render/rendergl3.cpp)
|
|
||||||
elseif(OPENGL STREQUAL 1)
|
|
||||||
set(gl_SOURCES
|
|
||||||
render/rendergl1.cpp)
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "Unsupported OpenGL version ${OPENGL}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(platform_SOURCES
|
|
||||||
${gl_SOURCES}
|
|
||||||
platform/entrygui.cpp)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
list(APPEND platform_SOURCES
|
|
||||||
platform/guiwin.cpp)
|
|
||||||
|
|
||||||
set(platform_LIBRARIES
|
|
||||||
comctl32
|
|
||||||
${SPACEWARE_LIBRARIES})
|
|
||||||
elseif(APPLE)
|
|
||||||
add_compile_options(
|
|
||||||
-fobjc-arc)
|
|
||||||
|
|
||||||
list(APPEND platform_SOURCES
|
|
||||||
platform/guimac.mm)
|
|
||||||
else()
|
|
||||||
list(APPEND platform_SOURCES
|
|
||||||
platform/guigtk.cpp)
|
|
||||||
|
|
||||||
set(platform_LIBRARIES
|
|
||||||
${SPACEWARE_LIBRARIES})
|
|
||||||
|
|
||||||
foreach(pkg_config_lib GTKMM JSONC FONTCONFIG)
|
|
||||||
include_directories(${${pkg_config_lib}_INCLUDE_DIRS})
|
|
||||||
link_directories(${${pkg_config_lib}_LIBRARY_DIRS})
|
|
||||||
list(APPEND platform_LIBRARIES ${${pkg_config_lib}_LIBRARIES})
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(every_platform_SOURCES
|
set(every_platform_SOURCES
|
||||||
platform/guiwin.cpp
|
platform/guiwin.cpp
|
||||||
platform/guigtk.cpp
|
platform/guigtk.cpp
|
||||||
platform/guimac.mm)
|
platform/guimac.mm
|
||||||
|
platform/guihtml.cpp)
|
||||||
|
|
||||||
# solvespace library
|
# solvespace library
|
||||||
|
|
||||||
set(solvespace_core_HEADERS
|
set(solvespace_core_gl_SOURCES
|
||||||
|
solvespace.cpp)
|
||||||
|
|
||||||
|
add_library(solvespace-core STATIC
|
||||||
dsc.h
|
dsc.h
|
||||||
expr.h
|
expr.h
|
||||||
polygon.h
|
polygon.h
|
||||||
|
@ -152,9 +121,7 @@ set(solvespace_core_HEADERS
|
||||||
platform/platform.h
|
platform/platform.h
|
||||||
render/render.h
|
render/render.h
|
||||||
render/gl3shader.h
|
render/gl3shader.h
|
||||||
srf/surface.h)
|
srf/surface.h
|
||||||
|
|
||||||
set(solvespace_core_SOURCES
|
|
||||||
bsp.cpp
|
bsp.cpp
|
||||||
clipboard.cpp
|
clipboard.cpp
|
||||||
confscreen.cpp
|
confscreen.cpp
|
||||||
|
@ -176,6 +143,7 @@ set(solvespace_core_SOURCES
|
||||||
groupmesh.cpp
|
groupmesh.cpp
|
||||||
importdxf.cpp
|
importdxf.cpp
|
||||||
importidf.cpp
|
importidf.cpp
|
||||||
|
importmesh.cpp
|
||||||
mesh.cpp
|
mesh.cpp
|
||||||
modify.cpp
|
modify.cpp
|
||||||
mouse.cpp
|
mouse.cpp
|
||||||
|
@ -201,44 +169,19 @@ set(solvespace_core_SOURCES
|
||||||
srf/merge.cpp
|
srf/merge.cpp
|
||||||
srf/ratpoly.cpp
|
srf/ratpoly.cpp
|
||||||
srf/raycast.cpp
|
srf/raycast.cpp
|
||||||
|
srf/shell.cpp
|
||||||
srf/surface.cpp
|
srf/surface.cpp
|
||||||
srf/surfinter.cpp
|
srf/surfinter.cpp
|
||||||
srf/triangulate.cpp)
|
srf/triangulate.cpp)
|
||||||
|
|
||||||
set(solvespace_core_gl_SOURCES
|
target_link_libraries(solvespace-core PUBLIC slvs_deps)
|
||||||
solvespace.cpp)
|
|
||||||
|
|
||||||
add_library(solvespace-core STATIC
|
|
||||||
${util_SOURCES}
|
|
||||||
${solvespace_core_HEADERS}
|
|
||||||
${solvespace_core_SOURCES})
|
|
||||||
|
|
||||||
add_dependencies(solvespace-core
|
|
||||||
mimalloc-static)
|
|
||||||
|
|
||||||
target_link_libraries(solvespace-core
|
|
||||||
${OpenMP_CXX_LIBRARIES}
|
|
||||||
dxfrw
|
|
||||||
${util_LIBRARIES}
|
|
||||||
${ZLIB_LIBRARY}
|
|
||||||
${PNG_LIBRARY}
|
|
||||||
${FREETYPE_LIBRARY}
|
|
||||||
mimalloc-static)
|
|
||||||
|
|
||||||
if(Backtrace_FOUND)
|
|
||||||
target_link_libraries(solvespace-core
|
|
||||||
${Backtrace_LIBRARY})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_compile_options(solvespace-core
|
|
||||||
PRIVATE ${COVERAGE_FLAGS})
|
|
||||||
|
|
||||||
# solvespace translations
|
# solvespace translations
|
||||||
|
|
||||||
if(HAVE_GETTEXT)
|
if(HAVE_GETTEXT)
|
||||||
|
get_target_property(solvespace_core_SOURCES solvespace-core SOURCES)
|
||||||
set(inputs
|
set(inputs
|
||||||
${solvespace_core_SOURCES}
|
${solvespace_core_SOURCES}
|
||||||
${solvespace_core_HEADERS}
|
|
||||||
${every_platform_SOURCES}
|
${every_platform_SOURCES}
|
||||||
${solvespace_core_gl_SOURCES})
|
${solvespace_core_gl_SOURCES})
|
||||||
|
|
||||||
|
@ -265,9 +208,9 @@ if(HAVE_GETTEXT)
|
||||||
--keyword --keyword=_ --keyword=N_ --keyword=C_:2,1c --keyword=CN_:2,1c
|
--keyword --keyword=_ --keyword=N_ --keyword=C_:2,1c --keyword=CN_:2,1c
|
||||||
--force-po --width=100 --sort-by-file
|
--force-po --width=100 --sort-by-file
|
||||||
--package-name=SolveSpace
|
--package-name=SolveSpace
|
||||||
--package-version=${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}
|
--package-version=${PROJECT_VERSION}
|
||||||
"--copyright-holder=the PACKAGE authors"
|
"--copyright-holder=the PACKAGE authors"
|
||||||
--msgid-bugs-address=whitequark@whitequark.org
|
--msgid-bugs-address=phkahler@gmail.com
|
||||||
--from-code=utf-8 --output=${gen_output_pot} ${inputs}
|
--from-code=utf-8 --output=${gen_output_pot} ${inputs}
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_pot} ${output_pot}
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_pot} ${output_pot}
|
||||||
DEPENDS ${inputs}
|
DEPENDS ${inputs}
|
||||||
|
@ -321,52 +264,137 @@ endif()
|
||||||
if(ENABLE_GUI)
|
if(ENABLE_GUI)
|
||||||
add_executable(solvespace WIN32 MACOSX_BUNDLE
|
add_executable(solvespace WIN32 MACOSX_BUNDLE
|
||||||
${solvespace_core_gl_SOURCES}
|
${solvespace_core_gl_SOURCES}
|
||||||
${platform_SOURCES}
|
platform/entrygui.cpp
|
||||||
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)
|
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)
|
||||||
|
|
||||||
add_dependencies(solvespace
|
add_dependencies(solvespace
|
||||||
resources)
|
resources)
|
||||||
|
|
||||||
target_link_libraries(solvespace
|
target_link_libraries(solvespace
|
||||||
|
PRIVATE
|
||||||
solvespace-core
|
solvespace-core
|
||||||
${OPENGL_LIBRARIES}
|
${OPENGL_LIBRARIES})
|
||||||
${platform_LIBRARIES}
|
|
||||||
${COVERAGE_LIBRARY})
|
|
||||||
|
|
||||||
if(MSVC)
|
# OpenGL version
|
||||||
set_target_properties(solvespace PROPERTIES
|
if(OPENGL STREQUAL 3)
|
||||||
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
|
target_sources(solvespace PRIVATE
|
||||||
|
render/gl3shader.cpp
|
||||||
|
render/rendergl3.cpp)
|
||||||
|
elseif(OPENGL STREQUAL 1)
|
||||||
|
target_sources(solvespace PRIVATE
|
||||||
|
render/rendergl1.cpp)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Unsupported OpenGL version ${OPENGL}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Platform-specific
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(solvespace PRIVATE
|
||||||
|
platform/guiwin.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(solvespace PRIVATE comctl32)
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
|
target_compile_options(solvespace PRIVATE -fobjc-arc)
|
||||||
|
target_compile_definitions(solvespace PRIVATE GL_SILENCE_DEPRECATION)
|
||||||
|
|
||||||
|
target_sources(solvespace PRIVATE
|
||||||
|
platform/guimac.mm)
|
||||||
set_target_properties(solvespace PROPERTIES
|
set_target_properties(solvespace PROPERTIES
|
||||||
OUTPUT_NAME SolveSpace
|
OUTPUT_NAME SolveSpace
|
||||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
|
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
|
||||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
|
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
set(SHELL ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/emshell.html)
|
||||||
|
set(LINK_FLAGS
|
||||||
|
--bind --shell-file ${SHELL}
|
||||||
|
--no-heap-copy -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s ASYNCIFY=1
|
||||||
|
-s DYNCALLS=1 -s ASSERTIONS=1
|
||||||
|
-s TOTAL_STACK=33554432 -s TOTAL_MEMORY=134217728)
|
||||||
|
|
||||||
|
get_target_property(resource_names resources NAMES)
|
||||||
|
foreach(resource ${resource_names})
|
||||||
|
list(APPEND LINK_FLAGS --preload-file ${resource})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
|
list(APPEND LINK_FLAGS
|
||||||
|
--emrun --emit-symbol-map
|
||||||
|
-s DEMANGLE_SUPPORT=1
|
||||||
|
-s SAFE_HEAP=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources(solvespace PRIVATE
|
||||||
|
platform/guihtml.cpp)
|
||||||
|
|
||||||
|
string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}")
|
||||||
|
set_target_properties(solvespace PROPERTIES
|
||||||
|
LINK_FLAGS "${LINK_FLAGS}")
|
||||||
|
set_source_files_properties(platform/guihtml.cpp PROPERTIES
|
||||||
|
OBJECT_DEPENDS ${SHELL})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET solvespace POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.css
|
||||||
|
${EXECUTABLE_OUTPUT_PATH}/solvespaceui.css
|
||||||
|
COMMENT "Copying UI stylesheet"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET solvespace POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.js
|
||||||
|
${EXECUTABLE_OUTPUT_PATH}/solvespaceui.js
|
||||||
|
COMMENT "Copying UI script solvespaceui.js"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET solvespace POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/filemanagerui.js
|
||||||
|
${EXECUTABLE_OUTPUT_PATH}/filemanagerui.js
|
||||||
|
COMMENT "Copying UI script filemanagerui.sj"
|
||||||
|
VERBATIM)
|
||||||
|
else()
|
||||||
|
target_sources(solvespace PRIVATE
|
||||||
|
platform/guigtk.cpp)
|
||||||
|
|
||||||
|
target_include_directories(solvespace PRIVATE SYSTEM
|
||||||
|
${GTKMM_INCLUDE_DIRS}
|
||||||
|
${JSONC_INCLUDE_DIRS}
|
||||||
|
${FONTCONFIG_INCLUDE_DIRS})
|
||||||
|
target_link_directories(solvespace PRIVATE
|
||||||
|
${GTKMM_LIBRARY_DIRS}
|
||||||
|
${JSONC_LIBRARY_DIRS}
|
||||||
|
${FONTCONFIG_LIBRARY_DIRS})
|
||||||
|
target_link_libraries(solvespace PRIVATE
|
||||||
|
${GTKMM_LIBRARIES}
|
||||||
|
${JSONC_LIBRARIES}
|
||||||
|
${FONTCONFIG_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
set_target_properties(solvespace PROPERTIES
|
||||||
|
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# solvespace headless library
|
# solvespace headless library
|
||||||
|
|
||||||
set(headless_SOURCES
|
add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL
|
||||||
|
${solvespace_core_gl_SOURCES}
|
||||||
platform/guinone.cpp
|
platform/guinone.cpp
|
||||||
render/rendercairo.cpp)
|
render/rendercairo.cpp)
|
||||||
|
|
||||||
add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL
|
|
||||||
${solvespace_core_gl_SOURCES}
|
|
||||||
${headless_SOURCES})
|
|
||||||
|
|
||||||
target_compile_definitions(solvespace-headless
|
target_compile_definitions(solvespace-headless
|
||||||
PRIVATE -DHEADLESS)
|
PRIVATE HEADLESS)
|
||||||
|
|
||||||
target_include_directories(solvespace-headless
|
target_include_directories(solvespace-headless
|
||||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PUBLIC ${EIGEN3_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_libraries(solvespace-headless
|
target_link_libraries(solvespace-headless
|
||||||
solvespace-core
|
PRIVATE
|
||||||
${CAIRO_LIBRARIES})
|
solvespace-core)
|
||||||
|
|
||||||
target_compile_options(solvespace-headless
|
|
||||||
PRIVATE ${COVERAGE_FLAGS})
|
|
||||||
|
|
||||||
# solvespace command-line executable
|
# solvespace command-line executable
|
||||||
|
|
||||||
|
@ -390,7 +418,7 @@ endif()
|
||||||
|
|
||||||
# solvespace unix package
|
# solvespace unix package
|
||||||
|
|
||||||
if(NOT (WIN32 OR APPLE))
|
if(NOT (WIN32 OR APPLE OR EMSCRIPTEN))
|
||||||
if(ENABLE_GUI)
|
if(ENABLE_GUI)
|
||||||
install(TARGETS solvespace
|
install(TARGETS solvespace
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
|
@ -249,8 +249,10 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
||||||
case Constraint::Type::COMMENT:
|
case Constraint::Type::COMMENT:
|
||||||
c.disp.offset = c.disp.offset.Plus(trans);
|
c.disp.offset = c.disp.offset.Plus(trans);
|
||||||
break;
|
break;
|
||||||
case Constraint::Type::PT_PT_DISTANCE:
|
|
||||||
case Constraint::Type::PT_LINE_DISTANCE:
|
case Constraint::Type::PT_LINE_DISTANCE:
|
||||||
|
c.valA *= scale;
|
||||||
|
break;
|
||||||
|
case Constraint::Type::PT_PT_DISTANCE:
|
||||||
case Constraint::Type::PROJ_PT_DISTANCE:
|
case Constraint::Type::PROJ_PT_DISTANCE:
|
||||||
case Constraint::Type::DIAMETER:
|
case Constraint::Type::DIAMETER:
|
||||||
c.valA *= fabs(scale);
|
c.valA *= fabs(scale);
|
||||||
|
@ -281,7 +283,7 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
||||||
}
|
}
|
||||||
case Constraint::Type::HORIZONTAL:
|
case Constraint::Type::HORIZONTAL:
|
||||||
case Constraint::Type::VERTICAL:
|
case Constraint::Type::VERTICAL:
|
||||||
// When rotating 90 or 270 degrees, swap the vertical / horizontal constaints
|
// When rotating 90 or 270 degrees, swap the vertical / horizontal constraints
|
||||||
if (EXACT(fmod(theta + (PI/2), PI) == 0)) {
|
if (EXACT(fmod(theta + (PI/2), PI) == 0)) {
|
||||||
if(c.type == Constraint::Type::HORIZONTAL) {
|
if(c.type == Constraint::Type::HORIZONTAL) {
|
||||||
c.type = Constraint::Type::VERTICAL;
|
c.type = Constraint::Type::VERTICAL;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#ifndef SOLVESPACE_CONFIG_H
|
#ifndef SOLVESPACE_CONFIG_H
|
||||||
#define SOLVESPACE_CONFIG_H
|
#define SOLVESPACE_CONFIG_H
|
||||||
|
|
||||||
#define PACKAGE_VERSION "@solvespace_VERSION_MAJOR@.@solvespace_VERSION_MINOR@~@solvespace_GIT_HASH@"
|
#define PACKAGE_VERSION "@PROJECT_VERSION@~@solvespace_GIT_HASH@"
|
||||||
|
#define GIT_HASH_URL "https://github.com/solvespace/solvespace/commit/@solvespace_GIT_HASH@"
|
||||||
|
|
||||||
/* Non-OS X *nix only */
|
/* Non-OS X *nix only */
|
||||||
#define UNIX_DATADIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATAROOTDIR@/solvespace"
|
#define UNIX_DATADIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATAROOTDIR@/solvespace"
|
||||||
|
|
|
@ -9,24 +9,6 @@
|
||||||
#include <omp.h>
|
#include <omp.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) {
|
|
||||||
SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v])));
|
|
||||||
SS.TW.edit.meaning = Edit::LIGHT_DIRECTION;
|
|
||||||
SS.TW.edit.i = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextWindow::ScreenChangeLightIntensity(int link, uint32_t v) {
|
|
||||||
SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.lightIntensity[v]));
|
|
||||||
SS.TW.edit.meaning = Edit::LIGHT_INTENSITY;
|
|
||||||
SS.TW.edit.i = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextWindow::ScreenChangeLightAmbient(int link, uint32_t v) {
|
|
||||||
SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.ambientIntensity));
|
|
||||||
SS.TW.edit.meaning = Edit::LIGHT_AMBIENT;
|
|
||||||
SS.TW.edit.i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextWindow::ScreenChangeColor(int link, uint32_t v) {
|
void TextWindow::ScreenChangeColor(int link, uint32_t v) {
|
||||||
SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]);
|
SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]);
|
||||||
|
|
||||||
|
@ -58,13 +40,8 @@ void TextWindow::ScreenChangeExportMaxSegments(int link, uint32_t v) {
|
||||||
SS.TW.edit.i = 1;
|
SS.TW.edit.i = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextWindow::ScreenChangeCameraTangent(int link, uint32_t v) {
|
|
||||||
SS.TW.ShowEditControl(3, ssprintf("%.3f", 1000*SS.cameraTangent));
|
|
||||||
SS.TW.edit.meaning = Edit::CAMERA_TANGENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextWindow::ScreenChangeGridSpacing(int link, uint32_t v) {
|
void TextWindow::ScreenChangeGridSpacing(int link, uint32_t v) {
|
||||||
SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing));
|
SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing, true));
|
||||||
SS.TW.edit.meaning = Edit::GRID_SPACING;
|
SS.TW.edit.meaning = Edit::GRID_SPACING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +66,19 @@ void TextWindow::ScreenChangeExportScale(int link, uint32_t v) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextWindow::ScreenChangeExportOffset(int link, uint32_t v) {
|
void TextWindow::ScreenChangeExportOffset(int link, uint32_t v) {
|
||||||
SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset));
|
SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset, true));
|
||||||
SS.TW.edit.meaning = Edit::EXPORT_OFFSET;
|
SS.TW.edit.meaning = Edit::EXPORT_OFFSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextWindow::ScreenChangeArcDimDefault(int link, uint32_t v) {
|
||||||
|
SS.arcDimDefaultDiameter = !SS.arcDimDefaultDiameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::ScreenChangeShowFullFilePath(int link, uint32_t v) {
|
||||||
|
SS.showFullFilePath = !SS.showFullFilePath;
|
||||||
|
SS.UpdateWindowTitles();
|
||||||
|
}
|
||||||
|
|
||||||
void TextWindow::ScreenChangeFixExportColors(int link, uint32_t v) {
|
void TextWindow::ScreenChangeFixExportColors(int link, uint32_t v) {
|
||||||
SS.fixExportColors = !SS.fixExportColors;
|
SS.fixExportColors = !SS.fixExportColors;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +101,10 @@ void TextWindow::ScreenChangeTurntableNav(int link, uint32_t v) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextWindow::ScreenChangeCameraNav(int link, uint32_t v) {
|
||||||
|
SS.cameraNav = !SS.cameraNav;
|
||||||
|
}
|
||||||
|
|
||||||
void TextWindow::ScreenChangeImmediatelyEditDimension(int link, uint32_t v) {
|
void TextWindow::ScreenChangeImmediatelyEditDimension(int link, uint32_t v) {
|
||||||
SS.immediatelyEditDimension = !SS.immediatelyEditDimension;
|
SS.immediatelyEditDimension = !SS.immediatelyEditDimension;
|
||||||
SS.GW.Invalidate(/*clearPersistent=*/true);
|
SS.GW.Invalidate(/*clearPersistent=*/true);
|
||||||
|
@ -171,7 +161,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, uint32_t v) {
|
||||||
}
|
}
|
||||||
int col = 13;
|
int col = 13;
|
||||||
if(v < 10) col = 11;
|
if(v < 10) col = 11;
|
||||||
SS.TW.ShowEditControl(col, SS.MmToString(d));
|
SS.TW.ShowEditControl(col, SS.MmToString(d, true));
|
||||||
SS.TW.edit.meaning = Edit::CANVAS_SIZE;
|
SS.TW.edit.meaning = Edit::CANVAS_SIZE;
|
||||||
SS.TW.edit.i = v;
|
SS.TW.edit.i = v;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +171,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) {
|
||||||
switch(link) {
|
switch(link) {
|
||||||
case 'd':
|
case 'd':
|
||||||
SS.TW.edit.meaning = Edit::G_CODE_DEPTH;
|
SS.TW.edit.meaning = Edit::G_CODE_DEPTH;
|
||||||
buf += SS.MmToString(SS.gCode.depth);
|
buf += SS.MmToString(SS.gCode.depth, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
SS.TW.edit.meaning = Edit::G_CODE_SAFE_HEIGHT;
|
||||||
|
buf += SS.MmToString(SS.gCode.safeHeight, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
|
@ -191,12 +186,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) {
|
||||||
|
|
||||||
case 'F':
|
case 'F':
|
||||||
SS.TW.edit.meaning = Edit::G_CODE_FEED;
|
SS.TW.edit.meaning = Edit::G_CODE_FEED;
|
||||||
buf += SS.MmToString(SS.gCode.feed);
|
buf += SS.MmToString(SS.gCode.feed, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
SS.TW.edit.meaning = Edit::G_CODE_PLUNGE_FEED;
|
SS.TW.edit.meaning = Edit::G_CODE_PLUNGE_FEED;
|
||||||
buf += SS.MmToString(SS.gCode.plungeFeed);
|
buf += SS.MmToString(SS.gCode.plungeFeed, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SS.TW.ShowEditControl(14, buf);
|
SS.TW.ShowEditControl(14, buf);
|
||||||
|
@ -227,18 +222,6 @@ void TextWindow::ShowConfiguration() {
|
||||||
&ScreenChangeColor, i);
|
&ScreenChangeColor, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Printf(false, "");
|
|
||||||
Printf(false, "%Ft light direction intensity");
|
|
||||||
for(i = 0; i < 2; i++) {
|
|
||||||
Printf(false, "%Bp #%d (%2,%2,%2)%Fl%D%f%Ll[c]%E "
|
|
||||||
"%2 %Fl%D%f%Ll[c]%E",
|
|
||||||
(i & 1) ? 'd' : 'a', i,
|
|
||||||
CO(SS.lightDir[i]), i, &ScreenChangeLightDirection,
|
|
||||||
SS.lightIntensity[i], i, &ScreenChangeLightIntensity);
|
|
||||||
}
|
|
||||||
Printf(false, "%Ba ambient lighting %2 %Fl%f%Ll[c]%E",
|
|
||||||
SS.ambientIntensity, &ScreenChangeLightAmbient);
|
|
||||||
|
|
||||||
Printf(false, "");
|
Printf(false, "");
|
||||||
Printf(false, "%Ft chord tolerance (in percents)%E");
|
Printf(false, "%Ft chord tolerance (in percents)%E");
|
||||||
Printf(false, "%Ba %@ %% %Fl%Ll%f%D[change]%E; %@ mm, %d triangles",
|
Printf(false, "%Ba %@ %% %Fl%Ll%f%D[change]%E; %@ mm, %d triangles",
|
||||||
|
@ -260,11 +243,6 @@ void TextWindow::ShowConfiguration() {
|
||||||
SS.exportMaxSegments,
|
SS.exportMaxSegments,
|
||||||
&ScreenChangeExportMaxSegments);
|
&ScreenChangeExportMaxSegments);
|
||||||
|
|
||||||
Printf(false, "");
|
|
||||||
Printf(false, "%Ft perspective factor (0 for parallel)%E");
|
|
||||||
Printf(false, "%Ba %# %Fl%Ll%f%D[change]%E",
|
|
||||||
SS.cameraTangent*1000,
|
|
||||||
&ScreenChangeCameraTangent, 0);
|
|
||||||
Printf(false, "%Ft snap grid spacing%E");
|
Printf(false, "%Ft snap grid spacing%E");
|
||||||
Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E",
|
Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E",
|
||||||
SS.MmToString(SS.gridSpacing).c_str(),
|
SS.MmToString(SS.gridSpacing).c_str(),
|
||||||
|
@ -368,11 +346,18 @@ void TextWindow::ShowConfiguration() {
|
||||||
Printf(false, " %Fd%f%Ll%s enable automatic line constraints%E",
|
Printf(false, " %Fd%f%Ll%s enable automatic line constraints%E",
|
||||||
&ScreenChangeAutomaticLineConstraints,
|
&ScreenChangeAutomaticLineConstraints,
|
||||||
SS.automaticLineConstraints ? CHECK_TRUE : CHECK_FALSE);
|
SS.automaticLineConstraints ? CHECK_TRUE : CHECK_FALSE);
|
||||||
|
Printf(false, " %Fd%f%Ll%s use camera mouse navigation%E", &ScreenChangeCameraNav,
|
||||||
|
SS.cameraNav ? CHECK_TRUE : CHECK_FALSE);
|
||||||
Printf(false, " %Fd%f%Ll%s use turntable mouse navigation%E", &ScreenChangeTurntableNav,
|
Printf(false, " %Fd%f%Ll%s use turntable mouse navigation%E", &ScreenChangeTurntableNav,
|
||||||
SS.turntableNav ? CHECK_TRUE : CHECK_FALSE);
|
SS.turntableNav ? CHECK_TRUE : CHECK_FALSE);
|
||||||
Printf(false, " %Fd%f%Ll%s edit newly added dimensions%E",
|
Printf(false, " %Fd%f%Ll%s edit newly added dimensions%E",
|
||||||
&ScreenChangeImmediatelyEditDimension,
|
&ScreenChangeImmediatelyEditDimension,
|
||||||
SS.immediatelyEditDimension ? CHECK_TRUE : CHECK_FALSE);
|
SS.immediatelyEditDimension ? CHECK_TRUE : CHECK_FALSE);
|
||||||
|
Printf(false, " %Fd%f%Ll%s arc default is diameter%E",
|
||||||
|
&ScreenChangeArcDimDefault,
|
||||||
|
SS.arcDimDefaultDiameter ? CHECK_TRUE : CHECK_FALSE);
|
||||||
|
Printf(false, " %Fd%f%Ll%s display the full path in the title bar%E",
|
||||||
|
&ScreenChangeShowFullFilePath, SS.showFullFilePath ? CHECK_TRUE : CHECK_FALSE);
|
||||||
Printf(false, "");
|
Printf(false, "");
|
||||||
Printf(false, "%Ft autosave interval (in minutes)%E");
|
Printf(false, "%Ft autosave interval (in minutes)%E");
|
||||||
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
|
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
|
||||||
|
@ -459,6 +444,11 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {
|
||||||
SS.GW.Invalidate();
|
SS.GW.Invalidate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Edit::EXPLODE_DISTANCE: {
|
||||||
|
SS.explodeDistance = min(1e4, max(-1e4, SS.StringToMm(s)));
|
||||||
|
SS.MarkGroupDirty(SS.GW.activeGroup, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Edit::DIGITS_AFTER_DECIMAL: {
|
case Edit::DIGITS_AFTER_DECIMAL: {
|
||||||
int v = atoi(s.c_str());
|
int v = atoi(s.c_str());
|
||||||
if(v < 0 || v > 8) {
|
if(v < 0 || v > 8) {
|
||||||
|
@ -527,6 +517,11 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {
|
||||||
if(e) SS.gCode.depth = (float)SS.ExprToMm(e);
|
if(e) SS.gCode.depth = (float)SS.ExprToMm(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Edit::G_CODE_SAFE_HEIGHT: {
|
||||||
|
Expr *e = Expr::From(s, /*popUpError=*/true);
|
||||||
|
if(e) SS.gCode.safeHeight = (float)SS.ExprToMm(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Edit::G_CODE_PASSES: {
|
case Edit::G_CODE_PASSES: {
|
||||||
Expr *e = Expr::From(s, /*popUpError=*/true);
|
Expr *e = Expr::From(s, /*popUpError=*/true);
|
||||||
if(e) SS.gCode.passes = (int)(e->Eval());
|
if(e) SS.gCode.passes = (int)(e->Eval());
|
||||||
|
|
|
@ -22,7 +22,11 @@ std::string Constraint::DescriptionString() const {
|
||||||
case Type::EQ_LEN_PT_LINE_D: s = C_("constr-name", "eq-length-and-pt-ln-dist"); break;
|
case Type::EQ_LEN_PT_LINE_D: s = C_("constr-name", "eq-length-and-pt-ln-dist"); break;
|
||||||
case Type::EQ_PT_LN_DISTANCES: s = C_("constr-name", "eq-pt-line-distances"); break;
|
case Type::EQ_PT_LN_DISTANCES: s = C_("constr-name", "eq-pt-line-distances"); break;
|
||||||
case Type::LENGTH_RATIO: s = C_("constr-name", "length-ratio"); break;
|
case Type::LENGTH_RATIO: s = C_("constr-name", "length-ratio"); break;
|
||||||
|
case Type::ARC_ARC_LEN_RATIO: s = C_("constr-name", "arc-arc-length-ratio"); break;
|
||||||
|
case Type::ARC_LINE_LEN_RATIO: s = C_("constr-name", "arc-line-length-ratio"); break;
|
||||||
case Type::LENGTH_DIFFERENCE: s = C_("constr-name", "length-difference"); break;
|
case Type::LENGTH_DIFFERENCE: s = C_("constr-name", "length-difference"); break;
|
||||||
|
case Type::ARC_ARC_DIFFERENCE: s = C_("constr-name", "arc-arc-len-difference"); break;
|
||||||
|
case Type::ARC_LINE_DIFFERENCE: s = C_("constr-name", "arc-line-len-difference"); break;
|
||||||
case Type::SYMMETRIC: s = C_("constr-name", "symmetric"); break;
|
case Type::SYMMETRIC: s = C_("constr-name", "symmetric"); break;
|
||||||
case Type::SYMMETRIC_HORIZ: s = C_("constr-name", "symmetric-h"); break;
|
case Type::SYMMETRIC_HORIZ: s = C_("constr-name", "symmetric-h"); break;
|
||||||
case Type::SYMMETRIC_VERT: s = C_("constr-name", "symmetric-v"); break;
|
case Type::SYMMETRIC_VERT: s = C_("constr-name", "symmetric-v"); break;
|
||||||
|
@ -191,6 +195,8 @@ bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *e
|
||||||
}
|
}
|
||||||
|
|
||||||
void Constraint::MenuConstrain(Command id) {
|
void Constraint::MenuConstrain(Command id) {
|
||||||
|
std::vector<Constraint> newcons;
|
||||||
|
|
||||||
Constraint c = {};
|
Constraint c = {};
|
||||||
c.group = SS.GW.activeGroup;
|
c.group = SS.GW.activeGroup;
|
||||||
c.workplane = SS.GW.ActiveWorkplane();
|
c.workplane = SS.GW.ActiveWorkplane();
|
||||||
|
@ -230,6 +236,12 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
} else if(gs.circlesOrArcs == 1 && gs.n == 1) {
|
} else if(gs.circlesOrArcs == 1 && gs.n == 1) {
|
||||||
c.type = Type::DIAMETER;
|
c.type = Type::DIAMETER;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
|
Entity* arc = SK.GetEntity(gs.entity[0]);
|
||||||
|
if ((arc->type == EntityBase::Type::ARC_OF_CIRCLE)
|
||||||
|
&& (!SS.arcDimDefaultDiameter))
|
||||||
|
{
|
||||||
|
c.other = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for distance / diameter constraint. This "
|
Error(_("Bad selection for distance / diameter constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
|
@ -259,54 +271,69 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.valA = 0;
|
c.valA = 0;
|
||||||
c.ModifyToSatisfy();
|
c.ModifyToSatisfy();
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::ON_ENTITY:
|
case Command::ON_ENTITY:
|
||||||
if(gs.points == 2 && gs.n == 2) {
|
if(gs.points >= 2 && gs.points == gs.n) {
|
||||||
c.type = Type::POINTS_COINCIDENT;
|
c.type = Type::POINTS_COINCIDENT;
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.ptB = gs.point[1];
|
for(int k = 1; k < gs.points; k++) {
|
||||||
|
c.ptB = gs.point[k];
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
} else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) {
|
} else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) {
|
||||||
c.type = Type::PT_IN_PLANE;
|
c.type = Type::PT_IN_PLANE;
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
|
newcons.push_back(c);
|
||||||
} else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
} else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
||||||
c.type = Type::PT_ON_LINE;
|
c.type = Type::PT_ON_LINE;
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
|
newcons.push_back(c);
|
||||||
} else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
|
} else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
|
||||||
c.type = Type::PT_ON_CIRCLE;
|
c.type = Type::PT_ON_CIRCLE;
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
} else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) {
|
newcons.push_back(c);
|
||||||
|
} else if(gs.points == 1 && gs.faces >= 1 && gs.n == gs.points+gs.faces) {
|
||||||
c.type = Type::PT_ON_FACE;
|
c.type = Type::PT_ON_FACE;
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.entityA = gs.face[0];
|
for (int k=0; k<gs.faces; k++) {
|
||||||
|
c.entityA = gs.face[k];
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for on point / curve / plane constraint. "
|
Error(_("Bad selection for on point / curve / plane constraint. "
|
||||||
"This constraint can apply to:\n\n"
|
"This constraint can apply to:\n\n"
|
||||||
" * two points (points coincident)\n"
|
" * two or more points (points coincident)\n"
|
||||||
" * a point and a workplane (point in plane)\n"
|
" * a point and a workplane (point in plane)\n"
|
||||||
" * a point and a line segment (point on line)\n"
|
" * a point and a line segment (point on line)\n"
|
||||||
" * a point and a circle or arc (point on curve)\n"
|
" * a point and a circle or arc (point on curve)\n"
|
||||||
" * a point and a plane face (point on face)\n"));
|
" * a point and one to three plane faces (point on face(s))\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
for (auto&& nc : newcons)
|
||||||
|
AddConstraint(&nc);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::EQUAL:
|
case Command::EQUAL:
|
||||||
if(gs.lineSegments == 2 && gs.n == 2) {
|
if(gs.lineSegments >= 2 && gs.lineSegments == gs.n) {
|
||||||
c.type = Type::EQUAL_LENGTH_LINES;
|
c.type = Type::EQUAL_LENGTH_LINES;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.entityB = gs.entity[1];
|
for (std::vector<hEntity>::size_type k = 1;k < gs.entity.size(); ++k){
|
||||||
|
c.entityB = gs.entity[k];
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
} else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) {
|
} else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) {
|
||||||
c.type = Type::EQ_PT_LN_DISTANCES;
|
c.type = Type::EQ_PT_LN_DISTANCES;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.entityB = gs.entity[1];
|
c.entityB = gs.entity[1];
|
||||||
c.ptB = gs.point[1];
|
c.ptB = gs.point[1];
|
||||||
|
newcons.push_back(c);
|
||||||
} else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
|
} else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
|
||||||
// The same line segment for the distances, but different
|
// The same line segment for the distances, but different
|
||||||
// points.
|
// points.
|
||||||
|
@ -315,27 +342,20 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
c.entityB = gs.entity[0];
|
c.entityB = gs.entity[0];
|
||||||
c.ptB = gs.point[1];
|
c.ptB = gs.point[1];
|
||||||
|
newcons.push_back(c);
|
||||||
} else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
|
} else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
|
||||||
c.type = Type::EQ_LEN_PT_LINE_D;
|
c.type = Type::EQ_LEN_PT_LINE_D;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.entityB = gs.entity[1];
|
c.entityB = gs.entity[1];
|
||||||
c.ptA = gs.point[0];
|
c.ptA = gs.point[0];
|
||||||
} else if(gs.vectors == 4 && gs.n == 4) {
|
newcons.push_back(c);
|
||||||
c.type = Type::EQUAL_ANGLE;
|
} else if(gs.circlesOrArcs >= 2 && gs.circlesOrArcs == gs.n) {
|
||||||
c.entityA = gs.vector[0];
|
|
||||||
c.entityB = gs.vector[1];
|
|
||||||
c.entityC = gs.vector[2];
|
|
||||||
c.entityD = gs.vector[3];
|
|
||||||
} else if(gs.vectors == 3 && gs.n == 3) {
|
|
||||||
c.type = Type::EQUAL_ANGLE;
|
|
||||||
c.entityA = gs.vector[0];
|
|
||||||
c.entityB = gs.vector[1];
|
|
||||||
c.entityC = gs.vector[1];
|
|
||||||
c.entityD = gs.vector[2];
|
|
||||||
} else if(gs.circlesOrArcs == 2 && gs.n == 2) {
|
|
||||||
c.type = Type::EQUAL_RADIUS;
|
c.type = Type::EQUAL_RADIUS;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.entityB = gs.entity[1];
|
for (std::vector<hEntity>::size_type k = 1;k < gs.entity.size(); ++k){
|
||||||
|
c.entityB = gs.entity[k];
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
} else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
} else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
||||||
c.type = Type::EQUAL_LINE_ARC_LEN;
|
c.type = Type::EQUAL_LINE_ARC_LEN;
|
||||||
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||||
|
@ -345,26 +365,25 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.entityB = gs.entity[1];
|
c.entityB = gs.entity[1];
|
||||||
}
|
}
|
||||||
|
newcons.push_back(c);
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for equal length / radius constraint. "
|
Error(_("Bad selection for equal length / radius constraint. "
|
||||||
"This constraint can apply to:\n\n"
|
"This constraint can apply to:\n\n"
|
||||||
" * two line segments (equal length)\n"
|
" * two or more line segments (equal length)\n"
|
||||||
" * two line segments and two points "
|
" * two line segments and two points "
|
||||||
"(equal point-line distances)\n"
|
"(equal point-line distances)\n"
|
||||||
" * a line segment and two points "
|
" * a line segment and two points "
|
||||||
"(equal point-line distances)\n"
|
"(equal point-line distances)\n"
|
||||||
" * a line segment, and a point and line segment "
|
" * a line segment, and a point and line segment "
|
||||||
"(point-line distance equals length)\n"
|
"(point-line distance equals length)\n"
|
||||||
" * four line segments or normals "
|
" * two or more circles or arcs (equal radius)\n"
|
||||||
"(equal angle between A,B and C,D)\n"
|
|
||||||
" * three line segments or normals "
|
|
||||||
"(equal angle between A,B and B,C)\n"
|
|
||||||
" * two circles or arcs (equal radius)\n"
|
|
||||||
" * a line segment and an arc "
|
" * a line segment and an arc "
|
||||||
"(line segment length equals arc length)\n"));
|
"(line segment length equals arc length)\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(c.type == Type::EQUAL_ANGLE) {
|
SS.UndoRemember();
|
||||||
|
for (auto&& nc : newcons){
|
||||||
|
if(nc.type == Type::EQUAL_ANGLE) {
|
||||||
// Infer the nearest supplementary angle from the sketch.
|
// Infer the nearest supplementary angle from the sketch.
|
||||||
Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
|
Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
|
||||||
b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
|
b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
|
||||||
|
@ -373,10 +392,11 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
|
double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
|
||||||
|
|
||||||
if(d1*d2 < 0) {
|
if(d1*d2 < 0) {
|
||||||
c.other = true;
|
nc.other = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
AddConstraint(&nc, /*rememberForUndo=*/false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::RATIO:
|
case Command::RATIO:
|
||||||
|
@ -384,16 +404,34 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.type = Type::LENGTH_RATIO;
|
c.type = Type::LENGTH_RATIO;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.entityB = gs.entity[1];
|
c.entityB = gs.entity[1];
|
||||||
|
}
|
||||||
|
else if(gs.arcs == 2 && gs.n == 2) {
|
||||||
|
c.type = Type::ARC_ARC_LEN_RATIO;
|
||||||
|
c.entityA = gs.entity[0];
|
||||||
|
c.entityB = gs.entity[1];
|
||||||
|
}
|
||||||
|
else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
||||||
|
c.type = Type::ARC_LINE_LEN_RATIO;
|
||||||
|
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||||
|
c.entityA = gs.entity[1];
|
||||||
|
c.entityB = gs.entity[0];
|
||||||
|
} else {
|
||||||
|
c.entityA = gs.entity[0];
|
||||||
|
c.entityB = gs.entity[1];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for length ratio constraint. This "
|
Error(_("Bad selection for length ratio constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
" * two line segments\n"));
|
" * two line segments\n"
|
||||||
|
" * two arcs\n"
|
||||||
|
" * one arc and one line segment\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.valA = 0;
|
c.valA = 0;
|
||||||
c.ModifyToSatisfy();
|
c.ModifyToSatisfy();
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::DIFFERENCE:
|
case Command::DIFFERENCE:
|
||||||
|
@ -401,16 +439,34 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.type = Type::LENGTH_DIFFERENCE;
|
c.type = Type::LENGTH_DIFFERENCE;
|
||||||
c.entityA = gs.entity[0];
|
c.entityA = gs.entity[0];
|
||||||
c.entityB = gs.entity[1];
|
c.entityB = gs.entity[1];
|
||||||
|
}
|
||||||
|
else if(gs.arcs == 2 && gs.n == 2) {
|
||||||
|
c.type = Type::ARC_ARC_DIFFERENCE;
|
||||||
|
c.entityA = gs.entity[0];
|
||||||
|
c.entityB = gs.entity[1];
|
||||||
|
}
|
||||||
|
else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
||||||
|
c.type = Type::ARC_LINE_DIFFERENCE;
|
||||||
|
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||||
|
c.entityA = gs.entity[1];
|
||||||
|
c.entityB = gs.entity[0];
|
||||||
|
} else {
|
||||||
|
c.entityA = gs.entity[0];
|
||||||
|
c.entityB = gs.entity[1];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for length difference constraint. This "
|
Error(_("Bad selection for length difference constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
" * two line segments\n"));
|
" * two line segments\n"
|
||||||
|
" * two arcs\n"
|
||||||
|
" * one arc and one line segment\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.valA = 0;
|
c.valA = 0;
|
||||||
c.ModifyToSatisfy();
|
c.ModifyToSatisfy();
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::AT_MIDPOINT:
|
case Command::AT_MIDPOINT:
|
||||||
|
@ -424,12 +480,15 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
SS.UndoRemember();
|
SS.UndoRemember();
|
||||||
DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA);
|
DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA);
|
||||||
AddConstraint(&c, /*rememberForUndo=*/false);
|
AddConstraint(&c, /*rememberForUndo=*/false);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
} else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
|
} else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
|
||||||
c.type = Type::AT_MIDPOINT;
|
c.type = Type::AT_MIDPOINT;
|
||||||
int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
|
int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
|
||||||
c.entityA = gs.entity[i];
|
c.entityA = gs.entity[i];
|
||||||
c.entityB = gs.entity[1-i];
|
c.entityB = gs.entity[1-i];
|
||||||
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for at midpoint constraint. This "
|
Error(_("Bad selection for at midpoint constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
|
@ -439,7 +498,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
"(line's midpoint on plane)\n"));
|
"(line's midpoint on plane)\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::SYMMETRIC:
|
case Command::SYMMETRIC:
|
||||||
|
@ -528,41 +587,47 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]),
|
DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]),
|
||||||
Entity::NO_ENTITY);
|
Entity::NO_ENTITY);
|
||||||
AddConstraint(&c, /*rememberForUndo=*/false);
|
AddConstraint(&c, /*rememberForUndo=*/false);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::VERTICAL:
|
case Command::VERTICAL:
|
||||||
case Command::HORIZONTAL: {
|
case Command::HORIZONTAL: {
|
||||||
hEntity ha, hb;
|
|
||||||
if(c.workplane == Entity::FREE_IN_3D) {
|
|
||||||
Error(_("Activate a workplane (with Sketch -> In Workplane) before "
|
|
||||||
"applying a horizontal or vertical constraint."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(gs.lineSegments == 1 && gs.n == 1) {
|
|
||||||
c.entityA = gs.entity[0];
|
|
||||||
Entity *e = SK.GetEntity(c.entityA);
|
|
||||||
ha = e->point[0];
|
|
||||||
hb = e->point[1];
|
|
||||||
} else if(gs.points == 2 && gs.n == 2) {
|
|
||||||
ha = c.ptA = gs.point[0];
|
|
||||||
hb = c.ptB = gs.point[1];
|
|
||||||
} else {
|
|
||||||
Error(_("Bad selection for horizontal / vertical constraint. "
|
|
||||||
"This constraint can apply to:\n\n"
|
|
||||||
" * two points\n"
|
|
||||||
" * a line segment\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(id == Command::HORIZONTAL) {
|
if(id == Command::HORIZONTAL) {
|
||||||
c.type = Type::HORIZONTAL;
|
c.type = Type::HORIZONTAL;
|
||||||
} else {
|
} else {
|
||||||
c.type = Type::VERTICAL;
|
c.type = Type::VERTICAL;
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
if(c.workplane == Entity::FREE_IN_3D) {
|
||||||
|
Error(_("Activate a workplane (with Sketch -> In Workplane) before "
|
||||||
|
"applying a horizontal or vertical constraint."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(gs.lineSegments > 0 && gs.lineSegments == gs.n) {
|
||||||
|
for (auto enti : gs.entity){
|
||||||
|
c.entityA = enti;
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
|
} else if(gs.points >= 2 && gs.n == gs.points) {
|
||||||
|
c.ptA = gs.point[0];
|
||||||
|
for (int k = 1; k<gs.points; k++) {
|
||||||
|
c.ptB = gs.point[k];
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error(_("Bad selection for horizontal / vertical constraint. "
|
||||||
|
"This constraint can apply to:\n\n"
|
||||||
|
" * two or more points\n"
|
||||||
|
" * one or more line segments\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SS.UndoRemember();
|
||||||
|
for (auto && nc: newcons)
|
||||||
|
AddConstraint(&nc, /*rememberForUndo=*/false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,6 +668,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
nfree->NormalForceTo(Quaternion::From(fu, fv));
|
nfree->NormalForceTo(Quaternion::From(fu, fv));
|
||||||
}
|
}
|
||||||
AddConstraint(&c, /*rememberForUndo=*/false);
|
AddConstraint(&c, /*rememberForUndo=*/false);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,7 +706,19 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
|
|
||||||
case Command::ANGLE:
|
case Command::ANGLE:
|
||||||
case Command::REF_ANGLE: {
|
case Command::REF_ANGLE: {
|
||||||
if(gs.vectors == 2 && gs.n == 2) {
|
if(gs.vectors == 3 && gs.n == 3) {
|
||||||
|
c.type = Type::EQUAL_ANGLE;
|
||||||
|
c.entityA = gs.vector[0];
|
||||||
|
c.entityB = gs.vector[1];
|
||||||
|
c.entityC = gs.vector[1];
|
||||||
|
c.entityD = gs.vector[2];
|
||||||
|
} else if(gs.vectors == 4 && gs.n == 4) {
|
||||||
|
c.type = Type::EQUAL_ANGLE;
|
||||||
|
c.entityA = gs.vector[0];
|
||||||
|
c.entityB = gs.vector[1];
|
||||||
|
c.entityC = gs.vector[2];
|
||||||
|
c.entityD = gs.vector[3];
|
||||||
|
} else if(gs.vectors == 2 && gs.n == 2) {
|
||||||
c.type = Type::ANGLE;
|
c.type = Type::ANGLE;
|
||||||
c.entityA = gs.vector[0];
|
c.entityA = gs.vector[0];
|
||||||
c.entityB = gs.vector[1];
|
c.entityB = gs.vector[1];
|
||||||
|
@ -648,9 +726,15 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for angle constraint. This constraint "
|
Error(_("Bad selection for angle constraint. This constraint "
|
||||||
"can apply to:\n\n"
|
"can apply to:\n\n"
|
||||||
|
"Angle between:\n"
|
||||||
" * two line segments\n"
|
" * two line segments\n"
|
||||||
" * a line segment and a normal\n"
|
" * a line segment and a normal\n"
|
||||||
" * two normals\n"));
|
" * two normals\n"
|
||||||
|
"\nEqual angles:\n"
|
||||||
|
" * four line segments or normals "
|
||||||
|
"(equal angle between A,B and C,D)\n"
|
||||||
|
" * three line segments or normals "
|
||||||
|
"(equal angle between A,B and B,C)\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,14 +763,23 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
|
|
||||||
c.ModifyToSatisfy();
|
c.ModifyToSatisfy();
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::PARALLEL:
|
case Command::PARALLEL:
|
||||||
if(gs.vectors == 2 && gs.n == 2) {
|
if(gs.faces == 2 && gs.n == 2) {
|
||||||
|
c.type = Type::PARALLEL;
|
||||||
|
c.entityA = gs.face[0];
|
||||||
|
c.entityB = gs.face[1];
|
||||||
|
newcons.push_back(c);
|
||||||
|
} else if(gs.vectors > 1 && gs.vectors == gs.n) {
|
||||||
c.type = Type::PARALLEL;
|
c.type = Type::PARALLEL;
|
||||||
c.entityA = gs.vector[0];
|
c.entityA = gs.vector[0];
|
||||||
c.entityB = gs.vector[1];
|
for (std::vector<hEntity>::size_type k = 1; k < gs.vector.size();++k ){
|
||||||
|
c.entityB = gs.vector[k];
|
||||||
|
newcons.push_back(c);
|
||||||
|
}
|
||||||
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
||||||
Entity *line = SK.GetEntity(gs.entity[0]),
|
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||||
*arc = SK.GetEntity(gs.entity[1]);
|
*arc = SK.GetEntity(gs.entity[1]);
|
||||||
|
@ -699,6 +792,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.type = Type::ARC_LINE_TANGENT;
|
c.type = Type::ARC_LINE_TANGENT;
|
||||||
c.entityA = arc->h;
|
c.entityA = arc->h;
|
||||||
c.entityB = line->h;
|
c.entityB = line->h;
|
||||||
|
newcons.push_back(c);
|
||||||
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
||||||
Entity *line = SK.GetEntity(gs.entity[0]),
|
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||||
*cubic = SK.GetEntity(gs.entity[1]);
|
*cubic = SK.GetEntity(gs.entity[1]);
|
||||||
|
@ -711,6 +805,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.type = Type::CUBIC_LINE_TANGENT;
|
c.type = Type::CUBIC_LINE_TANGENT;
|
||||||
c.entityA = cubic->h;
|
c.entityA = cubic->h;
|
||||||
c.entityB = line->h;
|
c.entityB = line->h;
|
||||||
|
newcons.push_back(c);
|
||||||
} else if(gs.cubics + gs.arcs == 2 && gs.n == 2) {
|
} else if(gs.cubics + gs.arcs == 2 && gs.n == 2) {
|
||||||
if(!SS.GW.LockedInWorkplane()) {
|
if(!SS.GW.LockedInWorkplane()) {
|
||||||
Error(_("Curve-curve tangency must apply in workplane."));
|
Error(_("Curve-curve tangency must apply in workplane."));
|
||||||
|
@ -724,33 +819,43 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.type = Type::CURVE_CURVE_TANGENT;
|
c.type = Type::CURVE_CURVE_TANGENT;
|
||||||
c.entityA = eA->h;
|
c.entityA = eA->h;
|
||||||
c.entityB = eB->h;
|
c.entityB = eB->h;
|
||||||
|
newcons.push_back(c);
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for parallel / tangent constraint. This "
|
Error(_("Bad selection for parallel / tangent constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
" * two line segments (parallel)\n"
|
" * two faces\n"
|
||||||
" * a line segment and a normal (parallel)\n"
|
" * two or more line segments (parallel)\n"
|
||||||
" * two normals (parallel)\n"
|
" * one or more line segments and one or more normals (parallel)\n"
|
||||||
|
" * two or more normals (parallel)\n"
|
||||||
" * two line segments, arcs, or beziers, that share "
|
" * two line segments, arcs, or beziers, that share "
|
||||||
"an endpoint (tangent)\n"));
|
"an endpoint (tangent)\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
SS.UndoRemember();
|
||||||
|
for (auto&& nc:newcons)
|
||||||
|
AddConstraint(&nc, /*rememberForUndo=*/false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::PERPENDICULAR:
|
case Command::PERPENDICULAR:
|
||||||
if(gs.vectors == 2 && gs.n == 2) {
|
if(gs.faces == 2 && gs.n == 2) {
|
||||||
|
c.type = Type::PERPENDICULAR;
|
||||||
|
c.entityA = gs.face[0];
|
||||||
|
c.entityB = gs.face[1];
|
||||||
|
} else if(gs.vectors == 2 && gs.n == 2) {
|
||||||
c.type = Type::PERPENDICULAR;
|
c.type = Type::PERPENDICULAR;
|
||||||
c.entityA = gs.vector[0];
|
c.entityA = gs.vector[0];
|
||||||
c.entityB = gs.vector[1];
|
c.entityB = gs.vector[1];
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for perpendicular constraint. This "
|
Error(_("Bad selection for perpendicular constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
|
" * two faces\n"
|
||||||
" * two line segments\n"
|
" * two line segments\n"
|
||||||
" * a line segment and a normal\n"
|
" * a line segment and a normal\n"
|
||||||
" * two normals\n"));
|
" * two normals\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::WHERE_DRAGGED:
|
case Command::WHERE_DRAGGED:
|
||||||
|
@ -764,6 +869,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::COMMENT:
|
case Command::COMMENT:
|
||||||
|
@ -774,6 +880,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.workplane = SS.GW.ActiveWorkplane();
|
c.workplane = SS.GW.ActiveWorkplane();
|
||||||
c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");
|
c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");
|
||||||
AddConstraint(&c);
|
AddConstraint(&c);
|
||||||
|
newcons.push_back(c);
|
||||||
} else {
|
} else {
|
||||||
SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND;
|
SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND;
|
||||||
SS.GW.pending.command = Command::COMMENT;
|
SS.GW.pending.command = Command::COMMENT;
|
||||||
|
@ -784,11 +891,11 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
|
|
||||||
default: ssassert(false, "Unexpected menu ID");
|
default: ssassert(false, "Unexpected menu ID");
|
||||||
}
|
}
|
||||||
|
for (auto nc:newcons){
|
||||||
for(const Constraint &cc : SK.constraint) {
|
for(const Constraint &cc : SK.constraint) {
|
||||||
if(c.h != cc.h && c.Equals(cc)) {
|
if(nc.h != cc.h && nc.Equals(cc)) {
|
||||||
// Oops, we already have this exact constraint. Remove the one we just added.
|
// Oops, we already have this exact constraint. Remove the one we just added.
|
||||||
SK.constraint.RemoveById(c.h);
|
SK.constraint.RemoveById(nc.h);
|
||||||
SS.GW.ClearSelection();
|
SS.GW.ClearSelection();
|
||||||
// And now select the old one, to give feedback.
|
// And now select the old one, to give feedback.
|
||||||
SS.GW.MakeSelected(cc.h);
|
SS.GW.MakeSelected(cc.h);
|
||||||
|
@ -796,9 +903,9 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SK.constraint.FindByIdNoOops(c.h)) {
|
if(SK.constraint.FindByIdNoOops(nc.h)) {
|
||||||
Constraint *constraint = SK.GetConstraint(c.h);
|
Constraint *constraint = SK.GetConstraint(nc.h);
|
||||||
if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY &&
|
if(SS.TestRankForGroup(nc.group) == SolveResult::REDUNDANT_OKAY &&
|
||||||
!SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
|
!SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
|
||||||
constraint->HasLabel()) {
|
constraint->HasLabel()) {
|
||||||
constraint->reference = true;
|
constraint->reference = true;
|
||||||
|
@ -808,7 +915,8 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
|
if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
|
||||||
id == Command::RATIO || id == Command::DIFFERENCE) &&
|
id == Command::RATIO || id == Command::DIFFERENCE) &&
|
||||||
SS.immediatelyEditDimension) {
|
SS.immediatelyEditDimension) {
|
||||||
SS.GW.EditConstraint(c.h);
|
SS.GW.EditConstraint(nc.h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SS.GW.ClearSelection();
|
SS.GW.ClearSelection();
|
||||||
|
|
|
@ -18,7 +18,11 @@ bool ConstraintBase::HasLabel() const {
|
||||||
case Type::PROJ_PT_DISTANCE:
|
case Type::PROJ_PT_DISTANCE:
|
||||||
case Type::DIAMETER:
|
case Type::DIAMETER:
|
||||||
case Type::LENGTH_RATIO:
|
case Type::LENGTH_RATIO:
|
||||||
|
case Type::ARC_ARC_LEN_RATIO:
|
||||||
|
case Type::ARC_LINE_LEN_RATIO:
|
||||||
case Type::LENGTH_DIFFERENCE:
|
case Type::LENGTH_DIFFERENCE:
|
||||||
|
case Type::ARC_ARC_DIFFERENCE:
|
||||||
|
case Type::ARC_LINE_DIFFERENCE:
|
||||||
case Type::ANGLE:
|
case Type::ANGLE:
|
||||||
case Type::COMMENT:
|
case Type::COMMENT:
|
||||||
return true;
|
return true;
|
||||||
|
@ -39,7 +43,11 @@ bool ConstraintBase::IsProjectible() const {
|
||||||
case Type::EQ_PT_LN_DISTANCES:
|
case Type::EQ_PT_LN_DISTANCES:
|
||||||
case Type::EQUAL_ANGLE:
|
case Type::EQUAL_ANGLE:
|
||||||
case Type::LENGTH_RATIO:
|
case Type::LENGTH_RATIO:
|
||||||
|
case Type::ARC_ARC_LEN_RATIO:
|
||||||
|
case Type::ARC_LINE_LEN_RATIO:
|
||||||
case Type::LENGTH_DIFFERENCE:
|
case Type::LENGTH_DIFFERENCE:
|
||||||
|
case Type::ARC_ARC_DIFFERENCE:
|
||||||
|
case Type::ARC_LINE_DIFFERENCE:
|
||||||
case Type::SYMMETRIC:
|
case Type::SYMMETRIC:
|
||||||
case Type::SYMMETRIC_HORIZ:
|
case Type::SYMMETRIC_HORIZ:
|
||||||
case Type::SYMMETRIC_VERT:
|
case Type::SYMMETRIC_VERT:
|
||||||
|
@ -335,6 +343,110 @@ void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Type::ARC_ARC_LEN_RATIO: {
|
||||||
|
EntityBase *arc1 = SK.GetEntity(entityA),
|
||||||
|
*arc2 = SK.GetEntity(entityB);
|
||||||
|
|
||||||
|
// And get the arc1 radius, and the cosine of its angle
|
||||||
|
EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
|
||||||
|
*as1 = SK.GetEntity(arc1->point[1]),
|
||||||
|
*af1 = SK.GetEntity(arc1->point[2]);
|
||||||
|
|
||||||
|
ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
|
||||||
|
aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
|
||||||
|
Expr *r1 = aof1.Magnitude();
|
||||||
|
|
||||||
|
ExprVector n1 = arc1->Normal()->NormalExprsN();
|
||||||
|
ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
|
||||||
|
ExprVector v1 = n1.Cross(u1);
|
||||||
|
// so in our new csys, we start at (1, 0, 0)
|
||||||
|
Expr *costheta1 = aof1.Dot(u1)->Div(r1);
|
||||||
|
Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
|
||||||
|
|
||||||
|
double thetas1, thetaf1, dtheta1;
|
||||||
|
arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
|
||||||
|
Expr *theta1;
|
||||||
|
if(dtheta1 < 3*PI/4) {
|
||||||
|
theta1 = costheta1->ACos();
|
||||||
|
} else if(dtheta1 < 5*PI/4) {
|
||||||
|
// As the angle crosses pi, cos theta1 is not invertible;
|
||||||
|
// so use the sine to stop blowing up
|
||||||
|
theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
|
||||||
|
} else {
|
||||||
|
theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
|
||||||
|
}
|
||||||
|
|
||||||
|
// And get the arc2 radius, and the cosine of its angle
|
||||||
|
EntityBase *ao2 = SK.GetEntity(arc2->point[0]),
|
||||||
|
*as2 = SK.GetEntity(arc2->point[1]),
|
||||||
|
*af2 = SK.GetEntity(arc2->point[2]);
|
||||||
|
|
||||||
|
ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()),
|
||||||
|
aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs());
|
||||||
|
Expr *r2 = aof2.Magnitude();
|
||||||
|
|
||||||
|
ExprVector n2 = arc2->Normal()->NormalExprsN();
|
||||||
|
ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0));
|
||||||
|
ExprVector v2 = n2.Cross(u2);
|
||||||
|
// so in our new csys, we start at (1, 0, 0)
|
||||||
|
Expr *costheta2 = aof2.Dot(u2)->Div(r2);
|
||||||
|
Expr *sintheta2 = aof2.Dot(v2)->Div(r2);
|
||||||
|
|
||||||
|
double thetas2, thetaf2, dtheta2;
|
||||||
|
arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2);
|
||||||
|
Expr *theta2;
|
||||||
|
if(dtheta2 < 3*PI/4) {
|
||||||
|
theta2 = costheta2->ACos();
|
||||||
|
} else if(dtheta2 < 5*PI/4) {
|
||||||
|
// As the angle crosses pi, cos theta2 is not invertible;
|
||||||
|
// so use the sine to stop blowing up
|
||||||
|
theta2 = Expr::From(PI)->Minus(sintheta2->ASin());
|
||||||
|
} else {
|
||||||
|
theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos());
|
||||||
|
}
|
||||||
|
// And write the equation; (r1*theta1) / ( r2*theta2) = some ratio
|
||||||
|
AddEq(l, (r1->Times(theta1))->Div(r2->Times(theta2))->Minus(exA), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Type::ARC_LINE_LEN_RATIO: {
|
||||||
|
EntityBase *line = SK.GetEntity(entityA),
|
||||||
|
*arc1 = SK.GetEntity(entityB);
|
||||||
|
|
||||||
|
Expr *ll = Distance(workplane, line->point[0], line->point[1]);
|
||||||
|
|
||||||
|
// And get the arc1 radius, and the cosine of its angle
|
||||||
|
EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
|
||||||
|
*as1 = SK.GetEntity(arc1->point[1]),
|
||||||
|
*af1 = SK.GetEntity(arc1->point[2]);
|
||||||
|
|
||||||
|
ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
|
||||||
|
aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
|
||||||
|
Expr *r1 = aof1.Magnitude();
|
||||||
|
ExprVector n1 = arc1->Normal()->NormalExprsN();
|
||||||
|
ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
|
||||||
|
ExprVector v1 = n1.Cross(u1);
|
||||||
|
// so in our new csys, we start at (1, 0, 0)
|
||||||
|
Expr *costheta1 = aof1.Dot(u1)->Div(r1);
|
||||||
|
Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
|
||||||
|
|
||||||
|
double thetas1, thetaf1, dtheta1;
|
||||||
|
arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
|
||||||
|
Expr *theta1;
|
||||||
|
if(dtheta1 < 3*PI/4) {
|
||||||
|
theta1 = costheta1->ACos();
|
||||||
|
} else if(dtheta1 < 5*PI/4) {
|
||||||
|
// As the angle crosses pi, cos theta1 is not invertible;
|
||||||
|
// so use the sine to stop blowing up
|
||||||
|
theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
|
||||||
|
} else {
|
||||||
|
theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
|
||||||
|
}
|
||||||
|
// And write the equation; (r1*theta1) / ( length) = some ratio
|
||||||
|
AddEq(l, (r1->Times(theta1))->Div(ll)->Minus(exA), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case Type::LENGTH_DIFFERENCE: {
|
case Type::LENGTH_DIFFERENCE: {
|
||||||
EntityBase *a = SK.GetEntity(entityA);
|
EntityBase *a = SK.GetEntity(entityA);
|
||||||
EntityBase *b = SK.GetEntity(entityB);
|
EntityBase *b = SK.GetEntity(entityB);
|
||||||
|
@ -344,6 +456,110 @@ void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Type::ARC_ARC_DIFFERENCE: {
|
||||||
|
EntityBase *arc1 = SK.GetEntity(entityA),
|
||||||
|
*arc2 = SK.GetEntity(entityB);
|
||||||
|
|
||||||
|
// And get the arc1 radius, and the cosine of its angle
|
||||||
|
EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
|
||||||
|
*as1 = SK.GetEntity(arc1->point[1]),
|
||||||
|
*af1 = SK.GetEntity(arc1->point[2]);
|
||||||
|
|
||||||
|
ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
|
||||||
|
aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
|
||||||
|
Expr *r1 = aof1.Magnitude();
|
||||||
|
|
||||||
|
ExprVector n1 = arc1->Normal()->NormalExprsN();
|
||||||
|
ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
|
||||||
|
ExprVector v1 = n1.Cross(u1);
|
||||||
|
// so in our new csys, we start at (1, 0, 0)
|
||||||
|
Expr *costheta1 = aof1.Dot(u1)->Div(r1);
|
||||||
|
Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
|
||||||
|
|
||||||
|
double thetas1, thetaf1, dtheta1;
|
||||||
|
arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
|
||||||
|
Expr *theta1;
|
||||||
|
if(dtheta1 < 3*PI/4) {
|
||||||
|
theta1 = costheta1->ACos();
|
||||||
|
} else if(dtheta1 < 5*PI/4) {
|
||||||
|
// As the angle crosses pi, cos theta1 is not invertible;
|
||||||
|
// so use the sine to stop blowing up
|
||||||
|
theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
|
||||||
|
} else {
|
||||||
|
theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
|
||||||
|
}
|
||||||
|
|
||||||
|
// And get the arc2 radius, and the cosine of its angle
|
||||||
|
EntityBase *ao2 = SK.GetEntity(arc2->point[0]),
|
||||||
|
*as2 = SK.GetEntity(arc2->point[1]),
|
||||||
|
*af2 = SK.GetEntity(arc2->point[2]);
|
||||||
|
|
||||||
|
ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()),
|
||||||
|
aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs());
|
||||||
|
Expr *r2 = aof2.Magnitude();
|
||||||
|
|
||||||
|
ExprVector n2 = arc2->Normal()->NormalExprsN();
|
||||||
|
ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0));
|
||||||
|
ExprVector v2 = n2.Cross(u2);
|
||||||
|
// so in our new csys, we start at (1, 0, 0)
|
||||||
|
Expr *costheta2 = aof2.Dot(u2)->Div(r2);
|
||||||
|
Expr *sintheta2 = aof2.Dot(v2)->Div(r2);
|
||||||
|
|
||||||
|
double thetas2, thetaf2, dtheta2;
|
||||||
|
arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2);
|
||||||
|
Expr *theta2;
|
||||||
|
if(dtheta2 < 3*PI/4) {
|
||||||
|
theta2 = costheta2->ACos();
|
||||||
|
} else if(dtheta2 < 5*PI/4) {
|
||||||
|
// As the angle crosses pi, cos theta2 is not invertible;
|
||||||
|
// so use the sine to stop blowing up
|
||||||
|
theta2 = Expr::From(PI)->Minus(sintheta2->ASin());
|
||||||
|
} else {
|
||||||
|
theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos());
|
||||||
|
}
|
||||||
|
// And write the equation; (r1*theta1) - ( r2*theta2) = some difference
|
||||||
|
AddEq(l, (r1->Times(theta1))->Minus(r2->Times(theta2))->Minus(exA), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Type::ARC_LINE_DIFFERENCE: {
|
||||||
|
EntityBase *line = SK.GetEntity(entityA),
|
||||||
|
*arc1 = SK.GetEntity(entityB);
|
||||||
|
|
||||||
|
Expr *ll = Distance(workplane, line->point[0], line->point[1]);
|
||||||
|
|
||||||
|
// And get the arc1 radius, and the cosine of its angle
|
||||||
|
EntityBase *ao1 = SK.GetEntity(arc1->point[0]),
|
||||||
|
*as1 = SK.GetEntity(arc1->point[1]),
|
||||||
|
*af1 = SK.GetEntity(arc1->point[2]);
|
||||||
|
|
||||||
|
ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()),
|
||||||
|
aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs());
|
||||||
|
Expr *r1 = aof1.Magnitude();
|
||||||
|
ExprVector n1 = arc1->Normal()->NormalExprsN();
|
||||||
|
ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0));
|
||||||
|
ExprVector v1 = n1.Cross(u1);
|
||||||
|
// so in our new csys, we start at (1, 0, 0)
|
||||||
|
Expr *costheta1 = aof1.Dot(u1)->Div(r1);
|
||||||
|
Expr *sintheta1 = aof1.Dot(v1)->Div(r1);
|
||||||
|
|
||||||
|
double thetas1, thetaf1, dtheta1;
|
||||||
|
arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1);
|
||||||
|
Expr *theta1;
|
||||||
|
if(dtheta1 < 3*PI/4) {
|
||||||
|
theta1 = costheta1->ACos();
|
||||||
|
} else if(dtheta1 < 5*PI/4) {
|
||||||
|
// As the angle crosses pi, cos theta1 is not invertible;
|
||||||
|
// so use the sine to stop blowing up
|
||||||
|
theta1 = Expr::From(PI)->Minus(sintheta1->ASin());
|
||||||
|
} else {
|
||||||
|
theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos());
|
||||||
|
}
|
||||||
|
// And write the equation; (r1*theta1) - ( length) = some difference
|
||||||
|
AddEq(l, (r1->Times(theta1))->Minus(ll)->Minus(exA), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case Type::DIAMETER: {
|
case Type::DIAMETER: {
|
||||||
EntityBase *circle = SK.GetEntity(entityA);
|
EntityBase *circle = SK.GetEntity(entityA);
|
||||||
Expr *r = circle->CircleGetRadiusExpr();
|
Expr *r = circle->CircleGetRadiusExpr();
|
||||||
|
|
|
@ -19,6 +19,17 @@ void TextWindow::ScreenEditTtfText(int link, uint32_t v) {
|
||||||
SS.TW.edit.request = hr;
|
SS.TW.edit.request = hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextWindow::ScreenToggleTtfKerning(int link, uint32_t v) {
|
||||||
|
hRequest hr = { v };
|
||||||
|
Request *r = SK.GetRequest(hr);
|
||||||
|
|
||||||
|
SS.UndoRemember();
|
||||||
|
r->extraPoints = !r->extraPoints;
|
||||||
|
|
||||||
|
SS.MarkGroupDirty(r->group);
|
||||||
|
SS.ScheduleShowTW();
|
||||||
|
}
|
||||||
|
|
||||||
void TextWindow::ScreenSetTtfFont(int link, uint32_t v) {
|
void TextWindow::ScreenSetTtfFont(int link, uint32_t v) {
|
||||||
int i = (int)v;
|
int i = (int)v;
|
||||||
if(i < 0) return;
|
if(i < 0) return;
|
||||||
|
@ -64,17 +75,36 @@ void TextWindow::ScreenConstraintShowAsRadius(int link, uint32_t v) {
|
||||||
void TextWindow::DescribeSelection() {
|
void TextWindow::DescribeSelection() {
|
||||||
Printf(false, "");
|
Printf(false, "");
|
||||||
|
|
||||||
|
#define COSTR_NO_LINK(p) \
|
||||||
|
SS.MmToString((p).x).c_str(), \
|
||||||
|
SS.MmToString((p).y).c_str(), \
|
||||||
|
SS.MmToString((p).z).c_str()
|
||||||
|
#define PT_AS_STR_NO_LINK "(%Fi%s%Fd, %Fi%s%Fd, %Fi%s%Fd)"
|
||||||
|
#define PT_AS_NUM "(%Fi%3%Fd, %Fi%3%Fd, %Fi%3%Fd)"
|
||||||
|
#define COSTR(e, p) \
|
||||||
|
e->h, (&TextWindow::ScreenSelectEntity), (&TextWindow::ScreenHoverEntity), \
|
||||||
|
COSTR_NO_LINK(p)
|
||||||
|
#define PT_AS_STR "%Ll%D%f%h" PT_AS_STR_NO_LINK "%E"
|
||||||
|
#define CO_LINK(e, p) e->h, (&TextWindow::ScreenSelectEntity), (&TextWindow::ScreenHoverEntity), CO(p)
|
||||||
|
#define PT_AS_NUM_LINK "%Ll%D%f%h" PT_AS_NUM "%E"
|
||||||
|
|
||||||
auto const &gs = SS.GW.gs;
|
auto const &gs = SS.GW.gs;
|
||||||
|
|
||||||
|
auto ListFaces = [&]() {
|
||||||
|
char abc = 'A';
|
||||||
|
for(auto &fc : gs.face) {
|
||||||
|
Vector n = SK.GetEntity(fc)->FaceGetNormalNum();
|
||||||
|
Printf(true, " plane%c normal = " PT_AS_NUM, abc, CO(n));
|
||||||
|
Vector p = SK.GetEntity(fc)->FaceGetPointNum();
|
||||||
|
Printf(false, " plane%c thru = " PT_AS_STR, abc, COSTR(SK.GetEntity(fc), p));
|
||||||
|
++abc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if(gs.n == 1 && (gs.points == 1 || gs.entities == 1)) {
|
if(gs.n == 1 && (gs.points == 1 || gs.entities == 1)) {
|
||||||
Entity *e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]);
|
Entity *e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]);
|
||||||
Vector p;
|
Vector p;
|
||||||
|
|
||||||
#define COSTR(p) \
|
|
||||||
SS.MmToString((p).x).c_str(), \
|
|
||||||
SS.MmToString((p).y).c_str(), \
|
|
||||||
SS.MmToString((p).z).c_str()
|
|
||||||
#define PT_AS_STR "(%Fi%s%E, %Fi%s%E, %Fi%s%E)"
|
|
||||||
#define PT_AS_NUM "(%Fi%3%E, %Fi%3%E, %Fi%3%E)"
|
|
||||||
switch(e->type) {
|
switch(e->type) {
|
||||||
case Entity::Type::POINT_IN_3D:
|
case Entity::Type::POINT_IN_3D:
|
||||||
case Entity::Type::POINT_IN_2D:
|
case Entity::Type::POINT_IN_2D:
|
||||||
|
@ -82,8 +112,9 @@ void TextWindow::DescribeSelection() {
|
||||||
case Entity::Type::POINT_N_ROT_TRANS:
|
case Entity::Type::POINT_N_ROT_TRANS:
|
||||||
case Entity::Type::POINT_N_COPY:
|
case Entity::Type::POINT_N_COPY:
|
||||||
case Entity::Type::POINT_N_ROT_AA:
|
case Entity::Type::POINT_N_ROT_AA:
|
||||||
|
case Entity::Type::POINT_N_ROT_AXIS_TRANS:
|
||||||
p = e->PointGetNum();
|
p = e->PointGetNum();
|
||||||
Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(p));
|
Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(e, p));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Entity::Type::NORMAL_IN_3D:
|
case Entity::Type::NORMAL_IN_3D:
|
||||||
|
@ -104,20 +135,20 @@ void TextWindow::DescribeSelection() {
|
||||||
case Entity::Type::WORKPLANE: {
|
case Entity::Type::WORKPLANE: {
|
||||||
p = SK.GetEntity(e->point[0])->PointGetNum();
|
p = SK.GetEntity(e->point[0])->PointGetNum();
|
||||||
Printf(false, "%FtWORKPLANE%E");
|
Printf(false, "%FtWORKPLANE%E");
|
||||||
Printf(true, " origin = " PT_AS_STR, COSTR(p));
|
Printf(true, " origin = " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p));
|
||||||
Quaternion q = e->Normal()->NormalGetNum();
|
Quaternion q = e->Normal()->NormalGetNum();
|
||||||
p = q.RotationN();
|
p = q.RotationN();
|
||||||
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
Printf(true, " normal = " PT_AS_NUM_LINK, CO_LINK(e->Normal(), p));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Entity::Type::LINE_SEGMENT: {
|
case Entity::Type::LINE_SEGMENT: {
|
||||||
Vector p0 = SK.GetEntity(e->point[0])->PointGetNum();
|
Vector p0 = SK.GetEntity(e->point[0])->PointGetNum();
|
||||||
p = p0;
|
p = p0;
|
||||||
Printf(false, "%FtLINE SEGMENT%E");
|
Printf(false, "%FtLINE SEGMENT%E");
|
||||||
Printf(true, " thru " PT_AS_STR, COSTR(p));
|
Printf(true, " thru " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p));
|
||||||
Vector p1 = SK.GetEntity(e->point[1])->PointGetNum();
|
Vector p1 = SK.GetEntity(e->point[1])->PointGetNum();
|
||||||
p = p1;
|
p = p1;
|
||||||
Printf(false, " " PT_AS_STR, COSTR(p));
|
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(e->point[1]), p));
|
||||||
Printf(true, " len = %Fi%s%E",
|
Printf(true, " len = %Fi%s%E",
|
||||||
SS.MmToString((p1.Minus(p0).Magnitude())).c_str());
|
SS.MmToString((p1.Minus(p0).Magnitude())).c_str());
|
||||||
break;
|
break;
|
||||||
|
@ -137,18 +168,18 @@ void TextWindow::DescribeSelection() {
|
||||||
}
|
}
|
||||||
for(int i = 0; i < pts; i++) {
|
for(int i = 0; i < pts; i++) {
|
||||||
p = SK.GetEntity(e->point[i])->PointGetNum();
|
p = SK.GetEntity(e->point[i])->PointGetNum();
|
||||||
Printf((i==0), " p%d = " PT_AS_STR, i, COSTR(p));
|
Printf((i==0), " p%d = " PT_AS_STR, i, COSTR(SK.GetEntity(e->point[i]), p));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Entity::Type::ARC_OF_CIRCLE: {
|
case Entity::Type::ARC_OF_CIRCLE: {
|
||||||
Printf(false, "%FtARC OF A CIRCLE%E");
|
Printf(false, "%FtARC OF A CIRCLE%E");
|
||||||
p = SK.GetEntity(e->point[0])->PointGetNum();
|
p = SK.GetEntity(e->point[0])->PointGetNum();
|
||||||
Printf(true, " center = " PT_AS_STR, COSTR(p));
|
Printf(true, " center = " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p));
|
||||||
p = SK.GetEntity(e->point[1])->PointGetNum();
|
p = SK.GetEntity(e->point[1])->PointGetNum();
|
||||||
Printf(true, " endpoints = " PT_AS_STR, COSTR(p));
|
Printf(true, " endpoints = " PT_AS_STR, COSTR(SK.GetEntity(e->point[1]), p));
|
||||||
p = SK.GetEntity(e->point[2])->PointGetNum();
|
p = SK.GetEntity(e->point[2])->PointGetNum();
|
||||||
Printf(false, " " PT_AS_STR, COSTR(p));
|
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(e->point[2]), p));
|
||||||
double r = e->CircleGetRadiusNum();
|
double r = e->CircleGetRadiusNum();
|
||||||
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
||||||
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
||||||
|
@ -160,10 +191,11 @@ void TextWindow::DescribeSelection() {
|
||||||
case Entity::Type::CIRCLE: {
|
case Entity::Type::CIRCLE: {
|
||||||
Printf(false, "%FtCIRCLE%E");
|
Printf(false, "%FtCIRCLE%E");
|
||||||
p = SK.GetEntity(e->point[0])->PointGetNum();
|
p = SK.GetEntity(e->point[0])->PointGetNum();
|
||||||
Printf(true, " center = " PT_AS_STR, COSTR(p));
|
Printf(true, " center = " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p));
|
||||||
double r = e->CircleGetRadiusNum();
|
double r = e->CircleGetRadiusNum();
|
||||||
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
||||||
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
||||||
|
Printf(false, " circumference = %Fi%s", SS.MmToString(2*M_PI*r).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Entity::Type::FACE_NORMAL_PT:
|
case Entity::Type::FACE_NORMAL_PT:
|
||||||
|
@ -171,19 +203,24 @@ void TextWindow::DescribeSelection() {
|
||||||
case Entity::Type::FACE_N_ROT_TRANS:
|
case Entity::Type::FACE_N_ROT_TRANS:
|
||||||
case Entity::Type::FACE_N_ROT_AA:
|
case Entity::Type::FACE_N_ROT_AA:
|
||||||
case Entity::Type::FACE_N_TRANS:
|
case Entity::Type::FACE_N_TRANS:
|
||||||
|
case Entity::Type::FACE_ROT_NORMAL_PT:
|
||||||
|
case Entity::Type::FACE_N_ROT_AXIS_TRANS:
|
||||||
Printf(false, "%FtPLANE FACE%E");
|
Printf(false, "%FtPLANE FACE%E");
|
||||||
p = e->FaceGetNormalNum();
|
p = e->FaceGetNormalNum();
|
||||||
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
||||||
p = e->FaceGetPointNum();
|
p = e->FaceGetPointNum();
|
||||||
Printf(false, " thru = " PT_AS_STR, COSTR(p));
|
Printf(false, " thru = " PT_AS_STR, COSTR(e, p));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Entity::Type::TTF_TEXT: {
|
case Entity::Type::TTF_TEXT: {
|
||||||
Printf(false, "%FtTRUETYPE FONT TEXT%E");
|
Printf(false, "%FtTRUETYPE FONT TEXT%E");
|
||||||
Printf(true, " font = '%Fi%s%E'", e->font.c_str());
|
Printf(true, " font = '%Fi%s%E'", e->font.c_str());
|
||||||
if(e->h.isFromRequest()) {
|
if(e->h.isFromRequest()) {
|
||||||
Printf(false, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E",
|
Printf(true, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E",
|
||||||
e->str.c_str(), &ScreenEditTtfText, e->h.request().v);
|
e->str.c_str(), &ScreenEditTtfText, e->h.request().v);
|
||||||
|
Printf(true, " %Fd%f%D%Ll%s apply kerning",
|
||||||
|
&ScreenToggleTtfKerning, e->h.request().v,
|
||||||
|
e->extraPoints ? CHECK_TRUE : CHECK_FALSE);
|
||||||
Printf(true, " select new font");
|
Printf(true, " select new font");
|
||||||
SS.fonts.LoadAll();
|
SS.fonts.LoadAll();
|
||||||
// Not using range-for here because we use i inside the output.
|
// Not using range-for here because we use i inside the output.
|
||||||
|
@ -315,12 +352,12 @@ void TextWindow::DescribeSelection() {
|
||||||
} else if(gs.n == 2 && gs.points == 2) {
|
} else if(gs.n == 2 && gs.points == 2) {
|
||||||
Printf(false, "%FtTWO POINTS");
|
Printf(false, "%FtTWO POINTS");
|
||||||
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
|
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
|
||||||
Printf(true, " at " PT_AS_STR, COSTR(p0));
|
Printf(true, " at " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), p0));
|
||||||
Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum();
|
Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum();
|
||||||
Printf(false, " " PT_AS_STR, COSTR(p1));
|
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(gs.point[1]), p1));
|
||||||
Vector dv = p1.Minus(p0);
|
Vector dv = p1.Minus(p0);
|
||||||
Printf(true, " d = %Fi%s", SS.MmToString(dv.Magnitude()).c_str());
|
Printf(true, " d = %Fi%s", SS.MmToString(dv.Magnitude()).c_str());
|
||||||
Printf(false, " d(x, y, z) = " PT_AS_STR, COSTR(dv));
|
Printf(false, " d(x, y, z) = " PT_AS_STR_NO_LINK, COSTR_NO_LINK(dv));
|
||||||
} else if(gs.n == 2 && gs.points == 1 && gs.circlesOrArcs == 1) {
|
} else if(gs.n == 2 && gs.points == 1 && gs.circlesOrArcs == 1) {
|
||||||
Entity *ec = SK.GetEntity(gs.entity[0]);
|
Entity *ec = SK.GetEntity(gs.entity[0]);
|
||||||
if(ec->type == Entity::Type::CIRCLE) {
|
if(ec->type == Entity::Type::CIRCLE) {
|
||||||
|
@ -329,9 +366,9 @@ void TextWindow::DescribeSelection() {
|
||||||
Printf(false, "%FtPOINT AND AN ARC");
|
Printf(false, "%FtPOINT AND AN ARC");
|
||||||
} else ssassert(false, "Unexpected entity type");
|
} else ssassert(false, "Unexpected entity type");
|
||||||
Vector p = SK.GetEntity(gs.point[0])->PointGetNum();
|
Vector p = SK.GetEntity(gs.point[0])->PointGetNum();
|
||||||
Printf(true, " pt at " PT_AS_STR, COSTR(p));
|
Printf(true, " pt at " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), p));
|
||||||
Vector c = SK.GetEntity(ec->point[0])->PointGetNum();
|
Vector c = SK.GetEntity(ec->point[0])->PointGetNum();
|
||||||
Printf(true, " center = " PT_AS_STR, COSTR(c));
|
Printf(true, " center = " PT_AS_STR, COSTR(SK.GetEntity(ec->point[0]), c));
|
||||||
double r = ec->CircleGetRadiusNum();
|
double r = ec->CircleGetRadiusNum();
|
||||||
Printf(false, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
Printf(false, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
||||||
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
||||||
|
@ -340,22 +377,22 @@ void TextWindow::DescribeSelection() {
|
||||||
} else if(gs.n == 2 && gs.faces == 1 && gs.points == 1) {
|
} else if(gs.n == 2 && gs.faces == 1 && gs.points == 1) {
|
||||||
Printf(false, "%FtA POINT AND A PLANE FACE");
|
Printf(false, "%FtA POINT AND A PLANE FACE");
|
||||||
Vector pt = SK.GetEntity(gs.point[0])->PointGetNum();
|
Vector pt = SK.GetEntity(gs.point[0])->PointGetNum();
|
||||||
Printf(true, " point = " PT_AS_STR, COSTR(pt));
|
Printf(true, " point = " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), pt));
|
||||||
Vector n = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
|
Vector n = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
|
||||||
Printf(true, " plane normal = " PT_AS_NUM, CO(n));
|
Printf(true, " plane normal = " PT_AS_NUM, CO(n));
|
||||||
Vector pl = SK.GetEntity(gs.face[0])->FaceGetPointNum();
|
Vector pl = SK.GetEntity(gs.face[0])->FaceGetPointNum();
|
||||||
Printf(false, " plane thru = " PT_AS_STR, COSTR(pl));
|
Printf(false, " plane thru = " PT_AS_STR, COSTR(SK.GetEntity(gs.face[0]), pl));
|
||||||
double dd = n.Dot(pl) - n.Dot(pt);
|
double dd = n.Dot(pl) - n.Dot(pt);
|
||||||
Printf(true, " distance = %Fi%s", SS.MmToString(dd).c_str());
|
Printf(true, " distance = %Fi%s", SS.MmToString(dd).c_str());
|
||||||
} else if(gs.n == 3 && gs.points == 2 && gs.vectors == 1) {
|
} else if(gs.n == 3 && gs.points == 2 && gs.vectors == 1) {
|
||||||
Printf(false, "%FtTWO POINTS AND A VECTOR");
|
Printf(false, "%FtTWO POINTS AND A VECTOR");
|
||||||
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
|
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
|
||||||
Printf(true, " pointA = " PT_AS_STR, COSTR(p0));
|
Printf(true, " pointA = " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), p0));
|
||||||
Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum();
|
Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum();
|
||||||
Printf(false, " pointB = " PT_AS_STR, COSTR(p1));
|
Printf(false, " pointB = " PT_AS_STR, COSTR(SK.GetEntity(gs.point[1]), p1));
|
||||||
Vector v = SK.GetEntity(gs.vector[0])->VectorGetNum();
|
Vector v = SK.GetEntity(gs.vector[0])->VectorGetNum();
|
||||||
v = v.WithMagnitude(1);
|
v = v.WithMagnitude(1);
|
||||||
Printf(true, " vector = " PT_AS_NUM, CO(v));
|
Printf(true, " vector = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.vector[0]), v));
|
||||||
double d = (p1.Minus(p0)).Dot(v);
|
double d = (p1.Minus(p0)).Dot(v);
|
||||||
Printf(true, " proj_d = %Fi%s", SS.MmToString(d).c_str());
|
Printf(true, " proj_d = %Fi%s", SS.MmToString(d).c_str());
|
||||||
} else if(gs.n == 2 && gs.lineSegments == 1 && gs.points == 1) {
|
} else if(gs.n == 2 && gs.lineSegments == 1 && gs.points == 1) {
|
||||||
|
@ -363,11 +400,11 @@ void TextWindow::DescribeSelection() {
|
||||||
Vector lp0 = SK.GetEntity(ln->point[0])->PointGetNum(),
|
Vector lp0 = SK.GetEntity(ln->point[0])->PointGetNum(),
|
||||||
lp1 = SK.GetEntity(ln->point[1])->PointGetNum();
|
lp1 = SK.GetEntity(ln->point[1])->PointGetNum();
|
||||||
Printf(false, "%FtLINE SEGMENT AND POINT%E");
|
Printf(false, "%FtLINE SEGMENT AND POINT%E");
|
||||||
Printf(true, " ln thru " PT_AS_STR, COSTR(lp0));
|
Printf(true, " ln thru " PT_AS_STR, COSTR(SK.GetEntity(ln->point[0]), lp0));
|
||||||
Printf(false, " " PT_AS_STR, COSTR(lp1));
|
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(ln->point[1]), lp1));
|
||||||
Entity *p = SK.GetEntity(gs.point[0]);
|
Entity *p = SK.GetEntity(gs.point[0]);
|
||||||
Vector pp = p->PointGetNum();
|
Vector pp = p->PointGetNum();
|
||||||
Printf(true, " point " PT_AS_STR, COSTR(pp));
|
Printf(true, " point " PT_AS_STR, COSTR(p, pp));
|
||||||
Printf(true, " pt-ln distance = %Fi%s%E",
|
Printf(true, " pt-ln distance = %Fi%s%E",
|
||||||
SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))).c_str());
|
SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))).c_str());
|
||||||
hEntity wrkpl = SS.GW.ActiveWorkplane();
|
hEntity wrkpl = SS.GW.ActiveWorkplane();
|
||||||
|
@ -386,8 +423,8 @@ void TextWindow::DescribeSelection() {
|
||||||
v0 = v0.WithMagnitude(1);
|
v0 = v0.WithMagnitude(1);
|
||||||
v1 = v1.WithMagnitude(1);
|
v1 = v1.WithMagnitude(1);
|
||||||
|
|
||||||
Printf(true, " vectorA = " PT_AS_NUM, CO(v0));
|
Printf(true, " vectorA = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.entity[0]), v0));
|
||||||
Printf(false, " vectorB = " PT_AS_NUM, CO(v1));
|
Printf(false, " vectorB = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.entity[1]), v1));
|
||||||
|
|
||||||
double theta = acos(v0.Dot(v1));
|
double theta = acos(v0.Dot(v1));
|
||||||
Printf(true, " angle = %Fi%2%E degrees", theta*180/PI);
|
Printf(true, " angle = %Fi%2%E degrees", theta*180/PI);
|
||||||
|
@ -397,15 +434,10 @@ void TextWindow::DescribeSelection() {
|
||||||
} else if(gs.n == 2 && gs.faces == 2) {
|
} else if(gs.n == 2 && gs.faces == 2) {
|
||||||
Printf(false, "%FtTWO PLANE FACES");
|
Printf(false, "%FtTWO PLANE FACES");
|
||||||
|
|
||||||
Vector n0 = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
|
ListFaces();
|
||||||
Printf(true, " planeA normal = " PT_AS_NUM, CO(n0));
|
|
||||||
Vector p0 = SK.GetEntity(gs.face[0])->FaceGetPointNum();
|
|
||||||
Printf(false, " planeA thru = " PT_AS_STR, COSTR(p0));
|
|
||||||
|
|
||||||
|
Vector n0 = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
|
||||||
Vector n1 = SK.GetEntity(gs.face[1])->FaceGetNormalNum();
|
Vector n1 = SK.GetEntity(gs.face[1])->FaceGetNormalNum();
|
||||||
Printf(true, " planeB normal = " PT_AS_NUM, CO(n1));
|
|
||||||
Vector p1 = SK.GetEntity(gs.face[1])->FaceGetPointNum();
|
|
||||||
Printf(false, " planeB thru = " PT_AS_STR, COSTR(p1));
|
|
||||||
|
|
||||||
double theta = acos(n0.Dot(n1));
|
double theta = acos(n0.Dot(n1));
|
||||||
Printf(true, " angle = %Fi%2%E degrees", theta*180/PI);
|
Printf(true, " angle = %Fi%2%E degrees", theta*180/PI);
|
||||||
|
@ -414,17 +446,32 @@ void TextWindow::DescribeSelection() {
|
||||||
Printf(false, " or angle = %Fi%2%E (mod 180)", theta*180/PI);
|
Printf(false, " or angle = %Fi%2%E (mod 180)", theta*180/PI);
|
||||||
|
|
||||||
if(fabs(theta) < 0.01) {
|
if(fabs(theta) < 0.01) {
|
||||||
|
Vector p0 = SK.GetEntity(gs.face[0])->FaceGetPointNum();
|
||||||
|
Vector p1 = SK.GetEntity(gs.face[1])->FaceGetPointNum();
|
||||||
|
|
||||||
double d = (p1.Minus(p0)).Dot(n0);
|
double d = (p1.Minus(p0)).Dot(n0);
|
||||||
Printf(true, " distance = %Fi%s", SS.MmToString(d).c_str());
|
Printf(true, " distance = %Fi%s", SS.MmToString(d).c_str());
|
||||||
}
|
}
|
||||||
} else if(gs.n == 0 && gs.stylables > 0) {
|
} else if(gs.n == 3 && gs.faces == 3) {
|
||||||
Printf(false, "%FtSELECTED:%E comment text");
|
Printf(false, "%FtTHREE PLANE FACES");
|
||||||
|
|
||||||
|
ListFaces();
|
||||||
|
|
||||||
|
// We should probably compute and show the intersection point if there is one.
|
||||||
|
|
||||||
} else if(gs.n == 0 && gs.constraints == 1) {
|
} else if(gs.n == 0 && gs.constraints == 1) {
|
||||||
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
||||||
const std::string &desc = c->DescriptionString().c_str();
|
const std::string &desc = c->DescriptionString().c_str();
|
||||||
|
|
||||||
if(c->type == Constraint::Type::COMMENT) {
|
if(c->type == Constraint::Type::COMMENT) {
|
||||||
Printf(false, "%FtCOMMENT%E %s", desc.c_str());
|
Printf(false, "%FtCOMMENT%E %s", desc.c_str());
|
||||||
|
if(c->ptA != Entity::NO_ENTITY) {
|
||||||
|
Vector p = SK.GetEntity(c->ptA)->PointGetNum();
|
||||||
|
Printf(true, " attached to point at: " PT_AS_STR, COSTR(SK.GetEntity(c->ptA), p));
|
||||||
|
Vector dv = c->disp.offset;
|
||||||
|
Printf(false, " distance = %Fi%s", SS.MmToString(dv.Magnitude()).c_str());
|
||||||
|
Printf(false, " d(x, y, z) = " PT_AS_STR_NO_LINK, COSTR_NO_LINK(dv));
|
||||||
|
}
|
||||||
} else if(c->HasLabel()) {
|
} else if(c->HasLabel()) {
|
||||||
if(c->reference) {
|
if(c->reference) {
|
||||||
Printf(false, "%FtREFERENCE%E %s", desc.c_str());
|
Printf(false, "%FtREFERENCE%E %s", desc.c_str());
|
||||||
|
|
25
src/draw.cpp
25
src/draw.cpp
|
@ -185,15 +185,17 @@ void GraphicsWindow::MakeSelected(Selection *stog) {
|
||||||
|
|
||||||
if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
|
if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
|
||||||
// In the interest of speed for the triangle drawing code,
|
// In the interest of speed for the triangle drawing code,
|
||||||
// only two faces may be selected at a time.
|
// only MAX_SELECTABLE_FACES faces may be selected at a time.
|
||||||
int c = 0;
|
unsigned int c = 0;
|
||||||
Selection *s;
|
Selection *s;
|
||||||
selection.ClearTags();
|
selection.ClearTags();
|
||||||
for(s = selection.First(); s; s = selection.NextAfter(s)) {
|
for(s = selection.First(); s; s = selection.NextAfter(s)) {
|
||||||
hEntity he = s->entity;
|
hEntity he = s->entity;
|
||||||
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
|
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
|
||||||
c++;
|
c++;
|
||||||
if(c >= 2) s->tag = 1;
|
// See also GraphicsWindow::GroupSelection "if(e->IsFace())"
|
||||||
|
// and Group::DrawMesh "case DrawMeshAs::SELECTED:"
|
||||||
|
if(c >= MAX_SELECTABLE_FACES) s->tag = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selection.RemoveTagged();
|
selection.RemoveTagged();
|
||||||
|
@ -218,13 +220,28 @@ void GraphicsWindow::SelectByMarquee() {
|
||||||
bool entityHasBBox;
|
bool entityHasBBox;
|
||||||
BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox);
|
BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox);
|
||||||
if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) {
|
if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) {
|
||||||
|
if(e.type == Entity::Type::LINE_SEGMENT) {
|
||||||
|
Vector p0 = SS.GW.ProjectPoint3(e.EndpointStart());
|
||||||
|
Vector p1 = SS.GW.ProjectPoint3(e.EndpointFinish());
|
||||||
|
if((!marqueeBBox.Contains({p0.x, p0.y}, 0)) &&
|
||||||
|
(!marqueeBBox.Contains({p1.x, p1.y}, 0))) {
|
||||||
|
// The selection marquee does not contain either of the line segment end points.
|
||||||
|
// This means that either the segment is entirely outside the marquee or that
|
||||||
|
// it intersects it. Check if it does...
|
||||||
|
if(!Vector::BoundingBoxIntersectsLine(marqueeBBox.maxp, marqueeBBox.minp, p0,
|
||||||
|
p1, true)) {
|
||||||
|
// ... it does not so it is outside.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
MakeSelected(e.h);
|
MakeSelected(e.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Sort the selection according to various critieria: the entities and
|
// Sort the selection according to various criteria: the entities and
|
||||||
// constraints separately, counts of certain types of entities (circles,
|
// constraints separately, counts of certain types of entities (circles,
|
||||||
// lines, etc.), and so on.
|
// lines, etc.), and so on.
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
|
@ -12,7 +12,7 @@ std::string Constraint::Label() const {
|
||||||
std::string result;
|
std::string result;
|
||||||
if(type == Type::ANGLE) {
|
if(type == Type::ANGLE) {
|
||||||
result = SS.DegreeToString(valA) + "°";
|
result = SS.DegreeToString(valA) + "°";
|
||||||
} else if(type == Type::LENGTH_RATIO) {
|
} else if(type == Type::LENGTH_RATIO || type == Type::ARC_ARC_LEN_RATIO || type == Type::ARC_LINE_LEN_RATIO) {
|
||||||
result = ssprintf("%.3f:1", valA);
|
result = ssprintf("%.3f:1", valA);
|
||||||
} else if(type == Type::COMMENT) {
|
} else if(type == Type::COMMENT) {
|
||||||
result = comment;
|
result = comment;
|
||||||
|
@ -267,7 +267,7 @@ void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
const Camera &camera = canvas->GetCamera();
|
const Camera &camera = canvas->GetCamera();
|
||||||
Entity *circ = SK.GetEntity(he);
|
Entity *circ = SK.GetEntity(he);
|
||||||
|
|
||||||
Vector center = SK.GetEntity(circ->point[0])->PointGetNum();
|
Vector center = SK.GetEntity(circ->point[0])->PointGetDrawNum();
|
||||||
double r = circ->CircleGetRadiusNum();
|
double r = circ->CircleGetRadiusNum();
|
||||||
Quaternion q = circ->Normal()->NormalGetNum();
|
Quaternion q = circ->Normal()->NormalGetNum();
|
||||||
Vector u = q.RotationU(), v = q.RotationV();
|
Vector u = q.RotationU(), v = q.RotationV();
|
||||||
|
@ -291,7 +291,8 @@ void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
|
|
||||||
void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
Vector a0, Vector da, Vector b0, Vector db,
|
Vector a0, Vector da, Vector b0, Vector db,
|
||||||
Vector offset, Vector *ref, bool trim)
|
Vector offset, Vector *ref, bool trim,
|
||||||
|
Vector explodeOffset)
|
||||||
{
|
{
|
||||||
const Camera &camera = canvas->GetCamera();
|
const Camera &camera = canvas->GetCamera();
|
||||||
double pixels = 1.0 / camera.scale;
|
double pixels = 1.0 / camera.scale;
|
||||||
|
@ -305,6 +306,9 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
db = db.ProjectVectorInto(workplane);
|
db = db.ProjectVectorInto(workplane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a0 = a0.Plus(explodeOffset);
|
||||||
|
b0 = b0.Plus(explodeOffset);
|
||||||
|
|
||||||
Vector a1 = a0.Plus(da);
|
Vector a1 = a0.Plus(da);
|
||||||
Vector b1 = b0.Plus(db);
|
Vector b1 = b0.Plus(db);
|
||||||
|
|
||||||
|
@ -445,7 +449,22 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Constraint::IsVisible() const {
|
bool Constraint::IsVisible() const {
|
||||||
if(!SS.GW.showConstraints) return false;
|
if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_NOSHOW)
|
||||||
|
return false;
|
||||||
|
bool isDim = false;
|
||||||
|
|
||||||
|
if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_DIM)
|
||||||
|
switch(type) {
|
||||||
|
case ConstraintBase::Type::ANGLE:
|
||||||
|
case ConstraintBase::Type::DIAMETER:
|
||||||
|
case ConstraintBase::Type::PT_PT_DISTANCE:
|
||||||
|
case ConstraintBase::Type::PT_FACE_DISTANCE:
|
||||||
|
case ConstraintBase::Type::PT_LINE_DISTANCE:
|
||||||
|
case ConstraintBase::Type::PT_PLANE_DISTANCE: isDim = true; break;
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_ALL || isDim ) {
|
||||||
Group *g = SK.GetGroup(group);
|
Group *g = SK.GetGroup(group);
|
||||||
// If the group is hidden, then the constraints are hidden and not
|
// If the group is hidden, then the constraints are hidden and not
|
||||||
// able to be selected.
|
// able to be selected.
|
||||||
|
@ -460,6 +479,8 @@ bool Constraint::IsVisible() const {
|
||||||
if(!s->visible) return false;
|
if(!s->visible) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,
|
bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
|
@ -534,6 +555,15 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
DoProjectedPoint(canvas, hcs, &bp);
|
DoProjectedPoint(canvas, hcs, &bp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
// Offset A and B by the same offset so the constraint is drawn
|
||||||
|
// in the plane of one of the exploded points (rather than at an
|
||||||
|
// angle)
|
||||||
|
Vector offset = SK.GetEntity(ptA)->ExplodeOffset();
|
||||||
|
ap = ap.Plus(offset);
|
||||||
|
bp = bp.Plus(offset);
|
||||||
|
}
|
||||||
|
|
||||||
Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
|
Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
|
|
||||||
|
@ -548,6 +578,19 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
dp = (bp.Minus(ap)),
|
dp = (bp.Minus(ap)),
|
||||||
pp = SK.GetEntity(entityA)->VectorGetNum();
|
pp = SK.GetEntity(entityA)->VectorGetNum();
|
||||||
|
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
// explode for whichever point is in the workplane (or the first if both are)
|
||||||
|
Entity *pt = SK.GetEntity(ptA);
|
||||||
|
if(pt->group != group) {
|
||||||
|
pt = SK.GetEntity(ptB);
|
||||||
|
}
|
||||||
|
if(pt->group == group) {
|
||||||
|
Vector offset = pt->ExplodeOffset();
|
||||||
|
ap = ap.Plus(offset);
|
||||||
|
bp = bp.Plus(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
|
Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset);
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
|
|
||||||
|
@ -564,7 +607,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
|
|
||||||
case Type::PT_FACE_DISTANCE:
|
case Type::PT_FACE_DISTANCE:
|
||||||
case Type::PT_PLANE_DISTANCE: {
|
case Type::PT_PLANE_DISTANCE: {
|
||||||
Vector pt = SK.GetEntity(ptA)->PointGetNum();
|
Vector pt = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||||
Entity *enta = SK.GetEntity(entityA);
|
Entity *enta = SK.GetEntity(entityA);
|
||||||
Vector n, p;
|
Vector n, p;
|
||||||
if(type == Type::PT_PLANE_DISTANCE) {
|
if(type == Type::PT_PLANE_DISTANCE) {
|
||||||
|
@ -590,7 +633,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
case Type::PT_LINE_DISTANCE: {
|
case Type::PT_LINE_DISTANCE: {
|
||||||
Vector pt = SK.GetEntity(ptA)->PointGetNum();
|
Entity *ptEntity = SK.GetEntity(ptA);
|
||||||
|
Vector pt = ptEntity->PointGetNum();
|
||||||
Entity *line = SK.GetEntity(entityA);
|
Entity *line = SK.GetEntity(entityA);
|
||||||
Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
|
Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
|
||||||
Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
|
Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
|
||||||
|
@ -602,6 +646,19 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
DoProjectedPoint(canvas, hcs, &pt);
|
DoProjectedPoint(canvas, hcs, &pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only explode if the point and line are in the same group (and that group is a sketch
|
||||||
|
// with explode enabled) otherwise it's too visually confusing to figure out what the
|
||||||
|
// correct projections should be.
|
||||||
|
bool shouldExplode = ShouldDrawExploded()
|
||||||
|
&& ptEntity->group == group
|
||||||
|
&& line->group == group;
|
||||||
|
if(shouldExplode) {
|
||||||
|
Vector explodeOffset = ptEntity->ExplodeOffset();
|
||||||
|
pt = pt.Plus(explodeOffset);
|
||||||
|
lA = lA.Plus(explodeOffset);
|
||||||
|
lB = lB.Plus(explodeOffset);
|
||||||
|
}
|
||||||
|
|
||||||
// Find the closest point on the line
|
// Find the closest point on the line
|
||||||
Vector closest = pt.ClosestPointOnLine(lA, dl);
|
Vector closest = pt.ClosestPointOnLine(lA, dl);
|
||||||
|
|
||||||
|
@ -655,7 +712,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
|
|
||||||
case Type::DIAMETER: {
|
case Type::DIAMETER: {
|
||||||
Entity *circle = SK.GetEntity(entityA);
|
Entity *circle = SK.GetEntity(entityA);
|
||||||
Vector center = SK.GetEntity(circle->point[0])->PointGetNum();
|
Vector center = SK.GetEntity(circle->point[0])->PointGetDrawNum();
|
||||||
Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();
|
Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();
|
||||||
Vector n = q.RotationN().WithMagnitude(1);
|
Vector n = q.RotationN().WithMagnitude(1);
|
||||||
double r = circle->CircleGetRadiusNum();
|
double r = circle->CircleGetRadiusNum();
|
||||||
|
@ -697,7 +754,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
Vector r = camera.projRight.ScaledBy((a+1)/camera.scale);
|
Vector r = camera.projRight.ScaledBy((a+1)/camera.scale);
|
||||||
Vector d = camera.projUp.ScaledBy((2-a)/camera.scale);
|
Vector d = camera.projUp.ScaledBy((2-a)/camera.scale);
|
||||||
for(int i = 0; i < 2; i++) {
|
for(int i = 0; i < 2; i++) {
|
||||||
Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum();
|
Vector p = SK.GetEntity(i == 0 ? ptA : ptB)->PointGetDrawNum();
|
||||||
if(refs) refs->push_back(p);
|
if(refs) refs->push_back(p);
|
||||||
canvas->DrawQuad(p.Plus (r).Plus (d),
|
canvas->DrawQuad(p.Plus (r).Plus (d),
|
||||||
p.Plus (r).Minus(d),
|
p.Plus (r).Minus(d),
|
||||||
|
@ -715,7 +772,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
case Type::PT_ON_FACE:
|
case Type::PT_ON_FACE:
|
||||||
case Type::PT_IN_PLANE: {
|
case Type::PT_IN_PLANE: {
|
||||||
double s = 8/camera.scale;
|
double s = 8/camera.scale;
|
||||||
Vector p = SK.GetEntity(ptA)->PointGetNum();
|
Vector p = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||||
if(refs) refs->push_back(p);
|
if(refs) refs->push_back(p);
|
||||||
Vector r, d;
|
Vector r, d;
|
||||||
if(type == Type::PT_ON_FACE) {
|
if(type == Type::PT_ON_FACE) {
|
||||||
|
@ -740,7 +797,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
case Type::WHERE_DRAGGED: {
|
case Type::WHERE_DRAGGED: {
|
||||||
Vector p = SK.GetEntity(ptA)->PointGetNum();
|
Vector p = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||||
if(refs) refs->push_back(p);
|
if(refs) refs->push_back(p);
|
||||||
Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus(
|
Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus(
|
||||||
gr.WithMagnitude(8/camera.scale)),
|
gr.WithMagnitude(8/camera.scale)),
|
||||||
|
@ -797,10 +854,10 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
DoArcForAngle(canvas, hcs, a0, da, b0, db,
|
DoArcForAngle(canvas, hcs, a0, da, b0, db,
|
||||||
da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false);
|
da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, a->ExplodeOffset());
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
DoArcForAngle(canvas, hcs, c0, dc, d0, dd,
|
DoArcForAngle(canvas, hcs, c0, dc, d0, dd,
|
||||||
dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false);
|
dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, c->ExplodeOffset());
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -820,7 +877,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector ref;
|
Vector ref;
|
||||||
DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true);
|
DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true, a->ExplodeOffset());
|
||||||
DoLabel(canvas, hcs, ref, labelPos, gr, gu);
|
DoLabel(canvas, hcs, ref, labelPos, gr, gu);
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
return;
|
return;
|
||||||
|
@ -855,7 +912,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
if(u.Dot(ru) < 0) u = u.ScaledBy(-1);
|
if(u.Dot(ru) < 0) u = u.ScaledBy(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector p = e->VectorGetRefPoint();
|
Vector p = e->VectorGetRefPoint().Plus(e->ExplodeOffset());
|
||||||
Vector s = p.Plus(u).Plus(v);
|
Vector s = p.Plus(u).Plus(v);
|
||||||
DoLine(canvas, hcs, s, s.Plus(v));
|
DoLine(canvas, hcs, s, s.Plus(v));
|
||||||
Vector m = s.Plus(v.ScaledBy(0.5));
|
Vector m = s.Plus(v.ScaledBy(0.5));
|
||||||
|
@ -873,9 +930,9 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
if(type == Type::ARC_LINE_TANGENT) {
|
if(type == Type::ARC_LINE_TANGENT) {
|
||||||
Entity *arc = SK.GetEntity(entityA);
|
Entity *arc = SK.GetEntity(entityA);
|
||||||
Entity *norm = SK.GetEntity(arc->normal);
|
Entity *norm = SK.GetEntity(arc->normal);
|
||||||
Vector c = SK.GetEntity(arc->point[0])->PointGetNum();
|
Vector c = SK.GetEntity(arc->point[0])->PointGetDrawNum();
|
||||||
Vector p =
|
Vector p =
|
||||||
SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum();
|
SK.GetEntity(arc->point[other ? 2 : 1])->PointGetDrawNum();
|
||||||
Vector r = p.Minus(c);
|
Vector r = p.Minus(c);
|
||||||
textAt = p.Plus(r.WithMagnitude(14/camera.scale));
|
textAt = p.Plus(r.WithMagnitude(14/camera.scale));
|
||||||
u = norm->NormalU();
|
u = norm->NormalU();
|
||||||
|
@ -896,6 +953,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
Entity *cubic = SK.GetEntity(entityA);
|
Entity *cubic = SK.GetEntity(entityA);
|
||||||
Vector p = other ? cubic->CubicGetFinishNum() :
|
Vector p = other ? cubic->CubicGetFinishNum() :
|
||||||
cubic->CubicGetStartNum();
|
cubic->CubicGetStartNum();
|
||||||
|
p = p.Plus(cubic->ExplodeOffset());
|
||||||
Vector dir = SK.GetEntity(entityB)->VectorGetNum();
|
Vector dir = SK.GetEntity(entityB)->VectorGetNum();
|
||||||
Vector out = n.Cross(dir);
|
Vector out = n.Cross(dir);
|
||||||
textAt = p.Plus(out.WithMagnitude(14/camera.scale));
|
textAt = p.Plus(out.WithMagnitude(14/camera.scale));
|
||||||
|
@ -905,12 +963,12 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
u = wn->NormalU();
|
u = wn->NormalU();
|
||||||
v = wn->NormalV();
|
v = wn->NormalV();
|
||||||
n = wn->NormalN();
|
n = wn->NormalN();
|
||||||
EntityBase *eA = SK.GetEntity(entityA);
|
Entity *eA = SK.GetEntity(entityA);
|
||||||
// Big pain; we have to get a vector tangent to the curve
|
// Big pain; we have to get a vector tangent to the curve
|
||||||
// at the shared point, which could be from either a cubic
|
// at the shared point, which could be from either a cubic
|
||||||
// or an arc.
|
// or an arc.
|
||||||
if(other) {
|
if(other) {
|
||||||
textAt = eA->EndpointFinish();
|
textAt = eA->EndpointFinish().Plus(eA->ExplodeOffset());
|
||||||
if(eA->type == Entity::Type::CUBIC) {
|
if(eA->type == Entity::Type::CUBIC) {
|
||||||
dir = eA->CubicGetFinishTangentNum();
|
dir = eA->CubicGetFinishTangentNum();
|
||||||
} else {
|
} else {
|
||||||
|
@ -919,7 +977,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
dir = n.Cross(dir);
|
dir = n.Cross(dir);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
textAt = eA->EndpointStart();
|
textAt = eA->EndpointStart().Plus(eA->ExplodeOffset());
|
||||||
if(eA->type == Entity::Type::CUBIC) {
|
if(eA->type == Entity::Type::CUBIC) {
|
||||||
dir = eA->CubicGetStartTangentNum();
|
dir = eA->CubicGetStartTangentNum();
|
||||||
} else {
|
} else {
|
||||||
|
@ -947,6 +1005,10 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale);
|
Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale);
|
||||||
Vector p = e->VectorGetRefPoint();
|
Vector p = e->VectorGetRefPoint();
|
||||||
|
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
p = p.Plus(e->ExplodeOffset());
|
||||||
|
}
|
||||||
|
|
||||||
DoLine(canvas, hcs, p.Plus(u), p.Plus(u).Plus(n));
|
DoLine(canvas, hcs, p.Plus(u), p.Plus(u).Plus(n));
|
||||||
DoLine(canvas, hcs, p.Minus(u), p.Minus(u).Plus(n));
|
DoLine(canvas, hcs, p.Minus(u), p.Minus(u).Plus(n));
|
||||||
if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5)));
|
if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5)));
|
||||||
|
@ -967,8 +1029,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
Entity *line = SK.GetEntity(entityA);
|
Entity *line = SK.GetEntity(entityA);
|
||||||
Vector ref;
|
Vector ref;
|
||||||
DoEqualLenTicks(canvas, hcs,
|
DoEqualLenTicks(canvas, hcs,
|
||||||
SK.GetEntity(line->point[0])->PointGetNum(),
|
SK.GetEntity(line->point[0])->PointGetDrawNum(),
|
||||||
SK.GetEntity(line->point[1])->PointGetNum(),
|
SK.GetEntity(line->point[1])->PointGetDrawNum(),
|
||||||
gn, &ref);
|
gn, &ref);
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
DoEqualRadiusTicks(canvas, hcs, entityB, &ref);
|
DoEqualRadiusTicks(canvas, hcs, entityB, &ref);
|
||||||
|
@ -990,6 +1052,12 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
DoProjectedPoint(canvas, hcs, &b);
|
DoProjectedPoint(canvas, hcs, &b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
Vector offset = e->ExplodeOffset();
|
||||||
|
a = a.Plus(offset);
|
||||||
|
b = b.Plus(offset);
|
||||||
|
}
|
||||||
|
|
||||||
Vector ref;
|
Vector ref;
|
||||||
DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);
|
DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);
|
||||||
if(refs) refs->push_back(ref);
|
if(refs) refs->push_back(ref);
|
||||||
|
@ -1000,6 +1068,41 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case Type::ARC_ARC_LEN_RATIO:
|
||||||
|
case Type::ARC_ARC_DIFFERENCE: {
|
||||||
|
Entity *circle = SK.GetEntity(entityA);
|
||||||
|
Vector center = SK.GetEntity(circle->point[0])->PointGetNum();
|
||||||
|
Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum();
|
||||||
|
Vector n = q.RotationN().WithMagnitude(1);
|
||||||
|
|
||||||
|
Vector ref2;
|
||||||
|
DoEqualRadiusTicks(canvas, hcs, entityA, &ref2);
|
||||||
|
DoEqualRadiusTicks(canvas, hcs, entityB, &ref2);
|
||||||
|
|
||||||
|
Vector ref = center.Plus(disp.offset);
|
||||||
|
// Force the label into the same plane as the circle.
|
||||||
|
ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center)));
|
||||||
|
if(refs) refs->push_back(ref);
|
||||||
|
Vector topLeft;
|
||||||
|
DoLabel(canvas, hcs, ref, &topLeft, gr, gu);
|
||||||
|
if(labelPos) *labelPos = topLeft;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case Type::ARC_LINE_LEN_RATIO:
|
||||||
|
case Type::ARC_LINE_DIFFERENCE: {
|
||||||
|
Vector a, b = Vector::From(0, 0, 0);
|
||||||
|
Vector ref;
|
||||||
|
Entity *e = SK.GetEntity(entityA);
|
||||||
|
a = SK.GetEntity(e->point[0])->PointGetNum();
|
||||||
|
b = SK.GetEntity(e->point[1])->PointGetNum();
|
||||||
|
DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);
|
||||||
|
if(refs) refs->push_back(ref);
|
||||||
|
DoEqualRadiusTicks(canvas, hcs, entityB, &ref);
|
||||||
|
if(refs) refs->push_back(ref);
|
||||||
|
ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset);
|
||||||
|
DoLabel(canvas, hcs, ref, labelPos, gr, gu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case Type::EQ_LEN_PT_LINE_D: {
|
case Type::EQ_LEN_PT_LINE_D: {
|
||||||
Entity *forLen = SK.GetEntity(entityA);
|
Entity *forLen = SK.GetEntity(entityA);
|
||||||
|
@ -1009,6 +1112,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
DoProjectedPoint(canvas, hcs, &a);
|
DoProjectedPoint(canvas, hcs, &a);
|
||||||
DoProjectedPoint(canvas, hcs, &b);
|
DoProjectedPoint(canvas, hcs, &b);
|
||||||
}
|
}
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
Vector offset = forLen->ExplodeOffset();
|
||||||
|
a = a.Plus(offset);
|
||||||
|
b = b.Plus(offset);
|
||||||
|
}
|
||||||
Vector refa;
|
Vector refa;
|
||||||
DoEqualLenTicks(canvas, hcs, a, b, gn, &refa);
|
DoEqualLenTicks(canvas, hcs, a, b, gn, &refa);
|
||||||
if(refs) refs->push_back(refa);
|
if(refs) refs->push_back(refa);
|
||||||
|
@ -1024,6 +1132,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));
|
Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
Vector offset = SK.GetEntity(ptA)->ExplodeOffset();
|
||||||
|
pt = pt.Plus(offset);
|
||||||
|
closest = closest.Plus(offset);
|
||||||
|
}
|
||||||
DoLine(canvas, hcs, pt, closest);
|
DoLine(canvas, hcs, pt, closest);
|
||||||
Vector refb;
|
Vector refb;
|
||||||
DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb);
|
DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb);
|
||||||
|
@ -1046,6 +1159,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));
|
Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la));
|
||||||
|
if(ShouldDrawExploded()) {
|
||||||
|
Vector offset = pte->ExplodeOffset();
|
||||||
|
pt = pt.Plus(offset);
|
||||||
|
closest = closest.Plus(offset);
|
||||||
|
}
|
||||||
DoLine(canvas, hcs, pt, closest);
|
DoLine(canvas, hcs, pt, closest);
|
||||||
|
|
||||||
Vector ref;
|
Vector ref;
|
||||||
|
@ -1075,8 +1193,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
||||||
goto s;
|
goto s;
|
||||||
}
|
}
|
||||||
s:
|
s:
|
||||||
Vector a = SK.GetEntity(ptA)->PointGetNum();
|
Vector a = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||||
Vector b = SK.GetEntity(ptB)->PointGetNum();
|
Vector b = SK.GetEntity(ptB)->PointGetDrawNum();
|
||||||
|
|
||||||
for(int i = 0; i < 2; i++) {
|
for(int i = 0; i < 2; i++) {
|
||||||
Vector tail = (i == 0) ? a : b;
|
Vector tail = (i == 0) ? a : b;
|
||||||
|
@ -1113,8 +1231,8 @@ s:
|
||||||
}
|
}
|
||||||
// For "at midpoint", this branch is always taken.
|
// For "at midpoint", this branch is always taken.
|
||||||
Entity *e = SK.GetEntity(entityA);
|
Entity *e = SK.GetEntity(entityA);
|
||||||
Vector a = SK.GetEntity(e->point[0])->PointGetNum();
|
Vector a = SK.GetEntity(e->point[0])->PointGetDrawNum();
|
||||||
Vector b = SK.GetEntity(e->point[1])->PointGetNum();
|
Vector b = SK.GetEntity(e->point[1])->PointGetDrawNum();
|
||||||
Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5));
|
Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5));
|
||||||
Vector offset = (a.Minus(b)).Cross(n);
|
Vector offset = (a.Minus(b)).Cross(n);
|
||||||
offset = offset.WithMagnitude(textHeight);
|
offset = offset.WithMagnitude(textHeight);
|
||||||
|
@ -1138,8 +1256,8 @@ s:
|
||||||
r.WithMagnitude(1), u.WithMagnitude(1), hcs);
|
r.WithMagnitude(1), u.WithMagnitude(1), hcs);
|
||||||
if(refs) refs->push_back(o);
|
if(refs) refs->push_back(o);
|
||||||
} else {
|
} else {
|
||||||
Vector a = SK.GetEntity(ptA)->PointGetNum();
|
Vector a = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||||
Vector b = SK.GetEntity(ptB)->PointGetNum();
|
Vector b = SK.GetEntity(ptB)->PointGetDrawNum();
|
||||||
|
|
||||||
Entity *w = SK.GetEntity(workplane);
|
Entity *w = SK.GetEntity(workplane);
|
||||||
Vector cu = w->Normal()->NormalU();
|
Vector cu = w->Normal()->NormalU();
|
||||||
|
@ -1243,7 +1361,11 @@ bool Constraint::HasLabel() const {
|
||||||
case Type::PT_FACE_DISTANCE:
|
case Type::PT_FACE_DISTANCE:
|
||||||
case Type::PROJ_PT_DISTANCE:
|
case Type::PROJ_PT_DISTANCE:
|
||||||
case Type::LENGTH_RATIO:
|
case Type::LENGTH_RATIO:
|
||||||
|
case Type::ARC_ARC_LEN_RATIO:
|
||||||
|
case Type::ARC_LINE_LEN_RATIO:
|
||||||
case Type::LENGTH_DIFFERENCE:
|
case Type::LENGTH_DIFFERENCE:
|
||||||
|
case Type::ARC_ARC_DIFFERENCE:
|
||||||
|
case Type::ARC_LINE_DIFFERENCE:
|
||||||
case Type::DIAMETER:
|
case Type::DIAMETER:
|
||||||
case Type::ANGLE:
|
case Type::ANGLE:
|
||||||
return true;
|
return true;
|
||||||
|
@ -1252,3 +1374,7 @@ bool Constraint::HasLabel() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Constraint::ShouldDrawExploded() const {
|
||||||
|
return SK.GetGroup(group)->ShouldDrawExploded();
|
||||||
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ void Entity::GetReferencePoints(std::vector<Vector> *refs) {
|
||||||
case Type::POINT_N_ROT_AXIS_TRANS:
|
case Type::POINT_N_ROT_AXIS_TRANS:
|
||||||
case Type::POINT_IN_3D:
|
case Type::POINT_IN_3D:
|
||||||
case Type::POINT_IN_2D:
|
case Type::POINT_IN_2D:
|
||||||
refs->push_back(PointGetNum());
|
refs->push_back(PointGetDrawNum());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Type::NORMAL_N_COPY:
|
case Type::NORMAL_N_COPY:
|
||||||
|
@ -103,12 +103,12 @@ void Entity::GetReferencePoints(std::vector<Vector> *refs) {
|
||||||
case Type::CUBIC_PERIODIC:
|
case Type::CUBIC_PERIODIC:
|
||||||
case Type::TTF_TEXT:
|
case Type::TTF_TEXT:
|
||||||
case Type::IMAGE:
|
case Type::IMAGE:
|
||||||
refs->push_back(SK.GetEntity(point[0])->PointGetNum());
|
refs->push_back(SK.GetEntity(point[0])->PointGetDrawNum());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Type::LINE_SEGMENT: {
|
case Type::LINE_SEGMENT: {
|
||||||
Vector a = SK.GetEntity(point[0])->PointGetNum(),
|
Vector a = SK.GetEntity(point[0])->PointGetDrawNum(),
|
||||||
b = SK.GetEntity(point[1])->PointGetNum();
|
b = SK.GetEntity(point[1])->PointGetDrawNum();
|
||||||
refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5)));
|
refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -180,19 +180,43 @@ bool Entity::IsVisible() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool PtCanDrag(hEntity pt) {
|
||||||
|
Entity* p = SK.GetEntity(pt);
|
||||||
|
// a numeric copy can not move
|
||||||
|
if(p->type == Entity::Type::POINT_N_COPY) return false;
|
||||||
|
// these transforms applied zero times can not be moved
|
||||||
|
if(((p->type == Entity::Type::POINT_N_TRANS) ||
|
||||||
|
(p->type == Entity::Type::POINT_N_ROT_AA) ||
|
||||||
|
(p->type == Entity::Type::POINT_N_ROT_AXIS_TRANS))
|
||||||
|
&& (p->timesApplied == 0)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// entities that were created via some copy types will not be
|
// entities that were created via some copy types will not be
|
||||||
// draggable with the mouse. We identify the undraggables here
|
// draggable with the mouse. We identify the undraggables here
|
||||||
bool Entity::CanBeDragged() const {
|
bool Entity::CanBeDragged() const {
|
||||||
// a numeric copy can not move
|
if(IsPoint()) {
|
||||||
if(type == Entity::Type::POINT_N_COPY) return false;
|
if(!PtCanDrag(h))
|
||||||
// these transforms applied zero times can not be moved
|
return false;
|
||||||
if(((type == Entity::Type::POINT_N_TRANS) ||
|
// are we constrained pt-on-point from a previous group?
|
||||||
(type == Entity::Type::POINT_N_ROT_AA) ||
|
for(const Constraint &cc : SK.constraint) {
|
||||||
(type == Entity::Type::POINT_N_ROT_AXIS_TRANS))
|
if(cc.group == group && cc.type == ConstraintBase::Type::POINTS_COINCIDENT) {
|
||||||
&& (timesApplied == 0)) return false;
|
if(cc.ptA == h) {
|
||||||
|
if((SK.GetEntity(cc.ptB)->group < group)
|
||||||
|
|| (!PtCanDrag(cc.ptB)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(cc.ptB == h) {
|
||||||
|
if((SK.GetEntity(cc.ptA)->group < group)
|
||||||
|
|| (!PtCanDrag(cc.ptA)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// for these types of entities the first point will indicate draggability
|
// for these types of entities the first point will indicate draggability
|
||||||
if(HasEndpoints() || type == Entity::Type::CIRCLE) {
|
if(HasEndpoints() || type == Entity::Type::CIRCLE) {
|
||||||
return SK.GetEntity(point[0])->CanBeDragged();
|
return PtCanDrag(point[0]);
|
||||||
}
|
}
|
||||||
// if we're not certain it can't be dragged then default to true
|
// if we're not certain it can't be dragged then default to true
|
||||||
return true;
|
return true;
|
||||||
|
@ -451,7 +475,8 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
|
||||||
Vector v = topLeft.Minus(botLeft);
|
Vector v = topLeft.Minus(botLeft);
|
||||||
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());
|
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());
|
||||||
|
|
||||||
SS.fonts.PlotString(font, str, sbl, botLeft, u, v);
|
// `extraPoints` is storing kerning boolean
|
||||||
|
SS.fonts.PlotString(font, str, sbl, extraPoints, botLeft, u, v);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,6 +491,26 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Entity::ShouldDrawExploded() const {
|
||||||
|
return SK.GetGroup(group)->ShouldDrawExploded();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector Entity::ExplodeOffset() const {
|
||||||
|
if(ShouldDrawExploded() && workplane.v != 0) {
|
||||||
|
int requestIdx = SK.GetRequest(h.request())->groupRequestIndex;
|
||||||
|
double offset = SS.explodeDistance * (requestIdx + 1);
|
||||||
|
return SK.GetEntity(workplane)->Normal()->NormalN().ScaledBy(offset);
|
||||||
|
} else {
|
||||||
|
return Vector::From(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector Entity::PointGetDrawNum() const {
|
||||||
|
// As per EntityBase::PointGetNum but specifically for when drawing/rendering the point
|
||||||
|
// (and not when solving), so we can potentially draw it somewhere different
|
||||||
|
return PointGetNum().Plus(ExplodeOffset());
|
||||||
|
}
|
||||||
|
|
||||||
void Entity::Draw(DrawAs how, Canvas *canvas) {
|
void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&
|
if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&
|
||||||
!IsVisible()) return;
|
!IsVisible()) return;
|
||||||
|
@ -557,16 +602,17 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
pointStroke.unit = Canvas::Unit::PX;
|
pointStroke.unit = Canvas::Unit::PX;
|
||||||
Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke);
|
Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke);
|
||||||
|
|
||||||
|
Vector p = PointGetDrawNum();
|
||||||
if(free) {
|
if(free) {
|
||||||
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
|
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
|
||||||
analyzeStroke.width = 14.0;
|
analyzeStroke.width = 14.0;
|
||||||
analyzeStroke.layer = Canvas::Layer::FRONT;
|
analyzeStroke.layer = Canvas::Layer::FRONT;
|
||||||
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
|
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
|
||||||
|
|
||||||
canvas->DrawPoint(PointGetNum(), hcsAnalyze);
|
canvas->DrawPoint(p, hcsAnalyze);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas->DrawPoint(PointGetNum(), hcsPoint);
|
canvas->DrawPoint(p, hcsPoint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,7 +667,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
tail = camera.projRight.ScaledBy(w/s).Plus(
|
tail = camera.projRight.ScaledBy(w/s).Plus(
|
||||||
camera.projUp. ScaledBy(h/s)).Minus(camera.offset);
|
camera.projUp. ScaledBy(h/s)).Minus(camera.offset);
|
||||||
} else {
|
} else {
|
||||||
tail = SK.GetEntity(point[0])->PointGetNum();
|
tail = SK.GetEntity(point[0])->PointGetDrawNum();
|
||||||
}
|
}
|
||||||
tail = camera.AlignToPixelGrid(tail);
|
tail = camera.AlignToPixelGrid(tail);
|
||||||
|
|
||||||
|
@ -709,8 +755,32 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
case Type::TTF_TEXT: {
|
case Type::TTF_TEXT: {
|
||||||
// Generate the rational polynomial curves, then piecewise linearize
|
// Generate the rational polynomial curves, then piecewise linearize
|
||||||
// them, and display those.
|
// them, and display those.
|
||||||
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) {
|
// Calculating the draw offset, if necessary.
|
||||||
canvas->DrawEdges(*GetOrGenerateEdges(), hcs);
|
const bool shouldExplode = ShouldDrawExploded();
|
||||||
|
Vector explodeOffset;
|
||||||
|
SBezierList offsetBeziers = {};
|
||||||
|
SBezierList *beziers = GetOrGenerateBezierCurves();
|
||||||
|
if(shouldExplode) {
|
||||||
|
explodeOffset = ExplodeOffset();
|
||||||
|
for(const SBezier& b : beziers->l) {
|
||||||
|
SBezier offset = b.TransformedBy(explodeOffset, Quaternion::IDENTITY, 1.0);
|
||||||
|
offsetBeziers.l.Add(&offset);
|
||||||
|
}
|
||||||
|
beziers = &offsetBeziers;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEdgeList *edges = nullptr;
|
||||||
|
SEdgeList offsetEdges = {};
|
||||||
|
|
||||||
|
if(!canvas->DrawBeziers(*beziers, hcs)) {
|
||||||
|
edges = GetOrGenerateEdges();
|
||||||
|
if(shouldExplode) {
|
||||||
|
for(const SEdge &e : edges->l) {
|
||||||
|
offsetEdges.AddEdge(e.a.Plus(explodeOffset), e.b.Plus(explodeOffset), e.auxA, e.auxB, e.tag);
|
||||||
|
}
|
||||||
|
edges = &offsetEdges;
|
||||||
|
}
|
||||||
|
canvas->DrawEdges(*edges, hcs);
|
||||||
}
|
}
|
||||||
if(type == Type::CIRCLE) {
|
if(type == Type::CIRCLE) {
|
||||||
Entity *dist = SK.GetEntity(distance);
|
Entity *dist = SK.GetEntity(distance);
|
||||||
|
@ -720,12 +790,14 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
|
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
|
||||||
analyzeStroke.layer = Canvas::Layer::FRONT;
|
analyzeStroke.layer = Canvas::Layer::FRONT;
|
||||||
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
|
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
|
||||||
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcsAnalyze)) {
|
if(!canvas->DrawBeziers(*beziers, hcsAnalyze)) {
|
||||||
canvas->DrawEdges(*GetOrGenerateEdges(), hcsAnalyze);
|
canvas->DrawEdges(*edges, hcsAnalyze);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
offsetBeziers.Clear();
|
||||||
|
offsetEdges.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case Type::IMAGE: {
|
case Type::IMAGE: {
|
||||||
|
@ -757,7 +829,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
Canvas::hFill hf = canvas->GetFill(fill);
|
Canvas::hFill hf = canvas->GetFill(fill);
|
||||||
Vector v[4] = {};
|
Vector v[4] = {};
|
||||||
for(int i = 0; i < 4; i++) {
|
for(int i = 0; i < 4; i++) {
|
||||||
v[i] = SK.GetEntity(point[i])->PointGetNum();
|
v[i] = SK.GetEntity(point[i])->PointGetDrawNum();
|
||||||
}
|
}
|
||||||
Vector iu = v[3].Minus(v[0]);
|
Vector iu = v[3].Minus(v[0]);
|
||||||
Vector iv = v[1].Minus(v[0]);
|
Vector iv = v[1].Minus(v[0]);
|
||||||
|
|
|
@ -26,6 +26,9 @@ bool EntityBase::HasVector() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
|
ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
|
||||||
|
if(IsFace()) {
|
||||||
|
return FaceGetNormalExprs();
|
||||||
|
}
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::LINE_SEGMENT:
|
case Type::LINE_SEGMENT:
|
||||||
return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus(
|
return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus(
|
||||||
|
@ -62,6 +65,9 @@ ExprVector EntityBase::VectorGetExprs() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EntityBase::VectorGetNum() const {
|
Vector EntityBase::VectorGetNum() const {
|
||||||
|
if(IsFace()) {
|
||||||
|
return FaceGetNormalNum();
|
||||||
|
}
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::LINE_SEGMENT:
|
case Type::LINE_SEGMENT:
|
||||||
return (SK.GetEntity(point[0])->PointGetNum()).Minus(
|
return (SK.GetEntity(point[0])->PointGetNum()).Minus(
|
||||||
|
@ -79,6 +85,9 @@ Vector EntityBase::VectorGetNum() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EntityBase::VectorGetRefPoint() const {
|
Vector EntityBase::VectorGetRefPoint() const {
|
||||||
|
if(IsFace()) {
|
||||||
|
return FaceGetPointNum();
|
||||||
|
}
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::LINE_SEGMENT:
|
case Type::LINE_SEGMENT:
|
||||||
return ((SK.GetEntity(point[0])->PointGetNum()).Plus(
|
return ((SK.GetEntity(point[0])->PointGetNum()).Plus(
|
||||||
|
|
|
@ -227,7 +227,7 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SS.GW.showConstraints) {
|
if(SS.GW.showConstraints != GraphicsWindow::ShowConstraintMode::SCM_NOSHOW ) {
|
||||||
if(!out->OutputConstraints(&SK.constraint)) {
|
if(!out->OutputConstraints(&SK.constraint)) {
|
||||||
GetEdgesCanvas canvas = {};
|
GetEdgesCanvas canvas = {};
|
||||||
canvas.camera = SS.GW.GetCamera();
|
canvas.camera = SS.GW.GetCamera();
|
||||||
|
|
|
@ -1308,9 +1308,9 @@ void GCodeFileWriter::FinishAndCloseFile() {
|
||||||
SS.MmToString(pt->p.x).c_str(), SS.MmToString(pt->p.y).c_str(),
|
SS.MmToString(pt->p.x).c_str(), SS.MmToString(pt->p.y).c_str(),
|
||||||
SS.MmToString(SS.gCode.feed).c_str());
|
SS.MmToString(SS.gCode.feed).c_str());
|
||||||
}
|
}
|
||||||
// Move up to a clearance plane 5mm above the work.
|
// Move up to a clearance plane above the work.
|
||||||
fprintf(f, "G00 Z%s\r\n",
|
fprintf(f, "G00 Z%s\r\n",
|
||||||
SS.MmToString(SS.gCode.depth < 0 ? +5 : -5).c_str());
|
SS.MmToString(SS.gCode.safeHeight).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
src/expr.cpp
28
src/expr.cpp
|
@ -400,15 +400,24 @@ Expr *Expr::PartialWrt(hParam p) const {
|
||||||
ssassert(false, "Unexpected operation");
|
ssassert(false, "Unexpected operation");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Expr::ParamsUsed() const {
|
void Expr::ParamsUsedList(std::vector<hParam> *list) const {
|
||||||
uint64_t r = 0;
|
if(op == Op::PARAM || op == Op::PARAM_PTR) {
|
||||||
if(op == Op::PARAM) r |= ((uint64_t)1 << (parh.v % 61));
|
// leaf: just add ourselves if we aren't already there
|
||||||
if(op == Op::PARAM_PTR) r |= ((uint64_t)1 << (parp->h.v % 61));
|
hParam param = (op == Op::PARAM) ? parh : parp->h;
|
||||||
|
if(list->end() != std::find_if(list->begin(), list->end(),
|
||||||
|
[=](const hParam &p) { return p.v == param.v; })) {
|
||||||
|
// We found ourselves in the list already, early out.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list->push_back(param);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int c = Children();
|
int c = Children();
|
||||||
if(c >= 1) r |= a->ParamsUsed();
|
if(c >= 1) {
|
||||||
if(c >= 2) r |= b->ParamsUsed();
|
a->ParamsUsedList(list);
|
||||||
return r;
|
if(c >= 2) b->ParamsUsedList(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Expr::DependsOn(hParam p) const {
|
bool Expr::DependsOn(hParam p) const {
|
||||||
|
@ -424,6 +433,11 @@ bool Expr::DependsOn(hParam p) const {
|
||||||
bool Expr::Tol(double a, double b) {
|
bool Expr::Tol(double a, double b) {
|
||||||
return fabs(a - b) < 0.001;
|
return fabs(a - b) < 0.001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Expr::IsZeroConst() const {
|
||||||
|
return op == Op::CONSTANT && EXACT(v == 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
Expr *Expr::FoldConstants() {
|
Expr *Expr::FoldConstants() {
|
||||||
Expr *n = AllocExpr();
|
Expr *n = AllocExpr();
|
||||||
*n = *this;
|
*n = *this;
|
||||||
|
|
|
@ -70,9 +70,10 @@ public:
|
||||||
|
|
||||||
Expr *PartialWrt(hParam p) const;
|
Expr *PartialWrt(hParam p) const;
|
||||||
double Eval() const;
|
double Eval() const;
|
||||||
uint64_t ParamsUsed() const;
|
void ParamsUsedList(std::vector<hParam> *list) const;
|
||||||
bool DependsOn(hParam p) const;
|
bool DependsOn(hParam p) const;
|
||||||
static bool Tol(double a, double b);
|
static bool Tol(double a, double b);
|
||||||
|
bool IsZeroConst() const;
|
||||||
Expr *FoldConstants();
|
Expr *FoldConstants();
|
||||||
void Substitute(hParam oldh, hParam newh);
|
void Substitute(hParam oldh, hParam newh);
|
||||||
|
|
||||||
|
|
|
@ -711,6 +711,8 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityLi
|
||||||
{
|
{
|
||||||
if(strcmp(filename.Extension().c_str(), "emn")==0) {
|
if(strcmp(filename.Extension().c_str(), "emn")==0) {
|
||||||
return LinkIDF(filename, le, m, sh);
|
return LinkIDF(filename, le, m, sh);
|
||||||
|
} else if(strcmp(filename.Extension().c_str(), "stl")==0) {
|
||||||
|
return LinkStl(filename, le, m, sh);
|
||||||
} else {
|
} else {
|
||||||
return LoadEntitiesFromSlvs(filename, le, m, sh);
|
return LoadEntitiesFromSlvs(filename, le, m, sh);
|
||||||
}
|
}
|
||||||
|
@ -907,6 +909,8 @@ try_again:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(g.IsTriangleMeshAssembly())
|
||||||
|
g.forceToMesh = true;
|
||||||
} else if(linkMap.count(g.linkFile) == 0) {
|
} else if(linkMap.count(g.linkFile) == 0) {
|
||||||
dbp("Missing file for group: %s", g.name.c_str());
|
dbp("Missing file for group: %s", g.name.c_str());
|
||||||
// The file was moved; prompt the user for its new location.
|
// The file was moved; prompt the user for its new location.
|
||||||
|
@ -914,7 +918,7 @@ try_again:
|
||||||
switch(LocateImportedFile(linkFileRelative, canCancel)) {
|
switch(LocateImportedFile(linkFileRelative, canCancel)) {
|
||||||
case Platform::MessageDialog::Response::YES: {
|
case Platform::MessageDialog::Response::YES: {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
dialog->AddFilters(Platform::SolveSpaceLinkFileFilters);
|
||||||
dialog->ThawChoices(settings, "LinkSketch");
|
dialog->ThawChoices(settings, "LinkSketch");
|
||||||
dialog->SuggestFilename(linkFileRelative);
|
dialog->SuggestFilename(linkFileRelative);
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
|
|
|
@ -224,9 +224,11 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
|
||||||
if(PruneGroups(hg))
|
if(PruneGroups(hg))
|
||||||
goto pruned;
|
goto pruned;
|
||||||
|
|
||||||
|
int groupRequestIndex = 0;
|
||||||
for(auto &req : SK.request) {
|
for(auto &req : SK.request) {
|
||||||
Request *r = &req;
|
Request *r = &req;
|
||||||
if(r->group != hg) continue;
|
if(r->group != hg) continue;
|
||||||
|
r->groupRequestIndex = groupRequestIndex++;
|
||||||
|
|
||||||
r->Generate(&(SK.entity), &(SK.param));
|
r->Generate(&(SK.entity), &(SK.param));
|
||||||
}
|
}
|
||||||
|
@ -548,8 +550,11 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg, int *rank) {
|
SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg, int *rank) {
|
||||||
WriteEqSystemForGroup(hg);
|
|
||||||
Group *g = SK.GetGroup(hg);
|
Group *g = SK.GetGroup(hg);
|
||||||
|
// If we don't calculate dof or redundant is allowed, there is
|
||||||
|
// no point to solve rank because this result is not meaningful
|
||||||
|
if(g->suppressDofCalculation || g->allowRedundant) return SolveResult::OKAY;
|
||||||
|
WriteEqSystemForGroup(hg);
|
||||||
SolveResult result = sys.SolveRank(g, rank);
|
SolveResult result = sys.SolveRank(g, rank);
|
||||||
FreeAllTemporary();
|
FreeAllTemporary();
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -43,7 +43,7 @@ const MenuEntry Menu[] = {
|
||||||
{ 1, N_("&Open..."), Command::OPEN, C|'o', KN, mFile },
|
{ 1, N_("&Open..."), Command::OPEN, C|'o', KN, mFile },
|
||||||
{ 1, N_("Open &Recent"), Command::OPEN_RECENT, 0, KN, mFile },
|
{ 1, N_("Open &Recent"), Command::OPEN_RECENT, 0, KN, mFile },
|
||||||
{ 1, N_("&Save"), Command::SAVE, C|'s', KN, mFile },
|
{ 1, N_("&Save"), Command::SAVE, C|'s', KN, mFile },
|
||||||
{ 1, N_("Save &As..."), Command::SAVE_AS, 0, KN, mFile },
|
{ 1, N_("Save &As..."), Command::SAVE_AS, C|S|'s', KN, mFile },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("Export &Image..."), Command::EXPORT_IMAGE, 0, KN, mFile },
|
{ 1, N_("Export &Image..."), Command::EXPORT_IMAGE, 0, KN, mFile },
|
||||||
{ 1, N_("Export 2d &View..."), Command::EXPORT_VIEW, 0, KN, mFile },
|
{ 1, N_("Export 2d &View..."), Command::EXPORT_VIEW, 0, KN, mFile },
|
||||||
|
@ -94,12 +94,14 @@ const MenuEntry Menu[] = {
|
||||||
{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView },
|
{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView },
|
||||||
{ 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView },
|
{ 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView },
|
||||||
{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView },
|
{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView },
|
||||||
|
{ 1, N_("Show E&xploded View"), Command::EXPLODE_SKETCH, '\\', KC, mView },
|
||||||
{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL },
|
{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL },
|
||||||
{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView },
|
{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView },
|
||||||
{ 2, N_("Dimensions in M&eters"), Command::UNITS_METERS, 0, KR, mView },
|
{ 2, N_("Dimensions in M&eters"), Command::UNITS_METERS, 0, KR, mView },
|
||||||
{ 2, N_("Dimensions in &Inches"), Command::UNITS_INCHES, 0, KR, mView },
|
{ 2, N_("Dimensions in &Inches"), Command::UNITS_INCHES, 0, KR, mView },
|
||||||
|
{ 2, N_("Dimensions in &Feet and Inches"), Command::UNITS_FEET_INCHES, 0, KR, mView },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("Show &Toolbar"), Command::SHOW_TOOLBAR, 0, KC, mView },
|
{ 1, N_("Show &Toolbar"), Command::SHOW_TOOLBAR, C|'\t', KC, mView },
|
||||||
{ 1, N_("Show Property Bro&wser"), Command::SHOW_TEXT_WND, '\t', KC, mView },
|
{ 1, N_("Show Property Bro&wser"), Command::SHOW_TEXT_WND, '\t', KC, mView },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("&Full Screen"), Command::FULL_SCREEN, C|F|11, KC, mView },
|
{ 1, N_("&Full Screen"), Command::FULL_SCREEN, C|F|11, KC, mView },
|
||||||
|
@ -124,7 +126,7 @@ const MenuEntry Menu[] = {
|
||||||
{ 1, N_("Anywhere In &3d"), Command::FREE_IN_3D, '3', KR, mReq },
|
{ 1, N_("Anywhere In &3d"), Command::FREE_IN_3D, '3', KR, mReq },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("Datum &Point"), Command::DATUM_POINT, 'p', KN, mReq },
|
{ 1, N_("Datum &Point"), Command::DATUM_POINT, 'p', KN, mReq },
|
||||||
{ 1, N_("&Workplane"), Command::WORKPLANE, 0, KN, mReq },
|
{ 1, N_("Wor&kplane"), Command::WORKPLANE, 0, KN, mReq },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("Line &Segment"), Command::LINE_SEGMENT, 's', KN, mReq },
|
{ 1, N_("Line &Segment"), Command::LINE_SEGMENT, 's', KN, mReq },
|
||||||
{ 1, N_("C&onstruction Line Segment"), Command::CONSTR_SEGMENT, S|'s', KN, mReq },
|
{ 1, N_("C&onstruction Line Segment"), Command::CONSTR_SEGMENT, S|'s', KN, mReq },
|
||||||
|
@ -134,16 +136,16 @@ const MenuEntry Menu[] = {
|
||||||
{ 1, N_("&Bezier Cubic Spline"), Command::CUBIC, 'b', KN, mReq },
|
{ 1, N_("&Bezier Cubic Spline"), Command::CUBIC, 'b', KN, mReq },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("&Text in TrueType Font"), Command::TTF_TEXT, 't', KN, mReq },
|
{ 1, N_("&Text in TrueType Font"), Command::TTF_TEXT, 't', KN, mReq },
|
||||||
{ 1, N_("&Image"), Command::IMAGE, 0, KN, mReq },
|
{ 1, N_("I&mage"), Command::IMAGE, 0, KN, mReq },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("To&ggle Construction"), Command::CONSTRUCTION, 'g', KN, mReq },
|
{ 1, N_("To&ggle Construction"), Command::CONSTRUCTION, 'g', KN, mReq },
|
||||||
{ 1, N_("Tangent &Arc at Point"), Command::TANGENT_ARC, S|'a', KN, mReq },
|
{ 1, N_("Ta&ngent Arc at Point"), Command::TANGENT_ARC, S|'a', KN, mReq },
|
||||||
{ 1, N_("Split Curves at &Intersection"), Command::SPLIT_CURVES, 'i', KN, mReq },
|
{ 1, N_("Split Curves at &Intersection"), Command::SPLIT_CURVES, 'i', KN, mReq },
|
||||||
|
|
||||||
{ 0, N_("&Constrain"), Command::NONE, 0, KN, mCon },
|
{ 0, N_("&Constrain"), Command::NONE, 0, KN, mCon },
|
||||||
{ 1, N_("&Distance / Diameter"), Command::DISTANCE_DIA, 'd', KN, mCon },
|
{ 1, N_("&Distance / Diameter"), Command::DISTANCE_DIA, 'd', KN, mCon },
|
||||||
{ 1, N_("Re&ference Dimension"), Command::REF_DISTANCE, S|'d', KN, mCon },
|
{ 1, N_("Re&ference Dimension"), Command::REF_DISTANCE, S|'d', KN, mCon },
|
||||||
{ 1, N_("A&ngle"), Command::ANGLE, 'n', KN, mCon },
|
{ 1, N_("A&ngle / Equal Angle"), Command::ANGLE, 'n', KN, mCon },
|
||||||
{ 1, N_("Reference An&gle"), Command::REF_ANGLE, S|'n', KN, mCon },
|
{ 1, N_("Reference An&gle"), Command::REF_ANGLE, S|'n', KN, mCon },
|
||||||
{ 1, N_("Other S&upplementary Angle"), Command::OTHER_ANGLE, 'u', KN, mCon },
|
{ 1, N_("Other S&upplementary Angle"), Command::OTHER_ANGLE, 'u', KN, mCon },
|
||||||
{ 1, N_("Toggle R&eference Dim"), Command::REFERENCE, 'e', KN, mCon },
|
{ 1, N_("Toggle R&eference Dim"), Command::REFERENCE, 'e', KN, mCon },
|
||||||
|
@ -152,9 +154,9 @@ const MenuEntry Menu[] = {
|
||||||
{ 1, N_("&Vertical"), Command::VERTICAL, 'v', KN, mCon },
|
{ 1, N_("&Vertical"), Command::VERTICAL, 'v', KN, mCon },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("&On Point / Curve / Plane"), Command::ON_ENTITY, 'o', KN, mCon },
|
{ 1, N_("&On Point / Curve / Plane"), Command::ON_ENTITY, 'o', KN, mCon },
|
||||||
{ 1, N_("E&qual Length / Radius / Angle"), Command::EQUAL, 'q', KN, mCon },
|
{ 1, N_("E&qual Length / Radius"), Command::EQUAL, 'q', KN, mCon },
|
||||||
{ 1, N_("Length Ra&tio"), Command::RATIO, 'z', KN, mCon },
|
{ 1, N_("Length / Arc Ra&tio"), Command::RATIO, 'z', KN, mCon },
|
||||||
{ 1, N_("Length Diff&erence"), Command::DIFFERENCE, 'j', KN, mCon },
|
{ 1, N_("Length / Arc Diff&erence"), Command::DIFFERENCE, 'j', KN, mCon },
|
||||||
{ 1, N_("At &Midpoint"), Command::AT_MIDPOINT, 'm', KN, mCon },
|
{ 1, N_("At &Midpoint"), Command::AT_MIDPOINT, 'm', KN, mCon },
|
||||||
{ 1, N_("S&ymmetric"), Command::SYMMETRIC, 'y', KN, mCon },
|
{ 1, N_("S&ymmetric"), Command::SYMMETRIC, 'y', KN, mCon },
|
||||||
{ 1, N_("Para&llel / Tangent"), Command::PARALLEL, 'l', KN, mCon },
|
{ 1, N_("Para&llel / Tangent"), Command::PARALLEL, 'l', KN, mCon },
|
||||||
|
@ -181,6 +183,7 @@ const MenuEntry Menu[] = {
|
||||||
{ 0, N_("&Help"), Command::NONE, 0, KN, mHelp },
|
{ 0, N_("&Help"), Command::NONE, 0, KN, mHelp },
|
||||||
{ 1, N_("&Language"), Command::LOCALE, 0, KN, mHelp },
|
{ 1, N_("&Language"), Command::LOCALE, 0, KN, mHelp },
|
||||||
{ 1, N_("&Website / Manual"), Command::WEBSITE, 0, KN, mHelp },
|
{ 1, N_("&Website / Manual"), Command::WEBSITE, 0, KN, mHelp },
|
||||||
|
{ 1, N_("&Go to GitHub commit"), Command::GITHUB, 0, KN, mHelp },
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
{ 1, N_("&About"), Command::ABOUT, 0, KN, mHelp },
|
{ 1, N_("&About"), Command::ABOUT, 0, KN, mHelp },
|
||||||
#endif
|
#endif
|
||||||
|
@ -297,7 +300,7 @@ void GraphicsWindow::PopulateMainMenu() {
|
||||||
|
|
||||||
SS.UpdateWindowTitles();
|
SS.UpdateWindowTitles();
|
||||||
PopulateMainMenu();
|
PopulateMainMenu();
|
||||||
EnsureValidActives();
|
SS.GW.EnsureValidActives();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if(Menu[i].fn == NULL) {
|
} else if(Menu[i].fn == NULL) {
|
||||||
|
@ -317,6 +320,8 @@ void GraphicsWindow::PopulateMainMenu() {
|
||||||
dimSolidModelMenuItem = menuItem;
|
dimSolidModelMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
|
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
|
||||||
perspectiveProjMenuItem = menuItem;
|
perspectiveProjMenuItem = menuItem;
|
||||||
|
} else if(Menu[i].cmd == Command::EXPLODE_SKETCH) {
|
||||||
|
explodeMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
|
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
|
||||||
showToolbarMenuItem = menuItem;
|
showToolbarMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::SHOW_TEXT_WND) {
|
} else if(Menu[i].cmd == Command::SHOW_TEXT_WND) {
|
||||||
|
@ -329,6 +334,8 @@ void GraphicsWindow::PopulateMainMenu() {
|
||||||
unitsMetersMenuItem = menuItem;
|
unitsMetersMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::UNITS_INCHES) {
|
} else if(Menu[i].cmd == Command::UNITS_INCHES) {
|
||||||
unitsInchesMenuItem = menuItem;
|
unitsInchesMenuItem = menuItem;
|
||||||
|
} else if(Menu[i].cmd == Command::UNITS_FEET_INCHES) {
|
||||||
|
unitsFeetInchesMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::SEL_WORKPLANE) {
|
} else if(Menu[i].cmd == Command::SEL_WORKPLANE) {
|
||||||
inWorkplaneMenuItem = menuItem;
|
inWorkplaneMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::FREE_IN_3D) {
|
} else if(Menu[i].cmd == Command::FREE_IN_3D) {
|
||||||
|
@ -370,7 +377,7 @@ static void PopulateMenuWithPathnames(Platform::MenuRef menu,
|
||||||
void GraphicsWindow::PopulateRecentFiles() {
|
void GraphicsWindow::PopulateRecentFiles() {
|
||||||
PopulateMenuWithPathnames(openRecentMenu, SS.recentFiles, [](const Platform::Path &path) {
|
PopulateMenuWithPathnames(openRecentMenu, SS.recentFiles, [](const Platform::Path &path) {
|
||||||
// OkayToStartNewFile could mutate recentFiles, which will invalidate path (which is a
|
// OkayToStartNewFile could mutate recentFiles, which will invalidate path (which is a
|
||||||
// refererence into the recentFiles vector), so take a copy of it here.
|
// reference into the recentFiles vector), so take a copy of it here.
|
||||||
Platform::Path pathCopy(path);
|
Platform::Path pathCopy(path);
|
||||||
if(!SS.OkayToStartNewFile()) return;
|
if(!SS.OkayToStartNewFile()) return;
|
||||||
SS.Load(pathCopy);
|
SS.Load(pathCopy);
|
||||||
|
@ -402,11 +409,13 @@ void GraphicsWindow::Init() {
|
||||||
showNormals = true;
|
showNormals = true;
|
||||||
showPoints = true;
|
showPoints = true;
|
||||||
showConstruction = true;
|
showConstruction = true;
|
||||||
showConstraints = true;
|
showConstraints = ShowConstraintMode::SCM_SHOW_ALL;
|
||||||
showShaded = true;
|
showShaded = true;
|
||||||
showEdges = true;
|
showEdges = true;
|
||||||
showMesh = false;
|
showMesh = false;
|
||||||
showOutlines = false;
|
showOutlines = false;
|
||||||
|
showFacesDrawing = false;
|
||||||
|
showFacesNonDrawing = true;
|
||||||
drawOccludedAs = DrawOccludedAs::INVISIBLE;
|
drawOccludedAs = DrawOccludedAs::INVISIBLE;
|
||||||
|
|
||||||
showTextWindow = true;
|
showTextWindow = true;
|
||||||
|
@ -422,8 +431,13 @@ void GraphicsWindow::Init() {
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
// Do this first, so that if it causes an onRender event we don't try to paint without
|
// Do this first, so that if it causes an onRender event we don't try to paint without
|
||||||
// a canvas.
|
// a canvas.
|
||||||
window->SetMinContentSize(720, 670);
|
window->SetMinContentSize(720, /*ToolbarDrawOrHitTest 636*/ 32 * 18 + 3 * 16 + 8 + 4);
|
||||||
window->onClose = std::bind(&SolveSpaceUI::MenuFile, Command::EXIT);
|
window->onClose = std::bind(&SolveSpaceUI::MenuFile, Command::EXIT);
|
||||||
|
window->onContextLost = [&] {
|
||||||
|
canvas = NULL;
|
||||||
|
persistentCanvas = NULL;
|
||||||
|
persistentDirty = true;
|
||||||
|
};
|
||||||
window->onRender = std::bind(&GraphicsWindow::Paint, this);
|
window->onRender = std::bind(&GraphicsWindow::Paint, this);
|
||||||
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
|
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
|
||||||
window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1);
|
window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1);
|
||||||
|
@ -490,11 +504,11 @@ void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) {
|
||||||
// Animate transition, unless it's a tiny move.
|
// Animate transition, unless it's a tiny move.
|
||||||
int64_t t0 = GetMilliseconds();
|
int64_t t0 = GetMilliseconds();
|
||||||
int32_t dt = (mp < 0.01 && mo < 10) ? (-20) :
|
int32_t dt = (mp < 0.01 && mo < 10) ? (-20) :
|
||||||
(int32_t)(100 + 1000*mp + 0.4*mo);
|
(int32_t)(100 + 600*mp + 0.4*mo);
|
||||||
// Don't ever animate for longer than 2000 ms; we can get absurdly
|
// Don't ever animate for longer than 800 ms; we can get absurdly
|
||||||
// long translations (as measured in pixels) if the user zooms out, moves,
|
// long translations (as measured in pixels) if the user zooms out, moves,
|
||||||
// and then zooms in again.
|
// and then zooms in again.
|
||||||
if(dt > 2000) dt = 2000;
|
if(dt > 800) dt = 800;
|
||||||
Quaternion dq = quatf.Times(quat0.Inverse());
|
Quaternion dq = quatf.Times(quat0.Inverse());
|
||||||
|
|
||||||
if(!animateTimer) {
|
if(!animateTimer) {
|
||||||
|
@ -703,16 +717,47 @@ double GraphicsWindow::ZoomToFit(const Camera &camera,
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GraphicsWindow::ZoomToMouse(double zoomMultiplyer) {
|
||||||
|
double offsetRight = offset.Dot(projRight);
|
||||||
|
double offsetUp = offset.Dot(projUp);
|
||||||
|
|
||||||
|
double width, height;
|
||||||
|
window->GetContentSize(&width, &height);
|
||||||
|
|
||||||
|
double righti = currentMousePosition.x / scale - offsetRight;
|
||||||
|
double upi = currentMousePosition.y / scale - offsetUp;
|
||||||
|
|
||||||
|
// zoomMultiplyer of 1 gives a default zoom factor of 1.2x: zoomMultiplyer * 1.2
|
||||||
|
// zoom = adjusted zoom negative zoomMultiplyer will zoom out, positive will zoom in
|
||||||
|
//
|
||||||
|
|
||||||
|
scale *= exp(0.1823216 * zoomMultiplyer); // ln(1.2) = 0.1823216
|
||||||
|
|
||||||
|
double rightf = currentMousePosition.x / scale - offsetRight;
|
||||||
|
double upf = currentMousePosition.y / scale - offsetUp;
|
||||||
|
|
||||||
|
offset = offset.Plus(projRight.ScaledBy(rightf - righti));
|
||||||
|
offset = offset.Plus(projUp.ScaledBy(upf - upi));
|
||||||
|
|
||||||
|
if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) {
|
||||||
|
if(havePainted) {
|
||||||
|
SS.ScheduleShowTW();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
havePainted = false;
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void GraphicsWindow::MenuView(Command id) {
|
void GraphicsWindow::MenuView(Command id) {
|
||||||
switch(id) {
|
switch(id) {
|
||||||
case Command::ZOOM_IN:
|
case Command::ZOOM_IN:
|
||||||
SS.GW.scale *= 1.2;
|
SS.GW.ZoomToMouse(1);
|
||||||
SS.ScheduleShowTW();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::ZOOM_OUT:
|
case Command::ZOOM_OUT:
|
||||||
SS.GW.scale /= 1.2;
|
SS.GW.ZoomToMouse(-1);
|
||||||
SS.ScheduleShowTW();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::ZOOM_TO_FIT:
|
case Command::ZOOM_TO_FIT:
|
||||||
|
@ -748,6 +793,12 @@ void GraphicsWindow::MenuView(Command id) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Command::EXPLODE_SKETCH:
|
||||||
|
SS.explode = !SS.explode;
|
||||||
|
SS.GW.EnsureValidActives();
|
||||||
|
SS.MarkGroupDirty(SS.GW.activeGroup, true);
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::ONTO_WORKPLANE:
|
case Command::ONTO_WORKPLANE:
|
||||||
if(SS.GW.LockedInWorkplane()) {
|
if(SS.GW.LockedInWorkplane()) {
|
||||||
SS.GW.AnimateOntoWorkplane();
|
SS.GW.AnimateOntoWorkplane();
|
||||||
|
@ -766,13 +817,18 @@ void GraphicsWindow::MenuView(Command id) {
|
||||||
Quaternion quatf = quat0;
|
Quaternion quatf = quat0;
|
||||||
double dmin = 1e10;
|
double dmin = 1e10;
|
||||||
|
|
||||||
// There are 24 possible views; 3*2*2*2
|
// There are 24 possible views (3*2*2*2), if all are
|
||||||
int i, j, negi, negj;
|
// allowed. If the user is in turn-table mode, the
|
||||||
for(i = 0; i < 3; i++) {
|
// isometric view must have the z-axis facing up, leaving
|
||||||
for(j = 0; j < 3; j++) {
|
// 8 possible views (2*1*2*2).
|
||||||
|
|
||||||
|
bool require_turntable = (id==Command::NEAREST_ISO && SS.turntableNav);
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
for(int j = 0; j < 3; j++) {
|
||||||
if(i == j) continue;
|
if(i == j) continue;
|
||||||
for(negi = 0; negi < 2; negi++) {
|
if(require_turntable && (j!=2)) continue;
|
||||||
for(negj = 0; negj < 2; negj++) {
|
for(int negi = 0; negi < 2; negi++) {
|
||||||
|
for(int negj = 0; negj < 2; negj++) {
|
||||||
Vector ou = ortho[i], ov = ortho[j];
|
Vector ou = ortho[i], ov = ortho[j];
|
||||||
if(negi) ou = ou.ScaledBy(-1);
|
if(negi) ou = ou.ScaledBy(-1);
|
||||||
if(negj) ov = ov.ScaledBy(-1);
|
if(negj) ov = ov.ScaledBy(-1);
|
||||||
|
@ -841,6 +897,12 @@ void GraphicsWindow::MenuView(Command id) {
|
||||||
SS.GW.EnsureValidActives();
|
SS.GW.EnsureValidActives();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Command::UNITS_FEET_INCHES:
|
||||||
|
SS.viewUnits = Unit::FEET_INCHES;
|
||||||
|
SS.ScheduleShowTW();
|
||||||
|
SS.GW.EnsureValidActives();
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::UNITS_MM:
|
case Command::UNITS_MM:
|
||||||
SS.viewUnits = Unit::MM;
|
SS.viewUnits = Unit::MM;
|
||||||
SS.ScheduleShowTW();
|
SS.ScheduleShowTW();
|
||||||
|
@ -923,6 +985,7 @@ void GraphicsWindow::EnsureValidActives() {
|
||||||
case Unit::MM:
|
case Unit::MM:
|
||||||
case Unit::METERS:
|
case Unit::METERS:
|
||||||
case Unit::INCHES:
|
case Unit::INCHES:
|
||||||
|
case Unit::FEET_INCHES:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
SS.viewUnits = Unit::MM;
|
SS.viewUnits = Unit::MM;
|
||||||
|
@ -931,6 +994,7 @@ void GraphicsWindow::EnsureValidActives() {
|
||||||
unitsMmMenuItem->SetActive(SS.viewUnits == Unit::MM);
|
unitsMmMenuItem->SetActive(SS.viewUnits == Unit::MM);
|
||||||
unitsMetersMenuItem->SetActive(SS.viewUnits == Unit::METERS);
|
unitsMetersMenuItem->SetActive(SS.viewUnits == Unit::METERS);
|
||||||
unitsInchesMenuItem->SetActive(SS.viewUnits == Unit::INCHES);
|
unitsInchesMenuItem->SetActive(SS.viewUnits == Unit::INCHES);
|
||||||
|
unitsFeetInchesMenuItem->SetActive(SS.viewUnits == Unit::FEET_INCHES);
|
||||||
|
|
||||||
if(SS.TW.window) SS.TW.window->SetVisible(SS.GW.showTextWindow);
|
if(SS.TW.window) SS.TW.window->SetVisible(SS.GW.showTextWindow);
|
||||||
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
|
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
|
||||||
|
@ -938,6 +1002,7 @@ void GraphicsWindow::EnsureValidActives() {
|
||||||
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
|
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
|
||||||
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
|
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
|
||||||
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
|
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
|
||||||
|
explodeMenuItem->SetActive(SS.explode);
|
||||||
showToolbarMenuItem->SetActive(SS.showToolbar);
|
showToolbarMenuItem->SetActive(SS.showToolbar);
|
||||||
fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen());
|
fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen());
|
||||||
|
|
||||||
|
@ -1037,6 +1102,16 @@ void GraphicsWindow::MenuEdit(Command id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// some pending operations need an Undo to properly clean up on ESC
|
||||||
|
if ( (SS.GW.pending.operation == Pending::DRAGGING_NEW_POINT)
|
||||||
|
|| (SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT)
|
||||||
|
|| (SS.GW.pending.operation == Pending::DRAGGING_NEW_ARC_POINT)
|
||||||
|
|| (SS.GW.pending.operation == Pending::DRAGGING_NEW_CUBIC_POINT)
|
||||||
|
|| (SS.GW.pending.operation == Pending::DRAGGING_NEW_RADIUS) )
|
||||||
|
{
|
||||||
|
SS.GW.ClearSuper();
|
||||||
|
SS.UndoUndo();
|
||||||
|
}
|
||||||
SS.GW.ClearSuper();
|
SS.GW.ClearSuper();
|
||||||
SS.TW.HideEditControl();
|
SS.TW.HideEditControl();
|
||||||
SS.nakedEdges.Clear();
|
SS.nakedEdges.Clear();
|
||||||
|
@ -1366,6 +1441,14 @@ void GraphicsWindow::ToggleBool(bool *v) {
|
||||||
SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE);
|
SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(v == &showFaces) {
|
||||||
|
if(g->type == Group::Type::DRAWING_WORKPLANE || g->type == Group::Type::DRAWING_3D) {
|
||||||
|
showFacesDrawing = showFaces;
|
||||||
|
} else {
|
||||||
|
showFacesNonDrawing = showFaces;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Invalidate(/*clearPersistent=*/true);
|
Invalidate(/*clearPersistent=*/true);
|
||||||
SS.ScheduleShowTW();
|
SS.ScheduleShowTW();
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,14 +136,30 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
|
||||||
g.predef.negateV = wrkplg->predef.negateV;
|
g.predef.negateV = wrkplg->predef.negateV;
|
||||||
} else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
|
} else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
|
||||||
g.predef.q = wrkplg->predef.q;
|
g.predef.q = wrkplg->predef.q;
|
||||||
|
} else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
|
||||||
|
g.predef.q = wrkplg->predef.q;
|
||||||
|
g.predef.entityB = wrkplg->predef.entityB;
|
||||||
} else ssassert(false, "Unexpected workplane subtype");
|
} else ssassert(false, "Unexpected workplane subtype");
|
||||||
}
|
}
|
||||||
|
} else if(gs.anyNormals == 1 && gs.points == 1 && gs.n == 2) {
|
||||||
|
g.subtype = Subtype::WORKPLANE_BY_POINT_NORMAL;
|
||||||
|
g.predef.entityB = gs.anyNormal[0];
|
||||||
|
g.predef.q = SK.GetEntity(gs.anyNormal[0])->NormalGetNum();
|
||||||
|
g.predef.origin = gs.point[0];
|
||||||
|
//} else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) {
|
||||||
|
// g.subtype = Subtype::WORKPLANE_BY_POINT_FACE;
|
||||||
|
// g.predef.q = SK.GetEntity(gs.face[0])->NormalGetNum();
|
||||||
|
// g.predef.origin = gs.point[0];
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for new sketch in workplane. This "
|
Error(_("Bad selection for new sketch in workplane. This "
|
||||||
"group can be created with:\n\n"
|
"group can be created with:\n\n"
|
||||||
" * a point (through the point, orthogonal to coordinate axes)\n"
|
" * a point (through the point, orthogonal to coordinate axes)\n"
|
||||||
" * a point and two line segments (through the point, "
|
" * a point and two line segments (through the point, "
|
||||||
"parallel to the lines)\n"
|
"parallel to the lines)\n"
|
||||||
|
" * a point and a normal (through the point, "
|
||||||
|
"orthogonal to the normal)\n"
|
||||||
|
/*" * a point and a face (through the point, "
|
||||||
|
"parallel to the face)\n"*/
|
||||||
" * a workplane (copy of the workplane)\n"));
|
" * a workplane (copy of the workplane)\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -392,7 +408,13 @@ bool Group::IsForcedToMeshBySource() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Group::IsForcedToMesh() const {
|
bool Group::IsForcedToMesh() const {
|
||||||
return forceToMesh || IsForcedToMeshBySource();
|
return forceToMesh || IsTriangleMeshAssembly() || IsForcedToMeshBySource();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Group::IsTriangleMeshAssembly() const {
|
||||||
|
if (type != Type::LINKED) return false;
|
||||||
|
if (!impMesh.IsEmpty() && impShell.IsEmpty()) return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Group::DescriptionString() {
|
std::string Group::DescriptionString() {
|
||||||
|
@ -404,11 +426,10 @@ std::string Group::DescriptionString() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Group::Activate() {
|
void Group::Activate() {
|
||||||
if(type == Type::EXTRUDE || type == Type::LINKED || type == Type::LATHE ||
|
if(type == Type::DRAWING_WORKPLANE || type == Type::DRAWING_3D) {
|
||||||
type == Type::REVOLVE || type == Type::HELIX || type == Type::TRANSLATE || type == Type::ROTATE) {
|
SS.GW.showFaces = SS.GW.showFacesDrawing;
|
||||||
SS.GW.showFaces = true;
|
|
||||||
} else {
|
} else {
|
||||||
SS.GW.showFaces = false;
|
SS.GW.showFaces = SS.GW.showFacesNonDrawing;
|
||||||
}
|
}
|
||||||
SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
|
SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
|
||||||
SS.ScheduleShowTW();
|
SS.ScheduleShowTW();
|
||||||
|
@ -443,11 +464,14 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
|
||||||
} else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
|
} else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
|
||||||
// Already given, numerically.
|
// Already given, numerically.
|
||||||
q = predef.q;
|
q = predef.q;
|
||||||
|
} else if(subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
|
||||||
|
q = SK.GetEntity(predef.entityB)->NormalGetNum();
|
||||||
} else ssassert(false, "Unexpected workplane subtype");
|
} else ssassert(false, "Unexpected workplane subtype");
|
||||||
|
|
||||||
Entity normal = {};
|
Entity normal = {};
|
||||||
normal.type = Entity::Type::NORMAL_N_COPY;
|
normal.type = Entity::Type::NORMAL_N_COPY;
|
||||||
normal.numNormal = q;
|
normal.numNormal = q;
|
||||||
|
|
||||||
normal.point[0] = h.entity(2);
|
normal.point[0] = h.entity(2);
|
||||||
normal.group = h;
|
normal.group = h;
|
||||||
normal.h = h.entity(1);
|
normal.h = h.entity(1);
|
||||||
|
@ -800,6 +824,12 @@ void Group::GenerateEquations(IdList<Equation,hEquation> *l) {
|
||||||
AddEq(l, (EC(axis.z))->Minus(EP(6)), 5);
|
AddEq(l, (EC(axis.z))->Minus(EP(6)), 5);
|
||||||
#undef EC
|
#undef EC
|
||||||
#undef EP
|
#undef EP
|
||||||
|
if(type == Type::HELIX) {
|
||||||
|
if(valB != 0.0) {
|
||||||
|
AddEq(l, Expr::From(h.param(7))->Times(Expr::From(PI))->
|
||||||
|
Minus(Expr::From(h.param(3))->Times(Expr::From(valB))), 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if(type == Type::EXTRUDE) {
|
} else if(type == Type::EXTRUDE) {
|
||||||
if(predef.entityB != Entity::FREE_IN_3D) {
|
if(predef.entityB != Entity::FREE_IN_3D) {
|
||||||
// The extrusion path is locked along a line, normal to the
|
// The extrusion path is locked along a line, normal to the
|
||||||
|
@ -1150,6 +1180,11 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
if((Entity::Type::IMAGE == ep->type) && (true == ep->construction)) {
|
||||||
|
// Do not copy image entities if they are construction.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int i, points;
|
int i, points;
|
||||||
bool hasNormal, hasDistance;
|
bool hasNormal, hasDistance;
|
||||||
EntReqTable::GetEntityInfo(ep->type, ep->extraPoints,
|
EntReqTable::GetEntityInfo(ep->type, ep->extraPoints,
|
||||||
|
@ -1172,3 +1207,6 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
||||||
el->Add(&en);
|
el->Add(&en);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Group::ShouldDrawExploded() const {
|
||||||
|
return SS.explode && h == SS.GW.activeGroup && type == Type::DRAWING_WORKPLANE && !SS.exportMode;
|
||||||
|
}
|
||||||
|
|
|
@ -635,8 +635,11 @@ void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
|
||||||
std::vector<uint32_t> faces;
|
std::vector<uint32_t> faces;
|
||||||
SS.GW.GroupSelection();
|
SS.GW.GroupSelection();
|
||||||
auto const &gs = SS.GW.gs;
|
auto const &gs = SS.GW.gs;
|
||||||
if(gs.faces > 0) faces.push_back(gs.face[0].v);
|
// See also GraphicsWindow::MakeSelected "if(c >= MAX_SELECTABLE_FACES)"
|
||||||
if(gs.faces > 1) faces.push_back(gs.face[1].v);
|
// and GraphicsWindow::GroupSelection "if(e->IsFace())"
|
||||||
|
for(auto &fc : gs.face) {
|
||||||
|
faces.push_back(fc.v);
|
||||||
|
}
|
||||||
canvas->DrawFaces(displayMesh, faces, hcf);
|
canvas->DrawFaces(displayMesh, faces, hcf);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,22 @@ static std::vector <std::string> splitString(const std::string line) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isHoleDuplicate(EntityList *el, double x, double y, double r) {
|
||||||
|
bool duplicate = false;
|
||||||
|
for(int i = 0; i < el->n && !duplicate; i++) {
|
||||||
|
Entity &en = el->Get(i);
|
||||||
|
if(en.type != Entity::Type::CIRCLE)
|
||||||
|
continue;
|
||||||
|
Entity *distance = el->FindById(en.distance);
|
||||||
|
Entity *center = el->FindById(en.point[0]);
|
||||||
|
duplicate =
|
||||||
|
center->actPoint.x == x && center->actPoint.y == y && distance->actDistance == r;
|
||||||
|
}
|
||||||
|
return duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// Functions for linking an IDF file - we need to create entites that
|
// Functions for linking an IDF file - we need to create entities that
|
||||||
// get remapped into a linked group similar to linking .slvs files
|
// get remapped into a linked group similar to linking .slvs files
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -291,9 +305,9 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
|
||||||
namespace SolveSpace {
|
namespace SolveSpace {
|
||||||
|
|
||||||
// Here we read the important section of an IDF file. SolveSpace Entities are directly created by
|
// Here we read the important section of an IDF file. SolveSpace Entities are directly created by
|
||||||
// the funcions above, which is only OK because of the way linking works. For example points do
|
// the functions above, which is only OK because of the way linking works. For example points do
|
||||||
// not have handles for solver parameters (coordinates), they only have their actPoint values
|
// not have handles for solver parameters (coordinates), they only have their actPoint values
|
||||||
// set (or actNormal or actDistance). These are incompete entites and would be a problem if
|
// set (or actNormal or actDistance). These are incomplete entities and would be a problem if
|
||||||
// they were part of the sketch, but they are not. After making a list of them here, a new group
|
// they were part of the sketch, but they are not. After making a list of them here, a new group
|
||||||
// gets created from copies of these. Those copies are complete and part of the sketch group.
|
// gets created from copies of these. Those copies are complete and part of the sketch group.
|
||||||
bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {
|
bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {
|
||||||
|
@ -332,7 +346,8 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
|
|
||||||
double board_thickness = 10.0;
|
double board_thickness = 10.0;
|
||||||
double scale = 1.0; //mm
|
double scale = 1.0; //mm
|
||||||
bool topEntities, bottomEntities;
|
bool topEntities = false;
|
||||||
|
bool bottomEntities = false;
|
||||||
|
|
||||||
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
|
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
|
||||||
hEntity hnorm = newNormal(el, &entityCount, normal);
|
hEntity hnorm = newNormal(el, &entityCount, normal);
|
||||||
|
@ -461,9 +476,10 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
double d = stof(values[0]);
|
double d = stof(values[0]);
|
||||||
double x = stof(values[1]);
|
double x = stof(values[1]);
|
||||||
double y = stof(values[2]);
|
double y = stof(values[2]);
|
||||||
|
bool duplicate = isHoleDuplicate(el, x, y, d / 2);
|
||||||
// Only show holes likely to be useful in MCAD to reduce complexity.
|
// Only show holes likely to be useful in MCAD to reduce complexity.
|
||||||
if((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)
|
if(((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)
|
||||||
|| (values[5].compare(0,3,"MTG") == 0)) {
|
|| (values[5].compare(0,3,"MTG") == 0)) && !duplicate) {
|
||||||
// create the entity
|
// create the entity
|
||||||
Vector cent = Vector::From(x,y,0.0);
|
Vector cent = Vector::From(x,y,0.0);
|
||||||
hEntity hcent = newPoint(el, &entityCount, cent);
|
hEntity hcent = newPoint(el, &entityCount, cent);
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Triangle mesh file reader. Reads an STL file triangle mesh and creates
|
||||||
|
// a SovleSpace SMesh from it. Supports only Linking, not import.
|
||||||
|
//
|
||||||
|
// Copyright 2020 Paul Kahler.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
#include "solvespace.h"
|
||||||
|
#include "sketch.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define MIN_POINT_DISTANCE 0.001
|
||||||
|
|
||||||
|
// we will check for duplicate vertices and keep all their normals
|
||||||
|
class vertex {
|
||||||
|
public:
|
||||||
|
Vector p;
|
||||||
|
std::vector<Vector> normal;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool isEdgeVertex(vertex &v) {
|
||||||
|
unsigned int i,j;
|
||||||
|
bool result = false;
|
||||||
|
for(i=0;i<v.normal.size();i++) {
|
||||||
|
for(j=i;j<v.normal.size();j++) {
|
||||||
|
if(v.normal[i].Dot(v.normal[j]) < 0.9) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// This function has poor performance, used inside a loop it is O(n**2)
|
||||||
|
static void addUnique(std::vector<vertex> &lv, Vector &p, Vector &n) {
|
||||||
|
unsigned int i;
|
||||||
|
for(i=0; i<lv.size(); i++) {
|
||||||
|
if(lv[i].p.Equals(p, MIN_POINT_DISTANCE)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(i==lv.size()) {
|
||||||
|
vertex v;
|
||||||
|
v.p = p;
|
||||||
|
lv.push_back(v);
|
||||||
|
}
|
||||||
|
// we could improve a little by only storing unique normals
|
||||||
|
lv[i].normal.push_back(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make a new point - type doesn't matter since we will make a copy later
|
||||||
|
static hEntity newPoint(EntityList *el, int *id, Vector p) {
|
||||||
|
Entity en = {};
|
||||||
|
en.type = Entity::Type::POINT_N_COPY;
|
||||||
|
en.extraPoints = 0;
|
||||||
|
en.timesApplied = 0;
|
||||||
|
en.group.v = 462;
|
||||||
|
en.actPoint = p;
|
||||||
|
en.construction = false;
|
||||||
|
en.style.v = Style::DATUM;
|
||||||
|
en.actVisible = true;
|
||||||
|
en.forceHidden = false;
|
||||||
|
|
||||||
|
en.h.v = *id + en.group.v*65536;
|
||||||
|
*id = *id+1;
|
||||||
|
el->Add(&en);
|
||||||
|
return en.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a vertex is unique and add it via newPoint if it is.
|
||||||
|
static void addVertex(EntityList *el, Vector v) {
|
||||||
|
if(el->n < 15000) {
|
||||||
|
int id = el->n;
|
||||||
|
newPoint(el, &id, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static hEntity newNormal(EntityList *el, int *id, Quaternion normal, hEntity p) {
|
||||||
|
// normals have parameters, but we don't need them to make a NORMAL_N_COPY from this
|
||||||
|
Entity en = {};
|
||||||
|
en.type = Entity::Type::NORMAL_N_COPY;
|
||||||
|
en.extraPoints = 0;
|
||||||
|
en.timesApplied = 0;
|
||||||
|
en.group.v = 472;
|
||||||
|
en.actNormal = normal;
|
||||||
|
en.construction = false;
|
||||||
|
en.style.v = Style::NORMALS;
|
||||||
|
// to be visible we need to add a point.
|
||||||
|
// en.point[0] = newPoint(el, id, Vector::From(0,0,0));
|
||||||
|
en.point[0] = p;
|
||||||
|
en.actVisible = true;
|
||||||
|
en.forceHidden = false;
|
||||||
|
|
||||||
|
*id = *id+1;
|
||||||
|
en.h.v = *id + en.group.v*65536;
|
||||||
|
el->Add(&en);
|
||||||
|
return en.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) {
|
||||||
|
Entity en = {};
|
||||||
|
en.type = Entity::Type::LINE_SEGMENT;
|
||||||
|
en.point[0] = p0;
|
||||||
|
en.point[1] = p1;
|
||||||
|
en.extraPoints = 0;
|
||||||
|
en.timesApplied = 0;
|
||||||
|
en.group.v = 493;
|
||||||
|
en.construction = true;
|
||||||
|
en.style.v = Style::CONSTRUCTION;
|
||||||
|
en.actVisible = true;
|
||||||
|
en.forceHidden = false;
|
||||||
|
|
||||||
|
en.h.v = *id + en.group.v*65536;
|
||||||
|
*id = *id + 1;
|
||||||
|
el->Add(&en);
|
||||||
|
return en.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SolveSpace {
|
||||||
|
|
||||||
|
bool LinkStl(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {
|
||||||
|
dbp("\nLink STL triangle mesh.");
|
||||||
|
el->Clear();
|
||||||
|
std::string data;
|
||||||
|
if(!ReadFile(filename, &data)) {
|
||||||
|
Error("Couldn't read from '%s'", filename.raw.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream f(data);
|
||||||
|
|
||||||
|
char str[80] = {};
|
||||||
|
f.read(str, 80);
|
||||||
|
|
||||||
|
if(0==memcmp("solid", str, 5)) {
|
||||||
|
// just returning false will trigger the warning that linked file is not present
|
||||||
|
// best solution is to add an importer for text STL.
|
||||||
|
Message(_("Text-formated STL files are not currently supported"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t n;
|
||||||
|
uint32_t color;
|
||||||
|
|
||||||
|
f.read((char*)&n, 4);
|
||||||
|
dbp("%d triangles", n);
|
||||||
|
|
||||||
|
float x,y,z;
|
||||||
|
float xn,yn,zn;
|
||||||
|
|
||||||
|
std::vector<vertex> verts = {};
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i<n; i++) {
|
||||||
|
STriangle tr = {};
|
||||||
|
|
||||||
|
// read the triangle normal
|
||||||
|
f.read((char*)&xn, 4);
|
||||||
|
f.read((char*)&yn, 4);
|
||||||
|
f.read((char*)&zn, 4);
|
||||||
|
tr.an = Vector::From(xn,yn,zn);
|
||||||
|
tr.bn = tr.an;
|
||||||
|
tr.cn = tr.an;
|
||||||
|
|
||||||
|
f.read((char*)&x, 4);
|
||||||
|
f.read((char*)&y, 4);
|
||||||
|
f.read((char*)&z, 4);
|
||||||
|
tr.a.x = x;
|
||||||
|
tr.a.y = y;
|
||||||
|
tr.a.z = z;
|
||||||
|
|
||||||
|
f.read((char*)&x, 4);
|
||||||
|
f.read((char*)&y, 4);
|
||||||
|
f.read((char*)&z, 4);
|
||||||
|
tr.b.x = x;
|
||||||
|
tr.b.y = y;
|
||||||
|
tr.b.z = z;
|
||||||
|
|
||||||
|
f.read((char*)&x, 4);
|
||||||
|
f.read((char*)&y, 4);
|
||||||
|
f.read((char*)&z, 4);
|
||||||
|
tr.c.x = x;
|
||||||
|
tr.c.y = y;
|
||||||
|
tr.c.z = z;
|
||||||
|
|
||||||
|
f.read((char*)&color,2);
|
||||||
|
if(color & 0x8000) {
|
||||||
|
tr.meta.color.red = (color >> 7) & 0xf8;
|
||||||
|
tr.meta.color.green = (color >> 2) & 0xf8;
|
||||||
|
tr.meta.color.blue = (color << 3);
|
||||||
|
tr.meta.color.alpha = 255;
|
||||||
|
} else {
|
||||||
|
tr.meta.color.red = 90;
|
||||||
|
tr.meta.color.green = 120;
|
||||||
|
tr.meta.color.blue = 140;
|
||||||
|
tr.meta.color.alpha = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
m->AddTriangle(&tr);
|
||||||
|
Vector normal = tr.Normal().WithMagnitude(1.0);
|
||||||
|
addUnique(verts, tr.a, normal);
|
||||||
|
addUnique(verts, tr.b, normal);
|
||||||
|
addUnique(verts, tr.c, normal);
|
||||||
|
}
|
||||||
|
dbp("%d vertices", verts.size());
|
||||||
|
|
||||||
|
int id = 1;
|
||||||
|
|
||||||
|
//add the STL origin and normals
|
||||||
|
hEntity origin = newPoint(el, &id, Vector::From(0.0, 0.0, 0.0));
|
||||||
|
newNormal(el, &id, Quaternion::From(Vector::From(1,0,0),Vector::From(0,1,0)), origin);
|
||||||
|
newNormal(el, &id, Quaternion::From(Vector::From(0,1,0),Vector::From(0,0,1)), origin);
|
||||||
|
newNormal(el, &id, Quaternion::From(Vector::From(0,0,1),Vector::From(1,0,0)), origin);
|
||||||
|
|
||||||
|
BBox box = {};
|
||||||
|
box.minp = verts[0].p;
|
||||||
|
box.maxp = verts[0].p;
|
||||||
|
|
||||||
|
// determine the bounding box for all vertexes
|
||||||
|
for(unsigned int i=1; i<verts.size(); i++) {
|
||||||
|
box.Include(verts[i].p);
|
||||||
|
}
|
||||||
|
|
||||||
|
hEntity p[8];
|
||||||
|
p[0] = newPoint(el, &id, Vector::From(box.minp.x, box.minp.y, box.minp.z));
|
||||||
|
p[1] = newPoint(el, &id, Vector::From(box.maxp.x, box.minp.y, box.minp.z));
|
||||||
|
p[2] = newPoint(el, &id, Vector::From(box.minp.x, box.maxp.y, box.minp.z));
|
||||||
|
p[3] = newPoint(el, &id, Vector::From(box.maxp.x, box.maxp.y, box.minp.z));
|
||||||
|
p[4] = newPoint(el, &id, Vector::From(box.minp.x, box.minp.y, box.maxp.z));
|
||||||
|
p[5] = newPoint(el, &id, Vector::From(box.maxp.x, box.minp.y, box.maxp.z));
|
||||||
|
p[6] = newPoint(el, &id, Vector::From(box.minp.x, box.maxp.y, box.maxp.z));
|
||||||
|
p[7] = newPoint(el, &id, Vector::From(box.maxp.x, box.maxp.y, box.maxp.z));
|
||||||
|
|
||||||
|
newLine(el, &id, p[0], p[1]);
|
||||||
|
newLine(el, &id, p[0], p[2]);
|
||||||
|
newLine(el, &id, p[3], p[1]);
|
||||||
|
newLine(el, &id, p[3], p[2]);
|
||||||
|
|
||||||
|
newLine(el, &id, p[4], p[5]);
|
||||||
|
newLine(el, &id, p[4], p[6]);
|
||||||
|
newLine(el, &id, p[7], p[5]);
|
||||||
|
newLine(el, &id, p[7], p[6]);
|
||||||
|
|
||||||
|
newLine(el, &id, p[0], p[4]);
|
||||||
|
newLine(el, &id, p[1], p[5]);
|
||||||
|
newLine(el, &id, p[2], p[6]);
|
||||||
|
newLine(el, &id, p[3], p[7]);
|
||||||
|
|
||||||
|
for(unsigned int i=0; i<verts.size(); i++) {
|
||||||
|
// create point entities for edge vertexes
|
||||||
|
if(isEdgeVertex(verts[i])) {
|
||||||
|
addVertex(el, verts[i].p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -131,11 +131,15 @@ case SLVS_C_PT_ON_LINE: t = Constraint::Type::PT_ON_LINE; break;
|
||||||
case SLVS_C_PT_ON_FACE: t = Constraint::Type::PT_ON_FACE; break;
|
case SLVS_C_PT_ON_FACE: t = Constraint::Type::PT_ON_FACE; break;
|
||||||
case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; break;
|
case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; break;
|
||||||
case SLVS_C_LENGTH_RATIO: t = Constraint::Type::LENGTH_RATIO; break;
|
case SLVS_C_LENGTH_RATIO: t = Constraint::Type::LENGTH_RATIO; break;
|
||||||
|
case SLVS_C_ARC_ARC_LEN_RATIO: t = Constraint::Type::ARC_ARC_LEN_RATIO; break;
|
||||||
|
case SLVS_C_ARC_LINE_LEN_RATIO: t = Constraint::Type::ARC_LINE_LEN_RATIO; break;
|
||||||
case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::Type::EQ_LEN_PT_LINE_D; break;
|
case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::Type::EQ_LEN_PT_LINE_D; break;
|
||||||
case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::Type::EQ_PT_LN_DISTANCES; break;
|
case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::Type::EQ_PT_LN_DISTANCES; break;
|
||||||
case SLVS_C_EQUAL_ANGLE: t = Constraint::Type::EQUAL_ANGLE; break;
|
case SLVS_C_EQUAL_ANGLE: t = Constraint::Type::EQUAL_ANGLE; break;
|
||||||
case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::Type::EQUAL_LINE_ARC_LEN; break;
|
case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::Type::EQUAL_LINE_ARC_LEN; break;
|
||||||
case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::Type::LENGTH_DIFFERENCE; break;
|
case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::Type::LENGTH_DIFFERENCE; break;
|
||||||
|
case SLVS_C_ARC_ARC_DIFFERENCE: t = Constraint::Type::ARC_ARC_DIFFERENCE; break;
|
||||||
|
case SLVS_C_ARC_LINE_DIFFERENCE:t = Constraint::Type::ARC_LINE_DIFFERENCE; break;
|
||||||
case SLVS_C_SYMMETRIC: t = Constraint::Type::SYMMETRIC; break;
|
case SLVS_C_SYMMETRIC: t = Constraint::Type::SYMMETRIC; break;
|
||||||
case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; break;
|
case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; break;
|
||||||
case SLVS_C_SYMMETRIC_VERT: t = Constraint::Type::SYMMETRIC_VERT; break;
|
case SLVS_C_SYMMETRIC_VERT: t = Constraint::Type::SYMMETRIC_VERT; break;
|
||||||
|
|
|
@ -16,7 +16,7 @@ void SMesh::AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c) {
|
||||||
Vector ab = b.Minus(a), bc = c.Minus(b);
|
Vector ab = b.Minus(a), bc = c.Minus(b);
|
||||||
Vector np = ab.Cross(bc);
|
Vector np = ab.Cross(bc);
|
||||||
if(np.Magnitude() < 1e-10) {
|
if(np.Magnitude() < 1e-10) {
|
||||||
// ugh; gl sometimes tesselates to collinear triangles
|
// ugh; gl sometimes tessellates to collinear triangles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(np.Dot(n) > 0) {
|
if(np.Dot(n) > 0) {
|
||||||
|
|
|
@ -103,7 +103,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
||||||
shiftDown = !shiftDown;
|
shiftDown = !shiftDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SS.showToolbar) {
|
// Not passing right-button and middle-button drags to the toolbar avoids
|
||||||
|
// some cosmetic issues with trackpad pans/rotates implemented with
|
||||||
|
// simulated right-button drag events causing spurious hover events.
|
||||||
|
if(SS.showToolbar && !middleDown) {
|
||||||
if(ToolbarMouseMoved((int)x, (int)y)) {
|
if(ToolbarMouseMoved((int)x, (int)y)) {
|
||||||
hover.Clear();
|
hover.Clear();
|
||||||
return;
|
return;
|
||||||
|
@ -136,7 +139,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
||||||
double dy = (y - orig.mouse.y) / scale;
|
double dy = (y - orig.mouse.y) / scale;
|
||||||
|
|
||||||
if(!(shiftDown || ctrlDown)) {
|
if(!(shiftDown || ctrlDown)) {
|
||||||
double s = 0.3*(PI/180)*scale; // degrees per pixel
|
double sign = SS.cameraNav ? -1.0 : 1.0;
|
||||||
|
double s = 0.3*(PI/180)*scale*sign; // degrees per pixel
|
||||||
if(SS.turntableNav) { // lock the Z to vertical
|
if(SS.turntableNav) { // lock the Z to vertical
|
||||||
projRight = orig.projRight.RotatedAbout(Vector::From(0, 0, 1), -s * dx);
|
projRight = orig.projRight.RotatedAbout(Vector::From(0, 0, 1), -s * dx);
|
||||||
projUp = orig.projUp.RotatedAbout(
|
projUp = orig.projUp.RotatedAbout(
|
||||||
|
@ -821,7 +825,7 @@ Vector GraphicsWindow::SnapToEntityByScreenPoint(Point2d pp, hEntity he) {
|
||||||
SEdgeList *edges = e->GetOrGenerateEdges();
|
SEdgeList *edges = e->GetOrGenerateEdges();
|
||||||
|
|
||||||
double minD = -1.0f;
|
double minD = -1.0f;
|
||||||
double k;
|
double k = 0.0;
|
||||||
const SEdge *edge = NULL;
|
const SEdge *edge = NULL;
|
||||||
for(const auto &e : edges->l) {
|
for(const auto &e : edges->l) {
|
||||||
Point2d p0 = ProjectPoint(e.a);
|
Point2d p0 = ProjectPoint(e.a);
|
||||||
|
@ -911,7 +915,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MouseEvent::Type::SCROLL_VERT:
|
case MouseEvent::Type::SCROLL_VERT:
|
||||||
this->MouseScroll(event.x, event.y, event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);
|
this->MouseScroll(event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MouseEvent::Type::LEAVE:
|
case MouseEvent::Type::LEAVE:
|
||||||
|
@ -1134,6 +1138,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct
|
||||||
AddToPending(hr);
|
AddToPending(hr);
|
||||||
Request *r = SK.GetRequest(hr);
|
Request *r = SK.GetRequest(hr);
|
||||||
r->file = pending.filename;
|
r->file = pending.filename;
|
||||||
|
r->construction = true;
|
||||||
|
|
||||||
for(int i = 1; i <= 4; i++) {
|
for(int i = 1; i <= 4; i++) {
|
||||||
SK.GetEntity(hr.entity(i))->PointForceTo(v);
|
SK.GetEntity(hr.entity(i))->PointForceTo(v);
|
||||||
|
@ -1373,12 +1378,12 @@ void GraphicsWindow::EditConstraint(hConstraint constraint) {
|
||||||
value /= 2;
|
value /= 2;
|
||||||
|
|
||||||
// Try showing value with default number of digits after decimal first.
|
// Try showing value with default number of digits after decimal first.
|
||||||
if(c->type == Constraint::Type::LENGTH_RATIO) {
|
if(c->type == Constraint::Type::LENGTH_RATIO || c->type == Constraint::Type::ARC_ARC_LEN_RATIO || c->type == Constraint::Type::ARC_LINE_LEN_RATIO) {
|
||||||
editValue = ssprintf("%.3f", value);
|
editValue = ssprintf("%.3f", value);
|
||||||
} else if(c->type == Constraint::Type::ANGLE) {
|
} else if(c->type == Constraint::Type::ANGLE) {
|
||||||
editValue = SS.DegreeToString(value);
|
editValue = SS.DegreeToString(value);
|
||||||
} else {
|
} else {
|
||||||
editValue = SS.MmToString(value);
|
editValue = SS.MmToString(value, true);
|
||||||
value /= SS.MmPerUnit();
|
value /= SS.MmPerUnit();
|
||||||
}
|
}
|
||||||
// If that's not enough to represent it exactly, show the value with as many
|
// If that's not enough to represent it exactly, show the value with as many
|
||||||
|
@ -1434,7 +1439,9 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
|
||||||
case Constraint::Type::PT_LINE_DISTANCE:
|
case Constraint::Type::PT_LINE_DISTANCE:
|
||||||
case Constraint::Type::PT_FACE_DISTANCE:
|
case Constraint::Type::PT_FACE_DISTANCE:
|
||||||
case Constraint::Type::PT_PLANE_DISTANCE:
|
case Constraint::Type::PT_PLANE_DISTANCE:
|
||||||
case Constraint::Type::LENGTH_DIFFERENCE: {
|
case Constraint::Type::LENGTH_DIFFERENCE:
|
||||||
|
case Constraint::Type::ARC_ARC_DIFFERENCE:
|
||||||
|
case Constraint::Type::ARC_LINE_DIFFERENCE: {
|
||||||
// The sign is not displayed to the user, but this is a signed
|
// The sign is not displayed to the user, but this is a signed
|
||||||
// distance internally. To flip the sign, the user enters a
|
// distance internally. To flip the sign, the user enters a
|
||||||
// negative distance.
|
// negative distance.
|
||||||
|
@ -1448,6 +1455,8 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
|
||||||
}
|
}
|
||||||
case Constraint::Type::ANGLE:
|
case Constraint::Type::ANGLE:
|
||||||
case Constraint::Type::LENGTH_RATIO:
|
case Constraint::Type::LENGTH_RATIO:
|
||||||
|
case Constraint::Type::ARC_ARC_LEN_RATIO:
|
||||||
|
case Constraint::Type::ARC_LINE_LEN_RATIO:
|
||||||
// These don't get the units conversion for distance, and
|
// These don't get the units conversion for distance, and
|
||||||
// they're always positive
|
// they're always positive
|
||||||
c->valA = fabs(e->Eval());
|
c->valA = fabs(e->Eval());
|
||||||
|
@ -1471,17 +1480,10 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsWindow::MouseScroll(double x, double y, double delta) {
|
void GraphicsWindow::MouseScroll(double zoomMultiplyer) {
|
||||||
double offsetRight = offset.Dot(projRight);
|
|
||||||
double offsetUp = offset.Dot(projUp);
|
|
||||||
|
|
||||||
double righti = x/scale - offsetRight;
|
|
||||||
double upi = y/scale - offsetUp;
|
|
||||||
|
|
||||||
// The default zoom factor is 1.2x for one scroll wheel click (delta==1).
|
|
||||||
// To support smooth scrolling where scroll wheel events come in increments
|
// To support smooth scrolling where scroll wheel events come in increments
|
||||||
// smaller (or larger) than 1 we do:
|
// smaller (or larger) than 1 we do:
|
||||||
// scale *= exp(ln(1.2) * delta);
|
// scale *= exp(ln(1.2) * zoomMultiplyer);
|
||||||
// to ensure that the same total scroll delta always results in the same
|
// to ensure that the same total scroll delta always results in the same
|
||||||
// total zoom irrespective of in how many increments the zoom was applied.
|
// total zoom irrespective of in how many increments the zoom was applied.
|
||||||
// For example if we scroll a total delta of a+b in two events vs. one then
|
// For example if we scroll a total delta of a+b in two events vs. one then
|
||||||
|
@ -1489,21 +1491,7 @@ void GraphicsWindow::MouseScroll(double x, double y, double delta) {
|
||||||
// while
|
// while
|
||||||
// scale * a * b != scale * (a+b)
|
// scale * a * b != scale * (a+b)
|
||||||
// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x
|
// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x
|
||||||
scale *= exp(0.1823216 * delta);
|
ZoomToMouse(zoomMultiplyer);
|
||||||
|
|
||||||
double rightf = x/scale - offsetRight;
|
|
||||||
double upf = y/scale - offsetUp;
|
|
||||||
|
|
||||||
offset = offset.Plus(projRight.ScaledBy(rightf - righti));
|
|
||||||
offset = offset.Plus(projUp.ScaledBy(upf - upi));
|
|
||||||
|
|
||||||
if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) {
|
|
||||||
if(havePainted) {
|
|
||||||
SS.ScheduleShowTW();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
havePainted = false;
|
|
||||||
Invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsWindow::MouseLeave() {
|
void GraphicsWindow::MouseLeave() {
|
||||||
|
|
|
@ -86,8 +86,10 @@ std::vector<FileFilter> SolveSpaceModelFileFilters = {
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<FileFilter> SolveSpaceLinkFileFilters = {
|
std::vector<FileFilter> SolveSpaceLinkFileFilters = {
|
||||||
|
{ CN_("file-type", "ALL"), { "slvs", "emn", "stl" } },
|
||||||
{ CN_("file-type", "SolveSpace models"), { "slvs" } },
|
{ CN_("file-type", "SolveSpace models"), { "slvs" } },
|
||||||
{ CN_("file-type", "IDF circuit board"), { "emn" } },
|
{ CN_("file-type", "IDF circuit board"), { "emn" } },
|
||||||
|
{ CN_("file-type", "STL triangle mesh"), { "stl" } },
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<FileFilter> RasterFileFilters = {
|
std::vector<FileFilter> RasterFileFilters = {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#ifndef SOLVESPACE_GUI_H
|
#ifndef SOLVESPACE_GUI_H
|
||||||
#define SOLVESPACE_GUI_H
|
#define SOLVESPACE_GUI_H
|
||||||
|
|
||||||
|
namespace SolveSpace {
|
||||||
class RgbaColor;
|
class RgbaColor;
|
||||||
|
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
@ -220,6 +221,7 @@ public:
|
||||||
std::function<bool(KeyboardEvent)> onKeyboardEvent;
|
std::function<bool(KeyboardEvent)> onKeyboardEvent;
|
||||||
std::function<void(std::string)> onEditingDone;
|
std::function<void(std::string)> onEditingDone;
|
||||||
std::function<void(double)> onScrollbarAdjusted;
|
std::function<void(double)> onScrollbarAdjusted;
|
||||||
|
std::function<void()> onContextLost;
|
||||||
std::function<void()> onRender;
|
std::function<void()> onRender;
|
||||||
|
|
||||||
virtual ~Window() = default;
|
virtual ~Window() = default;
|
||||||
|
@ -228,7 +230,7 @@ public:
|
||||||
virtual double GetPixelDensity() = 0;
|
virtual double GetPixelDensity() = 0;
|
||||||
// Returns raster graphics and coordinate scale (already applied on the platform side),
|
// Returns raster graphics and coordinate scale (already applied on the platform side),
|
||||||
// i.e. size of logical pixel in physical pixels, or device pixel ratio.
|
// i.e. size of logical pixel in physical pixels, or device pixel ratio.
|
||||||
virtual int GetDevicePixelRatio() = 0;
|
virtual double GetDevicePixelRatio() = 0;
|
||||||
// Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio.
|
// Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio.
|
||||||
virtual double GetDeviceFontScale() {
|
virtual double GetDeviceFontScale() {
|
||||||
return GetPixelDensity() / GetDevicePixelRatio() / 96.0;
|
return GetPixelDensity() / GetDevicePixelRatio() / 96.0;
|
||||||
|
@ -386,5 +388,6 @@ void ExitGui();
|
||||||
void ClearGui();
|
void ClearGui();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} // namespace SolveSpace
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -33,7 +33,13 @@
|
||||||
|
|
||||||
#if defined(HAVE_SPACEWARE)
|
#if defined(HAVE_SPACEWARE)
|
||||||
# include <spnav.h>
|
# include <spnav.h>
|
||||||
|
# include <gdk/gdk.h>
|
||||||
|
# if defined(GDK_WINDOWING_X11)
|
||||||
# include <gdk/gdkx.h>
|
# include <gdk/gdkx.h>
|
||||||
|
# endif
|
||||||
|
# if defined(GDK_WINDOWING_WAYLAND)
|
||||||
|
# include <gdk/gdkwayland.h>
|
||||||
|
# endif
|
||||||
# if GTK_CHECK_VERSION(3, 20, 0)
|
# if GTK_CHECK_VERSION(3, 20, 0)
|
||||||
# include <gdkmm/seat.h>
|
# include <gdkmm/seat.h>
|
||||||
# else
|
# else
|
||||||
|
@ -511,8 +517,12 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_motion_notify_event(GdkEventMotion *gdk_event) override {
|
bool on_motion_notify_event(GdkEventMotion *gdk_event) override {
|
||||||
if(process_pointer_event(MouseEvent::Type::MOTION,
|
double x,y;
|
||||||
gdk_event->x, gdk_event->y, gdk_event->state))
|
GdkModifierType state;
|
||||||
|
gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y);
|
||||||
|
gdk_event_get_state((GdkEvent*)gdk_event, &state);
|
||||||
|
|
||||||
|
if(process_pointer_event(MouseEvent::Type::MOTION, x, y, state))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return Gtk::GLArea::on_motion_notify_event(gdk_event);
|
return Gtk::GLArea::on_motion_notify_event(gdk_event);
|
||||||
|
@ -520,51 +530,79 @@ protected:
|
||||||
|
|
||||||
bool on_button_press_event(GdkEventButton *gdk_event) override {
|
bool on_button_press_event(GdkEventButton *gdk_event) override {
|
||||||
MouseEvent::Type type;
|
MouseEvent::Type type;
|
||||||
if(gdk_event->type == GDK_BUTTON_PRESS) {
|
GdkEventType gdk_type;
|
||||||
|
gdk_type = gdk_event_get_event_type((GdkEvent*)gdk_event);
|
||||||
|
|
||||||
|
if(gdk_type == GDK_BUTTON_PRESS) {
|
||||||
type = MouseEvent::Type::PRESS;
|
type = MouseEvent::Type::PRESS;
|
||||||
} else if(gdk_event->type == GDK_2BUTTON_PRESS) {
|
} else if(gdk_type == GDK_2BUTTON_PRESS) {
|
||||||
type = MouseEvent::Type::DBL_PRESS;
|
type = MouseEvent::Type::DBL_PRESS;
|
||||||
} else {
|
} else {
|
||||||
return Gtk::GLArea::on_button_press_event(gdk_event);
|
return Gtk::GLArea::on_button_press_event(gdk_event);
|
||||||
}
|
}
|
||||||
|
double x,y;
|
||||||
|
gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y);
|
||||||
|
GdkModifierType state;
|
||||||
|
gdk_event_get_state((GdkEvent*)gdk_event, &state);
|
||||||
|
guint button;
|
||||||
|
gdk_event_get_button((GdkEvent*)gdk_event, &button);
|
||||||
|
|
||||||
if(process_pointer_event(type, gdk_event->x, gdk_event->y,
|
if(process_pointer_event(type, x, y, state, button))
|
||||||
gdk_event->state, gdk_event->button))
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return Gtk::GLArea::on_button_press_event(gdk_event);
|
return Gtk::GLArea::on_button_press_event(gdk_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_button_release_event(GdkEventButton *gdk_event) override {
|
bool on_button_release_event(GdkEventButton *gdk_event) override {
|
||||||
if(process_pointer_event(MouseEvent::Type::RELEASE,
|
double x,y;
|
||||||
gdk_event->x, gdk_event->y,
|
gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y);
|
||||||
gdk_event->state, gdk_event->button))
|
GdkModifierType state;
|
||||||
|
gdk_event_get_state((GdkEvent*)gdk_event, &state);
|
||||||
|
guint button;
|
||||||
|
gdk_event_get_button((GdkEvent*)gdk_event, &button);
|
||||||
|
if(process_pointer_event(MouseEvent::Type::RELEASE, x, y, state, button))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return Gtk::GLArea::on_button_release_event(gdk_event);
|
return Gtk::GLArea::on_button_release_event(gdk_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_scroll_event(GdkEventScroll *gdk_event) override {
|
bool on_scroll_event(GdkEventScroll *gdk_event) override {
|
||||||
|
double dx, dy;
|
||||||
|
GdkScrollDirection dir;
|
||||||
|
// for gtk4 ??
|
||||||
|
// gdk_scroll_event_get_deltas((GdkEvent*)gdk_event, &dx, &dy);
|
||||||
|
// gdk_scroll_event_get_direction((GdkEvent*)gdk_event, &dir);
|
||||||
|
gdk_event_get_scroll_direction((GdkEvent*)gdk_event, &dir);
|
||||||
|
gdk_event_get_scroll_deltas((GdkEvent*)gdk_event, &dx, &dy);
|
||||||
|
|
||||||
double delta;
|
double delta;
|
||||||
if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) {
|
if(dy < 0 || dir == GDK_SCROLL_UP) {
|
||||||
delta = 1;
|
delta = 1;
|
||||||
} else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) {
|
} else if(dy > 0 || dir == GDK_SCROLL_DOWN) {
|
||||||
delta = -1;
|
delta = -1;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double x,y;
|
||||||
|
gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y);
|
||||||
|
GdkModifierType state;
|
||||||
|
gdk_event_get_state((GdkEvent*)gdk_event, &state);
|
||||||
|
|
||||||
if(process_pointer_event(MouseEvent::Type::SCROLL_VERT,
|
if(process_pointer_event(MouseEvent::Type::SCROLL_VERT,
|
||||||
gdk_event->x, gdk_event->y,
|
x, y, state, 0, delta))
|
||||||
gdk_event->state, 0, delta))
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return Gtk::GLArea::on_scroll_event(gdk_event);
|
return Gtk::GLArea::on_scroll_event(gdk_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_leave_notify_event(GdkEventCrossing *gdk_event) override {
|
bool on_leave_notify_event(GdkEventCrossing *gdk_event) override {
|
||||||
if(process_pointer_event(MouseEvent::Type::LEAVE,
|
double x,y;
|
||||||
gdk_event->x, gdk_event->y, gdk_event->state))
|
gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y);
|
||||||
|
GdkModifierType state;
|
||||||
|
gdk_event_get_state((GdkEvent*)gdk_event, &state);
|
||||||
|
|
||||||
|
if(process_pointer_event(MouseEvent::Type::LEAVE, x, y, state))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return Gtk::GLArea::on_leave_notify_event(gdk_event);
|
return Gtk::GLArea::on_leave_notify_event(gdk_event);
|
||||||
|
@ -574,22 +612,28 @@ protected:
|
||||||
KeyboardEvent event = {};
|
KeyboardEvent event = {};
|
||||||
event.type = type;
|
event.type = type;
|
||||||
|
|
||||||
|
GdkModifierType state;
|
||||||
|
gdk_event_get_state((GdkEvent*)gdk_event, &state);
|
||||||
|
|
||||||
Gdk::ModifierType mod_mask = get_modifier_mask(Gdk::MODIFIER_INTENT_DEFAULT_MOD_MASK);
|
Gdk::ModifierType mod_mask = get_modifier_mask(Gdk::MODIFIER_INTENT_DEFAULT_MOD_MASK);
|
||||||
if((gdk_event->state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
|
if((state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.shiftDown = (gdk_event->state & GDK_SHIFT_MASK) != 0;
|
event.shiftDown = (state & GDK_SHIFT_MASK) != 0;
|
||||||
event.controlDown = (gdk_event->state & GDK_CONTROL_MASK) != 0;
|
event.controlDown = (state & GDK_CONTROL_MASK) != 0;
|
||||||
|
|
||||||
char32_t chr = gdk_keyval_to_unicode(gdk_keyval_to_lower(gdk_event->keyval));
|
guint keyval;
|
||||||
|
gdk_event_get_keyval((GdkEvent*)gdk_event, &keyval);
|
||||||
|
|
||||||
|
char32_t chr = gdk_keyval_to_unicode(gdk_keyval_to_lower(keyval));
|
||||||
if(chr != 0) {
|
if(chr != 0) {
|
||||||
event.key = KeyboardEvent::Key::CHARACTER;
|
event.key = KeyboardEvent::Key::CHARACTER;
|
||||||
event.chr = chr;
|
event.chr = chr;
|
||||||
} else if(gdk_event->keyval >= GDK_KEY_F1 &&
|
} else if(keyval >= GDK_KEY_F1 &&
|
||||||
gdk_event->keyval <= GDK_KEY_F12) {
|
keyval <= GDK_KEY_F12) {
|
||||||
event.key = KeyboardEvent::Key::FUNCTION;
|
event.key = KeyboardEvent::Key::FUNCTION;
|
||||||
event.num = gdk_event->keyval - GDK_KEY_F1 + 1;
|
event.num = keyval - GDK_KEY_F1 + 1;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -691,8 +735,11 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool on_key_press_event(GdkEventKey *gdk_event) override {
|
bool on_key_press_event(GdkEventKey *gdk_event) override {
|
||||||
|
guint keyval;
|
||||||
|
gdk_event_get_keyval((GdkEvent*)gdk_event, &keyval);
|
||||||
|
|
||||||
if(is_editing()) {
|
if(is_editing()) {
|
||||||
if(gdk_event->keyval == GDK_KEY_Escape) {
|
if(keyval == GDK_KEY_Escape) {
|
||||||
return _gl_widget.event((GdkEvent *)gdk_event);
|
return _gl_widget.event((GdkEvent *)gdk_event);
|
||||||
} else {
|
} else {
|
||||||
_entry.event((GdkEvent *)gdk_event);
|
_entry.event((GdkEvent *)gdk_event);
|
||||||
|
@ -835,7 +882,11 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_window_state_event(GdkEventWindowState *gdk_event) override {
|
bool on_window_state_event(GdkEventWindowState *gdk_event) override {
|
||||||
_is_fullscreen = gdk_event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
|
// window state event is superseded by GdkWindow::state on GTK4
|
||||||
|
GdkWindowState new_window_state;
|
||||||
|
new_window_state = gdk_event->new_window_state;
|
||||||
|
|
||||||
|
_is_fullscreen = new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
|
||||||
if(_receiver->onFullScreen) {
|
if(_receiver->onFullScreen) {
|
||||||
_receiver->onFullScreen(_is_fullscreen);
|
_receiver->onFullScreen(_is_fullscreen);
|
||||||
}
|
}
|
||||||
|
@ -883,7 +934,7 @@ public:
|
||||||
return gtkWindow.get_screen()->get_resolution();
|
return gtkWindow.get_screen()->get_resolution();
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDevicePixelRatio() override {
|
double GetDevicePixelRatio() override {
|
||||||
return gtkWindow.get_scale_factor();
|
return gtkWindow.get_scale_factor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1047,7 +1098,7 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
||||||
void Open3DConnexion() {}
|
void Open3DConnexion() {}
|
||||||
void Close3DConnexion() {}
|
void Close3DConnexion() {}
|
||||||
|
|
||||||
#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11)
|
#if defined(HAVE_SPACEWARE) && (defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND))
|
||||||
static void ProcessSpnavEvent(WindowImplGtk *window, const spnav_event &spnavEvent, bool shiftDown, bool controlDown) {
|
static void ProcessSpnavEvent(WindowImplGtk *window, const spnav_event &spnavEvent, bool shiftDown, bool controlDown) {
|
||||||
switch(spnavEvent.type) {
|
switch(spnavEvent.type) {
|
||||||
case SPNAV_EVENT_MOTION: {
|
case SPNAV_EVENT_MOTION: {
|
||||||
|
@ -1129,10 +1180,8 @@ void Request3DConnexionEventsForWindow(WindowRef window) {
|
||||||
std::static_pointer_cast<WindowImplGtk>(window);
|
std::static_pointer_cast<WindowImplGtk>(window);
|
||||||
|
|
||||||
Glib::RefPtr<Gdk::Window> gdkWindow = windowImpl->gtkWindow.get_window();
|
Glib::RefPtr<Gdk::Window> gdkWindow = windowImpl->gtkWindow.get_window();
|
||||||
if(!GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) {
|
#if defined(GDK_WINDOWING_X11)
|
||||||
return;
|
if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) {
|
||||||
}
|
|
||||||
|
|
||||||
if(spnav_x11_open(gdk_x11_get_default_xdisplay(),
|
if(spnav_x11_open(gdk_x11_get_default_xdisplay(),
|
||||||
gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) {
|
gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) {
|
||||||
gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get());
|
gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get());
|
||||||
|
@ -1140,6 +1189,17 @@ void Request3DConnexionEventsForWindow(WindowRef window) {
|
||||||
g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN,
|
g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN,
|
||||||
ConsumeSpnavQueue, windowImpl.get());
|
ConsumeSpnavQueue, windowImpl.get());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GDK_WINDOWING_WAYLAND)
|
||||||
|
if(GDK_IS_WAYLAND_DISPLAY(gdkWindow->get_display()->gobj())) {
|
||||||
|
if(spnav_open() != -1) {
|
||||||
|
g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN,
|
||||||
|
ConsumeSpnavQueue, windowImpl.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
void Request3DConnexionEventsForWindow(WindowRef window) {}
|
void Request3DConnexionEventsForWindow(WindowRef window) {}
|
||||||
|
@ -1383,6 +1443,9 @@ public:
|
||||||
gtkDialog.add_button(isSave ? C_("button", "_Save")
|
gtkDialog.add_button(isSave ? C_("button", "_Save")
|
||||||
: C_("button", "_Open"), Gtk::RESPONSE_OK);
|
: C_("button", "_Open"), Gtk::RESPONSE_OK);
|
||||||
gtkDialog.set_default_response(Gtk::RESPONSE_OK);
|
gtkDialog.set_default_response(Gtk::RESPONSE_OK);
|
||||||
|
if(isSave) {
|
||||||
|
gtkDialog.set_do_overwrite_confirmation(true);
|
||||||
|
}
|
||||||
InitFileChooser(gtkDialog);
|
InitFileChooser(gtkDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1416,6 +1479,9 @@ public:
|
||||||
isSave ? C_("button", "_Save")
|
isSave ? C_("button", "_Save")
|
||||||
: C_("button", "_Open"),
|
: C_("button", "_Open"),
|
||||||
C_("button", "_Cancel"));
|
C_("button", "_Cancel"));
|
||||||
|
if(isSave) {
|
||||||
|
gtkNative->set_do_overwrite_confirmation(true);
|
||||||
|
}
|
||||||
// Seriously, GTK?!
|
// Seriously, GTK?!
|
||||||
InitFileChooser(*gtkNative.operator->());
|
InitFileChooser(*gtkNative.operator->());
|
||||||
}
|
}
|
||||||
|
@ -1478,7 +1544,8 @@ std::vector<Platform::Path> GetFontFiles() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenInBrowser(const std::string &url) {
|
void OpenInBrowser(const std::string &url) {
|
||||||
gtk_show_uri(Gdk::Screen::get_default()->gobj(), url.c_str(), GDK_CURRENT_TIME, NULL);
|
// first param should be our window?
|
||||||
|
gtk_show_uri_on_window(NULL, url.c_str(), GDK_CURRENT_TIME, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk::Main *gtkMain;
|
Gtk::Main *gtkMain;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -286,7 +286,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopUp() override {
|
void PopUp() override {
|
||||||
[NSMenu popUpContextMenu:nsMenu withEvent:[NSApp currentEvent] forView:nil];
|
NSEvent* event = [NSApp currentEvent];
|
||||||
|
[NSMenu popUpContextMenu:nsMenu withEvent:event forView:event.window.contentView];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Clear() override {
|
void Clear() override {
|
||||||
|
@ -358,18 +359,27 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
- (void)didEdit:(NSString *)text;
|
- (void)didEdit:(NSString *)text;
|
||||||
|
|
||||||
@property double scrollerMin;
|
@property double scrollerMin;
|
||||||
@property double scrollerMax;
|
@property double scrollerSize;
|
||||||
|
@property double pageSize;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SSView
|
@implementation SSView
|
||||||
{
|
{
|
||||||
NSTrackingArea *trackingArea;
|
NSTrackingArea *trackingArea;
|
||||||
NSTextField *editor;
|
NSTextField *editor;
|
||||||
|
double magnificationGestureCurrentZ;
|
||||||
|
double rotationGestureCurrent;
|
||||||
|
Point2d trackpadPositionShift;
|
||||||
|
bool inTrackpadScrollGesture;
|
||||||
|
int activeTrackpadTouches;
|
||||||
|
bool scrollFromTrackpadTouch;
|
||||||
|
Platform::Window::Kind kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize acceptsFirstResponder;
|
@synthesize acceptsFirstResponder;
|
||||||
|
|
||||||
- (id)initWithFrame:(NSRect)frameRect {
|
- (id)initWithKind:(Platform::Window::Kind)aKind {
|
||||||
NSOpenGLPixelFormatAttribute attrs[] = {
|
NSOpenGLPixelFormatAttribute attrs[] = {
|
||||||
NSOpenGLPFADoubleBuffer,
|
NSOpenGLPFADoubleBuffer,
|
||||||
NSOpenGLPFAColorSize, 24,
|
NSOpenGLPFAColorSize, 24,
|
||||||
|
@ -377,7 +387,7 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
|
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
|
||||||
if(self = [super initWithFrame:frameRect pixelFormat:pixelFormat]) {
|
if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0) pixelFormat:pixelFormat]) {
|
||||||
self.wantsBestResolutionOpenGLSurface = YES;
|
self.wantsBestResolutionOpenGLSurface = YES;
|
||||||
self.wantsLayer = YES;
|
self.wantsLayer = YES;
|
||||||
editor = [[NSTextField alloc] init];
|
editor = [[NSTextField alloc] init];
|
||||||
|
@ -387,6 +397,21 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
editor.bezeled = NO;
|
editor.bezeled = NO;
|
||||||
editor.target = self;
|
editor.target = self;
|
||||||
editor.action = @selector(didEdit:);
|
editor.action = @selector(didEdit:);
|
||||||
|
|
||||||
|
inTrackpadScrollGesture = false;
|
||||||
|
activeTrackpadTouches = 0;
|
||||||
|
scrollFromTrackpadTouch = false;
|
||||||
|
self.acceptsTouchEvents = YES;
|
||||||
|
kind = aKind;
|
||||||
|
if(kind == Platform::Window::Kind::TOPLEVEL) {
|
||||||
|
NSGestureRecognizer *mag = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self
|
||||||
|
action:@selector(magnifyGesture:)];
|
||||||
|
[self addGestureRecognizer:mag];
|
||||||
|
|
||||||
|
NSRotationGestureRecognizer* rot = [[NSRotationGestureRecognizer alloc] initWithTarget:self
|
||||||
|
action:@selector(rotateGesture:)];
|
||||||
|
[self addGestureRecognizer:rot];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -427,9 +452,9 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
- (Platform::MouseEvent)convertMouseEvent:(NSEvent *)nsEvent {
|
- (Platform::MouseEvent)convertMouseEvent:(NSEvent *)nsEvent {
|
||||||
Platform::MouseEvent event = {};
|
Platform::MouseEvent event = {};
|
||||||
|
|
||||||
NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:self];
|
NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:nil];
|
||||||
event.x = nsPoint.x;
|
event.x = nsPoint.x;
|
||||||
event.y = self.bounds.size.height - nsPoint.y;
|
event.y = nsPoint.y;
|
||||||
|
|
||||||
NSUInteger nsFlags = [nsEvent modifierFlags];
|
NSUInteger nsFlags = [nsEvent modifierFlags];
|
||||||
if(nsFlags & NSEventModifierFlagShift) event.shiftDown = true;
|
if(nsFlags & NSEventModifierFlagShift) event.shiftDown = true;
|
||||||
|
@ -553,14 +578,78 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
using Platform::MouseEvent;
|
using Platform::MouseEvent;
|
||||||
|
|
||||||
MouseEvent event = [self convertMouseEvent:nsEvent];
|
MouseEvent event = [self convertMouseEvent:nsEvent];
|
||||||
|
if(nsEvent.phase == NSEventPhaseBegan) {
|
||||||
|
// If this scroll began on trackpad then touchesBeganWithEvent was called prior to this
|
||||||
|
// event and we have at least one active trackpad touch. We store this information so we
|
||||||
|
// can handle scroll originating from trackpad differently below.
|
||||||
|
scrollFromTrackpadTouch = activeTrackpadTouches > 0 &&
|
||||||
|
nsEvent.subtype == NSEventSubtypeTabletPoint &&
|
||||||
|
kind == Platform::Window::Kind::TOPLEVEL;
|
||||||
|
}
|
||||||
|
// Check if we are scrolling on trackpad and handle things differently.
|
||||||
|
if(scrollFromTrackpadTouch) {
|
||||||
|
// This is how Cocoa represents 2 finger trackpad drag gestures, rather than going via
|
||||||
|
// NSPanGestureRecognizer which is how you might expect this to work... We complicate this
|
||||||
|
// further by also handling shift-two-finger-drag to mean rotate. Fortunately we're using
|
||||||
|
// shift in the same way as right-mouse-button MouseEvent does (to converts a pan to a
|
||||||
|
// rotate) so we get the rotate support for free. It's a bit ugly having to fake mouse
|
||||||
|
// events and track the deviation from the actual mouse cursor with trackpadPositionShift,
|
||||||
|
// but in lieu of an event API that allows us to request a rotate/pan with relative
|
||||||
|
// coordinates, it's the best we can do.
|
||||||
|
event.button = MouseEvent::Button::RIGHT;
|
||||||
|
// Make sure control (actually cmd) isn't passed through, ctrl-right-click-drag has special
|
||||||
|
// meaning as rotate which we don't want to inadvertently trigger.
|
||||||
|
event.controlDown = false;
|
||||||
|
if(nsEvent.scrollingDeltaX == 0 && nsEvent.scrollingDeltaY == 0) {
|
||||||
|
// Cocoa represents the point where the user lifts their fingers off (and any inertial
|
||||||
|
// scrolling has finished) by an event with scrollingDeltaX and scrollingDeltaY both 0.
|
||||||
|
// Sometimes you also get a zero scroll at the start of a two-finger-rotate (probably
|
||||||
|
// reflecting the internal implementation of that being a cancelled possible pan
|
||||||
|
// gesture), which is why this conditional is structured the way it is.
|
||||||
|
if(inTrackpadScrollGesture) {
|
||||||
|
event.x += trackpadPositionShift.x;
|
||||||
|
event.y += trackpadPositionShift.y;
|
||||||
|
event.type = MouseEvent::Type::RELEASE;
|
||||||
|
receiver->onMouseEvent(event);
|
||||||
|
inTrackpadScrollGesture = false;
|
||||||
|
trackpadPositionShift = Point2d::From(0, 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if(!inTrackpadScrollGesture) {
|
||||||
|
inTrackpadScrollGesture = true;
|
||||||
|
trackpadPositionShift = Point2d::From(0, 0);
|
||||||
|
event.type = MouseEvent::Type::PRESS;
|
||||||
|
receiver->onMouseEvent(event);
|
||||||
|
// And drop through
|
||||||
|
}
|
||||||
|
|
||||||
|
trackpadPositionShift.x += nsEvent.scrollingDeltaX;
|
||||||
|
trackpadPositionShift.y += nsEvent.scrollingDeltaY;
|
||||||
|
event.type = MouseEvent::Type::MOTION;
|
||||||
|
event.x += trackpadPositionShift.x;
|
||||||
|
event.y += trackpadPositionShift.y;
|
||||||
|
receiver->onMouseEvent(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.type = MouseEvent::Type::SCROLL_VERT;
|
event.type = MouseEvent::Type::SCROLL_VERT;
|
||||||
|
|
||||||
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
|
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
|
||||||
event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5);
|
event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5);
|
||||||
|
|
||||||
if(receiver->onMouseEvent) {
|
|
||||||
receiver->onMouseEvent(event);
|
receiver->onMouseEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)touchesBeganWithEvent:(NSEvent *)event {
|
||||||
|
activeTrackpadTouches++;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)touchesEndedWithEvent:(NSEvent *)event {
|
||||||
|
activeTrackpadTouches--;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)touchesCancelledWithEvent:(NSEvent *)event {
|
||||||
|
activeTrackpadTouches--;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)mouseExited:(NSEvent *)nsEvent {
|
- (void)mouseExited:(NSEvent *)nsEvent {
|
||||||
|
@ -638,6 +727,50 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
[super keyUp:nsEvent];
|
[super keyUp:nsEvent];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)magnifyGesture:(NSMagnificationGestureRecognizer *)gesture {
|
||||||
|
// The onSixDofEvent API doesn't allow us to specify the scaling's origin, so for expediency
|
||||||
|
// we fake out a scrollwheel MouseEvent with a suitably-scaled scrollDelta with a bit of
|
||||||
|
// absolute-to-relative positioning conversion tracked using magnificationGestureCurrentZ.
|
||||||
|
|
||||||
|
if(gesture.state == NSGestureRecognizerStateBegan) {
|
||||||
|
magnificationGestureCurrentZ = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magic number to make gesture.magnification align roughly with what scrollDelta expects
|
||||||
|
constexpr double kScale = 10.0;
|
||||||
|
double z = ((double)gesture.magnification * kScale);
|
||||||
|
double zdelta = z - magnificationGestureCurrentZ;
|
||||||
|
magnificationGestureCurrentZ = z;
|
||||||
|
|
||||||
|
using Platform::MouseEvent;
|
||||||
|
MouseEvent event = {};
|
||||||
|
event.type = MouseEvent::Type::SCROLL_VERT;
|
||||||
|
NSPoint nsPoint = [gesture locationInView:self];
|
||||||
|
event.x = nsPoint.x;
|
||||||
|
event.y = nsPoint.y;
|
||||||
|
event.scrollDelta = zdelta;
|
||||||
|
if(receiver->onMouseEvent) {
|
||||||
|
receiver->onMouseEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)rotateGesture:(NSRotationGestureRecognizer *)gesture {
|
||||||
|
if(gesture.state == NSGestureRecognizerStateBegan) {
|
||||||
|
rotationGestureCurrent = 0.0;
|
||||||
|
}
|
||||||
|
double rotation = gesture.rotation;
|
||||||
|
double rotationDelta = rotation - rotationGestureCurrent;
|
||||||
|
rotationGestureCurrent = rotation;
|
||||||
|
|
||||||
|
using Platform::SixDofEvent;
|
||||||
|
SixDofEvent event = {};
|
||||||
|
event.type = SixDofEvent::Type::MOTION;
|
||||||
|
event.rotationZ = rotationDelta;
|
||||||
|
if(receiver->onSixDofEvent) {
|
||||||
|
receiver->onSixDofEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@synthesize editing;
|
@synthesize editing;
|
||||||
|
|
||||||
- (void)startEditing:(NSString *)text at:(NSPoint)origin withHeight:(double)fontHeight
|
- (void)startEditing:(NSString *)text at:(NSPoint)origin withHeight:(double)fontHeight
|
||||||
|
@ -698,11 +831,27 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize scrollerMin;
|
@synthesize scrollerMin;
|
||||||
@synthesize scrollerMax;
|
@synthesize scrollerSize;
|
||||||
|
@synthesize pageSize;
|
||||||
|
|
||||||
- (void)didScroll:(NSScroller *)sender {
|
- (void)didScroll:(NSScroller *)sender {
|
||||||
|
double pos;
|
||||||
|
switch(sender.hitPart) {
|
||||||
|
case NSScrollerKnob:
|
||||||
|
case NSScrollerKnobSlot:
|
||||||
|
pos = receiver->GetScrollbarPosition();
|
||||||
|
break;
|
||||||
|
case NSScrollerDecrementPage:
|
||||||
|
pos = receiver->GetScrollbarPosition() - pageSize;
|
||||||
|
break;
|
||||||
|
case NSScrollerIncrementPage:
|
||||||
|
pos = receiver->GetScrollbarPosition() + pageSize;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(receiver->onScrollbarAdjusted) {
|
if(receiver->onScrollbarAdjusted) {
|
||||||
double pos = scrollerMin + [sender doubleValue] * (scrollerMax - scrollerMin);
|
|
||||||
receiver->onScrollbarAdjusted(pos);
|
receiver->onScrollbarAdjusted(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -769,7 +918,7 @@ public:
|
||||||
NSString *nsToolTip;
|
NSString *nsToolTip;
|
||||||
|
|
||||||
WindowImplCocoa(Window::Kind kind, std::shared_ptr<WindowImplCocoa> parentWindow) {
|
WindowImplCocoa(Window::Kind kind, std::shared_ptr<WindowImplCocoa> parentWindow) {
|
||||||
ssView = [[SSView alloc] init];
|
ssView = [[SSView alloc] initWithKind:kind];
|
||||||
ssView.translatesAutoresizingMaskIntoConstraints = NO;
|
ssView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
ssView.receiver = this;
|
ssView.receiver = this;
|
||||||
|
|
||||||
|
@ -838,10 +987,10 @@ public:
|
||||||
return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f;
|
return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDevicePixelRatio() override {
|
double GetDevicePixelRatio() override {
|
||||||
NSSize unitSize = { 1.0f, 0.0f };
|
NSSize unitSize = { 1.0f, 0.0f };
|
||||||
unitSize = [ssView convertSizeToBacking:unitSize];
|
unitSize = [ssView convertSizeToBacking:unitSize];
|
||||||
return (int)unitSize.width;
|
return unitSize.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsVisible() override {
|
bool IsVisible() override {
|
||||||
|
@ -962,21 +1111,22 @@ public:
|
||||||
|
|
||||||
void ConfigureScrollbar(double min, double max, double pageSize) override {
|
void ConfigureScrollbar(double min, double max, double pageSize) override {
|
||||||
ssView.scrollerMin = min;
|
ssView.scrollerMin = min;
|
||||||
ssView.scrollerMax = max - pageSize;
|
ssView.scrollerSize = max + 1 - min;
|
||||||
[nsScroller setKnobProportion:(pageSize / (ssView.scrollerMax - ssView.scrollerMin))];
|
ssView.pageSize = pageSize;
|
||||||
|
nsScroller.knobProportion = pageSize / ssView.scrollerSize;
|
||||||
|
nsScroller.hidden = pageSize >= ssView.scrollerSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
double GetScrollbarPosition() override {
|
double GetScrollbarPosition() override {
|
||||||
|
// Platform::Window scrollbar positions are in the range [min, max+1 - pageSize] inclusive,
|
||||||
|
// and Cocoa scrollbars are from 0.0 to 1.0 inclusive, so we have to apply some scaling and
|
||||||
|
// transforming. (scrollerSize is max+1-min, see ConfigureScrollbar above)
|
||||||
return ssView.scrollerMin +
|
return ssView.scrollerMin +
|
||||||
[nsScroller doubleValue] * (ssView.scrollerMax - ssView.scrollerMin);
|
nsScroller.doubleValue * (ssView.scrollerSize - ssView.pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetScrollbarPosition(double pos) override {
|
void SetScrollbarPosition(double pos) override {
|
||||||
if(pos > ssView.scrollerMax)
|
nsScroller.doubleValue = (pos - ssView.scrollerMin) / ( ssView.scrollerSize - ssView.pageSize);
|
||||||
pos = ssView.scrollerMax;
|
|
||||||
if(GetScrollbarPosition() == pos)
|
|
||||||
return;
|
|
||||||
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Invalidate() override {
|
void Invalidate() override {
|
||||||
|
@ -1426,9 +1576,22 @@ void OpenInBrowser(const std::string &url) {
|
||||||
- (IBAction)preferences:(id)sender;
|
- (IBAction)preferences:(id)sender;
|
||||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
|
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
|
||||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
||||||
|
|
||||||
|
@property BOOL exiting;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SSApplicationDelegate
|
@implementation SSApplicationDelegate
|
||||||
|
|
||||||
|
@synthesize exiting;
|
||||||
|
|
||||||
|
- (id)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.exiting = false;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)preferences:(id)sender {
|
- (IBAction)preferences:(id)sender {
|
||||||
if (!SS.GW.showTextWindow) {
|
if (!SS.GW.showTextWindow) {
|
||||||
SolveSpace::SS.GW.MenuView(SolveSpace::Command::SHOW_TEXT_WND);
|
SolveSpace::SS.GW.MenuView(SolveSpace::Command::SHOW_TEXT_WND);
|
||||||
|
@ -1443,12 +1606,27 @@ void OpenInBrowser(const std::string &url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||||
[[[NSApp mainWindow] delegate] windowShouldClose:[NSApp mainWindow]];
|
if(!SS.unsaved) {
|
||||||
return NSTerminateCancel;
|
return NSTerminateNow;
|
||||||
|
} else {
|
||||||
|
[self performSelectorOnMainThread:@selector(applicationTerminatePrompt) withObject:nil
|
||||||
|
waitUntilDone:NO modes:@[NSDefaultRunLoopMode, NSModalPanelRunLoopMode]];
|
||||||
|
return NSTerminateLater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillTerminate:(NSNotification *)notification {
|
||||||
|
if(!exiting) {
|
||||||
|
// Prevent the Platform::ExitGui() call from SolveSpaceUI::Exit()
|
||||||
|
// triggering another terminate
|
||||||
|
exiting = true;
|
||||||
|
// Now let SS save settings etc
|
||||||
|
SS.Exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationTerminatePrompt {
|
- (void)applicationTerminatePrompt {
|
||||||
SolveSpace::SS.MenuFile(SolveSpace::Command::EXIT);
|
[NSApp replyToApplicationShouldTerminate:SS.OkayToStartNewFile()];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -1469,6 +1647,14 @@ std::vector<std::string> InitGui(int argc, char **argv) {
|
||||||
ssDelegate = [[SSApplicationDelegate alloc] init];
|
ssDelegate = [[SSApplicationDelegate alloc] init];
|
||||||
NSApplication.sharedApplication.delegate = ssDelegate;
|
NSApplication.sharedApplication.delegate = ssDelegate;
|
||||||
|
|
||||||
|
// Setting this prevents "Show Tab Bar" and "Show All Tabs" items from being
|
||||||
|
// automagically added to the View menu
|
||||||
|
NSWindow.allowsAutomaticWindowTabbing = NO;
|
||||||
|
|
||||||
|
// And this prevents the duplicate "Enter Full Screen" menu item, see
|
||||||
|
// https://stackoverflow.com/questions/52154977/how-to-get-rid-of-enter-full-screen-menu-item
|
||||||
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
|
||||||
|
|
||||||
[NSBundle.mainBundle loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil];
|
[NSBundle.mainBundle loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil];
|
||||||
|
|
||||||
NSArray *languages = NSLocale.preferredLanguages;
|
NSArray *languages = NSLocale.preferredLanguages;
|
||||||
|
@ -1487,8 +1673,10 @@ void RunGui() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExitGui() {
|
void ExitGui() {
|
||||||
[NSApp setDelegate:nil];
|
if(!ssDelegate.exiting) {
|
||||||
|
ssDelegate.exiting = true;
|
||||||
[NSApp terminate:nil];
|
[NSApp terminate:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearGui() {}
|
void ClearGui() {}
|
||||||
|
|
|
@ -793,7 +793,7 @@ public:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM_SIZING: {
|
case WM_SIZING: {
|
||||||
int pixelRatio = window->GetDevicePixelRatio();
|
double pixelRatio = window->GetDevicePixelRatio();
|
||||||
|
|
||||||
RECT rcw, rcc;
|
RECT rcw, rcc;
|
||||||
sscheck(GetWindowRect(window->hWindow, &rcw));
|
sscheck(GetWindowRect(window->hWindow, &rcw));
|
||||||
|
@ -806,10 +806,10 @@ public:
|
||||||
int adjHeight = rc->bottom - rc->top;
|
int adjHeight = rc->bottom - rc->top;
|
||||||
|
|
||||||
adjWidth -= nonClientWidth;
|
adjWidth -= nonClientWidth;
|
||||||
adjWidth = max(window->minWidth * pixelRatio, adjWidth);
|
adjWidth = max((int)(window->minWidth * pixelRatio), adjWidth);
|
||||||
adjWidth += nonClientWidth;
|
adjWidth += nonClientWidth;
|
||||||
adjHeight -= nonClientHeight;
|
adjHeight -= nonClientHeight;
|
||||||
adjHeight = max(window->minHeight * pixelRatio, adjHeight);
|
adjHeight = max((int)(window->minHeight * pixelRatio), adjHeight);
|
||||||
adjHeight += nonClientHeight;
|
adjHeight += nonClientHeight;
|
||||||
switch(wParam) {
|
switch(wParam) {
|
||||||
case WMSZ_RIGHT:
|
case WMSZ_RIGHT:
|
||||||
|
@ -868,7 +868,7 @@ public:
|
||||||
case WM_MOUSEMOVE:
|
case WM_MOUSEMOVE:
|
||||||
case WM_MOUSEWHEEL:
|
case WM_MOUSEWHEEL:
|
||||||
case WM_MOUSELEAVE: {
|
case WM_MOUSELEAVE: {
|
||||||
int pixelRatio = window->GetDevicePixelRatio();
|
double pixelRatio = window->GetDevicePixelRatio();
|
||||||
|
|
||||||
MouseEvent event = {};
|
MouseEvent event = {};
|
||||||
event.x = GET_X_LPARAM(lParam) / pixelRatio;
|
event.x = GET_X_LPARAM(lParam) / pixelRatio;
|
||||||
|
@ -941,7 +941,7 @@ public:
|
||||||
event.y = pt.y / pixelRatio;
|
event.y = pt.y / pixelRatio;
|
||||||
|
|
||||||
event.type = MouseEvent::Type::SCROLL_VERT;
|
event.type = MouseEvent::Type::SCROLL_VERT;
|
||||||
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
|
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM_MOUSELEAVE:
|
case WM_MOUSELEAVE:
|
||||||
|
@ -1109,10 +1109,10 @@ public:
|
||||||
return (double)dpi;
|
return (double)dpi;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDevicePixelRatio() override {
|
double GetDevicePixelRatio() override {
|
||||||
UINT dpi;
|
UINT dpi;
|
||||||
sscheck(dpi = ssGetDpiForWindow(hWindow));
|
sscheck(dpi = ssGetDpiForWindow(hWindow));
|
||||||
return dpi / USER_DEFAULT_SCREEN_DPI;
|
return (double)dpi / USER_DEFAULT_SCREEN_DPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsVisible() override {
|
bool IsVisible() override {
|
||||||
|
@ -1177,7 +1177,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetContentSize(double *width, double *height) override {
|
void GetContentSize(double *width, double *height) override {
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
|
|
||||||
RECT rc;
|
RECT rc;
|
||||||
sscheck(GetClientRect(hWindow, &rc));
|
sscheck(GetClientRect(hWindow, &rc));
|
||||||
|
@ -1189,15 +1189,15 @@ public:
|
||||||
minWidth = (int)width;
|
minWidth = (int)width;
|
||||||
minHeight = (int)height;
|
minHeight = (int)height;
|
||||||
|
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
|
|
||||||
RECT rc;
|
RECT rc;
|
||||||
sscheck(GetClientRect(hWindow, &rc));
|
sscheck(GetClientRect(hWindow, &rc));
|
||||||
if(rc.right - rc.left < minWidth * pixelRatio) {
|
if(rc.right - rc.left < minWidth * pixelRatio) {
|
||||||
rc.right = rc.left + minWidth * pixelRatio;
|
rc.right = rc.left + (LONG)(minWidth * pixelRatio);
|
||||||
}
|
}
|
||||||
if(rc.bottom - rc.top < minHeight * pixelRatio) {
|
if(rc.bottom - rc.top < minHeight * pixelRatio) {
|
||||||
rc.bottom = rc.top + minHeight * pixelRatio;
|
rc.bottom = rc.top + (LONG)(minHeight * pixelRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1229,7 +1229,7 @@ public:
|
||||||
sscheck(GetMonitorInfo(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), &mi));
|
sscheck(GetMonitorInfo(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), &mi));
|
||||||
|
|
||||||
// If it somehow ended up off-screen, then put it back.
|
// If it somehow ended up off-screen, then put it back.
|
||||||
// and make it visible by at least this portion of the scrren
|
// and make it visible by at least this portion of the screen
|
||||||
const LONG movein = 40;
|
const LONG movein = 40;
|
||||||
|
|
||||||
RECT mrc = mi.rcMonitor;
|
RECT mrc = mi.rcMonitor;
|
||||||
|
@ -1270,7 +1270,7 @@ public:
|
||||||
tooltipText = newText;
|
tooltipText = newText;
|
||||||
|
|
||||||
if(!newText.empty()) {
|
if(!newText.empty()) {
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
RECT toolRect;
|
RECT toolRect;
|
||||||
toolRect.left = (int)(x * pixelRatio);
|
toolRect.left = (int)(x * pixelRatio);
|
||||||
toolRect.top = (int)(y * pixelRatio);
|
toolRect.top = (int)(y * pixelRatio);
|
||||||
|
@ -1301,9 +1301,9 @@ public:
|
||||||
bool isMonospace, const std::string &text) override {
|
bool isMonospace, const std::string &text) override {
|
||||||
if(IsEditorVisible()) return;
|
if(IsEditorVisible()) return;
|
||||||
|
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
|
|
||||||
HFONT hFont = CreateFontW(-(LONG)fontHeight * GetDevicePixelRatio(), 0, 0, 0,
|
HFONT hFont = CreateFontW(-(int)(fontHeight * GetDevicePixelRatio()), 0, 0, 0,
|
||||||
FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
||||||
DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial");
|
DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial");
|
||||||
if(hFont == NULL) {
|
if(hFont == NULL) {
|
||||||
|
@ -1324,12 +1324,12 @@ public:
|
||||||
sscheck(ReleaseDC(hEditor, hDc));
|
sscheck(ReleaseDC(hEditor, hDc));
|
||||||
|
|
||||||
RECT rc;
|
RECT rc;
|
||||||
rc.left = (LONG)x * pixelRatio;
|
rc.left = (LONG)(x * pixelRatio);
|
||||||
rc.top = (LONG)y * pixelRatio - tm.tmAscent;
|
rc.top = (LONG)(y * pixelRatio) - tm.tmAscent;
|
||||||
// Add one extra char width to avoid scrolling.
|
// Add one extra char width to avoid scrolling.
|
||||||
rc.right = (LONG)x * pixelRatio +
|
rc.right = (LONG)(x * pixelRatio) +
|
||||||
std::max((LONG)minWidth * pixelRatio, ts.cx + tm.tmAveCharWidth);
|
std::max((LONG)(minWidth * pixelRatio), ts.cx + tm.tmAveCharWidth);
|
||||||
rc.bottom = (LONG)y * pixelRatio + tm.tmDescent;
|
rc.bottom = (LONG)(y * pixelRatio) + tm.tmDescent;
|
||||||
sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE,
|
sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE,
|
||||||
ssGetDpiForWindow(hWindow)));
|
ssGetDpiForWindow(hWindow)));
|
||||||
|
|
||||||
|
@ -1608,7 +1608,7 @@ public:
|
||||||
|
|
||||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
std::string desc, patterns;
|
std::string desc, patterns;
|
||||||
for(auto extension : extensions) {
|
for(auto &extension : extensions) {
|
||||||
std::string pattern = "*." + extension;
|
std::string pattern = "*." + extension;
|
||||||
if(!desc.empty()) desc += ", ";
|
if(!desc.empty()) desc += ", ";
|
||||||
desc += pattern;
|
desc += pattern;
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html><!--
|
||||||
|
--><head><!--
|
||||||
|
--><meta charset="utf-8"><!--
|
||||||
|
--><title>SolveSpace Web Edition (EXPERIMENTAL)</title><!--
|
||||||
|
--><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css" integrity="sha384-O8whS3fhG2OnA5Kas0Y9l3cfpmYjapjI0E4theH4iuMD+pLhbf6JI0jIMfYcK3yZ" crossorigin="anonymous"><!--
|
||||||
|
--><link rel="stylesheet" href="solvespaceui.css"><!--
|
||||||
|
--><script src="solvespaceui.js"></script><!--
|
||||||
|
--><script src="filemanagerui.js"></script><!--
|
||||||
|
--></head><!--
|
||||||
|
--><body><!--
|
||||||
|
--><div id="splash">
|
||||||
|
<div class="center">
|
||||||
|
<div id="spinner"></div>
|
||||||
|
<div id="status">Downloading...</div>
|
||||||
|
<div id="crash" style="display:none;">
|
||||||
|
SolveSpace has crashed. See console for details.<br>
|
||||||
|
The Web Edition of SolveSpace is experimental,<br>
|
||||||
|
and may not be as reliable as the Desktop Edition.<br>
|
||||||
|
<a href="javascript:window.location.reload()">Restart</a>
|
||||||
|
</div>
|
||||||
|
<progress id="progress" value="0" max="100" hidden="1"></progress>
|
||||||
|
</div>
|
||||||
|
</div><!--
|
||||||
|
--><main><!--
|
||||||
|
FIXME(emscripten): without this, a window resize is required in Chrome
|
||||||
|
to get the layout to update and canvas size to match up. What?
|
||||||
|
--><ul class="menu menubar" style="visibility: hidden"><li>None</li></ul><!--
|
||||||
|
--><div id="container"><!--
|
||||||
|
--><div id="container0"><canvas id="canvas0"></canvas></div><!--
|
||||||
|
--><div id="view_separator"></div><!--
|
||||||
|
--><div id="container1parent"><!--
|
||||||
|
--><div id="container1"><canvas id="canvas1"></canvas></div><!--
|
||||||
|
--><div id="canvas1scrollbarbox"><!--
|
||||||
|
--><div id="canvas1scrollbar"></div><!--
|
||||||
|
--></div><!--
|
||||||
|
--></div><!--
|
||||||
|
--></div><!--
|
||||||
|
--></main><!--
|
||||||
|
--><script type="text/javascript">
|
||||||
|
var splashElement = document.getElementById('splash');
|
||||||
|
var spinnerElement = document.getElementById('spinner');
|
||||||
|
var statusElement = document.getElementById('status');
|
||||||
|
var progressElement = document.getElementById('progress');
|
||||||
|
var crashElement = document.getElementById('crash');
|
||||||
|
var canvas0Element = document.getElementById('canvas0');
|
||||||
|
var canvas1Element = document.getElementById('canvas1');
|
||||||
|
|
||||||
|
canvas0Element.oncontextmenu = function(event) { event.preventDefault(); }
|
||||||
|
canvas1Element.oncontextmenu = function(event) { event.preventDefault(); }
|
||||||
|
|
||||||
|
var Module = {
|
||||||
|
preRun: [],
|
||||||
|
postRun: [],
|
||||||
|
print: console.log,
|
||||||
|
printErr: console.error,
|
||||||
|
state: 'loading',
|
||||||
|
setStatus: function(text) {
|
||||||
|
if(this.state == 'crashed') {
|
||||||
|
spinnerElement.style.display = 'none';
|
||||||
|
statusElement.style.display = 'none';
|
||||||
|
crashElement.style.display = '';
|
||||||
|
splashElement.style.display = '';
|
||||||
|
} else if(text != '') {
|
||||||
|
console.log('Status:', text);
|
||||||
|
statusElement.innerText = text;
|
||||||
|
} else if(this.state != 'done') {
|
||||||
|
console.log('Status: Done!');
|
||||||
|
splashElement.style.display = 'none';
|
||||||
|
this.state = 'done';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
totalDependencies: 0,
|
||||||
|
monitorRunDependencies: function(remainingDependencies) {
|
||||||
|
this.totalDependencies = Math.max(this.totalDependencies, remainingDependencies);
|
||||||
|
if(remainingDependencies > 0) {
|
||||||
|
var completeDependencies = this.totalDependencies - remainingDependencies;
|
||||||
|
Module.setStatus('Preparing... (' + completeDependencies + '/' +
|
||||||
|
this.totalDependencies + ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Module.setStatus('Downloading...');
|
||||||
|
window.onerror = function() {
|
||||||
|
Module.state = 'crashed';
|
||||||
|
Module.setStatus();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
</script><!--
|
||||||
|
-->{{{ SCRIPT }}}<!--
|
||||||
|
--></body></html>
|
|
@ -0,0 +1,525 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const FileManagerUI_OPEN = 0;
|
||||||
|
const FileManagerUI_SAVE = FileManagerUI_OPEN + 1;
|
||||||
|
const FileManagerUI_BROWSE = FileManagerUI_SAVE + 1;
|
||||||
|
|
||||||
|
//FIXME(emscripten): File size thresholds. How large file can we accept safely ?
|
||||||
|
|
||||||
|
/** Maximum filesize for a uploaded file.
|
||||||
|
* @type {number} */
|
||||||
|
const FileManagerUI_UPLOAD_FILE_SIZE_LIMIT = 50 * 1000 * 1000;
|
||||||
|
|
||||||
|
const tryMakeDirectory = (path) => {
|
||||||
|
try {
|
||||||
|
FS.mkdir(path);
|
||||||
|
} catch {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FileManagerUI {
|
||||||
|
/**
|
||||||
|
* @param {number} mode - dialog mode FileManagerUI_[ OPEN, SAVE, BROWSE ]
|
||||||
|
*/
|
||||||
|
constructor(mode) {
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.__isOpenDialog = false;
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.__isSaveDialog = false;
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.__isBrowseDialog = false;
|
||||||
|
|
||||||
|
if (mode == FileManagerUI_OPEN) {
|
||||||
|
this.__isOpenDialog = true;
|
||||||
|
} else if (mode == FileManagerUI_SAVE) {
|
||||||
|
this.__isSaveDialog = true;
|
||||||
|
} else {
|
||||||
|
this.__isBrowseDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {boolean} true if the dialog is shown. */
|
||||||
|
this.__isShown = false;
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
this.__extension_filters = [".slvs"];
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
this.__basePathInFilesystem = "";
|
||||||
|
|
||||||
|
/** @type {string} filename user selected. empty if nothing selected */
|
||||||
|
this.__selectedFilename = "";
|
||||||
|
|
||||||
|
this.__closedWithCancel = false;
|
||||||
|
|
||||||
|
this.__defaultFilename = "untitled";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** deconstructor
|
||||||
|
*/
|
||||||
|
dispose() {
|
||||||
|
if (this.__dialogRootElement) {
|
||||||
|
this.__dialogHeaderElement = null;
|
||||||
|
this.__descriptionElement = null;
|
||||||
|
this.__filelistElement = null;
|
||||||
|
this.__fileInputElement = null;
|
||||||
|
this.__saveFilenameInputElement = null;
|
||||||
|
this.__buttonContainerElement = null;
|
||||||
|
this.__dialogRootElement.parentElement.removeChild(this.__dialogRootElement);
|
||||||
|
this.__dialogRootElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} label
|
||||||
|
* @param {string} response
|
||||||
|
* @param {bool} isDefault
|
||||||
|
*/
|
||||||
|
__addButton(label, response, isDefault, onclick) {
|
||||||
|
const buttonElem = document.createElement("div");
|
||||||
|
addClass(buttonElem, "button");
|
||||||
|
setLabelWithMnemonic(buttonElem, label);
|
||||||
|
if (isDefault) {
|
||||||
|
addClass(buttonElem, "default");
|
||||||
|
addClass(buttonElem, "selected");
|
||||||
|
}
|
||||||
|
buttonElem.addEventListener("click", () => {
|
||||||
|
if (onclick) {
|
||||||
|
if (onclick()) {
|
||||||
|
this.__close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.__close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.__buttonContainerElement.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} div element that built
|
||||||
|
*/
|
||||||
|
buildDialog() {
|
||||||
|
const root = document.createElement('div');
|
||||||
|
addClass(root, "modal");
|
||||||
|
root.style.display = "none";
|
||||||
|
root.style.zIndex = 1000;
|
||||||
|
|
||||||
|
const dialog = document.createElement('div');
|
||||||
|
addClass(dialog, "dialog");
|
||||||
|
addClass(dialog, "wide");
|
||||||
|
root.appendChild(dialog);
|
||||||
|
|
||||||
|
const messageHeader = document.createElement('strong');
|
||||||
|
this.__dialogHeaderElement = messageHeader;
|
||||||
|
addClass(messageHeader, "dialog_header");
|
||||||
|
dialog.appendChild(messageHeader);
|
||||||
|
|
||||||
|
const description = document.createElement('p');
|
||||||
|
this.__descriptionElement = description;
|
||||||
|
dialog.appendChild(description);
|
||||||
|
|
||||||
|
const filelistheader = document.createElement('h3');
|
||||||
|
filelistheader.textContent = 'Files:';
|
||||||
|
dialog.appendChild(filelistheader);
|
||||||
|
|
||||||
|
const filelist = document.createElement('ul');
|
||||||
|
this.__filelistElement = filelist;
|
||||||
|
addClass(filelist, 'filelist');
|
||||||
|
dialog.appendChild(filelist);
|
||||||
|
|
||||||
|
const dummyfilelistitem = document.createElement('li');
|
||||||
|
dummyfilelistitem.textContent = "(No file in pseudo filesystem)";
|
||||||
|
filelist.appendChild(dummyfilelistitem);
|
||||||
|
|
||||||
|
if (this.__isOpenDialog) {
|
||||||
|
const fileuploadcontainer = document.createElement('div');
|
||||||
|
dialog.appendChild(fileuploadcontainer);
|
||||||
|
|
||||||
|
const fileuploadheader = document.createElement('h3');
|
||||||
|
fileuploadheader.textContent = "Upload file:";
|
||||||
|
fileuploadcontainer.appendChild(fileuploadheader);
|
||||||
|
|
||||||
|
const dragdropdescription = document.createElement('p');
|
||||||
|
dragdropdescription.textContent = "(Drag & drop file to the following box)";
|
||||||
|
dragdropdescription.style.fontSize = "0.8em";
|
||||||
|
dragdropdescription.style.margin = "0.1em";
|
||||||
|
fileuploadcontainer.appendChild(dragdropdescription);
|
||||||
|
|
||||||
|
const filedroparea = document.createElement('div');
|
||||||
|
addClass(filedroparea, 'filedrop');
|
||||||
|
filedroparea.addEventListener('dragstart', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
filedroparea.addEventListener('dragover', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
filedroparea.addEventListener('dragleave', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
filedroparea.addEventListener('drop', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
fileuploadcontainer.appendChild(filedroparea);
|
||||||
|
|
||||||
|
const fileinput = document.createElement('input');
|
||||||
|
this.__fileInputElement = fileinput;
|
||||||
|
fileinput.setAttribute('type', 'file');
|
||||||
|
fileinput.style.width = "100%";
|
||||||
|
fileinput.addEventListener('change', (ev) => this.__onFileInputChanged(ev));
|
||||||
|
filedroparea.appendChild(fileinput);
|
||||||
|
|
||||||
|
} else if (this.__isSaveDialog) {
|
||||||
|
const filenameinputcontainer = document.createElement('div');
|
||||||
|
dialog.appendChild(filenameinputcontainer);
|
||||||
|
|
||||||
|
const filenameinputheader = document.createElement('h3');
|
||||||
|
filenameinputheader.textContent = "Filename:";
|
||||||
|
filenameinputcontainer.appendChild(filenameinputheader);
|
||||||
|
|
||||||
|
const filenameinput = document.createElement('input');
|
||||||
|
filenameinput.setAttribute('type', 'input');
|
||||||
|
filenameinput.style.width = "90%";
|
||||||
|
filenameinput.style.margin = "auto 1em auto 1em";
|
||||||
|
this.__saveFilenameInputElement = filenameinput;
|
||||||
|
filenameinputcontainer.appendChild(filenameinput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paragraph element for spacer
|
||||||
|
dialog.appendChild(document.createElement('p'));
|
||||||
|
|
||||||
|
const buttoncontainer = document.createElement('div');
|
||||||
|
this.__buttonContainerElement = buttoncontainer;
|
||||||
|
addClass(buttoncontainer, "buttons");
|
||||||
|
dialog.appendChild(buttoncontainer);
|
||||||
|
|
||||||
|
this.__addButton('OK', 0, false, () => {
|
||||||
|
if (this.__isOpenDialog) {
|
||||||
|
let selectedFilename = null;
|
||||||
|
const fileitems = document.querySelectorAll('input[type="radio"][name="filemanager_filelist"]');
|
||||||
|
Array.from(fileitems).forEach((radiobox) => {
|
||||||
|
if (radiobox.checked) {
|
||||||
|
selectedFilename = radiobox.parentElement.getAttribute('data-filename');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (selectedFilename) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.__addButton('Cancel', 1, true, () => {
|
||||||
|
this.__closedWithCancel = true;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
setTitle(text) {
|
||||||
|
this.__dialogHeaderText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
setDescription(text) {
|
||||||
|
this.__descriptionText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path file prefix. (ex) 'tmp/' to '/tmp/filename.txt'
|
||||||
|
*/
|
||||||
|
setBasePath(path) {
|
||||||
|
this.__basePathInFilesystem = path;
|
||||||
|
tryMakeDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filename
|
||||||
|
*/
|
||||||
|
setDefaultFilename(filename) {
|
||||||
|
this.__defaultFilename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} filter comma-separated extensions like ".slvs,.stl;."
|
||||||
|
*/
|
||||||
|
setFilter(filter) {
|
||||||
|
const exts = filter.split(',');
|
||||||
|
this.__extension_filters = exts;
|
||||||
|
}
|
||||||
|
|
||||||
|
__buildFileEntry(filename) {
|
||||||
|
const lielem = document.createElement('li');
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.setAttribute('data-filename', filename);
|
||||||
|
lielem.appendChild(label);
|
||||||
|
const radiobox = document.createElement('input');
|
||||||
|
radiobox.setAttribute('type', 'radio');
|
||||||
|
if (!this.__isOpenDialog) {
|
||||||
|
radiobox.style.display = "none";
|
||||||
|
}
|
||||||
|
radiobox.setAttribute('name', 'filemanager_filelist');
|
||||||
|
label.appendChild(radiobox);
|
||||||
|
const filenametext = document.createTextNode(filename);
|
||||||
|
label.appendChild(filenametext);
|
||||||
|
|
||||||
|
return lielem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string[]} filename array
|
||||||
|
*/
|
||||||
|
__getFileEntries() {
|
||||||
|
const basePath = this.__basePathInFilesystem;
|
||||||
|
/** @type {any[]} */
|
||||||
|
const nodes = FS.readdir(basePath);
|
||||||
|
/** @type {string[]} */
|
||||||
|
const files = nodes.filter((nodename) => {
|
||||||
|
return FS.isFile(FS.lstat(basePath + nodename).mode);
|
||||||
|
});
|
||||||
|
/*.map((filename) => {
|
||||||
|
return basePath + filename;
|
||||||
|
});*/
|
||||||
|
console.log(`__getFileEntries():`, files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]?} files file list already constructed
|
||||||
|
* @returns {string[]} filename array
|
||||||
|
*/
|
||||||
|
__getFileEntries_recurse(basePath) {
|
||||||
|
//FIXME:remove try catch block
|
||||||
|
try {
|
||||||
|
//const basePath = this.__basePathInFilesystem;
|
||||||
|
FS.currentPath = basePath;
|
||||||
|
/** @type {any[]} */
|
||||||
|
const nodes = FS.readdir(basePath);
|
||||||
|
|
||||||
|
const filesInThisDirectory = nodes.filter((nodename) => {
|
||||||
|
return FS.isFile(FS.lstat(basePath + "/" + nodename).mode);
|
||||||
|
}).map((filename) => {
|
||||||
|
return basePath + "/" + filename;
|
||||||
|
});
|
||||||
|
let files = filesInThisDirectory;
|
||||||
|
|
||||||
|
const directories = nodes.filter((nodename) => {
|
||||||
|
return FS.isDir(FS.lstat(basePath + "/" + nodename).mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < directories.length; i++) {
|
||||||
|
const directoryname = directories[i];
|
||||||
|
if (directoryname == '.' || directoryname == '..') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const orig_cwd = FS.currentPath;
|
||||||
|
const directoryfullpath = basePath + "/" + directoryname;
|
||||||
|
FS.currentPath = directoryfullpath;
|
||||||
|
files = files.concat(this.__getFileEntries_recurse(directoryfullpath));
|
||||||
|
FS.currentPath = orig_cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`__getFileEntries_recurse(): in "${basePath}"`, files);
|
||||||
|
return files;
|
||||||
|
|
||||||
|
} catch (excep) {
|
||||||
|
console.log(excep);
|
||||||
|
throw excep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__updateFileList() {
|
||||||
|
console.log(`__updateFileList()`);
|
||||||
|
Array.from(this.__filelistElement.children).forEach((elem) => {
|
||||||
|
this.__filelistElement.removeChild(elem);
|
||||||
|
});
|
||||||
|
// const files = this.__getFileEntries();
|
||||||
|
FS.currentPath = this.__basePathInFilesystem;
|
||||||
|
const files = this.__getFileEntries_recurse(this.__basePathInFilesystem);
|
||||||
|
if (files.length < 1) {
|
||||||
|
const dummyfilelistitem = document.createElement('li');
|
||||||
|
dummyfilelistitem.textContent = "(No file in pseudo filesystem)";
|
||||||
|
this.__filelistElement.appendChild(dummyfilelistitem);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
files.forEach((entry) => {
|
||||||
|
this.__filelistElement.appendChild(this.__buildFileEntry(entry));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {File} file
|
||||||
|
*/
|
||||||
|
__getFileAsArrayBuffer(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const filereader = new FileReader();
|
||||||
|
filereader.onerror = (ev) => {
|
||||||
|
reject(ev);
|
||||||
|
};
|
||||||
|
filereader.onload = (ev) => {
|
||||||
|
resolve(ev.target.result);
|
||||||
|
};
|
||||||
|
filereader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
*/
|
||||||
|
async __tryAddFile(file) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if (!file) {
|
||||||
|
reject(new Error(`Invalid arg: file is ${file}`));
|
||||||
|
|
||||||
|
} else if (file.size > FileManagerUI_UPLOAD_FILE_SIZE_LIMIT) {
|
||||||
|
//FIXME(emscripten): Use our MessageDialog instead of browser's alert().
|
||||||
|
alert(`Specified file is larger than limit of ${FileManagerUI_UPLOAD_FILE_SIZE_LIMIT} bytes. Canceced.`);
|
||||||
|
reject(new Error(`File is too large: "${file.name} is ${file.size} bytes`));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Just add to Filesystem
|
||||||
|
const path = `${this.__basePathInFilesystem}${file.name}`;
|
||||||
|
const blobArrayBuffer = await this.__getFileAsArrayBuffer(file);
|
||||||
|
const u8array = new Uint8Array(blobArrayBuffer);
|
||||||
|
const fs = FS.open(path, "w");
|
||||||
|
FS.write(fs, u8array, 0, u8array.length, 0);
|
||||||
|
FS.close(fs);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
__addSelectedFile() {
|
||||||
|
if (this.__fileInputElement.files.length < 1) {
|
||||||
|
console.warn(`No file selected.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = this.__fileInputElement.files[0];
|
||||||
|
this.__tryAddFile(file)
|
||||||
|
.then(() => {
|
||||||
|
this.__updateFileList();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.__fileInputElement.value = null;
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DragEvent} ev
|
||||||
|
*/
|
||||||
|
__onFileDragDrop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (ev.type == "dragenter" || ev.type == "dragover" || ev.type == "dragleave") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.dataTransfer.files.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.__fileInputElement.files = ev.dataTransfer.files;
|
||||||
|
|
||||||
|
this.__addSelectedFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {InputEvent} _ev
|
||||||
|
*/
|
||||||
|
__onFileInputChanged(_ev) {
|
||||||
|
this.__addSelectedFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Show the FileManager UI dialog */
|
||||||
|
__show() {
|
||||||
|
this.__closedWithCancel = false;
|
||||||
|
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
this.__dialogRootElement = this.buildDialog();
|
||||||
|
document.querySelector('body').appendChild(this.__dialogRootElement);
|
||||||
|
|
||||||
|
this.__dialogHeaderElement.textContent = this.__dialogHeaderText || "File manager";
|
||||||
|
this.__descriptionElement.textContent = this.__descriptionText || "Select a file.";
|
||||||
|
if (this.__extension_filters) {
|
||||||
|
this.__descriptionElement.textContent += "Requested filter is " + this.__extension_filters.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.__isOpenDialog && this.__extension_filters) {
|
||||||
|
this.__fileInputElement.accept = this.__extension_filters.concat(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.__isSaveDialog) {
|
||||||
|
this.__saveFilenameInputElement.value = this.__defaultFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__dialogRootElement.style.display = "block";
|
||||||
|
this.__isShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Close the dialog */
|
||||||
|
__close() {
|
||||||
|
this.__selectedFilename = "";
|
||||||
|
if (this.__isOpenDialog) {
|
||||||
|
Array.from(document.querySelectorAll('input[type="radio"][name="filemanager_filelist"]'))
|
||||||
|
.forEach((elem) => {
|
||||||
|
if (elem.checked) {
|
||||||
|
this.__selectedFilename = elem.parentElement.getAttribute("data-filename");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.__isSaveDialog) {
|
||||||
|
if (!this.__closedWithCancel) {
|
||||||
|
this.__selectedFilename = this.__saveFilenameInputElement.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(this.__filelistElement.children).forEach((elem) => {
|
||||||
|
this.__filelistElement.removeChild(elem);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dispose();
|
||||||
|
|
||||||
|
this.__isShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isShown() {
|
||||||
|
return this.__isShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {Promise} filename string on resolved.
|
||||||
|
*/
|
||||||
|
showModalAsync() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.__show();
|
||||||
|
this.__updateFileList();
|
||||||
|
const intervalTimer = setInterval(() => {
|
||||||
|
if (!this.isShown()) {
|
||||||
|
clearInterval(intervalTimer);
|
||||||
|
resolve(this.__selectedFilename);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedFilename() {
|
||||||
|
return this.__selectedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.__show();
|
||||||
|
this.__updateFileList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
window.FileManagerUI = FileManagerUI;
|
|
@ -0,0 +1,344 @@
|
||||||
|
* {
|
||||||
|
font-family: sans;
|
||||||
|
}
|
||||||
|
html, body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
html, body, canvas, #splash, #container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splashscreen */
|
||||||
|
#splash {
|
||||||
|
z-index: 1000;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#splash .center {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#splash a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#spinner {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
margin: 0px auto;
|
||||||
|
border-left: 10px solid rgb(255, 255, 255);
|
||||||
|
border-top: 10px solid rgb(0, 255, 0);
|
||||||
|
border-right: 10px solid rgb(255, 0, 255);
|
||||||
|
border-bottom: 10px solid rgb(0, 255, 0);
|
||||||
|
border-radius: 100%;
|
||||||
|
animation: rotation 3s linear infinite;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
@keyframes rotation {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid layout for main */
|
||||||
|
main {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* Use CSS Grid layout for vertical placement. */
|
||||||
|
display: grid;
|
||||||
|
/* Row 0 for menubar (fit to content), Row 1 for canvas0, canvas1 (rest of space) */
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.button {
|
||||||
|
border: 1px solid hsl(0, 0%, 60%);
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.button.selected {
|
||||||
|
background: hsl(0, 0%, 20%);
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background: hsl(0, 0%, 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editors */
|
||||||
|
.editor {
|
||||||
|
position: fixed;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menus */
|
||||||
|
.menu {
|
||||||
|
font-size: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
list-style-type: none;
|
||||||
|
background: hsl(0, 0%, 20%);
|
||||||
|
color: white;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normal menu items */
|
||||||
|
.menu > li {
|
||||||
|
z-index: 100;
|
||||||
|
font-size: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 19px;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.menu > li::before, .menu > li::after {
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.menu > li.hover,
|
||||||
|
.menu > li.selected,
|
||||||
|
.menu.menubar > li:hover:not(.selected) {
|
||||||
|
background: hsl(0, 0%, 30%);
|
||||||
|
}
|
||||||
|
.menu > li.disabled {
|
||||||
|
color: hsl(0, 0%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check and radio menu items */
|
||||||
|
.menu > li {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
.menu > li::before {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: 0px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
.menu > li.check::before {
|
||||||
|
content: '\f0c8';
|
||||||
|
}
|
||||||
|
.menu > li.check.active::before {
|
||||||
|
content: '\f14a';
|
||||||
|
}
|
||||||
|
.menu > li.radio::before {
|
||||||
|
content: '\f111';
|
||||||
|
}
|
||||||
|
.menu > li.radio.active::before {
|
||||||
|
content: '\f192';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separator menu items */
|
||||||
|
.menu > li.separator {
|
||||||
|
height: 0px;
|
||||||
|
border-top: 1px solid hsl(0, 0%, 30%);
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accelerators */
|
||||||
|
.menu > li > .accel {
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submenus */
|
||||||
|
.menu > li > .menu,
|
||||||
|
.menu.popup {
|
||||||
|
display: none;
|
||||||
|
white-space: normal;
|
||||||
|
padding-right: 31px;
|
||||||
|
}
|
||||||
|
.menu > li.has-submenu::after {
|
||||||
|
content: '\f0da';
|
||||||
|
}
|
||||||
|
.menu > li.selected > .menu,
|
||||||
|
.menu > li.hover > .menu,
|
||||||
|
.menu.popup {
|
||||||
|
display: block;
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
border: 1px solid hsl(0, 0%, 30%);
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popup menus */
|
||||||
|
.menu.popup {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menubars */
|
||||||
|
.menubar {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.menubar > li {
|
||||||
|
width: auto;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.menubar > li.selected {
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
border: 1px solid hsl(0, 0%, 30%);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.menubar.menu > li.selected > .menu {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: -1px;
|
||||||
|
top: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal popups */
|
||||||
|
.modal {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: hsla(0, 0%, 0%, 60%);
|
||||||
|
}
|
||||||
|
.modal > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dialogs */
|
||||||
|
.dialog {
|
||||||
|
border: 1px solid hsl(0, 0%, 30%);
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 400px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-height: 70%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.dialog.wide {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
.dialog > .buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
.dialog .filedrop {
|
||||||
|
margin: 1em 0 1em 0;
|
||||||
|
padding: 1em;
|
||||||
|
border: 2px solid black;
|
||||||
|
background-color: hsl(0, 0%, 50%);
|
||||||
|
}
|
||||||
|
.dialog .filelist {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.dialog .filelist li {
|
||||||
|
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonics */
|
||||||
|
.label > u {
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
body.mnemonic .label > u {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvases */
|
||||||
|
canvas {
|
||||||
|
border: 0px none;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
/* FIXME(emscripten): this should be dynamically adjustable, not hardcoded in CSS */
|
||||||
|
#container0 {
|
||||||
|
flex-basis: 80%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container1parent {
|
||||||
|
flex-basis: 20%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 410px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 19px;
|
||||||
|
grid-template-rows: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container1 {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas1scrollbarbox {
|
||||||
|
/* 19px is a magic number for scrollbar width (Yes, this is platform-dependent value but looks almost working.) */
|
||||||
|
width: 19px;
|
||||||
|
min-width: 19px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
-webkit-overflow-scrolling: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas1scrollbar {
|
||||||
|
/* 0px will disable the scrollbar by browser. */
|
||||||
|
width: 1px;
|
||||||
|
/* Disable scrollbar as default. This value will be overwritten by program. */
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view_separator {
|
||||||
|
width: 4px;
|
||||||
|
background: hsl(0, 0%, 20%);
|
||||||
|
}
|
|
@ -0,0 +1,813 @@
|
||||||
|
function isModal() {
|
||||||
|
var hasModal = !!document.querySelector('.modal');
|
||||||
|
var hasMenuBar = !!document.querySelector('.menubar .selected');
|
||||||
|
var hasPopupMenu = !!document.querySelector('.menu.popup');
|
||||||
|
return hasModal || hasMenuBar || hasPopupMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* String helpers */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s - original string
|
||||||
|
* @param {number} digits - char length of generating string
|
||||||
|
* @param {string} ch - string to be used for padding
|
||||||
|
* @return {string} generated string ($digits chars length) or $s
|
||||||
|
*/
|
||||||
|
function stringPadLeft(s, digits, ch) {
|
||||||
|
if (s.length > digits) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
for (let i = s.length; i < digits; i++) {
|
||||||
|
s = ch + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generate a string expression of now
|
||||||
|
* @return {string} like a "2022_08_31_2245" string (for 2022-08-31 22:45; local time)
|
||||||
|
*/
|
||||||
|
function GetCurrentDateTimeString() {
|
||||||
|
const now = new Date();
|
||||||
|
const padLeft2 = (num) => { return stringPadLeft(num.toString(), 2, '0') };
|
||||||
|
return (`${now.getFullYear()}_${padLeft2(now.getMonth()+1)}_${padLeft2(now.getDate())}` +
|
||||||
|
`_` + `${padLeft2(now.getHours())}${padLeft2(now.getMinutes())}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CSS helpers */
|
||||||
|
function hasClass(element, className) {
|
||||||
|
return element.classList.contains(className);
|
||||||
|
}
|
||||||
|
function addClass(element, className) {
|
||||||
|
element.classList.add(className);
|
||||||
|
}
|
||||||
|
function removeClass(element, className) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
}
|
||||||
|
function removeClassFromAllChildren(element, className) {
|
||||||
|
element.querySelectorAll('.' + className).forEach(function(element) {
|
||||||
|
removeClass(element, className);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonic helpers */
|
||||||
|
function setLabelWithMnemonic(element, labelText) {
|
||||||
|
var label = document.createElement('span');
|
||||||
|
addClass(label, 'label');
|
||||||
|
element.appendChild(label);
|
||||||
|
|
||||||
|
var matches = labelText.match('(.*?)&(.)(.*)?');
|
||||||
|
if(matches) {
|
||||||
|
label.appendChild(document.createTextNode(matches[1]));
|
||||||
|
if(matches[2]) {
|
||||||
|
var mnemonic = document.createElement('u');
|
||||||
|
mnemonic.innerText = matches[2];
|
||||||
|
label.appendChild(mnemonic);
|
||||||
|
addClass(element, 'mnemonic-Key' + matches[2].toUpperCase());
|
||||||
|
}
|
||||||
|
if(matches[3]) {
|
||||||
|
label.appendChild(document.createTextNode(matches[3]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label.appendChild(document.createTextNode(labelText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Touchevent helper
|
||||||
|
* @param {TouchEvent} event
|
||||||
|
* @return {boolean} true if same element is target of touchstart and touchend
|
||||||
|
*/
|
||||||
|
function isSameElementOnTouchstartAndTouchend(event) {
|
||||||
|
const elementOnTouchStart = event.target;
|
||||||
|
const elementOnTouchEnd = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
||||||
|
return elementOnTouchStart == elementOnTouchEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button helpers */
|
||||||
|
function isButton(element) {
|
||||||
|
return hasClass(element, 'button');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button DOM traversal helpers */
|
||||||
|
function getButton(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(element.tagName == 'U') {
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
if(hasClass(element, 'label')) {
|
||||||
|
return getButton(element.parentElement);
|
||||||
|
} else if(isButton(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button behavior */
|
||||||
|
window.addEventListener('click', function(event) {
|
||||||
|
var button = getButton(event.target);
|
||||||
|
if(button) {
|
||||||
|
button.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("touchend", (event) => {
|
||||||
|
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const button = getButton(event.target);
|
||||||
|
if (button) {
|
||||||
|
button.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var selected = document.querySelector('.button.selected');
|
||||||
|
if(!selected) return;
|
||||||
|
|
||||||
|
var outSelected, newSelected;
|
||||||
|
if(event.key == 'ArrowRight') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.nextElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
} else if(event.key == 'ArrowLeft') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.previousElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.lastElementChild;
|
||||||
|
}
|
||||||
|
} else if(event.key == 'Enter') {
|
||||||
|
selected.dispatchEvent(new Event('trigger'));
|
||||||
|
} else if(event.key == 'Escape' && hasClass(selected, 'default')) {
|
||||||
|
selected.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outSelected) removeClass(outSelected, 'selected');
|
||||||
|
if(newSelected) addClass(newSelected, 'selected');
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Editor helpers */
|
||||||
|
function isEditor(element) {
|
||||||
|
return hasClass(element, 'editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor DOM traversal helpers */
|
||||||
|
function getEditor(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(isEditor(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor behavior */
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var editor = getEditor(event.target);
|
||||||
|
if(editor) {
|
||||||
|
if(event.key == 'Enter') {
|
||||||
|
editor.dispatchEvent(new Event('trigger'));
|
||||||
|
} else if(event.key == 'Escape') {
|
||||||
|
editor.style.display = 'none';
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}, {capture: true});
|
||||||
|
|
||||||
|
/* Menu helpers */
|
||||||
|
function isMenubar(element) {
|
||||||
|
return hasClass(element, 'menubar');
|
||||||
|
}
|
||||||
|
function isMenu(element) {
|
||||||
|
return hasClass(element, 'menu');
|
||||||
|
}
|
||||||
|
function isPopupMenu(element) {
|
||||||
|
return isMenu(element) && hasClass(element, 'popup')
|
||||||
|
}
|
||||||
|
function hasSubmenu(menuItem) {
|
||||||
|
return !!menuItem.querySelector('.menu');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu item helpers */
|
||||||
|
function isMenuItemSelectable(menuItem) {
|
||||||
|
return !(hasClass(menuItem, 'disabled') || hasClass(menuItem, 'separator'));
|
||||||
|
}
|
||||||
|
function isMenuItemSelected(menuItem) {
|
||||||
|
return hasClass(menuItem, 'selected') || hasClass(menuItem, 'hover');
|
||||||
|
}
|
||||||
|
function deselectMenuItem(menuItem) {
|
||||||
|
removeClass(menuItem, 'selected');
|
||||||
|
removeClass(menuItem, 'hover');
|
||||||
|
removeClassFromAllChildren(menuItem, 'selected');
|
||||||
|
removeClassFromAllChildren(menuItem, 'hover');
|
||||||
|
}
|
||||||
|
function selectMenuItem(menuItem) {
|
||||||
|
var menu = menuItem.parentElement;
|
||||||
|
removeClassFromAllChildren(menu, 'selected');
|
||||||
|
removeClassFromAllChildren(menu, 'hover');
|
||||||
|
if(isMenubar(menu)) {
|
||||||
|
addClass(menuItem, 'selected');
|
||||||
|
} else {
|
||||||
|
addClass(menuItem, 'hover');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function triggerMenuItem(menuItem) {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
if(hasSubmenu(menuItem)) {
|
||||||
|
selectMenuItem(menuItem.querySelector('li:first-child'));
|
||||||
|
} else {
|
||||||
|
var parent = menuItem.parentElement;
|
||||||
|
while(!isMenubar(parent) && !isPopupMenu(parent)) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
removeClassFromAllChildren(parent, 'selected');
|
||||||
|
removeClassFromAllChildren(parent, 'hover');
|
||||||
|
if(isPopupMenu(parent)) {
|
||||||
|
parent.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItem.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu DOM traversal helpers */
|
||||||
|
function getMenuItem(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(element.tagName == 'U') {
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
if(hasClass(element, 'label')) {
|
||||||
|
return getMenuItem(element.parentElement);
|
||||||
|
} else if(element.tagName == 'LI' && isMenu(element.parentElement)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getMenu(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(isMenu(element)) {
|
||||||
|
return element;
|
||||||
|
} else {
|
||||||
|
var menuItem = getMenuItem(element);
|
||||||
|
if(menuItem && isMenu(menuItem.parentElement)) {
|
||||||
|
return menuItem.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu behavior */
|
||||||
|
window.addEventListener('click', function(event) {
|
||||||
|
var menuItem = getMenuItem(event.target);
|
||||||
|
var menu = getMenu(menuItem);
|
||||||
|
if(menu && isMenubar(menu)) {
|
||||||
|
if(hasClass(menuItem, 'selected')) {
|
||||||
|
removeClass(menuItem, 'selected');
|
||||||
|
} else {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
} else if(menu) {
|
||||||
|
if(!hasSubmenu(menuItem)) {
|
||||||
|
triggerMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.menu .selected, .menu .hover')
|
||||||
|
.forEach(function(menuItem) {
|
||||||
|
deselectMenuItem(menuItem);
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.menu.popup')
|
||||||
|
.forEach(function(menu) {
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("touchend", (event) => {
|
||||||
|
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var menuItem = getMenuItem(event.target);
|
||||||
|
var menu = getMenu(menuItem);
|
||||||
|
if(menu && isMenubar(menu)) {
|
||||||
|
if(hasClass(menuItem, 'selected')) {
|
||||||
|
removeClass(menuItem, 'selected');
|
||||||
|
} else {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if(menu) {
|
||||||
|
if(!hasSubmenu(menuItem)) {
|
||||||
|
triggerMenuItem(menuItem);
|
||||||
|
} else {
|
||||||
|
addClass(menuItem, "selected");
|
||||||
|
addClass(menuItem, "hover");
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.menu .selected, .menu .hover')
|
||||||
|
.forEach(function(menuItem) {
|
||||||
|
deselectMenuItem(menuItem);
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.menu.popup')
|
||||||
|
.forEach(function(menu) {
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('mouseover', function(event) {
|
||||||
|
var menuItem = getMenuItem(event.target);
|
||||||
|
var menu = getMenu(menuItem);
|
||||||
|
if(menu) {
|
||||||
|
var selected = menu.querySelectorAll('.selected, .hover');
|
||||||
|
if(isMenubar(menu)) {
|
||||||
|
if(selected.length > 0) {
|
||||||
|
selected.forEach(function(menuItem) {
|
||||||
|
if(selected != menuItem) {
|
||||||
|
deselectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addClass(menuItem, 'selected');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(isMenuItemSelectable(menuItem)) {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var allSelected = document.querySelectorAll('.menubar .selected, .menubar .hover,' +
|
||||||
|
'.menu.popup .selected, .menu.popup .hover');
|
||||||
|
if(allSelected.length == 0) return;
|
||||||
|
|
||||||
|
var selected = allSelected[allSelected.length - 1];
|
||||||
|
var outSelected, newSelected;
|
||||||
|
var isMenubarItem = isMenubar(getMenu(selected));
|
||||||
|
|
||||||
|
if(isMenubarItem && event.key == 'ArrowRight' ||
|
||||||
|
!isMenubarItem && event.key == 'ArrowDown') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.nextElementSibling;
|
||||||
|
while(newSelected && !isMenuItemSelectable(newSelected)) {
|
||||||
|
newSelected = newSelected.nextElementSibling;
|
||||||
|
}
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
} else if(isMenubarItem && event.key == 'ArrowLeft' ||
|
||||||
|
!isMenubarItem && event.key == 'ArrowUp') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.previousElementSibling;
|
||||||
|
while(newSelected && !isMenuItemSelectable(newSelected)) {
|
||||||
|
newSelected = newSelected.previousElementSibling;
|
||||||
|
}
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.lastElementChild;
|
||||||
|
}
|
||||||
|
} else if(!isMenubarItem && event.key == 'ArrowRight') {
|
||||||
|
if(hasSubmenu(selected)) {
|
||||||
|
selectMenuItem(selected.querySelector('li:first-child'));
|
||||||
|
} else {
|
||||||
|
outSelected = allSelected[0];
|
||||||
|
newSelected = outSelected.nextElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(!isMenubarItem && event.key == 'ArrowLeft') {
|
||||||
|
if(allSelected.length > 2) {
|
||||||
|
outSelected = selected;
|
||||||
|
} else {
|
||||||
|
outSelected = allSelected[0];
|
||||||
|
newSelected = outSelected.previousElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.lastElementChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(isMenubarItem && event.key == 'ArrowDown') {
|
||||||
|
newSelected = selected.querySelector('li:first-child');
|
||||||
|
} else if(event.key == 'Enter') {
|
||||||
|
triggerMenuItem(selected);
|
||||||
|
} else if(event.key == 'Escape') {
|
||||||
|
outSelected = allSelected[0];
|
||||||
|
} else {
|
||||||
|
var withMnemonic = getMenu(selected).querySelector('.mnemonic-' + event.key);
|
||||||
|
if(withMnemonic) {
|
||||||
|
triggerMenuItem(withMnemonic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outSelected) deselectMenuItem(outSelected);
|
||||||
|
if(newSelected) selectMenuItem(newSelected);
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Mnemonic behavior */
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var withMnemonic;
|
||||||
|
if(event.altKey && event.key == 'Alt') {
|
||||||
|
addClass(document.body, 'mnemonic');
|
||||||
|
} else if(!isModal() && event.altKey && (withMnemonic =
|
||||||
|
document.querySelector('.menubar > .mnemonic-' + event.code))) {
|
||||||
|
triggerMenuItem(withMnemonic);
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
removeClass(document.body, 'mnemonic');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', function(event) {
|
||||||
|
if(event.key == 'Alt') {
|
||||||
|
removeClass(document.body, 'mnemonic');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME(emscripten): Should be implemented in guihtmlcpp ?
|
||||||
|
class FileUploadHelper {
|
||||||
|
constructor() {
|
||||||
|
this.modalRoot = document.createElement("div");
|
||||||
|
addClass(this.modalRoot, "modal");
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
this.modalRoot.style.zIndex = 1000;
|
||||||
|
|
||||||
|
this.dialogRoot = document.createElement("div");
|
||||||
|
addClass(this.dialogRoot, "dialog");
|
||||||
|
this.modalRoot.appendChild(this.dialogRoot);
|
||||||
|
|
||||||
|
this.messageHeader = document.createElement("strong");
|
||||||
|
this.dialogRoot.appendChild(this.messageHeader);
|
||||||
|
|
||||||
|
this.descriptionParagraph = document.createElement("p");
|
||||||
|
this.dialogRoot.appendChild(this.descriptionParagraph);
|
||||||
|
|
||||||
|
this.currentFileListHeader = document.createElement("p");
|
||||||
|
this.currentFileListHeader.textContent = "Current uploaded files:";
|
||||||
|
this.dialogRoot.appendChild(this.currentFileListHeader);
|
||||||
|
|
||||||
|
this.currentFileList = document.createElement("div");
|
||||||
|
this.dialogRoot.appendChild(this.currentFileList);
|
||||||
|
|
||||||
|
this.fileInputContainer = document.createElement("div");
|
||||||
|
|
||||||
|
this.fileInputElement = document.createElement("input");
|
||||||
|
this.fileInputElement.setAttribute("type", "file");
|
||||||
|
this.fileInputElement.addEventListener("change", (ev)=> this.onFileInputChanged(ev));
|
||||||
|
this.fileInputContainer.appendChild(this.fileInputElement);
|
||||||
|
|
||||||
|
this.dialogRoot.appendChild(this.fileInputContainer);
|
||||||
|
|
||||||
|
this.buttonHolder = document.createElement("div");
|
||||||
|
addClass(this.buttonHolder, "buttons");
|
||||||
|
this.dialogRoot.appendChild(this.buttonHolder);
|
||||||
|
|
||||||
|
this.AddButton("OK", 0, false);
|
||||||
|
this.AddButton("Cancel", 1, true);
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
|
||||||
|
document.querySelector("body").appendChild(this.modalRoot);
|
||||||
|
|
||||||
|
this.currentFilename = null;
|
||||||
|
|
||||||
|
// FIXME(emscripten): For debugging
|
||||||
|
this.title = "";
|
||||||
|
this.filename = "";
|
||||||
|
this.filters = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
document.querySelector("body").removeChild(this.modalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddButton(label, response, isDefault) {
|
||||||
|
// FIXME(emscripten): implement
|
||||||
|
const buttonElem = document.createElement("div");
|
||||||
|
addClass(buttonElem, "button");
|
||||||
|
setLabelWithMnemonic(buttonElem, label);
|
||||||
|
if (isDefault) {
|
||||||
|
addClass(buttonElem, "default");
|
||||||
|
addClass(buttonElem, "selected");
|
||||||
|
}
|
||||||
|
buttonElem.addEventListener("click", () => {
|
||||||
|
this.closeDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.buttonHolder.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileEntries() {
|
||||||
|
const basePath = '/';
|
||||||
|
/** @type {Array<object} */
|
||||||
|
const nodes = FS.readdir(basePath);
|
||||||
|
const files = nodes.filter((nodename) => {
|
||||||
|
return FS.isFile(FS.lstat(basePath + nodename).mode);
|
||||||
|
}).map((filename) => {
|
||||||
|
return basePath + filename;
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateFileList() {
|
||||||
|
let filepaths = this.getFileEntries();
|
||||||
|
const listElem = document.createElement("ul");
|
||||||
|
for (let i = 0; i < filepaths.length; i++) {
|
||||||
|
const listitemElem = document.createElement("li");
|
||||||
|
const stat = FS.lstat(filepaths[i]);
|
||||||
|
const text = `"${filepaths[i]}" (${stat.size} bytes)`;
|
||||||
|
listitemElem.textContent = text;
|
||||||
|
listElem.appendChild(listitemElem);
|
||||||
|
}
|
||||||
|
return listElem;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFileList() {
|
||||||
|
this.currentFileList.innerHTML = "";
|
||||||
|
this.currentFileList.appendChild(this.generateFileList());
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileInputChanged(ev) {
|
||||||
|
const selectedFiles = ev.target.files;
|
||||||
|
if (selectedFiles.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedFile = selectedFiles[0];
|
||||||
|
const selectedFilename = selectedFile.name;
|
||||||
|
this.filename = selectedFilename;
|
||||||
|
this.currentFilename = selectedFilename;
|
||||||
|
|
||||||
|
// Prepare FileReader
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
const fileReaderReadAsArrayBufferPromise = new Promise((resolve, reject) => {
|
||||||
|
fileReader.addEventListener("load", (ev) => {
|
||||||
|
resolve(ev.target.result);
|
||||||
|
});
|
||||||
|
fileReader.addEventListener("abort", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
fileReader.readAsArrayBuffer(selectedFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileReaderReadAsArrayBufferPromise
|
||||||
|
.then((arrayBuffer) => {
|
||||||
|
// Write selected file to FS
|
||||||
|
console.log(`Write uploaded file blob to filesystem. "${selectedFilename}" (${arrayBuffer.byteLength} bytes)`);
|
||||||
|
const u8array = new Uint8Array(arrayBuffer);
|
||||||
|
const fs = FS.open("/" + selectedFilename, "w");
|
||||||
|
FS.write(fs, u8array, 0, u8array.length, 0);
|
||||||
|
FS.close(fs);
|
||||||
|
|
||||||
|
// Update file list in dialog
|
||||||
|
this.updateFileList();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error while fileReader.readAsArrayBuffer():", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog() {
|
||||||
|
this.updateFileList();
|
||||||
|
|
||||||
|
this.is_shown = true;
|
||||||
|
this.modalRoot.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.is_shown = false;
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME(emscripten): Workaround
|
||||||
|
function createFileUploadHelperInstance() {
|
||||||
|
return new FileUploadHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(emscripten): Should be implemented in guihtmlcpp ?
|
||||||
|
class FileDownloadHelper {
|
||||||
|
constructor() {
|
||||||
|
this.modalRoot = document.createElement("div");
|
||||||
|
addClass(this.modalRoot, "modal");
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
this.modalRoot.style.zIndex = 1000;
|
||||||
|
|
||||||
|
this.dialogRoot = document.createElement("div");
|
||||||
|
addClass(this.dialogRoot, "dialog");
|
||||||
|
this.modalRoot.appendChild(this.dialogRoot);
|
||||||
|
|
||||||
|
this.messageHeader = document.createElement("strong");
|
||||||
|
this.dialogRoot.appendChild(this.messageHeader);
|
||||||
|
|
||||||
|
this.descriptionParagraph = document.createElement("p");
|
||||||
|
this.dialogRoot.appendChild(this.descriptionParagraph);
|
||||||
|
|
||||||
|
this.buttonHolder = document.createElement("div");
|
||||||
|
addClass(this.buttonHolder, "buttons");
|
||||||
|
this.dialogRoot.appendChild(this.buttonHolder);
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
|
||||||
|
document.querySelector("body").appendChild(this.modalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
document.querySelector("body").removeChild(this.modalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddButton(label, response, isDefault) {
|
||||||
|
// FIXME(emscripten): implement
|
||||||
|
const buttonElem = document.createElement("div");
|
||||||
|
addClass(buttonElem, "button");
|
||||||
|
setLabelWithMnemonic(buttonElem, label);
|
||||||
|
if (isDefault) {
|
||||||
|
addClass(buttonElem, "default");
|
||||||
|
addClass(buttonElem, "selected");
|
||||||
|
}
|
||||||
|
buttonElem.addEventListener("click", () => {
|
||||||
|
this.closeDialog();
|
||||||
|
this.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.buttonHolder.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
createBlobURLFromArrayBuffer(arrayBuffer) {
|
||||||
|
const u8array = new Uint8Array(arrayBuffer);
|
||||||
|
let dataUrl = "data:application/octet-stream;base64,";
|
||||||
|
let binaryString = "";
|
||||||
|
for (let i = 0; i < u8array.length; i++) {
|
||||||
|
binaryString += String.fromCharCode(u8array[i]);
|
||||||
|
}
|
||||||
|
dataUrl += btoa(binaryString);
|
||||||
|
|
||||||
|
return dataUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareFile(filename) {
|
||||||
|
this.messageHeader.textContent = "Your file ready";
|
||||||
|
|
||||||
|
const stat = FS.lstat(filename);
|
||||||
|
const filesize = stat.size;
|
||||||
|
const fs = FS.open(filename, "r");
|
||||||
|
const readbuffer = new Uint8Array(filesize);
|
||||||
|
FS.read(fs, readbuffer, 0, filesize, 0);
|
||||||
|
FS.close(fs);
|
||||||
|
|
||||||
|
const blobURL = this.createBlobURLFromArrayBuffer(readbuffer.buffer);
|
||||||
|
|
||||||
|
this.descriptionParagraph.innerHTML = "";
|
||||||
|
const linkElem = document.createElement("a");
|
||||||
|
//let downloadfilename = "solvespace_browser-";
|
||||||
|
//downloadfilename += `${GetCurrentDateTimeString()}.slvs`;
|
||||||
|
let downloadfilename = filename;
|
||||||
|
linkElem.setAttribute("download", downloadfilename);
|
||||||
|
linkElem.setAttribute("href", blobURL);
|
||||||
|
// WORKAROUND: FIXME(emscripten)
|
||||||
|
linkElem.style.color = "lightblue";
|
||||||
|
linkElem.textContent = downloadfilename;
|
||||||
|
this.descriptionParagraph.appendChild(linkElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog() {
|
||||||
|
this.is_shown = true;
|
||||||
|
this.modalRoot.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.is_shown = false;
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function saveFileDone(filename, isSaveAs, isAutosave) {
|
||||||
|
console.log(`saveFileDone(${filename}, ${isSaveAs}, ${isAutosave})`);
|
||||||
|
if (isAutosave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fileDownloadHelper = new FileDownloadHelper();
|
||||||
|
fileDownloadHelper.AddButton("OK", 0, true);
|
||||||
|
fileDownloadHelper.prepareFile(filename);
|
||||||
|
console.log(`Calling shoDialog()...`);
|
||||||
|
fileDownloadHelper.showDialog();
|
||||||
|
console.log(`shoDialog() finished.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ScrollbarHelper {
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elementquery CSS query string for the element that has scrollbar.
|
||||||
|
*/
|
||||||
|
constructor(elementquery) {
|
||||||
|
this.target = document.querySelector(elementquery);
|
||||||
|
this.rangeMin = 0;
|
||||||
|
this.rangeMax = 0;
|
||||||
|
this.currentRatio = 0;
|
||||||
|
|
||||||
|
this.onScrollCallback = null;
|
||||||
|
this.onScrollCallbackTicking = false;
|
||||||
|
if (this.target) {
|
||||||
|
// console.log("addEventListner scroll");
|
||||||
|
this.target.parentElement.addEventListener('scroll', () => {
|
||||||
|
if (this.onScrollCallbackTicking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (this.onScrollCallback) {
|
||||||
|
this.onScrollCallback();
|
||||||
|
}
|
||||||
|
this.onScrollCallbackTicking = false;
|
||||||
|
});
|
||||||
|
this.onScrollCallbackTicking = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} ratio how long against to the viewport height (1.0 to exact same as viewport's height)
|
||||||
|
*/
|
||||||
|
setScrollbarSize(ratio) {
|
||||||
|
// if (isNaN(ratio)) {
|
||||||
|
// console.warn(`setScrollbarSize(): ratio is Nan = ${ratio}`);
|
||||||
|
// }
|
||||||
|
// if (ratio < 0 || ratio > 1) {
|
||||||
|
// console.warn(`setScrollbarSize(): ratio is out of range 0-1 but ${ratio}`);
|
||||||
|
// }
|
||||||
|
// console.log(`ScrollbarHelper.setScrollbarSize(): ratio=${ratio}`);
|
||||||
|
this.target.style.height = `${100 * ratio}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScrollbarPosition() {
|
||||||
|
const scrollbarElem = this.target.parentElement;
|
||||||
|
const scrollTopMin = 0;
|
||||||
|
const scrollTopMax = scrollbarElem.scrollHeight - scrollbarElem.clientHeight;
|
||||||
|
const ratioOnScrollbar = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
||||||
|
this.currentRatio = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
||||||
|
let pos = this.currentRatio * (this.rangeMax - this.pageSize - this.rangeMin) + this.rangeMin;
|
||||||
|
// console.log(`ScrollbarHelper.getScrollbarPosition(): ratio=${ratioOnScrollbar}, pos=${pos}, scrollTop=${scrollbarElem.scrollTop}, scrollTopMin=${scrollTopMin}, scrollTopMax=${scrollTopMax}, rangeMin=${this.rangeMin}, rangeMax=${this.rangeMax}, pageSize=${this.pageSize}`);
|
||||||
|
if (isNaN(pos)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} value in range of rangeMin and rangeMax
|
||||||
|
*/
|
||||||
|
setScrollbarPosition(position) {
|
||||||
|
const positionMin = this.rangeMin;
|
||||||
|
const positionMax = this.rangeMax - this.pageSize;
|
||||||
|
const currentPositionRatio = (position - positionMin) / (positionMax - positionMin);
|
||||||
|
|
||||||
|
const scrollbarElement = this.target.parentElement;
|
||||||
|
const scrollTopMin = 0;
|
||||||
|
const scrollTopMax = scrollbarElement.scrollHeight - scrollbarElement.clientHeight;
|
||||||
|
const scrollWidth = scrollTopMax - scrollTopMin;
|
||||||
|
const newScrollTop = currentPositionRatio * scrollWidth;
|
||||||
|
scrollbarElement.scrollTop = currentPositionRatio * scrollWidth;
|
||||||
|
|
||||||
|
// console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, currentPositionRatio=${currentPositionRatio}, calculated scrollTop=${newScrollTop}`);
|
||||||
|
|
||||||
|
if (false) {
|
||||||
|
// const ratio = (position - this.rangeMin) * ((this.rangeMax - this.pageSize) - this.rangeMin);
|
||||||
|
|
||||||
|
const scrollTopMin = 0;
|
||||||
|
const scrollTopMax = this.target.scrollHeight - this.target.clientHeight;
|
||||||
|
const scrollWidth = scrollTopMax - scrollTopMin;
|
||||||
|
const newScrollTop = ratio * scrollWidth;
|
||||||
|
// this.target.parentElement.scrollTop = ratio * scrollWidth;
|
||||||
|
this.target.scrollTop = ratio * scrollWidth;
|
||||||
|
|
||||||
|
console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, ratio=${ratio}, calculated scrollTop=${newScrollTop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
setRange(min, max, pageSize) {
|
||||||
|
this.rangeMin = min;
|
||||||
|
this.rangeMax = max;
|
||||||
|
this.currentRatio = 0;
|
||||||
|
|
||||||
|
this.setPageSize(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageSize(pageSize) {
|
||||||
|
if (this.rangeMin == this.rangeMax) {
|
||||||
|
// console.log(`ScrollbarHelper::setPageSize(): size=${size}, but rangeMin == rangeMax`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
const ratio = (this.rangeMax - this.rangeMin) / this.pageSize;
|
||||||
|
// console.log(`ScrollbarHelper::setPageSize(): pageSize=${pageSize}, ratio=${ratio}`);
|
||||||
|
this.setScrollbarSize(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
setScrollbarEnabled(enabled) {
|
||||||
|
if (!enabled) {
|
||||||
|
this.target.style.height = "100%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.ScrollbarHelper = ScrollbarHelper;
|
|
@ -93,9 +93,7 @@ static std::vector<std::string> Split(const std::string &joined, char separator)
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldpos != joined.length() - 1) {
|
|
||||||
parts.push_back(joined.substr(oldpos));
|
parts.push_back(joined.substr(oldpos));
|
||||||
}
|
|
||||||
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
@ -239,7 +237,8 @@ Path Path::Parent() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concatenates a component to this path.
|
// Concatenates a component to this path.
|
||||||
// Returns an empty path if this path or the component is empty.
|
// Returns a relative path if this path is empty.
|
||||||
|
// Returns an empty path if the component is absolute.
|
||||||
Path Path::Join(const std::string &component) const {
|
Path Path::Join(const std::string &component) const {
|
||||||
ssassert(component.find(SEPARATOR) == std::string::npos,
|
ssassert(component.find(SEPARATOR) == std::string::npos,
|
||||||
"Use the Path::Join(const Path &) overload to append an entire path");
|
"Use the Path::Join(const Path &) overload to append an entire path");
|
||||||
|
@ -247,13 +246,20 @@ Path Path::Join(const std::string &component) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concatenates a relative path to this path.
|
// Concatenates a relative path to this path.
|
||||||
// Returns an empty path if either path is empty, or the other path is absolute.
|
// Returns a relative path if this path is empty.
|
||||||
|
// Returns an empty path if the other path is absolute.
|
||||||
Path Path::Join(const Path &other) const {
|
Path Path::Join(const Path &other) const {
|
||||||
if(IsEmpty() || other.IsEmpty() || other.IsAbsolute()) {
|
if(other.IsAbsolute()) {
|
||||||
return From("");
|
return From("");
|
||||||
}
|
}
|
||||||
|
|
||||||
Path joined = { raw };
|
Path joined;
|
||||||
|
if(IsEmpty()) {
|
||||||
|
joined.raw = ".";
|
||||||
|
} else {
|
||||||
|
joined.raw = raw;
|
||||||
|
}
|
||||||
|
|
||||||
if(joined.raw.back() != SEPARATOR) {
|
if(joined.raw.back() != SEPARATOR) {
|
||||||
joined.raw += SEPARATOR;
|
joined.raw += SEPARATOR;
|
||||||
}
|
}
|
||||||
|
@ -321,16 +327,15 @@ static std::string FilesystemNormalize(const std::string &str) {
|
||||||
std::transform(strW.begin(), strW.end(), strW.begin(), towlower);
|
std::transform(strW.begin(), strW.end(), strW.begin(), towlower);
|
||||||
return Narrow(strW);
|
return Narrow(strW);
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
CFStringRef cfStr = CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(),
|
CFMutableStringRef cfStr =
|
||||||
kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull);
|
CFStringCreateMutableCopy(NULL, 0,
|
||||||
CFMutableStringRef cfmStr = CFStringCreateMutableCopy(NULL, 0, cfStr);
|
CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(),
|
||||||
CFStringLowercase(cfmStr, NULL);
|
kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull));
|
||||||
|
CFStringLowercase(cfStr, NULL);
|
||||||
std::string normalizedStr;
|
std::string normalizedStr;
|
||||||
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfmStr));
|
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr));
|
||||||
CFStringGetFileSystemRepresentation(cfmStr, &normalizedStr[0], normalizedStr.size());
|
CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size());
|
||||||
normalizedStr.erase(normalizedStr.find('\0'));
|
normalizedStr.erase(normalizedStr.find('\0'));
|
||||||
CFRelease(cfmStr);
|
|
||||||
CFRelease(cfStr);
|
|
||||||
return normalizedStr;
|
return normalizedStr;
|
||||||
#else
|
#else
|
||||||
return str;
|
return str;
|
||||||
|
@ -517,6 +522,12 @@ static Platform::Path ResourcePath(const std::string &name) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(__EMSCRIPTEN__)
|
||||||
|
|
||||||
|
static Platform::Path ResourcePath(const std::string &name) {
|
||||||
|
return Path::From("res/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
#elif !defined(WIN32)
|
#elif !defined(WIN32)
|
||||||
|
|
||||||
# if defined(__linux__)
|
# if defined(__linux__)
|
||||||
|
@ -640,6 +651,12 @@ std::vector<std::string> InitCli(int argc, char **argv) {
|
||||||
|
|
||||||
#if defined(WIN32)
|
#if defined(WIN32)
|
||||||
|
|
||||||
|
#if !defined(_alloca)
|
||||||
|
// Fix for compiling with MinGW.org GCC-6.3.0-1
|
||||||
|
#define _alloca alloca
|
||||||
|
#include <malloc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
void DebugPrint(const char *fmt, ...)
|
void DebugPrint(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list va;
|
va_list va;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#ifndef SOLVESPACE_PLATFORM_H
|
#ifndef SOLVESPACE_PLATFORM_H
|
||||||
#define SOLVESPACE_PLATFORM_H
|
#define SOLVESPACE_PLATFORM_H
|
||||||
|
|
||||||
|
namespace SolveSpace {
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
|
|
||||||
// UTF-8 ⟷ UTF-16 conversion, for Windows.
|
// UTF-8 ⟷ UTF-16 conversion, for Windows.
|
||||||
|
@ -80,6 +81,7 @@ void DebugPrint(const char *fmt, ...);
|
||||||
void *AllocTemporary(size_t size);
|
void *AllocTemporary(size_t size);
|
||||||
void FreeAllTemporary();
|
void FreeAllTemporary();
|
||||||
|
|
||||||
}
|
} // namespace Platform
|
||||||
|
} // namespace SolveSpace
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef SOLVESPACE_GL3SHADER_H
|
#ifndef SOLVESPACE_GL3SHADER_H
|
||||||
#define SOLVESPACE_GL3SHADER_H
|
#define SOLVESPACE_GL3SHADER_H
|
||||||
|
|
||||||
#if defined(WIN32)
|
#if defined(WIN32) || defined(__EMSCRIPTEN__)
|
||||||
# define GL_APICALL /*static linkage*/
|
# define GL_APICALL /*static linkage*/
|
||||||
# define GL_GLEXT_PROTOTYPES
|
# define GL_GLEXT_PROTOTYPES
|
||||||
# include <GLES2/gl2.h>
|
# include <GLES2/gl2.h>
|
||||||
|
|
|
@ -90,7 +90,8 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
|
||||||
// Request-specific generation.
|
// Request-specific generation.
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::TTF_TEXT: {
|
case Type::TTF_TEXT: {
|
||||||
double actualAspectRatio = SS.fonts.AspectRatio(font, str);
|
// `extraPoints` is storing kerning boolean
|
||||||
|
double actualAspectRatio = SS.fonts.AspectRatio(font, str, extraPoints);
|
||||||
if(EXACT(actualAspectRatio != 0.0)) {
|
if(EXACT(actualAspectRatio != 0.0)) {
|
||||||
// We could load the font, so use the actual value.
|
// We could load the font, so use the actual value.
|
||||||
aspectRatio = actualAspectRatio;
|
aspectRatio = actualAspectRatio;
|
||||||
|
|
|
@ -179,7 +179,8 @@ void Pixmap::ConvertTo(Format newFormat) {
|
||||||
|
|
||||||
static std::shared_ptr<Pixmap> ReadPngIntoPixmap(png_struct *png_ptr, png_info *info_ptr,
|
static std::shared_ptr<Pixmap> ReadPngIntoPixmap(png_struct *png_ptr, png_info *info_ptr,
|
||||||
bool flip) {
|
bool flip) {
|
||||||
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL);
|
png_read_png(png_ptr, info_ptr,
|
||||||
|
PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_SCALE_16, NULL);
|
||||||
|
|
||||||
std::shared_ptr<Pixmap> pixmap = std::make_shared<Pixmap>();
|
std::shared_ptr<Pixmap> pixmap = std::make_shared<Pixmap>();
|
||||||
pixmap->width = png_get_image_width(png_ptr, info_ptr);
|
pixmap->width = png_get_image_width(png_ptr, info_ptr);
|
||||||
|
@ -272,8 +273,8 @@ std::shared_ptr<Pixmap> Pixmap::ReadPng(const Platform::Path &filename, bool fli
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Pixmap::WritePng(FILE *f, bool flip) {
|
bool Pixmap::WritePng(FILE *f, bool flip) {
|
||||||
int colorType = 0;
|
colorType = 0;
|
||||||
bool bgr = false;
|
bgr = false;
|
||||||
switch(format) {
|
switch(format) {
|
||||||
case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; bgr = false; break;
|
case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; bgr = false; break;
|
||||||
case Format::BGRA: colorType = PNG_COLOR_TYPE_RGBA; bgr = true; break;
|
case Format::BGRA: colorType = PNG_COLOR_TYPE_RGBA; bgr = true; break;
|
||||||
|
@ -564,7 +565,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
|
||||||
// Find the hex representation in the (sorted) Unifont file.
|
// Find the hex representation in the (sorted) Unifont file.
|
||||||
auto first = unifontData.cbegin(),
|
auto first = unifontData.cbegin(),
|
||||||
last = unifontData.cend();
|
last = unifontData.cend();
|
||||||
while(first <= last) {
|
while(first < last) {
|
||||||
auto mid = first + (last - first) / 2;
|
auto mid = first + (last - first) / 2;
|
||||||
while(mid != unifontData.cbegin()) {
|
while(mid != unifontData.cbegin()) {
|
||||||
if(*mid == '\n') {
|
if(*mid == '\n') {
|
||||||
|
@ -588,7 +589,10 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
|
||||||
if(foundCodepoint < codepoint) {
|
if(foundCodepoint < codepoint) {
|
||||||
first = mid + 1;
|
first = mid + 1;
|
||||||
while(first != unifontData.cend()) {
|
while(first != unifontData.cend()) {
|
||||||
if(*first == '\n') break;
|
if(*first == '\n') {
|
||||||
|
first++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
first++;
|
first++;
|
||||||
}
|
}
|
||||||
continue; // and last stays the same
|
continue; // and last stays the same
|
||||||
|
|
|
@ -7,11 +7,23 @@
|
||||||
#ifndef SOLVESPACE_RESOURCE_H
|
#ifndef SOLVESPACE_RESOURCE_H
|
||||||
#define SOLVESPACE_RESOURCE_H
|
#define SOLVESPACE_RESOURCE_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace SolveSpace {
|
||||||
|
|
||||||
class Camera;
|
class Camera;
|
||||||
class Point2d;
|
class Point2d;
|
||||||
class Pixmap;
|
class Pixmap;
|
||||||
class Vector;
|
class Vector;
|
||||||
class RgbaColor;
|
class RgbaColor;
|
||||||
|
namespace Platform {
|
||||||
|
class Path;
|
||||||
|
} // namespace Platform
|
||||||
|
|
||||||
std::string LoadString(const std::string &name);
|
std::string LoadString(const std::string &name);
|
||||||
std::string LoadStringFromGzip(const std::string &name);
|
std::string LoadStringFromGzip(const std::string &name);
|
||||||
|
@ -26,6 +38,8 @@ public:
|
||||||
size_t height;
|
size_t height;
|
||||||
size_t stride;
|
size_t stride;
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
|
int colorType;
|
||||||
|
bool bgr;
|
||||||
|
|
||||||
static std::shared_ptr<Pixmap> Create(Format format, size_t width, size_t height);
|
static std::shared_ptr<Pixmap> Create(Format format, size_t width, size_t height);
|
||||||
static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size, bool flip = false);
|
static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size, bool flip = false);
|
||||||
|
@ -109,4 +123,5 @@ public:
|
||||||
const std::function<void(Vector, Vector)> &traceEdge, const Camera &camera);
|
const std::function<void(Vector, Vector)> &traceEdge, const Camera &camera);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
22
src/sketch.h
22
src/sketch.h
|
@ -175,6 +175,7 @@ public:
|
||||||
bool suppress;
|
bool suppress;
|
||||||
bool relaxConstraints;
|
bool relaxConstraints;
|
||||||
bool allowRedundant;
|
bool allowRedundant;
|
||||||
|
bool suppressDofCalculation;
|
||||||
bool allDimsReference;
|
bool allDimsReference;
|
||||||
double scale;
|
double scale;
|
||||||
|
|
||||||
|
@ -198,6 +199,9 @@ public:
|
||||||
// For drawings in 2d
|
// For drawings in 2d
|
||||||
WORKPLANE_BY_POINT_ORTHO = 6000,
|
WORKPLANE_BY_POINT_ORTHO = 6000,
|
||||||
WORKPLANE_BY_LINE_SEGMENTS = 6001,
|
WORKPLANE_BY_LINE_SEGMENTS = 6001,
|
||||||
|
WORKPLANE_BY_POINT_NORMAL = 6002,
|
||||||
|
//WORKPLANE_BY_POINT_FACE = 6003,
|
||||||
|
//WORKPLANE_BY_FACE = 6004,
|
||||||
// For extrudes, translates, and rotates
|
// For extrudes, translates, and rotates
|
||||||
ONE_SIDED = 7000,
|
ONE_SIDED = 7000,
|
||||||
TWO_SIDED = 7001
|
TWO_SIDED = 7001
|
||||||
|
@ -266,6 +270,7 @@ public:
|
||||||
void Generate(EntityList *entity, ParamList *param);
|
void Generate(EntityList *entity, ParamList *param);
|
||||||
bool IsSolvedOkay();
|
bool IsSolvedOkay();
|
||||||
void TransformImportedBy(Vector t, Quaternion q);
|
void TransformImportedBy(Vector t, Quaternion q);
|
||||||
|
bool IsTriangleMeshAssembly() const;
|
||||||
bool IsForcedToMeshBySource() const;
|
bool IsForcedToMeshBySource() const;
|
||||||
bool IsForcedToMesh() const;
|
bool IsForcedToMesh() const;
|
||||||
// When a request generates entities from entities, and the source
|
// When a request generates entities from entities, and the source
|
||||||
|
@ -323,6 +328,7 @@ public:
|
||||||
void DrawPolyError(Canvas *canvas);
|
void DrawPolyError(Canvas *canvas);
|
||||||
void DrawFilledPaths(Canvas *canvas);
|
void DrawFilledPaths(Canvas *canvas);
|
||||||
void DrawContourAreaLabels(Canvas *canvas);
|
void DrawContourAreaLabels(Canvas *canvas);
|
||||||
|
bool ShouldDrawExploded() const;
|
||||||
|
|
||||||
SPolygon GetPolygon();
|
SPolygon GetPolygon();
|
||||||
|
|
||||||
|
@ -368,6 +374,7 @@ public:
|
||||||
std::string font;
|
std::string font;
|
||||||
Platform::Path file;
|
Platform::Path file;
|
||||||
double aspectRatio;
|
double aspectRatio;
|
||||||
|
int groupRequestIndex;
|
||||||
|
|
||||||
static hParam AddParam(ParamList *param, hParam hp);
|
static hParam AddParam(ParamList *param, hParam hp);
|
||||||
void Generate(EntityList *entity, ParamList *param);
|
void Generate(EntityList *entity, ParamList *param);
|
||||||
|
@ -591,6 +598,10 @@ public:
|
||||||
beziers.l.Clear();
|
beziers.l.Clear();
|
||||||
edges.l.Clear();
|
edges.l.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShouldDrawExploded() const;
|
||||||
|
Vector ExplodeOffset() const;
|
||||||
|
Vector PointGetDrawNum() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class EntReqTable {
|
class EntReqTable {
|
||||||
|
@ -612,7 +623,7 @@ public:
|
||||||
bool free;
|
bool free;
|
||||||
|
|
||||||
// Used only in the solver
|
// Used only in the solver
|
||||||
hParam substd;
|
Param *substd;
|
||||||
|
|
||||||
static const hParam NO_PARAM;
|
static const hParam NO_PARAM;
|
||||||
|
|
||||||
|
@ -673,7 +684,10 @@ public:
|
||||||
CURVE_CURVE_TANGENT = 125,
|
CURVE_CURVE_TANGENT = 125,
|
||||||
EQUAL_RADIUS = 130,
|
EQUAL_RADIUS = 130,
|
||||||
WHERE_DRAGGED = 200,
|
WHERE_DRAGGED = 200,
|
||||||
|
ARC_ARC_LEN_RATIO = 210,
|
||||||
|
ARC_LINE_LEN_RATIO = 211,
|
||||||
|
ARC_ARC_DIFFERENCE = 212,
|
||||||
|
ARC_LINE_DIFFERENCE = 213,
|
||||||
COMMENT = 1000
|
COMMENT = 1000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -757,7 +771,7 @@ public:
|
||||||
Vector p0, Vector p1, Vector pt, double salient);
|
Vector p0, Vector p1, Vector pt, double salient);
|
||||||
void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
Vector a0, Vector da, Vector b0, Vector db,
|
Vector a0, Vector da, Vector b0, Vector db,
|
||||||
Vector offset, Vector *ref, bool trim);
|
Vector offset, Vector *ref, bool trim, Vector explodeOffset);
|
||||||
void DoArrow(Canvas *canvas, Canvas::hStroke hcs,
|
void DoArrow(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
Vector p, Vector dir, Vector n, double width, double angle, double da);
|
Vector p, Vector dir, Vector n, double width, double angle, double da);
|
||||||
void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
|
void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
|
||||||
|
@ -779,6 +793,8 @@ public:
|
||||||
|
|
||||||
std::string DescriptionString() const;
|
std::string DescriptionString() const;
|
||||||
|
|
||||||
|
bool ShouldDrawExploded() const;
|
||||||
|
|
||||||
static hConstraint AddConstraint(Constraint *c, bool rememberForUndo = true);
|
static hConstraint AddConstraint(Constraint *c, bool rememberForUndo = true);
|
||||||
static void MenuConstrain(Command id);
|
static void MenuConstrain(Command id);
|
||||||
static void DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA);
|
static void DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA);
|
||||||
|
|
|
@ -19,6 +19,7 @@ void SolveSpaceUI::Init() {
|
||||||
Platform::SettingsRef settings = Platform::GetSettings();
|
Platform::SettingsRef settings = Platform::GetSettings();
|
||||||
|
|
||||||
SS.tangentArcRadius = 10.0;
|
SS.tangentArcRadius = 10.0;
|
||||||
|
SS.explodeDistance = 1.0;
|
||||||
|
|
||||||
// Then, load the registry settings.
|
// Then, load the registry settings.
|
||||||
// Default list of colors for the model material
|
// Default list of colors for the model material
|
||||||
|
@ -68,12 +69,18 @@ void SolveSpaceUI::Init() {
|
||||||
exportScale = settings->ThawFloat("ExportScale", 1.0);
|
exportScale = settings->ThawFloat("ExportScale", 1.0);
|
||||||
// Export offset (cutter radius comp)
|
// Export offset (cutter radius comp)
|
||||||
exportOffset = settings->ThawFloat("ExportOffset", 0.0);
|
exportOffset = settings->ThawFloat("ExportOffset", 0.0);
|
||||||
|
// Dimensions on arcs default to diameter vs radius
|
||||||
|
arcDimDefaultDiameter = settings->ThawBool("ArcDimDefaultDiameter", false);
|
||||||
|
// Show full file path in the menu bar
|
||||||
|
showFullFilePath = settings->ThawBool("ShowFullFilePath", true);
|
||||||
// Rewrite exported colors close to white into black (assuming white bg)
|
// Rewrite exported colors close to white into black (assuming white bg)
|
||||||
fixExportColors = settings->ThawBool("FixExportColors", true);
|
fixExportColors = settings->ThawBool("FixExportColors", true);
|
||||||
// Export background color
|
// Export background color
|
||||||
exportBackgroundColor = settings->ThawBool("ExportBackgroundColor", false);
|
exportBackgroundColor = settings->ThawBool("ExportBackgroundColor", false);
|
||||||
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
||||||
drawBackFaces = settings->ThawBool("DrawBackFaces", true);
|
drawBackFaces = settings->ThawBool("DrawBackFaces", true);
|
||||||
|
// Use camera mouse navigation
|
||||||
|
cameraNav = settings->ThawBool("CameraNav", false);
|
||||||
// Use turntable mouse navigation
|
// Use turntable mouse navigation
|
||||||
turntableNav = settings->ThawBool("TurntableNav", false);
|
turntableNav = settings->ThawBool("TurntableNav", false);
|
||||||
// Immediately edit dimension
|
// Immediately edit dimension
|
||||||
|
@ -104,6 +111,7 @@ void SolveSpaceUI::Init() {
|
||||||
exportCanvas.dy = settings->ThawFloat("ExportCanvas_Dy", 5.0);
|
exportCanvas.dy = settings->ThawFloat("ExportCanvas_Dy", 5.0);
|
||||||
// Extra parameters when exporting G code
|
// Extra parameters when exporting G code
|
||||||
gCode.depth = settings->ThawFloat("GCode_Depth", 10.0);
|
gCode.depth = settings->ThawFloat("GCode_Depth", 10.0);
|
||||||
|
gCode.safeHeight = settings->ThawFloat("GCode_SafeHeight", 5.0);
|
||||||
gCode.passes = settings->ThawInt("GCode_Passes", 1);
|
gCode.passes = settings->ThawInt("GCode_Passes", 1);
|
||||||
gCode.feed = settings->ThawFloat("GCode_Feed", 10.0);
|
gCode.feed = settings->ThawFloat("GCode_Feed", 10.0);
|
||||||
gCode.plungeFeed = settings->ThawFloat("GCode_PlungeFeed", 10.0);
|
gCode.plungeFeed = settings->ThawFloat("GCode_PlungeFeed", 10.0);
|
||||||
|
@ -123,12 +131,8 @@ void SolveSpaceUI::Init() {
|
||||||
SetLocale(locale);
|
SetLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAllTimer = Platform::CreateTimer();
|
refreshTimer = Platform::CreateTimer();
|
||||||
generateAllTimer->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY,
|
refreshTimer->onTimeout = std::bind(&SolveSpaceUI::Refresh, &SS);
|
||||||
/*andFindFree=*/false, /*genForBBox=*/false);
|
|
||||||
|
|
||||||
showTWTimer = Platform::CreateTimer();
|
|
||||||
showTWTimer->onTimeout = std::bind(&TextWindow::Show, &TW);
|
|
||||||
|
|
||||||
autosaveTimer = Platform::CreateTimer();
|
autosaveTimer = Platform::CreateTimer();
|
||||||
autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS);
|
autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS);
|
||||||
|
@ -250,6 +254,10 @@ void SolveSpaceUI::Exit() {
|
||||||
settings->FreezeFloat("ExportScale", exportScale);
|
settings->FreezeFloat("ExportScale", exportScale);
|
||||||
// Export offset (cutter radius comp)
|
// Export offset (cutter radius comp)
|
||||||
settings->FreezeFloat("ExportOffset", exportOffset);
|
settings->FreezeFloat("ExportOffset", exportOffset);
|
||||||
|
// Rewrite the default arc dimension setting
|
||||||
|
settings->FreezeBool("ArcDimDefaultDiameter", arcDimDefaultDiameter);
|
||||||
|
// Show full file path in the menu bar
|
||||||
|
settings->FreezeBool("ShowFullFilePath", showFullFilePath);
|
||||||
// Rewrite exported colors close to white into black (assuming white bg)
|
// Rewrite exported colors close to white into black (assuming white bg)
|
||||||
settings->FreezeBool("FixExportColors", fixExportColors);
|
settings->FreezeBool("FixExportColors", fixExportColors);
|
||||||
// Export background color
|
// Export background color
|
||||||
|
@ -260,6 +268,8 @@ void SolveSpaceUI::Exit() {
|
||||||
settings->FreezeBool("ShowContourAreas", showContourAreas);
|
settings->FreezeBool("ShowContourAreas", showContourAreas);
|
||||||
// Check that contours are closed and not self-intersecting
|
// Check that contours are closed and not self-intersecting
|
||||||
settings->FreezeBool("CheckClosedContour", checkClosedContour);
|
settings->FreezeBool("CheckClosedContour", checkClosedContour);
|
||||||
|
// Use camera mouse navigation
|
||||||
|
settings->FreezeBool("CameraNav", cameraNav);
|
||||||
// Use turntable mouse navigation
|
// Use turntable mouse navigation
|
||||||
settings->FreezeBool("TurntableNav", turntableNav);
|
settings->FreezeBool("TurntableNav", turntableNav);
|
||||||
// Immediately edit dimensions
|
// Immediately edit dimensions
|
||||||
|
@ -300,12 +310,28 @@ void SolveSpaceUI::Exit() {
|
||||||
Platform::ExitGui();
|
Platform::ExitGui();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SolveSpaceUI::Refresh() {
|
||||||
|
// generateAll must happen bfore updating displays
|
||||||
|
if(scheduledGenerateAll) {
|
||||||
|
// Clear the flag so that if the call to GenerateAll is blocked by a Message or Error,
|
||||||
|
// subsequent refreshes do not try to Generate again.
|
||||||
|
scheduledGenerateAll = false;
|
||||||
|
GenerateAll(Generate::DIRTY, /*andFindFree=*/false, /*genForBBox=*/false);
|
||||||
|
}
|
||||||
|
if(scheduledShowTW) {
|
||||||
|
scheduledShowTW = false;
|
||||||
|
TW.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::ScheduleGenerateAll() {
|
void SolveSpaceUI::ScheduleGenerateAll() {
|
||||||
generateAllTimer->RunAfterProcessingEvents();
|
scheduledGenerateAll = true;
|
||||||
|
refreshTimer->RunAfterProcessingEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::ScheduleShowTW() {
|
void SolveSpaceUI::ScheduleShowTW() {
|
||||||
showTWTimer->RunAfterProcessingEvents();
|
scheduledShowTW = true;
|
||||||
|
refreshTimer->RunAfterProcessingEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::ScheduleAutosave() {
|
void SolveSpaceUI::ScheduleAutosave() {
|
||||||
|
@ -315,6 +341,7 @@ void SolveSpaceUI::ScheduleAutosave() {
|
||||||
double SolveSpaceUI::MmPerUnit() {
|
double SolveSpaceUI::MmPerUnit() {
|
||||||
switch(viewUnits) {
|
switch(viewUnits) {
|
||||||
case Unit::INCHES: return 25.4;
|
case Unit::INCHES: return 25.4;
|
||||||
|
case Unit::FEET_INCHES: return 25.4; // The 'unit' is still inches
|
||||||
case Unit::METERS: return 1000.0;
|
case Unit::METERS: return 1000.0;
|
||||||
case Unit::MM: return 1.0;
|
case Unit::MM: return 1.0;
|
||||||
}
|
}
|
||||||
|
@ -323,14 +350,47 @@ double SolveSpaceUI::MmPerUnit() {
|
||||||
const char *SolveSpaceUI::UnitName() {
|
const char *SolveSpaceUI::UnitName() {
|
||||||
switch(viewUnits) {
|
switch(viewUnits) {
|
||||||
case Unit::INCHES: return "in";
|
case Unit::INCHES: return "in";
|
||||||
|
case Unit::FEET_INCHES: return "in";
|
||||||
case Unit::METERS: return "m";
|
case Unit::METERS: return "m";
|
||||||
case Unit::MM: return "mm";
|
case Unit::MM: return "mm";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SolveSpaceUI::MmToString(double v) {
|
std::string SolveSpaceUI::MmToString(double v, bool editable) {
|
||||||
v /= MmPerUnit();
|
v /= MmPerUnit();
|
||||||
|
// The syntax 2' 6" for feet and inches is not something we can (currently)
|
||||||
|
// parse back from a string so if editable is true, we treat FEET_INCHES the
|
||||||
|
// same as INCHES and just return the unadorned decimal number of inches.
|
||||||
|
if(viewUnits == Unit::FEET_INCHES && !editable) {
|
||||||
|
// Now convert v from inches to 64'ths of an inch, to make rounding easier.
|
||||||
|
v = floor((v + (1.0 / 128.0)) * 64.0);
|
||||||
|
int feet = (int)(v / (12.0 * 64.0));
|
||||||
|
v = v - (feet * 12.0 * 64.0);
|
||||||
|
// v is now the feet-less remainder in 1/64 inches
|
||||||
|
int inches = (int)(v / 64.0);
|
||||||
|
int numerator = (int)(v - ((double)inches * 64.0));
|
||||||
|
int denominator = 64;
|
||||||
|
// Divide down to smallest denominator where the numerator is still a whole number
|
||||||
|
while ((numerator != 0) && ((numerator & 1) == 0)) {
|
||||||
|
numerator /= 2;
|
||||||
|
denominator /= 2;
|
||||||
|
}
|
||||||
|
std::ostringstream str;
|
||||||
|
if(feet != 0) {
|
||||||
|
str << feet << "'-";
|
||||||
|
}
|
||||||
|
// For something like 0.5, show 1/2" rather than 0 1/2"
|
||||||
|
if(!(feet == 0 && inches == 0 && numerator != 0)) {
|
||||||
|
str << inches;
|
||||||
|
}
|
||||||
|
if(numerator != 0) {
|
||||||
|
str << " " << numerator << "/" << denominator;
|
||||||
|
}
|
||||||
|
str << "\"";
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
int digits = UnitDigitsAfterDecimal();
|
int digits = UnitDigitsAfterDecimal();
|
||||||
double minimum = 0.5 * pow(10,-digits);
|
double minimum = 0.5 * pow(10,-digits);
|
||||||
while ((v < minimum) && (v > LENGTH_EPS)) {
|
while ((v < minimum) && (v > LENGTH_EPS)) {
|
||||||
|
@ -349,7 +409,7 @@ static const char *DimToString(int dim) {
|
||||||
}
|
}
|
||||||
static std::pair<int, std::string> SelectSIPrefixMm(int ord, int dim) {
|
static std::pair<int, std::string> SelectSIPrefixMm(int ord, int dim) {
|
||||||
// decide what units to use depending on the order of magnitude of the
|
// decide what units to use depending on the order of magnitude of the
|
||||||
// measure in meters and the dimmension (1,2,3 lenear, area, volume)
|
// measure in meters and the dimension (1,2,3 lenear, area, volume)
|
||||||
switch(dim) {
|
switch(dim) {
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -394,17 +454,22 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) {
|
||||||
dim = 1;
|
dim = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim);
|
bool inches = (viewUnits == Unit::INCHES) || (viewUnits == Unit::FEET_INCHES);
|
||||||
|
v /= pow(inches ? 25.4 : 1000, dim);
|
||||||
int vdeg = (int)(log10(fabs(v)));
|
int vdeg = (int)(log10(fabs(v)));
|
||||||
std::string unit;
|
std::string unit;
|
||||||
if(fabs(v) > 0.0) {
|
if(fabs(v) > 0.0) {
|
||||||
int sdeg = 0;
|
int sdeg = 0;
|
||||||
std::tie(sdeg, unit) =
|
std::tie(sdeg, unit) =
|
||||||
(viewUnits == Unit::INCHES)
|
inches
|
||||||
? SelectSIPrefixInch(vdeg/dim)
|
? SelectSIPrefixInch(vdeg/dim)
|
||||||
: SelectSIPrefixMm(vdeg, dim);
|
: SelectSIPrefixMm(vdeg, dim);
|
||||||
v /= pow(10.0, sdeg * dim);
|
v /= pow(10.0, sdeg * dim);
|
||||||
}
|
}
|
||||||
|
if(viewUnits == Unit::FEET_INCHES && fabs(v) > pow(12.0, dim)) {
|
||||||
|
unit = "ft";
|
||||||
|
v /= pow(12.0, dim);
|
||||||
|
}
|
||||||
int pdeg = (int)ceil(log10(fabs(v) + 1e-10));
|
int pdeg = (int)ceil(log10(fabs(v) + 1e-10));
|
||||||
return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v,
|
return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v,
|
||||||
compact ? "" : " ", unit.c_str(), DimToString(dim));
|
compact ? "" : " ", unit.c_str(), DimToString(dim));
|
||||||
|
@ -434,10 +499,11 @@ int SolveSpaceUI::GetMaxSegments() {
|
||||||
return maxSegments;
|
return maxSegments;
|
||||||
}
|
}
|
||||||
int SolveSpaceUI::UnitDigitsAfterDecimal() {
|
int SolveSpaceUI::UnitDigitsAfterDecimal() {
|
||||||
return (viewUnits == Unit::INCHES) ? afterDecimalInch : afterDecimalMm;
|
return (viewUnits == Unit::INCHES || viewUnits == Unit::FEET_INCHES) ?
|
||||||
|
afterDecimalInch : afterDecimalMm;
|
||||||
}
|
}
|
||||||
void SolveSpaceUI::SetUnitDigitsAfterDecimal(int v) {
|
void SolveSpaceUI::SetUnitDigitsAfterDecimal(int v) {
|
||||||
if(viewUnits == Unit::INCHES) {
|
if(viewUnits == Unit::INCHES || viewUnits == Unit::FEET_INCHES) {
|
||||||
afterDecimalInch = v;
|
afterDecimalInch = v;
|
||||||
} else {
|
} else {
|
||||||
afterDecimalMm = v;
|
afterDecimalMm = v;
|
||||||
|
@ -508,12 +574,18 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
||||||
|
|
||||||
if(saveAs || saveFile.IsEmpty()) {
|
if(saveAs || saveFile.IsEmpty()) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window);
|
||||||
|
// FIXME(emscripten):
|
||||||
|
dbp("Calling AddFilter()...");
|
||||||
dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT });
|
dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT });
|
||||||
|
dbp("Calling ThawChoices()...");
|
||||||
dialog->ThawChoices(settings, "Sketch");
|
dialog->ThawChoices(settings, "Sketch");
|
||||||
if(!newSaveFile.IsEmpty()) {
|
if(!newSaveFile.IsEmpty()) {
|
||||||
|
dbp("Calling SetFilename()...");
|
||||||
dialog->SetFilename(newSaveFile);
|
dialog->SetFilename(newSaveFile);
|
||||||
}
|
}
|
||||||
|
dbp("Calling RunModal()...");
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
|
dbp("Calling FreezeChoices()...");
|
||||||
dialog->FreezeChoices(settings, "Sketch");
|
dialog->FreezeChoices(settings, "Sketch");
|
||||||
newSaveFile = dialog->GetFilename();
|
newSaveFile = dialog->GetFilename();
|
||||||
} else {
|
} else {
|
||||||
|
@ -526,6 +598,9 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
||||||
RemoveAutosave();
|
RemoveAutosave();
|
||||||
saveFile = newSaveFile;
|
saveFile = newSaveFile;
|
||||||
unsaved = false;
|
unsaved = false;
|
||||||
|
if (this->OnSaveFinished) {
|
||||||
|
this->OnSaveFinished(newSaveFile, saveAs, false);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -537,7 +612,11 @@ void SolveSpaceUI::Autosave()
|
||||||
ScheduleAutosave();
|
ScheduleAutosave();
|
||||||
|
|
||||||
if(!saveFile.IsEmpty() && unsaved) {
|
if(!saveFile.IsEmpty() && unsaved) {
|
||||||
SaveToFile(saveFile.WithExtension(BACKUP_EXT));
|
Platform::Path saveFileName = saveFile.WithExtension(BACKUP_EXT);
|
||||||
|
SaveToFile(saveFileName);
|
||||||
|
if (this->OnSaveFinished) {
|
||||||
|
this->OnSaveFinished(saveFileName, false, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,7 +668,11 @@ void SolveSpaceUI::UpdateWindowTitles() {
|
||||||
GW.window->SetTitle(C_("title", "(new sketch)"));
|
GW.window->SetTitle(C_("title", "(new sketch)"));
|
||||||
} else {
|
} else {
|
||||||
if(!GW.window->SetTitleForFilename(saveFile)) {
|
if(!GW.window->SetTitleForFilename(saveFile)) {
|
||||||
|
if(SS.showFullFilePath) {
|
||||||
GW.window->SetTitle(saveFile.raw);
|
GW.window->SetTitle(saveFile.raw);
|
||||||
|
} else {
|
||||||
|
GW.window->SetTitle(saveFile.raw.substr(saveFile.raw.find_last_of("/\\") + 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,6 +720,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
dialog->FreezeChoices(settings, "ExportImage");
|
dialog->FreezeChoices(settings, "ExportImage");
|
||||||
SS.ExportAsPngTo(dialog->GetFilename());
|
SS.ExportAsPngTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -651,10 +737,8 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
|
|
||||||
// If the user is exporting something where it would be
|
// If the user is exporting something where it would be
|
||||||
// inappropriate to include the constraints, then warn.
|
// inappropriate to include the constraints, then warn.
|
||||||
if(SS.GW.showConstraints &&
|
if(SS.GW.showConstraints != GraphicsWindow::ShowConstraintMode::SCM_NOSHOW &&
|
||||||
(dialog->GetFilename().HasExtension("txt") ||
|
(dialog->GetFilename().HasExtension("txt") || fabs(SS.exportOffset) > LENGTH_EPS)) {
|
||||||
fabs(SS.exportOffset) > LENGTH_EPS))
|
|
||||||
{
|
|
||||||
Message(_("Constraints are currently shown, and will be exported "
|
Message(_("Constraints are currently shown, and will be exported "
|
||||||
"in the toolpath. This is probably not what you want; "
|
"in the toolpath. This is probably not what you want; "
|
||||||
"hide them by clicking the link at the top of the "
|
"hide them by clicking the link at the top of the "
|
||||||
|
@ -662,6 +746,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false);
|
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false);
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,6 +761,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
dialog->FreezeChoices(settings, "ExportWireframe");
|
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||||
|
|
||||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -686,6 +776,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
dialog->FreezeChoices(settings, "ExportSection");
|
dialog->FreezeChoices(settings, "ExportSection");
|
||||||
|
|
||||||
SS.ExportSectionTo(dialog->GetFilename());
|
SS.ExportSectionTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -698,6 +791,10 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
dialog->FreezeChoices(settings, "ExportMesh");
|
dialog->FreezeChoices(settings, "ExportMesh");
|
||||||
|
|
||||||
SS.ExportMeshTo(dialog->GetFilename());
|
SS.ExportMeshTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,6 +808,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
|
|
||||||
StepFileWriter sfw = {};
|
StepFileWriter sfw = {};
|
||||||
sfw.ExportSurfacesTo(dialog->GetFilename());
|
sfw.ExportSurfacesTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,7 +864,11 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
|
||||||
SS.TW.stepDim.isDistance =
|
SS.TW.stepDim.isDistance =
|
||||||
(c->type != Constraint::Type::ANGLE) &&
|
(c->type != Constraint::Type::ANGLE) &&
|
||||||
(c->type != Constraint::Type::LENGTH_RATIO) &&
|
(c->type != Constraint::Type::LENGTH_RATIO) &&
|
||||||
(c->type != Constraint::Type::LENGTH_DIFFERENCE);
|
(c->type != Constraint::Type::ARC_ARC_LEN_RATIO) &&
|
||||||
|
(c->type != Constraint::Type::ARC_LINE_LEN_RATIO) &&
|
||||||
|
(c->type != Constraint::Type::LENGTH_DIFFERENCE) &&
|
||||||
|
(c->type != Constraint::Type::ARC_ARC_DIFFERENCE) &&
|
||||||
|
(c->type != Constraint::Type::ARC_LINE_DIFFERENCE) ;
|
||||||
SS.TW.shown.constraint = c->h;
|
SS.TW.shown.constraint = c->h;
|
||||||
SS.TW.shown.screen = TextWindow::Screen::STEP_DIMENSION;
|
SS.TW.shown.screen = TextWindow::Screen::STEP_DIMENSION;
|
||||||
|
|
||||||
|
@ -1007,7 +1111,11 @@ void SolveSpaceUI::MenuHelp(Command id) {
|
||||||
"law. For details, visit http://gnu.org/licenses/\n"
|
"law. For details, visit http://gnu.org/licenses/\n"
|
||||||
"\n"
|
"\n"
|
||||||
"© 2008-%d Jonathan Westhues and other authors.\n"),
|
"© 2008-%d Jonathan Westhues and other authors.\n"),
|
||||||
PACKAGE_VERSION, 2021);
|
PACKAGE_VERSION, 2024);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Command::GITHUB:
|
||||||
|
Platform::OpenInBrowser(GIT_HASH_URL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: ssassert(false, "Unexpected menu ID");
|
default: ssassert(false, "Unexpected menu ID");
|
||||||
|
@ -1026,12 +1134,14 @@ void SolveSpaceUI::Clear() {
|
||||||
GW.showGridMenuItem = NULL;
|
GW.showGridMenuItem = NULL;
|
||||||
GW.dimSolidModelMenuItem = NULL;
|
GW.dimSolidModelMenuItem = NULL;
|
||||||
GW.perspectiveProjMenuItem = NULL;
|
GW.perspectiveProjMenuItem = NULL;
|
||||||
|
GW.explodeMenuItem = NULL;
|
||||||
GW.showToolbarMenuItem = NULL;
|
GW.showToolbarMenuItem = NULL;
|
||||||
GW.showTextWndMenuItem = NULL;
|
GW.showTextWndMenuItem = NULL;
|
||||||
GW.fullScreenMenuItem = NULL;
|
GW.fullScreenMenuItem = NULL;
|
||||||
GW.unitsMmMenuItem = NULL;
|
GW.unitsMmMenuItem = NULL;
|
||||||
GW.unitsMetersMenuItem = NULL;
|
GW.unitsMetersMenuItem = NULL;
|
||||||
GW.unitsInchesMenuItem = NULL;
|
GW.unitsInchesMenuItem = NULL;
|
||||||
|
GW.unitsFeetInchesMenuItem = NULL;
|
||||||
GW.inWorkplaneMenuItem = NULL;
|
GW.inWorkplaneMenuItem = NULL;
|
||||||
GW.in3dMenuItem = NULL;
|
GW.in3dMenuItem = NULL;
|
||||||
GW.undoMenuItem = NULL;
|
GW.undoMenuItem = NULL;
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
#ifndef SOLVESPACE_H
|
#ifndef SOLVESPACE_H
|
||||||
#define SOLVESPACE_H
|
#define SOLVESPACE_H
|
||||||
|
|
||||||
|
#include "resource.h"
|
||||||
|
#include "platform/platform.h"
|
||||||
|
#include "platform/gui.h"
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
@ -30,6 +34,10 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define EIGEN_NO_DEBUG
|
||||||
|
#undef Success
|
||||||
|
#include <Eigen/SparseCore>
|
||||||
|
|
||||||
// We declare these in advance instead of simply using FT_Library
|
// We declare these in advance instead of simply using FT_Library
|
||||||
// (defined as typedef FT_LibraryRec_* FT_Library) because including
|
// (defined as typedef FT_LibraryRec_* FT_Library) because including
|
||||||
// freetype.h invokes indescribable horrors and we would like to avoid
|
// freetype.h invokes indescribable horrors and we would like to avoid
|
||||||
|
@ -122,9 +130,6 @@ static constexpr double LENGTH_EPS = 1e-6;
|
||||||
static constexpr double VERY_POSITIVE = 1e10;
|
static constexpr double VERY_POSITIVE = 1e10;
|
||||||
static constexpr double VERY_NEGATIVE = -1e10;
|
static constexpr double VERY_NEGATIVE = -1e10;
|
||||||
|
|
||||||
#include "platform/platform.h"
|
|
||||||
#include "platform/gui.h"
|
|
||||||
#include "resource.h"
|
|
||||||
|
|
||||||
using Platform::AllocTemporary;
|
using Platform::AllocTemporary;
|
||||||
using Platform::FreeAllTemporary;
|
using Platform::FreeAllTemporary;
|
||||||
|
@ -138,7 +143,8 @@ enum class Command : uint32_t;
|
||||||
enum class Unit : uint32_t {
|
enum class Unit : uint32_t {
|
||||||
MM = 0,
|
MM = 0,
|
||||||
INCHES,
|
INCHES,
|
||||||
METERS
|
METERS,
|
||||||
|
FEET_INCHES
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Key, class T>
|
template<class Key, class T>
|
||||||
|
@ -209,7 +215,7 @@ void Error(const char *fmt, ...);
|
||||||
|
|
||||||
class System {
|
class System {
|
||||||
public:
|
public:
|
||||||
enum { MAX_UNKNOWNS = 1024 };
|
enum { MAX_UNKNOWNS = 2048 };
|
||||||
|
|
||||||
EntityList entity;
|
EntityList entity;
|
||||||
ParamList param;
|
ParamList param;
|
||||||
|
@ -231,37 +237,34 @@ public:
|
||||||
// The system Jacobian matrix
|
// The system Jacobian matrix
|
||||||
struct {
|
struct {
|
||||||
// The corresponding equation for each row
|
// The corresponding equation for each row
|
||||||
hEquation eq[MAX_UNKNOWNS];
|
std::vector<Equation *> eq;
|
||||||
|
|
||||||
// The corresponding parameter for each column
|
// The corresponding parameter for each column
|
||||||
hParam param[MAX_UNKNOWNS];
|
std::vector<hParam> param;
|
||||||
|
|
||||||
// We're solving AX = B
|
// We're solving AX = B
|
||||||
int m, n;
|
int m, n;
|
||||||
struct {
|
struct {
|
||||||
Expr *sym[MAX_UNKNOWNS][MAX_UNKNOWNS];
|
// This only observes the Expr - does not own them!
|
||||||
double num[MAX_UNKNOWNS][MAX_UNKNOWNS];
|
Eigen::SparseMatrix<Expr *> sym;
|
||||||
|
Eigen::SparseMatrix<double> num;
|
||||||
} A;
|
} A;
|
||||||
|
|
||||||
double scale[MAX_UNKNOWNS];
|
Eigen::VectorXd scale;
|
||||||
|
Eigen::VectorXd X;
|
||||||
// Some helpers for the least squares solve
|
|
||||||
double AAt[MAX_UNKNOWNS][MAX_UNKNOWNS];
|
|
||||||
double Z[MAX_UNKNOWNS];
|
|
||||||
|
|
||||||
double X[MAX_UNKNOWNS];
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Expr *sym[MAX_UNKNOWNS];
|
// This only observes the Expr - does not own them!
|
||||||
double num[MAX_UNKNOWNS];
|
std::vector<Expr *> sym;
|
||||||
|
Eigen::VectorXd num;
|
||||||
} B;
|
} B;
|
||||||
} mat;
|
} mat;
|
||||||
|
|
||||||
static const double RANK_MAG_TOLERANCE, CONVERGE_TOLERANCE;
|
static const double CONVERGE_TOLERANCE;
|
||||||
int CalculateRank();
|
int CalculateRank();
|
||||||
bool TestRank(int *rank = NULL);
|
bool TestRank(int *dof = NULL);
|
||||||
static bool SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS],
|
static bool SolveLinearSystem(const Eigen::SparseMatrix<double> &A,
|
||||||
double B[], int N);
|
const Eigen::VectorXd &B, Eigen::VectorXd *X);
|
||||||
bool SolveLeastSquares();
|
bool SolveLeastSquares();
|
||||||
|
|
||||||
bool WriteJacobian(int tag);
|
bool WriteJacobian(int tag);
|
||||||
|
@ -277,7 +280,6 @@ public:
|
||||||
bool NewtonSolve(int tag);
|
bool NewtonSolve(int tag);
|
||||||
|
|
||||||
void MarkParamsFree(bool findFree);
|
void MarkParamsFree(bool findFree);
|
||||||
int CalculateDof();
|
|
||||||
|
|
||||||
SolveResult Solve(Group *g, int *rank = NULL, int *dof = NULL,
|
SolveResult Solve(Group *g, int *rank = NULL, int *dof = NULL,
|
||||||
List<hConstraint> *bad = NULL,
|
List<hConstraint> *bad = NULL,
|
||||||
|
@ -289,6 +291,9 @@ public:
|
||||||
bool andFindBad = false, bool andFindFree = false);
|
bool andFindBad = false, bool andFindFree = false);
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
|
Param *GetLastParamSubstitution(Param *p);
|
||||||
|
void SubstituteParamsByLast(Expr *e);
|
||||||
|
void SortSubstitutionByDragged(Param *p);
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "ttf.h"
|
#include "ttf.h"
|
||||||
|
@ -568,11 +573,14 @@ public:
|
||||||
double gridSpacing;
|
double gridSpacing;
|
||||||
double exportScale;
|
double exportScale;
|
||||||
double exportOffset;
|
double exportOffset;
|
||||||
|
bool arcDimDefaultDiameter;
|
||||||
|
bool showFullFilePath;
|
||||||
bool fixExportColors;
|
bool fixExportColors;
|
||||||
bool exportBackgroundColor;
|
bool exportBackgroundColor;
|
||||||
bool drawBackFaces;
|
bool drawBackFaces;
|
||||||
bool showContourAreas;
|
bool showContourAreas;
|
||||||
bool checkClosedContour;
|
bool checkClosedContour;
|
||||||
|
bool cameraNav;
|
||||||
bool turntableNav;
|
bool turntableNav;
|
||||||
bool immediatelyEditDimension;
|
bool immediatelyEditDimension;
|
||||||
bool automaticLineConstraints;
|
bool automaticLineConstraints;
|
||||||
|
@ -597,6 +605,7 @@ public:
|
||||||
} exportCanvas;
|
} exportCanvas;
|
||||||
struct {
|
struct {
|
||||||
double depth;
|
double depth;
|
||||||
|
double safeHeight;
|
||||||
int passes;
|
int passes;
|
||||||
double feed;
|
double feed;
|
||||||
double plungeFeed;
|
double plungeFeed;
|
||||||
|
@ -608,8 +617,10 @@ public:
|
||||||
int afterDecimalDegree;
|
int afterDecimalDegree;
|
||||||
bool useSIPrefixes;
|
bool useSIPrefixes;
|
||||||
int autosaveInterval; // in minutes
|
int autosaveInterval; // in minutes
|
||||||
|
bool explode;
|
||||||
|
double explodeDistance;
|
||||||
|
|
||||||
std::string MmToString(double v);
|
std::string MmToString(double v, bool editable=false);
|
||||||
std::string MmToStringSI(double v, int dim = 0);
|
std::string MmToStringSI(double v, int dim = 0);
|
||||||
std::string DegreeToString(double v);
|
std::string DegreeToString(double v);
|
||||||
double ExprToMm(Expr *e);
|
double ExprToMm(Expr *e);
|
||||||
|
@ -675,6 +686,7 @@ public:
|
||||||
void NewFile();
|
void NewFile();
|
||||||
bool SaveToFile(const Platform::Path &filename);
|
bool SaveToFile(const Platform::Path &filename);
|
||||||
bool LoadAutosaveFor(const Platform::Path &filename);
|
bool LoadAutosaveFor(const Platform::Path &filename);
|
||||||
|
std::function<void(const Platform::Path &filename, bool is_saveAs, bool is_autosave)> OnSaveFinished;
|
||||||
bool LoadFromFile(const Platform::Path &filename, bool canCancel = false);
|
bool LoadFromFile(const Platform::Path &filename, bool canCancel = false);
|
||||||
void UpgradeLegacyData();
|
void UpgradeLegacyData();
|
||||||
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
||||||
|
@ -785,9 +797,11 @@ public:
|
||||||
// the sketch!
|
// the sketch!
|
||||||
bool allConsistent;
|
bool allConsistent;
|
||||||
|
|
||||||
Platform::TimerRef showTWTimer;
|
bool scheduledGenerateAll;
|
||||||
Platform::TimerRef generateAllTimer;
|
bool scheduledShowTW;
|
||||||
|
Platform::TimerRef refreshTimer;
|
||||||
Platform::TimerRef autosaveTimer;
|
Platform::TimerRef autosaveTimer;
|
||||||
|
void Refresh();
|
||||||
void ScheduleShowTW();
|
void ScheduleShowTW();
|
||||||
void ScheduleGenerateAll();
|
void ScheduleGenerateAll();
|
||||||
void ScheduleAutosave();
|
void ScheduleAutosave();
|
||||||
|
@ -812,6 +826,7 @@ public:
|
||||||
void ImportDxf(const Platform::Path &file);
|
void ImportDxf(const Platform::Path &file);
|
||||||
void ImportDwg(const Platform::Path &file);
|
void ImportDwg(const Platform::Path &file);
|
||||||
bool LinkIDF(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh);
|
bool LinkIDF(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh);
|
||||||
|
bool LinkStl(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh);
|
||||||
|
|
||||||
extern SolveSpaceUI SS;
|
extern SolveSpaceUI SS;
|
||||||
extern Sketch SK;
|
extern Sketch SK;
|
||||||
|
|
|
@ -29,7 +29,7 @@ void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will be inserting other curve verticies into our curves to split them.
|
// We will be inserting other curve vertices into our curves to split them.
|
||||||
// This is helpful when curved surfaces become tangent along a trim and the
|
// This is helpful when curved surfaces become tangent along a trim and the
|
||||||
// usual tests for curve-surface intersection don't split the curve at a vertex.
|
// usual tests for curve-surface intersection don't split the curve at a vertex.
|
||||||
// This is faster than the previous version that split at surface corners and
|
// This is faster than the previous version that split at surface corners and
|
||||||
|
@ -270,32 +270,35 @@ void SSurface::TrimFromEdgeList(SEdgeList *el, bool asUv) {
|
||||||
|
|
||||||
static bool KeepRegion(SSurface::CombineAs type, bool opA, SShell::Class shell, SShell::Class orig)
|
static bool KeepRegion(SSurface::CombineAs type, bool opA, SShell::Class shell, SShell::Class orig)
|
||||||
{
|
{
|
||||||
bool inShell = (shell == SShell::Class::INSIDE),
|
bool inShell = (shell == SShell::Class::SURF_INSIDE),
|
||||||
outSide = (shell == SShell::Class::OUTSIDE),
|
outSide = (shell == SShell::Class::SURF_OUTSIDE),
|
||||||
inSame = (shell == SShell::Class::COINC_SAME),
|
coincSame = (shell == SShell::Class::SURF_COINC_SAME),
|
||||||
inOrig = (orig == SShell::Class::INSIDE);
|
coincOpp = (shell == SShell::Class::SURF_COINC_OPP),
|
||||||
|
inOrig = (orig == SShell::Class::SURF_INSIDE);
|
||||||
|
|
||||||
|
// This one line is not really part of this functions logic
|
||||||
if(!inOrig) return false;
|
if(!inOrig) return false;
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case SSurface::CombineAs::UNION:
|
case SSurface::CombineAs::UNION:
|
||||||
if(opA) {
|
if(opA) {
|
||||||
return outSide;
|
return outSide;
|
||||||
} else {
|
} else {
|
||||||
return outSide || inSame;
|
return outSide || coincSame;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SSurface::CombineAs::DIFFERENCE:
|
case SSurface::CombineAs::DIFFERENCE:
|
||||||
if(opA) {
|
if(opA) {
|
||||||
return outSide;
|
return outSide || coincOpp;
|
||||||
} else {
|
} else {
|
||||||
return inShell || inSame;
|
return inShell;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SSurface::CombineAs::INTERSECTION:
|
case SSurface::CombineAs::INTERSECTION:
|
||||||
if(opA) {
|
if(opA) {
|
||||||
return inShell;
|
return inShell;
|
||||||
} else {
|
} else {
|
||||||
return inShell || inSame;
|
return inShell || coincSame;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: ssassert(false, "Unexpected combine type");
|
default: ssassert(false, "Unexpected combine type");
|
||||||
|
@ -318,29 +321,29 @@ static void TagByClassifiedEdge(SBspUv::Class bspclass, SShell::Class *indir, SS
|
||||||
{
|
{
|
||||||
switch(bspclass) {
|
switch(bspclass) {
|
||||||
case SBspUv::Class::INSIDE:
|
case SBspUv::Class::INSIDE:
|
||||||
*indir = SShell::Class::INSIDE;
|
*indir = SShell::Class::SURF_INSIDE;
|
||||||
*outdir = SShell::Class::INSIDE;
|
*outdir = SShell::Class::SURF_INSIDE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SBspUv::Class::OUTSIDE:
|
case SBspUv::Class::OUTSIDE:
|
||||||
*indir = SShell::Class::OUTSIDE;
|
*indir = SShell::Class::SURF_OUTSIDE;
|
||||||
*outdir = SShell::Class::OUTSIDE;
|
*outdir = SShell::Class::SURF_OUTSIDE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SBspUv::Class::EDGE_PARALLEL:
|
case SBspUv::Class::EDGE_PARALLEL:
|
||||||
*indir = SShell::Class::INSIDE;
|
*indir = SShell::Class::SURF_INSIDE;
|
||||||
*outdir = SShell::Class::OUTSIDE;
|
*outdir = SShell::Class::SURF_OUTSIDE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SBspUv::Class::EDGE_ANTIPARALLEL:
|
case SBspUv::Class::EDGE_ANTIPARALLEL:
|
||||||
*indir = SShell::Class::OUTSIDE;
|
*indir = SShell::Class::SURF_OUTSIDE;
|
||||||
*outdir = SShell::Class::INSIDE;
|
*outdir = SShell::Class::SURF_INSIDE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
dbp("TagByClassifiedEdge: fail!");
|
dbp("TagByClassifiedEdge: fail!");
|
||||||
*indir = SShell::Class::OUTSIDE;
|
*indir = SShell::Class::SURF_OUTSIDE;
|
||||||
*outdir = SShell::Class::OUTSIDE;
|
*outdir = SShell::Class::SURF_OUTSIDE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,13 +441,12 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv,
|
||||||
double t;
|
double t;
|
||||||
sc->exact.ClosestPointTo(*pt, &t, /*mustConverge=*/false);
|
sc->exact.ClosestPointTo(*pt, &t, /*mustConverge=*/false);
|
||||||
*pt = sc->exact.PointAt(t);
|
*pt = sc->exact.PointAt(t);
|
||||||
ClosestPointTo(*pt, &muv);
|
|
||||||
} else if(!sc->isExact) {
|
} else if(!sc->isExact) {
|
||||||
SSurface *trimmedA = sc->GetSurfaceA(sha, shb),
|
SSurface *trimmedA = sc->GetSurfaceA(sha, shb),
|
||||||
*trimmedB = sc->GetSurfaceB(sha, shb);
|
*trimmedB = sc->GetSurfaceB(sha, shb);
|
||||||
*pt = trimmedA->ClosestPointOnThisAndSurface(trimmedB, *pt);
|
*pt = trimmedA->ClosestPointOnThisAndSurface(trimmedB, *pt);
|
||||||
ClosestPointTo(*pt, &muv);
|
|
||||||
}
|
}
|
||||||
|
ClosestPointTo(*pt, &muv);
|
||||||
|
|
||||||
*surfn = NormalAt(muv.x, muv.y);
|
*surfn = NormalAt(muv.x, muv.y);
|
||||||
|
|
||||||
|
@ -472,6 +474,9 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv,
|
||||||
pout = PointAt(muv.Plus(enuv));
|
pout = PointAt(muv.Plus(enuv));
|
||||||
*enin = pin.Minus(*pt),
|
*enin = pin.Minus(*pt),
|
||||||
*enout = pout.Minus(*pt);
|
*enout = pout.Minus(*pt);
|
||||||
|
// ideally this should work (fail screwdriver file)
|
||||||
|
// *enin = enxyz.ScaledBy(-1.0);
|
||||||
|
// *enout = enxyz;
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -612,8 +617,8 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent,
|
||||||
|
|
||||||
SShell::Class indir_shell, outdir_shell, indir_orig, outdir_orig;
|
SShell::Class indir_shell, outdir_shell, indir_orig, outdir_orig;
|
||||||
|
|
||||||
indir_orig = SShell::Class::INSIDE;
|
indir_orig = SShell::Class::SURF_INSIDE;
|
||||||
outdir_orig = SShell::Class::OUTSIDE;
|
outdir_orig = SShell::Class::SURF_OUTSIDE;
|
||||||
|
|
||||||
agnst->ClassifyEdge(&indir_shell, &outdir_shell,
|
agnst->ClassifyEdge(&indir_shell, &outdir_shell,
|
||||||
ret.PointAt(auv), ret.PointAt(buv), pt,
|
ret.PointAt(auv), ret.PointAt(buv), pt,
|
||||||
|
@ -796,7 +801,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) {
|
||||||
b->MakeClassifyingBsps(NULL);
|
b->MakeClassifyingBsps(NULL);
|
||||||
|
|
||||||
// Copy over all the original curves, splitting them so that a
|
// Copy over all the original curves, splitting them so that a
|
||||||
// piecwise linear segment never crosses a surface from the other
|
// piecewise linear segment never crosses a surface from the other
|
||||||
// shell.
|
// shell.
|
||||||
a->CopyCurvesSplitAgainst(/*opA=*/true, b, this);
|
a->CopyCurvesSplitAgainst(/*opA=*/true, b, this);
|
||||||
b->CopyCurvesSplitAgainst(/*opA=*/false, a, this);
|
b->CopyCurvesSplitAgainst(/*opA=*/false, a, this);
|
||||||
|
|
|
@ -817,7 +817,7 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the curve is exact and points are >0.05 appart wrt t, point is there
|
// if the curve is exact and points are >0.05 apart wrt t, point is there
|
||||||
// deliberately regardless of chord tolerance (ex: small circles)
|
// deliberately regardless of chord tolerance (ex: small circles)
|
||||||
tprev = t = tnext = 0;
|
tprev = t = tnext = 0;
|
||||||
if (isExact) {
|
if (isExact) {
|
||||||
|
|
|
@ -398,14 +398,14 @@ SShell::Class SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n,
|
||||||
// are coincident. Test the edge's surface normal
|
// are coincident. Test the edge's surface normal
|
||||||
// to see if it's with same or opposite normals.
|
// to see if it's with same or opposite normals.
|
||||||
if(inter_surf_n.Dot(edge_surf_n) > 0) {
|
if(inter_surf_n.Dot(edge_surf_n) > 0) {
|
||||||
return Class::COINC_SAME;
|
return Class::SURF_COINC_SAME;
|
||||||
} else {
|
} else {
|
||||||
return Class::COINC_OPP;
|
return Class::SURF_COINC_OPP;
|
||||||
}
|
}
|
||||||
} else if(dot > 0) {
|
} else if(dot > 0) {
|
||||||
return Class::OUTSIDE;
|
return Class::SURF_OUTSIDE;
|
||||||
} else {
|
} else {
|
||||||
return Class::INSIDE;
|
return Class::SURF_INSIDE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,7 +474,7 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
||||||
swap(inter_edge_n[0], inter_edge_n[1]);
|
swap(inter_edge_n[0], inter_edge_n[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Class coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? Class::COINC_SAME : Class::COINC_OPP;
|
Class coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? Class::SURF_COINC_SAME : Class::SURF_COINC_OPP;
|
||||||
|
|
||||||
if(fabs(dotp[0]) < DOTP_TOL && fabs(dotp[1]) < DOTP_TOL) {
|
if(fabs(dotp[0]) < DOTP_TOL && fabs(dotp[1]) < DOTP_TOL) {
|
||||||
// This is actually an edge on face case, just that the face
|
// This is actually an edge on face case, just that the face
|
||||||
|
@ -484,25 +484,25 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
||||||
} else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] > DOTP_TOL) {
|
} else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] > DOTP_TOL) {
|
||||||
if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
|
if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
|
||||||
*indir = coinc;
|
*indir = coinc;
|
||||||
*outdir = Class::OUTSIDE;
|
*outdir = Class::SURF_OUTSIDE;
|
||||||
} else {
|
} else {
|
||||||
*indir = Class::INSIDE;
|
*indir = Class::SURF_INSIDE;
|
||||||
*outdir = coinc;
|
*outdir = coinc;
|
||||||
}
|
}
|
||||||
} else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) {
|
} else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) {
|
||||||
if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
|
if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
|
||||||
*indir = coinc;
|
*indir = coinc;
|
||||||
*outdir = Class::INSIDE;
|
*outdir = Class::SURF_INSIDE;
|
||||||
} else {
|
} else {
|
||||||
*indir = Class::OUTSIDE;
|
*indir = Class::SURF_OUTSIDE;
|
||||||
*outdir = coinc;
|
*outdir = coinc;
|
||||||
}
|
}
|
||||||
} else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) {
|
} else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) {
|
||||||
*indir = Class::INSIDE;
|
*indir = Class::SURF_INSIDE;
|
||||||
*outdir = Class::OUTSIDE;
|
*outdir = Class::SURF_OUTSIDE;
|
||||||
} else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) {
|
} else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) {
|
||||||
*indir = Class::OUTSIDE;
|
*indir = Class::SURF_OUTSIDE;
|
||||||
*outdir = Class::INSIDE;
|
*outdir = Class::SURF_INSIDE;
|
||||||
} else {
|
} else {
|
||||||
// Edge is tangent to the shell at shell's edge, so can't be
|
// Edge is tangent to the shell at shell's edge, so can't be
|
||||||
// a boundary of the surface.
|
// a boundary of the surface.
|
||||||
|
@ -530,7 +530,7 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
||||||
SBspUv::Class c = (srf.bsp) ? srf.bsp->ClassifyPoint(puv, dummy, &srf) : SBspUv::Class::OUTSIDE;
|
SBspUv::Class c = (srf.bsp) ? srf.bsp->ClassifyPoint(puv, dummy, &srf) : SBspUv::Class::OUTSIDE;
|
||||||
if(c == SBspUv::Class::OUTSIDE) continue;
|
if(c == SBspUv::Class::OUTSIDE) continue;
|
||||||
|
|
||||||
// Edge-on-face (unless edge-on-edge above superceded)
|
// Edge-on-face (unless edge-on-edge above superseded)
|
||||||
Point2d pin, pout;
|
Point2d pin, pout;
|
||||||
srf.ClosestPointTo(p.Plus(edge_n_in), &pin, /*mustConverge=*/false);
|
srf.ClosestPointTo(p.Plus(edge_n_in), &pin, /*mustConverge=*/false);
|
||||||
srf.ClosestPointTo(p.Plus(edge_n_out), &pout, /*mustConverge=*/false);
|
srf.ClosestPointTo(p.Plus(edge_n_out), &pout, /*mustConverge=*/false);
|
||||||
|
@ -557,8 +557,8 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
||||||
/*asSegment=*/false, /*trimmed=*/true, /*inclTangent=*/false);
|
/*asSegment=*/false, /*trimmed=*/true, /*inclTangent=*/false);
|
||||||
|
|
||||||
// no intersections means it's outside
|
// no intersections means it's outside
|
||||||
*indir = Class::OUTSIDE;
|
*indir = Class::SURF_OUTSIDE;
|
||||||
*outdir = Class::OUTSIDE;
|
*outdir = Class::SURF_OUTSIDE;
|
||||||
double dmin = VERY_POSITIVE;
|
double dmin = VERY_POSITIVE;
|
||||||
bool onEdge = false;
|
bool onEdge = false;
|
||||||
edge_inters = 0;
|
edge_inters = 0;
|
||||||
|
@ -584,11 +584,11 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
||||||
// Edge does not lie on surface; either strictly inside
|
// Edge does not lie on surface; either strictly inside
|
||||||
// or strictly outside
|
// or strictly outside
|
||||||
if((si->surfNormal).Dot(ray) > 0) {
|
if((si->surfNormal).Dot(ray) > 0) {
|
||||||
*indir = Class::INSIDE;
|
*indir = Class::SURF_INSIDE;
|
||||||
*outdir = Class::INSIDE;
|
*outdir = Class::SURF_INSIDE;
|
||||||
} else {
|
} else {
|
||||||
*indir = Class::OUTSIDE;
|
*indir = Class::SURF_OUTSIDE;
|
||||||
*outdir = Class::OUTSIDE;
|
*outdir = Class::SURF_OUTSIDE;
|
||||||
}
|
}
|
||||||
onEdge = si->onEdge;
|
onEdge = si->onEdge;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,614 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Anything involving NURBS shells (i.e., shells); except
|
||||||
|
// for the real math, which is in ratpoly.cpp.
|
||||||
|
//
|
||||||
|
// Copyright 2008-2013 Jonathan Westhues.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
#include "../solvespace.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
hSCurve hc;
|
||||||
|
hSSurface hs;
|
||||||
|
} TrimLine;
|
||||||
|
|
||||||
|
|
||||||
|
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color)
|
||||||
|
{
|
||||||
|
// Make the extrusion direction consistent with respect to the normal
|
||||||
|
// of the sketch we're extruding.
|
||||||
|
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
|
||||||
|
swap(t0, t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a coordinate system to contain the original sketch, and get
|
||||||
|
// a bounding box in that csys
|
||||||
|
Vector n = sbls->normal.ScaledBy(-1);
|
||||||
|
Vector u = n.Normal(0), v = n.Normal(1);
|
||||||
|
Vector orig = sbls->point;
|
||||||
|
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||||
|
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||||
|
// and now fix things up so that all u and v lie between 0 and 1
|
||||||
|
orig = orig.Plus(u.ScaledBy(umin));
|
||||||
|
orig = orig.Plus(v.ScaledBy(vmin));
|
||||||
|
u = u.ScaledBy(umax - umin);
|
||||||
|
v = v.ScaledBy(vmax - vmin);
|
||||||
|
|
||||||
|
// So we can now generate the top and bottom surfaces of the extrusion,
|
||||||
|
// planes within a translated (and maybe mirrored) version of that csys.
|
||||||
|
SSurface s0, s1;
|
||||||
|
s0 = SSurface::FromPlane(orig.Plus(t0), u, v);
|
||||||
|
s0.color = color;
|
||||||
|
s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);
|
||||||
|
s1.color = color;
|
||||||
|
hSSurface hs0 = surface.AddAndAssignId(&s0),
|
||||||
|
hs1 = surface.AddAndAssignId(&s1);
|
||||||
|
|
||||||
|
// Now go through the input curves. For each one, generate its surface
|
||||||
|
// of extrusion, its two translated trim curves, and one trim line. We
|
||||||
|
// go through by loops so that we can assign the lines correctly.
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
SBezier *sb;
|
||||||
|
List<TrimLine> trimLines = {};
|
||||||
|
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
// Generate the surface of extrusion of this curve, and add
|
||||||
|
// it to the list
|
||||||
|
SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);
|
||||||
|
ss.color = color;
|
||||||
|
hSSurface hsext = surface.AddAndAssignId(&ss);
|
||||||
|
|
||||||
|
// Translate the curve by t0 and t1 to produce two trim curves
|
||||||
|
SCurve sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = hs0;
|
||||||
|
sc.surfB = hsext;
|
||||||
|
hSCurve hc0 = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = hs1;
|
||||||
|
sc.surfB = hsext;
|
||||||
|
hSCurve hc1 = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb0, stb1;
|
||||||
|
// The translated curves trim the flat top and bottom surfaces.
|
||||||
|
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/false);
|
||||||
|
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/true);
|
||||||
|
(surface.FindById(hs0))->trim.Add(&stb0);
|
||||||
|
(surface.FindById(hs1))->trim.Add(&stb1);
|
||||||
|
|
||||||
|
// The translated curves also trim the surface of extrusion.
|
||||||
|
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/true);
|
||||||
|
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/false);
|
||||||
|
(surface.FindById(hsext))->trim.Add(&stb0);
|
||||||
|
(surface.FindById(hsext))->trim.Add(&stb1);
|
||||||
|
|
||||||
|
// And form the trim line
|
||||||
|
Vector pt = sb->Finish();
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
hSCurve hl = curve.AddAndAssignId(&sc);
|
||||||
|
// save this for later
|
||||||
|
TrimLine tl;
|
||||||
|
tl.hc = hl;
|
||||||
|
tl.hs = hsext;
|
||||||
|
trimLines.Add(&tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < trimLines.n; i++) {
|
||||||
|
TrimLine *tl = &(trimLines[i]);
|
||||||
|
SSurface *ss = surface.FindById(tl->hs);
|
||||||
|
|
||||||
|
TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true);
|
||||||
|
ss->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, tlp->hc, /*backwards=*/false);
|
||||||
|
ss->trim.Add(&stb);
|
||||||
|
|
||||||
|
(curve.FindById(tl->hc))->surfA = ss->h;
|
||||||
|
(curve.FindById(tlp->hc))->surfB = ss->h;
|
||||||
|
}
|
||||||
|
trimLines.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx)
|
||||||
|
// Check that the direction of revolution/extrusion ends up parallel to the normal of
|
||||||
|
// the sketch, on the side of the axis where the sketch is.
|
||||||
|
{
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
Vector pto;
|
||||||
|
double md = VERY_NEGATIVE;
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
SBezier *sb;
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
// Choose the point farthest from the axis; we'll get garbage
|
||||||
|
// if we choose a point that lies on the axis, for example.
|
||||||
|
// (And our surface will be self-intersecting if the sketch
|
||||||
|
// spans the axis, so don't worry about that.)
|
||||||
|
for(int i = 0; i <= sb->deg; i++) {
|
||||||
|
Vector p = sb->ctrl[i];
|
||||||
|
double d = p.DistanceToLine(pt, axis);
|
||||||
|
if(d > md) {
|
||||||
|
md = d;
|
||||||
|
pto = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector ptc = pto.ClosestPointOnLine(pt, axis),
|
||||||
|
up = axis.Cross(pto.Minus(ptc)).ScaledBy(da),
|
||||||
|
vp = up.Plus(axis.ScaledBy(dx));
|
||||||
|
|
||||||
|
return (vp.Dot(sbls->normal) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sketch must not contain the axis of revolution as a non-construction line for helix
|
||||||
|
void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
|
||||||
|
RgbaColor color, Group *group, double angles,
|
||||||
|
double anglef, double dists, double distf) {
|
||||||
|
int i0 = surface.n; // number of pre-existing surfaces
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
// for testing - hard code the axial distance, and number of sections.
|
||||||
|
// distance will need to be parameters in the future.
|
||||||
|
double dist = distf - dists;
|
||||||
|
int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1);
|
||||||
|
double wedge = (anglef - angles) / sections;
|
||||||
|
int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END;
|
||||||
|
|
||||||
|
if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) {
|
||||||
|
swap(angles, anglef);
|
||||||
|
swap(dists, distf);
|
||||||
|
dist = -dist;
|
||||||
|
wedge = -wedge;
|
||||||
|
swap(startMapping, endMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a coordinate system to contain the original sketch, and get
|
||||||
|
// a bounding box in that csys
|
||||||
|
Vector n = sbls->normal.ScaledBy(-1);
|
||||||
|
Vector u = n.Normal(0), v = n.Normal(1);
|
||||||
|
Vector orig = sbls->point;
|
||||||
|
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||||
|
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||||
|
// and now fix things up so that all u and v lie between 0 and 1
|
||||||
|
orig = orig.Plus(u.ScaledBy(umin));
|
||||||
|
orig = orig.Plus(v.ScaledBy(vmin));
|
||||||
|
u = u.ScaledBy(umax - umin);
|
||||||
|
v = v.ScaledBy(vmax - vmin);
|
||||||
|
|
||||||
|
// So we can now generate the end caps of the extrusion within
|
||||||
|
// a translated and rotated (and maybe mirrored) version of that csys.
|
||||||
|
SSurface s0, s1;
|
||||||
|
s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)),
|
||||||
|
u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles));
|
||||||
|
s0.color = color;
|
||||||
|
|
||||||
|
hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping);
|
||||||
|
s0.face = face0.v;
|
||||||
|
|
||||||
|
s1 = SSurface::FromPlane(
|
||||||
|
orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)),
|
||||||
|
u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef));
|
||||||
|
s1.color = color;
|
||||||
|
|
||||||
|
hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping);
|
||||||
|
s1.face = face1.v;
|
||||||
|
|
||||||
|
hSSurface hs0 = surface.AddAndAssignId(&s0);
|
||||||
|
hSSurface hs1 = surface.AddAndAssignId(&s1);
|
||||||
|
|
||||||
|
// Now we actually build and trim the swept surfaces. One loop at a time.
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
int i, j;
|
||||||
|
SBezier *sb;
|
||||||
|
List<std::vector<hSSurface>> hsl = {};
|
||||||
|
|
||||||
|
// This is where all the NURBS are created and Remapped to the generating curve
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
std::vector<hSSurface> revs(sections);
|
||||||
|
for(j = 0; j < sections; j++) {
|
||||||
|
if((dist == 0) && sb->deg == 1 &&
|
||||||
|
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
||||||
|
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) {
|
||||||
|
// This is a line on the axis of revolution; it does
|
||||||
|
// not contribute a surface.
|
||||||
|
revs[j].v = 0;
|
||||||
|
} else {
|
||||||
|
SSurface ss = SSurface::FromRevolutionOf(
|
||||||
|
sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1),
|
||||||
|
dists + j * dist / sections, dists + (j + 1) * dist / sections);
|
||||||
|
ss.color = color;
|
||||||
|
if(sb->entity != 0) {
|
||||||
|
hEntity he;
|
||||||
|
he.v = sb->entity;
|
||||||
|
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
||||||
|
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
||||||
|
ss.face = hface.v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revs[j] = surface.AddAndAssignId(&ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hsl.Add(&revs);
|
||||||
|
}
|
||||||
|
// Still the same loop. Need to create trim curves
|
||||||
|
for(i = 0; i < sbl->l.n; i++) {
|
||||||
|
std::vector<hSSurface> revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)];
|
||||||
|
|
||||||
|
sb = &(sbl->l[i]);
|
||||||
|
|
||||||
|
// we will need the grid t-values for this entire row of surfaces
|
||||||
|
List<double> t_values;
|
||||||
|
t_values = {};
|
||||||
|
if (revs[0].v) {
|
||||||
|
double ps = 0.0;
|
||||||
|
t_values.Add(&ps);
|
||||||
|
(surface.FindById(revs[0]))->MakeTriangulationGridInto(
|
||||||
|
&t_values, 0.0, 1.0, true, 0);
|
||||||
|
}
|
||||||
|
// we generate one more curve than we did surfaces
|
||||||
|
for(j = 0; j <= sections; j++) {
|
||||||
|
SCurve sc;
|
||||||
|
Quaternion qs = Quaternion::From(axis, angles + wedge * j);
|
||||||
|
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
||||||
|
Vector ts =
|
||||||
|
pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections));
|
||||||
|
|
||||||
|
// If this input curve generated a surface, then trim that
|
||||||
|
// surface with the rotated version of the input curve.
|
||||||
|
if(revs[0].v) { // not d[j] because crash on j==sections
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
||||||
|
// make the PWL for the curve based on t value list
|
||||||
|
for(int x = 0; x < t_values.n; x++) {
|
||||||
|
SCurvePt scpt;
|
||||||
|
scpt.tag = 0;
|
||||||
|
scpt.p = sc.exact.PointAt(t_values[x]);
|
||||||
|
scpt.vertex = (x == 0) || (x == (t_values.n - 1));
|
||||||
|
sc.pts.Add(&scpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the surfaces already exists so trim with this curve
|
||||||
|
if(j < sections) {
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
} else {
|
||||||
|
sc.surfA = hs1; // end cap
|
||||||
|
}
|
||||||
|
|
||||||
|
if(j > 0) {
|
||||||
|
sc.surfB = revs[j - 1];
|
||||||
|
} else {
|
||||||
|
sc.surfB = hs0; // staring cap
|
||||||
|
}
|
||||||
|
|
||||||
|
hSCurve hcb = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
} else if(j == 0) { // curve was on the rotation axis and is shared by the end caps.
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = hs1; // end cap
|
||||||
|
sc.surfB = hs0; // staring cap
|
||||||
|
hSCurve hcb = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And if this input curve and the one after it both generated
|
||||||
|
// surfaces, then trim both of those by the appropriate
|
||||||
|
// curve based on the control points.
|
||||||
|
if((j < sections) && revs[j].v && revsp[j].v) {
|
||||||
|
SSurface *ss = surface.FindById(revs[j]);
|
||||||
|
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]);
|
||||||
|
sc.exact.weight[1] = ss->weight[0][1];
|
||||||
|
double max_dt = 0.5;
|
||||||
|
if (sc.exact.deg > 1) max_dt = 0.125;
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
sc.surfB = revsp[j];
|
||||||
|
|
||||||
|
hSCurve hcc = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t_values.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
hsl.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dist == 0) {
|
||||||
|
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color,
|
||||||
|
Group *group) {
|
||||||
|
int i0 = surface.n; // number of pre-existing surfaces
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
|
||||||
|
if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) {
|
||||||
|
axis = axis.ScaledBy(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we actually build and trim the surfaces.
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
int i, j;
|
||||||
|
SBezier *sb;
|
||||||
|
List<std::vector<hSSurface>> hsl = {};
|
||||||
|
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
std::vector<hSSurface> revs(4);
|
||||||
|
for(j = 0; j < 4; j++) {
|
||||||
|
if(sb->deg == 1 &&
|
||||||
|
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
||||||
|
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS)
|
||||||
|
{
|
||||||
|
// This is a line on the axis of revolution; it does
|
||||||
|
// not contribute a surface.
|
||||||
|
revs[j].v = 0;
|
||||||
|
} else {
|
||||||
|
SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j,
|
||||||
|
(PI / 2) * (j + 1), 0.0, 0.0);
|
||||||
|
ss.color = color;
|
||||||
|
if(sb->entity != 0) {
|
||||||
|
hEntity he;
|
||||||
|
he.v = sb->entity;
|
||||||
|
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
||||||
|
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
||||||
|
ss.face = hface.v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revs[j] = surface.AddAndAssignId(&ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hsl.Add(&revs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < sbl->l.n; i++) {
|
||||||
|
std::vector<hSSurface> revs = hsl[i],
|
||||||
|
revsp = hsl[WRAP(i-1, sbl->l.n)];
|
||||||
|
|
||||||
|
sb = &(sbl->l[i]);
|
||||||
|
|
||||||
|
for(j = 0; j < 4; j++) {
|
||||||
|
SCurve sc;
|
||||||
|
Quaternion qs = Quaternion::From(axis, (PI/2)*j);
|
||||||
|
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
||||||
|
Vector ts = pt.Minus(qs.Rotate(pt));
|
||||||
|
|
||||||
|
// If this input curve generate a surface, then trim that
|
||||||
|
// surface with the rotated version of the input curve.
|
||||||
|
if(revs[j].v) {
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
sc.surfB = revs[WRAP(j-1, 4)];
|
||||||
|
|
||||||
|
hSCurve hcb = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And if this input curve and the one after it both generated
|
||||||
|
// surfaces, then trim both of those by the appropriate
|
||||||
|
// circle.
|
||||||
|
if(revs[j].v && revsp[j].v) {
|
||||||
|
SSurface *ss = surface.FindById(revs[j]);
|
||||||
|
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = SBezier::From(ss->ctrl[0][0],
|
||||||
|
ss->ctrl[0][1],
|
||||||
|
ss->ctrl[0][2]);
|
||||||
|
sc.exact.weight[1] = ss->weight[0][1];
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
sc.surfB = revsp[j];
|
||||||
|
|
||||||
|
hSCurve hcc = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hsl.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = i0; i < surface.n; i++) {
|
||||||
|
SSurface *srf = &(surface[i]);
|
||||||
|
|
||||||
|
// Revolution of a line; this is potentially a plane, which we can
|
||||||
|
// rewrite to have degree (1, 1).
|
||||||
|
if(srf->degm == 1 && srf->degn == 2) {
|
||||||
|
// close start, far start, far finish
|
||||||
|
Vector cs, fs, ff;
|
||||||
|
double d0, d1;
|
||||||
|
d0 = (srf->ctrl[0][0]).DistanceToLine(pt, axis);
|
||||||
|
d1 = (srf->ctrl[1][0]).DistanceToLine(pt, axis);
|
||||||
|
|
||||||
|
if(d0 > d1) {
|
||||||
|
cs = srf->ctrl[1][0];
|
||||||
|
fs = srf->ctrl[0][0];
|
||||||
|
ff = srf->ctrl[0][2];
|
||||||
|
} else {
|
||||||
|
cs = srf->ctrl[0][0];
|
||||||
|
fs = srf->ctrl[1][0];
|
||||||
|
ff = srf->ctrl[1][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// origin close, origin far
|
||||||
|
Vector oc = cs.ClosestPointOnLine(pt, axis),
|
||||||
|
of = fs.ClosestPointOnLine(pt, axis);
|
||||||
|
|
||||||
|
if(oc.Equals(of)) {
|
||||||
|
// This is a plane, not a (non-degenerate) cone.
|
||||||
|
Vector oldn = srf->NormalAt(0.5, 0.5);
|
||||||
|
|
||||||
|
Vector u = fs.Minus(of), v;
|
||||||
|
|
||||||
|
v = (axis.Cross(u)).WithMagnitude(1);
|
||||||
|
|
||||||
|
double vm = (ff.Minus(of)).Dot(v);
|
||||||
|
v = v.ScaledBy(vm);
|
||||||
|
|
||||||
|
srf->degm = 1;
|
||||||
|
srf->degn = 1;
|
||||||
|
srf->ctrl[0][0] = of;
|
||||||
|
srf->ctrl[0][1] = of.Plus(u);
|
||||||
|
srf->ctrl[1][0] = of.Plus(v);
|
||||||
|
srf->ctrl[1][1] = of.Plus(u).Plus(v);
|
||||||
|
srf->weight[0][0] = 1;
|
||||||
|
srf->weight[0][1] = 1;
|
||||||
|
srf->weight[1][0] = 1;
|
||||||
|
srf->weight[1][1] = 1;
|
||||||
|
|
||||||
|
if(oldn.Dot(srf->NormalAt(0.5, 0.5)) < 0) {
|
||||||
|
swap(srf->ctrl[0][0], srf->ctrl[1][0]);
|
||||||
|
swap(srf->ctrl[0][1], srf->ctrl[1][1]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fabs(d0 - d1) < LENGTH_EPS) {
|
||||||
|
// This is a cylinder; so transpose it so that we'll recognize
|
||||||
|
// it as a surface of extrusion.
|
||||||
|
SSurface sn = *srf;
|
||||||
|
|
||||||
|
// Transposing u and v flips the normal, so reverse u to
|
||||||
|
// flip it again and put it back where we started.
|
||||||
|
sn.degm = 2;
|
||||||
|
sn.degn = 1;
|
||||||
|
int dm, dn;
|
||||||
|
for(dm = 0; dm <= 1; dm++) {
|
||||||
|
for(dn = 0; dn <= 2; dn++) {
|
||||||
|
sn.ctrl [dn][dm] = srf->ctrl [1-dm][dn];
|
||||||
|
sn.weight[dn][dm] = srf->weight[1-dm][dn];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*srf = sn;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFromCopyOf(SShell *a) {
|
||||||
|
ssassert(this != a, "Can't make from copy of self");
|
||||||
|
MakeFromTransformationOf(a,
|
||||||
|
Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFromTransformationOf(SShell *a,
|
||||||
|
Vector t, Quaternion q, double scale)
|
||||||
|
{
|
||||||
|
booleanFailed = false;
|
||||||
|
surface.ReserveMore(a->surface.n);
|
||||||
|
for(SSurface &s : a->surface) {
|
||||||
|
SSurface n;
|
||||||
|
n = SSurface::FromTransformationOf(&s, t, q, scale, /*includingTrims=*/true);
|
||||||
|
surface.Add(&n); // keeping the old ID
|
||||||
|
}
|
||||||
|
|
||||||
|
curve.ReserveMore(a->curve.n);
|
||||||
|
for(SCurve &c : a->curve) {
|
||||||
|
SCurve n;
|
||||||
|
n = SCurve::FromTransformationOf(&c, t, q, scale);
|
||||||
|
curve.Add(&n); // keeping the old ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeEdgesInto(SEdgeList *sel) {
|
||||||
|
for(SSurface &s : surface) {
|
||||||
|
s.MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl)
|
||||||
|
{
|
||||||
|
for(SSurface &s : surface) {
|
||||||
|
if(s.CoincidentWithPlane(n, d)) {
|
||||||
|
s.MakeSectionEdgesInto(this, sel, sbl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::TriangulateInto(SMesh *sm) {
|
||||||
|
#pragma omp parallel for
|
||||||
|
for(int i=0; i<surface.n; i++) {
|
||||||
|
SSurface *s = &surface[i];
|
||||||
|
SMesh m;
|
||||||
|
s->TriangulateInto(this, &m);
|
||||||
|
#pragma omp critical
|
||||||
|
sm->MakeFromCopyOf(&m);
|
||||||
|
m.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SShell::IsEmpty() const {
|
||||||
|
return surface.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::Clear() {
|
||||||
|
for(SSurface &s : surface) {
|
||||||
|
s.Clear();
|
||||||
|
}
|
||||||
|
surface.Clear();
|
||||||
|
|
||||||
|
for(SCurve &c : curve) {
|
||||||
|
c.Clear();
|
||||||
|
}
|
||||||
|
curve.Clear();
|
||||||
|
}
|
|
@ -489,608 +489,4 @@ void SSurface::Clear() {
|
||||||
trim.Clear();
|
trim.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
hSCurve hc;
|
|
||||||
hSSurface hs;
|
|
||||||
} TrimLine;
|
|
||||||
|
|
||||||
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color)
|
|
||||||
{
|
|
||||||
// Make the extrusion direction consistent with respect to the normal
|
|
||||||
// of the sketch we're extruding.
|
|
||||||
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
|
|
||||||
swap(t0, t1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a coordinate system to contain the original sketch, and get
|
|
||||||
// a bounding box in that csys
|
|
||||||
Vector n = sbls->normal.ScaledBy(-1);
|
|
||||||
Vector u = n.Normal(0), v = n.Normal(1);
|
|
||||||
Vector orig = sbls->point;
|
|
||||||
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
|
||||||
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
|
||||||
// and now fix things up so that all u and v lie between 0 and 1
|
|
||||||
orig = orig.Plus(u.ScaledBy(umin));
|
|
||||||
orig = orig.Plus(v.ScaledBy(vmin));
|
|
||||||
u = u.ScaledBy(umax - umin);
|
|
||||||
v = v.ScaledBy(vmax - vmin);
|
|
||||||
|
|
||||||
// So we can now generate the top and bottom surfaces of the extrusion,
|
|
||||||
// planes within a translated (and maybe mirrored) version of that csys.
|
|
||||||
SSurface s0, s1;
|
|
||||||
s0 = SSurface::FromPlane(orig.Plus(t0), u, v);
|
|
||||||
s0.color = color;
|
|
||||||
s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);
|
|
||||||
s1.color = color;
|
|
||||||
hSSurface hs0 = surface.AddAndAssignId(&s0),
|
|
||||||
hs1 = surface.AddAndAssignId(&s1);
|
|
||||||
|
|
||||||
// Now go through the input curves. For each one, generate its surface
|
|
||||||
// of extrusion, its two translated trim curves, and one trim line. We
|
|
||||||
// go through by loops so that we can assign the lines correctly.
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
SBezier *sb;
|
|
||||||
List<TrimLine> trimLines = {};
|
|
||||||
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
// Generate the surface of extrusion of this curve, and add
|
|
||||||
// it to the list
|
|
||||||
SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);
|
|
||||||
ss.color = color;
|
|
||||||
hSSurface hsext = surface.AddAndAssignId(&ss);
|
|
||||||
|
|
||||||
// Translate the curve by t0 and t1 to produce two trim curves
|
|
||||||
SCurve sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = hs0;
|
|
||||||
sc.surfB = hsext;
|
|
||||||
hSCurve hc0 = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = hs1;
|
|
||||||
sc.surfB = hsext;
|
|
||||||
hSCurve hc1 = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb0, stb1;
|
|
||||||
// The translated curves trim the flat top and bottom surfaces.
|
|
||||||
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/false);
|
|
||||||
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/true);
|
|
||||||
(surface.FindById(hs0))->trim.Add(&stb0);
|
|
||||||
(surface.FindById(hs1))->trim.Add(&stb1);
|
|
||||||
|
|
||||||
// The translated curves also trim the surface of extrusion.
|
|
||||||
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/true);
|
|
||||||
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/false);
|
|
||||||
(surface.FindById(hsext))->trim.Add(&stb0);
|
|
||||||
(surface.FindById(hsext))->trim.Add(&stb1);
|
|
||||||
|
|
||||||
// And form the trim line
|
|
||||||
Vector pt = sb->Finish();
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
hSCurve hl = curve.AddAndAssignId(&sc);
|
|
||||||
// save this for later
|
|
||||||
TrimLine tl;
|
|
||||||
tl.hc = hl;
|
|
||||||
tl.hs = hsext;
|
|
||||||
trimLines.Add(&tl);
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for(i = 0; i < trimLines.n; i++) {
|
|
||||||
TrimLine *tl = &(trimLines[i]);
|
|
||||||
SSurface *ss = surface.FindById(tl->hs);
|
|
||||||
|
|
||||||
TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true);
|
|
||||||
ss->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, tlp->hc, /*backwards=*/false);
|
|
||||||
ss->trim.Add(&stb);
|
|
||||||
|
|
||||||
(curve.FindById(tl->hc))->surfA = ss->h;
|
|
||||||
(curve.FindById(tlp->hc))->surfB = ss->h;
|
|
||||||
}
|
|
||||||
trimLines.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx)
|
|
||||||
// Check that the direction of revolution/extrusion ends up parallel to the normal of
|
|
||||||
// the sketch, on the side of the axis where the sketch is.
|
|
||||||
{
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
Vector pto;
|
|
||||||
double md = VERY_NEGATIVE;
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
SBezier *sb;
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
// Choose the point farthest from the axis; we'll get garbage
|
|
||||||
// if we choose a point that lies on the axis, for example.
|
|
||||||
// (And our surface will be self-intersecting if the sketch
|
|
||||||
// spans the axis, so don't worry about that.)
|
|
||||||
for(int i = 0; i <= sb->deg; i++) {
|
|
||||||
Vector p = sb->ctrl[i];
|
|
||||||
double d = p.DistanceToLine(pt, axis);
|
|
||||||
if(d > md) {
|
|
||||||
md = d;
|
|
||||||
pto = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vector ptc = pto.ClosestPointOnLine(pt, axis),
|
|
||||||
up = axis.Cross(pto.Minus(ptc)).ScaledBy(da),
|
|
||||||
vp = up.Plus(axis.ScaledBy(dx));
|
|
||||||
|
|
||||||
return (vp.Dot(sbls->normal) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sketch must not contain the axis of revolution as a non-construction line for helix
|
|
||||||
void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
|
|
||||||
RgbaColor color, Group *group, double angles,
|
|
||||||
double anglef, double dists, double distf) {
|
|
||||||
int i0 = surface.n; // number of pre-existing surfaces
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
// for testing - hard code the axial distance, and number of sections.
|
|
||||||
// distance will need to be parameters in the future.
|
|
||||||
double dist = distf - dists;
|
|
||||||
int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1);
|
|
||||||
double wedge = (anglef - angles) / sections;
|
|
||||||
int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END;
|
|
||||||
|
|
||||||
if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) {
|
|
||||||
swap(angles, anglef);
|
|
||||||
swap(dists, distf);
|
|
||||||
dist = -dist;
|
|
||||||
wedge = -wedge;
|
|
||||||
swap(startMapping, endMapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a coordinate system to contain the original sketch, and get
|
|
||||||
// a bounding box in that csys
|
|
||||||
Vector n = sbls->normal.ScaledBy(-1);
|
|
||||||
Vector u = n.Normal(0), v = n.Normal(1);
|
|
||||||
Vector orig = sbls->point;
|
|
||||||
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
|
||||||
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
|
||||||
// and now fix things up so that all u and v lie between 0 and 1
|
|
||||||
orig = orig.Plus(u.ScaledBy(umin));
|
|
||||||
orig = orig.Plus(v.ScaledBy(vmin));
|
|
||||||
u = u.ScaledBy(umax - umin);
|
|
||||||
v = v.ScaledBy(vmax - vmin);
|
|
||||||
|
|
||||||
// So we can now generate the end caps of the extrusion within
|
|
||||||
// a translated and rotated (and maybe mirrored) version of that csys.
|
|
||||||
SSurface s0, s1;
|
|
||||||
s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)),
|
|
||||||
u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles));
|
|
||||||
s0.color = color;
|
|
||||||
|
|
||||||
hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping);
|
|
||||||
s0.face = face0.v;
|
|
||||||
|
|
||||||
s1 = SSurface::FromPlane(
|
|
||||||
orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)),
|
|
||||||
u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef));
|
|
||||||
s1.color = color;
|
|
||||||
|
|
||||||
hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping);
|
|
||||||
s1.face = face1.v;
|
|
||||||
|
|
||||||
hSSurface hs0 = surface.AddAndAssignId(&s0);
|
|
||||||
hSSurface hs1 = surface.AddAndAssignId(&s1);
|
|
||||||
|
|
||||||
// Now we actually build and trim the swept surfaces. One loop at a time.
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
int i, j;
|
|
||||||
SBezier *sb;
|
|
||||||
List<std::vector<hSSurface>> hsl = {};
|
|
||||||
|
|
||||||
// This is where all the NURBS are created and Remapped to the generating curve
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
std::vector<hSSurface> revs(sections);
|
|
||||||
for(j = 0; j < sections; j++) {
|
|
||||||
if((dist == 0) && sb->deg == 1 &&
|
|
||||||
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
|
||||||
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) {
|
|
||||||
// This is a line on the axis of revolution; it does
|
|
||||||
// not contribute a surface.
|
|
||||||
revs[j].v = 0;
|
|
||||||
} else {
|
|
||||||
SSurface ss = SSurface::FromRevolutionOf(
|
|
||||||
sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1),
|
|
||||||
dists + j * dist / sections, dists + (j + 1) * dist / sections);
|
|
||||||
ss.color = color;
|
|
||||||
if(sb->entity != 0) {
|
|
||||||
hEntity he;
|
|
||||||
he.v = sb->entity;
|
|
||||||
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
|
||||||
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
|
||||||
ss.face = hface.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
revs[j] = surface.AddAndAssignId(&ss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hsl.Add(&revs);
|
|
||||||
}
|
|
||||||
// Still the same loop. Need to create trim curves
|
|
||||||
for(i = 0; i < sbl->l.n; i++) {
|
|
||||||
std::vector<hSSurface> revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)];
|
|
||||||
|
|
||||||
sb = &(sbl->l[i]);
|
|
||||||
|
|
||||||
// we will need the grid t-values for this entire row of surfaces
|
|
||||||
List<double> t_values;
|
|
||||||
t_values = {};
|
|
||||||
if (revs[0].v) {
|
|
||||||
double ps = 0.0;
|
|
||||||
t_values.Add(&ps);
|
|
||||||
(surface.FindById(revs[0]))->MakeTriangulationGridInto(
|
|
||||||
&t_values, 0.0, 1.0, true, 0);
|
|
||||||
}
|
|
||||||
// we generate one more curve than we did surfaces
|
|
||||||
for(j = 0; j <= sections; j++) {
|
|
||||||
SCurve sc;
|
|
||||||
Quaternion qs = Quaternion::From(axis, angles + wedge * j);
|
|
||||||
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
|
||||||
Vector ts =
|
|
||||||
pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections));
|
|
||||||
|
|
||||||
// If this input curve generated a surface, then trim that
|
|
||||||
// surface with the rotated version of the input curve.
|
|
||||||
if(revs[0].v) { // not d[j] because crash on j==sections
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
|
||||||
// make the PWL for the curve based on t value list
|
|
||||||
for(int x = 0; x < t_values.n; x++) {
|
|
||||||
SCurvePt scpt;
|
|
||||||
scpt.tag = 0;
|
|
||||||
scpt.p = sc.exact.PointAt(t_values[x]);
|
|
||||||
scpt.vertex = (x == 0) || (x == (t_values.n - 1));
|
|
||||||
sc.pts.Add(&scpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the surfaces already exists so trim with this curve
|
|
||||||
if(j < sections) {
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
} else {
|
|
||||||
sc.surfA = hs1; // end cap
|
|
||||||
}
|
|
||||||
|
|
||||||
if(j > 0) {
|
|
||||||
sc.surfB = revs[j - 1];
|
|
||||||
} else {
|
|
||||||
sc.surfB = hs0; // staring cap
|
|
||||||
}
|
|
||||||
|
|
||||||
hSCurve hcb = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
} else if(j == 0) { // curve was on the rotation axis and is shared by the end caps.
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = hs1; // end cap
|
|
||||||
sc.surfB = hs0; // staring cap
|
|
||||||
hSCurve hcb = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// And if this input curve and the one after it both generated
|
|
||||||
// surfaces, then trim both of those by the appropriate
|
|
||||||
// curve based on the control points.
|
|
||||||
if((j < sections) && revs[j].v && revsp[j].v) {
|
|
||||||
SSurface *ss = surface.FindById(revs[j]);
|
|
||||||
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]);
|
|
||||||
sc.exact.weight[1] = ss->weight[0][1];
|
|
||||||
double max_dt = 0.5;
|
|
||||||
if (sc.exact.deg > 1) max_dt = 0.125;
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
sc.surfB = revsp[j];
|
|
||||||
|
|
||||||
hSCurve hcc = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t_values.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
hsl.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dist == 0) {
|
|
||||||
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color,
|
|
||||||
Group *group) {
|
|
||||||
int i0 = surface.n; // number of pre-existing surfaces
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
|
|
||||||
if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) {
|
|
||||||
axis = axis.ScaledBy(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we actually build and trim the surfaces.
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
int i, j;
|
|
||||||
SBezier *sb;
|
|
||||||
List<std::vector<hSSurface>> hsl = {};
|
|
||||||
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
std::vector<hSSurface> revs(4);
|
|
||||||
for(j = 0; j < 4; j++) {
|
|
||||||
if(sb->deg == 1 &&
|
|
||||||
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
|
||||||
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS)
|
|
||||||
{
|
|
||||||
// This is a line on the axis of revolution; it does
|
|
||||||
// not contribute a surface.
|
|
||||||
revs[j].v = 0;
|
|
||||||
} else {
|
|
||||||
SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j,
|
|
||||||
(PI / 2) * (j + 1), 0.0, 0.0);
|
|
||||||
ss.color = color;
|
|
||||||
if(sb->entity != 0) {
|
|
||||||
hEntity he;
|
|
||||||
he.v = sb->entity;
|
|
||||||
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
|
||||||
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
|
||||||
ss.face = hface.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
revs[j] = surface.AddAndAssignId(&ss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hsl.Add(&revs);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(i = 0; i < sbl->l.n; i++) {
|
|
||||||
std::vector<hSSurface> revs = hsl[i],
|
|
||||||
revsp = hsl[WRAP(i-1, sbl->l.n)];
|
|
||||||
|
|
||||||
sb = &(sbl->l[i]);
|
|
||||||
|
|
||||||
for(j = 0; j < 4; j++) {
|
|
||||||
SCurve sc;
|
|
||||||
Quaternion qs = Quaternion::From(axis, (PI/2)*j);
|
|
||||||
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
|
||||||
Vector ts = pt.Minus(qs.Rotate(pt));
|
|
||||||
|
|
||||||
// If this input curve generate a surface, then trim that
|
|
||||||
// surface with the rotated version of the input curve.
|
|
||||||
if(revs[j].v) {
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
sc.surfB = revs[WRAP(j-1, 4)];
|
|
||||||
|
|
||||||
hSCurve hcb = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// And if this input curve and the one after it both generated
|
|
||||||
// surfaces, then trim both of those by the appropriate
|
|
||||||
// circle.
|
|
||||||
if(revs[j].v && revsp[j].v) {
|
|
||||||
SSurface *ss = surface.FindById(revs[j]);
|
|
||||||
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = SBezier::From(ss->ctrl[0][0],
|
|
||||||
ss->ctrl[0][1],
|
|
||||||
ss->ctrl[0][2]);
|
|
||||||
sc.exact.weight[1] = ss->weight[0][1];
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
sc.surfB = revsp[j];
|
|
||||||
|
|
||||||
hSCurve hcc = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hsl.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = i0; i < surface.n; i++) {
|
|
||||||
SSurface *srf = &(surface[i]);
|
|
||||||
|
|
||||||
// Revolution of a line; this is potentially a plane, which we can
|
|
||||||
// rewrite to have degree (1, 1).
|
|
||||||
if(srf->degm == 1 && srf->degn == 2) {
|
|
||||||
// close start, far start, far finish
|
|
||||||
Vector cs, fs, ff;
|
|
||||||
double d0, d1;
|
|
||||||
d0 = (srf->ctrl[0][0]).DistanceToLine(pt, axis);
|
|
||||||
d1 = (srf->ctrl[1][0]).DistanceToLine(pt, axis);
|
|
||||||
|
|
||||||
if(d0 > d1) {
|
|
||||||
cs = srf->ctrl[1][0];
|
|
||||||
fs = srf->ctrl[0][0];
|
|
||||||
ff = srf->ctrl[0][2];
|
|
||||||
} else {
|
|
||||||
cs = srf->ctrl[0][0];
|
|
||||||
fs = srf->ctrl[1][0];
|
|
||||||
ff = srf->ctrl[1][2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// origin close, origin far
|
|
||||||
Vector oc = cs.ClosestPointOnLine(pt, axis),
|
|
||||||
of = fs.ClosestPointOnLine(pt, axis);
|
|
||||||
|
|
||||||
if(oc.Equals(of)) {
|
|
||||||
// This is a plane, not a (non-degenerate) cone.
|
|
||||||
Vector oldn = srf->NormalAt(0.5, 0.5);
|
|
||||||
|
|
||||||
Vector u = fs.Minus(of), v;
|
|
||||||
|
|
||||||
v = (axis.Cross(u)).WithMagnitude(1);
|
|
||||||
|
|
||||||
double vm = (ff.Minus(of)).Dot(v);
|
|
||||||
v = v.ScaledBy(vm);
|
|
||||||
|
|
||||||
srf->degm = 1;
|
|
||||||
srf->degn = 1;
|
|
||||||
srf->ctrl[0][0] = of;
|
|
||||||
srf->ctrl[0][1] = of.Plus(u);
|
|
||||||
srf->ctrl[1][0] = of.Plus(v);
|
|
||||||
srf->ctrl[1][1] = of.Plus(u).Plus(v);
|
|
||||||
srf->weight[0][0] = 1;
|
|
||||||
srf->weight[0][1] = 1;
|
|
||||||
srf->weight[1][0] = 1;
|
|
||||||
srf->weight[1][1] = 1;
|
|
||||||
|
|
||||||
if(oldn.Dot(srf->NormalAt(0.5, 0.5)) < 0) {
|
|
||||||
swap(srf->ctrl[0][0], srf->ctrl[1][0]);
|
|
||||||
swap(srf->ctrl[0][1], srf->ctrl[1][1]);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fabs(d0 - d1) < LENGTH_EPS) {
|
|
||||||
// This is a cylinder; so transpose it so that we'll recognize
|
|
||||||
// it as a surface of extrusion.
|
|
||||||
SSurface sn = *srf;
|
|
||||||
|
|
||||||
// Transposing u and v flips the normal, so reverse u to
|
|
||||||
// flip it again and put it back where we started.
|
|
||||||
sn.degm = 2;
|
|
||||||
sn.degn = 1;
|
|
||||||
int dm, dn;
|
|
||||||
for(dm = 0; dm <= 1; dm++) {
|
|
||||||
for(dn = 0; dn <= 2; dn++) {
|
|
||||||
sn.ctrl [dn][dm] = srf->ctrl [1-dm][dn];
|
|
||||||
sn.weight[dn][dm] = srf->weight[1-dm][dn];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*srf = sn;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFromCopyOf(SShell *a) {
|
|
||||||
ssassert(this != a, "Can't make from copy of self");
|
|
||||||
MakeFromTransformationOf(a,
|
|
||||||
Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFromTransformationOf(SShell *a,
|
|
||||||
Vector t, Quaternion q, double scale)
|
|
||||||
{
|
|
||||||
booleanFailed = false;
|
|
||||||
surface.ReserveMore(a->surface.n);
|
|
||||||
for(SSurface &s : a->surface) {
|
|
||||||
SSurface n;
|
|
||||||
n = SSurface::FromTransformationOf(&s, t, q, scale, /*includingTrims=*/true);
|
|
||||||
surface.Add(&n); // keeping the old ID
|
|
||||||
}
|
|
||||||
|
|
||||||
curve.ReserveMore(a->curve.n);
|
|
||||||
for(SCurve &c : a->curve) {
|
|
||||||
SCurve n;
|
|
||||||
n = SCurve::FromTransformationOf(&c, t, q, scale);
|
|
||||||
curve.Add(&n); // keeping the old ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeEdgesInto(SEdgeList *sel) {
|
|
||||||
for(SSurface &s : surface) {
|
|
||||||
s.MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl)
|
|
||||||
{
|
|
||||||
for(SSurface &s : surface) {
|
|
||||||
if(s.CoincidentWithPlane(n, d)) {
|
|
||||||
s.MakeSectionEdgesInto(this, sel, sbl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::TriangulateInto(SMesh *sm) {
|
|
||||||
#pragma omp parallel for
|
|
||||||
for(int i=0; i<surface.n; i++) {
|
|
||||||
SSurface *s = &surface[i];
|
|
||||||
SMesh m;
|
|
||||||
s->TriangulateInto(this, &m);
|
|
||||||
#pragma omp critical
|
|
||||||
sm->MakeFromCopyOf(&m);
|
|
||||||
m.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SShell::IsEmpty() const {
|
|
||||||
return surface.IsEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::Clear() {
|
|
||||||
for(SSurface &s : surface) {
|
|
||||||
s.Clear();
|
|
||||||
}
|
|
||||||
surface.Clear();
|
|
||||||
|
|
||||||
for(SCurve &c : curve) {
|
|
||||||
c.Clear();
|
|
||||||
}
|
|
||||||
curve.Clear();
|
|
||||||
}
|
|
||||||
|
|
|
@ -407,10 +407,10 @@ public:
|
||||||
// outside, or coincident (with parallel or antiparallel normal) with a
|
// outside, or coincident (with parallel or antiparallel normal) with a
|
||||||
// shell.
|
// shell.
|
||||||
enum class Class : uint32_t {
|
enum class Class : uint32_t {
|
||||||
INSIDE = 100,
|
SURF_INSIDE = 100,
|
||||||
OUTSIDE = 200,
|
SURF_OUTSIDE = 200,
|
||||||
COINC_SAME = 300,
|
SURF_COINC_SAME = 300,
|
||||||
COINC_OPP = 400
|
SURF_COINC_OPP = 400
|
||||||
};
|
};
|
||||||
static const double DOTP_TOL;
|
static const double DOTP_TOL;
|
||||||
Class ClassifyRegion(Vector edge_n, Vector inter_surf_n,
|
Class ClassifyRegion(Vector edge_n, Vector inter_surf_n,
|
||||||
|
|
|
@ -456,13 +456,11 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector dp = nb.Cross(na).WithMagnitude(1.0);
|
||||||
|
if(!fwd) dp = dp.ScaledBy(-1.0);
|
||||||
int i;
|
int i;
|
||||||
for(i = 0; i < 20; i++) {
|
for(i = 0; i < 20; i++) {
|
||||||
Vector dp = nb.Cross(na);
|
np = start.Plus(dp.ScaledBy(step));
|
||||||
if(!fwd) dp = dp.ScaledBy(-1);
|
|
||||||
dp = dp.WithMagnitude(step);
|
|
||||||
|
|
||||||
np = start.Plus(dp);
|
|
||||||
npc = ClosestPointOnThisAndSurface(b, np);
|
npc = ClosestPointOnThisAndSurface(b, np);
|
||||||
tol = (npc.Minus(np)).Magnitude();
|
tol = (npc.Minus(np)).Magnitude();
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue