pull/493/head
KmolYuan 2021-02-12 11:16:11 +08:00
commit 2f527a5f2d
85 changed files with 6417 additions and 2273 deletions

View File

@ -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

27
.github/scripts/build-macos.sh vendored Executable file
View File

@ -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

3
.github/scripts/build-snap.sh vendored Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh -xe
./pkg/snap/build.sh --use-lxd

11
.github/scripts/build-ubuntu.sh vendored Executable file
View File

@ -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

36
.github/scripts/build-windows.sh vendored Executable file
View File

@ -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

4
.github/scripts/install-macos.sh vendored Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh -xe
brew install libomp
git submodule update --init

6
.github/scripts/install-snap.sh vendored Executable file
View File

@ -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"

View File

@ -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

View File

@ -1,5 +1,3 @@
#!/bin/sh -xe
brew update
brew install freetype cairo
git submodule update --init

70
.github/scripts/sign-macos.sh vendored Executable file
View File

@ -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}"

240
.github/workflows/cd.yml vendored Normal file
View File

@ -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

44
.github/workflows/test.yml vendored Normal file
View File

@ -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

1
.gitignore vendored
View File

@ -13,3 +13,4 @@
/debian/libslvs1-dev/
/obj-*/
/*.slvs
.vscode/

6
.gitmodules vendored
View File

@ -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

View File

@ -1,57 +0,0 @@
language: c
git:
submodules: false
jobs:
allow_failures:
- stage: deploy
name: Snap arm64
include:
- stage: test
name: "Debian"
os: linux
dist: bionic
install: ./.travis/install-debian.sh
script: ./.travis/build-debian.sh
- stage: deploy
name: "OSX"
os: osx
osx_image: xcode8.3
install: ./.travis/install-macos.sh
# the awk command is a workaround for https://github.com/travis-ci/travis-ci/issues/4704.
script: ./.travis/build-macos.sh | awk '/.{0,32}/ {print $0}'
deploy:
provider: releases
api_key:
secure: dDlkIawHcODlW9B/20/cQCtzeoocvs0hKuNngRKXKqzXLWTRq33oq/B7+39tAixWbmv6exTpijiKrRNFiSCW5Z4iwHLwaRD4XJznxw63e/Hus/dxg2Tvqx7XFpkCz8mT1Z+gZQE5YxAngeZPpI/sZbZtF1UO3yH5eLeeokZ15p26ZskQUPoYuzrTgTzYL3XfpG3F+20rNBawH1ycsCTVD/08/n31d2m3CrKAsbW7er92ek6w4fzKr7NW8WeXjrPJETVpw5fQg1Od3pRGW8dPQaJcvKQEogMp8Mm0ETYd0qigg89/giBz7QwOgmAWQ4dH+DfZH4Ojl//127QztBolMvyDMQBykWrtJoGcij05sT6K2IJr2FHeUBO12MAEdjiVvhQj3DtTzjPiZAHHDBSLWxLKWWhlhHE4pq7g1MQhqXkaAHI2BLNzwLmaowbMT0bECf9yfz6xx18h6XPQFX44oOktraobVALFlyHqeKa8zdcUt22LF6uAL1m5dxL0tny3eXCIPE4UH/RZgua/cHV9G3cUvKQa/QnFSLRhvWVSbGB+7YsHouBJcsUOOW1gmd5442XuC7mpppccRldh+GSxUk6TBJRAx7TeQ0ybDUaoco9MUqp2twv3KreR2+8Q12PDaAhfQVNEGdF3wTm1sShImjCN4VN3eSLlBEbve1QRQXM=
skip_cleanup: true
file: build/SolveSpace.dmg
on:
repo: solvespace/solvespace
tags: true
- &deploy-snap
stage: deploy
name: Snap amd64
os: linux
arch: amd64
dist: bionic
addons:
snaps:
- name: snapcraft
confinement: classic
script: ./.travis/build-snap.sh
deploy:
- provider: script
script: sudo ./.travis/deploy-snap.sh edge
skip_cleanup: true
on:
branch: master
tags: false
- provider: script
script: sudo ./.travis/deploy-snap.sh edge,beta
skip_cleanup: true
on:
branch: master
tags: true
- <<: *deploy-snap
name: Snap arm64
arch: arm64

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
#!/bin/sh -xe
sudo apt-get update
sudo ./pkg/snap/build.sh --destructive-mode

View File

@ -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

View File

@ -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.

View File

@ -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 "")

View File

@ -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
-------------------------

View File

@ -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).

View File

@ -1,29 +0,0 @@
version: '{build}'
clone_depth: 1
before_build:
- git submodule update --init
- set tag=x%APPVEYOR_REPO_TAG_NAME%
- if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug)
- mkdir build
- cmake -G"Visual Studio 14" -Tv140 -Bbuild -H.
build_script:
- msbuild "build\src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- msbuild "build\src\solvespace-cli.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
test_script:
- build\bin\%BUILD_TYPE%\solvespace-testsuite.exe
artifacts:
- path: build\bin\%BUILD_TYPE%\solvespace.exe
name: solvespace.exe
- path: build\bin\%BUILD_TYPE%\solvespace-cli.exe
name: solvespace-cli.exe
- path: build\bin\%BUILD_TYPE%\solvespace.pdb
name: solvespace.pdb
deploy:
- provider: GitHub
auth_token:
secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC
description: ""
artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb
on:
appveyor_repo_tag: true

@ -1 +0,0 @@
Subproject commit f73d205bc7536991e620d3027a711e713a789967

@ -1 +1 @@
Subproject commit a9686d6ecf00e4467e772f7c0b4ef76a15f325f6
Subproject commit 27627843648ef84aee1621976f25bee5946e6bda

@ -1 +0,0 @@
Subproject commit 880db1d34706778f216a2308fd82a9a3adacb314

View File

@ -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

View File

@ -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

View File

@ -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

2023
res/locales/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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

View File

@ -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()

View File

@ -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);
}
}
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -47,6 +47,7 @@ void TextWindow::ScreenConstraintToggleReference(int link, uint32_t v) {
SS.UndoRemember();
c->reference = !c->reference;
SS.MarkGroupDirty(c->group);
SS.ScheduleShowTW();
}

View File

@ -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);
}

View File

@ -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(

View File

@ -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;
};

View File

@ -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"

View File

@ -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();
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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++;

View File

@ -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();

View File

@ -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" } },
};

View File

@ -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);

View File

@ -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 {

View File

@ -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]);

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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));

View File

@ -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;

View File

@ -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

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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 =

View File

@ -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);

View File

@ -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

View File

@ -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");
}