Fix conflict.
commit
fb5e731e6e
|
@ -1,8 +1,7 @@
|
||||||
### System information
|
### System information
|
||||||
|
|
||||||
SolveSpace version: <!--e.g. 3.0~3dd2fc00; go to Help → About...-->
|
- **SolveSpace version:** <!--e.g. 3.0~3dd2fc00; go to Help → About...-->
|
||||||
|
- **Operating system:** <!--e.g. Debian testing-->
|
||||||
Operating system: <!--e.g. Debian testing-->
|
|
||||||
|
|
||||||
### Expected behavior
|
### Expected behavior
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
mkdir build || true
|
||||||
|
cd build
|
||||||
|
|
||||||
|
OSX_TARGET="10.9"
|
||||||
|
|
||||||
|
if [ "$1" = "release" ]; then
|
||||||
|
BUILD_TYPE=RelWithDebInfo
|
||||||
|
cmake \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_TARGET}" \
|
||||||
|
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||||
|
-DENABLE_OPENMP="ON" \
|
||||||
|
-DENABLE_LTO="ON" \
|
||||||
|
..
|
||||||
|
else
|
||||||
|
BUILD_TYPE=Debug
|
||||||
|
cmake \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_TARGET}" \
|
||||||
|
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||||
|
-DENABLE_OPENMP="ON" \
|
||||||
|
-DENABLE_SANITIZERS="ON" \
|
||||||
|
..
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmake --build . --config "${BUILD_TYPE}" -- -j$(nproc)
|
||||||
|
make -j$(nproc) test_solvespace
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
./pkg/snap/build.sh --use-lxd
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake \
|
||||||
|
-DCMAKE_BUILD_TYPE="Debug" \
|
||||||
|
-DENABLE_OPENMP="ON" \
|
||||||
|
-DENABLE_SANITIZERS="ON" \
|
||||||
|
..
|
||||||
|
make -j$(nproc) VERBOSE=1
|
||||||
|
make test_solvespace
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
if [ "$1" = "release" ]; then
|
||||||
|
if [ "$2" = "openmp" ]; then
|
||||||
|
ENABLE_OPENMP="ON"
|
||||||
|
else
|
||||||
|
ENABLE_OPENMP="OFF"
|
||||||
|
fi
|
||||||
|
BUILD_TYPE=RelWithDebInfo
|
||||||
|
cmake \
|
||||||
|
-G "Visual Studio 16 2019" \
|
||||||
|
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||||
|
-DENABLE_OPENMP="${ENABLE_OPENMP}" \
|
||||||
|
-DENABLE_LTO=ON \
|
||||||
|
-DCMAKE_GENERATOR_PLATFORM="Win32" \
|
||||||
|
..
|
||||||
|
else
|
||||||
|
BUILD_TYPE=Debug
|
||||||
|
cmake \
|
||||||
|
-G "Visual Studio 16 2019" \
|
||||||
|
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||||
|
-DENABLE_OPENMP="ON" \
|
||||||
|
-DCMAKE_GENERATOR_PLATFORM="Win32" \
|
||||||
|
..
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmake --build . --config "${BUILD_TYPE}" -- -maxcpucount
|
||||||
|
|
||||||
|
bin/$BUILD_TYPE/solvespace-testsuite.exe
|
||||||
|
|
||||||
|
if [ "$2" = "openmp" ]; then
|
||||||
|
mv bin/$BUILD_TYPE/solvespace.exe bin/$BUILD_TYPE/solvespace-openmp.exe
|
||||||
|
fi
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
brew install libomp
|
||||||
|
git submodule update --init
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
sudo /snap/bin/lxd waitready
|
||||||
|
sudo /snap/bin/lxd init --auto
|
||||||
|
sudo chgrp travis /var/snap/lxd/common/lxd/unix.socket
|
||||||
|
mkdir -p "$TRAVIS_BUILD_DIR/snaps-cache"
|
|
@ -7,4 +7,4 @@ sudo apt-get install -q -y \
|
||||||
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev libgl-dev \
|
libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev libgl-dev \
|
||||||
libgl-dev libglu-dev libspnav-dev
|
libgl-dev libglu-dev libspnav-dev
|
||||||
|
|
||||||
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc
|
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
|
@ -1,5 +1,3 @@
|
||||||
#!/bin/sh -xe
|
#!/bin/sh -xe
|
||||||
|
|
||||||
brew update
|
|
||||||
brew install freetype cairo
|
|
||||||
git submodule update --init
|
git submodule update --init
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/bin/bash -xe
|
||||||
|
|
||||||
|
cd build
|
||||||
|
|
||||||
|
openmp="bin/SolveSpace.app/Contents/Resources/lib/libomp.dylib"
|
||||||
|
app="bin/SolveSpace.app"
|
||||||
|
dmg="bin/SolveSpace.dmg"
|
||||||
|
bundle_id="com.solvespace.solvespace"
|
||||||
|
|
||||||
|
if [ "$CI" = "true" ]; then
|
||||||
|
# get the signing certificate (this is the Developer ID:Application: Your Name, exported to a p12 file, then converted to base64, e.g.: cat ~/Desktop/certificate.p12 | base64 | pbcopy)
|
||||||
|
echo $MACOS_CERTIFICATE_P12 | base64 --decode > certificate.p12
|
||||||
|
|
||||||
|
# create a keychain
|
||||||
|
security create-keychain -p secret build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p secret build.keychain
|
||||||
|
|
||||||
|
# import the key
|
||||||
|
security import certificate.p12 -k build.keychain -P "${MACOS_CERTIFICATE_PASSWORD}" -T /usr/bin/codesign
|
||||||
|
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -s -k secret build.keychain
|
||||||
|
|
||||||
|
# check if all is good
|
||||||
|
security find-identity -v
|
||||||
|
fi
|
||||||
|
|
||||||
|
# sign openmp
|
||||||
|
codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${openmp}"
|
||||||
|
|
||||||
|
# sign the .app
|
||||||
|
codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${app}"
|
||||||
|
|
||||||
|
# create the .dmg from the signed .app
|
||||||
|
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'})
|
||||||
|
|
||||||
|
echo $notarize_uuid
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# staple
|
||||||
|
xcrun stapler staple "${dmg}"
|
|
@ -0,0 +1,240 @@
|
||||||
|
name: CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test_ubuntu:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
name: Test Ubuntu
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-ubuntu.sh
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-ubuntu.sh
|
||||||
|
|
||||||
|
test_windows:
|
||||||
|
runs-on: windows-2019
|
||||||
|
name: Test Windows
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-windows.sh
|
||||||
|
shell: bash
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-windows.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
test_macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
name: Test macOS
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-macos.sh
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-macos.sh
|
||||||
|
|
||||||
|
build_release_windows:
|
||||||
|
needs: [test_ubuntu, test_windows, test_macos]
|
||||||
|
name: Build Release Windows
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-windows.sh
|
||||||
|
shell: bash
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-windows.sh release
|
||||||
|
shell: bash
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: build/bin/RelWithDebInfo/solvespace.exe
|
||||||
|
|
||||||
|
build_release_windows_openmp:
|
||||||
|
needs: [test_ubuntu, test_windows, test_macos]
|
||||||
|
name: Build Release Windows (OpenMP)
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-windows.sh
|
||||||
|
shell: bash
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-windows.sh release openmp
|
||||||
|
shell: bash
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: windows-openmp
|
||||||
|
path: build/bin/RelWithDebInfo/solvespace-openmp.exe
|
||||||
|
|
||||||
|
build_release_macos:
|
||||||
|
needs: [test_ubuntu, test_windows, test_macos]
|
||||||
|
name: Build Release macOS
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-macos.sh
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-macos.sh release
|
||||||
|
- name: Sign Build
|
||||||
|
run: .github/scripts/sign-macos.sh
|
||||||
|
env:
|
||||||
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||||
|
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
|
||||||
|
MACOS_APPSTORE_APP_PASSWORD: ${{ secrets.MACOS_APPSTORE_APP_PASSWORD }}
|
||||||
|
MACOS_APPSTORE_USERNAME: ${{ secrets.MACOS_APPSTORE_USERNAME }}
|
||||||
|
MACOS_DEVELOPER_ID: ${{ secrets.MACOS_DEVELOPER_ID }}
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
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
|
||||||
|
- 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: always() && github.event_name == 'push'
|
||||||
|
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.1.2
|
||||||
|
with:
|
||||||
|
delete_release: true
|
||||||
|
tag_name: edge
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create New Edge Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: edge
|
||||||
|
release_name: Edge
|
||||||
|
prerelease: true
|
||||||
|
draft: false
|
||||||
|
body: ${{ github.event.head_commit.message }}
|
||||||
|
|
||||||
|
upload_release_assets:
|
||||||
|
name: Upload Release Assets
|
||||||
|
needs: [build_release_windows, build_release_windows_openmp, build_release_macos, update_edge_release]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download All Workflow Artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
- 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
|
||||||
|
echo "::set-output name=upload_url::$upload_url"
|
||||||
|
echo "Upload URL: $upload_url"
|
||||||
|
- name: Upload solvespace.exe
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
|
||||||
|
asset_path: windows/solvespace.exe
|
||||||
|
asset_name: solvespace.exe
|
||||||
|
asset_content_type: binary/octet-stream
|
||||||
|
- name: Upload solvespace-openmp.exe
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
|
||||||
|
asset_path: windows-openmp/solvespace-openmp.exe
|
||||||
|
asset_name: solvespace-openmp.exe
|
||||||
|
asset_content_type: binary/octet-stream
|
||||||
|
- name: Upload SolveSpace.dmg
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
|
||||||
|
asset_path: macos/SolveSpace.dmg
|
||||||
|
asset_name: SolveSpace.dmg
|
||||||
|
asset_content_type: binary/octet-stream
|
|
@ -0,0 +1,44 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- master
|
||||||
|
tags-ignore:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test_ubuntu:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
name: Test Ubuntu
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-ubuntu.sh
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-ubuntu.sh
|
||||||
|
|
||||||
|
test_windows:
|
||||||
|
runs-on: windows-2019
|
||||||
|
name: Test Windows
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-windows.sh
|
||||||
|
shell: bash
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-windows.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
test_macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
name: Test macOS
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: .github/scripts/install-macos.sh
|
||||||
|
- name: Build & Test
|
||||||
|
run: .github/scripts/build-macos.sh
|
|
@ -20,12 +20,6 @@
|
||||||
[submodule "extlib/angle"]
|
[submodule "extlib/angle"]
|
||||||
path = extlib/angle
|
path = extlib/angle
|
||||||
url = https://github.com/solvespace/angle
|
url = https://github.com/solvespace/angle
|
||||||
[submodule "extlib/flatbuffers"]
|
|
||||||
path = extlib/flatbuffers
|
|
||||||
url = https://github.com/google/flatbuffers
|
|
||||||
[submodule "extlib/q3d"]
|
|
||||||
path = extlib/q3d
|
|
||||||
url = https://github.com/q3k/q3d
|
|
||||||
[submodule "extlib/mimalloc"]
|
[submodule "extlib/mimalloc"]
|
||||||
path = extlib/mimalloc
|
path = extlib/mimalloc
|
||||||
url = https://github.com/microsoft/mimalloc
|
url = https://github.com/microsoft/mimalloc
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/sh -xe
|
|
||||||
|
|
||||||
if echo $TRAVIS_TAG | grep ^v; then BUILD_TYPE=RelWithDebInfo; else BUILD_TYPE=Debug; fi
|
|
||||||
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake .. \
|
|
||||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
|
||||||
-DENABLE_SANITIZERS=ON
|
|
||||||
make VERBOSE=1
|
|
||||||
make test_solvespace
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/sh -xe
|
|
||||||
|
|
||||||
if echo $TRAVIS_TAG | grep ^v; then BUILD_TYPE=RelWithDebInfo; else BUILD_TYPE=Debug; fi
|
|
||||||
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
|
||||||
make VERBOSE=1
|
|
||||||
make test_solvespace
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/sh -xe
|
|
||||||
|
|
||||||
sudo apt-get update
|
|
||||||
sudo ./pkg/snap/build.sh --destructive-mode
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/sh -e
|
|
||||||
|
|
||||||
channels="$1"
|
|
||||||
echo "$SNAP_TOKEN" | snapcraft login --with -
|
|
||||||
|
|
||||||
for snap in ./pkg/snap/*.snap; do
|
|
||||||
snapcraft push "$snap" --release "$channels"
|
|
||||||
done
|
|
|
@ -27,6 +27,8 @@ New sketch features:
|
||||||
* "Split Curves at Intersection" can now split curves at point lying on curve,
|
* "Split Curves at Intersection" can now split curves at point lying on curve,
|
||||||
not just at intersection of two curves.
|
not just at intersection of two curves.
|
||||||
* Property browser now shows amount of degrees of freedom in group list.
|
* Property browser now shows amount of degrees of freedom in group list.
|
||||||
|
It also shows a yellow "err" if the sketch has problems (e.g. self
|
||||||
|
intersecting) that would propagate in subsequent groups.
|
||||||
|
|
||||||
New constraint features:
|
New constraint features:
|
||||||
* When dragging an arc or rectangle point, it will be automatically
|
* When dragging an arc or rectangle point, it will be automatically
|
||||||
|
@ -53,7 +55,8 @@ New export/import features:
|
||||||
* Wavefront OBJ: a material file is exported alongside the model, containing
|
* Wavefront OBJ: a material file is exported alongside the model, containing
|
||||||
mesh color information.
|
mesh color information.
|
||||||
* DXF/DWG: 3D DXF files are imported as construction entities, in 3d.
|
* DXF/DWG: 3D DXF files are imported as construction entities, in 3d.
|
||||||
* Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be
|
* [ADDED 2019-02-25](https://github.com/solvespace/solvespace/pull/384) and [REMOVED 2020-11-13](https://github.com/solvespace/solvespace/issues/795):
|
||||||
|
Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be
|
||||||
exported. This format allows to easily hack on triangle mesh data created
|
exported. This format allows to easily hack on triangle mesh data created
|
||||||
in SolveSpace, supports colour information and is more space efficient than
|
in SolveSpace, supports colour information and is more space efficient than
|
||||||
most other formats.
|
most other formats.
|
||||||
|
@ -110,6 +113,7 @@ Other new features:
|
||||||
that are shortcuts to the respective configuration screens.
|
that are shortcuts to the respective configuration screens.
|
||||||
* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes
|
* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes
|
||||||
to enable support for multi-threading and link-time optimization.
|
to enable support for multi-threading and link-time optimization.
|
||||||
|
* "Shift+Scroll" for ten times finer zoom.
|
||||||
|
|
||||||
Bugs fixed:
|
Bugs fixed:
|
||||||
* Fixed broken --view options for command line thumbnail image creation.
|
* Fixed broken --view options for command line thumbnail image creation.
|
||||||
|
|
|
@ -11,6 +11,11 @@ cmake_minimum_required(VERSION 3.7.2 FATAL_ERROR)
|
||||||
if(NOT CMAKE_VERSION VERSION_LESS 3.11.0)
|
if(NOT CMAKE_VERSION VERSION_LESS 3.11.0)
|
||||||
cmake_policy(VERSION 3.11.0)
|
cmake_policy(VERSION 3.11.0)
|
||||||
endif()
|
endif()
|
||||||
|
if(NOT CMAKE_VERSION VERSION_LESS 3.9)
|
||||||
|
# LTO/IPO with non-Intel compilers on Linux requires policy CMP0069 to be set to NEW.
|
||||||
|
# Set it explicitly until cmake_minimum_required is raised to >= 3.9.
|
||||||
|
cmake_policy(SET CMP0069 NEW)
|
||||||
|
endif()
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||||
"${CMAKE_SOURCE_DIR}/cmake/")
|
"${CMAKE_SOURCE_DIR}/cmake/")
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
@ -22,6 +27,10 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE
|
||||||
set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX
|
set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX
|
||||||
"${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake")
|
"${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake")
|
||||||
|
|
||||||
|
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||||
|
endif()
|
||||||
|
|
||||||
# project
|
# project
|
||||||
|
|
||||||
# NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds
|
# NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds
|
||||||
|
@ -48,6 +57,8 @@ set(ENABLE_SANITIZERS OFF CACHE BOOL
|
||||||
"Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer")
|
"Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer")
|
||||||
set(ENABLE_OPENMP OFF CACHE BOOL
|
set(ENABLE_OPENMP OFF CACHE BOOL
|
||||||
"Whether geometric operations will be parallelized using OpenMP")
|
"Whether geometric operations will be parallelized using OpenMP")
|
||||||
|
set(ENABLE_LTO OFF CACHE BOOL
|
||||||
|
"Whether interprocedural (global) optimizations are enabled")
|
||||||
|
|
||||||
set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)")
|
set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)")
|
||||||
|
|
||||||
|
@ -69,7 +80,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# common compiler flags
|
# common compiler flags
|
||||||
|
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
|
|
||||||
set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.")
|
set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.")
|
||||||
|
@ -99,20 +109,19 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "X8
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_LTO)
|
if(ENABLE_LTO)
|
||||||
|
include(CheckIPOSupported)
|
||||||
|
check_ipo_supported()
|
||||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_OPENMP)
|
if(ENABLE_OPENMP)
|
||||||
include(FindOpenMP)
|
find_package( OpenMP REQUIRED )
|
||||||
if(OpenMP_FOUND)
|
if(OPENMP_FOUND)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
|
||||||
|
message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} )
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
|
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
endif()
|
endif()
|
||||||
|
@ -144,8 +153,12 @@ if(ENABLE_SANITIZERS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}")
|
string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}")
|
||||||
set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}")
|
|
||||||
set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-sanitize-recover=address,undefined")
|
if (NOT APPLE)
|
||||||
|
set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS} -fno-sanitize-recover=address,undefined")
|
||||||
|
else()
|
||||||
|
set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls")
|
set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls")
|
||||||
|
@ -168,17 +181,6 @@ endif()
|
||||||
message(STATUS "Using in-tree libdxfrw")
|
message(STATUS "Using in-tree libdxfrw")
|
||||||
add_subdirectory(extlib/libdxfrw)
|
add_subdirectory(extlib/libdxfrw)
|
||||||
|
|
||||||
message(STATUS "Using in-tree flatbuffers")
|
|
||||||
set(FLATBUFFERS_BUILD_FLATLIB ON CACHE BOOL "")
|
|
||||||
set(FLATBUFFERS_BUILD_FLATC ON CACHE BOOL "")
|
|
||||||
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "")
|
|
||||||
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "")
|
|
||||||
add_subdirectory(extlib/flatbuffers EXCLUDE_FROM_ALL)
|
|
||||||
|
|
||||||
message(STATUS "Using in-tree q3d")
|
|
||||||
add_subdirectory(extlib/q3d)
|
|
||||||
set(Q3D_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/q3d)
|
|
||||||
|
|
||||||
message(STATUS "Using in-tree mimalloc")
|
message(STATUS "Using in-tree mimalloc")
|
||||||
set(MI_OVERRIDE OFF CACHE BOOL "")
|
set(MI_OVERRIDE OFF CACHE BOOL "")
|
||||||
set(MI_BUILD_SHARED OFF CACHE BOOL "")
|
set(MI_BUILD_SHARED OFF CACHE BOOL "")
|
||||||
|
|
|
@ -14,11 +14,11 @@ Bug reports are always welcome! When reporting a bug, please include the followi
|
||||||
GitHub does not allow attaching `*.slvs` files, but it does allow attaching `*.zip` files,
|
GitHub does not allow attaching `*.slvs` files, but it does allow attaching `*.zip` files,
|
||||||
so any savefiles should first be archived.
|
so any savefiles should first be archived.
|
||||||
|
|
||||||
Signing the CLA
|
Licensing
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
To contribute code, translations, artwork, or other resources to SolveSpace, it is necessary to
|
SolveSpace is licensed under the GPLv3 and any contributions must be made available
|
||||||
sign a [Contributor License Agreement](https://cla-assistant.io/solvespace/solvespace).
|
under the terms of that license.
|
||||||
|
|
||||||
Contributing translations
|
Contributing translations
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
44
README.md
44
README.md
|
@ -1,5 +1,10 @@
|
||||||
|
<img src="res/freedesktop/solvespace-scalable.svg" width="70" height="70" alt="SolveSpace Logo" align="left">
|
||||||
|
|
||||||
SolveSpace
|
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
|
This repository contains the source code of [SolveSpace][], a parametric
|
||||||
2d/3d CAD.
|
2d/3d CAD.
|
||||||
|
@ -83,26 +88,29 @@ Before building, check out the project and the necessary submodules:
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
git clone https://github.com/solvespace/solvespace
|
||||||
cd solvespace
|
cd solvespace
|
||||||
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc
|
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||||
make
|
make
|
||||||
sudo make install
|
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
|
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
|
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.
|
||||||
by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
|
||||||
|
|
||||||
### Building for Windows
|
### Building for Windows
|
||||||
|
|
||||||
You will need the usual build tools, CMake, a Windows cross-compiler, and flatc.
|
Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed to work.
|
||||||
On a Debian derivative (e.g. Ubuntu) these can be installed with:
|
|
||||||
|
|
||||||
apt-get install git build-essential cmake mingw-w64 libflatbuffers-dev
|
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:
|
||||||
|
|
||||||
|
apt-get install git build-essential cmake mingw-w64
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, check out the project and the necessary submodules:
|
||||||
|
|
||||||
|
@ -110,16 +118,7 @@ Before building, check out the project and the necessary submodules:
|
||||||
cd solvespace
|
cd solvespace
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
|
|
||||||
After that, build 32-bit SolveSpace as following:
|
Build 64-bit SolveSpace with the following:
|
||||||
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw32.cmake \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DFLATC=$(which flatc)
|
|
||||||
make
|
|
||||||
|
|
||||||
Or, build 64-bit SolveSpace as following:
|
|
||||||
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
@ -157,9 +156,12 @@ After that, build SolveSpace as following:
|
||||||
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||||
make
|
make
|
||||||
|
|
||||||
|
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:
|
Alternatively, generate an XCode project, open it, and build the "Release" scheme:
|
||||||
|
|
||||||
mkdir build
|
mkdir build
|
||||||
|
@ -185,7 +187,7 @@ Before building, check out the project and the necessary submodules:
|
||||||
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
git clone https://github.com/solvespace/solvespace
|
||||||
cd solvespace
|
cd solvespace
|
||||||
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc
|
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
|
@ -257,6 +259,4 @@ and debug SolveSpace.
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt). It is possible
|
SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt).
|
||||||
to license SolveSpace for use in a commercial application; to do so,
|
|
||||||
[contact](http://solvespace.com/contact.pl) the developers.
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit f73d205bc7536991e620d3027a711e713a789967
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a9686d6ecf00e4467e772f7c0b4ef76a15f325f6
|
Subproject commit 27627843648ef84aee1621976f25bee5946e6bda
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 880db1d34706778f216a2308fd82a9a3adacb314
|
|
|
@ -45,12 +45,14 @@ parts:
|
||||||
snapcraftctl set-version "$version"
|
snapcraftctl set-version "$version"
|
||||||
git describe --exact-match HEAD && grade="stable" || grade="devel"
|
git describe --exact-match HEAD && grade="stable" || grade="devel"
|
||||||
snapcraftctl set-grade "$grade"
|
snapcraftctl set-grade "$grade"
|
||||||
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc
|
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||||
configflags:
|
configflags:
|
||||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
- -DCMAKE_BUILD_TYPE=Release
|
- -DCMAKE_BUILD_TYPE=Release
|
||||||
- -DENABLE_TESTS=OFF
|
- -DENABLE_TESTS=OFF
|
||||||
- -DSNAP=ON
|
- -DSNAP=ON
|
||||||
|
- -DENABLE_OPENMP=ON
|
||||||
|
- -DENABLE_LTO=ON
|
||||||
build-packages:
|
build-packages:
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
- libpng-dev
|
- libpng-dev
|
||||||
|
|
|
@ -213,6 +213,7 @@ add_resources(
|
||||||
icons/graphics-window/construction.png
|
icons/graphics-window/construction.png
|
||||||
icons/graphics-window/equal.png
|
icons/graphics-window/equal.png
|
||||||
icons/graphics-window/extrude.png
|
icons/graphics-window/extrude.png
|
||||||
|
icons/graphics-window/helix.png
|
||||||
icons/graphics-window/horiz.png
|
icons/graphics-window/horiz.png
|
||||||
icons/graphics-window/image.png
|
icons/graphics-window/image.png
|
||||||
icons/graphics-window/in3d.png
|
icons/graphics-window/in3d.png
|
||||||
|
@ -227,6 +228,7 @@ add_resources(
|
||||||
icons/graphics-window/point.png
|
icons/graphics-window/point.png
|
||||||
icons/graphics-window/rectangle.png
|
icons/graphics-window/rectangle.png
|
||||||
icons/graphics-window/ref.png
|
icons/graphics-window/ref.png
|
||||||
|
icons/graphics-window/revolve.png
|
||||||
icons/graphics-window/same-orientation.png
|
icons/graphics-window/same-orientation.png
|
||||||
icons/graphics-window/sketch-in-3d.png
|
icons/graphics-window/sketch-in-3d.png
|
||||||
icons/graphics-window/sketch-in-plane.png
|
icons/graphics-window/sketch-in-plane.png
|
||||||
|
@ -256,6 +258,7 @@ add_resources(
|
||||||
locales/fr_FR.po
|
locales/fr_FR.po
|
||||||
locales/uk_UA.po
|
locales/uk_UA.po
|
||||||
locales/ru_RU.po
|
locales/ru_RU.po
|
||||||
|
locales/zh_CN.po
|
||||||
fonts/unifont.hex.gz
|
fonts/unifont.hex.gz
|
||||||
fonts/private/0-check-false.png
|
fonts/private/0-check-false.png
|
||||||
fonts/private/1-check-true.png
|
fonts/private/1-check-true.png
|
||||||
|
@ -281,7 +284,7 @@ add_resources(
|
||||||
shaders/edge.frag
|
shaders/edge.frag
|
||||||
shaders/edge.vert
|
shaders/edge.vert
|
||||||
shaders/outline.vert
|
shaders/outline.vert
|
||||||
threejs/three-r76.js.gz
|
threejs/three-r111.min.js.gz
|
||||||
threejs/hammer-2.0.8.js.gz
|
threejs/hammer-2.0.8.js.gz
|
||||||
threejs/SolveSpaceControls.js)
|
threejs/SolveSpaceControls.js)
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 664 B |
Binary file not shown.
After Width: | Height: | Size: 680 B |
|
@ -5,3 +5,4 @@ en-US,0409,English (US)
|
||||||
fr-FR,040C,Français
|
fr-FR,040C,Français
|
||||||
ru-RU,0419,Русский
|
ru-RU,0419,Русский
|
||||||
uk-UA,0422,Українська
|
uk-UA,0422,Українська
|
||||||
|
zh-CN,0804,简体中文
|
||||||
|
|
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
723
res/messages.pot
723
res/messages.pot
File diff suppressed because it is too large
Load Diff
|
@ -470,9 +470,9 @@ solvespace = function(obj, params) {
|
||||||
changeBasis.makeBasis(camera.right, camera.up, n);
|
changeBasis.makeBasis(camera.right, camera.up, n);
|
||||||
|
|
||||||
for (var i = 0; i < 2; i++) {
|
for (var i = 0; i < 2; i++) {
|
||||||
var newLightPos = changeBasis.applyToVector3Array(
|
var newLightPos = new THREE.Vector3(obj.lights.d[i].direction[0],
|
||||||
[obj.lights.d[i].direction[0], obj.lights.d[i].direction[1],
|
obj.lights.d[i].direction[1],
|
||||||
obj.lights.d[i].direction[2]]);
|
obj.lights.d[i].direction[2]).applyMatrix4(changeBasis);
|
||||||
directionalLightArray[i].position.set(newLightPos[0],
|
directionalLightArray[i].position.set(newLightPos[0],
|
||||||
newLightPos[1], newLightPos[2]);
|
newLightPos[1], newLightPos[2]);
|
||||||
}
|
}
|
||||||
|
@ -515,7 +515,7 @@ solvespace = function(obj, params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
geometry.computeBoundingSphere();
|
geometry.computeBoundingSphere();
|
||||||
return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList));
|
return new THREE.Mesh(geometry, materialList);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEdges(meshObj) {
|
function createEdges(meshObj) {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -18,7 +18,7 @@ BEGIN
|
||||||
VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}"
|
VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}"
|
||||||
VALUE "OriginalFilename", "solvespace.exe"
|
VALUE "OriginalFilename", "solvespace.exe"
|
||||||
VALUE "InternalName", "solvespace"
|
VALUE "InternalName", "solvespace"
|
||||||
VALUE "LegalCopyright", "(c) 2008-2016 Jonathan Westhues and other authors"
|
VALUE "LegalCopyright", "(c) 2008-2021 Jonathan Westhues and other authors"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|
|
@ -80,8 +80,8 @@ include_directories(
|
||||||
${PNG_PNG_INCLUDE_DIR}
|
${PNG_PNG_INCLUDE_DIR}
|
||||||
${FREETYPE_INCLUDE_DIRS}
|
${FREETYPE_INCLUDE_DIRS}
|
||||||
${CAIRO_INCLUDE_DIRS}
|
${CAIRO_INCLUDE_DIRS}
|
||||||
${Q3D_INCLUDE_DIR}
|
${MIMALLOC_INCLUDE_DIR}
|
||||||
${MIMALLOC_INCLUDE_DIR})
|
${OpenMP_CXX_INCLUDE_DIRS})
|
||||||
|
|
||||||
if(Backtrace_FOUND)
|
if(Backtrace_FOUND)
|
||||||
include_directories(
|
include_directories(
|
||||||
|
@ -214,7 +214,6 @@ add_library(solvespace-core STATIC
|
||||||
${solvespace_core_SOURCES})
|
${solvespace_core_SOURCES})
|
||||||
|
|
||||||
add_dependencies(solvespace-core
|
add_dependencies(solvespace-core
|
||||||
q3d_header
|
|
||||||
mimalloc-static)
|
mimalloc-static)
|
||||||
|
|
||||||
target_link_libraries(solvespace-core
|
target_link_libraries(solvespace-core
|
||||||
|
@ -224,7 +223,6 @@ target_link_libraries(solvespace-core
|
||||||
${ZLIB_LIBRARY}
|
${ZLIB_LIBRARY}
|
||||||
${PNG_LIBRARY}
|
${PNG_LIBRARY}
|
||||||
${FREETYPE_LIBRARY}
|
${FREETYPE_LIBRARY}
|
||||||
flatbuffers
|
|
||||||
mimalloc-static)
|
mimalloc-static)
|
||||||
|
|
||||||
if(Backtrace_FOUND)
|
if(Backtrace_FOUND)
|
||||||
|
@ -241,7 +239,8 @@ if(HAVE_GETTEXT)
|
||||||
set(inputs
|
set(inputs
|
||||||
${solvespace_core_SOURCES}
|
${solvespace_core_SOURCES}
|
||||||
${solvespace_core_HEADERS}
|
${solvespace_core_HEADERS}
|
||||||
${every_platform_SOURCES})
|
${every_platform_SOURCES}
|
||||||
|
${solvespace_core_gl_SOURCES})
|
||||||
|
|
||||||
set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po)
|
set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po)
|
||||||
|
|
||||||
|
@ -404,20 +403,15 @@ endif()
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(bundle SolveSpace)
|
set(bundle SolveSpace)
|
||||||
set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS)
|
set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS)
|
||||||
|
set(bundle_resources ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/Resources/lib)
|
||||||
|
execute_process(
|
||||||
|
COMMAND mkdir -p ${bundle_resources}
|
||||||
|
COMMAND cp -p /usr/local/opt/libomp/lib/libomp.dylib ${bundle_resources}/libomp.dylib
|
||||||
|
)
|
||||||
add_custom_command(TARGET solvespace POST_BUILD
|
add_custom_command(TARGET solvespace POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_bin}
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_bin}
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:solvespace-cli> ${bundle_bin}
|
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:solvespace-cli> ${bundle_bin}
|
||||||
|
COMMAND install_name_tool -change /usr/local/opt/libomp/lib/libomp.dylib "@executable_path/../Resources/lib/libomp.dylib" ${bundle_bin}/${bundle}
|
||||||
COMMENT "Bundling executable solvespace-cli"
|
COMMENT "Bundling executable solvespace-cli"
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
|
|
||||||
add_custom_command(OUTPUT ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E remove ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg
|
|
||||||
COMMAND hdiutil create -srcfolder ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app
|
|
||||||
${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg
|
|
||||||
DEPENDS solvespace
|
|
||||||
COMMENT "Building ${bundle}.dmg"
|
|
||||||
VERBATIM)
|
|
||||||
add_custom_target(${bundle}-dmg ALL
|
|
||||||
DEPENDS ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -227,7 +227,6 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
||||||
MakeSelected(hr.entity(j));
|
MakeSelected(hr.entity(j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint *cc;
|
Constraint *cc;
|
||||||
for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) {
|
for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) {
|
||||||
Constraint c = {};
|
Constraint c = {};
|
||||||
|
@ -246,25 +245,62 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
||||||
c.reference = cc->reference;
|
c.reference = cc->reference;
|
||||||
c.disp = cc->disp;
|
c.disp = cc->disp;
|
||||||
c.comment = cc->comment;
|
c.comment = cc->comment;
|
||||||
|
bool dontAddConstraint = false;
|
||||||
switch(c.type) {
|
switch(c.type) {
|
||||||
case Constraint::Type::COMMENT:
|
case Constraint::Type::COMMENT:
|
||||||
c.disp.offset = c.disp.offset.Plus(trans);
|
c.disp.offset = c.disp.offset.Plus(trans);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constraint::Type::PT_PT_DISTANCE:
|
case Constraint::Type::PT_PT_DISTANCE:
|
||||||
case Constraint::Type::PT_LINE_DISTANCE:
|
case Constraint::Type::PT_LINE_DISTANCE:
|
||||||
case Constraint::Type::PROJ_PT_DISTANCE:
|
case Constraint::Type::PROJ_PT_DISTANCE:
|
||||||
case Constraint::Type::DIAMETER:
|
case Constraint::Type::DIAMETER:
|
||||||
c.valA *= fabs(scale);
|
c.valA *= fabs(scale);
|
||||||
break;
|
break;
|
||||||
|
case Constraint::Type::ARC_LINE_TANGENT: {
|
||||||
|
Entity *line = SK.GetEntity(c.entityB),
|
||||||
|
*arc = SK.GetEntity(c.entityA);
|
||||||
|
if(line->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||||
|
swap(line, arc);
|
||||||
|
}
|
||||||
|
Constraint::ConstrainArcLineTangent(&c, line, arc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constraint::Type::CUBIC_LINE_TANGENT: {
|
||||||
|
Entity *line = SK.GetEntity(c.entityB),
|
||||||
|
*cubic = SK.GetEntity(c.entityA);
|
||||||
|
if(line->type == Entity::Type::CUBIC) {
|
||||||
|
swap(line, cubic);
|
||||||
|
}
|
||||||
|
Constraint::ConstrainCubicLineTangent(&c, line, cubic);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constraint::Type::CURVE_CURVE_TANGENT: {
|
||||||
|
Entity *eA = SK.GetEntity(c.entityA),
|
||||||
|
*eB = SK.GetEntity(c.entityB);
|
||||||
|
Constraint::ConstrainCurveCurveTangent(&c, eA, eB);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constraint::Type::HORIZONTAL:
|
||||||
|
case Constraint::Type::VERTICAL:
|
||||||
|
// When rotating 90 or 270 degrees, swap the vertical / horizontal constaints
|
||||||
|
if (EXACT(fmod(theta + (PI/2), PI) == 0)) {
|
||||||
|
if(c.type == Constraint::Type::HORIZONTAL) {
|
||||||
|
c.type = Constraint::Type::VERTICAL;
|
||||||
|
} else {
|
||||||
|
c.type = Constraint::Type::HORIZONTAL;
|
||||||
|
}
|
||||||
|
} else if (fmod(theta, PI/2) != 0) {
|
||||||
|
dontAddConstraint = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!dontAddConstraint) {
|
||||||
hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
|
hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
|
||||||
if(c.type == Constraint::Type::COMMENT) {
|
if(c.type == Constraint::Type::COMMENT) {
|
||||||
MakeSelected(hc);
|
MakeSelected(hc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
// Copyright 2008-2013 Jonathan Westhues.
|
// Copyright 2008-2013 Jonathan Westhues.
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
#include "solvespace.h"
|
#include "solvespace.h"
|
||||||
|
#if defined(_OPENMP)
|
||||||
|
#include <omp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) {
|
void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) {
|
||||||
SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v])));
|
SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v])));
|
||||||
|
@ -389,6 +392,11 @@ void TextWindow::ShowConfiguration() {
|
||||||
Printf(false, " %Ft renderer %E%s", gl_renderer);
|
Printf(false, " %Ft renderer %E%s", gl_renderer);
|
||||||
Printf(false, " %Ft version %E%s", gl_version);
|
Printf(false, " %Ft version %E%s", gl_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(_OPENMP)
|
||||||
|
Printf(false, " %FtOpenMP enabled");
|
||||||
|
Printf(false, " %Ft threads %E%d", omp_get_max_threads());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {
|
bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {
|
||||||
|
|
|
@ -127,6 +127,66 @@ hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
|
||||||
Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false);
|
Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc) {
|
||||||
|
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
|
||||||
|
l1 = SK.GetEntity(line->point[1])->PointGetNum();
|
||||||
|
Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
|
||||||
|
a2 = SK.GetEntity(arc->point[2])->PointGetNum();
|
||||||
|
if(l0.Equals(a1) || l1.Equals(a1)) {
|
||||||
|
c->other = false;
|
||||||
|
} else if(l0.Equals(a2) || l1.Equals(a2)) {
|
||||||
|
c->other = true;
|
||||||
|
} else {
|
||||||
|
Error(_("The tangent arc and line segment must share an "
|
||||||
|
"endpoint. Constrain them with Constrain -> "
|
||||||
|
"On Point before constraining tangent."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic) {
|
||||||
|
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
|
||||||
|
l1 = SK.GetEntity(line->point[1])->PointGetNum();
|
||||||
|
Vector as = cubic->CubicGetStartNum(),
|
||||||
|
af = cubic->CubicGetFinishNum();
|
||||||
|
|
||||||
|
if(l0.Equals(as) || l1.Equals(as)) {
|
||||||
|
c->other = false;
|
||||||
|
} else if(l0.Equals(af) || l1.Equals(af)) {
|
||||||
|
c->other = true;
|
||||||
|
} else {
|
||||||
|
Error(_("The tangent cubic and line segment must share an "
|
||||||
|
"endpoint. Constrain them with Constrain -> "
|
||||||
|
"On Point before constraining tangent."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB) {
|
||||||
|
Vector as = eA->EndpointStart(),
|
||||||
|
af = eA->EndpointFinish(),
|
||||||
|
bs = eB->EndpointStart(),
|
||||||
|
bf = eB->EndpointFinish();
|
||||||
|
if(as.Equals(bs)) {
|
||||||
|
c->other = false;
|
||||||
|
c->other2 = false;
|
||||||
|
} else if(as.Equals(bf)) {
|
||||||
|
c->other = false;
|
||||||
|
c->other2 = true;
|
||||||
|
} else if(af.Equals(bs)) {
|
||||||
|
c->other = true;
|
||||||
|
c->other2 = false;
|
||||||
|
} else if(af.Equals(bf)) {
|
||||||
|
c->other = true;
|
||||||
|
c->other2 = true;
|
||||||
|
} else {
|
||||||
|
Error(_("The curves must share an endpoint. Constrain them "
|
||||||
|
"with Constrain -> On Point before constraining "
|
||||||
|
"tangent."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Constraint::MenuConstrain(Command id) {
|
void Constraint::MenuConstrain(Command id) {
|
||||||
Constraint c = {};
|
Constraint c = {};
|
||||||
c.group = SS.GW.activeGroup;
|
c.group = SS.GW.activeGroup;
|
||||||
|
@ -617,50 +677,22 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
c.entityA = gs.vector[0];
|
c.entityA = gs.vector[0];
|
||||||
c.entityB = gs.vector[1];
|
c.entityB = gs.vector[1];
|
||||||
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
||||||
Entity *line = SK.GetEntity(gs.entity[0]);
|
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||||
Entity *arc = SK.GetEntity(gs.entity[1]);
|
*arc = SK.GetEntity(gs.entity[1]);
|
||||||
if(line->type == Entity::Type::ARC_OF_CIRCLE) {
|
if(line->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||||
swap(line, arc);
|
swap(line, arc);
|
||||||
}
|
}
|
||||||
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
|
ConstrainArcLineTangent(&c, line, arc);
|
||||||
l1 = SK.GetEntity(line->point[1])->PointGetNum();
|
|
||||||
Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
|
|
||||||
a2 = SK.GetEntity(arc->point[2])->PointGetNum();
|
|
||||||
|
|
||||||
if(l0.Equals(a1) || l1.Equals(a1)) {
|
|
||||||
c.other = false;
|
|
||||||
} else if(l0.Equals(a2) || l1.Equals(a2)) {
|
|
||||||
c.other = true;
|
|
||||||
} else {
|
|
||||||
Error(_("The tangent arc and line segment must share an "
|
|
||||||
"endpoint. Constrain them with Constrain -> "
|
|
||||||
"On Point before constraining tangent."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
c.type = Type::ARC_LINE_TANGENT;
|
c.type = Type::ARC_LINE_TANGENT;
|
||||||
c.entityA = arc->h;
|
c.entityA = arc->h;
|
||||||
c.entityB = line->h;
|
c.entityB = line->h;
|
||||||
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
||||||
Entity *line = SK.GetEntity(gs.entity[0]);
|
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||||
Entity *cubic = SK.GetEntity(gs.entity[1]);
|
*cubic = SK.GetEntity(gs.entity[1]);
|
||||||
if(line->type == Entity::Type::CUBIC) {
|
if(line->type == Entity::Type::CUBIC) {
|
||||||
swap(line, cubic);
|
swap(line, cubic);
|
||||||
}
|
}
|
||||||
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
|
ConstrainCubicLineTangent(&c, line, cubic);
|
||||||
l1 = SK.GetEntity(line->point[1])->PointGetNum();
|
|
||||||
Vector as = cubic->CubicGetStartNum(),
|
|
||||||
af = cubic->CubicGetFinishNum();
|
|
||||||
|
|
||||||
if(l0.Equals(as) || l1.Equals(as)) {
|
|
||||||
c.other = false;
|
|
||||||
} else if(l0.Equals(af) || l1.Equals(af)) {
|
|
||||||
c.other = true;
|
|
||||||
} else {
|
|
||||||
Error(_("The tangent cubic and line segment must share an "
|
|
||||||
"endpoint. Constrain them with Constrain -> "
|
|
||||||
"On Point before constraining tangent."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
c.type = Type::CUBIC_LINE_TANGENT;
|
c.type = Type::CUBIC_LINE_TANGENT;
|
||||||
c.entityA = cubic->h;
|
c.entityA = cubic->h;
|
||||||
c.entityB = line->h;
|
c.entityB = line->h;
|
||||||
|
@ -671,24 +703,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
}
|
}
|
||||||
Entity *eA = SK.GetEntity(gs.entity[0]),
|
Entity *eA = SK.GetEntity(gs.entity[0]),
|
||||||
*eB = SK.GetEntity(gs.entity[1]);
|
*eB = SK.GetEntity(gs.entity[1]);
|
||||||
Vector as = eA->EndpointStart(),
|
ConstrainCurveCurveTangent(&c, eA, eB);
|
||||||
af = eA->EndpointFinish(),
|
|
||||||
bs = eB->EndpointStart(),
|
|
||||||
bf = eB->EndpointFinish();
|
|
||||||
if(as.Equals(bs)) {
|
|
||||||
c.other = false; c.other2 = false;
|
|
||||||
} else if(as.Equals(bf)) {
|
|
||||||
c.other = false; c.other2 = true;
|
|
||||||
} else if(af.Equals(bs)) {
|
|
||||||
c.other = true; c.other2 = false;
|
|
||||||
} else if(af.Equals(bf)) {
|
|
||||||
c.other = true; c.other2 = true;
|
|
||||||
} else {
|
|
||||||
Error(_("The curves must share an endpoint. Constrain them "
|
|
||||||
"with Constrain -> On Point before constraining "
|
|
||||||
"tangent."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
c.type = Type::CURVE_CURVE_TANGENT;
|
c.type = Type::CURVE_CURVE_TANGENT;
|
||||||
c.entityA = eA->h;
|
c.entityA = eA->h;
|
||||||
c.entityB = eB->h;
|
c.entityB = eB->h;
|
||||||
|
|
|
@ -47,6 +47,7 @@ void TextWindow::ScreenConstraintToggleReference(int link, uint32_t v) {
|
||||||
SS.UndoRemember();
|
SS.UndoRemember();
|
||||||
c->reference = !c->reference;
|
c->reference = !c->reference;
|
||||||
|
|
||||||
|
SS.MarkGroupDirty(c->group);
|
||||||
SS.ScheduleShowTW();
|
SS.ScheduleShowTW();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
src/draw.cpp
31
src/draw.cpp
|
@ -207,8 +207,8 @@ void GraphicsWindow::MakeSelected(Selection *stog) {
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void GraphicsWindow::SelectByMarquee() {
|
void GraphicsWindow::SelectByMarquee() {
|
||||||
Point2d marqueePoint = ProjectPoint(orig.marqueePoint);
|
Point2d marqueePoint = ProjectPoint(orig.marqueePoint);
|
||||||
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, -1),
|
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE),
|
||||||
Vector::From(orig.mouse.x, orig.mouse.y, 1));
|
Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE));
|
||||||
|
|
||||||
Entity *e;
|
Entity *e;
|
||||||
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
|
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
|
||||||
|
@ -305,9 +305,16 @@ void GraphicsWindow::GroupSelection() {
|
||||||
|
|
||||||
Camera GraphicsWindow::GetCamera() const {
|
Camera GraphicsWindow::GetCamera() const {
|
||||||
Camera camera = {};
|
Camera camera = {};
|
||||||
window->GetContentSize(&camera.width, &camera.height);
|
if(window) {
|
||||||
camera.pixelRatio = window->GetDevicePixelRatio();
|
window->GetContentSize(&camera.width, &camera.height);
|
||||||
camera.gridFit = (window->GetDevicePixelRatio() == 1);
|
camera.pixelRatio = window->GetDevicePixelRatio();
|
||||||
|
camera.gridFit = (window->GetDevicePixelRatio() == 1);
|
||||||
|
} else { // solvespace-cli
|
||||||
|
camera.width = 297.0; // A4? Whatever...
|
||||||
|
camera.height = 210.0;
|
||||||
|
camera.pixelRatio = 1.0;
|
||||||
|
camera.gridFit = camera.pixelRatio == 1.0;
|
||||||
|
}
|
||||||
camera.offset = offset;
|
camera.offset = offset;
|
||||||
camera.projUp = projUp;
|
camera.projUp = projUp;
|
||||||
camera.projRight = projRight;
|
camera.projRight = projRight;
|
||||||
|
@ -335,6 +342,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
|
||||||
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
||||||
int bestOrder = -1;
|
int bestOrder = -1;
|
||||||
int bestZIndex = 0;
|
int bestZIndex = 0;
|
||||||
|
double bestDepth = VERY_POSITIVE;
|
||||||
|
|
||||||
for(const Hover &hov : hoverList) {
|
for(const Hover &hov : hoverList) {
|
||||||
hGroup hg = {};
|
hGroup hg = {};
|
||||||
if(hov.selection.entity.v != 0) {
|
if(hov.selection.entity.v != 0) {
|
||||||
|
@ -345,9 +354,12 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
|
||||||
|
|
||||||
Group *g = SK.GetGroup(hg);
|
Group *g = SK.GetGroup(hg);
|
||||||
if(g->order > activeGroup->order) continue;
|
if(g->order > activeGroup->order) continue;
|
||||||
if(bestOrder != -1 && (bestOrder >= g->order || bestZIndex > hov.zIndex)) continue;
|
if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;
|
||||||
|
// we have hov.zIndex is >= best and hov.group is >= best (but not > active group)
|
||||||
|
if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;
|
||||||
bestOrder = g->order;
|
bestOrder = g->order;
|
||||||
bestZIndex = hov.zIndex;
|
bestZIndex = hov.zIndex;
|
||||||
|
bestDepth = hov.depth;
|
||||||
sel = hov.selection;
|
sel = hov.selection;
|
||||||
}
|
}
|
||||||
return sel;
|
return sel;
|
||||||
|
@ -363,6 +375,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
|
||||||
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
||||||
int bestOrder = -1;
|
int bestOrder = -1;
|
||||||
int bestZIndex = 0;
|
int bestZIndex = 0;
|
||||||
|
double bestDepth = VERY_POSITIVE;
|
||||||
|
|
||||||
for(const Hover &hov : hoverList) {
|
for(const Hover &hov : hoverList) {
|
||||||
hGroup hg = {};
|
hGroup hg = {};
|
||||||
if(hov.selection.entity.v != 0) {
|
if(hov.selection.entity.v != 0) {
|
||||||
|
@ -375,7 +389,9 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
|
||||||
|
|
||||||
Group *g = SK.GetGroup(hg);
|
Group *g = SK.GetGroup(hg);
|
||||||
if(g->order > activeGroup->order) continue;
|
if(g->order > activeGroup->order) continue;
|
||||||
if(bestOrder != -1 && (bestOrder >= g->order || bestZIndex > hov.zIndex)) continue;
|
if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;
|
||||||
|
// we have hov.zIndex is >= best and hov.group is >= best (but not > active group)
|
||||||
|
if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;
|
||||||
bestOrder = g->order;
|
bestOrder = g->order;
|
||||||
bestZIndex = hov.zIndex;
|
bestZIndex = hov.zIndex;
|
||||||
sel = hov.selection;
|
sel = hov.selection;
|
||||||
|
@ -432,6 +448,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
|
||||||
Hover hov = {};
|
Hover hov = {};
|
||||||
hov.distance = canvas.minDistance;
|
hov.distance = canvas.minDistance;
|
||||||
hov.zIndex = canvas.maxZIndex;
|
hov.zIndex = canvas.maxZIndex;
|
||||||
|
hov.depth = canvas.minDepth;
|
||||||
hov.selection.entity = e.h;
|
hov.selection.entity = e.h;
|
||||||
hoverList.Add(&hov);
|
hoverList.Add(&hov);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,13 +64,13 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
|
||||||
Vector proj = SS.GW.ProjectPoint3(PointGetNum());
|
Vector proj = SS.GW.ProjectPoint3(PointGetNum());
|
||||||
screenBBox = BBox::From(proj, proj);
|
screenBBox = BBox::From(proj, proj);
|
||||||
} else if(IsNormal()) {
|
} else if(IsNormal()) {
|
||||||
Vector proj = SK.GetEntity(point[0])->PointGetNum();
|
Vector proj = SS.GW.ProjectPoint3(SK.GetEntity(point[0])->PointGetNum());
|
||||||
screenBBox = BBox::From(proj, proj);
|
screenBBox = BBox::From(proj, proj);
|
||||||
} else if(!sbl->l.IsEmpty()) {
|
} else if(!sbl->l.IsEmpty()) {
|
||||||
Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]);
|
Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]);
|
||||||
screenBBox = BBox::From(first, first);
|
screenBBox = BBox::From(first, first);
|
||||||
for(auto &sb : sbl->l) {
|
for(auto &sb : sbl->l) {
|
||||||
for(int i = 0; i < sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); }
|
for(int i = 0; i <= sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); }
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
ssassert(false, "Expected entity to be a point or have beziers");
|
ssassert(false, "Expected entity to be a point or have beziers");
|
||||||
|
@ -315,9 +315,13 @@ void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const {
|
||||||
} else {
|
} else {
|
||||||
// The wrapping would work, except when n = 1 and everything
|
// The wrapping would work, except when n = 1 and everything
|
||||||
// wraps to zero...
|
// wraps to zero...
|
||||||
if(i > 0) bm.A[i][i - 1] = eq.x;
|
if(i > 0) {
|
||||||
/**/ bm.A[i][i] = eq.y;
|
bm.A[i][i - 1] = eq.x;
|
||||||
if(i < (n-1)) bm.A[i][i + 1] = eq.z;
|
}
|
||||||
|
bm.A[i][i] = eq.y;
|
||||||
|
if(i < (n-1)) {
|
||||||
|
bm.A[i][i + 1] = eq.z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bm.Solve();
|
bm.Solve();
|
||||||
|
@ -468,13 +472,13 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
|
|
||||||
int zIndex;
|
int zIndex;
|
||||||
if(IsPoint()) {
|
if(IsPoint()) {
|
||||||
zIndex = 5;
|
zIndex = 6;
|
||||||
} else if(how == DrawAs::HIDDEN) {
|
} else if(how == DrawAs::HIDDEN) {
|
||||||
zIndex = 2;
|
zIndex = 2;
|
||||||
} else if(group != SS.GW.activeGroup) {
|
} else if(group != SS.GW.activeGroup) {
|
||||||
zIndex = 3;
|
zIndex = 3;
|
||||||
} else {
|
} else {
|
||||||
zIndex = 4;
|
zIndex = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
hStyle hs;
|
hStyle hs;
|
||||||
|
@ -484,6 +488,9 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
hs.v = Style::NORMALS;
|
hs.v = Style::NORMALS;
|
||||||
} else {
|
} else {
|
||||||
hs = Style::ForEntity(h);
|
hs = Style::ForEntity(h);
|
||||||
|
if (hs.v == Style::CONSTRUCTION) {
|
||||||
|
zIndex = 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas::Stroke stroke = Style::Stroke(hs);
|
Canvas::Stroke stroke = Style::Stroke(hs);
|
||||||
|
@ -608,7 +615,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
||||||
double w = 60 - camera.width / 2.0;
|
double w = 60 - camera.width / 2.0;
|
||||||
// Shift the axis to the right if they would overlap with the toolbar.
|
// Shift the axis to the right if they would overlap with the toolbar.
|
||||||
if(SS.showToolbar) {
|
if(SS.showToolbar) {
|
||||||
if(h + 30 > -(34*16 + 3*16 + 8) / 2)
|
if(h + 30 > -(32*18 + 3*16 + 8) / 2)
|
||||||
w += 60;
|
w += 60;
|
||||||
}
|
}
|
||||||
tail = camera.projRight.ScaledBy(w/s).Plus(
|
tail = camera.projRight.ScaledBy(w/s).Plus(
|
||||||
|
|
46
src/dsc.h
46
src/dsc.h
|
@ -155,6 +155,52 @@ inline bool Vector::Equals(Vector v, double tol) const {
|
||||||
return dv.MagSquared() < tol*tol;
|
return dv.MagSquared() < tol*tol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Vector Vector::From(double x, double y, double z) {
|
||||||
|
return {x, y, z};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vector Vector::Plus(Vector b) const {
|
||||||
|
return {x + b.x, y + b.y, z + b.z};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vector Vector::Minus(Vector b) const {
|
||||||
|
return {x - b.x, y - b.y, z - b.z};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vector Vector::Negated() const {
|
||||||
|
return {-x, -y, -z};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vector Vector::Cross(Vector b) const {
|
||||||
|
return {-(z * b.y) + (y * b.z), (z * b.x) - (x * b.z), -(y * b.x) + (x * b.y)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double Vector::Dot(Vector b) const {
|
||||||
|
return (x * b.x + y * b.y + z * b.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double Vector::MagSquared() const {
|
||||||
|
return x * x + y * y + z * z;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double Vector::Magnitude() const {
|
||||||
|
return sqrt(x * x + y * y + z * z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vector Vector::ScaledBy(const double v) const {
|
||||||
|
return {x * v, y * v, z * v};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const {
|
||||||
|
maxv->x = max(maxv->x, x);
|
||||||
|
maxv->y = max(maxv->y, y);
|
||||||
|
maxv->z = max(maxv->z, z);
|
||||||
|
|
||||||
|
minv->x = min(minv->x, x);
|
||||||
|
minv->y = min(minv->y, y);
|
||||||
|
minv->z = min(minv->z, z);
|
||||||
|
}
|
||||||
|
|
||||||
struct VectorHash {
|
struct VectorHash {
|
||||||
size_t operator()(const Vector &v) const;
|
size_t operator()(const Vector &v) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -843,8 +843,6 @@ void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) {
|
||||||
ExportMeshAsObjTo(f, fMtl, m);
|
ExportMeshAsObjTo(f, fMtl, m);
|
||||||
|
|
||||||
fclose(fMtl);
|
fclose(fMtl);
|
||||||
} else if(filename.HasExtension("q3do")) {
|
|
||||||
ExportMeshAsQ3doTo(f, m);
|
|
||||||
} else if(filename.HasExtension("js") ||
|
} else if(filename.HasExtension("js") ||
|
||||||
filename.HasExtension("html")) {
|
filename.HasExtension("html")) {
|
||||||
SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines);
|
SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines);
|
||||||
|
@ -898,54 +896,6 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
// Export the mesh as a Q3DO (https://github.com/q3k/q3d) file.
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#include "q3d_object_generated.h"
|
|
||||||
void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) {
|
|
||||||
flatbuffers::FlatBufferBuilder builder(1024);
|
|
||||||
double s = SS.exportScale;
|
|
||||||
|
|
||||||
// Create a material for every colour used, keep note of triangles belonging to color/material.
|
|
||||||
std::map<RgbaColor, flatbuffers::Offset<q3d::Material>, RgbaColorCompare> materials;
|
|
||||||
std::map<RgbaColor, std::vector<flatbuffers::Offset<q3d::Triangle>>, RgbaColorCompare> materialTriangles;
|
|
||||||
for (const STriangle &t : sm->l) {
|
|
||||||
auto color = t.meta.color;
|
|
||||||
if (materials.find(color) == materials.end()) {
|
|
||||||
auto name = builder.CreateString(ssprintf("Color #%02x%02x%02x%02x", color.red, color.green, color.blue, color.alpha));
|
|
||||||
auto co = q3d::CreateColor(builder, color.red, color.green, color.blue, color.alpha);
|
|
||||||
auto mo = q3d::CreateMaterial(builder, name, co);
|
|
||||||
materials.emplace(color, mo);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector faceNormal = t.Normal();
|
|
||||||
auto a = q3d::Vector3((float)(t.a.x/s), (float)(t.a.y/s), (float)(t.a.z/s));
|
|
||||||
auto b = q3d::Vector3((float)(t.b.x/s), (float)(t.b.y/s), (float)(t.b.z/s));
|
|
||||||
auto c = q3d::Vector3((float)(t.c.x/s), (float)(t.c.y/s), (float)(t.c.z/s));
|
|
||||||
auto fn = q3d::Vector3((float)faceNormal.x, (float)faceNormal.y, (float)faceNormal.x);
|
|
||||||
auto n1 = q3d::Vector3((float)t.normals[0].x, (float)t.normals[0].y, (float)t.normals[0].z);
|
|
||||||
auto n2 = q3d::Vector3((float)t.normals[1].x, (float)t.normals[1].y, (float)t.normals[1].z);
|
|
||||||
auto n3 = q3d::Vector3((float)t.normals[2].x, (float)t.normals[2].y, (float)t.normals[2].z);
|
|
||||||
auto tri = q3d::CreateTriangle(builder, &a, &b, &c, &fn, &n1, &n2, &n3);
|
|
||||||
materialTriangles[color].push_back(tri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build all meshes sorted by material.
|
|
||||||
std::vector<flatbuffers::Offset<q3d::Mesh>> meshes;
|
|
||||||
for (auto &it : materials) {
|
|
||||||
auto &mato = it.second;
|
|
||||||
auto to = builder.CreateVector(materialTriangles[it.first]);
|
|
||||||
auto mo = q3d::CreateMesh(builder, to, mato);
|
|
||||||
meshes.push_back(mo);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mo = builder.CreateVector(meshes);
|
|
||||||
auto o = q3d::CreateObject(builder, mo);
|
|
||||||
q3d::FinishObjectBuffer(builder, o);
|
|
||||||
fwrite(builder.GetBufferPointer(), builder.GetSize(), 1, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Export the mesh as Wavefront OBJ format. This requires us to reduce all the
|
// Export the mesh as Wavefront OBJ format. This requires us to reduce all the
|
||||||
// identical vertices to the same identifier, so do that first.
|
// identical vertices to the same identifier, so do that first.
|
||||||
|
@ -1006,15 +956,20 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename
|
||||||
SPointList spl = {};
|
SPointList spl = {};
|
||||||
STriangle *tr;
|
STriangle *tr;
|
||||||
Vector bndl, bndh;
|
Vector bndl, bndh;
|
||||||
|
|
||||||
|
const std::string THREE_FN("three-r111.min.js");
|
||||||
|
const std::string HAMMER_FN("hammer-2.0.8.js");
|
||||||
|
const std::string CONTROLS_FN("SolveSpaceControls.js");
|
||||||
|
|
||||||
const char htmlbegin[] = R"(
|
const char htmlbegin[] = R"(
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"></meta>
|
<meta charset="utf-8"></meta>
|
||||||
<title>Three.js Solvespace Mesh</title>
|
<title>Three.js Solvespace Mesh</title>
|
||||||
<script id="three-r76.js">%s</script>
|
<script id="%s">%s</script>
|
||||||
<script id="hammer-2.0.8.js">%s</script>
|
<script id="%s">%s</script>
|
||||||
<script id="SolveSpaceControls.js">%s</script>
|
<script id="%s">%s</script>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body { margin: 0; overflow: hidden; }
|
body { margin: 0; overflow: hidden; }
|
||||||
</style>
|
</style>
|
||||||
|
@ -1064,9 +1019,12 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename
|
||||||
|
|
||||||
if(filename.HasExtension("html")) {
|
if(filename.HasExtension("html")) {
|
||||||
fprintf(f, htmlbegin,
|
fprintf(f, htmlbegin,
|
||||||
LoadStringFromGzip("threejs/three-r76.js.gz").c_str(),
|
THREE_FN.c_str(),
|
||||||
LoadStringFromGzip("threejs/hammer-2.0.8.js.gz").c_str(),
|
LoadStringFromGzip("threejs/" + THREE_FN + ".gz").c_str(),
|
||||||
LoadString("threejs/SolveSpaceControls.js").c_str());
|
HAMMER_FN.c_str(),
|
||||||
|
LoadStringFromGzip("threejs/" + HAMMER_FN + ".gz").c_str(),
|
||||||
|
CONTROLS_FN.c_str(),
|
||||||
|
LoadString("threejs/" + CONTROLS_FN).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(f, "var solvespace_model_%s = {\n"
|
fprintf(f, "var solvespace_model_%s = {\n"
|
||||||
|
|
|
@ -273,9 +273,45 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(f, "),#%d,.T.);\n", srfid);
|
fprintf(f, "),#%d,.T.);\n", srfid);
|
||||||
fprintf(f, "\n");
|
|
||||||
advancedFaces.Add(&advFaceId);
|
advancedFaces.Add(&advFaceId);
|
||||||
|
|
||||||
|
// Export the surface color and transparency
|
||||||
|
// https://www.cax-if.org/documents/rec_prac_styling_org_v16.pdf sections 4.4.2 4.2.4 etc.
|
||||||
|
// https://tracker.dev.opencascade.org/view.php?id=31550
|
||||||
|
fprintf(f, "#%d=COLOUR_RGB('',%.2f,%.2f,%.2f);\n", ++id, ss->color.redF(),
|
||||||
|
ss->color.greenF(), ss->color.blueF());
|
||||||
|
|
||||||
|
/* // This works in Kisters 3DViewStation but not in KiCAD and Horison EDA,
|
||||||
|
// it seems they do not support transparency so use the more verbose one below
|
||||||
|
fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF());
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n",
|
||||||
|
id, id - 2, id - 1);
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d));\n", id, id - 1);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This works in Horison EDA but is more verbose.
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=FILL_AREA_STYLE_COLOUR('',#%d);\n", id, id - 1);
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=FILL_AREA_STYLE('',(#%d));\n", id, id - 1);
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=SURFACE_STYLE_FILL_AREA(#%d);\n", id, id - 1);
|
||||||
|
fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF());
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n", id, id - 5, id - 1);
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d, #%d));\n", id, id - 3, id - 1);
|
||||||
|
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=SURFACE_STYLE_USAGE(.BOTH.,#%d);\n", id, id - 1);
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=PRESENTATION_STYLE_ASSIGNMENT((#%d));\n", id, id - 1);
|
||||||
|
++id;
|
||||||
|
fprintf(f, "#%d=STYLED_ITEM('',(#%d),#%d);\n", id, id - 1, advFaceId);
|
||||||
|
fprintf(f, "\n");
|
||||||
|
|
||||||
id++;
|
id++;
|
||||||
listOfLoops.Clear();
|
listOfLoops.Clear();
|
||||||
}
|
}
|
||||||
|
|
13
src/file.cpp
13
src/file.cpp
|
@ -468,6 +468,7 @@ void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, cha
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) {
|
bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) {
|
||||||
|
bool fileIsEmpty = true;
|
||||||
allConsistent = false;
|
allConsistent = false;
|
||||||
fileLoadError = false;
|
fileLoadError = false;
|
||||||
|
|
||||||
|
@ -485,6 +486,8 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel)
|
||||||
|
|
||||||
char line[1024];
|
char line[1024];
|
||||||
while(fgets(line, (int)sizeof(line), fh)) {
|
while(fgets(line, (int)sizeof(line), fh)) {
|
||||||
|
fileIsEmpty = false;
|
||||||
|
|
||||||
char *s = strchr(line, '\n');
|
char *s = strchr(line, '\n');
|
||||||
if(s) *s = '\0';
|
if(s) *s = '\0';
|
||||||
// We should never get files with \r characters in them, but mailers
|
// We should never get files with \r characters in them, but mailers
|
||||||
|
@ -545,6 +548,11 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel)
|
||||||
|
|
||||||
fclose(fh);
|
fclose(fh);
|
||||||
|
|
||||||
|
if(fileIsEmpty) {
|
||||||
|
Error(_("The file is empty. It may be corrupt."));
|
||||||
|
NewFile();
|
||||||
|
}
|
||||||
|
|
||||||
if(fileLoadError) {
|
if(fileLoadError) {
|
||||||
Error(_("Unrecognized data in file. This file may be corrupt, or "
|
Error(_("Unrecognized data in file. This file may be corrupt, or "
|
||||||
"from a newer version of the program."));
|
"from a newer version of the program."));
|
||||||
|
@ -904,11 +912,13 @@ try_again:
|
||||||
} else if(linkMap.count(g.linkFile) == 0) {
|
} else if(linkMap.count(g.linkFile) == 0) {
|
||||||
dbp("Missing file for group: %s", g.name.c_str());
|
dbp("Missing file for group: %s", g.name.c_str());
|
||||||
// The file was moved; prompt the user for its new location.
|
// The file was moved; prompt the user for its new location.
|
||||||
switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) {
|
const auto linkFileRelative = g.linkFile.RelativeTo(saveFile);
|
||||||
|
switch(LocateImportedFile(linkFileRelative, canCancel)) {
|
||||||
case Platform::MessageDialog::Response::YES: {
|
case Platform::MessageDialog::Response::YES: {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||||
dialog->ThawChoices(settings, "LinkSketch");
|
dialog->ThawChoices(settings, "LinkSketch");
|
||||||
|
dialog->SuggestFilename(linkFileRelative);
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
dialog->FreezeChoices(settings, "LinkSketch");
|
dialog->FreezeChoices(settings, "LinkSketch");
|
||||||
linkMap[g.linkFile] = dialog->GetFilename();
|
linkMap[g.linkFile] = dialog->GetFilename();
|
||||||
|
@ -985,6 +995,7 @@ bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
|
||||||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::RasterFileFilters);
|
dialog->AddFilters(Platform::RasterFileFilters);
|
||||||
dialog->ThawChoices(settings, "LinkImage");
|
dialog->ThawChoices(settings, "LinkImage");
|
||||||
|
dialog->SuggestFilename(filename->RelativeTo(saveFile));
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
dialog->FreezeChoices(settings, "LinkImage");
|
dialog->FreezeChoices(settings, "LinkImage");
|
||||||
*filename = dialog->GetFilename();
|
*filename = dialog->GetFilename();
|
||||||
|
|
|
@ -92,6 +92,7 @@ const MenuEntry Menu[] = {
|
||||||
{ 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView },
|
{ 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView },
|
{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView },
|
||||||
|
{ 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView },
|
||||||
{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView },
|
{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView },
|
||||||
{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL },
|
{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL },
|
||||||
{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView },
|
{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView },
|
||||||
|
@ -312,6 +313,8 @@ void GraphicsWindow::PopulateMainMenu() {
|
||||||
|
|
||||||
if(Menu[i].cmd == Command::SHOW_GRID) {
|
if(Menu[i].cmd == Command::SHOW_GRID) {
|
||||||
showGridMenuItem = menuItem;
|
showGridMenuItem = menuItem;
|
||||||
|
} else if(Menu[i].cmd == Command::DIM_SOLID_MODEL) {
|
||||||
|
dimSolidModelMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
|
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
|
||||||
perspectiveProjMenuItem = menuItem;
|
perspectiveProjMenuItem = menuItem;
|
||||||
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
|
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
|
||||||
|
@ -406,6 +409,7 @@ void GraphicsWindow::Init() {
|
||||||
showTextWindow = true;
|
showTextWindow = true;
|
||||||
|
|
||||||
showSnapGrid = false;
|
showSnapGrid = false;
|
||||||
|
dimSolidModel = true;
|
||||||
context.active = false;
|
context.active = false;
|
||||||
toolbarHovered = Command::NONE;
|
toolbarHovered = Command::NONE;
|
||||||
|
|
||||||
|
@ -722,6 +726,12 @@ void GraphicsWindow::MenuView(Command id) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Command::DIM_SOLID_MODEL:
|
||||||
|
SS.GW.dimSolidModel = !SS.GW.dimSolidModel;
|
||||||
|
SS.GW.EnsureValidActives();
|
||||||
|
SS.GW.Invalidate(/*clearPersistent=*/true);
|
||||||
|
break;
|
||||||
|
|
||||||
case Command::PERSPECTIVE_PROJ:
|
case Command::PERSPECTIVE_PROJ:
|
||||||
SS.usePerspectiveProj = !SS.usePerspectiveProj;
|
SS.usePerspectiveProj = !SS.usePerspectiveProj;
|
||||||
SS.GW.EnsureValidActives();
|
SS.GW.EnsureValidActives();
|
||||||
|
@ -923,6 +933,7 @@ void GraphicsWindow::EnsureValidActives() {
|
||||||
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
|
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
|
||||||
|
|
||||||
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
|
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
|
||||||
|
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
|
||||||
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
|
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
|
||||||
showToolbarMenuItem->SetActive(SS.showToolbar);
|
showToolbarMenuItem->SetActive(SS.showToolbar);
|
||||||
fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen());
|
fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen());
|
||||||
|
@ -1175,10 +1186,7 @@ void GraphicsWindow::MenuEdit(Command id) {
|
||||||
}
|
}
|
||||||
// Regenerate, with these points marked as dragged so that they
|
// Regenerate, with these points marked as dragged so that they
|
||||||
// get placed as close as possible to our snap grid.
|
// get placed as close as possible to our snap grid.
|
||||||
SS.GW.ClearPending();
|
|
||||||
|
|
||||||
SS.GW.ClearSelection();
|
SS.GW.ClearSelection();
|
||||||
SS.GW.Invalidate();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -569,7 +569,8 @@ void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
|
||||||
if(!SS.GW.showShaded) {
|
if(!SS.GW.showShaded) {
|
||||||
fillFront.layer = Canvas::Layer::DEPTH_ONLY;
|
fillFront.layer = Canvas::Layer::DEPTH_ONLY;
|
||||||
}
|
}
|
||||||
if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) {
|
if((type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE)
|
||||||
|
&& SS.GW.dimSolidModel) {
|
||||||
fillFront.color = Style::Color(Style::DIM_SOLID);
|
fillFront.color = Style::Color(Style::DIM_SOLID);
|
||||||
}
|
}
|
||||||
Canvas::hFill hcfFront = canvas->GetFill(fillFront);
|
Canvas::hFill hcfFront = canvas->GetFill(fillFront);
|
||||||
|
|
|
@ -236,21 +236,18 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
|
||||||
|
|
||||||
Vector u = q.RotationU(), v = q.RotationV();
|
Vector u = q.RotationU(), v = q.RotationV();
|
||||||
double r = pa.Minus(center).Magnitude();
|
double r = pa.Minus(center).Magnitude();
|
||||||
double thetaa, thetab, dtheta;
|
double theta, dtheta;
|
||||||
|
|
||||||
if(angle == 360.0) {
|
if(angle == 360.0) {
|
||||||
thetaa = 0;
|
theta = 0;
|
||||||
thetab = 2*PI;
|
|
||||||
dtheta = 2*PI;
|
|
||||||
} else {
|
} else {
|
||||||
Point2d c2 = center.Project2d(u, v);
|
Point2d c2 = center.Project2d(u, v);
|
||||||
Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
|
Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
|
||||||
Point2d pb2 = (pb.Project2d(u, v)).Minus(c2);
|
|
||||||
|
|
||||||
thetaa = atan2(pa2.y, pa2.x);
|
theta = atan2(pa2.y, pa2.x);
|
||||||
thetab = atan2(pb2.y, pb2.x);
|
|
||||||
dtheta = thetab - thetaa;
|
|
||||||
}
|
}
|
||||||
|
dtheta = angle * PI/180;
|
||||||
|
|
||||||
int i, n;
|
int i, n;
|
||||||
if(dtheta > (3*PI/2 + 0.01)) {
|
if(dtheta > (3*PI/2 + 0.01)) {
|
||||||
n = 4;
|
n = 4;
|
||||||
|
@ -266,17 +263,17 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
|
||||||
for(i = 0; i < n; i++) {
|
for(i = 0; i < n; i++) {
|
||||||
double s, c;
|
double s, c;
|
||||||
|
|
||||||
c = cos(thetaa);
|
c = cos(theta);
|
||||||
s = sin(thetaa);
|
s = sin(theta);
|
||||||
// The start point of the curve, and the tangent vector at
|
// The start point of the curve, and the tangent vector at
|
||||||
// that start point.
|
// that start point.
|
||||||
Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
||||||
t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
||||||
|
|
||||||
thetaa += dtheta;
|
theta += dtheta;
|
||||||
|
|
||||||
c = cos(thetaa);
|
c = cos(theta);
|
||||||
s = sin(thetaa);
|
s = sin(theta);
|
||||||
Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
||||||
t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
||||||
|
|
||||||
|
@ -335,6 +332,7 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
|
|
||||||
double board_thickness = 10.0;
|
double board_thickness = 10.0;
|
||||||
double scale = 1.0; //mm
|
double scale = 1.0; //mm
|
||||||
|
bool topEntities, bottomEntities;
|
||||||
|
|
||||||
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
|
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
|
||||||
hEntity hnorm = newNormal(el, &entityCount, normal);
|
hEntity hnorm = newNormal(el, &entityCount, normal);
|
||||||
|
@ -347,6 +345,7 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
for(std::string line; getline( stream, line ); ) {
|
for(std::string line; getline( stream, line ); ) {
|
||||||
if (line.find(".END_") == 0) {
|
if (line.find(".END_") == 0) {
|
||||||
section = none;
|
section = none;
|
||||||
|
curve = -1;
|
||||||
}
|
}
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case none:
|
case none:
|
||||||
|
@ -356,6 +355,10 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
} else if (line.find(".BOARD_OUTLINE") == 0) {
|
} else if (line.find(".BOARD_OUTLINE") == 0) {
|
||||||
section = board_outline;
|
section = board_outline;
|
||||||
record_number = 1;
|
record_number = 1;
|
||||||
|
// no keepouts for now - they should also be shown as construction?
|
||||||
|
// } else if (line.find(".ROUTE_KEEPOUT") == 0) {
|
||||||
|
// section = routing_keepout;
|
||||||
|
// record_number = 1;
|
||||||
} else if(line.find(".DRILLED_HOLES") == 0) {
|
} else if(line.find(".DRILLED_HOLES") == 0) {
|
||||||
section = drilled_holes;
|
section = drilled_holes;
|
||||||
record_number = 1;
|
record_number = 1;
|
||||||
|
@ -375,11 +378,23 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case routing_keepout:
|
||||||
case board_outline:
|
case board_outline:
|
||||||
if (record_number == 2) {
|
if (record_number == 2) {
|
||||||
board_thickness = std::stod(line) * scale;
|
if(section == board_outline) {
|
||||||
dbp("IDF board thickness: %lf", board_thickness);
|
topEntities = true;
|
||||||
|
bottomEntities = true;
|
||||||
|
board_thickness = std::stod(line) * scale;
|
||||||
|
dbp("IDF board thickness: %lf", board_thickness);
|
||||||
|
} else if (section == routing_keepout) {
|
||||||
|
topEntities = false;
|
||||||
|
bottomEntities = false;
|
||||||
|
if(line.find("TOP") == 0 || line.find("BOTH") == 0)
|
||||||
|
topEntities = true;
|
||||||
|
if(line.find("BOTTOM") == 0 || line.find("BOTH") == 0)
|
||||||
|
bottomEntities = true;
|
||||||
|
}
|
||||||
} else { // records 3+ are lines, arcs, and circles
|
} else { // records 3+ are lines, arcs, and circles
|
||||||
std::vector <std::string> values = splitString(line);
|
std::vector <std::string> values = splitString(line);
|
||||||
if(values.size() != 4) continue;
|
if(values.size() != 4) continue;
|
||||||
|
@ -391,36 +406,43 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
Vector pTop = Vector::From(x,y,board_thickness);
|
Vector pTop = Vector::From(x,y,board_thickness);
|
||||||
if(c != curve) { // start a new curve
|
if(c != curve) { // start a new curve
|
||||||
curve = c;
|
curve = c;
|
||||||
hprev = newPoint(el, &entityCount, point, /*visible=*/false);
|
if (bottomEntities)
|
||||||
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
|
hprev = newPoint(el, &entityCount, point, /*visible=*/false);
|
||||||
|
if (topEntities)
|
||||||
|
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
|
||||||
pprev = point;
|
pprev = point;
|
||||||
pprevTop = pTop;
|
pprevTop = pTop;
|
||||||
} else {
|
} else {
|
||||||
// create a bezier for the extrusion
|
if(section == board_outline) {
|
||||||
if (ang == 0) {
|
// create a bezier for the extrusion
|
||||||
// straight lines
|
if (ang == 0) {
|
||||||
SBezier sb = SBezier::From(pprev, point);
|
// straight lines
|
||||||
sbl.l.Add(&sb);
|
SBezier sb = SBezier::From(pprev, point);
|
||||||
} else if (ang != 360.0) {
|
sbl.l.Add(&sb);
|
||||||
// Arcs
|
} else if (ang != 360.0) {
|
||||||
Vector c = ArcCenter(pprev, point, ang);
|
// Arcs
|
||||||
MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
|
Vector c = ArcCenter(pprev, point, ang);
|
||||||
} else {
|
MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
|
||||||
// circles
|
} else {
|
||||||
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
|
// circles
|
||||||
|
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// next create the entities
|
// next create the entities
|
||||||
// only curves and points at circle centers will be visible
|
// only curves and points at circle centers will be visible
|
||||||
bool vis = (ang == 360.0);
|
bool vis = (ang == 360.0);
|
||||||
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
|
if (bottomEntities) {
|
||||||
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
|
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
|
||||||
pprev = point;
|
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
|
||||||
hprev = hp;
|
pprev = point;
|
||||||
hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
|
hprev = hp;
|
||||||
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
|
}
|
||||||
pprevTop = pTop;
|
if (topEntities) {
|
||||||
hprevTop = hp;
|
hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
|
||||||
|
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
|
||||||
|
pprevTop = pTop;
|
||||||
|
hprevTop = hp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -428,7 +450,6 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
||||||
case other_outline:
|
case other_outline:
|
||||||
case routing_outline:
|
case routing_outline:
|
||||||
case placement_outline:
|
case placement_outline:
|
||||||
case routing_keepout:
|
|
||||||
case via_keepout:
|
case via_keepout:
|
||||||
case placement_group:
|
case placement_group:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -305,7 +305,6 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
||||||
HitTestMakeSelection(mp);
|
HitTestMakeSelection(mp);
|
||||||
SS.MarkGroupDirtyByEntity(pending.point);
|
SS.MarkGroupDirtyByEntity(pending.point);
|
||||||
orig.mouse = mp;
|
orig.mouse = mp;
|
||||||
Invalidate();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Pending::DRAGGING_POINTS:
|
case Pending::DRAGGING_POINTS:
|
||||||
|
@ -624,24 +623,24 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
||||||
}
|
}
|
||||||
if(gs.withEndpoints > 0) {
|
if(gs.withEndpoints > 0) {
|
||||||
menu->AddItem(_("Select Edge Chain"),
|
menu->AddItem(_("Select Edge Chain"),
|
||||||
[this]() { MenuEdit(Command::SELECT_CHAIN); });
|
[]() { MenuEdit(Command::SELECT_CHAIN); });
|
||||||
}
|
}
|
||||||
if(gs.constraints == 1 && gs.n == 0) {
|
if(gs.constraints == 1 && gs.n == 0) {
|
||||||
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
||||||
if(c->HasLabel() && c->type != Constraint::Type::COMMENT) {
|
if(c->HasLabel() && c->type != Constraint::Type::COMMENT) {
|
||||||
menu->AddItem(_("Toggle Reference Dimension"),
|
menu->AddItem(_("Toggle Reference Dimension"),
|
||||||
[]() { Constraint::MenuConstrain(Command::REFERENCE); });
|
[]() { Constraint::MenuConstrain(Command::REFERENCE); });
|
||||||
}
|
}
|
||||||
if(c->type == Constraint::Type::ANGLE ||
|
if(c->type == Constraint::Type::ANGLE ||
|
||||||
c->type == Constraint::Type::EQUAL_ANGLE)
|
c->type == Constraint::Type::EQUAL_ANGLE)
|
||||||
{
|
{
|
||||||
menu->AddItem(_("Other Supplementary Angle"),
|
menu->AddItem(_("Other Supplementary Angle"),
|
||||||
[]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); });
|
[]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(gs.constraintLabels > 0 || gs.points > 0) {
|
if(gs.constraintLabels > 0 || gs.points > 0) {
|
||||||
menu->AddItem(_("Snap to Grid"),
|
menu->AddItem(_("Snap to Grid"),
|
||||||
[this]() { MenuEdit(Command::SNAP_TO_GRID); });
|
[]() { MenuEdit(Command::SNAP_TO_GRID); });
|
||||||
}
|
}
|
||||||
|
|
||||||
if(gs.points == 1 && gs.point[0].isFromRequest()) {
|
if(gs.points == 1 && gs.point[0].isFromRequest()) {
|
||||||
|
@ -714,7 +713,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
||||||
}
|
}
|
||||||
if(gs.entities == gs.n) {
|
if(gs.entities == gs.n) {
|
||||||
menu->AddItem(_("Toggle Construction"),
|
menu->AddItem(_("Toggle Construction"),
|
||||||
[this]() { MenuRequest(Command::CONSTRUCTION); });
|
[]() { MenuRequest(Command::CONSTRUCTION); });
|
||||||
}
|
}
|
||||||
|
|
||||||
if(gs.points == 1) {
|
if(gs.points == 1) {
|
||||||
|
@ -748,28 +747,28 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
||||||
menu->AddSeparator();
|
menu->AddSeparator();
|
||||||
if(LockedInWorkplane()) {
|
if(LockedInWorkplane()) {
|
||||||
menu->AddItem(_("Cut"),
|
menu->AddItem(_("Cut"),
|
||||||
[this]() { MenuClipboard(Command::CUT); });
|
[]() { MenuClipboard(Command::CUT); });
|
||||||
menu->AddItem(_("Copy"),
|
menu->AddItem(_("Copy"),
|
||||||
[this]() { MenuClipboard(Command::COPY); });
|
[]() { MenuClipboard(Command::COPY); });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
menu->AddItem(_("Select All"),
|
menu->AddItem(_("Select All"),
|
||||||
[this]() { MenuEdit(Command::SELECT_ALL); });
|
[]() { MenuEdit(Command::SELECT_ALL); });
|
||||||
}
|
}
|
||||||
|
|
||||||
if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) {
|
if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) {
|
||||||
menu->AddItem(_("Paste"),
|
menu->AddItem(_("Paste"),
|
||||||
[this]() { MenuClipboard(Command::PASTE); });
|
[]() { MenuClipboard(Command::PASTE); });
|
||||||
menu->AddItem(_("Paste Transformed..."),
|
menu->AddItem(_("Paste Transformed..."),
|
||||||
[this]() { MenuClipboard(Command::PASTE_TRANSFORM); });
|
[]() { MenuClipboard(Command::PASTE_TRANSFORM); });
|
||||||
}
|
}
|
||||||
|
|
||||||
if(itemsSelected) {
|
if(itemsSelected) {
|
||||||
menu->AddItem(_("Delete"),
|
menu->AddItem(_("Delete"),
|
||||||
[this]() { MenuClipboard(Command::DELETE); });
|
[]() { MenuClipboard(Command::DELETE); });
|
||||||
menu->AddSeparator();
|
menu->AddSeparator();
|
||||||
menu->AddItem(_("Unselect All"),
|
menu->AddItem(_("Unselect All"),
|
||||||
[this]() { MenuEdit(Command::UNSELECT_ALL); });
|
[]() { MenuEdit(Command::UNSELECT_ALL); });
|
||||||
}
|
}
|
||||||
// If only one item is selected, then it must be the one that we just
|
// If only one item is selected, then it must be the one that we just
|
||||||
// selected from the hovered item; in which case unselect all and hovered
|
// selected from the hovered item; in which case unselect all and hovered
|
||||||
|
@ -785,7 +784,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
||||||
if(itemsSelected) {
|
if(itemsSelected) {
|
||||||
menu->AddSeparator();
|
menu->AddSeparator();
|
||||||
menu->AddItem(_("Zoom to Fit"),
|
menu->AddItem(_("Zoom to Fit"),
|
||||||
[this]() { MenuView(Command::ZOOM_TO_FIT); });
|
[]() { MenuView(Command::ZOOM_TO_FIT); });
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->PopUp();
|
menu->PopUp();
|
||||||
|
@ -877,7 +876,6 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projecte
|
||||||
|
|
||||||
bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
||||||
using Platform::MouseEvent;
|
using Platform::MouseEvent;
|
||||||
|
|
||||||
double width, height;
|
double width, height;
|
||||||
window->GetContentSize(&width, &height);
|
window->GetContentSize(&width, &height);
|
||||||
|
|
||||||
|
@ -918,7 +916,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MouseEvent::Type::SCROLL_VERT:
|
case MouseEvent::Type::SCROLL_VERT:
|
||||||
this->MouseScroll(event.x, event.y, (int)event.scrollDelta);
|
this->MouseScroll(event.x, event.y, event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MouseEvent::Type::LEAVE:
|
case MouseEvent::Type::LEAVE:
|
||||||
|
@ -1117,7 +1115,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct
|
||||||
AddToPending(hr);
|
AddToPending(hr);
|
||||||
Request *r = SK.GetRequest(hr);
|
Request *r = SK.GetRequest(hr);
|
||||||
r->str = "Abc";
|
r->str = "Abc";
|
||||||
r->font = "BitstreamVeraSans-Roman-builtin.ttf";
|
r->font = Platform::embeddedFont;
|
||||||
|
|
||||||
for(int i = 1; i <= 4; i++) {
|
for(int i = 1; i <= 4; i++) {
|
||||||
SK.GetEntity(hr.entity(i))->PointForceTo(v);
|
SK.GetEntity(hr.entity(i))->PointForceTo(v);
|
||||||
|
@ -1472,18 +1470,25 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsWindow::MouseScroll(double x, double y, int delta) {
|
void GraphicsWindow::MouseScroll(double x, double y, double delta) {
|
||||||
double offsetRight = offset.Dot(projRight);
|
double offsetRight = offset.Dot(projRight);
|
||||||
double offsetUp = offset.Dot(projUp);
|
double offsetUp = offset.Dot(projUp);
|
||||||
|
|
||||||
double righti = x/scale - offsetRight;
|
double righti = x/scale - offsetRight;
|
||||||
double upi = y/scale - offsetUp;
|
double upi = y/scale - offsetUp;
|
||||||
|
|
||||||
if(delta > 0) {
|
// The default zoom factor is 1.2x for one scroll wheel click (delta==1).
|
||||||
scale *= 1.2;
|
// To support smooth scrolling where scroll wheel events come in increments
|
||||||
} else if(delta < 0) {
|
// smaller (or larger) than 1 we do:
|
||||||
scale /= 1.2;
|
// scale *= exp(ln(1.2) * delta);
|
||||||
} else return;
|
// 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
|
||||||
|
// scale * e^a * e^b == scale * e^(a+b)
|
||||||
|
// 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 rightf = x/scale - offsetRight;
|
||||||
double upf = y/scale - offsetUp;
|
double upf = y/scale - offsetUp;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// Copyright 2016 whitequark
|
// Copyright 2016 whitequark
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
#include "solvespace.h"
|
#include "solvespace.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
static void ShowUsage(const std::string &cmd) {
|
static void ShowUsage(const std::string &cmd) {
|
||||||
fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", cmd.c_str());
|
fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", cmd.c_str());
|
||||||
|
@ -29,6 +30,8 @@ Common options:
|
||||||
Whether to export the background colour in vector formats. Defaults to off.
|
Whether to export the background colour in vector formats. Defaults to off.
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
|
version
|
||||||
|
Prints the current solvespace version.
|
||||||
thumbnail --output <pattern> --size <size> --view <direction>
|
thumbnail --output <pattern> --size <size> --view <direction>
|
||||||
[--chord-tol <tolerance>]
|
[--chord-tol <tolerance>]
|
||||||
Outputs a rendered view of the sketch, like the SolveSpace GUI would.
|
Outputs a rendered view of the sketch, like the SolveSpace GUI would.
|
||||||
|
@ -174,7 +177,10 @@ static bool RunCommand(const std::vector<std::string> args) {
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned width = 0, height = 0;
|
unsigned width = 0, height = 0;
|
||||||
if(args[1] == "thumbnail") {
|
if(args[1] == "version") {
|
||||||
|
fprintf(stderr, "SolveSpace version %s \n\n", PACKAGE_VERSION);
|
||||||
|
return false;
|
||||||
|
} else if(args[1] == "thumbnail") {
|
||||||
auto ParseSize = [&](size_t &argn) {
|
auto ParseSize = [&](size_t &argn) {
|
||||||
if(argn + 1 < args.size() && args[argn] == "--size") {
|
if(argn + 1 < args.size() && args[argn] == "--size") {
|
||||||
argn++;
|
argn++;
|
||||||
|
|
|
@ -21,7 +21,7 @@ int main(int argc, char** argv) {
|
||||||
dbp("Only the first file passed on command line will be opened.");
|
dbp("Only the first file passed on command line will be opened.");
|
||||||
}
|
}
|
||||||
|
|
||||||
SS.Load(Platform::Path::From(args.back()).Expand(/*fromCurrentDirectory=*/true));
|
SS.Load(Platform::Path::From(args.back()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform::RunGui();
|
Platform::RunGui();
|
||||||
|
|
|
@ -99,7 +99,6 @@ std::vector<FileFilter> MeshFileFilters = {
|
||||||
{ CN_("file-type", "Wavefront OBJ mesh"), { "obj" } },
|
{ CN_("file-type", "Wavefront OBJ mesh"), { "obj" } },
|
||||||
{ CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } },
|
{ CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } },
|
||||||
{ CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } },
|
{ CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } },
|
||||||
{ CN_("file-type", "Q3D Object file"), { "q3do" } },
|
|
||||||
{ CN_("file-type", "VRML text file"), { "wrl" } },
|
{ CN_("file-type", "VRML text file"), { "wrl" } },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -356,6 +356,7 @@ public:
|
||||||
|
|
||||||
virtual Platform::Path GetFilename() = 0;
|
virtual Platform::Path GetFilename() = 0;
|
||||||
virtual void SetFilename(Platform::Path path) = 0;
|
virtual void SetFilename(Platform::Path path) = 0;
|
||||||
|
virtual void SuggestFilename(Platform::Path path) = 0;
|
||||||
|
|
||||||
virtual void AddFilter(std::string name, std::vector<std::string> extensions) = 0;
|
virtual void AddFilter(std::string name, std::vector<std::string> extensions) = 0;
|
||||||
void AddFilter(const FileFilter &filter);
|
void AddFilter(const FileFilter &filter);
|
||||||
|
|
|
@ -472,7 +472,7 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool process_pointer_event(MouseEvent::Type type, double x, double y,
|
bool process_pointer_event(MouseEvent::Type type, double x, double y,
|
||||||
guint state, guint button = 0, int scroll_delta = 0) {
|
guint state, guint button = 0, double scroll_delta = 0) {
|
||||||
MouseEvent event = {};
|
MouseEvent event = {};
|
||||||
event.type = type;
|
event.type = type;
|
||||||
event.x = x;
|
event.x = x;
|
||||||
|
@ -536,7 +536,7 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_scroll_event(GdkEventScroll *gdk_event) override {
|
bool on_scroll_event(GdkEventScroll *gdk_event) override {
|
||||||
int delta;
|
double delta;
|
||||||
if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) {
|
if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) {
|
||||||
delta = 1;
|
delta = 1;
|
||||||
} else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) {
|
} else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) {
|
||||||
|
@ -1246,6 +1246,10 @@ public:
|
||||||
gtkChooser->set_filename(path.raw);
|
gtkChooser->set_filename(path.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SuggestFilename(Platform::Path path) override {
|
||||||
|
gtkChooser->set_current_name(path.FileStem()+"."+GetExtension());
|
||||||
|
}
|
||||||
|
|
||||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
Glib::RefPtr<Gtk::FileFilter> gtkFilter = Gtk::FileFilter::create();
|
Glib::RefPtr<Gtk::FileFilter> gtkFilter = Gtk::FileFilter::create();
|
||||||
Glib::ustring desc;
|
Glib::ustring desc;
|
||||||
|
@ -1291,13 +1295,16 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: This is not getting called when the extension selection is changed.
|
||||||
void FilterChanged() {
|
void FilterChanged() {
|
||||||
std::string extension = GetExtension();
|
std::string extension = GetExtension();
|
||||||
if(extension.empty())
|
if(extension.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Platform::Path path = GetFilename();
|
Platform::Path path = GetFilename();
|
||||||
SetCurrentName(path.WithExtension(extension).FileName());
|
if(gtkChooser->get_action() != GTK_FILE_CHOOSER_ACTION_OPEN) {
|
||||||
|
SetCurrentName(path.WithExtension(extension).FileName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
|
|
@ -371,6 +371,7 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
|
|
||||||
- (id)initWithFrame:(NSRect)frameRect {
|
- (id)initWithFrame:(NSRect)frameRect {
|
||||||
NSOpenGLPixelFormatAttribute attrs[] = {
|
NSOpenGLPixelFormatAttribute attrs[] = {
|
||||||
|
NSOpenGLPFADoubleBuffer,
|
||||||
NSOpenGLPFAColorSize, 24,
|
NSOpenGLPFAColorSize, 24,
|
||||||
NSOpenGLPFADepthSize, 24,
|
NSOpenGLPFADepthSize, 24,
|
||||||
0
|
0
|
||||||
|
@ -553,7 +554,9 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
|
|
||||||
MouseEvent event = [self convertMouseEvent:nsEvent];
|
MouseEvent event = [self convertMouseEvent:nsEvent];
|
||||||
event.type = MouseEvent::Type::SCROLL_VERT;
|
event.type = MouseEvent::Type::SCROLL_VERT;
|
||||||
event.scrollDelta = [nsEvent deltaY];
|
|
||||||
|
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
|
||||||
|
event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5);
|
||||||
|
|
||||||
if(receiver->onMouseEvent) {
|
if(receiver->onMouseEvent) {
|
||||||
receiver->onMouseEvent(event);
|
receiver->onMouseEvent(event);
|
||||||
|
@ -974,9 +977,6 @@ public:
|
||||||
if(GetScrollbarPosition() == pos)
|
if(GetScrollbarPosition() == pos)
|
||||||
return;
|
return;
|
||||||
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
|
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
|
||||||
if(onScrollbarAdjusted) {
|
|
||||||
onScrollbarAdjusted(pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Invalidate() override {
|
void Invalidate() override {
|
||||||
|
@ -1273,6 +1273,10 @@ public:
|
||||||
nsPanel.nameFieldStringValue = Wrap(path.FileStem());
|
nsPanel.nameFieldStringValue = Wrap(path.FileStem());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SuggestFilename(Platform::Path path) override {
|
||||||
|
SetFilename(path.WithExtension(""));
|
||||||
|
}
|
||||||
|
|
||||||
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||||
settings->FreezeString("Dialog_" + key + "_Folder",
|
settings->FreezeString("Dialog_" + key + "_Folder",
|
||||||
[nsPanel.directoryURL.absoluteString UTF8String]);
|
[nsPanel.directoryURL.absoluteString UTF8String]);
|
||||||
|
|
|
@ -183,7 +183,7 @@ public:
|
||||||
|
|
||||||
HKEY GetKey() {
|
HKEY GetKey() {
|
||||||
if(hKey == NULL) {
|
if(hKey == NULL) {
|
||||||
sscheck(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0,
|
sscheck(ERROR_SUCCESS == RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0,
|
||||||
KEY_ALL_ACCESS, NULL, &hKey, NULL));
|
KEY_ALL_ACCESS, NULL, &hKey, NULL));
|
||||||
}
|
}
|
||||||
return hKey;
|
return hKey;
|
||||||
|
@ -191,12 +191,12 @@ public:
|
||||||
|
|
||||||
~SettingsImplWin32() {
|
~SettingsImplWin32() {
|
||||||
if(hKey != NULL) {
|
if(hKey != NULL) {
|
||||||
sscheck(RegCloseKey(hKey));
|
sscheck(ERROR_SUCCESS == RegCloseKey(hKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreezeInt(const std::string &key, uint32_t value) {
|
void FreezeInt(const std::string &key, uint32_t value) {
|
||||||
sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0,
|
sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0,
|
||||||
REG_DWORD, (const BYTE *)&value, sizeof(value)));
|
REG_DWORD, (const BYTE *)&value, sizeof(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreezeFloat(const std::string &key, double value) {
|
void FreezeFloat(const std::string &key, double value) {
|
||||||
sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0,
|
sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0,
|
||||||
REG_QWORD, (const BYTE *)&value, sizeof(value)));
|
REG_QWORD, (const BYTE *)&value, sizeof(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ public:
|
||||||
ssassert(value.length() == strlen(value.c_str()),
|
ssassert(value.length() == strlen(value.c_str()),
|
||||||
"illegal null byte in middle of a string setting");
|
"illegal null byte in middle of a string setting");
|
||||||
std::wstring valueW = Widen(value);
|
std::wstring valueW = Widen(value);
|
||||||
sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0,
|
sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0,
|
||||||
REG_SZ, (const BYTE *)&valueW[0], (valueW.length() + 1) * 2));
|
REG_SZ, (const BYTE *)&valueW[0], (valueW.length() + 1) * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ public:
|
||||||
if(result == ERROR_SUCCESS && type == REG_SZ) {
|
if(result == ERROR_SUCCESS && type == REG_SZ) {
|
||||||
std::wstring valueW;
|
std::wstring valueW;
|
||||||
valueW.resize(length / 2 - 1);
|
valueW.resize(length / 2 - 1);
|
||||||
sscheck(RegQueryValueExW(GetKey(), &Widen(key)[0], 0,
|
sscheck(ERROR_SUCCESS == RegQueryValueExW(GetKey(), &Widen(key)[0], 0,
|
||||||
&type, (BYTE *)&valueW[0], &length));
|
&type, (BYTE *)&valueW[0], &length));
|
||||||
return Narrow(valueW);
|
return Narrow(valueW);
|
||||||
}
|
}
|
||||||
|
@ -734,6 +734,11 @@ public:
|
||||||
event.type = SixDofEvent::Type::RELEASE;
|
event.type = SixDofEvent::Type::RELEASE;
|
||||||
event.button = SixDofEvent::Button::FIT;
|
event.button = SixDofEvent::Button::FIT;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(window->onSixDofEvent) {
|
||||||
|
window->onSixDofEvent(event);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -907,8 +912,8 @@ public:
|
||||||
// Make the mousewheel work according to which window the mouse is
|
// Make the mousewheel work according to which window the mouse is
|
||||||
// over, not according to which window is active.
|
// over, not according to which window is active.
|
||||||
POINT pt;
|
POINT pt;
|
||||||
pt.x = LOWORD(lParam);
|
pt.x = GET_X_LPARAM(lParam);
|
||||||
pt.y = HIWORD(lParam);
|
pt.y = GET_Y_LPARAM(lParam);
|
||||||
HWND hWindowUnderMouse;
|
HWND hWindowUnderMouse;
|
||||||
sscheck(hWindowUnderMouse = WindowFromPoint(pt));
|
sscheck(hWindowUnderMouse = WindowFromPoint(pt));
|
||||||
if(hWindowUnderMouse && hWindowUnderMouse != h) {
|
if(hWindowUnderMouse && hWindowUnderMouse != h) {
|
||||||
|
@ -917,8 +922,15 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the mouse coordinates from screen to client area so that
|
||||||
|
// scroll wheel zooming remains centered irrespective of the window
|
||||||
|
// position.
|
||||||
|
ScreenToClient(hWindowUnderMouse, &pt);
|
||||||
|
event.x = pt.x / pixelRatio;
|
||||||
|
event.y = pt.y / pixelRatio;
|
||||||
|
|
||||||
event.type = MouseEvent::Type::SCROLL_VERT;
|
event.type = MouseEvent::Type::SCROLL_VERT;
|
||||||
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1 : -1;
|
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM_MOUSELEAVE:
|
case WM_MOUSELEAVE:
|
||||||
|
@ -1094,12 +1106,12 @@ public:
|
||||||
|
|
||||||
bool IsVisible() override {
|
bool IsVisible() override {
|
||||||
BOOL isVisible;
|
BOOL isVisible;
|
||||||
sscheck(isVisible = IsWindowVisible(hWindow));
|
isVisible = IsWindowVisible(hWindow);
|
||||||
return isVisible == TRUE;
|
return isVisible == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetVisible(bool visible) override {
|
void SetVisible(bool visible) override {
|
||||||
sscheck(ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE));
|
ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Focus() override {
|
void Focus() override {
|
||||||
|
@ -1267,7 +1279,7 @@ public:
|
||||||
|
|
||||||
bool IsEditorVisible() override {
|
bool IsEditorVisible() override {
|
||||||
BOOL visible;
|
BOOL visible;
|
||||||
sscheck(visible = IsWindowVisible(hEditor));
|
visible = IsWindowVisible(hEditor);
|
||||||
return visible == TRUE;
|
return visible == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1309,7 +1321,7 @@ public:
|
||||||
|
|
||||||
sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
||||||
/*bRepaint=*/true));
|
/*bRepaint=*/true));
|
||||||
sscheck(ShowWindow(hEditor, SW_SHOW));
|
ShowWindow(hEditor, SW_SHOW);
|
||||||
if(!textW.empty()) {
|
if(!textW.empty()) {
|
||||||
sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str()));
|
sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str()));
|
||||||
sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length()));
|
sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length()));
|
||||||
|
@ -1320,7 +1332,7 @@ public:
|
||||||
void HideEditor() override {
|
void HideEditor() override {
|
||||||
if(!IsEditorVisible()) return;
|
if(!IsEditorVisible()) return;
|
||||||
|
|
||||||
sscheck(ShowWindow(hEditor, SW_HIDE));
|
ShowWindow(hEditor, SW_HIDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetScrollbarVisible(bool visible) override {
|
void SetScrollbarVisible(bool visible) override {
|
||||||
|
@ -1335,7 +1347,7 @@ public:
|
||||||
si.nMin = (UINT)(min * SCROLLBAR_UNIT);
|
si.nMin = (UINT)(min * SCROLLBAR_UNIT);
|
||||||
si.nMax = (UINT)(max * SCROLLBAR_UNIT);
|
si.nMax = (UINT)(max * SCROLLBAR_UNIT);
|
||||||
si.nPage = (UINT)(pageSize * SCROLLBAR_UNIT);
|
si.nPage = (UINT)(pageSize * SCROLLBAR_UNIT);
|
||||||
sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE));
|
SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE); // Returns scrollbar position
|
||||||
}
|
}
|
||||||
|
|
||||||
double GetScrollbarPosition() override {
|
double GetScrollbarPosition() override {
|
||||||
|
@ -1359,7 +1371,7 @@ public:
|
||||||
return;
|
return;
|
||||||
|
|
||||||
si.nPos = (int)(pos * SCROLLBAR_UNIT);
|
si.nPos = (int)(pos * SCROLLBAR_UNIT);
|
||||||
sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE));
|
SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE); // Returns scrollbar position
|
||||||
|
|
||||||
// Windows won't synthesize a WM_VSCROLL for us here.
|
// Windows won't synthesize a WM_VSCROLL for us here.
|
||||||
if(onScrollbarAdjusted) {
|
if(onScrollbarAdjusted) {
|
||||||
|
@ -1443,7 +1455,10 @@ public:
|
||||||
void SetType(Type type) override {
|
void SetType(Type type) override {
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::INFORMATION:
|
case Type::INFORMATION:
|
||||||
style = MB_ICONINFORMATION;
|
style = MB_USERICON; // Avoid beep
|
||||||
|
mbp.hInstance = GetModuleHandle(NULL);
|
||||||
|
mbp.lpszIcon = MAKEINTRESOURCE(4000); // Use SolveSpace icon
|
||||||
|
// mbp.lpszIcon = IDI_INFORMATION;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Type::QUESTION:
|
case Type::QUESTION:
|
||||||
|
@ -1455,7 +1470,10 @@ public:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Type::ERROR:
|
case Type::ERROR:
|
||||||
style = MB_ICONERROR;
|
style = MB_USERICON; // Avoid beep
|
||||||
|
mbp.hInstance = GetModuleHandle(NULL);
|
||||||
|
mbp.lpszIcon = MAKEINTRESOURCE(4000); // Use SolveSpace icon
|
||||||
|
// mbp.lpszIcon = IDI_ERROR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1575,6 +1593,10 @@ public:
|
||||||
wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1);
|
wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SuggestFilename(Platform::Path path) override {
|
||||||
|
SetFilename(Platform::Path::From(path.FileStem()));
|
||||||
|
}
|
||||||
|
|
||||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
std::string desc, patterns;
|
std::string desc, patterns;
|
||||||
for(auto extension : extensions) {
|
for(auto extension : extensions) {
|
||||||
|
|
|
@ -185,8 +185,10 @@ Path Path::WithExtension(std::string ext) const {
|
||||||
if(dot != std::string::npos) {
|
if(dot != std::string::npos) {
|
||||||
withExt.raw.erase(dot);
|
withExt.raw.erase(dot);
|
||||||
}
|
}
|
||||||
withExt.raw += ".";
|
if(!ext.empty()) {
|
||||||
withExt.raw += ext;
|
withExt.raw += ".";
|
||||||
|
withExt.raw += ext;
|
||||||
|
}
|
||||||
return withExt;
|
return withExt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +403,7 @@ FILE *OpenFile(const Platform::Path &filename, const char *mode) {
|
||||||
ssassert(filename.raw.length() == strlen(filename.raw.c_str()),
|
ssassert(filename.raw.length() == strlen(filename.raw.c_str()),
|
||||||
"Unexpected null byte in middle of a path");
|
"Unexpected null byte in middle of a path");
|
||||||
#if defined(WIN32)
|
#if defined(WIN32)
|
||||||
return _wfopen(Widen(filename.Expand().raw).c_str(), Widen(mode).c_str());
|
return _wfopen(Widen(filename.Expand(/*fromCurrentDirectory=*/true).raw).c_str(), Widen(mode).c_str());
|
||||||
#else
|
#else
|
||||||
return fopen(filename.raw.c_str(), mode);
|
return fopen(filename.raw.c_str(), mode);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -17,6 +17,12 @@ std::string Narrow(const std::wstring &s);
|
||||||
std::wstring Widen(const std::string &s);
|
std::wstring Widen(const std::string &s);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
const std::string embeddedFont = "res://fonts/BitstreamVeraSans-Roman-builtin.ttf";
|
||||||
|
#else // Linux and macOS
|
||||||
|
const std::string embeddedFont = "BitstreamVeraSans-Roman-builtin.ttf";
|
||||||
|
#endif
|
||||||
|
|
||||||
// A filesystem path, respecting the conventions of the current platform.
|
// A filesystem path, respecting the conventions of the current platform.
|
||||||
// Transformation functions return an empty path on error.
|
// Transformation functions return an empty path on error.
|
||||||
class Path {
|
class Path {
|
||||||
|
|
|
@ -343,9 +343,10 @@ void UiCanvas::DrawBitmapText(const std::string &str, int x, int y, RgbaColor co
|
||||||
// A canvas that performs picking against drawn geometry.
|
// A canvas that performs picking against drawn geometry.
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
void ObjectPicker::DoCompare(double distance, int zIndex, int comparePosition) {
|
void ObjectPicker::DoCompare(double depth, double distance, int zIndex, int comparePosition) {
|
||||||
if(distance > selRadius) return;
|
if(distance > selRadius) return;
|
||||||
if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) {
|
if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) {
|
||||||
|
minDepth = depth;
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
maxZIndex = zIndex;
|
maxZIndex = zIndex;
|
||||||
position = comparePosition;
|
position = comparePosition;
|
||||||
|
@ -372,10 +373,10 @@ void ObjectPicker::DoQuad(const Vector &a, const Vector &b, const Vector &c, con
|
||||||
|
|
||||||
bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE);
|
bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE);
|
||||||
if(insideQuad) {
|
if(insideQuad) {
|
||||||
DoCompare(0.0, zIndex, comparePosition);
|
DoCompare(0, 0.0, zIndex, comparePosition);
|
||||||
} else {
|
} else {
|
||||||
double distance = std::min(fabs(minNegative), fabs(maxPositive));
|
double distance = std::min(fabs(minNegative), fabs(maxPositive));
|
||||||
DoCompare(distance, zIndex, comparePosition);
|
DoCompare(0, distance, zIndex, comparePosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +385,8 @@ void ObjectPicker::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
|
||||||
Point2d ap = camera.ProjectPoint(a);
|
Point2d ap = camera.ProjectPoint(a);
|
||||||
Point2d bp = camera.ProjectPoint(b);
|
Point2d bp = camera.ProjectPoint(b);
|
||||||
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
|
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
|
||||||
DoCompare(distance - stroke->width / 2.0, stroke->zIndex);
|
double depth = 0.5 * (camera.ProjectPoint3(a).z + camera.ProjectPoint3(b).z) ;
|
||||||
|
DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
|
void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
|
||||||
|
@ -393,7 +395,8 @@ void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
|
||||||
Point2d ap = camera.ProjectPoint(e.a);
|
Point2d ap = camera.ProjectPoint(e.a);
|
||||||
Point2d bp = camera.ProjectPoint(e.b);
|
Point2d bp = camera.ProjectPoint(e.b);
|
||||||
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
|
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
|
||||||
DoCompare(distance - stroke->width / 2.0, stroke->zIndex, e.auxB);
|
double depth = 0.5 * (camera.ProjectPoint3(e.a).z + camera.ProjectPoint3(e.b).z) ;
|
||||||
|
DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex, e.auxB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +426,8 @@ void ObjectPicker::DrawQuad(const Vector &a, const Vector &b, const Vector &c, c
|
||||||
void ObjectPicker::DrawPoint(const Vector &o, Canvas::hStroke hcs) {
|
void ObjectPicker::DrawPoint(const Vector &o, Canvas::hStroke hcs) {
|
||||||
Stroke *stroke = strokes.FindById(hcs);
|
Stroke *stroke = strokes.FindById(hcs);
|
||||||
double distance = point.DistanceTo(camera.ProjectPoint(o)) - stroke->width / 2;
|
double distance = point.DistanceTo(camera.ProjectPoint(o)) - stroke->width / 2;
|
||||||
DoCompare(distance, stroke->zIndex);
|
double depth = camera.ProjectPoint3(o).z;
|
||||||
|
DoCompare(depth, distance, stroke->zIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) {
|
void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) {
|
||||||
|
@ -445,6 +449,7 @@ void ObjectPicker::DrawPixmap(std::shared_ptr<const Pixmap> pm,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectPicker::Pick(const std::function<void()> &drawFn) {
|
bool ObjectPicker::Pick(const std::function<void()> &drawFn) {
|
||||||
|
minDepth = VERY_POSITIVE;
|
||||||
minDistance = VERY_POSITIVE;
|
minDistance = VERY_POSITIVE;
|
||||||
maxZIndex = INT_MIN;
|
maxZIndex = INT_MIN;
|
||||||
|
|
||||||
|
|
|
@ -232,6 +232,7 @@ public:
|
||||||
double selRadius = 0.0;
|
double selRadius = 0.0;
|
||||||
// Picking state.
|
// Picking state.
|
||||||
double minDistance = 0.0;
|
double minDistance = 0.0;
|
||||||
|
double minDepth = 1e10;
|
||||||
int maxZIndex = 0;
|
int maxZIndex = 0;
|
||||||
uint32_t position = 0;
|
uint32_t position = 0;
|
||||||
|
|
||||||
|
@ -257,7 +258,7 @@ public:
|
||||||
const Point2d &ta, const Point2d &tb, hFill hcf) override;
|
const Point2d &ta, const Point2d &tb, hFill hcf) override;
|
||||||
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {}
|
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {}
|
||||||
|
|
||||||
void DoCompare(double distance, int zIndex, int comparePosition = 0);
|
void DoCompare(double depth, double distance, int zIndex, int comparePosition = 0);
|
||||||
void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
|
void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
|
||||||
int zIndex, int comparePosition = 0);
|
int zIndex, int comparePosition = 0);
|
||||||
|
|
||||||
|
|
|
@ -790,6 +790,9 @@ public:
|
||||||
static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
|
static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
|
||||||
hEntity entityA, hEntity entityB = Entity::NO_ENTITY,
|
hEntity entityA, hEntity entityB = Entity::NO_ENTITY,
|
||||||
bool other = false, bool other2 = false);
|
bool other = false, bool other2 = false);
|
||||||
|
static void ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc);
|
||||||
|
static void ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic);
|
||||||
|
static void ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB);
|
||||||
};
|
};
|
||||||
|
|
||||||
class hEquation {
|
class hEquation {
|
||||||
|
|
|
@ -77,7 +77,7 @@ void SolveSpaceUI::Init() {
|
||||||
// Use turntable mouse navigation
|
// Use turntable mouse navigation
|
||||||
turntableNav = settings->ThawBool("TurntableNav", false);
|
turntableNav = settings->ThawBool("TurntableNav", false);
|
||||||
// Immediately edit dimension
|
// Immediately edit dimension
|
||||||
immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", false);
|
immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", true);
|
||||||
// Check that contours are closed and not self-intersecting
|
// Check that contours are closed and not self-intersecting
|
||||||
checkClosedContour = settings->ThawBool("CheckClosedContour", true);
|
checkClosedContour = settings->ThawBool("CheckClosedContour", true);
|
||||||
// Enable automatic constrains for lines
|
// Enable automatic constrains for lines
|
||||||
|
@ -331,7 +331,13 @@ const char *SolveSpaceUI::UnitName() {
|
||||||
|
|
||||||
std::string SolveSpaceUI::MmToString(double v) {
|
std::string SolveSpaceUI::MmToString(double v) {
|
||||||
v /= MmPerUnit();
|
v /= MmPerUnit();
|
||||||
return ssprintf("%.*f", UnitDigitsAfterDecimal(), v);
|
int digits = UnitDigitsAfterDecimal();
|
||||||
|
double minimum = 0.5 * pow(10,-digits);
|
||||||
|
while ((v < minimum) && (v > LENGTH_EPS)) {
|
||||||
|
digits++;
|
||||||
|
minimum *= 0.1;
|
||||||
|
}
|
||||||
|
return ssprintf("%.*f", digits, v);
|
||||||
}
|
}
|
||||||
static const char *DimToString(int dim) {
|
static const char *DimToString(int dim) {
|
||||||
switch(dim) {
|
switch(dim) {
|
||||||
|
@ -341,13 +347,39 @@ static const char *DimToString(int dim) {
|
||||||
default: ssassert(false, "Unexpected dimension");
|
default: ssassert(false, "Unexpected dimension");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static std::pair<int, std::string> SelectSIPrefixMm(int deg) {
|
static std::pair<int, std::string> SelectSIPrefixMm(int ord, int dim) {
|
||||||
if(deg >= 3) return { 3, "km" };
|
// decide what units to use depending on the order of magnitude of the
|
||||||
else if(deg >= 0) return { 0, "m" };
|
// measure in meters and the dimmension (1,2,3 lenear, area, volume)
|
||||||
else if(deg >= -2) return { -2, "cm" };
|
switch(dim) {
|
||||||
else if(deg >= -3) return { -3, "mm" };
|
case 0:
|
||||||
else if(deg >= -6) return { -6, "µm" };
|
case 1:
|
||||||
else return { -9, "nm" };
|
if(ord >= 3) return { 3, "km" };
|
||||||
|
else if(ord >= 0) return { 0, "m" };
|
||||||
|
else if(ord >= -2) return { -2, "cm" };
|
||||||
|
else if(ord >= -3) return { -3, "mm" };
|
||||||
|
else if(ord >= -6) return { -6, "µm" };
|
||||||
|
else return { -9, "nm" };
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if(ord >= 5) return { 3, "km" };
|
||||||
|
else if(ord >= 0) return { 0, "m" };
|
||||||
|
else if(ord >= -2) return { -2, "cm" };
|
||||||
|
else if(ord >= -6) return { -3, "mm" };
|
||||||
|
else if(ord >= -13) return { -6, "µm" };
|
||||||
|
else return { -9, "nm" };
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if(ord >= 7) return { 3, "km" };
|
||||||
|
else if(ord >= 0) return { 0, "m" };
|
||||||
|
else if(ord >= -5) return { -2, "cm" };
|
||||||
|
else if(ord >= -11) return { -3, "mm" };
|
||||||
|
else return { -6, "µm" };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dbp ("dimensions over 3 not supported");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {0, "m"};
|
||||||
}
|
}
|
||||||
static std::pair<int, std::string> SelectSIPrefixInch(int deg) {
|
static std::pair<int, std::string> SelectSIPrefixInch(int deg) {
|
||||||
if(deg >= 0) return { 0, "in" };
|
if(deg >= 0) return { 0, "in" };
|
||||||
|
@ -363,14 +395,14 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim);
|
v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim);
|
||||||
int vdeg = (int)((log10(fabs(v))) / dim);
|
int vdeg = (int)(log10(fabs(v)));
|
||||||
std::string unit;
|
std::string unit;
|
||||||
if(fabs(v) > 0.0) {
|
if(fabs(v) > 0.0) {
|
||||||
int sdeg = 0;
|
int sdeg = 0;
|
||||||
std::tie(sdeg, unit) =
|
std::tie(sdeg, unit) =
|
||||||
(viewUnits == Unit::INCHES)
|
(viewUnits == Unit::INCHES)
|
||||||
? SelectSIPrefixInch(vdeg)
|
? SelectSIPrefixInch(vdeg/dim)
|
||||||
: SelectSIPrefixMm(vdeg);
|
: SelectSIPrefixMm(vdeg, dim);
|
||||||
v /= pow(10.0, sdeg * dim);
|
v /= pow(10.0, sdeg * dim);
|
||||||
}
|
}
|
||||||
int pdeg = (int)ceil(log10(fabs(v) + 1e-10));
|
int pdeg = (int)ceil(log10(fabs(v) + 1e-10));
|
||||||
|
@ -601,6 +633,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::RasterFileFilters);
|
dialog->AddFilters(Platform::RasterFileFilters);
|
||||||
dialog->ThawChoices(settings, "ExportImage");
|
dialog->ThawChoices(settings, "ExportImage");
|
||||||
|
dialog->SuggestFilename(SS.saveFile);
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
dialog->FreezeChoices(settings, "ExportImage");
|
dialog->FreezeChoices(settings, "ExportImage");
|
||||||
SS.ExportAsPngTo(dialog->GetFilename());
|
SS.ExportAsPngTo(dialog->GetFilename());
|
||||||
|
@ -612,6 +645,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::VectorFileFilters);
|
dialog->AddFilters(Platform::VectorFileFilters);
|
||||||
dialog->ThawChoices(settings, "ExportView");
|
dialog->ThawChoices(settings, "ExportView");
|
||||||
|
dialog->SuggestFilename(SS.saveFile);
|
||||||
if(!dialog->RunModal()) break;
|
if(!dialog->RunModal()) break;
|
||||||
dialog->FreezeChoices(settings, "ExportView");
|
dialog->FreezeChoices(settings, "ExportView");
|
||||||
|
|
||||||
|
@ -635,6 +669,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::Vector3dFileFilters);
|
dialog->AddFilters(Platform::Vector3dFileFilters);
|
||||||
dialog->ThawChoices(settings, "ExportWireframe");
|
dialog->ThawChoices(settings, "ExportWireframe");
|
||||||
|
dialog->SuggestFilename(SS.saveFile);
|
||||||
if(!dialog->RunModal()) break;
|
if(!dialog->RunModal()) break;
|
||||||
dialog->FreezeChoices(settings, "ExportWireframe");
|
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||||
|
|
||||||
|
@ -646,6 +681,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::VectorFileFilters);
|
dialog->AddFilters(Platform::VectorFileFilters);
|
||||||
dialog->ThawChoices(settings, "ExportSection");
|
dialog->ThawChoices(settings, "ExportSection");
|
||||||
|
dialog->SuggestFilename(SS.saveFile);
|
||||||
if(!dialog->RunModal()) break;
|
if(!dialog->RunModal()) break;
|
||||||
dialog->FreezeChoices(settings, "ExportSection");
|
dialog->FreezeChoices(settings, "ExportSection");
|
||||||
|
|
||||||
|
@ -657,6 +693,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::MeshFileFilters);
|
dialog->AddFilters(Platform::MeshFileFilters);
|
||||||
dialog->ThawChoices(settings, "ExportMesh");
|
dialog->ThawChoices(settings, "ExportMesh");
|
||||||
|
dialog->SuggestFilename(SS.saveFile);
|
||||||
if(!dialog->RunModal()) break;
|
if(!dialog->RunModal()) break;
|
||||||
dialog->FreezeChoices(settings, "ExportMesh");
|
dialog->FreezeChoices(settings, "ExportMesh");
|
||||||
|
|
||||||
|
@ -668,6 +705,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::SurfaceFileFilters);
|
dialog->AddFilters(Platform::SurfaceFileFilters);
|
||||||
dialog->ThawChoices(settings, "ExportSurfaces");
|
dialog->ThawChoices(settings, "ExportSurfaces");
|
||||||
|
dialog->SuggestFilename(SS.saveFile);
|
||||||
if(!dialog->RunModal()) break;
|
if(!dialog->RunModal()) break;
|
||||||
dialog->FreezeChoices(settings, "ExportSurfaces");
|
dialog->FreezeChoices(settings, "ExportSurfaces");
|
||||||
|
|
||||||
|
@ -878,9 +916,13 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::STOP_TRACING: {
|
case Command::STOP_TRACING: {
|
||||||
|
if (SS.traced.point == Entity::NO_ENTITY) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
dialog->AddFilters(Platform::CsvFileFilters);
|
dialog->AddFilters(Platform::CsvFileFilters);
|
||||||
dialog->ThawChoices(settings, "Trace");
|
dialog->ThawChoices(settings, "Trace");
|
||||||
|
dialog->SetFilename(SS.saveFile);
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
dialog->FreezeChoices(settings, "Trace");
|
dialog->FreezeChoices(settings, "Trace");
|
||||||
|
|
||||||
|
@ -965,7 +1007,7 @@ void SolveSpaceUI::MenuHelp(Command id) {
|
||||||
"law. For details, visit http://gnu.org/licenses/\n"
|
"law. For details, visit http://gnu.org/licenses/\n"
|
||||||
"\n"
|
"\n"
|
||||||
"© 2008-%d Jonathan Westhues and other authors.\n"),
|
"© 2008-%d Jonathan Westhues and other authors.\n"),
|
||||||
PACKAGE_VERSION, 2019);
|
PACKAGE_VERSION, 2021);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: ssassert(false, "Unexpected menu ID");
|
default: ssassert(false, "Unexpected menu ID");
|
||||||
|
@ -982,6 +1024,7 @@ void SolveSpaceUI::Clear() {
|
||||||
GW.openRecentMenu = NULL;
|
GW.openRecentMenu = NULL;
|
||||||
GW.linkRecentMenu = NULL;
|
GW.linkRecentMenu = NULL;
|
||||||
GW.showGridMenuItem = NULL;
|
GW.showGridMenuItem = NULL;
|
||||||
|
GW.dimSolidModelMenuItem = NULL;
|
||||||
GW.perspectiveProjMenuItem = NULL;
|
GW.perspectiveProjMenuItem = NULL;
|
||||||
GW.showToolbarMenuItem = NULL;
|
GW.showToolbarMenuItem = NULL;
|
||||||
GW.showTextWndMenuItem = NULL;
|
GW.showTextWndMenuItem = NULL;
|
||||||
|
|
|
@ -513,7 +513,7 @@ public:
|
||||||
GraphicsWindow GW;
|
GraphicsWindow GW;
|
||||||
|
|
||||||
// The state for undo/redo
|
// The state for undo/redo
|
||||||
typedef struct {
|
typedef struct UndoState {
|
||||||
IdList<Group,hGroup> group;
|
IdList<Group,hGroup> group;
|
||||||
List<hGroup> groupOrder;
|
List<hGroup> groupOrder;
|
||||||
IdList<Request,hRequest> request;
|
IdList<Request,hRequest> request;
|
||||||
|
@ -530,7 +530,7 @@ public:
|
||||||
style.Clear();
|
style.Clear();
|
||||||
}
|
}
|
||||||
} UndoState;
|
} UndoState;
|
||||||
enum { MAX_UNDO = 16 };
|
enum { MAX_UNDO = 100 };
|
||||||
typedef struct {
|
typedef struct {
|
||||||
UndoState d[MAX_UNDO];
|
UndoState d[MAX_UNDO];
|
||||||
int cnt;
|
int cnt;
|
||||||
|
@ -686,7 +686,6 @@ public:
|
||||||
void ExportAsPngTo(const Platform::Path &filename);
|
void ExportAsPngTo(const Platform::Path &filename);
|
||||||
void ExportMeshTo(const Platform::Path &filename);
|
void ExportMeshTo(const Platform::Path &filename);
|
||||||
void ExportMeshAsStlTo(FILE *f, SMesh *sm);
|
void ExportMeshAsStlTo(FILE *f, SMesh *sm);
|
||||||
void ExportMeshAsQ3doTo(FILE *f, SMesh *sm);
|
|
||||||
void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm);
|
void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm);
|
||||||
void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename,
|
void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename,
|
||||||
SMesh *sm, SOutlineList *sol);
|
SMesh *sm, SOutlineList *sol);
|
||||||
|
|
|
@ -20,6 +20,50 @@ void SShell::MakeFromIntersectionOf(SShell *a, SShell *b) {
|
||||||
MakeFromBoolean(a, b, SSurface::CombineAs::INTERSECTION);
|
MakeFromBoolean(a, b, SSurface::CombineAs::INTERSECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const {
|
||||||
|
*ptMax = {VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE};
|
||||||
|
*ptMin = {VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE};
|
||||||
|
|
||||||
|
for(int i = 0; i <= exact.deg; i++) {
|
||||||
|
exact.ctrl[i].MakeMaxMin(ptMax, ptMin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will be inserting other curve verticies 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
|
||||||
|
// handles more buggy cases. It's not clear this is the best way but it works ok.
|
||||||
|
static void FindVertsOnCurve(List<SInter> *l, const SCurve *curve, SShell *sh) {
|
||||||
|
|
||||||
|
Vector amax, amin;
|
||||||
|
curve->GetAxisAlignedBounding(&amax, &amin);
|
||||||
|
|
||||||
|
for(auto sc : sh->curve) {
|
||||||
|
if(!sc.isExact) continue;
|
||||||
|
|
||||||
|
Vector cmax, cmin;
|
||||||
|
sc.GetAxisAlignedBounding(&cmax, &cmin);
|
||||||
|
|
||||||
|
if(Vector::BoundingBoxesDisjoint(amax, amin, cmax, cmin)) {
|
||||||
|
// They cannot possibly intersect, no curves to generate
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0; i<2; i++) {
|
||||||
|
Vector pt = sc.exact.ctrl[ i==0 ? 0 : sc.exact.deg ];
|
||||||
|
double t;
|
||||||
|
curve->exact.ClosestPointTo(pt, &t, /*must converge=*/ false);
|
||||||
|
double d = pt.Minus(curve->exact.PointAt(t)).Magnitude();
|
||||||
|
if((t>LENGTH_EPS) && (t<(1.0-LENGTH_EPS)) && (d < LENGTH_EPS)) {
|
||||||
|
SInter inter;
|
||||||
|
inter.p = pt;
|
||||||
|
l->Add(&inter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Take our original pwl curve. Wherever an edge intersects a surface within
|
// Take our original pwl curve. Wherever an edge intersects a surface within
|
||||||
// either agnstA or agnstB, split the piecewise linear element. Then refine
|
// either agnstA or agnstB, split the piecewise linear element. Then refine
|
||||||
|
@ -35,12 +79,21 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
|
||||||
ret = *this;
|
ret = *this;
|
||||||
ret.pts = {};
|
ret.pts = {};
|
||||||
|
|
||||||
|
// First find any vertex that lies on our curve.
|
||||||
|
List<SInter> vertpts = {};
|
||||||
|
if(isExact) {
|
||||||
|
if(agnstA)
|
||||||
|
FindVertsOnCurve(&vertpts, this, agnstA);
|
||||||
|
if(agnstB)
|
||||||
|
FindVertsOnCurve(&vertpts, this, agnstB);
|
||||||
|
}
|
||||||
|
|
||||||
const SCurvePt *p = pts.First();
|
const SCurvePt *p = pts.First();
|
||||||
ssassert(p != NULL, "Cannot split an empty curve");
|
ssassert(p != NULL, "Cannot split an empty curve");
|
||||||
SCurvePt prev = *p;
|
SCurvePt prev = *p;
|
||||||
ret.pts.Add(p);
|
ret.pts.Add(p);
|
||||||
p = pts.NextAfter(p);
|
p = pts.NextAfter(p);
|
||||||
|
|
||||||
for(; p; p = pts.NextAfter(p)) {
|
for(; p; p = pts.NextAfter(p)) {
|
||||||
List<SInter> il = {};
|
List<SInter> il = {};
|
||||||
|
|
||||||
|
@ -100,12 +153,22 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
|
||||||
pi->p = (pi->srf)->PointAt(puv);
|
pi->p = (pi->srf)->PointAt(puv);
|
||||||
}
|
}
|
||||||
il.RemoveTagged();
|
il.RemoveTagged();
|
||||||
|
}
|
||||||
|
// Now add any vertex that is on this segment
|
||||||
|
const Vector lineStart = prev.p;
|
||||||
|
const Vector lineDirection = (p->p).Minus(prev.p);
|
||||||
|
for(auto vtx : vertpts) {
|
||||||
|
double t = (vtx.p.Minus(lineStart)).DivProjected(lineDirection);
|
||||||
|
if((0.0 < t) && (t < 1.0)) {
|
||||||
|
il.Add(&vtx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!il.IsEmpty()) {
|
||||||
|
SInter *pi;
|
||||||
|
|
||||||
// And now sort them in order along the line. Note that we must
|
// And now sort them in order along the line. Note that we must
|
||||||
// do that after refining, in case the refining would make two
|
// do that after refining, in case the refining would make two
|
||||||
// points switch places.
|
// points switch places.
|
||||||
const Vector lineStart = prev.p;
|
|
||||||
const Vector lineDirection = (p->p).Minus(prev.p);
|
|
||||||
std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) {
|
std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) {
|
||||||
double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection);
|
double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection);
|
||||||
double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection);
|
double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection);
|
||||||
|
@ -133,20 +196,24 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
|
||||||
ret.pts.Add(p);
|
ret.pts.Add(p);
|
||||||
prev = *p;
|
prev = *p;
|
||||||
}
|
}
|
||||||
|
vertpts.Clear();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) {
|
void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) {
|
||||||
SCurve *sc;
|
#pragma omp parallel for
|
||||||
for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) {
|
for(int i=0; i<curve.n; i++) {
|
||||||
|
SCurve *sc = &curve[i];
|
||||||
SCurve scn = sc->MakeCopySplitAgainst(agnst, NULL,
|
SCurve scn = sc->MakeCopySplitAgainst(agnst, NULL,
|
||||||
surface.FindById(sc->surfA),
|
surface.FindById(sc->surfA),
|
||||||
surface.FindById(sc->surfB));
|
surface.FindById(sc->surfB));
|
||||||
scn.source = opA ? SCurve::Source::A : SCurve::Source::B;
|
scn.source = opA ? SCurve::Source::A : SCurve::Source::B;
|
||||||
|
#pragma omp critical
|
||||||
hSCurve hsc = into->curve.AddAndAssignId(&scn);
|
{
|
||||||
// And note the new ID so that we can rewrite the trims appropriately
|
hSCurve hsc = into->curve.AddAndAssignId(&scn);
|
||||||
sc->newH = hsc;
|
// And note the new ID so that we can rewrite the trims appropriately
|
||||||
|
sc->newH = hsc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,7 @@
|
||||||
// and convergence should be fast by now.
|
// and convergence should be fast by now.
|
||||||
#define RATPOLY_EPS (LENGTH_EPS/(1e2))
|
#define RATPOLY_EPS (LENGTH_EPS/(1e2))
|
||||||
|
|
||||||
static double Bernstein(int k, int deg, double t)
|
static inline double Bernstein(int k, int deg, double t) {
|
||||||
{
|
|
||||||
// indexed by [degree][k][exponent]
|
// indexed by [degree][k][exponent]
|
||||||
static const double bernstein_coeff[4][4][4] = {
|
static const double bernstein_coeff[4][4][4] = {
|
||||||
{ { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 } },
|
{ { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 } },
|
||||||
|
@ -22,21 +21,18 @@ static double Bernstein(int k, int deg, double t)
|
||||||
{ { 1.0,-2.0,1.0,0.0 }, { 0.0,2.0,-2.0,0.0 },{ 0.0,0.0,1.0,0.0 }, { 0.0,0.0,0.0,0.0 } },
|
{ { 1.0,-2.0,1.0,0.0 }, { 0.0,2.0,-2.0,0.0 },{ 0.0,0.0,1.0,0.0 }, { 0.0,0.0,0.0,0.0 } },
|
||||||
{ { 1.0,-3.0,3.0,-1.0 },{ 0.0,3.0,-6.0,3.0 },{ 0.0,0.0,3.0,-3.0}, { 0.0,0.0,0.0,1.0 } } };
|
{ { 1.0,-3.0,3.0,-1.0 },{ 0.0,3.0,-6.0,3.0 },{ 0.0,0.0,3.0,-3.0}, { 0.0,0.0,0.0,1.0 } } };
|
||||||
|
|
||||||
const double *c;
|
const double *c = bernstein_coeff[deg][k];
|
||||||
c = bernstein_coeff[deg][k];
|
|
||||||
return (((c[3]*t+c[2])*t)+c[1])*t+c[0];
|
return (((c[3]*t+c[2])*t)+c[1])*t+c[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static double BernsteinDerivative(int k, int deg, double t)
|
static inline double BernsteinDerivative(int k, int deg, double t) {
|
||||||
{
|
|
||||||
static const double bernstein_derivative_coeff[4][4][3] = {
|
static const double bernstein_derivative_coeff[4][4][3] = {
|
||||||
{ { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } },
|
{ { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } },
|
||||||
{ { -1.0,0.0,0.0 }, { 1.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } },
|
{ { -1.0,0.0,0.0 }, { 1.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } },
|
||||||
{ { -2.0,2.0,0.0 }, { 2.0,-4.0,0.0 },{ 0.0,2.0,0.0 }, { 0.0,0.0,0.0 } },
|
{ { -2.0,2.0,0.0 }, { 2.0,-4.0,0.0 },{ 0.0,2.0,0.0 }, { 0.0,0.0,0.0 } },
|
||||||
{ { -3.0,6.0,-3.0 },{ 3.0,-12.0,9.0 },{ 0.0,6.0,-9.0}, { 0.0,0.0,3.0 } } };
|
{ { -3.0,6.0,-3.0 },{ 3.0,-12.0,9.0 },{ 0.0,6.0,-9.0}, { 0.0,0.0,3.0 } } };
|
||||||
|
|
||||||
const double *c;
|
const double *c = bernstein_derivative_coeff[deg][k];
|
||||||
c = bernstein_derivative_coeff[deg][k];
|
|
||||||
return ((c[2]*t)+c[1])*t+c[0];
|
return ((c[2]*t)+c[1])*t+c[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +328,7 @@ Vector SSurface::PointAt(double u, double v) const {
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) const {
|
void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry) const {
|
||||||
Vector num = Vector::From(0, 0, 0),
|
Vector num = Vector::From(0, 0, 0),
|
||||||
num_u = Vector::From(0, 0, 0),
|
num_u = Vector::From(0, 0, 0),
|
||||||
num_v = Vector::From(0, 0, 0);
|
num_v = Vector::From(0, 0, 0);
|
||||||
|
@ -364,6 +360,12 @@ void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) const {
|
||||||
|
|
||||||
*tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v)));
|
*tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v)));
|
||||||
*tv = tv->ScaledBy(1.0/(den*den));
|
*tv = tv->ScaledBy(1.0/(den*den));
|
||||||
|
|
||||||
|
// Tangent is zero at sungularities like the north pole. Move away a bit and retry.
|
||||||
|
if(tv->Equals(Vector::From(0,0,0)) && retry)
|
||||||
|
TangentsAt(u+(0.5-u)*0.00001, v, tu, tv, false);
|
||||||
|
if(tu->Equals(Vector::From(0,0,0)) && retry)
|
||||||
|
TangentsAt(u, v+(0.5-v)*0.00001, tu, tv, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector SSurface::NormalAt(Point2d puv) const {
|
Vector SSurface::NormalAt(Point2d puv) const {
|
||||||
|
|
|
@ -507,9 +507,9 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb
|
||||||
Vector n = sbls->normal.ScaledBy(-1);
|
Vector n = sbls->normal.ScaledBy(-1);
|
||||||
Vector u = n.Normal(0), v = n.Normal(1);
|
Vector u = n.Normal(0), v = n.Normal(1);
|
||||||
Vector orig = sbls->point;
|
Vector orig = sbls->point;
|
||||||
double umax = 1e-10, umin = 1e10;
|
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||||
double vmax = 1e-10, vmin = 1e10;
|
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
||||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||||
// and now fix things up so that all u and v lie between 0 and 1
|
// 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(u.ScaledBy(umin));
|
||||||
|
@ -663,9 +663,9 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
|
||||||
Vector n = sbls->normal.ScaledBy(-1);
|
Vector n = sbls->normal.ScaledBy(-1);
|
||||||
Vector u = n.Normal(0), v = n.Normal(1);
|
Vector u = n.Normal(0), v = n.Normal(1);
|
||||||
Vector orig = sbls->point;
|
Vector orig = sbls->point;
|
||||||
double umax = 1e-10, umin = 1e10;
|
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||||
double vmax = 1e-10, vmin = 1e10;
|
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
||||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||||
// and now fix things up so that all u and v lie between 0 and 1
|
// 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(u.ScaledBy(umin));
|
||||||
|
|
|
@ -219,6 +219,7 @@ public:
|
||||||
SSurface *GetSurfaceB(SShell *a, SShell *b) const;
|
SSurface *GetSurfaceB(SShell *a, SShell *b) const;
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
|
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A segment of a curve by which a surface is trimmed: indicates which curve,
|
// A segment of a curve by which a surface is trimmed: indicates which curve,
|
||||||
|
@ -335,13 +336,14 @@ public:
|
||||||
void PointOnCurve(const SBezier *curve, double *up, double *vp);
|
void PointOnCurve(const SBezier *curve, double *up, double *vp);
|
||||||
Vector PointAt(double u, double v) const;
|
Vector PointAt(double u, double v) const;
|
||||||
Vector PointAt(Point2d puv) const;
|
Vector PointAt(Point2d puv) const;
|
||||||
void TangentsAt(double u, double v, Vector *tu, Vector *tv) const;
|
void TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry=true) const;
|
||||||
Vector NormalAt(Point2d puv) const;
|
Vector NormalAt(Point2d puv) const;
|
||||||
Vector NormalAt(double u, double v) const;
|
Vector NormalAt(double u, double v) const;
|
||||||
bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const;
|
bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const;
|
||||||
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
|
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
|
||||||
bool CoincidentWithPlane(Vector n, double d) const;
|
bool CoincidentWithPlane(Vector n, double d) const;
|
||||||
bool CoincidentWith(SSurface *ss, bool sameNormal) const;
|
bool CoincidentWith(SSurface *ss, bool sameNormal) const;
|
||||||
|
bool ContainsPlaneCurve(SCurve *sc) const;
|
||||||
bool IsExtrusion(SBezier *of, Vector *along) const;
|
bool IsExtrusion(SBezier *of, Vector *along) const;
|
||||||
bool IsCylinder(Vector *axis, Vector *center, double *r,
|
bool IsCylinder(Vector *axis, Vector *center, double *r,
|
||||||
Vector *start, Vector *finish) const;
|
Vector *start, Vector *finish) const;
|
||||||
|
|
|
@ -313,6 +313,45 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
|
||||||
inters.Clear();
|
inters.Clear();
|
||||||
lv.Clear();
|
lv.Clear();
|
||||||
} else {
|
} else {
|
||||||
|
if((degm == 1 && degn == 1) || (b->degm == 1 && b->degn == 1)) {
|
||||||
|
// we should only be here if just one surface is a plane because the
|
||||||
|
// plane-plane case was already handled above. Need to check the other
|
||||||
|
// nonplanar surface for trim curves that lie in the plane and are not
|
||||||
|
// already trimming both surfaces. This happens when we cut a Lathe shell
|
||||||
|
// on one of the seams for example.
|
||||||
|
// This also seems necessary to merge some coincident surfaces.
|
||||||
|
SSurface *splane, *sext;
|
||||||
|
SShell *shext;
|
||||||
|
if(degm == 1 && degn == 1) { // this and other checks assume coplanar ctrl pts.
|
||||||
|
splane = this;
|
||||||
|
sext = b;
|
||||||
|
shext = agnstB;
|
||||||
|
} else {
|
||||||
|
splane = b;
|
||||||
|
sext = this;
|
||||||
|
shext = agnstA;
|
||||||
|
}
|
||||||
|
bool foundExact = false;
|
||||||
|
SCurve *sc;
|
||||||
|
for(sc = shext->curve.First(); sc; sc = shext->curve.NextAfter(sc)) {
|
||||||
|
if(sc->source == SCurve::Source::INTERSECTION) continue;
|
||||||
|
if(!sc->isExact) continue;
|
||||||
|
if((sc->surfA != sext->h) && (sc->surfB != sext->h)) continue;
|
||||||
|
// we have a curve belonging to the curved surface and not the plane.
|
||||||
|
// does it lie completely in the plane?
|
||||||
|
if(splane->ContainsPlaneCurve(sc)) {
|
||||||
|
SBezier bezier = sc->exact;
|
||||||
|
AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into);
|
||||||
|
foundExact = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we found at lest one of these we don't want to do the numerical
|
||||||
|
// intersection as well. Sometimes it will also find the same curve but
|
||||||
|
// with different PWLs and the polygon will fail to assemble.
|
||||||
|
if(foundExact)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try intersecting the surfaces numerically, by a marching algorithm.
|
// Try intersecting the surfaces numerically, by a marching algorithm.
|
||||||
// First, we find all the intersections between a surface and the
|
// First, we find all the intersections between a surface and the
|
||||||
// boundary of the other surface.
|
// boundary of the other surface.
|
||||||
|
@ -505,6 +544,24 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Does a planar surface contain a curve? Does the curve lie completely in plane?
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool SSurface::ContainsPlaneCurve(SCurve *sc) const {
|
||||||
|
if(degm != 1 || degn != 1) return false;
|
||||||
|
if(!sc->isExact) return false; // we don't handle those (yet?)
|
||||||
|
|
||||||
|
Vector p = ctrl[0][0];
|
||||||
|
Vector n = NormalAt(0, 0).WithMagnitude(1);
|
||||||
|
double d = n.Dot(p);
|
||||||
|
|
||||||
|
// check all control points on the curve
|
||||||
|
for(int i=0; i<= sc->exact.deg; i++) {
|
||||||
|
if(fabs(n.Dot(sc->exact.ctrl[i]) - d) > LENGTH_EPS) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// In our shell, find all surfaces that are coincident with the prototype
|
// In our shell, find all surfaces that are coincident with the prototype
|
||||||
// surface (with same or opposite normal, as specified), and copy all of
|
// surface (with same or opposite normal, as specified), and copy all of
|
||||||
|
|
|
@ -143,7 +143,7 @@ void TextWindow::ShowListOfGroups() {
|
||||||
// Link to the errors, if a problem occurred while solving
|
// Link to the errors, if a problem occurred while solving
|
||||||
ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x',
|
ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x',
|
||||||
g->h.v, (&TextWindow::ScreenHowGroupSolved),
|
g->h.v, (&TextWindow::ScreenHowGroupSolved),
|
||||||
ok ? (warn ? "err" : sdof) : "",
|
ok ? ((warn && SS.checkClosedContour) ? "err" : sdof) : "",
|
||||||
ok ? "" : "ERR",
|
ok ? "" : "ERR",
|
||||||
// Link to a screen that gives more details on the group
|
// Link to a screen that gives more details on the group
|
||||||
g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str());
|
g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str());
|
||||||
|
|
|
@ -252,8 +252,18 @@ void TextWindow::Init() {
|
||||||
MouseLeave();
|
MouseLeave();
|
||||||
return true;
|
return true;
|
||||||
} else if(event.type == MouseEvent::Type::SCROLL_VERT) {
|
} else if(event.type == MouseEvent::Type::SCROLL_VERT) {
|
||||||
window->SetScrollbarPosition(window->GetScrollbarPosition() -
|
if (event.scrollDelta == 0) {
|
||||||
LINE_HEIGHT / 2 * event.scrollDelta);
|
return true;
|
||||||
|
}
|
||||||
|
if (abs(event.scrollDelta) < 0.2) {
|
||||||
|
if (event.scrollDelta > 0) {
|
||||||
|
event.scrollDelta = 0.2;
|
||||||
|
} else {
|
||||||
|
event.scrollDelta = -0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double offset = LINE_HEIGHT / 2 * event.scrollDelta;
|
||||||
|
ScrollbarEvent(window->GetScrollbarPosition() - offset);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -338,11 +348,22 @@ void TextWindow::ClearScreen() {
|
||||||
rows = 0;
|
rows = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This message was addded when someone had too many fonts for the text window
|
||||||
|
// Scrolling seemed to be broken, but was actaully at the MAX_ROWS.
|
||||||
|
static const char* endString = " **** End of Text Screen ****";
|
||||||
|
|
||||||
void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
|
void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
|
||||||
if(!canvas) return;
|
if(!canvas) return;
|
||||||
|
|
||||||
if(rows >= MAX_ROWS) return;
|
if(rows >= MAX_ROWS) return;
|
||||||
|
|
||||||
|
if(rows >= MAX_ROWS-2 && (fmt != endString)) {
|
||||||
|
// twice due to some half-row issues on resizing
|
||||||
|
Printf(halfLine, endString);
|
||||||
|
Printf(halfLine, endString);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
va_list vl;
|
va_list vl;
|
||||||
va_start(vl, fmt);
|
va_start(vl, fmt);
|
||||||
|
|
||||||
|
@ -1130,16 +1151,16 @@ void TextWindow::MouseLeave() {
|
||||||
|
|
||||||
void TextWindow::ScrollbarEvent(double newPos) {
|
void TextWindow::ScrollbarEvent(double newPos) {
|
||||||
if(window->IsEditorVisible()) {
|
if(window->IsEditorVisible()) {
|
||||||
window->SetScrollbarPosition(scrollPos);
|
// An edit field is active. Do not move the scrollbar.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bottom = top[rows-1] + 2;
|
int bottom = top[rows-1] + 2;
|
||||||
newPos = min((int)newPos, bottom - halfRows);
|
newPos = min((int)newPos, bottom - halfRows);
|
||||||
newPos = max((int)newPos, 0);
|
newPos = max((int)newPos, 0);
|
||||||
|
|
||||||
if(newPos != scrollPos) {
|
if(newPos != scrollPos) {
|
||||||
scrollPos = (int)newPos;
|
scrollPos = (int)newPos;
|
||||||
|
window->SetScrollbarPosition(scrollPos);
|
||||||
window->Invalidate();
|
window->Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,10 @@ static ToolIcon Toolbar[] = {
|
||||||
N_("New group extruding active sketch"), {} },
|
N_("New group extruding active sketch"), {} },
|
||||||
{ "lathe", Command::GROUP_LATHE,
|
{ "lathe", Command::GROUP_LATHE,
|
||||||
N_("New group rotating active sketch"), {} },
|
N_("New group rotating active sketch"), {} },
|
||||||
|
{ "helix", Command::GROUP_HELIX,
|
||||||
|
N_("New group helix from active sketch"), {} },
|
||||||
|
{ "revolve", Command::GROUP_REVOLVE,
|
||||||
|
N_("New group revolve active sketch"), {} },
|
||||||
{ "step-rotate", Command::GROUP_ROT,
|
{ "step-rotate", Command::GROUP_ROT,
|
||||||
N_("New group step and repeat rotating"), {} },
|
N_("New group step and repeat rotating"), {} },
|
||||||
{ "step-translate", Command::GROUP_TRANS,
|
{ "step-translate", Command::GROUP_TRANS,
|
||||||
|
@ -153,7 +157,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas,
|
||||||
|
|
||||||
// When changing these values, also change the asReference drawing code in drawentity.cpp.
|
// When changing these values, also change the asReference drawing code in drawentity.cpp.
|
||||||
int fudge = 8;
|
int fudge = 8;
|
||||||
int h = 34*16 + 3*16 + fudge;
|
int h = 32*18 + 3*16 + fudge;
|
||||||
int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h;
|
int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h;
|
||||||
|
|
||||||
bool withinToolbar =
|
bool withinToolbar =
|
||||||
|
|
8
src/ui.h
8
src/ui.h
|
@ -80,6 +80,7 @@ enum class Command : uint32_t {
|
||||||
ZOOM_OUT,
|
ZOOM_OUT,
|
||||||
ZOOM_TO_FIT,
|
ZOOM_TO_FIT,
|
||||||
SHOW_GRID,
|
SHOW_GRID,
|
||||||
|
DIM_SOLID_MODEL,
|
||||||
PERSPECTIVE_PROJ,
|
PERSPECTIVE_PROJ,
|
||||||
ONTO_WORKPLANE,
|
ONTO_WORKPLANE,
|
||||||
NEAREST_ORTHO,
|
NEAREST_ORTHO,
|
||||||
|
@ -179,7 +180,7 @@ public:
|
||||||
enum {
|
enum {
|
||||||
MAX_COLS = 100,
|
MAX_COLS = 100,
|
||||||
MIN_COLS = 45,
|
MIN_COLS = 45,
|
||||||
MAX_ROWS = 2000
|
MAX_ROWS = 4000
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -532,6 +533,7 @@ public:
|
||||||
Platform::MenuRef linkRecentMenu;
|
Platform::MenuRef linkRecentMenu;
|
||||||
|
|
||||||
Platform::MenuItemRef showGridMenuItem;
|
Platform::MenuItemRef showGridMenuItem;
|
||||||
|
Platform::MenuItemRef dimSolidModelMenuItem;
|
||||||
Platform::MenuItemRef perspectiveProjMenuItem;
|
Platform::MenuItemRef perspectiveProjMenuItem;
|
||||||
Platform::MenuItemRef showToolbarMenuItem;
|
Platform::MenuItemRef showToolbarMenuItem;
|
||||||
Platform::MenuItemRef showTextWndMenuItem;
|
Platform::MenuItemRef showTextWndMenuItem;
|
||||||
|
@ -727,6 +729,7 @@ public:
|
||||||
public:
|
public:
|
||||||
int zIndex;
|
int zIndex;
|
||||||
double distance;
|
double distance;
|
||||||
|
double depth;
|
||||||
Selection selection;
|
Selection selection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -802,6 +805,7 @@ public:
|
||||||
DrawOccludedAs drawOccludedAs;
|
DrawOccludedAs drawOccludedAs;
|
||||||
|
|
||||||
bool showSnapGrid;
|
bool showSnapGrid;
|
||||||
|
bool dimSolidModel;
|
||||||
void DrawSnapGrid(Canvas *canvas);
|
void DrawSnapGrid(Canvas *canvas);
|
||||||
|
|
||||||
void AddPointToDraggedList(hEntity hp);
|
void AddPointToDraggedList(hEntity hp);
|
||||||
|
@ -824,7 +828,7 @@ public:
|
||||||
void MouseLeftDoubleClick(double x, double y);
|
void MouseLeftDoubleClick(double x, double y);
|
||||||
void MouseMiddleOrRightDown(double x, double y);
|
void MouseMiddleOrRightDown(double x, double y);
|
||||||
void MouseRightUp(double x, double y);
|
void MouseRightUp(double x, double y);
|
||||||
void MouseScroll(double x, double y, int delta);
|
void MouseScroll(double x, double y, double delta);
|
||||||
void MouseLeave();
|
void MouseLeave();
|
||||||
bool KeyboardEvent(Platform::KeyboardEvent event);
|
bool KeyboardEvent(Platform::KeyboardEvent event);
|
||||||
void EditControlDone(const std::string &s);
|
void EditControlDone(const std::string &s);
|
||||||
|
|
78
src/util.cpp
78
src/util.cpp
|
@ -428,12 +428,6 @@ Quaternion Quaternion::Mirror() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Vector Vector::From(double x, double y, double z) {
|
|
||||||
Vector v;
|
|
||||||
v.x = x; v.y = y; v.z = z;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector Vector::From(hParam x, hParam y, hParam z) {
|
Vector Vector::From(hParam x, hParam y, hParam z) {
|
||||||
Vector v;
|
Vector v;
|
||||||
v.x = SK.GetParam(x)->val;
|
v.x = SK.GetParam(x)->val;
|
||||||
|
@ -448,50 +442,6 @@ bool Vector::EqualsExactly(Vector v) const {
|
||||||
z == v.z);
|
z == v.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector Vector::Plus(Vector b) const {
|
|
||||||
Vector r;
|
|
||||||
|
|
||||||
r.x = x + b.x;
|
|
||||||
r.y = y + b.y;
|
|
||||||
r.z = z + b.z;
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector Vector::Minus(Vector b) const {
|
|
||||||
Vector r;
|
|
||||||
|
|
||||||
r.x = x - b.x;
|
|
||||||
r.y = y - b.y;
|
|
||||||
r.z = z - b.z;
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector Vector::Negated() const {
|
|
||||||
Vector r;
|
|
||||||
|
|
||||||
r.x = -x;
|
|
||||||
r.y = -y;
|
|
||||||
r.z = -z;
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector Vector::Cross(Vector b) const {
|
|
||||||
Vector r;
|
|
||||||
|
|
||||||
r.x = -(z*b.y) + (y*b.z);
|
|
||||||
r.y = (z*b.x) - (x*b.z);
|
|
||||||
r.z = -(y*b.x) + (x*b.y);
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Vector::Dot(Vector b) const {
|
|
||||||
return (x*b.x + y*b.y + z*b.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
double Vector::DirectionCosineWith(Vector b) const {
|
double Vector::DirectionCosineWith(Vector b) const {
|
||||||
Vector a = this->WithMagnitude(1);
|
Vector a = this->WithMagnitude(1);
|
||||||
b = b.WithMagnitude(1);
|
b = b.WithMagnitude(1);
|
||||||
|
@ -629,24 +579,6 @@ Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) const {
|
||||||
return this->Plus(n.WithMagnitude(d));
|
return this->Plus(n.WithMagnitude(d));
|
||||||
}
|
}
|
||||||
|
|
||||||
double Vector::MagSquared() const {
|
|
||||||
return x*x + y*y + z*z;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Vector::Magnitude() const {
|
|
||||||
return sqrt(x*x + y*y + z*z);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector Vector::ScaledBy(double v) const {
|
|
||||||
Vector r;
|
|
||||||
|
|
||||||
r.x = x * v;
|
|
||||||
r.y = y * v;
|
|
||||||
r.z = z * v;
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector Vector::WithMagnitude(double v) const {
|
Vector Vector::WithMagnitude(double v) const {
|
||||||
double m = Magnitude();
|
double m = Magnitude();
|
||||||
if(EXACT(m == 0)) {
|
if(EXACT(m == 0)) {
|
||||||
|
@ -729,16 +661,6 @@ Vector Vector::ClampWithin(double minv, double maxv) const {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const {
|
|
||||||
maxv->x = max(maxv->x, x);
|
|
||||||
maxv->y = max(maxv->y, y);
|
|
||||||
maxv->z = max(maxv->z, z);
|
|
||||||
|
|
||||||
minv->x = min(minv->x, x);
|
|
||||||
minv->y = min(minv->y, y);
|
|
||||||
minv->z = min(minv->z, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) const {
|
bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) const {
|
||||||
return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) ||
|
return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) ||
|
||||||
(y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) ||
|
(y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) ||
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
@ -83,6 +83,7 @@ TEST_CASE(extension) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(with_extension) {
|
TEST_CASE(with_extension) {
|
||||||
|
CHECK_EQ_STR(Path::From("foo.bar").WithExtension("").raw, "foo");
|
||||||
CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz");
|
CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz");
|
||||||
CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz");
|
CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue