Fix conflict.

pull/493/head
KmolYuan 2021-02-12 11:17:45 +08:00
commit fb5e731e6e
82 changed files with 6416 additions and 2187 deletions

View File

@ -1,8 +1,7 @@
### System information ### System information
SolveSpace version: <!--e.g. 3.0~3dd2fc00; go to Help → About...--> - **SolveSpace version:** <!--e.g. 3.0~3dd2fc00; go to Help → About...-->
- **Operating system:** <!--e.g. Debian testing-->
Operating system: <!--e.g. Debian testing-->
### Expected behavior ### Expected behavior

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 \ libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev libgl-dev \
libgl-dev libglu-dev libspnav-dev libgl-dev libglu-dev libspnav-dev
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc git submodule update --init extlib/libdxfrw extlib/mimalloc

View File

@ -1,5 +1,3 @@
#!/bin/sh -xe #!/bin/sh -xe
brew update
brew install freetype cairo
git submodule update --init 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

6
.gitmodules vendored
View File

@ -20,12 +20,6 @@
[submodule "extlib/angle"] [submodule "extlib/angle"]
path = extlib/angle path = extlib/angle
url = https://github.com/solvespace/angle url = https://github.com/solvespace/angle
[submodule "extlib/flatbuffers"]
path = extlib/flatbuffers
url = https://github.com/google/flatbuffers
[submodule "extlib/q3d"]
path = extlib/q3d
url = https://github.com/q3k/q3d
[submodule "extlib/mimalloc"] [submodule "extlib/mimalloc"]
path = extlib/mimalloc path = extlib/mimalloc
url = https://github.com/microsoft/mimalloc url = https://github.com/microsoft/mimalloc

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, * "Split Curves at Intersection" can now split curves at point lying on curve,
not just at intersection of two curves. not just at intersection of two curves.
* Property browser now shows amount of degrees of freedom in group list. * Property browser now shows amount of degrees of freedom in group list.
It also shows a yellow "err" if the sketch has problems (e.g. self
intersecting) that would propagate in subsequent groups.
New constraint features: New constraint features:
* When dragging an arc or rectangle point, it will be automatically * When dragging an arc or rectangle point, it will be automatically
@ -53,7 +55,8 @@ New export/import features:
* Wavefront OBJ: a material file is exported alongside the model, containing * Wavefront OBJ: a material file is exported alongside the model, containing
mesh color information. mesh color information.
* DXF/DWG: 3D DXF files are imported as construction entities, in 3d. * DXF/DWG: 3D DXF files are imported as construction entities, in 3d.
* Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be * [ADDED 2019-02-25](https://github.com/solvespace/solvespace/pull/384) and [REMOVED 2020-11-13](https://github.com/solvespace/solvespace/issues/795):
Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be
exported. This format allows to easily hack on triangle mesh data created exported. This format allows to easily hack on triangle mesh data created
in SolveSpace, supports colour information and is more space efficient than in SolveSpace, supports colour information and is more space efficient than
most other formats. most other formats.
@ -110,6 +113,7 @@ Other new features:
that are shortcuts to the respective configuration screens. that are shortcuts to the respective configuration screens.
* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes * New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes
to enable support for multi-threading and link-time optimization. to enable support for multi-threading and link-time optimization.
* "Shift+Scroll" for ten times finer zoom.
Bugs fixed: Bugs fixed:
* Fixed broken --view options for command line thumbnail image creation. * Fixed broken --view options for command line thumbnail image creation.

View File

@ -11,6 +11,11 @@ cmake_minimum_required(VERSION 3.7.2 FATAL_ERROR)
if(NOT CMAKE_VERSION VERSION_LESS 3.11.0) if(NOT CMAKE_VERSION VERSION_LESS 3.11.0)
cmake_policy(VERSION 3.11.0) cmake_policy(VERSION 3.11.0)
endif() endif()
if(NOT CMAKE_VERSION VERSION_LESS 3.9)
# LTO/IPO with non-Intel compilers on Linux requires policy CMP0069 to be set to NEW.
# Set it explicitly until cmake_minimum_required is raised to >= 3.9.
cmake_policy(SET CMP0069 NEW)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_SOURCE_DIR}/cmake/") "${CMAKE_SOURCE_DIR}/cmake/")
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
@ -22,6 +27,10 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE
set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX
"${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake") "${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake")
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
# project # project
# NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds # NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds
@ -48,6 +57,8 @@ set(ENABLE_SANITIZERS OFF CACHE BOOL
"Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer") "Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer")
set(ENABLE_OPENMP OFF CACHE BOOL set(ENABLE_OPENMP OFF CACHE BOOL
"Whether geometric operations will be parallelized using OpenMP") "Whether geometric operations will be parallelized using OpenMP")
set(ENABLE_LTO OFF CACHE BOOL
"Whether interprocedural (global) optimizations are enabled")
set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)") set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)")
@ -69,7 +80,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
endif() endif()
# common compiler flags # common compiler flags
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.") set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.")
@ -99,20 +109,19 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "X8
endif() endif()
if(ENABLE_LTO) if(ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif() endif()
if(ENABLE_OPENMP) if(ENABLE_OPENMP)
include(FindOpenMP) find_package( OpenMP REQUIRED )
if(OpenMP_FOUND) if(OPENMP_FOUND)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} )
endif() endif()
endif() endif()
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
endif() endif()
@ -144,8 +153,12 @@ if(ENABLE_SANITIZERS)
endif() endif()
string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}") string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}")
set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}")
set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-sanitize-recover=address,undefined") if (NOT APPLE)
set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS} -fno-sanitize-recover=address,undefined")
else()
set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls") set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls")
@ -168,17 +181,6 @@ endif()
message(STATUS "Using in-tree libdxfrw") message(STATUS "Using in-tree libdxfrw")
add_subdirectory(extlib/libdxfrw) add_subdirectory(extlib/libdxfrw)
message(STATUS "Using in-tree flatbuffers")
set(FLATBUFFERS_BUILD_FLATLIB ON CACHE BOOL "")
set(FLATBUFFERS_BUILD_FLATC ON CACHE BOOL "")
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "")
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "")
add_subdirectory(extlib/flatbuffers EXCLUDE_FROM_ALL)
message(STATUS "Using in-tree q3d")
add_subdirectory(extlib/q3d)
set(Q3D_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/q3d)
message(STATUS "Using in-tree mimalloc") message(STATUS "Using in-tree mimalloc")
set(MI_OVERRIDE OFF CACHE BOOL "") set(MI_OVERRIDE OFF CACHE BOOL "")
set(MI_BUILD_SHARED OFF CACHE BOOL "") set(MI_BUILD_SHARED OFF CACHE BOOL "")

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, GitHub does not allow attaching `*.slvs` files, but it does allow attaching `*.zip` files,
so any savefiles should first be archived. so any savefiles should first be archived.
Signing the CLA Licensing
--------------- ---------------
To contribute code, translations, artwork, or other resources to SolveSpace, it is necessary to SolveSpace is licensed under the GPLv3 and any contributions must be made available
sign a [Contributor License Agreement](https://cla-assistant.io/solvespace/solvespace). under the terms of that license.
Contributing translations Contributing translations
------------------------- -------------------------

View File

@ -1,5 +1,10 @@
<img src="res/freedesktop/solvespace-scalable.svg" width="70" height="70" alt="SolveSpace Logo" align="left">
SolveSpace SolveSpace
========== ==========
[![Build Status](https://github.com/solvespace/solvespace/workflows/CD/badge.svg)](https://github.com/solvespace/solvespace/actions)
[![solvespace](https://snapcraft.io/solvespace/badge.svg)](https://snapcraft.io/solvespace)
[![solvespace](https://snapcraft.io/solvespace/trending.svg?name=0)](https://snapcraft.io/solvespace)
This repository contains the source code of [SolveSpace][], a parametric This repository contains the source code of [SolveSpace][], a parametric
2d/3d CAD. 2d/3d CAD.
@ -83,26 +88,29 @@ Before building, check out the project and the necessary submodules:
git clone https://github.com/solvespace/solvespace git clone https://github.com/solvespace/solvespace
cd solvespace cd solvespace
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc git submodule update --init extlib/libdxfrw extlib/mimalloc
After that, build SolveSpace as following: After that, build SolveSpace as following:
mkdir build mkdir build
cd build cd build
cmake .. -DCMAKE_BUILD_TYPE=Release cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
make make
sudo make install sudo make install
Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the
expense of longer build time.
The graphical interface is built as `build/bin/solvespace`, and the command-line interface The graphical interface is built as `build/bin/solvespace`, and the command-line interface
is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
### Building for Windows ### Building for Windows
You will need the usual build tools, CMake, a Windows cross-compiler, and flatc. Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed to work.
On a Debian derivative (e.g. Ubuntu) these can be installed with:
apt-get install git build-essential cmake mingw-w64 libflatbuffers-dev You will need the usual build tools, CMake, a Windows cross-compiler, and flatc. On a Debian derivative (e.g. Ubuntu) these can be installed with:
apt-get install git build-essential cmake mingw-w64
Before building, check out the project and the necessary submodules: Before building, check out the project and the necessary submodules:
@ -110,16 +118,7 @@ Before building, check out the project and the necessary submodules:
cd solvespace cd solvespace
git submodule update --init git submodule update --init
After that, build 32-bit SolveSpace as following: Build 64-bit SolveSpace with the following:
mkdir build
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw32.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DFLATC=$(which flatc)
make
Or, build 64-bit SolveSpace as following:
mkdir build mkdir build
cd build cd build
@ -157,9 +156,12 @@ After that, build SolveSpace as following:
mkdir build mkdir build
cd build cd build
cmake .. -DCMAKE_BUILD_TYPE=Release cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON
make make
Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the
expense of longer build time.
Alternatively, generate an XCode project, open it, and build the "Release" scheme: Alternatively, generate an XCode project, open it, and build the "Release" scheme:
mkdir build mkdir build
@ -185,7 +187,7 @@ Before building, check out the project and the necessary submodules:
git clone https://github.com/solvespace/solvespace git clone https://github.com/solvespace/solvespace
cd solvespace cd solvespace
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc git submodule update --init extlib/libdxfrw extlib/mimalloc
After that, build SolveSpace as following: After that, build SolveSpace as following:
@ -257,6 +259,4 @@ and debug SolveSpace.
License License
------- -------
SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt). It is possible SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt).
to license SolveSpace for use in a commercial application; to do so,
[contact](http://solvespace.com/contact.pl) the developers.

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

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

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

View File

@ -45,12 +45,14 @@ parts:
snapcraftctl set-version "$version" snapcraftctl set-version "$version"
git describe --exact-match HEAD && grade="stable" || grade="devel" git describe --exact-match HEAD && grade="stable" || grade="devel"
snapcraftctl set-grade "$grade" snapcraftctl set-grade "$grade"
git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc git submodule update --init extlib/libdxfrw extlib/mimalloc
configflags: configflags:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
- -DENABLE_TESTS=OFF - -DENABLE_TESTS=OFF
- -DSNAP=ON - -DSNAP=ON
- -DENABLE_OPENMP=ON
- -DENABLE_LTO=ON
build-packages: build-packages:
- zlib1g-dev - zlib1g-dev
- libpng-dev - libpng-dev

View File

@ -213,6 +213,7 @@ add_resources(
icons/graphics-window/construction.png icons/graphics-window/construction.png
icons/graphics-window/equal.png icons/graphics-window/equal.png
icons/graphics-window/extrude.png icons/graphics-window/extrude.png
icons/graphics-window/helix.png
icons/graphics-window/horiz.png icons/graphics-window/horiz.png
icons/graphics-window/image.png icons/graphics-window/image.png
icons/graphics-window/in3d.png icons/graphics-window/in3d.png
@ -227,6 +228,7 @@ add_resources(
icons/graphics-window/point.png icons/graphics-window/point.png
icons/graphics-window/rectangle.png icons/graphics-window/rectangle.png
icons/graphics-window/ref.png icons/graphics-window/ref.png
icons/graphics-window/revolve.png
icons/graphics-window/same-orientation.png icons/graphics-window/same-orientation.png
icons/graphics-window/sketch-in-3d.png icons/graphics-window/sketch-in-3d.png
icons/graphics-window/sketch-in-plane.png icons/graphics-window/sketch-in-plane.png
@ -256,6 +258,7 @@ add_resources(
locales/fr_FR.po locales/fr_FR.po
locales/uk_UA.po locales/uk_UA.po
locales/ru_RU.po locales/ru_RU.po
locales/zh_CN.po
fonts/unifont.hex.gz fonts/unifont.hex.gz
fonts/private/0-check-false.png fonts/private/0-check-false.png
fonts/private/1-check-true.png fonts/private/1-check-true.png
@ -281,7 +284,7 @@ add_resources(
shaders/edge.frag shaders/edge.frag
shaders/edge.vert shaders/edge.vert
shaders/outline.vert shaders/outline.vert
threejs/three-r76.js.gz threejs/three-r111.min.js.gz
threejs/hammer-2.0.8.js.gz threejs/hammer-2.0.8.js.gz
threejs/SolveSpaceControls.js) threejs/SolveSpaceControls.js)

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

View File

@ -5,3 +5,4 @@ en-US,0409,English (US)
fr-FR,040C,Français fr-FR,040C,Français
ru-RU,0419,Русский ru-RU,0419,Русский
uk-UA,0422,Українська uk-UA,0422,Українська
zh-CN,0804,简体中文

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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); changeBasis.makeBasis(camera.right, camera.up, n);
for (var i = 0; i < 2; i++) { for (var i = 0; i < 2; i++) {
var newLightPos = changeBasis.applyToVector3Array( var newLightPos = new THREE.Vector3(obj.lights.d[i].direction[0],
[obj.lights.d[i].direction[0], obj.lights.d[i].direction[1], obj.lights.d[i].direction[1],
obj.lights.d[i].direction[2]]); obj.lights.d[i].direction[2]).applyMatrix4(changeBasis);
directionalLightArray[i].position.set(newLightPos[0], directionalLightArray[i].position.set(newLightPos[0],
newLightPos[1], newLightPos[2]); newLightPos[1], newLightPos[2]);
} }
@ -515,7 +515,7 @@ solvespace = function(obj, params) {
} }
geometry.computeBoundingSphere(); geometry.computeBoundingSphere();
return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList)); return new THREE.Mesh(geometry, materialList);
} }
function createEdges(meshObj) { function createEdges(meshObj) {

Binary file not shown.

Binary file not shown.

View File

@ -18,7 +18,7 @@ BEGIN
VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}" VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}"
VALUE "OriginalFilename", "solvespace.exe" VALUE "OriginalFilename", "solvespace.exe"
VALUE "InternalName", "solvespace" VALUE "InternalName", "solvespace"
VALUE "LegalCopyright", "(c) 2008-2016 Jonathan Westhues and other authors" VALUE "LegalCopyright", "(c) 2008-2021 Jonathan Westhues and other authors"
END END
END END

View File

@ -80,8 +80,8 @@ include_directories(
${PNG_PNG_INCLUDE_DIR} ${PNG_PNG_INCLUDE_DIR}
${FREETYPE_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS}
${CAIRO_INCLUDE_DIRS} ${CAIRO_INCLUDE_DIRS}
${Q3D_INCLUDE_DIR} ${MIMALLOC_INCLUDE_DIR}
${MIMALLOC_INCLUDE_DIR}) ${OpenMP_CXX_INCLUDE_DIRS})
if(Backtrace_FOUND) if(Backtrace_FOUND)
include_directories( include_directories(
@ -214,7 +214,6 @@ add_library(solvespace-core STATIC
${solvespace_core_SOURCES}) ${solvespace_core_SOURCES})
add_dependencies(solvespace-core add_dependencies(solvespace-core
q3d_header
mimalloc-static) mimalloc-static)
target_link_libraries(solvespace-core target_link_libraries(solvespace-core
@ -224,7 +223,6 @@ target_link_libraries(solvespace-core
${ZLIB_LIBRARY} ${ZLIB_LIBRARY}
${PNG_LIBRARY} ${PNG_LIBRARY}
${FREETYPE_LIBRARY} ${FREETYPE_LIBRARY}
flatbuffers
mimalloc-static) mimalloc-static)
if(Backtrace_FOUND) if(Backtrace_FOUND)
@ -241,7 +239,8 @@ if(HAVE_GETTEXT)
set(inputs set(inputs
${solvespace_core_SOURCES} ${solvespace_core_SOURCES}
${solvespace_core_HEADERS} ${solvespace_core_HEADERS}
${every_platform_SOURCES}) ${every_platform_SOURCES}
${solvespace_core_gl_SOURCES})
set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po) set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po)
@ -404,20 +403,15 @@ endif()
if(APPLE) if(APPLE)
set(bundle SolveSpace) set(bundle SolveSpace)
set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS) set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS)
set(bundle_resources ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/Resources/lib)
execute_process(
COMMAND mkdir -p ${bundle_resources}
COMMAND cp -p /usr/local/opt/libomp/lib/libomp.dylib ${bundle_resources}/libomp.dylib
)
add_custom_command(TARGET solvespace POST_BUILD add_custom_command(TARGET solvespace POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_bin} COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_bin}
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:solvespace-cli> ${bundle_bin} COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:solvespace-cli> ${bundle_bin}
COMMAND install_name_tool -change /usr/local/opt/libomp/lib/libomp.dylib "@executable_path/../Resources/lib/libomp.dylib" ${bundle_bin}/${bundle}
COMMENT "Bundling executable solvespace-cli" COMMENT "Bundling executable solvespace-cli"
VERBATIM) VERBATIM)
add_custom_command(OUTPUT ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg
COMMAND ${CMAKE_COMMAND} -E remove ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg
COMMAND hdiutil create -srcfolder ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app
${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg
DEPENDS solvespace
COMMENT "Building ${bundle}.dmg"
VERBATIM)
add_custom_target(${bundle}-dmg ALL
DEPENDS ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg)
endif() endif()

View File

@ -227,7 +227,6 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
MakeSelected(hr.entity(j)); MakeSelected(hr.entity(j));
} }
} }
Constraint *cc; Constraint *cc;
for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) { for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) {
Constraint c = {}; Constraint c = {};
@ -246,25 +245,62 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
c.reference = cc->reference; c.reference = cc->reference;
c.disp = cc->disp; c.disp = cc->disp;
c.comment = cc->comment; c.comment = cc->comment;
bool dontAddConstraint = false;
switch(c.type) { switch(c.type) {
case Constraint::Type::COMMENT: case Constraint::Type::COMMENT:
c.disp.offset = c.disp.offset.Plus(trans); c.disp.offset = c.disp.offset.Plus(trans);
break; break;
case Constraint::Type::PT_PT_DISTANCE: case Constraint::Type::PT_PT_DISTANCE:
case Constraint::Type::PT_LINE_DISTANCE: case Constraint::Type::PT_LINE_DISTANCE:
case Constraint::Type::PROJ_PT_DISTANCE: case Constraint::Type::PROJ_PT_DISTANCE:
case Constraint::Type::DIAMETER: case Constraint::Type::DIAMETER:
c.valA *= fabs(scale); c.valA *= fabs(scale);
break; break;
case Constraint::Type::ARC_LINE_TANGENT: {
Entity *line = SK.GetEntity(c.entityB),
*arc = SK.GetEntity(c.entityA);
if(line->type == Entity::Type::ARC_OF_CIRCLE) {
swap(line, arc);
}
Constraint::ConstrainArcLineTangent(&c, line, arc);
break;
}
case Constraint::Type::CUBIC_LINE_TANGENT: {
Entity *line = SK.GetEntity(c.entityB),
*cubic = SK.GetEntity(c.entityA);
if(line->type == Entity::Type::CUBIC) {
swap(line, cubic);
}
Constraint::ConstrainCubicLineTangent(&c, line, cubic);
break;
}
case Constraint::Type::CURVE_CURVE_TANGENT: {
Entity *eA = SK.GetEntity(c.entityA),
*eB = SK.GetEntity(c.entityB);
Constraint::ConstrainCurveCurveTangent(&c, eA, eB);
break;
}
case Constraint::Type::HORIZONTAL:
case Constraint::Type::VERTICAL:
// When rotating 90 or 270 degrees, swap the vertical / horizontal constaints
if (EXACT(fmod(theta + (PI/2), PI) == 0)) {
if(c.type == Constraint::Type::HORIZONTAL) {
c.type = Constraint::Type::VERTICAL;
} else {
c.type = Constraint::Type::HORIZONTAL;
}
} else if (fmod(theta, PI/2) != 0) {
dontAddConstraint = true;
}
break;
default: default:
break; break;
} }
if (!dontAddConstraint) {
hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false); hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
if(c.type == Constraint::Type::COMMENT) { if(c.type == Constraint::Type::COMMENT) {
MakeSelected(hc); MakeSelected(hc);
}
} }
} }
} }

View File

@ -5,6 +5,9 @@
// Copyright 2008-2013 Jonathan Westhues. // Copyright 2008-2013 Jonathan Westhues.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "solvespace.h" #include "solvespace.h"
#if defined(_OPENMP)
#include <omp.h>
#endif
void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) { void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) {
SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v]))); SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v])));
@ -389,6 +392,11 @@ void TextWindow::ShowConfiguration() {
Printf(false, " %Ft renderer %E%s", gl_renderer); Printf(false, " %Ft renderer %E%s", gl_renderer);
Printf(false, " %Ft version %E%s", gl_version); Printf(false, " %Ft version %E%s", gl_version);
} }
#if defined(_OPENMP)
Printf(false, " %FtOpenMP enabled");
Printf(false, " %Ft threads %E%d", omp_get_max_threads());
#endif
} }
bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { bool TextWindow::EditControlDoneForConfiguration(const std::string &s) {

View File

@ -127,6 +127,66 @@ hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false); Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false);
} }
void Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc) {
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
l1 = SK.GetEntity(line->point[1])->PointGetNum();
Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
a2 = SK.GetEntity(arc->point[2])->PointGetNum();
if(l0.Equals(a1) || l1.Equals(a1)) {
c->other = false;
} else if(l0.Equals(a2) || l1.Equals(a2)) {
c->other = true;
} else {
Error(_("The tangent arc and line segment must share an "
"endpoint. Constrain them with Constrain -> "
"On Point before constraining tangent."));
return;
}
}
void Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic) {
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(),
l1 = SK.GetEntity(line->point[1])->PointGetNum();
Vector as = cubic->CubicGetStartNum(),
af = cubic->CubicGetFinishNum();
if(l0.Equals(as) || l1.Equals(as)) {
c->other = false;
} else if(l0.Equals(af) || l1.Equals(af)) {
c->other = true;
} else {
Error(_("The tangent cubic and line segment must share an "
"endpoint. Constrain them with Constrain -> "
"On Point before constraining tangent."));
return;
}
}
void Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB) {
Vector as = eA->EndpointStart(),
af = eA->EndpointFinish(),
bs = eB->EndpointStart(),
bf = eB->EndpointFinish();
if(as.Equals(bs)) {
c->other = false;
c->other2 = false;
} else if(as.Equals(bf)) {
c->other = false;
c->other2 = true;
} else if(af.Equals(bs)) {
c->other = true;
c->other2 = false;
} else if(af.Equals(bf)) {
c->other = true;
c->other2 = true;
} else {
Error(_("The curves must share an endpoint. Constrain them "
"with Constrain -> On Point before constraining "
"tangent."));
return;
}
}
void Constraint::MenuConstrain(Command id) { void Constraint::MenuConstrain(Command id) {
Constraint c = {}; Constraint c = {};
c.group = SS.GW.activeGroup; c.group = SS.GW.activeGroup;
@ -617,50 +677,22 @@ void Constraint::MenuConstrain(Command id) {
c.entityA = gs.vector[0]; c.entityA = gs.vector[0];
c.entityB = gs.vector[1]; c.entityB = gs.vector[1];
} else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) {
Entity *line = SK.GetEntity(gs.entity[0]); Entity *line = SK.GetEntity(gs.entity[0]),
Entity *arc = SK.GetEntity(gs.entity[1]); *arc = SK.GetEntity(gs.entity[1]);
if(line->type == Entity::Type::ARC_OF_CIRCLE) { if(line->type == Entity::Type::ARC_OF_CIRCLE) {
swap(line, arc); swap(line, arc);
} }
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), ConstrainArcLineTangent(&c, line, arc);
l1 = SK.GetEntity(line->point[1])->PointGetNum();
Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(),
a2 = SK.GetEntity(arc->point[2])->PointGetNum();
if(l0.Equals(a1) || l1.Equals(a1)) {
c.other = false;
} else if(l0.Equals(a2) || l1.Equals(a2)) {
c.other = true;
} else {
Error(_("The tangent arc and line segment must share an "
"endpoint. Constrain them with Constrain -> "
"On Point before constraining tangent."));
return;
}
c.type = Type::ARC_LINE_TANGENT; c.type = Type::ARC_LINE_TANGENT;
c.entityA = arc->h; c.entityA = arc->h;
c.entityB = line->h; c.entityB = line->h;
} else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) { } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) {
Entity *line = SK.GetEntity(gs.entity[0]); Entity *line = SK.GetEntity(gs.entity[0]),
Entity *cubic = SK.GetEntity(gs.entity[1]); *cubic = SK.GetEntity(gs.entity[1]);
if(line->type == Entity::Type::CUBIC) { if(line->type == Entity::Type::CUBIC) {
swap(line, cubic); swap(line, cubic);
} }
Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), ConstrainCubicLineTangent(&c, line, cubic);
l1 = SK.GetEntity(line->point[1])->PointGetNum();
Vector as = cubic->CubicGetStartNum(),
af = cubic->CubicGetFinishNum();
if(l0.Equals(as) || l1.Equals(as)) {
c.other = false;
} else if(l0.Equals(af) || l1.Equals(af)) {
c.other = true;
} else {
Error(_("The tangent cubic and line segment must share an "
"endpoint. Constrain them with Constrain -> "
"On Point before constraining tangent."));
return;
}
c.type = Type::CUBIC_LINE_TANGENT; c.type = Type::CUBIC_LINE_TANGENT;
c.entityA = cubic->h; c.entityA = cubic->h;
c.entityB = line->h; c.entityB = line->h;
@ -671,24 +703,7 @@ void Constraint::MenuConstrain(Command id) {
} }
Entity *eA = SK.GetEntity(gs.entity[0]), Entity *eA = SK.GetEntity(gs.entity[0]),
*eB = SK.GetEntity(gs.entity[1]); *eB = SK.GetEntity(gs.entity[1]);
Vector as = eA->EndpointStart(), ConstrainCurveCurveTangent(&c, eA, eB);
af = eA->EndpointFinish(),
bs = eB->EndpointStart(),
bf = eB->EndpointFinish();
if(as.Equals(bs)) {
c.other = false; c.other2 = false;
} else if(as.Equals(bf)) {
c.other = false; c.other2 = true;
} else if(af.Equals(bs)) {
c.other = true; c.other2 = false;
} else if(af.Equals(bf)) {
c.other = true; c.other2 = true;
} else {
Error(_("The curves must share an endpoint. Constrain them "
"with Constrain -> On Point before constraining "
"tangent."));
return;
}
c.type = Type::CURVE_CURVE_TANGENT; c.type = Type::CURVE_CURVE_TANGENT;
c.entityA = eA->h; c.entityA = eA->h;
c.entityB = eB->h; c.entityB = eB->h;

View File

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

View File

@ -207,8 +207,8 @@ void GraphicsWindow::MakeSelected(Selection *stog) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void GraphicsWindow::SelectByMarquee() { void GraphicsWindow::SelectByMarquee() {
Point2d marqueePoint = ProjectPoint(orig.marqueePoint); Point2d marqueePoint = ProjectPoint(orig.marqueePoint);
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, -1), BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE),
Vector::From(orig.mouse.x, orig.mouse.y, 1)); Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE));
Entity *e; Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
@ -305,9 +305,16 @@ void GraphicsWindow::GroupSelection() {
Camera GraphicsWindow::GetCamera() const { Camera GraphicsWindow::GetCamera() const {
Camera camera = {}; Camera camera = {};
window->GetContentSize(&camera.width, &camera.height); if(window) {
camera.pixelRatio = window->GetDevicePixelRatio(); window->GetContentSize(&camera.width, &camera.height);
camera.gridFit = (window->GetDevicePixelRatio() == 1); camera.pixelRatio = window->GetDevicePixelRatio();
camera.gridFit = (window->GetDevicePixelRatio() == 1);
} else { // solvespace-cli
camera.width = 297.0; // A4? Whatever...
camera.height = 210.0;
camera.pixelRatio = 1.0;
camera.gridFit = camera.pixelRatio == 1.0;
}
camera.offset = offset; camera.offset = offset;
camera.projUp = projUp; camera.projUp = projUp;
camera.projRight = projRight; camera.projRight = projRight;
@ -335,6 +342,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
int bestOrder = -1; int bestOrder = -1;
int bestZIndex = 0; int bestZIndex = 0;
double bestDepth = VERY_POSITIVE;
for(const Hover &hov : hoverList) { for(const Hover &hov : hoverList) {
hGroup hg = {}; hGroup hg = {};
if(hov.selection.entity.v != 0) { if(hov.selection.entity.v != 0) {
@ -345,9 +354,12 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
Group *g = SK.GetGroup(hg); Group *g = SK.GetGroup(hg);
if(g->order > activeGroup->order) continue; if(g->order > activeGroup->order) continue;
if(bestOrder != -1 && (bestOrder >= g->order || bestZIndex > hov.zIndex)) continue; if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;
// we have hov.zIndex is >= best and hov.group is >= best (but not > active group)
if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;
bestOrder = g->order; bestOrder = g->order;
bestZIndex = hov.zIndex; bestZIndex = hov.zIndex;
bestDepth = hov.depth;
sel = hov.selection; sel = hov.selection;
} }
return sel; return sel;
@ -363,6 +375,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
int bestOrder = -1; int bestOrder = -1;
int bestZIndex = 0; int bestZIndex = 0;
double bestDepth = VERY_POSITIVE;
for(const Hover &hov : hoverList) { for(const Hover &hov : hoverList) {
hGroup hg = {}; hGroup hg = {};
if(hov.selection.entity.v != 0) { if(hov.selection.entity.v != 0) {
@ -375,7 +389,9 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {
Group *g = SK.GetGroup(hg); Group *g = SK.GetGroup(hg);
if(g->order > activeGroup->order) continue; if(g->order > activeGroup->order) continue;
if(bestOrder != -1 && (bestOrder >= g->order || bestZIndex > hov.zIndex)) continue; if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;
// we have hov.zIndex is >= best and hov.group is >= best (but not > active group)
if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;
bestOrder = g->order; bestOrder = g->order;
bestZIndex = hov.zIndex; bestZIndex = hov.zIndex;
sel = hov.selection; sel = hov.selection;
@ -432,6 +448,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
Hover hov = {}; Hover hov = {};
hov.distance = canvas.minDistance; hov.distance = canvas.minDistance;
hov.zIndex = canvas.maxZIndex; hov.zIndex = canvas.maxZIndex;
hov.depth = canvas.minDepth;
hov.selection.entity = e.h; hov.selection.entity = e.h;
hoverList.Add(&hov); hoverList.Add(&hov);
} }

View File

@ -64,13 +64,13 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
Vector proj = SS.GW.ProjectPoint3(PointGetNum()); Vector proj = SS.GW.ProjectPoint3(PointGetNum());
screenBBox = BBox::From(proj, proj); screenBBox = BBox::From(proj, proj);
} else if(IsNormal()) { } else if(IsNormal()) {
Vector proj = SK.GetEntity(point[0])->PointGetNum(); Vector proj = SS.GW.ProjectPoint3(SK.GetEntity(point[0])->PointGetNum());
screenBBox = BBox::From(proj, proj); screenBBox = BBox::From(proj, proj);
} else if(!sbl->l.IsEmpty()) { } else if(!sbl->l.IsEmpty()) {
Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]); Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]);
screenBBox = BBox::From(first, first); screenBBox = BBox::From(first, first);
for(auto &sb : sbl->l) { for(auto &sb : sbl->l) {
for(int i = 0; i < sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); } for(int i = 0; i <= sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); }
} }
} else } else
ssassert(false, "Expected entity to be a point or have beziers"); ssassert(false, "Expected entity to be a point or have beziers");
@ -315,9 +315,13 @@ void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const {
} else { } else {
// The wrapping would work, except when n = 1 and everything // The wrapping would work, except when n = 1 and everything
// wraps to zero... // wraps to zero...
if(i > 0) bm.A[i][i - 1] = eq.x; if(i > 0) {
/**/ bm.A[i][i] = eq.y; bm.A[i][i - 1] = eq.x;
if(i < (n-1)) bm.A[i][i + 1] = eq.z; }
bm.A[i][i] = eq.y;
if(i < (n-1)) {
bm.A[i][i + 1] = eq.z;
}
} }
} }
bm.Solve(); bm.Solve();
@ -468,13 +472,13 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
int zIndex; int zIndex;
if(IsPoint()) { if(IsPoint()) {
zIndex = 5; zIndex = 6;
} else if(how == DrawAs::HIDDEN) { } else if(how == DrawAs::HIDDEN) {
zIndex = 2; zIndex = 2;
} else if(group != SS.GW.activeGroup) { } else if(group != SS.GW.activeGroup) {
zIndex = 3; zIndex = 3;
} else { } else {
zIndex = 4; zIndex = 5;
} }
hStyle hs; hStyle hs;
@ -484,6 +488,9 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
hs.v = Style::NORMALS; hs.v = Style::NORMALS;
} else { } else {
hs = Style::ForEntity(h); hs = Style::ForEntity(h);
if (hs.v == Style::CONSTRUCTION) {
zIndex = 4;
}
} }
Canvas::Stroke stroke = Style::Stroke(hs); Canvas::Stroke stroke = Style::Stroke(hs);
@ -608,7 +615,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
double w = 60 - camera.width / 2.0; double w = 60 - camera.width / 2.0;
// Shift the axis to the right if they would overlap with the toolbar. // Shift the axis to the right if they would overlap with the toolbar.
if(SS.showToolbar) { if(SS.showToolbar) {
if(h + 30 > -(34*16 + 3*16 + 8) / 2) if(h + 30 > -(32*18 + 3*16 + 8) / 2)
w += 60; w += 60;
} }
tail = camera.projRight.ScaledBy(w/s).Plus( tail = camera.projRight.ScaledBy(w/s).Plus(

View File

@ -155,6 +155,52 @@ inline bool Vector::Equals(Vector v, double tol) const {
return dv.MagSquared() < tol*tol; return dv.MagSquared() < tol*tol;
} }
inline Vector Vector::From(double x, double y, double z) {
return {x, y, z};
}
inline Vector Vector::Plus(Vector b) const {
return {x + b.x, y + b.y, z + b.z};
}
inline Vector Vector::Minus(Vector b) const {
return {x - b.x, y - b.y, z - b.z};
}
inline Vector Vector::Negated() const {
return {-x, -y, -z};
}
inline Vector Vector::Cross(Vector b) const {
return {-(z * b.y) + (y * b.z), (z * b.x) - (x * b.z), -(y * b.x) + (x * b.y)};
}
inline double Vector::Dot(Vector b) const {
return (x * b.x + y * b.y + z * b.z);
}
inline double Vector::MagSquared() const {
return x * x + y * y + z * z;
}
inline double Vector::Magnitude() const {
return sqrt(x * x + y * y + z * z);
}
inline Vector Vector::ScaledBy(const double v) const {
return {x * v, y * v, z * v};
}
inline void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const {
maxv->x = max(maxv->x, x);
maxv->y = max(maxv->y, y);
maxv->z = max(maxv->z, z);
minv->x = min(minv->x, x);
minv->y = min(minv->y, y);
minv->z = min(minv->z, z);
}
struct VectorHash { struct VectorHash {
size_t operator()(const Vector &v) const; size_t operator()(const Vector &v) const;
}; };

View File

@ -843,8 +843,6 @@ void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) {
ExportMeshAsObjTo(f, fMtl, m); ExportMeshAsObjTo(f, fMtl, m);
fclose(fMtl); fclose(fMtl);
} else if(filename.HasExtension("q3do")) {
ExportMeshAsQ3doTo(f, m);
} else if(filename.HasExtension("js") || } else if(filename.HasExtension("js") ||
filename.HasExtension("html")) { filename.HasExtension("html")) {
SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines);
@ -898,54 +896,6 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) {
} }
} }
//-----------------------------------------------------------------------------
// Export the mesh as a Q3DO (https://github.com/q3k/q3d) file.
//-----------------------------------------------------------------------------
#include "q3d_object_generated.h"
void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) {
flatbuffers::FlatBufferBuilder builder(1024);
double s = SS.exportScale;
// Create a material for every colour used, keep note of triangles belonging to color/material.
std::map<RgbaColor, flatbuffers::Offset<q3d::Material>, RgbaColorCompare> materials;
std::map<RgbaColor, std::vector<flatbuffers::Offset<q3d::Triangle>>, RgbaColorCompare> materialTriangles;
for (const STriangle &t : sm->l) {
auto color = t.meta.color;
if (materials.find(color) == materials.end()) {
auto name = builder.CreateString(ssprintf("Color #%02x%02x%02x%02x", color.red, color.green, color.blue, color.alpha));
auto co = q3d::CreateColor(builder, color.red, color.green, color.blue, color.alpha);
auto mo = q3d::CreateMaterial(builder, name, co);
materials.emplace(color, mo);
}
Vector faceNormal = t.Normal();
auto a = q3d::Vector3((float)(t.a.x/s), (float)(t.a.y/s), (float)(t.a.z/s));
auto b = q3d::Vector3((float)(t.b.x/s), (float)(t.b.y/s), (float)(t.b.z/s));
auto c = q3d::Vector3((float)(t.c.x/s), (float)(t.c.y/s), (float)(t.c.z/s));
auto fn = q3d::Vector3((float)faceNormal.x, (float)faceNormal.y, (float)faceNormal.x);
auto n1 = q3d::Vector3((float)t.normals[0].x, (float)t.normals[0].y, (float)t.normals[0].z);
auto n2 = q3d::Vector3((float)t.normals[1].x, (float)t.normals[1].y, (float)t.normals[1].z);
auto n3 = q3d::Vector3((float)t.normals[2].x, (float)t.normals[2].y, (float)t.normals[2].z);
auto tri = q3d::CreateTriangle(builder, &a, &b, &c, &fn, &n1, &n2, &n3);
materialTriangles[color].push_back(tri);
}
// Build all meshes sorted by material.
std::vector<flatbuffers::Offset<q3d::Mesh>> meshes;
for (auto &it : materials) {
auto &mato = it.second;
auto to = builder.CreateVector(materialTriangles[it.first]);
auto mo = q3d::CreateMesh(builder, to, mato);
meshes.push_back(mo);
}
auto mo = builder.CreateVector(meshes);
auto o = q3d::CreateObject(builder, mo);
q3d::FinishObjectBuffer(builder, o);
fwrite(builder.GetBufferPointer(), builder.GetSize(), 1, f);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Export the mesh as Wavefront OBJ format. This requires us to reduce all the // Export the mesh as Wavefront OBJ format. This requires us to reduce all the
// identical vertices to the same identifier, so do that first. // identical vertices to the same identifier, so do that first.
@ -1006,15 +956,20 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename
SPointList spl = {}; SPointList spl = {};
STriangle *tr; STriangle *tr;
Vector bndl, bndh; Vector bndl, bndh;
const std::string THREE_FN("three-r111.min.js");
const std::string HAMMER_FN("hammer-2.0.8.js");
const std::string CONTROLS_FN("SolveSpaceControls.js");
const char htmlbegin[] = R"( const char htmlbegin[] = R"(
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"></meta> <meta charset="utf-8"></meta>
<title>Three.js Solvespace Mesh</title> <title>Three.js Solvespace Mesh</title>
<script id="three-r76.js">%s</script> <script id="%s">%s</script>
<script id="hammer-2.0.8.js">%s</script> <script id="%s">%s</script>
<script id="SolveSpaceControls.js">%s</script> <script id="%s">%s</script>
<style type="text/css"> <style type="text/css">
body { margin: 0; overflow: hidden; } body { margin: 0; overflow: hidden; }
</style> </style>
@ -1064,9 +1019,12 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename
if(filename.HasExtension("html")) { if(filename.HasExtension("html")) {
fprintf(f, htmlbegin, fprintf(f, htmlbegin,
LoadStringFromGzip("threejs/three-r76.js.gz").c_str(), THREE_FN.c_str(),
LoadStringFromGzip("threejs/hammer-2.0.8.js.gz").c_str(), LoadStringFromGzip("threejs/" + THREE_FN + ".gz").c_str(),
LoadString("threejs/SolveSpaceControls.js").c_str()); HAMMER_FN.c_str(),
LoadStringFromGzip("threejs/" + HAMMER_FN + ".gz").c_str(),
CONTROLS_FN.c_str(),
LoadString("threejs/" + CONTROLS_FN).c_str());
} }
fprintf(f, "var solvespace_model_%s = {\n" fprintf(f, "var solvespace_model_%s = {\n"

View File

@ -273,9 +273,45 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
} }
fprintf(f, "),#%d,.T.);\n", srfid); fprintf(f, "),#%d,.T.);\n", srfid);
fprintf(f, "\n");
advancedFaces.Add(&advFaceId); advancedFaces.Add(&advFaceId);
// Export the surface color and transparency
// https://www.cax-if.org/documents/rec_prac_styling_org_v16.pdf sections 4.4.2 4.2.4 etc.
// https://tracker.dev.opencascade.org/view.php?id=31550
fprintf(f, "#%d=COLOUR_RGB('',%.2f,%.2f,%.2f);\n", ++id, ss->color.redF(),
ss->color.greenF(), ss->color.blueF());
/* // This works in Kisters 3DViewStation but not in KiCAD and Horison EDA,
// it seems they do not support transparency so use the more verbose one below
fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF());
++id;
fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n",
id, id - 2, id - 1);
++id;
fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d));\n", id, id - 1);
*/
// This works in Horison EDA but is more verbose.
++id;
fprintf(f, "#%d=FILL_AREA_STYLE_COLOUR('',#%d);\n", id, id - 1);
++id;
fprintf(f, "#%d=FILL_AREA_STYLE('',(#%d));\n", id, id - 1);
++id;
fprintf(f, "#%d=SURFACE_STYLE_FILL_AREA(#%d);\n", id, id - 1);
fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF());
++id;
fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n", id, id - 5, id - 1);
++id;
fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d, #%d));\n", id, id - 3, id - 1);
++id;
fprintf(f, "#%d=SURFACE_STYLE_USAGE(.BOTH.,#%d);\n", id, id - 1);
++id;
fprintf(f, "#%d=PRESENTATION_STYLE_ASSIGNMENT((#%d));\n", id, id - 1);
++id;
fprintf(f, "#%d=STYLED_ITEM('',(#%d),#%d);\n", id, id - 1, advFaceId);
fprintf(f, "\n");
id++; id++;
listOfLoops.Clear(); listOfLoops.Clear();
} }

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 SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) {
bool fileIsEmpty = true;
allConsistent = false; allConsistent = false;
fileLoadError = false; fileLoadError = false;
@ -485,6 +486,8 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel)
char line[1024]; char line[1024];
while(fgets(line, (int)sizeof(line), fh)) { while(fgets(line, (int)sizeof(line), fh)) {
fileIsEmpty = false;
char *s = strchr(line, '\n'); char *s = strchr(line, '\n');
if(s) *s = '\0'; if(s) *s = '\0';
// We should never get files with \r characters in them, but mailers // We should never get files with \r characters in them, but mailers
@ -545,6 +548,11 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel)
fclose(fh); fclose(fh);
if(fileIsEmpty) {
Error(_("The file is empty. It may be corrupt."));
NewFile();
}
if(fileLoadError) { if(fileLoadError) {
Error(_("Unrecognized data in file. This file may be corrupt, or " Error(_("Unrecognized data in file. This file may be corrupt, or "
"from a newer version of the program.")); "from a newer version of the program."));
@ -904,11 +912,13 @@ try_again:
} else if(linkMap.count(g.linkFile) == 0) { } else if(linkMap.count(g.linkFile) == 0) {
dbp("Missing file for group: %s", g.name.c_str()); dbp("Missing file for group: %s", g.name.c_str());
// The file was moved; prompt the user for its new location. // The file was moved; prompt the user for its new location.
switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) { const auto linkFileRelative = g.linkFile.RelativeTo(saveFile);
switch(LocateImportedFile(linkFileRelative, canCancel)) {
case Platform::MessageDialog::Response::YES: { case Platform::MessageDialog::Response::YES: {
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
dialog->AddFilters(Platform::SolveSpaceModelFileFilters); dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
dialog->ThawChoices(settings, "LinkSketch"); dialog->ThawChoices(settings, "LinkSketch");
dialog->SuggestFilename(linkFileRelative);
if(dialog->RunModal()) { if(dialog->RunModal()) {
dialog->FreezeChoices(settings, "LinkSketch"); dialog->FreezeChoices(settings, "LinkSketch");
linkMap[g.linkFile] = dialog->GetFilename(); linkMap[g.linkFile] = dialog->GetFilename();
@ -985,6 +995,7 @@ bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
dialog->AddFilters(Platform::RasterFileFilters); dialog->AddFilters(Platform::RasterFileFilters);
dialog->ThawChoices(settings, "LinkImage"); dialog->ThawChoices(settings, "LinkImage");
dialog->SuggestFilename(filename->RelativeTo(saveFile));
if(dialog->RunModal()) { if(dialog->RunModal()) {
dialog->FreezeChoices(settings, "LinkImage"); dialog->FreezeChoices(settings, "LinkImage");
*filename = dialog->GetFilename(); *filename = dialog->GetFilename();

View File

@ -92,6 +92,7 @@ const MenuEntry Menu[] = {
{ 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView }, { 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView },
{ 1, NULL, Command::NONE, 0, KN, NULL }, { 1, NULL, Command::NONE, 0, KN, NULL },
{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView }, { 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView },
{ 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView },
{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView }, { 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView },
{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL }, { 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL },
{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView }, { 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView },
@ -312,6 +313,8 @@ void GraphicsWindow::PopulateMainMenu() {
if(Menu[i].cmd == Command::SHOW_GRID) { if(Menu[i].cmd == Command::SHOW_GRID) {
showGridMenuItem = menuItem; showGridMenuItem = menuItem;
} else if(Menu[i].cmd == Command::DIM_SOLID_MODEL) {
dimSolidModelMenuItem = menuItem;
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) { } else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
perspectiveProjMenuItem = menuItem; perspectiveProjMenuItem = menuItem;
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) { } else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
@ -406,6 +409,7 @@ void GraphicsWindow::Init() {
showTextWindow = true; showTextWindow = true;
showSnapGrid = false; showSnapGrid = false;
dimSolidModel = true;
context.active = false; context.active = false;
toolbarHovered = Command::NONE; toolbarHovered = Command::NONE;
@ -722,6 +726,12 @@ void GraphicsWindow::MenuView(Command id) {
} }
break; break;
case Command::DIM_SOLID_MODEL:
SS.GW.dimSolidModel = !SS.GW.dimSolidModel;
SS.GW.EnsureValidActives();
SS.GW.Invalidate(/*clearPersistent=*/true);
break;
case Command::PERSPECTIVE_PROJ: case Command::PERSPECTIVE_PROJ:
SS.usePerspectiveProj = !SS.usePerspectiveProj; SS.usePerspectiveProj = !SS.usePerspectiveProj;
SS.GW.EnsureValidActives(); SS.GW.EnsureValidActives();
@ -923,6 +933,7 @@ void GraphicsWindow::EnsureValidActives() {
showTextWndMenuItem->SetActive(SS.GW.showTextWindow); showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
showGridMenuItem->SetActive(SS.GW.showSnapGrid); showGridMenuItem->SetActive(SS.GW.showSnapGrid);
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj); perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
showToolbarMenuItem->SetActive(SS.showToolbar); showToolbarMenuItem->SetActive(SS.showToolbar);
fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen()); fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen());
@ -1175,10 +1186,7 @@ void GraphicsWindow::MenuEdit(Command id) {
} }
// Regenerate, with these points marked as dragged so that they // Regenerate, with these points marked as dragged so that they
// get placed as close as possible to our snap grid. // get placed as close as possible to our snap grid.
SS.GW.ClearPending();
SS.GW.ClearSelection(); SS.GW.ClearSelection();
SS.GW.Invalidate();
break; break;
} }

View File

@ -569,7 +569,8 @@ void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) {
if(!SS.GW.showShaded) { if(!SS.GW.showShaded) {
fillFront.layer = Canvas::Layer::DEPTH_ONLY; fillFront.layer = Canvas::Layer::DEPTH_ONLY;
} }
if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) { if((type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE)
&& SS.GW.dimSolidModel) {
fillFront.color = Style::Color(Style::DIM_SOLID); fillFront.color = Style::Color(Style::DIM_SOLID);
} }
Canvas::hFill hcfFront = canvas->GetFill(fillFront); Canvas::hFill hcfFront = canvas->GetFill(fillFront);

View File

@ -236,21 +236,18 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
Vector u = q.RotationU(), v = q.RotationV(); Vector u = q.RotationU(), v = q.RotationV();
double r = pa.Minus(center).Magnitude(); double r = pa.Minus(center).Magnitude();
double thetaa, thetab, dtheta; double theta, dtheta;
if(angle == 360.0) { if(angle == 360.0) {
thetaa = 0; theta = 0;
thetab = 2*PI;
dtheta = 2*PI;
} else { } else {
Point2d c2 = center.Project2d(u, v); Point2d c2 = center.Project2d(u, v);
Point2d pa2 = (pa.Project2d(u, v)).Minus(c2); Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
Point2d pb2 = (pb.Project2d(u, v)).Minus(c2);
thetaa = atan2(pa2.y, pa2.x); theta = atan2(pa2.y, pa2.x);
thetab = atan2(pb2.y, pb2.x);
dtheta = thetab - thetaa;
} }
dtheta = angle * PI/180;
int i, n; int i, n;
if(dtheta > (3*PI/2 + 0.01)) { if(dtheta > (3*PI/2 + 0.01)) {
n = 4; n = 4;
@ -266,17 +263,17 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
for(i = 0; i < n; i++) { for(i = 0; i < n; i++) {
double s, c; double s, c;
c = cos(thetaa); c = cos(theta);
s = sin(thetaa); s = sin(theta);
// The start point of the curve, and the tangent vector at // The start point of the curve, and the tangent vector at
// that start point. // that start point.
Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
thetaa += dtheta; theta += dtheta;
c = cos(thetaa); c = cos(theta);
s = sin(thetaa); s = sin(theta);
Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
@ -335,6 +332,7 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
double board_thickness = 10.0; double board_thickness = 10.0;
double scale = 1.0; //mm double scale = 1.0; //mm
bool topEntities, bottomEntities;
Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0)); Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0));
hEntity hnorm = newNormal(el, &entityCount, normal); hEntity hnorm = newNormal(el, &entityCount, normal);
@ -347,6 +345,7 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
for(std::string line; getline( stream, line ); ) { for(std::string line; getline( stream, line ); ) {
if (line.find(".END_") == 0) { if (line.find(".END_") == 0) {
section = none; section = none;
curve = -1;
} }
switch (section) { switch (section) {
case none: case none:
@ -356,6 +355,10 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
} else if (line.find(".BOARD_OUTLINE") == 0) { } else if (line.find(".BOARD_OUTLINE") == 0) {
section = board_outline; section = board_outline;
record_number = 1; record_number = 1;
// no keepouts for now - they should also be shown as construction?
// } else if (line.find(".ROUTE_KEEPOUT") == 0) {
// section = routing_keepout;
// record_number = 1;
} else if(line.find(".DRILLED_HOLES") == 0) { } else if(line.find(".DRILLED_HOLES") == 0) {
section = drilled_holes; section = drilled_holes;
record_number = 1; record_number = 1;
@ -375,11 +378,23 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
} }
} }
break; break;
case routing_keepout:
case board_outline: case board_outline:
if (record_number == 2) { if (record_number == 2) {
board_thickness = std::stod(line) * scale; if(section == board_outline) {
dbp("IDF board thickness: %lf", board_thickness); topEntities = true;
bottomEntities = true;
board_thickness = std::stod(line) * scale;
dbp("IDF board thickness: %lf", board_thickness);
} else if (section == routing_keepout) {
topEntities = false;
bottomEntities = false;
if(line.find("TOP") == 0 || line.find("BOTH") == 0)
topEntities = true;
if(line.find("BOTTOM") == 0 || line.find("BOTH") == 0)
bottomEntities = true;
}
} else { // records 3+ are lines, arcs, and circles } else { // records 3+ are lines, arcs, and circles
std::vector <std::string> values = splitString(line); std::vector <std::string> values = splitString(line);
if(values.size() != 4) continue; if(values.size() != 4) continue;
@ -391,36 +406,43 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
Vector pTop = Vector::From(x,y,board_thickness); Vector pTop = Vector::From(x,y,board_thickness);
if(c != curve) { // start a new curve if(c != curve) { // start a new curve
curve = c; curve = c;
hprev = newPoint(el, &entityCount, point, /*visible=*/false); if (bottomEntities)
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false); hprev = newPoint(el, &entityCount, point, /*visible=*/false);
if (topEntities)
hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false);
pprev = point; pprev = point;
pprevTop = pTop; pprevTop = pTop;
} else { } else {
// create a bezier for the extrusion if(section == board_outline) {
if (ang == 0) { // create a bezier for the extrusion
// straight lines if (ang == 0) {
SBezier sb = SBezier::From(pprev, point); // straight lines
sbl.l.Add(&sb); SBezier sb = SBezier::From(pprev, point);
} else if (ang != 360.0) { sbl.l.Add(&sb);
// Arcs } else if (ang != 360.0) {
Vector c = ArcCenter(pprev, point, ang); // Arcs
MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang); Vector c = ArcCenter(pprev, point, ang);
} else { MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang);
// circles } else {
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang); // circles
MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang);
}
} }
// next create the entities // next create the entities
// only curves and points at circle centers will be visible // only curves and points at circle centers will be visible
bool vis = (ang == 360.0); bool vis = (ang == 360.0);
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis); if (bottomEntities) {
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang); hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
pprev = point; CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
hprev = hp; pprev = point;
hp = newPoint(el, &entityCount, pTop, /*visible=*/vis); hprev = hp;
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang); }
pprevTop = pTop; if (topEntities) {
hprevTop = hp; hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
pprevTop = pTop;
hprevTop = hp;
}
} }
} }
break; break;
@ -428,7 +450,6 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
case other_outline: case other_outline:
case routing_outline: case routing_outline:
case placement_outline: case placement_outline:
case routing_keepout:
case via_keepout: case via_keepout:
case placement_group: case placement_group:
break; break;

View File

@ -305,7 +305,6 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
HitTestMakeSelection(mp); HitTestMakeSelection(mp);
SS.MarkGroupDirtyByEntity(pending.point); SS.MarkGroupDirtyByEntity(pending.point);
orig.mouse = mp; orig.mouse = mp;
Invalidate();
break; break;
case Pending::DRAGGING_POINTS: case Pending::DRAGGING_POINTS:
@ -624,24 +623,24 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
} }
if(gs.withEndpoints > 0) { if(gs.withEndpoints > 0) {
menu->AddItem(_("Select Edge Chain"), menu->AddItem(_("Select Edge Chain"),
[this]() { MenuEdit(Command::SELECT_CHAIN); }); []() { MenuEdit(Command::SELECT_CHAIN); });
} }
if(gs.constraints == 1 && gs.n == 0) { if(gs.constraints == 1 && gs.n == 0) {
Constraint *c = SK.GetConstraint(gs.constraint[0]); Constraint *c = SK.GetConstraint(gs.constraint[0]);
if(c->HasLabel() && c->type != Constraint::Type::COMMENT) { if(c->HasLabel() && c->type != Constraint::Type::COMMENT) {
menu->AddItem(_("Toggle Reference Dimension"), menu->AddItem(_("Toggle Reference Dimension"),
[]() { Constraint::MenuConstrain(Command::REFERENCE); }); []() { Constraint::MenuConstrain(Command::REFERENCE); });
} }
if(c->type == Constraint::Type::ANGLE || if(c->type == Constraint::Type::ANGLE ||
c->type == Constraint::Type::EQUAL_ANGLE) c->type == Constraint::Type::EQUAL_ANGLE)
{ {
menu->AddItem(_("Other Supplementary Angle"), menu->AddItem(_("Other Supplementary Angle"),
[]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); }); []() { Constraint::MenuConstrain(Command::OTHER_ANGLE); });
} }
} }
if(gs.constraintLabels > 0 || gs.points > 0) { if(gs.constraintLabels > 0 || gs.points > 0) {
menu->AddItem(_("Snap to Grid"), menu->AddItem(_("Snap to Grid"),
[this]() { MenuEdit(Command::SNAP_TO_GRID); }); []() { MenuEdit(Command::SNAP_TO_GRID); });
} }
if(gs.points == 1 && gs.point[0].isFromRequest()) { if(gs.points == 1 && gs.point[0].isFromRequest()) {
@ -714,7 +713,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
} }
if(gs.entities == gs.n) { if(gs.entities == gs.n) {
menu->AddItem(_("Toggle Construction"), menu->AddItem(_("Toggle Construction"),
[this]() { MenuRequest(Command::CONSTRUCTION); }); []() { MenuRequest(Command::CONSTRUCTION); });
} }
if(gs.points == 1) { if(gs.points == 1) {
@ -748,28 +747,28 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
menu->AddSeparator(); menu->AddSeparator();
if(LockedInWorkplane()) { if(LockedInWorkplane()) {
menu->AddItem(_("Cut"), menu->AddItem(_("Cut"),
[this]() { MenuClipboard(Command::CUT); }); []() { MenuClipboard(Command::CUT); });
menu->AddItem(_("Copy"), menu->AddItem(_("Copy"),
[this]() { MenuClipboard(Command::COPY); }); []() { MenuClipboard(Command::COPY); });
} }
} else { } else {
menu->AddItem(_("Select All"), menu->AddItem(_("Select All"),
[this]() { MenuEdit(Command::SELECT_ALL); }); []() { MenuEdit(Command::SELECT_ALL); });
} }
if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) { if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) {
menu->AddItem(_("Paste"), menu->AddItem(_("Paste"),
[this]() { MenuClipboard(Command::PASTE); }); []() { MenuClipboard(Command::PASTE); });
menu->AddItem(_("Paste Transformed..."), menu->AddItem(_("Paste Transformed..."),
[this]() { MenuClipboard(Command::PASTE_TRANSFORM); }); []() { MenuClipboard(Command::PASTE_TRANSFORM); });
} }
if(itemsSelected) { if(itemsSelected) {
menu->AddItem(_("Delete"), menu->AddItem(_("Delete"),
[this]() { MenuClipboard(Command::DELETE); }); []() { MenuClipboard(Command::DELETE); });
menu->AddSeparator(); menu->AddSeparator();
menu->AddItem(_("Unselect All"), menu->AddItem(_("Unselect All"),
[this]() { MenuEdit(Command::UNSELECT_ALL); }); []() { MenuEdit(Command::UNSELECT_ALL); });
} }
// If only one item is selected, then it must be the one that we just // If only one item is selected, then it must be the one that we just
// selected from the hovered item; in which case unselect all and hovered // selected from the hovered item; in which case unselect all and hovered
@ -785,7 +784,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
if(itemsSelected) { if(itemsSelected) {
menu->AddSeparator(); menu->AddSeparator();
menu->AddItem(_("Zoom to Fit"), menu->AddItem(_("Zoom to Fit"),
[this]() { MenuView(Command::ZOOM_TO_FIT); }); []() { MenuView(Command::ZOOM_TO_FIT); });
} }
menu->PopUp(); menu->PopUp();
@ -877,7 +876,6 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projecte
bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) { bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
using Platform::MouseEvent; using Platform::MouseEvent;
double width, height; double width, height;
window->GetContentSize(&width, &height); window->GetContentSize(&width, &height);
@ -918,7 +916,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) {
break; break;
case MouseEvent::Type::SCROLL_VERT: case MouseEvent::Type::SCROLL_VERT:
this->MouseScroll(event.x, event.y, (int)event.scrollDelta); this->MouseScroll(event.x, event.y, event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);
break; break;
case MouseEvent::Type::LEAVE: case MouseEvent::Type::LEAVE:
@ -1117,7 +1115,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct
AddToPending(hr); AddToPending(hr);
Request *r = SK.GetRequest(hr); Request *r = SK.GetRequest(hr);
r->str = "Abc"; r->str = "Abc";
r->font = "BitstreamVeraSans-Roman-builtin.ttf"; r->font = Platform::embeddedFont;
for(int i = 1; i <= 4; i++) { for(int i = 1; i <= 4; i++) {
SK.GetEntity(hr.entity(i))->PointForceTo(v); SK.GetEntity(hr.entity(i))->PointForceTo(v);
@ -1472,18 +1470,25 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
} }
} }
void GraphicsWindow::MouseScroll(double x, double y, int delta) { void GraphicsWindow::MouseScroll(double x, double y, double delta) {
double offsetRight = offset.Dot(projRight); double offsetRight = offset.Dot(projRight);
double offsetUp = offset.Dot(projUp); double offsetUp = offset.Dot(projUp);
double righti = x/scale - offsetRight; double righti = x/scale - offsetRight;
double upi = y/scale - offsetUp; double upi = y/scale - offsetUp;
if(delta > 0) { // The default zoom factor is 1.2x for one scroll wheel click (delta==1).
scale *= 1.2; // To support smooth scrolling where scroll wheel events come in increments
} else if(delta < 0) { // smaller (or larger) than 1 we do:
scale /= 1.2; // scale *= exp(ln(1.2) * delta);
} else return; // to ensure that the same total scroll delta always results in the same
// total zoom irrespective of in how many increments the zoom was applied.
// For example if we scroll a total delta of a+b in two events vs. one then
// scale * e^a * e^b == scale * e^(a+b)
// while
// scale * a * b != scale * (a+b)
// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x
scale *= exp(0.1823216 * delta);
double rightf = x/scale - offsetRight; double rightf = x/scale - offsetRight;
double upf = y/scale - offsetUp; double upf = y/scale - offsetUp;

View File

@ -4,6 +4,7 @@
// Copyright 2016 whitequark // Copyright 2016 whitequark
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "solvespace.h" #include "solvespace.h"
#include "config.h"
static void ShowUsage(const std::string &cmd) { static void ShowUsage(const std::string &cmd) {
fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", cmd.c_str()); fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", cmd.c_str());
@ -29,6 +30,8 @@ Common options:
Whether to export the background colour in vector formats. Defaults to off. Whether to export the background colour in vector formats. Defaults to off.
Commands: Commands:
version
Prints the current solvespace version.
thumbnail --output <pattern> --size <size> --view <direction> thumbnail --output <pattern> --size <size> --view <direction>
[--chord-tol <tolerance>] [--chord-tol <tolerance>]
Outputs a rendered view of the sketch, like the SolveSpace GUI would. Outputs a rendered view of the sketch, like the SolveSpace GUI would.
@ -174,7 +177,10 @@ static bool RunCommand(const std::vector<std::string> args) {
}; };
unsigned width = 0, height = 0; unsigned width = 0, height = 0;
if(args[1] == "thumbnail") { if(args[1] == "version") {
fprintf(stderr, "SolveSpace version %s \n\n", PACKAGE_VERSION);
return false;
} else if(args[1] == "thumbnail") {
auto ParseSize = [&](size_t &argn) { auto ParseSize = [&](size_t &argn) {
if(argn + 1 < args.size() && args[argn] == "--size") { if(argn + 1 < args.size() && args[argn] == "--size") {
argn++; argn++;

View File

@ -21,7 +21,7 @@ int main(int argc, char** argv) {
dbp("Only the first file passed on command line will be opened."); dbp("Only the first file passed on command line will be opened.");
} }
SS.Load(Platform::Path::From(args.back()).Expand(/*fromCurrentDirectory=*/true)); SS.Load(Platform::Path::From(args.back()));
} }
Platform::RunGui(); Platform::RunGui();

View File

@ -99,7 +99,6 @@ std::vector<FileFilter> MeshFileFilters = {
{ CN_("file-type", "Wavefront OBJ mesh"), { "obj" } }, { CN_("file-type", "Wavefront OBJ mesh"), { "obj" } },
{ CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } }, { CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } },
{ CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } }, { CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } },
{ CN_("file-type", "Q3D Object file"), { "q3do" } },
{ CN_("file-type", "VRML text file"), { "wrl" } }, { CN_("file-type", "VRML text file"), { "wrl" } },
}; };

