Fix conflict.
commit
fb5e731e6e
|
@ -1,8 +1,7 @@
|
|||
### System information
|
||||
|
||||
SolveSpace version: <!--e.g. 3.0~3dd2fc00; go to Help → About...-->
|
||||
|
||||
Operating system: <!--e.g. Debian testing-->
|
||||
- **SolveSpace version:** <!--e.g. 3.0~3dd2fc00; go to Help → About...-->
|
||||
- **Operating system:** <!--e.g. Debian testing-->
|
||||
|
||||
### 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 \
|
||||
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
|
||||
|
||||
brew update
|
||||
brew install freetype cairo
|
||||
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"]
|
||||
path = extlib/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"]
|
||||
path = extlib/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,
|
||||
not just at intersection of two curves.
|
||||
* 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:
|
||||
* 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
|
||||
mesh color information.
|
||||
* 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
|
||||
in SolveSpace, supports colour information and is more space efficient than
|
||||
most other formats.
|
||||
|
@ -110,6 +113,7 @@ Other new features:
|
|||
that are shortcuts to the respective configuration screens.
|
||||
* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes
|
||||
to enable support for multi-threading and link-time optimization.
|
||||
* "Shift+Scroll" for ten times finer zoom.
|
||||
|
||||
Bugs fixed:
|
||||
* 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)
|
||||
cmake_policy(VERSION 3.11.0)
|
||||
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}
|
||||
"${CMAKE_SOURCE_DIR}/cmake/")
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
@ -22,6 +27,10 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE
|
|||
set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX
|
||||
"${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
|
||||
|
||||
# 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")
|
||||
set(ENABLE_OPENMP OFF CACHE BOOL
|
||||
"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)")
|
||||
|
||||
|
@ -69,7 +80,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|||
endif()
|
||||
|
||||
# common compiler flags
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
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()
|
||||
|
||||
if(ENABLE_LTO)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported()
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
if(ENABLE_OPENMP)
|
||||
include(FindOpenMP)
|
||||
if(OpenMP_FOUND)
|
||||
find_package( OpenMP REQUIRED )
|
||||
if(OPENMP_FOUND)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
|
||||
message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} )
|
||||
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")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
endif()
|
||||
|
@ -144,8 +153,12 @@ if(ENABLE_SANITIZERS)
|
|||
endif()
|
||||
|
||||
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")
|
||||
set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls")
|
||||
|
@ -168,17 +181,6 @@ endif()
|
|||
message(STATUS "Using in-tree 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")
|
||||
set(MI_OVERRIDE 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,
|
||||
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
|
||||
sign a [Contributor License Agreement](https://cla-assistant.io/solvespace/solvespace).
|
||||
SolveSpace is licensed under the GPLv3 and any contributions must be made available
|
||||
under the terms of that license.
|
||||
|
||||
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
|
||||
==========
|
||||
[![Build Status](https://github.com/solvespace/solvespace/workflows/CD/badge.svg)](https://github.com/solvespace/solvespace/actions)
|
||||
[![solvespace](https://snapcraft.io/solvespace/badge.svg)](https://snapcraft.io/solvespace)
|
||||
[![solvespace](https://snapcraft.io/solvespace/trending.svg?name=0)](https://snapcraft.io/solvespace)
|
||||
|
||||
This repository contains the source code of [SolveSpace][], a parametric
|
||||
2d/3d CAD.
|
||||
|
@ -83,26 +88,29 @@ Before building, check out the project and the necessary submodules:
|
|||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc
|
||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||
|
||||
After that, build SolveSpace as following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||
make
|
||||
sudo make install
|
||||
|
||||
Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the
|
||||
expense of longer build time.
|
||||
|
||||
The graphical interface is built as `build/bin/solvespace`, and the command-line interface
|
||||
is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface
|
||||
by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
||||
is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
||||
|
||||
### Building for Windows
|
||||
|
||||
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:
|
||||
Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed to work.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -110,16 +118,7 @@ Before building, check out the project and the necessary submodules:
|
|||
cd solvespace
|
||||
git submodule update --init
|
||||
|
||||
After that, build 32-bit SolveSpace as 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:
|
||||
Build 64-bit SolveSpace with the following:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
|
@ -157,9 +156,12 @@ After that, build SolveSpace as following:
|
|||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
|
||||
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:
|
||||
|
||||
mkdir build
|
||||
|
@ -185,7 +187,7 @@ Before building, check out the project and the necessary submodules:
|
|||
|
||||
git clone https://github.com/solvespace/solvespace
|
||||
cd solvespace
|
||||
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc
|
||||
git submodule update --init extlib/libdxfrw extlib/mimalloc
|
||||
|
||||
After that, build SolveSpace as following:
|
||||
|
||||
|
@ -257,6 +259,4 @@ and debug SolveSpace.
|
|||
License
|
||||
-------
|
||||
|
||||
SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt). It is possible
|
||||
to license SolveSpace for use in a commercial application; to do so,
|
||||
[contact](http://solvespace.com/contact.pl) the developers.
|
||||
SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt).
|
||||
|
|
|
@ -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"
|
||||
git describe --exact-match HEAD && grade="stable" || grade="devel"
|
||||
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:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DENABLE_TESTS=OFF
|
||||
- -DSNAP=ON
|
||||
- -DENABLE_OPENMP=ON
|
||||
- -DENABLE_LTO=ON
|
||||
build-packages:
|
||||
- zlib1g-dev
|
||||
- libpng-dev
|
||||
|
|
|
@ -213,6 +213,7 @@ add_resources(
|
|||
icons/graphics-window/construction.png
|
||||
icons/graphics-window/equal.png
|
||||
icons/graphics-window/extrude.png
|
||||
icons/graphics-window/helix.png
|
||||
icons/graphics-window/horiz.png
|
||||
icons/graphics-window/image.png
|
||||
icons/graphics-window/in3d.png
|
||||
|
@ -227,6 +228,7 @@ add_resources(
|
|||
icons/graphics-window/point.png
|
||||
icons/graphics-window/rectangle.png
|
||||
icons/graphics-window/ref.png
|
||||
icons/graphics-window/revolve.png
|
||||
icons/graphics-window/same-orientation.png
|
||||
icons/graphics-window/sketch-in-3d.png
|
||||
icons/graphics-window/sketch-in-plane.png
|
||||
|
@ -256,6 +258,7 @@ add_resources(
|
|||
locales/fr_FR.po
|
||||
locales/uk_UA.po
|
||||
locales/ru_RU.po
|
||||
locales/zh_CN.po
|
||||
fonts/unifont.hex.gz
|
||||
fonts/private/0-check-false.png
|
||||
fonts/private/1-check-true.png
|
||||
|
@ -281,7 +284,7 @@ add_resources(
|
|||
shaders/edge.frag
|
||||
shaders/edge.vert
|
||||
shaders/outline.vert
|
||||
threejs/three-r76.js.gz
|
||||
threejs/three-r111.min.js.gz
|
||||
threejs/hammer-2.0.8.js.gz
|
||||
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
|
||||
ru-RU,0419,Русский
|
||||
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);
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
var newLightPos = changeBasis.applyToVector3Array(
|
||||
[obj.lights.d[i].direction[0], obj.lights.d[i].direction[1],
|
||||
obj.lights.d[i].direction[2]]);
|
||||
var newLightPos = new THREE.Vector3(obj.lights.d[i].direction[0],
|
||||
obj.lights.d[i].direction[1],
|
||||
obj.lights.d[i].direction[2]).applyMatrix4(changeBasis);
|
||||
directionalLightArray[i].position.set(newLightPos[0],
|
||||
newLightPos[1], newLightPos[2]);
|
||||
}
|
||||
|
@ -515,7 +515,7 @@ solvespace = function(obj, params) {
|
|||
}
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList));
|
||||
return new THREE.Mesh(geometry, materialList);
|
||||
}
|
||||
|
||||
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 "OriginalFilename", "solvespace.exe"
|
||||
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
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@ include_directories(
|
|||
${PNG_PNG_INCLUDE_DIR}
|
||||
${FREETYPE_INCLUDE_DIRS}
|
||||
${CAIRO_INCLUDE_DIRS}
|
||||
${Q3D_INCLUDE_DIR}
|
||||
${MIMALLOC_INCLUDE_DIR})
|
||||
${MIMALLOC_INCLUDE_DIR}
|
||||
${OpenMP_CXX_INCLUDE_DIRS})
|
||||
|
||||
if(Backtrace_FOUND)
|
||||
include_directories(
|
||||
|
@ -214,7 +214,6 @@ add_library(solvespace-core STATIC
|
|||
${solvespace_core_SOURCES})
|
||||
|
||||
add_dependencies(solvespace-core
|
||||
q3d_header
|
||||
mimalloc-static)
|
||||
|
||||
target_link_libraries(solvespace-core
|
||||
|
@ -224,7 +223,6 @@ target_link_libraries(solvespace-core
|
|||
${ZLIB_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${FREETYPE_LIBRARY}
|
||||
flatbuffers
|
||||
mimalloc-static)
|
||||
|
||||
if(Backtrace_FOUND)
|
||||
|
@ -241,7 +239,8 @@ if(HAVE_GETTEXT)
|
|||
set(inputs
|
||||
${solvespace_core_SOURCES}
|
||||
${solvespace_core_HEADERS}
|
||||
${every_platform_SOURCES})
|
||||
${every_platform_SOURCES}
|
||||
${solvespace_core_gl_SOURCES})
|
||||
|
||||
set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po)
|
||||
|
||||
|
@ -404,20 +403,15 @@ endif()
|
|||
if(APPLE)
|
||||
set(bundle SolveSpace)
|
||||
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
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${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"
|
||||
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()
|
||||
|
|
|
@ -227,7 +227,6 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
|||
MakeSelected(hr.entity(j));
|
||||
}
|
||||
}
|
||||
|
||||
Constraint *cc;
|
||||
for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) {
|
||||
Constraint c = {};
|
||||
|
@ -246,25 +245,62 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
|
|||
c.reference = cc->reference;
|
||||
c.disp = cc->disp;
|
||||
c.comment = cc->comment;
|
||||
bool dontAddConstraint = false;
|
||||
switch(c.type) {
|
||||
case Constraint::Type::COMMENT:
|
||||
c.disp.offset = c.disp.offset.Plus(trans);
|
||||
break;
|
||||
|
||||
case Constraint::Type::PT_PT_DISTANCE:
|
||||
case Constraint::Type::PT_LINE_DISTANCE:
|
||||
case Constraint::Type::PROJ_PT_DISTANCE:
|
||||
case Constraint::Type::DIAMETER:
|
||||
c.valA *= fabs(scale);
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
|
||||
if(c.type == Constraint::Type::COMMENT) {
|
||||
MakeSelected(hc);
|
||||
if (!dontAddConstraint) {
|
||||
hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
|
||||
if(c.type == Constraint::Type::COMMENT) {
|
||||
MakeSelected(hc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
// Copyright 2008-2013 Jonathan Westhues.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
#if defined(_OPENMP)
|
||||
#include <omp.h>
|
||||
#endif
|
||||
|
||||
void TextWindow::ScreenChangeLightDirection(int link, uint32_t 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 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) {
|
||||
|
|
|
@ -127,6 +127,66 @@ hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
|
|||
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) {
|
||||
Constraint c = {};
|
||||
c.group = SS.GW.activeGroup;
|
||||
|
@ -617,50 +677,22 @@ void Constraint::MenuConstrain(Command id) {
|
|||
c.entityA = gs.vector[0];
|
||||
c.entityB = gs.vector[1];
|
||||
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
|
||||
Entity *line = SK.GetEntity(gs.entity[0]);
|
||||
Entity *arc = SK.GetEntity(gs.entity[1]);
|
||||
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||
*arc = SK.GetEntity(gs.entity[1]);
|
||||
if(line->type == Entity::Type::ARC_OF_CIRCLE) {
|
||||
swap(line, 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;
|
||||
}
|
||||
ConstrainArcLineTangent(&c, line, arc);
|
||||
c.type = Type::ARC_LINE_TANGENT;
|
||||
c.entityA = arc->h;
|
||||
c.entityB = line->h;
|
||||
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
|
||||
Entity *line = SK.GetEntity(gs.entity[0]);
|
||||
Entity *cubic = SK.GetEntity(gs.entity[1]);
|
||||
Entity *line = SK.GetEntity(gs.entity[0]),
|
||||
*cubic = SK.GetEntity(gs.entity[1]);
|
||||
if(line->type == Entity::Type::CUBIC) {
|
||||
swap(line, 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;
|
||||
}
|
||||
ConstrainCubicLineTangent(&c, line, cubic);
|
||||
c.type = Type::CUBIC_LINE_TANGENT;
|
||||
c.entityA = cubic->h;
|
||||
c.entityB = line->h;
|
||||
|
@ -671,24 +703,7 @@ void Constraint::MenuConstrain(Command id) {
|
|||
}
|
||||
Entity *eA = SK.GetEntity(gs.entity[0]),
|
||||
*eB = SK.GetEntity(gs.entity[1]);
|
||||
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;
|
||||
}
|
||||
ConstrainCurveCurveTangent(&c, eA, eB);
|
||||
c.type = Type::CURVE_CURVE_TANGENT;
|
||||
c.entityA = eA->h;
|
||||
c.entityB = eB->h;
|
||||
|
|
|
@ -47,6 +47,7 @@ void TextWindow::ScreenConstraintToggleReference(int link, uint32_t v) {
|
|||
SS.UndoRemember();
|
||||
c->reference = !c->reference;
|
||||
|
||||
SS.MarkGroupDirty(c->group);
|
||||
SS.ScheduleShowTW();
|
||||
}
|
||||
|
||||
|
|
31
src/draw.cpp
31
src/draw.cpp
|
@ -207,8 +207,8 @@ void GraphicsWindow::MakeSelected(Selection *stog) {
|
|||
//-----------------------------------------------------------------------------
|
||||
void GraphicsWindow::SelectByMarquee() {
|
||||
Point2d marqueePoint = ProjectPoint(orig.marqueePoint);
|
||||
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, -1),
|
||||
Vector::From(orig.mouse.x, orig.mouse.y, 1));
|
||||
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE),
|
||||
Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE));
|
||||
|
||||
Entity *e;
|
||||
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
|
||||
|
@ -305,9 +305,16 @@ void GraphicsWindow::GroupSelection() {
|
|||
|
||||
Camera GraphicsWindow::GetCamera() const {
|
||||
Camera camera = {};
|
||||
window->GetContentSize(&camera.width, &camera.height);
|
||||
camera.pixelRatio = window->GetDevicePixelRatio();
|
||||
camera.gridFit = (window->GetDevicePixelRatio() == 1);
|
||||
if(window) {
|
||||
window->GetContentSize(&camera.width, &camera.height);
|
||||
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.projUp = projUp;
|
||||
camera.projRight = projRight;
|
||||
|
@ -335,6 +342,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
|
|||
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
||||
int bestOrder = -1;
|
||||
int bestZIndex = 0;
|
||||
double bestDepth = VERY_POSITIVE;
|
||||
|
||||
for(const Hover &hov : hoverList) {
|
||||
hGroup hg = {};
|
||||
if(hov.selection.entity.v != 0) {
|
||||
|
@ -345,9 +354,12 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
|
|||
|
||||
Group *g = SK.GetGroup(hg);
|
||||
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;
|
||||
bestZIndex = hov.zIndex;
|
||||
bestDepth = hov.depth;
|
||||
sel = hov.selection;
|
||||
}
|
||||
return sel;
|
||||
|
@ -363,6 +375,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
|
|||
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
|
||||
int bestOrder = -1;
|
||||
int bestZIndex = 0;
|
||||
double bestDepth = VERY_POSITIVE;
|
||||
|
||||
for(const Hover &hov : hoverList) {
|
||||
hGroup hg = {};
|
||||
if(hov.selection.entity.v != 0) {
|
||||
|
@ -375,7 +389,9 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
|
|||
|
||||
Group *g = SK.GetGroup(hg);
|
||||
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;
|
||||
bestZIndex = hov.zIndex;
|
||||
sel = hov.selection;
|
||||
|
@ -432,6 +448,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
|
|||
Hover hov = {};
|
||||
hov.distance = canvas.minDistance;
|
||||
hov.zIndex = canvas.maxZIndex;
|
||||
hov.depth = canvas.minDepth;
|
||||
hov.selection.entity = e.h;
|
||||
hoverList.Add(&hov);
|
||||
}
|
||||
|
|
|
@ -64,13 +64,13 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
|
|||
Vector proj = SS.GW.ProjectPoint3(PointGetNum());
|
||||
screenBBox = BBox::From(proj, proj);
|
||||
} 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);
|
||||
} else if(!sbl->l.IsEmpty()) {
|
||||
Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]);
|
||||
screenBBox = BBox::From(first, first);
|
||||
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
|
||||
ssassert(false, "Expected entity to be a point or have beziers");
|
||||
|
@ -315,9 +315,13 @@ void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const {
|
|||
} else {
|
||||
// The wrapping would work, except when n = 1 and everything
|
||||
// wraps to zero...
|
||||
if(i > 0) bm.A[i][i - 1] = eq.x;
|
||||
/**/ bm.A[i][i] = eq.y;
|
||||
if(i < (n-1)) bm.A[i][i + 1] = eq.z;
|
||||
if(i > 0) {
|
||||
bm.A[i][i - 1] = eq.x;
|
||||
}
|
||||
bm.A[i][i] = eq.y;
|
||||
if(i < (n-1)) {
|
||||
bm.A[i][i + 1] = eq.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
bm.Solve();
|
||||
|
@ -468,13 +472,13 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
|
||||
int zIndex;
|
||||
if(IsPoint()) {
|
||||
zIndex = 5;
|
||||
zIndex = 6;
|
||||
} else if(how == DrawAs::HIDDEN) {
|
||||
zIndex = 2;
|
||||
} else if(group != SS.GW.activeGroup) {
|
||||
zIndex = 3;
|
||||
} else {
|
||||
zIndex = 4;
|
||||
zIndex = 5;
|
||||
}
|
||||
|
||||
hStyle hs;
|
||||
|
@ -484,6 +488,9 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
hs.v = Style::NORMALS;
|
||||
} else {
|
||||
hs = Style::ForEntity(h);
|
||||
if (hs.v == Style::CONSTRUCTION) {
|
||||
zIndex = 4;
|
||||
}
|
||||
}
|
||||
|
||||
Canvas::Stroke stroke = Style::Stroke(hs);
|
||||
|
@ -608,7 +615,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
|
|||
double w = 60 - camera.width / 2.0;
|
||||
// Shift the axis to the right if they would overlap with the toolbar.
|
||||
if(SS.showToolbar) {
|
||||
if(h + 30 > -(34*16 + 3*16 + 8) / 2)
|
||||
if(h + 30 > -(32*18 + 3*16 + 8) / 2)
|
||||
w += 60;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
size_t operator()(const Vector &v) const;
|
||||
};
|
||||
|
|
|
@ -843,8 +843,6 @@ void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) {
|
|||
ExportMeshAsObjTo(f, fMtl, m);
|
||||
|
||||
fclose(fMtl);
|
||||
} else if(filename.HasExtension("q3do")) {
|
||||
ExportMeshAsQ3doTo(f, m);
|
||||
} else if(filename.HasExtension("js") ||
|
||||
filename.HasExtension("html")) {
|
||||
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
|
||||
// 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 = {};
|
||||
STriangle *tr;
|
||||
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"(
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"></meta>
|
||||
<title>Three.js Solvespace Mesh</title>
|
||||
<script id="three-r76.js">%s</script>
|
||||
<script id="hammer-2.0.8.js">%s</script>
|
||||
<script id="SolveSpaceControls.js">%s</script>
|
||||
<script id="%s">%s</script>
|
||||
<script id="%s">%s</script>
|
||||
<script id="%s">%s</script>
|
||||
<style type="text/css">
|
||||
body { margin: 0; overflow: hidden; }
|
||||
</style>
|
||||
|
@ -1064,9 +1019,12 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename
|
|||
|
||||
if(filename.HasExtension("html")) {
|
||||
fprintf(f, htmlbegin,
|
||||
LoadStringFromGzip("threejs/three-r76.js.gz").c_str(),
|
||||
LoadStringFromGzip("threejs/hammer-2.0.8.js.gz").c_str(),
|
||||
LoadString("threejs/SolveSpaceControls.js").c_str());
|
||||
THREE_FN.c_str(),
|
||||
LoadStringFromGzip("threejs/" + THREE_FN + ".gz").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"
|
||||
|
|
|
@ -273,9 +273,45 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
|
|||
}
|
||||
|
||||
fprintf(f, "),#%d,.T.);\n", srfid);
|
||||
fprintf(f, "\n");
|
||||
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++;
|
||||
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 fileIsEmpty = true;
|
||||
allConsistent = false;
|
||||
fileLoadError = false;
|
||||
|
||||
|
@ -485,6 +486,8 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel)
|
|||
|
||||
char line[1024];
|
||||
while(fgets(line, (int)sizeof(line), fh)) {
|
||||
fileIsEmpty = false;
|
||||
|
||||
char *s = strchr(line, '\n');
|
||||
if(s) *s = '\0';
|
||||
// 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);
|
||||
|
||||
if(fileIsEmpty) {
|
||||
Error(_("The file is empty. It may be corrupt."));
|
||||
NewFile();
|
||||
}
|
||||
|
||||
if(fileLoadError) {
|
||||
Error(_("Unrecognized data in file. This file may be corrupt, or "
|
||||
"from a newer version of the program."));
|
||||
|
@ -904,11 +912,13 @@ try_again:
|
|||
} else if(linkMap.count(g.linkFile) == 0) {
|
||||
dbp("Missing file for group: %s", g.name.c_str());
|
||||
// The file was moved; prompt the user for its new location.
|
||||
switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) {
|
||||
const auto linkFileRelative = g.linkFile.RelativeTo(saveFile);
|
||||
switch(LocateImportedFile(linkFileRelative, canCancel)) {
|
||||
case Platform::MessageDialog::Response::YES: {
|
||||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||
dialog->ThawChoices(settings, "LinkSketch");
|
||||
dialog->SuggestFilename(linkFileRelative);
|
||||
if(dialog->RunModal()) {
|
||||
dialog->FreezeChoices(settings, "LinkSketch");
|
||||
linkMap[g.linkFile] = dialog->GetFilename();
|
||||
|
@ -985,6 +995,7 @@ bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
|
|||
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::RasterFileFilters);
|
||||
dialog->ThawChoices(settings, "LinkImage");
|
||||
dialog->SuggestFilename(filename->RelativeTo(saveFile));
|
||||
if(dialog->RunModal()) {
|
||||
dialog->FreezeChoices(settings, "LinkImage");
|
||||
*filename = dialog->GetFilename();
|
||||
|
|
|
@ -92,6 +92,7 @@ const MenuEntry Menu[] = {
|
|||
{ 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView },
|
||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||
{ 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_("Dimension &Units"), Command::NONE, 0, KN, NULL },
|
||||
{ 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) {
|
||||
showGridMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::DIM_SOLID_MODEL) {
|
||||
dimSolidModelMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
|
||||
perspectiveProjMenuItem = menuItem;
|
||||
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
|
||||
|
@ -406,6 +409,7 @@ void GraphicsWindow::Init() {
|
|||
showTextWindow = true;
|
||||
|
||||
showSnapGrid = false;
|
||||
dimSolidModel = true;
|
||||
context.active = false;
|
||||
toolbarHovered = Command::NONE;
|
||||
|
||||
|
@ -722,6 +726,12 @@ void GraphicsWindow::MenuView(Command id) {
|
|||
}
|
||||
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:
|
||||
SS.usePerspectiveProj = !SS.usePerspectiveProj;
|
||||
SS.GW.EnsureValidActives();
|
||||
|
@ -923,6 +933,7 @@ void GraphicsWindow::EnsureValidActives() {
|
|||
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
|
||||
|
||||
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
|
||||
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
|
||||
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
|
||||
showToolbarMenuItem->SetActive(SS.showToolbar);
|
||||
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
|
||||
// get placed as close as possible to our snap grid.
|
||||
SS.GW.ClearPending();
|
||||
|
||||
SS.GW.ClearSelection();
|
||||
SS.GW.Invalidate();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -569,7 +569,8 @@ void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
|
|||
if(!SS.GW.showShaded) {
|
||||
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);
|
||||
}
|
||||
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();
|
||||
double r = pa.Minus(center).Magnitude();
|
||||
double thetaa, thetab, dtheta;
|
||||
double theta, dtheta;
|
||||
|
||||
if(angle == 360.0) {
|
||||
thetaa = 0;
|
||||
thetab = 2*PI;
|
||||
dtheta = 2*PI;
|
||||
theta = 0;
|
||||
} else {
|
||||
Point2d c2 = center.Project2d(u, v);
|
||||
Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
|
||||
Point2d pb2 = (pb.Project2d(u, v)).Minus(c2);
|
||||
|
||||
thetaa = atan2(pa2.y, pa2.x);
|
||||
thetab = atan2(pb2.y, pb2.x);
|
||||
dtheta = thetab - thetaa;
|
||||
theta = atan2(pa2.y, pa2.x);
|
||||
}
|
||||
dtheta = angle * PI/180;
|
||||
|
||||
int i, n;
|
||||
if(dtheta > (3*PI/2 + 0.01)) {
|
||||
n = 4;
|
||||
|
@ -266,17 +263,17 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
|
|||
for(i = 0; i < n; i++) {
|
||||
double s, c;
|
||||
|
||||
c = cos(thetaa);
|
||||
s = sin(thetaa);
|
||||
c = cos(theta);
|
||||
s = sin(theta);
|
||||
// The start point of the curve, and the tangent vector at
|
||||
// that start point.
|
||||
Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
||||
t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
|
||||
|
||||
thetaa += dtheta;
|
||||
theta += dtheta;
|
||||
|
||||
c = cos(thetaa);
|
||||
s = sin(thetaa);
|
||||
c = cos(theta);
|
||||
s = sin(theta);
|
||||
Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
|
||||
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 scale = 1.0; //mm
|
||||
bool topEntities, bottomEntities;
|
||||
|
||||
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
|
||||
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 ); ) {
|
||||
if (line.find(".END_") == 0) {
|
||||
section = none;
|
||||
curve = -1;
|
||||
}
|
||||
switch (section) {
|
||||
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) {
|
||||
section = board_outline;
|
||||
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) {
|
||||
section = drilled_holes;
|
||||
record_number = 1;
|
||||
|
@ -375,11 +378,23 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case routing_keepout:
|
||||
case board_outline:
|
||||
if (record_number == 2) {
|
||||
board_thickness = std::stod(line) * scale;
|
||||
dbp("IDF board thickness: %lf", board_thickness);
|
||||
if(section == board_outline) {
|
||||
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
|
||||
std::vector <std::string> values = splitString(line);
|
||||
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);
|
||||
if(c != curve) { // start a new curve
|
||||
curve = c;
|
||||
hprev = newPoint(el, &entityCount, point, /*visible=*/false);
|
||||
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
|
||||
if (bottomEntities)
|
||||
hprev = newPoint(el, &entityCount, point, /*visible=*/false);
|
||||
if (topEntities)
|
||||
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
|
||||
pprev = point;
|
||||
pprevTop = pTop;
|
||||
} else {
|
||||
// create a bezier for the extrusion
|
||||
if (ang == 0) {
|
||||
// straight lines
|
||||
SBezier sb = SBezier::From(pprev, point);
|
||||
sbl.l.Add(&sb);
|
||||
} else if (ang != 360.0) {
|
||||
// Arcs
|
||||
Vector c = ArcCenter(pprev, point, ang);
|
||||
MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
|
||||
} else {
|
||||
// circles
|
||||
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
|
||||
if(section == board_outline) {
|
||||
// create a bezier for the extrusion
|
||||
if (ang == 0) {
|
||||
// straight lines
|
||||
SBezier sb = SBezier::From(pprev, point);
|
||||
sbl.l.Add(&sb);
|
||||
} else if (ang != 360.0) {
|
||||
// Arcs
|
||||
Vector c = ArcCenter(pprev, point, ang);
|
||||
MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
|
||||
} else {
|
||||
// circles
|
||||
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
|
||||
}
|
||||
}
|
||||
// next create the entities
|
||||
// only curves and points at circle centers will be visible
|
||||
bool vis = (ang == 360.0);
|
||||
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
|
||||
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
|
||||
pprev = point;
|
||||
hprev = hp;
|
||||
hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
|
||||
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
|
||||
pprevTop = pTop;
|
||||
hprevTop = hp;
|
||||
|
||||
if (bottomEntities) {
|
||||
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
|
||||
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
|
||||
pprev = point;
|
||||
hprev = hp;
|
||||
}
|
||||
if (topEntities) {
|
||||
hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
|
||||
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
|
||||
pprevTop = pTop;
|
||||
hprevTop = hp;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -428,7 +450,6 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
|
|||
case other_outline:
|
||||
case routing_outline:
|
||||
case placement_outline:
|
||||
case routing_keepout:
|
||||
case via_keepout:
|
||||
case placement_group:
|
||||
break;
|
||||
|
|
|
@ -305,7 +305,6 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
|||
HitTestMakeSelection(mp);
|
||||
SS.MarkGroupDirtyByEntity(pending.point);
|
||||
orig.mouse = mp;
|
||||
Invalidate();
|
||||
break;
|
||||
|
||||
case Pending::DRAGGING_POINTS:
|
||||
|
@ -624,24 +623,24 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
|||
}
|
||||
if(gs.withEndpoints > 0) {
|
||||
menu->AddItem(_("Select Edge Chain"),
|
||||
[this]() { MenuEdit(Command::SELECT_CHAIN); });
|
||||
[]() { MenuEdit(Command::SELECT_CHAIN); });
|
||||
}
|
||||
if(gs.constraints == 1 && gs.n == 0) {
|
||||
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
||||
if(c->HasLabel() && c->type != Constraint::Type::COMMENT) {
|
||||
menu->AddItem(_("Toggle Reference Dimension"),
|
||||
[]() { Constraint::MenuConstrain(Command::REFERENCE); });
|
||||
[]() { Constraint::MenuConstrain(Command::REFERENCE); });
|
||||
}
|
||||
if(c->type == Constraint::Type::ANGLE ||
|
||||
c->type == Constraint::Type::EQUAL_ANGLE)
|
||||
c->type == Constraint::Type::EQUAL_ANGLE)
|
||||
{
|
||||
menu->AddItem(_("Other Supplementary Angle"),
|
||||
[]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); });
|
||||
[]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); });
|
||||
}
|
||||
}
|
||||
if(gs.constraintLabels > 0 || gs.points > 0) {
|
||||
menu->AddItem(_("Snap to Grid"),
|
||||
[this]() { MenuEdit(Command::SNAP_TO_GRID); });
|
||||
[]() { MenuEdit(Command::SNAP_TO_GRID); });
|
||||
}
|
||||
|
||||
if(gs.points == 1 && gs.point[0].isFromRequest()) {
|
||||
|
@ -714,7 +713,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
|||
}
|
||||
if(gs.entities == gs.n) {
|
||||
menu->AddItem(_("Toggle Construction"),
|
||||
[this]() { MenuRequest(Command::CONSTRUCTION); });
|
||||
[]() { MenuRequest(Command::CONSTRUCTION); });
|
||||
}
|
||||
|
||||
if(gs.points == 1) {
|
||||
|
@ -748,28 +747,28 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
|
|||
menu->AddSeparator();
|
||||
if(LockedInWorkplane()) {
|
||||
menu->AddItem(_("Cut"),
|
||||
[this]() { MenuClipboard(Command::CUT); });
|
||||
[]() { MenuClipboard(Command::CUT); });
|
||||
menu->AddItem(_("Copy"),
|
||||
[this]() { MenuClipboard(Command::COPY); });
|
||||
[]() { MenuClipboard(Command::COPY); });
|
||||
}
|
||||
} else {
|
||||
menu->AddItem(_("Select All"),
|
||||
[this]() { MenuEdit(Command::SELECT_ALL); });
|
||||
[]() { MenuEdit(Command::SELECT_ALL); });
|
||||
}
|
||||
|
||||
if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) {
|
||||
menu->AddItem(_("Paste"),
|
||||
[this]() { MenuClipboard(Command::PASTE); });
|
||||
[]() { MenuClipboard(Command::PASTE); });
|
||||
menu->AddItem(_("Paste Transformed..."),
|
||||
[this]() { MenuClipboard(Command::PASTE_TRANSFORM); });
|
||||
[]() { MenuClipboard(Command::PASTE_TRANSFORM); });
|
||||
}
|
||||
|
||||
if(itemsSelected) {
|
||||
menu->AddItem(_("Delete"),
|
||||
[this]() { MenuClipboard(Command::DELETE); });
|
||||
[]() { MenuClipboard(Command::DELETE); });
|
||||
menu->AddSeparator();
|
||||
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
|
||||
// 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) {
|
||||
menu->AddSeparator();
|
||||
menu->AddItem(_("Zoom to Fit"),
|
||||
[this]() { MenuView(Command::ZOOM_TO_FIT); });
|
||||
[]() { MenuView(Command::ZOOM_TO_FIT); });
|
||||
}
|
||||
|
||||
menu->PopUp();
|
||||
|
@ -877,7 +876,6 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projecte
|
|||
|
||||
bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
||||
using Platform::MouseEvent;
|
||||
|
||||
double width, height;
|
||||
window->GetContentSize(&width, &height);
|
||||
|
||||
|
@ -918,7 +916,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
|
|||
break;
|
||||
|
||||
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;
|
||||
|
||||
case MouseEvent::Type::LEAVE:
|
||||
|
@ -1117,7 +1115,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct
|
|||
AddToPending(hr);
|
||||
Request *r = SK.GetRequest(hr);
|
||||
r->str = "Abc";
|
||||
r->font = "BitstreamVeraSans-Roman-builtin.ttf";
|
||||
r->font = Platform::embeddedFont;
|
||||
|
||||
for(int i = 1; i <= 4; i++) {
|
||||
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 offsetUp = offset.Dot(projUp);
|
||||
|
||||
double righti = x/scale - offsetRight;
|
||||
double upi = y/scale - offsetUp;
|
||||
|
||||
if(delta > 0) {
|
||||
scale *= 1.2;
|
||||
} else if(delta < 0) {
|
||||
scale /= 1.2;
|
||||
} else return;
|
||||
// The default zoom factor is 1.2x for one scroll wheel click (delta==1).
|
||||
// To support smooth scrolling where scroll wheel events come in increments
|
||||
// smaller (or larger) than 1 we do:
|
||||
// scale *= exp(ln(1.2) * delta);
|
||||
// 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 upf = y/scale - offsetUp;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// Copyright 2016 whitequark
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
#include "config.h"
|
||||
|
||||
static void ShowUsage(const std::string &cmd) {
|
||||
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.
|
||||
|
||||
Commands:
|
||||
version
|
||||
Prints the current solvespace version.
|
||||
thumbnail --output <pattern> --size <size> --view <direction>
|
||||
[--chord-tol <tolerance>]
|
||||
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;
|
||||
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) {
|
||||
if(argn + 1 < args.size() && args[argn] == "--size") {
|
||||
argn++;
|
||||
|
|
|
@ -21,7 +21,7 @@ int main(int argc, char** argv) {
|
|||
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();
|
||||
|
|
|
@ -99,7 +99,6 @@ std::vector<FileFilter> MeshFileFilters = {
|
|||
{ CN_("file-type", "Wavefront OBJ mesh"), { "obj" } },
|
||||
{ CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } },
|
||||
{ CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } },
|
||||
{ CN_("file-type", "Q3D Object file"), { "q3do" } },
|
||||
{ CN_("file-type", "VRML text file"), { "wrl" } },
|
||||
};
|
||||
|
||||
|
|
|
@ -356,6 +356,7 @@ public:
|
|||
|
||||
virtual Platform::Path GetFilename() = 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;
|
||||
void AddFilter(const FileFilter &filter);
|
||||
|
|
|
@ -472,7 +472,7 @@ protected:
|
|||
}
|
||||
|
||||
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 = {};
|
||||
event.type = type;
|
||||
event.x = x;
|
||||
|
@ -536,7 +536,7 @@ protected:
|
|||
}
|
||||
|
||||
bool on_scroll_event(GdkEventScroll *gdk_event) override {
|
||||
int delta;
|
||||
double delta;
|
||||
if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) {
|
||||
delta = 1;
|
||||
} else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) {
|
||||
|
@ -1246,6 +1246,10 @@ public:
|
|||
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 {
|
||||
Glib::RefPtr<Gtk::FileFilter> gtkFilter = Gtk::FileFilter::create();
|
||||
Glib::ustring desc;
|
||||
|
@ -1291,13 +1295,16 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: This is not getting called when the extension selection is changed.
|
||||
void FilterChanged() {
|
||||
std::string extension = GetExtension();
|
||||
if(extension.empty())
|
||||
return;
|
||||
|
||||
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 {
|
||||
|
|
|
@ -371,6 +371,7 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
|
||||
- (id)initWithFrame:(NSRect)frameRect {
|
||||
NSOpenGLPixelFormatAttribute attrs[] = {
|
||||
NSOpenGLPFADoubleBuffer,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFADepthSize, 24,
|
||||
0
|
||||
|
@ -553,7 +554,9 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
|||
|
||||
MouseEvent event = [self convertMouseEvent:nsEvent];
|
||||
event.type = MouseEvent::Type::SCROLL_VERT;
|
||||
event.scrollDelta = [nsEvent deltaY];
|
||||
|
||||
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
|
||||
event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5);
|
||||
|
||||
if(receiver->onMouseEvent) {
|
||||
receiver->onMouseEvent(event);
|
||||
|
@ -974,9 +977,6 @@ public:
|
|||
if(GetScrollbarPosition() == pos)
|
||||
return;
|
||||
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
|
||||
if(onScrollbarAdjusted) {
|
||||
onScrollbarAdjusted(pos);
|
||||
}
|
||||
}
|
||||
|
||||
void Invalidate() override {
|
||||
|
@ -1273,6 +1273,10 @@ public:
|
|||
nsPanel.nameFieldStringValue = Wrap(path.FileStem());
|
||||
}
|
||||
|
||||
void SuggestFilename(Platform::Path path) override {
|
||||
SetFilename(path.WithExtension(""));
|
||||
}
|
||||
|
||||
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||
settings->FreezeString("Dialog_" + key + "_Folder",
|
||||
[nsPanel.directoryURL.absoluteString UTF8String]);
|
||||
|
|
|
@ -183,7 +183,7 @@ public:
|
|||
|
||||
HKEY GetKey() {
|
||||
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));
|
||||
}
|
||||
return hKey;
|
||||
|
@ -191,12 +191,12 @@ public:
|
|||
|
||||
~SettingsImplWin32() {
|
||||
if(hKey != NULL) {
|
||||
sscheck(RegCloseKey(hKey));
|
||||
sscheck(ERROR_SUCCESS == RegCloseKey(hKey));
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,7 @@ public:
|
|||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
|
@ -231,7 +231,7 @@ public:
|
|||
ssassert(value.length() == strlen(value.c_str()),
|
||||
"illegal null byte in middle of a string setting");
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -242,7 +242,7 @@ public:
|
|||
if(result == ERROR_SUCCESS && type == REG_SZ) {
|
||||
std::wstring valueW;
|
||||
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));
|
||||
return Narrow(valueW);
|
||||
}
|
||||
|
@ -734,6 +734,11 @@ public:
|
|||
event.type = SixDofEvent::Type::RELEASE;
|
||||
event.button = SixDofEvent::Button::FIT;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if(window->onSixDofEvent) {
|
||||
window->onSixDofEvent(event);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -907,8 +912,8 @@ public:
|
|||
// Make the mousewheel work according to which window the mouse is
|
||||
// over, not according to which window is active.
|
||||
POINT pt;
|
||||
pt.x = LOWORD(lParam);
|
||||
pt.y = HIWORD(lParam);
|
||||
pt.x = GET_X_LPARAM(lParam);
|
||||
pt.y = GET_Y_LPARAM(lParam);
|
||||
HWND hWindowUnderMouse;
|
||||
sscheck(hWindowUnderMouse = WindowFromPoint(pt));
|
||||
if(hWindowUnderMouse && hWindowUnderMouse != h) {
|
||||
|
@ -917,8 +922,15 @@ public:
|
|||
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.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1 : -1;
|
||||
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
|
||||
break;
|
||||
|
||||
case WM_MOUSELEAVE:
|
||||
|
@ -1094,12 +1106,12 @@ public:
|
|||
|
||||
bool IsVisible() override {
|
||||
BOOL isVisible;
|
||||
sscheck(isVisible = IsWindowVisible(hWindow));
|
||||
isVisible = IsWindowVisible(hWindow);
|
||||
return isVisible == TRUE;
|
||||
}
|
||||
|
||||
void SetVisible(bool visible) override {
|
||||
sscheck(ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE));
|
||||
ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE);
|
||||
}
|
||||
|
||||
void Focus() override {
|
||||
|
@ -1267,7 +1279,7 @@ public:
|
|||
|
||||
bool IsEditorVisible() override {
|
||||
BOOL visible;
|
||||
sscheck(visible = IsWindowVisible(hEditor));
|
||||
visible = IsWindowVisible(hEditor);
|
||||
return visible == TRUE;
|
||||
}
|
||||
|
||||
|
@ -1309,7 +1321,7 @@ public:
|
|||
|
||||
sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
||||
/*bRepaint=*/true));
|
||||
sscheck(ShowWindow(hEditor, SW_SHOW));
|
||||
ShowWindow(hEditor, SW_SHOW);
|
||||
if(!textW.empty()) {
|
||||
sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str()));
|
||||
sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length()));
|
||||
|
@ -1320,7 +1332,7 @@ public:
|
|||
void HideEditor() override {
|
||||
if(!IsEditorVisible()) return;
|
||||
|
||||
sscheck(ShowWindow(hEditor, SW_HIDE));
|
||||
ShowWindow(hEditor, SW_HIDE);
|
||||
}
|
||||
|
||||
void SetScrollbarVisible(bool visible) override {
|
||||
|
@ -1335,7 +1347,7 @@ public:
|
|||
si.nMin = (UINT)(min * SCROLLBAR_UNIT);
|
||||
si.nMax = (UINT)(max * 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 {
|
||||
|
@ -1359,7 +1371,7 @@ public:
|
|||
return;
|
||||
|
||||
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.
|
||||
if(onScrollbarAdjusted) {
|
||||
|
@ -1443,7 +1455,10 @@ public:
|
|||
void SetType(Type type) override {
|
||||
switch(type) {
|
||||
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;
|
||||
|
||||
case Type::QUESTION:
|
||||
|
@ -1455,7 +1470,10 @@ public:
|
|||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1575,6 +1593,10 @@ public:
|
|||
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 {
|
||||
std::string desc, patterns;
|
||||
for(auto extension : extensions) {
|
||||
|
|
|
@ -185,8 +185,10 @@ Path Path::WithExtension(std::string ext) const {
|
|||
if(dot != std::string::npos) {
|
||||
withExt.raw.erase(dot);
|
||||
}
|
||||
withExt.raw += ".";
|
||||
withExt.raw += ext;
|
||||
if(!ext.empty()) {
|
||||
withExt.raw += ".";
|
||||
withExt.raw += ext;
|
||||
}
|
||||
return withExt;
|
||||
}
|
||||
|
||||
|
@ -401,7 +403,7 @@ FILE *OpenFile(const Platform::Path &filename, const char *mode) {
|
|||
ssassert(filename.raw.length() == strlen(filename.raw.c_str()),
|
||||
"Unexpected null byte in middle of a path");
|
||||
#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
|
||||
return fopen(filename.raw.c_str(), mode);
|
||||
#endif
|
||||
|
|
|
@ -17,6 +17,12 @@ std::string Narrow(const std::wstring &s);
|
|||
std::wstring Widen(const std::string &s);
|
||||
#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.
|
||||
// Transformation functions return an empty path on error.
|
||||
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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) {
|
||||
minDepth = depth;
|
||||
minDistance = distance;
|
||||
maxZIndex = zIndex;
|
||||
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);
|
||||
if(insideQuad) {
|
||||
DoCompare(0.0, zIndex, comparePosition);
|
||||
DoCompare(0, 0.0, zIndex, comparePosition);
|
||||
} else {
|
||||
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 bp = camera.ProjectPoint(b);
|
||||
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) {
|
||||
|
@ -393,7 +395,8 @@ void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
|
|||
Point2d ap = camera.ProjectPoint(e.a);
|
||||
Point2d bp = camera.ProjectPoint(e.b);
|
||||
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) {
|
||||
Stroke *stroke = strokes.FindById(hcs);
|
||||
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) {
|
||||
|
@ -445,6 +449,7 @@ void ObjectPicker::DrawPixmap(std::shared_ptr<const Pixmap> pm,
|
|||
}
|
||||
|
||||
bool ObjectPicker::Pick(const std::function<void()> &drawFn) {
|
||||
minDepth = VERY_POSITIVE;
|
||||
minDistance = VERY_POSITIVE;
|
||||
maxZIndex = INT_MIN;
|
||||
|
||||
|
|
|
@ -232,6 +232,7 @@ public:
|
|||
double selRadius = 0.0;
|
||||
// Picking state.
|
||||
double minDistance = 0.0;
|
||||
double minDepth = 1e10;
|
||||
int maxZIndex = 0;
|
||||
uint32_t position = 0;
|
||||
|
||||
|
@ -257,7 +258,7 @@ public:
|
|||
const Point2d &ta, const Point2d &tb, hFill hcf) 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,
|
||||
int zIndex, int comparePosition = 0);
|
||||
|
||||
|
|
|
@ -790,6 +790,9 @@ public:
|
|||
static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
|
||||
hEntity entityA, hEntity entityB = Entity::NO_ENTITY,
|
||||
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 {
|
||||
|
|
|
@ -77,7 +77,7 @@ void SolveSpaceUI::Init() {
|
|||
// Use turntable mouse navigation
|
||||
turntableNav = settings->ThawBool("TurntableNav", false);
|
||||
// Immediately edit dimension
|
||||
immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", false);
|
||||
immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", true);
|
||||
// Check that contours are closed and not self-intersecting
|
||||
checkClosedContour = settings->ThawBool("CheckClosedContour", true);
|
||||
// Enable automatic constrains for lines
|
||||
|
@ -331,7 +331,13 @@ const char *SolveSpaceUI::UnitName() {
|
|||
|
||||
std::string SolveSpaceUI::MmToString(double v) {
|
||||
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) {
|
||||
switch(dim) {
|
||||
|
@ -341,13 +347,39 @@ static const char *DimToString(int dim) {
|
|||
default: ssassert(false, "Unexpected dimension");
|
||||
}
|
||||
}
|
||||
static std::pair<int, std::string> SelectSIPrefixMm(int deg) {
|
||||
if(deg >= 3) return { 3, "km" };
|
||||
else if(deg >= 0) return { 0, "m" };
|
||||
else if(deg >= -2) return { -2, "cm" };
|
||||
else if(deg >= -3) return { -3, "mm" };
|
||||
else if(deg >= -6) return { -6, "µm" };
|
||||
else return { -9, "nm" };
|
||||
static std::pair<int, std::string> SelectSIPrefixMm(int ord, int dim) {
|
||||
// decide what units to use depending on the order of magnitude of the
|
||||
// measure in meters and the dimmension (1,2,3 lenear, area, volume)
|
||||
switch(dim) {
|
||||
case 0:
|
||||
case 1:
|
||||
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) {
|
||||
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);
|
||||
int vdeg = (int)((log10(fabs(v))) / dim);
|
||||
int vdeg = (int)(log10(fabs(v)));
|
||||
std::string unit;
|
||||
if(fabs(v) > 0.0) {
|
||||
int sdeg = 0;
|
||||
std::tie(sdeg, unit) =
|
||||
(viewUnits == Unit::INCHES)
|
||||
? SelectSIPrefixInch(vdeg)
|
||||
: SelectSIPrefixMm(vdeg);
|
||||
? SelectSIPrefixInch(vdeg/dim)
|
||||
: SelectSIPrefixMm(vdeg, dim);
|
||||
v /= pow(10.0, sdeg * dim);
|
||||
}
|
||||
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);
|
||||
dialog->AddFilters(Platform::RasterFileFilters);
|
||||
dialog->ThawChoices(settings, "ExportImage");
|
||||
dialog->SuggestFilename(SS.saveFile);
|
||||
if(dialog->RunModal()) {
|
||||
dialog->FreezeChoices(settings, "ExportImage");
|
||||
SS.ExportAsPngTo(dialog->GetFilename());
|
||||
|
@ -612,6 +645,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::VectorFileFilters);
|
||||
dialog->ThawChoices(settings, "ExportView");
|
||||
dialog->SuggestFilename(SS.saveFile);
|
||||
if(!dialog->RunModal()) break;
|
||||
dialog->FreezeChoices(settings, "ExportView");
|
||||
|
||||
|
@ -635,6 +669,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::Vector3dFileFilters);
|
||||
dialog->ThawChoices(settings, "ExportWireframe");
|
||||
dialog->SuggestFilename(SS.saveFile);
|
||||
if(!dialog->RunModal()) break;
|
||||
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||
|
||||
|
@ -646,6 +681,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::VectorFileFilters);
|
||||
dialog->ThawChoices(settings, "ExportSection");
|
||||
dialog->SuggestFilename(SS.saveFile);
|
||||
if(!dialog->RunModal()) break;
|
||||
dialog->FreezeChoices(settings, "ExportSection");
|
||||
|
||||
|
@ -657,6 +693,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::MeshFileFilters);
|
||||
dialog->ThawChoices(settings, "ExportMesh");
|
||||
dialog->SuggestFilename(SS.saveFile);
|
||||
if(!dialog->RunModal()) break;
|
||||
dialog->FreezeChoices(settings, "ExportMesh");
|
||||
|
||||
|
@ -668,6 +705,7 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::SurfaceFileFilters);
|
||||
dialog->ThawChoices(settings, "ExportSurfaces");
|
||||
dialog->SuggestFilename(SS.saveFile);
|
||||
if(!dialog->RunModal()) break;
|
||||
dialog->FreezeChoices(settings, "ExportSurfaces");
|
||||
|
||||
|
@ -878,9 +916,13 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
|
|||
break;
|
||||
|
||||
case Command::STOP_TRACING: {
|
||||
if (SS.traced.point == Entity::NO_ENTITY) {
|
||||
break;
|
||||
}
|
||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||
dialog->AddFilters(Platform::CsvFileFilters);
|
||||
dialog->ThawChoices(settings, "Trace");
|
||||
dialog->SetFilename(SS.saveFile);
|
||||
if(dialog->RunModal()) {
|
||||
dialog->FreezeChoices(settings, "Trace");
|
||||
|
||||
|
@ -965,7 +1007,7 @@ void SolveSpaceUI::MenuHelp(Command id) {
|
|||
"law. For details, visit http://gnu.org/licenses/\n"
|
||||
"\n"
|
||||
"© 2008-%d Jonathan Westhues and other authors.\n"),
|
||||
PACKAGE_VERSION, 2019);
|
||||
PACKAGE_VERSION, 2021);
|
||||
break;
|
||||
|
||||
default: ssassert(false, "Unexpected menu ID");
|
||||
|
@ -982,6 +1024,7 @@ void SolveSpaceUI::Clear() {
|
|||
GW.openRecentMenu = NULL;
|
||||
GW.linkRecentMenu = NULL;
|
||||
GW.showGridMenuItem = NULL;
|
||||
GW.dimSolidModelMenuItem = NULL;
|
||||
GW.perspectiveProjMenuItem = NULL;
|
||||
GW.showToolbarMenuItem = NULL;
|
||||
GW.showTextWndMenuItem = NULL;
|
||||
|
|
|
@ -513,7 +513,7 @@ public:
|
|||
GraphicsWindow GW;
|
||||
|
||||
// The state for undo/redo
|
||||
typedef struct {
|
||||
typedef struct UndoState {
|
||||
IdList<Group,hGroup> group;
|
||||
List<hGroup> groupOrder;
|
||||
IdList<Request,hRequest> request;
|
||||
|
@ -530,7 +530,7 @@ public:
|
|||
style.Clear();
|
||||
}
|
||||
} UndoState;
|
||||
enum { MAX_UNDO = 16 };
|
||||
enum { MAX_UNDO = 100 };
|
||||
typedef struct {
|
||||
UndoState d[MAX_UNDO];
|
||||
int cnt;
|
||||
|
@ -686,7 +686,6 @@ public:
|
|||
void ExportAsPngTo(const Platform::Path &filename);
|
||||
void ExportMeshTo(const Platform::Path &filename);
|
||||
void ExportMeshAsStlTo(FILE *f, SMesh *sm);
|
||||
void ExportMeshAsQ3doTo(FILE *f, SMesh *sm);
|
||||
void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm);
|
||||
void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename,
|
||||
SMesh *sm, SOutlineList *sol);
|
||||
|
|
|
@ -20,6 +20,50 @@ void SShell::MakeFromIntersectionOf(SShell *a, SShell *b) {
|
|||
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
|
||||
// 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.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();
|
||||
ssassert(p != NULL, "Cannot split an empty curve");
|
||||
SCurvePt prev = *p;
|
||||
ret.pts.Add(p);
|
||||
p = pts.NextAfter(p);
|
||||
|
||||
|
||||
for(; p; p = pts.NextAfter(p)) {
|
||||
List<SInter> il = {};
|
||||
|
||||
|
@ -100,12 +153,22 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
|
|||
pi->p = (pi->srf)->PointAt(puv);
|
||||
}
|
||||
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
|
||||
// do that after refining, in case the refining would make two
|
||||
// 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) {
|
||||
double ta = (a.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);
|
||||
prev = *p;
|
||||
}
|
||||
vertpts.Clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) {
|
||||
SCurve *sc;
|
||||
for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) {
|
||||
#pragma omp parallel for
|
||||
for(int i=0; i<curve.n; i++) {
|
||||
SCurve *sc = &curve[i];
|
||||
SCurve scn = sc->MakeCopySplitAgainst(agnst, NULL,
|
||||
surface.FindById(sc->surfA),
|
||||
surface.FindById(sc->surfB));
|
||||
scn.source = opA ? SCurve::Source::A : SCurve::Source::B;
|
||||
|
||||
hSCurve hsc = into->curve.AddAndAssignId(&scn);
|
||||
// And note the new ID so that we can rewrite the trims appropriately
|
||||
sc->newH = hsc;
|
||||
#pragma omp critical
|
||||
{
|
||||
hSCurve hsc = into->curve.AddAndAssignId(&scn);
|
||||
// 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.
|
||||
#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]
|
||||
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 } },
|
||||
|
@ -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,-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;
|
||||
c = bernstein_coeff[deg][k];
|
||||
const double *c = bernstein_coeff[deg][k];
|
||||
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] = {
|
||||
{ { 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 } },
|
||||
{ { -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 } } };
|
||||
|
||||
const double *c;
|
||||
c = bernstein_derivative_coeff[deg][k];
|
||||
const double *c = bernstein_derivative_coeff[deg][k];
|
||||
return ((c[2]*t)+c[1])*t+c[0];
|
||||
}
|
||||
|
||||
|
@ -332,7 +328,7 @@ Vector SSurface::PointAt(double u, double v) const {
|
|||
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),
|
||||
num_u = 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 = 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 {
|
||||
|
|
|
@ -507,9 +507,9 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb
|
|||
Vector n = sbls->normal.ScaledBy(-1);
|
||||
Vector u = n.Normal(0), v = n.Normal(1);
|
||||
Vector orig = sbls->point;
|
||||
double umax = 1e-10, umin = 1e10;
|
||||
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||
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);
|
||||
// and now fix things up so that all u and v lie between 0 and 1
|
||||
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 u = n.Normal(0), v = n.Normal(1);
|
||||
Vector orig = sbls->point;
|
||||
double umax = 1e-10, umin = 1e10;
|
||||
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||
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);
|
||||
// and now fix things up so that all u and v lie between 0 and 1
|
||||
orig = orig.Plus(u.ScaledBy(umin));
|
||||
|
|
|
@ -219,6 +219,7 @@ public:
|
|||
SSurface *GetSurfaceB(SShell *a, SShell *b) const;
|
||||
|
||||
void Clear();
|
||||
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
|
||||
};
|
||||
|
||||
// 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);
|
||||
Vector PointAt(double u, double v) 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(double u, double v) const;
|
||||
bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const;
|
||||
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
|
||||
bool CoincidentWithPlane(Vector n, double d) const;
|
||||
bool CoincidentWith(SSurface *ss, bool sameNormal) const;
|
||||
bool ContainsPlaneCurve(SCurve *sc) const;
|
||||
bool IsExtrusion(SBezier *of, Vector *along) const;
|
||||
bool IsCylinder(Vector *axis, Vector *center, double *r,
|
||||
Vector *start, Vector *finish) const;
|
||||
|
|
|
@ -313,6 +313,45 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
|
|||
inters.Clear();
|
||||
lv.Clear();
|
||||
} 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.
|
||||
// First, we find all the intersections between a surface and the
|
||||
// boundary of the other surface.
|
||||
|
@ -505,6 +544,24 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) const {
|
|||
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
|
||||
// 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
|
||||
ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x',
|
||||
g->h.v, (&TextWindow::ScreenHowGroupSolved),
|
||||
ok ? (warn ? "err" : sdof) : "",
|
||||
ok ? ((warn && SS.checkClosedContour) ? "err" : sdof) : "",
|
||||
ok ? "" : "ERR",
|
||||
// Link to a screen that gives more details on the group
|
||||
g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str());
|
||||
|
|
|
@ -252,8 +252,18 @@ void TextWindow::Init() {
|
|||
MouseLeave();
|
||||
return true;
|
||||
} else if(event.type == MouseEvent::Type::SCROLL_VERT) {
|
||||
window->SetScrollbarPosition(window->GetScrollbarPosition() -
|
||||
LINE_HEIGHT / 2 * event.scrollDelta);
|
||||
if (event.scrollDelta == 0) {
|
||||
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;
|
||||
};
|
||||
|
@ -338,11 +348,22 @@ void TextWindow::ClearScreen() {
|
|||
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, ...) {
|
||||
if(!canvas) 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_start(vl, fmt);
|
||||
|
||||
|
@ -1130,16 +1151,16 @@ void TextWindow::MouseLeave() {
|
|||
|
||||
void TextWindow::ScrollbarEvent(double newPos) {
|
||||
if(window->IsEditorVisible()) {
|
||||
window->SetScrollbarPosition(scrollPos);
|
||||
// An edit field is active. Do not move the scrollbar.
|
||||
return;
|
||||
}
|
||||
|
||||
int bottom = top[rows-1] + 2;
|
||||
newPos = min((int)newPos, bottom - halfRows);
|
||||
newPos = max((int)newPos, 0);
|
||||
|
||||
if(newPos != scrollPos) {
|
||||
scrollPos = (int)newPos;
|
||||
window->SetScrollbarPosition(scrollPos);
|
||||
window->Invalidate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,10 @@ static ToolIcon Toolbar[] = {
|
|||
N_("New group extruding active sketch"), {} },
|
||||
{ "lathe", Command::GROUP_LATHE,
|
||||
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,
|
||||
N_("New group step and repeat rotating"), {} },
|
||||
{ "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.
|
||||
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;
|
||||
|
||||
bool withinToolbar =
|
||||
|
|
8
src/ui.h
8
src/ui.h
|
@ -80,6 +80,7 @@ enum class Command : uint32_t {
|
|||
ZOOM_OUT,
|
||||
ZOOM_TO_FIT,
|
||||
SHOW_GRID,
|
||||
DIM_SOLID_MODEL,
|
||||
PERSPECTIVE_PROJ,
|
||||
ONTO_WORKPLANE,
|
||||
NEAREST_ORTHO,
|
||||
|
@ -179,7 +180,7 @@ public:
|
|||
enum {
|
||||
MAX_COLS = 100,
|
||||
MIN_COLS = 45,
|
||||
MAX_ROWS = 2000
|
||||
MAX_ROWS = 4000
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
@ -532,6 +533,7 @@ public:
|
|||
Platform::MenuRef linkRecentMenu;
|
||||
|
||||
Platform::MenuItemRef showGridMenuItem;
|
||||
Platform::MenuItemRef dimSolidModelMenuItem;
|
||||
Platform::MenuItemRef perspectiveProjMenuItem;
|
||||
Platform::MenuItemRef showToolbarMenuItem;
|
||||
Platform::MenuItemRef showTextWndMenuItem;
|
||||
|
@ -727,6 +729,7 @@ public:
|
|||
public:
|
||||
int zIndex;
|
||||
double distance;
|
||||
double depth;
|
||||
Selection selection;
|
||||
};
|
||||
|
||||
|
@ -802,6 +805,7 @@ public:
|
|||
DrawOccludedAs drawOccludedAs;
|
||||
|
||||
bool showSnapGrid;
|
||||
bool dimSolidModel;
|
||||
void DrawSnapGrid(Canvas *canvas);
|
||||
|
||||
void AddPointToDraggedList(hEntity hp);
|
||||
|
@ -824,7 +828,7 @@ public:
|
|||
void MouseLeftDoubleClick(double x, double y);
|
||||
void MouseMiddleOrRightDown(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();
|
||||
bool KeyboardEvent(Platform::KeyboardEvent event);
|
||||
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 v;
|
||||
v.x = SK.GetParam(x)->val;
|
||||
|
@ -448,50 +442,6 @@ bool Vector::EqualsExactly(Vector v) const {
|
|||
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 {
|
||||
Vector a = this->WithMagnitude(1);
|
||||
b = b.WithMagnitude(1);
|
||||
|
@ -629,24 +579,6 @@ Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) const {
|
|||
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 {
|
||||
double m = Magnitude();
|
||||
if(EXACT(m == 0)) {
|
||||
|
@ -729,16 +661,6 @@ Vector Vector::ClampWithin(double minv, double maxv) const {
|
|||
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 {
|
||||
return (x > maxv.x + LENGTH_EPS) || (x < minv.x - 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) {
|
||||
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").WithExtension("baz").raw, "foo.baz");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue