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
|
||||
|
||||
- **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-->
|
||||
|
||||
### Expected behavior
|
||||
|
|
|
@ -14,13 +14,13 @@ CMAKE_GENERATOR="Unix Makefiles"
|
|||
CMAKE_PREFIX_PATH=""
|
||||
if [ "$2" = "arm64" ]; then
|
||||
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..."
|
||||
mkdir build-arm64 || true
|
||||
cd build-arm64
|
||||
elif [ "$2" = "x86_64" ]; then
|
||||
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
|
||||
cd build
|
||||
else
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
#!/bin/sh -xe
|
||||
|
||||
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
|
||||
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
|
||||
tar -xzvf /tmp/libomp-x86_64.tar.gz -C /tmp/libomp-x86_64
|
||||
else
|
||||
brew install libomp
|
||||
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 \
|
||||
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
|
||||
codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${dmg}"
|
||||
|
||||
# notarize and store request uuid in variable
|
||||
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'})
|
||||
if ! command -v xcrun >/dev/null || ! xcrun --find notarytool >/dev/null; then
|
||||
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
|
||||
sleep 10
|
||||
|
||||
success=0
|
||||
for (( ; ; ))
|
||||
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
|
||||
echo "Not completed yet. Sleeping for 10 seconds"
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
if [ $? -eq 0 ]; then
|
||||
# Extract the operation ID from the output
|
||||
operation_id=$(echo "$notarization_output" | awk '/RequestUUID/ {print $NF}')
|
||||
echo "Notarization submitted. Operation ID: $operation_id"
|
||||
exit 0
|
||||
else
|
||||
echo "Notarization failed. Error: $notarization_output"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# staple
|
||||
xcrun stapler staple "${dmg}"
|
|
@ -12,17 +12,19 @@ jobs:
|
|||
cancel_previous_runs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Cancel Previous Runs
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@0.8.0
|
||||
- uses: styfle/cancel-workflow-action
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
test_ubuntu:
|
||||
needs: [cancel_previous_runs]
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
name: Test Ubuntu
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-ubuntu.sh
|
||||
- name: Build & Test
|
||||
|
@ -33,7 +35,7 @@ jobs:
|
|||
runs-on: windows-2019
|
||||
name: Test Windows
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-windows.sh
|
||||
shell: bash
|
||||
|
@ -43,10 +45,10 @@ jobs:
|
|||
|
||||
test_macos:
|
||||
needs: [cancel_previous_runs]
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-latest
|
||||
name: Test macOS
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-macos.sh ci
|
||||
- name: Build & Test
|
||||
|
@ -57,7 +59,7 @@ jobs:
|
|||
name: Build Release Windows
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-windows.sh
|
||||
shell: bash
|
||||
|
@ -65,7 +67,7 @@ jobs:
|
|||
run: .github/scripts/build-windows.sh release
|
||||
shell: bash
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: build/bin/RelWithDebInfo/solvespace.exe
|
||||
|
@ -75,7 +77,7 @@ jobs:
|
|||
name: Build Release Windows (OpenMP)
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-windows.sh
|
||||
shell: bash
|
||||
|
@ -83,7 +85,7 @@ jobs:
|
|||
run: .github/scripts/build-windows.sh release openmp
|
||||
shell: bash
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-openmp
|
||||
path: build/bin/RelWithDebInfo/solvespace-openmp.exe
|
||||
|
@ -91,9 +93,9 @@ jobs:
|
|||
build_release_macos:
|
||||
needs: [test_ubuntu, test_windows, test_macos]
|
||||
name: Build Release macOS
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-macos.sh ci
|
||||
- name: Build & Test
|
||||
|
@ -107,121 +109,56 @@ jobs:
|
|||
MACOS_APPSTORE_USERNAME: ${{ secrets.MACOS_APPSTORE_USERNAME }}
|
||||
MACOS_DEVELOPER_ID: ${{ secrets.MACOS_DEVELOPER_ID }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: build/bin/SolveSpace.dmg
|
||||
|
||||
deploy_snap_amd64:
|
||||
needs: [test_ubuntu, test_windows, test_macos]
|
||||
name: Deploy AMD64 Snap
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
deploy_snap_arm64:
|
||||
needs: [test_ubuntu, test_windows, test_macos]
|
||||
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 }}
|
||||
# deploy_snap_amd64:
|
||||
# needs: [test_ubuntu, test_windows, test_macos]
|
||||
# name: Deploy AMD64 Snap
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Fetch Tags
|
||||
# run: git fetch --force --tags
|
||||
# - name: Set Up Source
|
||||
# run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
|
||||
# - name: Build Snap
|
||||
# uses: snapcore/action-build@v1
|
||||
# id: build
|
||||
# with:
|
||||
# path: pkg/snap
|
||||
# - name: Upload & Release to Edge
|
||||
# if: github.event_name == 'push'
|
||||
# uses: snapcore/action-publish@v1
|
||||
# env:
|
||||
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPSTORE_LOGIN }}
|
||||
# with:
|
||||
# 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
|
||||
|
||||
upload_release_assets:
|
||||
name: Upload Release Assets
|
||||
needs: [build_release_windows, build_release_windows_openmp, build_release_macos, update_edge_release]
|
||||
if: "!cancelled()"
|
||||
needs: [build_release_windows, build_release_windows_openmp, build_release_macos]
|
||||
if: "!cancelled() && github.event_name == 'release'"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download All Workflow Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Get Release Upload URL
|
||||
id: get_upload_url
|
||||
env:
|
||||
event_name: ${{ github.event_name }}
|
||||
event: ${{ toJson(github.event) }}
|
||||
edge_upload_url: ${{ needs.update_edge_release.outputs.upload_url }}
|
||||
run: |
|
||||
if [ "$event_name" = "release" ]; then
|
||||
upload_url=$(echo "$event" | jq -r ".release.upload_url")
|
||||
else
|
||||
upload_url="$edge_upload_url"
|
||||
fi
|
||||
upload_url=$(echo "$event" | jq -r ".release.upload_url")
|
||||
echo "::set-output name=upload_url::$upload_url"
|
||||
echo "Upload URL: $upload_url"
|
||||
- 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:
|
||||
test_ubuntu:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
name: Test Ubuntu
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-ubuntu.sh
|
||||
- name: Build & Test
|
||||
|
@ -25,7 +25,7 @@ jobs:
|
|||
runs-on: windows-2019
|
||||
name: Test Windows
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-windows.sh
|
||||
shell: bash
|
||||
|
@ -34,11 +34,28 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
test_macos:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-latest
|
||||
name: Test macOS
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: .github/scripts/install-macos.sh ci
|
||||
- name: Build & Test
|
||||
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-*/
|
||||
/*.slvs
|
||||
.vscode/
|
||||
|
||||
# Visual Studio
|
||||
out/
|
||||
.vs/
|
||||
CMakeSettings.json
|
||||
|
|
|
@ -23,3 +23,6 @@
|
|||
[submodule "extlib/mimalloc"]
|
||||
path = extlib/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
|
||||
=========
|
||||
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
@ -8,10 +8,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
|
|||
" mkdir build && cd build && cmake ..")
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
list(APPEND CMAKE_MODULE_PATH
|
||||
"${CMAKE_SOURCE_DIR}/cmake/")
|
||||
|
||||
cmake_policy(SET CMP0048 OLD)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
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++")
|
||||
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
|
||||
|
||||
# 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:
|
||||
# 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)
|
||||
project(solvespace LANGUAGES C CXX ASM)
|
||||
project(solvespace
|
||||
VERSION 3.1
|
||||
LANGUAGES C CXX ASM)
|
||||
|
||||
set(ENABLE_GUI ON CACHE BOOL
|
||||
"Whether the graphical interface is enabled")
|
||||
|
@ -51,8 +55,11 @@ set(ENABLE_SANITIZERS OFF CACHE BOOL
|
|||
"Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer")
|
||||
set(ENABLE_OPENMP OFF CACHE BOOL
|
||||
"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")
|
||||
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)")
|
||||
|
||||
|
@ -84,6 +91,10 @@ endif()
|
|||
if(MINGW)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc")
|
||||
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()
|
||||
|
||||
# Ensure that all platforms use 64-bit IEEE floating point operations for consistency;
|
||||
|
@ -109,7 +120,12 @@ endif()
|
|||
if(ENABLE_OPENMP)
|
||||
find_package( OpenMP REQUIRED )
|
||||
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} )
|
||||
endif()
|
||||
endif()
|
||||
|
@ -170,6 +186,10 @@ if(APPLE)
|
|||
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
set(M_LIBRARY "" CACHE STRING "libm (not necessary)" FORCE)
|
||||
endif()
|
||||
|
||||
message(STATUS "Using in-tree libdxfrw")
|
||||
add_subdirectory(extlib/libdxfrw)
|
||||
|
||||
|
@ -181,7 +201,22 @@ set(MI_BUILD_TESTS OFF CACHE BOOL "")
|
|||
add_subdirectory(extlib/mimalloc EXCLUDE_FROM_ALL)
|
||||
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
|
||||
# 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
|
||||
|
@ -242,7 +277,7 @@ else()
|
|||
find_package(ZLIB REQUIRED)
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
pkg_check_modules(CAIRO REQUIRED cairo)
|
||||
find_package(Cairo REQUIRED)
|
||||
endif()
|
||||
|
||||
# GUI dependencies
|
||||
|
@ -276,7 +311,8 @@ if(ENABLE_GUI)
|
|||
elseif(APPLE)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
||||
set(util_LIBRARIES ${APPKIT_LIBRARY})
|
||||
elseif(EMSCRIPTEN)
|
||||
# Everything is built in
|
||||
else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(SpaceWare)
|
||||
|
@ -347,9 +383,19 @@ if(MSVC)
|
|||
# Same for the (C99) __func__ special variable; we use it only in C++ code.
|
||||
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
|
||||
# follows: /w4062=-Wswitch
|
||||
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()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
|
|
313
README.md
313
README.md
|
@ -1,172 +1,242 @@
|
|||
# SolveSpace
|
||||
|
||||
<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)
|
||||
[![solvespace](https://snapcraft.io/solvespace/badge.svg)](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
|
||||
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],
|
||||
[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/
|
||||
[ssref]: http://solvespace.com/ref.pl
|
||||
[sstutorial]: http://solvespace.com/tutorial.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
|
||||
available via [GitHub releases][rel]. These packages are automatically built by
|
||||
the SolveSpace maintainers for each stable release.
|
||||
_Official_ release packages for macOS (>=10.6 64-bit) and Windows
|
||||
(>=Vista 32-bit) are available via [GitHub releases][rel]. These packages are
|
||||
automatically built by the SolveSpace maintainers for each stable release.
|
||||
|
||||
[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
|
||||
|
||||
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)
|
||||
|
||||
Or install from a terminal:
|
||||
|
||||
```
|
||||
snap install --edge solvespace
|
||||
```sh
|
||||
# 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
|
||||
via [notesalexp.org][notesalexp]. These packages are automatically built from non-released
|
||||
source code. The SolveSpace maintainers do not control the contents of these packages
|
||||
and cannot guarantee their functionality.
|
||||
> :warning: **Edge builds might be unstable or contain severe bugs!**
|
||||
> They are intended for experienced users to test new features or verify bugfixes.
|
||||
|
||||
[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
|
||||
|
||||
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
|
||||
|
||||
You will need the usual build tools, CMake, zlib, libpng, cairo, freetype.
|
||||
To build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later), pangomm 1.4,
|
||||
OpenGL and OpenGL GLU, and optionally, the Space Navigator client library.
|
||||
On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
||||
You will need the usual build tools, CMake, zlib, libpng, cairo, freetype. To
|
||||
build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later),
|
||||
pangomm 1.4, OpenGL and OpenGL GLU, and optionally, the Space Navigator client
|
||||
library. On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
||||
|
||||
sudo apt install git build-essential cmake zlib1g-dev libpng-dev \
|
||||
libcairo2-dev libfreetype6-dev libjson-c-dev \
|
||||
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev \
|
||||
libgl-dev libglu-dev libspnav-dev
|
||||
```sh
|
||||
sudo apt install git build-essential cmake zlib1g-dev libpng-dev \
|
||||
libcairo2-dev libfreetype6-dev libjson-c-dev \
|
||||
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-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 \
|
||||
cairo-devel freetype-devel json-c-devel \
|
||||
fontconfig-devel gtkmm30-devel pangomm-devel \
|
||||
mesa-libGL-devel mesa-libGLU-devel libspnav-devel
|
||||
```sh
|
||||
sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \
|
||||
cairo-devel freetype-devel json-c-devel \
|
||||
fontconfig-devel gtkmm30-devel pangomm-devel \
|
||||
mesa-libGL-devel mesa-libGLU-devel libspnav-devel
|
||||
```
|
||||
|
||||
Before building, check out the project and the necessary submodules:
|
||||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||
|
||||
After that, build SolveSpace as following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||
make
|
||||
sudo make install
|
||||
```sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||
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.
|
||||
|
||||
The graphical interface is built as `build/bin/solvespace`, and the command-line 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.
|
||||
The graphical interface is built as `build/bin/solvespace`, and the command-line
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init
|
||||
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||
|
||||
Build 64-bit SolveSpace with the following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DFLATC=$(which flatc)
|
||||
make
|
||||
```sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
make
|
||||
```
|
||||
|
||||
The graphical interface is built as `build/bin/solvespace.exe`, and the command-line interface
|
||||
is built as `build/bin/solvespace-cli.exe`.
|
||||
The graphical interface is built as `build/bin/solvespace.exe`, and the
|
||||
command-line interface is built as `build/bin/solvespace-cli.exe`.
|
||||
|
||||
Space Navigator support will not be available.
|
||||
|
||||
If using Ubuntu to cross-compile, Ubuntu 17.10 or newer (or, alternatively, MinGW from the Ubuntu
|
||||
17.10 repositories) is required.
|
||||
### Building for web (very experimental)
|
||||
|
||||
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][]:
|
||||
|
||||
brew install git cmake
|
||||
```sh
|
||||
brew install git cmake libomp
|
||||
```
|
||||
|
||||
XCode has to be installed via AppStore or [the Apple website][appledeveloper];
|
||||
it requires a free Apple ID.
|
||||
|
||||
Before building, check out the project and the necessary submodules:
|
||||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init
|
||||
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||
|
||||
After that, build SolveSpace as following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||
make
|
||||
```sh
|
||||
mkdir build
|
||||
cd build
|
||||
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.
|
||||
|
||||
Alternatively, generate an XCode project, open it, and build the "Release" scheme:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G Xcode
|
||||
```sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G Xcode
|
||||
```
|
||||
|
||||
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
|
||||
|
@ -175,26 +245,26 @@ is `build/bin/SolveSpace.app/Contents/MacOS/solvespace-cli`.
|
|||
[homebrew]: https://brew.sh/
|
||||
[appledeveloper]: https://developer.apple.com/download/
|
||||
|
||||
Building on OpenBSD
|
||||
-------------------
|
||||
## Building on OpenBSD
|
||||
|
||||
You will need git, cmake, libexecinfo, libpng, gtk3mm and pangomm.
|
||||
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:
|
||||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||
|
||||
After that, build SolveSpace as following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make
|
||||
```sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
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
|
||||
(either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015
|
||||
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
|
||||
|
||||
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.
|
||||
Press "Configure" and "Generate", then open `build\solvespace.sln` with
|
||||
Visual C++ and build it.
|
||||
|
||||
### 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
|
||||
Visual Studio install. Then, run the following in cmd or PowerShell:
|
||||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||
nmake
|
||||
```bat
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||
nmake
|
||||
```
|
||||
|
||||
### 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
|
||||
in bash:
|
||||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make
|
||||
```sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make
|
||||
```
|
||||
|
||||
[gitwin]: https://git-scm.com/download/win
|
||||
[cmakewin]: http://www.cmake.org/download/#latest
|
||||
[mingw]: http://www.mingw.org/
|
||||
|
||||
Contributing
|
||||
------------
|
||||
## Contributing
|
||||
|
||||
See the [guide for contributors](CONTRIBUTING.md) for the best way to file issues, contribute code,
|
||||
and debug SolveSpace.
|
||||
|
||||
License
|
||||
-------
|
||||
## License
|
||||
|
||||
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")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE)
|
||||
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()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE)
|
||||
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()
|
||||
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.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SPACEWARE DEFAULT_MSG
|
||||
find_package_handle_standard_args(SpaceWare DEFAULT_MSG
|
||||
SPACEWARE_LIBRARY SPACEWARE_INCLUDE_DIR)
|
||||
|
||||
if(SPACEWARE_FOUND)
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}</string>
|
||||
<string>${PROJECT_VERSION}~${solvespace_GIT_HASH}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}</string>
|
||||
<string>${PROJECT_VERSION}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© 2008-2016 Jonathan Westhues and other authors</string>
|
||||
<string>© 2008-2024 Jonathan Westhues and other authors</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<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_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
||||
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_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
||||
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
|
||||
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)
|
||||
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:
|
||||
==========
|
||||
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
|
||||
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
|
||||
modify it. Entites 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
|
||||
constrained to entities on that box, the user can go back to the sketch and
|
||||
modify it. Entities can be added, modified an even deleted. So long as the
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@ -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).
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -45,5 +45,5 @@ Remap that was created the same way. This is how constructions are preserved
|
|||
across underlying changes.
|
||||
|
||||
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_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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
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_CURVE_CURVE_TANGENT 100032
|
||||
#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 {
|
||||
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",
|
||||
"runtime": "org.freedesktop.Platform",
|
||||
"runtime-version": "20.08",
|
||||
"runtime-version": "21.08",
|
||||
"sdk": "org.freedesktop.Sdk",
|
||||
"finish-args": [
|
||||
/* Access to display server and OpenGL */
|
||||
"--device=dri",
|
||||
"--share=ipc",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--device=dri",
|
||||
/* Access to save files */
|
||||
"--filesystem=home"
|
||||
"--socket=wayland"
|
||||
],
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/*/include",
|
||||
"*.a",
|
||||
"*.la",
|
||||
"*.m4",
|
||||
"/lib/libslvs*.so*",
|
||||
"/lib/libglibmm_generate_extra_defs*.so*",
|
||||
"/share/pkgconfig",
|
||||
"*.pc",
|
||||
"/share/man",
|
||||
"/share/doc",
|
||||
"/lib/cmake",
|
||||
"/lib/pkgconfig",
|
||||
"/share/aclocal",
|
||||
/* mm-common junk */
|
||||
"/bin/mm-common-prepare",
|
||||
"/share/mm-common"
|
||||
"/share/pkgconfig",
|
||||
"*.la"
|
||||
],
|
||||
"command": "solvespace",
|
||||
"modules": [
|
||||
{
|
||||
"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",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.gnome.org/sources/glibmm/2.64/glibmm-2.64.5.tar.xz",
|
||||
"sha256": "508fc86e2c9141198aa16c225b16fd6b911917c0d3817602652844d0973ea386"
|
||||
"url": "https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.4.tar.xz",
|
||||
"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": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/cairomm/1.12/cairomm-1.12.0.tar.xz",
|
||||
"sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6"
|
||||
"url": "https://download.gnome.org/sources/cairomm/1.12/cairomm-1.12.0.tar.xz",
|
||||
"sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6",
|
||||
"x-checker-data": {
|
||||
"type": "gnome",
|
||||
"name": "cairomm",
|
||||
"stable-only": true,
|
||||
"versions": {
|
||||
"<": "1.16.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
"/lib/cairomm-*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.4.tar.xz",
|
||||
"sha256": "9beb71c3e90cfcfb790396b51e3f5e7169966751efd4f3ef9697114be3be6743"
|
||||
"url": "https://download.gnome.org/sources/pangomm/2.46/pangomm-2.46.2.tar.xz",
|
||||
"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",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DBUILD_STATIC_LIBS=OFF",
|
||||
"-DENABLE_THREADING=ON"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
/* 0.15-nodoc doesn't build */
|
||||
"type": "archive",
|
||||
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.13.1-nodoc.tar.gz",
|
||||
"sha256": "94a26340c0785fcff4f46ff38609cf84ebcd670df0c8efd75d039cc951d80132"
|
||||
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.16.tar.gz",
|
||||
"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": [
|
||||
{
|
||||
"type": "dir",
|
||||
"path": "../.."
|
||||
}
|
||||
],
|
||||
"buildsystem": "cmake",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DFLATPAK=ON",
|
||||
"-DENABLE_CLI=OFF",
|
||||
"-DENABLE_TESTS=OFF"
|
||||
"cleanup": [
|
||||
"/lib/libslvs*.so*"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: solvespace
|
||||
base: core18
|
||||
base: core22
|
||||
summary: Parametric 2d/3d CAD
|
||||
adopt-info: solvespace
|
||||
description: |
|
||||
|
@ -14,6 +14,8 @@ description: |
|
|||
|
||||
confinement: strict
|
||||
license: GPL-3.0
|
||||
compression: lzo
|
||||
grade: stable
|
||||
|
||||
layout:
|
||||
/usr/share/solvespace:
|
||||
|
@ -23,13 +25,13 @@ apps:
|
|||
solvespace:
|
||||
command: usr/bin/solvespace
|
||||
desktop: solvespace.desktop
|
||||
extensions: [gnome-3-34]
|
||||
extensions: [gnome]
|
||||
plugs: [opengl, unity7, home, removable-media, gsettings, network]
|
||||
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:
|
||||
command: usr/bin/solvespace-cli
|
||||
extensions: [gnome-3-34]
|
||||
extensions: [gnome]
|
||||
plugs: [home, removable-media, network]
|
||||
|
||||
parts:
|
||||
|
@ -38,15 +40,15 @@ parts:
|
|||
source: ./solvespace-snap-src
|
||||
source-type: local
|
||||
override-pull: |
|
||||
snapcraftctl pull
|
||||
version_major=$(grep "solvespace_VERSION_MAJOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2)
|
||||
version_minor=$(grep "solvespace_VERSION_MINOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2)
|
||||
version="$version_major.$version_minor~$(git rev-parse --short=8 HEAD)"
|
||||
snapcraftctl set-version "$version"
|
||||
git describe --exact-match HEAD && grade="stable" || grade="devel"
|
||||
snapcraftctl set-grade "$grade"
|
||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||
configflags:
|
||||
craftctl default
|
||||
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
||||
override-build: |
|
||||
craftctl default
|
||||
project_version=$(grep CMAKE_PROJECT_VERSION:STATIC CMakeCache.txt | cut -d "=" -f2)
|
||||
cd $CRAFT_PART_SRC
|
||||
version="$project_version~$(git rev-parse --short=8 HEAD)"
|
||||
craftctl set version="$version"
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DENABLE_TESTS=OFF
|
||||
|
@ -56,6 +58,7 @@ parts:
|
|||
build-packages:
|
||||
- zlib1g-dev
|
||||
- libpng-dev
|
||||
- libcairo2-dev
|
||||
- libfreetype6-dev
|
||||
- libjson-c-dev
|
||||
- libgl-dev
|
||||
|
@ -63,6 +66,7 @@ parts:
|
|||
- libspnav-dev
|
||||
- git
|
||||
- g++
|
||||
- libc6-dev
|
||||
stage-packages:
|
||||
- libspnav0
|
||||
- libsigc++-2.0-0v5
|
||||
|
@ -70,11 +74,14 @@ parts:
|
|||
cleanup:
|
||||
after: [solvespace]
|
||||
plugin: nil
|
||||
build-snaps: [core18, gnome-3-34-1804]
|
||||
build-snaps: [gnome-42-2204]
|
||||
override-prime: |
|
||||
# Remove all files from snap that are already included in the base snap or in
|
||||
# any connected content snaps
|
||||
set -eux
|
||||
for snap in "core18" "gnome-3-34-1804"; do # List all content-snaps and base snaps you're using here
|
||||
cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" \;
|
||||
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 "$CRAFT_PRIME/{}" "$CRAFT_PRIME/usr/{}" \;
|
||||
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.
|
||||
set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/)
|
||||
set(resource_list)
|
||||
set(resource_names)
|
||||
if(WIN32)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc)
|
||||
|
@ -83,6 +84,23 @@ elseif(APPLE)
|
|||
DEPENDS ${source}
|
||||
VERBATIM)
|
||||
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
|
||||
include(GNUInstallDirs)
|
||||
|
||||
|
@ -111,7 +129,8 @@ endif()
|
|||
function(add_resources)
|
||||
foreach(name ${ARGN})
|
||||
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()
|
||||
endfunction()
|
||||
|
||||
|
@ -249,6 +268,8 @@ add_resources(
|
|||
icons/graphics-window/trim.png
|
||||
icons/graphics-window/vert.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/edges.png
|
||||
icons/text-window/faces.png
|
||||
|
@ -262,13 +283,16 @@ add_resources(
|
|||
icons/text-window/shaded.png
|
||||
icons/text-window/workplane.png
|
||||
locales.txt
|
||||
locales/cs_CZ.po
|
||||
locales/de_DE.po
|
||||
locales/en_US.po
|
||||
locales/fr_FR.po
|
||||
locales/uk_UA.po
|
||||
locales/es_AR.po
|
||||
locales/tr_TR.po
|
||||
locales/ru_RU.po
|
||||
locales/zh_CN.po
|
||||
locales/ja_JP.po
|
||||
fonts/unifont.hex.gz
|
||||
fonts/private/0-check-false.png
|
||||
fonts/private/1-check-true.png
|
||||
|
@ -303,4 +327,6 @@ add_custom_target(resources
|
|||
DEPENDS ${resource_list})
|
||||
if(WIN32)
|
||||
set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file})
|
||||
elseif(EMSCRIPTEN)
|
||||
set_property(TARGET resources PROPERTY NAMES ${resource_names})
|
||||
endif()
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include:
|
||||
</p>
|
||||
<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 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>
|
||||
|
@ -31,6 +31,34 @@
|
|||
<url type="bugtracker">https://github.com/solvespace/solvespace/issues</url>
|
||||
|
||||
<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>
|
||||
<mediatype>application/x-solvespace</mediatype>
|
||||
</provides>
|
||||
|
@ -38,6 +66,19 @@
|
|||
<content_rating type="oars-1.0" />
|
||||
|
||||
<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">
|
||||
<description>
|
||||
<p>Major new stable release. Includes new intersection boolean operation,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Version=1.0
|
||||
Name=SolveSpace
|
||||
Comment=A parametric 2d/3d CAD
|
||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
|
||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f
|
||||
MimeType=application/x-solvespace
|
||||
Icon=com.solvespace.SolveSpace
|
||||
Type=Application
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Version=1.0
|
||||
Name=SolveSpace
|
||||
Comment=A parametric 2d/3d CAD
|
||||
Exec=solvespace
|
||||
Exec=solvespace %f
|
||||
MimeType=application/x-solvespace
|
||||
Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg
|
||||
Type=Application
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Version=1.0
|
||||
Name=SolveSpace
|
||||
Comment=A parametric 2d/3d CAD
|
||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
|
||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f
|
||||
MimeType=application/x-solvespace
|
||||
Icon=solvespace
|
||||
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,
|
||||
# and human-readable names for every culture supported by SolveSpace.
|
||||
cs-CZ,1029,Česky
|
||||
de-DE,0407,Deutsch
|
||||
en-US,0409,English (US)
|
||||
fr-FR,040C,Français
|
||||
es-AR,2C0A,español (AR)
|
||||
ru-RU,0419,Русский
|
||||
tr-TR,041F,Türkçe
|
||||
uk-UA,0422,Українська
|
||||
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
1312
res/locales/tr_TR.po
1312
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
|
||||
FILEVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0
|
||||
PRODUCTVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0
|
||||
FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},0,0
|
||||
PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},0,0
|
||||
FILEFLAGSMASK 0
|
||||
FILEFLAGS 0
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
|
@ -13,12 +13,12 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "The SolveSpace authors"
|
||||
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 "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}"
|
||||
VALUE "FileVersion", "${PROJECT_VERSION}~${solvespace_GIT_HASH}"
|
||||
VALUE "OriginalFilename", "solvespace.exe"
|
||||
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
|
||||
|
||||
|
|
|
@ -19,50 +19,78 @@ endif()
|
|||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
|
||||
${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
|
||||
|
||||
if(APPLE)
|
||||
set(util_LIBRARIES
|
||||
target_link_libraries(slvs_deps INTERFACE
|
||||
${APPKIT_LIBRARY})
|
||||
endif()
|
||||
|
||||
# libslvs
|
||||
|
||||
set(libslvs_SOURCES
|
||||
add_library(slvs SHARED
|
||||
solvespace.h
|
||||
platform/platform.h
|
||||
util.cpp
|
||||
entity.cpp
|
||||
expr.cpp
|
||||
constraint.cpp
|
||||
constrainteq.cpp
|
||||
system.cpp
|
||||
platform/platform.cpp)
|
||||
|
||||
set(libslvs_HEADERS
|
||||
solvespace.h
|
||||
platform/platform.h)
|
||||
|
||||
add_library(slvs SHARED
|
||||
${libslvs_SOURCES}
|
||||
${libslvs_HEADERS}
|
||||
${util_SOURCES}
|
||||
platform/platform.cpp
|
||||
lib.cpp)
|
||||
|
||||
target_compile_definitions(slvs
|
||||
PRIVATE -DLIBRARY)
|
||||
|
||||
target_include_directories(slvs
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${EIGEN3_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(slvs
|
||||
${util_LIBRARIES}
|
||||
mimalloc-static)
|
||||
|
||||
add_dependencies(slvs
|
||||
mimalloc-static)
|
||||
target_link_libraries(slvs PRIVATE slvs_deps)
|
||||
|
||||
set_target_properties(slvs PROPERTIES
|
||||
PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h
|
||||
VERSION ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION 1)
|
||||
|
||||
if(NOT WIN32)
|
||||
|
@ -72,77 +100,18 @@ if(NOT WIN32)
|
|||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
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
|
||||
platform/guiwin.cpp
|
||||
platform/guigtk.cpp
|
||||
platform/guimac.mm)
|
||||
platform/guimac.mm
|
||||
platform/guihtml.cpp)
|
||||
|
||||
# solvespace library
|
||||
|
||||
set(solvespace_core_HEADERS
|
||||
set(solvespace_core_gl_SOURCES
|
||||
solvespace.cpp)
|
||||
|
||||
add_library(solvespace-core STATIC
|
||||
dsc.h
|
||||
expr.h
|
||||
polygon.h
|
||||
|
@ -152,9 +121,7 @@ set(solvespace_core_HEADERS
|
|||
platform/platform.h
|
||||
render/render.h
|
||||
render/gl3shader.h
|
||||
srf/surface.h)
|
||||
|
||||
set(solvespace_core_SOURCES
|
||||
srf/surface.h
|
||||
bsp.cpp
|
||||
clipboard.cpp
|
||||
confscreen.cpp
|
||||
|
@ -176,6 +143,7 @@ set(solvespace_core_SOURCES
|
|||
groupmesh.cpp
|
||||
importdxf.cpp
|
||||
importidf.cpp
|
||||
importmesh.cpp
|
||||
mesh.cpp
|
||||
modify.cpp
|
||||
mouse.cpp
|
||||
|
@ -201,44 +169,19 @@ set(solvespace_core_SOURCES
|
|||
srf/merge.cpp
|
||||
srf/ratpoly.cpp
|
||||
srf/raycast.cpp
|
||||
srf/shell.cpp
|
||||
srf/surface.cpp
|
||||
srf/surfinter.cpp
|
||||
srf/triangulate.cpp)
|
||||
|
||||
set(solvespace_core_gl_SOURCES
|
||||
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})
|
||||
target_link_libraries(solvespace-core PUBLIC slvs_deps)
|
||||
|
||||
# solvespace translations
|
||||
|
||||
if(HAVE_GETTEXT)
|
||||
get_target_property(solvespace_core_SOURCES solvespace-core SOURCES)
|
||||
set(inputs
|
||||
${solvespace_core_SOURCES}
|
||||
${solvespace_core_HEADERS}
|
||||
${every_platform_SOURCES}
|
||||
${solvespace_core_gl_SOURCES})
|
||||
|
||||
|
@ -265,9 +208,9 @@ if(HAVE_GETTEXT)
|
|||
--keyword --keyword=_ --keyword=N_ --keyword=C_:2,1c --keyword=CN_:2,1c
|
||||
--force-po --width=100 --sort-by-file
|
||||
--package-name=SolveSpace
|
||||
--package-version=${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}
|
||||
--package-version=${PROJECT_VERSION}
|
||||
"--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}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_pot} ${output_pot}
|
||||
DEPENDS ${inputs}
|
||||
|
@ -321,52 +264,137 @@ endif()
|
|||
if(ENABLE_GUI)
|
||||
add_executable(solvespace WIN32 MACOSX_BUNDLE
|
||||
${solvespace_core_gl_SOURCES}
|
||||
${platform_SOURCES}
|
||||
platform/entrygui.cpp
|
||||
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)
|
||||
|
||||
add_dependencies(solvespace
|
||||
resources)
|
||||
|
||||
target_link_libraries(solvespace
|
||||
PRIVATE
|
||||
solvespace-core
|
||||
${OPENGL_LIBRARIES}
|
||||
${platform_LIBRARIES}
|
||||
${COVERAGE_LIBRARY})
|
||||
${OPENGL_LIBRARIES})
|
||||
|
||||
if(MSVC)
|
||||
set_target_properties(solvespace PROPERTIES
|
||||
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
|
||||
# OpenGL version
|
||||
if(OPENGL STREQUAL 3)
|
||||
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)
|
||||
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
|
||||
OUTPUT_NAME SolveSpace
|
||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
|
||||
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()
|
||||
|
||||
# solvespace headless library
|
||||
|
||||
set(headless_SOURCES
|
||||
add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL
|
||||
${solvespace_core_gl_SOURCES}
|
||||
platform/guinone.cpp
|
||||
render/rendercairo.cpp)
|
||||
|
||||
add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL
|
||||
${solvespace_core_gl_SOURCES}
|
||||
${headless_SOURCES})
|
||||
|
||||
target_compile_definitions(solvespace-headless
|
||||
PRIVATE -DHEADLESS)
|
||||
PRIVATE 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
|
||||
solvespace-core
|
||||
${CAIRO_LIBRARIES})
|
||||
|
||||
target_compile_options(solvespace-headless
|
||||
PRIVATE ${COVERAGE_FLAGS})
|
||||
PRIVATE
|
||||
solvespace-core)
|
||||
|
||||
# solvespace command-line executable
|
||||
|
||||
|
@ -390,7 +418,7 @@ endif()
|
|||
|
||||
# solvespace unix package
|
||||
|
||||
if(NOT (WIN32 OR APPLE))
|
||||
if(NOT (WIN32 OR APPLE OR EMSCRIPTEN))
|
||||
if(ENABLE_GUI)
|
||||
install(TARGETS solvespace
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
|
|
@ -249,8 +249,10 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
|||
case Constraint::Type::COMMENT:
|
||||
c.disp.offset = c.disp.offset.Plus(trans);
|
||||
break;
|
||||
case Constraint::Type::PT_PT_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::DIAMETER:
|
||||
c.valA *= fabs(scale);
|
||||
|
@ -281,7 +283,7 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
|||
}
|
||||
case Constraint::Type::HORIZONTAL:
|
||||
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(c.type == Constraint::Type::HORIZONTAL) {
|
||||
c.type = Constraint::Type::VERTICAL;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#ifndef 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 */
|
||||
#define UNIX_DATADIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATAROOTDIR@/solvespace"
|
||||
|
|
|
@ -9,24 +9,6 @@
|
|||
#include <omp.h>
|
||||
#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) {
|
||||
SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]);
|
||||
|
||||
|
@ -58,13 +40,8 @@ void TextWindow::ScreenChangeExportMaxSegments(int link, uint32_t v) {
|
|||
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) {
|
||||
SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing));
|
||||
SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing, true));
|
||||
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) {
|
||||
SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset));
|
||||
SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset, true));
|
||||
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) {
|
||||
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) {
|
||||
SS.immediatelyEditDimension = !SS.immediatelyEditDimension;
|
||||
SS.GW.Invalidate(/*clearPersistent=*/true);
|
||||
|
@ -171,7 +161,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, uint32_t v) {
|
|||
}
|
||||
int col = 13;
|
||||
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.i = v;
|
||||
}
|
||||
|
@ -181,7 +171,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) {
|
|||
switch(link) {
|
||||
case 'd':
|
||||
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;
|
||||
|
||||
case 's':
|
||||
|
@ -191,12 +186,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) {
|
|||
|
||||
case 'F':
|
||||
SS.TW.edit.meaning = Edit::G_CODE_FEED;
|
||||
buf += SS.MmToString(SS.gCode.feed);
|
||||
buf += SS.MmToString(SS.gCode.feed, true);
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
SS.TW.edit.meaning = Edit::G_CODE_PLUNGE_FEED;
|
||||
buf += SS.MmToString(SS.gCode.plungeFeed);
|
||||
buf += SS.MmToString(SS.gCode.plungeFeed, true);
|
||||
break;
|
||||
}
|
||||
SS.TW.ShowEditControl(14, buf);
|
||||
|
@ -227,18 +222,6 @@ void TextWindow::ShowConfiguration() {
|
|||
&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, "%Ft chord tolerance (in percents)%E");
|
||||
Printf(false, "%Ba %@ %% %Fl%Ll%f%D[change]%E; %@ mm, %d triangles",
|
||||
|
@ -260,11 +243,6 @@ void TextWindow::ShowConfiguration() {
|
|||
SS.exportMaxSegments,
|
||||
&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, "%Ba %s %Fl%Ll%f%D[change]%E",
|
||||
SS.MmToString(SS.gridSpacing).c_str(),
|
||||
|
@ -368,11 +346,18 @@ void TextWindow::ShowConfiguration() {
|
|||
Printf(false, " %Fd%f%Ll%s enable automatic line constraints%E",
|
||||
&ScreenChangeAutomaticLineConstraints,
|
||||
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,
|
||||
SS.turntableNav ? CHECK_TRUE : CHECK_FALSE);
|
||||
Printf(false, " %Fd%f%Ll%s edit newly added dimensions%E",
|
||||
&ScreenChangeImmediatelyEditDimension,
|
||||
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, "%Ft autosave interval (in minutes)%E");
|
||||
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
|
||||
|
@ -459,6 +444,11 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {
|
|||
SS.GW.Invalidate();
|
||||
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: {
|
||||
int v = atoi(s.c_str());
|
||||
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);
|
||||
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: {
|
||||
Expr *e = Expr::From(s, /*popUpError=*/true);
|
||||
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_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::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::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_HORIZ: s = C_("constr-name", "symmetric-h"); 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) {
|
||||
std::vector<Constraint> newcons;
|
||||
|
||||
Constraint c = {};
|
||||
c.group = SS.GW.activeGroup;
|
||||
c.workplane = SS.GW.ActiveWorkplane();
|
||||
|
@ -230,6 +236,12 @@ void Constraint::MenuConstrain(Command id) {
|
|||
} else if(gs.circlesOrArcs == 1 && gs.n == 1) {
|
||||
c.type = Type::DIAMETER;
|
||||
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 {
|
||||
Error(_("Bad selection for distance / diameter constraint. This "
|
||||
"constraint can apply to:\n\n"
|
||||
|
@ -259,54 +271,69 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.valA = 0;
|
||||
c.ModifyToSatisfy();
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
}
|
||||
|
||||
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.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) {
|
||||
c.type = Type::PT_IN_PLANE;
|
||||
c.ptA = gs.point[0];
|
||||
c.entityA = gs.entity[0];
|
||||
newcons.push_back(c);
|
||||
} else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) {
|
||||
c.type = Type::PT_ON_LINE;
|
||||
c.ptA = gs.point[0];
|
||||
c.entityA = gs.entity[0];
|
||||
newcons.push_back(c);
|
||||
} else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) {
|
||||
c.type = Type::PT_ON_CIRCLE;
|
||||
c.ptA = gs.point[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.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 {
|
||||
Error(_("Bad selection for on point / curve / plane constraint. "
|
||||
"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 line segment (point on line)\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;
|
||||
}
|
||||
AddConstraint(&c);
|
||||
for (auto&& nc : newcons)
|
||||
AddConstraint(&nc);
|
||||
break;
|
||||
|
||||
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.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) {
|
||||
c.type = Type::EQ_PT_LN_DISTANCES;
|
||||
c.entityA = gs.entity[0];
|
||||
c.ptA = gs.point[0];
|
||||
c.entityB = gs.entity[1];
|
||||
c.ptB = gs.point[1];
|
||||
newcons.push_back(c);
|
||||
} else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) {
|
||||
// The same line segment for the distances, but different
|
||||
// points.
|
||||
|
@ -315,27 +342,20 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.ptA = gs.point[0];
|
||||
c.entityB = gs.entity[0];
|
||||
c.ptB = gs.point[1];
|
||||
newcons.push_back(c);
|
||||
} else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) {
|
||||
c.type = Type::EQ_LEN_PT_LINE_D;
|
||||
c.entityA = gs.entity[0];
|
||||
c.entityB = gs.entity[1];
|
||||
c.ptA = gs.point[0];
|
||||
} 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 == 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) {
|
||||
newcons.push_back(c);
|
||||
} else if(gs.circlesOrArcs >= 2 && gs.circlesOrArcs == gs.n) {
|
||||
c.type = Type::EQUAL_RADIUS;
|
||||
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) {
|
||||
c.type = Type::EQUAL_LINE_ARC_LEN;
|
||||
if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||
|
@ -345,38 +365,38 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.entityA = gs.entity[0];
|
||||
c.entityB = gs.entity[1];
|
||||
}
|
||||
newcons.push_back(c);
|
||||
} else {
|
||||
Error(_("Bad selection for equal length / radius constraint. "
|
||||
"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 "
|
||||
"(equal point-line distances)\n"
|
||||
" * a line segment and two points "
|
||||
"(equal point-line distances)\n"
|
||||
" * a line segment, and a point and line segment "
|
||||
"(point-line distance equals length)\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"
|
||||
" * two circles or arcs (equal radius)\n"
|
||||
" * two or more circles or arcs (equal radius)\n"
|
||||
" * a line segment and an arc "
|
||||
"(line segment length equals arc length)\n"));
|
||||
return;
|
||||
}
|
||||
if(c.type == Type::EQUAL_ANGLE) {
|
||||
// Infer the nearest supplementary angle from the sketch.
|
||||
Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
|
||||
b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
|
||||
a2 = SK.GetEntity(c.entityC)->VectorGetNum(),
|
||||
b2 = SK.GetEntity(c.entityD)->VectorGetNum();
|
||||
double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
|
||||
SS.UndoRemember();
|
||||
for (auto&& nc : newcons){
|
||||
if(nc.type == Type::EQUAL_ANGLE) {
|
||||
// Infer the nearest supplementary angle from the sketch.
|
||||
Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(),
|
||||
b1 = SK.GetEntity(c.entityB)->VectorGetNum(),
|
||||
a2 = SK.GetEntity(c.entityC)->VectorGetNum(),
|
||||
b2 = SK.GetEntity(c.entityD)->VectorGetNum();
|
||||
double d1 = a1.Dot(b1), d2 = a2.Dot(b2);
|
||||
|
||||
if(d1*d2 < 0) {
|
||||
c.other = true;
|
||||
if(d1*d2 < 0) {
|
||||
nc.other = true;
|
||||
}
|
||||
}
|
||||
AddConstraint(&nc, /*rememberForUndo=*/false);
|
||||
}
|
||||
AddConstraint(&c);
|
||||
break;
|
||||
|
||||
case Command::RATIO:
|
||||
|
@ -384,16 +404,34 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.type = Type::LENGTH_RATIO;
|
||||
c.entityA = gs.entity[0];
|
||||
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 {
|
||||
Error(_("Bad selection for length ratio constraint. This "
|
||||
"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;
|
||||
}
|
||||
|
||||
c.valA = 0;
|
||||
c.ModifyToSatisfy();
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
|
||||
case Command::DIFFERENCE:
|
||||
|
@ -401,16 +439,34 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.type = Type::LENGTH_DIFFERENCE;
|
||||
c.entityA = gs.entity[0];
|
||||
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 {
|
||||
Error(_("Bad selection for length difference constraint. This "
|
||||
"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;
|
||||
}
|
||||
|
||||
c.valA = 0;
|
||||
c.ModifyToSatisfy();
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
|
||||
case Command::AT_MIDPOINT:
|
||||
|
@ -424,12 +480,15 @@ void Constraint::MenuConstrain(Command id) {
|
|||
SS.UndoRemember();
|
||||
DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA);
|
||||
AddConstraint(&c, /*rememberForUndo=*/false);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
} else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) {
|
||||
c.type = Type::AT_MIDPOINT;
|
||||
int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0;
|
||||
c.entityA = gs.entity[i];
|
||||
c.entityB = gs.entity[1-i];
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
} else {
|
||||
Error(_("Bad selection for at midpoint constraint. This "
|
||||
"constraint can apply to:\n\n"
|
||||
|
@ -439,7 +498,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
"(line's midpoint on plane)\n"));
|
||||
return;
|
||||
}
|
||||
AddConstraint(&c);
|
||||
|
||||
break;
|
||||
|
||||
case Command::SYMMETRIC:
|
||||
|
@ -528,41 +587,47 @@ void Constraint::MenuConstrain(Command id) {
|
|||
DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]),
|
||||
Entity::NO_ENTITY);
|
||||
AddConstraint(&c, /*rememberForUndo=*/false);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
|
||||
case Command::VERTICAL:
|
||||
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) {
|
||||
c.type = Type::HORIZONTAL;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -603,6 +668,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
nfree->NormalForceTo(Quaternion::From(fu, fv));
|
||||
}
|
||||
AddConstraint(&c, /*rememberForUndo=*/false);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -640,7 +706,19 @@ void Constraint::MenuConstrain(Command id) {
|
|||
|
||||
case Command::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.entityA = gs.vector[0];
|
||||
c.entityB = gs.vector[1];
|
||||
|
@ -648,9 +726,15 @@ void Constraint::MenuConstrain(Command id) {
|
|||
} else {
|
||||
Error(_("Bad selection for angle constraint. This constraint "
|
||||
"can apply to:\n\n"
|
||||
"Angle between:\n"
|
||||
" * two line segments\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;
|
||||
}
|
||||
|
||||
|
@ -679,14 +763,23 @@ void Constraint::MenuConstrain(Command id) {
|
|||
|
||||
c.ModifyToSatisfy();
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
}
|
||||
|
||||
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.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) {
|
||||
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||
*arc = SK.GetEntity(gs.entity[1]);
|
||||
|
@ -699,6 +792,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.type = Type::ARC_LINE_TANGENT;
|
||||
c.entityA = arc->h;
|
||||
c.entityB = line->h;
|
||||
newcons.push_back(c);
|
||||
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
||||
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||
*cubic = SK.GetEntity(gs.entity[1]);
|
||||
|
@ -711,6 +805,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.type = Type::CUBIC_LINE_TANGENT;
|
||||
c.entityA = cubic->h;
|
||||
c.entityB = line->h;
|
||||
newcons.push_back(c);
|
||||
} else if(gs.cubics + gs.arcs == 2 && gs.n == 2) {
|
||||
if(!SS.GW.LockedInWorkplane()) {
|
||||
Error(_("Curve-curve tangency must apply in workplane."));
|
||||
|
@ -724,33 +819,43 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.type = Type::CURVE_CURVE_TANGENT;
|
||||
c.entityA = eA->h;
|
||||
c.entityB = eB->h;
|
||||
newcons.push_back(c);
|
||||
} else {
|
||||
Error(_("Bad selection for parallel / tangent constraint. This "
|
||||
"constraint can apply to:\n\n"
|
||||
" * two line segments (parallel)\n"
|
||||
" * a line segment and a normal (parallel)\n"
|
||||
" * two normals (parallel)\n"
|
||||
" * two faces\n"
|
||||
" * two or more line segments (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 "
|
||||
"an endpoint (tangent)\n"));
|
||||
return;
|
||||
}
|
||||
AddConstraint(&c);
|
||||
SS.UndoRemember();
|
||||
for (auto&& nc:newcons)
|
||||
AddConstraint(&nc, /*rememberForUndo=*/false);
|
||||
break;
|
||||
|
||||
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.entityA = gs.vector[0];
|
||||
c.entityB = gs.vector[1];
|
||||
} else {
|
||||
Error(_("Bad selection for perpendicular constraint. This "
|
||||
"constraint can apply to:\n\n"
|
||||
" * two faces\n"
|
||||
" * two line segments\n"
|
||||
" * a line segment and a normal\n"
|
||||
" * two normals\n"));
|
||||
return;
|
||||
}
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
|
||||
case Command::WHERE_DRAGGED:
|
||||
|
@ -764,6 +869,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
return;
|
||||
}
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
break;
|
||||
|
||||
case Command::COMMENT:
|
||||
|
@ -774,6 +880,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.workplane = SS.GW.ActiveWorkplane();
|
||||
c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT");
|
||||
AddConstraint(&c);
|
||||
newcons.push_back(c);
|
||||
} else {
|
||||
SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND;
|
||||
SS.GW.pending.command = Command::COMMENT;
|
||||
|
@ -784,31 +891,32 @@ void Constraint::MenuConstrain(Command id) {
|
|||
|
||||
default: ssassert(false, "Unexpected menu ID");
|
||||
}
|
||||
|
||||
for(const Constraint &cc : SK.constraint) {
|
||||
if(c.h != cc.h && c.Equals(cc)) {
|
||||
// Oops, we already have this exact constraint. Remove the one we just added.
|
||||
SK.constraint.RemoveById(c.h);
|
||||
SS.GW.ClearSelection();
|
||||
// And now select the old one, to give feedback.
|
||||
SS.GW.MakeSelected(cc.h);
|
||||
return;
|
||||
for (auto nc:newcons){
|
||||
for(const Constraint &cc : SK.constraint) {
|
||||
if(nc.h != cc.h && nc.Equals(cc)) {
|
||||
// Oops, we already have this exact constraint. Remove the one we just added.
|
||||
SK.constraint.RemoveById(nc.h);
|
||||
SS.GW.ClearSelection();
|
||||
// And now select the old one, to give feedback.
|
||||
SS.GW.MakeSelected(cc.h);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(SK.constraint.FindByIdNoOops(c.h)) {
|
||||
Constraint *constraint = SK.GetConstraint(c.h);
|
||||
if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY &&
|
||||
!SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
|
||||
constraint->HasLabel()) {
|
||||
constraint->reference = true;
|
||||
if(SK.constraint.FindByIdNoOops(nc.h)) {
|
||||
Constraint *constraint = SK.GetConstraint(nc.h);
|
||||
if(SS.TestRankForGroup(nc.group) == SolveResult::REDUNDANT_OKAY &&
|
||||
!SK.GetGroup(SS.GW.activeGroup)->allowRedundant &&
|
||||
constraint->HasLabel()) {
|
||||
constraint->reference = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
|
||||
id == Command::RATIO || id == Command::DIFFERENCE) &&
|
||||
SS.immediatelyEditDimension) {
|
||||
SS.GW.EditConstraint(c.h);
|
||||
if((id == Command::DISTANCE_DIA || id == Command::ANGLE ||
|
||||
id == Command::RATIO || id == Command::DIFFERENCE) &&
|
||||
SS.immediatelyEditDimension) {
|
||||
SS.GW.EditConstraint(nc.h);
|
||||
}
|
||||
}
|
||||
|
||||
SS.GW.ClearSelection();
|
||||
|
|
|
@ -18,7 +18,11 @@ bool ConstraintBase::HasLabel() const {
|
|||
case Type::PROJ_PT_DISTANCE:
|
||||
case Type::DIAMETER:
|
||||
case Type::LENGTH_RATIO:
|
||||
case Type::ARC_ARC_LEN_RATIO:
|
||||
case Type::ARC_LINE_LEN_RATIO:
|
||||
case Type::LENGTH_DIFFERENCE:
|
||||
case Type::ARC_ARC_DIFFERENCE:
|
||||
case Type::ARC_LINE_DIFFERENCE:
|
||||
case Type::ANGLE:
|
||||
case Type::COMMENT:
|
||||
return true;
|
||||
|
@ -39,7 +43,11 @@ bool ConstraintBase::IsProjectible() const {
|
|||
case Type::EQ_PT_LN_DISTANCES:
|
||||
case Type::EQUAL_ANGLE:
|
||||
case Type::LENGTH_RATIO:
|
||||
case Type::ARC_ARC_LEN_RATIO:
|
||||
case Type::ARC_LINE_LEN_RATIO:
|
||||
case Type::LENGTH_DIFFERENCE:
|
||||
case Type::ARC_ARC_DIFFERENCE:
|
||||
case Type::ARC_LINE_DIFFERENCE:
|
||||
case Type::SYMMETRIC:
|
||||
case Type::SYMMETRIC_HORIZ:
|
||||
case Type::SYMMETRIC_VERT:
|
||||
|
@ -335,6 +343,110 @@ void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
|
|||
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: {
|
||||
EntityBase *a = SK.GetEntity(entityA);
|
||||
EntityBase *b = SK.GetEntity(entityB);
|
||||
|
@ -344,6 +456,110 @@ void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
|
|||
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: {
|
||||
EntityBase *circle = SK.GetEntity(entityA);
|
||||
Expr *r = circle->CircleGetRadiusExpr();
|
||||
|
|
|
@ -19,6 +19,17 @@ void TextWindow::ScreenEditTtfText(int link, uint32_t v) {
|
|||
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) {
|
||||
int i = (int)v;
|
||||
if(i < 0) return;
|
||||
|
@ -64,17 +75,36 @@ void TextWindow::ScreenConstraintShowAsRadius(int link, uint32_t v) {
|
|||
void TextWindow::DescribeSelection() {
|
||||
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 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)) {
|
||||
Entity *e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]);
|
||||
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) {
|
||||
case Entity::Type::POINT_IN_3D:
|
||||
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_COPY:
|
||||
case Entity::Type::POINT_N_ROT_AA:
|
||||
case Entity::Type::POINT_N_ROT_AXIS_TRANS:
|
||||
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;
|
||||
|
||||
case Entity::Type::NORMAL_IN_3D:
|
||||
|
@ -104,20 +135,20 @@ void TextWindow::DescribeSelection() {
|
|||
case Entity::Type::WORKPLANE: {
|
||||
p = SK.GetEntity(e->point[0])->PointGetNum();
|
||||
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();
|
||||
p = q.RotationN();
|
||||
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
||||
Printf(true, " normal = " PT_AS_NUM_LINK, CO_LINK(e->Normal(), p));
|
||||
break;
|
||||
}
|
||||
case Entity::Type::LINE_SEGMENT: {
|
||||
Vector p0 = SK.GetEntity(e->point[0])->PointGetNum();
|
||||
p = p0;
|
||||
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();
|
||||
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",
|
||||
SS.MmToString((p1.Minus(p0).Magnitude())).c_str());
|
||||
break;
|
||||
|
@ -137,18 +168,18 @@ void TextWindow::DescribeSelection() {
|
|||
}
|
||||
for(int i = 0; i < pts; i++) {
|
||||
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;
|
||||
|
||||
case Entity::Type::ARC_OF_CIRCLE: {
|
||||
Printf(false, "%FtARC OF A CIRCLE%E");
|
||||
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();
|
||||
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();
|
||||
Printf(false, " " PT_AS_STR, COSTR(p));
|
||||
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(e->point[2]), p));
|
||||
double r = e->CircleGetRadiusNum();
|
||||
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
||||
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
||||
|
@ -160,10 +191,11 @@ void TextWindow::DescribeSelection() {
|
|||
case Entity::Type::CIRCLE: {
|
||||
Printf(false, "%FtCIRCLE%E");
|
||||
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();
|
||||
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
||||
Printf(false, " radius = %Fi%s", SS.MmToString(r).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, " circumference = %Fi%s", SS.MmToString(2*M_PI*r).c_str());
|
||||
break;
|
||||
}
|
||||
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_AA:
|
||||
case Entity::Type::FACE_N_TRANS:
|
||||
Printf(false, "%FtPLANE FACE%E");
|
||||
case Entity::Type::FACE_ROT_NORMAL_PT:
|
||||
case Entity::Type::FACE_N_ROT_AXIS_TRANS:
|
||||
Printf(false, "%FtPLANE FACE%E");
|
||||
p = e->FaceGetNormalNum();
|
||||
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
||||
p = e->FaceGetPointNum();
|
||||
Printf(false, " thru = " PT_AS_STR, COSTR(p));
|
||||
Printf(false, " thru = " PT_AS_STR, COSTR(e, p));
|
||||
break;
|
||||
|
||||
case Entity::Type::TTF_TEXT: {
|
||||
Printf(false, "%FtTRUETYPE FONT TEXT%E");
|
||||
Printf(true, " font = '%Fi%s%E'", e->font.c_str());
|
||||
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);
|
||||
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");
|
||||
SS.fonts.LoadAll();
|
||||
// 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) {
|
||||
Printf(false, "%FtTWO POINTS");
|
||||
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();
|
||||
Printf(false, " " PT_AS_STR, COSTR(p1));
|
||||
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(gs.point[1]), p1));
|
||||
Vector dv = p1.Minus(p0);
|
||||
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) {
|
||||
Entity *ec = SK.GetEntity(gs.entity[0]);
|
||||
if(ec->type == Entity::Type::CIRCLE) {
|
||||
|
@ -329,9 +366,9 @@ void TextWindow::DescribeSelection() {
|
|||
Printf(false, "%FtPOINT AND AN ARC");
|
||||
} else ssassert(false, "Unexpected entity type");
|
||||
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();
|
||||
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();
|
||||
Printf(false, " diameter = %Fi%s", SS.MmToString(r*2).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) {
|
||||
Printf(false, "%FtA POINT AND A PLANE FACE");
|
||||
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();
|
||||
Printf(true, " plane normal = " PT_AS_NUM, CO(n));
|
||||
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);
|
||||
Printf(true, " distance = %Fi%s", SS.MmToString(dd).c_str());
|
||||
} else if(gs.n == 3 && gs.points == 2 && gs.vectors == 1) {
|
||||
Printf(false, "%FtTWO POINTS AND A VECTOR");
|
||||
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();
|
||||
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();
|
||||
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);
|
||||
Printf(true, " proj_d = %Fi%s", SS.MmToString(d).c_str());
|
||||
} 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(),
|
||||
lp1 = SK.GetEntity(ln->point[1])->PointGetNum();
|
||||
Printf(false, "%FtLINE SEGMENT AND POINT%E");
|
||||
Printf(true, " ln thru " PT_AS_STR, COSTR(lp0));
|
||||
Printf(false, " " PT_AS_STR, COSTR(lp1));
|
||||
Printf(true, " ln thru " PT_AS_STR, COSTR(SK.GetEntity(ln->point[0]), lp0));
|
||||
Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(ln->point[1]), lp1));
|
||||
Entity *p = SK.GetEntity(gs.point[0]);
|
||||
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",
|
||||
SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))).c_str());
|
||||
hEntity wrkpl = SS.GW.ActiveWorkplane();
|
||||
|
@ -386,8 +423,8 @@ void TextWindow::DescribeSelection() {
|
|||
v0 = v0.WithMagnitude(1);
|
||||
v1 = v1.WithMagnitude(1);
|
||||
|
||||
Printf(true, " vectorA = " PT_AS_NUM, CO(v0));
|
||||
Printf(false, " vectorB = " PT_AS_NUM, CO(v1));
|
||||
Printf(true, " vectorA = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.entity[0]), v0));
|
||||
Printf(false, " vectorB = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.entity[1]), v1));
|
||||
|
||||
double theta = acos(v0.Dot(v1));
|
||||
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) {
|
||||
Printf(false, "%FtTWO PLANE FACES");
|
||||
|
||||
Vector n0 = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
|
||||
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));
|
||||
ListFaces();
|
||||
|
||||
Vector n0 = SK.GetEntity(gs.face[0])->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));
|
||||
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);
|
||||
|
||||
if(fabs(theta) < 0.01) {
|
||||
double d = (p1.Minus(p0)).Dot(n0);
|
||||
Vector p0 = SK.GetEntity(gs.face[0])->FaceGetPointNum();
|
||||
Vector p1 = SK.GetEntity(gs.face[1])->FaceGetPointNum();
|
||||
|
||||
double d = (p1.Minus(p0)).Dot(n0);
|
||||
Printf(true, " distance = %Fi%s", SS.MmToString(d).c_str());
|
||||
}
|
||||
} else if(gs.n == 0 && gs.stylables > 0) {
|
||||
Printf(false, "%FtSELECTED:%E comment text");
|
||||
} else if(gs.n == 3 && gs.faces == 3) {
|
||||
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) {
|
||||
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
||||
const std::string &desc = c->DescriptionString().c_str();
|
||||
|
||||
if(c->type == Constraint::Type::COMMENT) {
|
||||
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()) {
|
||||
if(c->reference) {
|
||||
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()) {
|
||||
// In the interest of speed for the triangle drawing code,
|
||||
// only two faces may be selected at a time.
|
||||
int c = 0;
|
||||
// only MAX_SELECTABLE_FACES faces may be selected at a time.
|
||||
unsigned int c = 0;
|
||||
Selection *s;
|
||||
selection.ClearTags();
|
||||
for(s = selection.First(); s; s = selection.NextAfter(s)) {
|
||||
hEntity he = s->entity;
|
||||
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
|
||||
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();
|
||||
|
@ -218,13 +220,28 @@ void GraphicsWindow::SelectByMarquee() {
|
|||
bool entityHasBBox;
|
||||
BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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,
|
||||
// lines, etc.), and so on.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -12,7 +12,7 @@ std::string Constraint::Label() const {
|
|||
std::string result;
|
||||
if(type == Type::ANGLE) {
|
||||
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);
|
||||
} else if(type == Type::COMMENT) {
|
||||
result = comment;
|
||||
|
@ -267,7 +267,7 @@ void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,
|
|||
const Camera &camera = canvas->GetCamera();
|
||||
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();
|
||||
Quaternion q = circ->Normal()->NormalGetNum();
|
||||
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,
|
||||
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();
|
||||
double pixels = 1.0 / camera.scale;
|
||||
|
@ -305,6 +306,9 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
|||
db = db.ProjectVectorInto(workplane);
|
||||
}
|
||||
|
||||
a0 = a0.Plus(explodeOffset);
|
||||
b0 = b0.Plus(explodeOffset);
|
||||
|
||||
Vector a1 = a0.Plus(da);
|
||||
Vector b1 = b0.Plus(db);
|
||||
|
||||
|
@ -445,21 +449,38 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
|||
}
|
||||
|
||||
bool Constraint::IsVisible() const {
|
||||
if(!SS.GW.showConstraints) return false;
|
||||
Group *g = SK.GetGroup(group);
|
||||
// If the group is hidden, then the constraints are hidden and not
|
||||
// able to be selected.
|
||||
if(!(g->visible)) return false;
|
||||
// And likewise if the group is not the active group; except for comments
|
||||
// with an assigned style.
|
||||
if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) {
|
||||
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);
|
||||
// If the group is hidden, then the constraints are hidden and not
|
||||
// able to be selected.
|
||||
if(!(g->visible)) return false;
|
||||
// And likewise if the group is not the active group; except for comments
|
||||
// with an assigned style.
|
||||
if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) {
|
||||
return false;
|
||||
}
|
||||
if(disp.style.v) {
|
||||
Style *s = Style::Get(disp.style);
|
||||
if(!s->visible) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if(disp.style.v) {
|
||||
Style *s = Style::Get(disp.style);
|
||||
if(!s->visible) return false;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,
|
||||
|
@ -534,6 +555,15 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
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);
|
||||
if(refs) refs->push_back(ref);
|
||||
|
||||
|
@ -548,6 +578,19 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
dp = (bp.Minus(ap)),
|
||||
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);
|
||||
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_PLANE_DISTANCE: {
|
||||
Vector pt = SK.GetEntity(ptA)->PointGetNum();
|
||||
Vector pt = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||
Entity *enta = SK.GetEntity(entityA);
|
||||
Vector n, p;
|
||||
if(type == Type::PT_PLANE_DISTANCE) {
|
||||
|
@ -590,7 +633,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
}
|
||||
|
||||
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);
|
||||
Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
|
||||
Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
|
||||
|
@ -602,6 +646,19 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
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
|
||||
Vector closest = pt.ClosestPointOnLine(lA, dl);
|
||||
|
||||
|
@ -655,7 +712,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
|
||||
case Type::DIAMETER: {
|
||||
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();
|
||||
Vector n = q.RotationN().WithMagnitude(1);
|
||||
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 d = camera.projUp.ScaledBy((2-a)/camera.scale);
|
||||
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);
|
||||
canvas->DrawQuad(p.Plus (r).Plus (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_IN_PLANE: {
|
||||
double s = 8/camera.scale;
|
||||
Vector p = SK.GetEntity(ptA)->PointGetNum();
|
||||
Vector p = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||
if(refs) refs->push_back(p);
|
||||
Vector r, d;
|
||||
if(type == Type::PT_ON_FACE) {
|
||||
|
@ -740,7 +797,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
}
|
||||
|
||||
case Type::WHERE_DRAGGED: {
|
||||
Vector p = SK.GetEntity(ptA)->PointGetNum();
|
||||
Vector p = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||
if(refs) refs->push_back(p);
|
||||
Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus(
|
||||
gr.WithMagnitude(8/camera.scale)),
|
||||
|
@ -797,10 +854,10 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
return;
|
||||
|
@ -820,7 +877,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
}
|
||||
|
||||
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);
|
||||
if(refs) refs->push_back(ref);
|
||||
return;
|
||||
|
@ -855,7 +912,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
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);
|
||||
DoLine(canvas, hcs, s, s.Plus(v));
|
||||
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) {
|
||||
Entity *arc = SK.GetEntity(entityA);
|
||||
Entity *norm = SK.GetEntity(arc->normal);
|
||||
Vector c = SK.GetEntity(arc->point[0])->PointGetNum();
|
||||
Vector c = SK.GetEntity(arc->point[0])->PointGetDrawNum();
|
||||
Vector p =
|
||||
SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum();
|
||||
SK.GetEntity(arc->point[other ? 2 : 1])->PointGetDrawNum();
|
||||
Vector r = p.Minus(c);
|
||||
textAt = p.Plus(r.WithMagnitude(14/camera.scale));
|
||||
u = norm->NormalU();
|
||||
|
@ -896,6 +953,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
Entity *cubic = SK.GetEntity(entityA);
|
||||
Vector p = other ? cubic->CubicGetFinishNum() :
|
||||
cubic->CubicGetStartNum();
|
||||
p = p.Plus(cubic->ExplodeOffset());
|
||||
Vector dir = SK.GetEntity(entityB)->VectorGetNum();
|
||||
Vector out = n.Cross(dir);
|
||||
textAt = p.Plus(out.WithMagnitude(14/camera.scale));
|
||||
|
@ -905,12 +963,12 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
u = wn->NormalU();
|
||||
v = wn->NormalV();
|
||||
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
|
||||
// at the shared point, which could be from either a cubic
|
||||
// or an arc.
|
||||
if(other) {
|
||||
textAt = eA->EndpointFinish();
|
||||
textAt = eA->EndpointFinish().Plus(eA->ExplodeOffset());
|
||||
if(eA->type == Entity::Type::CUBIC) {
|
||||
dir = eA->CubicGetFinishTangentNum();
|
||||
} else {
|
||||
|
@ -919,7 +977,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
dir = n.Cross(dir);
|
||||
}
|
||||
} else {
|
||||
textAt = eA->EndpointStart();
|
||||
textAt = eA->EndpointStart().Plus(eA->ExplodeOffset());
|
||||
if(eA->type == Entity::Type::CUBIC) {
|
||||
dir = eA->CubicGetStartTangentNum();
|
||||
} else {
|
||||
|
@ -947,6 +1005,10 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale);
|
||||
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.Minus(u), p.Minus(u).Plus(n));
|
||||
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);
|
||||
Vector ref;
|
||||
DoEqualLenTicks(canvas, hcs,
|
||||
SK.GetEntity(line->point[0])->PointGetNum(),
|
||||
SK.GetEntity(line->point[1])->PointGetNum(),
|
||||
SK.GetEntity(line->point[0])->PointGetDrawNum(),
|
||||
SK.GetEntity(line->point[1])->PointGetDrawNum(),
|
||||
gn, &ref);
|
||||
if(refs) refs->push_back(ref);
|
||||
DoEqualRadiusTicks(canvas, hcs, entityB, &ref);
|
||||
|
@ -990,6 +1052,12 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
DoProjectedPoint(canvas, hcs, &b);
|
||||
}
|
||||
|
||||
if(ShouldDrawExploded()) {
|
||||
Vector offset = e->ExplodeOffset();
|
||||
a = a.Plus(offset);
|
||||
b = b.Plus(offset);
|
||||
}
|
||||
|
||||
Vector ref;
|
||||
DoEqualLenTicks(canvas, hcs, a, b, gn, &ref);
|
||||
if(refs) refs->push_back(ref);
|
||||
|
@ -1000,6 +1068,41 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
}
|
||||
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: {
|
||||
Entity *forLen = SK.GetEntity(entityA);
|
||||
|
@ -1009,6 +1112,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
DoProjectedPoint(canvas, hcs, &a);
|
||||
DoProjectedPoint(canvas, hcs, &b);
|
||||
}
|
||||
if(ShouldDrawExploded()) {
|
||||
Vector offset = forLen->ExplodeOffset();
|
||||
a = a.Plus(offset);
|
||||
b = b.Plus(offset);
|
||||
}
|
||||
Vector refa;
|
||||
DoEqualLenTicks(canvas, hcs, a, b, gn, &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));
|
||||
if(ShouldDrawExploded()) {
|
||||
Vector offset = SK.GetEntity(ptA)->ExplodeOffset();
|
||||
pt = pt.Plus(offset);
|
||||
closest = closest.Plus(offset);
|
||||
}
|
||||
DoLine(canvas, hcs, pt, closest);
|
||||
Vector 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));
|
||||
if(ShouldDrawExploded()) {
|
||||
Vector offset = pte->ExplodeOffset();
|
||||
pt = pt.Plus(offset);
|
||||
closest = closest.Plus(offset);
|
||||
}
|
||||
DoLine(canvas, hcs, pt, closest);
|
||||
|
||||
Vector ref;
|
||||
|
@ -1075,8 +1193,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
|
|||
goto s;
|
||||
}
|
||||
s:
|
||||
Vector a = SK.GetEntity(ptA)->PointGetNum();
|
||||
Vector b = SK.GetEntity(ptB)->PointGetNum();
|
||||
Vector a = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||
Vector b = SK.GetEntity(ptB)->PointGetDrawNum();
|
||||
|
||||
for(int i = 0; i < 2; i++) {
|
||||
Vector tail = (i == 0) ? a : b;
|
||||
|
@ -1113,8 +1231,8 @@ s:
|
|||
}
|
||||
// For "at midpoint", this branch is always taken.
|
||||
Entity *e = SK.GetEntity(entityA);
|
||||
Vector a = SK.GetEntity(e->point[0])->PointGetNum();
|
||||
Vector b = SK.GetEntity(e->point[1])->PointGetNum();
|
||||
Vector a = SK.GetEntity(e->point[0])->PointGetDrawNum();
|
||||
Vector b = SK.GetEntity(e->point[1])->PointGetDrawNum();
|
||||
Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5));
|
||||
Vector offset = (a.Minus(b)).Cross(n);
|
||||
offset = offset.WithMagnitude(textHeight);
|
||||
|
@ -1138,8 +1256,8 @@ s:
|
|||
r.WithMagnitude(1), u.WithMagnitude(1), hcs);
|
||||
if(refs) refs->push_back(o);
|
||||
} else {
|
||||
Vector a = SK.GetEntity(ptA)->PointGetNum();
|
||||
Vector b = SK.GetEntity(ptB)->PointGetNum();
|
||||
Vector a = SK.GetEntity(ptA)->PointGetDrawNum();
|
||||
Vector b = SK.GetEntity(ptB)->PointGetDrawNum();
|
||||
|
||||
Entity *w = SK.GetEntity(workplane);
|
||||
Vector cu = w->Normal()->NormalU();
|
||||
|
@ -1243,7 +1361,11 @@ bool Constraint::HasLabel() const {
|
|||
case Type::PT_FACE_DISTANCE:
|
||||
case Type::PROJ_PT_DISTANCE:
|
||||
case Type::LENGTH_RATIO:
|
||||
case Type::ARC_ARC_LEN_RATIO:
|
||||
case Type::ARC_LINE_LEN_RATIO:
|
||||
case Type::LENGTH_DIFFERENCE:
|
||||
case Type::ARC_ARC_DIFFERENCE:
|
||||
case Type::ARC_LINE_DIFFERENCE:
|
||||
case Type::DIAMETER:
|
||||
case Type::ANGLE:
|
||||
return true;
|
||||
|
@ -1252,3 +1374,7 @@ bool Constraint::HasLabel() const {
|
|||
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_IN_3D:
|
||||
case Type::POINT_IN_2D:
|
||||
refs->push_back(PointGetNum());
|
||||
refs->push_back(PointGetDrawNum());
|
||||
break;
|
||||
|
||||
case Type::NORMAL_N_COPY:
|
||||
|
@ -103,12 +103,12 @@ void Entity::GetReferencePoints(std::vector<Vector> *refs) {
|
|||
case Type::CUBIC_PERIODIC:
|
||||
case Type::TTF_TEXT:
|
||||
case Type::IMAGE:
|
||||
refs->push_back(SK.GetEntity(point[0])->PointGetNum());
|
||||
refs->push_back(SK.GetEntity(point[0])->PointGetDrawNum());
|
||||
break;
|
||||
|
||||
case Type::LINE_SEGMENT: {
|
||||
Vector a = SK.GetEntity(point[0])->PointGetNum(),
|
||||
b = SK.GetEntity(point[1])->PointGetNum();
|
||||
Vector a = SK.GetEntity(point[0])->PointGetDrawNum(),
|
||||
b = SK.GetEntity(point[1])->PointGetDrawNum();
|
||||
refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5)));
|
||||
break;
|
||||
}
|
||||
|
@ -180,19 +180,43 @@ bool Entity::IsVisible() const {
|
|||
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
|
||||
// draggable with the mouse. We identify the undraggables here
|
||||
bool Entity::CanBeDragged() const {
|
||||
// a numeric copy can not move
|
||||
if(type == Entity::Type::POINT_N_COPY) return false;
|
||||
// these transforms applied zero times can not be moved
|
||||
if(((type == Entity::Type::POINT_N_TRANS) ||
|
||||
(type == Entity::Type::POINT_N_ROT_AA) ||
|
||||
(type == Entity::Type::POINT_N_ROT_AXIS_TRANS))
|
||||
&& (timesApplied == 0)) return false;
|
||||
if(IsPoint()) {
|
||||
if(!PtCanDrag(h))
|
||||
return false;
|
||||
// are we constrained pt-on-point from a previous group?
|
||||
for(const Constraint &cc : SK.constraint) {
|
||||
if(cc.group == group && cc.type == ConstraintBase::Type::POINTS_COINCIDENT) {
|
||||
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
|
||||
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
|
||||
return true;
|
||||
|
@ -451,7 +475,8 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
|
|||
Vector v = topLeft.Minus(botLeft);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&
|
||||
!IsVisible()) return;
|
||||
|
@ -557,16 +602,17 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
pointStroke.unit = Canvas::Unit::PX;
|
||||
Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke);
|
||||
|
||||
Vector p = PointGetDrawNum();
|
||||
if(free) {
|
||||
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
|
||||
analyzeStroke.width = 14.0;
|
||||
analyzeStroke.layer = Canvas::Layer::FRONT;
|
||||
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
|
||||
|
||||
canvas->DrawPoint(PointGetNum(), hcsAnalyze);
|
||||
canvas->DrawPoint(p, hcsAnalyze);
|
||||
}
|
||||
|
||||
canvas->DrawPoint(PointGetNum(), hcsPoint);
|
||||
canvas->DrawPoint(p, hcsPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -621,7 +667,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
tail = camera.projRight.ScaledBy(w/s).Plus(
|
||||
camera.projUp. ScaledBy(h/s)).Minus(camera.offset);
|
||||
} else {
|
||||
tail = SK.GetEntity(point[0])->PointGetNum();
|
||||
tail = SK.GetEntity(point[0])->PointGetDrawNum();
|
||||
}
|
||||
tail = camera.AlignToPixelGrid(tail);
|
||||
|
||||
|
@ -709,8 +755,32 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
case Type::TTF_TEXT: {
|
||||
// Generate the rational polynomial curves, then piecewise linearize
|
||||
// them, and display those.
|
||||
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) {
|
||||
canvas->DrawEdges(*GetOrGenerateEdges(), hcs);
|
||||
// Calculating the draw offset, if necessary.
|
||||
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) {
|
||||
Entity *dist = SK.GetEntity(distance);
|
||||
|
@ -720,12 +790,14 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
|
||||
analyzeStroke.layer = Canvas::Layer::FRONT;
|
||||
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
|
||||
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcsAnalyze)) {
|
||||
canvas->DrawEdges(*GetOrGenerateEdges(), hcsAnalyze);
|
||||
if(!canvas->DrawBeziers(*beziers, hcsAnalyze)) {
|
||||
canvas->DrawEdges(*edges, hcsAnalyze);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offsetBeziers.Clear();
|
||||
offsetEdges.Clear();
|
||||
return;
|
||||
}
|
||||
case Type::IMAGE: {
|
||||
|
@ -757,7 +829,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
Canvas::hFill hf = canvas->GetFill(fill);
|
||||
Vector v[4] = {};
|
||||
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 iv = v[1].Minus(v[0]);
|
||||
|
|
|
@ -26,6 +26,9 @@ bool EntityBase::HasVector() const {
|
|||
}
|
||||
|
||||
ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
|
||||
if(IsFace()) {
|
||||
return FaceGetNormalExprs();
|
||||
}
|
||||
switch(type) {
|
||||
case Type::LINE_SEGMENT:
|
||||
return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus(
|
||||
|
@ -62,6 +65,9 @@ ExprVector EntityBase::VectorGetExprs() const {
|
|||
}
|
||||
|
||||
Vector EntityBase::VectorGetNum() const {
|
||||
if(IsFace()) {
|
||||
return FaceGetNormalNum();
|
||||
}
|
||||
switch(type) {
|
||||
case Type::LINE_SEGMENT:
|
||||
return (SK.GetEntity(point[0])->PointGetNum()).Minus(
|
||||
|
@ -79,6 +85,9 @@ Vector EntityBase::VectorGetNum() const {
|
|||
}
|
||||
|
||||
Vector EntityBase::VectorGetRefPoint() const {
|
||||
if(IsFace()) {
|
||||
return FaceGetPointNum();
|
||||
}
|
||||
switch(type) {
|
||||
case Type::LINE_SEGMENT:
|
||||
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)) {
|
||||
GetEdgesCanvas canvas = {};
|
||||
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(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",
|
||||
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");
|
||||
}
|
||||
|
||||
uint64_t Expr::ParamsUsed() const {
|
||||
uint64_t r = 0;
|
||||
if(op == Op::PARAM) r |= ((uint64_t)1 << (parh.v % 61));
|
||||
if(op == Op::PARAM_PTR) r |= ((uint64_t)1 << (parp->h.v % 61));
|
||||
void Expr::ParamsUsedList(std::vector<hParam> *list) const {
|
||||
if(op == Op::PARAM || op == Op::PARAM_PTR) {
|
||||
// leaf: just add ourselves if we aren't already there
|
||||
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();
|
||||
if(c >= 1) r |= a->ParamsUsed();
|
||||
if(c >= 2) r |= b->ParamsUsed();
|
||||
return r;
|
||||
if(c >= 1) {
|
||||
a->ParamsUsedList(list);
|
||||
if(c >= 2) b->ParamsUsedList(list);
|
||||
}
|
||||
}
|
||||
|
||||
bool Expr::DependsOn(hParam p) const {
|
||||
|
@ -424,6 +433,11 @@ bool Expr::DependsOn(hParam p) const {
|
|||
bool Expr::Tol(double a, double b) {
|
||||
return fabs(a - b) < 0.001;
|
||||
}
|
||||
|
||||
bool Expr::IsZeroConst() const {
|
||||
return op == Op::CONSTANT && EXACT(v == 0.0);
|
||||
}
|
||||
|
||||
Expr *Expr::FoldConstants() {
|
||||
Expr *n = AllocExpr();
|
||||
*n = *this;
|
||||
|
|
|
@ -70,9 +70,10 @@ public:
|
|||
|
||||
Expr *PartialWrt(hParam p) const;
|
||||
double Eval() const;
|
||||
uint64_t ParamsUsed() const;
|
||||
void ParamsUsedList(std::vector<hParam> *list) const;
|
||||
bool DependsOn(hParam p) const;
|
||||
static bool Tol(double a, double b);
|
||||
bool IsZeroConst() const;
|
||||
Expr *FoldConstants();
|
||||
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) {
|
||||
return LinkIDF(filename, le, m, sh);
|
||||
} else if(strcmp(filename.Extension().c_str(), "stl")==0) {
|
||||
return LinkStl(filename, le, m, sh);
|
||||
} else {
|
||||
return LoadEntitiesFromSlvs(filename, le, m, sh);
|
||||
}
|
||||
|
@ -907,6 +909,8 @@ try_again:
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if(g.IsTriangleMeshAssembly())
|
||||
g.forceToMesh = true;
|
||||
} else if(linkMap.count(g.linkFile) == 0) {
|
||||
dbp("Missing file for group: %s", g.name.c_str());
|
||||
// The file was moved; prompt the user for its new location.
|
||||
|
@ -914,7 +918,7 @@ try_again:
|
|||
switch(LocateImportedFile(linkFileRelative, canCancel)) {
|
||||
case Platform::MessageDialog::Response::YES: {
|
||||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||
dialog->AddFilters(Platform::SolveSpaceLinkFileFilters);
|
||||
dialog->ThawChoices(settings, "LinkSketch");
|
||||
dialog->SuggestFilename(linkFileRelative);
|
||||
if(dialog->RunModal()) {
|
||||
|
|
|
@ -224,9 +224,11 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
|
|||
if(PruneGroups(hg))
|
||||
goto pruned;
|
||||
|
||||
int groupRequestIndex = 0;
|
||||
for(auto &req : SK.request) {
|
||||
Request *r = &req;
|
||||
if(r->group != hg) continue;
|
||||
r->groupRequestIndex = groupRequestIndex++;
|
||||
|
||||
r->Generate(&(SK.entity), &(SK.param));
|
||||
}
|
||||
|
@ -548,8 +550,11 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) {
|
|||
}
|
||||
|
||||
SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg, int *rank) {
|
||||
WriteEqSystemForGroup(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);
|
||||
FreeAllTemporary();
|
||||
return result;
|
||||
|
|
|
@ -43,7 +43,7 @@ const MenuEntry Menu[] = {
|
|||
{ 1, N_("&Open..."), Command::OPEN, C|'o', KN, mFile },
|
||||
{ 1, N_("Open &Recent"), Command::OPEN_RECENT, 0, 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, N_("Export &Image..."), Command::EXPORT_IMAGE, 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_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, 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 },
|
||||
{ 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 &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, 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, NULL, Command::NONE, 0, KN, NULL },
|
||||
{ 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, NULL, Command::NONE, 0, KN, NULL },
|
||||
{ 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, N_("Line &Segment"), Command::LINE_SEGMENT, '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, NULL, Command::NONE, 0, KN, NULL },
|
||||
{ 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, 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 },
|
||||
|
||||
{ 0, N_("&Constrain"), Command::NONE, 0, 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_("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_("Other S&upplementary Angle"), Command::OTHER_ANGLE, 'u', 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, NULL, Command::NONE, 0, KN, NULL },
|
||||
{ 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_("Length Ra&tio"), Command::RATIO, 'z', KN, mCon },
|
||||
{ 1, N_("Length Diff&erence"), Command::DIFFERENCE, 'j', KN, mCon },
|
||||
{ 1, N_("E&qual Length / Radius"), Command::EQUAL, 'q', KN, mCon },
|
||||
{ 1, N_("Length / Arc Ra&tio"), Command::RATIO, 'z', 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_("S&ymmetric"), Command::SYMMETRIC, 'y', 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 },
|
||||
{ 1, N_("&Language"), Command::LOCALE, 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__
|
||||
{ 1, N_("&About"), Command::ABOUT, 0, KN, mHelp },
|
||||
#endif
|
||||
|
@ -297,7 +300,7 @@ void GraphicsWindow::PopulateMainMenu() {
|
|||
|
||||
SS.UpdateWindowTitles();
|
||||
PopulateMainMenu();
|
||||
EnsureValidActives();
|
||||
SS.GW.EnsureValidActives();
|
||||
});
|
||||
}
|
||||
} else if(Menu[i].fn == NULL) {
|
||||
|
@ -317,6 +320,8 @@ void GraphicsWindow::PopulateMainMenu() {
|
|||
dimSolidModelMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
|
||||
perspectiveProjMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::EXPLODE_SKETCH) {
|
||||
explodeMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
|
||||
showToolbarMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::SHOW_TEXT_WND) {
|
||||
|
@ -329,6 +334,8 @@ void GraphicsWindow::PopulateMainMenu() {
|
|||
unitsMetersMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::UNITS_INCHES) {
|
||||
unitsInchesMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::UNITS_FEET_INCHES) {
|
||||
unitsFeetInchesMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::SEL_WORKPLANE) {
|
||||
inWorkplaneMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::FREE_IN_3D) {
|
||||
|
@ -370,7 +377,7 @@ static void PopulateMenuWithPathnames(Platform::MenuRef menu,
|
|||
void GraphicsWindow::PopulateRecentFiles() {
|
||||
PopulateMenuWithPathnames(openRecentMenu, SS.recentFiles, [](const Platform::Path &path) {
|
||||
// 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);
|
||||
if(!SS.OkayToStartNewFile()) return;
|
||||
SS.Load(pathCopy);
|
||||
|
@ -402,11 +409,13 @@ void GraphicsWindow::Init() {
|
|||
showNormals = true;
|
||||
showPoints = true;
|
||||
showConstruction = true;
|
||||
showConstraints = true;
|
||||
showConstraints = ShowConstraintMode::SCM_SHOW_ALL;
|
||||
showShaded = true;
|
||||
showEdges = true;
|
||||
showMesh = false;
|
||||
showOutlines = false;
|
||||
showFacesDrawing = false;
|
||||
showFacesNonDrawing = true;
|
||||
drawOccludedAs = DrawOccludedAs::INVISIBLE;
|
||||
|
||||
showTextWindow = true;
|
||||
|
@ -422,8 +431,13 @@ void GraphicsWindow::Init() {
|
|||
using namespace std::placeholders;
|
||||
// Do this first, so that if it causes an onRender event we don't try to paint without
|
||||
// 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->onContextLost = [&] {
|
||||
canvas = NULL;
|
||||
persistentCanvas = NULL;
|
||||
persistentDirty = true;
|
||||
};
|
||||
window->onRender = std::bind(&GraphicsWindow::Paint, this);
|
||||
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, 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.
|
||||
int64_t t0 = GetMilliseconds();
|
||||
int32_t dt = (mp < 0.01 && mo < 10) ? (-20) :
|
||||
(int32_t)(100 + 1000*mp + 0.4*mo);
|
||||
// Don't ever animate for longer than 2000 ms; we can get absurdly
|
||||
(int32_t)(100 + 600*mp + 0.4*mo);
|
||||
// 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,
|
||||
// and then zooms in again.
|
||||
if(dt > 2000) dt = 2000;
|
||||
if(dt > 800) dt = 800;
|
||||
Quaternion dq = quatf.Times(quat0.Inverse());
|
||||
|
||||
if(!animateTimer) {
|
||||
|
@ -703,16 +717,47 @@ double GraphicsWindow::ZoomToFit(const Camera &camera,
|
|||
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) {
|
||||
switch(id) {
|
||||
case Command::ZOOM_IN:
|
||||
SS.GW.scale *= 1.2;
|
||||
SS.ScheduleShowTW();
|
||||
SS.GW.ZoomToMouse(1);
|
||||
break;
|
||||
|
||||
case Command::ZOOM_OUT:
|
||||
SS.GW.scale /= 1.2;
|
||||
SS.ScheduleShowTW();
|
||||
SS.GW.ZoomToMouse(-1);
|
||||
break;
|
||||
|
||||
case Command::ZOOM_TO_FIT:
|
||||
|
@ -748,6 +793,12 @@ void GraphicsWindow::MenuView(Command id) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Command::EXPLODE_SKETCH:
|
||||
SS.explode = !SS.explode;
|
||||
SS.GW.EnsureValidActives();
|
||||
SS.MarkGroupDirty(SS.GW.activeGroup, true);
|
||||
break;
|
||||
|
||||
case Command::ONTO_WORKPLANE:
|
||||
if(SS.GW.LockedInWorkplane()) {
|
||||
SS.GW.AnimateOntoWorkplane();
|
||||
|
@ -766,13 +817,18 @@ void GraphicsWindow::MenuView(Command id) {
|
|||
Quaternion quatf = quat0;
|
||||
double dmin = 1e10;
|
||||
|
||||
// There are 24 possible views; 3*2*2*2
|
||||
int i, j, negi, negj;
|
||||
for(i = 0; i < 3; i++) {
|
||||
for(j = 0; j < 3; j++) {
|
||||
// There are 24 possible views (3*2*2*2), if all are
|
||||
// allowed. If the user is in turn-table mode, the
|
||||
// isometric view must have the z-axis facing up, leaving
|
||||
// 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;
|
||||
for(negi = 0; negi < 2; negi++) {
|
||||
for(negj = 0; negj < 2; negj++) {
|
||||
if(require_turntable && (j!=2)) continue;
|
||||
for(int negi = 0; negi < 2; negi++) {
|
||||
for(int negj = 0; negj < 2; negj++) {
|
||||
Vector ou = ortho[i], ov = ortho[j];
|
||||
if(negi) ou = ou.ScaledBy(-1);
|
||||
if(negj) ov = ov.ScaledBy(-1);
|
||||
|
@ -841,6 +897,12 @@ void GraphicsWindow::MenuView(Command id) {
|
|||
SS.GW.EnsureValidActives();
|
||||
break;
|
||||
|
||||
case Command::UNITS_FEET_INCHES:
|
||||
SS.viewUnits = Unit::FEET_INCHES;
|
||||
SS.ScheduleShowTW();
|
||||
SS.GW.EnsureValidActives();
|
||||
break;
|
||||
|
||||
case Command::UNITS_MM:
|
||||
SS.viewUnits = Unit::MM;
|
||||
SS.ScheduleShowTW();
|
||||
|
@ -923,6 +985,7 @@ void GraphicsWindow::EnsureValidActives() {
|
|||
case Unit::MM:
|
||||
case Unit::METERS:
|
||||
case Unit::INCHES:
|
||||
case Unit::FEET_INCHES:
|
||||
break;
|
||||
default:
|
||||
SS.viewUnits = Unit::MM;
|
||||
|
@ -931,6 +994,7 @@ void GraphicsWindow::EnsureValidActives() {
|
|||
unitsMmMenuItem->SetActive(SS.viewUnits == Unit::MM);
|
||||
unitsMetersMenuItem->SetActive(SS.viewUnits == Unit::METERS);
|
||||
unitsInchesMenuItem->SetActive(SS.viewUnits == Unit::INCHES);
|
||||
unitsFeetInchesMenuItem->SetActive(SS.viewUnits == Unit::FEET_INCHES);
|
||||
|
||||
if(SS.TW.window) SS.TW.window->SetVisible(SS.GW.showTextWindow);
|
||||
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
|
||||
|
@ -938,6 +1002,7 @@ void GraphicsWindow::EnsureValidActives() {
|
|||
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
|
||||
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
|
||||
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
|
||||
explodeMenuItem->SetActive(SS.explode);
|
||||
showToolbarMenuItem->SetActive(SS.showToolbar);
|
||||
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.TW.HideEditControl();
|
||||
SS.nakedEdges.Clear();
|
||||
|
@ -1366,6 +1441,14 @@ void GraphicsWindow::ToggleBool(bool *v) {
|
|||
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);
|
||||
SS.ScheduleShowTW();
|
||||
}
|
||||
|
|
|
@ -136,14 +136,30 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
|
|||
g.predef.negateV = wrkplg->predef.negateV;
|
||||
} else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
|
||||
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 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 {
|
||||
Error(_("Bad selection for new sketch in workplane. This "
|
||||
"group can be created with:\n\n"
|
||||
" * a point (through the point, orthogonal to coordinate axes)\n"
|
||||
" * 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"));
|
||||
return;
|
||||
}
|
||||
|
@ -392,7 +408,13 @@ bool Group::IsForcedToMeshBySource() 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() {
|
||||
|
@ -404,11 +426,10 @@ std::string Group::DescriptionString() {
|
|||
}
|
||||
|
||||
void Group::Activate() {
|
||||
if(type == Type::EXTRUDE || type == Type::LINKED || type == Type::LATHE ||
|
||||
type == Type::REVOLVE || type == Type::HELIX || type == Type::TRANSLATE || type == Type::ROTATE) {
|
||||
SS.GW.showFaces = true;
|
||||
if(type == Type::DRAWING_WORKPLANE || type == Type::DRAWING_3D) {
|
||||
SS.GW.showFaces = SS.GW.showFacesDrawing;
|
||||
} else {
|
||||
SS.GW.showFaces = false;
|
||||
SS.GW.showFaces = SS.GW.showFacesNonDrawing;
|
||||
}
|
||||
SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
|
||||
SS.ScheduleShowTW();
|
||||
|
@ -443,11 +464,14 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
|
|||
} else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
|
||||
// Already given, numerically.
|
||||
q = predef.q;
|
||||
} else if(subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) {
|
||||
q = SK.GetEntity(predef.entityB)->NormalGetNum();
|
||||
} else ssassert(false, "Unexpected workplane subtype");
|
||||
|
||||
Entity normal = {};
|
||||
normal.type = Entity::Type::NORMAL_N_COPY;
|
||||
normal.numNormal = q;
|
||||
|
||||
normal.point[0] = h.entity(2);
|
||||
normal.group = h;
|
||||
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);
|
||||
#undef EC
|
||||
#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) {
|
||||
if(predef.entityB != Entity::FREE_IN_3D) {
|
||||
// The extrusion path is locked along a line, normal to the
|
||||
|
@ -1150,6 +1180,11 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
|||
break;
|
||||
|
||||
default: {
|
||||
if((Entity::Type::IMAGE == ep->type) && (true == ep->construction)) {
|
||||
// Do not copy image entities if they are construction.
|
||||
return;
|
||||
}
|
||||
|
||||
int i, points;
|
||||
bool hasNormal, hasDistance;
|
||||
EntReqTable::GetEntityInfo(ep->type, ep->extraPoints,
|
||||
|
@ -1172,3 +1207,6 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
|
|||
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;
|
||||
SS.GW.GroupSelection();
|
||||
auto const &gs = SS.GW.gs;
|
||||
if(gs.faces > 0) faces.push_back(gs.face[0].v);
|
||||
if(gs.faces > 1) faces.push_back(gs.face[1].v);
|
||||
// See also GraphicsWindow::MakeSelected "if(c >= MAX_SELECTABLE_FACES)"
|
||||
// and GraphicsWindow::GroupSelection "if(e->IsFace())"
|
||||
for(auto &fc : gs.face) {
|
||||
faces.push_back(fc.v);
|
||||
}
|
||||
canvas->DrawFaces(displayMesh, faces, hcf);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -53,8 +53,22 @@ static std::vector <std::string> splitString(const std::string line) {
|
|||
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
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -291,9 +305,9 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
|
|||
namespace SolveSpace {
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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) {
|
||||
|
@ -332,7 +346,8 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
|||
|
||||
double board_thickness = 10.0;
|
||||
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));
|
||||
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 x = stof(values[1]);
|
||||
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.
|
||||
if((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)
|
||||
|| (values[5].compare(0,3,"MTG") == 0)) {
|
||||
if(((d > 1.7) || (values[5].compare(0,3,"PIN") == 0)
|
||||
|| (values[5].compare(0,3,"MTG") == 0)) && !duplicate) {
|
||||
// create the entity
|
||||
Vector cent = Vector::From(x,y,0.0);
|
||||
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_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; 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_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_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_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_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; 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 np = ab.Cross(bc);
|
||||
if(np.Magnitude() < 1e-10) {
|
||||
// ugh; gl sometimes tesselates to collinear triangles
|
||||
// ugh; gl sometimes tessellates to collinear triangles
|
||||
return;
|
||||
}
|
||||
if(np.Dot(n) > 0) {
|
||||
|
|
|
@ -103,7 +103,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
|||
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)) {
|
||||
hover.Clear();
|
||||
return;
|
||||
|
@ -136,8 +139,9 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
|||
double dy = (y - orig.mouse.y) / scale;
|
||||
|
||||
if(!(shiftDown || ctrlDown)) {
|
||||
double s = 0.3*(PI/180)*scale; // degrees per pixel
|
||||
if(SS.turntableNav) { // lock the Z to vertical
|
||||
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
|
||||
projRight = orig.projRight.RotatedAbout(Vector::From(0, 0, 1), -s * dx);
|
||||
projUp = orig.projUp.RotatedAbout(
|
||||
Vector::From(orig.projRight.x, orig.projRight.y, orig.projRight.y), s * dy);
|
||||
|
@ -821,7 +825,7 @@ Vector GraphicsWindow::SnapToEntityByScreenPoint(Point2d pp, hEntity he) {
|
|||
SEdgeList *edges = e->GetOrGenerateEdges();
|
||||
|
||||
double minD = -1.0f;
|
||||
double k;
|
||||
double k = 0.0;
|
||||
const SEdge *edge = NULL;
|
||||
for(const auto &e : edges->l) {
|
||||
Point2d p0 = ProjectPoint(e.a);
|
||||
|
@ -911,7 +915,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
|||
break;
|
||||
|
||||
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;
|
||||
|
||||
case MouseEvent::Type::LEAVE:
|
||||
|
@ -1134,6 +1138,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct
|
|||
AddToPending(hr);
|
||||
Request *r = SK.GetRequest(hr);
|
||||
r->file = pending.filename;
|
||||
r->construction = true;
|
||||
|
||||
for(int i = 1; i <= 4; i++) {
|
||||
SK.GetEntity(hr.entity(i))->PointForceTo(v);
|
||||
|
@ -1373,12 +1378,12 @@ void GraphicsWindow::EditConstraint(hConstraint constraint) {
|
|||
value /= 2;
|
||||
|
||||
// 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);
|
||||
} else if(c->type == Constraint::Type::ANGLE) {
|
||||
editValue = SS.DegreeToString(value);
|
||||
} else {
|
||||
editValue = SS.MmToString(value);
|
||||
editValue = SS.MmToString(value, true);
|
||||
value /= SS.MmPerUnit();
|
||||
}
|
||||
// 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_FACE_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
|
||||
// distance internally. To flip the sign, the user enters a
|
||||
// negative distance.
|
||||
|
@ -1448,6 +1455,8 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
|
|||
}
|
||||
case Constraint::Type::ANGLE:
|
||||
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
|
||||
// they're always positive
|
||||
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) {
|
||||
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).
|
||||
void GraphicsWindow::MouseScroll(double zoomMultiplyer) {
|
||||
// To support smooth scrolling where scroll wheel events come in increments
|
||||
// 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
|
||||
// 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
|
||||
|
@ -1489,21 +1491,7 @@ void GraphicsWindow::MouseScroll(double x, double y, double delta) {
|
|||
// while
|
||||
// scale * a * b != scale * (a+b)
|
||||
// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x
|
||||
scale *= exp(0.1823216 * delta);
|
||||
|
||||
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();
|
||||
ZoomToMouse(zoomMultiplyer);
|
||||
}
|
||||
|
||||
void GraphicsWindow::MouseLeave() {
|
||||
|
|
|
@ -86,8 +86,10 @@ std::vector<FileFilter> SolveSpaceModelFileFilters = {
|
|||
};
|
||||
|
||||
std::vector<FileFilter> SolveSpaceLinkFileFilters = {
|
||||
{ CN_("file-type", "ALL"), { "slvs", "emn", "stl" } },
|
||||
{ CN_("file-type", "SolveSpace models"), { "slvs" } },
|
||||
{ CN_("file-type", "IDF circuit board"), { "emn" } },
|
||||
{ CN_("file-type", "STL triangle mesh"), { "stl" } },
|
||||
};
|
||||
|
||||
std::vector<FileFilter> RasterFileFilters = {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef SOLVESPACE_GUI_H
|
||||
#define SOLVESPACE_GUI_H
|
||||
|
||||
namespace SolveSpace {
|
||||
class RgbaColor;
|
||||
|
||||
namespace Platform {
|
||||
|
@ -220,6 +221,7 @@ public:
|
|||
std::function<bool(KeyboardEvent)> onKeyboardEvent;
|
||||
std::function<void(std::string)> onEditingDone;
|
||||
std::function<void(double)> onScrollbarAdjusted;
|
||||
std::function<void()> onContextLost;
|
||||
std::function<void()> onRender;
|
||||
|
||||
virtual ~Window() = default;
|
||||
|
@ -228,7 +230,7 @@ public:
|
|||
virtual double GetPixelDensity() = 0;
|
||||
// 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.
|
||||
virtual int GetDevicePixelRatio() = 0;
|
||||
virtual double GetDevicePixelRatio() = 0;
|
||||
// Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio.
|
||||
virtual double GetDeviceFontScale() {
|
||||
return GetPixelDensity() / GetDevicePixelRatio() / 96.0;
|
||||
|
@ -386,5 +388,6 @@ void ExitGui();
|
|||
void ClearGui();
|
||||
|
||||
}
|
||||
} // namespace SolveSpace
|
||||
|
||||
#endif
|
||||
|
|
|
@ -33,7 +33,13 @@
|
|||
|
||||
#if defined(HAVE_SPACEWARE)
|
||||
# include <spnav.h>
|
||||
# include <gdk/gdkx.h>
|
||||
# include <gdk/gdk.h>
|
||||
# if defined(GDK_WINDOWING_X11)
|
||||
# include <gdk/gdkx.h>
|
||||
# endif
|
||||
# if defined(GDK_WINDOWING_WAYLAND)
|
||||
# include <gdk/gdkwayland.h>
|
||||
# endif
|
||||
# if GTK_CHECK_VERSION(3, 20, 0)
|
||||
# include <gdkmm/seat.h>
|
||||
# else
|
||||
|
@ -511,8 +517,12 @@ protected:
|
|||
}
|
||||
|
||||
bool on_motion_notify_event(GdkEventMotion *gdk_event) override {
|
||||
if(process_pointer_event(MouseEvent::Type::MOTION,
|
||||
gdk_event->x, gdk_event->y, gdk_event->state))
|
||||
double x,y;
|
||||
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 Gtk::GLArea::on_motion_notify_event(gdk_event);
|
||||
|
@ -520,51 +530,79 @@ protected:
|
|||
|
||||
bool on_button_press_event(GdkEventButton *gdk_event) override {
|
||||
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;
|
||||
} else if(gdk_event->type == GDK_2BUTTON_PRESS) {
|
||||
} else if(gdk_type == GDK_2BUTTON_PRESS) {
|
||||
type = MouseEvent::Type::DBL_PRESS;
|
||||
} else {
|
||||
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,
|
||||
gdk_event->state, gdk_event->button))
|
||||
if(process_pointer_event(type, x, y, state, button))
|
||||
return true;
|
||||
|
||||
return Gtk::GLArea::on_button_press_event(gdk_event);
|
||||
}
|
||||
|
||||
bool on_button_release_event(GdkEventButton *gdk_event) override {
|
||||
if(process_pointer_event(MouseEvent::Type::RELEASE,
|
||||
gdk_event->x, gdk_event->y,
|
||||
gdk_event->state, gdk_event->button))
|
||||
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(MouseEvent::Type::RELEASE, x, y, state, button))
|
||||
return true;
|
||||
|
||||
return Gtk::GLArea::on_button_release_event(gdk_event);
|
||||
}
|
||||
|
||||
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;
|
||||
if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) {
|
||||
if(dy < 0 || dir == GDK_SCROLL_UP) {
|
||||
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;
|
||||
} else {
|
||||
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,
|
||||
gdk_event->x, gdk_event->y,
|
||||
gdk_event->state, 0, delta))
|
||||
x, y, state, 0, delta))
|
||||
return true;
|
||||
|
||||
return Gtk::GLArea::on_scroll_event(gdk_event);
|
||||
}
|
||||
|
||||
bool on_leave_notify_event(GdkEventCrossing *gdk_event) override {
|
||||
if(process_pointer_event(MouseEvent::Type::LEAVE,
|
||||
gdk_event->x, gdk_event->y, gdk_event->state))
|
||||
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::LEAVE, x, y, state))
|
||||
return true;
|
||||
|
||||
return Gtk::GLArea::on_leave_notify_event(gdk_event);
|
||||
|
@ -574,22 +612,28 @@ protected:
|
|||
KeyboardEvent event = {};
|
||||
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);
|
||||
if((gdk_event->state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
|
||||
if((state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.shiftDown = (gdk_event->state & GDK_SHIFT_MASK) != 0;
|
||||
event.controlDown = (gdk_event->state & GDK_CONTROL_MASK) != 0;
|
||||
event.shiftDown = (state & GDK_SHIFT_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) {
|
||||
event.key = KeyboardEvent::Key::CHARACTER;
|
||||
event.chr = chr;
|
||||
} else if(gdk_event->keyval >= GDK_KEY_F1 &&
|
||||
gdk_event->keyval <= GDK_KEY_F12) {
|
||||
} else if(keyval >= GDK_KEY_F1 &&
|
||||
keyval <= GDK_KEY_F12) {
|
||||
event.key = KeyboardEvent::Key::FUNCTION;
|
||||
event.num = gdk_event->keyval - GDK_KEY_F1 + 1;
|
||||
event.num = keyval - GDK_KEY_F1 + 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -691,8 +735,11 @@ public:
|
|||
|
||||
protected:
|
||||
bool on_key_press_event(GdkEventKey *gdk_event) override {
|
||||
guint keyval;
|
||||
gdk_event_get_keyval((GdkEvent*)gdk_event, &keyval);
|
||||
|
||||
if(is_editing()) {
|
||||
if(gdk_event->keyval == GDK_KEY_Escape) {
|
||||
if(keyval == GDK_KEY_Escape) {
|
||||
return _gl_widget.event((GdkEvent *)gdk_event);
|
||||
} else {
|
||||
_entry.event((GdkEvent *)gdk_event);
|
||||
|
@ -835,7 +882,11 @@ protected:
|
|||
}
|
||||
|
||||
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) {
|
||||
_receiver->onFullScreen(_is_fullscreen);
|
||||
}
|
||||
|
@ -883,7 +934,7 @@ public:
|
|||
return gtkWindow.get_screen()->get_resolution();
|
||||
}
|
||||
|
||||
int GetDevicePixelRatio() override {
|
||||
double GetDevicePixelRatio() override {
|
||||
return gtkWindow.get_scale_factor();
|
||||
}
|
||||
|
||||
|
@ -1047,7 +1098,7 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||
void Open3DConnexion() {}
|
||||
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) {
|
||||
switch(spnavEvent.type) {
|
||||
case SPNAV_EVENT_MOTION: {
|
||||
|
@ -1129,17 +1180,26 @@ void Request3DConnexionEventsForWindow(WindowRef window) {
|
|||
std::static_pointer_cast<WindowImplGtk>(window);
|
||||
|
||||
Glib::RefPtr<Gdk::Window> gdkWindow = windowImpl->gtkWindow.get_window();
|
||||
if(!GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) {
|
||||
return;
|
||||
#if defined(GDK_WINDOWING_X11)
|
||||
if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) {
|
||||
if(spnav_x11_open(gdk_x11_get_default_xdisplay(),
|
||||
gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) {
|
||||
gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get());
|
||||
} else if(spnav_open() != -1) {
|
||||
g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN,
|
||||
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
|
||||
|
||||
if(spnav_x11_open(gdk_x11_get_default_xdisplay(),
|
||||
gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) {
|
||||
gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get());
|
||||
} else if(spnav_open() != -1) {
|
||||
g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN,
|
||||
ConsumeSpnavQueue, windowImpl.get());
|
||||
}
|
||||
}
|
||||
#else
|
||||
void Request3DConnexionEventsForWindow(WindowRef window) {}
|
||||
|
@ -1383,6 +1443,9 @@ public:
|
|||
gtkDialog.add_button(isSave ? C_("button", "_Save")
|
||||
: C_("button", "_Open"), Gtk::RESPONSE_OK);
|
||||
gtkDialog.set_default_response(Gtk::RESPONSE_OK);
|
||||
if(isSave) {
|
||||
gtkDialog.set_do_overwrite_confirmation(true);
|
||||
}
|
||||
InitFileChooser(gtkDialog);
|
||||
}
|
||||
|
||||
|
@ -1416,6 +1479,9 @@ public:
|
|||
isSave ? C_("button", "_Save")
|
||||
: C_("button", "_Open"),
|
||||
C_("button", "_Cancel"));
|
||||
if(isSave) {
|
||||
gtkNative->set_do_overwrite_confirmation(true);
|
||||
}
|
||||
// Seriously, GTK?!
|
||||
InitFileChooser(*gtkNative.operator->());
|
||||
}
|
||||
|
@ -1478,7 +1544,8 @@ std::vector<Platform::Path> GetFontFiles() {
|
|||
}
|
||||
|
||||
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;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -286,7 +286,8 @@ public:
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -358,18 +359,27 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
- (void)didEdit:(NSString *)text;
|
||||
|
||||
@property double scrollerMin;
|
||||
@property double scrollerMax;
|
||||
@property double scrollerSize;
|
||||
@property double pageSize;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SSView
|
||||
{
|
||||
NSTrackingArea *trackingArea;
|
||||
NSTextField *editor;
|
||||
double magnificationGestureCurrentZ;
|
||||
double rotationGestureCurrent;
|
||||
Point2d trackpadPositionShift;
|
||||
bool inTrackpadScrollGesture;
|
||||
int activeTrackpadTouches;
|
||||
bool scrollFromTrackpadTouch;
|
||||
Platform::Window::Kind kind;
|
||||
}
|
||||
|
||||
@synthesize acceptsFirstResponder;
|
||||
|
||||
- (id)initWithFrame:(NSRect)frameRect {
|
||||
- (id)initWithKind:(Platform::Window::Kind)aKind {
|
||||
NSOpenGLPixelFormatAttribute attrs[] = {
|
||||
NSOpenGLPFADoubleBuffer,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
|
@ -377,7 +387,7 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
0
|
||||
};
|
||||
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.wantsLayer = YES;
|
||||
editor = [[NSTextField alloc] init];
|
||||
|
@ -387,6 +397,21 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
editor.bezeled = NO;
|
||||
editor.target = self;
|
||||
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;
|
||||
}
|
||||
|
@ -427,9 +452,9 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
- (Platform::MouseEvent)convertMouseEvent:(NSEvent *)nsEvent {
|
||||
Platform::MouseEvent event = {};
|
||||
|
||||
NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:self];
|
||||
NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:nil];
|
||||
event.x = nsPoint.x;
|
||||
event.y = self.bounds.size.height - nsPoint.y;
|
||||
event.y = nsPoint.y;
|
||||
|
||||
NSUInteger nsFlags = [nsEvent modifierFlags];
|
||||
if(nsFlags & NSEventModifierFlagShift) event.shiftDown = true;
|
||||
|
@ -553,14 +578,78 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
using Platform::MouseEvent;
|
||||
|
||||
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;
|
||||
|
||||
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
|
||||
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 {
|
||||
|
@ -638,6 +727,50 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
[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;
|
||||
|
||||
- (void)startEditing:(NSString *)text at:(NSPoint)origin withHeight:(double)fontHeight
|
||||
|
@ -698,11 +831,27 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
}
|
||||
|
||||
@synthesize scrollerMin;
|
||||
@synthesize scrollerMax;
|
||||
@synthesize scrollerSize;
|
||||
@synthesize pageSize;
|
||||
|
||||
- (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) {
|
||||
double pos = scrollerMin + [sender doubleValue] * (scrollerMax - scrollerMin);
|
||||
receiver->onScrollbarAdjusted(pos);
|
||||
}
|
||||
}
|
||||
|
@ -769,7 +918,7 @@ public:
|
|||
NSString *nsToolTip;
|
||||
|
||||
WindowImplCocoa(Window::Kind kind, std::shared_ptr<WindowImplCocoa> parentWindow) {
|
||||
ssView = [[SSView alloc] init];
|
||||
ssView = [[SSView alloc] initWithKind:kind];
|
||||
ssView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
ssView.receiver = this;
|
||||
|
||||
|
@ -838,10 +987,10 @@ public:
|
|||
return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f;
|
||||
}
|
||||
|
||||
int GetDevicePixelRatio() override {
|
||||
double GetDevicePixelRatio() override {
|
||||
NSSize unitSize = { 1.0f, 0.0f };
|
||||
unitSize = [ssView convertSizeToBacking:unitSize];
|
||||
return (int)unitSize.width;
|
||||
return unitSize.width;
|
||||
}
|
||||
|
||||
bool IsVisible() override {
|
||||
|
@ -962,21 +1111,22 @@ public:
|
|||
|
||||
void ConfigureScrollbar(double min, double max, double pageSize) override {
|
||||
ssView.scrollerMin = min;
|
||||
ssView.scrollerMax = max - pageSize;
|
||||
[nsScroller setKnobProportion:(pageSize / (ssView.scrollerMax - ssView.scrollerMin))];
|
||||
ssView.scrollerSize = max + 1 - min;
|
||||
ssView.pageSize = pageSize;
|
||||
nsScroller.knobProportion = pageSize / ssView.scrollerSize;
|
||||
nsScroller.hidden = pageSize >= ssView.scrollerSize;
|
||||
}
|
||||
|
||||
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 +
|
||||
[nsScroller doubleValue] * (ssView.scrollerMax - ssView.scrollerMin);
|
||||
nsScroller.doubleValue * (ssView.scrollerSize - ssView.pageSize);
|
||||
}
|
||||
|
||||
void SetScrollbarPosition(double pos) override {
|
||||
if(pos > ssView.scrollerMax)
|
||||
pos = ssView.scrollerMax;
|
||||
if(GetScrollbarPosition() == pos)
|
||||
return;
|
||||
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
|
||||
nsScroller.doubleValue = (pos - ssView.scrollerMin) / ( ssView.scrollerSize - ssView.pageSize);
|
||||
}
|
||||
|
||||
void Invalidate() override {
|
||||
|
@ -1426,9 +1576,22 @@ void OpenInBrowser(const std::string &url) {
|
|||
- (IBAction)preferences:(id)sender;
|
||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
||||
|
||||
@property BOOL exiting;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SSApplicationDelegate
|
||||
|
||||
@synthesize exiting;
|
||||
|
||||
- (id)init {
|
||||
if (self = [super init]) {
|
||||
self.exiting = false;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (IBAction)preferences:(id)sender {
|
||||
if (!SS.GW.showTextWindow) {
|
||||
SolveSpace::SS.GW.MenuView(SolveSpace::Command::SHOW_TEXT_WND);
|
||||
|
@ -1443,12 +1606,27 @@ void OpenInBrowser(const std::string &url) {
|
|||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
[[[NSApp mainWindow] delegate] windowShouldClose:[NSApp mainWindow]];
|
||||
return NSTerminateCancel;
|
||||
if(!SS.unsaved) {
|
||||
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 {
|
||||
SolveSpace::SS.MenuFile(SolveSpace::Command::EXIT);
|
||||
[NSApp replyToApplicationShouldTerminate:SS.OkayToStartNewFile()];
|
||||
}
|
||||
@end
|
||||
|
||||
|
@ -1469,6 +1647,14 @@ std::vector<std::string> InitGui(int argc, char **argv) {
|
|||
ssDelegate = [[SSApplicationDelegate alloc] init];
|
||||
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];
|
||||
|
||||
NSArray *languages = NSLocale.preferredLanguages;
|
||||
|
@ -1487,8 +1673,10 @@ void RunGui() {
|
|||
}
|
||||
|
||||
void ExitGui() {
|
||||
[NSApp setDelegate:nil];
|
||||
[NSApp terminate:nil];
|
||||
if(!ssDelegate.exiting) {
|
||||
ssDelegate.exiting = true;
|
||||
[NSApp terminate:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void ClearGui() {}
|
||||
|
|
|
@ -793,7 +793,7 @@ public:
|
|||
break;
|
||||
|
||||
case WM_SIZING: {
|
||||
int pixelRatio = window->GetDevicePixelRatio();
|
||||
double pixelRatio = window->GetDevicePixelRatio();
|
||||
|
||||
RECT rcw, rcc;
|
||||
sscheck(GetWindowRect(window->hWindow, &rcw));
|
||||
|
@ -806,10 +806,10 @@ public:
|
|||
int adjHeight = rc->bottom - rc->top;
|
||||
|
||||
adjWidth -= nonClientWidth;
|
||||
adjWidth = max(window->minWidth * pixelRatio, adjWidth);
|
||||
adjWidth += nonClientWidth;
|
||||
adjWidth = max((int)(window->minWidth * pixelRatio), adjWidth);
|
||||
adjWidth += nonClientWidth;
|
||||
adjHeight -= nonClientHeight;
|
||||
adjHeight = max(window->minHeight * pixelRatio, adjHeight);
|
||||
adjHeight = max((int)(window->minHeight * pixelRatio), adjHeight);
|
||||
adjHeight += nonClientHeight;
|
||||
switch(wParam) {
|
||||
case WMSZ_RIGHT:
|
||||
|
@ -868,7 +868,7 @@ public:
|
|||
case WM_MOUSEMOVE:
|
||||
case WM_MOUSEWHEEL:
|
||||
case WM_MOUSELEAVE: {
|
||||
int pixelRatio = window->GetDevicePixelRatio();
|
||||
double pixelRatio = window->GetDevicePixelRatio();
|
||||
|
||||
MouseEvent event = {};
|
||||
event.x = GET_X_LPARAM(lParam) / pixelRatio;
|
||||
|
@ -941,7 +941,7 @@ public:
|
|||
event.y = pt.y / pixelRatio;
|
||||
|
||||
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;
|
||||
|
||||
case WM_MOUSELEAVE:
|
||||
|
@ -1109,10 +1109,10 @@ public:
|
|||
return (double)dpi;
|
||||
}
|
||||
|
||||
int GetDevicePixelRatio() override {
|
||||
double GetDevicePixelRatio() override {
|
||||
UINT dpi;
|
||||
sscheck(dpi = ssGetDpiForWindow(hWindow));
|
||||
return dpi / USER_DEFAULT_SCREEN_DPI;
|
||||
return (double)dpi / USER_DEFAULT_SCREEN_DPI;
|
||||
}
|
||||
|
||||
bool IsVisible() override {
|
||||
|
@ -1177,27 +1177,27 @@ public:
|
|||
}
|
||||
|
||||
void GetContentSize(double *width, double *height) override {
|
||||
int pixelRatio = GetDevicePixelRatio();
|
||||
double pixelRatio = GetDevicePixelRatio();
|
||||
|
||||
RECT rc;
|
||||
sscheck(GetClientRect(hWindow, &rc));
|
||||
*width = (rc.right - rc.left) / pixelRatio;
|
||||
*height = (rc.bottom - rc.top) / pixelRatio;
|
||||
*width = (rc.right - rc.left) / pixelRatio;
|
||||
*height = (rc.bottom - rc.top) / pixelRatio;
|
||||
}
|
||||
|
||||
void SetMinContentSize(double width, double height) {
|
||||
minWidth = (int)width;
|
||||
minHeight = (int)height;
|
||||
|
||||
int pixelRatio = GetDevicePixelRatio();
|
||||
double pixelRatio = GetDevicePixelRatio();
|
||||
|
||||
RECT rc;
|
||||
sscheck(GetClientRect(hWindow, &rc));
|
||||
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) {
|
||||
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));
|
||||
|
||||
// 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;
|
||||
|
||||
RECT mrc = mi.rcMonitor;
|
||||
|
@ -1270,7 +1270,7 @@ public:
|
|||
tooltipText = newText;
|
||||
|
||||
if(!newText.empty()) {
|
||||
int pixelRatio = GetDevicePixelRatio();
|
||||
double pixelRatio = GetDevicePixelRatio();
|
||||
RECT toolRect;
|
||||
toolRect.left = (int)(x * pixelRatio);
|
||||
toolRect.top = (int)(y * pixelRatio);
|
||||
|
@ -1301,9 +1301,9 @@ public:
|
|||
bool isMonospace, const std::string &text) override {
|
||||
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,
|
||||
DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial");
|
||||
if(hFont == NULL) {
|
||||
|
@ -1324,12 +1324,12 @@ public:
|
|||
sscheck(ReleaseDC(hEditor, hDc));
|
||||
|
||||
RECT rc;
|
||||
rc.left = (LONG)x * pixelRatio;
|
||||
rc.top = (LONG)y * pixelRatio - tm.tmAscent;
|
||||
rc.left = (LONG)(x * pixelRatio);
|
||||
rc.top = (LONG)(y * pixelRatio) - tm.tmAscent;
|
||||
// Add one extra char width to avoid scrolling.
|
||||
rc.right = (LONG)x * pixelRatio +
|
||||
std::max((LONG)minWidth * pixelRatio, ts.cx + tm.tmAveCharWidth);
|
||||
rc.bottom = (LONG)y * pixelRatio + tm.tmDescent;
|
||||
rc.right = (LONG)(x * pixelRatio) +
|
||||
std::max((LONG)(minWidth * pixelRatio), ts.cx + tm.tmAveCharWidth);
|
||||
rc.bottom = (LONG)(y * pixelRatio) + tm.tmDescent;
|
||||
sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE,
|
||||
ssGetDpiForWindow(hWindow)));
|
||||
|
||||
|
@ -1608,7 +1608,7 @@ public:
|
|||
|
||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||
std::string desc, patterns;
|
||||
for(auto extension : extensions) {
|
||||
for(auto &extension : extensions) {
|
||||
std::string pattern = "*." + extension;
|
||||
if(!desc.empty()) desc += ", ";
|
||||
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;
|
||||
}
|
||||
|
||||
if(oldpos != joined.length() - 1) {
|
||||
parts.push_back(joined.substr(oldpos));
|
||||
}
|
||||
parts.push_back(joined.substr(oldpos));
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
@ -239,7 +237,8 @@ Path Path::Parent() const {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
ssassert(component.find(SEPARATOR) == std::string::npos,
|
||||
"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.
|
||||
// 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 {
|
||||
if(IsEmpty() || other.IsEmpty() || other.IsAbsolute()) {
|
||||
if(other.IsAbsolute()) {
|
||||
return From("");
|
||||
}
|
||||
|
||||
Path joined = { raw };
|
||||
Path joined;
|
||||
if(IsEmpty()) {
|
||||
joined.raw = ".";
|
||||
} else {
|
||||
joined.raw = raw;
|
||||
}
|
||||
|
||||
if(joined.raw.back() != 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);
|
||||
return Narrow(strW);
|
||||
#elif defined(__APPLE__)
|
||||
CFStringRef cfStr = CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(),
|
||||
kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull);
|
||||
CFMutableStringRef cfmStr = CFStringCreateMutableCopy(NULL, 0, cfStr);
|
||||
CFStringLowercase(cfmStr, NULL);
|
||||
CFMutableStringRef cfStr =
|
||||
CFStringCreateMutableCopy(NULL, 0,
|
||||
CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(),
|
||||
kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull));
|
||||
CFStringLowercase(cfStr, NULL);
|
||||
std::string normalizedStr;
|
||||
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfmStr));
|
||||
CFStringGetFileSystemRepresentation(cfmStr, &normalizedStr[0], normalizedStr.size());
|
||||
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr));
|
||||
CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size());
|
||||
normalizedStr.erase(normalizedStr.find('\0'));
|
||||
CFRelease(cfmStr);
|
||||
CFRelease(cfStr);
|
||||
return normalizedStr;
|
||||
#else
|
||||
return str;
|
||||
|
@ -517,6 +522,12 @@ static Platform::Path ResourcePath(const std::string &name) {
|
|||
return path;
|
||||
}
|
||||
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
|
||||
static Platform::Path ResourcePath(const std::string &name) {
|
||||
return Path::From("res/" + name);
|
||||
}
|
||||
|
||||
#elif !defined(WIN32)
|
||||
|
||||
# if defined(__linux__)
|
||||
|
@ -640,6 +651,12 @@ std::vector<std::string> InitCli(int argc, char **argv) {
|
|||
|
||||
#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, ...)
|
||||
{
|
||||
va_list va;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef SOLVESPACE_PLATFORM_H
|
||||
#define SOLVESPACE_PLATFORM_H
|
||||
|
||||
namespace SolveSpace {
|
||||
namespace Platform {
|
||||
|
||||
// UTF-8 ⟷ UTF-16 conversion, for Windows.
|
||||
|
@ -80,6 +81,7 @@ void DebugPrint(const char *fmt, ...);
|
|||
void *AllocTemporary(size_t size);
|
||||
void FreeAllTemporary();
|
||||
|
||||
}
|
||||
} // namespace Platform
|
||||
} // namespace SolveSpace
|
||||
|
||||
#endif
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#ifndef SOLVESPACE_GL3SHADER_H
|
||||
#define SOLVESPACE_GL3SHADER_H
|
||||
|
||||
#if defined(WIN32)
|
||||
#if defined(WIN32) || defined(__EMSCRIPTEN__)
|
||||
# define GL_APICALL /*static linkage*/
|
||||
# define GL_GLEXT_PROTOTYPES
|
||||
# include <GLES2/gl2.h>
|
||||
|
|
|
@ -90,7 +90,8 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
|
|||
// Request-specific generation.
|
||||
switch(type) {
|
||||
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)) {
|
||||
// We could load the font, so use the actual value.
|
||||
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,
|
||||
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>();
|
||||
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) {
|
||||
int colorType = 0;
|
||||
bool bgr = false;
|
||||
colorType = 0;
|
||||
bgr = false;
|
||||
switch(format) {
|
||||
case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; bgr = false; 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.
|
||||
auto first = unifontData.cbegin(),
|
||||
last = unifontData.cend();
|
||||
while(first <= last) {
|
||||
while(first < last) {
|
||||
auto mid = first + (last - first) / 2;
|
||||
while(mid != unifontData.cbegin()) {
|
||||
if(*mid == '\n') {
|
||||
|
@ -588,7 +589,10 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
|
|||
if(foundCodepoint < codepoint) {
|
||||
first = mid + 1;
|
||||
while(first != unifontData.cend()) {
|
||||
if(*first == '\n') break;
|
||||
if(*first == '\n') {
|
||||
first++;
|
||||
break;
|
||||
}
|
||||
first++;
|
||||
}
|
||||
continue; // and last stays the same
|
||||
|
|
|
@ -7,11 +7,23 @@
|
|||
#ifndef 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 Point2d;
|
||||
class Pixmap;
|
||||
class Vector;
|
||||
class RgbaColor;
|
||||
namespace Platform {
|
||||
class Path;
|
||||
} // namespace Platform
|
||||
|
||||
std::string LoadString(const std::string &name);
|
||||
std::string LoadStringFromGzip(const std::string &name);
|
||||
|
@ -26,6 +38,8 @@ public:
|
|||
size_t height;
|
||||
size_t stride;
|
||||
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> 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);
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
22
src/sketch.h
22
src/sketch.h
|
@ -175,6 +175,7 @@ public:
|
|||
bool suppress;
|
||||
bool relaxConstraints;
|
||||
bool allowRedundant;
|
||||
bool suppressDofCalculation;
|
||||
bool allDimsReference;
|
||||
double scale;
|
||||
|
||||
|
@ -198,6 +199,9 @@ public:
|
|||
// For drawings in 2d
|
||||
WORKPLANE_BY_POINT_ORTHO = 6000,
|
||||
WORKPLANE_BY_LINE_SEGMENTS = 6001,
|
||||
WORKPLANE_BY_POINT_NORMAL = 6002,
|
||||
//WORKPLANE_BY_POINT_FACE = 6003,
|
||||
//WORKPLANE_BY_FACE = 6004,
|
||||
// For extrudes, translates, and rotates
|
||||
ONE_SIDED = 7000,
|
||||
TWO_SIDED = 7001
|
||||
|
@ -266,6 +270,7 @@ public:
|
|||
void Generate(EntityList *entity, ParamList *param);
|
||||
bool IsSolvedOkay();
|
||||
void TransformImportedBy(Vector t, Quaternion q);
|
||||
bool IsTriangleMeshAssembly() const;
|
||||
bool IsForcedToMeshBySource() const;
|
||||
bool IsForcedToMesh() const;
|
||||
// When a request generates entities from entities, and the source
|
||||
|
@ -323,6 +328,7 @@ public:
|
|||
void DrawPolyError(Canvas *canvas);
|
||||
void DrawFilledPaths(Canvas *canvas);
|
||||
void DrawContourAreaLabels(Canvas *canvas);
|
||||
bool ShouldDrawExploded() const;
|
||||
|
||||
SPolygon GetPolygon();
|
||||
|
||||
|
@ -368,6 +374,7 @@ public:
|
|||
std::string font;
|
||||
Platform::Path file;
|
||||
double aspectRatio;
|
||||
int groupRequestIndex;
|
||||
|
||||
static hParam AddParam(ParamList *param, hParam hp);
|
||||
void Generate(EntityList *entity, ParamList *param);
|
||||
|
@ -591,6 +598,10 @@ public:
|
|||
beziers.l.Clear();
|
||||
edges.l.Clear();
|
||||
}
|
||||
|
||||
bool ShouldDrawExploded() const;
|
||||
Vector ExplodeOffset() const;
|
||||
Vector PointGetDrawNum() const;
|
||||
};
|
||||
|
||||
class EntReqTable {
|
||||
|
@ -612,7 +623,7 @@ public:
|
|||
bool free;
|
||||
|
||||
// Used only in the solver
|
||||
hParam substd;
|
||||
Param *substd;
|
||||
|
||||
static const hParam NO_PARAM;
|
||||
|
||||
|
@ -673,7 +684,10 @@ public:
|
|||
CURVE_CURVE_TANGENT = 125,
|
||||
EQUAL_RADIUS = 130,
|
||||
WHERE_DRAGGED = 200,
|
||||
|
||||
ARC_ARC_LEN_RATIO = 210,
|
||||
ARC_LINE_LEN_RATIO = 211,
|
||||
ARC_ARC_DIFFERENCE = 212,
|
||||
ARC_LINE_DIFFERENCE = 213,
|
||||
COMMENT = 1000
|
||||
};
|
||||
|
||||
|
@ -757,7 +771,7 @@ public:
|
|||
Vector p0, Vector p1, Vector pt, double salient);
|
||||
void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
|
||||
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,
|
||||
Vector p, Vector dir, Vector n, double width, double angle, double da);
|
||||
void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
|
||||
|
@ -779,6 +793,8 @@ public:
|
|||
|
||||
std::string DescriptionString() const;
|
||||
|
||||
bool ShouldDrawExploded() const;
|
||||
|
||||
static hConstraint AddConstraint(Constraint *c, bool rememberForUndo = true);
|
||||
static void MenuConstrain(Command id);
|
||||
static void DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA);
|
||||
|
|
|
@ -19,6 +19,7 @@ void SolveSpaceUI::Init() {
|
|||
Platform::SettingsRef settings = Platform::GetSettings();
|
||||
|
||||
SS.tangentArcRadius = 10.0;
|
||||
SS.explodeDistance = 1.0;
|
||||
|
||||
// Then, load the registry settings.
|
||||
// Default list of colors for the model material
|
||||
|
@ -68,12 +69,18 @@ void SolveSpaceUI::Init() {
|
|||
exportScale = settings->ThawFloat("ExportScale", 1.0);
|
||||
// Export offset (cutter radius comp)
|
||||
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)
|
||||
fixExportColors = settings->ThawBool("FixExportColors", true);
|
||||
// Export background color
|
||||
exportBackgroundColor = settings->ThawBool("ExportBackgroundColor", false);
|
||||
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
|
||||
drawBackFaces = settings->ThawBool("DrawBackFaces", true);
|
||||
// Use camera mouse navigation
|
||||
cameraNav = settings->ThawBool("CameraNav", false);
|
||||
// Use turntable mouse navigation
|
||||
turntableNav = settings->ThawBool("TurntableNav", false);
|
||||
// Immediately edit dimension
|
||||
|
@ -104,6 +111,7 @@ void SolveSpaceUI::Init() {
|
|||
exportCanvas.dy = settings->ThawFloat("ExportCanvas_Dy", 5.0);
|
||||
// Extra parameters when exporting G code
|
||||
gCode.depth = settings->ThawFloat("GCode_Depth", 10.0);
|
||||
gCode.safeHeight = settings->ThawFloat("GCode_SafeHeight", 5.0);
|
||||
gCode.passes = settings->ThawInt("GCode_Passes", 1);
|
||||
gCode.feed = settings->ThawFloat("GCode_Feed", 10.0);
|
||||
gCode.plungeFeed = settings->ThawFloat("GCode_PlungeFeed", 10.0);
|
||||
|
@ -123,12 +131,8 @@ void SolveSpaceUI::Init() {
|
|||
SetLocale(locale);
|
||||
}
|
||||
|
||||
generateAllTimer = Platform::CreateTimer();
|
||||
generateAllTimer->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY,
|
||||
/*andFindFree=*/false, /*genForBBox=*/false);
|
||||
|
||||
showTWTimer = Platform::CreateTimer();
|
||||
showTWTimer->onTimeout = std::bind(&TextWindow::Show, &TW);
|
||||
refreshTimer = Platform::CreateTimer();
|
||||
refreshTimer->onTimeout = std::bind(&SolveSpaceUI::Refresh, &SS);
|
||||
|
||||
autosaveTimer = Platform::CreateTimer();
|
||||
autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS);
|
||||
|
@ -250,6 +254,10 @@ void SolveSpaceUI::Exit() {
|
|||
settings->FreezeFloat("ExportScale", exportScale);
|
||||
// Export offset (cutter radius comp)
|
||||
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)
|
||||
settings->FreezeBool("FixExportColors", fixExportColors);
|
||||
// Export background color
|
||||
|
@ -260,6 +268,8 @@ void SolveSpaceUI::Exit() {
|
|||
settings->FreezeBool("ShowContourAreas", showContourAreas);
|
||||
// Check that contours are closed and not self-intersecting
|
||||
settings->FreezeBool("CheckClosedContour", checkClosedContour);
|
||||
// Use camera mouse navigation
|
||||
settings->FreezeBool("CameraNav", cameraNav);
|
||||
// Use turntable mouse navigation
|
||||
settings->FreezeBool("TurntableNav", turntableNav);
|
||||
// Immediately edit dimensions
|
||||
|
@ -300,12 +310,28 @@ void SolveSpaceUI::Exit() {
|
|||
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() {
|
||||
generateAllTimer->RunAfterProcessingEvents();
|
||||
scheduledGenerateAll = true;
|
||||
refreshTimer->RunAfterProcessingEvents();
|
||||
}
|
||||
|
||||
void SolveSpaceUI::ScheduleShowTW() {
|
||||
showTWTimer->RunAfterProcessingEvents();
|
||||
scheduledShowTW = true;
|
||||
refreshTimer->RunAfterProcessingEvents();
|
||||
}
|
||||
|
||||
void SolveSpaceUI::ScheduleAutosave() {
|
||||
|
@ -315,6 +341,7 @@ void SolveSpaceUI::ScheduleAutosave() {
|
|||
double SolveSpaceUI::MmPerUnit() {
|
||||
switch(viewUnits) {
|
||||
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::MM: return 1.0;
|
||||
}
|
||||
|
@ -323,14 +350,47 @@ double SolveSpaceUI::MmPerUnit() {
|
|||
const char *SolveSpaceUI::UnitName() {
|
||||
switch(viewUnits) {
|
||||
case Unit::INCHES: return "in";
|
||||
case Unit::FEET_INCHES: return "in";
|
||||
case Unit::METERS: return "m";
|
||||
case Unit::MM: return "mm";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string SolveSpaceUI::MmToString(double v) {
|
||||
std::string SolveSpaceUI::MmToString(double v, bool editable) {
|
||||
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();
|
||||
double minimum = 0.5 * pow(10,-digits);
|
||||
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) {
|
||||
// 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) {
|
||||
case 0:
|
||||
case 1:
|
||||
|
@ -394,17 +454,22 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) {
|
|||
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)));
|
||||
std::string unit;
|
||||
if(fabs(v) > 0.0) {
|
||||
int sdeg = 0;
|
||||
std::tie(sdeg, unit) =
|
||||
(viewUnits == Unit::INCHES)
|
||||
inches
|
||||
? SelectSIPrefixInch(vdeg/dim)
|
||||
: SelectSIPrefixMm(vdeg, 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));
|
||||
return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v,
|
||||
compact ? "" : " ", unit.c_str(), DimToString(dim));
|
||||
|
@ -434,10 +499,11 @@ int SolveSpaceUI::GetMaxSegments() {
|
|||
return maxSegments;
|
||||
}
|
||||
int SolveSpaceUI::UnitDigitsAfterDecimal() {
|
||||
return (viewUnits == Unit::INCHES) ? afterDecimalInch : afterDecimalMm;
|
||||
return (viewUnits == Unit::INCHES || viewUnits == Unit::FEET_INCHES) ?
|
||||
afterDecimalInch : afterDecimalMm;
|
||||
}
|
||||
void SolveSpaceUI::SetUnitDigitsAfterDecimal(int v) {
|
||||
if(viewUnits == Unit::INCHES) {
|
||||
if(viewUnits == Unit::INCHES || viewUnits == Unit::FEET_INCHES) {
|
||||
afterDecimalInch = v;
|
||||
} else {
|
||||
afterDecimalMm = v;
|
||||
|
@ -508,12 +574,18 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
|||
|
||||
if(saveAs || saveFile.IsEmpty()) {
|
||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window);
|
||||
// FIXME(emscripten):
|
||||
dbp("Calling AddFilter()...");
|
||||
dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT });
|
||||
dbp("Calling ThawChoices()...");
|
||||
dialog->ThawChoices(settings, "Sketch");
|
||||
if(!newSaveFile.IsEmpty()) {
|
||||
dbp("Calling SetFilename()...");
|
||||
dialog->SetFilename(newSaveFile);
|
||||
}
|
||||
dbp("Calling RunModal()...");
|
||||
if(dialog->RunModal()) {
|
||||
dbp("Calling FreezeChoices()...");
|
||||
dialog->FreezeChoices(settings, "Sketch");
|
||||
newSaveFile = dialog->GetFilename();
|
||||
} else {
|
||||
|
@ -526,6 +598,9 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
|||
RemoveAutosave();
|
||||
saveFile = newSaveFile;
|
||||
unsaved = false;
|
||||
if (this->OnSaveFinished) {
|
||||
this->OnSaveFinished(newSaveFile, saveAs, false);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -537,7 +612,11 @@ void SolveSpaceUI::Autosave()
|
|||
ScheduleAutosave();
|
||||
|
||||
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)"));
|
||||
} else {
|
||||
if(!GW.window->SetTitleForFilename(saveFile)) {
|
||||
GW.window->SetTitle(saveFile.raw);
|
||||
if(SS.showFullFilePath) {
|
||||
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()) {
|
||||
dialog->FreezeChoices(settings, "ExportImage");
|
||||
SS.ExportAsPngTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -651,10 +737,8 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
|
||||
// If the user is exporting something where it would be
|
||||
// inappropriate to include the constraints, then warn.
|
||||
if(SS.GW.showConstraints &&
|
||||
(dialog->GetFilename().HasExtension("txt") ||
|
||||
fabs(SS.exportOffset) > LENGTH_EPS))
|
||||
{
|
||||
if(SS.GW.showConstraints != GraphicsWindow::ShowConstraintMode::SCM_NOSHOW &&
|
||||
(dialog->GetFilename().HasExtension("txt") || fabs(SS.exportOffset) > LENGTH_EPS)) {
|
||||
Message(_("Constraints are currently shown, and will be exported "
|
||||
"in the toolpath. This is probably not what you want; "
|
||||
"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);
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -674,6 +761,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||
|
||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -686,6 +776,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
dialog->FreezeChoices(settings, "ExportSection");
|
||||
|
||||
SS.ExportSectionTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -698,6 +791,10 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
dialog->FreezeChoices(settings, "ExportMesh");
|
||||
|
||||
SS.ExportMeshTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -711,6 +808,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
|
||||
StepFileWriter sfw = {};
|
||||
sfw.ExportSurfacesTo(dialog->GetFilename());
|
||||
if (SS.OnSaveFinished) {
|
||||
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -764,7 +864,11 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
|
|||
SS.TW.stepDim.isDistance =
|
||||
(c->type != Constraint::Type::ANGLE) &&
|
||||
(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.screen = TextWindow::Screen::STEP_DIMENSION;
|
||||
|
||||
|
@ -1007,7 +1111,11 @@ void SolveSpaceUI::MenuHelp(Command id) {
|
|||
"law. For details, visit http://gnu.org/licenses/\n"
|
||||
"\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;
|
||||
|
||||
default: ssassert(false, "Unexpected menu ID");
|
||||
|
@ -1026,12 +1134,14 @@ void SolveSpaceUI::Clear() {
|
|||
GW.showGridMenuItem = NULL;
|
||||
GW.dimSolidModelMenuItem = NULL;
|
||||
GW.perspectiveProjMenuItem = NULL;
|
||||
GW.explodeMenuItem = NULL;
|
||||
GW.showToolbarMenuItem = NULL;
|
||||
GW.showTextWndMenuItem = NULL;
|
||||
GW.fullScreenMenuItem = NULL;
|
||||
GW.unitsMmMenuItem = NULL;
|
||||
GW.unitsMetersMenuItem = NULL;
|
||||
GW.unitsInchesMenuItem = NULL;
|
||||
GW.unitsFeetInchesMenuItem = NULL;
|
||||
GW.inWorkplaneMenuItem = NULL;
|
||||
GW.in3dMenuItem = NULL;
|
||||
GW.undoMenuItem = NULL;
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
#ifndef SOLVESPACE_H
|
||||
#define SOLVESPACE_H
|
||||
|
||||
#include "resource.h"
|
||||
#include "platform/platform.h"
|
||||
#include "platform/gui.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
|
@ -30,6 +34,10 @@
|
|||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#define EIGEN_NO_DEBUG
|
||||
#undef Success
|
||||
#include <Eigen/SparseCore>
|
||||
|
||||
// We declare these in advance instead of simply using FT_Library
|
||||
// (defined as typedef FT_LibraryRec_* FT_Library) because including
|
||||
// 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_NEGATIVE = -1e10;
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/gui.h"
|
||||
#include "resource.h"
|
||||
|
||||
using Platform::AllocTemporary;
|
||||
using Platform::FreeAllTemporary;
|
||||
|
@ -138,7 +143,8 @@ enum class Command : uint32_t;
|
|||
enum class Unit : uint32_t {
|
||||
MM = 0,
|
||||
INCHES,
|
||||
METERS
|
||||
METERS,
|
||||
FEET_INCHES
|
||||
};
|
||||
|
||||
template<class Key, class T>
|
||||
|
@ -209,7 +215,7 @@ void Error(const char *fmt, ...);
|
|||
|
||||
class System {
|
||||
public:
|
||||
enum { MAX_UNKNOWNS = 1024 };
|
||||
enum { MAX_UNKNOWNS = 2048 };
|
||||
|
||||
EntityList entity;
|
||||
ParamList param;
|
||||
|
@ -231,37 +237,34 @@ public:
|
|||
// The system Jacobian matrix
|
||||
struct {
|
||||
// The corresponding equation for each row
|
||||
hEquation eq[MAX_UNKNOWNS];
|
||||
std::vector<Equation *> eq;
|
||||
|
||||
// The corresponding parameter for each column
|
||||
hParam param[MAX_UNKNOWNS];
|
||||
std::vector<hParam> param;
|
||||
|
||||
// We're solving AX = B
|
||||
int m, n;
|
||||
struct {
|
||||
Expr *sym[MAX_UNKNOWNS][MAX_UNKNOWNS];
|
||||
double num[MAX_UNKNOWNS][MAX_UNKNOWNS];
|
||||
} A;
|
||||
// This only observes the Expr - does not own them!
|
||||
Eigen::SparseMatrix<Expr *> sym;
|
||||
Eigen::SparseMatrix<double> num;
|
||||
} A;
|
||||
|
||||
double scale[MAX_UNKNOWNS];
|
||||
|
||||
// Some helpers for the least squares solve
|
||||
double AAt[MAX_UNKNOWNS][MAX_UNKNOWNS];
|
||||
double Z[MAX_UNKNOWNS];
|
||||
|
||||
double X[MAX_UNKNOWNS];
|
||||
Eigen::VectorXd scale;
|
||||
Eigen::VectorXd X;
|
||||
|
||||
struct {
|
||||
Expr *sym[MAX_UNKNOWNS];
|
||||
double num[MAX_UNKNOWNS];
|
||||
} B;
|
||||
// This only observes the Expr - does not own them!
|
||||
std::vector<Expr *> sym;
|
||||
Eigen::VectorXd num;
|
||||
} B;
|
||||
} mat;
|
||||
|
||||
static const double RANK_MAG_TOLERANCE, CONVERGE_TOLERANCE;
|
||||
static const double CONVERGE_TOLERANCE;
|
||||
int CalculateRank();
|
||||
bool TestRank(int *rank = NULL);
|
||||
static bool SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS],
|
||||
double B[], int N);
|
||||
bool TestRank(int *dof = NULL);
|
||||
static bool SolveLinearSystem(const Eigen::SparseMatrix<double> &A,
|
||||
const Eigen::VectorXd &B, Eigen::VectorXd *X);
|
||||
bool SolveLeastSquares();
|
||||
|
||||
bool WriteJacobian(int tag);
|
||||
|
@ -277,7 +280,6 @@ public:
|
|||
bool NewtonSolve(int tag);
|
||||
|
||||
void MarkParamsFree(bool findFree);
|
||||
int CalculateDof();
|
||||
|
||||
SolveResult Solve(Group *g, int *rank = NULL, int *dof = NULL,
|
||||
List<hConstraint> *bad = NULL,
|
||||
|
@ -289,6 +291,9 @@ public:
|
|||
bool andFindBad = false, bool andFindFree = false);
|
||||
|
||||
void Clear();
|
||||
Param *GetLastParamSubstitution(Param *p);
|
||||
void SubstituteParamsByLast(Expr *e);
|
||||
void SortSubstitutionByDragged(Param *p);
|
||||
};
|
||||
|
||||
#include "ttf.h"
|
||||
|
@ -568,11 +573,14 @@ public:
|
|||
double gridSpacing;
|
||||
double exportScale;
|
||||
double exportOffset;
|
||||
bool arcDimDefaultDiameter;
|
||||
bool showFullFilePath;
|
||||
bool fixExportColors;
|
||||
bool exportBackgroundColor;
|
||||
bool drawBackFaces;
|
||||
bool showContourAreas;
|
||||
bool checkClosedContour;
|
||||
bool cameraNav;
|
||||
bool turntableNav;
|
||||
bool immediatelyEditDimension;
|
||||
bool automaticLineConstraints;
|
||||
|
@ -597,6 +605,7 @@ public:
|
|||
} exportCanvas;
|
||||
struct {
|
||||
double depth;
|
||||
double safeHeight;
|
||||
int passes;
|
||||
double feed;
|
||||
double plungeFeed;
|
||||
|
@ -608,8 +617,10 @@ public:
|
|||
int afterDecimalDegree;
|
||||
bool useSIPrefixes;
|
||||
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 DegreeToString(double v);
|
||||
double ExprToMm(Expr *e);
|
||||
|
@ -675,6 +686,7 @@ public:
|
|||
void NewFile();
|
||||
bool SaveToFile(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);
|
||||
void UpgradeLegacyData();
|
||||
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
||||
|
@ -785,9 +797,11 @@ public:
|
|||
// the sketch!
|
||||
bool allConsistent;
|
||||
|
||||
Platform::TimerRef showTWTimer;
|
||||
Platform::TimerRef generateAllTimer;
|
||||
bool scheduledGenerateAll;
|
||||
bool scheduledShowTW;
|
||||
Platform::TimerRef refreshTimer;
|
||||
Platform::TimerRef autosaveTimer;
|
||||
void Refresh();
|
||||
void ScheduleShowTW();
|
||||
void ScheduleGenerateAll();
|
||||
void ScheduleAutosave();
|
||||
|
@ -812,6 +826,7 @@ public:
|
|||
void ImportDxf(const Platform::Path &file);
|
||||
void ImportDwg(const Platform::Path &file);
|
||||
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 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
|
||||
// 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
|
||||
|
@ -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)
|
||||
{
|
||||
bool inShell = (shell == SShell::Class::INSIDE),
|
||||
outSide = (shell == SShell::Class::OUTSIDE),
|
||||
inSame = (shell == SShell::Class::COINC_SAME),
|
||||
inOrig = (orig == SShell::Class::INSIDE);
|
||||
bool inShell = (shell == SShell::Class::SURF_INSIDE),
|
||||
outSide = (shell == SShell::Class::SURF_OUTSIDE),
|
||||
coincSame = (shell == SShell::Class::SURF_COINC_SAME),
|
||||
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;
|
||||
|
||||
switch(type) {
|
||||
case SSurface::CombineAs::UNION:
|
||||
if(opA) {
|
||||
return outSide;
|
||||
} else {
|
||||
return outSide || inSame;
|
||||
return outSide || coincSame;
|
||||
}
|
||||
|
||||
case SSurface::CombineAs::DIFFERENCE:
|
||||
if(opA) {
|
||||
return outSide;
|
||||
return outSide || coincOpp;
|
||||
} else {
|
||||
return inShell || inSame;
|
||||
return inShell;
|
||||
}
|
||||
|
||||
case SSurface::CombineAs::INTERSECTION:
|
||||
if(opA) {
|
||||
return inShell;
|
||||
} else {
|
||||
return inShell || inSame;
|
||||
return inShell || coincSame;
|
||||
}
|
||||
|
||||
default: ssassert(false, "Unexpected combine type");
|
||||
|
@ -318,29 +321,29 @@ static void TagByClassifiedEdge(SBspUv::Class bspclass, SShell::Class *indir, SS
|
|||
{
|
||||
switch(bspclass) {
|
||||
case SBspUv::Class::INSIDE:
|
||||
*indir = SShell::Class::INSIDE;
|
||||
*outdir = SShell::Class::INSIDE;
|
||||
*indir = SShell::Class::SURF_INSIDE;
|
||||
*outdir = SShell::Class::SURF_INSIDE;
|
||||
break;
|
||||
|
||||
case SBspUv::Class::OUTSIDE:
|
||||
*indir = SShell::Class::OUTSIDE;
|
||||
*outdir = SShell::Class::OUTSIDE;
|
||||
*indir = SShell::Class::SURF_OUTSIDE;
|
||||
*outdir = SShell::Class::SURF_OUTSIDE;
|
||||
break;
|
||||
|
||||
case SBspUv::Class::EDGE_PARALLEL:
|
||||
*indir = SShell::Class::INSIDE;
|
||||
*outdir = SShell::Class::OUTSIDE;
|
||||
*indir = SShell::Class::SURF_INSIDE;
|
||||
*outdir = SShell::Class::SURF_OUTSIDE;
|
||||
break;
|
||||
|
||||
case SBspUv::Class::EDGE_ANTIPARALLEL:
|
||||
*indir = SShell::Class::OUTSIDE;
|
||||
*outdir = SShell::Class::INSIDE;
|
||||
*indir = SShell::Class::SURF_OUTSIDE;
|
||||
*outdir = SShell::Class::SURF_INSIDE;
|
||||
break;
|
||||
|
||||
default:
|
||||
dbp("TagByClassifiedEdge: fail!");
|
||||
*indir = SShell::Class::OUTSIDE;
|
||||
*outdir = SShell::Class::OUTSIDE;
|
||||
*indir = SShell::Class::SURF_OUTSIDE;
|
||||
*outdir = SShell::Class::SURF_OUTSIDE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -438,13 +441,12 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv,
|
|||
double t;
|
||||
sc->exact.ClosestPointTo(*pt, &t, /*mustConverge=*/false);
|
||||
*pt = sc->exact.PointAt(t);
|
||||
ClosestPointTo(*pt, &muv);
|
||||
} else if(!sc->isExact) {
|
||||
SSurface *trimmedA = sc->GetSurfaceA(sha, shb),
|
||||
*trimmedB = sc->GetSurfaceB(sha, shb);
|
||||
*pt = trimmedA->ClosestPointOnThisAndSurface(trimmedB, *pt);
|
||||
ClosestPointTo(*pt, &muv);
|
||||
}
|
||||
ClosestPointTo(*pt, &muv);
|
||||
|
||||
*surfn = NormalAt(muv.x, muv.y);
|
||||
|
||||
|
@ -472,6 +474,9 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv,
|
|||
pout = PointAt(muv.Plus(enuv));
|
||||
*enin = pin.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;
|
||||
|
||||
indir_orig = SShell::Class::INSIDE;
|
||||
outdir_orig = SShell::Class::OUTSIDE;
|
||||
indir_orig = SShell::Class::SURF_INSIDE;
|
||||
outdir_orig = SShell::Class::SURF_OUTSIDE;
|
||||
|
||||
agnst->ClassifyEdge(&indir_shell, &outdir_shell,
|
||||
ret.PointAt(auv), ret.PointAt(buv), pt,
|
||||
|
@ -796,7 +801,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) {
|
|||
b->MakeClassifyingBsps(NULL);
|
||||
|
||||
// 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.
|
||||
a->CopyCurvesSplitAgainst(/*opA=*/true, b, this);
|
||||
b->CopyCurvesSplitAgainst(/*opA=*/false, a, this);
|
||||
|
|
|
@ -817,7 +817,7 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) {
|
|||
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)
|
||||
tprev = t = tnext = 0;
|
||||
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
|
||||
// to see if it's with same or opposite normals.
|
||||
if(inter_surf_n.Dot(edge_surf_n) > 0) {
|
||||
return Class::COINC_SAME;
|
||||
return Class::SURF_COINC_SAME;
|
||||
} else {
|
||||
return Class::COINC_OPP;
|
||||
return Class::SURF_COINC_OPP;
|
||||
}
|
||||
} else if(dot > 0) {
|
||||
return Class::OUTSIDE;
|
||||
return Class::SURF_OUTSIDE;
|
||||
} 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]);
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
|
||||
*indir = coinc;
|
||||
*outdir = Class::OUTSIDE;
|
||||
*outdir = Class::SURF_OUTSIDE;
|
||||
} else {
|
||||
*indir = Class::INSIDE;
|
||||
*indir = Class::SURF_INSIDE;
|
||||
*outdir = coinc;
|
||||
}
|
||||
} else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) {
|
||||
if(edge_n_out.Dot(inter_edge_n[0]) > 0) {
|
||||
*indir = coinc;
|
||||
*outdir = Class::INSIDE;
|
||||
*outdir = Class::SURF_INSIDE;
|
||||
} else {
|
||||
*indir = Class::OUTSIDE;
|
||||
*indir = Class::SURF_OUTSIDE;
|
||||
*outdir = coinc;
|
||||
}
|
||||
} else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) {
|
||||
*indir = Class::INSIDE;
|
||||
*outdir = Class::OUTSIDE;
|
||||
*indir = Class::SURF_INSIDE;
|
||||
*outdir = Class::SURF_OUTSIDE;
|
||||
} else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) {
|
||||
*indir = Class::OUTSIDE;
|
||||
*outdir = Class::INSIDE;
|
||||
*indir = Class::SURF_OUTSIDE;
|
||||
*outdir = Class::SURF_INSIDE;
|
||||
} else {
|
||||
// Edge is tangent to the shell at shell's edge, so can't be
|
||||
// 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;
|
||||
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;
|
||||
srf.ClosestPointTo(p.Plus(edge_n_in), &pin, /*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);
|
||||
|
||||
// no intersections means it's outside
|
||||
*indir = Class::OUTSIDE;
|
||||
*outdir = Class::OUTSIDE;
|
||||
*indir = Class::SURF_OUTSIDE;
|
||||
*outdir = Class::SURF_OUTSIDE;
|
||||
double dmin = VERY_POSITIVE;
|
||||
bool onEdge = false;
|
||||
edge_inters = 0;
|
||||
|
@ -584,11 +584,11 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
|
|||
// Edge does not lie on surface; either strictly inside
|
||||
// or strictly outside
|
||||
if((si->surfNormal).Dot(ray) > 0) {
|
||||
*indir = Class::INSIDE;
|
||||
*outdir = Class::INSIDE;
|
||||
*indir = Class::SURF_INSIDE;
|
||||
*outdir = Class::SURF_INSIDE;
|
||||
} else {
|
||||
*indir = Class::OUTSIDE;
|
||||
*outdir = Class::OUTSIDE;
|
||||
*indir = Class::SURF_OUTSIDE;
|
||||
*outdir = Class::SURF_OUTSIDE;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
// shell.
|
||||
enum class Class : uint32_t {
|
||||
INSIDE = 100,
|
||||
OUTSIDE = 200,
|
||||
COINC_SAME = 300,
|
||||
COINC_OPP = 400
|
||||
SURF_INSIDE = 100,
|
||||
SURF_OUTSIDE = 200,
|
||||
SURF_COINC_SAME = 300,
|
||||
SURF_COINC_OPP = 400
|
||||
};
|
||||
static const double DOTP_TOL;
|
||||
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;
|
||||
for(i = 0; i < 20; i++) {
|
||||
Vector dp = nb.Cross(na);
|
||||
if(!fwd) dp = dp.ScaledBy(-1);
|
||||
dp = dp.WithMagnitude(step);
|
||||
|
||||
np = start.Plus(dp);
|
||||
np = start.Plus(dp.ScaledBy(step));
|
||||
npc = ClosestPointOnThisAndSurface(b, np);
|
||||
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