View File

@ -356,6 +356,7 @@ public:
virtual Platform::Path GetFilename() = 0; virtual Platform::Path GetFilename() = 0;
virtual void SetFilename(Platform::Path path) = 0; virtual void SetFilename(Platform::Path path) = 0;
virtual void SuggestFilename(Platform::Path path) = 0;
virtual void AddFilter(std::string name, std::vector<std::string> extensions) = 0; virtual void AddFilter(std::string name, std::vector<std::string> extensions) = 0;
void AddFilter(const FileFilter &filter); void AddFilter(const FileFilter &filter);

View File

@ -472,7 +472,7 @@ protected:
} }
bool process_pointer_event(MouseEvent::Type type, double x, double y, bool process_pointer_event(MouseEvent::Type type, double x, double y,
guint state, guint button = 0, int scroll_delta = 0) { guint state, guint button = 0, double scroll_delta = 0) {
MouseEvent event = {}; MouseEvent event = {};
event.type = type; event.type = type;
event.x = x; event.x = x;
@ -536,7 +536,7 @@ protected:
} }
bool on_scroll_event(GdkEventScroll *gdk_event) override { bool on_scroll_event(GdkEventScroll *gdk_event) override {
int delta; double delta;
if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) { if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) {
delta = 1; delta = 1;
} else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) { } else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) {
@ -1246,6 +1246,10 @@ public:
gtkChooser->set_filename(path.raw); gtkChooser->set_filename(path.raw);
} }
void SuggestFilename(Platform::Path path) override {
gtkChooser->set_current_name(path.FileStem()+"."+GetExtension());
}
void AddFilter(std::string name, std::vector<std::string> extensions) override { void AddFilter(std::string name, std::vector<std::string> extensions) override {
Glib::RefPtr<Gtk::FileFilter> gtkFilter = Gtk::FileFilter::create(); Glib::RefPtr<Gtk::FileFilter> gtkFilter = Gtk::FileFilter::create();
Glib::ustring desc; Glib::ustring desc;
@ -1291,13 +1295,16 @@ public:
} }
} }
//TODO: This is not getting called when the extension selection is changed.
void FilterChanged() { void FilterChanged() {
std::string extension = GetExtension(); std::string extension = GetExtension();
if(extension.empty()) if(extension.empty())
return; return;
Platform::Path path = GetFilename(); Platform::Path path = GetFilename();
SetCurrentName(path.WithExtension(extension).FileName()); if(gtkChooser->get_action() != GTK_FILE_CHOOSER_ACTION_OPEN) {
SetCurrentName(path.WithExtension(extension).FileName());
}
} }
void FreezeChoices(SettingsRef settings, const std::string &key) override { void FreezeChoices(SettingsRef settings, const std::string &key) override {

View File

@ -371,6 +371,7 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
- (id)initWithFrame:(NSRect)frameRect { - (id)initWithFrame:(NSRect)frameRect {
NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPixelFormatAttribute attrs[] = {
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize, 24, NSOpenGLPFAColorSize, 24,
NSOpenGLPFADepthSize, 24, NSOpenGLPFADepthSize, 24,
0 0
@ -553,7 +554,9 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
MouseEvent event = [self convertMouseEvent:nsEvent]; MouseEvent event = [self convertMouseEvent:nsEvent];
event.type = MouseEvent::Type::SCROLL_VERT; event.type = MouseEvent::Type::SCROLL_VERT;
event.scrollDelta = [nsEvent deltaY];
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5);
if(receiver->onMouseEvent) { if(receiver->onMouseEvent) {
receiver->onMouseEvent(event); receiver->onMouseEvent(event);
@ -974,9 +977,6 @@ public:
if(GetScrollbarPosition() == pos) if(GetScrollbarPosition() == pos)
return; return;
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))]; [nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
if(onScrollbarAdjusted) {
onScrollbarAdjusted(pos);
}
} }
void Invalidate() override { void Invalidate() override {
@ -1273,6 +1273,10 @@ public:
nsPanel.nameFieldStringValue = Wrap(path.FileStem()); nsPanel.nameFieldStringValue = Wrap(path.FileStem());
} }
void SuggestFilename(Platform::Path path) override {
SetFilename(path.WithExtension(""));
}
void FreezeChoices(SettingsRef settings, const std::string &key) override { void FreezeChoices(SettingsRef settings, const std::string &key) override {
settings->FreezeString("Dialog_" + key + "_Folder", settings->FreezeString("Dialog_" + key + "_Folder",
[nsPanel.directoryURL.absoluteString UTF8String]); [nsPanel.directoryURL.absoluteString UTF8String]);

View File

@ -183,7 +183,7 @@ public:
HKEY GetKey() { HKEY GetKey() {
if(hKey == NULL) { if(hKey == NULL) {
sscheck(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0, sscheck(ERROR_SUCCESS == RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0,
KEY_ALL_ACCESS, NULL, &hKey, NULL)); KEY_ALL_ACCESS, NULL, &hKey, NULL));
} }
return hKey; return hKey;
@ -191,12 +191,12 @@ public:
~SettingsImplWin32() { ~SettingsImplWin32() {
if(hKey != NULL) { if(hKey != NULL) {
sscheck(RegCloseKey(hKey)); sscheck(ERROR_SUCCESS == RegCloseKey(hKey));
} }
} }
void FreezeInt(const std::string &key, uint32_t value) { void FreezeInt(const std::string &key, uint32_t value) {
sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0,
REG_DWORD, (const BYTE *)&value, sizeof(value))); REG_DWORD, (const BYTE *)&value, sizeof(value)));
} }
@ -212,7 +212,7 @@ public:
} }
void FreezeFloat(const std::string &key, double value) { void FreezeFloat(const std::string &key, double value) {
sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0,
REG_QWORD, (const BYTE *)&value, sizeof(value))); REG_QWORD, (const BYTE *)&value, sizeof(value)));
} }
@ -231,7 +231,7 @@ public:
ssassert(value.length() == strlen(value.c_str()), ssassert(value.length() == strlen(value.c_str()),
"illegal null byte in middle of a string setting"); "illegal null byte in middle of a string setting");
std::wstring valueW = Widen(value); std::wstring valueW = Widen(value);
sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0,
REG_SZ, (const BYTE *)&valueW[0], (valueW.length() + 1) * 2)); REG_SZ, (const BYTE *)&valueW[0], (valueW.length() + 1) * 2));
} }
@ -242,7 +242,7 @@ public:
if(result == ERROR_SUCCESS && type == REG_SZ) { if(result == ERROR_SUCCESS && type == REG_SZ) {
std::wstring valueW; std::wstring valueW;
valueW.resize(length / 2 - 1); valueW.resize(length / 2 - 1);
sscheck(RegQueryValueExW(GetKey(), &Widen(key)[0], 0, sscheck(ERROR_SUCCESS == RegQueryValueExW(GetKey(), &Widen(key)[0], 0,
&type, (BYTE *)&valueW[0], &length)); &type, (BYTE *)&valueW[0], &length));
return Narrow(valueW); return Narrow(valueW);
} }
@ -734,6 +734,11 @@ public:
event.type = SixDofEvent::Type::RELEASE; event.type = SixDofEvent::Type::RELEASE;
event.button = SixDofEvent::Button::FIT; event.button = SixDofEvent::Button::FIT;
} }
} else {
return 0;
}
if(window->onSixDofEvent) {
window->onSixDofEvent(event);
} }
return 0; return 0;
} }
@ -907,8 +912,8 @@ public:
// Make the mousewheel work according to which window the mouse is // Make the mousewheel work according to which window the mouse is
// over, not according to which window is active. // over, not according to which window is active.
POINT pt; POINT pt;
pt.x = LOWORD(lParam); pt.x = GET_X_LPARAM(lParam);
pt.y = HIWORD(lParam); pt.y = GET_Y_LPARAM(lParam);
HWND hWindowUnderMouse; HWND hWindowUnderMouse;
sscheck(hWindowUnderMouse = WindowFromPoint(pt)); sscheck(hWindowUnderMouse = WindowFromPoint(pt));
if(hWindowUnderMouse && hWindowUnderMouse != h) { if(hWindowUnderMouse && hWindowUnderMouse != h) {
@ -917,8 +922,15 @@ public:
break; break;
} }
// Convert the mouse coordinates from screen to client area so that
// scroll wheel zooming remains centered irrespective of the window
// position.
ScreenToClient(hWindowUnderMouse, &pt);
event.x = pt.x / pixelRatio;
event.y = pt.y / pixelRatio;
event.type = MouseEvent::Type::SCROLL_VERT; event.type = MouseEvent::Type::SCROLL_VERT;
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1 : -1; event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
break; break;
case WM_MOUSELEAVE: case WM_MOUSELEAVE:
@ -1094,12 +1106,12 @@ public:
bool IsVisible() override { bool IsVisible() override {
BOOL isVisible; BOOL isVisible;
sscheck(isVisible = IsWindowVisible(hWindow)); isVisible = IsWindowVisible(hWindow);
return isVisible == TRUE; return isVisible == TRUE;
} }
void SetVisible(bool visible) override { void SetVisible(bool visible) override {
sscheck(ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE)); ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE);
} }
void Focus() override { void Focus() override {
@ -1267,7 +1279,7 @@ public:
bool IsEditorVisible() override { bool IsEditorVisible() override {
BOOL visible; BOOL visible;
sscheck(visible = IsWindowVisible(hEditor)); visible = IsWindowVisible(hEditor);
return visible == TRUE; return visible == TRUE;
} }
@ -1309,7 +1321,7 @@ public:
sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
/*bRepaint=*/true)); /*bRepaint=*/true));
sscheck(ShowWindow(hEditor, SW_SHOW)); ShowWindow(hEditor, SW_SHOW);
if(!textW.empty()) { if(!textW.empty()) {
sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str())); sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str()));
sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length())); sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length()));
@ -1320,7 +1332,7 @@ public:
void HideEditor() override { void HideEditor() override {
if(!IsEditorVisible()) return; if(!IsEditorVisible()) return;
sscheck(ShowWindow(hEditor, SW_HIDE)); ShowWindow(hEditor, SW_HIDE);
} }
void SetScrollbarVisible(bool visible) override { void SetScrollbarVisible(bool visible) override {
@ -1335,7 +1347,7 @@ public:
si.nMin = (UINT)(min * SCROLLBAR_UNIT); si.nMin = (UINT)(min * SCROLLBAR_UNIT);
si.nMax = (UINT)(max * SCROLLBAR_UNIT); si.nMax = (UINT)(max * SCROLLBAR_UNIT);
si.nPage = (UINT)(pageSize * SCROLLBAR_UNIT); si.nPage = (UINT)(pageSize * SCROLLBAR_UNIT);
sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE)); SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE); // Returns scrollbar position
} }
double GetScrollbarPosition() override { double GetScrollbarPosition() override {
@ -1359,7 +1371,7 @@ public:
return; return;
si.nPos = (int)(pos * SCROLLBAR_UNIT); si.nPos = (int)(pos * SCROLLBAR_UNIT);
sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE)); SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE); // Returns scrollbar position
// Windows won't synthesize a WM_VSCROLL for us here. // Windows won't synthesize a WM_VSCROLL for us here.
if(onScrollbarAdjusted) { if(onScrollbarAdjusted) {
@ -1443,7 +1455,10 @@ public:
void SetType(Type type) override { void SetType(Type type) override {
switch(type) { switch(type) {
case Type::INFORMATION: case Type::INFORMATION:
style = MB_ICONINFORMATION; style = MB_USERICON; // Avoid beep
mbp.hInstance = GetModuleHandle(NULL);
mbp.lpszIcon = MAKEINTRESOURCE(4000); // Use SolveSpace icon
// mbp.lpszIcon = IDI_INFORMATION;
break; break;
case Type::QUESTION: case Type::QUESTION:
@ -1455,7 +1470,10 @@ public:
break; break;
case Type::ERROR: case Type::ERROR:
style = MB_ICONERROR; style = MB_USERICON; // Avoid beep
mbp.hInstance = GetModuleHandle(NULL);
mbp.lpszIcon = MAKEINTRESOURCE(4000); // Use SolveSpace icon
// mbp.lpszIcon = IDI_ERROR;
break; break;
} }
} }
@ -1575,6 +1593,10 @@ public:
wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1); wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1);
} }
void SuggestFilename(Platform::Path path) override {
SetFilename(Platform::Path::From(path.FileStem()));
}
void AddFilter(std::string name, std::vector<std::string> extensions) override { void AddFilter(std::string name, std::vector<std::string> extensions) override {
std::string desc, patterns; std::string desc, patterns;
for(auto extension : extensions) { for(auto extension : extensions) {

View File

@ -185,8 +185,10 @@ Path Path::WithExtension(std::string ext) const {
if(dot != std::string::npos) { if(dot != std::string::npos) {
withExt.raw.erase(dot); withExt.raw.erase(dot);
} }
withExt.raw += "."; if(!ext.empty()) {
withExt.raw += ext; withExt.raw += ".";
withExt.raw += ext;
}
return withExt; return withExt;
} }
@ -401,7 +403,7 @@ FILE *OpenFile(const Platform::Path &filename, const char *mode) {
ssassert(filename.raw.length() == strlen(filename.raw.c_str()), ssassert(filename.raw.length() == strlen(filename.raw.c_str()),
"Unexpected null byte in middle of a path"); "Unexpected null byte in middle of a path");
#if defined(WIN32) #if defined(WIN32)
return _wfopen(Widen(filename.Expand().raw).c_str(), Widen(mode).c_str()); return _wfopen(Widen(filename.Expand(/*fromCurrentDirectory=*/true).raw).c_str(), Widen(mode).c_str());
#else #else
return fopen(filename.raw.c_str(), mode); return fopen(filename.raw.c_str(), mode);
#endif #endif

View File

@ -17,6 +17,12 @@ std::string Narrow(const std::wstring &s);
std::wstring Widen(const std::string &s); std::wstring Widen(const std::string &s);
#endif #endif
#if defined(_WIN32)
const std::string embeddedFont = "res://fonts/BitstreamVeraSans-Roman-builtin.ttf";
#else // Linux and macOS
const std::string embeddedFont = "BitstreamVeraSans-Roman-builtin.ttf";
#endif
// A filesystem path, respecting the conventions of the current platform. // A filesystem path, respecting the conventions of the current platform.
// Transformation functions return an empty path on error. // Transformation functions return an empty path on error.
class Path { class Path {

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. // A canvas that performs picking against drawn geometry.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void ObjectPicker::DoCompare(double distance, int zIndex, int comparePosition) { void ObjectPicker::DoCompare(double depth, double distance, int zIndex, int comparePosition) {
if(distance > selRadius) return; if(distance > selRadius) return;
if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) { if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) {
minDepth = depth;
minDistance = distance; minDistance = distance;
maxZIndex = zIndex; maxZIndex = zIndex;
position = comparePosition; position = comparePosition;
@ -372,10 +373,10 @@ void ObjectPicker::DoQuad(const Vector &a, const Vector &b, const Vector &c, con
bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE); bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE);
if(insideQuad) { if(insideQuad) {
DoCompare(0.0, zIndex, comparePosition); DoCompare(0, 0.0, zIndex, comparePosition);
} else { } else {
double distance = std::min(fabs(minNegative), fabs(maxPositive)); double distance = std::min(fabs(minNegative), fabs(maxPositive));
DoCompare(distance, zIndex, comparePosition); DoCompare(0, distance, zIndex, comparePosition);
} }
} }
@ -384,7 +385,8 @@ void ObjectPicker::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
Point2d ap = camera.ProjectPoint(a); Point2d ap = camera.ProjectPoint(a);
Point2d bp = camera.ProjectPoint(b); Point2d bp = camera.ProjectPoint(b);
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
DoCompare(distance - stroke->width / 2.0, stroke->zIndex); double depth = 0.5 * (camera.ProjectPoint3(a).z + camera.ProjectPoint3(b).z) ;
DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex);
} }
void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) { void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
@ -393,7 +395,8 @@ void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) {
Point2d ap = camera.ProjectPoint(e.a); Point2d ap = camera.ProjectPoint(e.a);
Point2d bp = camera.ProjectPoint(e.b); Point2d bp = camera.ProjectPoint(e.b);
double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true);
DoCompare(distance - stroke->width / 2.0, stroke->zIndex, e.auxB); double depth = 0.5 * (camera.ProjectPoint3(e.a).z + camera.ProjectPoint3(e.b).z) ;
DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex, e.auxB);
} }
} }
@ -423,7 +426,8 @@ void ObjectPicker::DrawQuad(const Vector &a, const Vector &b, const Vector &c, c
void ObjectPicker::DrawPoint(const Vector &o, Canvas::hStroke hcs) { void ObjectPicker::DrawPoint(const Vector &o, Canvas::hStroke hcs) {
Stroke *stroke = strokes.FindById(hcs); Stroke *stroke = strokes.FindById(hcs);
double distance = point.DistanceTo(camera.ProjectPoint(o)) - stroke->width / 2; double distance = point.DistanceTo(camera.ProjectPoint(o)) - stroke->width / 2;
DoCompare(distance, stroke->zIndex); double depth = camera.ProjectPoint3(o).z;
DoCompare(depth, distance, stroke->zIndex);
} }
void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) { void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) {
@ -445,6 +449,7 @@ void ObjectPicker::DrawPixmap(std::shared_ptr<const Pixmap> pm,
} }
bool ObjectPicker::Pick(const std::function<void()> &drawFn) { bool ObjectPicker::Pick(const std::function<void()> &drawFn) {
minDepth = VERY_POSITIVE;
minDistance = VERY_POSITIVE; minDistance = VERY_POSITIVE;
maxZIndex = INT_MIN; maxZIndex = INT_MIN;

View File

@ -232,6 +232,7 @@ public:
double selRadius = 0.0; double selRadius = 0.0;
// Picking state. // Picking state.
double minDistance = 0.0; double minDistance = 0.0;
double minDepth = 1e10;
int maxZIndex = 0; int maxZIndex = 0;
uint32_t position = 0; uint32_t position = 0;
@ -257,7 +258,7 @@ public:
const Point2d &ta, const Point2d &tb, hFill hcf) override; const Point2d &ta, const Point2d &tb, hFill hcf) override;
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {} void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {}
void DoCompare(double distance, int zIndex, int comparePosition = 0); void DoCompare(double depth, double distance, int zIndex, int comparePosition = 0);
void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
int zIndex, int comparePosition = 0); int zIndex, int comparePosition = 0);

View File

@ -790,6 +790,9 @@ public:
static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB, static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB,
hEntity entityA, hEntity entityB = Entity::NO_ENTITY, hEntity entityA, hEntity entityB = Entity::NO_ENTITY,
bool other = false, bool other2 = false); bool other = false, bool other2 = false);
static void ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc);
static void ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic);
static void ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB);
}; };
class hEquation { class hEquation {

View File

@ -77,7 +77,7 @@ void SolveSpaceUI::Init() {
// Use turntable mouse navigation // Use turntable mouse navigation
turntableNav = settings->ThawBool("TurntableNav", false); turntableNav = settings->ThawBool("TurntableNav", false);
// Immediately edit dimension // Immediately edit dimension
immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", false); immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", true);
// Check that contours are closed and not self-intersecting // Check that contours are closed and not self-intersecting
checkClosedContour = settings->ThawBool("CheckClosedContour", true); checkClosedContour = settings->ThawBool("CheckClosedContour", true);
// Enable automatic constrains for lines // Enable automatic constrains for lines
@ -331,7 +331,13 @@ const char *SolveSpaceUI::UnitName() {
std::string SolveSpaceUI::MmToString(double v) { std::string SolveSpaceUI::MmToString(double v) {
v /= MmPerUnit(); v /= MmPerUnit();
return ssprintf("%.*f", UnitDigitsAfterDecimal(), v); int digits = UnitDigitsAfterDecimal();
double minimum = 0.5 * pow(10,-digits);
while ((v < minimum) && (v > LENGTH_EPS)) {
digits++;
minimum *= 0.1;
}
return ssprintf("%.*f", digits, v);
} }
static const char *DimToString(int dim) { static const char *DimToString(int dim) {
switch(dim) { switch(dim) {
@ -341,13 +347,39 @@ static const char *DimToString(int dim) {
default: ssassert(false, "Unexpected dimension"); default: ssassert(false, "Unexpected dimension");
} }
} }
static std::pair<int, std::string> SelectSIPrefixMm(int deg) { static std::pair<int, std::string> SelectSIPrefixMm(int ord, int dim) {
if(deg >= 3) return { 3, "km" }; // decide what units to use depending on the order of magnitude of the
else if(deg >= 0) return { 0, "m" }; // measure in meters and the dimmension (1,2,3 lenear, area, volume)
else if(deg >= -2) return { -2, "cm" }; switch(dim) {
else if(deg >= -3) return { -3, "mm" }; case 0:
else if(deg >= -6) return { -6, "µm" }; case 1:
else return { -9, "nm" }; if(ord >= 3) return { 3, "km" };
else if(ord >= 0) return { 0, "m" };
else if(ord >= -2) return { -2, "cm" };
else if(ord >= -3) return { -3, "mm" };
else if(ord >= -6) return { -6, "µm" };
else return { -9, "nm" };
break;
case 2:
if(ord >= 5) return { 3, "km" };
else if(ord >= 0) return { 0, "m" };
else if(ord >= -2) return { -2, "cm" };
else if(ord >= -6) return { -3, "mm" };
else if(ord >= -13) return { -6, "µm" };
else return { -9, "nm" };
break;
case 3:
if(ord >= 7) return { 3, "km" };
else if(ord >= 0) return { 0, "m" };
else if(ord >= -5) return { -2, "cm" };
else if(ord >= -11) return { -3, "mm" };
else return { -6, "µm" };
break;
default:
dbp ("dimensions over 3 not supported");
break;
}
return {0, "m"};
} }
static std::pair<int, std::string> SelectSIPrefixInch(int deg) { static std::pair<int, std::string> SelectSIPrefixInch(int deg) {
if(deg >= 0) return { 0, "in" }; if(deg >= 0) return { 0, "in" };
@ -363,14 +395,14 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) {
} }
v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim); v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim);
int vdeg = (int)((log10(fabs(v))) / dim); int vdeg = (int)(log10(fabs(v)));
std::string unit; std::string unit;
if(fabs(v) > 0.0) { if(fabs(v) > 0.0) {
int sdeg = 0; int sdeg = 0;
std::tie(sdeg, unit) = std::tie(sdeg, unit) =
(viewUnits == Unit::INCHES) (viewUnits == Unit::INCHES)
? SelectSIPrefixInch(vdeg) ? SelectSIPrefixInch(vdeg/dim)
: SelectSIPrefixMm(vdeg); : SelectSIPrefixMm(vdeg, dim);
v /= pow(10.0, sdeg * dim); v /= pow(10.0, sdeg * dim);
} }
int pdeg = (int)ceil(log10(fabs(v) + 1e-10)); int pdeg = (int)ceil(log10(fabs(v) + 1e-10));
@ -601,6 +633,7 @@ void SolveSpaceUI::MenuFile(Command id) {
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::RasterFileFilters); dialog->AddFilters(Platform::RasterFileFilters);
dialog->ThawChoices(settings, "ExportImage"); dialog->ThawChoices(settings, "ExportImage");
dialog->SuggestFilename(SS.saveFile);
if(dialog->RunModal()) { if(dialog->RunModal()) {
dialog->FreezeChoices(settings, "ExportImage"); dialog->FreezeChoices(settings, "ExportImage");
SS.ExportAsPngTo(dialog->GetFilename()); SS.ExportAsPngTo(dialog->GetFilename());
@ -612,6 +645,7 @@ void SolveSpaceUI::MenuFile(Command id) {
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::VectorFileFilters); dialog->AddFilters(Platform::VectorFileFilters);
dialog->ThawChoices(settings, "ExportView"); dialog->ThawChoices(settings, "ExportView");
dialog->SuggestFilename(SS.saveFile);
if(!dialog->RunModal()) break; if(!dialog->RunModal()) break;
dialog->FreezeChoices(settings, "ExportView"); dialog->FreezeChoices(settings, "ExportView");
@ -635,6 +669,7 @@ void SolveSpaceUI::MenuFile(Command id) {
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::Vector3dFileFilters); dialog->AddFilters(Platform::Vector3dFileFilters);
dialog->ThawChoices(settings, "ExportWireframe"); dialog->ThawChoices(settings, "ExportWireframe");
dialog->SuggestFilename(SS.saveFile);
if(!dialog->RunModal()) break; if(!dialog->RunModal()) break;
dialog->FreezeChoices(settings, "ExportWireframe"); dialog->FreezeChoices(settings, "ExportWireframe");
@ -646,6 +681,7 @@ void SolveSpaceUI::MenuFile(Command id) {
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::VectorFileFilters); dialog->AddFilters(Platform::VectorFileFilters);
dialog->ThawChoices(settings, "ExportSection"); dialog->ThawChoices(settings, "ExportSection");
dialog->SuggestFilename(SS.saveFile);
if(!dialog->RunModal()) break; if(!dialog->RunModal()) break;
dialog->FreezeChoices(settings, "ExportSection"); dialog->FreezeChoices(settings, "ExportSection");
@ -657,6 +693,7 @@ void SolveSpaceUI::MenuFile(Command id) {
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::MeshFileFilters); dialog->AddFilters(Platform::MeshFileFilters);
dialog->ThawChoices(settings, "ExportMesh"); dialog->ThawChoices(settings, "ExportMesh");
dialog->SuggestFilename(SS.saveFile);
if(!dialog->RunModal()) break; if(!dialog->RunModal()) break;
dialog->FreezeChoices(settings, "ExportMesh"); dialog->FreezeChoices(settings, "ExportMesh");
@ -668,6 +705,7 @@ void SolveSpaceUI::MenuFile(Command id) {
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::SurfaceFileFilters); dialog->AddFilters(Platform::SurfaceFileFilters);
dialog->ThawChoices(settings, "ExportSurfaces"); dialog->ThawChoices(settings, "ExportSurfaces");
dialog->SuggestFilename(SS.saveFile);
if(!dialog->RunModal()) break; if(!dialog->RunModal()) break;
dialog->FreezeChoices(settings, "ExportSurfaces"); dialog->FreezeChoices(settings, "ExportSurfaces");
@ -878,9 +916,13 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
break; break;
case Command::STOP_TRACING: { case Command::STOP_TRACING: {
if (SS.traced.point == Entity::NO_ENTITY) {
break;
}
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
dialog->AddFilters(Platform::CsvFileFilters); dialog->AddFilters(Platform::CsvFileFilters);
dialog->ThawChoices(settings, "Trace"); dialog->ThawChoices(settings, "Trace");
dialog->SetFilename(SS.saveFile);
if(dialog->RunModal()) { if(dialog->RunModal()) {
dialog->FreezeChoices(settings, "Trace"); dialog->FreezeChoices(settings, "Trace");
@ -965,7 +1007,7 @@ void SolveSpaceUI::MenuHelp(Command id) {
"law. For details, visit http://gnu.org/licenses/\n" "law. For details, visit http://gnu.org/licenses/\n"
"\n" "\n"
"© 2008-%d Jonathan Westhues and other authors.\n"), "© 2008-%d Jonathan Westhues and other authors.\n"),
PACKAGE_VERSION, 2019); PACKAGE_VERSION, 2021);
break; break;
default: ssassert(false, "Unexpected menu ID"); default: ssassert(false, "Unexpected menu ID");
@ -982,6 +1024,7 @@ void SolveSpaceUI::Clear() {
GW.openRecentMenu = NULL; GW.openRecentMenu = NULL;
GW.linkRecentMenu = NULL; GW.linkRecentMenu = NULL;
GW.showGridMenuItem = NULL; GW.showGridMenuItem = NULL;
GW.dimSolidModelMenuItem = NULL;
GW.perspectiveProjMenuItem = NULL; GW.perspectiveProjMenuItem = NULL;
GW.showToolbarMenuItem = NULL; GW.showToolbarMenuItem = NULL;
GW.showTextWndMenuItem = NULL; GW.showTextWndMenuItem = NULL;

View File

@ -513,7 +513,7 @@ public:
GraphicsWindow GW; GraphicsWindow GW;
// The state for undo/redo // The state for undo/redo
typedef struct { typedef struct UndoState {
IdList<Group,hGroup> group; IdList<Group,hGroup> group;
List<hGroup> groupOrder; List<hGroup> groupOrder;
IdList<Request,hRequest> request; IdList<Request,hRequest> request;
@ -530,7 +530,7 @@ public:
style.Clear(); style.Clear();
} }
} UndoState; } UndoState;
enum { MAX_UNDO = 16 }; enum { MAX_UNDO = 100 };
typedef struct { typedef struct {
UndoState d[MAX_UNDO]; UndoState d[MAX_UNDO];
int cnt; int cnt;
@ -686,7 +686,6 @@ public:
void ExportAsPngTo(const Platform::Path &filename); void ExportAsPngTo(const Platform::Path &filename);
void ExportMeshTo(const Platform::Path &filename); void ExportMeshTo(const Platform::Path &filename);
void ExportMeshAsStlTo(FILE *f, SMesh *sm); void ExportMeshAsStlTo(FILE *f, SMesh *sm);
void ExportMeshAsQ3doTo(FILE *f, SMesh *sm);
void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm); void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm);
void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename,
SMesh *sm, SOutlineList *sol); SMesh *sm, SOutlineList *sol);

View File

@ -20,6 +20,50 @@ void SShell::MakeFromIntersectionOf(SShell *a, SShell *b) {
MakeFromBoolean(a, b, SSurface::CombineAs::INTERSECTION); MakeFromBoolean(a, b, SSurface::CombineAs::INTERSECTION);
} }
void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const {
*ptMax = {VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE};
*ptMin = {VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE};
for(int i = 0; i <= exact.deg; i++) {
exact.ctrl[i].MakeMaxMin(ptMax, ptMin);
}
}
// We will be inserting other curve verticies into our curves to split them.
// This is helpful when curved surfaces become tangent along a trim and the
// usual tests for curve-surface intersection don't split the curve at a vertex.
// This is faster than the previous version that split at surface corners and
// handles more buggy cases. It's not clear this is the best way but it works ok.
static void FindVertsOnCurve(List<SInter> *l, const SCurve *curve, SShell *sh) {
Vector amax, amin;
curve->GetAxisAlignedBounding(&amax, &amin);
for(auto sc : sh->curve) {
if(!sc.isExact) continue;
Vector cmax, cmin;
sc.GetAxisAlignedBounding(&cmax, &cmin);
if(Vector::BoundingBoxesDisjoint(amax, amin, cmax, cmin)) {
// They cannot possibly intersect, no curves to generate
continue;
}
for(int i=0; i<2; i++) {
Vector pt = sc.exact.ctrl[ i==0 ? 0 : sc.exact.deg ];
double t;
curve->exact.ClosestPointTo(pt, &t, /*must converge=*/ false);
double d = pt.Minus(curve->exact.PointAt(t)).Magnitude();
if((t>LENGTH_EPS) && (t<(1.0-LENGTH_EPS)) && (d < LENGTH_EPS)) {
SInter inter;
inter.p = pt;
l->Add(&inter);
}
}
}
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Take our original pwl curve. Wherever an edge intersects a surface within // Take our original pwl curve. Wherever an edge intersects a surface within
// either agnstA or agnstB, split the piecewise linear element. Then refine // either agnstA or agnstB, split the piecewise linear element. Then refine
@ -35,12 +79,21 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
ret = *this; ret = *this;
ret.pts = {}; ret.pts = {};
// First find any vertex that lies on our curve.
List<SInter> vertpts = {};
if(isExact) {
if(agnstA)
FindVertsOnCurve(&vertpts, this, agnstA);
if(agnstB)
FindVertsOnCurve(&vertpts, this, agnstB);
}
const SCurvePt *p = pts.First(); const SCurvePt *p = pts.First();
ssassert(p != NULL, "Cannot split an empty curve"); ssassert(p != NULL, "Cannot split an empty curve");
SCurvePt prev = *p; SCurvePt prev = *p;
ret.pts.Add(p); ret.pts.Add(p);
p = pts.NextAfter(p); p = pts.NextAfter(p);
for(; p; p = pts.NextAfter(p)) { for(; p; p = pts.NextAfter(p)) {
List<SInter> il = {}; List<SInter> il = {};
@ -100,12 +153,22 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
pi->p = (pi->srf)->PointAt(puv); pi->p = (pi->srf)->PointAt(puv);
} }
il.RemoveTagged(); il.RemoveTagged();
}
// Now add any vertex that is on this segment
const Vector lineStart = prev.p;
const Vector lineDirection = (p->p).Minus(prev.p);
for(auto vtx : vertpts) {
double t = (vtx.p.Minus(lineStart)).DivProjected(lineDirection);
if((0.0 < t) && (t < 1.0)) {
il.Add(&vtx);
}
}
if(!il.IsEmpty()) {
SInter *pi;
// And now sort them in order along the line. Note that we must // And now sort them in order along the line. Note that we must
// do that after refining, in case the refining would make two // do that after refining, in case the refining would make two
// points switch places. // points switch places.
const Vector lineStart = prev.p;
const Vector lineDirection = (p->p).Minus(prev.p);
std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) { std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) {
double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection); double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection);
double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection); double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection);
@ -133,20 +196,24 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
ret.pts.Add(p); ret.pts.Add(p);
prev = *p; prev = *p;
} }
vertpts.Clear();
return ret; return ret;
} }
void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) { void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) {
SCurve *sc; #pragma omp parallel for
for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { for(int i=0; i<curve.n; i++) {
SCurve *sc = &curve[i];
SCurve scn = sc->MakeCopySplitAgainst(agnst, NULL, SCurve scn = sc->MakeCopySplitAgainst(agnst, NULL,
surface.FindById(sc->surfA), surface.FindById(sc->surfA),
surface.FindById(sc->surfB)); surface.FindById(sc->surfB));
scn.source = opA ? SCurve::Source::A : SCurve::Source::B; scn.source = opA ? SCurve::Source::A : SCurve::Source::B;
#pragma omp critical
hSCurve hsc = into->curve.AddAndAssignId(&scn); {
// And note the new ID so that we can rewrite the trims appropriately hSCurve hsc = into->curve.AddAndAssignId(&scn);
sc->newH = hsc; // And note the new ID so that we can rewrite the trims appropriately
sc->newH = hsc;
}
} }
} }

View File

@ -13,8 +13,7 @@
// and convergence should be fast by now. // and convergence should be fast by now.
#define RATPOLY_EPS (LENGTH_EPS/(1e2)) #define RATPOLY_EPS (LENGTH_EPS/(1e2))
static double Bernstein(int k, int deg, double t) static inline double Bernstein(int k, int deg, double t) {
{
// indexed by [degree][k][exponent] // indexed by [degree][k][exponent]
static const double bernstein_coeff[4][4][4] = { static const double bernstein_coeff[4][4][4] = {
{ { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 } }, { { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 } },
@ -22,21 +21,18 @@ static double Bernstein(int k, int deg, double t)
{ { 1.0,-2.0,1.0,0.0 }, { 0.0,2.0,-2.0,0.0 },{ 0.0,0.0,1.0,0.0 }, { 0.0,0.0,0.0,0.0 } }, { { 1.0,-2.0,1.0,0.0 }, { 0.0,2.0,-2.0,0.0 },{ 0.0,0.0,1.0,0.0 }, { 0.0,0.0,0.0,0.0 } },
{ { 1.0,-3.0,3.0,-1.0 },{ 0.0,3.0,-6.0,3.0 },{ 0.0,0.0,3.0,-3.0}, { 0.0,0.0,0.0,1.0 } } }; { { 1.0,-3.0,3.0,-1.0 },{ 0.0,3.0,-6.0,3.0 },{ 0.0,0.0,3.0,-3.0}, { 0.0,0.0,0.0,1.0 } } };
const double *c; const double *c = bernstein_coeff[deg][k];
c = bernstein_coeff[deg][k];
return (((c[3]*t+c[2])*t)+c[1])*t+c[0]; return (((c[3]*t+c[2])*t)+c[1])*t+c[0];
} }
static double BernsteinDerivative(int k, int deg, double t) static inline double BernsteinDerivative(int k, int deg, double t) {
{
static const double bernstein_derivative_coeff[4][4][3] = { static const double bernstein_derivative_coeff[4][4][3] = {
{ { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } }, { { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } },
{ { -1.0,0.0,0.0 }, { 1.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } }, { { -1.0,0.0,0.0 }, { 1.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } },
{ { -2.0,2.0,0.0 }, { 2.0,-4.0,0.0 },{ 0.0,2.0,0.0 }, { 0.0,0.0,0.0 } }, { { -2.0,2.0,0.0 }, { 2.0,-4.0,0.0 },{ 0.0,2.0,0.0 }, { 0.0,0.0,0.0 } },
{ { -3.0,6.0,-3.0 },{ 3.0,-12.0,9.0 },{ 0.0,6.0,-9.0}, { 0.0,0.0,3.0 } } }; { { -3.0,6.0,-3.0 },{ 3.0,-12.0,9.0 },{ 0.0,6.0,-9.0}, { 0.0,0.0,3.0 } } };
const double *c; const double *c = bernstein_derivative_coeff[deg][k];
c = bernstein_derivative_coeff[deg][k];
return ((c[2]*t)+c[1])*t+c[0]; return ((c[2]*t)+c[1])*t+c[0];
} }
@ -332,7 +328,7 @@ Vector SSurface::PointAt(double u, double v) const {
return num; return num;
} }
void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) const { void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry) const {
Vector num = Vector::From(0, 0, 0), Vector num = Vector::From(0, 0, 0),
num_u = Vector::From(0, 0, 0), num_u = Vector::From(0, 0, 0),
num_v = Vector::From(0, 0, 0); num_v = Vector::From(0, 0, 0);
@ -364,6 +360,12 @@ void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) const {
*tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v))); *tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v)));
*tv = tv->ScaledBy(1.0/(den*den)); *tv = tv->ScaledBy(1.0/(den*den));
// Tangent is zero at sungularities like the north pole. Move away a bit and retry.
if(tv->Equals(Vector::From(0,0,0)) && retry)
TangentsAt(u+(0.5-u)*0.00001, v, tu, tv, false);
if(tu->Equals(Vector::From(0,0,0)) && retry)
TangentsAt(u, v+(0.5-v)*0.00001, tu, tv, false);
} }
Vector SSurface::NormalAt(Point2d puv) const { Vector SSurface::NormalAt(Point2d puv) const {

View File

@ -507,9 +507,9 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb
Vector n = sbls->normal.ScaledBy(-1); Vector n = sbls->normal.ScaledBy(-1);
Vector u = n.Normal(0), v = n.Normal(1); Vector u = n.Normal(0), v = n.Normal(1);
Vector orig = sbls->point; Vector orig = sbls->point;
double umax = 1e-10, umin = 1e10; double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
sbls->GetBoundingProjd(u, orig, &umin, &umax); sbls->GetBoundingProjd(u, orig, &umin, &umax);
double vmax = 1e-10, vmin = 1e10; double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
sbls->GetBoundingProjd(v, orig, &vmin, &vmax); sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
// and now fix things up so that all u and v lie between 0 and 1 // and now fix things up so that all u and v lie between 0 and 1
orig = orig.Plus(u.ScaledBy(umin)); orig = orig.Plus(u.ScaledBy(umin));
@ -663,9 +663,9 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
Vector n = sbls->normal.ScaledBy(-1); Vector n = sbls->normal.ScaledBy(-1);
Vector u = n.Normal(0), v = n.Normal(1); Vector u = n.Normal(0), v = n.Normal(1);
Vector orig = sbls->point; Vector orig = sbls->point;
double umax = 1e-10, umin = 1e10; double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
sbls->GetBoundingProjd(u, orig, &umin, &umax); sbls->GetBoundingProjd(u, orig, &umin, &umax);
double vmax = 1e-10, vmin = 1e10; double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
sbls->GetBoundingProjd(v, orig, &vmin, &vmax); sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
// and now fix things up so that all u and v lie between 0 and 1 // and now fix things up so that all u and v lie between 0 and 1
orig = orig.Plus(u.ScaledBy(umin)); orig = orig.Plus(u.ScaledBy(umin));

View File

@ -219,6 +219,7 @@ public:
SSurface *GetSurfaceB(SShell *a, SShell *b) const; SSurface *GetSurfaceB(SShell *a, SShell *b) const;
void Clear(); void Clear();
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
}; };
// A segment of a curve by which a surface is trimmed: indicates which curve, // A segment of a curve by which a surface is trimmed: indicates which curve,
@ -335,13 +336,14 @@ public:
void PointOnCurve(const SBezier *curve, double *up, double *vp); void PointOnCurve(const SBezier *curve, double *up, double *vp);
Vector PointAt(double u, double v) const; Vector PointAt(double u, double v) const;
Vector PointAt(Point2d puv) const; Vector PointAt(Point2d puv) const;
void TangentsAt(double u, double v, Vector *tu, Vector *tv) const; void TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry=true) const;
Vector NormalAt(Point2d puv) const; Vector NormalAt(Point2d puv) const;
Vector NormalAt(double u, double v) const; Vector NormalAt(double u, double v) const;
bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const; bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const;
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const; void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
bool CoincidentWithPlane(Vector n, double d) const; bool CoincidentWithPlane(Vector n, double d) const;
bool CoincidentWith(SSurface *ss, bool sameNormal) const; bool CoincidentWith(SSurface *ss, bool sameNormal) const;
bool ContainsPlaneCurve(SCurve *sc) const;
bool IsExtrusion(SBezier *of, Vector *along) const; bool IsExtrusion(SBezier *of, Vector *along) const;
bool IsCylinder(Vector *axis, Vector *center, double *r, bool IsCylinder(Vector *axis, Vector *center, double *r,
Vector *start, Vector *finish) const; Vector *start, Vector *finish) const;

View File

@ -313,6 +313,45 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
inters.Clear(); inters.Clear();
lv.Clear(); lv.Clear();
} else { } else {
if((degm == 1 && degn == 1) || (b->degm == 1 && b->degn == 1)) {
// we should only be here if just one surface is a plane because the
// plane-plane case was already handled above. Need to check the other
// nonplanar surface for trim curves that lie in the plane and are not
// already trimming both surfaces. This happens when we cut a Lathe shell
// on one of the seams for example.
// This also seems necessary to merge some coincident surfaces.
SSurface *splane, *sext;
SShell *shext;
if(degm == 1 && degn == 1) { // this and other checks assume coplanar ctrl pts.
splane = this;
sext = b;
shext = agnstB;
} else {
splane = b;
sext = this;
shext = agnstA;
}
bool foundExact = false;
SCurve *sc;
for(sc = shext->curve.First(); sc; sc = shext->curve.NextAfter(sc)) {
if(sc->source == SCurve::Source::INTERSECTION) continue;
if(!sc->isExact) continue;
if((sc->surfA != sext->h) && (sc->surfB != sext->h)) continue;
// we have a curve belonging to the curved surface and not the plane.
// does it lie completely in the plane?
if(splane->ContainsPlaneCurve(sc)) {
SBezier bezier = sc->exact;
AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into);
foundExact = true;
}
}
// if we found at lest one of these we don't want to do the numerical
// intersection as well. Sometimes it will also find the same curve but
// with different PWLs and the polygon will fail to assemble.
if(foundExact)
return;
}
// Try intersecting the surfaces numerically, by a marching algorithm. // Try intersecting the surfaces numerically, by a marching algorithm.
// First, we find all the intersections between a surface and the // First, we find all the intersections between a surface and the
// boundary of the other surface. // boundary of the other surface.
@ -505,6 +544,24 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) const {
return true; return true;
} }
//-----------------------------------------------------------------------------
// Does a planar surface contain a curve? Does the curve lie completely in plane?
//-----------------------------------------------------------------------------
bool SSurface::ContainsPlaneCurve(SCurve *sc) const {
if(degm != 1 || degn != 1) return false;
if(!sc->isExact) return false; // we don't handle those (yet?)
Vector p = ctrl[0][0];
Vector n = NormalAt(0, 0).WithMagnitude(1);
double d = n.Dot(p);
// check all control points on the curve
for(int i=0; i<= sc->exact.deg; i++) {
if(fabs(n.Dot(sc->exact.ctrl[i]) - d) > LENGTH_EPS) return false;
}
return true;
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// In our shell, find all surfaces that are coincident with the prototype // In our shell, find all surfaces that are coincident with the prototype
// surface (with same or opposite normal, as specified), and copy all of // surface (with same or opposite normal, as specified), and copy all of

View File

@ -143,7 +143,7 @@ void TextWindow::ShowListOfGroups() {
// Link to the errors, if a problem occurred while solving // Link to the errors, if a problem occurred while solving
ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x', ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x',
g->h.v, (&TextWindow::ScreenHowGroupSolved), g->h.v, (&TextWindow::ScreenHowGroupSolved),
ok ? (warn ? "err" : sdof) : "", ok ? ((warn && SS.checkClosedContour) ? "err" : sdof) : "",
ok ? "" : "ERR", ok ? "" : "ERR",
// Link to a screen that gives more details on the group // Link to a screen that gives more details on the group
g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str());

View File

@ -252,8 +252,18 @@ void TextWindow::Init() {
MouseLeave(); MouseLeave();
return true; return true;
} else if(event.type == MouseEvent::Type::SCROLL_VERT) { } else if(event.type == MouseEvent::Type::SCROLL_VERT) {
window->SetScrollbarPosition(window->GetScrollbarPosition() - if (event.scrollDelta == 0) {
LINE_HEIGHT / 2 * event.scrollDelta); return true;
}
if (abs(event.scrollDelta) < 0.2) {
if (event.scrollDelta > 0) {
event.scrollDelta = 0.2;
} else {
event.scrollDelta = -0.2;
}
}
double offset = LINE_HEIGHT / 2 * event.scrollDelta;
ScrollbarEvent(window->GetScrollbarPosition() - offset);
} }
return false; return false;
}; };
@ -338,11 +348,22 @@ void TextWindow::ClearScreen() {
rows = 0; rows = 0;
} }
// This message was addded when someone had too many fonts for the text window
// Scrolling seemed to be broken, but was actaully at the MAX_ROWS.
static const char* endString = " **** End of Text Screen ****";
void TextWindow::Printf(bool halfLine, const char *fmt, ...) { void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
if(!canvas) return; if(!canvas) return;
if(rows >= MAX_ROWS) return; if(rows >= MAX_ROWS) return;
if(rows >= MAX_ROWS-2 && (fmt != endString)) {
// twice due to some half-row issues on resizing
Printf(halfLine, endString);
Printf(halfLine, endString);
return;
}
va_list vl; va_list vl;
va_start(vl, fmt); va_start(vl, fmt);
@ -1130,16 +1151,16 @@ void TextWindow::MouseLeave() {
void TextWindow::ScrollbarEvent(double newPos) { void TextWindow::ScrollbarEvent(double newPos) {
if(window->IsEditorVisible()) { if(window->IsEditorVisible()) {
window->SetScrollbarPosition(scrollPos); // An edit field is active. Do not move the scrollbar.
return; return;
} }
int bottom = top[rows-1] + 2; int bottom = top[rows-1] + 2;
newPos = min((int)newPos, bottom - halfRows); newPos = min((int)newPos, bottom - halfRows);
newPos = max((int)newPos, 0); newPos = max((int)newPos, 0);
if(newPos != scrollPos) { if(newPos != scrollPos) {
scrollPos = (int)newPos; scrollPos = (int)newPos;
window->SetScrollbarPosition(scrollPos);
window->Invalidate(); window->Invalidate();
} }
} }

View File

@ -68,6 +68,10 @@ static ToolIcon Toolbar[] = {
N_("New group extruding active sketch"), {} }, N_("New group extruding active sketch"), {} },
{ "lathe", Command::GROUP_LATHE, { "lathe", Command::GROUP_LATHE,
N_("New group rotating active sketch"), {} }, N_("New group rotating active sketch"), {} },
{ "helix", Command::GROUP_HELIX,
N_("New group helix from active sketch"), {} },
{ "revolve", Command::GROUP_REVOLVE,
N_("New group revolve active sketch"), {} },
{ "step-rotate", Command::GROUP_ROT, { "step-rotate", Command::GROUP_ROT,
N_("New group step and repeat rotating"), {} }, N_("New group step and repeat rotating"), {} },
{ "step-translate", Command::GROUP_TRANS, { "step-translate", Command::GROUP_TRANS,
@ -153,7 +157,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas,
// When changing these values, also change the asReference drawing code in drawentity.cpp. // When changing these values, also change the asReference drawing code in drawentity.cpp.
int fudge = 8; int fudge = 8;
int h = 34*16 + 3*16 + fudge; int h = 32*18 + 3*16 + fudge;
int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h; int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h;
bool withinToolbar = bool withinToolbar =

View File

@ -80,6 +80,7 @@ enum class Command : uint32_t {
ZOOM_OUT, ZOOM_OUT,
ZOOM_TO_FIT, ZOOM_TO_FIT,
SHOW_GRID, SHOW_GRID,
DIM_SOLID_MODEL,
PERSPECTIVE_PROJ, PERSPECTIVE_PROJ,
ONTO_WORKPLANE, ONTO_WORKPLANE,
NEAREST_ORTHO, NEAREST_ORTHO,
@ -179,7 +180,7 @@ public:
enum { enum {
MAX_COLS = 100, MAX_COLS = 100,
MIN_COLS = 45, MIN_COLS = 45,
MAX_ROWS = 2000 MAX_ROWS = 4000
}; };
typedef struct { typedef struct {
@ -532,6 +533,7 @@ public:
Platform::MenuRef linkRecentMenu; Platform::MenuRef linkRecentMenu;
Platform::MenuItemRef showGridMenuItem; Platform::MenuItemRef showGridMenuItem;
Platform::MenuItemRef dimSolidModelMenuItem;
Platform::MenuItemRef perspectiveProjMenuItem; Platform::MenuItemRef perspectiveProjMenuItem;
Platform::MenuItemRef showToolbarMenuItem; Platform::MenuItemRef showToolbarMenuItem;
Platform::MenuItemRef showTextWndMenuItem; Platform::MenuItemRef showTextWndMenuItem;
@ -727,6 +729,7 @@ public:
public: public:
int zIndex; int zIndex;
double distance; double distance;
double depth;
Selection selection; Selection selection;
}; };
@ -802,6 +805,7 @@ public:
DrawOccludedAs drawOccludedAs; DrawOccludedAs drawOccludedAs;
bool showSnapGrid; bool showSnapGrid;
bool dimSolidModel;
void DrawSnapGrid(Canvas *canvas); void DrawSnapGrid(Canvas *canvas);
void AddPointToDraggedList(hEntity hp); void AddPointToDraggedList(hEntity hp);
@ -824,7 +828,7 @@ public:
void MouseLeftDoubleClick(double x, double y); void MouseLeftDoubleClick(double x, double y);
void MouseMiddleOrRightDown(double x, double y); void MouseMiddleOrRightDown(double x, double y);
void MouseRightUp(double x, double y); void MouseRightUp(double x, double y);
void MouseScroll(double x, double y, int delta); void MouseScroll(double x, double y, double delta);
void MouseLeave(); void MouseLeave();
bool KeyboardEvent(Platform::KeyboardEvent event); bool KeyboardEvent(Platform::KeyboardEvent event);
void EditControlDone(const std::string &s); void EditControlDone(const std::string &s);

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 Vector::From(hParam x, hParam y, hParam z) {
Vector v; Vector v;
v.x = SK.GetParam(x)->val; v.x = SK.GetParam(x)->val;
@ -448,50 +442,6 @@ bool Vector::EqualsExactly(Vector v) const {
z == v.z); z == v.z);
} }
Vector Vector::Plus(Vector b) const {
Vector r;
r.x = x + b.x;
r.y = y + b.y;
r.z = z + b.z;
return r;
}
Vector Vector::Minus(Vector b) const {
Vector r;
r.x = x - b.x;
r.y = y - b.y;
r.z = z - b.z;
return r;
}
Vector Vector::Negated() const {
Vector r;
r.x = -x;
r.y = -y;
r.z = -z;
return r;
}
Vector Vector::Cross(Vector b) const {
Vector r;
r.x = -(z*b.y) + (y*b.z);
r.y = (z*b.x) - (x*b.z);
r.z = -(y*b.x) + (x*b.y);
return r;
}
double Vector::Dot(Vector b) const {
return (x*b.x + y*b.y + z*b.z);
}
double Vector::DirectionCosineWith(Vector b) const { double Vector::DirectionCosineWith(Vector b) const {
Vector a = this->WithMagnitude(1); Vector a = this->WithMagnitude(1);
b = b.WithMagnitude(1); b = b.WithMagnitude(1);
@ -629,24 +579,6 @@ Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) const {
return this->Plus(n.WithMagnitude(d)); return this->Plus(n.WithMagnitude(d));
} }
double Vector::MagSquared() const {
return x*x + y*y + z*z;
}
double Vector::Magnitude() const {
return sqrt(x*x + y*y + z*z);
}
Vector Vector::ScaledBy(double v) const {
Vector r;
r.x = x * v;
r.y = y * v;
r.z = z * v;
return r;
}
Vector Vector::WithMagnitude(double v) const { Vector Vector::WithMagnitude(double v) const {
double m = Magnitude(); double m = Magnitude();
if(EXACT(m == 0)) { if(EXACT(m == 0)) {
@ -729,16 +661,6 @@ Vector Vector::ClampWithin(double minv, double maxv) const {
return ret; return ret;
} }
void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const {
maxv->x = max(maxv->x, x);
maxv->y = max(maxv->y, y);
maxv->z = max(maxv->z, z);
minv->x = min(minv->x, x);
minv->y = min(minv->y, y);
minv->z = min(minv->z, z);
}
bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) const { bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) const {
return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) || return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) ||
(y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) || (y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) ||

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -83,6 +83,7 @@ TEST_CASE(extension) {
} }
TEST_CASE(with_extension) { TEST_CASE(with_extension) {
CHECK_EQ_STR(Path::From("foo.bar").WithExtension("").raw, "foo");
CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz"); CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz");
CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz"); CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz");
} }